diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:19:40 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:01:50 +0000 |
commit | 51f6c2793adab2d864b3d2b360000ef8db1d3e92 (patch) | |
tree | 835b3b4446b012c75e80177cef9fbe6972cc7dbe /chromium/mojo | |
parent | 6036726eb981b6c4b42047513b9d3f4ac865daac (diff) | |
download | qtwebengine-chromium-51f6c2793adab2d864b3d2b360000ef8db1d3e92.tar.gz |
BASELINE: Update Chromium to 71.0.3578.93
Change-Id: I6a32086c33670e1b033f8b10e6bf1fd4da1d105d
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/mojo')
88 files changed, 10486 insertions, 20 deletions
diff --git a/chromium/mojo/core/message_unittest.cc b/chromium/mojo/core/message_unittest.cc index ce44f221441..8f047ac8ebd 100644 --- a/chromium/mojo/core/message_unittest.cc +++ b/chromium/mojo/core/message_unittest.cc @@ -227,6 +227,7 @@ const char kTestMessageWithContext1[] = "hello laziness"; const char kTestMessageWithContext2[] = "my old friend"; const char kTestMessageWithContext3[] = "something something"; const char kTestMessageWithContext4[] = "do moar ipc"; +const char kTestQuitMessage[] = "quit"; DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveMessageNoHandles, MessageTest, h) { MojoTestBase::WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE); @@ -272,6 +273,7 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveMessageOneHandle, MessageTest, h) { auto m = MojoTestBase::ReadMessageWithHandles(h, &h1, 1); EXPECT_EQ(kTestMessageWithContext1, m); MojoTestBase::WriteMessage(h1, kTestMessageWithContext2); + EXPECT_EQ(kTestQuitMessage, MojoTestBase::ReadMessage(h)); } TEST_F(MessageTest, SerializeSimpleMessageOneHandleWithContext) { @@ -283,6 +285,7 @@ TEST_F(MessageTest, SerializeSimpleMessageOneHandleWithContext) { nullptr); EXPECT_EQ(kTestMessageWithContext2, MojoTestBase::ReadMessage(pipe.handle1.get().value())); + MojoTestBase::WriteMessage(h, kTestQuitMessage); }); } @@ -295,6 +298,8 @@ DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveMessageWithHandles, MessageTest, h) { MojoTestBase::WriteMessage(handles[1], kTestMessageWithContext2); MojoTestBase::WriteMessage(handles[2], kTestMessageWithContext3); MojoTestBase::WriteMessage(handles[3], kTestMessageWithContext4); + + EXPECT_EQ(kTestQuitMessage, MojoTestBase::ReadMessage(h)); } TEST_F(MessageTest, SerializeSimpleMessageWithHandlesWithContext) { @@ -315,6 +320,8 @@ TEST_F(MessageTest, SerializeSimpleMessageWithHandlesWithContext) { MojoTestBase::ReadMessage(pipes[2].handle1.get().value())); EXPECT_EQ(kTestMessageWithContext4, MojoTestBase::ReadMessage(pipes[3].handle1.get().value())); + + MojoTestBase::WriteMessage(h, kTestQuitMessage); }); } diff --git a/chromium/mojo/core/ports/message_queue.cc b/chromium/mojo/core/ports/message_queue.cc index 074b35a46da..0abb713f762 100644 --- a/chromium/mojo/core/ports/message_queue.cc +++ b/chromium/mojo/core/ports/message_queue.cc @@ -6,6 +6,7 @@ #include <algorithm> +#include "base/compiler_specific.h" #include "base/logging.h" #include "mojo/core/ports/message_filter.h" @@ -53,6 +54,16 @@ void MessageQueue::GetNextMessage(std::unique_ptr<UserMessageEvent>* message, total_queued_bytes_ -= (*message)->GetSizeIfSerialized(); heap_.pop_back(); + // We keep the capacity of |heap_| in check so that a large batch of incoming + // messages doesn't permanently wreck available memory. The choice of interval + // here is somewhat arbitrary. + constexpr size_t kHeapMinimumShrinkSize = 16; + constexpr size_t kHeapShrinkInterval = 512; + if (UNLIKELY(heap_.size() > kHeapMinimumShrinkSize && + heap_.size() % kHeapShrinkInterval == 0)) { + heap_.shrink_to_fit(); + } + next_sequence_num_++; } diff --git a/chromium/mojo/core/scoped_process_handle.cc b/chromium/mojo/core/scoped_process_handle.cc index 65dfcf78aa6..720c7281859 100644 --- a/chromium/mojo/core/scoped_process_handle.cc +++ b/chromium/mojo/core/scoped_process_handle.cc @@ -48,7 +48,10 @@ ScopedProcessHandle ScopedProcessHandle::CloneFrom(base::ProcessHandle handle) { BOOL ok = ::DuplicateHandle(GetCurrentProcessHandle(), handle, GetCurrentProcessHandle(), &handle, 0, FALSE, DUPLICATE_SAME_ACCESS); - DCHECK(ok); + + // TODO(https://crbug.com/887576): Revert this to a DCHECK once we have sorted + // out the cause of handle verifier failures. + PCHECK(ok); #endif return ScopedProcessHandle(handle); } diff --git a/chromium/mojo/public/cpp/base/big_buffer.cc b/chromium/mojo/public/cpp/base/big_buffer.cc index 5f18359d2bf..d7223ef1f97 100644 --- a/chromium/mojo/public/cpp/base/big_buffer.cc +++ b/chromium/mojo/public/cpp/base/big_buffer.cc @@ -101,6 +101,12 @@ BigBufferView::BigBufferView(base::span<const uint8_t> bytes) { static_cast<uint8_t*>(shared_memory_->buffer_mapping_.get())); return; } + + // Shared memory allocation failed, so we're going to inline the data. If + // the data is large enough to be rejected by Mojo internals, we crash early + // to disambiguate this case from other intentional large-IPC crashes. See + // https://crbug.com/872237. + CHECK_LE(bytes.size(), 127u * 1024 * 1024); } // Either the data is small enough or shared memory allocation failed. Either diff --git a/chromium/mojo/public/cpp/bindings/associated_binding.h b/chromium/mojo/public/cpp/bindings/associated_binding.h index e8e0cb1e25e..27182560573 100644 --- a/chromium/mojo/public/cpp/bindings/associated_binding.h +++ b/chromium/mojo/public/cpp/bindings/associated_binding.h @@ -33,7 +33,7 @@ class MessageReceiver; class MOJO_CPP_BINDINGS_EXPORT AssociatedBindingBase { public: AssociatedBindingBase(); - ~AssociatedBindingBase(); + virtual ~AssociatedBindingBase(); // Adds a message filter to be notified of each incoming message before // dispatch. If a filter returns |false| from Accept(), the message is not @@ -112,7 +112,7 @@ class AssociatedBinding : public AssociatedBindingBase { Bind(std::move(request), std::move(runner)); } - ~AssociatedBinding() {} + ~AssociatedBinding() override {} // Sets up this object as the implementation side of an associated interface. void Bind(AssociatedInterfaceRequest<Interface> request, diff --git a/chromium/mojo/public/cpp/bindings/lib/connector.cc b/chromium/mojo/public/cpp/bindings/lib/connector.cc index 9a5c230e719..db72df0dba0 100644 --- a/chromium/mojo/public/cpp/bindings/lib/connector.cc +++ b/chromium/mojo/public/cpp/bindings/lib/connector.cc @@ -392,6 +392,7 @@ void Connector::WaitToReadMore() { CHECK(!paused_); DCHECK(!handle_watcher_); + DCHECK(task_runner_->RunsTasksInCurrentSequence()); handle_watcher_.reset(new SimpleWatcher( FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL, task_runner_)); handle_watcher_->set_heap_profiler_tag(heap_profiler_tag_); diff --git a/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.cc b/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.cc index 8cd23ea0670..9bd0dd9691c 100644 --- a/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.cc +++ b/chromium/mojo/public/cpp/bindings/lib/interface_ptr_state.cc @@ -80,6 +80,7 @@ bool InterfacePtrStateBase::InitializeEndpointClient( : (has_sync_methods ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS : MultiplexRouter::SINGLE_INTERFACE); + DCHECK(runner_->RunsTasksInCurrentSequence()); router_ = new MultiplexRouter(std::move(handle_), config, true, runner_); endpoint_client_.reset(new InterfaceEndpointClient( router_->CreateLocalEndpointHandle(kMasterInterfaceId), nullptr, diff --git a/chromium/mojo/public/cpp/bindings/sync_call_restrictions.h b/chromium/mojo/public/cpp/bindings/sync_call_restrictions.h index 0c5972ee7c6..b260b9f0761 100644 --- a/chromium/mojo/public/cpp/bindings/sync_call_restrictions.h +++ b/chromium/mojo/public/cpp/bindings/sync_call_restrictions.h @@ -21,10 +21,6 @@ namespace sync_preferences { class PrefServiceSyncable; } -namespace content { -class BlinkTestController; -} - namespace leveldb { class LevelDBMojoProxy; } @@ -96,8 +92,6 @@ class MOJO_CPP_BINDINGS_EXPORT SyncCallRestrictions { // For destroying the GL context/surface that draw to a platform window before // the platform window is destroyed. friend class viz::HostFrameSinkManager; - // Allow for layout test pixel dumps. - friend class content::BlinkTestController; // For preventing frame swaps of wrong size during resize on Windows. // (https://crbug.com/811945) friend class ui::HostContextFactoryPrivate; diff --git a/chromium/mojo/public/cpp/system/BUILD.gn b/chromium/mojo/public/cpp/system/BUILD.gn index 155ac14ab3a..07fc688f41e 100644 --- a/chromium/mojo/public/cpp/system/BUILD.gn +++ b/chromium/mojo/public/cpp/system/BUILD.gn @@ -62,6 +62,13 @@ component("system") { "wait_set.h", ] + if (is_nacl) { + # This file refers to a the base::File::File(path, flags) + # constructor which does not exist in nacl builds, and the code + # here is unused in nacl builds anyway. + sources -= [ "file_data_pipe_producer.cc" ] + } + public_deps = [ "//base", "//mojo/public/c/system", diff --git a/chromium/mojo/public/cpp/system/data_pipe.h b/chromium/mojo/public/cpp/system/data_pipe.h index 35844d5d15e..cc789df52a3 100644 --- a/chromium/mojo/public/cpp/system/data_pipe.h +++ b/chromium/mojo/public/cpp/system/data_pipe.h @@ -149,8 +149,7 @@ class DataPipe { inline DataPipe::DataPipe() { MojoResult result = CreateDataPipe(nullptr, &producer_handle, &consumer_handle); - ALLOW_UNUSED_LOCAL(result); - DCHECK_EQ(MOJO_RESULT_OK, result); + CHECK_EQ(MOJO_RESULT_OK, result); } inline DataPipe::DataPipe(uint32_t capacity_num_bytes) { @@ -161,15 +160,13 @@ inline DataPipe::DataPipe(uint32_t capacity_num_bytes) { options.capacity_num_bytes = capacity_num_bytes; MojoResult result = CreateDataPipe(&options, &producer_handle, &consumer_handle); - ALLOW_UNUSED_LOCAL(result); - DCHECK_EQ(MOJO_RESULT_OK, result); + CHECK_EQ(MOJO_RESULT_OK, result); } inline DataPipe::DataPipe(const MojoCreateDataPipeOptions& options) { MojoResult result = CreateDataPipe(&options, &producer_handle, &consumer_handle); - ALLOW_UNUSED_LOCAL(result); - DCHECK_EQ(MOJO_RESULT_OK, result); + CHECK_EQ(MOJO_RESULT_OK, result); } inline DataPipe::~DataPipe() { diff --git a/chromium/mojo/public/java/base/src/org/chromium/mojo_base/BigBufferUtil.java b/chromium/mojo/public/java/base/src/org/chromium/mojo_base/BigBufferUtil.java new file mode 100644 index 00000000000..aefc257e0bf --- /dev/null +++ b/chromium/mojo/public/java/base/src/org/chromium/mojo_base/BigBufferUtil.java @@ -0,0 +1,55 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo_base; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.impl.CoreImpl; +import org.chromium.mojo_base.mojom.BigBuffer; +import org.chromium.mojo_base.mojom.BigBufferSharedMemoryRegion; + +import java.nio.ByteBuffer; + +/** + * Static helper methods for working with the mojom BigBuffer type. + */ +public final class BigBufferUtil { + public static final int MAX_INLINE_ARRAY_SIZE = 64 * 1024; + + // Retrives a copy of the buffer's contents regardless of what type was backing it (i.e. array + // or shared memory). + public static byte[] getBytesFromBigBuffer(BigBuffer buffer) { + if (buffer.which() == BigBuffer.Tag.Bytes) { + return buffer.getBytes(); + } else { + BigBufferSharedMemoryRegion region = buffer.getSharedMemory(); + ByteBuffer byteBuffer = + region.bufferHandle.map(0, region.size, SharedBufferHandle.MapFlags.NONE); + byte[] bytes = new byte[region.size]; + byteBuffer.get(bytes); + return bytes; + } + } + + // Creates a new mojom.BigBuffer for IPC from a set of bytes. If the byte array is larger than + // MAX_INLINE_ARRAY_SIZE, shared memory will be used instead of an inline array. + public static BigBuffer createBigBufferFromBytes(byte[] bytes) { + BigBuffer buffer = new BigBuffer(); + if (bytes.length <= MAX_INLINE_ARRAY_SIZE) { + buffer.setBytes(bytes); + return buffer; + } + Core core = CoreImpl.getInstance(); + BigBufferSharedMemoryRegion region = new BigBufferSharedMemoryRegion(); + region.bufferHandle = + core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), bytes.length); + region.size = bytes.length; + ByteBuffer mappedRegion = + region.bufferHandle.map(0, bytes.length, SharedBufferHandle.MapFlags.NONE); + mappedRegion.put(bytes); + buffer.setSharedMemory(region); + return buffer; + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java new file mode 100644 index 00000000000..ee8f6310a10 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java @@ -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. + +package org.chromium.mojo.bindings; + +/** + * Associated interface is not supported yet. + */ +public class AssociatedInterfaceNotSupported { +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java new file mode 100644 index 00000000000..1b07cd11bad --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java @@ -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. + +package org.chromium.mojo.bindings; + +/** + * Associated interface is not supported yet. + */ +public class AssociatedInterfaceRequestNotSupported { +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java new file mode 100644 index 00000000000..1e399768240 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java @@ -0,0 +1,123 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; + +import java.util.concurrent.Executor; + +/** + * Wrapper around {@link Router} that will close the connection when not referenced anymore. + */ +class AutoCloseableRouter implements Router { + /** + * The underlying router. + */ + private final Router mRouter; + + /** + * The executor to close the underlying router. + */ + private final Executor mExecutor; + + /** + * Exception used to track AutoCloseableRouter's allocation location for debugging puproses when + * leaked. + */ + private final Exception mAllocationException; + + /** + * Flags to keep track if this router has been correctly closed. + */ + private boolean mClosed; + + /** + * Constructor. + */ + public AutoCloseableRouter(Core core, Router router) { + mRouter = router; + mExecutor = ExecutorFactory.getExecutorForCurrentThread(core); + mAllocationException = new Exception("AutocloseableRouter allocated at:"); + } + + /** + * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder) + */ + @Override + public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) { + mRouter.setIncomingMessageReceiver(incomingMessageReceiver); + } + + /** + * @see HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + return mRouter.passHandle(); + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + return mRouter.accept(message); + } + + /** + * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver) + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + return mRouter.acceptWithResponder(message, responder); + + } + + /** + * @see Router#start() + */ + @Override + public void start() { + mRouter.start(); + } + + /** + * @see Router#setErrorHandler(ConnectionErrorHandler) + */ + @Override + public void setErrorHandler(ConnectionErrorHandler errorHandler) { + mRouter.setErrorHandler(errorHandler); + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + mRouter.close(); + mClosed = true; + } + + /** + * @see Object#finalize() + */ + @Override + protected void finalize() throws Throwable { + if (!mClosed) { + mExecutor.execute(new Runnable() { + + @Override + public void run() { + close(); + } + }); + throw new IllegalStateException("Warning: Router objects should be explicitly closed " + + "when no longer required otherwise you may leak handles.", + mAllocationException); + } + super.finalize(); + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java new file mode 100644 index 00000000000..f77399d1b44 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java @@ -0,0 +1,199 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.Watcher; + +/** + * Helper functions. + */ +public class BindingsHelper { + /** + * Alignment in bytes for mojo serialization. + */ + public static final int ALIGNMENT = 8; + + /** + * The size, in bytes, of a serialized handle. A handle is serialized as an int representing the + * offset of the handle in the list of handles. + */ + public static final int SERIALIZED_HANDLE_SIZE = 4; + + /** + * The size, in bytes, of a serialized interface, which consists of a serialized handle (4 + * bytes) and a version number (4 bytes). + */ + public static final int SERIALIZED_INTERFACE_SIZE = 8; + + /** + * The size, in bytes, of a serialized pointer. A pointer is serializaed as an unsigned long + * representing the offset from its position to the pointed elemnt. + */ + public static final int POINTER_SIZE = 8; + + /** + * The size, in bytes, of a serialized union. + */ + public static final int UNION_SIZE = 16; + + /** + * The header for a serialized map element. + */ + public static final DataHeader MAP_STRUCT_HEADER = new DataHeader(24, 0); + + /** + * The value used for the expected length of a non-fixed size array. + */ + public static final int UNSPECIFIED_ARRAY_LENGTH = -1; + + /** + * Passed as |arrayNullability| when neither the array nor its elements are nullable. + */ + public static final int NOTHING_NULLABLE = 0; + + /** + * "Array bit" of |arrayNullability| is set iff the array itself is nullable. + */ + public static final int ARRAY_NULLABLE = (1 << 0); + + /** + * "Element bit" of |arrayNullability| is set iff the array elements are nullable. + */ + public static final int ELEMENT_NULLABLE = (1 << 1); + + public static boolean isArrayNullable(int arrayNullability) { + return (arrayNullability & ARRAY_NULLABLE) > 0; + } + + public static boolean isElementNullable(int arrayNullability) { + return (arrayNullability & ELEMENT_NULLABLE) > 0; + } + + /** + * Align |size| on {@link BindingsHelper#ALIGNMENT}. + */ + public static int align(int size) { + return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1); + } + + /** + * Align |size| on {@link BindingsHelper#ALIGNMENT}. + */ + public static long align(long size) { + return (size + ALIGNMENT - 1) & ~(ALIGNMENT - 1); + } + + /** + * Compute the size in bytes of the given string encoded as utf8. + */ + public static int utf8StringSizeInBytes(String s) { + int res = 0; + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + int codepoint = c; + if (isSurrogate(c)) { + i++; + char c2 = s.charAt(i); + codepoint = Character.toCodePoint(c, c2); + } + res += 1; + if (codepoint > 0x7f) { + res += 1; + if (codepoint > 0x7ff) { + res += 1; + if (codepoint > 0xffff) { + res += 1; + if (codepoint > 0x1fffff) { + res += 1; + if (codepoint > 0x3ffffff) { + res += 1; + } + } + } + } + } + } + return res; + } + + /** + * Returns |true| if and only if the two objects are equals, handling |null|. + */ + public static boolean equals(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + if (o1 == null) { + return false; + } + return o1.equals(o2); + } + + /** + * Returns the hash code of the object, handling |null|. + */ + public static int hashCode(Object o) { + if (o == null) { + return 0; + } + return o.hashCode(); + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(boolean o) { + return o ? 1231 : 1237; + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(long o) { + return (int) (o ^ (o >>> 32)); + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(float o) { + return Float.floatToIntBits(o); + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(double o) { + return hashCode(Double.doubleToLongBits(o)); + } + + /** + * Returns the hash code of the value. + */ + public static int hashCode(int o) { + return o; + } + + /** + * Determines if the given {@code char} value is a Unicode <i>surrogate code unit</i>. See + * {@link Character#isSurrogate}. Extracting here because the method only exists at API level + * 19. + */ + private static boolean isSurrogate(char c) { + return c >= Character.MIN_SURROGATE && c < (Character.MAX_SURROGATE + 1); + } + + /** + * Returns an {@link AsyncWaiter} to use with the given handle, or |null| if none if available. + */ + static Watcher getWatcherForHandle(Handle handle) { + if (handle.getCore() != null) { + return handle.getCore().getWatcher(); + } else { + return null; + } + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java new file mode 100644 index 00000000000..c6b14c14944 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java @@ -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. + +// This file was generated using +// mojo/tools/generate_java_callback_interfaces.py + +package org.chromium.mojo.bindings; + +/** + * Contains a generic interface for callbacks. + */ +public interface Callbacks { + + /** + * A generic callback. + */ + interface Callback0 { + /** + * Call the callback. + */ + public void call(); + } + + /** + * A generic 1-argument callback. + * + * @param <T1> the type of argument 1. + */ + interface Callback1<T1> { + /** + * Call the callback. + */ + public void call(T1 arg1); + } + + /** + * A generic 2-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + */ + interface Callback2<T1, T2> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2); + } + + /** + * A generic 3-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + */ + interface Callback3<T1, T2, T3> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3); + } + + /** + * A generic 4-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + * @param <T4> the type of argument 4. + */ + interface Callback4<T1, T2, T3, T4> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4); + } + + /** + * A generic 5-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + * @param <T4> the type of argument 4. + * @param <T5> the type of argument 5. + */ + interface Callback5<T1, T2, T3, T4, T5> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + } + + /** + * A generic 6-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + * @param <T4> the type of argument 4. + * @param <T5> the type of argument 5. + * @param <T6> the type of argument 6. + */ + interface Callback6<T1, T2, T3, T4, T5, T6> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6); + } + + /** + * A generic 7-argument callback. + * + * @param <T1> the type of argument 1. + * @param <T2> the type of argument 2. + * @param <T3> the type of argument 3. + * @param <T4> the type of argument 4. + * @param <T5> the type of argument 5. + * @param <T6> the type of argument 6. + * @param <T7> the type of argument 7. + */ + interface Callback7<T1, T2, T3, T4, T5, T6, T7> { + /** + * Call the callback. + */ + public void call(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7); + } + +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java new file mode 100644 index 00000000000..f601fb81b5b --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java @@ -0,0 +1,15 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.MojoException; + +/** + * A {@link ConnectionErrorHandler} is notified of an error happening while using the bindings over + * message pipes. + */ +public interface ConnectionErrorHandler { + public void onConnectionError(MojoException e); +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java new file mode 100644 index 00000000000..54612093d67 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java @@ -0,0 +1,222 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MessagePipeHandle.ReadMessageResult; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.Watcher; + +import java.nio.ByteBuffer; + +/** + * A {@link Connector} owns a {@link MessagePipeHandle} and will send any received messages to the + * registered {@link MessageReceiver}. It also acts as a {@link MessageReceiver} and will send any + * message through the handle. + * <p> + * The method |start| must be called before the {@link Connector} will start listening to incoming + * messages. + */ +public class Connector implements MessageReceiver, HandleOwner<MessagePipeHandle> { + + /** + * The callback that is notified when the state of the owned handle changes. + */ + private final WatcherCallback mWatcherCallback = new WatcherCallback(); + + /** + * The owned message pipe. + */ + private final MessagePipeHandle mMessagePipeHandle; + + /** + * A watcher which is notified when a new message is available on the owned message pipe. + */ + private final Watcher mWatcher; + + /** + * The {@link MessageReceiver} to which received messages are sent. + */ + private MessageReceiver mIncomingMessageReceiver; + + /** + * The error handler to notify of errors. + */ + private ConnectionErrorHandler mErrorHandler; + + /** + * Create a new connector over a |messagePipeHandle|. The created connector will use the default + * {@link AsyncWaiter} from the {@link Core} implementation of |messagePipeHandle|. + */ + public Connector(MessagePipeHandle messagePipeHandle) { + this(messagePipeHandle, BindingsHelper.getWatcherForHandle(messagePipeHandle)); + } + + /** + * Create a new connector over a |messagePipeHandle| using the given {@link AsyncWaiter} to get + * notified of changes on the handle. + */ + public Connector(MessagePipeHandle messagePipeHandle, Watcher watcher) { + mMessagePipeHandle = messagePipeHandle; + mWatcher = watcher; + } + + /** + * Set the {@link MessageReceiver} that will receive message from the owned message pipe. + */ + public void setIncomingMessageReceiver(MessageReceiver incomingMessageReceiver) { + mIncomingMessageReceiver = incomingMessageReceiver; + } + + /** + * Set the {@link ConnectionErrorHandler} that will be notified of errors on the owned message + * pipe. + */ + public void setErrorHandler(ConnectionErrorHandler errorHandler) { + mErrorHandler = errorHandler; + } + + /** + * Start listening for incoming messages. + */ + public void start() { + mWatcher.start(mMessagePipeHandle, Core.HandleSignals.READABLE, mWatcherCallback); + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + try { + mMessagePipeHandle.writeMessage(message.getData(), + message.getHandles(), MessagePipeHandle.WriteFlags.NONE); + return true; + } catch (MojoException e) { + onError(e); + return false; + } + } + + /** + * Pass the owned handle of the connector. After this, the connector is disconnected. It cannot + * accept new message and it isn't listening to the handle anymore. + * + * @see org.chromium.mojo.bindings.HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + cancelIfActive(); + MessagePipeHandle handle = mMessagePipeHandle.pass(); + if (mIncomingMessageReceiver != null) { + mIncomingMessageReceiver.close(); + } + return handle; + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + cancelIfActive(); + mMessagePipeHandle.close(); + if (mIncomingMessageReceiver != null) { + MessageReceiver incomingMessageReceiver = mIncomingMessageReceiver; + mIncomingMessageReceiver = null; + incomingMessageReceiver.close(); + } + } + + private class WatcherCallback implements Watcher.Callback { + /** + * @see org.chromium.mojo.system.Watcher.Callback#onResult(int) + */ + @Override + public void onResult(int result) { + Connector.this.onWatcherResult(result); + } + + } + + /** + * @see org.chromium.mojo.system.Watcher.Callback#onResult(int) + */ + private void onWatcherResult(int result) { + if (result == MojoResult.OK) { + readOutstandingMessages(); + } else { + onError(new MojoException(result)); + } + } + + private void onError(MojoException exception) { + close(); + if (mErrorHandler != null) { + try { + mErrorHandler.onConnectionError(exception); + } catch (RuntimeException e) { + ExceptionHandler.DefaultExceptionHandler.getInstance().handleException(e); + } + } + } + + /** + * Read all available messages on the owned message pipe. + */ + private void readOutstandingMessages() { + ResultAnd<Boolean> result; + do { + try { + result = readAndDispatchMessage(mMessagePipeHandle, mIncomingMessageReceiver); + } catch (MojoException e) { + onError(e); + return; + } + } while (result.getValue()); + if (result.getMojoResult() != MojoResult.SHOULD_WAIT) { + onError(new MojoException(result.getMojoResult())); + } + } + + private void cancelIfActive() { + mWatcher.cancel(); + mWatcher.destroy(); + } + + /** + * Read a message, and pass it to the given |MessageReceiver| if not null. If the + * |MessageReceiver| is null, the message is lost. + * + * @param receiver The {@link MessageReceiver} that will receive the read {@link Message}. Can + * be <code>null</code>, in which case the message is discarded. + */ + static ResultAnd<Boolean> readAndDispatchMessage( + MessagePipeHandle handle, MessageReceiver receiver) { + ResultAnd<ReadMessageResult> result = handle.readMessage(MessagePipeHandle.ReadFlags.NONE); + if (result.getMojoResult() != MojoResult.OK) { + return new ResultAnd<Boolean>(result.getMojoResult(), false); + } + ReadMessageResult readResult = result.getValue(); + assert readResult != null; + if (receiver != null) { + boolean accepted; + try { + accepted = receiver.accept( + new Message(ByteBuffer.wrap(readResult.mData), readResult.mHandles)); + } catch (RuntimeException e) { + // The DefaultExceptionHandler will decide whether any uncaught exception will + // close the connection or not. + accepted = + ExceptionHandler.DefaultExceptionHandler.getInstance().handleException(e); + } + return new ResultAnd<Boolean>(result.getMojoResult(), accepted); + } + return new ResultAnd<Boolean>(result.getMojoResult(), false); + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java new file mode 100644 index 00000000000..96acec94380 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java @@ -0,0 +1,70 @@ +// 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. + +package org.chromium.mojo.bindings; + +/** + * The header for a mojo complex element. + */ +public final class DataHeader { + /** + * The size of a serialized header, in bytes. + */ + public static final int HEADER_SIZE = 8; + + /** + * The offset of the size field. + */ + public static final int SIZE_OFFSET = 0; + + /** + * The offset of the number of fields field. + */ + public static final int ELEMENTS_OR_VERSION_OFFSET = 4; + + /** + * The size of the object owning this header. + */ + public final int size; + + /** + * Number of element (for an array) or version (for a struct) of the object owning this + * header. + */ + public final int elementsOrVersion; + + /** + * Constructor. + */ + public DataHeader(int size, int elementsOrVersion) { + super(); + this.size = size; + this.elementsOrVersion = elementsOrVersion; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + elementsOrVersion; + result = prime * result + size; + return result; + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (getClass() != object.getClass()) return false; + + DataHeader other = (DataHeader) object; + return (elementsOrVersion == other.elementsOrVersion && size == other.size); + } +}
\ No newline at end of file diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java new file mode 100644 index 00000000000..64ff1c08a1f --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java @@ -0,0 +1,776 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.bindings.Interface.Proxy; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; + +import java.nio.ByteOrder; +import java.nio.charset.Charset; + +/** + * A Decoder is a helper class for deserializing a mojo struct. It enables deserialization of basic + * types from a {@link Message} object at a given offset into it's byte buffer. + */ +public class Decoder { + + /** + * Helper class to validate the decoded message. + */ + static final class Validator { + + /** + * Minimal value for the next handle to deserialize. + */ + private int mMinNextClaimedHandle; + /** + * Minimal value of the start of the next memory to claim. + */ + private long mMinNextMemory; + /** + * The current nesting level when decoding. + */ + private long mStackDepth; + + /** + * The maximal memory accessible. + */ + private final long mMaxMemory; + + /** + * The number of handles in the message. + */ + private final long mNumberOfHandles; + + /** + * The maximum nesting level when decoding. + */ + private static final int MAX_RECURSION_DEPTH = 100; + + /** + * Constructor. + */ + Validator(long maxMemory, int numberOfHandles) { + mMaxMemory = maxMemory; + mNumberOfHandles = numberOfHandles; + mStackDepth = 0; + } + + public void claimHandle(int handle) { + if (handle < mMinNextClaimedHandle) { + throw new DeserializationException( + "Trying to access handle out of order."); + } + if (handle >= mNumberOfHandles) { + throw new DeserializationException("Trying to access non present handle."); + } + mMinNextClaimedHandle = handle + 1; + } + + public void claimMemory(long start, long end) { + if (start % BindingsHelper.ALIGNMENT != 0) { + throw new DeserializationException("Incorrect starting alignment: " + start + "."); + } + if (start < mMinNextMemory) { + throw new DeserializationException("Trying to access memory out of order."); + } + if (end < start) { + throw new DeserializationException("Incorrect memory range."); + } + if (end > mMaxMemory) { + throw new DeserializationException("Trying to access out of range memory."); + } + mMinNextMemory = BindingsHelper.align(end); + } + + public void increaseStackDepth() { + ++mStackDepth; + if (mStackDepth >= MAX_RECURSION_DEPTH) { + throw new DeserializationException("Recursion depth limit exceeded."); + } + } + + public void decreaseStackDepth() { + --mStackDepth; + } + } + + /** + * The message to deserialize from. + */ + private final Message mMessage; + + /** + * The base offset in the byte buffer. + */ + private final int mBaseOffset; + + /** + * Validator for the decoded message. + */ + private final Validator mValidator; + + /** + * Constructor. + * + * @param message The message to decode. + */ + public Decoder(Message message) { + this(message, new Validator(message.getData().limit(), message.getHandles().size()), 0); + } + + private Decoder(Message message, Validator validator, int baseOffset) { + mMessage = message; + mMessage.getData().order(ByteOrder.LITTLE_ENDIAN); + mBaseOffset = baseOffset; + mValidator = validator; + } + + /** + * Deserializes a {@link DataHeader} at the current position. + */ + public DataHeader readDataHeader() { + // Claim the memory for the header. + mValidator.claimMemory(mBaseOffset, mBaseOffset + DataHeader.HEADER_SIZE); + DataHeader result = readDataHeaderAtOffset(0, false); + // Claim the rest of the memory. + mValidator.claimMemory(mBaseOffset + DataHeader.HEADER_SIZE, mBaseOffset + result.size); + return result; + } + + /** + * Deserializes a {@link DataHeader} for an union at the given offset. + */ + public DataHeader readDataHeaderForUnion(int offset) { + DataHeader result = readDataHeaderAtOffset(offset, true); + if (result.size == 0) { + if (result.elementsOrVersion != 0) { + throw new DeserializationException( + "Unexpected version tag for a null union. Expecting 0, found: " + + result.elementsOrVersion); + } + } else if (result.size != BindingsHelper.UNION_SIZE) { + throw new DeserializationException( + "Unexpected size of an union. The size must be 0 for a null union, or 16 for " + + "a non-null union."); + } + return result; + } + + /** + * @returns a decoder suitable to decode an union defined as the root object of a message. + */ + public Decoder decoderForSerializedUnion() { + mValidator.claimMemory(0, BindingsHelper.UNION_SIZE); + return this; + } + + /** + * Deserializes a {@link DataHeader} at the given offset. + */ + private DataHeader readDataHeaderAtOffset(int offset, boolean isUnion) { + int size = readInt(offset + DataHeader.SIZE_OFFSET); + int elementsOrVersion = readInt(offset + DataHeader.ELEMENTS_OR_VERSION_OFFSET); + if (size < 0) { + throw new DeserializationException( + "Negative size. Unsigned integers are not valid for java."); + } + if (elementsOrVersion < 0 && (!isUnion || elementsOrVersion != -1)) { + throw new DeserializationException( + "Negative elements or version. Unsigned integers are not valid for java."); + } + + return new DataHeader(size, elementsOrVersion); + } + + public DataHeader readAndValidateDataHeader(DataHeader[] versionArray) { + DataHeader header = readDataHeader(); + int maxVersionIndex = versionArray.length - 1; + if (header.elementsOrVersion <= versionArray[maxVersionIndex].elementsOrVersion) { + DataHeader referenceHeader = null; + for (int index = maxVersionIndex; index >= 0; index--) { + DataHeader dataHeader = versionArray[index]; + if (header.elementsOrVersion >= dataHeader.elementsOrVersion) { + referenceHeader = dataHeader; + break; + } + } + if (referenceHeader == null || referenceHeader.size != header.size) { + throw new DeserializationException( + "Header doesn't correspond to any known version."); + } + } else { + if (header.size < versionArray[maxVersionIndex].size) { + throw new DeserializationException("Message newer than the last known version" + + " cannot be shorter than required by the last known version."); + } + } + return header; + } + + /** + * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an + * array where elements are pointers. + */ + public DataHeader readDataHeaderForPointerArray(int expectedLength) { + return readDataHeaderForArray(BindingsHelper.POINTER_SIZE, expectedLength); + } + + /** + * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an + * array where elements are unions. + */ + public DataHeader readDataHeaderForUnionArray(int expectedLength) { + return readDataHeaderForArray(BindingsHelper.UNION_SIZE, expectedLength); + } + + /** + * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for a map. + */ + public void readDataHeaderForMap() { + DataHeader si = readDataHeader(); + if (si.size != BindingsHelper.MAP_STRUCT_HEADER.size) { + throw new DeserializationException( + "Incorrect header for map. The size is incorrect."); + } + if (si.elementsOrVersion != BindingsHelper.MAP_STRUCT_HEADER.elementsOrVersion) { + throw new DeserializationException( + "Incorrect header for map. The version is incorrect."); + } + } + + /** + * Deserializes a byte at the given offset. + */ + public byte readByte(int offset) { + validateBufferSize(offset, 1); + return mMessage.getData().get(mBaseOffset + offset); + } + + /** + * Deserializes a boolean at the given offset, re-using any partially read byte. + */ + public boolean readBoolean(int offset, int bit) { + validateBufferSize(offset, 1); + return (readByte(offset) & (1 << bit)) != 0; + } + + /** + * Deserializes a short at the given offset. + */ + public short readShort(int offset) { + validateBufferSize(offset, 2); + return mMessage.getData().getShort(mBaseOffset + offset); + } + + /** + * Deserializes an int at the given offset. + */ + public int readInt(int offset) { + validateBufferSize(offset, 4); + return mMessage.getData().getInt(mBaseOffset + offset); + } + + /** + * Deserializes a float at the given offset. + */ + public float readFloat(int offset) { + validateBufferSize(offset, 4); + return mMessage.getData().getFloat(mBaseOffset + offset); + } + + /** + * Deserializes a long at the given offset. + */ + public long readLong(int offset) { + validateBufferSize(offset, 8); + return mMessage.getData().getLong(mBaseOffset + offset); + } + + /** + * Deserializes a double at the given offset. + */ + public double readDouble(int offset) { + validateBufferSize(offset, 8); + return mMessage.getData().getDouble(mBaseOffset + offset); + } + + /** + * Deserializes a pointer at the given offset. Returns a Decoder suitable to decode the content + * of the pointer. + */ + public Decoder readPointer(int offset, boolean nullable) { + int basePosition = mBaseOffset + offset; + long pointerOffset = readLong(offset); + if (pointerOffset == 0) { + if (!nullable) { + throw new DeserializationException( + "Trying to decode null pointer for a non-nullable type."); + } + return null; + } + int newPosition = (int) (basePosition + pointerOffset); + // The method |getDecoderAtPosition| will validate that the pointer address is valid. + return getDecoderAtPosition(newPosition); + + } + + /** + * Deserializes an array of boolean at the given offset. + */ + public boolean[] readBooleans(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForBooleanArray(expectedLength); + byte[] bytes = new byte[(si.elementsOrVersion + 7) / BindingsHelper.ALIGNMENT]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().get(bytes); + boolean[] result = new boolean[si.elementsOrVersion]; + for (int i = 0; i < bytes.length; ++i) { + for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) { + int booleanIndex = i * BindingsHelper.ALIGNMENT + j; + if (booleanIndex < result.length) { + result[booleanIndex] = (bytes[i] & (1 << j)) != 0; + } + } + } + return result; + } + + /** + * Deserializes an array of bytes at the given offset. + */ + public byte[] readBytes(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(1, expectedLength); + byte[] result = new byte[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().get(result); + return result; + } + + /** + * Deserializes an array of shorts at the given offset. + */ + public short[] readShorts(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(2, expectedLength); + short[] result = new short[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asShortBuffer().get(result); + return result; + } + + /** + * Deserializes an array of ints at the given offset. + */ + public int[] readInts(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + int[] result = new int[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asIntBuffer().get(result); + return result; + } + + /** + * Deserializes an array of floats at the given offset. + */ + public float[] readFloats(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + float[] result = new float[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asFloatBuffer().get(result); + return result; + } + + /** + * Deserializes an array of longs at the given offset. + */ + public long[] readLongs(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(8, expectedLength); + long[] result = new long[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asLongBuffer().get(result); + return result; + } + + /** + * Deserializes an array of doubles at the given offset. + */ + public double[] readDoubles(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(8, expectedLength); + double[] result = new double[si.elementsOrVersion]; + d.mMessage.getData().position(d.mBaseOffset + DataHeader.HEADER_SIZE); + d.mMessage.getData().asDoubleBuffer().get(result); + return result; + } + + /** + * Deserializes an |Handle| at the given offset. + */ + public Handle readHandle(int offset, boolean nullable) { + int index = readInt(offset); + if (index == -1) { + if (!nullable) { + throw new DeserializationException( + "Trying to decode an invalid handle for a non-nullable type."); + } + return InvalidHandle.INSTANCE; + } + mValidator.claimHandle(index); + return mMessage.getHandles().get(index); + } + + /** + * Deserializes an |UntypedHandle| at the given offset. + */ + public UntypedHandle readUntypedHandle(int offset, boolean nullable) { + return readHandle(offset, nullable).toUntypedHandle(); + } + + /** + * Deserializes a |ConsumerHandle| at the given offset. + */ + public DataPipe.ConsumerHandle readConsumerHandle(int offset, boolean nullable) { + return readUntypedHandle(offset, nullable).toDataPipeConsumerHandle(); + } + + /** + * Deserializes a |ProducerHandle| at the given offset. + */ + public DataPipe.ProducerHandle readProducerHandle(int offset, boolean nullable) { + return readUntypedHandle(offset, nullable).toDataPipeProducerHandle(); + } + + /** + * Deserializes a |MessagePipeHandle| at the given offset. + */ + public MessagePipeHandle readMessagePipeHandle(int offset, boolean nullable) { + return readUntypedHandle(offset, nullable).toMessagePipeHandle(); + } + + /** + * Deserializes a |SharedBufferHandle| at the given offset. + */ + public SharedBufferHandle readSharedBufferHandle(int offset, boolean nullable) { + return readUntypedHandle(offset, nullable).toSharedBufferHandle(); + } + + /** + * Deserializes an interface at the given offset. + * + * @return a proxy to the service. + */ + public <P extends Proxy> P readServiceInterface(int offset, boolean nullable, + Interface.Manager<?, P> manager) { + MessagePipeHandle handle = readMessagePipeHandle(offset, nullable); + if (!handle.isValid()) { + return null; + } + int version = readInt(offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); + return manager.attachProxy(handle, version); + } + + /** + * Deserializes a |InterfaceRequest| at the given offset. + */ + public <I extends Interface> InterfaceRequest<I> readInterfaceRequest(int offset, + boolean nullable) { + MessagePipeHandle handle = readMessagePipeHandle(offset, nullable); + if (handle == null) { + return null; + } + return new InterfaceRequest<I>(handle); + } + + /** + * Deserializes an associated interface at the given offset. Not yet supported. + */ + public AssociatedInterfaceNotSupported readAssociatedServiceInterfaceNotSupported(int offset, + boolean nullable) { + return null; + } + + /** + * Deserializes an associated interface request at the given offset. Not yet supported. + */ + public AssociatedInterfaceRequestNotSupported readAssociatedInterfaceRequestNotSupported( + int offset, boolean nullable) { + return null; + } + + /** + * Deserializes a string at the given offset. + */ + public String readString(int offset, boolean nullable) { + final int arrayNullability = nullable ? BindingsHelper.ARRAY_NULLABLE : 0; + byte[] bytes = readBytes(offset, arrayNullability, BindingsHelper.UNSPECIFIED_ARRAY_LENGTH); + if (bytes == null) { + return null; + } + return new String(bytes, Charset.forName("utf8")); + } + + /** + * Deserializes an array of |Handle| at the given offset. + */ + public Handle[] readHandles(int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + Handle[] result = new Handle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + } + + /** + * Deserializes an array of |UntypedHandle| at the given offset. + */ + public UntypedHandle[] readUntypedHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + UntypedHandle[] result = new UntypedHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readUntypedHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + } + + /** + * Deserializes an array of |ConsumerHandle| at the given offset. + */ + public DataPipe.ConsumerHandle[] readConsumerHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + DataPipe.ConsumerHandle[] result = new DataPipe.ConsumerHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readConsumerHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + } + + /** + * Deserializes an array of |ProducerHandle| at the given offset. + */ + public DataPipe.ProducerHandle[] readProducerHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + DataPipe.ProducerHandle[] result = new DataPipe.ProducerHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readProducerHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + + } + + /** + * Deserializes an array of |MessagePipeHandle| at the given offset. + */ + public MessagePipeHandle[] readMessagePipeHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + MessagePipeHandle[] result = new MessagePipeHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readMessagePipeHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + + } + + /** + * Deserializes an array of |SharedBufferHandle| at the given offset. + */ + public SharedBufferHandle[] readSharedBufferHandles( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + SharedBufferHandle[] result = new SharedBufferHandle[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readSharedBufferHandle( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + + } + + /** + * Deserializes an array of |ServiceHandle| at the given offset. + */ + public <S extends Interface, P extends Proxy> S[] readServiceInterfaces( + int offset, int arrayNullability, int expectedLength, Interface.Manager<S, P> manager) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = + d.readDataHeaderForArray(BindingsHelper.SERIALIZED_INTERFACE_SIZE, expectedLength); + S[] result = manager.buildArray(si.elementsOrVersion); + for (int i = 0; i < result.length; ++i) { + // This cast is necessary because java 6 doesn't handle wildcard correctly when using + // Manager<S, ? extends S> + @SuppressWarnings("unchecked") + S value = (S) d.readServiceInterface( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_INTERFACE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability), manager); + result[i] = value; + } + return result; + } + + /** + * Deserializes an array of |InterfaceRequest| at the given offset. + */ + public <I extends Interface> InterfaceRequest<I>[] readInterfaceRequests( + int offset, int arrayNullability, int expectedLength) { + Decoder d = readPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + if (d == null) { + return null; + } + DataHeader si = d.readDataHeaderForArray(4, expectedLength); + @SuppressWarnings("unchecked") + InterfaceRequest<I>[] result = new InterfaceRequest[si.elementsOrVersion]; + for (int i = 0; i < result.length; ++i) { + result[i] = d.readInterfaceRequest( + DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + return result; + } + + /** + * Deserializes an array of associated interfaces at the given offset. Not yet supported. + */ + public AssociatedInterfaceNotSupported[] readAssociatedServiceInterfaceNotSupporteds( + int offset, int arrayNullability, int expectedLength) { + return null; + } + + /** + * Deserializes an array of associated interface requests at the given offset. Not yet + * supported. + */ + public AssociatedInterfaceRequestNotSupported[] readAssociatedInterfaceRequestNotSupporteds( + int offset, int arrayNullability, int expectedLength) { + return null; + } + + /** + * Returns a view of this decoder at the offset |offset|. + */ + private Decoder getDecoderAtPosition(int offset) { + return new Decoder(mMessage, mValidator, offset); + } + + /** + * Deserializes a {@link DataHeader} at the given offset and checks if it is correct for an + * array of booleans. + */ + private DataHeader readDataHeaderForBooleanArray(int expectedLength) { + DataHeader dataHeader = readDataHeader(); + if (dataHeader.size < DataHeader.HEADER_SIZE + (dataHeader.elementsOrVersion + 7) / 8) { + throw new DeserializationException("Array header is incorrect."); + } + if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH + && dataHeader.elementsOrVersion != expectedLength) { + throw new DeserializationException("Incorrect array length. Expected: " + expectedLength + + ", but got: " + dataHeader.elementsOrVersion + "."); + } + return dataHeader; + } + + /** + * Deserializes a {@link DataHeader} of an array at the given offset. + */ + private DataHeader readDataHeaderForArray(long elementSize, int expectedLength) { + DataHeader dataHeader = readDataHeader(); + if (dataHeader.size + < (DataHeader.HEADER_SIZE + elementSize * dataHeader.elementsOrVersion)) { + throw new DeserializationException("Array header is incorrect."); + } + if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH + && dataHeader.elementsOrVersion != expectedLength) { + throw new DeserializationException("Incorrect array length. Expected: " + expectedLength + + ", but got: " + dataHeader.elementsOrVersion + "."); + } + return dataHeader; + } + + private void validateBufferSize(int offset, int size) { + if (mMessage.getData().limit() < offset + size) { + throw new DeserializationException("Buffer is smaller than expected."); + } + } + + public void increaseStackDepth() { + mValidator.increaseStackDepth(); + } + + public void decreaseStackDepth() { + mValidator.decreaseStackDepth(); + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java new file mode 100644 index 00000000000..2ee2ae7493d --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java @@ -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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.MojoException; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * A {@link ConnectionErrorHandler} that delegate the errors to a list of registered handlers. This + * class will use weak pointers to prevent keeping references to any handlers it delegates to. + */ +public class DelegatingConnectionErrorHandler implements ConnectionErrorHandler { + + /** + * The registered handlers. This uses a {@link WeakHashMap} so that it doesn't prevent the + * handler from being garbage collected. + */ + private final Set<ConnectionErrorHandler> mHandlers = + Collections.newSetFromMap(new WeakHashMap<ConnectionErrorHandler, Boolean>()); + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + for (ConnectionErrorHandler handler : mHandlers) { + handler.onConnectionError(e); + } + } + + /** + * Add a handler that will be notified of any error this object receives. + */ + public void addConnectionErrorHandler(ConnectionErrorHandler handler) { + mHandlers.add(handler); + } + + /** + * Remove a previously registered handler. + */ + public void removeConnectionErrorHandler(ConnectionErrorHandler handler) { + mHandlers.remove(handler); + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java new file mode 100644 index 00000000000..eeb511c5207 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java @@ -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. + +package org.chromium.mojo.bindings; + +/** + * Error when deserializing a mojo message. + */ +public class DeserializationException extends RuntimeException { + + /** + * Constructs a new deserialization exception with the specified detail message. + */ + public DeserializationException(String message) { + super(message); + } + + /** + * Constructs a new deserialization exception with the specified cause. + */ + public DeserializationException(Exception cause) { + super(cause); + } + +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java new file mode 100644 index 00000000000..3c86a8daa4c --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java @@ -0,0 +1,587 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.bindings.Interface.Proxy.Handler; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.Pair; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +/** + * Helper class to encode a mojo struct. It keeps track of the output buffer, resizing it as needed. + * It also keeps track of the associated handles, and the offset of the current data section. + */ +public class Encoder { + + /** + * Container class for all state that must be shared between the main encoder and any used sub + * encoder. + */ + private static class EncoderState { + + /** + * The core used to encode interfaces. + */ + public final Core core; + + /** + * The ByteBuffer to which the message will be encoded. + */ + public ByteBuffer byteBuffer; + + /** + * The list of encountered handles. + */ + public final List<Handle> handles = new ArrayList<Handle>(); + + /** + * The current absolute position for the next data section. + */ + public int dataEnd; + + /** + * @param core the |Core| implementation used to generate handles. Only used if the data + * structure being encoded contains interfaces, can be |null| otherwise. + * @param bufferSize A hint on the size of the message. Used to build the initial byte + * buffer. + */ + private EncoderState(Core core, int bufferSize) { + assert bufferSize % BindingsHelper.ALIGNMENT == 0; + this.core = core; + byteBuffer = ByteBuffer.allocateDirect( + bufferSize > 0 ? bufferSize : INITIAL_BUFFER_SIZE); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + dataEnd = 0; + } + + /** + * Claim the given amount of memory at the end of the buffer, resizing it if needed. + */ + public void claimMemory(int size) { + dataEnd += size; + growIfNeeded(); + } + + /** + * Grow the associated ByteBuffer if needed. + */ + private void growIfNeeded() { + if (byteBuffer.capacity() >= dataEnd) { + return; + } + int targetSize = byteBuffer.capacity() * 2; + while (targetSize < dataEnd) { + targetSize *= 2; + } + ByteBuffer newBuffer = ByteBuffer.allocateDirect(targetSize); + newBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.position(0); + byteBuffer.limit(byteBuffer.capacity()); + newBuffer.put(byteBuffer); + byteBuffer = newBuffer; + } + } + + /** + * Default initial size of the data buffer. This must be a multiple of 8 bytes. + */ + private static final int INITIAL_BUFFER_SIZE = 1024; + + /** + * Base offset in the byte buffer for writing. + */ + private int mBaseOffset; + + /** + * The encoder state shared by the main encoder and all its sub-encoder. + */ + private final EncoderState mEncoderState; + + /** + * Returns the result message. + */ + public Message getMessage() { + mEncoderState.byteBuffer.position(0); + mEncoderState.byteBuffer.limit(mEncoderState.dataEnd); + return new Message(mEncoderState.byteBuffer, mEncoderState.handles); + } + + /** + * Constructor. + * + * @param core the |Core| implementation used to generate handles. Only used if the data + * structure being encoded contains interfaces, can be |null| otherwise. + * @param sizeHint A hint on the size of the message. Used to build the initial byte buffer. + */ + public Encoder(Core core, int sizeHint) { + this(new EncoderState(core, sizeHint)); + } + + /** + * Private constructor for sub-encoders. + */ + private Encoder(EncoderState bufferInformation) { + mEncoderState = bufferInformation; + mBaseOffset = bufferInformation.dataEnd; + } + + /** + * Returns a new encoder that will append to the current buffer. + */ + public Encoder getEncoderAtDataOffset(DataHeader dataHeader) { + Encoder result = new Encoder(mEncoderState); + result.encode(dataHeader); + return result; + } + + /** + * Encode a {@link DataHeader} and claim the amount of memory required for the data section + * (resizing the buffer if required). + */ + public void encode(DataHeader s) { + mEncoderState.claimMemory(BindingsHelper.align(s.size)); + encode(s.size, DataHeader.SIZE_OFFSET); + encode(s.elementsOrVersion, DataHeader.ELEMENTS_OR_VERSION_OFFSET); + } + + /** + * Encode a byte at the given offset. + */ + public void encode(byte v, int offset) { + mEncoderState.byteBuffer.put(mBaseOffset + offset, v); + } + + /** + * Encode a boolean at the given offset. + */ + public void encode(boolean v, int offset, int bit) { + if (v) { + byte encodedValue = mEncoderState.byteBuffer.get(mBaseOffset + offset); + encodedValue |= (byte) (1 << bit); + mEncoderState.byteBuffer.put(mBaseOffset + offset, encodedValue); + } + } + + /** + * Encode a short at the given offset. + */ + public void encode(short v, int offset) { + mEncoderState.byteBuffer.putShort(mBaseOffset + offset, v); + } + + /** + * Encode an int at the given offset. + */ + public void encode(int v, int offset) { + mEncoderState.byteBuffer.putInt(mBaseOffset + offset, v); + } + + /** + * Encode a float at the given offset. + */ + public void encode(float v, int offset) { + mEncoderState.byteBuffer.putFloat(mBaseOffset + offset, v); + } + + /** + * Encode a long at the given offset. + */ + public void encode(long v, int offset) { + mEncoderState.byteBuffer.putLong(mBaseOffset + offset, v); + } + + /** + * Encode a double at the given offset. + */ + public void encode(double v, int offset) { + mEncoderState.byteBuffer.putDouble(mBaseOffset + offset, v); + } + + /** + * Encode a {@link Struct} at the given offset. + */ + public void encode(Struct v, int offset, boolean nullable) { + if (v == null) { + encodeNullPointer(offset, nullable); + return; + } + encodePointerToNextUnclaimedData(offset); + v.encode(this); + } + + /** + * Encode a {@link Union} at the given offset. + */ + public void encode(Union v, int offset, boolean nullable) { + if (v == null && !nullable) { + throw new SerializationException( + "Trying to encode a null pointer for a non-nullable type."); + } + if (v == null) { + encode(0L, offset); + encode(0L, offset + DataHeader.HEADER_SIZE); + return; + } + v.encode(this, offset); + } + + /** + * Encodes a String. + */ + public void encode(String v, int offset, boolean nullable) { + if (v == null) { + encodeNullPointer(offset, nullable); + return; + } + final int arrayNullability = nullable + ? BindingsHelper.ARRAY_NULLABLE : BindingsHelper.NOTHING_NULLABLE; + encode(v.getBytes(Charset.forName("utf8")), offset, arrayNullability, + BindingsHelper.UNSPECIFIED_ARRAY_LENGTH); + } + + /** + * Encodes a {@link Handle}. + */ + public void encode(Handle v, int offset, boolean nullable) { + if (v == null || !v.isValid()) { + encodeInvalidHandle(offset, nullable); + } else { + encode(mEncoderState.handles.size(), offset); + mEncoderState.handles.add(v); + } + } + + /** + * Encode an {@link Interface}. + */ + public <T extends Interface> void encode(T v, int offset, boolean nullable, + Interface.Manager<T, ?> manager) { + if (v == null) { + encodeInvalidHandle(offset, nullable); + encode(0, offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); + return; + } + if (mEncoderState.core == null) { + throw new UnsupportedOperationException( + "The encoder has been created without a Core. It can't encode an interface."); + } + // If the instance is a proxy, pass the proxy's handle instead of creating a new stub. + if (v instanceof Interface.Proxy) { + Handler handler = ((Interface.Proxy) v).getProxyHandler(); + encode(handler.passHandle(), offset, nullable); + encode(handler.getVersion(), offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); + return; + } + Pair<MessagePipeHandle, MessagePipeHandle> handles = + mEncoderState.core.createMessagePipe(null); + manager.bind(v, handles.first); + encode(handles.second, offset, nullable); + encode(manager.getVersion(), offset + BindingsHelper.SERIALIZED_HANDLE_SIZE); + } + + /** + * Encode an {@link InterfaceRequest}. + */ + public <I extends Interface> void encode(InterfaceRequest<I> v, int offset, boolean nullable) { + if (v == null) { + encodeInvalidHandle(offset, nullable); + return; + } + if (mEncoderState.core == null) { + throw new UnsupportedOperationException( + "The encoder has been created without a Core. It can't encode an interface."); + } + encode(v.passHandle(), offset, nullable); + } + + /** + * Encode an associated interface. Not yet supported. + */ + public void encode(AssociatedInterfaceNotSupported v, int offset, boolean nullable) { + } + + /** + * Encode an associated interface request. Not yet supported. + */ + public void encode(AssociatedInterfaceRequestNotSupported v, int offset, boolean nullable) { + } + + /** + * Returns an {@link Encoder} suitable for encoding an array of pointer of the given length. + */ + public Encoder encodePointerArray(int length, int offset, int expectedLength) { + return encoderForArray(BindingsHelper.POINTER_SIZE, length, offset, expectedLength); + } + + /** + * Returns an {@link Encoder} suitable for encoding an array of union of the given length. + */ + public Encoder encodeUnionArray(int length, int offset, int expectedLength) { + return encoderForArray(BindingsHelper.UNION_SIZE, length, offset, expectedLength); + } + + /** + * Encodes an array of booleans. + */ + public void encode(boolean[] v, int offset, int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH + && expectedLength != v.length) { + throw new SerializationException("Trying to encode a fixed array of incorrect length."); + } + byte[] bytes = new byte[(v.length + 7) / BindingsHelper.ALIGNMENT]; + for (int i = 0; i < bytes.length; ++i) { + for (int j = 0; j < BindingsHelper.ALIGNMENT; ++j) { + int booleanIndex = BindingsHelper.ALIGNMENT * i + j; + if (booleanIndex < v.length && v[booleanIndex]) { + bytes[i] |= (byte) (1 << j); + } + } + } + encodeByteArray(bytes, v.length, offset); + } + + /** + * Encodes an array of bytes. + */ + public void encode(byte[] v, int offset, int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH + && expectedLength != v.length) { + throw new SerializationException("Trying to encode a fixed array of incorrect length."); + } + encodeByteArray(v, v.length, offset); + } + + /** + * Encodes an array of shorts. + */ + public void encode(short[] v, int offset, int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + encoderForArray(2, v.length, offset, expectedLength).append(v); + } + + /** + * Encodes an array of ints. + */ + public void encode(int[] v, int offset, int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + encoderForArray(4, v.length, offset, expectedLength).append(v); + } + + /** + * Encodes an array of floats. + */ + public void encode(float[] v, int offset, int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + encoderForArray(4, v.length, offset, expectedLength).append(v); + } + + /** + * Encodes an array of longs. + */ + public void encode(long[] v, int offset, int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + encoderForArray(8, v.length, offset, expectedLength).append(v); + } + + /** + * Encodes an array of doubles. + */ + public void encode(double[] v, int offset, int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + encoderForArray(8, v.length, offset, expectedLength).append(v); + } + + /** + * Encodes an array of {@link Handle}. + */ + public void encode(Handle[] v, int offset, int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + Encoder e = encoderForArray( + BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength); + for (int i = 0; i < v.length; ++i) { + e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + } + + /** + * Encodes an array of {@link Interface}. + */ + public <T extends Interface> void encode(T[] v, int offset, int arrayNullability, + int expectedLength, Interface.Manager<T, ?> manager) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + Encoder e = encoderForArray( + BindingsHelper.SERIALIZED_INTERFACE_SIZE, v.length, offset, expectedLength); + for (int i = 0; i < v.length; ++i) { + e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_INTERFACE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability), manager); + } + } + + public Encoder encoderForMap(int offset) { + encodePointerToNextUnclaimedData(offset); + return getEncoderAtDataOffset(BindingsHelper.MAP_STRUCT_HEADER); + } + + /** + * Encodes a pointer to the next unclaimed memory and returns an encoder suitable to encode an + * union at this location. + */ + public Encoder encoderForUnionPointer(int offset) { + encodePointerToNextUnclaimedData(offset); + Encoder result = new Encoder(mEncoderState); + result.mEncoderState.claimMemory(16); + return result; + } + + /** + * Encodes an array of {@link InterfaceRequest}. + */ + public <I extends Interface> void encode(InterfaceRequest<I>[] v, int offset, + int arrayNullability, int expectedLength) { + if (v == null) { + encodeNullPointer(offset, BindingsHelper.isArrayNullable(arrayNullability)); + return; + } + Encoder e = encoderForArray( + BindingsHelper.SERIALIZED_HANDLE_SIZE, v.length, offset, expectedLength); + for (int i = 0; i < v.length; ++i) { + e.encode(v[i], DataHeader.HEADER_SIZE + BindingsHelper.SERIALIZED_HANDLE_SIZE * i, + BindingsHelper.isElementNullable(arrayNullability)); + } + } + + /** + * Encodes an array of associated interfaces. Not yet supported. + */ + public void encode(AssociatedInterfaceNotSupported[] v, int offset, int arrayNullability, + int expectedLength) {} + + /** + * Encodes an array of associated interface requests. Not yet supported. + */ + public void encode(AssociatedInterfaceRequestNotSupported[] v, int offset, int arrayNullability, + int expectedLength) {} + + /** + * Encodes a <code>null</code> pointer iff the object is nullable, raises an exception + * otherwise. + */ + public void encodeNullPointer(int offset, boolean nullable) { + if (!nullable) { + throw new SerializationException( + "Trying to encode a null pointer for a non-nullable type."); + } + mEncoderState.byteBuffer.putLong(mBaseOffset + offset, 0); + } + + /** + * Encodes an invalid handle iff the object is nullable, raises an exception otherwise. + */ + public void encodeInvalidHandle(int offset, boolean nullable) { + if (!nullable) { + throw new SerializationException( + "Trying to encode an invalid handle for a non-nullable type."); + } + mEncoderState.byteBuffer.putInt(mBaseOffset + offset, -1); + } + + /** + * Claim the given amount of memory at the end of the buffer, resizing it if needed. + */ + void claimMemory(int size) { + mEncoderState.claimMemory(BindingsHelper.align(size)); + } + + private void encodePointerToNextUnclaimedData(int offset) { + encode((long) mEncoderState.dataEnd - (mBaseOffset + offset), offset); + } + + private Encoder encoderForArray( + int elementSizeInByte, int length, int offset, int expectedLength) { + if (expectedLength != BindingsHelper.UNSPECIFIED_ARRAY_LENGTH + && expectedLength != length) { + throw new SerializationException("Trying to encode a fixed array of incorrect length."); + } + return encoderForArrayByTotalSize(length * elementSizeInByte, length, offset); + } + + private Encoder encoderForArrayByTotalSize(int byteSize, int length, int offset) { + encodePointerToNextUnclaimedData(offset); + return getEncoderAtDataOffset( + new DataHeader(DataHeader.HEADER_SIZE + byteSize, length)); + } + + private void encodeByteArray(byte[] bytes, int length, int offset) { + encoderForArrayByTotalSize(bytes.length, length, offset).append(bytes); + } + + private void append(byte[] v) { + mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); + mEncoderState.byteBuffer.put(v); + } + + private void append(short[] v) { + mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); + mEncoderState.byteBuffer.asShortBuffer().put(v); + } + + private void append(int[] v) { + mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); + mEncoderState.byteBuffer.asIntBuffer().put(v); + } + + private void append(float[] v) { + mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); + mEncoderState.byteBuffer.asFloatBuffer().put(v); + } + + private void append(double[] v) { + mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); + mEncoderState.byteBuffer.asDoubleBuffer().put(v); + } + + private void append(long[] v) { + mEncoderState.byteBuffer.position(mBaseOffset + DataHeader.HEADER_SIZE); + mEncoderState.byteBuffer.asLongBuffer().put(v); + } + +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java new file mode 100644 index 00000000000..8961d22d3ee --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExceptionHandler.java @@ -0,0 +1,59 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +/** + * An {@link ExceptionHandler} is notified of any {@link RuntimeException} happening in the + * bindings or any of the callbacks. + */ +public interface ExceptionHandler { + /** + * Receives a notification that an unhandled {@link RuntimeException} has been thrown in an + * {@link Interface} implementation or one of the {@link Callbacks} internal classes. + * + * Normal implementations should either throw the exception or return whether the connection + * should be kept alive or terminated. + */ + public boolean handleException(RuntimeException e); + + /** + * The default ExceptionHandler, which simply throws the exception upon receiving it. It can + * also delegate the handling of the exceptions to another instance of ExceptionHandler. + */ + public static class DefaultExceptionHandler implements ExceptionHandler { + private ExceptionHandler mDelegate; + + @Override + public boolean handleException(RuntimeException e) { + if (mDelegate != null) { + return mDelegate.handleException(e); + } + throw e; + } + + private DefaultExceptionHandler() {} + + /** + * Static class that implements the initialization-on-demand holder idiom. + */ + private static class LazyHolder { + static final DefaultExceptionHandler INSTANCE = new DefaultExceptionHandler(); + } + + /** + * Gets the singleton instance for the DefaultExceptionHandler. + */ + public static DefaultExceptionHandler getInstance() { + return LazyHolder.INSTANCE; + } + + /** + * Sets a delegate ExceptionHandler, in case throwing an exception is not desirable. + */ + public void setDelegate(ExceptionHandler exceptionHandler) { + mDelegate = exceptionHandler; + } + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java new file mode 100644 index 00000000000..8eb96aa10be --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java @@ -0,0 +1,169 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MessagePipeHandle.ReadMessageResult; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.Watcher; +import org.chromium.mojo.system.Watcher.Callback; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +/** + * A factory which provides per-thread executors, which enable execution on the thread from which + * they were obtained. + */ +class ExecutorFactory { + + /** + * A null buffer which is used to send messages without any data on the PipedExecutor's + * signaling handles. + */ + private static final ByteBuffer NOTIFY_BUFFER = null; + + /** + * Implementation of the executor which uses a pair of {@link MessagePipeHandle} for signaling. + * The executor will wait asynchronously on one end of a {@link MessagePipeHandle} on the thread + * on which it was created. Other threads can call execute with a {@link Runnable}, and the + * executor will queue the {@link Runnable} and write a message on the other end of the handle. + * This will wake up the executor which is waiting on the handle, which will then dequeue the + * {@link Runnable} and execute it on the original thread. + */ + private static class PipedExecutor implements Executor, Callback { + + /** + * The handle which is written to. Access to this object must be protected with |mLock|. + */ + private final MessagePipeHandle mWriteHandle; + /** + * The handle which is read from. + */ + private final MessagePipeHandle mReadHandle; + /** + * The list of actions left to be run. Access to this object must be protected with |mLock|. + */ + private final List<Runnable> mPendingActions; + /** + * Lock protecting access to |mWriteHandle| and |mPendingActions|. + */ + private final Object mLock; + /** + * The {@link Watcher} to get notified of new message availability on |mReadHandle|. + */ + private final Watcher mWatcher; + + /** + * Constructor. + */ + public PipedExecutor(Core core) { + mWatcher = core.getWatcher(); + assert mWatcher != null; + mLock = new Object(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe( + new MessagePipeHandle.CreateOptions()); + mReadHandle = handles.first; + mWriteHandle = handles.second; + mPendingActions = new ArrayList<Runnable>(); + mWatcher.start(mReadHandle, Core.HandleSignals.READABLE, this); + } + + /** + * @see Callback#onResult(int) + */ + @Override + public void onResult(int result) { + if (result == MojoResult.OK && readNotifyBufferMessage()) { + runNextAction(); + } else { + close(); + } + } + + /** + * Close the handles. Should only be called on the executor thread. + */ + private void close() { + synchronized (mLock) { + mWriteHandle.close(); + mPendingActions.clear(); + } + mWatcher.cancel(); + mWatcher.destroy(); + mReadHandle.close(); + } + + /** + * Read the next message on |mReadHandle|, and return |true| if successful, |false| + * otherwise. + */ + private boolean readNotifyBufferMessage() { + try { + ResultAnd<ReadMessageResult> readMessageResult = + mReadHandle.readMessage(MessagePipeHandle.ReadFlags.NONE); + if (readMessageResult.getMojoResult() == MojoResult.OK) { + return true; + } + } catch (MojoException e) { + // Will be closed by the fall back at the end of this method. + } + return false; + } + + /** + * Run the next action in the |mPendingActions| queue. + */ + private void runNextAction() { + Runnable toRun = null; + synchronized (mLock) { + toRun = mPendingActions.remove(0); + } + toRun.run(); + } + + /** + * Execute the given |command| in the executor thread. This can be called on any thread. + * + * @see Executor#execute(Runnable) + */ + @Override + public void execute(Runnable command) { + // Accessing the write handle must be protected by the lock, because it can be closed + // from the executor's thread. + synchronized (mLock) { + if (!mWriteHandle.isValid()) { + throw new IllegalStateException( + "Trying to execute an action on a closed executor."); + } + mPendingActions.add(command); + mWriteHandle.writeMessage(NOTIFY_BUFFER, null, MessagePipeHandle.WriteFlags.NONE); + } + } + } + + /** + * Keep one executor per executor thread. + */ + private static final ThreadLocal<Executor> EXECUTORS = new ThreadLocal<Executor>(); + + /** + * Returns an {@link Executor} that will run all of its actions in the current thread. + */ + public static Executor getExecutorForCurrentThread(Core core) { + Executor executor = EXECUTORS.get(); + if (executor == null) { + executor = new PipedExecutor(core); + EXECUTORS.set(executor); + } + return executor; + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java new file mode 100644 index 00000000000..60fc33ba4ef --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java @@ -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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Handle; + +import java.io.Closeable; + +/** + * Describes a class that owns a handle. + * + * @param <H> The type of the owned handle. + */ +public interface HandleOwner<H extends Handle> extends Closeable { + + /** + * Pass the handle owned by this class. + */ + public H passHandle(); + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close(); + +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java new file mode 100644 index 00000000000..f7d3f80256d --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java @@ -0,0 +1,507 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.Interface.AbstractProxy.HandlerImpl; +import org.chromium.mojo.bindings.interfacecontrol.QueryVersion; +import org.chromium.mojo.bindings.interfacecontrol.RequireVersion; +import org.chromium.mojo.bindings.interfacecontrol.RunInput; +import org.chromium.mojo.bindings.interfacecontrol.RunMessageParams; +import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeInput; +import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeMessageParams; +import org.chromium.mojo.bindings.interfacecontrol.RunOutput; +import org.chromium.mojo.bindings.interfacecontrol.RunResponseMessageParams; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.Pair; + +import java.io.Closeable; +import java.util.concurrent.Executor; + +/** + * Base class for mojo generated interfaces. + */ +public interface Interface extends ConnectionErrorHandler, Closeable { + + /** + * The close method is called when the connection to the interface is closed. + * + * @see java.io.Closeable#close() + */ + @Override + public void close(); + + /** + * A proxy to a mojo interface. This is base class for all generated proxies. It implements the + * Interface and each time a method is called, the parameters are serialized and sent to the + * {@link MessageReceiverWithResponder}, along with the response callback if needed. + */ + public interface Proxy extends Interface { + /** + * Class allowing to interact with the proxy itself. + */ + public interface Handler extends Closeable { + /** + * Sets the {@link ConnectionErrorHandler} that will be notified of errors. + */ + public void setErrorHandler(ConnectionErrorHandler errorHandler); + + /** + * Unbinds the proxy and passes the handle. Can return null if the proxy is not bound or + * if the proxy is not over a message pipe. + */ + public MessagePipeHandle passHandle(); + + /** + * Returns the version number of the interface that the remote side supports. + */ + public int getVersion(); + + /** + * Queries the max version that the remote side supports. On completion, the result will + * be returned as the input of |callback|. The version number of this interface pointer + * will also be updated. + */ + public void queryVersion(Callback1<Integer> callback); + + /** + * If the remote side doesn't support the specified version, it will close its end of + * the message pipe asynchronously. The call does nothing if |version| is no greater + * than getVersion(). + * <p> + * If you make a call to requireVersion() with a version number X which is not supported + * by the remote side, it is guaranteed that all calls to the interface methods after + * requireVersion(X) will be ignored. + */ + public void requireVersion(int version); + } + + /** + * Returns the {@link Handler} object allowing to interact with the proxy itself. + */ + public Handler getProxyHandler(); + } + + /** + * Base implementation of {@link Proxy}. + */ + abstract class AbstractProxy implements Proxy { + /** + * Implementation of {@link Handler}. + */ + protected static class HandlerImpl implements Proxy.Handler, ConnectionErrorHandler { + /** + * The {@link Core} implementation to use. + */ + private final Core mCore; + + /** + * The {@link MessageReceiverWithResponder} that will receive a serialized message for + * each method call. + */ + private final MessageReceiverWithResponder mMessageReceiver; + + /** + * The {@link ConnectionErrorHandler} that will be notified of errors. + */ + private ConnectionErrorHandler mErrorHandler; + + /** + * The currently known version of the interface. + */ + private int mVersion; + + /** + * Constructor. + * + * @param core the Core implementation used to create pipes and access the async waiter. + * @param messageReceiver the message receiver to send message to. + */ + protected HandlerImpl(Core core, MessageReceiverWithResponder messageReceiver) { + this.mCore = core; + this.mMessageReceiver = messageReceiver; + } + + void setVersion(int version) { + mVersion = version; + } + + /** + * Returns the message receiver to send message to. + */ + public MessageReceiverWithResponder getMessageReceiver() { + return mMessageReceiver; + } + + /** + * Returns the Core implementation. + */ + public Core getCore() { + return mCore; + } + + /** + * Sets the {@link ConnectionErrorHandler} that will be notified of errors. + */ + @Override + public void setErrorHandler(ConnectionErrorHandler errorHandler) { + this.mErrorHandler = errorHandler; + } + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + if (mErrorHandler != null) { + mErrorHandler.onConnectionError(e); + } + } + + /** + * @see Closeable#close() + */ + @Override + public void close() { + mMessageReceiver.close(); + } + + /** + * @see Interface.Proxy.Handler#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + @SuppressWarnings("unchecked") + HandleOwner<MessagePipeHandle> handleOwner = + (HandleOwner<MessagePipeHandle>) mMessageReceiver; + return handleOwner.passHandle(); + } + + /** + * @see Handler#getVersion() + */ + @Override + public int getVersion() { + return mVersion; + } + + /** + * @see Handler#queryVersion(org.chromium.mojo.bindings.Callbacks.Callback1) + */ + @Override + public void queryVersion(final Callback1<Integer> callback) { + RunMessageParams message = new RunMessageParams(); + message.input = new RunInput(); + message.input.setQueryVersion(new QueryVersion()); + + InterfaceControlMessagesHelper.sendRunMessage(getCore(), mMessageReceiver, message, + new Callback1<RunResponseMessageParams>() { + @Override + public void call(RunResponseMessageParams response) { + if (response.output != null + && response.output.which() + == RunOutput.Tag.QueryVersionResult) { + mVersion = response.output.getQueryVersionResult().version; + } + callback.call(mVersion); + } + }); + } + + /** + * @see Handler#requireVersion(int) + */ + @Override + public void requireVersion(int version) { + if (mVersion >= version) { + return; + } + mVersion = version; + RunOrClosePipeMessageParams message = new RunOrClosePipeMessageParams(); + message.input = new RunOrClosePipeInput(); + message.input.setRequireVersion(new RequireVersion()); + message.input.getRequireVersion().version = version; + InterfaceControlMessagesHelper.sendRunOrClosePipeMessage( + getCore(), mMessageReceiver, message); + } + } + + /** + * The handler associated with this proxy. + */ + private final HandlerImpl mHandler; + + protected AbstractProxy(Core core, MessageReceiverWithResponder messageReceiver) { + mHandler = new HandlerImpl(core, messageReceiver); + } + + /** + * @see Interface#close() + */ + @Override + public void close() { + mHandler.close(); + } + + /** + * @see Proxy#getProxyHandler() + */ + @Override + public HandlerImpl getProxyHandler() { + return mHandler; + } + + /** + * @see ConnectionErrorHandler#onConnectionError(org.chromium.mojo.system.MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mHandler.onConnectionError(e); + } + } + + /** + * Base implementation of Stub. Stubs are message receivers that deserialize the payload and + * call the appropriate method in the implementation. If the method returns result, the stub + * serializes the response and sends it back. + * + * @param <I> the type of the interface to delegate calls to. + */ + abstract class Stub<I extends Interface> implements MessageReceiverWithResponder { + + /** + * The {@link Core} implementation to use. + */ + private final Core mCore; + + /** + * The implementation to delegate calls to. + */ + private final I mImpl; + + /** + * Constructor. + * + * @param core the {@link Core} implementation to use. + * @param impl the implementation to delegate calls to. + */ + public Stub(Core core, I impl) { + mCore = core; + mImpl = impl; + } + + /** + * Returns the Core implementation. + */ + protected Core getCore() { + return mCore; + } + + /** + * Returns the implementation to delegate calls to. + */ + protected I getImpl() { + return mImpl; + } + + /** + * @see org.chromium.mojo.bindings.MessageReceiver#close() + */ + @Override + public void close() { + mImpl.close(); + } + + } + + /** + * A {@link MessageReceiverWithResponder} implementation that forwards all calls to the thread + * the ThreadSafeForwarder was created. + */ + class ThreadSafeForwarder implements MessageReceiverWithResponder { + + /** + * The {@link MessageReceiverWithResponder} that will receive a serialized message for + * each method call. + */ + private final MessageReceiverWithResponder mMessageReceiver; + + /** + * The {@link Executor} to forward all tasks to. + */ + private final Executor mExecutor; + + /** + * Constructor. + * + * @param core the Core implementation used to create pipes and access the async waiter. + * @param messageReceiver the message receiver to send message to. + */ + public ThreadSafeForwarder(Core core, MessageReceiverWithResponder messageReceiver) { + mMessageReceiver = messageReceiver; + mExecutor = ExecutorFactory.getExecutorForCurrentThread(core); + } + + /** + * @see org.chromium.mojo.bindings.MessageReceiver#close() + */ + @Override + public void close() { + mExecutor.execute(() -> { + mMessageReceiver.close(); + }); + } + + /** + * @see org.chromium.mojo.bindings.MessageReceiver#accept() + */ + @Override + public boolean accept(Message message) { + mExecutor.execute(() -> { + mMessageReceiver.accept(message); + }); + return true; + } + + /** + * @see org.chromium.mojo.bindings.MessageReceiverWithResponder#acceptWithResponder() + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + mExecutor.execute(() -> { + mMessageReceiver.acceptWithResponder(message, responder); + }); + return true; + } + } + + /** + * The |Manager| object enables building of proxies and stubs for a given interface. + * + * @param <I> the type of the interface the manager can handle. + * @param <P> the type of the proxy the manager can handle. To be noted, P always extends I. + */ + abstract class Manager<I extends Interface, P extends Proxy> { + + /** + * Returns the name of the interface. This is an opaque (but human readable) identifier used + * by the service provider to identify services. + */ + public abstract String getName(); + + /** + * Returns the version of the managed interface. + */ + public abstract int getVersion(); + + /** + * Binds the given implementation to the handle. + */ + public void bind(I impl, MessagePipeHandle handle) { + // The router (and by consequence the handle) is intentionally leaked. It will close + // itself when the connected handle is closed and the proxy receives the connection + // error. + Router router = new RouterImpl(handle); + bind(handle.getCore(), impl, router); + router.start(); + } + + /** + * Binds the given implementation to the InterfaceRequest. + */ + public final void bind(I impl, InterfaceRequest<I> request) { + bind(impl, request.passHandle()); + } + + /** + * Returns a Proxy that will send messages to the given |handle|. This implies that the + * other end of the handle must be bound to an implementation of the interface. + */ + public final P attachProxy(MessagePipeHandle handle, int version) { + RouterImpl router = new RouterImpl(handle); + P proxy = attachProxy(handle.getCore(), router); + DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler(); + handlers.addConnectionErrorHandler(proxy); + router.setErrorHandler(handlers); + router.start(); + ((HandlerImpl) proxy.getProxyHandler()).setVersion(version); + return proxy; + } + + /** + * Constructs a new |InterfaceRequest| for the interface. This method returns a Pair where + * the first element is a proxy, and the second element is the request. The proxy can be + * used immediately. + */ + public final Pair<P, InterfaceRequest<I>> getInterfaceRequest(Core core) { + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + P proxy = attachProxy(handles.first, 0); + return Pair.create(proxy, new InterfaceRequest<I>(handles.second)); + } + + public final InterfaceRequest<I> asInterfaceRequest(MessagePipeHandle handle) { + return new InterfaceRequest<I>(handle); + } + + /** + * Constructs a thread-safe Proxy forwarding the calls to the given message receiver. + * All calls can be performed from any thread and are posted to the {@link Executor} that + * is associated with the thread on which this method was called on. + * + * The original Proxy object is unbound. + */ + public final P buildThreadSafeProxy(P proxy) { + HandlerImpl handlerImpl = (HandlerImpl) proxy.getProxyHandler(); + Core core = handlerImpl.getCore(); + int version = handlerImpl.getVersion(); + + Router router = new RouterImpl(handlerImpl.passHandle()); + // Close the original proxy now that its handle has been passed. + proxy.close(); + + proxy = buildProxy( + core, new ThreadSafeForwarder(core, new AutoCloseableRouter(core, router))); + DelegatingConnectionErrorHandler handlers = new DelegatingConnectionErrorHandler(); + handlers.addConnectionErrorHandler(proxy); + router.setErrorHandler(handlers); + router.start(); + ((HandlerImpl) proxy.getProxyHandler()).setVersion(version); + return proxy; + } + + /** + * Binds the implementation to the given |router|. + */ + final void bind(Core core, I impl, Router router) { + router.setErrorHandler(impl); + router.setIncomingMessageReceiver(buildStub(core, impl)); + } + + /** + * Returns a Proxy that will send messages to the given |router|. + */ + final P attachProxy(Core core, Router router) { + return buildProxy(core, new AutoCloseableRouter(core, router)); + } + + /** + * Creates a new array of the given |size|. + */ + protected abstract I[] buildArray(int size); + + /** + * Constructs a Stub delegating to the given implementation. + */ + protected abstract Stub<I> buildStub(Core core, I impl); + + /** + * Constructs a Proxy forwarding the calls to the given message receiver. + */ + protected abstract P buildProxy(Core core, MessageReceiverWithResponder messageReceiver); + + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java new file mode 100644 index 00000000000..51f543d567e --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java @@ -0,0 +1,105 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.Interface.Manager; +import org.chromium.mojo.bindings.Interface.Proxy; +import org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants; +import org.chromium.mojo.bindings.interfacecontrol.QueryVersionResult; +import org.chromium.mojo.bindings.interfacecontrol.RunInput; +import org.chromium.mojo.bindings.interfacecontrol.RunMessageParams; +import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeInput; +import org.chromium.mojo.bindings.interfacecontrol.RunOrClosePipeMessageParams; +import org.chromium.mojo.bindings.interfacecontrol.RunOutput; +import org.chromium.mojo.bindings.interfacecontrol.RunResponseMessageParams; +import org.chromium.mojo.system.Core; + +/** + * Helper class to handle interface control messages. See + * mojo/public/interfaces/bindings/interface_control_messages.mojom. + */ +public class InterfaceControlMessagesHelper { + /** + * MessageReceiver that forwards a message containing a {@link RunResponseMessageParams} to a + * callback. + */ + private static class RunResponseForwardToCallback + extends SideEffectFreeCloseable implements MessageReceiver { + private final Callback1<RunResponseMessageParams> mCallback; + + RunResponseForwardToCallback(Callback1<RunResponseMessageParams> callback) { + mCallback = callback; + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + RunResponseMessageParams response = + RunResponseMessageParams.deserialize(message.asServiceMessage().getPayload()); + mCallback.call(response); + return true; + } + } + + /** + * Sends the given run message through the receiver, registering the callback. + */ + public static void sendRunMessage(Core core, MessageReceiverWithResponder receiver, + RunMessageParams params, Callback1<RunResponseMessageParams> callback) { + Message message = params.serializeWithHeader( + core, new MessageHeader(InterfaceControlMessagesConstants.RUN_MESSAGE_ID, + MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0)); + receiver.acceptWithResponder(message, new RunResponseForwardToCallback(callback)); + } + + /** + * Sends the given run or close pipe message through the receiver. + */ + public static void sendRunOrClosePipeMessage( + Core core, MessageReceiverWithResponder receiver, RunOrClosePipeMessageParams params) { + Message message = params.serializeWithHeader(core, + new MessageHeader(InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID)); + receiver.accept(message); + } + + /** + * Handles a received run message. + */ + public static <I extends Interface, P extends Proxy> boolean handleRun( + Core core, Manager<I, P> manager, ServiceMessage message, MessageReceiver responder) { + Message payload = message.getPayload(); + RunMessageParams query = RunMessageParams.deserialize(payload); + RunResponseMessageParams response = new RunResponseMessageParams(); + response.output = new RunOutput(); + if (query.input.which() == RunInput.Tag.QueryVersion) { + response.output.setQueryVersionResult(new QueryVersionResult()); + response.output.getQueryVersionResult().version = manager.getVersion(); + } else { + response.output = null; + } + + return responder.accept(response.serializeWithHeader( + core, new MessageHeader(InterfaceControlMessagesConstants.RUN_MESSAGE_ID, + MessageHeader.MESSAGE_IS_RESPONSE_FLAG, + message.getHeader().getRequestId()))); + } + + /** + * Handles a received run or close pipe message. Closing the pipe is handled by returning + * |false|. + */ + public static <I extends Interface, P extends Proxy> boolean handleRunOrClosePipe( + Manager<I, P> manager, ServiceMessage message) { + Message payload = message.getPayload(); + RunOrClosePipeMessageParams query = RunOrClosePipeMessageParams.deserialize(payload); + if (query.input.which() == RunOrClosePipeInput.Tag.RequireVersion) { + return query.input.getRequireVersion().version <= manager.getVersion(); + } + return false; + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java new file mode 100644 index 00000000000..61899b44679 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java @@ -0,0 +1,58 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.MessagePipeHandle; + +/** + * One end of the message pipe representing a request to create an implementation to be bound to it. + * The other end of the pipe is bound to a proxy, which can be used immediately, while the + * InterfaceRequest is being sent. + * <p> + * InterfaceRequest are built using |Interface.Manager|. + * + * @param <P> the type of the remote interface proxy. + */ +public class InterfaceRequest<P extends Interface> implements HandleOwner<MessagePipeHandle> { + + /** + * The handle which will be sent and will be connected to the implementation. + */ + private final MessagePipeHandle mHandle; + + /** + * Constructor. + * + * @param handle the handle which will be sent and will be connected to the implementation. + */ + InterfaceRequest(MessagePipeHandle handle) { + mHandle = handle; + } + + /** + * @see HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + return mHandle.pass(); + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + mHandle.close(); + } + + /** + * Returns an {@link InterfaceRequest} that wraps the given handle. This method is not type safe + * and should be avoided unless absolutely necessary. + */ + @SuppressWarnings("rawtypes") + public static InterfaceRequest asInterfaceRequestUnsafe(MessagePipeHandle handle) { + return new InterfaceRequest(handle); + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java new file mode 100644 index 00000000000..de484c4b412 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java @@ -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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * A raw message to be sent/received from a {@link MessagePipeHandle}. Note that this can contain + * any data, not necessarily a Mojo message with a proper header. See also {@link ServiceMessage}. + */ +public class Message { + + /** + * The data of the message. + */ + private final ByteBuffer mBuffer; + + /** + * The handles of the message. + */ + private final List<? extends Handle> mHandles; + + /** + * This message interpreted as a message for a mojo service with an appropriate header. + */ + private ServiceMessage mWithHeader; + + /** + * Constructor. + * + * @param buffer The buffer containing the bytes to send. This must be a direct buffer. + * @param handles The list of handles to send. + */ + public Message(ByteBuffer buffer, List<? extends Handle> handles) { + mBuffer = buffer; + mHandles = handles; + } + + /** + * The data of the message. + */ + public ByteBuffer getData() { + return mBuffer; + } + + /** + * The handles of the message. + */ + public List<? extends Handle> getHandles() { + return mHandles; + } + + /** + * Returns the message interpreted as a message for a mojo service. + */ + public ServiceMessage asServiceMessage() { + if (mWithHeader == null) { + mWithHeader = new ServiceMessage(this); + } + return mWithHeader; + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java new file mode 100644 index 00000000000..771d22b5c37 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java @@ -0,0 +1,248 @@ +// 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. + +package org.chromium.mojo.bindings; + +import java.nio.ByteBuffer; + +/** + * Header information for a message. + */ +public class MessageHeader { + + private static final int SIMPLE_MESSAGE_SIZE = 24; + private static final int SIMPLE_MESSAGE_VERSION = 0; + private static final DataHeader SIMPLE_MESSAGE_STRUCT_INFO = + new DataHeader(SIMPLE_MESSAGE_SIZE, SIMPLE_MESSAGE_VERSION); + + private static final int MESSAGE_WITH_REQUEST_ID_SIZE = 32; + private static final int MESSAGE_WITH_REQUEST_ID_VERSION = 1; + private static final DataHeader MESSAGE_WITH_REQUEST_ID_STRUCT_INFO = + new DataHeader(MESSAGE_WITH_REQUEST_ID_SIZE, MESSAGE_WITH_REQUEST_ID_VERSION); + + private static final int INTERFACE_ID_OFFSET = 8; + private static final int TYPE_OFFSET = 12; + private static final int FLAGS_OFFSET = 16; + private static final int REQUEST_ID_OFFSET = 24; + + /** + * Flag for a header of a simple message. + */ + public static final int NO_FLAG = 0; + + /** + * Flag for a header of a message that expected a response. + */ + public static final int MESSAGE_EXPECTS_RESPONSE_FLAG = 1 << 0; + + /** + * Flag for a header of a message that is a response. + */ + public static final int MESSAGE_IS_RESPONSE_FLAG = 1 << 1; + + private final DataHeader mDataHeader; + private final int mType; + private final int mFlags; + private long mRequestId; + + /** + * Constructor for the header of a message which does not have a response. + */ + public MessageHeader(int type) { + mDataHeader = SIMPLE_MESSAGE_STRUCT_INFO; + mType = type; + mFlags = 0; + mRequestId = 0; + } + + /** + * Constructor for the header of a message which have a response or being itself a response. + */ + public MessageHeader(int type, int flags, long requestId) { + assert mustHaveRequestId(flags); + mDataHeader = MESSAGE_WITH_REQUEST_ID_STRUCT_INFO; + mType = type; + mFlags = flags; + mRequestId = requestId; + } + + /** + * Constructor, parsing the header from a message. Should only be used by {@link Message} + * itself. + */ + MessageHeader(Message message) { + Decoder decoder = new Decoder(message); + mDataHeader = decoder.readDataHeader(); + validateDataHeader(mDataHeader); + + // Currently associated interfaces are not supported. + int interfaceId = decoder.readInt(INTERFACE_ID_OFFSET); + if (interfaceId != 0) { + throw new DeserializationException("Non-zero interface ID, expecting zero since " + + "associated interfaces are not yet supported."); + } + + mType = decoder.readInt(TYPE_OFFSET); + mFlags = decoder.readInt(FLAGS_OFFSET); + if (mustHaveRequestId(mFlags)) { + if (mDataHeader.size < MESSAGE_WITH_REQUEST_ID_SIZE) { + throw new DeserializationException("Incorrect message size, expecting at least " + + MESSAGE_WITH_REQUEST_ID_SIZE + + " for a message with a request identifier, but got: " + mDataHeader.size); + + } + mRequestId = decoder.readLong(REQUEST_ID_OFFSET); + } else { + mRequestId = 0; + } + } + + /** + * Returns the size in bytes of the serialization of the header. + */ + public int getSize() { + return mDataHeader.size; + } + + /** + * Returns the type of the message. + */ + public int getType() { + return mType; + } + + /** + * Returns the flags associated to the message. + */ + public int getFlags() { + return mFlags; + } + + /** + * Returns if the message has the given flag. + */ + public boolean hasFlag(int flag) { + return (mFlags & flag) == flag; + } + + /** + * Returns if the message has a request id. + */ + public boolean hasRequestId() { + return mustHaveRequestId(mFlags); + } + + /** + * Return the request id for the message. Must only be called if the message has a request id. + */ + public long getRequestId() { + assert hasRequestId(); + return mRequestId; + } + + /** + * Encode the header. + */ + public void encode(Encoder encoder) { + encoder.encode(mDataHeader); + // Set the interface ID field to 0. + encoder.encode(0, INTERFACE_ID_OFFSET); + encoder.encode(getType(), TYPE_OFFSET); + encoder.encode(getFlags(), FLAGS_OFFSET); + if (hasRequestId()) { + encoder.encode(getRequestId(), REQUEST_ID_OFFSET); + } + } + + /** + * Returns true if the header has the expected flags. Only considers flags this class knows + * about in order to allow this class to work with future version of the header format. + */ + public boolean validateHeader(int expectedFlags) { + int knownFlags = getFlags() & (MESSAGE_EXPECTS_RESPONSE_FLAG | MESSAGE_IS_RESPONSE_FLAG); + return knownFlags == expectedFlags; + } + + /** + * Returns true if the header has the expected type and flags. Only consider flags this class + * knows about in order to allow this class to work with future version of the header format. + */ + public boolean validateHeader(int expectedType, int expectedFlags) { + return getType() == expectedType && validateHeader(expectedFlags); + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mDataHeader == null) ? 0 : mDataHeader.hashCode()); + result = prime * result + mFlags; + result = prime * result + (int) (mRequestId ^ (mRequestId >>> 32)); + result = prime * result + mType; + return result; + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object object) { + if (object == this) return true; + if (object == null) return false; + if (getClass() != object.getClass()) return false; + + MessageHeader other = (MessageHeader) object; + return (BindingsHelper.equals(mDataHeader, other.mDataHeader) + && mFlags == other.mFlags + && mRequestId == other.mRequestId + && mType == other.mType); + } + + /** + * Set the request id on the message contained in the given buffer. + */ + void setRequestId(ByteBuffer buffer, long requestId) { + assert mustHaveRequestId(buffer.getInt(FLAGS_OFFSET)); + buffer.putLong(REQUEST_ID_OFFSET, requestId); + mRequestId = requestId; + } + + /** + * Returns whether a message with the given flags must have a request Id. + */ + private static boolean mustHaveRequestId(int flags) { + return (flags & (MESSAGE_EXPECTS_RESPONSE_FLAG | MESSAGE_IS_RESPONSE_FLAG)) != 0; + } + + /** + * Validate that the given {@link DataHeader} can be the data header of a message header. + */ + private static void validateDataHeader(DataHeader dataHeader) { + if (dataHeader.elementsOrVersion < SIMPLE_MESSAGE_VERSION) { + throw new DeserializationException("Incorrect number of fields, expecting at least " + + SIMPLE_MESSAGE_VERSION + ", but got: " + dataHeader.elementsOrVersion); + } + if (dataHeader.size < SIMPLE_MESSAGE_SIZE) { + throw new DeserializationException( + "Incorrect message size, expecting at least " + SIMPLE_MESSAGE_SIZE + + ", but got: " + dataHeader.size); + } + if (dataHeader.elementsOrVersion == SIMPLE_MESSAGE_VERSION + && dataHeader.size != SIMPLE_MESSAGE_SIZE) { + throw new DeserializationException("Incorrect message size for a message with " + + SIMPLE_MESSAGE_VERSION + " fields, expecting " + SIMPLE_MESSAGE_SIZE + + ", but got: " + dataHeader.size); + } + if (dataHeader.elementsOrVersion == MESSAGE_WITH_REQUEST_ID_VERSION + && dataHeader.size != MESSAGE_WITH_REQUEST_ID_SIZE) { + throw new DeserializationException("Incorrect message size for a message with " + + MESSAGE_WITH_REQUEST_ID_VERSION + " fields, expecting " + + MESSAGE_WITH_REQUEST_ID_SIZE + ", but got: " + dataHeader.size); + } + } + +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java new file mode 100644 index 00000000000..42232979147 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java @@ -0,0 +1,25 @@ +// 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. + +package org.chromium.mojo.bindings; + +import java.io.Closeable; + +/** + * A class which implements this interface can receive {@link Message} objects. + */ +public interface MessageReceiver extends Closeable { + + /** + * Receive a {@link Message}. The {@link MessageReceiver} is allowed to mutate the message. + * Returns |true| if the message has been handled, |false| otherwise. + */ + boolean accept(Message message); + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close(); +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java new file mode 100644 index 00000000000..e24a5586c60 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java @@ -0,0 +1,21 @@ +// 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. + +package org.chromium.mojo.bindings; + +/** + * A {@link MessageReceiver} that can also handle the handle the response message generated from the + * given message. + */ +public interface MessageReceiverWithResponder extends MessageReceiver { + + /** + * A variant on {@link #accept(Message)} that registers a {@link MessageReceiver} + * (known as the responder) to handle the response message generated from the given message. The + * responder's {@link #accept(Message)} method may be called as part of the call to + * {@link #acceptWithResponder(Message, MessageReceiver)}, or some time after its + * return. + */ + boolean acceptWithResponder(Message message, MessageReceiver responder); +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java new file mode 100644 index 00000000000..ba19ae5ac4b --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java @@ -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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.MessagePipeHandle; + +/** + * A {@link Router} will handle mojo message and forward those to a {@link Connector}. It deals with + * parsing of headers and adding of request ids in order to be able to match a response to a + * request. + */ +public interface Router extends MessageReceiverWithResponder, HandleOwner<MessagePipeHandle> { + + /** + * Start listening for incoming messages. + */ + public void start(); + + /** + * Set the {@link MessageReceiverWithResponder} that will deserialize and use the message + * received from the pipe. + */ + public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver); + + /** + * Set the handle that will be notified of errors on the message pipe. + */ + public void setErrorHandler(ConnectionErrorHandler errorHandler); +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java new file mode 100644 index 00000000000..a278cc5ea85 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java @@ -0,0 +1,267 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.annotation.SuppressLint; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.Watcher; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Executor; + +/** + * Implementation of {@link Router}. + */ +@SuppressLint("UseSparseArrays") // https://crbug.com/600699 +public class RouterImpl implements Router { + + /** + * {@link MessageReceiver} used as the {@link Connector} callback. + */ + private class HandleIncomingMessageThunk implements MessageReceiver { + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + return handleIncomingMessage(message); + } + + /** + * @see MessageReceiver#close() + */ + @Override + public void close() { + handleConnectorClose(); + } + + } + + /** + * + * {@link MessageReceiver} used to return responses to the caller. + */ + class ResponderThunk implements MessageReceiver { + private boolean mAcceptWasInvoked; + + /** + * @see + * MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + mAcceptWasInvoked = true; + return RouterImpl.this.accept(message); + } + + /** + * @see MessageReceiver#close() + */ + @Override + public void close() { + RouterImpl.this.close(); + } + + @Override + protected void finalize() throws Throwable { + if (!mAcceptWasInvoked) { + // We close the pipe here as a way of signaling to the calling application that an + // error condition occurred. Without this the calling application would have no + // way of knowing it should stop waiting for a response. + RouterImpl.this.closeOnHandleThread(); + } + super.finalize(); + } + } + + /** + * The {@link Connector} which is connected to the handle. + */ + private final Connector mConnector; + + /** + * The {@link MessageReceiverWithResponder} that will consume the messages received from the + * pipe. + */ + private MessageReceiverWithResponder mIncomingMessageReceiver; + + /** + * The next id to use for a request id which needs a response. It is auto-incremented. + */ + private long mNextRequestId = 1; + + /** + * The map from request ids to {@link MessageReceiver} of request currently in flight. + */ + private Map<Long, MessageReceiver> mResponders = new HashMap<Long, MessageReceiver>(); + + /** + * An Executor that will run on the thread associated with the MessagePipe to which + * this Router is bound. This may be {@code Null} if the MessagePipeHandle passed + * in to the constructor is not valid. + */ + private final Executor mExecutor; + + /** + * Constructor that will use the default {@link Watcher}. + * + * @param messagePipeHandle The {@link MessagePipeHandle} to route message for. + */ + public RouterImpl(MessagePipeHandle messagePipeHandle) { + this(messagePipeHandle, BindingsHelper.getWatcherForHandle(messagePipeHandle)); + } + + /** + * Constructor. + * + * @param messagePipeHandle The {@link MessagePipeHandle} to route message for. + * @param watcher the {@link Watcher} to use to get notification of new messages on the + * handle. + */ + public RouterImpl(MessagePipeHandle messagePipeHandle, Watcher watcher) { + mConnector = new Connector(messagePipeHandle, watcher); + mConnector.setIncomingMessageReceiver(new HandleIncomingMessageThunk()); + Core core = messagePipeHandle.getCore(); + if (core != null) { + mExecutor = ExecutorFactory.getExecutorForCurrentThread(core); + } else { + mExecutor = null; + } + } + + /** + * @see org.chromium.mojo.bindings.Router#start() + */ + @Override + public void start() { + mConnector.start(); + } + + /** + * @see Router#setIncomingMessageReceiver(MessageReceiverWithResponder) + */ + @Override + public void setIncomingMessageReceiver(MessageReceiverWithResponder incomingMessageReceiver) { + this.mIncomingMessageReceiver = incomingMessageReceiver; + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + // A message without responder is directly forwarded to the connector. + return mConnector.accept(message); + } + + /** + * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver) + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + // The message must have a header. + ServiceMessage messageWithHeader = message.asServiceMessage(); + // Checking the message expects a response. + assert messageWithHeader.getHeader().hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG); + + // Compute a request id for being able to route the response. + long requestId = mNextRequestId++; + // Reserve 0 in case we want it to convey special meaning in the future. + if (requestId == 0) { + requestId = mNextRequestId++; + } + if (mResponders.containsKey(requestId)) { + throw new IllegalStateException("Unable to find a new request identifier."); + } + messageWithHeader.setRequestId(requestId); + if (!mConnector.accept(messageWithHeader)) { + return false; + } + // Only keep the responder is the message has been accepted. + mResponders.put(requestId, responder); + return true; + } + + /** + * @see org.chromium.mojo.bindings.HandleOwner#passHandle() + */ + @Override + public MessagePipeHandle passHandle() { + return mConnector.passHandle(); + } + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + mConnector.close(); + } + + /** + * @see Router#setErrorHandler(ConnectionErrorHandler) + */ + @Override + public void setErrorHandler(ConnectionErrorHandler errorHandler) { + mConnector.setErrorHandler(errorHandler); + } + + /** + * Receive a message from the connector. Returns |true| if the message has been handled. + */ + private boolean handleIncomingMessage(Message message) { + MessageHeader header = message.asServiceMessage().getHeader(); + if (header.hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)) { + if (mIncomingMessageReceiver != null) { + return mIncomingMessageReceiver.acceptWithResponder(message, new ResponderThunk()); + } + // If we receive a request expecting a response when the client is not + // listening, then we have no choice but to tear down the pipe. + close(); + return false; + } else if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) { + long requestId = header.getRequestId(); + MessageReceiver responder = mResponders.get(requestId); + if (responder == null) { + return false; + } + mResponders.remove(requestId); + return responder.accept(message); + } else { + if (mIncomingMessageReceiver != null) { + return mIncomingMessageReceiver.accept(message); + } + // OK to drop the message. + } + return false; + } + + private void handleConnectorClose() { + if (mIncomingMessageReceiver != null) { + mIncomingMessageReceiver.close(); + } + } + + /** + * Invokes {@link #close()} asynchronously on the thread associated with + * this Router's Handle. If this Router was constructed with an invalid + * handle then this method does nothing. + */ + private void closeOnHandleThread() { + if (mExecutor != null) { + mExecutor.execute(new Runnable() { + + @Override + public void run() { + close(); + } + }); + } + } +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java new file mode 100644 index 00000000000..d4f550227fe --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java @@ -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. + +package org.chromium.mojo.bindings; + +/** + * Error that can be thrown when serializing a mojo message. + */ +public class SerializationException extends RuntimeException { + + /** + * Constructs a new serialization exception with the specified detail message. + */ + public SerializationException(String message) { + super(message); + } + + /** + * Constructs a new serialization exception with the specified cause. + */ + public SerializationException(Exception cause) { + super(cause); + } + +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java new file mode 100644 index 00000000000..313dc6aea39 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java @@ -0,0 +1,73 @@ +// 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. + +package org.chromium.mojo.bindings; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Represents a {@link Message} which contains a {@link MessageHeader}. Deals with parsing the + * {@link MessageHeader} for a message. + */ +public class ServiceMessage extends Message { + + private final MessageHeader mHeader; + private Message mPayload; + + /** + * Reinterpret the given |message| as a message with the given |header|. The |message| must + * contain the |header| as the start of its raw data. + */ + public ServiceMessage(Message baseMessage, MessageHeader header) { + super(baseMessage.getData(), baseMessage.getHandles()); + assert header.equals(new org.chromium.mojo.bindings.MessageHeader(baseMessage)); + this.mHeader = header; + } + + /** + * Reinterpret the given |message| as a message with a header. The |message| must contain a + * header as the start of it's raw data, which will be parsed by this constructor. + */ + ServiceMessage(Message baseMessage) { + this(baseMessage, new org.chromium.mojo.bindings.MessageHeader(baseMessage)); + } + + /** + * @see Message#asServiceMessage() + */ + @Override + public ServiceMessage asServiceMessage() { + return this; + } + + /** + * Returns the header of the given message. This will throw a {@link DeserializationException} + * if the start of the message is not a valid header. + */ + public MessageHeader getHeader() { + return mHeader; + } + + /** + * Returns the payload of the message. + */ + public Message getPayload() { + if (mPayload == null) { + ByteBuffer truncatedBuffer = + ((ByteBuffer) getData().position(getHeader().getSize())).slice(); + truncatedBuffer.order(ByteOrder.LITTLE_ENDIAN); + mPayload = new Message(truncatedBuffer, getHandles()); + } + return mPayload; + } + + /** + * Set the request identifier on the message. + */ + void setRequestId(long requestId) { + mHeader.setRequestId(getData(), requestId); + } + +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java new file mode 100644 index 00000000000..118c991f7b0 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java @@ -0,0 +1,21 @@ +// 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. + +package org.chromium.mojo.bindings; + +import java.io.Closeable; + +/** + * An implementation of closeable that doesn't do anything. + */ +public class SideEffectFreeCloseable implements Closeable { + + /** + * @see java.io.Closeable#close() + */ + @Override + public void close() { + } + +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java new file mode 100644 index 00000000000..14f4e1e7261 --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java @@ -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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Core; + +import java.nio.ByteBuffer; + +/** + * Base class for all mojo structs. + */ +public abstract class Struct { + /** + * The base size of the encoded struct. + */ + private final int mEncodedBaseSize; + + /** + * The version of the struct. + */ + private final int mVersion; + + /** + * Constructor. + */ + protected Struct(int encodedBaseSize, int version) { + mEncodedBaseSize = encodedBaseSize; + mVersion = version; + } + + /** + * Returns the version of the struct. It is the max version of the struct in the mojom if it has + * been created locally, and the version of the received struct if it has been deserialized. + */ + public int getVersion() { + return mVersion; + } + + /** + * Returns the serialization of the struct. This method can close Handles. + * + * @param core the |Core| implementation used to generate handles. Only used if the data + * structure being encoded contains interfaces, can be |null| otherwise. + */ + public Message serialize(Core core) { + Encoder encoder = new Encoder(core, mEncodedBaseSize); + encode(encoder); + return encoder.getMessage(); + } + + /** + * Similar to the method above, but returns the serialization result as |ByteBuffer|. + * + * @throws UnsupportedOperationException if the struct contains interfaces or handles. + * @throws SerializationException on serialization failure. + */ + public ByteBuffer serialize() { + // If the struct contains interfaces which require a non-null |Core| instance, it will throw + // UnsupportedOperationException. + Message message = serialize(null); + + if (!message.getHandles().isEmpty()) + throw new UnsupportedOperationException("Handles are discarded."); + + return message.getData(); + } + + /** + * Returns the serialization of the struct prepended with the given header. + * + * @param header the header to prepend to the returned message. + * @param core the |Core| implementation used to generate handles. Only used if the |Struct| + * being encoded contains interfaces, can be |null| otherwise. + */ + public ServiceMessage serializeWithHeader(Core core, MessageHeader header) { + Encoder encoder = new Encoder(core, mEncodedBaseSize + header.getSize()); + header.encode(encoder); + encode(encoder); + return new ServiceMessage(encoder.getMessage(), header); + } + + /** + * Use the given encoder to serialize this data structure. + */ + protected abstract void encode(Encoder encoder); +} diff --git a/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java new file mode 100644 index 00000000000..8712e19846c --- /dev/null +++ b/chromium/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java @@ -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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.system.Core; + +/** + * Base class for all mojo unions. + */ +public abstract class Union { + /** They type of object that has been set. */ + protected int mTag; + + /** Returns the type of object being held by this union. */ + public int which() { + return mTag; + } + + /** Returns whether the type of object in this union is known. */ + public boolean isUnknown() { + return mTag == -1; + } + + /** + * Returns the serialization of the union. This method can close Handles. + * + * @param core the |Core| implementation used to generate handles. Only used if the data + * structure being encoded contains interfaces, can be |null| otherwise. + */ + public Message serialize(Core core) { + Encoder encoder = new Encoder(core, BindingsHelper.UNION_SIZE); + encoder.claimMemory(16); + encode(encoder, 0); + return encoder.getMessage(); + } + + /** + * Serializes this data structure using the given encoder. + */ + protected abstract void encode(Encoder encoder, int offset); +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/HandleMock.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/HandleMock.java new file mode 100644 index 00000000000..715e1e54b43 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/HandleMock.java @@ -0,0 +1,220 @@ +// 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. + +package org.chromium.mojo; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * A mock handle, that does nothing. + */ +public class HandleMock implements UntypedHandle, MessagePipeHandle, ProducerHandle, ConsumerHandle, + SharedBufferHandle { + /** + * @see Handle#close() + */ + @Override + public void close() { + // Do nothing. + } + + /** + * @see Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + return null; + } + + /** + * @see Handle#isValid() + */ + @Override + public boolean isValid() { + return true; + } + + /** + * @see Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return this; + } + + /** + * @see org.chromium.mojo.system.Handle#getCore() + */ + @Override + public Core getCore() { + return CoreImpl.getInstance(); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#pass() + */ + @Override + public HandleMock pass() { + return this; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + return 0; + } + + /** + * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + @Override + public int discardData(int numBytes, DataPipe.ReadFlags flags) { + // Do nothing. + return 0; + } + + /** + * @see ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags) + */ + @Override + public ResultAnd<Integer> readData(ByteBuffer elements, DataPipe.ReadFlags flags) { + // Do nothing. + return new ResultAnd<Integer>(MojoResult.OK, 0); + } + + /** + * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, DataPipe.ReadFlags flags) { + // Do nothing. + return null; + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + // Do nothing. + } + + /** + * @see ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags) + */ + @Override + public ResultAnd<Integer> writeData(ByteBuffer elements, DataPipe.WriteFlags flags) { + // Do nothing. + return new ResultAnd<Integer>(MojoResult.OK, 0); + } + + /** + * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, DataPipe.WriteFlags flags) { + // Do nothing. + return null; + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + // Do nothing. + } + + /** + * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List, + * MessagePipeHandle.WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags) { + // Do nothing. + } + + /** + * @see MessagePipeHandle#readMessage(MessagePipeHandle.ReadFlags) + */ + @Override + public ResultAnd<ReadMessageResult> readMessage(ReadFlags flags) { + // Do nothing. + return new ResultAnd<ReadMessageResult>(MojoResult.OK, new ReadMessageResult()); + } + + /** + * @see UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return this; + } + + /** + * @see UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return this; + } + + /** + * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + // Do nothing. + return null; + } + + /** + * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + // Do nothing. + return null; + } + + /** + * @see SharedBufferHandle#unmap(java.nio.ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + // Do nothing. + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/MojoTestRule.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/MojoTestRule.java new file mode 100644 index 00000000000..68a6e6a9a08 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/MojoTestRule.java @@ -0,0 +1,81 @@ +// 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. + +package org.chromium.mojo; + +import android.support.annotation.IntDef; + +import org.junit.rules.ExternalResource; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.LibraryProcessType; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Base class to test mojo. Setup the environment. + */ +@JNINamespace("mojo::android") +public class MojoTestRule extends ExternalResource { + @IntDef({MojoCore.SKIP_INITIALIZATION, MojoCore.INITIALIZE}) + @Retention(RetentionPolicy.SOURCE) + public @interface MojoCore { + int SKIP_INITIALIZATION = 0; + int INITIALIZE = 1; + } + + private static boolean sIsCoreInitialized; + private final boolean mShouldInitCore; + private long mTestEnvironmentPointer; + + public MojoTestRule() { + this(MojoCore.SKIP_INITIALIZATION); + } + + public MojoTestRule(@MojoCore int shouldInitMojoCore) { + mShouldInitCore = shouldInitMojoCore == MojoCore.INITIALIZE; + } + + @Override + protected void before() throws Throwable { + LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); + if (mShouldInitCore && !sIsCoreInitialized) { + nativeInitCore(); + sIsCoreInitialized = true; + } + nativeInit(); + mTestEnvironmentPointer = nativeSetupTestEnvironment(); + } + + @Override + protected void after() { + nativeTearDownTestEnvironment(mTestEnvironmentPointer); + } + + /** + * Runs the run loop for the given time. + */ + public void runLoop(long timeoutMS) { + nativeRunLoop(timeoutMS); + } + + /** + * Runs the run loop until no handle or task are immediately available. + */ + public void runLoopUntilIdle() { + nativeRunLoop(0); + } + + private static native void nativeInitCore(); + + private native void nativeInit(); + + private native long nativeSetupTestEnvironment(); + + private native void nativeTearDownTestEnvironment(long testEnvironment); + + private native void nativeRunLoop(long timeoutMS); +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/TestUtils.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/TestUtils.java new file mode 100644 index 00000000000..3eaf4fa0b74 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/TestUtils.java @@ -0,0 +1,30 @@ +// 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. + +package org.chromium.mojo; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Random; + +/** + * Utilities methods for tests. + */ +public final class TestUtils { + private static final Random RANDOM = new Random(); + + /** + * Returns a new direct ByteBuffer of the given size with random (but reproducible) data. + */ + public static ByteBuffer newRandomBuffer(int size) { + byte bytes[] = new byte[size]; + RANDOM.setSeed(size); + RANDOM.nextBytes(bytes); + ByteBuffer data = ByteBuffer.allocateDirect(size); + data.order(ByteOrder.LITTLE_ENDIAN); + data.put(bytes); + data.flip(); + return data; + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java new file mode 100644 index 00000000000..871d1e07b66 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java @@ -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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; + +import java.nio.charset.Charset; + +/** + * Testing {@link BindingsHelper}. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class BindingsHelperTest { + /** + * Testing {@link BindingsHelper#utf8StringSizeInBytes(String)}. + */ + @Test + @SmallTest + public void testUTF8StringLength() { + String[] stringsToTest = { + "", "a", "hello world", "éléphant", "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕", "你午饭想吃什么", + "你午饭想吃什么\0éléphant", + }; + for (String s : stringsToTest) { + Assert.assertEquals(s.getBytes(Charset.forName("utf8")).length, + BindingsHelper.utf8StringSizeInBytes(s)); + } + Assert.assertEquals(1, BindingsHelper.utf8StringSizeInBytes("\0")); + String s = new StringBuilder() + .appendCodePoint(0x0) + .appendCodePoint(0x80) + .appendCodePoint(0x800) + .appendCodePoint(0x10000) + .toString(); + Assert.assertEquals(10, BindingsHelper.utf8StringSizeInBytes(s)); + Assert.assertEquals(10, s.getBytes(Charset.forName("utf8")).length); + } + + /** + * Testing {@link BindingsHelper#align(int)}. + */ + @Test + @SmallTest + public void testAlign() { + for (int i = 0; i < 3 * BindingsHelper.ALIGNMENT; ++i) { + int j = BindingsHelper.align(i); + Assert.assertTrue(j >= i); + Assert.assertTrue(j % BindingsHelper.ALIGNMENT == 0); + Assert.assertTrue(j - i < BindingsHelper.ALIGNMENT); + } + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTest.java new file mode 100644 index 00000000000..9c9d96c67c9 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTest.java @@ -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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.bindings.test.mojom.imported.Color; +import org.chromium.mojo.bindings.test.mojom.imported.Point; +import org.chromium.mojo.bindings.test.mojom.imported.Shape; +import org.chromium.mojo.bindings.test.mojom.imported.Thing; +import org.chromium.mojo.bindings.test.mojom.sample.Bar; +import org.chromium.mojo.bindings.test.mojom.sample.Bar.Type; +import org.chromium.mojo.bindings.test.mojom.sample.DefaultsTest; +import org.chromium.mojo.bindings.test.mojom.sample.Enum; +import org.chromium.mojo.bindings.test.mojom.sample.Foo; +import org.chromium.mojo.bindings.test.mojom.sample.InterfaceConstants; +import org.chromium.mojo.bindings.test.mojom.sample.SampleServiceConstants; +import org.chromium.mojo.bindings.test.mojom.test_structs.EmptyStruct; +import org.chromium.mojo.bindings.test.mojom.test_structs.Rect; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.MessagePipeHandle; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * Testing generated classes and associated features. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class BindingsTest { + /** + * Create a new typical Bar instance. + */ + private static Bar newBar() { + Bar bar = new Bar(); + bar.alpha = (byte) 0x01; + bar.beta = (byte) 0x02; + bar.gamma = (byte) 0x03; + bar.type = Type.BOTH; + return bar; + } + + /** + * Create a new typical Foo instance. + */ + private static Foo createFoo() { + Foo foo = new Foo(); + foo.name = "HELLO WORLD"; + foo.arrayOfArrayOfBools = new boolean[][] {{true, false, true}, {}, {}, {false}, {true}}; + foo.bar = newBar(); + foo.a = true; + foo.c = true; + foo.data = new byte[] {0x01, 0x02, 0x03}; + foo.extraBars = new Bar[] {newBar(), newBar()}; + String[][][] strings = new String[3][2][1]; + for (int i0 = 0; i0 < strings.length; ++i0) { + for (int i1 = 0; i1 < strings[i0].length; ++i1) { + for (int i2 = 0; i2 < strings[i0][i1].length; ++i2) { + strings[i0][i1][i2] = "Hello(" + i0 + ", " + i1 + ", " + i2 + ")"; + } + } + } + foo.multiArrayOfStrings = strings; + ConsumerHandle[] inputStreams = new ConsumerHandle[5]; + for (int i = 0; i < inputStreams.length; ++i) { + inputStreams[i] = new HandleMock(); + } + foo.inputStreams = inputStreams; + ProducerHandle[] outputStreams = new ProducerHandle[3]; + for (int i = 0; i < outputStreams.length; ++i) { + outputStreams[i] = new HandleMock(); + } + foo.outputStreams = outputStreams; + foo.source = new HandleMock(); + return foo; + } + + private static Rect createRect(int x, int y, int width, int height) { + Rect rect = new Rect(); + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; + return rect; + } + + private static <T> void checkConstantField(Field field, Class<T> expectedClass, T value) + throws IllegalAccessException { + Assert.assertEquals(expectedClass, field.getType()); + Assert.assertEquals(Modifier.FINAL, field.getModifiers() & Modifier.FINAL); + Assert.assertEquals(Modifier.STATIC, field.getModifiers() & Modifier.STATIC); + Assert.assertEquals(value, field.get(null)); + } + + private static <T> void checkField(Field field, Class<T> expectedClass, Object object, T value) + throws IllegalArgumentException, IllegalAccessException { + Assert.assertEquals(expectedClass, field.getType()); + Assert.assertEquals(0, field.getModifiers() & Modifier.FINAL); + Assert.assertEquals(0, field.getModifiers() & Modifier.STATIC); + Assert.assertEquals(value, field.get(object)); + } + + /** + * Testing constants are correctly generated. + */ + @Test + @SmallTest + public void testConstants() + throws NoSuchFieldException, SecurityException, IllegalAccessException { + checkConstantField(SampleServiceConstants.class.getField("TWELVE"), byte.class, (byte) 12); + checkConstantField(InterfaceConstants.class.getField("LONG"), long.class, 4405L); + } + + /** + * Testing enums are correctly generated. + */ + @Test + @SmallTest + public void testEnums() throws NoSuchFieldException, SecurityException, IllegalAccessException { + checkConstantField(Color.class.getField("RED"), int.class, 0); + checkConstantField(Color.class.getField("BLACK"), int.class, 1); + + checkConstantField(Enum.class.getField("VALUE"), int.class, 0); + + checkConstantField(Shape.class.getField("RECTANGLE"), int.class, 1); + checkConstantField(Shape.class.getField("CIRCLE"), int.class, 2); + checkConstantField(Shape.class.getField("TRIANGLE"), int.class, 3); + } + + /** + * Testing default values on structs. + * + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + @Test + @SmallTest + public void testStructDefaults() throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + // Check default values. + DefaultsTest test = new DefaultsTest(); + + checkField(DefaultsTest.class.getField("a0"), byte.class, test, (byte) -12); + checkField(DefaultsTest.class.getField("a1"), byte.class, test, (byte) 12); + checkField(DefaultsTest.class.getField("a2"), short.class, test, (short) 1234); + checkField(DefaultsTest.class.getField("a3"), short.class, test, (short) 34567); + checkField(DefaultsTest.class.getField("a4"), int.class, test, 123456); + checkField(DefaultsTest.class.getField("a5"), int.class, test, (int) 3456789012L); + checkField(DefaultsTest.class.getField("a6"), long.class, test, -111111111111L); + // -8446744073709551617 == 9999999999999999999 - 2 ^ 64. + checkField(DefaultsTest.class.getField("a7"), long.class, test, -8446744073709551617L); + checkField(DefaultsTest.class.getField("a8"), int.class, test, 0x12345); + checkField(DefaultsTest.class.getField("a9"), int.class, test, -0x12345); + checkField(DefaultsTest.class.getField("a10"), int.class, test, 1234); + checkField(DefaultsTest.class.getField("a11"), boolean.class, test, true); + checkField(DefaultsTest.class.getField("a12"), boolean.class, test, false); + checkField(DefaultsTest.class.getField("a13"), float.class, test, (float) 123.25); + checkField(DefaultsTest.class.getField("a14"), double.class, test, 1234567890.123); + checkField(DefaultsTest.class.getField("a15"), double.class, test, 1E10); + checkField(DefaultsTest.class.getField("a16"), double.class, test, -1.2E+20); + checkField(DefaultsTest.class.getField("a17"), double.class, test, +1.23E-20); + checkField(DefaultsTest.class.getField("a18"), byte[].class, test, null); + checkField(DefaultsTest.class.getField("a19"), String.class, test, null); + checkField(DefaultsTest.class.getField("a20"), int.class, test, Bar.Type.BOTH); + checkField(DefaultsTest.class.getField("a21"), Point.class, test, null); + + Assert.assertNotNull(test.a22); + checkField(DefaultsTest.class.getField("a22"), Thing.class, test, test.a22); + checkField(DefaultsTest.class.getField("a23"), long.class, test, -1L); + checkField(DefaultsTest.class.getField("a24"), long.class, test, 0x123456789L); + checkField(DefaultsTest.class.getField("a25"), long.class, test, -0x123456789L); + } + + /** + * Testing generation of the Foo class. + * + * @throws IllegalAccessException + */ + @Test + @SmallTest + public void testFooGeneration() + throws NoSuchFieldException, SecurityException, IllegalAccessException { + // Checking Foo constants. + checkConstantField(Foo.class.getField("FOOBY"), String.class, "Fooby"); + + // Checking Foo default values. + Foo foo = new Foo(); + checkField(Foo.class.getField("name"), String.class, foo, Foo.FOOBY); + + Assert.assertNotNull(foo.source); + Assert.assertFalse(foo.source.isValid()); + checkField(Foo.class.getField("source"), MessagePipeHandle.class, foo, foo.source); + } + + /** + * Testing serialization of the Foo class. + */ + @Test + @SmallTest + public void testFooSerialization() { + // Checking serialization and deserialization of a Foo object. + Foo typicalFoo = createFoo(); + Message serializedFoo = typicalFoo.serialize(null); + Foo deserializedFoo = Foo.deserialize(serializedFoo); + Assert.assertTrue(BindingsTestUtils.structsEqual(typicalFoo, deserializedFoo)); + } + + /** + * Testing serialization of the EmptyStruct class. + */ + @Test + @SmallTest + public void testEmptyStructSerialization() { + // Checking serialization and deserialization of a EmptyStruct object. + Message serializedStruct = new EmptyStruct().serialize(null); + EmptyStruct emptyStruct = EmptyStruct.deserialize(serializedStruct); + Assert.assertNotNull(emptyStruct); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java new file mode 100644 index 00000000000..8efd2ad3b44 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java @@ -0,0 +1,109 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.TestUtils; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for bindings tests. + */ +public class BindingsTestUtils { + /** + * {@link MessageReceiver} that records any message it receives. + */ + public static class RecordingMessageReceiver + extends SideEffectFreeCloseable implements MessageReceiver { + public final List<Message> messages = new ArrayList<Message>(); + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + messages.add(message); + return true; + } + } + + /** + * {@link MessageReceiverWithResponder} that records any message it receives. + */ + public static class RecordingMessageReceiverWithResponder + extends RecordingMessageReceiver implements MessageReceiverWithResponder { + public final List<Pair<Message, MessageReceiver>> messagesWithReceivers = + new ArrayList<Pair<Message, MessageReceiver>>(); + + /** + * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver) + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + messagesWithReceivers.add(Pair.create(message, responder)); + return true; + } + } + + /** + * {@link ConnectionErrorHandler} that records any error it received. + */ + public static class CapturingErrorHandler implements ConnectionErrorHandler { + private MojoException mLastMojoException; + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mLastMojoException = e; + } + + /** + * Returns the last recorded exception. + */ + public MojoException getLastMojoException() { + return mLastMojoException; + } + } + + /** + * Creates a new valid {@link Message}. The message will have a valid header. + */ + public static Message newRandomMessage(int size) { + assert size > 16; + ByteBuffer message = TestUtils.newRandomBuffer(size); + int[] headerAsInts = {16, 2, 0, 0}; + for (int i = 0; i < 4; ++i) { + message.putInt(4 * i, headerAsInts[i]); + } + message.position(0); + return new Message(message, new ArrayList<Handle>()); + } + + public static <I extends Interface, P extends Interface.Proxy> P newProxyOverPipe( + Interface.Manager<I, P> manager, I impl, List<Closeable> toClose) { + Pair<MessagePipeHandle, MessagePipeHandle> handles = + CoreImpl.getInstance().createMessagePipe(null); + P proxy = manager.attachProxy(handles.first, 0); + toClose.add(proxy); + manager.bind(impl, handles.second); + return proxy; + } + + public static boolean structsEqual(Struct s1, Struct s2) { + if (s1 == s2) return true; + if (s1 == null || s2 == null) return false; + return s1.serialize(null).getData().equals(s2.serialize(null).getData()); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java new file mode 100644 index 00000000000..734f9a00f4a --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java @@ -0,0 +1,223 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStruct; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV0; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV1; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV3; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV5; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV7; +import org.chromium.mojo.bindings.test.mojom.test_structs.Rect; +import org.chromium.mojo.system.impl.CoreImpl; + +/** + * Testing generated classes with the [MinVersion] annotation. Struct in this test are from: + * mojo/public/interfaces/bindings/tests/rect.mojom and + * mojo/public/interfaces/bindings/tests/test_structs.mojom + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class BindingsVersioningTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private static Rect newRect(int factor) { + Rect rect = new Rect(); + rect.x = factor; + rect.y = 2 * factor; + rect.width = 10 * factor; + rect.height = 20 * factor; + return rect; + } + + private static MultiVersionStruct newStruct() { + MultiVersionStruct struct = new MultiVersionStruct(); + struct.fInt32 = 123; + struct.fRect = newRect(5); + struct.fString = "hello"; + struct.fArray = new byte[] {10, 9, 8}; + struct.fBool = true; + struct.fInt16 = 256; + return struct; + } + + /** + * Testing serializing old struct version to newer one. + */ + @Test + @SmallTest + public void testOldToNew() { + { + MultiVersionStructV0 v0 = new MultiVersionStructV0(); + v0.fInt32 = 123; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v0.serialize(null)); + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(0, v0.getVersion()); + Assert.assertEquals(0, output.getVersion()); + } + + { + MultiVersionStructV1 v1 = new MultiVersionStructV1(); + v1.fInt32 = 123; + v1.fRect = newRect(5); + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + + MultiVersionStruct output = MultiVersionStruct.deserialize(v1.serialize(null)); + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(1, v1.getVersion()); + Assert.assertEquals(1, output.getVersion()); + } + + { + MultiVersionStructV3 v3 = new MultiVersionStructV3(); + v3.fInt32 = 123; + v3.fRect = newRect(5); + v3.fString = "hello"; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v3.serialize(null)); + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(3, v3.getVersion()); + Assert.assertEquals(3, output.getVersion()); + } + + { + MultiVersionStructV5 v5 = new MultiVersionStructV5(); + v5.fInt32 = 123; + v5.fRect = newRect(5); + v5.fString = "hello"; + v5.fArray = new byte[] {10, 9, 8}; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v5.serialize(null)); + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(5, v5.getVersion()); + Assert.assertEquals(5, output.getVersion()); + } + + { + int expectedHandle = 42; + MultiVersionStructV7 v7 = new MultiVersionStructV7(); + v7.fInt32 = 123; + v7.fRect = newRect(5); + v7.fString = "hello"; + v7.fArray = new byte[] {10, 9, 8}; + v7.fMessagePipe = CoreImpl.getInstance() + .acquireNativeHandle(expectedHandle) + .toMessagePipeHandle(); + v7.fBool = true; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + expected.fBool = true; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v7.serialize(null)); + + // Handles must be tested separately. + Assert.assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle()); + output.fMessagePipe = expected.fMessagePipe; + + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(7, v7.getVersion()); + Assert.assertEquals(7, output.getVersion()); + } + } + + /** + * Testing serializing new struct version to older one. + */ + @Test + @SmallTest + public void testNewToOld() { + MultiVersionStruct struct = newStruct(); + { + MultiVersionStructV0 expected = new MultiVersionStructV0(); + expected.fInt32 = 123; + + MultiVersionStructV0 output = MultiVersionStructV0.deserialize(struct.serialize(null)); + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV1 expected = new MultiVersionStructV1(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + + MultiVersionStructV1 output = MultiVersionStructV1.deserialize(struct.serialize(null)); + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV3 expected = new MultiVersionStructV3(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + + MultiVersionStructV3 output = MultiVersionStructV3.deserialize(struct.serialize(null)); + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV5 expected = new MultiVersionStructV5(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + + MultiVersionStructV5 output = MultiVersionStructV5.deserialize(struct.serialize(null)); + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(9, output.getVersion()); + } + + { + int expectedHandle = 42; + MultiVersionStructV7 expected = new MultiVersionStructV7(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + expected.fBool = true; + + MultiVersionStruct input = struct; + input.fMessagePipe = CoreImpl.getInstance() + .acquireNativeHandle(expectedHandle) + .toMessagePipeHandle(); + + MultiVersionStructV7 output = MultiVersionStructV7.deserialize(input.serialize(null)); + + Assert.assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle()); + output.fMessagePipe = expected.fMessagePipe; + + Assert.assertTrue(BindingsTestUtils.structsEqual(expected, output)); + Assert.assertEquals(9, output.getVersion()); + } + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java new file mode 100644 index 00000000000..8379826b9e5 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java @@ -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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.Callbacks.Callback7; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Testing generated callbacks + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class CallbacksTest { + /** + * Testing {@link Callback1}. + */ + @Test + @SmallTest + public void testCallback1() { + final List<Integer> parameters = new ArrayList<Integer>(); + new Callback1<Integer>() { + @Override + public void call(Integer i1) { + parameters.add(i1); + } + } + .call(1); + Assert.assertEquals(Arrays.asList(1), parameters); + } + + /** + * Testing {@link Callback7}. + */ + @Test + @SmallTest + public void testCallback7() { + final List<Integer> parameters = new ArrayList<Integer>(); + new Callback7<Integer, Integer, Integer, Integer, Integer, Integer, Integer>() { + @Override + public void call(Integer i1, Integer i2, Integer i3, Integer i4, Integer i5, Integer i6, + Integer i7) { + parameters.add(i1); + parameters.add(i2); + parameters.add(i3); + parameters.add(i4); + parameters.add(i5); + parameters.add(i6); + parameters.add(i7); + } + } + .call(1, 2, 3, 4, 5, 6, 7); + Assert.assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), parameters); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java new file mode 100644 index 00000000000..ad38d7c0c8a --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java @@ -0,0 +1,119 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Testing the {@link Connector} class. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class ConnectorTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private static final int DATA_LENGTH = 1024; + + private MessagePipeHandle mHandle; + private Connector mConnector; + private Message mTestMessage; + private RecordingMessageReceiver mReceiver; + private CapturingErrorHandler mErrorHandler; + + /** + * @see MojoTestCase#setUp() + */ + @Before + public void setUp() throws Exception { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = + core.createMessagePipe(new MessagePipeHandle.CreateOptions()); + mHandle = handles.first; + mConnector = new Connector(handles.second); + mReceiver = new RecordingMessageReceiver(); + mConnector.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mConnector.setErrorHandler(mErrorHandler); + mConnector.start(); + mTestMessage = BindingsTestUtils.newRandomMessage(DATA_LENGTH); + Assert.assertNull(mErrorHandler.getLastMojoException()); + Assert.assertEquals(0, mReceiver.messages.size()); + } + + /** + * @see MojoTestCase#tearDown() + */ + @After + public void tearDown() throws Exception { + mConnector.close(); + mHandle.close(); + } + + /** + * Test sending a message through a {@link Connector}. + */ + @Test + @SmallTest + public void testSendingMessage() { + mConnector.accept(mTestMessage); + Assert.assertNull(mErrorHandler.getLastMojoException()); + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + mHandle.readMessage(MessagePipeHandle.ReadFlags.NONE); + Assert.assertEquals(MojoResult.OK, result.getMojoResult()); + Assert.assertEquals(DATA_LENGTH, result.getValue().mData.length); + Assert.assertEquals(mTestMessage.getData(), ByteBuffer.wrap(result.getValue().mData)); + } + + /** + * Test receiving a message through a {@link Connector} + */ + @Test + @SmallTest + public void testReceivingMessage() { + mHandle.writeMessage( + mTestMessage.getData(), new ArrayList<Handle>(), MessagePipeHandle.WriteFlags.NONE); + mTestRule.runLoopUntilIdle(); + Assert.assertNull(mErrorHandler.getLastMojoException()); + Assert.assertEquals(1, mReceiver.messages.size()); + Message received = mReceiver.messages.get(0); + Assert.assertEquals(0, received.getHandles().size()); + Assert.assertEquals(mTestMessage.getData(), received.getData()); + } + + /** + * Test receiving an error through a {@link Connector}. + */ + @Test + @SmallTest + public void testErrors() { + mHandle.close(); + mTestRule.runLoopUntilIdle(); + Assert.assertNotNull(mErrorHandler.getLastMojoException()); + Assert.assertEquals(MojoResult.FAILED_PRECONDITION, + mErrorHandler.getLastMojoException().getMojoResult()); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java new file mode 100644 index 00000000000..b92370fb3d2 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java @@ -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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Testing the executor factory. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class ExecutorFactoryTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private static final long RUN_LOOP_TIMEOUT_MS = 50; + private static final int CONCURRENCY_LEVEL = 5; + private static final ExecutorService WORKERS = Executors.newFixedThreadPool(CONCURRENCY_LEVEL); + + private Executor mExecutor; + private List<Thread> mThreadContainer; + + /** + * @see MojoTestCase#setUp() + */ + @Before + public void setUp() throws Exception { + mExecutor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance()); + mThreadContainer = new ArrayList<Thread>(); + } + + /** + * Testing the {@link Executor} when called from the executor thread. + */ + @Test + @SmallTest + public void testExecutorOnCurrentThread() { + Runnable action = new Runnable() { + @Override + public void run() { + mThreadContainer.add(Thread.currentThread()); + } + }; + mExecutor.execute(action); + mExecutor.execute(action); + Assert.assertEquals(0, mThreadContainer.size()); + mTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); + Assert.assertEquals(2, mThreadContainer.size()); + for (Thread thread : mThreadContainer) { + Assert.assertEquals(Thread.currentThread(), thread); + } + } + + /** + * Testing the {@link Executor} when called from another thread. + */ + @Test + @SmallTest + public void testExecutorOnOtherThread() { + final CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY_LEVEL + 1); + for (int i = 0; i < CONCURRENCY_LEVEL; ++i) { + WORKERS.execute(new Runnable() { + @Override + public void run() { + mExecutor.execute(new Runnable() { + + @Override + public void run() { + mThreadContainer.add(Thread.currentThread()); + } + }); + try { + barrier.await(); + } catch (InterruptedException e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } catch (BrokenBarrierException e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } + } + }); + } + try { + barrier.await(); + } catch (InterruptedException e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } catch (BrokenBarrierException e) { + Assert.fail("Unexpected exception: " + e.getMessage()); + } + Assert.assertEquals(0, mThreadContainer.size()); + mTestRule.runLoop(RUN_LOOP_TIMEOUT_MS); + Assert.assertEquals(CONCURRENCY_LEVEL, mThreadContainer.size()); + for (Thread thread : mThreadContainer) { + Assert.assertEquals(Thread.currentThread(), thread); + } + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java new file mode 100644 index 00000000000..96b60753fbf --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java @@ -0,0 +1,140 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.test.mojom.sample.IntegerAccessor; +import org.chromium.mojo.system.MojoException; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for interface control messages. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class InterfaceControlMessageTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private final List<Closeable> mCloseablesToClose = new ArrayList<Closeable>(); + + /** + * See mojo/public/interfaces/bindings/tests/sample_interfaces.mojom. + */ + class IntegerAccessorImpl extends SideEffectFreeCloseable implements IntegerAccessor { + private long mValue; + private int mEnum; + private boolean mEncounteredError; + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mEncounteredError = true; + } + + /** + * @see IntegerAccessor#getInteger(IntegerAccessor.GetIntegerResponse) + */ + @Override + public void getInteger(GetIntegerResponse response) { + response.call(mValue, mEnum); + } + + /** + * @see IntegerAccessor#setInteger(long, int) + */ + @Override + public void setInteger(long value, int enumValue) { + mValue = value; + mEnum = enumValue; + } + + public long getValue() { + return mValue; + } + + public boolean encounteredError() { + return mEncounteredError; + } + } + + /** + * @see MojoTestCase#tearDown() + */ + @After + public void tearDown() throws Exception { + // Close the elements in the reverse order they were added. This is needed because it is an + // error to close the handle of a proxy without closing the proxy first. + Collections.reverse(mCloseablesToClose); + for (Closeable c : mCloseablesToClose) { + c.close(); + } + } + + @Test + @SmallTest + public void testQueryVersion() { + IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe( + IntegerAccessor.MANAGER, new IntegerAccessorImpl(), mCloseablesToClose); + Assert.assertEquals(0, p.getProxyHandler().getVersion()); + p.getProxyHandler().queryVersion(new Callback1<Integer>() { + @Override + public void call(Integer version) { + Assert.assertEquals(3, version.intValue()); + } + }); + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(3, p.getProxyHandler().getVersion()); + } + + @Test + @SmallTest + public void testRequireVersion() { + IntegerAccessorImpl impl = new IntegerAccessorImpl(); + IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe( + IntegerAccessor.MANAGER, impl, mCloseablesToClose); + + Assert.assertEquals(0, p.getProxyHandler().getVersion()); + + p.getProxyHandler().requireVersion(1); + Assert.assertEquals(1, p.getProxyHandler().getVersion()); + p.setInteger(123, Enum.VALUE); + mTestRule.runLoopUntilIdle(); + Assert.assertFalse(impl.encounteredError()); + Assert.assertEquals(123, impl.getValue()); + + p.getProxyHandler().requireVersion(3); + Assert.assertEquals(3, p.getProxyHandler().getVersion()); + p.setInteger(456, Enum.VALUE); + mTestRule.runLoopUntilIdle(); + Assert.assertFalse(impl.encounteredError()); + Assert.assertEquals(456, impl.getValue()); + + // Require a version that is not supported by the implementation side. + p.getProxyHandler().requireVersion(4); + // getVersion() is updated synchronously. + Assert.assertEquals(4, p.getProxyHandler().getVersion()); + p.setInteger(789, Enum.VALUE); + mTestRule.runLoopUntilIdle(); + Assert.assertTrue(impl.encounteredError()); + // The call to setInteger() after requireVersion() is ignored. + Assert.assertEquals(456, impl.getValue()); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java new file mode 100644 index 00000000000..468b1f496a3 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java @@ -0,0 +1,296 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.test.mojom.imported.ImportedInterface; +import org.chromium.mojo.bindings.test.mojom.sample.Factory; +import org.chromium.mojo.bindings.test.mojom.sample.NamedObject; +import org.chromium.mojo.bindings.test.mojom.sample.NamedObject.GetNameResponse; +import org.chromium.mojo.bindings.test.mojom.sample.Request; +import org.chromium.mojo.bindings.test.mojom.sample.Response; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for interfaces / proxies / stubs generated for sample_factory.mojom. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class InterfacesTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private static final String OBJECT_NAME = "hello world"; + + private final List<Closeable> mCloseablesToClose = new ArrayList<Closeable>(); + + /** + * Basic implementation of {@link NamedObject}. + */ + public static class MockNamedObjectImpl extends CapturingErrorHandler implements NamedObject { + private String mName = ""; + + /** + * @see org.chromium.mojo.bindings.Interface#close() + */ + @Override + public void close() {} + + @Override + public void setName(String name) { + mName = name; + } + + @Override + public void getName(GetNameResponse callback) { + callback.call(mName); + } + + public String getNameSynchronously() { + return mName; + } + } + + /** + * Implementation of {@link GetNameResponse} keeping track of usage. + */ + public static class RecordingGetNameResponse implements GetNameResponse { + private String mName; + private boolean mCalled; + + public RecordingGetNameResponse() { + reset(); + } + + @Override + public void call(String name) { + mName = name; + mCalled = true; + } + + public String getName() { + return mName; + } + + public boolean wasCalled() { + return mCalled; + } + + public void reset() { + mName = null; + mCalled = false; + } + } + + /** + * Basic implementation of {@link Factory}. + */ + public class MockFactoryImpl extends CapturingErrorHandler implements Factory { + private boolean mClosed; + + public boolean isClosed() { + return mClosed; + } + + /** + * @see org.chromium.mojo.bindings.Interface#close() + */ + @Override + public void close() { + mClosed = true; + } + + @Override + public void doStuff(Request request, MessagePipeHandle pipe, DoStuffResponse callback) { + if (pipe != null) { + pipe.close(); + } + Response response = new Response(); + response.x = 42; + callback.call(response, "Hello"); + } + + @Override + public void doStuff2(ConsumerHandle pipe, DoStuff2Response callback) { + callback.call("World"); + } + + @Override + public void createNamedObject(InterfaceRequest<NamedObject> obj) { + NamedObject.MANAGER.bind(new MockNamedObjectImpl(), obj); + } + + @Override + public void requestImportedInterface(InterfaceRequest<ImportedInterface> obj, + RequestImportedInterfaceResponse callback) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override + public void takeImportedInterface( + ImportedInterface obj, TakeImportedInterfaceResponse callback) { + throw new UnsupportedOperationException("Not implemented."); + } + } + + /** + * Implementation of DoStuffResponse that keeps track of if the response is called. + */ + public static class DoStuffResponseImpl implements Factory.DoStuffResponse { + private boolean mResponseCalled; + + public boolean wasResponseCalled() { + return mResponseCalled; + } + + @Override + public void call(Response response, String string) { + mResponseCalled = true; + } + } + + /** + * @see MojoTestCase#tearDown() + */ + @After + public void tearDown() throws Exception { + // Close the elements in the reverse order they were added. This is needed because it is an + // error to close the handle of a proxy without closing the proxy first. + Collections.reverse(mCloseablesToClose); + for (Closeable c : mCloseablesToClose) { + c.close(); + } + } + + /** + * Check that the given proxy receives the calls. If |impl| is not null, also check that the + * calls are forwared to |impl|. + */ + private void checkProxy(NamedObject.Proxy proxy, MockNamedObjectImpl impl) { + RecordingGetNameResponse callback = new RecordingGetNameResponse(); + CapturingErrorHandler errorHandler = new CapturingErrorHandler(); + proxy.getProxyHandler().setErrorHandler(errorHandler); + + if (impl != null) { + Assert.assertNull(impl.getLastMojoException()); + Assert.assertEquals("", impl.getNameSynchronously()); + } + + proxy.getName(callback); + mTestRule.runLoopUntilIdle(); + + Assert.assertNull(errorHandler.getLastMojoException()); + Assert.assertTrue(callback.wasCalled()); + Assert.assertEquals("", callback.getName()); + + callback.reset(); + proxy.setName(OBJECT_NAME); + mTestRule.runLoopUntilIdle(); + + Assert.assertNull(errorHandler.getLastMojoException()); + if (impl != null) { + Assert.assertNull(impl.getLastMojoException()); + Assert.assertEquals(OBJECT_NAME, impl.getNameSynchronously()); + } + + proxy.getName(callback); + mTestRule.runLoopUntilIdle(); + + Assert.assertNull(errorHandler.getLastMojoException()); + Assert.assertTrue(callback.wasCalled()); + Assert.assertEquals(OBJECT_NAME, callback.getName()); + } + + @Test + @SmallTest + public void testName() { + Assert.assertEquals("sample.NamedObject", NamedObject.MANAGER.getName()); + } + + @Test + @SmallTest + public void testProxyAndStub() { + MockNamedObjectImpl impl = new MockNamedObjectImpl(); + NamedObject.Proxy proxy = + NamedObject.MANAGER.buildProxy(null, NamedObject.MANAGER.buildStub(null, impl)); + + checkProxy(proxy, impl); + } + + @Test + @SmallTest + public void testProxyAndStubOverPipe() { + MockNamedObjectImpl impl = new MockNamedObjectImpl(); + NamedObject.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(NamedObject.MANAGER, impl, mCloseablesToClose); + + checkProxy(proxy, impl); + } + + @Test + @SmallTest + public void testFactoryOverPipe() { + Factory.Proxy proxy = BindingsTestUtils.newProxyOverPipe( + Factory.MANAGER, new MockFactoryImpl(), mCloseablesToClose); + Pair<NamedObject.Proxy, InterfaceRequest<NamedObject>> request = + NamedObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance()); + mCloseablesToClose.add(request.first); + proxy.createNamedObject(request.second); + + checkProxy(request.first, null); + } + + @Test + @SmallTest + public void testInterfaceClosing() { + MockFactoryImpl impl = new MockFactoryImpl(); + Factory.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose); + + Assert.assertFalse(impl.isClosed()); + + proxy.close(); + mTestRule.runLoopUntilIdle(); + + Assert.assertTrue(impl.isClosed()); + } + + @Test + @SmallTest + public void testResponse() { + MockFactoryImpl impl = new MockFactoryImpl(); + Factory.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose); + Request request = new Request(); + request.x = 42; + Pair<MessagePipeHandle, MessagePipeHandle> handles = + CoreImpl.getInstance().createMessagePipe(null); + DoStuffResponseImpl response = new DoStuffResponseImpl(); + proxy.doStuff(request, handles.first, response); + + Assert.assertFalse(response.wasResponseCalled()); + + mTestRule.runLoopUntilIdle(); + + Assert.assertTrue(response.wasResponseCalled()); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java new file mode 100644 index 00000000000..e2d24ed4c76 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java @@ -0,0 +1,74 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.bindings.test.mojom.imported.Point; + +/** + * Testing internal classes of interfaces. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class MessageHeaderTest { + /** + * Testing that headers are identical after being serialized/deserialized. + */ + @Test + @SmallTest + public void testSimpleMessageHeader() { + final int xValue = 1; + final int yValue = 2; + final int type = 6; + Point p = new Point(); + p.x = xValue; + p.y = yValue; + ServiceMessage message = p.serializeWithHeader(null, new MessageHeader(type)); + + MessageHeader header = message.getHeader(); + Assert.assertTrue(header.validateHeader(type, 0)); + Assert.assertEquals(type, header.getType()); + Assert.assertEquals(0, header.getFlags()); + + Point p2 = Point.deserialize(message.getPayload()); + Assert.assertNotNull(p2); + Assert.assertEquals(p.x, p2.x); + Assert.assertEquals(p.y, p2.y); + } + + /** + * Testing that headers are identical after being serialized/deserialized. + */ + @Test + @SmallTest + public void testMessageWithRequestIdHeader() { + final int xValue = 1; + final int yValue = 2; + final int type = 6; + final long requestId = 0x1deadbeafL; + Point p = new Point(); + p.x = xValue; + p.y = yValue; + ServiceMessage message = p.serializeWithHeader( + null, new MessageHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0)); + message.setRequestId(requestId); + + MessageHeader header = message.getHeader(); + Assert.assertTrue(header.validateHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)); + Assert.assertEquals(type, header.getType()); + Assert.assertEquals(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, header.getFlags()); + Assert.assertEquals(requestId, header.getRequestId()); + + Point p2 = Point.deserialize(message.getPayload()); + Assert.assertNotNull(p2); + Assert.assertEquals(p.x, p2.x); + Assert.assertEquals(p.y, p2.y); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java new file mode 100644 index 00000000000..2a5a4b27854 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/NameGeneratorTest.java @@ -0,0 +1,80 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.bindings.test.mojom.sample.NameGeneratorConstants; +import org.chromium.mojo.bindings.test.mojom.sample.SupportedCases; + +/** + * Test mojom constant names generated for java. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class NameGeneratorTest { + @Test + @SmallTest + public void testLowerCamelCase() { + Assert.assertTrue(classHasField(SupportedCases.class, "LOWER_CAMEL_CASE")); + } + + @Test + @SmallTest + public void testUpperCamelCase() { + Assert.assertTrue(classHasField(SupportedCases.class, "UPPER_CAMEL_CASE")); + } + + @Test + @SmallTest + public void testSnakeCase() { + Assert.assertTrue(classHasField(SupportedCases.class, "SNAKE_CASE")); + } + + @Test + @SmallTest + public void testMacroCase() { + Assert.assertTrue(classHasField(SupportedCases.class, "MACRO_CASE")); + } + + @Test + @SmallTest + public void testHungarianNotation() { + Assert.assertTrue(classHasField(SupportedCases.class, "HUNGARIAN_NOTATION")); + } + + @Test + @SmallTest + public void testUpperAcronym() { + Assert.assertTrue(classHasField(SupportedCases.class, "UPPER_ACRONYM_CASE")); + } + + @Test + @SmallTest + public void testNames() { + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "PAD_RSA_PKCS1_1_5_SIGN")); + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "DIGEST_SHA1")); + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "E2E_INTEGRATION")); + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "M3_TEST")); + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "URL_LOADER_FACTORY")); + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "IPV6_ADDRESS")); + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "NUMB3R5_IN_TH3_MIDDL3")); + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "NAME_WITH_UNDERSCORE")); + Assert.assertTrue(classHasField(NameGeneratorConstants.class, "SINGLETON")); + } + + private static <T> boolean classHasField(Class<T> clazz, String fieldName) { + try { + clazz.getField(fieldName); + return true; + } catch (NoSuchFieldException e) { + return false; + } + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java new file mode 100644 index 00000000000..b6283b87815 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java @@ -0,0 +1,123 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Testing {@link Connector#readAndDispatchMessage}. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class ReadAndDispatchMessageTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private static final int DATA_SIZE = 1024; + + private ByteBuffer mData; + private Pair<MessagePipeHandle, MessagePipeHandle> mHandles; + private List<Handle> mHandlesToSend = new ArrayList<Handle>(); + private List<Handle> mHandlesToClose = new ArrayList<Handle>(); + private RecordingMessageReceiver mMessageReceiver; + + /** + * @see org.chromium.mojo.MojoTestCase#setUp() + */ + @Before + public void setUp() throws Exception { + Core core = CoreImpl.getInstance(); + mData = BindingsTestUtils.newRandomMessage(DATA_SIZE).getData(); + mMessageReceiver = new RecordingMessageReceiver(); + mHandles = core.createMessagePipe(new MessagePipeHandle.CreateOptions()); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> datapipe = core.createDataPipe(null); + mHandlesToSend.addAll(Arrays.asList(datapipe.first, datapipe.second)); + mHandlesToClose.addAll(Arrays.asList(mHandles.first, mHandles.second)); + mHandlesToClose.addAll(mHandlesToSend); + } + + /** + * @see org.chromium.mojo.MojoTestCase#tearDown() + */ + @After + public void tearDown() throws Exception { + for (Handle handle : mHandlesToClose) { + handle.close(); + } + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + */ + @Test + @SmallTest + public void testReadAndDispatchMessage() { + mHandles.first.writeMessage(mData, mHandlesToSend, MessagePipeHandle.WriteFlags.NONE); + Assert.assertEquals(MojoResult.OK, + Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver) + .getMojoResult()); + Assert.assertEquals(1, mMessageReceiver.messages.size()); + Message message = mMessageReceiver.messages.get(0); + mHandlesToClose.addAll(message.getHandles()); + Assert.assertEquals(mData, message.getData()); + Assert.assertEquals(2, message.getHandles().size()); + for (Handle handle : message.getHandles()) { + Assert.assertTrue(handle.isValid()); + } + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + * with no message available. + */ + @Test + @SmallTest + public void testReadAndDispatchMessageOnEmptyHandle() { + Assert.assertEquals(MojoResult.SHOULD_WAIT, + Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver) + .getMojoResult()); + Assert.assertEquals(0, mMessageReceiver.messages.size()); + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + * on closed handle. + */ + @Test + @SmallTest + public void testReadAndDispatchMessageOnClosedHandle() { + mHandles.first.close(); + try { + Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver); + Assert.fail("MojoException should have been thrown"); + } catch (MojoException expected) { + Assert.assertEquals(MojoResult.FAILED_PRECONDITION, expected.getMojoResult()); + } + Assert.assertEquals(0, mMessageReceiver.messages.size()); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/RouterTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/RouterTest.java new file mode 100644 index 00000000000..c2680ae269e --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/RouterTest.java @@ -0,0 +1,241 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiverWithResponder; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignals; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Testing {@link Router} + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class RouterTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private MessagePipeHandle mHandle; + private Router mRouter; + private RecordingMessageReceiverWithResponder mReceiver; + private CapturingErrorHandler mErrorHandler; + + /** + * @see MojoTestCase#setUp() + */ + @Before + public void setUp() throws Exception { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + mHandle = handles.first; + mRouter = new RouterImpl(handles.second); + mReceiver = new RecordingMessageReceiverWithResponder(); + mRouter.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mRouter.setErrorHandler(mErrorHandler); + mRouter.start(); + } + + /** + * Testing sending a message via the router that expected a response. + */ + @Test + @SmallTest + public void testSendingToRouterWithResponse() { + final int requestMessageType = 0xdead; + final int responseMessageType = 0xbeaf; + + // Sending a message expecting a response. + MessageHeader header = new MessageHeader( + requestMessageType, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0); + Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + header.encode(encoder); + mRouter.acceptWithResponder(encoder.getMessage(), mReceiver); + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + mHandle.readMessage(MessagePipeHandle.ReadFlags.NONE); + + Assert.assertEquals(MojoResult.OK, result.getMojoResult()); + MessageHeader receivedHeader = + new Message(ByteBuffer.wrap(result.getValue().mData), new ArrayList<Handle>()) + .asServiceMessage() + .getHeader(); + + Assert.assertEquals(header.getType(), receivedHeader.getType()); + Assert.assertEquals(header.getFlags(), receivedHeader.getFlags()); + Assert.assertTrue(receivedHeader.getRequestId() != 0); + + // Sending the response. + MessageHeader responseHeader = new MessageHeader(responseMessageType, + MessageHeader.MESSAGE_IS_RESPONSE_FLAG, receivedHeader.getRequestId()); + encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + responseHeader.encode(encoder); + Message responseMessage = encoder.getMessage(); + mHandle.writeMessage(responseMessage.getData(), new ArrayList<Handle>(), + MessagePipeHandle.WriteFlags.NONE); + mTestRule.runLoopUntilIdle(); + + Assert.assertEquals(1, mReceiver.messages.size()); + ServiceMessage receivedResponseMessage = mReceiver.messages.get(0).asServiceMessage(); + Assert.assertEquals(MessageHeader.MESSAGE_IS_RESPONSE_FLAG, + receivedResponseMessage.getHeader().getFlags()); + Assert.assertEquals(responseMessage.getData(), receivedResponseMessage.getData()); + } + + /** + * Sends a message to the Router. + * + * @param messageIndex Used when sending multiple messages to indicate the index of this + * message. + * @param requestMessageType The message type to use in the header of the sent message. + * @param requestId The requestId to use in the header of the sent message. + */ + private void sendMessageToRouter(int messageIndex, int requestMessageType, int requestId) { + MessageHeader header = new MessageHeader( + requestMessageType, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, requestId); + Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + header.encode(encoder); + Message headerMessage = encoder.getMessage(); + mHandle.writeMessage(headerMessage.getData(), new ArrayList<Handle>(), + MessagePipeHandle.WriteFlags.NONE); + mTestRule.runLoopUntilIdle(); + + Assert.assertEquals(messageIndex + 1, mReceiver.messagesWithReceivers.size()); + Pair<Message, MessageReceiver> receivedMessage = + mReceiver.messagesWithReceivers.get(messageIndex); + Assert.assertEquals(headerMessage.getData(), receivedMessage.first.getData()); + } + + /** + * Sends a response message from the Router. + * + * @param messageIndex Used when sending responses to multiple messages to indicate the index + * of the message that this message is a response to. + * @param responseMessageType The message type to use in the header of the response message. + */ + private void sendResponseFromRouter(int messageIndex, int responseMessageType) { + Pair<Message, MessageReceiver> receivedMessage = + mReceiver.messagesWithReceivers.get(messageIndex); + + long requestId = receivedMessage.first.asServiceMessage().getHeader().getRequestId(); + + MessageHeader responseHeader = new MessageHeader( + responseMessageType, MessageHeader.MESSAGE_IS_RESPONSE_FLAG, requestId); + Encoder encoder = new Encoder(CoreImpl.getInstance(), responseHeader.getSize()); + responseHeader.encode(encoder); + Message message = encoder.getMessage(); + receivedMessage.second.accept(message); + + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + mHandle.readMessage(MessagePipeHandle.ReadFlags.NONE); + + Assert.assertEquals(MojoResult.OK, result.getMojoResult()); + Assert.assertEquals(message.getData(), ByteBuffer.wrap(result.getValue().mData)); + } + + /** + * Clears {@code mReceiver.messagesWithReceivers} allowing all message receivers to be + * finalized. + * <p> + * Since there is no way to force the Garbage Collector to actually call finalize and we want to + * test the effects of the finalize() method, we explicitly call finalize() on all of the + * message receivers. We do this in a custom thread to better approximate what the JVM does. + */ + private void clearAllMessageReceivers() { + Thread myFinalizerThread = new Thread() { + @Override + public void run() { + for (Pair<Message, MessageReceiver> receivedMessage : + mReceiver.messagesWithReceivers) { + RouterImpl.ResponderThunk thunk = + (RouterImpl.ResponderThunk) receivedMessage.second; + try { + thunk.finalize(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } + }; + myFinalizerThread.start(); + try { + myFinalizerThread.join(); + } catch (InterruptedException e) { + // ignore. + } + mReceiver.messagesWithReceivers.clear(); + } + + /** + * Testing receiving a message via the router that expected a response. + */ + @Test + @SmallTest + public void testReceivingViaRouterWithResponse() { + final int requestMessageType = 0xdead; + final int responseMessageType = 0xbeef; + final int requestId = 0xdeadbeaf; + + // Send a message expecting a response. + sendMessageToRouter(0, requestMessageType, requestId); + + // Sending the response. + sendResponseFromRouter(0, responseMessageType); + } + + /** + * Tests that if a callback is dropped (i.e. becomes unreachable and is finalized + * without being used), then the message pipe will be closed. + */ + @Test + @SmallTest + public void testDroppingReceiverWithoutUsingIt() { + // Send 10 messages to the router without sending a response. + for (int i = 0; i < 10; i++) { + sendMessageToRouter(i, i, i); + } + + // Now send the 10 responses. This should work fine. + for (int i = 0; i < 10; i++) { + sendResponseFromRouter(i, i); + } + + // Clear all MessageRecievers so that the ResponderThunks will + // be finalized. + clearAllMessageReceivers(); + + // Send another message to the router without sending a response. + sendMessageToRouter(0, 0, 0); + + // Clear the MessageReciever so that the ResponderThunk will + // be finalized. Since the RespondeThunk was never used, this + // should close the pipe. + clearAllMessageReceivers(); + // The close() occurs asynchronously on this thread. + mTestRule.runLoopUntilIdle(); + + // Confirm that the pipe was closed on the Router side. + HandleSignals closedFlag = HandleSignals.none().setPeerClosed(true); + Assert.assertEquals(closedFlag, mHandle.querySignalsState().getSatisfiedSignals()); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/SerializationTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/SerializationTest.java new file mode 100644 index 00000000000..5ee6743b460 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/SerializationTest.java @@ -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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct1; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct2; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct3; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct4; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct5; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct6; +import org.chromium.mojo.bindings.test.mojom.mojo.StructOfNullables; + +import java.nio.ByteBuffer; + +/** + * Tests for the serialization logic of the generated structs, using structs defined in + * mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom . + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class SerializationTest { + private static void assertThrowsSerializationException(Struct struct) { + try { + struct.serialize(null); + Assert.fail("Serialization of invalid struct should have thrown an exception."); + } catch (SerializationException ex) { + // Expected. + } + } + + /** + * Verifies that serializing a struct with an invalid handle of a non-nullable type throws an + * exception. + */ + @Test + @SmallTest + public void testHandle() { + Struct2 struct = new Struct2(); + Assert.assertFalse(struct.hdl.isValid()); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.hdl = new HandleMock(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a null struct pointer throws an exception. + */ + @Test + @SmallTest + public void testStructPointer() { + Struct3 struct = new Struct3(); + Assert.assertNull(struct.struct1); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.struct1 = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with an array of structs throws an exception when the + * struct is invalid. + */ + @Test + @SmallTest + public void testStructArray() { + Struct4 struct = new Struct4(); + Assert.assertNull(struct.data); + assertThrowsSerializationException(struct); + + // Create the (1-element) array but have the element null. + struct.data = new Struct1[1]; + assertThrowsSerializationException(struct); + + // Create the array element, struct should serialize now. + struct.data[0] = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a fixed-size array of incorrect length throws an + * exception. + */ + @Test + @SmallTest + public void testFixedSizeArray() { + Struct5 struct = new Struct5(); + Assert.assertNull(struct.pair); + assertThrowsSerializationException(struct); + + // Create the (1-element) array, 2-element array is required. + struct.pair = new Struct1[1]; + struct.pair[0] = new Struct1(); + assertThrowsSerializationException(struct); + + // Create the array of a correct size, struct should serialize now. + struct.pair = new Struct1[2]; + struct.pair[0] = new Struct1(); + struct.pair[1] = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a null string throws an exception. + */ + @Test + @SmallTest + public void testString() { + Struct6 struct = new Struct6(); + Assert.assertNull(struct.str); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.str = ""; + struct.serialize(null); + } + + /** + * Verifies that a struct with an invalid nullable handle, null nullable struct pointer and null + * nullable string serializes without an exception. + */ + @Test + @SmallTest + public void testNullableFields() { + StructOfNullables struct = new StructOfNullables(); + Assert.assertFalse(struct.hdl.isValid()); + Assert.assertNull(struct.struct1); + Assert.assertNull(struct.str); + struct.serialize(null); + } + + /** + * Verifies that a struct can be serialized to and deserialized from a ByteBuffer. + */ + @Test + @SmallTest + public void testByteBufferSerialization() { + Struct1 input = new Struct1(); + input.i = 0x7F; + + ByteBuffer buf = input.serialize(); + + byte[] expected_raw_bytes = {16, 0, 0, 0, 0, 0, 0, 0, 0x7F, 0, 0, 0, 0, 0, 0, 0}; + ByteBuffer expected_buf = ByteBuffer.wrap(expected_raw_bytes); + Assert.assertEquals(expected_buf, buf); + + Struct1 output = Struct1.deserialize(buf); + Assert.assertEquals(0x7F, output.i); + } + + /** + * Verifies that a struct with handles cannot be serialized to a ByteBuffer. + */ + @Test + @SmallTest + public void testByteBufferSerializationWithHandles() { + StructOfNullables struct = new StructOfNullables(); + Assert.assertFalse(struct.hdl.isValid()); + Assert.assertNull(struct.struct1); + Assert.assertNull(struct.str); + + // It is okay to serialize invalid handles. + struct.serialize(); + + struct.hdl = new HandleMock(); + + try { + struct.serialize(); + Assert.fail("Serializing a struct with handles to a ByteBuffer should have thrown an " + + "exception."); + } catch (UnsupportedOperationException ex) { + // Expected. + } + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTest.java new file mode 100644 index 00000000000..101f166fd0f --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTest.java @@ -0,0 +1,247 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.annotation.SuppressLint; +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.base.test.util.UrlUtils; +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.bindings.test.mojom.mojo.ConformanceTestInterface; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterfaceTestHelper; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * Testing validation upon deserialization using the interfaces defined in the + * mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom file. + * <p> + * One needs to pass '--test_data=bindings:{path to mojo/public/interfaces/bindings/tests/data}' to + * the test_runner script for this test to find the validation data it needs. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class ValidationTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + /** + * The path where validation test data is. + */ + private static final File VALIDATION_TEST_DATA_PATH = new File(UrlUtils.getIsolatedTestFilePath( + "mojo/public/interfaces/bindings/tests/data/validation")); + + /** + * The data needed for a validation test. + */ + private static class TestData { + public File dataFile; + public ValidationTestUtil.Data inputData; + public String expectedResult; + } + + private static class DataFileFilter implements FileFilter { + private final String mPrefix; + + public DataFileFilter(String prefix) { + this.mPrefix = prefix; + } + + @Override + public boolean accept(File pathname) { + // TODO(yzshen, qsr): skip some interface versioning tests. + if (pathname.getName().startsWith("conformance_mthd13_good_2")) { + return false; + } + return pathname.isFile() && pathname.getName().startsWith(mPrefix) + && pathname.getName().endsWith(".data"); + } + } + + @SuppressLint("NewApi") + private static String getStringContent(File f) throws FileNotFoundException { + // TODO(crbug.com/635567): Fix this properly. + try (Scanner scanner = new Scanner(f)) { + scanner.useDelimiter("\\Z"); + StringBuilder result = new StringBuilder(); + while (scanner.hasNext()) { + result.append(scanner.next()); + } + return result.toString().trim(); + } + } + + private static List<TestData> getTestData(String prefix) throws FileNotFoundException { + List<TestData> results = new ArrayList<TestData>(); + + // Fail if the test data is not present. + if (!VALIDATION_TEST_DATA_PATH.isDirectory()) { + Assert.fail("No test data directory found. " + + "Expected directory at: " + VALIDATION_TEST_DATA_PATH); + } + + File[] files = VALIDATION_TEST_DATA_PATH.listFiles(new DataFileFilter(prefix)); + if (files != null) { + for (File dataFile : files) { + File resultFile = new File(dataFile.getParent(), + dataFile.getName().replaceFirst("\\.data$", ".expected")); + TestData testData = new TestData(); + testData.dataFile = dataFile; + testData.inputData = ValidationTestUtil.parseData(getStringContent(dataFile)); + testData.expectedResult = getStringContent(resultFile); + results.add(testData); + } + } + return results; + } + + /** + * Runs all the test with the given prefix on the given {@link MessageReceiver}. + */ + private static void runTest(String prefix, MessageReceiver messageReceiver) + throws FileNotFoundException { + List<TestData> testData = getTestData(prefix); + for (TestData test : testData) { + Assert.assertNull("Unable to read: " + test.dataFile.getName() + ": " + + test.inputData.getErrorMessage(), + test.inputData.getErrorMessage()); + List<Handle> handles = new ArrayList<Handle>(); + for (int i = 0; i < test.inputData.getHandlesCount(); ++i) { + handles.add(new HandleMock()); + } + Message message = new Message(test.inputData.getData(), handles); + boolean passed = messageReceiver.accept(message); + if (passed && !test.expectedResult.equals("PASS")) { + Assert.fail("Input: " + test.dataFile.getName() + + ": The message should have been refused. Expected error: " + + test.expectedResult); + } + if (!passed && test.expectedResult.equals("PASS")) { + Assert.fail("Input: " + test.dataFile.getName() + + ": The message should have been accepted."); + } + } + } + + private static class RoutingMessageReceiver implements MessageReceiver { + private final MessageReceiverWithResponder mRequest; + private final MessageReceiver mResponse; + + private RoutingMessageReceiver( + MessageReceiverWithResponder request, MessageReceiver response) { + this.mRequest = request; + this.mResponse = response; + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + try { + MessageHeader header = message.asServiceMessage().getHeader(); + if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) { + return mResponse.accept(message); + } else { + return mRequest.acceptWithResponder(message, new SinkMessageReceiver()); + } + } catch (DeserializationException e) { + return false; + } + } + + /** + * @see MessageReceiver#close() + */ + @Override + public void close() {} + } + + /** + * A trivial message receiver that refuses all messages it receives. + */ + private static class SinkMessageReceiver implements MessageReceiverWithResponder { + @Override + public boolean accept(Message message) { + return true; + } + + @Override + public void close() {} + + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + return true; + } + } + + /** + * Testing the conformance suite. + */ + @Test + @SmallTest + public void testConformance() throws FileNotFoundException { + runTest("conformance_", + ConformanceTestInterface.MANAGER.buildStub(CoreImpl.getInstance(), + ConformanceTestInterface.MANAGER.buildProxy( + CoreImpl.getInstance(), new SinkMessageReceiver()))); + } + + /** + * Testing the integration suite for message headers. + */ + @Test + @SmallTest + public void testIntegrationMessageHeader() throws FileNotFoundException { + runTest("integration_msghdr_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy( + null, new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } + + /** + * Testing the integration suite for request messages. + */ + @Test + @SmallTest + public void testIntegrationRequestMessage() throws FileNotFoundException { + runTest("integration_intf_rqst_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy( + null, new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } + + /** + * Testing the integration suite for response messages. + */ + @Test + @SmallTest + public void testIntegrationResponseMessage() throws FileNotFoundException { + runTest("integration_intf_resp_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy( + null, new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java new file mode 100644 index 00000000000..3b59ffdae3f --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java @@ -0,0 +1,67 @@ +// 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. + +package org.chromium.mojo.bindings; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Utility class for testing message validation. The file format used to describe a message is + * described in The format is described in + * mojo/public/cpp/bindings/tests/validation_test_input_parser.h + */ +@JNINamespace("mojo::android") +public class ValidationTestUtil { + /** + * Content of a '.data' file. + */ + public static class Data { + private final ByteBuffer mData; + private final int mHandlesCount; + private final String mErrorMessage; + + public ByteBuffer getData() { + return mData; + } + + public int getHandlesCount() { + return mHandlesCount; + } + + public String getErrorMessage() { + return mErrorMessage; + } + + private Data(ByteBuffer data, int handlesCount, String errorMessage) { + this.mData = data; + this.mHandlesCount = handlesCount; + this.mErrorMessage = errorMessage; + } + } + + /** + * Parse a '.data' file. + */ + public static Data parseData(String dataAsString) { + return nativeParseData(dataAsString); + } + + private static native Data nativeParseData(String dataAsString); + + @CalledByNative + private static Data buildData(ByteBuffer data, int handlesCount, String errorMessage) { + ByteBuffer copiedData = null; + if (data != null) { + copiedData = ByteBuffer.allocateDirect(data.limit()); + copiedData.order(ByteOrder.LITTLE_ENDIAN); + copiedData.put(data); + copiedData.flip(); + } + return new Data(copiedData, handlesCount, errorMessage); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java new file mode 100644 index 00000000000..314a29fc652 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java @@ -0,0 +1,150 @@ +// 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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Testing {@link ValidationTestUtil}. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class ValidationTestUtilTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + /** + * Check that the input parser is correct on a given input. + */ + public static void checkInputParser( + String input, boolean isInputValid, ByteBuffer expectedData, int expectedHandlesCount) { + ValidationTestUtil.Data data = ValidationTestUtil.parseData(input); + if (isInputValid) { + Assert.assertNull(data.getErrorMessage()); + Assert.assertEquals(expectedData, data.getData()); + Assert.assertEquals(expectedHandlesCount, data.getHandlesCount()); + } else { + Assert.assertNotNull(data.getErrorMessage()); + Assert.assertNull(data.getData()); + } + } + + /** + * Testing {@link ValidationTestUtil#parseData(String)}. + */ + @Test + @SmallTest + public void testCorrectMessageParsing() { + { + // Test empty input. + String input = ""; + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.LITTLE_ENDIAN); + + checkInputParser(input, true, expected, 0); + } + { + // Test input that only consists of comments and whitespaces. + String input = " \t // hello world \n\r \t// the answer is 42 "; + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.nativeOrder()); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" + + "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; + ByteBuffer expected = ByteBuffer.allocateDirect(17); + expected.order(ByteOrder.nativeOrder()); + expected.put((byte) 0x10); + expected.putShort((short) 65535); + expected.putInt(65536); + expected.putLong(-1); + expected.put((byte) 0); + expected.put((byte) 0xff); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; + ByteBuffer expected = ByteBuffer.allocateDirect(15); + expected.order(ByteOrder.nativeOrder()); + expected.putLong(-0x800); + expected.put((byte) -128); + expected.putShort((short) 0); + expected.putInt(-40); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; + ByteBuffer expected = ByteBuffer.allocateDirect(3); + expected.order(ByteOrder.nativeOrder()); + expected.put((byte) 11); + expected.put((byte) 128); + expected.put((byte) 0); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[f]+.3e9 [d]-10.03"; + ByteBuffer expected = ByteBuffer.allocateDirect(12); + expected.order(ByteOrder.nativeOrder()); + expected.putFloat(+.3e9f); + expected.putDouble(-10.03); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; + ByteBuffer expected = ByteBuffer.allocateDirect(14); + expected.order(ByteOrder.nativeOrder()); + expected.putInt(14); + expected.put((byte) 0); + expected.putLong(9); + expected.put((byte) 0); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "// This message has handles! \n[handles]50 [u8]2"; + ByteBuffer expected = ByteBuffer.allocateDirect(8); + expected.order(ByteOrder.nativeOrder()); + expected.putLong(2); + expected.flip(); + + checkInputParser(input, true, expected, 50); + } + + // Test some failure cases. + { + String error_inputs[] = {"/ hello world", "[u1]x", "[u2]-1000", "[u1]0x100", + "[s2]-0x8001", "[b]1", "[b]1111111k", "[dist4]unmatched", + "[anchr]hello [dist8]hello", "[dist4]a [dist4]a [anchr]a", + "[dist4]a [anchr]a [dist4]a [anchr]a", "0 [handles]50"}; + + for (String input : error_inputs) { + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.nativeOrder()); + checkInputParser(input, false, expected, 0); + } + } + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java new file mode 100644 index 00000000000..697086017ec --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java @@ -0,0 +1,529 @@ +// 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. + +package org.chromium.mojo.system.impl; + +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignals; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.SharedBufferHandle; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * Testing the core API. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class CoreImplTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private static final long RUN_LOOP_TIMEOUT_MS = 5; + + private static final ScheduledExecutorService WORKER = + Executors.newSingleThreadScheduledExecutor(); + + private static final HandleSignals ALL_SIGNALS = + HandleSignals.none().setPeerClosed(true).setReadable(true).setWritable(true); + + private List<Handle> mHandlesToClose = new ArrayList<Handle>(); + + /** + * @see MojoTestCase#tearDown() + */ + @After + public void tearDown() throws Exception { + MojoException toThrow = null; + for (Handle handle : mHandlesToClose) { + try { + handle.close(); + } catch (MojoException e) { + if (toThrow == null) { + toThrow = e; + } + } + } + if (toThrow != null) { + throw toThrow; + } + } + + private void addHandleToClose(Handle handle) { + mHandlesToClose.add(handle); + } + + private void addHandlePairToClose(Pair<? extends Handle, ? extends Handle> handles) { + mHandlesToClose.add(handles.first); + mHandlesToClose.add(handles.second); + } + + private static void checkSendingMessage(MessagePipeHandle in, MessagePipeHandle out) { + Random random = new Random(); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + in.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE); + + // Read the message back. + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + out.readMessage(MessagePipeHandle.ReadFlags.NONE); + Assert.assertEquals(MojoResult.OK, result.getMojoResult()); + Assert.assertTrue(Arrays.equals(bytes, result.getValue().mData)); + Assert.assertEquals(0, result.getValue().mHandles.size()); + } + + private static void checkSendingData(DataPipe.ProducerHandle in, DataPipe.ConsumerHandle out) { + Random random = new Random(); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + ResultAnd<Integer> result = in.writeData(buffer, DataPipe.WriteFlags.NONE); + Assert.assertEquals(MojoResult.OK, result.getMojoResult()); + Assert.assertEquals(bytes.length, result.getValue().intValue()); + + // Query number of bytes available. + ResultAnd<Integer> readResult = out.readData(null, DataPipe.ReadFlags.none().query(true)); + Assert.assertEquals(MojoResult.OK, readResult.getMojoResult()); + Assert.assertEquals(bytes.length, readResult.getValue().intValue()); + + // Peek data into a buffer. + ByteBuffer peekBuffer = ByteBuffer.allocateDirect(bytes.length); + readResult = out.readData(peekBuffer, DataPipe.ReadFlags.none().peek(true)); + Assert.assertEquals(MojoResult.OK, readResult.getMojoResult()); + Assert.assertEquals(bytes.length, readResult.getValue().intValue()); + Assert.assertEquals(bytes.length, peekBuffer.limit()); + byte[] peekBytes = new byte[bytes.length]; + peekBuffer.get(peekBytes); + Assert.assertTrue(Arrays.equals(bytes, peekBytes)); + + // Read into a buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length); + readResult = out.readData(receiveBuffer, DataPipe.ReadFlags.NONE); + Assert.assertEquals(MojoResult.OK, readResult.getMojoResult()); + Assert.assertEquals(bytes.length, readResult.getValue().intValue()); + Assert.assertEquals(0, receiveBuffer.position()); + Assert.assertEquals(bytes.length, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length]; + receiveBuffer.get(receivedBytes); + Assert.assertTrue(Arrays.equals(bytes, receivedBytes)); + } + + private static void checkSharing(SharedBufferHandle in, SharedBufferHandle out) { + Random random = new Random(); + + ByteBuffer buffer1 = in.map(0, 8, SharedBufferHandle.MapFlags.NONE); + Assert.assertEquals(8, buffer1.capacity()); + ByteBuffer buffer2 = out.map(0, 8, SharedBufferHandle.MapFlags.NONE); + Assert.assertEquals(8, buffer2.capacity()); + + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + buffer1.put(bytes); + + byte[] receivedBytes = new byte[bytes.length]; + buffer2.get(receivedBytes); + + Assert.assertTrue(Arrays.equals(bytes, receivedBytes)); + + in.unmap(buffer1); + out.unmap(buffer2); + } + + /** + * Testing that Core can be retrieved from a handle. + */ + @Test + @SmallTest + public void testGetCore() { + Core core = CoreImpl.getInstance(); + + Pair<? extends Handle, ? extends Handle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + Assert.assertEquals(core, handles.first.getCore()); + Assert.assertEquals(core, handles.second.getCore()); + + handles = core.createDataPipe(null); + addHandlePairToClose(handles); + Assert.assertEquals(core, handles.first.getCore()); + Assert.assertEquals(core, handles.second.getCore()); + + SharedBufferHandle handle = core.createSharedBuffer(null, 100); + SharedBufferHandle handle2 = handle.duplicate(null); + addHandleToClose(handle); + addHandleToClose(handle2); + Assert.assertEquals(core, handle.getCore()); + Assert.assertEquals(core, handle2.getCore()); + } + + private static void createAndCloseMessagePipe(MessagePipeHandle.CreateOptions options) { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(options); + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link MessagePipeHandle} creation. + */ + @Test + @SmallTest + public void testMessagePipeCreation() { + // Test creation with null options. + createAndCloseMessagePipe(null); + // Test creation with default options. + createAndCloseMessagePipe(new MessagePipeHandle.CreateOptions()); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @Test + @SmallTest + public void testMessagePipeEmpty() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + // Testing read on an empty pipe. + ResultAnd<MessagePipeHandle.ReadMessageResult> readResult = + handles.first.readMessage(MessagePipeHandle.ReadFlags.NONE); + Assert.assertEquals(MojoResult.SHOULD_WAIT, readResult.getMojoResult()); + + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @Test + @SmallTest + public void testMessagePipeSend() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + checkSendingMessage(handles.first, handles.second); + checkSendingMessage(handles.second, handles.first); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @Test + @SmallTest + public void testMessagePipeSendHandles() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + Pair<MessagePipeHandle, MessagePipeHandle> handlesToShare = core.createMessagePipe(null); + addHandlePairToClose(handles); + addHandlePairToClose(handlesToShare); + + handles.first.writeMessage(null, Collections.<Handle>singletonList(handlesToShare.second), + MessagePipeHandle.WriteFlags.NONE); + Assert.assertFalse(handlesToShare.second.isValid()); + ResultAnd<MessagePipeHandle.ReadMessageResult> readMessageResult = + handles.second.readMessage(MessagePipeHandle.ReadFlags.NONE); + Assert.assertEquals(1, readMessageResult.getValue().mHandles.size()); + MessagePipeHandle newHandle = + readMessageResult.getValue().mHandles.get(0).toMessagePipeHandle(); + addHandleToClose(newHandle); + Assert.assertTrue(newHandle.isValid()); + checkSendingMessage(handlesToShare.first, newHandle); + checkSendingMessage(newHandle, handlesToShare.first); + } + + private static void createAndCloseDataPipe(DataPipe.CreateOptions options) { + Core core = CoreImpl.getInstance(); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = + core.createDataPipe(options); + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link DataPipe}. + */ + @Test + @SmallTest + public void testDataPipeCreation() { + // Create datapipe with null options. + createAndCloseDataPipe(null); + DataPipe.CreateOptions options = new DataPipe.CreateOptions(); + // Create datapipe with element size set. + options.setElementNumBytes(24); + createAndCloseDataPipe(options); + // Create datapipe with capacity set. + options.setCapacityNumBytes(1024 * options.getElementNumBytes()); + createAndCloseDataPipe(options); + } + + /** + * Testing {@link DataPipe}. + */ + @Test + @SmallTest + public void testDataPipeSend() { + Core core = CoreImpl.getInstance(); + + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + checkSendingData(handles.first, handles.second); + } + + /** + * Testing {@link DataPipe}. + */ + @Test + @SmallTest + public void testDataPipeTwoPhaseSend() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = handles.first.beginWriteData(bytes.length, DataPipe.WriteFlags.NONE); + Assert.assertTrue(buffer.capacity() >= bytes.length); + buffer.put(bytes); + handles.first.endWriteData(bytes.length); + + // Read into a buffer. + ByteBuffer receiveBuffer = + handles.second.beginReadData(bytes.length, DataPipe.ReadFlags.NONE); + Assert.assertEquals(0, receiveBuffer.position()); + Assert.assertEquals(bytes.length, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length]; + receiveBuffer.get(receivedBytes); + Assert.assertTrue(Arrays.equals(bytes, receivedBytes)); + handles.second.endReadData(bytes.length); + } + + /** + * Testing {@link DataPipe}. + */ + @Test + @SmallTest + public void testDataPipeDiscard() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + ResultAnd<Integer> result = handles.first.writeData(buffer, DataPipe.WriteFlags.NONE); + Assert.assertEquals(MojoResult.OK, result.getMojoResult()); + Assert.assertEquals(bytes.length, result.getValue().intValue()); + + // Discard bytes. + final int nbBytesToDiscard = 4; + Assert.assertEquals(nbBytesToDiscard, + handles.second.discardData(nbBytesToDiscard, DataPipe.ReadFlags.NONE)); + + // Read into a buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length - nbBytesToDiscard); + ResultAnd<Integer> readResult = + handles.second.readData(receiveBuffer, DataPipe.ReadFlags.NONE); + Assert.assertEquals(MojoResult.OK, readResult.getMojoResult()); + Assert.assertEquals(bytes.length - nbBytesToDiscard, readResult.getValue().intValue()); + Assert.assertEquals(0, receiveBuffer.position()); + Assert.assertEquals(bytes.length - nbBytesToDiscard, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length - nbBytesToDiscard]; + receiveBuffer.get(receivedBytes); + Assert.assertTrue(Arrays.equals( + Arrays.copyOfRange(bytes, nbBytesToDiscard, bytes.length), receivedBytes)); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @Test + @SmallTest + public void testSharedBufferCreation() { + Core core = CoreImpl.getInstance(); + // Test creation with empty options. + core.createSharedBuffer(null, 8).close(); + // Test creation with default options. + core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), 8).close(); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @Test + @SmallTest + public void testSharedBufferDuplication() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + + // Test duplication with empty options. + handle.duplicate(null).close(); + // Test creation with default options. + handle.duplicate(new SharedBufferHandle.DuplicateOptions()).close(); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @Test + @SmallTest + public void testSharedBufferSending() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + SharedBufferHandle newHandle = handle.duplicate(null); + addHandleToClose(newHandle); + + checkSharing(handle, newHandle); + checkSharing(newHandle, handle); + } + + /** + * Testing that invalid handle can be used with this implementation. + */ + @Test + @SmallTest + public void testInvalidHandle() { + Core core = CoreImpl.getInstance(); + Handle handle = InvalidHandle.INSTANCE; + + // Checking sending an invalid handle. Should result in an ABORTED + // exception. + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + try { + handles.first.writeMessage(null, Collections.<Handle>singletonList(handle), + MessagePipeHandle.WriteFlags.NONE); + Assert.fail(); + } catch (MojoException e) { + Assert.assertEquals(MojoResult.ABORTED, e.getMojoResult()); + } + } + + /** + * Testing the pass method on message pipes. + */ + @Test + @SmallTest + public void testMessagePipeHandlePass() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + Assert.assertTrue(handles.first.isValid()); + MessagePipeHandle handleClone = handles.first.pass(); + + addHandleToClose(handleClone); + + Assert.assertFalse(handles.first.isValid()); + Assert.assertTrue(handleClone.isValid()); + checkSendingMessage(handleClone, handles.second); + checkSendingMessage(handles.second, handleClone); + } + + /** + * Testing the pass method on data pipes. + */ + @Test + @SmallTest + public void testDataPipeHandlePass() { + Core core = CoreImpl.getInstance(); + Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + DataPipe.ProducerHandle producerClone = handles.first.pass(); + DataPipe.ConsumerHandle consumerClone = handles.second.pass(); + + addHandleToClose(producerClone); + addHandleToClose(consumerClone); + + Assert.assertFalse(handles.first.isValid()); + Assert.assertFalse(handles.second.isValid()); + Assert.assertTrue(producerClone.isValid()); + Assert.assertTrue(consumerClone.isValid()); + checkSendingData(producerClone, consumerClone); + } + + /** + * Testing the pass method on shared buffers. + */ + @Test + @SmallTest + public void testSharedBufferPass() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + SharedBufferHandle newHandle = handle.duplicate(null); + addHandleToClose(newHandle); + + SharedBufferHandle handleClone = handle.pass(); + SharedBufferHandle newHandleClone = newHandle.pass(); + + addHandleToClose(handleClone); + addHandleToClose(newHandleClone); + + Assert.assertFalse(handle.isValid()); + Assert.assertTrue(handleClone.isValid()); + checkSharing(handleClone, newHandleClone); + checkSharing(newHandleClone, handleClone); + } + + /** + * esting handle conversion to native and back. + */ + @Test + @SmallTest + public void testHandleConversion() { + Core core = CoreImpl.getInstance(); + Pair<MessagePipeHandle, MessagePipeHandle> handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + MessagePipeHandle converted = + core.acquireNativeHandle(handles.first.releaseNativeHandle()).toMessagePipeHandle(); + addHandleToClose(converted); + + Assert.assertFalse(handles.first.isValid()); + + checkSendingMessage(converted, handles.second); + checkSendingMessage(handles.second, converted); + } +} diff --git a/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java new file mode 100644 index 00000000000..11f83e84640 --- /dev/null +++ b/chromium/mojo/public/java/system/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java @@ -0,0 +1,271 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import android.support.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.chromium.base.test.BaseJUnit4ClassRunner; +import org.chromium.mojo.MojoTestRule; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.Watcher; +import org.chromium.mojo.system.Watcher.Callback; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Testing the Watcher. + */ +@RunWith(BaseJUnit4ClassRunner.class) +public class WatcherImplTest { + @Rule + public MojoTestRule mTestRule = new MojoTestRule(); + + private List<Handle> mHandlesToClose = new ArrayList<Handle>(); + private Watcher mWatcher; + private Core mCore; + + /** + * @see MojoTestCase#setUp() + */ + @Before + public void setUp() throws Exception { + mWatcher = new WatcherImpl(); + mCore = CoreImpl.getInstance(); + } + + /** + * @see MojoTestCase#tearDown() + */ + @After + public void tearDown() throws Exception { + mWatcher.destroy(); + MojoException toThrow = null; + for (Handle handle : mHandlesToClose) { + try { + handle.close(); + } catch (MojoException e) { + if (toThrow == null) { + toThrow = e; + } + } + } + if (toThrow != null) { + throw toThrow; + } + } + + private void addHandlePairToClose(Pair<? extends Handle, ? extends Handle> handles) { + mHandlesToClose.add(handles.first); + mHandlesToClose.add(handles.second); + } + + private static class WatcherResult implements Callback { + private int mResult = Integer.MIN_VALUE; + private MessagePipeHandle mReadPipe; + + /** + * @param readPipe A MessagePipeHandle to read from when onResult triggers success. + */ + public WatcherResult(MessagePipeHandle readPipe) { + mReadPipe = readPipe; + } + public WatcherResult() { + this(null); + } + + /** + * @see Callback#onResult(int) + */ + @Override + public void onResult(int result) { + this.mResult = result; + + if (result == MojoResult.OK && mReadPipe != null) { + mReadPipe.readMessage(MessagePipeHandle.ReadFlags.NONE); + } + } + + /** + * @return the result + */ + public int getResult() { + return mResult; + } + } + + /** + * Testing {@link Watcher} implementation. + */ + @Test + @SmallTest + public void testCorrectResult() { + // Checking a correct result. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + final WatcherResult watcherResult = new WatcherResult(handles.first); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.writeMessage( + ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE); + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(MojoResult.OK, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @Test + @SmallTest + public void testClosingPeerHandle() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.close(); + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(MojoResult.FAILED_PRECONDITION, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @Test + @SmallTest + public void testClosingWatchedHandle() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.first.close(); + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(MojoResult.CANCELLED, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @Test + @SmallTest + public void testInvalidHandle() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.first.close(); + Assert.assertEquals(MojoResult.INVALID_ARGUMENT, + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult)); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @Test + @SmallTest + public void testDefaultInvalidHandle() { + final WatcherResult watcherResult = new WatcherResult(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + Assert.assertEquals(MojoResult.INVALID_ARGUMENT, + mWatcher.start(InvalidHandle.INSTANCE, Core.HandleSignals.READABLE, watcherResult)); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @Test + @SmallTest + public void testCancel() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.cancel(); + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.writeMessage( + ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE); + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @Test + @SmallTest + public void testImmediateCancelOnInvalidHandle() { + // Closing the peer handle. + Pair<MessagePipeHandle, MessagePipeHandle> handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + handles.first.close(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + mWatcher.cancel(); + + mTestRule.runLoopUntilIdle(); + Assert.assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Core.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Core.java new file mode 100644 index 00000000000..40e4be365d0 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Core.java @@ -0,0 +1,183 @@ +// 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. + +package org.chromium.mojo.system; + +/** + * Core mojo interface giving access to the base operations. See |src/mojo/public/c/system/core.h| + * for the underlying api. + */ +public interface Core { + + /** + * Used to indicate an infinite deadline (timeout). + */ + public static final long DEADLINE_INFINITE = -1; + + /** + * Signals for the wait operations on handles. + */ + public static class HandleSignals extends Flags<HandleSignals> { + /** + * Constructor. + * + * @param signals the serialized signals. + */ + public HandleSignals(int signals) { + super(signals); + } + + private static final int FLAG_NONE = 0; + private static final int FLAG_READABLE = 1 << 0; + private static final int FLAG_WRITABLE = 1 << 1; + private static final int FLAG_PEER_CLOSED = 1 << 2; + + /** + * Immutable signals. + */ + public static final HandleSignals NONE = HandleSignals.none().immutable(); + public static final HandleSignals READABLE = + HandleSignals.none().setReadable(true).immutable(); + public static final HandleSignals WRITABLE = + HandleSignals.none().setWritable(true).immutable(); + + /** + * Change the readable bit of this signal. + * + * @param readable the new value of the readable bit. + * @return this. + */ + public HandleSignals setReadable(boolean readable) { + return setFlag(FLAG_READABLE, readable); + } + + /** + * Change the writable bit of this signal. + * + * @param writable the new value of the writable bit. + * @return this. + */ + public HandleSignals setWritable(boolean writable) { + return setFlag(FLAG_WRITABLE, writable); + } + + /** + * Change the peer closed bit of this signal. + * + * @param peerClosed the new value of the peer closed bit. + * @return this. + */ + public HandleSignals setPeerClosed(boolean peerClosed) { + return setFlag(FLAG_PEER_CLOSED, peerClosed); + } + + /** + * Returns a signal with no bit set. + */ + public static HandleSignals none() { + return new HandleSignals(FLAG_NONE); + } + + } + + /** + * Returns a platform-dependent monotonically increasing tick count representing "right now." + */ + public long getTimeTicksNow(); + + /** + * Returned by wait functions to indicate the signaling state of handles. + */ + public static class HandleSignalsState { + /** + * Signals that were satisfied at some time // before the call returned. + */ + private final HandleSignals mSatisfiedSignals; + + /** + * Signals that are possible to satisfy. For example, if the return value was + * |MOJO_RESULT_FAILED_PRECONDITION|, you can use this field to determine which, if any, of + * the signals can still be satisfied. + */ + private final HandleSignals mSatisfiableSignals; + + /** + * Constructor. + */ + public HandleSignalsState( + HandleSignals satisfiedSignals, HandleSignals satisfiableSignals) { + mSatisfiedSignals = satisfiedSignals; + mSatisfiableSignals = satisfiableSignals; + } + + /** + * Returns the satisfiedSignals. + */ + public HandleSignals getSatisfiedSignals() { + return mSatisfiedSignals; + } + + /** + * Returns the satisfiableSignals. + */ + public HandleSignals getSatisfiableSignals() { + return mSatisfiableSignals; + } + } + + /** + * Creates a message pipe, which is a bidirectional communication channel for framed data (i.e., + * messages), with the given options. Messages can contain plain data and/or Mojo handles. + * + * @return the set of handles for the two endpoints (ports) of the message pipe. + */ + public Pair<MessagePipeHandle, MessagePipeHandle> createMessagePipe( + MessagePipeHandle.CreateOptions options); + + /** + * Creates a data pipe, which is a unidirectional communication channel for unframed data, with + * the given options. Data is unframed, but must come as (multiples of) discrete elements, of + * the size given in |options|. See |DataPipe.CreateOptions| for a description of the different + * options available for data pipes. |options| may be set to null for a data pipe with the + * default options (which will have an element size of one byte and have some system-dependent + * capacity). + * + * @return the set of handles for the two endpoints of the data pipe. + */ + public Pair<DataPipe.ProducerHandle, DataPipe.ConsumerHandle> createDataPipe( + DataPipe.CreateOptions options); + + /** + * Creates a buffer that can be shared between applications (by duplicating the handle -- see + * |SharedBufferHandle.duplicate()| -- and passing it over a message pipe). To access the + * buffer, one must call |SharedBufferHandle.map|. + * + * @return the new |SharedBufferHandle|. + */ + public SharedBufferHandle createSharedBuffer(SharedBufferHandle.CreateOptions options, + long numBytes); + + /** + * Acquires a handle from the native side. The handle will be owned by the returned object and + * must not be closed outside of it. + * + * @return a new {@link UntypedHandle} representing the native handle. + */ + public UntypedHandle acquireNativeHandle(int handle); + + /** + * Returns an implementation of {@link Watcher}. + */ + public Watcher getWatcher(); + + /** + * Returns a new run loop. + */ + public RunLoop createDefaultRunLoop(); + + /** + * Returns the current run loop if it exists. + */ + public RunLoop getCurrentRunLoop(); +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java new file mode 100644 index 00000000000..4deaf0975d3 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java @@ -0,0 +1,334 @@ +// 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. + +package org.chromium.mojo.system; + +import java.nio.ByteBuffer; + +/** + * Interface for data pipes. A data pipe is a unidirectional communication channel for unframed + * data. Data is unframed, but must come as (multiples of) discrete elements, of the size given at + * creation time. + */ +public interface DataPipe { + + /** + * Flags for the data pipe creation operation. + */ + public static class CreateFlags extends Flags<CreateFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final CreateFlags NONE = CreateFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected CreateFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static CreateFlags none() { + return new CreateFlags(FLAG_NONE); + } + + } + + /** + * Used to specify creation parameters for a data pipe to |Core.createDataPipe()|. + */ + public static class CreateOptions { + + /** + * Used to specify different modes of operation, see |DataPipe.CreateFlags|. + */ + private CreateFlags mFlags = CreateFlags.none(); + /** + * The size of an element, in bytes. All transactions and buffers will consist of an + * integral number of elements. Must be nonzero. + */ + private int mElementNumBytes; + /** + * The capacity of the data pipe, in number of bytes; must be a multiple of + * |element_num_bytes|. The data pipe will always be able to queue AT LEAST this much data. + * Set to zero to opt for a system-dependent automatically-calculated capacity (which will + * always be at least one element). + */ + private int mCapacityNumBytes; + + /** + * @return the flags + */ + public CreateFlags getFlags() { + return mFlags; + } + + /** + * @return the elementNumBytes + */ + public int getElementNumBytes() { + return mElementNumBytes; + } + + /** + * @param elementNumBytes the elementNumBytes to set + */ + public void setElementNumBytes(int elementNumBytes) { + mElementNumBytes = elementNumBytes; + } + + /** + * @return the capacityNumBytes + */ + public int getCapacityNumBytes() { + return mCapacityNumBytes; + } + + /** + * @param capacityNumBytes the capacityNumBytes to set + */ + public void setCapacityNumBytes(int capacityNumBytes) { + mCapacityNumBytes = capacityNumBytes; + } + + } + + /** + * Flags for the write operations on MessagePipeHandle . + */ + public static class WriteFlags extends Flags<WriteFlags> { + private static final int FLAG_NONE = 0; + private static final int FLAG_ALL_OR_NONE = 1 << 0; + + /** + * Immutable flag with not bit set. + */ + public static final WriteFlags NONE = WriteFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + private WriteFlags(int flags) { + super(flags); + } + + /** + * Change the all-or-none bit of those flags. If set, write either all the elements + * requested or none of them. + * + * @param allOrNone the new value of all-or-none bit. + * @return this. + */ + public WriteFlags setAllOrNone(boolean allOrNone) { + return setFlag(FLAG_ALL_OR_NONE, allOrNone); + } + + /** + * @return a flag with no bit set. + */ + public static WriteFlags none() { + return new WriteFlags(FLAG_NONE); + } + } + + /** + * Flags for the read operations on MessagePipeHandle. + */ + public static class ReadFlags extends Flags<ReadFlags> { + private static final int FLAG_NONE = 0; + private static final int FLAG_ALL_OR_NONE = 1 << 0; + private static final int FLAG_QUERY = 1 << 2; + private static final int FLAG_PEEK = 1 << 3; + + /** + * Immutable flag with not bit set. + */ + public static final ReadFlags NONE = ReadFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flag. + */ + private ReadFlags(int flags) { + super(flags); + } + + /** + * Change the all-or-none bit of this flag. If set, read (or discard) either the requested + * number of elements or none. + * + * @param allOrNone the new value of the all-or-none bit. + * @return this. + */ + public ReadFlags setAllOrNone(boolean allOrNone) { + return setFlag(FLAG_ALL_OR_NONE, allOrNone); + } + + /** + * Change the query bit of this flag. If set query the number of elements available to read. + * Mutually exclusive with |discard| and |allOrNone| is ignored if this flag is set. + * + * @param query the new value of the query bit. + * @return this. + */ + public ReadFlags query(boolean query) { + return setFlag(FLAG_QUERY, query); + } + + /** + * Change the peek bit of this flag. If set, read the requested number of elements, and + * leave those elements in the pipe. A later read will return the same data. + * Mutually exclusive with |discard| and |query|. + * + * @param peek the new value of the peek bit. + * @return this. + */ + public ReadFlags peek(boolean peek) { + return setFlag(FLAG_PEEK, peek); + } + + /** + * @return a flag with no bit set. + */ + public static ReadFlags none() { + return new ReadFlags(FLAG_NONE); + } + + } + + /** + * Handle for the producer part of a data pipe. + */ + public static interface ProducerHandle extends Handle { + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public ProducerHandle pass(); + + /** + * Writes the given data to the data pipe producer. |elements| points to data; the buffer + * must be a direct ByteBuffer and the limit should be a multiple of the data pipe's element + * size. If |allOrNone| is set in |flags|, either all the data will be written or none is. + * <p> + * On success, returns the amount of data that was actually written. + * <p> + * Note: If the data pipe has the "may discard" option flag (specified on creation), this + * will discard as much data as required to write the given data, starting with the earliest + * written data that has not been consumed. However, even with "may discard", if the buffer + * limit is greater than the data pipe's capacity (and |allOrNone| is not set), this will + * write the maximum amount possible (namely, the data pipe's capacity) and return that + * amount. It will *not* discard data from |elements|. + * + * @return number of written bytes. + */ + public ResultAnd<Integer> writeData(ByteBuffer elements, WriteFlags flags); + + /** + * Begins a two-phase write to the data pipe producer . On success, returns a |ByteBuffer| + * to which the caller can write. If flags has |allOrNone| set, then the buffer capacity + * will be at least as large as |numBytes|, which must also be a multiple of the element + * size (if |allOrNone| is not set, |numBytes| is ignored and the caller must check the + * capacity of the buffer). + * <p> + * During a two-phase write, this handle is *not* writable. E.g., if another thread tries to + * write to it, it will throw a |MojoException| with code |MojoResult.BUSY|; that thread can + * then wait for this handle to become writable again. + * <p> + * Once the caller has finished writing data to the buffer, it should call |endWriteData()| + * to specify the amount written and to complete the two-phase write. + * <p> + * Note: If the data pipe has the "may discard" option flag (specified on creation) and + * |flags| has |allOrNone| set, this may discard some data. + * + * @return The buffer to write to. + */ + public ByteBuffer beginWriteData(int numBytes, WriteFlags flags); + + /** + * Ends a two-phase write to the data pipe producer that was begun by a call to + * |beginWriteData()| on the same handle. |numBytesWritten| should indicate the amount of + * data actually written; it must be less than or equal to the capacity of the buffer + * returned by |beginWriteData()| and must be a multiple of the element size. The buffer + * returned from |beginWriteData()| must have been filled with exactly |numBytesWritten| + * bytes of data. + * <p> + * On failure, the two-phase write (if any) is ended (so the handle may become writable + * again, if there's space available) but no data written to the buffer is "put into" the + * data pipe. + */ + public void endWriteData(int numBytesWritten); + } + + /** + * Handle for the consumer part of a data pipe. + */ + public static interface ConsumerHandle extends Handle { + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public ConsumerHandle pass(); + + /** + * Discards data on the data pie consumer. This method discards up to |numBytes| (which + * again be a multiple of the element size) bytes of data, returning the amount actually + * discarded. if |flags| has |allOrNone|, it will either discard exactly |numBytes| bytes of + * data or none. In this case, |query| must not be set. + */ + public int discardData(int numBytes, ReadFlags flags); + + /** + * Reads data from the data pipe consumer. May also be used to query the amount of data + * available. If |flags| has not |query| set, this tries to read up to |elements| capacity + * (which must be a multiple of the data pipe's element size) bytes of data to |elements| + * and returns the amount actually read. |elements| must be a direct ByteBuffer. If flags + * has |allOrNone| set, it will either read exactly |elements| capacity bytes of data or + * none. + * <p> + * If flags has |query| set, it queries the amount of data available, returning the number + * of bytes available. In this case |allOrNone| is ignored, as are |elements|. + */ + public ResultAnd<Integer> readData(ByteBuffer elements, ReadFlags flags); + + /** + * Begins a two-phase read from the data pipe consumer. On success, returns a |ByteBuffer| + * from which the caller can read up to its limit bytes of data. If flags has |allOrNone| + * set, then the limit will be at least as large as |numBytes|, which must also be a + * multiple of the element size (if |allOrNone| is not set, |numBytes| is ignored). |flags| + * must not have |query| set. + * <p> + * During a two-phase read, this handle is *not* readable. E.g., if another thread tries to + * read from it, it will throw a |MojoException| with code |MojoResult.BUSY|; that thread + * can then wait for this handle to become readable again. + * <p> + * Once the caller has finished reading data from the buffer, it should call |endReadData()| + * to specify the amount read and to complete the two-phase read. + */ + public ByteBuffer beginReadData(int numBytes, ReadFlags flags); + + /** + * Ends a two-phase read from the data pipe consumer that was begun by a call to + * |beginReadData()| on the same handle. |numBytesRead| should indicate the amount of data + * actually read; it must be less than or equal to the limit of the buffer returned by + * |beginReadData()| and must be a multiple of the element size. + * <p> + * On failure, the two-phase read (if any) is ended (so the handle may become readable + * again) but no data is "removed" from the data pipe. + */ + public void endReadData(int numBytesRead); + } + +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java new file mode 100644 index 00000000000..30ff07f7100 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java @@ -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. + +package org.chromium.mojo.system; + +/** + * Base class for bit field used as flags. + * + * @param <F> the type of the flags. + */ +public abstract class Flags<F extends Flags<F>> { + private int mFlags; + private boolean mImmutable; + + /** + * Dedicated constructor. + * + * @param flags initial value of the flag. + */ + protected Flags(int flags) { + mImmutable = false; + mFlags = flags; + } + + /** + * @return the computed flag. + */ + public int getFlags() { + return mFlags; + } + + /** + * Change the given bit of this flag. + * + * @param value the new value of given bit. + * @return this. + */ + protected F setFlag(int flag, boolean value) { + if (mImmutable) { + throw new UnsupportedOperationException("Flags is immutable."); + } + if (value) { + mFlags |= flag; + } else { + mFlags &= ~flag; + } + @SuppressWarnings("unchecked") + F f = (F) this; + return f; + } + + /** + * Makes this flag immutable. This is a non-reversable operation. + */ + protected F immutable() { + mImmutable = true; + @SuppressWarnings("unchecked") + F f = (F) this; + return f; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + return mFlags; + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Flags<?> other = (Flags<?>) obj; + if (mFlags != other.mFlags) return false; + return true; + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java new file mode 100644 index 00000000000..903f36d6776 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java @@ -0,0 +1,61 @@ +// 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. + +package org.chromium.mojo.system; + +import org.chromium.mojo.system.Core.HandleSignalsState; + +import java.io.Closeable; + +/** + * A generic mojo handle. + */ +public interface Handle extends Closeable { + + /** + * Closes the given |handle|. + * <p> + * Concurrent operations on |handle| may succeed (or fail as usual) if they happen before the + * close, be cancelled with result |MojoResult.CANCELLED| if they properly overlap (this is + * likely the case with |wait()|, etc.), or fail with |MojoResult.INVALID_ARGUMENT| if they + * happen after. + */ + @Override + public void close(); + + /** + * @return the last known signaling state of the handle. + */ + public HandleSignalsState querySignalsState(); + + /** + * @return whether the handle is valid. A handle is valid until it has been explicitly closed or + * send through a message pipe via |MessagePipeHandle.writeMessage|. + */ + public boolean isValid(); + + /** + * Converts this handle into an {@link UntypedHandle}, invalidating this handle. + */ + public UntypedHandle toUntypedHandle(); + + /** + * Returns the {@link Core} implementation for this handle. Can be null if this handle is + * invalid. + */ + public Core getCore(); + + /** + * Passes ownership of the handle from this handle to the newly created Handle object, + * invalidating this handle object in the process. + */ + public Handle pass(); + + /** + * Releases the native handle backed by this {@link Handle}. The caller owns the handle and must + * close it. + */ + public int releaseNativeHandle(); + +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java new file mode 100644 index 00000000000..52926055886 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java @@ -0,0 +1,218 @@ +// 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. + +package org.chromium.mojo.system; + +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * A handle that will always be invalid. + */ +public class InvalidHandle implements UntypedHandle, MessagePipeHandle, ConsumerHandle, + ProducerHandle, SharedBufferHandle { + + /** + * Instance singleton. + */ + public static final InvalidHandle INSTANCE = new InvalidHandle(); + + /** + * Private constructor. + */ + private InvalidHandle() { + } + + /** + * @see Handle#close() + */ + @Override + public void close() { + // Do nothing. + } + + /** + * @see Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see Handle#isValid() + */ + @Override + public boolean isValid() { + return false; + } + + /** + * @see Handle#getCore() + */ + @Override + public Core getCore() { + return null; + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public InvalidHandle pass() { + return this; + } + + /** + * @see Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return this; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + return 0; + } + + /** + * @see UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return this; + } + + /** + * @see UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return this; + } + + /** + * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see SharedBufferHandle#unmap(java.nio.ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags) + */ + @Override + public ResultAnd<Integer> writeData(ByteBuffer elements, DataPipe.WriteFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, + DataPipe.WriteFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + @Override + public int discardData(int numBytes, DataPipe.ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags) + */ + @Override + public ResultAnd<Integer> readData(ByteBuffer elements, DataPipe.ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, + DataPipe.ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see DataPipe.ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List, + * MessagePipeHandle.WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + + /** + * @see MessagePipeHandle#readMessage(MessagePipeHandle.ReadFlags) + */ + @Override + public ResultAnd<ReadMessageResult> readMessage(ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java new file mode 100644 index 00000000000..baa5a9549e7 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java @@ -0,0 +1,166 @@ +// 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. + +package org.chromium.mojo.system; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Message pipes are bidirectional communication channel for framed data (i.e., messages). Messages + * can contain plain data and/or Mojo handles. + */ +public interface MessagePipeHandle extends Handle { + + /** + * Flags for the message pipe creation operation. + */ + public static class CreateFlags extends Flags<CreateFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final CreateFlags NONE = CreateFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected CreateFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static CreateFlags none() { + return new CreateFlags(FLAG_NONE); + } + + } + + /** + * Used to specify creation parameters for a message pipe to |Core#createMessagePipe()|. + */ + public static class CreateOptions { + private CreateFlags mFlags = CreateFlags.NONE; + + /** + * @return the flags + */ + public CreateFlags getFlags() { + return mFlags; + } + + } + + /** + * Flags for the write operations on MessagePipeHandle . + */ + public static class WriteFlags extends Flags<WriteFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with no bit set. + */ + public static final WriteFlags NONE = WriteFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flag. + */ + private WriteFlags(int flags) { + super(flags); + } + + /** + * @return a flag with no bit set. + */ + public static WriteFlags none() { + return new WriteFlags(FLAG_NONE); + } + } + + /** + * Flags for the read operations on MessagePipeHandle. + */ + public static class ReadFlags extends Flags<ReadFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with no bit set. + */ + public static final ReadFlags NONE = ReadFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flag. + */ + private ReadFlags(int flags) { + super(flags); + } + + /** + * @return a flag with no bit set. + */ + public static ReadFlags none() { + return new ReadFlags(FLAG_NONE); + } + + } + + /** + * Result of the |readMessage| method. + */ + public static class ReadMessageResult { + /** + * If a message was read, this contains the bytes of its data. + */ + public byte[] mData; + /** + * If a message was read, this contains the raw handle values. + */ + public int[] mRawHandles; + /** + * If a message was read, the handles contained in the message, undefined otherwise. + */ + public List<UntypedHandle> mHandles; + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public MessagePipeHandle pass(); + + /** + * Writes a message to the message pipe endpoint, with message data specified by |bytes| and + * attached handles specified by |handles|, and options specified by |flags|. If there is no + * message data, |bytes| may be null, otherwise it must be a direct ByteBuffer. If there are no + * attached handles, |handles| may be null. + * <p> + * If handles are attached, on success the handles will no longer be valid (the receiver will + * receive equivalent, but logically different, handles). Handles to be sent should not be in + * simultaneous use (e.g., on another thread). + */ + void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags); + + /** + * Reads a message from the message pipe endpoint; also usable to query the size of the next + * message or discard the next message. |bytes| indicate the buffer/buffer size to receive the + * message data (if any) and |maxNumberOfHandles| indicate the maximum handle count to receive + * the attached handles (if any). |bytes| is optional. If null, |maxNumberOfHandles| must be + * zero, and the return value will indicate the size of the next message. If non-null, it must + * be a direct ByteBuffer and the return value will indicate if the message was read or not. If + * the message was read its content will be in |bytes|, and the attached handles will be in the + * return value. Partial reads are NEVER done. Either a full read is done and |wasMessageRead| + * will be true, or the read is NOT done and |wasMessageRead| will be false (if |mayDiscard| was + * set, the message is also discarded in this case). + */ + ResultAnd<ReadMessageResult> readMessage(ReadFlags flags); +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java new file mode 100644 index 00000000000..4e0e3e95971 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java @@ -0,0 +1,44 @@ +// 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. + +package org.chromium.mojo.system; + +/** + * Exception for the core mojo API. + */ +public class MojoException extends RuntimeException { + + private final int mCode; + + /** + * Constructor. + */ + public MojoException(int code) { + mCode = code; + } + + /** + * Constructor. + */ + public MojoException(Throwable cause) { + super(cause); + mCode = MojoResult.UNKNOWN; + } + + /** + * The mojo result code associated with this exception. See {@link MojoResult} for possible + * values. + */ + public int getMojoResult() { + return mCode; + } + + /** + * @see Object#toString() + */ + @Override + public String toString() { + return "MojoResult(" + mCode + "): " + MojoResult.describe(mCode); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java new file mode 100644 index 00000000000..2602cb5e1e3 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java @@ -0,0 +1,82 @@ +// 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. + +package org.chromium.mojo.system; + +/** + * The different mojo result codes. + */ +public final class MojoResult { + public static final int OK = 0; + public static final int CANCELLED = 1; + public static final int UNKNOWN = 2; + public static final int INVALID_ARGUMENT = 3; + public static final int DEADLINE_EXCEEDED = 4; + public static final int NOT_FOUND = 5; + public static final int ALREADY_EXISTS = 6; + public static final int PERMISSION_DENIED = 7; + public static final int RESOURCE_EXHAUSTED = 8; + public static final int FAILED_PRECONDITION = 9; + public static final int ABORTED = 10; + public static final int OUT_OF_RANGE = 11; + public static final int UNIMPLEMENTED = 12; + public static final int INTERNAL = 13; + public static final int UNAVAILABLE = 14; + public static final int DATA_LOSS = 15; + public static final int BUSY = 16; + public static final int SHOULD_WAIT = 17; + + /** + * never instantiate. + */ + private MojoResult() { + } + + /** + * Describes the given result code. + */ + public static String describe(int mCode) { + switch (mCode) { + case OK: + return "OK"; + case CANCELLED: + return "CANCELLED"; + case UNKNOWN: + return "UNKNOWN"; + case INVALID_ARGUMENT: + return "INVALID_ARGUMENT"; + case DEADLINE_EXCEEDED: + return "DEADLINE_EXCEEDED"; + case NOT_FOUND: + return "NOT_FOUND"; + case ALREADY_EXISTS: + return "ALREADY_EXISTS"; + case PERMISSION_DENIED: + return "PERMISSION_DENIED"; + case RESOURCE_EXHAUSTED: + return "RESOURCE_EXHAUSTED"; + case FAILED_PRECONDITION: + return "FAILED_PRECONDITION"; + case ABORTED: + return "ABORTED"; + case OUT_OF_RANGE: + return "OUT_OF_RANGE"; + case UNIMPLEMENTED: + return "UNIMPLEMENTED"; + case INTERNAL: + return "INTERNAL"; + case UNAVAILABLE: + return "UNAVAILABLE"; + case DATA_LOSS: + return "DATA_LOSS"; + case BUSY: + return "BUSY"; + case SHOULD_WAIT: + return "SHOULD_WAIT"; + default: + return "UNKNOWN"; + } + + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java new file mode 100644 index 00000000000..2ead0204f9c --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java @@ -0,0 +1,67 @@ +// 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. + +package org.chromium.mojo.system; + + +/** + * A pair of object. + * + * @param <F> Type of the first element. + * @param <S> Type of the second element. + */ +public class Pair<F, S> { + + public final F first; + public final S second; + + /** + * Dedicated constructor. + * + * @param first the first element of the pair. + * @param second the second element of the pair. + */ + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + /** + * equals() that handles null values. + */ + private boolean equals(Object o1, Object o2) { + return o1 == null ? o2 == null : o1.equals(o2); + } + + /** + * @see Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof Pair)) { + return false; + } + Pair<?, ?> p = (Pair<?, ?>) o; + return equals(first, p.first) && equals(second, p.second); + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + } + + /** + * Helper method for creating a pair. + * + * @param a the first element of the pair. + * @param b the second element of the pair. + * @return the pair containing a and b. + */ + public static <A, B> Pair<A, B> create(A a, B b) { + return new Pair<A, B>(a, b); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java new file mode 100644 index 00000000000..656d0d64e49 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java @@ -0,0 +1,34 @@ +// 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. + +package org.chromium.mojo.system; + +/** + * Container that contains a mojo result and a value. + * + * @param <A> the type of the value. + */ +public class ResultAnd<A> { + private final int mMojoResult; + private final A mValue; + + public ResultAnd(int result, A value) { + this.mMojoResult = result; + this.mValue = value; + } + + /** + * Returns the mojo result. + */ + public int getMojoResult() { + return mMojoResult; + } + + /** + * Returns the value. + */ + public A getValue() { + return mValue; + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java new file mode 100644 index 00000000000..4038b2954e5 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java @@ -0,0 +1,41 @@ +// 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. + +package org.chromium.mojo.system; + +import java.io.Closeable; + +/** + * Definition of a run loop. + */ +public interface RunLoop extends Closeable { + /** + * Start the run loop. It will continue until quit() is called. + */ + public void run(); + + /** + * Start the run loop and stop it as soon as no task is present in the work queue. + */ + public void runUntilIdle(); + + /* + * Quit the currently running run loop. + */ + public void quit(); + + /** + * Add a runnable to the queue of tasks. + * @param runnable Callback to be executed by the run loop. + * @param delay Delay, in MojoTimeTicks (microseconds) before the callback should + * be executed. + */ + public void postDelayedTask(Runnable runnable, long delay); + + /** + * Destroy the run loop and deregister it from Core. + */ + @Override + public abstract void close(); +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java new file mode 100644 index 00000000000..df317d134d5 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java @@ -0,0 +1,160 @@ +// 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. + +package org.chromium.mojo.system; + +import java.nio.ByteBuffer; + +/** + * A buffer that can be shared between applications. + */ +public interface SharedBufferHandle extends Handle { + + /** + * Flags for the shared buffer creation operation. + */ + public static class CreateFlags extends Flags<CreateFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final CreateFlags NONE = CreateFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected CreateFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static CreateFlags none() { + return new CreateFlags(FLAG_NONE); + } + + } + + /** + * Used to specify creation parameters for a shared buffer to |Core#createSharedBuffer()|. + */ + public static class CreateOptions { + private CreateFlags mFlags = CreateFlags.NONE; + + /** + * @return the flags + */ + public CreateFlags getFlags() { + return mFlags; + } + + } + + /** + * Flags for the shared buffer duplication operation. + */ + public static class DuplicateFlags extends Flags<DuplicateFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final DuplicateFlags NONE = DuplicateFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected DuplicateFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static DuplicateFlags none() { + return new DuplicateFlags(FLAG_NONE); + } + + } + + /** + * Used to specify parameters in duplicating access to a shared buffer to + * |SharedBufferHandle#duplicate| + */ + public static class DuplicateOptions { + private DuplicateFlags mFlags = DuplicateFlags.NONE; + + /** + * @return the flags + */ + public DuplicateFlags getFlags() { + return mFlags; + } + + } + + /** + * Flags for the shared buffer map operation. + */ + public static class MapFlags extends Flags<MapFlags> { + private static final int FLAG_NONE = 0; + + /** + * Immutable flag with not bit set. + */ + public static final MapFlags NONE = MapFlags.none().immutable(); + + /** + * Dedicated constructor. + * + * @param flags initial value of the flags. + */ + protected MapFlags(int flags) { + super(flags); + } + + /** + * @return flags with no bit set. + */ + public static MapFlags none() { + return new MapFlags(FLAG_NONE); + } + + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public SharedBufferHandle pass(); + + /** + * Duplicates the handle. This creates another handle (returned on success), which can then be + * sent to another application over a message pipe, while retaining access to this handle (and + * any mappings that it may have). + */ + public SharedBufferHandle duplicate(DuplicateOptions options); + + /** + * Map the part (at offset |offset| of length |numBytes|) of the buffer given by this handle + * into memory. |offset + numBytes| must be less than or equal to the size of the buffer. On + * success, the returned buffer points to memory with the requested part of the buffer. A single + * buffer handle may have multiple active mappings (possibly depending on the buffer type). The + * permissions (e.g., writable or executable) of the returned memory may depend on the + * properties of the buffer and properties attached to the buffer handle as well as |flags|. + */ + public ByteBuffer map(long offset, long numBytes, MapFlags flags); + + /** + * Unmap a buffer pointer that was mapped by |map()|. + */ + public void unmap(ByteBuffer buffer); + +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java new file mode 100644 index 00000000000..199b0a1c04a --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java @@ -0,0 +1,45 @@ +// 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. + +package org.chromium.mojo.system; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; + +/** + * A mojo handle of unknown type. This handle can be typed by using one of its methods, which will + * return a handle of the requested type and invalidate this object. No validation is made when the + * conversion operation is called. + */ +public interface UntypedHandle extends Handle { + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public UntypedHandle pass(); + + /** + * Returns the underlying handle, as a {@link MessagePipeHandle}, invalidating this + * representation. + */ + public MessagePipeHandle toMessagePipeHandle(); + + /** + * Returns the underlying handle, as a {@link ConsumerHandle}, invalidating this representation. + */ + public ConsumerHandle toDataPipeConsumerHandle(); + + /** + * Returns the underlying handle, as a {@link ProducerHandle}, invalidating this representation. + */ + public ProducerHandle toDataPipeProducerHandle(); + + /** + * Returns the underlying handle, as a {@link SharedBufferHandle}, invalidating this + * representation. + */ + public SharedBufferHandle toSharedBufferHandle(); + +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java new file mode 100644 index 00000000000..9c701610674 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java @@ -0,0 +1,38 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system; + +import org.chromium.mojo.system.Core.HandleSignals; + +/** + * Watches a handle for signals being satisfied. + */ +public interface Watcher { + /** + * Callback passed to {@link Watcher#start}. + */ + public interface Callback { + /** + * Called when the handle is ready. + */ + public void onResult(int result); + } + + /** + * Starts watching a handle. + */ + int start(Handle handle, HandleSignals signals, Callback callback); + + /** + * Cancels an already-started watch. + */ + void cancel(); + + /** + * Destroys the underlying implementation. Other methods will fail after destroy has been + * called. + */ + void destroy(); +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java new file mode 100644 index 00000000000..8295906f3ac --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java @@ -0,0 +1,74 @@ +// 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.mojo.system.RunLoop; + +/** + * Implementation of {@link RunLoop} suitable for the base:: message loop implementation. + */ +@JNINamespace("mojo::android") +class BaseRunLoop implements RunLoop { + /** + * Pointer to the C run loop. + */ + private long mRunLoopID; + private final CoreImpl mCore; + + BaseRunLoop(CoreImpl core) { + this.mCore = core; + this.mRunLoopID = nativeCreateBaseRunLoop(); + } + + @Override + public void run() { + assert mRunLoopID != 0 : "The run loop cannot run once closed"; + nativeRun(); + } + + @Override + public void runUntilIdle() { + assert mRunLoopID != 0 : "The run loop cannot run once closed"; + nativeRunUntilIdle(); + } + + @Override + public void quit() { + assert mRunLoopID != 0 : "The run loop cannot be quitted run once closed"; + nativeQuit(); + } + + @Override + public void postDelayedTask(Runnable runnable, long delay) { + assert mRunLoopID != 0 : "The run loop cannot run tasks once closed"; + nativePostDelayedTask(mRunLoopID, runnable, delay); + } + + @Override + public void close() { + if (mRunLoopID == 0) { + return; + } + // We don't want to de-register a different run loop! + assert mCore.getCurrentRunLoop() == this : "Only the current run loop can be closed"; + mCore.clearCurrentRunLoop(); + nativeDeleteMessageLoop(mRunLoopID); + mRunLoopID = 0; + } + + @CalledByNative + private static void runRunnable(Runnable runnable) { + runnable.run(); + } + + private native long nativeCreateBaseRunLoop(); + private native void nativeRun(); + private native void nativeRunUntilIdle(); + private native void nativeQuit(); + private native void nativePostDelayedTask(long runLoopID, Runnable runnable, long delay); + private native void nativeDeleteMessageLoop(long runLoopID); +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java new file mode 100644 index 00000000000..6ddac5b99b0 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/CoreImpl.java @@ -0,0 +1,515 @@ +// 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.RunLoop; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.SharedBufferHandle.DuplicateOptions; +import org.chromium.mojo.system.SharedBufferHandle.MapFlags; +import org.chromium.mojo.system.UntypedHandle; +import org.chromium.mojo.system.Watcher; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link Core}. + */ +@JNINamespace("mojo::android") +@MainDex +public class CoreImpl implements Core { + /** + * Discard flag for the |MojoReadData| operation. + */ + private static final int MOJO_READ_DATA_FLAG_DISCARD = 1 << 1; + + /** + * the size of a handle, in bytes. + */ + private static final int HANDLE_SIZE = 4; + + /** + * the size of a flag, in bytes. + */ + private static final int FLAG_SIZE = 4; + + /** + * The mojo handle for an invalid handle. + */ + static final int INVALID_HANDLE = 0; + + private static class LazyHolder { private static final Core INSTANCE = new CoreImpl(); } + + /** + * The run loop for the current thread. + */ + private final ThreadLocal<BaseRunLoop> mCurrentRunLoop = new ThreadLocal<BaseRunLoop>(); + + /** + * The offset needed to get an aligned buffer. + */ + private final int mByteBufferOffset; + + /** + * @return the instance. + */ + public static Core getInstance() { + return LazyHolder.INSTANCE; + } + + private CoreImpl() { + // Fix for the ART runtime, before: + // https://android.googlesource.com/platform/libcore/+/fb6c80875a8a8d0a9628562f89c250b6a962e824%5E!/ + // This assumes consistent allocation. + mByteBufferOffset = nativeGetNativeBufferOffset(ByteBuffer.allocateDirect(8), 8); + } + + /** + * @see Core#getTimeTicksNow() + */ + @Override + public long getTimeTicksNow() { + return nativeGetTimeTicksNow(); + } + + /** + * @see Core#createMessagePipe(MessagePipeHandle.CreateOptions) + */ + @Override + public Pair<MessagePipeHandle, MessagePipeHandle> createMessagePipe( + MessagePipeHandle.CreateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd<IntegerPair> result = nativeCreateMessagePipe(optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return Pair.<MessagePipeHandle, MessagePipeHandle>create( + new MessagePipeHandleImpl(this, result.getValue().first), + new MessagePipeHandleImpl(this, result.getValue().second)); + } + + /** + * @see Core#createDataPipe(DataPipe.CreateOptions) + */ + @Override + public Pair<ProducerHandle, ConsumerHandle> createDataPipe(DataPipe.CreateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(16); + optionsBuffer.putInt(0, 16); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + optionsBuffer.putInt(8, options.getElementNumBytes()); + optionsBuffer.putInt(12, options.getCapacityNumBytes()); + } + ResultAnd<IntegerPair> result = nativeCreateDataPipe(optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return Pair.<ProducerHandle, ConsumerHandle>create( + new DataPipeProducerHandleImpl(this, result.getValue().first), + new DataPipeConsumerHandleImpl(this, result.getValue().second)); + } + + /** + * @see Core#createSharedBuffer(SharedBufferHandle.CreateOptions, long) + */ + @Override + public SharedBufferHandle createSharedBuffer( + SharedBufferHandle.CreateOptions options, long numBytes) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd<Integer> result = nativeCreateSharedBuffer(optionsBuffer, numBytes); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return new SharedBufferHandleImpl(this, result.getValue()); + } + + /** + * @see org.chromium.mojo.system.Core#acquireNativeHandle(int) + */ + @Override + public UntypedHandle acquireNativeHandle(int handle) { + return new UntypedHandleImpl(this, handle); + } + + /** + * @see Core#getWatcher() + */ + @Override + public Watcher getWatcher() { + return new WatcherImpl(); + } + + /** + * @see Core#createDefaultRunLoop() + */ + @Override + public RunLoop createDefaultRunLoop() { + if (mCurrentRunLoop.get() != null) { + throw new MojoException(MojoResult.FAILED_PRECONDITION); + } + BaseRunLoop runLoop = new BaseRunLoop(this); + mCurrentRunLoop.set(runLoop); + return runLoop; + } + + /** + * @see Core#getCurrentRunLoop() + */ + @Override + public RunLoop getCurrentRunLoop() { + return mCurrentRunLoop.get(); + } + + /** + * Remove the current run loop. + */ + void clearCurrentRunLoop() { + mCurrentRunLoop.remove(); + } + + int closeWithResult(int mojoHandle) { + return nativeClose(mojoHandle); + } + + void close(int mojoHandle) { + int mojoResult = nativeClose(mojoHandle); + if (mojoResult != MojoResult.OK) { + throw new MojoException(mojoResult); + } + } + + HandleSignalsState queryHandleSignalsState(int mojoHandle) { + ByteBuffer buffer = allocateDirectBuffer(8); + int result = nativeQueryHandleSignalsState(mojoHandle, buffer); + if (result != MojoResult.OK) throw new MojoException(result); + return new HandleSignalsState( + new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4))); + } + + /** + * @see MessagePipeHandle#writeMessage(ByteBuffer, List, MessagePipeHandle.WriteFlags) + */ + void writeMessage(MessagePipeHandleImpl pipeHandle, ByteBuffer bytes, + List<? extends Handle> handles, MessagePipeHandle.WriteFlags flags) { + ByteBuffer handlesBuffer = null; + if (handles != null && !handles.isEmpty()) { + handlesBuffer = allocateDirectBuffer(handles.size() * HANDLE_SIZE); + for (Handle handle : handles) { + handlesBuffer.putInt(getMojoHandle(handle)); + } + handlesBuffer.position(0); + } + int mojoResult = nativeWriteMessage(pipeHandle.getMojoHandle(), bytes, + bytes == null ? 0 : bytes.limit(), handlesBuffer, flags.getFlags()); + if (mojoResult != MojoResult.OK) { + throw new MojoException(mojoResult); + } + // Success means the handles have been invalidated. + if (handles != null) { + for (Handle handle : handles) { + if (handle.isValid()) { + ((HandleBase) handle).invalidateHandle(); + } + } + } + } + + /** + * @see MessagePipeHandle#readMessage(MessagePipeHandle.ReadFlags) + */ + ResultAnd<MessagePipeHandle.ReadMessageResult> readMessage( + MessagePipeHandleImpl handle, MessagePipeHandle.ReadFlags flags) { + ResultAnd<MessagePipeHandle.ReadMessageResult> result = + nativeReadMessage(handle.getMojoHandle(), flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK + && result.getMojoResult() != MojoResult.SHOULD_WAIT) { + throw new MojoException(result.getMojoResult()); + } + + MessagePipeHandle.ReadMessageResult readResult = result.getValue(); + int[] rawHandles = readResult.mRawHandles; + if (rawHandles != null && rawHandles.length != 0) { + readResult.mHandles = new ArrayList<UntypedHandle>(rawHandles.length); + for (int rawHandle : rawHandles) { + readResult.mHandles.add(new UntypedHandleImpl(this, rawHandle)); + } + } else { + readResult.mHandles = new ArrayList<UntypedHandle>(0); + } + + return result; + } + + /** + * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + int discardData(DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) { + ResultAnd<Integer> result = nativeReadData(handle.getMojoHandle(), null, numBytes, + flags.getFlags() | MOJO_READ_DATA_FLAG_DISCARD); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see ConsumerHandle#readData(ByteBuffer, DataPipe.ReadFlags) + */ + ResultAnd<Integer> readData( + DataPipeConsumerHandleImpl handle, ByteBuffer elements, DataPipe.ReadFlags flags) { + ResultAnd<Integer> result = nativeReadData(handle.getMojoHandle(), elements, + elements == null ? 0 : elements.capacity(), flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK + && result.getMojoResult() != MojoResult.SHOULD_WAIT) { + throw new MojoException(result.getMojoResult()); + } + if (result.getMojoResult() == MojoResult.OK) { + if (elements != null) { + elements.limit(result.getValue()); + } + } + return result; + } + + /** + * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + ByteBuffer beginReadData( + DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) { + ResultAnd<ByteBuffer> result = + nativeBeginReadData(handle.getMojoHandle(), numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue().asReadOnlyBuffer(); + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + void endReadData(DataPipeConsumerHandleImpl handle, int numBytesRead) { + int result = nativeEndReadData(handle.getMojoHandle(), numBytesRead); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @see ProducerHandle#writeData(ByteBuffer, DataPipe.WriteFlags) + */ + ResultAnd<Integer> writeData( + DataPipeProducerHandleImpl handle, ByteBuffer elements, DataPipe.WriteFlags flags) { + return nativeWriteData( + handle.getMojoHandle(), elements, elements.limit(), flags.getFlags()); + } + + /** + * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + ByteBuffer beginWriteData( + DataPipeProducerHandleImpl handle, int numBytes, DataPipe.WriteFlags flags) { + ResultAnd<ByteBuffer> result = + nativeBeginWriteData(handle.getMojoHandle(), numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + void endWriteData(DataPipeProducerHandleImpl handle, int numBytesWritten) { + int result = nativeEndWriteData(handle.getMojoHandle(), numBytesWritten); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @see SharedBufferHandle#duplicate(DuplicateOptions) + */ + SharedBufferHandle duplicate(SharedBufferHandleImpl handle, DuplicateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd<Integer> result = nativeDuplicate(handle.getMojoHandle(), optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return new SharedBufferHandleImpl(this, result.getValue()); + } + + /** + * @see SharedBufferHandle#map(long, long, MapFlags) + */ + ByteBuffer map(SharedBufferHandleImpl handle, long offset, long numBytes, MapFlags flags) { + ResultAnd<ByteBuffer> result = + nativeMap(handle.getMojoHandle(), offset, numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see SharedBufferHandle#unmap(ByteBuffer) + */ + void unmap(ByteBuffer buffer) { + int result = nativeUnmap(buffer); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @return the mojo handle associated to the given handle, considering invalid handles. + */ + private int getMojoHandle(Handle handle) { + if (handle.isValid()) { + return ((HandleBase) handle).getMojoHandle(); + } + return 0; + } + + private static boolean isUnrecoverableError(int code) { + switch (code) { + case MojoResult.OK: + case MojoResult.DEADLINE_EXCEEDED: + case MojoResult.CANCELLED: + case MojoResult.FAILED_PRECONDITION: + return false; + default: + return true; + } + } + + private static int filterMojoResultForWait(int code) { + if (isUnrecoverableError(code)) { + throw new MojoException(code); + } + return code; + } + + private ByteBuffer allocateDirectBuffer(int capacity) { + ByteBuffer buffer = ByteBuffer.allocateDirect(capacity + mByteBufferOffset); + if (mByteBufferOffset != 0) { + buffer.position(mByteBufferOffset); + buffer = buffer.slice(); + } + return buffer.order(ByteOrder.nativeOrder()); + } + + @CalledByNative + private static ResultAnd<ByteBuffer> newResultAndBuffer(int mojoResult, ByteBuffer buffer) { + return new ResultAnd<>(mojoResult, buffer); + } + + /** + * Trivial alias for Pair<Integer, Integer>. This is needed because our jni generator is unable + * to handle class that contains space. + */ + private static final class IntegerPair extends Pair<Integer, Integer> { + public IntegerPair(Integer first, Integer second) { + super(first, second); + } + } + + @CalledByNative + private static ResultAnd<MessagePipeHandle.ReadMessageResult> newReadMessageResult( + int mojoResult, byte[] data, int[] rawHandles) { + MessagePipeHandle.ReadMessageResult result = new MessagePipeHandle.ReadMessageResult(); + if (mojoResult == MojoResult.OK) { + result.mData = data; + result.mRawHandles = rawHandles; + } + return new ResultAnd<>(mojoResult, result); + } + + @CalledByNative + private static ResultAnd<Integer> newResultAndInteger(int mojoResult, int numBytesRead) { + return new ResultAnd<>(mojoResult, numBytesRead); + } + + @CalledByNative + private static ResultAnd<IntegerPair> newNativeCreationResult( + int mojoResult, int mojoHandle1, int mojoHandle2) { + return new ResultAnd<>(mojoResult, new IntegerPair(mojoHandle1, mojoHandle2)); + } + + private native long nativeGetTimeTicksNow(); + + private native ResultAnd<IntegerPair> nativeCreateMessagePipe(ByteBuffer optionsBuffer); + + private native ResultAnd<IntegerPair> nativeCreateDataPipe(ByteBuffer optionsBuffer); + + private native ResultAnd<Integer> nativeCreateSharedBuffer( + ByteBuffer optionsBuffer, long numBytes); + + private native int nativeClose(int mojoHandle); + + private native int nativeQueryHandleSignalsState(int mojoHandle, ByteBuffer signalsStateBuffer); + + private native int nativeWriteMessage( + int mojoHandle, ByteBuffer bytes, int numBytes, ByteBuffer handlesBuffer, int flags); + + private native ResultAnd<MessagePipeHandle.ReadMessageResult> nativeReadMessage( + int mojoHandle, int flags); + + private native ResultAnd<Integer> nativeReadData( + int mojoHandle, ByteBuffer elements, int elementsSize, int flags); + + private native ResultAnd<ByteBuffer> nativeBeginReadData( + int mojoHandle, int numBytes, int flags); + + private native int nativeEndReadData(int mojoHandle, int numBytesRead); + + private native ResultAnd<Integer> nativeWriteData( + int mojoHandle, ByteBuffer elements, int limit, int flags); + + private native ResultAnd<ByteBuffer> nativeBeginWriteData( + int mojoHandle, int numBytes, int flags); + + private native int nativeEndWriteData(int mojoHandle, int numBytesWritten); + + private native ResultAnd<Integer> nativeDuplicate(int mojoHandle, ByteBuffer optionsBuffer); + + private native ResultAnd<ByteBuffer> nativeMap( + int mojoHandle, long offset, long numBytes, int flags); + + private native int nativeUnmap(ByteBuffer buffer); + + private native int nativeGetNativeBufferOffset(ByteBuffer buffer, int alignment); +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java new file mode 100644 index 00000000000..820a6f94f81 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java @@ -0,0 +1,70 @@ +// 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ReadFlags; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link ConsumerHandle}. + */ +class DataPipeConsumerHandleImpl extends HandleBase implements ConsumerHandle { + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + DataPipeConsumerHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + DataPipeConsumerHandleImpl(HandleBase other) { + super(other); + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public ConsumerHandle pass() { + return new DataPipeConsumerHandleImpl(this); + } + + /** + * @see ConsumerHandle#discardData(int, ReadFlags) + */ + @Override + public int discardData(int numBytes, ReadFlags flags) { + return mCore.discardData(this, numBytes, flags); + } + + /** + * @see ConsumerHandle#readData(ByteBuffer, ReadFlags) + */ + @Override + public ResultAnd<Integer> readData(ByteBuffer elements, ReadFlags flags) { + return mCore.readData(this, elements, flags); + } + + /** + * @see ConsumerHandle#beginReadData(int, ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, ReadFlags flags) { + return mCore.beginReadData(this, numBytes, flags); + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + mCore.endReadData(this, numBytesRead); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java new file mode 100644 index 00000000000..cf4eebe056f --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java @@ -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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.DataPipe.WriteFlags; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link ProducerHandle}. + */ +class DataPipeProducerHandleImpl extends HandleBase implements ProducerHandle { + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + DataPipeProducerHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + DataPipeProducerHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.DataPipe.ProducerHandle#pass() + */ + @Override + public ProducerHandle pass() { + return new DataPipeProducerHandleImpl(this); + } + + /** + * @see ProducerHandle#writeData(ByteBuffer, WriteFlags) + */ + @Override + public ResultAnd<Integer> writeData(ByteBuffer elements, WriteFlags flags) { + return mCore.writeData(this, elements, flags); + } + + /** + * @see ProducerHandle#beginWriteData(int, WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, WriteFlags flags) { + return mCore.beginWriteData(this, numBytes, flags); + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + mCore.endWriteData(this, numBytesWritten); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/HandleBase.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/HandleBase.java new file mode 100644 index 00000000000..9b7f5de9608 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/HandleBase.java @@ -0,0 +1,137 @@ +// 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.Log; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.UntypedHandle; + +/** + * Implementation of {@link Handle}. + */ +abstract class HandleBase implements Handle { + private static final String TAG = "HandleImpl"; + + /** + * The pointer to the scoped handle owned by this object. + */ + private int mMojoHandle; + + /** + * The core implementation. Will be used to delegate all behavior. + */ + protected CoreImpl mCore; + + /** + * Base constructor. Takes ownership of the passed handle. + */ + HandleBase(CoreImpl core, int mojoHandle) { + mCore = core; + mMojoHandle = mojoHandle; + } + + /** + * Constructor for transforming {@link HandleBase} into a specific one. It is used to transform + * an {@link UntypedHandle} into a typed one, or any handle into an {@link UntypedHandle}. + */ + protected HandleBase(HandleBase other) { + mCore = other.mCore; + HandleBase otherAsHandleImpl = other; + int mojoHandle = otherAsHandleImpl.mMojoHandle; + otherAsHandleImpl.mMojoHandle = CoreImpl.INVALID_HANDLE; + mMojoHandle = mojoHandle; + } + + /** + * @see org.chromium.mojo.system.Handle#close() + */ + @Override + public void close() { + if (mMojoHandle != CoreImpl.INVALID_HANDLE) { + // After a close, the handle is invalid whether the close succeed or not. + int handle = mMojoHandle; + mMojoHandle = CoreImpl.INVALID_HANDLE; + mCore.close(handle); + } + } + + /** + * @see org.chromium.mojo.system.Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + return mCore.queryHandleSignalsState(mMojoHandle); + } + + /** + * @see org.chromium.mojo.system.Handle#isValid() + */ + @Override + public boolean isValid() { + return mMojoHandle != CoreImpl.INVALID_HANDLE; + } + + /** + * @see org.chromium.mojo.system.Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return new UntypedHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.Handle#getCore() + */ + @Override + public Core getCore() { + return mCore; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + int result = mMojoHandle; + mMojoHandle = CoreImpl.INVALID_HANDLE; + return result; + } + + /** + * Getter for the native scoped handle. + * + * @return the native scoped handle. + */ + int getMojoHandle() { + return mMojoHandle; + } + + /** + * invalidate the handle. The caller must ensures that the handle does not leak. + */ + void invalidateHandle() { + mMojoHandle = CoreImpl.INVALID_HANDLE; + } + + /** + * Close the handle if it is valid. Necessary because we cannot let handle leak, and we cannot + * ensure that every handle will be manually closed. + * + * @see java.lang.Object#finalize() + */ + @Override + protected final void finalize() throws Throwable { + if (isValid()) { + // This should not happen, as the user of this class should close the handle. Adding a + // warning. + Log.w(TAG, "Handle was not closed."); + // Ignore result at this point. + mCore.closeWithResult(mMojoHandle); + } + super.finalize(); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java new file mode 100644 index 00000000000..a2dd75d0121 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java @@ -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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Implementation of {@link MessagePipeHandle}. + */ +class MessagePipeHandleImpl extends HandleBase implements MessagePipeHandle { + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + MessagePipeHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + MessagePipeHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.MessagePipeHandle#pass() + */ + @Override + public MessagePipeHandle pass() { + return new MessagePipeHandleImpl(this); + } + + /** + * @see MessagePipeHandle#writeMessage(ByteBuffer, List, WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List<? extends Handle> handles, WriteFlags flags) { + mCore.writeMessage(this, bytes, handles, flags); + } + + /** + * @see MessagePipeHandle#readMessage(ReadFlags) + */ + @Override + public ResultAnd<ReadMessageResult> readMessage(ReadFlags flags) { + return mCore.readMessage(this, flags); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java new file mode 100644 index 00000000000..cd28a48174b --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java @@ -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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.SharedBufferHandle; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link SharedBufferHandle}. + */ +class SharedBufferHandleImpl extends HandleBase implements SharedBufferHandle { + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + SharedBufferHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + SharedBufferHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.SharedBufferHandle#pass() + */ + @Override + public SharedBufferHandle pass() { + return new SharedBufferHandleImpl(this); + } + + /** + * @see SharedBufferHandle#duplicate(DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + return mCore.duplicate(this, options); + } + + /** + * @see SharedBufferHandle#map(long, long, MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + return mCore.map(this, offset, numBytes, flags); + } + + /** + * @see SharedBufferHandle#unmap(ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + mCore.unmap(buffer); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java new file mode 100644 index 00000000000..e365abf31d8 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java @@ -0,0 +1,70 @@ +// 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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; + +/** + * Implementation of {@link UntypedHandle}. + */ +class UntypedHandleImpl extends HandleBase implements UntypedHandle { + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + UntypedHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + UntypedHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#pass() + */ + @Override + public UntypedHandle pass() { + return new UntypedHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return new MessagePipeHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return new DataPipeConsumerHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return new DataPipeProducerHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return new SharedBufferHandleImpl(this); + } +} diff --git a/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java new file mode 100644 index 00000000000..094ad902653 --- /dev/null +++ b/chromium/mojo/public/java/system/src/org/chromium/mojo/system/impl/WatcherImpl.java @@ -0,0 +1,63 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Watcher; + +@JNINamespace("mojo::android") +class WatcherImpl implements Watcher { + private long mImplPtr = nativeCreateWatcher(); + private Callback mCallback; + + @Override + public int start(Handle handle, Core.HandleSignals signals, Callback callback) { + if (mImplPtr == 0) { + return MojoResult.INVALID_ARGUMENT; + } + if (!(handle instanceof HandleBase)) { + return MojoResult.INVALID_ARGUMENT; + } + int result = + nativeStart(mImplPtr, ((HandleBase) handle).getMojoHandle(), signals.getFlags()); + if (result == MojoResult.OK) mCallback = callback; + return result; + } + + @Override + public void cancel() { + if (mImplPtr == 0) { + return; + } + mCallback = null; + nativeCancel(mImplPtr); + } + + @Override + public void destroy() { + if (mImplPtr == 0) { + return; + } + nativeDelete(mImplPtr); + mImplPtr = 0; + } + + @CalledByNative + private void onHandleReady(int result) { + mCallback.onResult(result); + } + + private native long nativeCreateWatcher(); + + private native int nativeStart(long implPtr, int mojoHandle, int flags); + + private native void nativeCancel(long implPtr); + + private native void nativeDelete(long implPtr); +} diff --git a/chromium/mojo/public/js/BUILD.gn b/chromium/mojo/public/js/BUILD.gn index d6b97bda6bf..8db2da6ed08 100644 --- a/chromium/mojo/public/js/BUILD.gn +++ b/chromium/mojo/public/js/BUILD.gn @@ -62,11 +62,14 @@ grit("resources") { "grit/mojo_bindings_resources_map.h", "mojo_bindings_resources.pak", ] + grit_flags = [ "-E", "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir), ] + deps = [ ":bindings", + "//mojo/public/mojom/base:base_js", ] } diff --git a/chromium/mojo/public/js/mojo_bindings_resources.grd b/chromium/mojo/public/js/mojo_bindings_resources.grd index b92f7974430..174d2cfadf2 100644 --- a/chromium/mojo/public/js/mojo_bindings_resources.grd +++ b/chromium/mojo/public/js/mojo_bindings_resources.grd @@ -13,9 +13,18 @@ <translations /> <release seq="1"> <includes> - <include name="IDR_MOJO_BINDINGS_JS" - file="${root_gen_dir}\mojo\public\js\mojo_bindings.js" - type="BINDATA" use_base_dir="false" compress="gzip" /> + <include name="IDR_MOJO_MOJO_BINDINGS_JS" + file="${root_gen_dir}/mojo/public/js/mojo_bindings.js" + use_base_dir="false" + type="BINDATA" + compress="gzip" /> + <if expr="is_win or is_macosx or is_linux"> + <include name="IDR_MOJO_TIME_MOJOM_JS" + file="${root_gen_dir}/mojo/public/mojom/base/time.mojom.js" + use_base_dir="false" + type="BINDATA" + compress="gzip" /> + </if> </includes> </release> </grit> diff --git a/chromium/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/chromium/mojo/public/tools/bindings/chromium_bindings_configuration.gni index 5c3c59f7926..6579bb020b0 100644 --- a/chromium/mojo/public/tools/bindings/chromium_bindings_configuration.gni +++ b/chromium/mojo/public/tools/bindings/chromium_bindings_configuration.gni @@ -5,7 +5,6 @@ _typemap_imports = [ "//ash/public/interfaces/typemaps.gni", "//chrome/chrome_cleaner/interfaces/typemaps/typemaps.gni", - "//chrome/common/extensions/typemaps.gni", "//chrome/common/importer/typemaps.gni", "//chrome/common/media_router/mojo/typemaps.gni", "//chrome/typemaps.gni", @@ -14,7 +13,6 @@ _typemap_imports = [ "//chromeos/services/device_sync/public/mojom/typemaps.gni", "//chromeos/services/secure_channel/public/mojom/typemaps.gni", "//components/arc/common/typemaps.gni", - "//components/metrics/public/cpp/typemaps.gni", "//components/sync/mojo/typemaps.gni", "//components/typemaps.gni", "//content/common/bluetooth/typemaps.gni", |