diff options
author | Hidehiko Abe <hidehiko@google.com> | 2018-04-23 20:01:13 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2018-04-23 20:01:13 -0700 |
commit | ed7128dca79cff94e99465e3c1bc31d91d83c76d (patch) | |
tree | bd2d04362f66c36d4279f7a9735ba21ea3a2a021 /mojo/public | |
parent | d6187ab7d79d95d101c2ecb83aa98c05bcdcccd1 (diff) | |
parent | 0ab20ac2283987e63b0e7c1318db2a5cf7c668d2 (diff) | |
download | libchrome-ed7128dca79cff94e99465e3c1bc31d91d83c76d.tar.gz |
Migrate libmojo repository into libchrome, part 2. am: b268b43ac6
am: 0ab20ac228
Change-Id: I1b1afe0c902f1d122b100f5bf56d1df4a94eb090
Diffstat (limited to 'mojo/public')
719 files changed, 74200 insertions, 0 deletions
diff --git a/mojo/public/BUILD.gn b/mojo/public/BUILD.gn new file mode 100644 index 0000000000..3baf667064 --- /dev/null +++ b/mojo/public/BUILD.gn @@ -0,0 +1,28 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +group("public") { + # Meta-target, don't link into production code. + testonly = true + deps = [ + ":sdk", + "cpp/bindings", + "interfaces/bindings/tests:test_interfaces", + ] + + if (is_android) { + deps += [ + "java:bindings_java", + "java:system_java", + ] + } +} + +group("sdk") { + deps = [ + "c/system", + "cpp/bindings", + "js", + ] +} diff --git a/mojo/public/DEPS b/mojo/public/DEPS new file mode 100644 index 0000000000..2e49995741 --- /dev/null +++ b/mojo/public/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + # This code is checked into the chromium repo so it's fine to depend on this. + "+base", + "+build", + "+testing", + + "+ipc/ipc_param_traits.h", + + # internal includes. + "+mojo", +] diff --git a/mojo/public/LICENSE b/mojo/public/LICENSE new file mode 100644 index 0000000000..972bb2edb0 --- /dev/null +++ b/mojo/public/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/mojo/public/c/system/BUILD.gn b/mojo/public/c/system/BUILD.gn new file mode 100644 index 0000000000..08185c7514 --- /dev/null +++ b/mojo/public/c/system/BUILD.gn @@ -0,0 +1,37 @@ +# 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. + +component("system") { + output_name = "mojo_public_system" + + sources = [ + "buffer.h", + "core.h", + "data_pipe.h", + "functions.h", + "macros.h", + "message_pipe.h", + "platform_handle.h", + "system_export.h", + "thunks.cc", + "thunks.h", + "types.h", + "watcher.h", + ] + + defines = [ "MOJO_SYSTEM_IMPLEMENTATION" ] +} + +# This should ONLY be depended upon directly by shared_library targets which +# need to export the MojoSetSystemThunks symbol, like targets generated by the +# mojo_native_application template in //services/service_manager/public/cpp/service.gni. +source_set("set_thunks_for_app") { + sources = [ + "set_thunks_for_app.cc", + ] + + public_deps = [ + ":system", + ] +} diff --git a/mojo/public/c/system/README.md b/mojo/public/c/system/README.md new file mode 100644 index 0000000000..2abe80ffc7 --- /dev/null +++ b/mojo/public/c/system/README.md @@ -0,0 +1,869 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C System API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview +The Mojo C System API is a lightweight API (with an eventually-stable ABI) upon +which all higher layers of the Mojo system are built. + +This API exposes the fundamental capabilities to: create, read from, and write +to **message pipes**; create, read from, and write to **data pipes**; create +**shared buffers** and generate sharable handles to them; wrap platform-specific +handle objects (such as **file descriptors**, **Windows handles**, and +**Mach ports**) for seamless transit over message pipes; and efficiently watch +handles for various types of state transitions. + +This document provides a brief guide to API usage with example code snippets. +For a detailed API references please consult the headers in +[//mojo/public/c/system](https://cs.chromium.org/chromium/src/mojo/public/c/system/). + +### A Note About Multithreading + +The Mojo C System API is entirely thread-agnostic. This means that all functions +may be called from any thread in a process, and there are no restrictions on how +many threads can use the same object at the same time. + +Of course this does not mean you can completely ignore potential concurrency +issues -- such as a handle being closed on one thread while another thread is +trying to perform an operation on the same handle -- but there is nothing +fundamentally incorrect about using any given API or handle from multiple +threads. + +### A Note About Synchronization + +Every Mojo API call is non-blocking and synchronously yields some kind of status +result code, but the call's side effects -- such as affecting the state of +one or more handles in the system -- may or may not occur asynchronously. + +Mojo objects can be observed for interesting state changes in a way that is +thread-agnostic and in some ways similar to POSIX signal handlers: *i.e.* +user-provided notification handlers may be invoked at any time on arbitrary +threads in the process. It is entirely up to the API user to take appropriate +measures to synchronize operations against other application state. + +The higher level [system](/mojo#High-Level-System-APIs) and +[bindings](/mojo#High-Level-Bindings-APIs) APIs provide helpers to simplify Mojo +usage in this regard, at the expense of some flexibility. + +## Result Codes + +Most API functions return a value of type `MojoResult`. This is an integral +result code used to convey some meaningful level of detail about the result of a +requested operation. + +See [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h) +for different possible values. See documentation for individual API calls for +more specific contextual meaning of various result codes. + +## Handles + +Every Mojo IPC primitive is identified by a generic, opaque integer handle of +type `MojoHandle`. Handles can be acquired by creating new objects using various +API calls, or by reading messages which contain attached handles. + +A `MojoHandle` can represent a message pipe endpoint, a data pipe consumer, +a data pipe producer, a shared buffer reference, a wrapped native platform +handle such as a POSIX file descriptor or a Windows system handle, or a watcher +object (see [Signals & Watchers](#Signals-Watchers) below.) + +All types of handles except for watchers (which are an inherently local concept) +can be attached to messages and sent over message pipes. + +Any `MojoHandle` may be closed by calling `MojoClose`: + +``` c +MojoHandle x = DoSomethingToGetAValidHandle(); +MojoResult result = MojoClose(x); +``` + +If the handle passed to `MojoClose` was a valid handle, it will be closed and +`MojoClose` returns `MOJO_RESULT_OK`. Otherwise it returns +`MOJO_RESULT_INVALID_ARGUMENT`. + +Similar to native system handles on various popular platforms, `MojoHandle` +values may be reused over time. Thus it is important to avoid logical errors +which lead to misplaced handle ownership, double-closes, *etc.* + +## Message Pipes + +A message pipe is a bidirectional messaging channel which can carry arbitrary +unstructured binary messages with zero or more `MojoHandle` attachments to be +transferred from one end of a pipe to the other. Message pipes work seamlessly +across process boundaries or within a single process. + +The [Embedder Development Kit (EDK)](/mojo/edk/embedder) provides the means to +bootstrap one or more primordial cross-process message pipes, and it's up to +Mojo embedders to expose this capability in some useful way. Once such a pipe is +established, additional handles -- including other message pipe handles -- may +be sent to a remote process using that pipe (or in turn, over other pipes sent +over that pipe, or pipes sent over *that* pipe, and so on...) + +The public C System API exposes the ability to read and write messages on pipes +and to create new message pipes. + +See [//mojo/public/c/system/message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/message_pipe.h) +for detailed message pipe API documentation. + +### Creating Message Pipes + +`MojoCreateMessagePipe` can be used to create a new message pipe: + +``` c +MojoHandle a, b; +MojoResult result = MojoCreateMessagePipe(NULL, &a, &b); +``` + +After this snippet, `result` should be `MOJO_RESULT_OK` (it's really hard for +this to fail!), and `a` and `b` will contain valid Mojo handles, one for each +end of the new message pipe. + +Any messages written to `a` are eventually readable from `b`, and any messages +written to `b` are eventually readable from `a`. If `a` is closed at any point, +`b` will eventually become aware of this fact; likewise if `b` is closed, `a` +will become aware of that. + +The state of these conditions can be queried and watched asynchronously as +described in the [Signals & Watchers](#Signals-Watchers) section below. + +### Allocating Messages + +In order to avoid redundant internal buffer copies, Mojo would like to allocate +your message storage buffers for you. This is easy: + +``` c +MojoMessageHandle message; +MojoResult result = MojoAllocMessage(6, NULL, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, + &message); +``` + +Note that we have a special `MojoMessageHandle` type for message objects. + +The code above allocates a buffer for a message payload of 6 bytes with no +handles attached. + +If we change our mind and decide not to send this message, we can delete it: + +``` c +MojoResult result = MojoFreeMessage(message); +``` + +If we instead decide to send our newly allocated message, we first need to fill +in the payload data with something interesting. How about a pleasant greeting: + +``` c +void* buffer = NULL; +MojoResult result = MojoGetMessageBuffer(message, &buffer); +memcpy(buffer, "hello", 6); +``` + +Now we can write the message to a pipe. Note that attempting to write a message +transfers ownership of the message object (and any attached handles) into the +target pipe and there is therefore no need to subsequently call +`MojoFreeMessage` on that message. + +### Writing Messages + +``` c +result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE); +``` + +`MojoWriteMessage` is a *non-blocking* call: it always returns +immediately. If its return code is `MOJO_RESULT_OK` the message will eventually +find its way to the other end of the pipe -- assuming that end isn't closed +first, of course. If the return code is anything else, the message is deleted +and not transferred. + +In this case since we know `b` is still open, we also know the message will +eventually arrive at `b`. `b` can be queried or watched to become aware of when +the message arrives, but we'll ignore that complexity for now. See +[Signals & Watchers](#Signals-Watchers) below for more information. + +*** aside +**NOTE**: Although this is an implementation detail and not strictly guaranteed by the +System API, it is true in the current implementation that the message will +arrive at `b` before the above `MojoWriteMessage` call even returns, because `b` +is in the same process as `a` and has never been transferred over another pipe. +*** + +### Reading Messages + +We can read a new message object from a pipe: + +``` c +MojoMessageHandle message; +uint32_t num_bytes; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +and map its buffer to retrieve the contents: + +``` c +void* buffer = NULL; +MojoResult result = MojoGetMessageBuffer(message, &buffer); +printf("Pipe says: %s", (const char*)buffer); +``` + +`result` should be `MOJO_RESULT_OK` and this snippet should write `"hello"` to +`stdout`. + +If we try were to try reading again now that there are no messages on `b`: + +``` c +MojoMessageHandle message; +MojoResult result = MojoReadMessageNew(b, &message, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +We'll get a `result` of `MOJO_RESULT_SHOULD_WAIT`, indicating that the pipe is +not yet readable. + +### Messages With Handles + +Probably the most useful feature of Mojo IPC is that message pipes can carry +arbitrary Mojo handles, including other message pipes. This is also +straightforward. + +Here's an example which creates two pipes, using the first pipe to transfer +one end of the second pipe. If you have a good imagination you can pretend the +first pipe spans a process boundary, which makes the example more practically +interesting: + +``` c +MojoHandle a, b; +MojoHandle c, d; +MojoMessage message; + +// Allocate a message with an empty payload and handle |c| attached. Note that +// this takes ownership of |c|, effectively invalidating its handle value. +MojoResult result = MojoAllocMessage(0, &c, 1, MOJO_ALLOC_MESSAGE_FLAG_NONE, + message); + +result = MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE); + +// Some time later... +uint32_t num_bytes; +MojoHandle e; +uint32_t num_handles = 1; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, &e, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +At this point the handle in `e` is now referencing the same message pipe +endpoint which was originally referenced by `c`. + +Note that `num_handles` above is initialized to 1 before we pass its address to +`MojoReadMessageNew`. This is to indicate how much `MojoHandle` storage is +available at the output buffer we gave it (`&e` above). + +If we didn't know how many handles to expect in an incoming message -- which is +often the case -- we can use `MojoReadMessageNew` to query for this information +first: + +``` c +MojoMessageHandle message; +uint32_t num_bytes = 0; +uint32_t num_handles = 0; +MojoResult result = MojoReadMessageNew(b, &message, &num_bytes, NULL, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +If in this case there were a received message on `b` with some nonzero number +of handles, `result` would be `MOJO_RESULT_RESOURCE_EXHAUSTED`, and both +`num_bytes` and `num_handles` would be updated to reflect the payload size and +number of attached handles on the next available message. + +It's also worth noting that if there did happen to be a message available with +no payload and no handles (*i.e.* an empty message), this would actually return +`MOJO_RESULT_OK`. + +## Data Pipes + +Data pipes provide an efficient unidirectional channel for moving large amounts +of unframed data between two endpoints. Every data pipe has a fixed +**element size** and **capacity**. Reads and writes must be done in sizes that +are a multiple of the element size, and writes to the pipe can only be queued +up to the pipe's capacity before reads must be done to make more space +available. + +Every data pipe has a single **producer** handle used to write data into the +pipe and a single **consumer** handle used to read data out of the pipe. + +Finally, data pipes support both immediate I/O -- reading into and writing out +from user-supplied buffers -- as well as two-phase I/O, allowing callers to +temporarily lock some portion of the data pipe in order to read or write its +contents directly. + +See [//mojo/public/c/system/data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/data_pipe.h) +for detailed data pipe API documentation. + +### Creating Data Pipes + +Use `MojoCreateDataPipe` to create a new data pipe. The +`MojoCreateDataPipeOptions` structure is used to configure the new pipe, but +this can be omitted to assume the default options of a single-byte element size +and an implementation-defined default capacity (64 kB at the time of this +writing.) + +``` c +MojoHandle producer, consumer; +MojoResult result = MojoCreateDataPipe(NULL, &producer, &consumer); +``` + +### Immediate I/O + +Data can be written into or read out of a data pipe using buffers provided by +the caller. This is generally more convenient than two-phase I/O but is +also less efficient due to extra copying. + +``` c +uint32_t num_bytes = 12; +MojoResult result = MojoWriteData(producer, "datadatadata", &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); +``` + +The above snippet will attempt to write 12 bytes into the data pipe, which +should succeed and return `MOJO_RESULT_OK`. If the available capacity on the +pipe was less than the amount requested (the input value of `*num_bytes`) this +will copy what it can into the pipe and return the number of bytes written in +`*num_bytes`. If no data could be copied this will instead return +`MOJO_RESULT_SHOULD_WAIT`. + +Reading from the consumer is a similar operation. + +``` c +char buffer[64]; +uint32_t num_bytes = 64; +MojoResult result = MojoReadData(consumer, buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +``` + +This will attempt to read up to 64 bytes, returning the actual number of bytes +read in `*num_bytes`. + +`MojoReadData` supports a number of interesting flags to change the behavior: +you can peek at the data (copy bytes out without removing them from the pipe), +query the number of bytes available without doing any actual reading of the +contents, or discard data from the pipe without bothering to copy it anywhere. + +This also supports a `MOJO_READ_DATA_FLAG_ALL_OR_NONE` which ensures that the +call succeeds **only** if the exact number of bytes requested could be read. +Otherwise such a request will fail with `MOJO_READ_DATA_OUT_OF_RANGE`. + +### Two-Phase I/O + +Data pipes also support two-phase I/O operations, allowing a caller to +temporarily lock a portion of the data pipe's storage for direct memory access. + +``` c +void* buffer; +uint32_t num_bytes = 1024; +MojoResult result = MojoBeginWriteData(producer, &buffer, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); +``` + +This requests write access to a region of up to 1024 bytes of the data pipe's +next available capacity. Upon success, `buffer` will point to the writable +storage and `num_bytes` will indicate the size of the buffer there. + +The caller should then write some data into the memory region and release it +ASAP, indicating the number of bytes actually written: + +``` c +memcpy(buffer, "hello", 6); +MojoResult result = MojoEndWriteData(producer, 6); +``` + +Two-phase reads look similar: + +``` c +void* buffer; +uint32_t num_bytes = 1024; +MojoResult result = MojoBeginReadData(consumer, &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +// result should be MOJO_RESULT_OK, since there is some data available. + +printf("Pipe says: %s", (const char*)buffer); // Should say "hello". + +result = MojoEndReadData(consumer, 1); // Say we only consumed one byte. + +num_bytes = 1024; +result = MojoBeginReadData(consumer, &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); +printf("Pipe says: %s", (const char*)buffer); // Should say "ello". +result = MojoEndReadData(consumer, 5); +``` + +## Shared Buffers + +Shared buffers are chunks of memory which can be mapped simultaneously by +multiple processes. Mojo provides a simple API to make these available to +applications. + +See [//mojo/public/c/system/buffer.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/buffer.h) +for detailed shared buffer API documentation. + +### Creating Buffer Handles + +Usage is straightforward. You can create a new buffer: + +``` c +// Allocate a shared buffer of 4 kB. +MojoHandle buffer; +MojoResult result = MojoCreateSharedBuffer(NULL, 4096, &buffer); +``` + +You can also duplicate an existing shared buffer handle: + +``` c +MojoHandle another_name_for_buffer; +MojoResult result = MojoDuplicateBufferHandle(buffer, NULL, + &another_name_for_buffer); +``` + +This is useful if you want to retain a handle to the buffer while also sharing +handles with one or more other clients. The allocated buffer remains valid as +long as at least one shared buffer handle exists to reference it. + +### Mapping Buffers + +You can map (and later unmap) a specified range of the buffer to get direct +memory access to its contents: + +``` c +void* data; +MojoResult result = MojoMapBuffer(buffer, 0, 64, &data, + MOJO_MAP_BUFFER_FLAG_NONE); + +*(int*)data = 42; +result = MojoUnmapBuffer(data); +``` + +A buffer may have any number of active mappings at a time, in any number of +processes. + +### Read-Only Handles + +An option can also be specified on `MojoDuplicateBufferHandle` to ensure +that the newly duplicated handle can only be mapped to read-only memory: + +``` c +MojoHandle read_only_buffer; +MojoDuplicateBufferHandleOptions options; +options.struct_size = sizeof(options); +options.flags = MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; +MojoResult result = MojoDuplicateBufferHandle(buffer, &options, + &read_only_buffer); + +// Attempt to map and write to the buffer using the read-only handle: +void* data; +result = MojoMapBuffer(read_only_buffer, 0, 64, &data, + MOJO_MAP_BUFFER_FLAG_NONE); +*(int*)data = 42; // CRASH +``` + +*** note +**NOTE:** One important limitation of the current implementation is that +read-only handles can only be produced from a handle that was originally created +by `MojoCreateSharedBuffer` (*i.e.*, you cannot create a read-only duplicate +from a non-read-only duplicate), and the handle cannot have been transferred +over a message pipe first. +*** + +## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) + +Native platform handles to system objects can be wrapped as Mojo handles for +seamless transit over message pipes. Mojo currently supports wrapping POSIX +file descriptors, Windows handles, and Mach ports. + +See [//mojo/public/c/system/platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/platform_handle.h) +for detailed platform handle API documentation. + +### Wrapping Basic Handle Types + +Wrapping a POSIX file descriptor is simple: + +``` c +MojoPlatformHandle platform_handle; +platform_handle.struct_size = sizeof(platform_handle); +platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; +platform_handle.value = (uint64_t)fd; +MojoHandle handle; +MojoResult result = MojoWrapPlatformHandle(&platform_handle, &handle); +``` + +Note that at this point `handle` effectively owns the file descriptor +and if you were to call `MojoClose(handle)`, the file descriptor would be closed +too; but we're not going to close it here! We're going to pretend we've sent it +over a message pipe, and now we want to unwrap it on the other side: + +``` c +MojoPlatformHandle platform_handle; +platform_handle.struct_size = sizeof(platform_handle); +MojoResult result = MojoUnwrapPlatformHandle(handle, &platform_handle); +int fd = (int)platform_handle.value; +``` + +The situation looks nearly identical for wrapping and unwrapping Windows handles +and Mach ports. + +### Wrapping Shared Buffer Handles + +Unlike other handle types, shared buffers have special meaning in Mojo, and it +may be desirable to wrap a native platform handle -- along with some extra +metadata -- such that be treated like a real Mojo shared buffer handle. +Conversely it can also be useful to unpack a Mojo shared buffer handle into +a native platform handle which references the buffer object. Both of these +things can be done using the `MojoWrapPlatformSharedBuffer` and +`MojoUnwrapPlatformSharedBuffer` APIs. + +On Windows, the wrapped platform handle must always be a Windows handle to +a file mapping object. + +On OS X, the wrapped platform handle must be a memory-object send right. + +On all other POSIX systems, the wrapped platform handle must be a file +descriptor for a shared memory object. + +## Signals & Watchers + +Message pipe and data pipe (producer and consumer) handles can change state in +ways that may be interesting to a Mojo API user. For example, you may wish to +know when a message pipe handle has messages available to be read or when its +peer has been closed. Such states are reflected by a fixed set of boolean +signals on each pipe handle. + +### Signals + +Every message pipe and data pipe handle maintains a notion of +**signaling state** which may be queried at any time. For example: + +``` c +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +MojoHandleSignalsState state; +MojoResult result = MojoQueryHandleSignalsState(a, &state); +``` + +The `MojoHandleSignalsState` structure exposes two fields: `satisfied_signals` +and `satisfiable_signals`. Both of these are bitmasks of the type +`MojoHandleSignals` (see [//mojo/public/c/system/types.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/types.h) +for more details.) + +The `satisfied_signals` bitmask indicates signals which were satisfied on the +handle at the time of the call, while the `satisfiable_signals` bitmask +indicates signals which were still possible to satisfy at the time of the call. +It is thus by definition always true that: + +``` c +(satisfied_signals | satisfiable_signals) == satisfiable_signals +``` + +In other words a signal obviously cannot be satisfied if it is no longer +satisfiable. Furthermore once a signal is unsatisfiable, *i.e.* is no longer +set in `sastisfiable_signals`, it can **never** become satisfiable again. + +To illustrate this more clearly, consider the message pipe created above. Both +ends of the pipe are still open and neither has been written to yet. Thus both +handles start out with the same signaling state: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_WRITABLE` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_WRITABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +Writing a message to handle `b` will eventually alter the signaling state of `a` +such that `MOJO_HANDLE_SIGNAL_READABLE` also becomes satisfied. If we were to +then close `b`, the signaling state of `a` would look like: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_READABLE + MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +Note that even though `a`'s peer is known to be closed (hence making `a` +permanently unwritable) it remains readable because there's still an unread +received message waiting to be read from `a`. + +Finally if we read the last message from `a` its signaling state becomes: + +| Field | State | +|-----------------------|-------| +| `satisfied_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED` +| `satisfiable_signals` | `MOJO_HANDLE_SIGNAL_PEER_CLOSED` + +and we know definitively that `a` can never be read from again. + +### Watching Signals + +The ability to query a handle's signaling state can be useful, but it's not +sufficient to support robust and efficient pipe usage. Mojo watchers empower +users with the ability to **watch** a handle's signaling state for interesting +changes and automatically invoke a notification handler in response. + +When a watcher is created it must be bound to a function pointer matching +the following signature, defined in +[//mojo/public/c/system/watcher.h](https://cs.chromium.org/chromium/src/mojo/public/c/system/watcher.h): + +``` c +typedef void (*MojoWatcherNotificationCallback)( + uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags); +``` + +The `context` argument corresponds to a specific handle being watched by the +watcher (read more below), and the remaining arguments provide details regarding +the specific reason for the notification. It's important to be aware that a +watcher's registered handler may be called **at any time** and +**on any thread**. + +It's also helpful to understand a bit about the mechanism by which the handler +can be invoked. Essentially, any Mojo C System API call may elicit a handle +state change of some kind. If such a change is relevant to conditions watched by +a watcher, and that watcher is in a state which allows it raise a corresponding +notification, its notification handler will be invoked synchronously some time +before the outermost System API call on the current thread's stack returns. + +Handle state changes can also occur as a result of incoming IPC from an external +process. If a pipe in the current process is connected to an endpoint in another +process and the internal Mojo system receives an incoming message bound for the +local endpoint, the arrival of that message will trigger a state change on the +receiving handle and may thus invoke one or more watchers' notification handlers +as a result. + +The `MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM` flag on the notification +handler's `flags` argument is used to indicate whether the handler was invoked +due to such an internal system IPC event (if the flag is set), or if it was +invoked synchronously due to some local API call (if the flag is unset.) +This distinction can be useful to make in certain cases to *e.g.* avoid +accidental reentrancy in user code. + +### Creating a Watcher + +Creating a watcher is simple: + +``` c + +void OnNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + // ... +} + +MojoHandle w; +MojoResult result = MojoCreateWatcher(&OnNotification, &w); +``` + +Like all other `MojoHandle` types, watchers may be destroyed by closing them +with `MojoClose`. Unlike other `MojoHandle` types, watcher handles are **not** +transferrable across message pipes. + +In order for a watcher to be useful, it has to watch at least one handle. + +### Adding a Handle to a Watcher + +Any given watcher can watch any given (message or data pipe) handle for some set +of signaling conditions. A handle may be watched simultaneously by multiple +watchers, and a single watcher can watch multiple different handles +simultaneously. + +``` c +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +// Watch handle |a| for readability. +const uintptr_t context = 1234; +MojoResult result = MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context); +``` + +We've successfully instructed watcher `w` to begin watching pipe handle `a` for +readability. However, our recently created watcher is still in a **disarmed** +state, meaning that it will never fire a notification pertaining to this watched +signaling condition. It must be **armed** before that can happen. + +### Arming a Watcher + +In order for a watcher to invoke its notification handler in response to a +relevant signaling state change on a watched handle, it must first be armed. A +watcher may only be armed if none of its watched handles would elicit a +notification immediately once armed. + +In this case `a` is clearly not yet readable, so arming should succeed: + +``` c +MojoResult result = MojoArmWatcher(w, NULL, NULL, NULL, NULL); +``` + +Now we can write to `b` to make `a` readable: + +``` c +MojoWriteMessage(b, NULL, 0, NULL, 0, MOJO_WRITE_MESSAGE_NONE); +``` + +Eventually -- and in practice possibly before `MojoWriteMessage` even +returns -- this will cause `OnNotification` to be invoked on the calling thread +with the `context` value (*i.e.* 1234) that was given when the handle was added +to the watcher. + +The `result` parameter will be `MOJO_RESULT_OK` to indicate that the watched +signaling condition has been *satisfied*. If the watched condition had instead +become permanently *unsatisfiable* (*e.g.*, if `b` were instead closed), `result` +would instead indicate `MOJO_RESULT_FAILED_PRECONDITION`. + +**NOTE:** Immediately before a watcher decides to invoke its notification +handler, it automatically disarms itself to prevent another state change from +eliciting another notification. Therefore a watcher must be repeatedly rearmed +in order to continue dispatching signaling notifications. + +As noted above, arming a watcher may fail if any of the watched conditions for +a handle are already partially satisfied or fully unsatisfiable. In that case +the caller may provide buffers for `MojoArmWatcher` to store information about +a subset of the relevant watches which caused it to fail: + +``` c +// Provide some storage for information about watches that are already ready. +uint32_t num_ready_contexts = 4; +uintptr_t ready_contexts[4]; +MojoResult ready_results[4]; +struct MojoHandleSignalsStates ready_states[4]; +MojoResult result = MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states); +``` + +Because `a` is still readable this operation will fail with +`MOJO_RESULT_FAILED_PRECONDITION`. The input value of `num_ready_contexts` +informs `MojoArmWatcher` that it may store information regarding up to 4 watches +which currently prevent arming. In this case of course there is only one active +watch, so upon return we will see: + +* `num_ready_contexts` is `1`. +* `ready_contexts[0]` is `1234`. +* `ready_results[0]` is `MOJO_RESULT_OK` +* `ready_states[0]` is the last known signaling state of handle `a`. + +In other words the stored information mirrors what would have been the +notification handler's arguments if the watcher were allowed to arm and thus +notify immediately. + +### Cancelling a Watch + +There are three ways a watch can be cancelled: + +* The watched handle is closed +* The watcher handle is closed (in which case all of its watches are cancelled.) +* `MojoCancelWatch` is explicitly called for a given `context`. + +In the above example this means any of the following operations will cancel the +watch on `a`: + +``` c +// Close the watched handle... +MojoClose(a); + +// OR close the watcher handle... +MojoClose(w); + +// OR explicitly cancel. +MojoResult result = MojoCancelWatch(w, 1234); +``` + +In every case the watcher's notification handler is invoked for the cancelled +watch(es) regardless of whether or not the watcher is or was armed at the time. +The notification handler receives a `result` of `MOJO_RESULT_CANCELLED` for +these notifications, and this is guaranteed to be the final notification for any +given watch context. + +### Practical Watch Context Usage + +It is common and probably wise to treat a watch's `context` value as an opaque +pointer to some thread-safe state associated in some way with the handle being +watched. Here's a small example which uses a single watcher to watch both ends +of a message pipe and accumulate a count of messages received at each end. + +``` c +// NOTE: For the sake of simplicity this example code is not in fact +// thread-safe. As long as there's only one thread running in the process and +// no external process connections, this is fine. + +struct WatchedHandleState { + MojoHandle watcher; + MojoHandle handle; + int message_count; +}; + +void OnNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + struct WatchedHandleState* state = (struct WatchedHandleState*)(context); + MojoResult rv; + + if (result == MOJO_RESULT_CANCELLED) { + // Cancellation is always the last notification and is guaranteed to + // eventually happen for every context, assuming no handles are leaked. We + // treat this as an opportunity to free the WatchedHandleState. + free(state); + return; + } + + if (result == MOJO_RESULT_FAILED_PRECONDITION) { + // No longer readable, i.e. the other handle must have been closed. Better + // cancel. Note that we could also just call MojoClose(state->watcher) here + // since we know |context| is its only registered watch. + MojoCancelWatch(state->watcher, context); + return; + } + + // This is the only handle watched by the watcher, so as long as we can't arm + // the watcher we know something's up with this handle. Try to read messages + // until we can successfully arm again or something goes terribly wrong. + while (MojoArmWatcher(state->watcher, NULL, NULL, NULL, NULL) == + MOJO_RESULT_FAILED_PRECONDITION) { + rv = MojoReadMessageNew(state->handle, NULL, NULL, NULL, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + if (rv == MOJO_RESULT_OK) { + state->message_count++; + } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + MojoCancelWatch(state->watcher, context); + return; + } + } +} + +MojoHandle a, b; +MojoCreateMessagePipe(NULL, &a, &b); + +MojoHandle a_watcher, b_watcher; +MojoCreateWatcher(&OnNotification, &a_watcher); +MojoCreateWatcher(&OnNotification, &b_watcher) + +struct WatchedHandleState* a_state = malloc(sizeof(struct WatchedHandleState)); +a_state->watcher = a_watcher; +a_state->handle = a; +a_state->message_count = 0; + +struct WatchedHandleState* b_state = malloc(sizeof(struct WatchedHandleState)); +b_state->watcher = b_watcher; +b_state->handle = b; +b_state->message_count = 0; + +MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)a_state); +MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, (uintptr_t)b_state); + +MojoArmWatcher(a_watcher, NULL, NULL, NULL, NULL); +MojoArmWatcher(b_watcher, NULL, NULL, NULL, NULL); +``` + +Now any writes to `a` will increment `message_count` in `b_state`, and any +writes to `b` will increment `message_count` in `a_state`. + +If either `a` or `b` is closed, both watches will be cancelled - one because +watch cancellation is implicit in handle closure, and the other because its +watcher will eventually detect that the handle is no longer readable. diff --git a/mojo/public/c/system/buffer.h b/mojo/public/c/system/buffer.h new file mode 100644 index 0000000000..285e0d7b03 --- /dev/null +++ b/mojo/public/c/system/buffer.h @@ -0,0 +1,188 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains types/constants and functions specific to shared buffers. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ +#define MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ + +#include <stdint.h> + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoCreateSharedBufferOptions|: Used to specify creation parameters for a +// shared buffer to |MojoCreateSharedBuffer()|. +// +// |uint32_t struct_size|: Set to the size of the +// |MojoCreateSharedBufferOptions| struct. (Used to allow for future +// extensions.) +// +// |MojoCreateSharedBufferOptionsFlags flags|: Reserved for future use. +// |MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE|: No flags; default mode. + +typedef uint32_t MojoCreateSharedBufferOptionsFlags; + +#ifdef __cplusplus +const MojoCreateSharedBufferOptionsFlags + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE = 0; +#else +#define MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE \ + ((MojoCreateSharedBufferOptionsFlags)0) +#endif + +MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment"); +struct MOJO_ALIGNAS(8) MojoCreateSharedBufferOptions { + uint32_t struct_size; + MojoCreateSharedBufferOptionsFlags flags; +}; +MOJO_STATIC_ASSERT(sizeof(MojoCreateSharedBufferOptions) == 8, + "MojoCreateSharedBufferOptions has wrong size"); + +// |MojoDuplicateBufferHandleOptions|: Used to specify parameters in duplicating +// access to a shared buffer to |MojoDuplicateBufferHandle()|. +// +// |uint32_t struct_size|: Set to the size of the +// |MojoDuplicateBufferHandleOptions| struct. (Used to allow for future +// extensions.) +// +// |MojoDuplicateBufferHandleOptionsFlags flags|: Flags to control the +// behavior of |MojoDuplicateBufferHandle()|. May be some combination of +// the following: +// +// |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE|: No flags; default +// mode. +// |MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY|: The duplicate +// shared buffer can only be mapped read-only. A read-only duplicate +// may only be created before any handles to the buffer are passed +// over a message pipe. + +typedef uint32_t MojoDuplicateBufferHandleOptionsFlags; + +#ifdef __cplusplus +const MojoDuplicateBufferHandleOptionsFlags + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE = 0; +const MojoDuplicateBufferHandleOptionsFlags + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY = 1 << 0; +#else +#define MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE \ + ((MojoDuplicateBufferHandleOptionsFlags)0) +#define MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY \ + ((MojoDuplicateBufferHandleOptionsFlags)1 << 0) +#endif + +struct MojoDuplicateBufferHandleOptions { + uint32_t struct_size; + MojoDuplicateBufferHandleOptionsFlags flags; +}; +MOJO_STATIC_ASSERT(sizeof(MojoDuplicateBufferHandleOptions) == 8, + "MojoDuplicateBufferHandleOptions has wrong size"); + +// |MojoMapBufferFlags|: Used to specify different modes to |MojoMapBuffer()|. +// |MOJO_MAP_BUFFER_FLAG_NONE| - No flags; default mode. + +typedef uint32_t MojoMapBufferFlags; + +#ifdef __cplusplus +const MojoMapBufferFlags MOJO_MAP_BUFFER_FLAG_NONE = 0; +#else +#define MOJO_MAP_BUFFER_FLAG_NONE ((MojoMapBufferFlags)0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a buffer of size |num_bytes| bytes that can be shared between +// processes. The returned handle may be duplicated any number of times by +// |MojoDuplicateBufferHandle()|. +// +// To access the buffer's storage, one must call |MojoMapBuffer()|. +// +// |options| may be set to null for a shared buffer with the default options. +// +// On success, |*shared_buffer_handle| will be set to the handle for the shared +// buffer. On failure it is not modified. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |*options| is invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached (e.g., if the requested size was too large, or if the +// maximum number of handles was exceeded). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateSharedBuffer( + const struct MojoCreateSharedBufferOptions* options, // Optional. + uint64_t num_bytes, // In. + MojoHandle* shared_buffer_handle); // Out. + +// Duplicates the handle |buffer_handle| as a new shared buffer handle. On +// success this returns the new handle in |*new_buffer_handle|. A shared buffer +// remains allocated as long as there is at least one shared buffer handle +// referencing it in at least one process in the system. +// +// |options| may be set to null to duplicate the buffer handle with the default +// options. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |buffer_handle| is not a valid buffer handle or |*options| is invalid). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoDuplicateBufferHandle( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, // Optional. + MojoHandle* new_buffer_handle); // Out. + +// Maps the part (at offset |offset| of length |num_bytes|) of the buffer given +// by |buffer_handle| into memory, with options specified by |flags|. |offset + +// num_bytes| must be less than or equal to the size of the buffer. On success, +// |*buffer| points to memory with the requested part of the buffer. On +// failure |*buffer| it is not modified. +// +// A single buffer handle may have multiple active mappings The permissions +// (e.g., writable or executable) of the returned memory depend on th +// properties of the buffer and properties attached to the buffer handle, as +// well as |flags|. +// +// A mapped buffer must eventually be unmapped by calling |MojoUnmapBuffer()| +// with the value of |*buffer| returned by this function. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |buffer_handle| is not a valid buffer handle or the range specified by +// |offset| and |num_bytes| is not valid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the mapping operation itself failed +// (e.g., due to not having appropriate address space available). +MOJO_SYSTEM_EXPORT MojoResult MojoMapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, // Out. + MojoMapBufferFlags flags); + +// Unmaps a buffer pointer that was mapped by |MojoMapBuffer()|. |buffer| must +// have been the result of |MojoMapBuffer()| (not some other pointer inside +// the mapped memory), and the entire mapping will be removed. +// +// A mapping may only be unmapped once. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |buffer| is invalid (e.g., is not the +// result of |MojoMapBuffer()| or has already been unmapped). +MOJO_SYSTEM_EXPORT MojoResult MojoUnmapBuffer(void* buffer); // In. + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_BUFFER_H_ diff --git a/mojo/public/c/system/core.h b/mojo/public/c/system/core.h new file mode 100644 index 0000000000..03c0652a57 --- /dev/null +++ b/mojo/public/c/system/core.h @@ -0,0 +1,22 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a catch-all header that includes everything. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_CORE_H_ +#define MOJO_PUBLIC_C_SYSTEM_CORE_H_ + +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/platform_handle.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" + +#endif // MOJO_PUBLIC_C_SYSTEM_CORE_H_ diff --git a/mojo/public/c/system/data_pipe.h b/mojo/public/c/system/data_pipe.h new file mode 100644 index 0000000000..f51e36cb2e --- /dev/null +++ b/mojo/public/c/system/data_pipe.h @@ -0,0 +1,344 @@ +// 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 contains types/constants and functions specific to data pipes. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ +#define MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ + +#include <stdint.h> + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoCreateDataPipeOptions|: Used to specify creation parameters for a data +// pipe to |MojoCreateDataPipe()|. +// +// |uint32_t struct_size|: Set to the size of the |MojoCreateDataPipeOptions| +// struct. (Used to allow for future extensions.) +// +// |MojoCreateDataPipeOptionsFlags flags|: Used to specify different modes of +// operation. May be some combination of the following values: +// +// |MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode. +// +// |uint32_t element_num_bytes|: The size of an element, in bytes. All +// transactions and buffers will consist of an integral number of +// elements. Must be nonzero. +// +// |uint32_t capacity_num_bytes|: 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). + +typedef uint32_t MojoCreateDataPipeOptionsFlags; + +#ifdef __cplusplus +const MojoCreateDataPipeOptionsFlags MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE = + 0; +#else +#define MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE \ + ((MojoCreateDataPipeOptionsFlags)0) +#endif + +MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment"); +struct MOJO_ALIGNAS(8) MojoCreateDataPipeOptions { + MOJO_ALIGNAS(4) uint32_t struct_size; + MOJO_ALIGNAS(4) MojoCreateDataPipeOptionsFlags flags; + MOJO_ALIGNAS(4) uint32_t element_num_bytes; + MOJO_ALIGNAS(4) uint32_t capacity_num_bytes; +}; +MOJO_STATIC_ASSERT(sizeof(MojoCreateDataPipeOptions) == 16, + "MojoCreateDataPipeOptions has wrong size"); + +// |MojoWriteDataFlags|: Used to specify different modes to |MojoWriteData()| +// and |MojoBeginWriteData()|. May be some combination of the following values: +// +// |MOJO_WRITE_DATA_FLAG_NONE| - No flags; default mode. +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| - Write either all the elements +// requested or none of them. + +typedef uint32_t MojoWriteDataFlags; + +#ifdef __cplusplus +const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_NONE = 0; +const MojoWriteDataFlags MOJO_WRITE_DATA_FLAG_ALL_OR_NONE = 1 << 0; +#else +#define MOJO_WRITE_DATA_FLAG_NONE ((MojoWriteDataFlags)0) +#define MOJO_WRITE_DATA_FLAG_ALL_OR_NONE ((MojoWriteDataFlags)1 << 0) +#endif + +// |MojoReadDataFlags|: Used to specify different modes to |MojoReadData()| and +// |MojoBeginReadData()|. May be some combination of the following values: +// +// |MOJO_READ_DATA_FLAG_NONE| - No flags; default mode. +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| - Read (or discard) either the requested +// number of elements or none. NOTE: This flag is not currently supported +// by |MojoBeginReadData()|. +// |MOJO_READ_DATA_FLAG_DISCARD| - Discard (up to) the requested number of +// elements. +// |MOJO_READ_DATA_FLAG_QUERY| - Query the number of elements available to +// read. For use with |MojoReadData()| only. Mutually exclusive with +// |MOJO_READ_DATA_FLAG_DISCARD|, and |MOJO_READ_DATA_FLAG_ALL_OR_NONE| +// is ignored if this flag is set. +// |MOJO_READ_DATA_FLAG_PEEK| - Read elements without removing them. For use +// with |MojoReadData()| only. Mutually exclusive with +// |MOJO_READ_DATA_FLAG_DISCARD| and |MOJO_READ_DATA_FLAG_QUERY|. + +typedef uint32_t MojoReadDataFlags; + +#ifdef __cplusplus +const MojoReadDataFlags MOJO_READ_DATA_FLAG_NONE = 0; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_ALL_OR_NONE = 1 << 0; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_DISCARD = 1 << 1; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_QUERY = 1 << 2; +const MojoReadDataFlags MOJO_READ_DATA_FLAG_PEEK = 1 << 3; +#else +#define MOJO_READ_DATA_FLAG_NONE ((MojoReadDataFlags)0) +#define MOJO_READ_DATA_FLAG_ALL_OR_NONE ((MojoReadDataFlags)1 << 0) +#define MOJO_READ_DATA_FLAG_DISCARD ((MojoReadDataFlags)1 << 1) +#define MOJO_READ_DATA_FLAG_QUERY ((MojoReadDataFlags)1 << 2) +#define MOJO_READ_DATA_FLAG_PEEK ((MojoReadDataFlags)1 << 3) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a data pipe, which is a unidirectional communication channel for +// unframed data. Data must be read and written in multiples of discrete +// discrete elements of size given in |options|. +// +// See |MojoCreateDataPipeOptions| 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). +// +// On success, |*data_pipe_producer_handle| will be set to the handle for the +// producer and |*data_pipe_consumer_handle| will be set to the handle for the +// consumer. On failure they are not modified. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |*options| is invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached (e.g., if the requested capacity was too large, or if the +// maximum number of handles was exceeded). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateDataPipe( + const struct MojoCreateDataPipeOptions* options, // Optional. + MojoHandle* data_pipe_producer_handle, // Out. + MojoHandle* data_pipe_consumer_handle); // Out. + +// Writes the data pipe producer given by |data_pipe_producer_handle|. +// +// |elements| points to data of size |*num_bytes|; |*num_bytes| must be a +// multiple of the data pipe's element size. If +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| is set in |flags|, either all the data +// is written (if enough write capacity is available) or none is. +// +// On success |*num_bytes| is set to the amount of data that was actually +// written. On failure it is unmodified. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_producer_dispatcher| is not a handle to a data pipe +// producer or |*num_bytes| is not a multiple of the data pipe's element +// size.) +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been +// closed. +// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set and the required amount of data +// (specified by |*num_bytes|) could not be written. +// |MOJO_RESULT_BUSY| if there is a two-phase write ongoing with +// |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been +// called, but not yet the matching |MojoEndWriteData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the +// consumer is still open) and |flags| does *not* have +// |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set. +MOJO_SYSTEM_EXPORT MojoResult + MojoWriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, // In/out. + MojoWriteDataFlags flags); + +// Begins a two-phase write to the data pipe producer given by +// |data_pipe_producer_handle|. On success |*buffer| will be a pointer to which +// the caller can write up to |*buffer_num_bytes| bytes of data. +// +// During a two-phase write, |data_pipe_producer_handle| is *not* writable. +// If another caller tries to write to it by calling |MojoWriteData()| or +// |MojoBeginWriteData()|, their request will fail with |MOJO_RESULT_BUSY|. +// +// If |MojoBeginWriteData()| returns MOJO_RESULT_OK and once the caller has +// finished writing data to |*buffer|, |MojoEndWriteData()| must be called to +// indicate the amount of data actually written and to complete the two-phase +// write operation. |MojoEndWriteData()| need not be called when +// |MojoBeginWriteData()| fails. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_producer_handle| is not a handle to a data pipe producer or +// flags has |MOJO_WRITE_DATA_FLAG_ALL_OR_NONE| set. +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer handle has been +// closed. +// |MOJO_RESULT_BUSY| if there is already a two-phase write ongoing with +// |data_pipe_producer_handle| (i.e., |MojoBeginWriteData()| has been +// called, but not yet the matching |MojoEndWriteData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be written (and the +// consumer is still open). +MOJO_SYSTEM_EXPORT MojoResult + MojoBeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, // Out. + uint32_t* buffer_num_bytes, // In/out. + MojoWriteDataFlags flags); + +// Ends a two-phase write that was previously initiated by +// |MojoBeginWriteData()| for the same |data_pipe_producer_handle|. +// +// |num_bytes_written| must indicate the number of bytes actually written into +// the two-phase write buffer. It must be less than or equal to the value of +// |*buffer_num_bytes| output by |MojoBeginWriteData()|, and it must be a +// multiple of the data pipe's element size. +// +// 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 |*buffer| +// is "put into" the data pipe. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_producer_handle| is not a handle to a data pipe producer or +// |num_bytes_written| is invalid (greater than the maximum value provided +// by |MojoBeginWriteData()| or not a multiple of the element size). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer is not in a +// two-phase write (e.g., |MojoBeginWriteData()| was not called or +// |MojoEndWriteData()| has already been called). +MOJO_SYSTEM_EXPORT MojoResult + MojoEndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written); + +// Reads data from the data pipe consumer given by |data_pipe_consumer_handle|. +// May also be used to discard data or query the amount of data available. +// +// If |flags| has neither |MOJO_READ_DATA_FLAG_DISCARD| nor +// |MOJO_READ_DATA_FLAG_QUERY| set, this tries to read up to |*num_bytes| (which +// must be a multiple of the data pipe's element size) bytes of data to +// |elements| and set |*num_bytes| to the amount actually read. If flags has +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set, it will either read exactly +// |*num_bytes| bytes of data or none. Additionally, if flags has +// |MOJO_READ_DATA_FLAG_PEEK| set, the data read will remain in the pipe and be +// available to future reads. +// +// If flags has |MOJO_READ_DATA_FLAG_DISCARD| set, it discards up to +// |*num_bytes| (which again must be a multiple of the element size) bytes of +// data, setting |*num_bytes| to the amount actually discarded. If flags has +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE|, it will either discard exactly +// |*num_bytes| bytes of data or none. In this case, |MOJO_READ_DATA_FLAG_QUERY| +// must not be set, and |elements| is ignored (and should typically be set to +// null). +// +// If flags has |MOJO_READ_DATA_FLAG_QUERY| set, it queries the amount of data +// available, setting |*num_bytes| to the number of bytes available. In this +// case, |MOJO_READ_DATA_FLAG_DISCARD| must not be set, and +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| is ignored, as are |elements| and the input +// value of |*num_bytes|. +// +// Returns: +// |MOJO_RESULT_OK| on success (see above for a description of the different +// operations). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_consumer_handle| is invalid, the combination of flags in +// |flags| is invalid, etc.). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been +// closed and data (or the required amount of data) was not available to +// be read or discarded. +// |MOJO_RESULT_OUT_OF_RANGE| if |flags| has |MOJO_READ_DATA_FLAG_ALL_OR_NONE| +// set and the required amount of data is not available to be read or +// discarded (and the producer is still open). +// |MOJO_RESULT_BUSY| if there is a two-phase read ongoing with +// |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been +// called, but not yet the matching |MojoEndReadData()|). +// |MOJO_RESULT_SHOULD_WAIT| if there is no data to be read or discarded (and +// the producer is still open) and |flags| does *not* have +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set. +MOJO_SYSTEM_EXPORT MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle, + void* elements, // Out. + uint32_t* num_bytes, // In/out. + MojoReadDataFlags flags); + +// Begins a two-phase read from the data pipe consumer given by +// |data_pipe_consumer_handle|. On success, |*buffer| will be a pointer from +// which the caller can read up to |*buffer_num_bytes| bytes of data. +// +// During a two-phase read, |data_pipe_consumer_handle| is *not* readable. +// If another caller tries to read from it by calling |MojoReadData()| or +// |MojoBeginReadData()|, their request will fail with |MOJO_RESULT_BUSY|. +// +// Once the caller has finished reading data from |*buffer|, |MojoEndReadData()| +// must be called to indicate the number of bytes read and to complete the +// two-phase read operation. +// +// |flags| must not have |MOJO_READ_DATA_FLAG_DISCARD|, +// |MOJO_READ_DATA_FLAG_QUERY|, |MOJO_READ_DATA_FLAG_PEEK|, or +// |MOJO_READ_DATA_FLAG_ALL_OR_NONE| set. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_consumer_handle| is not a handle to a data pipe consumer, +// or |flags| has invalid flags set.) +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe producer handle has been +// closed. +// |MOJO_RESULT_BUSY| if there is already a two-phase read ongoing with +// |data_pipe_consumer_handle| (i.e., |MojoBeginReadData()| has been +// called, but not yet the matching |MojoEndReadData()|). +// |MOJO_RESULT_SHOULD_WAIT| if no data can currently be read (and the +// producer is still open). +MOJO_SYSTEM_EXPORT MojoResult + MojoBeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, // Out. + uint32_t* buffer_num_bytes, // In/out. + MojoReadDataFlags flags); + +// Ends a two-phase read from the data pipe consumer given by +// |data_pipe_consumer_handle| that was begun by a call to |MojoBeginReadData()| +// on the same handle. |num_bytes_read| should indicate the amount of data +// actually read; it must be less than or equal to the value of +// |*buffer_num_bytes| output by |MojoBeginReadData()| and must be a multiple of +// the element size. +// +// 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. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |data_pipe_consumer_handle| is not a handle to a data pipe consumer or +// |num_bytes_written| is greater than the maximum value provided by +// |MojoBeginReadData()| or not a multiple of the element size). +// |MOJO_RESULT_FAILED_PRECONDITION| if the data pipe consumer is not in a +// two-phase read (e.g., |MojoBeginReadData()| was not called or +// |MojoEndReadData()| has already been called). +MOJO_SYSTEM_EXPORT MojoResult + MojoEndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_DATA_PIPE_H_ diff --git a/mojo/public/c/system/functions.h b/mojo/public/c/system/functions.h new file mode 100644 index 0000000000..d0656c67fc --- /dev/null +++ b/mojo/public/c/system/functions.h @@ -0,0 +1,78 @@ +// 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 contains basic functions common to different Mojo system APIs. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ +#define MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: Pointer parameters that are labelled "optional" may be null (at least +// under some circumstances). Non-const pointer parameters are also labeled +// "in", "out", or "in/out", to indicate how they are used. (Note that how/if +// such a parameter is used may depend on other parameters or the requested +// operation's success/failure. E.g., a separate |flags| parameter may control +// whether a given "in/out" parameter is used for input, output, or both.) + +// Returns the time, in microseconds, since some undefined point in the past. +// The values are only meaningful relative to other values that were obtained +// from the same device without an intervening system restart. Such values are +// guaranteed to be monotonically non-decreasing with the passage of real time. +// Although the units are microseconds, the resolution of the clock may vary and +// is typically in the range of ~1-15 ms. +MOJO_SYSTEM_EXPORT MojoTimeTicks MojoGetTimeTicksNow(void); + +// Closes the given |handle|. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle. +// +// Concurrent operations on |handle| may succeed (or fail as usual) if they +// happen before the close, be cancelled with result |MOJO_RESULT_CANCELLED| if +// they properly overlap (this is likely the case with watchers), or fail with +// |MOJO_RESULT_INVALID_ARGUMENT| if they happen after. +MOJO_SYSTEM_EXPORT MojoResult MojoClose(MojoHandle handle); + +// Queries the last known signals state of a handle. +// +// Note that no guarantees can be made about the accuracy of the returned +// signals state by the time this returns, as other threads in the system may +// change the handle's state at any time. Use with appropriate discretion. +// +// Returns: +// |MOJO_RESULT_OK| on success. |*signals_state| is populated with the +// last known signals state of |handle|. +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle or +// |signals_state| is null. +MOJO_SYSTEM_EXPORT MojoResult +MojoQueryHandleSignalsState(MojoHandle handle, + struct MojoHandleSignalsState* signals_state); + +// Retrieves system properties. See the documentation for |MojoPropertyType| for +// supported property types and their corresponding output value type. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if |type| is not recognized. In this case, +// |value| is untouched. +MOJO_SYSTEM_EXPORT MojoResult MojoGetProperty(MojoPropertyType type, + void* value); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_FUNCTIONS_H_ diff --git a/mojo/public/c/system/macros.h b/mojo/public/c/system/macros.h new file mode 100644 index 0000000000..917c69cc15 --- /dev/null +++ b/mojo/public/c/system/macros.h @@ -0,0 +1,49 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_SYSTEM_MACROS_H_ +#define MOJO_PUBLIC_C_SYSTEM_MACROS_H_ + +#include <stddef.h> + +// Assert things at compile time. (|msg| should be a valid identifier name.) +// This macro is currently C++-only, but we want to use it in the C core.h. +// Use like: +// MOJO_STATIC_ASSERT(sizeof(Foo) == 12, "Foo has invalid size"); +#if defined(__cplusplus) +#define MOJO_STATIC_ASSERT(expr, msg) static_assert(expr, msg) +#else +#define MOJO_STATIC_ASSERT(expr, msg) +#endif + +// Like the C++11 |alignof| operator. +#if __cplusplus >= 201103L +#define MOJO_ALIGNOF(type) alignof(type) +#elif defined(__GNUC__) +#define MOJO_ALIGNOF(type) __alignof__(type) +#elif defined(_MSC_VER) +// The use of |sizeof| is to work around a bug in MSVC 2010 (see +// http://goo.gl/isH0C; supposedly fixed since then). +#define MOJO_ALIGNOF(type) (sizeof(type) - sizeof(type) + __alignof(type)) +#else +#error "Please define MOJO_ALIGNOF() for your compiler." +#endif + +// Specify the alignment of a |struct|, etc. +// Use like: +// struct MOJO_ALIGNAS(8) Foo { ... }; +// Unlike the C++11 |alignas()|, |alignment| must be an integer. It may not be a +// type, nor can it be an expression like |MOJO_ALIGNOF(type)| (due to the +// non-C++11 MSVS version). +#if __cplusplus >= 201103L +#define MOJO_ALIGNAS(alignment) alignas(alignment) +#elif defined(__GNUC__) +#define MOJO_ALIGNAS(alignment) __attribute__((aligned(alignment))) +#elif defined(_MSC_VER) +#define MOJO_ALIGNAS(alignment) __declspec(align(alignment)) +#else +#error "Please define MOJO_ALIGNAS() for your compiler." +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_MACROS_H_ diff --git a/mojo/public/c/system/message_pipe.h b/mojo/public/c/system/message_pipe.h new file mode 100644 index 0000000000..b759bc73db --- /dev/null +++ b/mojo/public/c/system/message_pipe.h @@ -0,0 +1,341 @@ +// 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 contains types/constants and functions specific to message pipes. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ +#define MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ + +#include <stdint.h> + +#include "mojo/public/c/system/macros.h" +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +// |MojoMessageHandle|: Used to refer to message objects created by +// |MojoAllocMessage()| and transferred by |MojoWriteMessageNew()| or +// |MojoReadMessageNew()|. + +typedef uintptr_t MojoMessageHandle; + +#ifdef __cplusplus +const MojoMessageHandle MOJO_MESSAGE_HANDLE_INVALID = 0; +#else +#define MOJO_MESSAGE_HANDLE_INVALID ((MojoMessageHandle)0) +#endif + +// |MojoCreateMessagePipeOptions|: Used to specify creation parameters for a +// message pipe to |MojoCreateMessagePipe()|. +// |uint32_t struct_size|: Set to the size of the +// |MojoCreateMessagePipeOptions| struct. (Used to allow for future +// extensions.) +// |MojoCreateMessagePipeOptionsFlags flags|: Used to specify different modes +// of operation. +// |MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE|: No flags; default mode. + +typedef uint32_t MojoCreateMessagePipeOptionsFlags; + +#ifdef __cplusplus +const MojoCreateMessagePipeOptionsFlags + MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE = 0; +#else +#define MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE \ + ((MojoCreateMessagePipeOptionsFlags)0) +#endif + +MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment"); +struct MOJO_ALIGNAS(8) MojoCreateMessagePipeOptions { + uint32_t struct_size; + MojoCreateMessagePipeOptionsFlags flags; +}; +MOJO_STATIC_ASSERT(sizeof(MojoCreateMessagePipeOptions) == 8, + "MojoCreateMessagePipeOptions has wrong size"); + +// |MojoWriteMessageFlags|: Used to specify different modes to +// |MojoWriteMessage()|. +// |MOJO_WRITE_MESSAGE_FLAG_NONE| - No flags; default mode. + +typedef uint32_t MojoWriteMessageFlags; + +#ifdef __cplusplus +const MojoWriteMessageFlags MOJO_WRITE_MESSAGE_FLAG_NONE = 0; +#else +#define MOJO_WRITE_MESSAGE_FLAG_NONE ((MojoWriteMessageFlags)0) +#endif + +// |MojoReadMessageFlags|: Used to specify different modes to +// |MojoReadMessage()|. +// |MOJO_READ_MESSAGE_FLAG_NONE| - No flags; default mode. +// |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| - If the message is unable to be read +// for whatever reason (e.g., the caller-supplied buffer is too small), +// discard the message (i.e., simply dequeue it). + +typedef uint32_t MojoReadMessageFlags; + +#ifdef __cplusplus +const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_NONE = 0; +const MojoReadMessageFlags MOJO_READ_MESSAGE_FLAG_MAY_DISCARD = 1 << 0; +#else +#define MOJO_READ_MESSAGE_FLAG_NONE ((MojoReadMessageFlags)0) +#define MOJO_READ_MESSAGE_FLAG_MAY_DISCARD ((MojoReadMessageFlags)1 << 0) +#endif + +// |MojoAllocMessageFlags|: Used to specify different options for +// |MojoAllocMessage()|. +// |MOJO_ALLOC_MESSAGE_FLAG_NONE| - No flags; default mode. + +typedef uint32_t MojoAllocMessageFlags; + +#ifdef __cplusplus +const MojoAllocMessageFlags MOJO_ALLOC_MESSAGE_FLAG_NONE = 0; +#else +#define MOJO_ALLOC_MESSAGE_FLAG_NONE ((MojoAllocMessageFlags)0) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: See the comment in functions.h about the meaning of the "optional" +// label for pointer parameters. + +// Creates a message pipe, which is a bidirectional communication channel for +// framed data (i.e., messages). Messages can contain plain data and/or Mojo +// handles. +// +// |options| may be set to null for a message pipe with the default options. +// +// On success, |*message_pipe_handle0| and |*message_pipe_handle1| are set to +// handles for the two endpoints (ports) for the message pipe. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., +// |*options| is invalid). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a process/system/quota/etc. limit has +// been reached. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateMessagePipe( + const struct MojoCreateMessagePipeOptions* options, // Optional. + MojoHandle* message_pipe_handle0, // Out. + MojoHandle* message_pipe_handle1); // Out. + +// Writes a message to the message pipe endpoint given by |message_pipe_handle|, +// with message data specified by |bytes| of size |num_bytes| and attached +// handles specified by |handles| of count |num_handles|, and options specified +// by |flags|. If there is no message data, |bytes| may be null, in which case +// |num_bytes| must be zero. If there are no attached handles, |handles| may be +// null, in which case |num_handles| must be zero. +// +// If handles are attached, the handles will no longer be valid (on success the +// receiver will receive equivalent, but logically different, handles). Handles +// to be sent should not be in simultaneous use (e.g., on another thread). +// +// Returns: +// |MOJO_RESULT_OK| on success (i.e., the message was enqueued). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid (e.g., if +// |message_pipe_handle| is not a valid handle, or some of the +// requirements above are not satisfied). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if some system limit has been reached, or +// the number of handles to send is too large (TODO(vtl): reconsider the +// latter case). +// |MOJO_RESULT_FAILED_PRECONDITION| if the other endpoint has been closed. +// Note that closing an endpoint is not necessarily synchronous (e.g., +// across processes), so this function may succeed even if the other +// endpoint has been closed (in which case the message would be dropped). +// |MOJO_RESULT_UNIMPLEMENTED| if an unsupported flag was set in |*options|. +// |MOJO_RESULT_BUSY| if some handle to be sent is currently in use. +// +// TODO(vtl): Add a notion of capacity for message pipes, and return +// |MOJO_RESULT_SHOULD_WAIT| if the message pipe is full. +MOJO_SYSTEM_EXPORT MojoResult + MojoWriteMessage(MojoHandle message_pipe_handle, + const void* bytes, // Optional. + uint32_t num_bytes, + const MojoHandle* handles, // Optional. + uint32_t num_handles, + MojoWriteMessageFlags flags); + +// Writes a message to the message pipe endpoint given by |message_pipe_handle|. +// +// |message|: A message object allocated by |MojoAllocMessage()|. Ownership of +// the message is passed into Mojo. +// +// Returns results corresponding to |MojoWriteMessage()| above. +MOJO_SYSTEM_EXPORT MojoResult + MojoWriteMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags); + +// Reads the next message from a message pipe, or indicates the size of the +// message if it cannot fit in the provided buffers. The message will be read +// in its entirety or not at all; if it is not, it will remain enqueued unless +// the |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| flag was passed. At most one +// message will be consumed from the queue, and the return value will indicate +// whether a message was successfully read. +// +// |num_bytes| and |num_handles| are optional in/out parameters that on input +// must be set to the sizes of the |bytes| and |handles| arrays, and on output +// will be set to the actual number of bytes or handles contained in the +// message (even if the message was not retrieved due to being too large). +// Either |num_bytes| or |num_handles| may be null if the message is not +// expected to contain the corresponding type of data, but such a call would +// fail with |MOJO_RESULT_RESOURCE_EXHAUSTED| if the message in fact did +// contain that type of data. +// +// |bytes| and |handles| will receive the contents of the message, if it is +// retrieved. Either or both may be null, in which case the corresponding size +// parameter(s) must also be set to zero or passed as null. +// +// Returns: +// |MOJO_RESULT_OK| on success (i.e., a message was actually read). +// |MOJO_RESULT_INVALID_ARGUMENT| if some argument was invalid. +// |MOJO_RESULT_FAILED_PRECONDITION| if the other endpoint has been closed. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the message was too large to fit in the +// provided buffer(s). The message will have been left in the queue or +// discarded, depending on flags. +// |MOJO_RESULT_SHOULD_WAIT| if no message was available to be read. +// +// TODO(vtl): Reconsider the |MOJO_RESULT_RESOURCE_EXHAUSTED| error code; should +// distinguish this from the hitting-system-limits case. +MOJO_SYSTEM_EXPORT MojoResult + MojoReadMessage(MojoHandle message_pipe_handle, + void* bytes, // Optional out. + uint32_t* num_bytes, // Optional in/out. + MojoHandle* handles, // Optional out. + uint32_t* num_handles, // Optional in/out. + MojoReadMessageFlags flags); + +// Reads the next message from a message pipe and returns a message containing +// the message bytes. The returned message must eventually be freed using +// |MojoFreeMessage()|. +// +// Message payload can be accessed using |MojoGetMessageBuffer()|. +// +// |message_pipe_handle|, |num_bytes|, |handles|, |num_handles|, and |flags| +// correspond to their use in |MojoReadMessage()| above, with the +// exception that |num_bytes| is only an output argument. +// |message| must be non-null unless |MOJO_READ_MESSAGE_FLAG_MAY_DISCARD| is +// set in flags. +// +// Return values correspond to the return values for |MojoReadMessage()| above. +// On success (MOJO_RESULT_OK), |*message| will contain a handle to a message +// object which may be passed to |MojoGetMessageBuffer()|. The caller owns the +// message object and is responsible for freeing it via |MojoFreeMessage()|. +MOJO_SYSTEM_EXPORT MojoResult + MojoReadMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle* message, // Optional out. + uint32_t* num_bytes, // Optional out. + MojoHandle* handles, // Optional out. + uint32_t* num_handles, // Optional in/out. + MojoReadMessageFlags flags); + +// Fuses two message pipe endpoints together. Given two pipes: +// +// A <-> B and C <-> D +// +// Fusing handle B and handle C results in a single pipe: +// +// A <-> D +// +// Handles B and C are ALWAYS closed. Any unread messages at C will eventually +// be delivered to A, and any unread messages at B will eventually be delivered +// to D. +// +// NOTE: A handle may only be fused if it is an open message pipe handle which +// has not been written to. +// +// Returns: +// |MOJO_RESULT_OK| on success. +// |MOJO_RESULT_FAILED_PRECONDITION| if both handles were valid message pipe +// handles but could not be merged (e.g. one of them has been written to). +// |MOJO_INVALID_ARGUMENT| if either handle is not a fusable message pipe +// handle. +MOJO_SYSTEM_EXPORT MojoResult + MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1); + +// Allocates a new message whose ownership may be passed to +// |MojoWriteMessageNew()|. Use |MojoGetMessageBuffer()| to retrieve the address +// of the mutable message payload. +// +// |num_bytes|: The size of the message payload in bytes. +// |handles|: An array of handles to transfer in the message. This takes +// ownership of and invalidates all contained handles. Must be null if and +// only if |num_handles| is 0. +// |num_handles|: The number of handles contained in |handles|. +// |flags|: Must be |MOJO_CREATE_MESSAGE_FLAG_NONE|. +// |message|: The address of a handle to be filled with the allocated message's +// handle. Must be non-null. +// +// Returns: +// |MOJO_RESULT_OK| if the message was successfully allocated. In this case +// |*message| will be populated with a handle to an allocated message +// with a buffer large enough to hold |num_bytes| contiguous bytes. +// |MOJO_RESULT_INVALID_ARGUMENT| if one or more handles in |handles| was +// invalid, or |handles| was null with a non-zero |num_handles|. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if allocation failed because either +// |num_bytes| or |num_handles| exceeds an implementation-defined maximum. +// |MOJO_RESULT_BUSY| if one or more handles in |handles| cannot be sent at +// the time of this call. +// +// Only upon successful message allocation will all handles in |handles| be +// transferred into the message and invalidated. +MOJO_SYSTEM_EXPORT MojoResult +MojoAllocMessage(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message); // Out + +// Frees a message allocated by |MojoAllocMessage()| or |MojoReadMessageNew()|. +// +// |message|: The message to free. This must correspond to a message previously +// allocated by |MojoAllocMessage()| or |MojoReadMessageNew()|. Note that if +// the message has already been passed to |MojoWriteMessageNew()| it should +// NOT also be freed with this API. +// +// Returns: +// |MOJO_RESULT_OK| if |message| was valid and has been freed. +// |MOJO_RESULT_INVALID_ARGUMENT| if |message| was not a valid message. +MOJO_SYSTEM_EXPORT MojoResult MojoFreeMessage(MojoMessageHandle message); + +// Retrieves the address of mutable message bytes for a message allocated by +// either |MojoAllocMessage()| or |MojoReadMessageNew()|. +// +// Returns: +// |MOJO_RESULT_OK| if |message| is a valid message object. |*buffer| will +// be updated to point to mutable message bytes. +// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message object. +// +// NOTE: A returned buffer address is always guaranteed to be 8-byte aligned. +MOJO_SYSTEM_EXPORT MojoResult MojoGetMessageBuffer(MojoMessageHandle message, + void** buffer); // Out + +// Notifies the system that a bad message was received on a message pipe, +// according to whatever criteria the caller chooses. This ultimately tries to +// notify the embedder about the bad message, and the embedder may enforce some +// policy for dealing with the source of the message (e.g. close the pipe, +// terminate, a process, etc.) The embedder may not be notified if the calling +// process has lost its connection to the source process. +// +// |message|: The message to report as bad. This must have come from a call to +// |MojoReadMessageNew()|. +// |error|: An error string which may provide the embedder with context when +// notified of this error. +// |error_num_bytes|: The length of |error| in bytes. +// +// Returns: +// |MOJO_RESULT_OK| if successful. +// |MOJO_RESULT_INVALID_ARGUMENT| if |message| is not a valid message. +MOJO_SYSTEM_EXPORT MojoResult +MojoNotifyBadMessage(MojoMessageHandle message, + const char* error, + size_t error_num_bytes); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_MESSAGE_PIPE_H_ diff --git a/mojo/public/c/system/platform_handle.h b/mojo/public/c/system/platform_handle.h new file mode 100644 index 0000000000..7449c2e794 --- /dev/null +++ b/mojo/public/c/system/platform_handle.h @@ -0,0 +1,191 @@ +// 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. + +// This file contains types/functions and constants for platform handle wrapping +// and unwrapping APIs. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_ +#define MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_ + +#include <stdint.h> + +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// |MojoPlatformHandleType|: A value indicating the specific type of platform +// handle encapsulated by a MojoPlatformHandle (see below.) This is stored +// in the MojoPlatformHandle's |type| field and determines how the |value| +// field is interpreted. +// +// |MOJO_PLATFORM_HANDLE_TYPE_INVALID| - An invalid platform handle. +// |MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR| - A file descriptor. Only valid +// on POSIX systems. +// |MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT| - A Mach port. Only valid on OS X. +// |MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE| - A Windows HANDLE value. Only +// valid on Windows. + +typedef uint32_t MojoPlatformHandleType; + +#ifdef __cplusplus +const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_INVALID = 0; +const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR = 1; +const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT = 2; +const MojoPlatformHandleType MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE = 3; +#else +#define MOJO_PLATFORM_HANDLE_TYPE_INVALID ((MojoPlatformHandleType)0) +#define MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR ((MojoPlatformHandleType)1) +#define MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT ((MojoPlatformHandleType)2) +#define MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE ((MojoPlatformHandleType)3) +#endif + +// |MojoPlatformHandle|: A handle to a native platform object. +// +// |uint32_t struct_size|: The size of this structure. Used for versioning +// to allow for future extensions. +// +// |MojoPlatformHandleType type|: The type of handle stored in |value|. +// +// |uint64_t value|: The value of this handle. Ignored if |type| is +// MOJO_PLATFORM_HANDLE_TYPE_INVALID. Otherwise the meaning of this +// value depends on the value of |type|. +// + +struct MOJO_ALIGNAS(8) MojoPlatformHandle { + uint32_t struct_size; + MojoPlatformHandleType type; + uint64_t value; +}; +MOJO_STATIC_ASSERT(sizeof(MojoPlatformHandle) == 16, + "MojoPlatformHandle has wrong size"); + +// |MojoPlatformSharedBufferHandleFlags|: Flags relevant to wrapped platform +// shared buffers. +// +// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_NONE| - No flags. +// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_READ_ONLY| - Indicates that the wrapped +// buffer handle may only be mapped for reading. + +typedef uint32_t MojoPlatformSharedBufferHandleFlags; + +#ifdef __cplusplus +const MojoPlatformSharedBufferHandleFlags +MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE = 0; + +const MojoPlatformSharedBufferHandleFlags +MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY = 1 << 0; +#else +#define MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE \ + ((MojoPlatformSharedBufferHandleFlags)0) + +#define MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY \ + ((MojoPlatformSharedBufferHandleFlags)1 << 0) +#endif + +// Wraps a native platform handle as a Mojo handle which can be transferred +// over a message pipe. Takes ownership of the underlying native platform +// object. +// +// |platform_handle|: The platform handle to wrap. +// +// Returns: +// |MOJO_RESULT_OK| if the handle was successfully wrapped. In this case +// |*mojo_handle| contains the Mojo handle of the wrapped object. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if the system is out of handles. +// |MOJO_RESULT_INVALID_ARGUMENT| if |platform_handle| was not a valid +// platform handle. +// +// NOTE: It is not always possible to detect if |platform_handle| is valid, +// particularly when |platform_handle->type| is valid but +// |platform_handle->value| does not represent a valid platform object. +MOJO_SYSTEM_EXPORT MojoResult +MojoWrapPlatformHandle(const struct MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle); // Out + +// Unwraps a native platform handle from a Mojo handle. If this call succeeds, +// ownership of the underlying platform object is assumed by the caller. The +// The Mojo handle is always closed regardless of success or failure. +// +// |mojo_handle|: The Mojo handle from which to unwrap the native platform +// handle. +// +// Returns: +// |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case +// |*platform_handle| contains the unwrapped platform handle. +// |MOJO_RESULT_INVALID_ARGUMENT| if |mojo_handle| was not a valid Mojo +// handle wrapping a platform handle. +MOJO_SYSTEM_EXPORT MojoResult +MojoUnwrapPlatformHandle(MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle); // Out + +// Wraps a native platform shared buffer handle as a Mojo shared buffer handle +// which can be used exactly like a shared buffer handle created by +// |MojoCreateSharedBuffer()| or |MojoDuplicateBufferHandle()|. +// +// Takes ownership of the native platform shared buffer handle. +// +// |platform_handle|: The platform handle to wrap. Must be a native handle to a +// shared buffer object. +// |num_bytes|: The size of the shared buffer in bytes. +// |flags|: Flags which influence the treatment of the shared buffer object. See +// below. +// +// Flags: +// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE| indicates default behavior. +// No flags set. +// |MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY| indicates that the +// buffer handled to be wrapped may only be mapped as read-only. This +// flag does NOT change the access control of the buffer in any way. +// +// Returns: +// |MOJO_RESULT_OK| if the handle was successfully wrapped. In this case +// |*mojo_handle| contains a Mojo shared buffer handle. +// |MOJO_RESULT_INVALID_ARGUMENT| if |platform_handle| was not a valid +// platform shared buffer handle. +MOJO_SYSTEM_EXPORT MojoResult +MojoWrapPlatformSharedBufferHandle( + const struct MojoPlatformHandle* platform_handle, + size_t num_bytes, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle); // Out + +// Unwraps a native platform shared buffer handle from a Mojo shared buffer +// handle. If this call succeeds, ownership of the underlying shared buffer +// object is assumed by the caller. +// +// The Mojo handle is always closed regardless of success or failure. +// +// |mojo_handle|: The Mojo shared buffer handle to unwrap. +// +// |platform_handle|, |num_bytes| and |flags| are used to receive output values +// and MUST always be non-null. +// +// Returns: +// |MOJO_RESULT_OK| if the handle was successfully unwrapped. In this case +// |*platform_handle| contains a platform shared buffer handle, +// |*num_bytes| contains the size of the shared buffer object, and +// |*flags| indicates flags relevant to the wrapped buffer (see below). +// |MOJO_RESULT_INVALID_ARGUMENT| if |mojo_handle| is not a valid Mojo +// shared buffer handle. +// +// Flags which may be set in |*flags| upon success: +// |MOJO_PLATFORM_SHARED_BUFFER_FLAG_READ_ONLY| is set iff the unwrapped +// shared buffer handle may only be mapped as read-only. +MOJO_SYSTEM_EXPORT MojoResult +MojoUnwrapPlatformSharedBufferHandle( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle, + size_t* num_bytes, + MojoPlatformSharedBufferHandleFlags* flags); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_PLATFORM_HANDLE_H_ diff --git a/mojo/public/c/system/set_thunks_for_app.cc b/mojo/public/c/system/set_thunks_for_app.cc new file mode 100644 index 0000000000..335cc02b79 --- /dev/null +++ b/mojo/public/c/system/set_thunks_for_app.cc @@ -0,0 +1,20 @@ +// 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. + +#include "mojo/public/c/system/thunks.h" + +extern "C" { + +#if defined(WIN32) +#define THUNKS_EXPORT __declspec(dllexport) +#else +#define THUNKS_EXPORT __attribute__((visibility("default"))) +#endif + +THUNKS_EXPORT size_t MojoSetSystemThunks( + const MojoSystemThunks* system_thunks) { + return MojoEmbedderSetSystemThunks(system_thunks); +} + +} // extern "C" diff --git a/mojo/public/c/system/system_export.h b/mojo/public/c/system/system_export.h new file mode 100644 index 0000000000..775f6679f9 --- /dev/null +++ b/mojo/public/c/system/system_export.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ +#define MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPLEMENTATION) +#define MOJO_SYSTEM_EXPORT __declspec(dllexport) +#else +#define MOJO_SYSTEM_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPLEMENTATION) +#define MOJO_SYSTEM_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_SYSTEM_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) + +#define MOJO_SYSTEM_EXPORT + +#endif // defined(COMPONENT_BUILD) + +#endif // MOJO_PUBLIC_C_SYSTEM_SYSTEM_EXPORT_H_ diff --git a/mojo/public/c/system/tests/BUILD.gn b/mojo/public/c/system/tests/BUILD.gn new file mode 100644 index 0000000000..bace63c4aa --- /dev/null +++ b/mojo/public/c/system/tests/BUILD.gn @@ -0,0 +1,38 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("tests") { + testonly = true + + visibility = [ + "//mojo/public/cpp/system/tests:mojo_public_system_unittests", + "//mojo/public/cpp/system/tests:tests", + ] + + sources = [ + "core_unittest.cc", + "core_unittest_pure_c.c", + "macros_unittest.cc", + ] + + deps = [ + "//mojo/public/c/system", + "//mojo/public/cpp/system", + "//testing/gtest", + ] +} + +source_set("perftests") { + testonly = true + + sources = [ + "core_perftest.cc", + ] + + deps = [ + "//mojo/public/cpp/system", + "//mojo/public/cpp/test_support:test_utils", + "//testing/gtest", + ] +} diff --git a/mojo/public/c/system/tests/core_perftest.cc b/mojo/public/c/system/tests/core_perftest.cc new file mode 100644 index 0000000000..cab465b43d --- /dev/null +++ b/mojo/public/c/system/tests/core_perftest.cc @@ -0,0 +1,329 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This tests the performance of the C API. + +#include "mojo/public/c/system/core.h" + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> + +#include "base/macros.h" +#include "base/threading/simple_thread.h" +#include "mojo/public/cpp/system/wait.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if !defined(WIN32) +#include <time.h> +#endif // !defined(WIN32) + +namespace { + +#if !defined(WIN32) +class MessagePipeWriterThread : public base::SimpleThread { + public: + MessagePipeWriterThread(MojoHandle handle, uint32_t num_bytes) + : SimpleThread("MessagePipeWriterThread"), + handle_(handle), + num_bytes_(num_bytes), + num_writes_(0) {} + ~MessagePipeWriterThread() override {} + + void Run() override { + char buffer[10000]; + assert(num_bytes_ <= sizeof(buffer)); + + // TODO(vtl): Should I throttle somehow? + for (;;) { + MojoResult result = MojoWriteMessage(handle_, buffer, num_bytes_, nullptr, + 0, MOJO_WRITE_MESSAGE_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + num_writes_++; + continue; + } + + // We failed to write. + // Either |handle_| or its peer was closed. + assert(result == MOJO_RESULT_INVALID_ARGUMENT || + result == MOJO_RESULT_FAILED_PRECONDITION); + break; + } + } + + // Use only after joining the thread. + int64_t num_writes() const { return num_writes_; } + + private: + const MojoHandle handle_; + const uint32_t num_bytes_; + int64_t num_writes_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipeWriterThread); +}; + +class MessagePipeReaderThread : public base::SimpleThread { + public: + explicit MessagePipeReaderThread(MojoHandle handle) + : SimpleThread("MessagePipeReaderThread"), + handle_(handle), + num_reads_(0) {} + ~MessagePipeReaderThread() override {} + + void Run() override { + char buffer[10000]; + + for (;;) { + uint32_t num_bytes = static_cast<uint32_t>(sizeof(buffer)); + MojoResult result = MojoReadMessage(handle_, buffer, &num_bytes, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + num_reads_++; + continue; + } + + if (result == MOJO_RESULT_SHOULD_WAIT) { + result = mojo::Wait(mojo::Handle(handle_), MOJO_HANDLE_SIGNAL_READABLE); + if (result == MOJO_RESULT_OK) { + // Go to the top of the loop to read again. + continue; + } + } + + // We failed to read and possibly failed to wait. + // Either |handle_| or its peer was closed. + assert(result == MOJO_RESULT_INVALID_ARGUMENT || + result == MOJO_RESULT_FAILED_PRECONDITION); + break; + } + } + + // Use only after joining the thread. + int64_t num_reads() const { return num_reads_; } + + private: + const MojoHandle handle_; + int64_t num_reads_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipeReaderThread); +}; +#endif // !defined(WIN32) + +class CorePerftest : public testing::Test { + public: + CorePerftest() : buffer_(nullptr), num_bytes_(0) {} + ~CorePerftest() override {} + + static void NoOp(void* /*closure*/) {} + + static void MessagePipe_CreateAndClose(void* closure) { + CorePerftest* self = static_cast<CorePerftest*>(closure); + MojoResult result = MojoCreateMessagePipe(nullptr, &self->h0_, &self->h1_); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + result = MojoClose(self->h0_); + assert(result == MOJO_RESULT_OK); + result = MojoClose(self->h1_); + assert(result == MOJO_RESULT_OK); + } + + static void MessagePipe_WriteAndRead(void* closure) { + CorePerftest* self = static_cast<CorePerftest*>(closure); + MojoResult result = + MojoWriteMessage(self->h0_, self->buffer_, self->num_bytes_, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + uint32_t read_bytes = self->num_bytes_; + result = MojoReadMessage(self->h1_, self->buffer_, &read_bytes, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE); + assert(result == MOJO_RESULT_OK); + } + + static void MessagePipe_EmptyRead(void* closure) { + CorePerftest* self = static_cast<CorePerftest*>(closure); + MojoResult result = + MojoReadMessage(self->h0_, nullptr, nullptr, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_SHOULD_WAIT); + } + + protected: +#if !defined(WIN32) + void DoMessagePipeThreadedTest(unsigned num_writers, + unsigned num_readers, + uint32_t num_bytes) { + static const int64_t kPerftestTimeMicroseconds = 3 * 1000000; + + assert(num_writers > 0); + assert(num_readers > 0); + + MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + + std::vector<MessagePipeWriterThread*> writers; + for (unsigned i = 0; i < num_writers; i++) + writers.push_back(new MessagePipeWriterThread(h0_, num_bytes)); + + std::vector<MessagePipeReaderThread*> readers; + for (unsigned i = 0; i < num_readers; i++) + readers.push_back(new MessagePipeReaderThread(h1_)); + + // Start time here, just before we fire off the threads. + const MojoTimeTicks start_time = MojoGetTimeTicksNow(); + + // Interleave the starts. + for (unsigned i = 0; i < num_writers || i < num_readers; i++) { + if (i < num_writers) + writers[i]->Start(); + if (i < num_readers) + readers[i]->Start(); + } + + Sleep(kPerftestTimeMicroseconds); + + // Close both handles to make writers and readers stop immediately. + result = MojoClose(h0_); + assert(result == MOJO_RESULT_OK); + result = MojoClose(h1_); + assert(result == MOJO_RESULT_OK); + + // Join everything. + for (unsigned i = 0; i < num_writers; i++) + writers[i]->Join(); + for (unsigned i = 0; i < num_readers; i++) + readers[i]->Join(); + + // Stop time here. + MojoTimeTicks end_time = MojoGetTimeTicksNow(); + + // Add up write and read counts, and destroy the threads. + int64_t num_writes = 0; + for (unsigned i = 0; i < num_writers; i++) { + num_writes += writers[i]->num_writes(); + delete writers[i]; + } + writers.clear(); + int64_t num_reads = 0; + for (unsigned i = 0; i < num_readers; i++) { + num_reads += readers[i]->num_reads(); + delete readers[i]; + } + readers.clear(); + + char sub_test_name[200]; + sprintf(sub_test_name, "%uw_%ur_%ubytes", num_writers, num_readers, + static_cast<unsigned>(num_bytes)); + mojo::test::LogPerfResult( + "MessagePipe_Threaded_Writes", sub_test_name, + 1000000.0 * static_cast<double>(num_writes) / (end_time - start_time), + "writes/second"); + mojo::test::LogPerfResult( + "MessagePipe_Threaded_Reads", sub_test_name, + 1000000.0 * static_cast<double>(num_reads) / (end_time - start_time), + "reads/second"); + } +#endif // !defined(WIN32) + + MojoHandle h0_; + MojoHandle h1_; + + void* buffer_; + uint32_t num_bytes_; + + private: +#if !defined(WIN32) + void Sleep(int64_t microseconds) { + struct timespec req = { + static_cast<time_t>(microseconds / 1000000), // Seconds. + static_cast<long>(microseconds % 1000000) * 1000L // Nanoseconds. + }; + int rv = nanosleep(&req, nullptr); + ALLOW_UNUSED_LOCAL(rv); + assert(rv == 0); + } +#endif // !defined(WIN32) + + DISALLOW_COPY_AND_ASSIGN(CorePerftest); +}; + +// A no-op test so we can compare performance. +TEST_F(CorePerftest, NoOp) { + mojo::test::IterateAndReportPerf("Iterate_NoOp", nullptr, &CorePerftest::NoOp, + this); +} + +TEST_F(CorePerftest, MessagePipe_CreateAndClose) { + mojo::test::IterateAndReportPerf("MessagePipe_CreateAndClose", nullptr, + &CorePerftest::MessagePipe_CreateAndClose, + this); +} + +TEST_F(CorePerftest, MessagePipe_WriteAndRead) { + MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + char buffer[10000] = {0}; + buffer_ = buffer; + num_bytes_ = 10u; + mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "10bytes", + &CorePerftest::MessagePipe_WriteAndRead, + this); + num_bytes_ = 100u; + mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "100bytes", + &CorePerftest::MessagePipe_WriteAndRead, + this); + num_bytes_ = 1000u; + mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "1000bytes", + &CorePerftest::MessagePipe_WriteAndRead, + this); + num_bytes_ = 10000u; + mojo::test::IterateAndReportPerf("MessagePipe_WriteAndRead", "10000bytes", + &CorePerftest::MessagePipe_WriteAndRead, + this); + result = MojoClose(h0_); + assert(result == MOJO_RESULT_OK); + result = MojoClose(h1_); + assert(result == MOJO_RESULT_OK); +} + +TEST_F(CorePerftest, MessagePipe_EmptyRead) { + MojoResult result = MojoCreateMessagePipe(nullptr, &h0_, &h1_); + ALLOW_UNUSED_LOCAL(result); + assert(result == MOJO_RESULT_OK); + mojo::test::IterateAndReportPerf("MessagePipe_EmptyRead", nullptr, + &CorePerftest::MessagePipe_EmptyRead, this); + result = MojoClose(h0_); + assert(result == MOJO_RESULT_OK); + result = MojoClose(h1_); + assert(result == MOJO_RESULT_OK); +} + +#if !defined(WIN32) +TEST_F(CorePerftest, MessagePipe_Threaded) { + DoMessagePipeThreadedTest(1u, 1u, 100u); + DoMessagePipeThreadedTest(2u, 2u, 100u); + DoMessagePipeThreadedTest(3u, 3u, 100u); + DoMessagePipeThreadedTest(10u, 10u, 100u); + DoMessagePipeThreadedTest(10u, 1u, 100u); + DoMessagePipeThreadedTest(1u, 10u, 100u); + + // For comparison of overhead: + DoMessagePipeThreadedTest(1u, 1u, 10u); + // 100 was done above. + DoMessagePipeThreadedTest(1u, 1u, 1000u); + DoMessagePipeThreadedTest(1u, 1u, 10000u); + + DoMessagePipeThreadedTest(3u, 3u, 10u); + // 100 was done above. + DoMessagePipeThreadedTest(3u, 3u, 1000u); + DoMessagePipeThreadedTest(3u, 3u, 10000u); +} +#endif // !defined(WIN32) + +} // namespace diff --git a/mojo/public/c/system/tests/core_unittest.cc b/mojo/public/c/system/tests/core_unittest.cc new file mode 100644 index 0000000000..a9da255717 --- /dev/null +++ b/mojo/public/c/system/tests/core_unittest.cc @@ -0,0 +1,322 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file tests the C API. + +#include "mojo/public/c/system/core.h" + +#include <stdint.h> +#include <string.h> + +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const MojoHandleSignals kSignalReadadableWritable = + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE; + +const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; + +TEST(CoreTest, GetTimeTicksNow) { + const MojoTimeTicks start = MojoGetTimeTicksNow(); + EXPECT_NE(static_cast<MojoTimeTicks>(0), start) + << "MojoGetTimeTicksNow should return nonzero value"; +} + +// The only handle that's guaranteed to be invalid is |MOJO_HANDLE_INVALID|. +// Tests that everything that takes a handle properly recognizes it. +TEST(CoreTest, InvalidHandle) { + MojoHandle h0, h1; + char buffer[10] = {0}; + uint32_t buffer_size; + void* write_pointer; + const void* read_pointer; + + // Close: + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(MOJO_HANDLE_INVALID)); + + // Message pipe: + h0 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWriteMessage(h0, buffer, 3, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Data pipe: + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWriteData(h0, buffer, &buffer_size, MOJO_WRITE_DATA_FLAG_NONE)); + write_pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoBeginWriteData(h0, &write_pointer, &buffer_size, + MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndWriteData(h0, 1)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoReadData(h0, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE)); + read_pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoBeginReadData(h0, &read_pointer, &buffer_size, + MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoEndReadData(h0, 1)); + + // Shared buffer: + h1 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoDuplicateBufferHandle(h0, nullptr, &h1)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoMapBuffer(h0, 0, 1, &write_pointer, MOJO_MAP_BUFFER_FLAG_NONE)); +} + +TEST(CoreTest, BasicMessagePipe) { + MojoHandle h0, h1; + MojoHandleSignals sig; + char buffer[10] = {0}; + uint32_t buffer_size; + + h0 = MOJO_HANDLE_INVALID; + h1 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &h0, &h1)); + EXPECT_NE(h0, MOJO_HANDLE_INVALID); + EXPECT_NE(h1, MOJO_HANDLE_INVALID); + + // Shouldn't be readable, we haven't written anything. Should be writable. + MojoHandleSignalsState state; + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + // Try to read. + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Write to |h1|. + static const char kHello[] = "hello"; + buffer_size = static_cast<uint32_t>(sizeof(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWriteMessage(h1, kHello, buffer_size, nullptr, + 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // |h0| should be readable. + size_t result_index = 1; + MojoHandleSignalsState states[1]; + sig = MOJO_HANDLE_SIGNAL_READABLE; + Handle handle0(h0); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::WaitMany(&handle0, &sig, 1, &result_index, states)); + + EXPECT_EQ(0u, result_index); + EXPECT_EQ(kSignalReadadableWritable, states[0].satisfied_signals); + EXPECT_EQ(kSignalAll, states[0].satisfiable_signals); + + // Read from |h0|. + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast<uint32_t>(sizeof(kHello)), buffer_size); + EXPECT_STREQ(kHello, buffer); + + // |h0| should no longer be readable. + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(h0, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + // Close |h0|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0)); + + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h1), + MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + + // |h1| should no longer be readable or writable. + EXPECT_EQ( + MOJO_RESULT_FAILED_PRECONDITION, + mojo::Wait(mojo::Handle(h1), + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1)); +} + +TEST(CoreTest, BasicDataPipe) { + MojoHandle hp, hc; + MojoHandleSignals sig; + char buffer[20] = {0}; + uint32_t buffer_size; + void* write_pointer; + const void* read_pointer; + + hp = MOJO_HANDLE_INVALID; + hc = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &hp, &hc)); + EXPECT_NE(hp, MOJO_HANDLE_INVALID); + EXPECT_NE(hc, MOJO_HANDLE_INVALID); + + // The consumer |hc| shouldn't be readable. + MojoHandleSignalsState state; + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hc, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_NONE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfiable_signals); + + // The producer |hp| should be writable. + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(hp, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + // Try to read from |hc|. + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + MojoReadData(hc, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE)); + + // Try to begin a two-phase read from |hc|. + read_pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, + MojoBeginReadData(hc, &read_pointer, &buffer_size, + MOJO_READ_DATA_FLAG_NONE)); + + // Write to |hp|. + static const char kHello[] = "hello "; + // Don't include terminating null. + buffer_size = static_cast<uint32_t>(strlen(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(hp, kHello, &buffer_size, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // |hc| should be(come) readable. + size_t result_index = 1; + MojoHandleSignalsState states[1]; + sig = MOJO_HANDLE_SIGNAL_READABLE; + Handle consumer_handle(hc); + EXPECT_EQ(MOJO_RESULT_OK, + mojo::WaitMany(&consumer_handle, &sig, 1, &result_index, states)); + + EXPECT_EQ(0u, result_index); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + states[0].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + states[0].satisfiable_signals); + + // Do a two-phase write to |hp|. + EXPECT_EQ(MOJO_RESULT_OK, MojoBeginWriteData(hp, &write_pointer, &buffer_size, + MOJO_WRITE_DATA_FLAG_NONE)); + static const char kWorld[] = "world"; + ASSERT_GE(buffer_size, sizeof(kWorld)); + // Include the terminating null. + memcpy(write_pointer, kWorld, sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoEndWriteData(hp, static_cast<uint32_t>(sizeof(kWorld)))); + + // Read one character from |hc|. + memset(buffer, 0, sizeof(buffer)); + buffer_size = 1; + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadData(hc, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE)); + + // Close |hp|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hp)); + + // |hc| should still be readable. + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(hc), + MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + // Do a two-phase read from |hc|. + read_pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_OK, MojoBeginReadData(hc, &read_pointer, &buffer_size, + MOJO_READ_DATA_FLAG_NONE)); + ASSERT_LE(buffer_size, sizeof(buffer) - 1); + memcpy(&buffer[1], read_pointer, buffer_size); + EXPECT_EQ(MOJO_RESULT_OK, MojoEndReadData(hc, buffer_size)); + EXPECT_STREQ("hello world", buffer); + + // |hc| should no longer be readable. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + mojo::Wait(mojo::Handle(hc), MOJO_HANDLE_SIGNAL_READABLE, &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(hc)); + + // TODO(vtl): Test the other way around -- closing the consumer should make + // the producer never-writable? +} + +TEST(CoreTest, BasicSharedBuffer) { + MojoHandle h0, h1; + void* pointer; + + // Create a shared buffer (|h0|). + h0 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateSharedBuffer(nullptr, 100, &h0)); + EXPECT_NE(h0, MOJO_HANDLE_INVALID); + + // Map everything. + pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(h0, 0, 100, &pointer, MOJO_MAP_BUFFER_FLAG_NONE)); + ASSERT_TRUE(pointer); + static_cast<char*>(pointer)[50] = 'x'; + + // Duplicate |h0| to |h1|. + h1 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(h0, nullptr, &h1)); + EXPECT_NE(h1, MOJO_HANDLE_INVALID); + + // Close |h0|. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0)); + + // The mapping should still be good. + static_cast<char*>(pointer)[51] = 'y'; + + // Unmap it. + EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer)); + + // Map half of |h1|. + pointer = nullptr; + EXPECT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(h1, 50, 50, &pointer, MOJO_MAP_BUFFER_FLAG_NONE)); + ASSERT_TRUE(pointer); + + // It should have what we wrote. + EXPECT_EQ('x', static_cast<char*>(pointer)[0]); + EXPECT_EQ('y', static_cast<char*>(pointer)[1]); + + // Unmap it. + EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(pointer)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1)); +} + +// Defined in core_unittest_pure_c.c. +extern "C" const char* MinimalCTest(void); + +// This checks that things actually work in C (not C++). +TEST(CoreTest, MinimalCTest) { + const char* failure = MinimalCTest(); + EXPECT_FALSE(failure) << failure; +} + +// TODO(vtl): Add multi-threaded tests. + +} // namespace +} // namespace mojo diff --git a/mojo/public/c/system/tests/core_unittest_pure_c.c b/mojo/public/c/system/tests/core_unittest_pure_c.c new file mode 100644 index 0000000000..3164649cbd --- /dev/null +++ b/mojo/public/c/system/tests/core_unittest_pure_c.c @@ -0,0 +1,77 @@ +// 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. + +#ifdef __cplusplus +#error "This file should be compiled as C, not C++." +#endif + +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +// Include all the header files that are meant to be compilable as C. Start with +// core.h, since it's the most important one. +#include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/macros.h" + +// The joys of the C preprocessor.... +#define STRINGIFY(x) #x +#define STRINGIFY2(x) STRINGIFY(x) +#define FAILURE(message) \ + __FILE__ "(" STRINGIFY2(__LINE__) "): Failure: " message + +// Makeshift gtest. +#define EXPECT_EQ(a, b) \ + do { \ + if ((a) != (b)) \ + return FAILURE(STRINGIFY(a) " != " STRINGIFY(b) " (expected ==)"); \ + } while (0) +#define EXPECT_NE(a, b) \ + do { \ + if ((a) == (b)) \ + return FAILURE(STRINGIFY(a) " == " STRINGIFY(b) " (expected !=)"); \ + } while (0) + +// This function exists mainly to be compiled and linked. We do some cursory +// checks and call it from a unit test, to make sure that link problems aren't +// missed due to deadstripping. Returns null on success and a string on failure +// (describing the failure). +const char* MinimalCTest(void) { + // MSVS before 2013 *really* only supports C90: All variables must be declared + // at the top. (MSVS 2013 is more reasonable.) + MojoTimeTicks ticks; + MojoHandle handle0, handle1; + const char kHello[] = "hello"; + char buffer[200] = {0}; + uint32_t num_bytes; + + ticks = MojoGetTimeTicksNow(); + EXPECT_NE(ticks, 0); + + handle0 = MOJO_HANDLE_INVALID; + EXPECT_NE(MOJO_RESULT_OK, MojoClose(handle0)); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoQueryHandleSignalsState(handle0, NULL)); + + handle1 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(NULL, &handle0, &handle1)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(handle0, kHello, (uint32_t)sizeof(kHello), NULL, + 0u, MOJO_WRITE_DATA_FLAG_NONE)); + + num_bytes = (uint32_t)sizeof(buffer); + EXPECT_EQ(MOJO_RESULT_OK, MojoReadMessage(handle1, buffer, &num_bytes, NULL, + NULL, MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ((uint32_t)sizeof(kHello), num_bytes); + EXPECT_EQ(0, memcmp(buffer, kHello, sizeof(kHello))); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handle0)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handle1)); + + // TODO(vtl): data pipe + + return NULL; +} diff --git a/mojo/public/c/system/tests/macros_unittest.cc b/mojo/public/c/system/tests/macros_unittest.cc new file mode 100644 index 0000000000..fb9ff76405 --- /dev/null +++ b/mojo/public/c/system/tests/macros_unittest.cc @@ -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. + +// This file tests the C Mojo system macros and consists of "positive" tests, +// i.e., those verifying that things work (without compile errors, or even +// warnings if warnings are treated as errors). +// TODO(vtl): Fix no-compile tests (which are all disabled; crbug.com/105388) +// and write some "negative" tests. + +#include "mojo/public/c/system/macros.h" + +#include <assert.h> +#include <stdint.h> +#include <stdlib.h> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +// First test |MOJO_STATIC_ASSERT()| in a global scope. +MOJO_STATIC_ASSERT(sizeof(int64_t) == 2 * sizeof(int32_t), + "Bad static_assert() failure in global scope"); + +TEST(MacrosTest, CompileAssert) { + // Then in a local scope. + MOJO_STATIC_ASSERT(sizeof(int32_t) == 2 * sizeof(int16_t), + "Bad static_assert() failure"); +} + +TEST(MacrosTest, Alignof) { + // Strictly speaking, this isn't a portable test, but I think it'll pass on + // all the platforms we currently support. + EXPECT_EQ(1u, MOJO_ALIGNOF(char)); + EXPECT_EQ(4u, MOJO_ALIGNOF(int32_t)); + EXPECT_EQ(8u, MOJO_ALIGNOF(int64_t)); + EXPECT_EQ(8u, MOJO_ALIGNOF(double)); +} + +// These structs are used in the Alignas test. Define them globally to avoid +// MSVS warnings/errors. +#if defined(_MSC_VER) +#pragma warning(push) +// Disable the warning "structure was padded due to __declspec(align())". +#pragma warning(disable : 4324) +#endif +struct MOJO_ALIGNAS(1) StructAlignas1 { + char x; +}; +struct MOJO_ALIGNAS(4) StructAlignas4 { + char x; +}; +struct MOJO_ALIGNAS(8) StructAlignas8 { + char x; +}; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +TEST(MacrosTest, Alignas) { + EXPECT_EQ(1u, MOJO_ALIGNOF(StructAlignas1)); + EXPECT_EQ(4u, MOJO_ALIGNOF(StructAlignas4)); + EXPECT_EQ(8u, MOJO_ALIGNOF(StructAlignas8)); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/c/system/thunks.cc b/mojo/public/c/system/thunks.cc new file mode 100644 index 0000000000..67c568f7d7 --- /dev/null +++ b/mojo/public/c/system/thunks.cc @@ -0,0 +1,273 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/c/system/thunks.h" + +#include <assert.h> +#include <stddef.h> +#include <stdint.h> + +extern "C" { + +static MojoSystemThunks g_thunks = {0}; + +MojoTimeTicks MojoGetTimeTicksNow() { + assert(g_thunks.GetTimeTicksNow); + return g_thunks.GetTimeTicksNow(); +} + +MojoResult MojoClose(MojoHandle handle) { + assert(g_thunks.Close); + return g_thunks.Close(handle); +} + +MojoResult MojoQueryHandleSignalsState( + MojoHandle handle, + struct MojoHandleSignalsState* signals_state) { + assert(g_thunks.QueryHandleSignalsState); + return g_thunks.QueryHandleSignalsState(handle, signals_state); +} + +MojoResult MojoCreateMessagePipe(const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + assert(g_thunks.CreateMessagePipe); + return g_thunks.CreateMessagePipe(options, message_pipe_handle0, + message_pipe_handle1); +} + +MojoResult MojoWriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + assert(g_thunks.WriteMessage); + return g_thunks.WriteMessage(message_pipe_handle, bytes, num_bytes, handles, + num_handles, flags); +} + +MojoResult MojoReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + assert(g_thunks.ReadMessage); + return g_thunks.ReadMessage(message_pipe_handle, bytes, num_bytes, handles, + num_handles, flags); +} + +MojoResult MojoCreateDataPipe(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + assert(g_thunks.CreateDataPipe); + return g_thunks.CreateDataPipe(options, data_pipe_producer_handle, + data_pipe_consumer_handle); +} + +MojoResult MojoWriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags) { + assert(g_thunks.WriteData); + return g_thunks.WriteData(data_pipe_producer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags) { + assert(g_thunks.BeginWriteData); + return g_thunks.BeginWriteData(data_pipe_producer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written) { + assert(g_thunks.EndWriteData); + return g_thunks.EndWriteData(data_pipe_producer_handle, num_elements_written); +} + +MojoResult MojoReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags) { + assert(g_thunks.ReadData); + return g_thunks.ReadData(data_pipe_consumer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags) { + assert(g_thunks.BeginReadData); + return g_thunks.BeginReadData(data_pipe_consumer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read) { + assert(g_thunks.EndReadData); + return g_thunks.EndReadData(data_pipe_consumer_handle, num_elements_read); +} + +MojoResult MojoCreateSharedBuffer( + const struct MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + assert(g_thunks.CreateSharedBuffer); + return g_thunks.CreateSharedBuffer(options, num_bytes, shared_buffer_handle); +} + +MojoResult MojoDuplicateBufferHandle( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + assert(g_thunks.DuplicateBufferHandle); + return g_thunks.DuplicateBufferHandle(buffer_handle, options, + new_buffer_handle); +} + +MojoResult MojoMapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + assert(g_thunks.MapBuffer); + return g_thunks.MapBuffer(buffer_handle, offset, num_bytes, buffer, flags); +} + +MojoResult MojoUnmapBuffer(void* buffer) { + assert(g_thunks.UnmapBuffer); + return g_thunks.UnmapBuffer(buffer); +} + +MojoResult MojoCreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { + assert(g_thunks.CreateWatcher); + return g_thunks.CreateWatcher(callback, watcher_handle); +} + +MojoResult MojoWatch(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context) { + assert(g_thunks.Watch); + return g_thunks.Watch(watcher_handle, handle, signals, context); +} + +MojoResult MojoCancelWatch(MojoHandle watcher_handle, uintptr_t context) { + assert(g_thunks.CancelWatch); + return g_thunks.CancelWatch(watcher_handle, context); +} + +MojoResult MojoArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + assert(g_thunks.ArmWatcher); + return g_thunks.ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts, + ready_results, ready_signals_states); +} + +MojoResult MojoFuseMessagePipes(MojoHandle handle0, MojoHandle handle1) { + assert(g_thunks.FuseMessagePipes); + return g_thunks.FuseMessagePipes(handle0, handle1); +} + +MojoResult MojoWriteMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags) { + assert(g_thunks.WriteMessageNew); + return g_thunks.WriteMessageNew(message_pipe_handle, message, flags); +} + +MojoResult MojoReadMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + assert(g_thunks.ReadMessageNew); + return g_thunks.ReadMessageNew(message_pipe_handle, message, num_bytes, + handles, num_handles, flags); +} + +MojoResult MojoAllocMessage(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message) { + assert(g_thunks.AllocMessage); + return g_thunks.AllocMessage( + num_bytes, handles, num_handles, flags, message); +} + +MojoResult MojoFreeMessage(MojoMessageHandle message) { + assert(g_thunks.FreeMessage); + return g_thunks.FreeMessage(message); +} + +MojoResult MojoGetMessageBuffer(MojoMessageHandle message, void** buffer) { + assert(g_thunks.GetMessageBuffer); + return g_thunks.GetMessageBuffer(message, buffer); +} + +MojoResult MojoWrapPlatformHandle( + const struct MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle) { + assert(g_thunks.WrapPlatformHandle); + return g_thunks.WrapPlatformHandle(platform_handle, mojo_handle); +} + +MojoResult MojoUnwrapPlatformHandle( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle) { + assert(g_thunks.UnwrapPlatformHandle); + return g_thunks.UnwrapPlatformHandle(mojo_handle, platform_handle); +} + +MojoResult MojoWrapPlatformSharedBufferHandle( + const struct MojoPlatformHandle* platform_handle, + size_t num_bytes, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle) { + assert(g_thunks.WrapPlatformSharedBufferHandle); + return g_thunks.WrapPlatformSharedBufferHandle(platform_handle, num_bytes, + flags, mojo_handle); +} + +MojoResult MojoUnwrapPlatformSharedBufferHandle( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle, + size_t* num_bytes, + MojoPlatformSharedBufferHandleFlags* flags) { + assert(g_thunks.UnwrapPlatformSharedBufferHandle); + return g_thunks.UnwrapPlatformSharedBufferHandle(mojo_handle, platform_handle, + num_bytes, flags); +} + +MojoResult MojoNotifyBadMessage(MojoMessageHandle message, + const char* error, + size_t error_num_bytes) { + assert(g_thunks.NotifyBadMessage); + return g_thunks.NotifyBadMessage(message, error, error_num_bytes); +} + +MojoResult MojoGetProperty(MojoPropertyType type, void* value) { + assert(g_thunks.GetProperty); + return g_thunks.GetProperty(type, value); +} + +} // extern "C" + +size_t MojoEmbedderSetSystemThunks(const MojoSystemThunks* system_thunks) { + if (system_thunks->size >= sizeof(g_thunks)) + g_thunks = *system_thunks; + return sizeof(g_thunks); +} diff --git a/mojo/public/c/system/thunks.h b/mojo/public/c/system/thunks.h new file mode 100644 index 0000000000..e61bb46a46 --- /dev/null +++ b/mojo/public/c/system/thunks.h @@ -0,0 +1,148 @@ +// 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. + +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_THUNKS_H_ +#define MOJO_PUBLIC_C_SYSTEM_THUNKS_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/system_export.h" + +// Structure used to bind the basic Mojo Core functions to an embedder +// implementation. This is intended to eventually be used as a stable ABI +// between a Mojo embedder and some loaded application code, but for now it is +// still effectively safe to rearrange entries as needed. +#pragma pack(push, 8) +struct MojoSystemThunks { + size_t size; // Should be set to sizeof(MojoSystemThunks). + MojoTimeTicks (*GetTimeTicksNow)(); + MojoResult (*Close)(MojoHandle handle); + MojoResult (*QueryHandleSignalsState)( + MojoHandle handle, + struct MojoHandleSignalsState* signals_state); + MojoResult (*CreateMessagePipe)( + const struct MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1); + MojoResult (*WriteMessage)(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags); + MojoResult (*ReadMessage)(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult (*CreateDataPipe)(const struct MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle); + MojoResult (*WriteData)(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags); + MojoResult (*BeginWriteData)(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags); + MojoResult (*EndWriteData)(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written); + MojoResult (*ReadData)(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags); + MojoResult (*BeginReadData)(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags); + MojoResult (*EndReadData)(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read); + MojoResult (*CreateSharedBuffer)( + const struct MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle); + MojoResult (*DuplicateBufferHandle)( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle); + MojoResult (*MapBuffer)(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags); + MojoResult (*UnmapBuffer)(void* buffer); + MojoResult (*CreateWatcher)(MojoWatcherCallback callback, + MojoHandle* watcher_handle); + MojoResult (*Watch)(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context); + MojoResult (*CancelWatch)(MojoHandle watcher_handle, uintptr_t context); + MojoResult (*ArmWatcher)(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states); + MojoResult (*FuseMessagePipes)(MojoHandle handle0, MojoHandle handle1); + MojoResult (*WriteMessageNew)(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags); + MojoResult (*ReadMessageNew)(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult (*AllocMessage)(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message); + MojoResult (*FreeMessage)(MojoMessageHandle message); + MojoResult (*GetMessageBuffer)(MojoMessageHandle message, void** buffer); + MojoResult (*WrapPlatformHandle)( + const struct MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle); + MojoResult (*UnwrapPlatformHandle)( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle); + MojoResult (*WrapPlatformSharedBufferHandle)( + const struct MojoPlatformHandle* platform_handle, + size_t num_bytes, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle); + MojoResult (*UnwrapPlatformSharedBufferHandle)( + MojoHandle mojo_handle, + struct MojoPlatformHandle* platform_handle, + size_t* num_bytes, + MojoPlatformSharedBufferHandleFlags* flags); + MojoResult (*NotifyBadMessage)(MojoMessageHandle message, + const char* error, + size_t error_num_bytes); + MojoResult (*GetProperty)(MojoPropertyType type, void* value); +}; +#pragma pack(pop) + +// Use this type for the function found by dynamically discovering it in +// a DSO linked with mojo_system. For example: +// MojoSetSystemThunksFn mojo_set_system_thunks_fn = +// reinterpret_cast<MojoSetSystemThunksFn>(app_library.GetFunctionPointer( +// "MojoSetSystemThunks")); +// The expected size of |system_thunks| is returned. +// The contents of |system_thunks| are copied. +typedef size_t (*MojoSetSystemThunksFn)( + const struct MojoSystemThunks* system_thunks); + +// A function for setting up the embedder's own system thunks. This should only +// be called by Mojo embedder code. +MOJO_SYSTEM_EXPORT size_t MojoEmbedderSetSystemThunks( + const struct MojoSystemThunks* system_thunks); + +#endif // MOJO_PUBLIC_C_SYSTEM_THUNKS_H_ diff --git a/mojo/public/c/system/types.h b/mojo/public/c/system/types.h new file mode 100644 index 0000000000..15813b6b73 --- /dev/null +++ b/mojo/public/c/system/types.h @@ -0,0 +1,223 @@ +// 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 contains types and constants/macros common to different Mojo system +// APIs. +// +// Note: This header should be compilable as C. + +#ifndef MOJO_PUBLIC_C_SYSTEM_TYPES_H_ +#define MOJO_PUBLIC_C_SYSTEM_TYPES_H_ + +#include <stdint.h> + +#include "mojo/public/c/system/macros.h" + +// |MojoTimeTicks|: A time delta, in microseconds, the meaning of which is +// source-dependent. + +typedef int64_t MojoTimeTicks; + +// |MojoHandle|: Handles to Mojo objects. +// |MOJO_HANDLE_INVALID| - A value that is never a valid handle. + +typedef uint32_t MojoHandle; + +#ifdef __cplusplus +const MojoHandle MOJO_HANDLE_INVALID = 0; +#else +#define MOJO_HANDLE_INVALID ((MojoHandle)0) +#endif + +// |MojoResult|: Result codes for Mojo operations. The only success code is zero +// (|MOJO_RESULT_OK|); all non-zero values should be considered as error/failure +// codes (even if the value is not recognized). +// |MOJO_RESULT_OK| - Not an error; returned on success. +// |MOJO_RESULT_CANCELLED| - Operation was cancelled, typically by the caller. +// |MOJO_RESULT_UNKNOWN| - Unknown error (e.g., if not enough information is +// available for a more specific error). +// |MOJO_RESULT_INVALID_ARGUMENT| - Caller specified an invalid argument. This +// differs from |MOJO_RESULT_FAILED_PRECONDITION| in that the former +// indicates arguments that are invalid regardless of the state of the +// system. +// |MOJO_RESULT_DEADLINE_EXCEEDED| - Deadline expired before the operation +// could complete. +// |MOJO_RESULT_NOT_FOUND| - Some requested entity was not found (i.e., does +// not exist). +// |MOJO_RESULT_ALREADY_EXISTS| - Some entity or condition that we attempted +// to create already exists. +// |MOJO_RESULT_PERMISSION_DENIED| - The caller does not have permission to +// for the operation (use |MOJO_RESULT_RESOURCE_EXHAUSTED| for rejections +// caused by exhausting some resource instead). +// |MOJO_RESULT_RESOURCE_EXHAUSTED| - Some resource required for the call +// (possibly some quota) has been exhausted. +// |MOJO_RESULT_FAILED_PRECONDITION| - The system is not in a state required +// for the operation (use this if the caller must do something to rectify +// the state before retrying). +// |MOJO_RESULT_ABORTED| - The operation was aborted by the system, possibly +// due to a concurrency issue (use this if the caller may retry at a +// higher level). +// |MOJO_RESULT_OUT_OF_RANGE| - The operation was attempted past the valid +// range. Unlike |MOJO_RESULT_INVALID_ARGUMENT|, this indicates that the +// operation may be/become valid depending on the system state. (This +// error is similar to |MOJO_RESULT_FAILED_PRECONDITION|, but is more +// specific.) +// |MOJO_RESULT_UNIMPLEMENTED| - The operation is not implemented, supported, +// or enabled. +// |MOJO_RESULT_INTERNAL| - Internal error: this should never happen and +// indicates that some invariant expected by the system has been broken. +// |MOJO_RESULT_UNAVAILABLE| - The operation is (temporarily) currently +// unavailable. The caller may simply retry the operation (possibly with a +// backoff). +// |MOJO_RESULT_DATA_LOSS| - Unrecoverable data loss or corruption. +// |MOJO_RESULT_BUSY| - One of the resources involved is currently being used +// (possibly on another thread) in a way that prevents the current +// operation from proceeding, e.g., if the other operation may result in +// the resource being invalidated. +// |MOJO_RESULT_SHOULD_WAIT| - The request cannot currently be completed +// (e.g., if the data requested is not yet available). The caller should +// wait for it to be feasible using a watcher. +// +// The codes from |MOJO_RESULT_OK| to |MOJO_RESULT_DATA_LOSS| come from +// Google3's canonical error codes. + +typedef uint32_t MojoResult; + +#ifdef __cplusplus +const MojoResult MOJO_RESULT_OK = 0; +const MojoResult MOJO_RESULT_CANCELLED = 1; +const MojoResult MOJO_RESULT_UNKNOWN = 2; +const MojoResult MOJO_RESULT_INVALID_ARGUMENT = 3; +const MojoResult MOJO_RESULT_DEADLINE_EXCEEDED = 4; +const MojoResult MOJO_RESULT_NOT_FOUND = 5; +const MojoResult MOJO_RESULT_ALREADY_EXISTS = 6; +const MojoResult MOJO_RESULT_PERMISSION_DENIED = 7; +const MojoResult MOJO_RESULT_RESOURCE_EXHAUSTED = 8; +const MojoResult MOJO_RESULT_FAILED_PRECONDITION = 9; +const MojoResult MOJO_RESULT_ABORTED = 10; +const MojoResult MOJO_RESULT_OUT_OF_RANGE = 11; +const MojoResult MOJO_RESULT_UNIMPLEMENTED = 12; +const MojoResult MOJO_RESULT_INTERNAL = 13; +const MojoResult MOJO_RESULT_UNAVAILABLE = 14; +const MojoResult MOJO_RESULT_DATA_LOSS = 15; +const MojoResult MOJO_RESULT_BUSY = 16; +const MojoResult MOJO_RESULT_SHOULD_WAIT = 17; +#else +#define MOJO_RESULT_OK ((MojoResult)0) +#define MOJO_RESULT_CANCELLED ((MojoResult)1) +#define MOJO_RESULT_UNKNOWN ((MojoResult)2) +#define MOJO_RESULT_INVALID_ARGUMENT ((MojoResult)3) +#define MOJO_RESULT_DEADLINE_EXCEEDED ((MojoResult)4) +#define MOJO_RESULT_NOT_FOUND ((MojoResult)5) +#define MOJO_RESULT_ALREADY_EXISTS ((MojoResult)6) +#define MOJO_RESULT_PERMISSION_DENIED ((MojoResult)7) +#define MOJO_RESULT_RESOURCE_EXHAUSTED ((MojoResult)8) +#define MOJO_RESULT_FAILED_PRECONDITION ((MojoResult)9) +#define MOJO_RESULT_ABORTED ((MojoResult)10) +#define MOJO_RESULT_OUT_OF_RANGE ((MojoResult)11) +#define MOJO_RESULT_UNIMPLEMENTED ((MojoResult)12) +#define MOJO_RESULT_INTERNAL ((MojoResult)13) +#define MOJO_RESULT_UNAVAILABLE ((MojoResult)14) +#define MOJO_RESULT_DATA_LOSS ((MojoResult)15) +#define MOJO_RESULT_BUSY ((MojoResult)16) +#define MOJO_RESULT_SHOULD_WAIT ((MojoResult)17) +#endif + +// |MojoDeadline|: Used to specify deadlines (timeouts), in microseconds (except +// for |MOJO_DEADLINE_INDEFINITE|). +// |MOJO_DEADLINE_INDEFINITE| - Used to indicate "forever". + +typedef uint64_t MojoDeadline; + +#ifdef __cplusplus +const MojoDeadline MOJO_DEADLINE_INDEFINITE = static_cast<MojoDeadline>(-1); +#else +#define MOJO_DEADLINE_INDEFINITE ((MojoDeadline) - 1) +#endif + +// |MojoHandleSignals|: Used to specify signals that can be watched for on a +// handle (and which can be triggered), e.g., the ability to read or write to +// the handle. +// |MOJO_HANDLE_SIGNAL_NONE| - No flags. A registered watch will always fail +// to arm with |MOJO_RESULT_FAILED_PRECONDITION| when watching for this. +// |MOJO_HANDLE_SIGNAL_READABLE| - Can read (e.g., a message) from the handle. +// |MOJO_HANDLE_SIGNAL_WRITABLE| - Can write (e.g., a message) to the handle. +// |MOJO_HANDLE_SIGNAL_PEER_CLOSED| - The peer handle is closed. +// |MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE| - Can read data from a data pipe +// consumer handle (implying MOJO_HANDLE_SIGNAL_READABLE is also set), +// AND there is some nonzero quantity of new data available on the pipe +// since the last |MojoReadData()| or |MojoBeginReadData()| call on the +// handle. + +typedef uint32_t MojoHandleSignals; + +#ifdef __cplusplus +const MojoHandleSignals MOJO_HANDLE_SIGNAL_NONE = 0; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_READABLE = 1 << 0; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_WRITABLE = 1 << 1; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_PEER_CLOSED = 1 << 2; +const MojoHandleSignals MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE = 1 << 3; +#else +#define MOJO_HANDLE_SIGNAL_NONE ((MojoHandleSignals)0) +#define MOJO_HANDLE_SIGNAL_READABLE ((MojoHandleSignals)1 << 0) +#define MOJO_HANDLE_SIGNAL_WRITABLE ((MojoHandleSignals)1 << 1) +#define MOJO_HANDLE_SIGNAL_PEER_CLOSED ((MojoHandleSignals)1 << 2) +#define MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE ((MojoHandleSignals)1 << 3); +#endif + +// |MojoHandleSignalsState|: Returned by watch notification callbacks and +// |MojoQueryHandleSignalsState| functions to indicate the signaling state of +// handles. Members are as follows: +// - |satisfied signals|: Bitmask of signals that were satisfied at some time +// before the call returned. +// - |satisfiable signals|: These are the 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. +// Note: This struct is not extensible (and only has 32-bit quantities), so it's +// 32-bit-aligned. +MOJO_STATIC_ASSERT(MOJO_ALIGNOF(int32_t) == 4, "int32_t has weird alignment"); +struct MOJO_ALIGNAS(4) MojoHandleSignalsState { + MojoHandleSignals satisfied_signals; + MojoHandleSignals satisfiable_signals; +}; +MOJO_STATIC_ASSERT(sizeof(MojoHandleSignalsState) == 8, + "MojoHandleSignalsState has wrong size"); + +// |MojoWatcherNotificationFlags|: Passed to a callback invoked by a watcher +// when some observed signals are raised or a watched handle is closed. May take +// on any combination of the following values: +// +// |MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM| - The callback is being +// invoked as a result of a system-level event rather than a direct API +// call from user code. This may be used as an indication that user code +// is safe to call without fear of reentry. + +typedef uint32_t MojoWatcherNotificationFlags; + +#ifdef __cplusplus +const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_NONE = 0; +const MojoWatcherNotificationFlags MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM = + 1 << 0; +#else +#define MOJO_WATCHER_NOTIFICATION_FLAG_NONE ((MojoWatcherNotificationFlags)0) +#define MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM \ + ((MojoWatcherNotificationFlags)1 << 0); +#endif + +// |MojoPropertyType|: Property types that can be passed to |MojoGetProperty()| +// to retrieve system properties. May take the following values: +// |MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED| - Whether making synchronous calls +// (i.e., blocking to wait for a response to an outbound message) is +// allowed. The property value is of boolean type. If the value is true, +// users should refrain from making sync calls. +typedef uint32_t MojoPropertyType; + +#ifdef __cplusplus +const MojoPropertyType MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED = 0; +#else +#define MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED ((MojoPropertyType)0) +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_TYPES_H_ diff --git a/mojo/public/c/system/watcher.h b/mojo/public/c/system/watcher.h new file mode 100644 index 0000000000..e32856b6d9 --- /dev/null +++ b/mojo/public/c/system/watcher.h @@ -0,0 +1,184 @@ +// 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. + +#ifndef MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ +#define MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ + +#include <stdint.h> + +#include "mojo/public/c/system/system_export.h" +#include "mojo/public/c/system/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// A callback used to notify watchers about events on their watched handles. +// +// See documentation for |MojoWatcherNotificationFlags| for details regarding +// the possible values of |flags|. +// +// See documentation for |MojoWatch()| for details regarding the other arguments +// this callback receives when called. +typedef void (*MojoWatcherCallback)(uintptr_t context, + MojoResult result, + struct MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags); + +// Creates a new watcher. +// +// Watchers are used to trigger arbitrary code execution when one or more +// handles change state to meet certain conditions. +// +// A newly registered watcher is initially disarmed and may be armed using +// |MojoArmWatcher()|. A watcher is also always disarmed immediately before any +// invocation of one or more notification callbacks in response to a single +// handle's state changing in some relevant way. +// +// Parameters: +// |callback|: The |MojoWatcherCallback| to invoke any time the watcher is +// notified of an event. See |MojoWatch()| for details regarding arguments +// passed to the callback. Note that this may be called from any arbitrary +// thread. +// |watcher_handle|: The address at which to store the MojoHandle +// corresponding to the new watcher if successfully created. +// +// Returns: +// |MOJO_RESULT_OK| if the watcher has been successfully created. +// |MOJO_RESULT_RESOURCE_EXHAUSTED| if a handle could not be allocated for +// this watcher. +MOJO_SYSTEM_EXPORT MojoResult MojoCreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle); + +// Adds a watch to a watcher. This allows the watcher to fire notifications +// regarding state changes on the handle corresponding to the arguments given. +// +// Note that notifications for a given watch context are guaranteed to be +// mutually exclusive in execution: the callback will never be entered for a +// given context while another invocation of the callback is still executing for +// the same context. As a result it is generally a good idea to ensure that +// callbacks do as little work as necessary in order to process the +// notification. +// +// Parameters: +// |watcher_handle|: The watcher to which |handle| is to be added. +// |handle|: The handle to add to the watcher. +// |signals|: The signals to watch for on |handle|. +// |context|: An arbitrary context value given to any invocation of the +// watcher's callback when invoked as a result of some state change +// relevant to this combination of |handle| and |signals|. Must be +// unique within any given watcher. +// +// Callback parameters (see |MojoWatcherNotificationCallback| above): +// When the watcher invokes its callback as a result of some notification +// relevant to this watch operation, |context| receives the value given here +// and |signals_state| receives the last known signals state of this handle. +// +// |result| is one of the following: +// |MOJO_RESULT_OK| if at least one of the watched signals is satisfied. The +// watcher must be armed for this notification to fire. +// |MOJO_RESULT_FAILED_PRECONDITION| if all of the watched signals are +// permanently unsatisfiable. The watcher must be armed for this +// notification to fire. +// |MOJO_RESULT_CANCELLED| if the watch has been cancelled. The may occur if +// the watcher has been closed, the watched handle has been closed, or +// the watch for |context| has been explicitly cancelled. This is always +// the last result received for any given context, and it is guaranteed +// to be received exactly once per watch, regardless of how the watch +// was cancelled. +// +// Returns: +// |MOJO_RESULT_OK| if the handle is now being watched by the watcher. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle, +// |handle| is not a valid message pipe or data pipe handle. +// |MOJO_RESULT_ALREADY_EXISTS| if the watcher already has a watch registered +// for the given value of |context| or for the given |handle|. +MOJO_SYSTEM_EXPORT MojoResult MojoWatch(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context); + +// Removes a watch from a watcher. +// +// This ensures that the watch is cancelled as soon as possible. Cancellation +// may be deferred (or may even block) an aritrarily long time if the watch is +// already dispatching one or more notifications. +// +// When cancellation is complete, the watcher's callback is invoked one final +// time for |context|, with the result |MOJO_RESULT_CANCELLED|. +// +// The same behavior can be elicted by either closing the watched handle +// associated with this context, or by closing |watcher_handle| itself. In the +// lastter case, all registered contexts on the watcher are implicitly cancelled +// in a similar fashion. +// +// Parameters: +// |watcher_handle|: The handle of the watcher from which to remove a watch. +// |context|: The context of the watch to be removed. +// +// Returns: +// |MOJO_RESULT_OK| if the watch has been cancelled. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a watcher handle. +// |MOJO_RESULT_NOT_FOUND| if there is no watch registered on this watcher for +// the given value of |context|. +MOJO_SYSTEM_EXPORT MojoResult MojoCancelWatch(MojoHandle watcher_handle, + uintptr_t context); + +// Arms a watcher, enabling a single future event on one of the watched handles +// to trigger a single notification for each relevant watch context associated +// with that handle. +// +// Parameters: +// |watcher_handle|: The handle of the watcher. +// |num_ready_contexts|: An address pointing to the number of elements +// available for storage in the remaining output buffers. Optional and +// only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below for +// more details. +// |ready_contexts|: An output buffer for contexts corresponding to the +// watches which would have notified if the watcher were armed. Optional +// and only uesd on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below +// for more details. +// |ready_results|: An output buffer for MojoResult values corresponding to +// each context in |ready_contexts|. Optional and only used on failure. +// See |MOJO_RESULT_FAILED_PRECONDITION| below for more details. +// |ready_signals_states|: An output buffer for |MojoHandleSignalsState| +// structures corresponding to each context in |ready_contexts|. Optional +// and only used on failure. See |MOJO_RESULT_FAILED_PRECONDITION| below +// for more details. +// +// Returns: +// |MOJO_RESULT_OK| if the watcher has been successfully armed. All arguments +// other than |watcher_handle| are ignored in this case. +// |MOJO_RESULT_NOT_FOUND| if the watcher does not have any registered watch +// contexts. All arguments other than |watcher_handle| are ignored in this +// case. +// |MOJO_RESULT_INVALID_ARGUMENT| if |watcher_handle| is not a valid watcher +// handle, or if |num_ready_contexts| is non-null but any of the output +// buffer paramters is null. +// |MOJO_RESULT_FAILED_PRECONDITION| if one or more watches would have +// notified immediately upon arming the watcher. If |num_handles| is +// non-null, this assumes there is enough space for |*num_handles| entries +// in each of the subsequent output buffer arguments. +// +// At most that many entries are placed in the output buffers, +// corresponding to the watches which would have signalled if the watcher +// had been armed successfully. The actual number of entries placed in the +// output buffers is written to |*num_ready_contexts| before returning. +// +// If more than (input) |*num_ready_contexts| watch contexts were ready to +// notify, the subset presented in output buffers is arbitrary, but the +// implementation makes a best effort to circulate the outputs across +// consecutive calls so that callers may reliably avoid handle starvation. +MOJO_SYSTEM_EXPORT MojoResult +MojoArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + struct MojoHandleSignalsState* ready_signals_states); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_SYSTEM_WATCHER_H_ diff --git a/mojo/public/c/test_support/BUILD.gn b/mojo/public/c/test_support/BUILD.gn new file mode 100644 index 0000000000..e2abd5807e --- /dev/null +++ b/mojo/public/c/test_support/BUILD.gn @@ -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. + +static_library("test_support") { + output_name = "mojo_public_test_support" + + sources = [ + "test_support.h", + + # TODO(vtl): Convert this to thunks http://crbug.com/386799 + "../../tests/test_support_private.cc", + "../../tests/test_support_private.h", + ] +} diff --git a/mojo/public/c/test_support/test_support.h b/mojo/public/c/test_support/test_support.h new file mode 100644 index 0000000000..8e50441d68 --- /dev/null +++ b/mojo/public/c/test_support/test_support.h @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ +#define MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ + +// Note: This header should be compilable as C. + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +// |sub_test_name| is optional. If not null, it usually describes one particular +// configuration of the test. For example, if |test_name| is "TestPacketRate", +// |sub_test_name| could be "100BytesPerPacket". +// When the perf data is visualized by the performance dashboard, data with +// different |sub_test_name|s (but the same |test_name|) are depicted as +// different traces on the same chart. +void MojoTestSupportLogPerfResult( + const char* test_name, + const char* sub_test_name, + double value, + const char* units); + +// Opens a "/"-delimited file path relative to the source root. +FILE* MojoTestSupportOpenSourceRootRelativeFile( + const char* source_root_relative_path); + +// Enumerates a "/"-delimited directory path relative to the source root. +// Returns only regular files. The return value is a heap-allocated array of +// heap-allocated strings. Each must be free'd separately. +// +// The return value is built like so: +// +// char** rv = (char**) calloc(N + 1, sizeof(char*)); +// rv[0] = strdup("a"); +// rv[1] = strdup("b"); +// rv[2] = strdup("c"); +// ... +// rv[N] = NULL; +// +char** MojoTestSupportEnumerateSourceRootRelativeDirectory( + const char* source_root_relative_path); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MOJO_PUBLIC_C_TEST_SUPPORT_TEST_SUPPORT_H_ diff --git a/mojo/public/cpp/bindings/BUILD.gn b/mojo/public/cpp/bindings/BUILD.gn new file mode 100644 index 0000000000..bd87965fc8 --- /dev/null +++ b/mojo/public/cpp/bindings/BUILD.gn @@ -0,0 +1,194 @@ +# 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. + +interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings" + +component("bindings") { + sources = [ + # Normally, targets should depend on the source_sets generated by mojom + # targets. However, the generated source_sets use portions of the bindings + # library. In order to avoid linker warnings about locally-defined imports + # in Windows components build, this target depends on the generated C++ + # files directly so that the EXPORT macro defintions match. + "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared-internal.h", + "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared.cc", + "$interfaces_bindings_gen_dir/interface_control_messages.mojom-shared.h", + "$interfaces_bindings_gen_dir/interface_control_messages.mojom.cc", + "$interfaces_bindings_gen_dir/interface_control_messages.mojom.h", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared-internal.h", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared.cc", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom-shared.h", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.cc", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.h", + "array_data_view.h", + "array_traits.h", + "array_traits_carray.h", + "array_traits_stl.h", + "associated_binding.h", + "associated_binding_set.h", + "associated_group.h", + "associated_group_controller.h", + "associated_interface_ptr.h", + "associated_interface_ptr_info.h", + "associated_interface_request.h", + "binding.h", + "binding_set.h", + "bindings_export.h", + "clone_traits.h", + "connection_error_callback.h", + "connector.h", + "disconnect_reason.h", + "filter_chain.h", + "interface_data_view.h", + "interface_endpoint_client.h", + "interface_endpoint_controller.h", + "interface_id.h", + "interface_ptr.h", + "interface_ptr_info.h", + "interface_ptr_set.h", + "interface_request.h", + "lib/array_internal.cc", + "lib/array_internal.h", + "lib/array_serialization.h", + "lib/associated_binding.cc", + "lib/associated_group.cc", + "lib/associated_group_controller.cc", + "lib/associated_interface_ptr.cc", + "lib/associated_interface_ptr_state.h", + "lib/binding_state.cc", + "lib/binding_state.h", + "lib/bindings_internal.h", + "lib/buffer.h", + "lib/connector.cc", + "lib/control_message_handler.cc", + "lib/control_message_handler.h", + "lib/control_message_proxy.cc", + "lib/control_message_proxy.h", + "lib/equals_traits.h", + "lib/filter_chain.cc", + "lib/fixed_buffer.cc", + "lib/fixed_buffer.h", + "lib/handle_interface_serialization.h", + "lib/hash_util.h", + "lib/interface_endpoint_client.cc", + "lib/interface_ptr_state.h", + "lib/map_data_internal.h", + "lib/map_serialization.h", + "lib/may_auto_lock.h", + "lib/message.cc", + "lib/message_buffer.cc", + "lib/message_buffer.h", + "lib/message_builder.cc", + "lib/message_builder.h", + "lib/message_header_validator.cc", + "lib/message_internal.h", + "lib/multiplex_router.cc", + "lib/multiplex_router.h", + "lib/native_enum_data.h", + "lib/native_enum_serialization.h", + "lib/native_struct.cc", + "lib/native_struct_data.cc", + "lib/native_struct_data.h", + "lib/native_struct_serialization.cc", + "lib/native_struct_serialization.h", + "lib/pipe_control_message_handler.cc", + "lib/pipe_control_message_proxy.cc", + "lib/scoped_interface_endpoint_handle.cc", + "lib/serialization.h", + "lib/serialization_context.cc", + "lib/serialization_context.h", + "lib/serialization_forward.h", + "lib/serialization_util.h", + "lib/string_serialization.h", + "lib/string_traits_string16.cc", + "lib/sync_call_restrictions.cc", + "lib/sync_event_watcher.cc", + "lib/sync_handle_registry.cc", + "lib/sync_handle_watcher.cc", + "lib/template_util.h", + "lib/union_accessor.h", + "lib/validate_params.h", + "lib/validation_context.cc", + "lib/validation_context.h", + "lib/validation_errors.cc", + "lib/validation_errors.h", + "lib/validation_util.cc", + "lib/validation_util.h", + "map.h", + "map_data_view.h", + "map_traits.h", + "map_traits_stl.h", + "message.h", + "message_header_validator.h", + "native_enum.h", + "native_struct.h", + "native_struct_data_view.h", + "pipe_control_message_handler.h", + "pipe_control_message_handler_delegate.h", + "pipe_control_message_proxy.h", + "raw_ptr_impl_ref_traits.h", + "scoped_interface_endpoint_handle.h", + "string_data_view.h", + "string_traits.h", + "string_traits_stl.h", + "string_traits_string16.h", + "string_traits_string_piece.h", + "strong_associated_binding.h", + "strong_binding.h", + "strong_binding_set.h", + "struct_ptr.h", + "sync_call_restrictions.h", + "sync_event_watcher.h", + "sync_handle_registry.h", + "sync_handle_watcher.h", + "thread_safe_interface_ptr.h", + "type_converter.h", + "union_traits.h", + "unique_ptr_impl_ref_traits.h", + ] + + public_deps = [ + ":struct_traits", + "//base", + "//ipc:param_traits", + "//mojo/public/cpp/system", + ] + + deps = [ + "//base", + "//mojo/public/interfaces/bindings:bindings__generator", + "//mojo/public/interfaces/bindings:bindings_shared__generator", + ] + + defines = [ "MOJO_CPP_BINDINGS_IMPLEMENTATION" ] +} + +source_set("struct_traits") { + sources = [ + "enum_traits.h", + "struct_traits.h", + ] +} + +if (!is_ios) { + # TODO(yzshen): crbug.com/617718 Consider moving this into blink. + source_set("wtf_support") { + sources = [ + "array_traits_wtf_vector.h", + "lib/string_traits_wtf.cc", + "lib/wtf_clone_equals_util.h", + "lib/wtf_hash_util.h", + "lib/wtf_serialization.h", + "map_traits_wtf_hash_map.h", + "string_traits_wtf.h", + ] + + public_deps = [ + ":bindings", + "//third_party/WebKit/Source/wtf", + ] + + public_configs = [ "//third_party/WebKit/Source:config" ] + } +} diff --git a/mojo/public/cpp/bindings/DEPS b/mojo/public/cpp/bindings/DEPS new file mode 100644 index 0000000000..36eba448e8 --- /dev/null +++ b/mojo/public/cpp/bindings/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+third_party/WebKit/Source/wtf", +] diff --git a/mojo/public/cpp/bindings/README.md b/mojo/public/cpp/bindings/README.md new file mode 100644 index 0000000000..b37267a338 --- /dev/null +++ b/mojo/public/cpp/bindings/README.md @@ -0,0 +1,1231 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ Bindings API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview +The Mojo C++ Bindings API leverages the +[C++ System API](/mojo/public/cpp/system) to provide a more natural set of +primitives for communicating over Mojo message pipes. Combined with generated +code from the [Mojom IDL and bindings generator](/mojo/public/tools/bindings), +users can easily connect interface clients and implementations across arbitrary +intra- and inter-process bounaries. + +This document provides a detailed guide to bindings API usage with example code +snippets. For a detailed API references please consult the headers in +[//mojo/public/cpp/bindings](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/). + +## Getting Started + +When a Mojom IDL file is processed by the bindings generator, C++ code is +emitted in a series of `.h` and `.cc` files with names based on the input +`.mojom` file. Suppose we create the following Mojom file at +`//services/db/public/interfaces/db.mojom`: + +``` +module db.mojom; + +interface Table { + AddRow(int32 key, string data); +}; + +interface Database { + CreateTable(Table& table); +}; +``` + +And a GN target to generate the bindings in +`//services/db/public/interfaces/BUILD.gn`: + +``` +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "db.mojom", + ] +} +``` + +If we then build this target: + +``` +ninja -C out/r services/db/public/interfaces +``` + +This will produce several generated source files, some of which are relevant to +C++ bindings. Two of these files are: + +``` +out/gen/services/business/public/interfaces/factory.mojom.cc +out/gen/services/business/public/interfaces/factory.mojom.h +``` + +You can include the above generated header in your sources in order to use the +definitions therein: + +``` cpp +#include "services/business/public/interfaces/factory.mojom.h" + +class TableImpl : public db::mojom::Table { + // ... +}; +``` + +This document covers the different kinds of definitions generated by Mojom IDL +for C++ consumers and how they can effectively be used to communicate across +message pipes. + +*** note +**NOTE:** Using C++ bindings from within Blink code is typically subject to +special constraints which require the use of a different generated header. +For details, see [Blink Type Mapping](#Blink-Type-Mapping). +*** + +## Interfaces + +Mojom IDL interfaces are translated to corresponding C++ (pure virtual) class +interface definitions in the generated header, consisting of a single generated +method signature for each request message on the interface. Internally there is +also generated code for serialization and deserialization of messages, but this +detail is hidden from bindings consumers. + +### Basic Usage + +Let's consider a new `//sample/logger.mojom` to define a simple logging +interface which clients can use to log simple string messages: + +``` cpp +module sample.mojom; + +interface Logger { + Log(string message); +}; +``` + +Running this through the bindings generator will produce a `logging.mojom.h` +with the following definitions (modulo unimportant details): + +``` cpp +namespace sample { +namespace mojom { + +class Logger { + virtual ~Logger() {} + + virtual void Log(const std::string& message) = 0; +}; + +using LoggerPtr = mojo::InterfacePtr<Logger>; +using LoggerRequest = mojo::InterfaceRequest<Logger>; + +} // namespace mojom +} // namespace sample +``` + +Makes sense. Let's take a closer look at those type aliases at the end. + +### InterfacePtr and InterfaceRequest + +You will notice the type aliases for `LoggerPtr` and +`LoggerRequest` are using two of the most fundamental template types in the C++ +bindings library: **`InterfacePtr<T>`** and **`InterfaceRequest<T>`**. + +In the world of Mojo bindings libraries these are effectively strongly-typed +message pipe endpoints. If an `InterfacePtr<T>` is bound to a message pipe +endpoint, it can be dereferenced to make calls on an opaque `T` interface. These +calls immediately serialize their arguments (using generated code) and write a +corresponding message to the pipe. + +An `InterfaceRequest<T>` is essentially just a typed container to hold the other +end of an `InterfacePtr<T>`'s pipe -- the receiving end -- until it can be +routed to some implementation which will **bind** it. The `InterfaceRequest<T>` +doesn't actually *do* anything other than hold onto a pipe endpoint and carry +useful compile-time type information. + +![Diagram illustrating InterfacePtr and InterfaceRequest on either end of a message pipe](https://docs.google.com/drawings/d/17d5gvErbQ6DthEBMS7I1WhCh9bz0n12pvNjydzuRfTI/pub?w=600&h=100) + +So how do we create a strongly-typed message pipe? + +### Creating Interface Pipes + +One way to do this is by manually creating a pipe and binding each end: + +``` cpp +#include "sample/logger.mojom.h" + +mojo::MessagePipe pipe; +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request; + +logger.Bind(sample::mojom::LoggerPtrInfo(std::move(pipe.handle0), 0u)); +request.Bind(std::move(pipe.handle1)); +``` + +That's pretty verbose, but the C++ Bindings library provides more convenient +ways to accomplish the same thing. [interface_request.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_request.h) +defines a `MakeRequest` function: + +``` cpp +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request = mojo::MakeRequest(&logger); +``` + +and the `InterfaceRequest<T>` constructor can also take an explicit +`InterfacePtr<T>*` output argument: + +``` cpp +sample::mojom::LoggerPtr logger; +sample::mojom::LoggerRequest request(&logger); +``` + +Both of these last two snippets are equivalent to the first one. + +*** note +**NOTE:** In the first example above you may notice usage of the `LoggerPtrInfo` +type, which is a generated alias for `mojo::InterfacePtrInfo<Logger>`. This is +similar to an `InterfaceRequest<T>` in that it merely holds onto a pipe handle +and cannot actually read or write messages on the pipe. Both this type and +`InterfaceRequest<T>` are safe to move freely from thread to thread, whereas a +bound `InterfacePtr<T>` is bound to a single thread. + +An `InterfacePtr<T>` may be unbound by calling its `PassInterface()` method, +which returns a new `InterfacePtrInfo<T>`. Conversely, an `InterfacePtr<T>` may +bind (and thus take ownership of) an `InterfacePtrInfo<T>` so that interface +calls can be made on the pipe. + +The thread-bound nature of `InterfacePtr<T>` is necessary to support safe +dispatch of its [message responses](#Receiving-Responses) and +[connection error notifications](#Connection-Errors). +*** + +Once the `LoggerPtr` is bound we can immediately begin calling `Logger` +interface methods on it, which will immediately write messages into the pipe. +These messages will stay queued on the receiving end of the pipe until someone +binds to it and starts reading them. + +``` cpp +logger->Log("Hello!"); +``` + +This actually writes a `Log` message to the pipe. + +![Diagram illustrating a message traveling on a pipe from LoggerPtr to LoggerRequest](https://docs.google.com/a/google.com/drawings/d/1jWEc6jJIP2ed77Gg4JJ3EVC7hvnwcImNqQJywFwpT8g/pub?w=648&h=123) + +But as mentioned above, `InterfaceRequest` *doesn't actually do anything*, so +that message will just sit on the pipe forever. We need a way to read messages +off the other end of the pipe and dispatch them. We have to +**bind the interface request**. + +### Binding an Interface Request + +There are many different helper classes in the bindings library for binding the +receiving end of a message pipe. The most primitive among them is the aptly +named `mojo::Binding<T>`. A `mojo::Binding<T>` bridges an implementation of `T` +with a single bound message pipe endpoint (via a `mojo::InterfaceRequest<T>`), +which it continuously watches for readability. + +Any time the bound pipe becomes readable, the `Binding` will schedule a task to +read, deserialize (using generated code), and dispatch all available messages to +the bound `T` implementation. Below is a sample implementation of the `Logger` +interface. Notice that the implementation itself owns a `mojo::Binding`. This is +a common pattern, since a bound implementation must outlive any `mojo::Binding` +which binds it. + +``` cpp +#include "base/logging.h" +#include "base/macros.h" +#include "sample/logger.mojom.h" + +class LoggerImpl : public sample::mojom::Logger { + public: + // NOTE: A common pattern for interface implementations which have one + // instance per client is to take an InterfaceRequest in the constructor. + + explicit LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) {} + ~Logger() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + mojo::Binding<sample::mojom::Logger> binding_; + + DISALLOW_COPY_AND_ASSIGN(LoggerImpl); +}; +``` + +Now we can construct a `LoggerImpl` over our pending `LoggerRequest`, and the +previously queued `Log` message will be dispatched ASAP on the `LoggerImpl`'s +thread: + +``` cpp +LoggerImpl impl(std::move(request)); +``` + +The diagram below illustrates the following sequence of events, all set in +motion by the above line of code: + +1. The `LoggerImpl` constructor is called, passing the `LoggerRequest` along + to the `Binding`. +2. The `Binding` takes ownership of the `LoggerRequest`'s pipe endpoint and + begins watching it for readability. The pipe is readable immediately, so a + task is scheduled to read the pending `Log` message from the pipe ASAP. +3. The `Log` message is read and deserialized, causing the `Binding` to invoke + the `Logger::Log` implementation on its bound `LoggerImpl`. + +![Diagram illustrating the progression of binding a request, reading a pending message, and dispatching it](https://docs.google.com/drawings/d/1c73-PegT4lmjfHoxhWrHTQXRvzxgb0wdeBa35WBwZ3Q/pub?w=550&h=500) + +As a result, our implementation will eventually log the client's `"Hello!"` +message via `LOG(ERROR)`. + +*** note +**NOTE:** Messages will only be read and dispatched from a pipe as long as the +object which binds it (*i.e.* the `mojo::Binding` in the above example) remains +alive. +*** + +### Receiving Responses + +Some Mojom interface methods expect a response. Suppose we modify our `Logger` +interface so that the last logged line can be queried like so: + +``` cpp +module sample.mojom; + +interface Logger { + Log(string message); + GetTail() => (string message); +}; +``` + +The generated C++ interface will now look like: + +``` cpp +namespace sample { +namespace mojom { + +class Logger { + public: + virtual ~Logger() {} + + virtual void Log(const std::string& message) = 0; + + using GetTailCallback = base::Callback<void(const std::string& message)>; + + virtual void GetTail(const GetTailCallback& callback) = 0; +} + +} // namespace mojom +} // namespace sample +``` + +As before, both clients and implementations of this interface use the same +signature for the `GetTail` method: implementations use the `callback` argument +to *respond* to the request, while clients pass a `callback` argument to +asynchronously `receive` the response. Here's an updated implementation: + +```cpp +class LoggerImpl : public sample::mojom::Logger { + public: + // NOTE: A common pattern for interface implementations which have one + // instance per client is to take an InterfaceRequest in the constructor. + + explicit LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) {} + ~Logger() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + lines_.push_back(message); + } + + void GetTail(const GetTailCallback& callback) override { + callback.Run(lines_.back()); + } + + private: + mojo::Binding<sample::mojom::Logger> binding_; + std::vector<std::string> lines_; + + DISALLOW_COPY_AND_ASSIGN(LoggerImpl); +}; +``` + +And an updated client call: + +``` cpp +void OnGetTail(const std::string& message) { + LOG(ERROR) << "Tail was: " << message; +} + +logger->GetTail(base::Bind(&OnGetTail)); +``` + +Behind the scenes, the implementation-side callback is actually serializing the +response arguments and writing them onto the pipe for delivery back to the +client. Meanwhile the client-side callback is invoked by some internal logic +which watches the pipe for an incoming response message, reads and deserializes +it once it arrives, and then invokes the callback with the deserialized +parameters. + +### Connection Errors + +If there are no remaining messages available on a pipe and the remote end has +been closed, a connection error will be triggered on the local end. Connection +errors may also be triggered by automatic forced local pipe closure due to +*e.g.* a validation error when processing a received message. + +Regardless of the underlying cause, when a connection error is encountered on +a binding endpoint, that endpoint's **connection error handler** (if set) is +invoked. This handler is a simple `base::Closure` and may only be invoked +*once* as long as the endpoint is bound to the same pipe. Typically clients and +implementations use this handler to do some kind of cleanup or -- particuarly if +the error was unexpected -- create a new pipe and attempt to establish a new +connection with it. + +All message pipe-binding C++ objects (*e.g.*, `mojo::Binding<T>`, +`mojo::InterfacePtr<T>`, *etc.*) support setting their connection error handler +via a `set_connection_error_handler` method. + +We can set up another end-to-end `Logger` example to demonstrate error handler +invocation: + +``` cpp +sample::mojom::LoggerPtr logger; +LoggerImpl impl(mojo::MakeRequest(&logger)); +impl.set_connection_error_handler(base::Bind([] { LOG(ERROR) << "Bye."; })); +logger->Log("OK cool"); +logger.reset(); // Closes the client end. +``` + +As long as `impl` stays alive here, it will eventually receive the `Log` message +followed immediately by an invocation of the bound callback which outputs +`"Bye."`. Like all other bindings callbacks, a connection error handler will +**never** be invoked once its corresponding binding object has been destroyed. + +In fact, suppose instead that `LoggerImpl` had set up the following error +handler within its constructor: + +``` cpp +LoggerImpl::LoggerImpl(sample::mojom::LoggerRequest request) + : binding_(this, std::move(request)) { + binding_.set_connection_error_handler( + base::Bind(&LoggerImpl::OnError, base::Unretained(this))); +} + +void LoggerImpl::OnError() { + LOG(ERROR) << "Client disconnected! Purging log lines."; + lines_.clear(); +} +``` + +The use of `base::Unretained` is *safe* because the error handler will never be +invoked beyond the lifetime of `binding_`, and `this` owns `binding_`. + +### A Note About Ordering + +As mentioned in the previous section, closing one end of a pipe will eventually +trigger a connection error on the other end. However it's important to note that +this event is itself ordered with respect to any other event (*e.g.* writing a +message) on the pipe. + +This means that it's safe to write something contrived like: + +``` cpp +void GoBindALogger(sample::mojom::LoggerRequest request) { + LoggerImpl impl(std::move(request)); + base::RunLoop loop; + impl.set_connection_error_handler(loop.QuitClosure()); + loop.Run(); +} + +void LogSomething() { + sample::mojom::LoggerPtr logger; + bg_thread->task_runner()->PostTask( + FROM_HERE, base::BindOnce(&GoBindALogger, mojo::MakeRequest(&logger))); + logger->Log("OK Computer"); +} +``` + +When `logger` goes out of scope it immediately closes its end of the message +pipe, but the impl-side won't notice this until it receives the sent `Log` +message. Thus the `impl` above will first log our message and *then* see a +connection error and break out of the run loop. + +### Sending Interfaces Over Interfaces + +Now we know how to create interface pipes and use their Ptr and Request +endpoints in some interesting ways. This still doesn't add up to interesting +IPC! The bread and butter of Mojo IPC is the ability to transfer interface +endpoints across other interfaces, so let's take a look at how to accomplish +that. + +#### Sending Interface Requests + +Consider a new example Mojom in `//sample/db.mojom`: + +``` cpp +module db.mojom; + +interface Table { + void AddRow(int32 key, string data); +}; + +interface Database { + AddTable(Table& table); +}; +``` + +As noted in the +[Mojom IDL documentation](/mojo/public/tools/bindings#Primitive-Types), +the `Table&` syntax denotes a `Table` interface request. This corresponds +precisely to the `InterfaceRequest<T>` type discussed in the sections above, and +in fact the generated code for these interfaces is approximately: + +``` cpp +namespace db { +namespace mojom { + +class Table { + public: + virtual ~Table() {} + + virtual void AddRow(int32_t key, const std::string& data) = 0; +} + +using TablePtr = mojo::InterfacePtr<Table>; +using TableRequest = mojo::InterfaceRequest<Table>; + +class Database { + public: + virtual ~Database() {} + + virtual void AddTable(TableRequest table); +}; + +using DatabasePtr = mojo::InterfacePtr<Database>; +using DatabaseRequest = mojo::InterfaceRequest<Database>; + +} // namespace mojom +} // namespace db +``` + +We can put this all together now with an implementation of `Table` and +`Database`: + +``` cpp +#include "sample/db.mojom.h" + +class TableImpl : public db::mojom:Table { + public: + explicit TableImpl(db::mojom::TableRequest request) + : binding_(this, std::move(request)) {} + ~TableImpl() override {} + + // db::mojom::Table: + void AddRow(int32_t key, const std::string& data) override { + rows_.insert({key, data}); + } + + private: + mojo::Binding<db::mojom::Table> binding_; + std::map<int32_t, std::string> rows_; +}; + +class DatabaseImpl : public db::mojom::Database { + public: + explicit DatabaseImpl(db::mojom::DatabaseRequest request) + : binding_(this, std::move(request)) {} + ~DatabaseImpl() override {} + + // db::mojom::Database: + void AddTable(db::mojom::TableRequest table) { + tables_.emplace_back(base::MakeUnique<TableImpl>(std::move(table))); + } + + private: + mojo::Binding<db::mojom::Database> binding_; + std::vector<std::unique_ptr<TableImpl>> tables_; +}; +``` + +Pretty straightforward. The `Table&` Mojom paramter to `AddTable` translates to +a C++ `db::mojom::TableRequest`, aliased from +`mojo::InterfaceRequest<db::mojom::Table>`, which we know is just a +strongly-typed message pipe handle. When `DatabaseImpl` gets an `AddTable` call, +it constructs a new `TableImpl` and binds it to the received `TableRequest`. + +Let's see how this can be used. + +``` cpp +db::mojom::DatabasePtr database; +DatabaseImpl db_impl(mojo::MakeRequest(&database)); + +db::mojom::TablePtr table1, table2; +database->AddTable(mojo::MakeRequest(&table1)); +database->AddTable(mojo::MakeRequest(&table2)); + +table1->AddRow(1, "hiiiiiiii"); +table2->AddRow(2, "heyyyyyy"); +``` + +Notice that we can again start using the new `Table` pipes immediately, even +while their `TableRequest` endpoints are still in transit. + +#### Sending InterfacePtrs + +Of course we can also send `InterfacePtr`s: + +``` cpp +interface TableListener { + OnRowAdded(int32 key, string data); +}; + +interface Table { + AddRow(int32 key, string data); + + AddListener(TableListener listener); +}; +``` + +This would generate a `Table::AddListener` signature like so: + +``` cpp + virtual void AddListener(TableListenerPtr listener) = 0; +``` + +and this could be used like so: + +``` cpp +db::mojom::TableListenerPtr listener; +TableListenerImpl impl(mojo::MakeRequest(&listener)); +table->AddListener(std::move(listener)); +``` + +## Other Interface Binding Types + +The [Interfaces](#Interfaces) section above covers basic usage of the most +common bindings object types: `InterfacePtr`, `InterfaceRequest`, and `Binding`. +While these types are probably the most commonly used in practice, there are +several other ways of binding both client- and implementation-side interface +pipes. + +### Strong Bindings + +A **strong binding** exists as a standalone object which owns its interface +implementation and automatically cleans itself up when its bound interface +endpoint detects an error. The +[**`MakeStrongBinding`**](https://cs.chromim.org/chromium/src//mojo/public/cpp/bindings/strong_binding.h) +function is used to create such a binding. +. + +``` cpp +class LoggerImpl : public sample::mojom::Logger { + public: + LoggerImpl() {} + ~LoggerImpl() override {} + + // sample::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + // NOTE: This doesn't own any Binding object! +}; + +db::mojom::LoggerPtr logger; +mojo::MakeStrongBinding(base::MakeUnique<DatabaseImpl>(), + mojo::MakeRequest(&logger)); + +logger->Log("NOM NOM NOM MESSAGES"); +``` + +Now as long as `logger` remains open somewhere in the system, the bound +`DatabaseImpl` on the other end will remain alive. + +### Binding Sets + +Sometimes it's useful to share a single implementation instance with multiple +clients. [**`BindingSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/binding_set.h) +makes this easy. Consider the Mojom: + +``` cpp +module system.mojom; + +interface Logger { + Log(string message); +}; + +interface LoggerProvider { + GetLogger(Logger& logger); +}; +``` + +We can use `BindingSet` to bind multiple `Logger` requests to a single +implementation instance: + +``` cpp +class LogManager : public system::mojom::LoggerProvider, + public system::mojom::Logger { + public: + explicit LogManager(system::mojom::LoggerProviderRequest request) + : provider_binding_(this, std::move(request)) {} + ~LogManager() {} + + // system::mojom::LoggerProvider: + void GetLogger(LoggerRequest request) override { + logger_bindings_.AddBinding(this, std::move(request)); + } + + // system::mojom::Logger: + void Log(const std::string& message) override { + LOG(ERROR) << "[Logger] " << message; + } + + private: + mojo::Binding<system::mojom::LoggerProvider> provider_binding_; + mojo::BindingSet<system::mojom::Logger> logger_bindings_; +}; + +``` + + +### InterfacePtr Sets + +Similar to the `BindingSet` above, sometimes it's useful to maintain a set of +`InterfacePtr`s for *e.g.* a set of clients observing some event. +[**`InterfacePtrSet`**](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/interface_ptr_set.h) +is here to help. Take the Mojom: + +``` cpp +module db.mojom; + +interface TableListener { + OnRowAdded(int32 key, string data); +}; + +interface Table { + AddRow(int32 key, string data); + AddListener(TableListener listener); +}; +``` + +An implementation of `Table` might look something like like this: + +``` cpp +class TableImpl : public db::mojom::Table { + public: + TableImpl() {} + ~TableImpl() override {} + + // db::mojom::Table: + void AddRow(int32_t key, const std::string& data) override { + rows_.insert({key, data}); + listeners_.ForEach([key, &data](db::mojom::TableListener* listener) { + listener->OnRowAdded(key, data); + }); + } + + void AddListener(db::mojom::TableListenerPtr listener) { + listeners_.AddPtr(std::move(listener)); + } + + private: + mojo::InterfacePtrSet<db::mojom::Table> listeners_; + std::map<int32_t, std::string> rows_; +}; +``` + +## Associated Interfaces + +See [this document](https://www.chromium.org/developers/design-documents/mojo/associated-interfaces). + +TODO: Move the above doc into the repository markdown docs. + +## Synchronous Calls + +See [this document](https://www.chromium.org/developers/design-documents/mojo/synchronous-calls) + +TODO: Move the above doc into the repository markdown docs. + +## Type Mapping + +In many instances you might prefer that your generated C++ bindings use a more +natural type to represent certain Mojom types in your interface methods. For one +example consider a Mojom struct such as the `Rect` below: + +``` cpp +module gfx.mojom; + +struct Rect { + int32 x; + int32 y; + int32 width; + int32 height; +}; + +interface Canvas { + void FillRect(Rect rect); +}; +``` + +The `Canvas` Mojom interface would normally generate a C++ interface like: + +``` cpp +class Canvas { + public: + virtual void FillRect(RectPtr rect) = 0; +}; +``` + +However, the Chromium tree already defines a native +[`gfx::Rect`](https://cs.chromium.org/chromium/src/ui/gfx/geometry/rect.h) which +is equivalent in meaning but which also has useful helper methods. Instead of +manually converting between a `gfx::Rect` and the Mojom-generated `RectPtr` at +every message boundary, wouldn't it be nice if the Mojom bindings generator +could instead generate: + +``` cpp +class Canvas { + public: + virtual void FillRect(const gfx::Rect& rect) = 0; +} +``` + +The correct answer is, "Yes! That would be nice!" And fortunately, it can! + +### Global Configuration + +While this feature is quite powerful, it introduces some unavoidable complexity +into build system. This stems from the fact that type-mapping is an inherently +viral concept: if `gfx::mojom::Rect` is mapped to `gfx::Rect` anywhere, the +mapping needs to apply *everywhere*. + +For this reason we have a few global typemap configurations defined in +[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni) +and +[blink_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni). These configure the two supported [variants](#Variants) of Mojom generated +bindings in the repository. Read more on this in the sections that follow. + +For now, let's take a look at how to express the mapping from `gfx::mojom::Rect` +to `gfx::Rect`. + +### Defining `StructTraits` + +In order to teach generated bindings code how to serialize an arbitrary native +type `T` as an arbitrary Mojom type `mojom::U`, we need to define an appropriate +specialization of the +[`mojo::StructTraits`](https://cs.chromium.org/chromium/src/mojo/public/cpp/bindings/struct_traits.h) +template. + +A valid specialization of `StructTraits` MUST define the following static +methods: + +* A single static accessor for every field of the Mojom struct, with the exact + same name as the struct field. These accessors must all take a const ref to + an object of the native type, and must return a value compatible with the + Mojom struct field's type. This is used to safely and consistently extract + data from the native type during message serialization without incurring extra + copying costs. + +* A single static `Read` method which initializes an instance of the the native + type given a serialized representation of the Mojom struct. The `Read` method + must return a `bool` to indicate whether the incoming data is accepted + (`true`) or rejected (`false`). + +There are other methods a `StructTraits` specialization may define to satisfy +some less common requirements. See +[Advanced StructTraits Usage](#Advanced-StructTraits-Usage) for details. + +In order to define the mapping for `gfx::Rect`, we want the following +`StructTraits` specialization, which we'll define in +`//ui/gfx/geometry/mojo/geometry_struct_traits.h`: + +``` cpp +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/mojo/geometry.mojom.h" + +namespace mojo { + +template <> +class StructTraits<gfx::mojom::RectDataView, gfx::Rect> { + public: + static int32_t x(const gfx::Rect& r) { return r.x(); } + static int32_t y(const gfx::Rect& r) { return r.y(); } + static int32_t width(const gfx::Rect& r) { return r.width(); } + static int32_t height(const gfx::Rect& r) { return r.height(); } + + static bool Read(gfx::mojom::RectDataView data, gfx::Rect* out_rect); +}; + +} // namespace mojo +``` + +And in `//ui/gfx/geometry/mojo/geometry_struct_traits.cc`: + +``` cpp +#include "ui/gfx/geometry/mojo/geometry_struct_traits.h" + +namespace mojo { + +// static +template <> +bool StructTraits<gfx::mojom::RectDataView, gfx::Rect>::Read( + gfx::mojom::RectDataView data, + gfx::Rect* out_rect) { + if (data.width() < 0 || data.height() < 0) + return false; + + out_rect->SetRect(data.x(), data.y(), data.width(), data.height()); + return true; +}; + +} // namespace mojo +``` + +Note that the `Read()` method returns `false` if either the incoming `width` or +`height` fields are negative. This acts as a validation step during +deserialization: if a client sends a `gfx::Rect` with a negative width or +height, its message will be rejected and the pipe will be closed. In this way, +type mapping can serve to enable custom validation logic in addition to making +callsites and interface implemention more convenient. + +### Enabling a New Type Mapping + +We've defined the `StructTraits` necessary, but we still need to teach the +bindings generator (and hence the build system) about the mapping. To do this we +must create a **typemap** file, which uses familiar GN syntax to describe the +new type mapping. + +Let's place this `geometry.typemap` file alongside our Mojom file: + +``` +mojom = "//ui/gfx/geometry/mojo/geometry.mojom" +public_headers = [ "//ui/gfx/geometry/rect.h" ] +traits_headers = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.h" ] +sources = [ "//ui/gfx/geometry/mojo/geometry_struct_traits.cc" ] +public_deps = [ "//ui/gfx/geometry" ] +type_mappings = [ + "gfx.mojom.Rect=gfx::Rect", +] +``` + +Let's look at each of the variables above: + +* `mojom`: Specifies the `mojom` file to which the typemap applies. Many + typemaps may apply to the same `mojom` file, but any given typemap may only + apply to a single `mojom` file. +* `public_headers`: Additional headers required by any code which would depend + on the Mojom definition of `gfx.mojom.Rect` now that the typemap is applied. + Any headers required for the native target type definition should be listed + here. +* `traits_headers`: Headers which contain the relevant `StructTraits` + specialization(s) for any type mappings described by this file. +* `sources`: Any private implementation sources needed for the `StructTraits` + definition. +* `public_deps`: Target dependencies exposed by the `public_headers` and + `traits_headers`. +* `deps`: Target dependencies exposed by `sources` but not already covered by + `public_deps`. +* `type_mappings`: A list of type mappings to be applied for this typemap. The + strings in this list are of the format `"MojomType=CppType"`, where + `MojomType` must be a fully qualified Mojom typename and `CppType` must be a + fully qualified C++ typename. Additional attributes may be specified in square + brackets following the `CppType`: + * `move_only`: The `CppType` is move-only and should be passed by value + in any generated method signatures. Note that `move_only` is transitive, + so containers of `MojomType` will translate to containers of `CppType` + also passed by value. + * `copyable_pass_by_value`: Forces values of type `CppType` to be passed by + value without moving them. Unlike `move_only`, this is not transitive. + * `nullable_is_same_type`: By default a non-nullable `MojomType` will be + mapped to `CppType` while a nullable `MojomType?` will be mapped to + `base::Optional<CppType>`. If this attribute is set, the `base::Optional` + wrapper is omitted for nullable `MojomType?` values, but the + `StructTraits` definition for this type mapping must define additional + `IsNull` and `SetToNull` methods. See + [Specializing Nullability](#Specializing-Nullability) below. + + +Now that we have the typemap file we need to add it to a local list of typemaps +that can be added to the global configuration. We create a new +`//ui/gfx/typemaps.gni` file with the following contents: + +``` +typemaps = [ + "//ui/gfx/geometry/mojo/geometry.typemap", +] +``` + +And finally we can reference this file in the global default (Chromium) bindings +configuration by adding it to `_typemap_imports` in +[chromium_bindings_configuration.gni](https://cs.chromium.com/chromium/src/mojo/public/tools/bindings/chromium_bindings_configuration.gni): + +``` +_typemap_imports = [ + ..., + "//ui/gfx/typemaps.gni", + ..., +] +``` + +### StructTraits Reference + +Each of a `StructTraits` specialization's static getter methods -- one per +struct field -- must return a type which can be used as a data source for the +field during serialization. This is a quick reference mapping Mojom field type +to valid getter return types: + +| Mojom Field Type | C++ Getter Return Type | +|------------------------------|------------------------| +| `bool` | `bool` +| `int8` | `int8_t` +| `uint8` | `uint8_t` +| `int16` | `int16_t` +| `uint16` | `uint16_t` +| `int32` | `int32_t` +| `uint32` | `uint32_t` +| `int64` | `int64_t` +| `uint64` | `uint64_t` +| `float` | `float` +| `double` | `double` +| `handle` | `mojo::ScopedHandle` +| `handle<message_pipe>` | `mojo::ScopedMessagePipeHandle` +| `handle<data_pipe_consumer>` | `mojo::ScopedDataPipeConsumerHandle` +| `handle<data_pipe_producer>` | `mojo::ScopedDataPipeProducerHandle` +| `handle<shared_buffer>` | `mojo::ScopedSharedBufferHandle` +| `FooInterface` | `FooInterfacePtr` +| `FooInterface&` | `FooInterfaceRequest` +| `associated FooInterface` | `FooAssociatedInterfacePtr` +| `associated FooInterface&` | `FooAssociatedInterfaceRequest` +| `string` | Value or reference to any type `T` that has a `mojo::StringTraits` specialization defined. By default this includes `std::string`, `base::StringPiece`, and `WTF::String` (Blink). +| `array<T>` | Value or reference to any type `T` that has a `mojo::ArrayTraits` specialization defined. By default this includes `std::vector<T>`, `mojo::CArray<T>`, and `WTF::Vector<T>` (Blink). +| `map<K, V>` | Value or reference to any type `T` that has a `mojo::MapTraits` specialization defined. By default this includes `std::map<T>`, `mojo::unordered_map<T>`, and `WTF::HashMap<T>` (Blink). +| `FooEnum` | Value of any type that has an appropriate `EnumTraits` specialization defined. By default this inlcudes only the generated `FooEnum` type. +| `FooStruct` | Value or reference to any type that has an appropriate `StructTraits` specialization defined. By default this includes only the generated `FooStructPtr` type. +| `FooUnion` | Value of reference to any type that has an appropriate `UnionTraits` specialization defined. By default this includes only the generated `FooUnionPtr` type. + +### Using Generated DataView Types + +Static `Read` methods on `StructTraits` specializations get a generated +`FooDataView` argument (such as the `RectDataView` in the example above) which +exposes a direct view of the serialized Mojom structure within an incoming +message's contents. In order to make this as easy to work with as possible, the +generated `FooDataView` types have a generated method corresponding to every +struct field: + +* For POD field types (*e.g.* bools, floats, integers) these are simple accessor + methods with names identical to the field name. Hence in the `Rect` example we + can access things like `data.x()` and `data.width()`. The return types + correspond exactly to the mappings listed in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +* For handle and interface types (*e.g* `handle` or `FooInterface&`) these + are named `TakeFieldName` (for a field named `field_name`) and they return an + appropriate move-only handle type by value. The return types correspond + exactly to the mappings listed in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +* For all other field types (*e.g.*, enums, strings, arrays, maps, structs) + these are named `ReadFieldName` (for a field named `field_name`) and they + return a `bool` (to indicate success or failure in reading). On success they + fill their output argument with the deserialized field value. The output + argument may be a pointer to any type with an appropriate `StructTraits` + specialization defined, as mentioned in the table above, under + [StructTraits Reference](#StructTraits-Reference). + +An example would be useful here. Suppose we introduced a new Mojom struct: + +``` cpp +struct RectPair { + Rect left; + Rect right; +}; +``` + +and a corresponding C++ type: + +``` cpp +class RectPair { + public: + RectPair() {} + + const gfx::Rect& left() const { return left_; } + const gfx::Rect& right() const { return right_; } + + void Set(const gfx::Rect& left, const gfx::Rect& right) { + left_ = left; + right_ = right; + } + + // ... some other stuff + + private: + gfx::Rect left_; + gfx::Rect right_; +}; +``` + +Our traits to map `gfx::mojom::RectPair` to `gfx::RectPair` might look like +this: + +``` cpp +namespace mojo { + +template <> +class StructTraits + public: + static const gfx::Rect& left(const gfx::RectPair& pair) { + return pair.left(); + } + + static const gfx::Rect& right(const gfx::RectPair& pair) { + return pair.right(); + } + + static bool Read(gfx::mojom::RectPairDataView data, gfx::RectPair* out_pair) { + gfx::Rect left, right; + if (!data.ReadLeft(&left) || !data.ReadRight(&right)) + return false; + out_pair->Set(left, right); + return true; + } +} // namespace mojo +``` + +Generated `ReadFoo` methods always convert `multi_word_field_name` fields to +`ReadMultiWordFieldName` methods. + +### Variants + +By now you may have noticed that additional C++ sources are generated when a +Mojom is processed. These exist due to type mapping, and the source files we +refer to throughout this docuemnt (namely `foo.mojom.cc` and `foo.mojom.h`) are +really only one **variant** (the *default* or *chromium* variant) of the C++ +bindings for a given Mojom file. + +The only other variant currently defined in the tree is the *blink* variant, +which produces a few additional files: + +``` +out/gen/sample/db.mojom-blink.cc +out/gen/sample/db.mojom-blink.h +``` + +These files mirror the definitions in the default variant but with different +C++ types in place of certain builtin field and parameter types. For example, +Mojom strings are represented by `WTF::String` instead of `std::string`. To +avoid symbol collisions, the variant's symbols are nested in an extra inner +namespace, so Blink consumer of the interface might write something like: + +``` +#include "sample/db.mojom-blink.h" + +class TableImpl : public db::mojom::blink::Table { + public: + void AddRow(int32_t key, const WTF::String& data) override { + // ... + } +}; +``` + +In addition to using different C++ types for builtin strings, arrays, and maps, +the global typemap configuration for default and "blink" variants are completely +separate. To add a typemap for the Blink configuration, you can modify +[blink_bindings_configuration.gni](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings/blink_bindings_configuration.gni). + +All variants share some definitions which are unaffected by differences in the +type mapping configuration (enums, for example). These definitions are generated +in *shared* sources: + +``` +out/gen/sample/db.mojom-shared.cc +out/gen/sample/db.mojom-shared.h +out/gen/sample/db.mojom-shared-internal.h +``` + +Including either variant's header (`db.mojom.h` or `db.mojom-blink.h`) +implicitly includes the shared header, but you have on some occasions wish to +include *only* the shared header in some instances. + +Finally, note that for `mojom` GN targets, there is implicitly a corresponding +`mojom_{variant}` target defined for any supported bindings configuration. So +for example if you've defined in `//sample/BUILD.gn`: + +``` +import("mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "db.mojom", + ] +} +``` + +Code in Blink which wishes to use the generated Blink-variant definitions must +depend on `"//sample:interfaces_blink"`. + +## Versioning Considerations + +For general documentation of versioning in the Mojom IDL see +[Versioning](/mojo/public/tools/bindings#Versioning). + +This section briefly discusses some C++-specific considerations relevant to +versioned Mojom types. + +### Querying Interface Versions + +`InterfacePtr` defines the following methods to query or assert remote interface +version: + +```cpp +void QueryVersion(const base::Callback<void(uint32_t)>& callback); +``` + +This queries the remote endpoint for the version number of its binding. When a +response is received `callback` is invoked with the remote version number. Note +that this value is cached by the `InterfacePtr` instance to avoid redundant +queries. + +```cpp +void RequireVersion(uint32_t version); +``` + +Informs the remote endpoint that a minimum version of `version` is required by +the client. If the remote endpoint cannot support that version, it will close +its end of the pipe immediately, preventing any other requests from being +received. + +### Versioned Enums + +For convenience, every extensible enum has a generated helper function to +determine whether a received enum value is known by the implementation's current +version of the enum definition. For example: + +```cpp +[Extensible] +enum Department { + SALES, + DEV, + RESEARCH, +}; +``` + +generates the function in the same namespace as the generated C++ enum type: + +```cpp +inline bool IsKnownEnumValue(Department value); +``` + +### Additional Documentation + +[Calling Mojo From Blink](https://www.chromium.org/developers/design-documents/mojo/calling-mojo-from-blink) +: A brief overview of what it looks like to use Mojom C++ bindings from + within Blink code. diff --git a/mojo/public/cpp/bindings/array_data_view.h b/mojo/public/cpp/bindings/array_data_view.h new file mode 100644 index 0000000000..d02a8846ec --- /dev/null +++ b/mojo/public/cpp/bindings/array_data_view.h @@ -0,0 +1,244 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_ + +#include <type_traits> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" + +namespace mojo { +namespace internal { + +template <typename T, typename EnableType = void> +class ArrayDataViewImpl; + +template <typename T> +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo<T, MojomTypeCategory::POD>::value>::type> { + public: + using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + T operator[](size_t index) const { return data_->at(index); } + + const T* data() const { return data_->storage(); } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template <typename T> +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo<T, MojomTypeCategory::BOOLEAN>::value>::type> { + public: + using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + bool operator[](size_t index) const { return data_->at(index); } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template <typename T> +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo<T, MojomTypeCategory::ENUM>::value>::type> { + public: + static_assert(sizeof(T) == sizeof(int32_t), "Unexpected enum size"); + + using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + T operator[](size_t index) const { return static_cast<T>(data_->at(index)); } + + const T* data() const { return reinterpret_cast<const T*>(data_->storage()); } + + template <typename U> + bool Read(size_t index, U* output) { + return Deserialize<T>(data_->at(index), output); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template <typename T> +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo<T, + MojomTypeCategory::ASSOCIATED_INTERFACE | + MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST | + MojomTypeCategory::INTERFACE | + MojomTypeCategory::INTERFACE_REQUEST>::value>::type> { + public: + using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + template <typename U> + U Take(size_t index) { + U result; + bool ret = Deserialize<T>(&data_->at(index), &result, context_); + DCHECK(ret); + return result; + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template <typename T> +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo<T, MojomTypeCategory::HANDLE>::value>::type> { + public: + using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + T Take(size_t index) { + T result; + bool ret = Deserialize<T>(&data_->at(index), &result, context_); + DCHECK(ret); + return result; + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template <typename T> +class ArrayDataViewImpl<T, + typename std::enable_if<BelongsTo< + T, + MojomTypeCategory::ARRAY | MojomTypeCategory::MAP | + MojomTypeCategory::STRING | + MojomTypeCategory::STRUCT>::value>::type> { + public: + using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + void GetDataView(size_t index, T* output) { + *output = T(data_->at(index).Get(), context_); + } + + template <typename U> + bool Read(size_t index, U* output) { + return Deserialize<T>(data_->at(index).Get(), output, context_); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template <typename T> +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo<T, MojomTypeCategory::UNION>::value>::type> { + public: + using Data_ = typename MojomTypeTraits<ArrayDataView<T>>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + void GetDataView(size_t index, T* output) { + *output = T(&data_->at(index), context_); + } + + template <typename U> + bool Read(size_t index, U* output) { + return Deserialize<T>(&data_->at(index), output, context_); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +} // namespace internal + +template <typename K, typename V> +class MapDataView; + +template <typename T> +class ArrayDataView : public internal::ArrayDataViewImpl<T> { + public: + using Element = T; + using Data_ = typename internal::ArrayDataViewImpl<T>::Data_; + + ArrayDataView() : internal::ArrayDataViewImpl<T>(nullptr, nullptr) {} + + ArrayDataView(Data_* data, internal::SerializationContext* context) + : internal::ArrayDataViewImpl<T>(data, context) {} + + bool is_null() const { return !this->data_; } + + size_t size() const { return this->data_->size(); } + + // Methods to access elements are different for different element types. They + // are inherited from internal::ArrayDataViewImpl: + + // POD types except boolean and enums: + // T operator[](size_t index) const; + // const T* data() const; + + // Boolean: + // bool operator[](size_t index) const; + + // Enums: + // T operator[](size_t index) const; + // const T* data() const; + // template <typename U> + // bool Read(size_t index, U* output); + + // Handles: + // T Take(size_t index); + + // Interfaces: + // template <typename U> + // U Take(size_t index); + + // Object types: + // void GetDataView(size_t index, T* output); + // template <typename U> + // bool Read(size_t index, U* output); + + private: + template <typename K, typename V> + friend class MapDataView; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_DATA_VIEW_H_ diff --git a/mojo/public/cpp/bindings/array_traits.h b/mojo/public/cpp/bindings/array_traits.h new file mode 100644 index 0000000000..594b2e0789 --- /dev/null +++ b/mojo/public/cpp/bindings/array_traits.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_ + +namespace mojo { + +// This must be specialized for any type |T| to be serialized/deserialized as +// a mojom array. +// +// Usually you would like to do a partial specialization for a container (e.g. +// vector) template. Imagine you want to specialize it for Container<>, you need +// to implement: +// +// template <typename T> +// struct ArrayTraits<Container<T>> { +// using Element = T; +// // These two statements are optional. Use them if you'd like to serialize +// // a container that supports iterators but does not support O(1) random +// // access and so GetAt(...) would be expensive. +// // using Iterator = T::iterator; +// // using ConstIterator = T::const_iterator; +// +// // These two methods are optional. Please see comments in struct_traits.h +// static bool IsNull(const Container<T>& input); +// static void SetToNull(Container<T>* output); +// +// static size_t GetSize(const Container<T>& input); +// +// // These two methods are optional. They are used to access the +// // underlying storage of the array to speed up copy of POD types. +// static T* GetData(Container<T>& input); +// static const T* GetData(const Container<T>& input); +// +// // The following six methods are optional if the GetAt(...) methods are +// // implemented. These methods specify how to read the elements of +// // Container in some sequential order specified by the iterator. +// // +// // Acquires an iterator positioned at the first element in the container. +// static ConstIterator GetBegin(const Container<T>& input); +// static Iterator GetBegin(Container<T>& input); +// +// // Advances |iterator| to the next position within the container. +// static void AdvanceIterator(ConstIterator& iterator); +// static void AdvanceIterator(Iterator& iterator); +// +// // Returns a reference to the value at the current position of +// // |iterator|. Optionally, the ConstIterator version of GetValue can +// // return by value instead of by reference if it makes sense for the +// // type. +// static const T& GetValue(ConstIterator& iterator); +// static T& GetValue(Iterator& iterator); +// +// // These two methods are optional if the iterator methods are +// // implemented. +// static T& GetAt(Container<T>& input, size_t index); +// static const T& GetAt(const Container<T>& input, size_t index); +// +// // Returning false results in deserialization failure and causes the +// // message pipe receiving it to be disconnected. +// static bool Resize(Container<T>& input, size_t size); +// }; +// +template <typename T> +struct ArrayTraits; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/array_traits_carray.h b/mojo/public/cpp/bindings/array_traits_carray.h new file mode 100644 index 0000000000..3ff694b882 --- /dev/null +++ b/mojo/public/cpp/bindings/array_traits_carray.h @@ -0,0 +1,76 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_ + +#include "mojo/public/cpp/bindings/array_traits.h" + +namespace mojo { + +template <typename T> +struct CArray { + CArray() : size(0), max_size(0), data(nullptr) {} + CArray(size_t size, size_t max_size, T* data) + : size(size), max_size(max_size), data(data) {} + size_t size; + const size_t max_size; + T* data; +}; + +template <typename T> +struct ConstCArray { + ConstCArray() : size(0), data(nullptr) {} + ConstCArray(size_t size, const T* data) : size(size), data(data) {} + size_t size; + const T* data; +}; + +template <typename T> +struct ArrayTraits<CArray<T>> { + using Element = T; + + static bool IsNull(const CArray<T>& input) { return !input.data; } + + static void SetToNull(CArray<T>* output) { output->data = nullptr; } + + static size_t GetSize(const CArray<T>& input) { return input.size; } + + static T* GetData(CArray<T>& input) { return input.data; } + + static const T* GetData(const CArray<T>& input) { return input.data; } + + static T& GetAt(CArray<T>& input, size_t index) { return input.data[index]; } + + static const T& GetAt(const CArray<T>& input, size_t index) { + return input.data[index]; + } + + static bool Resize(CArray<T>& input, size_t size) { + if (size > input.max_size) + return false; + + input.size = size; + return true; + } +}; + +template <typename T> +struct ArrayTraits<ConstCArray<T>> { + using Element = T; + + static bool IsNull(const ConstCArray<T>& input) { return !input.data; } + + static size_t GetSize(const ConstCArray<T>& input) { return input.size; } + + static const T* GetData(const ConstCArray<T>& input) { return input.data; } + + static const T& GetAt(const ConstCArray<T>& input, size_t index) { + return input.data[index]; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_CARRAY_H_ diff --git a/mojo/public/cpp/bindings/array_traits_stl.h b/mojo/public/cpp/bindings/array_traits_stl.h new file mode 100644 index 0000000000..dec47bfe7d --- /dev/null +++ b/mojo/public/cpp/bindings/array_traits_stl.h @@ -0,0 +1,127 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_ + +#include <map> +#include <set> +#include <vector> + +#include "mojo/public/cpp/bindings/array_traits.h" + +namespace mojo { + +template <typename T> +struct ArrayTraits<std::vector<T>> { + using Element = T; + + static bool IsNull(const std::vector<T>& input) { + // std::vector<> is always converted to non-null mojom array. + return false; + } + + static void SetToNull(std::vector<T>* output) { + // std::vector<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const std::vector<T>& input) { return input.size(); } + + static T* GetData(std::vector<T>& input) { return input.data(); } + + static const T* GetData(const std::vector<T>& input) { return input.data(); } + + static typename std::vector<T>::reference GetAt(std::vector<T>& input, + size_t index) { + return input[index]; + } + + static typename std::vector<T>::const_reference GetAt( + const std::vector<T>& input, + size_t index) { + return input[index]; + } + + static inline bool Resize(std::vector<T>& input, size_t size) { + // Instead of calling std::vector<T>::resize() directly, this is a hack to + // make compilers happy. Some compilers (e.g., Mac, Android, Linux MSan) + // currently don't allow resizing types like + // std::vector<std::vector<MoveOnlyType>>. + // Because the deserialization code doesn't care about the original contents + // of |input|, we discard them directly. + // + // The "inline" keyword of this method matters. Without it, we have observed + // significant perf regression with some tests on Mac. crbug.com/631415 + if (input.size() != size) { + std::vector<T> temp(size); + input.swap(temp); + } + + return true; + } +}; + +// This ArrayTraits specialization is used only for serialization. +template <typename T> +struct ArrayTraits<std::set<T>> { + using Element = T; + using ConstIterator = typename std::set<T>::const_iterator; + + static bool IsNull(const std::set<T>& input) { + // std::set<> is always converted to non-null mojom array. + return false; + } + + static size_t GetSize(const std::set<T>& input) { return input.size(); } + + static ConstIterator GetBegin(const std::set<T>& input) { + return input.begin(); + } + static void AdvanceIterator(ConstIterator& iterator) { + ++iterator; + } + static const T& GetValue(ConstIterator& iterator) { + return *iterator; + } +}; + +template <typename K, typename V> +struct MapValuesArrayView { + explicit MapValuesArrayView(const std::map<K, V>& map) : map(map) {} + const std::map<K, V>& map; +}; + +// Convenience function to create a MapValuesArrayView<> that infers the +// template arguments from its argument type. +template <typename K, typename V> +MapValuesArrayView<K, V> MapValuesToArray(const std::map<K, V>& map) { + return MapValuesArrayView<K, V>(map); +} + +// This ArrayTraits specialization is used only for serialization and converts +// a map<K, V> into an array<V>, discarding the keys. +template <typename K, typename V> +struct ArrayTraits<MapValuesArrayView<K, V>> { + using Element = V; + using ConstIterator = typename std::map<K, V>::const_iterator; + + static bool IsNull(const MapValuesArrayView<K, V>& input) { + // std::map<> is always converted to non-null mojom array. + return false; + } + + static size_t GetSize(const MapValuesArrayView<K, V>& input) { + return input.map.size(); + } + static ConstIterator GetBegin(const MapValuesArrayView<K, V>& input) { + return input.map.begin(); + } + static void AdvanceIterator(ConstIterator& iterator) { ++iterator; } + static const V& GetValue(ConstIterator& iterator) { return iterator->second; } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_STL_H_ diff --git a/mojo/public/cpp/bindings/array_traits_wtf_vector.h b/mojo/public/cpp/bindings/array_traits_wtf_vector.h new file mode 100644 index 0000000000..6e207351fd --- /dev/null +++ b/mojo/public/cpp/bindings/array_traits_wtf_vector.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_ + +#include "mojo/public/cpp/bindings/array_traits.h" +#include "third_party/WebKit/Source/wtf/Vector.h" + +namespace mojo { + +template <typename U> +struct ArrayTraits<WTF::Vector<U>> { + using Element = U; + + static bool IsNull(const WTF::Vector<U>& input) { + // WTF::Vector<> is always converted to non-null mojom array. + return false; + } + + static void SetToNull(WTF::Vector<U>* output) { + // WTF::Vector<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const WTF::Vector<U>& input) { return input.size(); } + + static U* GetData(WTF::Vector<U>& input) { return input.data(); } + + static const U* GetData(const WTF::Vector<U>& input) { return input.data(); } + + static U& GetAt(WTF::Vector<U>& input, size_t index) { return input[index]; } + + static const U& GetAt(const WTF::Vector<U>& input, size_t index) { + return input[index]; + } + + static bool Resize(WTF::Vector<U>& input, size_t size) { + input.resize(size); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ARRAY_TRAITS_WTF_VECTOR_H_ diff --git a/mojo/public/cpp/bindings/associated_binding.h b/mojo/public/cpp/bindings/associated_binding.h new file mode 100644 index 0000000000..59411666f5 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_binding.h @@ -0,0 +1,171 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_ + +#include <memory> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +class MessageReceiver; + +// Base class used to factor out code in AssociatedBinding<T> expansions, in +// particular for Bind(). +class MOJO_CPP_BINDINGS_EXPORT AssociatedBindingBase { + public: + AssociatedBindingBase(); + ~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 + // dispatched and the pipe is closed. Filters cannot be removed. + void AddFilter(std::unique_ptr<MessageReceiver> filter); + + // Closes the associated interface. Puts this object into a state where it can + // be rebound. + void Close(); + + // Similar to the method above, but also specifies a disconnect reason. + void CloseWithReason(uint32_t custom_reason, const std::string& description); + + // Sets an error handler that will be called if a connection error occurs. + // + // This method may only be called after this AssociatedBinding has been bound + // to a message pipe. The error handler will be reset when this + // AssociatedBinding is unbound or closed. + void set_connection_error_handler(const base::Closure& error_handler); + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler); + + // Indicates whether the associated binding has been completed. + bool is_bound() const { return !!endpoint_client_; } + + // Sends a message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting(); + + protected: + void BindImpl(ScopedInterfaceEndpointHandle handle, + MessageReceiverWithResponderStatus* receiver, + std::unique_ptr<MessageReceiver> payload_validator, + bool expect_sync_requests, + scoped_refptr<base::SingleThreadTaskRunner> runner, + uint32_t interface_version); + + std::unique_ptr<InterfaceEndpointClient> endpoint_client_; +}; + +// Represents the implementation side of an associated interface. It is similar +// to Binding, except that it doesn't own a message pipe handle. +// +// When you bind this class to a request, optionally you can specify a +// base::SingleThreadTaskRunner. This task runner must belong to the same +// thread. It will be used to dispatch incoming method calls and connection +// error notification. It is useful when you attach multiple task runners to a +// single thread for the purposes of task scheduling. Please note that incoming +// synchrounous method calls may not be run from this task runner, when they +// reenter outgoing synchrounous calls on the same thread. +template <typename Interface, + typename ImplRefTraits = RawPtrImplRefTraits<Interface>> +class AssociatedBinding : public AssociatedBindingBase { + public: + using ImplPointerType = typename ImplRefTraits::PointerType; + + // Constructs an incomplete associated binding that will use the + // implementation |impl|. It may be completed with a subsequent call to the + // |Bind| method. Does not take ownership of |impl|, which must outlive this + // object. + explicit AssociatedBinding(ImplPointerType impl) { stub_.set_sink(impl); } + + // Constructs a completed associated binding of |impl|. The output |ptr_info| + // should be sent by another interface. |impl| must outlive this object. + AssociatedBinding(ImplPointerType impl, + AssociatedInterfacePtrInfo<Interface>* ptr_info, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) + : AssociatedBinding(std::move(impl)) { + Bind(ptr_info, std::move(runner)); + } + + // Constructs a completed associated binding of |impl|. |impl| must outlive + // the binding. + AssociatedBinding(ImplPointerType impl, + AssociatedInterfaceRequest<Interface> request, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) + : AssociatedBinding(std::move(impl)) { + Bind(std::move(request), std::move(runner)); + } + + ~AssociatedBinding() {} + + // Creates an associated inteface and sets up this object as the + // implementation side. The output |ptr_info| should be sent by another + // interface. + void Bind(AssociatedInterfacePtrInfo<Interface>* ptr_info, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + auto request = MakeRequest(ptr_info); + ptr_info->set_version(Interface::Version_); + Bind(std::move(request), std::move(runner)); + } + + // Sets up this object as the implementation side of an associated interface. + void Bind(AssociatedInterfaceRequest<Interface> request, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + BindImpl(request.PassHandle(), &stub_, + base::WrapUnique(new typename Interface::RequestValidator_()), + Interface::HasSyncMethods_, std::move(runner), + Interface::Version_); + } + + // Unbinds and returns the associated interface request so it can be + // used in another context, such as on another thread or with a different + // implementation. Puts this object into a state where it can be rebound. + AssociatedInterfaceRequest<Interface> Unbind() { + DCHECK(endpoint_client_); + + AssociatedInterfaceRequest<Interface> request; + request.Bind(endpoint_client_->PassHandle()); + + endpoint_client_.reset(); + + return request; + } + + // Returns the interface implementation that was previously specified. + Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); } + + private: + typename Interface::template Stub_<ImplRefTraits> stub_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedBinding); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_H_ diff --git a/mojo/public/cpp/bindings/associated_binding_set.h b/mojo/public/cpp/bindings/associated_binding_set.h new file mode 100644 index 0000000000..59600c64f3 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_binding_set.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_ + +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/binding_set.h" + +namespace mojo { + +template <typename Interface, typename ImplRefTraits> +struct BindingSetTraits<AssociatedBinding<Interface, ImplRefTraits>> { + using ProxyType = AssociatedInterfacePtr<Interface>; + using RequestType = AssociatedInterfaceRequest<Interface>; + using BindingType = AssociatedBinding<Interface, ImplRefTraits>; + using ImplPointerType = typename BindingType::ImplPointerType; +}; + +template <typename Interface, typename ContextType = void> +using AssociatedBindingSet = + BindingSetBase<Interface, AssociatedBinding<Interface>, ContextType>; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_BINDING_SET_H_ diff --git a/mojo/public/cpp/bindings/associated_group.h b/mojo/public/cpp/bindings/associated_group.h new file mode 100644 index 0000000000..14e78ec3f9 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_group.h @@ -0,0 +1,51 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +class AssociatedGroupController; + +// AssociatedGroup refers to all the interface endpoints running at one end of a +// message pipe. +// It is thread safe and cheap to make copies. +class MOJO_CPP_BINDINGS_EXPORT AssociatedGroup { + public: + AssociatedGroup(); + + explicit AssociatedGroup(scoped_refptr<AssociatedGroupController> controller); + + explicit AssociatedGroup(const ScopedInterfaceEndpointHandle& handle); + + AssociatedGroup(const AssociatedGroup& other); + + ~AssociatedGroup(); + + AssociatedGroup& operator=(const AssociatedGroup& other); + + // The return value of this getter if this object is initialized with a + // ScopedInterfaceEndpointHandle: + // - If the handle is invalid, the return value will always be null. + // - If the handle is valid and non-pending, the return value will be + // non-null and remain unchanged even if the handle is later reset. + // - If the handle is pending asssociation, the return value will initially + // be null, change to non-null when/if the handle is associated, and + // remain unchanged ever since. + AssociatedGroupController* GetController(); + + private: + base::Callback<AssociatedGroupController*()> controller_getter_; + scoped_refptr<AssociatedGroupController> controller_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_H_ diff --git a/mojo/public/cpp/bindings/associated_group_controller.h b/mojo/public/cpp/bindings/associated_group_controller.h new file mode 100644 index 0000000000..d33c2776d5 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_group_controller.h @@ -0,0 +1,93 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +class InterfaceEndpointClient; +class InterfaceEndpointController; + +// An internal interface used to manage endpoints within an associated group, +// which corresponds to one end of a message pipe. +class MOJO_CPP_BINDINGS_EXPORT AssociatedGroupController + : public base::RefCountedThreadSafe<AssociatedGroupController> { + public: + // Associates an interface with this AssociatedGroupController's message pipe. + // It takes ownership of |handle_to_send| and returns an interface ID that + // could be sent by any endpoints within the same associated group. + // If |handle_to_send| is not in pending association state, it returns + // kInvalidInterfaceId. Otherwise, the peer handle of |handle_to_send| joins + // the associated group and is no longer pending. + virtual InterfaceId AssociateInterface( + ScopedInterfaceEndpointHandle handle_to_send) = 0; + + // Creates an interface endpoint handle from a given interface ID. The handle + // joins this associated group. + // Typically, this method is used to (1) create an endpoint handle for the + // master interface; or (2) create an endpoint handle on receiving an + // interface ID from the message pipe. + // + // On failure, the method returns an invalid handle. Usually that is because + // the ID has already been used to create a handle. + virtual ScopedInterfaceEndpointHandle CreateLocalEndpointHandle( + InterfaceId id) = 0; + + // Closes an interface endpoint handle. + virtual void CloseEndpointHandle( + InterfaceId id, + const base::Optional<DisconnectReason>& reason) = 0; + + // Attaches a client to the specified endpoint to send and receive messages. + // The returned object is still owned by the controller. It must only be used + // on the same thread as this call, and only before the client is detached + // using DetachEndpointClient(). + virtual InterfaceEndpointController* AttachEndpointClient( + const ScopedInterfaceEndpointHandle& handle, + InterfaceEndpointClient* endpoint_client, + scoped_refptr<base::SingleThreadTaskRunner> runner) = 0; + + // Detaches the client attached to the specified endpoint. It must be called + // on the same thread as the corresponding AttachEndpointClient() call. + virtual void DetachEndpointClient( + const ScopedInterfaceEndpointHandle& handle) = 0; + + // Raises an error on the underlying message pipe. It disconnects the pipe + // and notifies all interfaces running on this pipe. + virtual void RaiseError() = 0; + + protected: + friend class base::RefCountedThreadSafe<AssociatedGroupController>; + + // Creates a new ScopedInterfaceEndpointHandle within this associated group. + ScopedInterfaceEndpointHandle CreateScopedInterfaceEndpointHandle( + InterfaceId id); + + // Notifies that the interface represented by |handle_to_send| and its peer + // has been associated with this AssociatedGroupController's message pipe, and + // |handle_to_send|'s peer has joined this associated group. (Note: it is the + // peer who has joined the associated group; |handle_to_send| will be sent to + // the remote side.) + // Returns false if |handle_to_send|'s peer has closed. + bool NotifyAssociation(ScopedInterfaceEndpointHandle* handle_to_send, + InterfaceId id); + + virtual ~AssociatedGroupController(); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_GROUP_CONTROLLER_H_ diff --git a/mojo/public/cpp/bindings/associated_interface_ptr.h b/mojo/public/cpp/bindings/associated_interface_ptr.h new file mode 100644 index 0000000000..8806a3e090 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_interface_ptr.h @@ -0,0 +1,283 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_ + +#include <stdint.h> + +#include <string> +#include <utility> + +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { + +// Represents the client side of an associated interface. It is similar to +// InterfacePtr, except that it doesn't own a message pipe handle. +template <typename Interface> +class AssociatedInterfacePtr { + public: + using InterfaceType = Interface; + using PtrInfoType = AssociatedInterfacePtrInfo<Interface>; + + // Constructs an unbound AssociatedInterfacePtr. + AssociatedInterfacePtr() {} + AssociatedInterfacePtr(decltype(nullptr)) {} + + AssociatedInterfacePtr(AssociatedInterfacePtr&& other) { + internal_state_.Swap(&other.internal_state_); + } + + AssociatedInterfacePtr& operator=(AssociatedInterfacePtr&& other) { + reset(); + internal_state_.Swap(&other.internal_state_); + return *this; + } + + // Assigning nullptr to this class causes it to closes the associated + // interface (if any) and returns the pointer to the unbound state. + AssociatedInterfacePtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + ~AssociatedInterfacePtr() {} + + // Sets up this object as the client side of an associated interface. + // Calling with an invalid |info| has the same effect as reset(). In this + // case, the AssociatedInterfacePtr is not considered as bound. + // + // |runner| must belong to the same thread. It will be used to dispatch all + // callbacks and connection error notification. It is useful when you attach + // multiple task runners to a single thread for the purposes of task + // scheduling. + // + // NOTE: The corresponding AssociatedInterfaceRequest must be sent over + // another interface before using this object to make calls. Please see the + // comments of MakeRequest(AssociatedInterfacePtr<Interface>*) for more + // details. + void Bind(AssociatedInterfacePtrInfo<Interface> info, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + reset(); + + if (info.is_valid()) + internal_state_.Bind(std::move(info), std::move(runner)); + } + + bool is_bound() const { return internal_state_.is_bound(); } + + Interface* get() const { return internal_state_.instance(); } + + // Functions like a pointer to Interface. Must already be bound. + Interface* operator->() const { return get(); } + Interface& operator*() const { return *get(); } + + // Returns the version number of the interface that the remote side supports. + uint32_t version() const { return internal_state_.version(); } + + // 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 object will also be updated. + void QueryVersion(const base::Callback<void(uint32_t)>& callback) { + internal_state_.QueryVersion(callback); + } + + // If the remote side doesn't support the specified version, it will close the + // associated interface asynchronously. This does nothing if it's already + // known that the remote side supports the specified version, i.e., if + // |version <= this->version()|. + // + // After calling RequireVersion() with a version not supported by the remote + // side, all subsequent calls to interface methods will be ignored. + void RequireVersion(uint32_t version) { + internal_state_.RequireVersion(version); + } + + // Sends a message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting() { internal_state_.FlushForTesting(); } + + // Closes the associated interface (if any) and returns the pointer to the + // unbound state. + void reset() { + State doomed; + internal_state_.Swap(&doomed); + } + + // Similar to the method above, but also specifies a disconnect reason. + void ResetWithReason(uint32_t custom_reason, const std::string& description) { + if (internal_state_.is_bound()) + internal_state_.CloseWithReason(custom_reason, description); + reset(); + } + + // Indicates whether an error has been encountered. If true, method calls made + // on this interface will be dropped (and may already have been dropped). + bool encountered_error() const { return internal_state_.encountered_error(); } + + // Registers a handler to receive error notifications. + // + // This method may only be called after the AssociatedInterfacePtr has been + // bound. + void set_connection_error_handler(const base::Closure& error_handler) { + internal_state_.set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + internal_state_.set_connection_error_with_reason_handler(error_handler); + } + + // Unbinds and returns the associated interface pointer information which + // could be used to setup an AssociatedInterfacePtr again. This method may be + // used to move the proxy to a different thread. + // + // It is an error to call PassInterface() while there are pending responses. + // TODO: fix this restriction, it's not always obvious when there is a + // pending response. + AssociatedInterfacePtrInfo<Interface> PassInterface() { + DCHECK(!internal_state_.has_pending_callbacks()); + State state; + internal_state_.Swap(&state); + + return state.PassInterface(); + } + + // DO NOT USE. Exposed only for internal use and for testing. + internal::AssociatedInterfacePtrState<Interface>* internal_state() { + return &internal_state_; + } + + // Allow AssociatedInterfacePtr<> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + // TODO(dcheng): Use an explicit conversion operator. + typedef internal::AssociatedInterfacePtrState<Interface> + AssociatedInterfacePtr::*Testable; + + public: + operator Testable() const { + return internal_state_.is_bound() ? &AssociatedInterfacePtr::internal_state_ + : nullptr; + } + + private: + // Forbid the == and != operators explicitly, otherwise AssociatedInterfacePtr + // will be converted to Testable to do == or != comparison. + template <typename T> + bool operator==(const AssociatedInterfacePtr<T>& other) const = delete; + template <typename T> + bool operator!=(const AssociatedInterfacePtr<T>& other) const = delete; + + typedef internal::AssociatedInterfacePtrState<Interface> State; + mutable State internal_state_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtr); +}; + +// Creates an associated interface. The returned request is supposed to be sent +// over another interface (either associated or non-associated). +// +// NOTE: |ptr| must NOT be used to make calls before the request is sent. +// Violating that will lead to crash. On the other hand, as soon as the request +// is sent, |ptr| is usable. There is no need to wait until the request is bound +// to an implementation at the remote side. +template <typename Interface> +AssociatedInterfaceRequest<Interface> MakeRequest( + AssociatedInterfacePtr<Interface>* ptr, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + AssociatedInterfacePtrInfo<Interface> ptr_info; + auto request = MakeRequest(&ptr_info); + ptr->Bind(std::move(ptr_info), std::move(runner)); + return request; +} + +// Creates an associated interface. One of the two endpoints is supposed to be +// sent over another interface (either associated or non-associated); while the +// other is used locally. +// +// NOTE: If |ptr_info| is used locally and bound to an AssociatedInterfacePtr, +// the interface pointer must NOT be used to make calls before the request is +// sent. Please see NOTE of the previous function for more details. +template <typename Interface> +AssociatedInterfaceRequest<Interface> MakeRequest( + AssociatedInterfacePtrInfo<Interface>* ptr_info) { + ScopedInterfaceEndpointHandle handle0; + ScopedInterfaceEndpointHandle handle1; + ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&handle0, + &handle1); + + ptr_info->set_handle(std::move(handle0)); + ptr_info->set_version(0); + + AssociatedInterfaceRequest<Interface> request; + request.Bind(std::move(handle1)); + return request; +} + +// Like MakeRequest() above, but it creates a dedicated message pipe. The +// returned request can be bound directly to an implementation, without being +// first passed through a message pipe endpoint. +// +// This function has two main uses: +// +// * In testing, where the returned request is bound to e.g. a mock and there +// are no other interfaces involved. +// +// * When discarding messages sent on an interface, which can be done by +// discarding the returned request. +template <typename Interface> +AssociatedInterfaceRequest<Interface> MakeIsolatedRequest( + AssociatedInterfacePtr<Interface>* ptr) { + MessagePipe pipe; + scoped_refptr<internal::MultiplexRouter> router0 = + new internal::MultiplexRouter(std::move(pipe.handle0), + internal::MultiplexRouter::MULTI_INTERFACE, + false, base::ThreadTaskRunnerHandle::Get()); + scoped_refptr<internal::MultiplexRouter> router1 = + new internal::MultiplexRouter(std::move(pipe.handle1), + internal::MultiplexRouter::MULTI_INTERFACE, + true, base::ThreadTaskRunnerHandle::Get()); + + ScopedInterfaceEndpointHandle endpoint0, endpoint1; + ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&endpoint0, + &endpoint1); + InterfaceId id = router1->AssociateInterface(std::move(endpoint0)); + endpoint0 = router0->CreateLocalEndpointHandle(id); + + ptr->Bind(AssociatedInterfacePtrInfo<Interface>(std::move(endpoint0), + Interface::Version_)); + + AssociatedInterfaceRequest<Interface> request; + request.Bind(std::move(endpoint1)); + return request; +} + +// |handle| is supposed to be the request of an associated interface. This +// method associates the interface with a dedicated, disconnected message pipe. +// That way, the corresponding associated interface pointer of |handle| can +// safely make calls (although those calls are silently dropped). +MOJO_CPP_BINDINGS_EXPORT void GetIsolatedInterface( + ScopedInterfaceEndpointHandle handle); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_H_ diff --git a/mojo/public/cpp/bindings/associated_interface_ptr_info.h b/mojo/public/cpp/bindings/associated_interface_ptr_info.h new file mode 100644 index 0000000000..3c6ca54603 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_interface_ptr_info.h @@ -0,0 +1,77 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ + +#include <stdint.h> +#include <utility> + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +// AssociatedInterfacePtrInfo stores necessary information to construct an +// associated interface pointer. It is similar to InterfacePtrInfo except that +// it doesn't own a message pipe handle. +template <typename Interface> +class AssociatedInterfacePtrInfo { + public: + AssociatedInterfacePtrInfo() : version_(0u) {} + AssociatedInterfacePtrInfo(std::nullptr_t) : version_(0u) {} + + AssociatedInterfacePtrInfo(AssociatedInterfacePtrInfo&& other) + : handle_(std::move(other.handle_)), version_(other.version_) { + other.version_ = 0u; + } + + AssociatedInterfacePtrInfo(ScopedInterfaceEndpointHandle handle, + uint32_t version) + : handle_(std::move(handle)), version_(version) {} + + ~AssociatedInterfacePtrInfo() {} + + AssociatedInterfacePtrInfo& operator=(AssociatedInterfacePtrInfo&& other) { + if (this != &other) { + handle_ = std::move(other.handle_); + version_ = other.version_; + other.version_ = 0u; + } + + return *this; + } + + bool is_valid() const { return handle_.is_valid(); } + + ScopedInterfaceEndpointHandle PassHandle() { + return std::move(handle_); + } + const ScopedInterfaceEndpointHandle& handle() const { return handle_; } + void set_handle(ScopedInterfaceEndpointHandle handle) { + handle_ = std::move(handle); + } + + uint32_t version() const { return version_; } + void set_version(uint32_t version) { version_ = version; } + + bool Equals(const AssociatedInterfacePtrInfo& other) const { + if (this == &other) + return true; + + // Now that the two refer to different objects, they are equivalent if + // and only if they are both invalid. + return !is_valid() && !other.is_valid(); + } + + private: + ScopedInterfaceEndpointHandle handle_; + uint32_t version_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtrInfo); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_PTR_INFO_H_ diff --git a/mojo/public/cpp/bindings/associated_interface_request.h b/mojo/public/cpp/bindings/associated_interface_request.h new file mode 100644 index 0000000000..c37636c9f3 --- /dev/null +++ b/mojo/public/cpp/bindings/associated_interface_request.h @@ -0,0 +1,90 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ + +#include <string> +#include <utility> + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +// AssociatedInterfaceRequest represents an associated interface request. It is +// similar to InterfaceRequest except that it doesn't own a message pipe handle. +template <typename Interface> +class AssociatedInterfaceRequest { + public: + // Constructs an empty AssociatedInterfaceRequest, representing that the + // client is not requesting an implementation of Interface. + AssociatedInterfaceRequest() {} + AssociatedInterfaceRequest(decltype(nullptr)) {} + + // Takes the interface endpoint handle from another + // AssociatedInterfaceRequest. + AssociatedInterfaceRequest(AssociatedInterfaceRequest&& other) { + handle_ = std::move(other.handle_); + } + AssociatedInterfaceRequest& operator=(AssociatedInterfaceRequest&& other) { + if (this != &other) + handle_ = std::move(other.handle_); + return *this; + } + + // Assigning to nullptr resets the AssociatedInterfaceRequest to an empty + // state, closing the interface endpoint handle currently bound to it (if + // any). + AssociatedInterfaceRequest& operator=(decltype(nullptr)) { + handle_.reset(); + return *this; + } + + // Indicates whether the request currently contains a valid interface endpoint + // handle. + bool is_pending() const { return handle_.is_valid(); } + + void Bind(ScopedInterfaceEndpointHandle handle) { + handle_ = std::move(handle); + } + + ScopedInterfaceEndpointHandle PassHandle() { + return std::move(handle_); + } + + const ScopedInterfaceEndpointHandle& handle() const { return handle_; } + + bool Equals(const AssociatedInterfaceRequest& other) const { + if (this == &other) + return true; + + // Now that the two refer to different objects, they are equivalent if + // and only if they are both invalid. + return !is_pending() && !other.is_pending(); + } + + void ResetWithReason(uint32_t custom_reason, const std::string& description) { + handle_.ResetWithReason(custom_reason, description); + } + + private: + ScopedInterfaceEndpointHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedInterfaceRequest); +}; + +// Makes an AssociatedInterfaceRequest bound to the specified associated +// endpoint. +template <typename Interface> +AssociatedInterfaceRequest<Interface> MakeAssociatedRequest( + ScopedInterfaceEndpointHandle handle) { + AssociatedInterfaceRequest<Interface> request; + request.Bind(std::move(handle)); + return request; +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ASSOCIATED_INTERFACE_REQUEST_H_ diff --git a/mojo/public/cpp/bindings/binding.h b/mojo/public/cpp/bindings/binding.h new file mode 100644 index 0000000000..88d2f4ba3e --- /dev/null +++ b/mojo/public/cpp/bindings/binding.h @@ -0,0 +1,276 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ + +#include <string> +#include <utility> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/binding_state.h" +#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +class MessageReceiver; + +// Represents the binding of an interface implementation to a message pipe. +// When the |Binding| object is destroyed, the binding between the message pipe +// and the interface is torn down and the message pipe is closed, leaving the +// interface implementation in an unbound state. +// +// Example: +// +// #include "foo.mojom.h" +// +// class FooImpl : public Foo { +// public: +// explicit FooImpl(InterfaceRequest<Foo> request) +// : binding_(this, std::move(request)) {} +// +// // Foo implementation here. +// +// private: +// Binding<Foo> binding_; +// }; +// +// class MyFooFactory : public InterfaceFactory<Foo> { +// public: +// void Create(..., InterfaceRequest<Foo> request) override { +// auto f = new FooImpl(std::move(request)); +// // Do something to manage the lifetime of |f|. Use StrongBinding<> to +// // delete FooImpl on connection errors. +// } +// }; +// +// This class is thread hostile while bound to a message pipe. All calls to this +// class must be from the thread that bound it. The interface implementation's +// methods will be called from the thread that bound this. If a Binding is not +// bound to a message pipe, it may be bound or destroyed on any thread. +// +// When you bind this class to a message pipe, optionally you can specify a +// base::SingleThreadTaskRunner. This task runner must belong to the same +// thread. It will be used to dispatch incoming method calls and connection +// error notification. It is useful when you attach multiple task runners to a +// single thread for the purposes of task scheduling. Please note that incoming +// synchrounous method calls may not be run from this task runner, when they +// reenter outgoing synchrounous calls on the same thread. +template <typename Interface, + typename ImplRefTraits = RawPtrImplRefTraits<Interface>> +class Binding { + public: + using ImplPointerType = typename ImplRefTraits::PointerType; + + // Constructs an incomplete binding that will use the implementation |impl|. + // The binding may be completed with a subsequent call to the |Bind| method. + // Does not take ownership of |impl|, which must outlive the binding. + explicit Binding(ImplPointerType impl) : internal_state_(std::move(impl)) {} + + // Constructs a completed binding of message pipe |handle| to implementation + // |impl|. Does not take ownership of |impl|, which must outlive the binding. + Binding(ImplPointerType impl, + ScopedMessagePipeHandle handle, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) + : Binding(std::move(impl)) { + Bind(std::move(handle), std::move(runner)); + } + + // Constructs a completed binding of |impl| to a new message pipe, passing the + // client end to |ptr|, which takes ownership of it. The caller is expected to + // pass |ptr| on to the client of the service. Does not take ownership of any + // of the parameters. |impl| must outlive the binding. |ptr| only needs to + // last until the constructor returns. + Binding(ImplPointerType impl, + InterfacePtr<Interface>* ptr, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) + : Binding(std::move(impl)) { + Bind(ptr, std::move(runner)); + } + + // Constructs a completed binding of |impl| to the message pipe endpoint in + // |request|, taking ownership of the endpoint. Does not take ownership of + // |impl|, which must outlive the binding. + Binding(ImplPointerType impl, + InterfaceRequest<Interface> request, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) + : Binding(std::move(impl)) { + Bind(request.PassMessagePipe(), std::move(runner)); + } + + // Tears down the binding, closing the message pipe and leaving the interface + // implementation unbound. + ~Binding() {} + + // Returns an InterfacePtr bound to one end of a pipe whose other end is + // bound to |this|. + InterfacePtr<Interface> CreateInterfacePtrAndBind( + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + InterfacePtr<Interface> interface_ptr; + Bind(&interface_ptr, std::move(runner)); + return interface_ptr; + } + + // Completes a binding that was constructed with only an interface + // implementation. Takes ownership of |handle| and binds it to the previously + // specified implementation. + void Bind(ScopedMessagePipeHandle handle, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + internal_state_.Bind(std::move(handle), std::move(runner)); + } + + // Completes a binding that was constructed with only an interface + // implementation by creating a new message pipe, binding one end of it to the + // previously specified implementation, and passing the other to |ptr|, which + // takes ownership of it. The caller is expected to pass |ptr| on to the + // eventual client of the service. Does not take ownership of |ptr|. + void Bind(InterfacePtr<Interface>* ptr, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + MessagePipe pipe; + ptr->Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), + Interface::Version_), + runner); + Bind(std::move(pipe.handle1), std::move(runner)); + } + + // Completes a binding that was constructed with only an interface + // implementation by removing the message pipe endpoint from |request| and + // binding it to the previously specified implementation. + void Bind(InterfaceRequest<Interface> request, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + Bind(request.PassMessagePipe(), std::move(runner)); + } + + // Adds a message filter to be notified of each incoming message before + // dispatch. If a filter returns |false| from Accept(), the message is not + // dispatched and the pipe is closed. Filters cannot be removed. + void AddFilter(std::unique_ptr<MessageReceiver> filter) { + DCHECK(is_bound()); + internal_state_.AddFilter(std::move(filter)); + } + + // Whether there are any associated interfaces running on the pipe currently. + bool HasAssociatedInterfaces() const { + return internal_state_.HasAssociatedInterfaces(); + } + + // Stops processing incoming messages until + // ResumeIncomingMethodCallProcessing(), or WaitForIncomingMethodCall(). + // Outgoing messages are still sent. + // + // No errors are detected on the message pipe while paused. + // + // This method may only be called if the object has been bound to a message + // pipe and there are no associated interfaces running. + void PauseIncomingMethodCallProcessing() { + CHECK(!HasAssociatedInterfaces()); + internal_state_.PauseIncomingMethodCallProcessing(); + } + void ResumeIncomingMethodCallProcessing() { + internal_state_.ResumeIncomingMethodCallProcessing(); + } + + // Blocks the calling thread until either a call arrives on the previously + // bound message pipe, the deadline is exceeded, or an error occurs. Returns + // true if a method was successfully read and dispatched. + // + // This method may only be called if the object has been bound to a message + // pipe. This returns once a message is received either on the master + // interface or any associated interfaces. + bool WaitForIncomingMethodCall( + MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE) { + return internal_state_.WaitForIncomingMethodCall(deadline); + } + + // Closes the message pipe that was previously bound. Put this object into a + // state where it can be rebound to a new pipe. + void Close() { internal_state_.Close(); } + + // Similar to the method above, but also specifies a disconnect reason. + void CloseWithReason(uint32_t custom_reason, const std::string& description) { + internal_state_.CloseWithReason(custom_reason, description); + } + + // Unbinds the underlying pipe from this binding and returns it so it can be + // used in another context, such as on another thread or with a different + // implementation. Put this object into a state where it can be rebound to a + // new pipe. + // + // This method may only be called if the object has been bound to a message + // pipe and there are no associated interfaces running. + // + // TODO(yzshen): For now, users need to make sure there is no one holding + // on to associated interface endpoint handles at both sides of the + // message pipe in order to call this method. We need a way to forcefully + // invalidate associated interface endpoint handles. + InterfaceRequest<Interface> Unbind() { + CHECK(!HasAssociatedInterfaces()); + return internal_state_.Unbind(); + } + + // Sets an error handler that will be called if a connection error occurs on + // the bound message pipe. + // + // This method may only be called after this Binding has been bound to a + // message pipe. The error handler will be reset when this Binding is unbound + // or closed. + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(is_bound()); + internal_state_.set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(is_bound()); + internal_state_.set_connection_error_with_reason_handler(error_handler); + } + + // Returns the interface implementation that was previously specified. Caller + // does not take ownership. + Interface* impl() { return internal_state_.impl(); } + + // Indicates whether the binding has been completed (i.e., whether a message + // pipe has been bound to the implementation). + bool is_bound() const { return internal_state_.is_bound(); } + + // Returns the value of the handle currently bound to this Binding which can + // be used to make explicit Wait/WaitMany calls. Requires that the Binding be + // bound. Ownership of the handle is retained by the Binding, it is not + // transferred to the caller. + MessagePipeHandle handle() const { return internal_state_.handle(); } + + // Sends a no-op message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting() { internal_state_.FlushForTesting(); } + + // Exposed for testing, should not generally be used. + void EnableTestingMode() { internal_state_.EnableTestingMode(); } + + private: + internal::BindingState<Interface, ImplRefTraits> internal_state_; + + DISALLOW_COPY_AND_ASSIGN(Binding); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDING_H_ diff --git a/mojo/public/cpp/bindings/binding_set.h b/mojo/public/cpp/bindings/binding_set.h new file mode 100644 index 0000000000..919f9c09ad --- /dev/null +++ b/mojo/public/cpp/bindings/binding_set.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_ + +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +template <typename BindingType> +struct BindingSetTraits; + +template <typename Interface, typename ImplRefTraits> +struct BindingSetTraits<Binding<Interface, ImplRefTraits>> { + using ProxyType = InterfacePtr<Interface>; + using RequestType = InterfaceRequest<Interface>; + using BindingType = Binding<Interface, ImplRefTraits>; + using ImplPointerType = typename BindingType::ImplPointerType; + + static RequestType MakeRequest(ProxyType* proxy) { + return mojo::MakeRequest(proxy); + } +}; + +using BindingId = size_t; + +template <typename ContextType> +struct BindingSetContextTraits { + using Type = ContextType; + + static constexpr bool SupportsContext() { return true; } +}; + +template <> +struct BindingSetContextTraits<void> { + // NOTE: This choice of Type only matters insofar as it affects the size of + // the |context_| field of a BindingSetBase::Entry with void context. The + // context value is never used in this case. + using Type = bool; + + static constexpr bool SupportsContext() { return false; } +}; + +// Generic definition used for BindingSet and AssociatedBindingSet to own a +// collection of bindings which point to the same implementation. +// +// If |ContextType| is non-void, then every added binding must include a context +// value of that type, and |dispatch_context()| will return that value during +// the extent of any message dispatch targeting that specific binding. +template <typename Interface, typename BindingType, typename ContextType> +class BindingSetBase { + public: + using ContextTraits = BindingSetContextTraits<ContextType>; + using Context = typename ContextTraits::Type; + using PreDispatchCallback = base::Callback<void(const Context&)>; + using Traits = BindingSetTraits<BindingType>; + using ProxyType = typename Traits::ProxyType; + using RequestType = typename Traits::RequestType; + using ImplPointerType = typename Traits::ImplPointerType; + + BindingSetBase() {} + + void set_connection_error_handler(const base::Closure& error_handler) { + error_handler_ = error_handler; + error_with_reason_handler_.Reset(); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + error_with_reason_handler_ = error_handler; + error_handler_.Reset(); + } + + // Sets a callback to be invoked immediately before dispatching any message or + // error received by any of the bindings in the set. This may only be used + // with a non-void |ContextType|. + void set_pre_dispatch_handler(const PreDispatchCallback& handler) { + static_assert(ContextTraits::SupportsContext(), + "Pre-dispatch handler usage requires non-void context type."); + pre_dispatch_handler_ = handler; + } + + // Adds a new binding to the set which binds |request| to |impl| with no + // additional context. + BindingId AddBinding(ImplPointerType impl, RequestType request) { + static_assert(!ContextTraits::SupportsContext(), + "Context value required for non-void context type."); + return AddBindingImpl(std::move(impl), std::move(request), false); + } + + // Adds a new binding associated with |context|. + BindingId AddBinding(ImplPointerType impl, + RequestType request, + Context context) { + static_assert(ContextTraits::SupportsContext(), + "Context value unsupported for void context type."); + return AddBindingImpl(std::move(impl), std::move(request), + std::move(context)); + } + + // Removes a binding from the set. Note that this is safe to call even if the + // binding corresponding to |id| has already been removed. + // + // Returns |true| if the binding was removed and |false| if it didn't exist. + bool RemoveBinding(BindingId id) { + auto it = bindings_.find(id); + if (it == bindings_.end()) + return false; + bindings_.erase(it); + return true; + } + + // Returns a proxy bound to one end of a pipe whose other end is bound to + // |this|. If |id_storage| is not null, |*id_storage| will be set to the ID + // of the added binding. + ProxyType CreateInterfacePtrAndBind(ImplPointerType impl, + BindingId* id_storage = nullptr) { + ProxyType proxy; + BindingId id = AddBinding(std::move(impl), Traits::MakeRequest(&proxy)); + if (id_storage) + *id_storage = id; + return proxy; + } + + void CloseAllBindings() { bindings_.clear(); } + + bool empty() const { return bindings_.empty(); } + + // Implementations may call this when processing a dispatched message or + // error. During the extent of message or error dispatch, this will return the + // context associated with the specific binding which received the message or + // error. Use AddBinding() to associated a context with a specific binding. + const Context& dispatch_context() const { + static_assert(ContextTraits::SupportsContext(), + "dispatch_context() requires non-void context type."); + DCHECK(dispatch_context_); + return *dispatch_context_; + } + + void FlushForTesting() { + for (auto& binding : bindings_) + binding.second->FlushForTesting(); + } + + private: + friend class Entry; + + class Entry { + public: + Entry(ImplPointerType impl, + RequestType request, + BindingSetBase* binding_set, + BindingId binding_id, + Context context) + : binding_(std::move(impl), std::move(request)), + binding_set_(binding_set), + binding_id_(binding_id), + context_(std::move(context)) { + if (ContextTraits::SupportsContext()) + binding_.AddFilter(base::MakeUnique<DispatchFilter>(this)); + binding_.set_connection_error_with_reason_handler( + base::Bind(&Entry::OnConnectionError, base::Unretained(this))); + } + + void FlushForTesting() { binding_.FlushForTesting(); } + + private: + class DispatchFilter : public MessageReceiver { + public: + explicit DispatchFilter(Entry* entry) : entry_(entry) {} + ~DispatchFilter() override {} + + private: + // MessageReceiver: + bool Accept(Message* message) override { + entry_->WillDispatch(); + return true; + } + + Entry* entry_; + + DISALLOW_COPY_AND_ASSIGN(DispatchFilter); + }; + + void WillDispatch() { + DCHECK(ContextTraits::SupportsContext()); + binding_set_->SetDispatchContext(&context_); + } + + void OnConnectionError(uint32_t custom_reason, + const std::string& description) { + if (ContextTraits::SupportsContext()) + WillDispatch(); + binding_set_->OnConnectionError(binding_id_, custom_reason, description); + } + + BindingType binding_; + BindingSetBase* const binding_set_; + const BindingId binding_id_; + Context const context_; + + DISALLOW_COPY_AND_ASSIGN(Entry); + }; + + void SetDispatchContext(const Context* context) { + DCHECK(ContextTraits::SupportsContext()); + dispatch_context_ = context; + if (!pre_dispatch_handler_.is_null()) + pre_dispatch_handler_.Run(*context); + } + + BindingId AddBindingImpl(ImplPointerType impl, + RequestType request, + Context context) { + BindingId id = next_binding_id_++; + DCHECK_GE(next_binding_id_, 0u); + auto entry = base::MakeUnique<Entry>(std::move(impl), std::move(request), + this, id, std::move(context)); + bindings_.insert(std::make_pair(id, std::move(entry))); + return id; + } + + void OnConnectionError(BindingId id, + uint32_t custom_reason, + const std::string& description) { + auto it = bindings_.find(id); + DCHECK(it != bindings_.end()); + + // We keep the Entry alive throughout error dispatch. + std::unique_ptr<Entry> entry = std::move(it->second); + bindings_.erase(it); + + if (!error_handler_.is_null()) + error_handler_.Run(); + else if (!error_with_reason_handler_.is_null()) + error_with_reason_handler_.Run(custom_reason, description); + } + + base::Closure error_handler_; + ConnectionErrorWithReasonCallback error_with_reason_handler_; + PreDispatchCallback pre_dispatch_handler_; + BindingId next_binding_id_ = 0; + std::map<BindingId, std::unique_ptr<Entry>> bindings_; + const Context* dispatch_context_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(BindingSetBase); +}; + +template <typename Interface, typename ContextType = void> +using BindingSet = BindingSetBase<Interface, Binding<Interface>, ContextType>; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDING_SET_H_ diff --git a/mojo/public/cpp/bindings/bindings_export.h b/mojo/public/cpp/bindings/bindings_export.h new file mode 100644 index 0000000000..9fd7a2784e --- /dev/null +++ b/mojo/public/cpp/bindings/bindings_export.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_CPP_BINDINGS_IMPLEMENTATION) +#define MOJO_CPP_BINDINGS_EXPORT __declspec(dllexport) +#else +#define MOJO_CPP_BINDINGS_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_CPP_BINDINGS_IMPLEMENTATION) +#define MOJO_CPP_BINDINGS_EXPORT __attribute((visibility("default"))) +#else +#define MOJO_CPP_BINDINGS_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) + +#define MOJO_CPP_BINDINGS_EXPORT + +#endif // defined(COMPONENT_BUILD) + +#endif // MOJO_PUBLIC_CPP_BINDINGS_BINDINGS_EXPORT_H_ diff --git a/mojo/public/cpp/bindings/clone_traits.h b/mojo/public/cpp/bindings/clone_traits.h new file mode 100644 index 0000000000..203ab34189 --- /dev/null +++ b/mojo/public/cpp/bindings/clone_traits.h @@ -0,0 +1,86 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_ + +#include <type_traits> +#include <unordered_map> +#include <vector> + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { + +template <typename T> +struct HasCloneMethod { + template <typename U> + static char Test(decltype(&U::Clone)); + template <typename U> + static int Test(...); + static const bool value = sizeof(Test<T>(0)) == sizeof(char); + + private: + internal::EnsureTypeIsComplete<T> check_t_; +}; + +template <typename T, bool has_clone_method = HasCloneMethod<T>::value> +struct CloneTraits; + +template <typename T> +T Clone(const T& input); + +template <typename T> +struct CloneTraits<T, true> { + static T Clone(const T& input) { return input.Clone(); } +}; + +template <typename T> +struct CloneTraits<T, false> { + static T Clone(const T& input) { return input; } +}; + +template <typename T> +struct CloneTraits<base::Optional<T>, false> { + static base::Optional<T> Clone(const base::Optional<T>& input) { + if (!input) + return base::nullopt; + + return base::Optional<T>(mojo::Clone(*input)); + } +}; + +template <typename T> +struct CloneTraits<std::vector<T>, false> { + static std::vector<T> Clone(const std::vector<T>& input) { + std::vector<T> result; + result.reserve(input.size()); + for (const auto& element : input) + result.push_back(mojo::Clone(element)); + + return result; + } +}; + +template <typename K, typename V> +struct CloneTraits<std::unordered_map<K, V>, false> { + static std::unordered_map<K, V> Clone(const std::unordered_map<K, V>& input) { + std::unordered_map<K, V> result; + for (const auto& element : input) { + result.insert(std::make_pair(mojo::Clone(element.first), + mojo::Clone(element.second))); + } + return result; + } +}; + +template <typename T> +T Clone(const T& input) { + return CloneTraits<T>::Clone(input); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CLONE_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/connection_error_callback.h b/mojo/public/cpp/bindings/connection_error_callback.h new file mode 100644 index 0000000000..306e99e45b --- /dev/null +++ b/mojo/public/cpp/bindings/connection_error_callback.h @@ -0,0 +1,21 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_ + +#include "base/callback.h" + +namespace mojo { + +// This callback type accepts user-defined disconnect reason and description. If +// the other side specifies a reason on closing the connection, it will be +// passed to the error handler. +using ConnectionErrorWithReasonCallback = + base::Callback<void(uint32_t /* custom_reason */, + const std::string& /* description */)>; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CONNECTION_ERROR_CALLBACK_H_ diff --git a/mojo/public/cpp/bindings/connector.h b/mojo/public/cpp/bindings/connector.h new file mode 100644 index 0000000000..cb065c174d --- /dev/null +++ b/mojo/public/cpp/bindings/connector.h @@ -0,0 +1,241 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_ + +#include <memory> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/sync_handle_watcher.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace base { +class Lock; +} + +namespace mojo { + +// The Connector class is responsible for performing read/write operations on a +// MessagePipe. It writes messages it receives through the MessageReceiver +// interface that it subclasses, and it forwards messages it reads through the +// MessageReceiver interface assigned as its incoming receiver. +// +// NOTE: +// - MessagePipe I/O is non-blocking. +// - Sending messages can be configured to be thread safe (please see comments +// of the constructor). Other than that, the object should only be accessed +// on the creating thread. +class MOJO_CPP_BINDINGS_EXPORT Connector + : NON_EXPORTED_BASE(public MessageReceiver) { + public: + enum ConnectorConfig { + // Connector::Accept() is only called from a single thread. + SINGLE_THREADED_SEND, + // Connector::Accept() is allowed to be called from multiple threads. + MULTI_THREADED_SEND + }; + + // The Connector takes ownership of |message_pipe|. + Connector(ScopedMessagePipeHandle message_pipe, + ConnectorConfig config, + scoped_refptr<base::SingleThreadTaskRunner> runner); + ~Connector() override; + + // Sets the receiver to handle messages read from the message pipe. The + // Connector will read messages from the pipe regardless of whether or not an + // incoming receiver has been set. + void set_incoming_receiver(MessageReceiver* receiver) { + DCHECK(thread_checker_.CalledOnValidThread()); + incoming_receiver_ = receiver; + } + + // Errors from incoming receivers will force the connector into an error + // state, where no more messages will be processed. This method is used + // during testing to prevent that from happening. + void set_enforce_errors_from_incoming_receiver(bool enforce) { + DCHECK(thread_checker_.CalledOnValidThread()); + enforce_errors_from_incoming_receiver_ = enforce; + } + + // Sets the error handler to receive notifications when an error is + // encountered while reading from the pipe or waiting to read from the pipe. + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + connection_error_handler_ = error_handler; + } + + // Returns true if an error was encountered while reading from the pipe or + // waiting to read from the pipe. + bool encountered_error() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return error_; + } + + // Closes the pipe. The connector is put into a quiescent state. + // + // Please note that this method shouldn't be called unless it results from an + // explicit request of the user of bindings (e.g., the user sets an + // InterfacePtr to null or closes a Binding). + void CloseMessagePipe(); + + // Releases the pipe. Connector is put into a quiescent state. + ScopedMessagePipeHandle PassMessagePipe(); + + // Enters the error state. The upper layer may do this for unrecoverable + // issues such as invalid messages are received. If a connection error handler + // has been set, it will be called asynchronously. + // + // It is a no-op if the connector is already in the error state or there isn't + // a bound message pipe. Otherwise, it closes the message pipe, which notifies + // the other end and also prevents potential danger (say, the caller raises + // an error because it believes the other end is malicious). In order to + // appear to the user that the connector still binds to a message pipe, it + // creates a new message pipe, closes one end and binds to the other. + void RaiseError(); + + // Is the connector bound to a MessagePipe handle? + bool is_valid() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return message_pipe_.is_valid(); + } + + // Waits for the next message on the pipe, blocking until one arrives, + // |deadline| elapses, or an error happens. Returns |true| if a message has + // been delivered, |false| otherwise. + bool WaitForIncomingMessage(MojoDeadline deadline); + + // See Binding for details of pause/resume. + void PauseIncomingMethodCallProcessing(); + void ResumeIncomingMethodCallProcessing(); + + // MessageReceiver implementation: + bool Accept(Message* message) override; + + MessagePipeHandle handle() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return message_pipe_.get(); + } + + // Allows |message_pipe_| to be watched while others perform sync handle + // watching on the same thread. Please see comments of + // SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread(). + void AllowWokenUpBySyncWatchOnSameThread(); + + // Watches |message_pipe_| (as well as other handles registered to be watched + // together) synchronously. + // This method: + // - returns true when |should_stop| is set to true; + // - return false when any error occurs, including |message_pipe_| being + // closed. + bool SyncWatch(const bool* should_stop); + + // Whether currently the control flow is inside the sync handle watcher + // callback. + // It always returns false after CloseMessagePipe()/PassMessagePipe(). + bool during_sync_handle_watcher_callback() const { + return sync_handle_watcher_callback_count_ > 0; + } + + base::SingleThreadTaskRunner* task_runner() const { + return task_runner_.get(); + } + + // Sets the tag used by the heap profiler. + // |tag| must be a const string literal. + void SetWatcherHeapProfilerTag(const char* tag); + + private: + class ActiveDispatchTracker; + class MessageLoopNestingObserver; + + // Callback of mojo::SimpleWatcher. + void OnWatcherHandleReady(MojoResult result); + // Callback of SyncHandleWatcher. + void OnSyncHandleWatcherHandleReady(MojoResult result); + void OnHandleReadyInternal(MojoResult result); + + void WaitToReadMore(); + + // Returns false if it is impossible to receive more messages in the future. + // |this| may have been destroyed in that case. + WARN_UNUSED_RESULT bool ReadSingleMessage(MojoResult* read_result); + + // |this| can be destroyed during message dispatch. + void ReadAllAvailableMessages(); + + // If |force_pipe_reset| is true, this method replaces the existing + // |message_pipe_| with a dummy message pipe handle (whose peer is closed). + // If |force_async_handler| is true, |connection_error_handler_| is called + // asynchronously. + void HandleError(bool force_pipe_reset, bool force_async_handler); + + // Cancels any calls made to |waiter_|. + void CancelWait(); + + void EnsureSyncWatcherExists(); + + base::Closure connection_error_handler_; + + ScopedMessagePipeHandle message_pipe_; + MessageReceiver* incoming_receiver_ = nullptr; + + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + std::unique_ptr<SimpleWatcher> handle_watcher_; + + bool error_ = false; + bool drop_writes_ = false; + bool enforce_errors_from_incoming_receiver_ = true; + + bool paused_ = false; + + // If sending messages is allowed from multiple threads, |lock_| is used to + // protect modifications to |message_pipe_| and |drop_writes_|. + base::Optional<base::Lock> lock_; + + std::unique_ptr<SyncHandleWatcher> sync_watcher_; + bool allow_woken_up_by_others_ = false; + // If non-zero, currently the control flow is inside the sync handle watcher + // callback. + size_t sync_handle_watcher_callback_count_ = 0; + + base::ThreadChecker thread_checker_; + + base::Lock connected_lock_; + bool connected_ = true; + + // The tag used to track heap allocations that originated from a Watcher + // notification. + const char* heap_profiler_tag_ = nullptr; + + // A cached pointer to the MessageLoopNestingObserver for the MessageLoop on + // which this Connector was created. + MessageLoopNestingObserver* const nesting_observer_; + + // |true| iff the Connector is currently dispatching a message. Used to detect + // nested dispatch operations. + bool is_dispatching_ = false; + + // Create a single weak ptr and use it everywhere, to avoid the malloc/free + // cost of creating a new weak ptr whenever it is needed. + // NOTE: This weak pointer is invalidated when the message pipe is closed or + // transferred (i.e., when |connected_| is set to false). + base::WeakPtr<Connector> weak_self_; + base::WeakPtrFactory<Connector> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(Connector); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_CONNECTOR_H_ diff --git a/mojo/public/cpp/bindings/disconnect_reason.h b/mojo/public/cpp/bindings/disconnect_reason.h new file mode 100644 index 0000000000..c04e8ada53 --- /dev/null +++ b/mojo/public/cpp/bindings/disconnect_reason.h @@ -0,0 +1,25 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_ + +#include <stdint.h> + +#include <string> + +namespace mojo { + +struct DisconnectReason { + public: + DisconnectReason(uint32_t in_custom_reason, const std::string& in_description) + : custom_reason(in_custom_reason), description(in_description) {} + + uint32_t custom_reason; + std::string description; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_DISCONNECT_REASON_H_ diff --git a/mojo/public/cpp/bindings/enum_traits.h b/mojo/public/cpp/bindings/enum_traits.h new file mode 100644 index 0000000000..2c528f3226 --- /dev/null +++ b/mojo/public/cpp/bindings/enum_traits.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_ + +namespace mojo { + +// This must be specialized for any type |T| to be serialized/deserialized as a +// mojom enum |MojomType|. Each specialization needs to implement: +// +// template <> +// struct EnumTraits<MojomType, T> { +// static MojomType ToMojom(T input); +// +// // Returning false results in deserialization failure and causes the +// // message pipe receiving it to be disconnected. +// static bool FromMojom(MojomType input, T* output); +// }; +// +template <typename MojomType, typename T> +struct EnumTraits; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_ENUM_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/filter_chain.h b/mojo/public/cpp/bindings/filter_chain.h new file mode 100644 index 0000000000..1262f39b80 --- /dev/null +++ b/mojo/public/cpp/bindings/filter_chain.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_ + +#include <utility> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +class MOJO_CPP_BINDINGS_EXPORT FilterChain + : NON_EXPORTED_BASE(public MessageReceiver) { + public: + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + explicit FilterChain(MessageReceiver* sink = nullptr); + + FilterChain(FilterChain&& other); + FilterChain& operator=(FilterChain&& other); + ~FilterChain() override; + + template <typename FilterType, typename... Args> + inline void Append(Args&&... args); + + void Append(std::unique_ptr<MessageReceiver> filter); + + // Doesn't take ownership of |sink|. Therefore |sink| has to stay alive while + // this object is alive. + void SetSink(MessageReceiver* sink); + + // MessageReceiver: + bool Accept(Message* message) override; + + private: + std::vector<std::unique_ptr<MessageReceiver>> filters_; + + MessageReceiver* sink_; + + DISALLOW_COPY_AND_ASSIGN(FilterChain); +}; + +template <typename FilterType, typename... Args> +inline void FilterChain::Append(Args&&... args) { + Append(base::MakeUnique<FilterType>(std::forward<Args>(args)...)); +} + +template <> +inline void FilterChain::Append<PassThroughFilter>() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_FILTER_CHAIN_H_ diff --git a/mojo/public/cpp/bindings/interface_data_view.h b/mojo/public/cpp/bindings/interface_data_view.h new file mode 100644 index 0000000000..ef1225431c --- /dev/null +++ b/mojo/public/cpp/bindings/interface_data_view.h @@ -0,0 +1,25 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_ + +namespace mojo { + +// They are used for type identification purpose only. +template <typename Interface> +class AssociatedInterfacePtrInfoDataView {}; + +template <typename Interface> +class AssociatedInterfaceRequestDataView {}; + +template <typename Interface> +class InterfacePtrDataView {}; + +template <typename Interface> +class InterfaceRequestDataView {}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_DATA_VIEW_H_ diff --git a/mojo/public/cpp/bindings/interface_endpoint_client.h b/mojo/public/cpp/bindings/interface_endpoint_client.h new file mode 100644 index 0000000000..b519fe92bb --- /dev/null +++ b/mojo/public/cpp/bindings/interface_endpoint_client.h @@ -0,0 +1,193 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_ + +#include <stdint.h> + +#include <map> +#include <memory> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/lib/control_message_handler.h" +#include "mojo/public/cpp/bindings/lib/control_message_proxy.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { + +class AssociatedGroup; +class InterfaceEndpointController; + +// InterfaceEndpointClient handles message sending and receiving of an interface +// endpoint, either the implementation side or the client side. +// It should only be accessed and destructed on the creating thread. +class MOJO_CPP_BINDINGS_EXPORT InterfaceEndpointClient + : NON_EXPORTED_BASE(public MessageReceiverWithResponder) { + public: + // |receiver| is okay to be null. If it is not null, it must outlive this + // object. + InterfaceEndpointClient(ScopedInterfaceEndpointHandle handle, + MessageReceiverWithResponderStatus* receiver, + std::unique_ptr<MessageReceiver> payload_validator, + bool expect_sync_requests, + scoped_refptr<base::SingleThreadTaskRunner> runner, + uint32_t interface_version); + ~InterfaceEndpointClient() override; + + // Sets the error handler to receive notifications when an error is + // encountered. + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + error_handler_ = error_handler; + error_with_reason_handler_.Reset(); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + error_with_reason_handler_ = error_handler; + error_handler_.Reset(); + } + + // Returns true if an error was encountered. + bool encountered_error() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return encountered_error_; + } + + // Returns true if this endpoint has any pending callbacks. + bool has_pending_responders() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return !async_responders_.empty() || !sync_responses_.empty(); + } + + AssociatedGroup* associated_group(); + + // Adds a MessageReceiver which can filter a message after validation but + // before dispatch. + void AddFilter(std::unique_ptr<MessageReceiver> filter); + + // After this call the object is in an invalid state and shouldn't be reused. + ScopedInterfaceEndpointHandle PassHandle(); + + // Raises an error on the underlying message pipe. It disconnects the pipe + // and notifies all interfaces running on this pipe. + void RaiseError(); + + void CloseWithReason(uint32_t custom_reason, const std::string& description); + + // MessageReceiverWithResponder implementation: + // They must only be called when the handle is not in pending association + // state. + bool Accept(Message* message) override; + bool AcceptWithResponder(Message* message, + std::unique_ptr<MessageReceiver> responder) override; + + // The following methods are called by the router. They must be called + // outside of the router's lock. + + // NOTE: |message| must have passed message header validation. + bool HandleIncomingMessage(Message* message); + void NotifyError(const base::Optional<DisconnectReason>& reason); + + // The following methods send interface control messages. + // They must only be called when the handle is not in pending association + // state. + void QueryVersion(const base::Callback<void(uint32_t)>& callback); + void RequireVersion(uint32_t version); + void FlushForTesting(); + + private: + // Maps from the id of a response to the MessageReceiver that handles the + // response. + using AsyncResponderMap = + std::map<uint64_t, std::unique_ptr<MessageReceiver>>; + + struct SyncResponseInfo { + public: + explicit SyncResponseInfo(bool* in_response_received); + ~SyncResponseInfo(); + + Message response; + + // Points to a stack-allocated variable. + bool* response_received; + + private: + DISALLOW_COPY_AND_ASSIGN(SyncResponseInfo); + }; + + using SyncResponseMap = std::map<uint64_t, std::unique_ptr<SyncResponseInfo>>; + + // Used as the sink for |payload_validator_| and forwards messages to + // HandleValidatedMessage(). + class HandleIncomingMessageThunk : public MessageReceiver { + public: + explicit HandleIncomingMessageThunk(InterfaceEndpointClient* owner); + ~HandleIncomingMessageThunk() override; + + // MessageReceiver implementation: + bool Accept(Message* message) override; + + private: + InterfaceEndpointClient* const owner_; + + DISALLOW_COPY_AND_ASSIGN(HandleIncomingMessageThunk); + }; + + void InitControllerIfNecessary(); + + void OnAssociationEvent( + ScopedInterfaceEndpointHandle::AssociationEvent event); + + bool HandleValidatedMessage(Message* message); + + const bool expect_sync_requests_ = false; + + ScopedInterfaceEndpointHandle handle_; + std::unique_ptr<AssociatedGroup> associated_group_; + InterfaceEndpointController* controller_ = nullptr; + + MessageReceiverWithResponderStatus* const incoming_receiver_ = nullptr; + HandleIncomingMessageThunk thunk_; + FilterChain filters_; + + AsyncResponderMap async_responders_; + SyncResponseMap sync_responses_; + + uint64_t next_request_id_ = 1; + + base::Closure error_handler_; + ConnectionErrorWithReasonCallback error_with_reason_handler_; + bool encountered_error_ = false; + + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + internal::ControlMessageProxy control_message_proxy_; + internal::ControlMessageHandler control_message_handler_; + + base::ThreadChecker thread_checker_; + + base::WeakPtrFactory<InterfaceEndpointClient> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(InterfaceEndpointClient); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CLIENT_H_ diff --git a/mojo/public/cpp/bindings/interface_endpoint_controller.h b/mojo/public/cpp/bindings/interface_endpoint_controller.h new file mode 100644 index 0000000000..8d99d4a45f --- /dev/null +++ b/mojo/public/cpp/bindings/interface_endpoint_controller.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_ + +namespace mojo { + +class Message; + +// A control interface exposed by AssociatedGroupController for interface +// endpoints. +class InterfaceEndpointController { + public: + virtual ~InterfaceEndpointController() {} + + virtual bool SendMessage(Message* message) = 0; + + // Allows the interface endpoint to watch for incoming sync messages while + // others perform sync handle watching on the same thread. Please see comments + // of SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread(). + virtual void AllowWokenUpBySyncWatchOnSameThread() = 0; + + // Watches the interface endpoint for incoming sync messages. (It also watches + // other other handles registered to be watched together.) + // This method: + // - returns true when |should_stop| is set to true; + // - return false otherwise, including + // MultiplexRouter::DetachEndpointClient() being called for the same + // interface endpoint. + virtual bool SyncWatch(const bool* should_stop) = 0; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ENDPOINT_CONTROLLER_H_ diff --git a/mojo/public/cpp/bindings/interface_id.h b/mojo/public/cpp/bindings/interface_id.h new file mode 100644 index 0000000000..53475d6f78 --- /dev/null +++ b/mojo/public/cpp/bindings/interface_id.h @@ -0,0 +1,35 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_ + +#include <stdint.h> + +namespace mojo { + +// The size of the type matters because it is directly used in messages. +using InterfaceId = uint32_t; + +// IDs of associated interface can be generated at both sides of the message +// pipe. In order to avoid collision, the highest bit is used as namespace bit: +// at the side where the client-side of the master interface lives, IDs are +// generated with the namespace bit set to 1; at the opposite side IDs are +// generated with the namespace bit set to 0. +const uint32_t kInterfaceIdNamespaceMask = 0x80000000; + +const InterfaceId kMasterInterfaceId = 0x00000000; +const InterfaceId kInvalidInterfaceId = 0xFFFFFFFF; + +inline bool IsMasterInterfaceId(InterfaceId id) { + return id == kMasterInterfaceId; +} + +inline bool IsValidInterfaceId(InterfaceId id) { + return id != kInvalidInterfaceId; +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_ID_H_ diff --git a/mojo/public/cpp/bindings/interface_ptr.h b/mojo/public/cpp/bindings/interface_ptr.h new file mode 100644 index 0000000000..e88be7436f --- /dev/null +++ b/mojo/public/cpp/bindings/interface_ptr.h @@ -0,0 +1,242 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ + +#include <stdint.h> + +#include <string> +#include <utility> + +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/lib/interface_ptr_state.h" + +namespace mojo { + +// A pointer to a local proxy of a remote Interface implementation. Uses a +// message pipe to communicate with the remote implementation, and automatically +// closes the pipe and deletes the proxy on destruction. The pointer must be +// bound to a message pipe before the interface methods can be called. +// +// This class is thread hostile, as is the local proxy it manages, while bound +// to a message pipe. All calls to this class or the proxy should be from the +// same thread that bound it. If you need to move the proxy to a different +// thread, extract the InterfacePtrInfo (containing just the message pipe and +// any version information) using PassInterface(), pass it to a different +// thread, and create and bind a new InterfacePtr from that thread. If an +// InterfacePtr is not bound to a message pipe, it may be bound or destroyed on +// any thread. +template <typename Interface> +class InterfacePtr { + public: + using InterfaceType = Interface; + using PtrInfoType = InterfacePtrInfo<Interface>; + + // Constructs an unbound InterfacePtr. + InterfacePtr() {} + InterfacePtr(decltype(nullptr)) {} + + // Takes over the binding of another InterfacePtr. + InterfacePtr(InterfacePtr&& other) { + internal_state_.Swap(&other.internal_state_); + } + + // Takes over the binding of another InterfacePtr, and closes any message pipe + // already bound to this pointer. + InterfacePtr& operator=(InterfacePtr&& other) { + reset(); + internal_state_.Swap(&other.internal_state_); + return *this; + } + + // Assigning nullptr to this class causes it to close the currently bound + // message pipe (if any) and returns the pointer to the unbound state. + InterfacePtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + // Closes the bound message pipe (if any) on destruction. + ~InterfacePtr() {} + + // Binds the InterfacePtr to a remote implementation of Interface. + // + // Calling with an invalid |info| (containing an invalid message pipe handle) + // has the same effect as reset(). In this case, the InterfacePtr is not + // considered as bound. + // + // |runner| must belong to the same thread. It will be used to dispatch all + // callbacks and connection error notification. It is useful when you attach + // multiple task runners to a single thread for the purposes of task + // scheduling. + void Bind(InterfacePtrInfo<Interface> info, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + reset(); + if (info.is_valid()) + internal_state_.Bind(std::move(info), std::move(runner)); + } + + // Returns whether or not this InterfacePtr is bound to a message pipe. + bool is_bound() const { return internal_state_.is_bound(); } + + // Returns a raw pointer to the local proxy. Caller does not take ownership. + // Note that the local proxy is thread hostile, as stated above. + Interface* get() const { return internal_state_.instance(); } + + // Functions like a pointer to Interface. Must already be bound. + Interface* operator->() const { return get(); } + Interface& operator*() const { return *get(); } + + // Returns the version number of the interface that the remote side supports. + uint32_t version() const { return internal_state_.version(); } + + // 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. + void QueryVersion(const base::Callback<void(uint32_t)>& callback) { + internal_state_.QueryVersion(callback); + } + + // If the remote side doesn't support the specified version, it will close its + // end of the message pipe asynchronously. This does nothing if it's already + // known that the remote side supports the specified version, i.e., if + // |version <= this->version()|. + // + // After calling RequireVersion() with a version not supported by the remote + // side, all subsequent calls to interface methods will be ignored. + void RequireVersion(uint32_t version) { + internal_state_.RequireVersion(version); + } + + // Sends a no-op message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting() { internal_state_.FlushForTesting(); } + + // Closes the bound message pipe (if any) and returns the pointer to the + // unbound state. + void reset() { + State doomed; + internal_state_.Swap(&doomed); + } + + // Similar to the method above, but also specifies a disconnect reason. + void ResetWithReason(uint32_t custom_reason, const std::string& description) { + if (internal_state_.is_bound()) + internal_state_.CloseWithReason(custom_reason, description); + reset(); + } + + // Whether there are any associated interfaces running on the pipe currently. + bool HasAssociatedInterfaces() const { + return internal_state_.HasAssociatedInterfaces(); + } + + // Indicates whether the message pipe has encountered an error. If true, + // method calls made on this interface will be dropped (and may already have + // been dropped). + bool encountered_error() const { return internal_state_.encountered_error(); } + + // Registers a handler to receive error notifications. The handler will be + // called from the thread that owns this InterfacePtr. + // + // This method may only be called after the InterfacePtr has been bound to a + // message pipe. + void set_connection_error_handler(const base::Closure& error_handler) { + internal_state_.set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + internal_state_.set_connection_error_with_reason_handler(error_handler); + } + + // Unbinds the InterfacePtr and returns the information which could be used + // to setup an InterfacePtr again. This method may be used to move the proxy + // to a different thread (see class comments for details). + // + // It is an error to call PassInterface() while: + // - there are pending responses; or + // TODO: fix this restriction, it's not always obvious when there is a + // pending response. + // - there are associated interfaces running. + // TODO(yzshen): For now, users need to make sure there is no one holding + // on to associated interface endpoint handles at both sides of the + // message pipe in order to call this method. We need a way to forcefully + // invalidate associated interface endpoint handles. + InterfacePtrInfo<Interface> PassInterface() { + CHECK(!HasAssociatedInterfaces()); + CHECK(!internal_state_.has_pending_callbacks()); + State state; + internal_state_.Swap(&state); + + return state.PassInterface(); + } + + bool Equals(const InterfacePtr& other) const { + if (this == &other) + return true; + + // Now that the two refer to different objects, they are equivalent if + // and only if they are both null. + return !(*this) && !other; + } + + // DO NOT USE. Exposed only for internal use and for testing. + internal::InterfacePtrState<Interface>* internal_state() { + return &internal_state_; + } + + // Allow InterfacePtr<> to be used in boolean expressions, but not + // implicitly convertible to a real bool (which is dangerous). + private: + // TODO(dcheng): Use an explicit conversion operator. + typedef internal::InterfacePtrState<Interface> InterfacePtr::*Testable; + + public: + operator Testable() const { + return internal_state_.is_bound() ? &InterfacePtr::internal_state_ + : nullptr; + } + + private: + // Forbid the == and != operators explicitly, otherwise InterfacePtr will be + // converted to Testable to do == or != comparison. + template <typename T> + bool operator==(const InterfacePtr<T>& other) const = delete; + template <typename T> + bool operator!=(const InterfacePtr<T>& other) const = delete; + + typedef internal::InterfacePtrState<Interface> State; + mutable State internal_state_; + + DISALLOW_COPY_AND_ASSIGN(InterfacePtr); +}; + +// If |info| is valid (containing a valid message pipe handle), returns an +// InterfacePtr bound to it. Otherwise, returns an unbound InterfacePtr. +template <typename Interface> +InterfacePtr<Interface> MakeProxy( + InterfacePtrInfo<Interface> info, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + InterfacePtr<Interface> ptr; + if (info.is_valid()) + ptr.Bind(std::move(info), std::move(runner)); + return std::move(ptr); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_H_ diff --git a/mojo/public/cpp/bindings/interface_ptr_info.h b/mojo/public/cpp/bindings/interface_ptr_info.h new file mode 100644 index 0000000000..0b2d8089c4 --- /dev/null +++ b/mojo/public/cpp/bindings/interface_ptr_info.h @@ -0,0 +1,63 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ + +#include <stdint.h> +#include <utility> + +#include "base/macros.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { + +// InterfacePtrInfo stores necessary information to communicate with a remote +// interface implementation, which could be used to construct an InterfacePtr. +template <typename Interface> +class InterfacePtrInfo { + public: + InterfacePtrInfo() : version_(0u) {} + + InterfacePtrInfo(ScopedMessagePipeHandle handle, uint32_t version) + : handle_(std::move(handle)), version_(version) {} + + InterfacePtrInfo(InterfacePtrInfo&& other) + : handle_(std::move(other.handle_)), version_(other.version_) { + other.version_ = 0u; + } + + ~InterfacePtrInfo() {} + + InterfacePtrInfo& operator=(InterfacePtrInfo&& other) { + if (this != &other) { + handle_ = std::move(other.handle_); + version_ = other.version_; + other.version_ = 0u; + } + + return *this; + } + + bool is_valid() const { return handle_.is_valid(); } + + ScopedMessagePipeHandle PassHandle() { return std::move(handle_); } + const ScopedMessagePipeHandle& handle() const { return handle_; } + void set_handle(ScopedMessagePipeHandle handle) { + handle_ = std::move(handle); + } + + uint32_t version() const { return version_; } + void set_version(uint32_t version) { version_ = version; } + + private: + ScopedMessagePipeHandle handle_; + uint32_t version_; + + DISALLOW_COPY_AND_ASSIGN(InterfacePtrInfo); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_INFO_H_ diff --git a/mojo/public/cpp/bindings/interface_ptr_set.h b/mojo/public/cpp/bindings/interface_ptr_set.h new file mode 100644 index 0000000000..09a268229d --- /dev/null +++ b/mojo/public/cpp/bindings/interface_ptr_set.h @@ -0,0 +1,107 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_ + +#include <utility> +#include <vector> + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" + +namespace mojo { +namespace internal { + +// TODO(blundell): This class should be rewritten to be structured +// similarly to BindingSet if possible, with PtrSet owning its +// Elements and those Elements calling back into PtrSet on connection +// error. +template <typename Interface, template <typename> class Ptr> +class PtrSet { + public: + PtrSet() {} + ~PtrSet() { CloseAll(); } + + void AddPtr(Ptr<Interface> ptr) { + auto weak_interface_ptr = new Element(std::move(ptr)); + ptrs_.push_back(weak_interface_ptr->GetWeakPtr()); + ClearNullPtrs(); + } + + template <typename FunctionType> + void ForAllPtrs(FunctionType function) { + for (const auto& it : ptrs_) { + if (it) + function(it->get()); + } + ClearNullPtrs(); + } + + void CloseAll() { + for (const auto& it : ptrs_) { + if (it) + it->Close(); + } + ptrs_.clear(); + } + + private: + class Element { + public: + explicit Element(Ptr<Interface> ptr) + : ptr_(std::move(ptr)), weak_ptr_factory_(this) { + ptr_.set_connection_error_handler(base::Bind(&DeleteElement, this)); + } + + ~Element() {} + + void Close() { + ptr_.reset(); + + // Resetting the interface ptr means that it won't call this object back + // on connection error anymore, so this object must delete itself now. + DeleteElement(this); + } + + Interface* get() { return ptr_.get(); } + + base::WeakPtr<Element> GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + static void DeleteElement(Element* element) { delete element; } + + Ptr<Interface> ptr_; + base::WeakPtrFactory<Element> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(Element); + }; + + void ClearNullPtrs() { + ptrs_.erase(std::remove_if(ptrs_.begin(), ptrs_.end(), + [](const base::WeakPtr<Element>& p) { + return p.get() == nullptr; + }), + ptrs_.end()); + } + + std::vector<base::WeakPtr<Element>> ptrs_; +}; + +} // namespace internal + +template <typename Interface> +using InterfacePtrSet = internal::PtrSet<Interface, InterfacePtr>; + +template <typename Interface> +using AssociatedInterfacePtrSet = + internal::PtrSet<Interface, AssociatedInterfacePtr>; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_PTR_SET_H_ diff --git a/mojo/public/cpp/bindings/interface_request.h b/mojo/public/cpp/bindings/interface_request.h new file mode 100644 index 0000000000..29d883615e --- /dev/null +++ b/mojo/public/cpp/bindings/interface_request.h @@ -0,0 +1,178 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ + +#include <string> +#include <utility> + +#include "base/macros.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { + +// Represents a request from a remote client for an implementation of Interface +// over a specified message pipe. The implementor of the interface should +// remove the message pipe by calling PassMessagePipe() and bind it to the +// implementation. If this is not done, the InterfaceRequest will automatically +// close the pipe on destruction. Can also represent the absence of a request +// if the client did not provide a message pipe. +template <typename Interface> +class InterfaceRequest { + public: + // Constructs an empty InterfaceRequest, representing that the client is not + // requesting an implementation of Interface. + InterfaceRequest() {} + InterfaceRequest(decltype(nullptr)) {} + + // Creates a new message pipe over which Interface is to be served, binding + // the specified InterfacePtr to one end of the message pipe and this + // InterfaceRequest to the other. For example usage, see comments on + // MakeRequest(InterfacePtr*) below. + explicit InterfaceRequest(InterfacePtr<Interface>* ptr, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + MessagePipe pipe; + ptr->Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u), + std::move(runner)); + Bind(std::move(pipe.handle1)); + } + + // Takes the message pipe from another InterfaceRequest. + InterfaceRequest(InterfaceRequest&& other) { + handle_ = std::move(other.handle_); + } + InterfaceRequest& operator=(InterfaceRequest&& other) { + handle_ = std::move(other.handle_); + return *this; + } + + // Assigning to nullptr resets the InterfaceRequest to an empty state, + // closing the message pipe currently bound to it (if any). + InterfaceRequest& operator=(decltype(nullptr)) { + handle_.reset(); + return *this; + } + + // Binds the request to a message pipe over which Interface is to be + // requested. If the request is already bound to a message pipe, the current + // message pipe will be closed. + void Bind(ScopedMessagePipeHandle handle) { handle_ = std::move(handle); } + + // Indicates whether the request currently contains a valid message pipe. + bool is_pending() const { return handle_.is_valid(); } + + // Removes the message pipe from the request and returns it. + ScopedMessagePipeHandle PassMessagePipe() { return std::move(handle_); } + + bool Equals(const InterfaceRequest& other) const { + if (this == &other) + return true; + + // Now that the two refer to different objects, they are equivalent if + // and only if they are both invalid. + return !is_pending() && !other.is_pending(); + } + + void ResetWithReason(uint32_t custom_reason, const std::string& description) { + if (!handle_.is_valid()) + return; + + Message message = + PipeControlMessageProxy::ConstructPeerEndpointClosedMessage( + kMasterInterfaceId, DisconnectReason(custom_reason, description)); + MojoResult result = WriteMessageNew( + handle_.get(), message.TakeMojoMessage(), MOJO_WRITE_MESSAGE_FLAG_NONE); + DCHECK_EQ(MOJO_RESULT_OK, result); + + handle_.reset(); + } + + private: + ScopedMessagePipeHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(InterfaceRequest); +}; + +// Makes an InterfaceRequest bound to the specified message pipe. If |handle| +// is empty or invalid, the resulting InterfaceRequest will represent the +// absence of a request. +template <typename Interface> +InterfaceRequest<Interface> MakeRequest(ScopedMessagePipeHandle handle) { + InterfaceRequest<Interface> request; + request.Bind(std::move(handle)); + return std::move(request); +} + +// Creates a new message pipe over which Interface is to be served. Binds the +// specified InterfacePtr to one end of the message pipe, and returns an +// InterfaceRequest bound to the other. The InterfacePtr should be passed to +// the client, and the InterfaceRequest should be passed to whatever will +// provide the implementation. The implementation should typically be bound to +// the InterfaceRequest using the Binding or StrongBinding classes. The client +// may begin to issue calls even before an implementation has been bound, since +// messages sent over the pipe will just queue up until they are consumed by +// the implementation. +// +// Example #1: Requesting a remote implementation of an interface. +// =============================================================== +// +// Given the following interface: +// +// interface Database { +// OpenTable(Table& table); +// } +// +// The client would have code similar to the following: +// +// DatabasePtr database = ...; // Connect to database. +// TablePtr table; +// database->OpenTable(MakeRequest(&table)); +// +// Upon return from MakeRequest, |table| is ready to have methods called on it. +// +// Example #2: Registering a local implementation with a remote service. +// ===================================================================== +// +// Given the following interface +// interface Collector { +// RegisterSource(Source source); +// } +// +// The client would have code similar to the following: +// +// CollectorPtr collector = ...; // Connect to Collector. +// SourcePtr source; +// InterfaceRequest<Source> source_request(&source); +// collector->RegisterSource(std::move(source)); +// CreateSource(std::move(source_request)); // Create implementation locally. +// +template <typename Interface> +InterfaceRequest<Interface> MakeRequest( + InterfacePtr<Interface>* ptr, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()) { + return InterfaceRequest<Interface>(ptr, runner); +} + +// Fuses an InterfaceRequest<T> endpoint with an InterfacePtrInfo<T> endpoint. +// Returns |true| on success or |false| on failure. +template <typename Interface> +bool FuseInterface(InterfaceRequest<Interface> request, + InterfacePtrInfo<Interface> proxy_info) { + MojoResult result = FuseMessagePipes(request.PassMessagePipe(), + proxy_info.PassHandle()); + return result == MOJO_RESULT_OK; +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_INTERFACE_REQUEST_H_ diff --git a/mojo/public/cpp/bindings/lib/array_internal.cc b/mojo/public/cpp/bindings/lib/array_internal.cc new file mode 100644 index 0000000000..dd24eac470 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_internal.cc @@ -0,0 +1,59 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/array_internal.h" + +#include <stddef.h> +#include <stdint.h> + +#include <sstream> + +namespace mojo { +namespace internal { + +std::string MakeMessageWithArrayIndex(const char* message, + size_t size, + size_t index) { + std::ostringstream stream; + stream << message << ": array size - " << size << "; index - " << index; + return stream.str(); +} + +std::string MakeMessageWithExpectedArraySize(const char* message, + size_t size, + size_t expected_size) { + std::ostringstream stream; + stream << message << ": array size - " << size << "; expected size - " + << expected_size; + return stream.str(); +} + +ArrayDataTraits<bool>::BitRef::~BitRef() { +} + +ArrayDataTraits<bool>::BitRef::BitRef(uint8_t* storage, uint8_t mask) + : storage_(storage), mask_(mask) { +} + +ArrayDataTraits<bool>::BitRef& ArrayDataTraits<bool>::BitRef::operator=( + bool value) { + if (value) { + *storage_ |= mask_; + } else { + *storage_ &= ~mask_; + } + return *this; +} + +ArrayDataTraits<bool>::BitRef& ArrayDataTraits<bool>::BitRef::operator=( + const BitRef& value) { + return (*this) = static_cast<bool>(value); +} + +ArrayDataTraits<bool>::BitRef::operator bool() const { + return (*storage_ & mask_) != 0; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/array_internal.h b/mojo/public/cpp/bindings/lib/array_internal.h new file mode 100644 index 0000000000..eecfcfbc28 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_internal.h @@ -0,0 +1,368 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <limits> +#include <new> + +#include "base/logging.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +namespace mojo { +namespace internal { + +template <typename K, typename V> +class Map_Data; + +MOJO_CPP_BINDINGS_EXPORT std::string +MakeMessageWithArrayIndex(const char* message, size_t size, size_t index); + +MOJO_CPP_BINDINGS_EXPORT std::string MakeMessageWithExpectedArraySize( + const char* message, + size_t size, + size_t expected_size); + +template <typename T> +struct ArrayDataTraits { + using StorageType = T; + using Ref = T&; + using ConstRef = const T&; + + static const uint32_t kMaxNumElements = + (std::numeric_limits<uint32_t>::max() - sizeof(ArrayHeader)) / + sizeof(StorageType); + + static uint32_t GetStorageSize(uint32_t num_elements) { + DCHECK(num_elements <= kMaxNumElements); + return sizeof(ArrayHeader) + sizeof(StorageType) * num_elements; + } + static Ref ToRef(StorageType* storage, size_t offset) { + return storage[offset]; + } + static ConstRef ToConstRef(const StorageType* storage, size_t offset) { + return storage[offset]; + } +}; + +// Specialization of Arrays for bools, optimized for space. It has the +// following differences from a generalized Array: +// * Each element takes up a single bit of memory. +// * Accessing a non-const single element uses a helper class |BitRef|, which +// emulates a reference to a bool. +template <> +struct ArrayDataTraits<bool> { + // Helper class to emulate a reference to a bool, used for direct element + // access. + class MOJO_CPP_BINDINGS_EXPORT BitRef { + public: + ~BitRef(); + BitRef& operator=(bool value); + BitRef& operator=(const BitRef& value); + operator bool() const; + + private: + friend struct ArrayDataTraits<bool>; + BitRef(uint8_t* storage, uint8_t mask); + BitRef(); + uint8_t* storage_; + uint8_t mask_; + }; + + // Because each element consumes only 1/8 byte. + static const uint32_t kMaxNumElements = std::numeric_limits<uint32_t>::max(); + + using StorageType = uint8_t; + using Ref = BitRef; + using ConstRef = bool; + + static uint32_t GetStorageSize(uint32_t num_elements) { + return sizeof(ArrayHeader) + ((num_elements + 7) / 8); + } + static BitRef ToRef(StorageType* storage, size_t offset) { + return BitRef(&storage[offset / 8], 1 << (offset % 8)); + } + static bool ToConstRef(const StorageType* storage, size_t offset) { + return (storage[offset / 8] & (1 << (offset % 8))) != 0; + } +}; + +// What follows is code to support the serialization/validation of +// Array_Data<T>. There are four interesting cases: arrays of primitives, +// arrays of handles/interfaces, arrays of objects and arrays of unions. +// Arrays of objects are represented as arrays of pointers to objects. Arrays +// of unions are inlined so they are not pointers, but comparing with primitives +// they require more work for serialization/validation. +// +// TODO(yzshen): Validation code should be organzied in a way similar to +// Serializer<>, or merged into it. It should be templatized with the mojo +// data view type instead of the data type, that way we can use MojomTypeTraits +// to determine the categories. + +template <typename T, bool is_union, bool is_handle_or_interface> +struct ArraySerializationHelper; + +template <typename T> +struct ArraySerializationHelper<T, false, false> { + using ElementType = typename ArrayDataTraits<T>::StorageType; + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + if (!validate_params->validate_enum_func) + return true; + + // Enum validation. + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->validate_enum_func(elements[i], validation_context)) + return false; + } + return true; + } +}; + +template <typename T> +struct ArraySerializationHelper<T, false, true> { + using ElementType = typename ArrayDataTraits<T>::StorageType; + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + DCHECK(!validate_params->element_validate_params) + << "Handle or interface type should not have array validate params"; + + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->element_is_nullable && + !IsHandleOrInterfaceValid(elements[i])) { + static const ValidationError kError = + std::is_same<T, Interface_Data>::value || + std::is_same<T, Handle_Data>::value + ? VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE + : VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID; + ReportValidationError( + validation_context, kError, + MakeMessageWithArrayIndex( + "invalid handle or interface ID in array expecting valid " + "handles or interface IDs", + header->num_elements, i) + .c_str()); + return false; + } + if (!ValidateHandleOrInterface(elements[i], validation_context)) + return false; + } + return true; + } +}; + +template <typename T> +struct ArraySerializationHelper<Pointer<T>, false, false> { + using ElementType = typename ArrayDataTraits<Pointer<T>>::StorageType; + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->element_is_nullable && !elements[i].offset) { + ReportValidationError( + validation_context, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid pointers", + header->num_elements, + i).c_str()); + return false; + } + if (!ValidateCaller<T>::Run(elements[i], validation_context, + validate_params->element_validate_params)) { + return false; + } + } + return true; + } + + private: + template <typename U, + bool is_array_or_map = IsSpecializationOf<Array_Data, U>::value || + IsSpecializationOf<Map_Data, U>::value> + struct ValidateCaller { + static bool Run(const Pointer<U>& data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + DCHECK(!validate_params) + << "Struct type should not have array validate params"; + + return ValidateStruct(data, validation_context); + } + }; + + template <typename U> + struct ValidateCaller<U, true> { + static bool Run(const Pointer<U>& data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + return ValidateContainer(data, validation_context, validate_params); + } + }; +}; + +template <typename U> +struct ArraySerializationHelper<U, true, false> { + using ElementType = typename ArrayDataTraits<U>::StorageType; + + static bool ValidateElements(const ArrayHeader* header, + const ElementType* elements, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + for (uint32_t i = 0; i < header->num_elements; ++i) { + if (!validate_params->element_is_nullable && elements[i].is_null()) { + ReportValidationError( + validation_context, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid unions", + header->num_elements, i) + .c_str()); + return false; + } + if (!ValidateInlinedUnion(elements[i], validation_context)) + return false; + } + return true; + } +}; + +template <typename T> +class Array_Data { + public: + using Traits = ArrayDataTraits<T>; + using StorageType = typename Traits::StorageType; + using Ref = typename Traits::Ref; + using ConstRef = typename Traits::ConstRef; + using Helper = ArraySerializationHelper< + T, + IsUnionDataType<T>::value, + std::is_same<T, AssociatedInterface_Data>::value || + std::is_same<T, AssociatedEndpointHandle_Data>::value || + std::is_same<T, Interface_Data>::value || + std::is_same<T, Handle_Data>::value>; + using Element = T; + + // Returns null if |num_elements| or the corresponding storage size cannot be + // stored in uint32_t. + static Array_Data<T>* New(size_t num_elements, Buffer* buf) { + if (num_elements > Traits::kMaxNumElements) + return nullptr; + + uint32_t num_bytes = + Traits::GetStorageSize(static_cast<uint32_t>(num_elements)); + return new (buf->Allocate(num_bytes)) + Array_Data<T>(num_bytes, static_cast<uint32_t>(num_elements)); + } + + static bool Validate(const void* data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + if (!data) + return true; + if (!IsAligned(data)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MISALIGNED_OBJECT); + return false; + } + if (!validation_context->IsValidRange(data, sizeof(ArrayHeader))) { + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + const ArrayHeader* header = static_cast<const ArrayHeader*>(data); + if (header->num_elements > Traits::kMaxNumElements || + header->num_bytes < Traits::GetStorageSize(header->num_elements)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER); + return false; + } + if (validate_params->expected_num_elements != 0 && + header->num_elements != validate_params->expected_num_elements) { + ReportValidationError( + validation_context, + VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + MakeMessageWithExpectedArraySize( + "fixed-size array has wrong number of elements", + header->num_elements, + validate_params->expected_num_elements).c_str()); + return false; + } + if (!validation_context->ClaimMemory(data, header->num_bytes)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + const Array_Data<T>* object = static_cast<const Array_Data<T>*>(data); + return Helper::ValidateElements(&object->header_, object->storage(), + validation_context, validate_params); + } + + size_t size() const { return header_.num_elements; } + + Ref at(size_t offset) { + DCHECK(offset < static_cast<size_t>(header_.num_elements)); + return Traits::ToRef(storage(), offset); + } + + ConstRef at(size_t offset) const { + DCHECK(offset < static_cast<size_t>(header_.num_elements)); + return Traits::ToConstRef(storage(), offset); + } + + StorageType* storage() { + return reinterpret_cast<StorageType*>(reinterpret_cast<char*>(this) + + sizeof(*this)); + } + + const StorageType* storage() const { + return reinterpret_cast<const StorageType*>( + reinterpret_cast<const char*>(this) + sizeof(*this)); + } + + private: + Array_Data(uint32_t num_bytes, uint32_t num_elements) { + header_.num_bytes = num_bytes; + header_.num_elements = num_elements; + } + ~Array_Data() = delete; + + internal::ArrayHeader header_; + + // Elements of type internal::ArrayDataTraits<T>::StorageType follow. +}; +static_assert(sizeof(Array_Data<char>) == 8, "Bad sizeof(Array_Data)"); + +// UTF-8 encoded +using String_Data = Array_Data<char>; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/array_serialization.h b/mojo/public/cpp/bindings/lib/array_serialization.h new file mode 100644 index 0000000000..d2f8ecfd72 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/array_serialization.h @@ -0,0 +1,555 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ + +#include <stddef.h> +#include <string.h> // For |memcpy()|. + +#include <limits> +#include <type_traits> +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/array_data_view.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +namespace mojo { +namespace internal { + +template <typename Traits, + typename MaybeConstUserType, + bool HasGetBegin = + HasGetBeginMethod<Traits, MaybeConstUserType>::value> +class ArrayIterator {}; + +// Used as the UserTypeIterator template parameter of ArraySerializer. +template <typename Traits, typename MaybeConstUserType> +class ArrayIterator<Traits, MaybeConstUserType, true> { + public: + using IteratorType = decltype( + CallGetBeginIfExists<Traits>(std::declval<MaybeConstUserType&>())); + + explicit ArrayIterator(MaybeConstUserType& input) + : input_(input), iter_(CallGetBeginIfExists<Traits>(input)) {} + ~ArrayIterator() {} + + size_t GetSize() const { return Traits::GetSize(input_); } + + using GetNextResult = + decltype(Traits::GetValue(std::declval<IteratorType&>())); + GetNextResult GetNext() { + GetNextResult value = Traits::GetValue(iter_); + Traits::AdvanceIterator(iter_); + return value; + } + + using GetDataIfExistsResult = decltype( + CallGetDataIfExists<Traits>(std::declval<MaybeConstUserType&>())); + GetDataIfExistsResult GetDataIfExists() { + return CallGetDataIfExists<Traits>(input_); + } + + private: + MaybeConstUserType& input_; + IteratorType iter_; +}; + +// Used as the UserTypeIterator template parameter of ArraySerializer. +template <typename Traits, typename MaybeConstUserType> +class ArrayIterator<Traits, MaybeConstUserType, false> { + public: + explicit ArrayIterator(MaybeConstUserType& input) : input_(input), iter_(0) {} + ~ArrayIterator() {} + + size_t GetSize() const { return Traits::GetSize(input_); } + + using GetNextResult = + decltype(Traits::GetAt(std::declval<MaybeConstUserType&>(), 0)); + GetNextResult GetNext() { + DCHECK_LT(iter_, Traits::GetSize(input_)); + return Traits::GetAt(input_, iter_++); + } + + using GetDataIfExistsResult = decltype( + CallGetDataIfExists<Traits>(std::declval<MaybeConstUserType&>())); + GetDataIfExistsResult GetDataIfExists() { + return CallGetDataIfExists<Traits>(input_); + } + + private: + MaybeConstUserType& input_; + size_t iter_; +}; + +// ArraySerializer is also used to serialize map keys and values. Therefore, it +// has a UserTypeIterator parameter which is an adaptor for reading to hide the +// difference between ArrayTraits and MapTraits. +template <typename MojomType, + typename MaybeConstUserType, + typename UserTypeIterator, + typename EnableType = void> +struct ArraySerializer; + +// Handles serialization and deserialization of arrays of pod types. +template <typename MojomType, + typename MaybeConstUserType, + typename UserTypeIterator> +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if<BelongsTo<typename MojomType::Element, + MojomTypeCategory::POD>::value>::type> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Data = typename MojomTypeTraits<MojomType>::Data; + using DataElement = typename Data::Element; + using Element = typename MojomType::Element; + using Traits = ArrayTraits<UserType>; + + static_assert(std::is_same<Element, DataElement>::value, + "Incorrect array serializer"); + static_assert(std::is_same<Element, typename Traits::Element>::value, + "Incorrect array serializer"); + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + return sizeof(Data) + Align(input->GetSize() * sizeof(DataElement)); + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + size_t size = input->GetSize(); + if (size == 0) + return; + + auto data = input->GetDataIfExists(); + if (data) { + memcpy(output->storage(), data, size * sizeof(DataElement)); + } else { + for (size_t i = 0; i < size; ++i) + output->at(i) = input->GetNext(); + } + } + + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator<Traits, UserType> iterator(*output); + if (input->size()) { + auto data = iterator.GetDataIfExists(); + if (data) { + memcpy(data, input->storage(), input->size() * sizeof(DataElement)); + } else { + for (size_t i = 0; i < input->size(); ++i) + iterator.GetNext() = input->at(i); + } + } + return true; + } +}; + +// Handles serialization and deserialization of arrays of enum types. +template <typename MojomType, + typename MaybeConstUserType, + typename UserTypeIterator> +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if<BelongsTo<typename MojomType::Element, + MojomTypeCategory::ENUM>::value>::type> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Data = typename MojomTypeTraits<MojomType>::Data; + using DataElement = typename Data::Element; + using Element = typename MojomType::Element; + using Traits = ArrayTraits<UserType>; + + static_assert(sizeof(Element) == sizeof(DataElement), + "Incorrect array serializer"); + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + return sizeof(Data) + Align(input->GetSize() * sizeof(DataElement)); + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) + Serialize<Element>(input->GetNext(), output->storage() + i); + } + + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator<Traits, UserType> iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize<Element>(input->at(i), &iterator.GetNext())) + return false; + } + return true; + } +}; + +// Serializes and deserializes arrays of bools. +template <typename MojomType, + typename MaybeConstUserType, + typename UserTypeIterator> +struct ArraySerializer<MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if<BelongsTo< + typename MojomType::Element, + MojomTypeCategory::BOOLEAN>::value>::type> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = ArrayTraits<UserType>; + using Data = typename MojomTypeTraits<MojomType>::Data; + + static_assert(std::is_same<bool, typename Traits::Element>::value, + "Incorrect array serializer"); + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + return sizeof(Data) + Align((input->GetSize() + 7) / 8); + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(!validate_params->element_is_nullable) + << "Primitive type should be non-nullable"; + DCHECK(!validate_params->element_validate_params) + << "Primitive type should not have array validate params"; + + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) + output->at(i) = input->GetNext(); + } + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator<Traits, UserType> iterator(*output); + for (size_t i = 0; i < input->size(); ++i) + iterator.GetNext() = input->at(i); + return true; + } +}; + +// Serializes and deserializes arrays of handles or interfaces. +template <typename MojomType, + typename MaybeConstUserType, + typename UserTypeIterator> +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if< + BelongsTo<typename MojomType::Element, + MojomTypeCategory::ASSOCIATED_INTERFACE | + MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST | + MojomTypeCategory::HANDLE | + MojomTypeCategory::INTERFACE | + MojomTypeCategory::INTERFACE_REQUEST>::value>::type> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Data = typename MojomTypeTraits<MojomType>::Data; + using Element = typename MojomType::Element; + using Traits = ArrayTraits<UserType>; + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + size_t element_count = input->GetSize(); + if (BelongsTo<Element, + MojomTypeCategory::ASSOCIATED_INTERFACE | + MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST>::value) { + for (size_t i = 0; i < element_count; ++i) { + typename UserTypeIterator::GetNextResult next = input->GetNext(); + size_t size = PrepareToSerialize<Element>(next, context); + DCHECK_EQ(size, 0u); + } + } + return sizeof(Data) + Align(element_count * sizeof(typename Data::Element)); + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(!validate_params->element_validate_params) + << "Handle or interface type should not have array validate params"; + + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) { + typename UserTypeIterator::GetNextResult next = input->GetNext(); + Serialize<Element>(next, &output->at(i), context); + + static const ValidationError kError = + BelongsTo<Element, + MojomTypeCategory::ASSOCIATED_INTERFACE | + MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST>::value + ? VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID + : VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE; + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && + !IsHandleOrInterfaceValid(output->at(i)), + kError, + MakeMessageWithArrayIndex("invalid handle or interface ID in array " + "expecting valid handles or interface IDs", + size, i)); + } + } + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator<Traits, UserType> iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + bool result = + Deserialize<Element>(&input->at(i), &iterator.GetNext(), context); + DCHECK(result); + } + return true; + } +}; + +// This template must only apply to pointer mojo entity (strings, structs, +// arrays and maps). +template <typename MojomType, + typename MaybeConstUserType, + typename UserTypeIterator> +struct ArraySerializer<MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if<BelongsTo< + typename MojomType::Element, + MojomTypeCategory::ARRAY | MojomTypeCategory::MAP | + MojomTypeCategory::STRING | + MojomTypeCategory::STRUCT>::value>::type> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Data = typename MojomTypeTraits<MojomType>::Data; + using Element = typename MojomType::Element; + using DataElementPtr = typename MojomTypeTraits<Element>::Data*; + using Traits = ArrayTraits<UserType>; + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + size_t element_count = input->GetSize(); + size_t size = sizeof(Data) + element_count * sizeof(typename Data::Element); + for (size_t i = 0; i < element_count; ++i) { + typename UserTypeIterator::GetNextResult next = input->GetNext(); + size += PrepareToSerialize<Element>(next, context); + } + return size; + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) { + DataElementPtr data_ptr; + typename UserTypeIterator::GetNextResult next = input->GetNext(); + SerializeCaller<Element>::Run(next, buf, &data_ptr, + validate_params->element_validate_params, + context); + output->at(i).Set(data_ptr); + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && !data_ptr, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid pointers", + size, i)); + } + } + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator<Traits, UserType> iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize<Element>(input->at(i).Get(), &iterator.GetNext(), + context)) + return false; + } + return true; + } + + private: + template <typename T, + bool is_array_or_map = BelongsTo<T, + MojomTypeCategory::ARRAY | + MojomTypeCategory::MAP>::value> + struct SerializeCaller { + template <typename InputElementType> + static void Run(InputElementType&& input, + Buffer* buf, + DataElementPtr* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + Serialize<T>(std::forward<InputElementType>(input), buf, output, context); + } + }; + + template <typename T> + struct SerializeCaller<T, true> { + template <typename InputElementType> + static void Run(InputElementType&& input, + Buffer* buf, + DataElementPtr* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + Serialize<T>(std::forward<InputElementType>(input), buf, output, + validate_params, context); + } + }; +}; + +// Handles serialization and deserialization of arrays of unions. +template <typename MojomType, + typename MaybeConstUserType, + typename UserTypeIterator> +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if<BelongsTo<typename MojomType::Element, + MojomTypeCategory::UNION>::value>::type> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Data = typename MojomTypeTraits<MojomType>::Data; + using Element = typename MojomType::Element; + using Traits = ArrayTraits<UserType>; + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + size_t element_count = input->GetSize(); + size_t size = sizeof(Data); + for (size_t i = 0; i < element_count; ++i) { + // Call with |inlined| set to false, so that it will account for both the + // data in the union and the space in the array used to hold the union. + typename UserTypeIterator::GetNextResult next = input->GetNext(); + size += PrepareToSerialize<Element>(next, false, context); + } + return size; + } + + static void SerializeElements(UserTypeIterator* input, + Buffer* buf, + Data* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + size_t size = input->GetSize(); + for (size_t i = 0; i < size; ++i) { + typename Data::Element* result = output->storage() + i; + typename UserTypeIterator::GetNextResult next = input->GetNext(); + Serialize<Element>(next, buf, &result, true, context); + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !validate_params->element_is_nullable && output->at(i).is_null(), + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + MakeMessageWithArrayIndex("null in array expecting valid unions", + size, i)); + } + } + + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator<Traits, UserType> iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize<Element>(&input->at(i), &iterator.GetNext(), context)) + return false; + } + return true; + } +}; + +template <typename Element, typename MaybeConstUserType> +struct Serializer<ArrayDataView<Element>, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = ArrayTraits<UserType>; + using Impl = ArraySerializer<ArrayDataView<Element>, + MaybeConstUserType, + ArrayIterator<Traits, MaybeConstUserType>>; + using Data = typename MojomTypeTraits<ArrayDataView<Element>>::Data; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) + return 0; + ArrayIterator<Traits, MaybeConstUserType> iterator(input); + return Impl::GetSerializedSize(&iterator, context); + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buf, + Data** output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + if (!CallIsNullIfExists<Traits>(input)) { + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + validate_params->expected_num_elements != 0 && + Traits::GetSize(input) != validate_params->expected_num_elements, + internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + internal::MakeMessageWithExpectedArraySize( + "fixed-size array has wrong number of elements", + Traits::GetSize(input), validate_params->expected_num_elements)); + Data* result = Data::New(Traits::GetSize(input), buf); + if (result) { + ArrayIterator<Traits, MaybeConstUserType> iterator(input); + Impl::SerializeElements(&iterator, buf, result, validate_params, + context); + } + *output = result; + } else { + *output = nullptr; + } + } + + static bool Deserialize(Data* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists<Traits>(output); + return Impl::DeserializeElements(input, output, context); + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ARRAY_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/associated_binding.cc b/mojo/public/cpp/bindings/lib/associated_binding.cc new file mode 100644 index 0000000000..6788e68e07 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_binding.cc @@ -0,0 +1,62 @@ +// 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. + +#include "mojo/public/cpp/bindings/associated_binding.h" + +namespace mojo { + +AssociatedBindingBase::AssociatedBindingBase() {} + +AssociatedBindingBase::~AssociatedBindingBase() {} + +void AssociatedBindingBase::AddFilter(std::unique_ptr<MessageReceiver> filter) { + DCHECK(endpoint_client_); + endpoint_client_->AddFilter(std::move(filter)); +} + +void AssociatedBindingBase::Close() { + endpoint_client_.reset(); +} + +void AssociatedBindingBase::CloseWithReason(uint32_t custom_reason, + const std::string& description) { + if (endpoint_client_) + endpoint_client_->CloseWithReason(custom_reason, description); + Close(); +} + +void AssociatedBindingBase::set_connection_error_handler( + const base::Closure& error_handler) { + DCHECK(is_bound()); + endpoint_client_->set_connection_error_handler(error_handler); +} + +void AssociatedBindingBase::set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(is_bound()); + endpoint_client_->set_connection_error_with_reason_handler(error_handler); +} + +void AssociatedBindingBase::FlushForTesting() { + endpoint_client_->FlushForTesting(); +} + +void AssociatedBindingBase::BindImpl( + ScopedInterfaceEndpointHandle handle, + MessageReceiverWithResponderStatus* receiver, + std::unique_ptr<MessageReceiver> payload_validator, + bool expect_sync_requests, + scoped_refptr<base::SingleThreadTaskRunner> runner, + uint32_t interface_version) { + if (!handle.is_valid()) { + endpoint_client_.reset(); + return; + } + + endpoint_client_.reset(new InterfaceEndpointClient( + std::move(handle), receiver, std::move(payload_validator), + expect_sync_requests, std::move(runner), interface_version)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_group.cc b/mojo/public/cpp/bindings/lib/associated_group.cc new file mode 100644 index 0000000000..3e95eeb027 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_group.cc @@ -0,0 +1,34 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/associated_group.h" + +#include "mojo/public/cpp/bindings/associated_group_controller.h" + +namespace mojo { + +AssociatedGroup::AssociatedGroup() = default; + +AssociatedGroup::AssociatedGroup( + scoped_refptr<AssociatedGroupController> controller) + : controller_(std::move(controller)) {} + +AssociatedGroup::AssociatedGroup(const ScopedInterfaceEndpointHandle& handle) + : controller_getter_(handle.CreateGroupControllerGetter()) {} + +AssociatedGroup::AssociatedGroup(const AssociatedGroup& other) = default; + +AssociatedGroup::~AssociatedGroup() = default; + +AssociatedGroup& AssociatedGroup::operator=(const AssociatedGroup& other) = + default; + +AssociatedGroupController* AssociatedGroup::GetController() { + if (controller_) + return controller_.get(); + + return controller_getter_.Run(); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_group_controller.cc b/mojo/public/cpp/bindings/lib/associated_group_controller.cc new file mode 100644 index 0000000000..f4a9aa2852 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_group_controller.cc @@ -0,0 +1,24 @@ +// 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. + +#include "mojo/public/cpp/bindings/associated_group_controller.h" + +#include "mojo/public/cpp/bindings/associated_group.h" + +namespace mojo { + +AssociatedGroupController::~AssociatedGroupController() {} + +ScopedInterfaceEndpointHandle +AssociatedGroupController::CreateScopedInterfaceEndpointHandle(InterfaceId id) { + return ScopedInterfaceEndpointHandle(id, this); +} + +bool AssociatedGroupController::NotifyAssociation( + ScopedInterfaceEndpointHandle* handle_to_send, + InterfaceId id) { + return handle_to_send->NotifyAssociation(id, this); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc new file mode 100644 index 0000000000..78281eda9a --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr.cc @@ -0,0 +1,18 @@ +// 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. + +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" + +namespace mojo { + +void GetIsolatedInterface(ScopedInterfaceEndpointHandle handle) { + MessagePipe pipe; + scoped_refptr<internal::MultiplexRouter> router = + new internal::MultiplexRouter(std::move(pipe.handle0), + internal::MultiplexRouter::MULTI_INTERFACE, + false, base::ThreadTaskRunnerHandle::Get()); + router->AssociateInterface(std::move(handle)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h new file mode 100644 index 0000000000..a4b51882d2 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h @@ -0,0 +1,157 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_ + +#include <stdint.h> + +#include <algorithm> // For |std::swap()|. +#include <memory> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/bindings/associated_group.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { +namespace internal { + +template <typename Interface> +class AssociatedInterfacePtrState { + public: + AssociatedInterfacePtrState() : version_(0u) {} + + ~AssociatedInterfacePtrState() { + endpoint_client_.reset(); + proxy_.reset(); + } + + Interface* instance() { + // This will be null if the object is not bound. + return proxy_.get(); + } + + uint32_t version() const { return version_; } + + void QueryVersion(const base::Callback<void(uint32_t)>& callback) { + // It is safe to capture |this| because the callback won't be run after this + // object goes away. + endpoint_client_->QueryVersion( + base::Bind(&AssociatedInterfacePtrState::OnQueryVersion, + base::Unretained(this), callback)); + } + + void RequireVersion(uint32_t version) { + if (version <= version_) + return; + + version_ = version; + endpoint_client_->RequireVersion(version); + } + + void FlushForTesting() { endpoint_client_->FlushForTesting(); } + + void CloseWithReason(uint32_t custom_reason, const std::string& description) { + endpoint_client_->CloseWithReason(custom_reason, description); + } + + void Swap(AssociatedInterfacePtrState* other) { + using std::swap; + swap(other->endpoint_client_, endpoint_client_); + swap(other->proxy_, proxy_); + swap(other->version_, version_); + } + + void Bind(AssociatedInterfacePtrInfo<Interface> info, + scoped_refptr<base::SingleThreadTaskRunner> runner) { + DCHECK(!endpoint_client_); + DCHECK(!proxy_); + DCHECK_EQ(0u, version_); + DCHECK(info.is_valid()); + + version_ = info.version(); + // The version is only queried from the client so the value passed here + // will not be used. + endpoint_client_.reset(new InterfaceEndpointClient( + info.PassHandle(), nullptr, + base::WrapUnique(new typename Interface::ResponseValidator_()), false, + std::move(runner), 0u)); + proxy_.reset(new Proxy(endpoint_client_.get())); + } + + // After this method is called, the object is in an invalid state and + // shouldn't be reused. + AssociatedInterfacePtrInfo<Interface> PassInterface() { + ScopedInterfaceEndpointHandle handle = endpoint_client_->PassHandle(); + endpoint_client_.reset(); + proxy_.reset(); + return AssociatedInterfacePtrInfo<Interface>(std::move(handle), version_); + } + + bool is_bound() const { return !!endpoint_client_; } + + bool encountered_error() const { + return endpoint_client_ ? endpoint_client_->encountered_error() : false; + } + + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(endpoint_client_); + endpoint_client_->set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(endpoint_client_); + endpoint_client_->set_connection_error_with_reason_handler(error_handler); + } + + // Returns true if bound and awaiting a response to a message. + bool has_pending_callbacks() const { + return endpoint_client_ && endpoint_client_->has_pending_responders(); + } + + AssociatedGroup* associated_group() { + return endpoint_client_ ? endpoint_client_->associated_group() : nullptr; + } + + void ForwardMessage(Message message) { endpoint_client_->Accept(&message); } + + void ForwardMessageWithResponder(Message message, + std::unique_ptr<MessageReceiver> responder) { + endpoint_client_->AcceptWithResponder(&message, std::move(responder)); + } + + private: + using Proxy = typename Interface::Proxy_; + + void OnQueryVersion(const base::Callback<void(uint32_t)>& callback, + uint32_t version) { + version_ = version; + callback.Run(version); + } + + std::unique_ptr<InterfaceEndpointClient> endpoint_client_; + std::unique_ptr<Proxy> proxy_; + + uint32_t version_; + + DISALLOW_COPY_AND_ASSIGN(AssociatedInterfacePtrState); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_ASSOCIATED_INTERFACE_PTR_STATE_H_ diff --git a/mojo/public/cpp/bindings/lib/binding_state.cc b/mojo/public/cpp/bindings/lib/binding_state.cc new file mode 100644 index 0000000000..b34cb47e28 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/binding_state.cc @@ -0,0 +1,90 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/binding_state.h" + +namespace mojo { +namespace internal { + +BindingStateBase::BindingStateBase() = default; + +BindingStateBase::~BindingStateBase() = default; + +void BindingStateBase::AddFilter(std::unique_ptr<MessageReceiver> filter) { + DCHECK(endpoint_client_); + endpoint_client_->AddFilter(std::move(filter)); +} + +bool BindingStateBase::HasAssociatedInterfaces() const { + return router_ ? router_->HasAssociatedEndpoints() : false; +} + +void BindingStateBase::PauseIncomingMethodCallProcessing() { + DCHECK(router_); + router_->PauseIncomingMethodCallProcessing(); +} +void BindingStateBase::ResumeIncomingMethodCallProcessing() { + DCHECK(router_); + router_->ResumeIncomingMethodCallProcessing(); +} + +bool BindingStateBase::WaitForIncomingMethodCall(MojoDeadline deadline) { + DCHECK(router_); + return router_->WaitForIncomingMessage(deadline); +} + +void BindingStateBase::Close() { + if (!router_) + return; + + endpoint_client_.reset(); + router_->CloseMessagePipe(); + router_ = nullptr; +} + +void BindingStateBase::CloseWithReason(uint32_t custom_reason, + const std::string& description) { + if (endpoint_client_) + endpoint_client_->CloseWithReason(custom_reason, description); + + Close(); +} + +void BindingStateBase::FlushForTesting() { + endpoint_client_->FlushForTesting(); +} + +void BindingStateBase::EnableTestingMode() { + DCHECK(is_bound()); + router_->EnableTestingMode(); +} + +void BindingStateBase::BindInternal( + ScopedMessagePipeHandle handle, + scoped_refptr<base::SingleThreadTaskRunner> runner, + const char* interface_name, + std::unique_ptr<MessageReceiver> request_validator, + bool passes_associated_kinds, + bool has_sync_methods, + MessageReceiverWithResponderStatus* stub, + uint32_t interface_version) { + DCHECK(!router_); + + MultiplexRouter::Config config = + passes_associated_kinds + ? MultiplexRouter::MULTI_INTERFACE + : (has_sync_methods + ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS + : MultiplexRouter::SINGLE_INTERFACE); + router_ = new MultiplexRouter(std::move(handle), config, false, runner); + router_->SetMasterInterfaceName(interface_name); + + endpoint_client_.reset(new InterfaceEndpointClient( + router_->CreateLocalEndpointHandle(kMasterInterfaceId), stub, + std::move(request_validator), has_sync_methods, std::move(runner), + interface_version)); +} + +} // namesapce internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/binding_state.h b/mojo/public/cpp/bindings/lib/binding_state.h new file mode 100644 index 0000000000..0b0dbee002 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/binding_state.h @@ -0,0 +1,128 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_ + +#include <memory> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace internal { + +class MOJO_CPP_BINDINGS_EXPORT BindingStateBase { + public: + BindingStateBase(); + ~BindingStateBase(); + + void AddFilter(std::unique_ptr<MessageReceiver> filter); + + bool HasAssociatedInterfaces() const; + + void PauseIncomingMethodCallProcessing(); + void ResumeIncomingMethodCallProcessing(); + + bool WaitForIncomingMethodCall( + MojoDeadline deadline = MOJO_DEADLINE_INDEFINITE); + + void Close(); + void CloseWithReason(uint32_t custom_reason, const std::string& description); + + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(is_bound()); + endpoint_client_->set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(is_bound()); + endpoint_client_->set_connection_error_with_reason_handler(error_handler); + } + + bool is_bound() const { return !!router_; } + + MessagePipeHandle handle() const { + DCHECK(is_bound()); + return router_->handle(); + } + + void FlushForTesting(); + + void EnableTestingMode(); + + protected: + void BindInternal(ScopedMessagePipeHandle handle, + scoped_refptr<base::SingleThreadTaskRunner> runner, + const char* interface_name, + std::unique_ptr<MessageReceiver> request_validator, + bool passes_associated_kinds, + bool has_sync_methods, + MessageReceiverWithResponderStatus* stub, + uint32_t interface_version); + + scoped_refptr<internal::MultiplexRouter> router_; + std::unique_ptr<InterfaceEndpointClient> endpoint_client_; +}; + +template <typename Interface, typename ImplRefTraits> +class BindingState : public BindingStateBase { + public: + using ImplPointerType = typename ImplRefTraits::PointerType; + + explicit BindingState(ImplPointerType impl) { + stub_.set_sink(std::move(impl)); + } + + ~BindingState() { Close(); } + + void Bind(ScopedMessagePipeHandle handle, + scoped_refptr<base::SingleThreadTaskRunner> runner) { + BindingStateBase::BindInternal( + std::move(handle), runner, Interface::Name_, + base::MakeUnique<typename Interface::RequestValidator_>(), + Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_, &stub_, + Interface::Version_); + } + + InterfaceRequest<Interface> Unbind() { + endpoint_client_.reset(); + InterfaceRequest<Interface> request = + MakeRequest<Interface>(router_->PassMessagePipe()); + router_ = nullptr; + return request; + } + + Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); } + + private: + typename Interface::template Stub_<ImplRefTraits> stub_; + + DISALLOW_COPY_AND_ASSIGN(BindingState); +}; + +} // namesapce internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDING_STATE_H_ diff --git a/mojo/public/cpp/bindings/lib/bindings_internal.h b/mojo/public/cpp/bindings/lib/bindings_internal.h new file mode 100644 index 0000000000..631daec392 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/bindings_internal.h @@ -0,0 +1,336 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ + +#include <stdint.h> + +#include <functional> + +#include "base/template_util.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +template <typename T> +class ArrayDataView; + +template <typename T> +class AssociatedInterfacePtrInfoDataView; + +template <typename T> +class AssociatedInterfaceRequestDataView; + +template <typename T> +class InterfacePtrDataView; + +template <typename T> +class InterfaceRequestDataView; + +template <typename K, typename V> +class MapDataView; + +class NativeStructDataView; + +class StringDataView; + +namespace internal { + +// Please note that this is a different value than |mojo::kInvalidHandleValue|, +// which is the "decoded" invalid handle. +const uint32_t kEncodedInvalidHandleValue = static_cast<uint32_t>(-1); + +// A serialized union always takes 16 bytes: +// 4-byte size + 4-byte tag + 8-byte payload. +const uint32_t kUnionDataSize = 16; + +template <typename T> +class Array_Data; + +template <typename K, typename V> +class Map_Data; + +class NativeStruct_Data; + +using String_Data = Array_Data<char>; + +inline size_t Align(size_t size) { + return (size + 7) & ~0x7; +} + +inline bool IsAligned(const void* ptr) { + return !(reinterpret_cast<uintptr_t>(ptr) & 0x7); +} + +// Pointers are encoded as relative offsets. The offsets are relative to the +// address of where the offset value is stored, such that the pointer may be +// recovered with the expression: +// +// ptr = reinterpret_cast<char*>(offset) + *offset +// +// A null pointer is encoded as an offset value of 0. +// +inline void EncodePointer(const void* ptr, uint64_t* offset) { + if (!ptr) { + *offset = 0; + return; + } + + const char* p_obj = reinterpret_cast<const char*>(ptr); + const char* p_slot = reinterpret_cast<const char*>(offset); + DCHECK(p_obj > p_slot); + + *offset = static_cast<uint64_t>(p_obj - p_slot); +} + +// Note: This function doesn't validate the encoded pointer value. +inline const void* DecodePointer(const uint64_t* offset) { + if (!*offset) + return nullptr; + return reinterpret_cast<const char*>(offset) + *offset; +} + +#pragma pack(push, 1) + +struct StructHeader { + uint32_t num_bytes; + uint32_t version; +}; +static_assert(sizeof(StructHeader) == 8, "Bad sizeof(StructHeader)"); + +struct ArrayHeader { + uint32_t num_bytes; + uint32_t num_elements; +}; +static_assert(sizeof(ArrayHeader) == 8, "Bad_sizeof(ArrayHeader)"); + +template <typename T> +struct Pointer { + using BaseType = T; + + void Set(T* ptr) { EncodePointer(ptr, &offset); } + const T* Get() const { return static_cast<const T*>(DecodePointer(&offset)); } + T* Get() { + return static_cast<T*>(const_cast<void*>(DecodePointer(&offset))); + } + + bool is_null() const { return offset == 0; } + + uint64_t offset; +}; +static_assert(sizeof(Pointer<char>) == 8, "Bad_sizeof(Pointer)"); + +using GenericPointer = Pointer<void>; + +struct Handle_Data { + Handle_Data() = default; + explicit Handle_Data(uint32_t value) : value(value) {} + + bool is_valid() const { return value != kEncodedInvalidHandleValue; } + + uint32_t value; +}; +static_assert(sizeof(Handle_Data) == 4, "Bad_sizeof(Handle_Data)"); + +struct Interface_Data { + Handle_Data handle; + uint32_t version; +}; +static_assert(sizeof(Interface_Data) == 8, "Bad_sizeof(Interface_Data)"); + +struct AssociatedEndpointHandle_Data { + AssociatedEndpointHandle_Data() = default; + explicit AssociatedEndpointHandle_Data(uint32_t value) : value(value) {} + + bool is_valid() const { return value != kEncodedInvalidHandleValue; } + + uint32_t value; +}; +static_assert(sizeof(AssociatedEndpointHandle_Data) == 4, + "Bad_sizeof(AssociatedEndpointHandle_Data)"); + +struct AssociatedInterface_Data { + AssociatedEndpointHandle_Data handle; + uint32_t version; +}; +static_assert(sizeof(AssociatedInterface_Data) == 8, + "Bad_sizeof(AssociatedInterface_Data)"); + +#pragma pack(pop) + +template <typename T> +T FetchAndReset(T* ptr) { + T temp = *ptr; + *ptr = T(); + return temp; +} + +template <typename T> +struct IsUnionDataType { + private: + template <typename U> + static YesType Test(const typename U::MojomUnionDataType*); + + template <typename U> + static NoType Test(...); + + EnsureTypeIsComplete<T> check_t_; + + public: + static const bool value = + sizeof(Test<T>(0)) == sizeof(YesType) && !IsConst<T>::value; +}; + +enum class MojomTypeCategory : uint32_t { + ARRAY = 1 << 0, + ASSOCIATED_INTERFACE = 1 << 1, + ASSOCIATED_INTERFACE_REQUEST = 1 << 2, + BOOLEAN = 1 << 3, + ENUM = 1 << 4, + HANDLE = 1 << 5, + INTERFACE = 1 << 6, + INTERFACE_REQUEST = 1 << 7, + MAP = 1 << 8, + // POD except boolean and enum. + POD = 1 << 9, + STRING = 1 << 10, + STRUCT = 1 << 11, + UNION = 1 << 12 +}; + +inline constexpr MojomTypeCategory operator&(MojomTypeCategory x, + MojomTypeCategory y) { + return static_cast<MojomTypeCategory>(static_cast<uint32_t>(x) & + static_cast<uint32_t>(y)); +} + +inline constexpr MojomTypeCategory operator|(MojomTypeCategory x, + MojomTypeCategory y) { + return static_cast<MojomTypeCategory>(static_cast<uint32_t>(x) | + static_cast<uint32_t>(y)); +} + +template <typename T, bool is_enum = std::is_enum<T>::value> +struct MojomTypeTraits { + using Data = T; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::POD; +}; + +template <typename T> +struct MojomTypeTraits<ArrayDataView<T>, false> { + using Data = Array_Data<typename MojomTypeTraits<T>::DataAsArrayElement>; + using DataAsArrayElement = Pointer<Data>; + + static const MojomTypeCategory category = MojomTypeCategory::ARRAY; +}; + +template <typename T> +struct MojomTypeTraits<AssociatedInterfacePtrInfoDataView<T>, false> { + using Data = AssociatedInterface_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::ASSOCIATED_INTERFACE; +}; + +template <typename T> +struct MojomTypeTraits<AssociatedInterfaceRequestDataView<T>, false> { + using Data = AssociatedEndpointHandle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST; +}; + +template <> +struct MojomTypeTraits<bool, false> { + using Data = bool; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::BOOLEAN; +}; + +template <typename T> +struct MojomTypeTraits<T, true> { + using Data = int32_t; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::ENUM; +}; + +template <typename T> +struct MojomTypeTraits<ScopedHandleBase<T>, false> { + using Data = Handle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::HANDLE; +}; + +template <typename T> +struct MojomTypeTraits<InterfacePtrDataView<T>, false> { + using Data = Interface_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::INTERFACE; +}; + +template <typename T> +struct MojomTypeTraits<InterfaceRequestDataView<T>, false> { + using Data = Handle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::INTERFACE_REQUEST; +}; + +template <typename K, typename V> +struct MojomTypeTraits<MapDataView<K, V>, false> { + using Data = Map_Data<typename MojomTypeTraits<K>::DataAsArrayElement, + typename MojomTypeTraits<V>::DataAsArrayElement>; + using DataAsArrayElement = Pointer<Data>; + + static const MojomTypeCategory category = MojomTypeCategory::MAP; +}; + +template <> +struct MojomTypeTraits<NativeStructDataView, false> { + using Data = internal::NativeStruct_Data; + using DataAsArrayElement = Pointer<Data>; + + static const MojomTypeCategory category = MojomTypeCategory::STRUCT; +}; + +template <> +struct MojomTypeTraits<StringDataView, false> { + using Data = String_Data; + using DataAsArrayElement = Pointer<Data>; + + static const MojomTypeCategory category = MojomTypeCategory::STRING; +}; + +template <typename T, MojomTypeCategory categories> +struct BelongsTo { + static const bool value = + static_cast<uint32_t>(MojomTypeTraits<T>::category & categories) != 0; +}; + +template <typename T> +struct EnumHashImpl { + static_assert(std::is_enum<T>::value, "Incorrect hash function."); + + size_t operator()(T input) const { + using UnderlyingType = typename base::underlying_type<T>::type; + return std::hash<UnderlyingType>()(static_cast<UnderlyingType>(input)); + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/buffer.h b/mojo/public/cpp/bindings/lib/buffer.h new file mode 100644 index 0000000000..213a44590f --- /dev/null +++ b/mojo/public/cpp/bindings/lib/buffer.h @@ -0,0 +1,70 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ + +#include <stddef.h> + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" + +namespace mojo { +namespace internal { + +// Buffer provides an interface to allocate memory blocks which are 8-byte +// aligned and zero-initialized. It doesn't own the underlying memory. Users +// must ensure that the memory stays valid while using the allocated blocks from +// Buffer. +class Buffer { + public: + Buffer() {} + + // The memory must have been zero-initialized. |data| must be 8-byte + // aligned. + void Initialize(void* data, size_t size) { + DCHECK(IsAligned(data)); + + data_ = data; + size_ = size; + cursor_ = reinterpret_cast<uintptr_t>(data); + data_end_ = cursor_ + size; + } + + size_t size() const { return size_; } + + void* data() const { return data_; } + + // Allocates |num_bytes| from the buffer and returns a pointer to the start of + // the allocated block. + // The resulting address is 8-byte aligned, and the content of the memory is + // zero-filled. + void* Allocate(size_t num_bytes) { + num_bytes = Align(num_bytes); + uintptr_t result = cursor_; + cursor_ += num_bytes; + if (cursor_ > data_end_ || cursor_ < result) { + NOTREACHED(); + cursor_ -= num_bytes; + return nullptr; + } + + return reinterpret_cast<void*>(result); + } + + private: + void* data_ = nullptr; + size_t size_ = 0; + + uintptr_t cursor_ = 0; + uintptr_t data_end_ = 0; + + DISALLOW_COPY_AND_ASSIGN(Buffer); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BUFFER_H_ diff --git a/mojo/public/cpp/bindings/lib/connector.cc b/mojo/public/cpp/bindings/lib/connector.cc new file mode 100644 index 0000000000..d93e45ed93 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/connector.cc @@ -0,0 +1,493 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/connector.h" + +#include <stdint.h> +#include <utility> + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_local.h" +#include "mojo/public/cpp/bindings/lib/may_auto_lock.h" +#include "mojo/public/cpp/bindings/sync_handle_watcher.h" +#include "mojo/public/cpp/system/wait.h" + +namespace mojo { + +namespace { + +// The NestingObserver for each thread. Note that this is always a +// Connector::MessageLoopNestingObserver; we use the base type here because that +// subclass is private to Connector. +base::LazyInstance< + base::ThreadLocalPointer<base::MessageLoop::NestingObserver>>::Leaky + g_tls_nesting_observer = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// Used to efficiently maintain a doubly-linked list of all Connectors +// currently dispatching on any given thread. +class Connector::ActiveDispatchTracker { + public: + explicit ActiveDispatchTracker(const base::WeakPtr<Connector>& connector); + ~ActiveDispatchTracker(); + + void NotifyBeginNesting(); + + private: + const base::WeakPtr<Connector> connector_; + MessageLoopNestingObserver* const nesting_observer_; + ActiveDispatchTracker* outer_tracker_ = nullptr; + ActiveDispatchTracker* inner_tracker_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(ActiveDispatchTracker); +}; + +// Watches the MessageLoop on the current thread. Notifies the current chain of +// ActiveDispatchTrackers when a nested message loop is started. +class Connector::MessageLoopNestingObserver + : public base::MessageLoop::NestingObserver, + public base::MessageLoop::DestructionObserver { + public: + MessageLoopNestingObserver() { + base::MessageLoop::current()->AddNestingObserver(this); + base::MessageLoop::current()->AddDestructionObserver(this); + } + + ~MessageLoopNestingObserver() override {} + + // base::MessageLoop::NestingObserver: + void OnBeginNestedMessageLoop() override { + if (top_tracker_) + top_tracker_->NotifyBeginNesting(); + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + base::MessageLoop::current()->RemoveNestingObserver(this); + base::MessageLoop::current()->RemoveDestructionObserver(this); + DCHECK_EQ(this, g_tls_nesting_observer.Get().Get()); + g_tls_nesting_observer.Get().Set(nullptr); + delete this; + } + + static MessageLoopNestingObserver* GetForThread() { + if (!base::MessageLoop::current() || + !base::MessageLoop::current()->nesting_allowed()) + return nullptr; + auto* observer = static_cast<MessageLoopNestingObserver*>( + g_tls_nesting_observer.Get().Get()); + if (!observer) { + observer = new MessageLoopNestingObserver; + g_tls_nesting_observer.Get().Set(observer); + } + return observer; + } + + private: + friend class ActiveDispatchTracker; + + ActiveDispatchTracker* top_tracker_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(MessageLoopNestingObserver); +}; + +Connector::ActiveDispatchTracker::ActiveDispatchTracker( + const base::WeakPtr<Connector>& connector) + : connector_(connector), nesting_observer_(connector_->nesting_observer_) { + DCHECK(nesting_observer_); + if (nesting_observer_->top_tracker_) { + outer_tracker_ = nesting_observer_->top_tracker_; + outer_tracker_->inner_tracker_ = this; + } + nesting_observer_->top_tracker_ = this; +} + +Connector::ActiveDispatchTracker::~ActiveDispatchTracker() { + if (nesting_observer_->top_tracker_ == this) + nesting_observer_->top_tracker_ = outer_tracker_; + else if (inner_tracker_) + inner_tracker_->outer_tracker_ = outer_tracker_; + if (outer_tracker_) + outer_tracker_->inner_tracker_ = inner_tracker_; +} + +void Connector::ActiveDispatchTracker::NotifyBeginNesting() { + if (connector_ && connector_->handle_watcher_) + connector_->handle_watcher_->ArmOrNotify(); + if (outer_tracker_) + outer_tracker_->NotifyBeginNesting(); +} + +Connector::Connector(ScopedMessagePipeHandle message_pipe, + ConnectorConfig config, + scoped_refptr<base::SingleThreadTaskRunner> runner) + : message_pipe_(std::move(message_pipe)), + task_runner_(std::move(runner)), + nesting_observer_(MessageLoopNestingObserver::GetForThread()), + weak_factory_(this) { + if (config == MULTI_THREADED_SEND) + lock_.emplace(); + + weak_self_ = weak_factory_.GetWeakPtr(); + // Even though we don't have an incoming receiver, we still want to monitor + // the message pipe to know if is closed or encounters an error. + WaitToReadMore(); +} + +Connector::~Connector() { + { + // Allow for quick destruction on any thread if the pipe is already closed. + base::AutoLock lock(connected_lock_); + if (!connected_) + return; + } + + DCHECK(thread_checker_.CalledOnValidThread()); + CancelWait(); +} + +void Connector::CloseMessagePipe() { + // Throw away the returned message pipe. + PassMessagePipe(); +} + +ScopedMessagePipeHandle Connector::PassMessagePipe() { + DCHECK(thread_checker_.CalledOnValidThread()); + + CancelWait(); + internal::MayAutoLock locker(&lock_); + ScopedMessagePipeHandle message_pipe = std::move(message_pipe_); + weak_factory_.InvalidateWeakPtrs(); + sync_handle_watcher_callback_count_ = 0; + + base::AutoLock lock(connected_lock_); + connected_ = false; + return message_pipe; +} + +void Connector::RaiseError() { + DCHECK(thread_checker_.CalledOnValidThread()); + + HandleError(true, true); +} + +bool Connector::WaitForIncomingMessage(MojoDeadline deadline) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (error_) + return false; + + ResumeIncomingMethodCallProcessing(); + + // TODO(rockot): Use a timed Wait here. Nobody uses anything but 0 or + // INDEFINITE deadlines at present, so we only support those. + DCHECK(deadline == 0 || deadline == MOJO_DEADLINE_INDEFINITE); + + MojoResult rv = MOJO_RESULT_UNKNOWN; + if (deadline == 0 && !message_pipe_->QuerySignalsState().readable()) + return false; + + if (deadline == MOJO_DEADLINE_INDEFINITE) { + rv = Wait(message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE); + if (rv != MOJO_RESULT_OK) { + // Users that call WaitForIncomingMessage() should expect their code to be + // re-entered, so we call the error handler synchronously. + HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); + return false; + } + } + + ignore_result(ReadSingleMessage(&rv)); + return (rv == MOJO_RESULT_OK); +} + +void Connector::PauseIncomingMethodCallProcessing() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (paused_) + return; + + paused_ = true; + CancelWait(); +} + +void Connector::ResumeIncomingMethodCallProcessing() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!paused_) + return; + + paused_ = false; + WaitToReadMore(); +} + +bool Connector::Accept(Message* message) { + DCHECK(lock_ || thread_checker_.CalledOnValidThread()); + + // It shouldn't hurt even if |error_| may be changed by a different thread at + // the same time. The outcome is that we may write into |message_pipe_| after + // encountering an error, which should be fine. + if (error_) + return false; + + internal::MayAutoLock locker(&lock_); + + if (!message_pipe_.is_valid() || drop_writes_) + return true; + + MojoResult rv = + WriteMessageNew(message_pipe_.get(), message->TakeMojoMessage(), + MOJO_WRITE_MESSAGE_FLAG_NONE); + + switch (rv) { + case MOJO_RESULT_OK: + break; + case MOJO_RESULT_FAILED_PRECONDITION: + // There's no point in continuing to write to this pipe since the other + // end is gone. Avoid writing any future messages. Hide write failures + // from the caller since we'd like them to continue consuming any backlog + // of incoming messages before regarding the message pipe as closed. + drop_writes_ = true; + break; + case MOJO_RESULT_BUSY: + // We'd get a "busy" result if one of the message's handles is: + // - |message_pipe_|'s own handle; + // - simultaneously being used on another thread; or + // - in a "busy" state that prohibits it from being transferred (e.g., + // a data pipe handle in the middle of a two-phase read/write, + // regardless of which thread that two-phase read/write is happening + // on). + // TODO(vtl): I wonder if this should be a |DCHECK()|. (But, until + // crbug.com/389666, etc. are resolved, this will make tests fail quickly + // rather than hanging.) + CHECK(false) << "Race condition or other bug detected"; + return false; + default: + // This particular write was rejected, presumably because of bad input. + // The pipe is not necessarily in a bad state. + return false; + } + return true; +} + +void Connector::AllowWokenUpBySyncWatchOnSameThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + + allow_woken_up_by_others_ = true; + + EnsureSyncWatcherExists(); + sync_watcher_->AllowWokenUpBySyncWatchOnSameThread(); +} + +bool Connector::SyncWatch(const bool* should_stop) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (error_) + return false; + + ResumeIncomingMethodCallProcessing(); + + EnsureSyncWatcherExists(); + return sync_watcher_->SyncWatch(should_stop); +} + +void Connector::SetWatcherHeapProfilerTag(const char* tag) { + heap_profiler_tag_ = tag; + if (handle_watcher_) { + handle_watcher_->set_heap_profiler_tag(tag); + } +} + +void Connector::OnWatcherHandleReady(MojoResult result) { + OnHandleReadyInternal(result); +} + +void Connector::OnSyncHandleWatcherHandleReady(MojoResult result) { + base::WeakPtr<Connector> weak_self(weak_self_); + + sync_handle_watcher_callback_count_++; + OnHandleReadyInternal(result); + // At this point, this object might have been deleted. + if (weak_self) { + DCHECK_LT(0u, sync_handle_watcher_callback_count_); + sync_handle_watcher_callback_count_--; + } +} + +void Connector::OnHandleReadyInternal(MojoResult result) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (result != MOJO_RESULT_OK) { + HandleError(result != MOJO_RESULT_FAILED_PRECONDITION, false); + return; + } + + ReadAllAvailableMessages(); + // At this point, this object might have been deleted. Return. +} + +void Connector::WaitToReadMore() { + CHECK(!paused_); + DCHECK(!handle_watcher_); + + handle_watcher_.reset(new SimpleWatcher( + FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL, task_runner_)); + if (heap_profiler_tag_) + handle_watcher_->set_heap_profiler_tag(heap_profiler_tag_); + MojoResult rv = handle_watcher_->Watch( + message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&Connector::OnWatcherHandleReady, base::Unretained(this))); + + if (rv != MOJO_RESULT_OK) { + // If the watch failed because the handle is invalid or its conditions can + // no longer be met, we signal the error asynchronously to avoid reentry. + task_runner_->PostTask( + FROM_HERE, + base::Bind(&Connector::OnWatcherHandleReady, weak_self_, rv)); + } else { + handle_watcher_->ArmOrNotify(); + } + + if (allow_woken_up_by_others_) { + EnsureSyncWatcherExists(); + sync_watcher_->AllowWokenUpBySyncWatchOnSameThread(); + } +} + +bool Connector::ReadSingleMessage(MojoResult* read_result) { + CHECK(!paused_); + + bool receiver_result = false; + + // Detect if |this| was destroyed or the message pipe was closed/transferred + // during message dispatch. + base::WeakPtr<Connector> weak_self = weak_self_; + + Message message; + const MojoResult rv = ReadMessage(message_pipe_.get(), &message); + *read_result = rv; + + if (rv == MOJO_RESULT_OK) { + base::Optional<ActiveDispatchTracker> dispatch_tracker; + if (!is_dispatching_ && nesting_observer_) { + is_dispatching_ = true; + dispatch_tracker.emplace(weak_self); + } + + receiver_result = + incoming_receiver_ && incoming_receiver_->Accept(&message); + + if (!weak_self) + return false; + + if (dispatch_tracker) { + is_dispatching_ = false; + dispatch_tracker.reset(); + } + } else if (rv == MOJO_RESULT_SHOULD_WAIT) { + return true; + } else { + HandleError(rv != MOJO_RESULT_FAILED_PRECONDITION, false); + return false; + } + + if (enforce_errors_from_incoming_receiver_ && !receiver_result) { + HandleError(true, false); + return false; + } + return true; +} + +void Connector::ReadAllAvailableMessages() { + while (!error_) { + base::WeakPtr<Connector> weak_self = weak_self_; + MojoResult rv; + + // May delete |this.| + if (!ReadSingleMessage(&rv)) + return; + + if (!weak_self || paused_) + return; + + DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_SHOULD_WAIT); + + if (rv == MOJO_RESULT_SHOULD_WAIT) { + // Attempt to re-arm the Watcher. + MojoResult ready_result; + MojoResult arm_result = handle_watcher_->Arm(&ready_result); + if (arm_result == MOJO_RESULT_OK) + return; + + // The watcher is already ready to notify again. + DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, arm_result); + + if (ready_result == MOJO_RESULT_FAILED_PRECONDITION) { + HandleError(false, false); + return; + } + + // There's more to read now, so we'll just keep looping. + DCHECK_EQ(MOJO_RESULT_OK, ready_result); + } + } +} + +void Connector::CancelWait() { + handle_watcher_.reset(); + sync_watcher_.reset(); +} + +void Connector::HandleError(bool force_pipe_reset, bool force_async_handler) { + if (error_ || !message_pipe_.is_valid()) + return; + + if (paused_) { + // Enforce calling the error handler asynchronously if the user has paused + // receiving messages. We need to wait until the user starts receiving + // messages again. + force_async_handler = true; + } + + if (!force_pipe_reset && force_async_handler) + force_pipe_reset = true; + + if (force_pipe_reset) { + CancelWait(); + internal::MayAutoLock locker(&lock_); + message_pipe_.reset(); + MessagePipe dummy_pipe; + message_pipe_ = std::move(dummy_pipe.handle0); + } else { + CancelWait(); + } + + if (force_async_handler) { + if (!paused_) + WaitToReadMore(); + } else { + error_ = true; + if (!connection_error_handler_.is_null()) + connection_error_handler_.Run(); + } +} + +void Connector::EnsureSyncWatcherExists() { + if (sync_watcher_) + return; + sync_watcher_.reset(new SyncHandleWatcher( + message_pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&Connector::OnSyncHandleWatcherHandleReady, + base::Unretained(this)))); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.cc b/mojo/public/cpp/bindings/lib/control_message_handler.cc new file mode 100644 index 0000000000..1b7bb78e5f --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_handler.cc @@ -0,0 +1,150 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/control_message_handler.h" + +#include <stddef.h> +#include <stdint.h> +#include <utility> + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +namespace mojo { +namespace internal { +namespace { + +bool ValidateControlRequestWithResponse(Message* message) { + ValidationContext validation_context(message->payload(), + message->payload_num_bytes(), 0, 0, + message, "ControlRequestValidator"); + if (!ValidateMessageIsRequestExpectingResponse(message, &validation_context)) + return false; + + switch (message->header()->name) { + case interface_control::kRunMessageId: + return ValidateMessagePayload< + interface_control::internal::RunMessageParams_Data>( + message, &validation_context); + } + return false; +} + +bool ValidateControlRequestWithoutResponse(Message* message) { + ValidationContext validation_context(message->payload(), + message->payload_num_bytes(), 0, 0, + message, "ControlRequestValidator"); + if (!ValidateMessageIsRequestWithoutResponse(message, &validation_context)) + return false; + + switch (message->header()->name) { + case interface_control::kRunOrClosePipeMessageId: + return ValidateMessageIsRequestWithoutResponse(message, + &validation_context) && + ValidateMessagePayload< + interface_control::internal::RunOrClosePipeMessageParams_Data>( + message, &validation_context); + } + return false; +} + +} // namespace + +// static +bool ControlMessageHandler::IsControlMessage(const Message* message) { + return message->header()->name == interface_control::kRunMessageId || + message->header()->name == interface_control::kRunOrClosePipeMessageId; +} + +ControlMessageHandler::ControlMessageHandler(uint32_t interface_version) + : interface_version_(interface_version) { +} + +ControlMessageHandler::~ControlMessageHandler() { +} + +bool ControlMessageHandler::Accept(Message* message) { + if (!ValidateControlRequestWithoutResponse(message)) + return false; + + if (message->header()->name == interface_control::kRunOrClosePipeMessageId) + return RunOrClosePipe(message); + + NOTREACHED(); + return false; +} + +bool ControlMessageHandler::AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) { + if (!ValidateControlRequestWithResponse(message)) + return false; + + if (message->header()->name == interface_control::kRunMessageId) + return Run(message, std::move(responder)); + + NOTREACHED(); + return false; +} + +bool ControlMessageHandler::Run( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) { + interface_control::internal::RunMessageParams_Data* params = + reinterpret_cast<interface_control::internal::RunMessageParams_Data*>( + message->mutable_payload()); + interface_control::RunMessageParamsPtr params_ptr; + Deserialize<interface_control::RunMessageParamsDataView>(params, ¶ms_ptr, + &context_); + auto& input = *params_ptr->input; + interface_control::RunOutputPtr output = interface_control::RunOutput::New(); + if (input.is_query_version()) { + output->set_query_version_result( + interface_control::QueryVersionResult::New()); + output->get_query_version_result()->version = interface_version_; + } else if (input.is_flush_for_testing()) { + output.reset(); + } else { + output.reset(); + } + + auto response_params_ptr = interface_control::RunResponseMessageParams::New(); + response_params_ptr->output = std::move(output); + size_t size = + PrepareToSerialize<interface_control::RunResponseMessageParamsDataView>( + response_params_ptr, &context_); + MessageBuilder builder(interface_control::kRunMessageId, + Message::kFlagIsResponse, size, 0); + builder.message()->set_request_id(message->request_id()); + + interface_control::internal::RunResponseMessageParams_Data* response_params = + nullptr; + Serialize<interface_control::RunResponseMessageParamsDataView>( + response_params_ptr, builder.buffer(), &response_params, &context_); + ignore_result(responder->Accept(builder.message())); + + return true; +} + +bool ControlMessageHandler::RunOrClosePipe(Message* message) { + interface_control::internal::RunOrClosePipeMessageParams_Data* params = + reinterpret_cast< + interface_control::internal::RunOrClosePipeMessageParams_Data*>( + message->mutable_payload()); + interface_control::RunOrClosePipeMessageParamsPtr params_ptr; + Deserialize<interface_control::RunOrClosePipeMessageParamsDataView>( + params, ¶ms_ptr, &context_); + auto& input = *params_ptr->input; + if (input.is_require_version()) + return interface_version_ >= input.get_require_version()->version; + + return false; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/control_message_handler.h b/mojo/public/cpp/bindings/lib/control_message_handler.h new file mode 100644 index 0000000000..5d1f716ea8 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_handler.h @@ -0,0 +1,48 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace internal { + +// Handlers for request messages defined in interface_control_messages.mojom. +class MOJO_CPP_BINDINGS_EXPORT ControlMessageHandler + : NON_EXPORTED_BASE(public MessageReceiverWithResponderStatus) { + public: + static bool IsControlMessage(const Message* message); + + explicit ControlMessageHandler(uint32_t interface_version); + ~ControlMessageHandler() override; + + // Call the following methods only if IsControlMessage() returned true. + bool Accept(Message* message) override; + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override; + + private: + bool Run(Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder); + bool RunOrClosePipe(Message* message); + + uint32_t interface_version_; + SerializationContext context_; + + DISALLOW_COPY_AND_ASSIGN(ControlMessageHandler); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_HANDLER_H_ diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.cc b/mojo/public/cpp/bindings/lib/control_message_proxy.cc new file mode 100644 index 0000000000..d082b49fb3 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_proxy.cc @@ -0,0 +1,188 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/control_message_proxy.h" + +#include <stddef.h> +#include <stdint.h> +#include <utility> + +#include "base/bind.h" +#include "base/callback_helpers.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +namespace mojo { +namespace internal { + +namespace { + +bool ValidateControlResponse(Message* message) { + ValidationContext validation_context(message->payload(), + message->payload_num_bytes(), 0, 0, + message, "ControlResponseValidator"); + if (!ValidateMessageIsResponse(message, &validation_context)) + return false; + + switch (message->header()->name) { + case interface_control::kRunMessageId: + return ValidateMessagePayload< + interface_control::internal::RunResponseMessageParams_Data>( + message, &validation_context); + } + return false; +} + +using RunCallback = + base::Callback<void(interface_control::RunResponseMessageParamsPtr)>; + +class RunResponseForwardToCallback : public MessageReceiver { + public: + explicit RunResponseForwardToCallback(const RunCallback& callback) + : callback_(callback) {} + bool Accept(Message* message) override; + + private: + RunCallback callback_; + DISALLOW_COPY_AND_ASSIGN(RunResponseForwardToCallback); +}; + +bool RunResponseForwardToCallback::Accept(Message* message) { + if (!ValidateControlResponse(message)) + return false; + + interface_control::internal::RunResponseMessageParams_Data* params = + reinterpret_cast< + interface_control::internal::RunResponseMessageParams_Data*>( + message->mutable_payload()); + interface_control::RunResponseMessageParamsPtr params_ptr; + SerializationContext context; + Deserialize<interface_control::RunResponseMessageParamsDataView>( + params, ¶ms_ptr, &context); + + callback_.Run(std::move(params_ptr)); + return true; +} + +void SendRunMessage(MessageReceiverWithResponder* receiver, + interface_control::RunInputPtr input_ptr, + const RunCallback& callback) { + SerializationContext context; + + auto params_ptr = interface_control::RunMessageParams::New(); + params_ptr->input = std::move(input_ptr); + size_t size = PrepareToSerialize<interface_control::RunMessageParamsDataView>( + params_ptr, &context); + MessageBuilder builder(interface_control::kRunMessageId, + Message::kFlagExpectsResponse, size, 0); + + interface_control::internal::RunMessageParams_Data* params = nullptr; + Serialize<interface_control::RunMessageParamsDataView>( + params_ptr, builder.buffer(), ¶ms, &context); + std::unique_ptr<MessageReceiver> responder = + base::MakeUnique<RunResponseForwardToCallback>(callback); + ignore_result( + receiver->AcceptWithResponder(builder.message(), std::move(responder))); +} + +Message ConstructRunOrClosePipeMessage( + interface_control::RunOrClosePipeInputPtr input_ptr) { + SerializationContext context; + + auto params_ptr = interface_control::RunOrClosePipeMessageParams::New(); + params_ptr->input = std::move(input_ptr); + + size_t size = PrepareToSerialize< + interface_control::RunOrClosePipeMessageParamsDataView>(params_ptr, + &context); + MessageBuilder builder(interface_control::kRunOrClosePipeMessageId, 0, size, + 0); + + interface_control::internal::RunOrClosePipeMessageParams_Data* params = + nullptr; + Serialize<interface_control::RunOrClosePipeMessageParamsDataView>( + params_ptr, builder.buffer(), ¶ms, &context); + return std::move(*builder.message()); +} + +void SendRunOrClosePipeMessage( + MessageReceiverWithResponder* receiver, + interface_control::RunOrClosePipeInputPtr input_ptr) { + Message message(ConstructRunOrClosePipeMessage(std::move(input_ptr))); + + ignore_result(receiver->Accept(&message)); +} + +void RunVersionCallback( + const base::Callback<void(uint32_t)>& callback, + interface_control::RunResponseMessageParamsPtr run_response) { + uint32_t version = 0u; + if (run_response->output && run_response->output->is_query_version_result()) + version = run_response->output->get_query_version_result()->version; + callback.Run(version); +} + +void RunClosure(const base::Closure& callback, + interface_control::RunResponseMessageParamsPtr run_response) { + callback.Run(); +} + +} // namespace + +ControlMessageProxy::ControlMessageProxy(MessageReceiverWithResponder* receiver) + : receiver_(receiver) { +} + +ControlMessageProxy::~ControlMessageProxy() = default; + +void ControlMessageProxy::QueryVersion( + const base::Callback<void(uint32_t)>& callback) { + auto input_ptr = interface_control::RunInput::New(); + input_ptr->set_query_version(interface_control::QueryVersion::New()); + SendRunMessage(receiver_, std::move(input_ptr), + base::Bind(&RunVersionCallback, callback)); +} + +void ControlMessageProxy::RequireVersion(uint32_t version) { + auto require_version = interface_control::RequireVersion::New(); + require_version->version = version; + auto input_ptr = interface_control::RunOrClosePipeInput::New(); + input_ptr->set_require_version(std::move(require_version)); + SendRunOrClosePipeMessage(receiver_, std::move(input_ptr)); +} + +void ControlMessageProxy::FlushForTesting() { + if (encountered_error_) + return; + + auto input_ptr = interface_control::RunInput::New(); + input_ptr->set_flush_for_testing(interface_control::FlushForTesting::New()); + base::RunLoop run_loop; + run_loop_quit_closure_ = run_loop.QuitClosure(); + SendRunMessage( + receiver_, std::move(input_ptr), + base::Bind(&RunClosure, + base::Bind(&ControlMessageProxy::RunFlushForTestingClosure, + base::Unretained(this)))); + run_loop.Run(); +} + +void ControlMessageProxy::RunFlushForTestingClosure() { + DCHECK(!run_loop_quit_closure_.is_null()); + base::ResetAndReturn(&run_loop_quit_closure_).Run(); +} + +void ControlMessageProxy::OnConnectionError() { + encountered_error_ = true; + if (!run_loop_quit_closure_.is_null()) + RunFlushForTestingClosure(); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/control_message_proxy.h b/mojo/public/cpp/bindings/lib/control_message_proxy.h new file mode 100644 index 0000000000..2f9314ebf0 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/control_message_proxy.h @@ -0,0 +1,49 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ + +#include <stdint.h> + +#include "base/callback.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" + +namespace mojo { + +class MessageReceiverWithResponder; + +namespace internal { + +// Proxy for request messages defined in interface_control_messages.mojom. +class MOJO_CPP_BINDINGS_EXPORT ControlMessageProxy { + public: + // Doesn't take ownership of |receiver|. It must outlive this object. + explicit ControlMessageProxy(MessageReceiverWithResponder* receiver); + ~ControlMessageProxy(); + + void QueryVersion(const base::Callback<void(uint32_t)>& callback); + void RequireVersion(uint32_t version); + + void FlushForTesting(); + void OnConnectionError(); + + private: + void RunFlushForTestingClosure(); + + // Not owned. + MessageReceiverWithResponder* receiver_; + bool encountered_error_ = false; + + base::Closure run_loop_quit_closure_; + + DISALLOW_COPY_AND_ASSIGN(ControlMessageProxy); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_CONTROL_MESSAGE_PROXY_H_ diff --git a/mojo/public/cpp/bindings/lib/equals_traits.h b/mojo/public/cpp/bindings/lib/equals_traits.h new file mode 100644 index 0000000000..53c7dce693 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/equals_traits.h @@ -0,0 +1,94 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_ + +#include <type_traits> +#include <unordered_map> +#include <vector> + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { +namespace internal { + +template <typename T> +struct HasEqualsMethod { + template <typename U> + static char Test(decltype(&U::Equals)); + template <typename U> + static int Test(...); + static const bool value = sizeof(Test<T>(0)) == sizeof(char); + + private: + EnsureTypeIsComplete<T> check_t_; +}; + +template <typename T, bool has_equals_method = HasEqualsMethod<T>::value> +struct EqualsTraits; + +template <typename T> +bool Equals(const T& a, const T& b); + +template <typename T> +struct EqualsTraits<T, true> { + static bool Equals(const T& a, const T& b) { return a.Equals(b); } +}; + +template <typename T> +struct EqualsTraits<T, false> { + static bool Equals(const T& a, const T& b) { return a == b; } +}; + +template <typename T> +struct EqualsTraits<base::Optional<T>, false> { + static bool Equals(const base::Optional<T>& a, const base::Optional<T>& b) { + if (!a && !b) + return true; + if (!a || !b) + return false; + + return internal::Equals(*a, *b); + } +}; + +template <typename T> +struct EqualsTraits<std::vector<T>, false> { + static bool Equals(const std::vector<T>& a, const std::vector<T>& b) { + if (a.size() != b.size()) + return false; + for (size_t i = 0; i < a.size(); ++i) { + if (!internal::Equals(a[i], b[i])) + return false; + } + return true; + } +}; + +template <typename K, typename V> +struct EqualsTraits<std::unordered_map<K, V>, false> { + static bool Equals(const std::unordered_map<K, V>& a, + const std::unordered_map<K, V>& b) { + if (a.size() != b.size()) + return false; + for (const auto& element : a) { + auto iter = b.find(element.first); + if (iter == b.end() || !internal::Equals(element.second, iter->second)) + return false; + } + return true; + } +}; + +template <typename T> +bool Equals(const T& a, const T& b) { + return EqualsTraits<T>::Equals(a, b); +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_EQUALS_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/lib/filter_chain.cc b/mojo/public/cpp/bindings/lib/filter_chain.cc new file mode 100644 index 0000000000..5d919fe172 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/filter_chain.cc @@ -0,0 +1,47 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/filter_chain.h" + +#include <algorithm> + +#include "base/logging.h" + +namespace mojo { + +FilterChain::FilterChain(MessageReceiver* sink) : sink_(sink) { +} + +FilterChain::FilterChain(FilterChain&& other) : sink_(other.sink_) { + other.sink_ = nullptr; + filters_.swap(other.filters_); +} + +FilterChain& FilterChain::operator=(FilterChain&& other) { + std::swap(sink_, other.sink_); + filters_.swap(other.filters_); + return *this; +} + +FilterChain::~FilterChain() { +} + +void FilterChain::SetSink(MessageReceiver* sink) { + DCHECK(!sink_); + sink_ = sink; +} + +bool FilterChain::Accept(Message* message) { + DCHECK(sink_); + for (auto& filter : filters_) + if (!filter->Accept(message)) + return false; + return sink_->Accept(message); +} + +void FilterChain::Append(std::unique_ptr<MessageReceiver> filter) { + filters_.emplace_back(std::move(filter)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.cc b/mojo/public/cpp/bindings/lib/fixed_buffer.cc new file mode 100644 index 0000000000..725a193cd7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/fixed_buffer.cc @@ -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. + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" + +#include <stdlib.h> + +namespace mojo { +namespace internal { + +FixedBufferForTesting::FixedBufferForTesting(size_t size) { + size = internal::Align(size); + // Use calloc here to ensure all message memory is zero'd out. + void* ptr = calloc(size, 1); + Initialize(ptr, size); +} + +FixedBufferForTesting::~FixedBufferForTesting() { + free(data()); +} + +void* FixedBufferForTesting::Leak() { + void* ptr = data(); + Initialize(nullptr, 0); + return ptr; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/fixed_buffer.h b/mojo/public/cpp/bindings/lib/fixed_buffer.h new file mode 100644 index 0000000000..070b0c8cef --- /dev/null +++ b/mojo/public/cpp/bindings/lib/fixed_buffer.h @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ + +#include <stddef.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" + +namespace mojo { +namespace internal { + +// FixedBufferForTesting owns its buffer. The Leak method may be used to steal +// the underlying memory. +class MOJO_CPP_BINDINGS_EXPORT FixedBufferForTesting + : NON_EXPORTED_BASE(public Buffer) { + public: + explicit FixedBufferForTesting(size_t size); + ~FixedBufferForTesting(); + + // Returns the internal memory owned by the Buffer to the caller. The Buffer + // relinquishes its pointer, effectively resetting the state of the Buffer + // and leaving the caller responsible for freeing the returned memory address + // when no longer needed. + void* Leak(); + + private: + DISALLOW_COPY_AND_ASSIGN(FixedBufferForTesting); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_FIXED_BUFFER_H_ diff --git a/mojo/public/cpp/bindings/lib/handle_interface_serialization.h b/mojo/public/cpp/bindings/lib/handle_interface_serialization.h new file mode 100644 index 0000000000..14ed21f0ac --- /dev/null +++ b/mojo/public/cpp/bindings/lib/handle_interface_serialization.h @@ -0,0 +1,181 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_ + +#include <type_traits> + +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/interface_data_view.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace internal { + +template <typename Base, typename T> +struct Serializer<AssociatedInterfacePtrInfoDataView<Base>, + AssociatedInterfacePtrInfo<T>> { + static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const AssociatedInterfacePtrInfo<T>& input, + SerializationContext* context) { + if (input.handle().is_valid()) + context->associated_endpoint_count++; + return 0; + } + + static void Serialize(AssociatedInterfacePtrInfo<T>& input, + AssociatedInterface_Data* output, + SerializationContext* context) { + DCHECK(!input.handle().is_valid() || input.handle().pending_association()); + if (input.handle().is_valid()) { + // Set to the index of the element pushed to the back of the vector. + output->handle.value = + static_cast<uint32_t>(context->associated_endpoint_handles.size()); + context->associated_endpoint_handles.push_back(input.PassHandle()); + } else { + output->handle.value = kEncodedInvalidHandleValue; + } + output->version = input.version(); + } + + static bool Deserialize(AssociatedInterface_Data* input, + AssociatedInterfacePtrInfo<T>* output, + SerializationContext* context) { + if (input->handle.is_valid()) { + DCHECK_LT(input->handle.value, + context->associated_endpoint_handles.size()); + output->set_handle( + std::move(context->associated_endpoint_handles[input->handle.value])); + } else { + output->set_handle(ScopedInterfaceEndpointHandle()); + } + output->set_version(input->version); + return true; + } +}; + +template <typename Base, typename T> +struct Serializer<AssociatedInterfaceRequestDataView<Base>, + AssociatedInterfaceRequest<T>> { + static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const AssociatedInterfaceRequest<T>& input, + SerializationContext* context) { + if (input.handle().is_valid()) + context->associated_endpoint_count++; + return 0; + } + + static void Serialize(AssociatedInterfaceRequest<T>& input, + AssociatedEndpointHandle_Data* output, + SerializationContext* context) { + DCHECK(!input.handle().is_valid() || input.handle().pending_association()); + if (input.handle().is_valid()) { + // Set to the index of the element pushed to the back of the vector. + output->value = + static_cast<uint32_t>(context->associated_endpoint_handles.size()); + context->associated_endpoint_handles.push_back(input.PassHandle()); + } else { + output->value = kEncodedInvalidHandleValue; + } + } + + static bool Deserialize(AssociatedEndpointHandle_Data* input, + AssociatedInterfaceRequest<T>* output, + SerializationContext* context) { + if (input->is_valid()) { + DCHECK_LT(input->value, context->associated_endpoint_handles.size()); + output->Bind( + std::move(context->associated_endpoint_handles[input->value])); + } else { + output->Bind(ScopedInterfaceEndpointHandle()); + } + return true; + } +}; + +template <typename Base, typename T> +struct Serializer<InterfacePtrDataView<Base>, InterfacePtr<T>> { + static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const InterfacePtr<T>& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(InterfacePtr<T>& input, + Interface_Data* output, + SerializationContext* context) { + InterfacePtrInfo<T> info = input.PassInterface(); + output->handle = context->handles.AddHandle(info.PassHandle().release()); + output->version = info.version(); + } + + static bool Deserialize(Interface_Data* input, + InterfacePtr<T>* output, + SerializationContext* context) { + output->Bind(InterfacePtrInfo<T>( + context->handles.TakeHandleAs<mojo::MessagePipeHandle>(input->handle), + input->version)); + return true; + } +}; + +template <typename Base, typename T> +struct Serializer<InterfaceRequestDataView<Base>, InterfaceRequest<T>> { + static_assert(std::is_base_of<Base, T>::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const InterfaceRequest<T>& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(InterfaceRequest<T>& input, + Handle_Data* output, + SerializationContext* context) { + *output = context->handles.AddHandle(input.PassMessagePipe().release()); + } + + static bool Deserialize(Handle_Data* input, + InterfaceRequest<T>* output, + SerializationContext* context) { + output->Bind(context->handles.TakeHandleAs<MessagePipeHandle>(*input)); + return true; + } +}; + +template <typename T> +struct Serializer<ScopedHandleBase<T>, ScopedHandleBase<T>> { + static size_t PrepareToSerialize(const ScopedHandleBase<T>& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(ScopedHandleBase<T>& input, + Handle_Data* output, + SerializationContext* context) { + *output = context->handles.AddHandle(input.release()); + } + + static bool Deserialize(Handle_Data* input, + ScopedHandleBase<T>* output, + SerializationContext* context) { + *output = context->handles.TakeHandleAs<T>(*input); + return true; + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_HANDLE_INTERFACE_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/hash_util.h b/mojo/public/cpp/bindings/lib/hash_util.h new file mode 100644 index 0000000000..93280d69da --- /dev/null +++ b/mojo/public/cpp/bindings/lib/hash_util.h @@ -0,0 +1,84 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_ + +#include <cstring> +#include <functional> +#include <type_traits> +#include <vector> + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { +namespace internal { + +template <typename T> +size_t HashCombine(size_t seed, const T& value) { + // Based on proposal in: + // http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf + return seed ^ (std::hash<T>()(value) + (seed << 6) + (seed >> 2)); +} + +template <typename T> +struct HasHashMethod { + template <typename U> + static char Test(decltype(&U::Hash)); + template <typename U> + static int Test(...); + static const bool value = sizeof(Test<T>(0)) == sizeof(char); + + private: + EnsureTypeIsComplete<T> check_t_; +}; + +template <typename T, bool has_hash_method = HasHashMethod<T>::value> +struct HashTraits; + +template <typename T> +size_t Hash(size_t seed, const T& value); + +template <typename T> +struct HashTraits<T, true> { + static size_t Hash(size_t seed, const T& value) { return value.Hash(seed); } +}; + +template <typename T> +struct HashTraits<T, false> { + static size_t Hash(size_t seed, const T& value) { + return HashCombine(seed, value); + } +}; + +template <typename T> +struct HashTraits<std::vector<T>, false> { + static size_t Hash(size_t seed, const std::vector<T>& value) { + for (const auto& element : value) { + seed = HashCombine(seed, element); + } + return seed; + } +}; + +template <typename T> +struct HashTraits<base::Optional<std::vector<T>>, false> { + static size_t Hash(size_t seed, const base::Optional<std::vector<T>>& value) { + if (!value) + return HashCombine(seed, 0); + + return Hash(seed, *value); + } +}; + +template <typename T> +size_t Hash(size_t seed, const T& value) { + return HashTraits<T>::Hash(seed, value); +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_HASH_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc new file mode 100644 index 0000000000..4682e72fad --- /dev/null +++ b/mojo/public/cpp/bindings/lib/interface_endpoint_client.cc @@ -0,0 +1,412 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" + +#include <stdint.h> + +#include <utility> + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "mojo/public/cpp/bindings/associated_group.h" +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/interface_endpoint_controller.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" +#include "mojo/public/cpp/bindings/sync_call_restrictions.h" + +namespace mojo { + +// ---------------------------------------------------------------------------- + +namespace { + +void DCheckIfInvalid(const base::WeakPtr<InterfaceEndpointClient>& client, + const std::string& message) { + bool is_valid = client && !client->encountered_error(); + DCHECK(!is_valid) << message; +} + +// When receiving an incoming message which expects a repsonse, +// InterfaceEndpointClient creates a ResponderThunk object and passes it to the +// incoming message receiver. When the receiver finishes processing the message, +// it can provide a response using this object. +class ResponderThunk : public MessageReceiverWithStatus { + public: + explicit ResponderThunk( + const base::WeakPtr<InterfaceEndpointClient>& endpoint_client, + scoped_refptr<base::SingleThreadTaskRunner> runner) + : endpoint_client_(endpoint_client), + accept_was_invoked_(false), + task_runner_(std::move(runner)) {} + ~ResponderThunk() override { + if (!accept_was_invoked_) { + // The Service handled a message that was expecting a response + // but did not send a response. + // We raise an error to signal 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. + if (task_runner_->RunsTasksOnCurrentThread()) { + // Please note that even if this code is run from a different task + // runner on the same thread as |task_runner_|, it is okay to directly + // call InterfaceEndpointClient::RaiseError(), because it will raise + // error from the correct task runner asynchronously. + if (endpoint_client_) { + endpoint_client_->RaiseError(); + } + } else { + task_runner_->PostTask( + FROM_HERE, + base::Bind(&InterfaceEndpointClient::RaiseError, endpoint_client_)); + } + } + } + + // MessageReceiver implementation: + bool Accept(Message* message) override { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + accept_was_invoked_ = true; + DCHECK(message->has_flag(Message::kFlagIsResponse)); + + bool result = false; + + if (endpoint_client_) + result = endpoint_client_->Accept(message); + + return result; + } + + // MessageReceiverWithStatus implementation: + bool IsValid() override { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + return endpoint_client_ && !endpoint_client_->encountered_error(); + } + + void DCheckInvalid(const std::string& message) override { + if (task_runner_->RunsTasksOnCurrentThread()) { + DCheckIfInvalid(endpoint_client_, message); + } else { + task_runner_->PostTask( + FROM_HERE, base::Bind(&DCheckIfInvalid, endpoint_client_, message)); + } + } + + private: + base::WeakPtr<InterfaceEndpointClient> endpoint_client_; + bool accept_was_invoked_; + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + DISALLOW_COPY_AND_ASSIGN(ResponderThunk); +}; + +} // namespace + +// ---------------------------------------------------------------------------- + +InterfaceEndpointClient::SyncResponseInfo::SyncResponseInfo( + bool* in_response_received) + : response_received(in_response_received) {} + +InterfaceEndpointClient::SyncResponseInfo::~SyncResponseInfo() {} + +// ---------------------------------------------------------------------------- + +InterfaceEndpointClient::HandleIncomingMessageThunk::HandleIncomingMessageThunk( + InterfaceEndpointClient* owner) + : owner_(owner) {} + +InterfaceEndpointClient::HandleIncomingMessageThunk:: + ~HandleIncomingMessageThunk() {} + +bool InterfaceEndpointClient::HandleIncomingMessageThunk::Accept( + Message* message) { + return owner_->HandleValidatedMessage(message); +} + +// ---------------------------------------------------------------------------- + +InterfaceEndpointClient::InterfaceEndpointClient( + ScopedInterfaceEndpointHandle handle, + MessageReceiverWithResponderStatus* receiver, + std::unique_ptr<MessageReceiver> payload_validator, + bool expect_sync_requests, + scoped_refptr<base::SingleThreadTaskRunner> runner, + uint32_t interface_version) + : expect_sync_requests_(expect_sync_requests), + handle_(std::move(handle)), + incoming_receiver_(receiver), + thunk_(this), + filters_(&thunk_), + task_runner_(std::move(runner)), + control_message_proxy_(this), + control_message_handler_(interface_version), + weak_ptr_factory_(this) { + DCHECK(handle_.is_valid()); + + // TODO(yzshen): the way to use validator (or message filter in general) + // directly is a little awkward. + if (payload_validator) + filters_.Append(std::move(payload_validator)); + + if (handle_.pending_association()) { + handle_.SetAssociationEventHandler(base::Bind( + &InterfaceEndpointClient::OnAssociationEvent, base::Unretained(this))); + } else { + InitControllerIfNecessary(); + } +} + +InterfaceEndpointClient::~InterfaceEndpointClient() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (controller_) + handle_.group_controller()->DetachEndpointClient(handle_); +} + +AssociatedGroup* InterfaceEndpointClient::associated_group() { + if (!associated_group_) + associated_group_ = base::MakeUnique<AssociatedGroup>(handle_); + return associated_group_.get(); +} + +ScopedInterfaceEndpointHandle InterfaceEndpointClient::PassHandle() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!has_pending_responders()); + + if (!handle_.is_valid()) + return ScopedInterfaceEndpointHandle(); + + handle_.SetAssociationEventHandler( + ScopedInterfaceEndpointHandle::AssociationEventCallback()); + + if (controller_) { + controller_ = nullptr; + handle_.group_controller()->DetachEndpointClient(handle_); + } + + return std::move(handle_); +} + +void InterfaceEndpointClient::AddFilter( + std::unique_ptr<MessageReceiver> filter) { + filters_.Append(std::move(filter)); +} + +void InterfaceEndpointClient::RaiseError() { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!handle_.pending_association()) + handle_.group_controller()->RaiseError(); +} + +void InterfaceEndpointClient::CloseWithReason(uint32_t custom_reason, + const std::string& description) { + DCHECK(thread_checker_.CalledOnValidThread()); + + auto handle = PassHandle(); + handle.ResetWithReason(custom_reason, description); +} + +bool InterfaceEndpointClient::Accept(Message* message) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!message->has_flag(Message::kFlagExpectsResponse)); + DCHECK(!handle_.pending_association()); + + // This has to been done even if connection error has occurred. For example, + // the message contains a pending associated request. The user may try to use + // the corresponding associated interface pointer after sending this message. + // That associated interface pointer has to join an associated group in order + // to work properly. + if (!message->associated_endpoint_handles()->empty()) + message->SerializeAssociatedEndpointHandles(handle_.group_controller()); + + if (encountered_error_) + return false; + + InitControllerIfNecessary(); + + return controller_->SendMessage(message); +} + +bool InterfaceEndpointClient::AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiver> responder) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(message->has_flag(Message::kFlagExpectsResponse)); + DCHECK(!handle_.pending_association()); + + // Please see comments in Accept(). + if (!message->associated_endpoint_handles()->empty()) + message->SerializeAssociatedEndpointHandles(handle_.group_controller()); + + if (encountered_error_) + return false; + + InitControllerIfNecessary(); + + // Reserve 0 in case we want it to convey special meaning in the future. + uint64_t request_id = next_request_id_++; + if (request_id == 0) + request_id = next_request_id_++; + + message->set_request_id(request_id); + + bool is_sync = message->has_flag(Message::kFlagIsSync); + if (!controller_->SendMessage(message)) + return false; + + if (!is_sync) { + async_responders_[request_id] = std::move(responder); + return true; + } + + SyncCallRestrictions::AssertSyncCallAllowed(); + + bool response_received = false; + sync_responses_.insert(std::make_pair( + request_id, base::MakeUnique<SyncResponseInfo>(&response_received))); + + base::WeakPtr<InterfaceEndpointClient> weak_self = + weak_ptr_factory_.GetWeakPtr(); + controller_->SyncWatch(&response_received); + // Make sure that this instance hasn't been destroyed. + if (weak_self) { + DCHECK(base::ContainsKey(sync_responses_, request_id)); + auto iter = sync_responses_.find(request_id); + DCHECK_EQ(&response_received, iter->second->response_received); + if (response_received) + ignore_result(responder->Accept(&iter->second->response)); + sync_responses_.erase(iter); + } + + return true; +} + +bool InterfaceEndpointClient::HandleIncomingMessage(Message* message) { + DCHECK(thread_checker_.CalledOnValidThread()); + return filters_.Accept(message); +} + +void InterfaceEndpointClient::NotifyError( + const base::Optional<DisconnectReason>& reason) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (encountered_error_) + return; + encountered_error_ = true; + + // Response callbacks may hold on to resource, and there's no need to keep + // them alive any longer. Note that it's allowed that a pending response + // callback may own this endpoint, so we simply move the responders onto the + // stack here and let them be destroyed when the stack unwinds. + AsyncResponderMap responders = std::move(async_responders_); + + control_message_proxy_.OnConnectionError(); + + if (!error_handler_.is_null()) { + base::Closure error_handler = std::move(error_handler_); + error_handler.Run(); + } else if (!error_with_reason_handler_.is_null()) { + ConnectionErrorWithReasonCallback error_with_reason_handler = + std::move(error_with_reason_handler_); + if (reason) { + error_with_reason_handler.Run(reason->custom_reason, reason->description); + } else { + error_with_reason_handler.Run(0, std::string()); + } + } +} + +void InterfaceEndpointClient::QueryVersion( + const base::Callback<void(uint32_t)>& callback) { + control_message_proxy_.QueryVersion(callback); +} + +void InterfaceEndpointClient::RequireVersion(uint32_t version) { + control_message_proxy_.RequireVersion(version); +} + +void InterfaceEndpointClient::FlushForTesting() { + control_message_proxy_.FlushForTesting(); +} + +void InterfaceEndpointClient::InitControllerIfNecessary() { + if (controller_ || handle_.pending_association()) + return; + + controller_ = handle_.group_controller()->AttachEndpointClient(handle_, this, + task_runner_); + if (expect_sync_requests_) + controller_->AllowWokenUpBySyncWatchOnSameThread(); +} + +void InterfaceEndpointClient::OnAssociationEvent( + ScopedInterfaceEndpointHandle::AssociationEvent event) { + if (event == ScopedInterfaceEndpointHandle::ASSOCIATED) { + InitControllerIfNecessary(); + } else if (event == + ScopedInterfaceEndpointHandle::PEER_CLOSED_BEFORE_ASSOCIATION) { + task_runner_->PostTask(FROM_HERE, + base::Bind(&InterfaceEndpointClient::NotifyError, + weak_ptr_factory_.GetWeakPtr(), + handle_.disconnect_reason())); + } +} + +bool InterfaceEndpointClient::HandleValidatedMessage(Message* message) { + DCHECK_EQ(handle_.id(), message->interface_id()); + + if (encountered_error_) { + // This message is received after error has been encountered. For associated + // interfaces, this means the remote side sends a + // PeerAssociatedEndpointClosed event but continues to send more messages + // for the same interface. Close the pipe because this shouldn't happen. + DVLOG(1) << "A message is received for an interface after it has been " + << "disconnected. Closing the pipe."; + return false; + } + + if (message->has_flag(Message::kFlagExpectsResponse)) { + std::unique_ptr<MessageReceiverWithStatus> responder = + base::MakeUnique<ResponderThunk>(weak_ptr_factory_.GetWeakPtr(), + task_runner_); + if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) { + return control_message_handler_.AcceptWithResponder(message, + std::move(responder)); + } else { + return incoming_receiver_->AcceptWithResponder(message, + std::move(responder)); + } + } else if (message->has_flag(Message::kFlagIsResponse)) { + uint64_t request_id = message->request_id(); + + if (message->has_flag(Message::kFlagIsSync)) { + auto it = sync_responses_.find(request_id); + if (it == sync_responses_.end()) + return false; + it->second->response = std::move(*message); + *it->second->response_received = true; + return true; + } + + auto it = async_responders_.find(request_id); + if (it == async_responders_.end()) + return false; + std::unique_ptr<MessageReceiver> responder = std::move(it->second); + async_responders_.erase(it); + return responder->Accept(message); + } else { + if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) + return control_message_handler_.Accept(message); + + return incoming_receiver_->Accept(message); + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/interface_ptr_state.h b/mojo/public/cpp/bindings/lib/interface_ptr_state.h new file mode 100644 index 0000000000..fa54979795 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/interface_ptr_state.h @@ -0,0 +1,226 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_ + +#include <stdint.h> + +#include <algorithm> // For |std::swap()|. +#include <memory> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback_forward.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/single_thread_task_runner.h" +#include "mojo/public/cpp/bindings/associated_group.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/interface_ptr_info.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace mojo { +namespace internal { + +template <typename Interface> +class InterfacePtrState { + public: + InterfacePtrState() : version_(0u) {} + + ~InterfacePtrState() { + endpoint_client_.reset(); + proxy_.reset(); + if (router_) + router_->CloseMessagePipe(); + } + + Interface* instance() { + ConfigureProxyIfNecessary(); + + // This will be null if the object is not bound. + return proxy_.get(); + } + + uint32_t version() const { return version_; } + + void QueryVersion(const base::Callback<void(uint32_t)>& callback) { + ConfigureProxyIfNecessary(); + + // It is safe to capture |this| because the callback won't be run after this + // object goes away. + endpoint_client_->QueryVersion(base::Bind( + &InterfacePtrState::OnQueryVersion, base::Unretained(this), callback)); + } + + void RequireVersion(uint32_t version) { + ConfigureProxyIfNecessary(); + + if (version <= version_) + return; + + version_ = version; + endpoint_client_->RequireVersion(version); + } + + void FlushForTesting() { + ConfigureProxyIfNecessary(); + endpoint_client_->FlushForTesting(); + } + + void CloseWithReason(uint32_t custom_reason, const std::string& description) { + ConfigureProxyIfNecessary(); + endpoint_client_->CloseWithReason(custom_reason, description); + } + + void Swap(InterfacePtrState* other) { + using std::swap; + swap(other->router_, router_); + swap(other->endpoint_client_, endpoint_client_); + swap(other->proxy_, proxy_); + handle_.swap(other->handle_); + runner_.swap(other->runner_); + swap(other->version_, version_); + } + + void Bind(InterfacePtrInfo<Interface> info, + scoped_refptr<base::SingleThreadTaskRunner> runner) { + DCHECK(!router_); + DCHECK(!endpoint_client_); + DCHECK(!proxy_); + DCHECK(!handle_.is_valid()); + DCHECK_EQ(0u, version_); + DCHECK(info.is_valid()); + + handle_ = info.PassHandle(); + version_ = info.version(); + runner_ = std::move(runner); + } + + bool HasAssociatedInterfaces() const { + return router_ ? router_->HasAssociatedEndpoints() : false; + } + + // After this method is called, the object is in an invalid state and + // shouldn't be reused. + InterfacePtrInfo<Interface> PassInterface() { + endpoint_client_.reset(); + proxy_.reset(); + return InterfacePtrInfo<Interface>( + router_ ? router_->PassMessagePipe() : std::move(handle_), version_); + } + + bool is_bound() const { return handle_.is_valid() || endpoint_client_; } + + bool encountered_error() const { + return endpoint_client_ ? endpoint_client_->encountered_error() : false; + } + + void set_connection_error_handler(const base::Closure& error_handler) { + ConfigureProxyIfNecessary(); + + DCHECK(endpoint_client_); + endpoint_client_->set_connection_error_handler(error_handler); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + ConfigureProxyIfNecessary(); + + DCHECK(endpoint_client_); + endpoint_client_->set_connection_error_with_reason_handler(error_handler); + } + + // Returns true if bound and awaiting a response to a message. + bool has_pending_callbacks() const { + return endpoint_client_ && endpoint_client_->has_pending_responders(); + } + + AssociatedGroup* associated_group() { + ConfigureProxyIfNecessary(); + return endpoint_client_->associated_group(); + } + + void EnableTestingMode() { + ConfigureProxyIfNecessary(); + router_->EnableTestingMode(); + } + + void ForwardMessage(Message message) { + ConfigureProxyIfNecessary(); + endpoint_client_->Accept(&message); + } + + void ForwardMessageWithResponder(Message message, + std::unique_ptr<MessageReceiver> responder) { + ConfigureProxyIfNecessary(); + endpoint_client_->AcceptWithResponder(&message, std::move(responder)); + } + + private: + using Proxy = typename Interface::Proxy_; + + void ConfigureProxyIfNecessary() { + // The proxy has been configured. + if (proxy_) { + DCHECK(router_); + DCHECK(endpoint_client_); + return; + } + // The object hasn't been bound. + if (!handle_.is_valid()) + return; + + MultiplexRouter::Config config = + Interface::PassesAssociatedKinds_ + ? MultiplexRouter::MULTI_INTERFACE + : (Interface::HasSyncMethods_ + ? MultiplexRouter::SINGLE_INTERFACE_WITH_SYNC_METHODS + : MultiplexRouter::SINGLE_INTERFACE); + router_ = new MultiplexRouter(std::move(handle_), config, true, runner_); + router_->SetMasterInterfaceName(Interface::Name_); + endpoint_client_.reset(new InterfaceEndpointClient( + router_->CreateLocalEndpointHandle(kMasterInterfaceId), nullptr, + base::WrapUnique(new typename Interface::ResponseValidator_()), false, + std::move(runner_), + // The version is only queried from the client so the value passed here + // will not be used. + 0u)); + proxy_.reset(new Proxy(endpoint_client_.get())); + } + + void OnQueryVersion(const base::Callback<void(uint32_t)>& callback, + uint32_t version) { + version_ = version; + callback.Run(version); + } + + scoped_refptr<MultiplexRouter> router_; + + std::unique_ptr<InterfaceEndpointClient> endpoint_client_; + std::unique_ptr<Proxy> proxy_; + + // |router_| (as well as other members above) is not initialized until + // read/write with the message pipe handle is needed. |handle_| is valid + // between the Bind() call and the initialization of |router_|. + ScopedMessagePipeHandle handle_; + scoped_refptr<base::SingleThreadTaskRunner> runner_; + + uint32_t version_; + + DISALLOW_COPY_AND_ASSIGN(InterfacePtrState); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_INTERFACE_PTR_STATE_H_ diff --git a/mojo/public/cpp/bindings/lib/map_data_internal.h b/mojo/public/cpp/bindings/lib/map_data_internal.h new file mode 100644 index 0000000000..f8e3d2918f --- /dev/null +++ b/mojo/public/cpp/bindings/lib/map_data_internal.h @@ -0,0 +1,85 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +namespace mojo { +namespace internal { + +// Map serializes into a struct which has two arrays as struct fields, the keys +// and the values. +template <typename Key, typename Value> +class Map_Data { + public: + static Map_Data* New(Buffer* buf) { + return new (buf->Allocate(sizeof(Map_Data))) Map_Data(); + } + + // |validate_params| must have non-null |key_validate_params| and + // |element_validate_params| members. + static bool Validate(const void* data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + if (!data) + return true; + + if (!ValidateStructHeaderAndClaimMemory(data, validation_context)) + return false; + + const Map_Data* object = static_cast<const Map_Data*>(data); + if (object->header_.num_bytes != sizeof(Map_Data) || + object->header_.version != 0) { + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + + if (!ValidatePointerNonNullable( + object->keys, "null key array in map struct", validation_context) || + !ValidateContainer(object->keys, validation_context, + validate_params->key_validate_params)) { + return false; + } + + if (!ValidatePointerNonNullable(object->values, + "null value array in map struct", + validation_context) || + !ValidateContainer(object->values, validation_context, + validate_params->element_validate_params)) { + return false; + } + + if (object->keys.Get()->size() != object->values.Get()->size()) { + ReportValidationError(validation_context, + VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP); + return false; + } + + return true; + } + + StructHeader header_; + + Pointer<Array_Data<Key>> keys; + Pointer<Array_Data<Value>> values; + + private: + Map_Data() { + header_.num_bytes = sizeof(*this); + header_.version = 0; + } + ~Map_Data() = delete; +}; +static_assert(sizeof(Map_Data<char, char>) == 24, "Bad sizeof(Map_Data)"); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_DATA_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/map_serialization.h b/mojo/public/cpp/bindings/lib/map_serialization.h new file mode 100644 index 0000000000..718a76307d --- /dev/null +++ b/mojo/public/cpp/bindings/lib/map_serialization.h @@ -0,0 +1,182 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ + +#include <type_traits> +#include <vector> + +#include "mojo/public/cpp/bindings/array_data_view.h" +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/map_data_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/map_data_view.h" + +namespace mojo { +namespace internal { + +template <typename MaybeConstUserType> +class MapReaderBase { + public: + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = MapTraits<UserType>; + using MaybeConstIterator = + decltype(Traits::GetBegin(std::declval<MaybeConstUserType&>())); + + explicit MapReaderBase(MaybeConstUserType& input) + : input_(input), iter_(Traits::GetBegin(input_)) {} + ~MapReaderBase() {} + + size_t GetSize() const { return Traits::GetSize(input_); } + + // Return null because key or value elements are not stored continuously in + // memory. + void* GetDataIfExists() { return nullptr; } + + protected: + MaybeConstUserType& input_; + MaybeConstIterator iter_; +}; + +// Used as the UserTypeReader template parameter of ArraySerializer. +template <typename MaybeConstUserType> +class MapKeyReader : public MapReaderBase<MaybeConstUserType> { + public: + using Base = MapReaderBase<MaybeConstUserType>; + using Traits = typename Base::Traits; + using MaybeConstIterator = typename Base::MaybeConstIterator; + + explicit MapKeyReader(MaybeConstUserType& input) : Base(input) {} + ~MapKeyReader() {} + + using GetNextResult = + decltype(Traits::GetKey(std::declval<MaybeConstIterator&>())); + GetNextResult GetNext() { + GetNextResult key = Traits::GetKey(this->iter_); + Traits::AdvanceIterator(this->iter_); + return key; + } +}; + +// Used as the UserTypeReader template parameter of ArraySerializer. +template <typename MaybeConstUserType> +class MapValueReader : public MapReaderBase<MaybeConstUserType> { + public: + using Base = MapReaderBase<MaybeConstUserType>; + using Traits = typename Base::Traits; + using MaybeConstIterator = typename Base::MaybeConstIterator; + + explicit MapValueReader(MaybeConstUserType& input) : Base(input) {} + ~MapValueReader() {} + + using GetNextResult = + decltype(Traits::GetValue(std::declval<MaybeConstIterator&>())); + GetNextResult GetNext() { + GetNextResult value = Traits::GetValue(this->iter_); + Traits::AdvanceIterator(this->iter_); + return value; + } +}; + +template <typename Key, typename Value, typename MaybeConstUserType> +struct Serializer<MapDataView<Key, Value>, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = MapTraits<UserType>; + using UserKey = typename Traits::Key; + using UserValue = typename Traits::Value; + using Data = typename MojomTypeTraits<MapDataView<Key, Value>>::Data; + using KeyArraySerializer = ArraySerializer<ArrayDataView<Key>, + std::vector<UserKey>, + MapKeyReader<MaybeConstUserType>>; + using ValueArraySerializer = + ArraySerializer<ArrayDataView<Value>, + std::vector<UserValue>, + MapValueReader<MaybeConstUserType>>; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) + return 0; + + size_t struct_overhead = sizeof(Data); + MapKeyReader<MaybeConstUserType> key_reader(input); + size_t keys_size = + KeyArraySerializer::GetSerializedSize(&key_reader, context); + MapValueReader<MaybeConstUserType> value_reader(input); + size_t values_size = + ValueArraySerializer::GetSerializedSize(&value_reader, context); + + return struct_overhead + keys_size + values_size; + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buf, + Data** output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + DCHECK(validate_params->key_validate_params); + DCHECK(validate_params->element_validate_params); + if (CallIsNullIfExists<Traits>(input)) { + *output = nullptr; + return; + } + + auto result = Data::New(buf); + if (result) { + auto keys_ptr = MojomTypeTraits<ArrayDataView<Key>>::Data::New( + Traits::GetSize(input), buf); + if (keys_ptr) { + MapKeyReader<MaybeConstUserType> key_reader(input); + KeyArraySerializer::SerializeElements( + &key_reader, buf, keys_ptr, validate_params->key_validate_params, + context); + result->keys.Set(keys_ptr); + } + + auto values_ptr = MojomTypeTraits<ArrayDataView<Value>>::Data::New( + Traits::GetSize(input), buf); + if (values_ptr) { + MapValueReader<MaybeConstUserType> value_reader(input); + ValueArraySerializer::SerializeElements( + &value_reader, buf, values_ptr, + validate_params->element_validate_params, context); + result->values.Set(values_ptr); + } + } + *output = result; + } + + static bool Deserialize(Data* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists<Traits>(output); + + std::vector<UserKey> keys; + std::vector<UserValue> values; + + if (!KeyArraySerializer::DeserializeElements(input->keys.Get(), &keys, + context) || + !ValueArraySerializer::DeserializeElements(input->values.Get(), &values, + context)) { + return false; + } + + DCHECK_EQ(keys.size(), values.size()); + size_t size = keys.size(); + Traits::SetToEmpty(output); + + for (size_t i = 0; i < size; ++i) { + if (!Traits::Insert(*output, std::move(keys[i]), std::move(values[i]))) + return false; + } + return true; + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAP_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/may_auto_lock.h b/mojo/public/cpp/bindings/lib/may_auto_lock.h new file mode 100644 index 0000000000..06091fee90 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/may_auto_lock.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_ + +#include "base/macros.h" +#include "base/optional.h" +#include "base/synchronization/lock.h" + +namespace mojo { +namespace internal { + +// Similar to base::AutoLock, except that it does nothing if |lock| passed into +// the constructor is null. +class MayAutoLock { + public: + explicit MayAutoLock(base::Optional<base::Lock>* lock) + : lock_(lock->has_value() ? &lock->value() : nullptr) { + if (lock_) + lock_->Acquire(); + } + + ~MayAutoLock() { + if (lock_) { + lock_->AssertAcquired(); + lock_->Release(); + } + } + + private: + base::Lock* lock_; + DISALLOW_COPY_AND_ASSIGN(MayAutoLock); +}; + +// Similar to base::AutoUnlock, except that it does nothing if |lock| passed +// into the constructor is null. +class MayAutoUnlock { + public: + explicit MayAutoUnlock(base::Optional<base::Lock>* lock) + : lock_(lock->has_value() ? &lock->value() : nullptr) { + if (lock_) { + lock_->AssertAcquired(); + lock_->Release(); + } + } + + ~MayAutoUnlock() { + if (lock_) + lock_->Acquire(); + } + + private: + base::Lock* lock_; + DISALLOW_COPY_AND_ASSIGN(MayAutoUnlock); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MAY_AUTO_LOCK_H_ diff --git a/mojo/public/cpp/bindings/lib/message.cc b/mojo/public/cpp/bindings/lib/message.cc new file mode 100644 index 0000000000..e5f3808117 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message.cc @@ -0,0 +1,332 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/message.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#include <algorithm> +#include <utility> + +#include "base/bind.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_local.h" +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" + +namespace mojo { + +namespace { + +base::LazyInstance<base::ThreadLocalPointer<internal::MessageDispatchContext>>:: + DestructorAtExit g_tls_message_dispatch_context = LAZY_INSTANCE_INITIALIZER; + +base::LazyInstance<base::ThreadLocalPointer<SyncMessageResponseContext>>:: + DestructorAtExit g_tls_sync_response_context = LAZY_INSTANCE_INITIALIZER; + +void DoNotifyBadMessage(Message message, const std::string& error) { + message.NotifyBadMessage(error); +} + +} // namespace + +Message::Message() { +} + +Message::Message(Message&& other) + : buffer_(std::move(other.buffer_)), + handles_(std::move(other.handles_)), + associated_endpoint_handles_( + std::move(other.associated_endpoint_handles_)) {} + +Message::~Message() { + CloseHandles(); +} + +Message& Message::operator=(Message&& other) { + Reset(); + std::swap(other.buffer_, buffer_); + std::swap(other.handles_, handles_); + std::swap(other.associated_endpoint_handles_, associated_endpoint_handles_); + return *this; +} + +void Message::Reset() { + CloseHandles(); + handles_.clear(); + associated_endpoint_handles_.clear(); + buffer_.reset(); +} + +void Message::Initialize(size_t capacity, bool zero_initialized) { + DCHECK(!buffer_); + buffer_.reset(new internal::MessageBuffer(capacity, zero_initialized)); +} + +void Message::InitializeFromMojoMessage(ScopedMessageHandle message, + uint32_t num_bytes, + std::vector<Handle>* handles) { + DCHECK(!buffer_); + buffer_.reset(new internal::MessageBuffer(std::move(message), num_bytes)); + handles_.swap(*handles); +} + +const uint8_t* Message::payload() const { + if (version() < 2) + return data() + header()->num_bytes; + + return static_cast<const uint8_t*>(header_v2()->payload.Get()); +} + +uint32_t Message::payload_num_bytes() const { + DCHECK_GE(data_num_bytes(), header()->num_bytes); + size_t num_bytes; + if (version() < 2) { + num_bytes = data_num_bytes() - header()->num_bytes; + } else { + auto payload = reinterpret_cast<uintptr_t>(header_v2()->payload.Get()); + if (!payload) { + num_bytes = 0; + } else { + auto payload_end = + reinterpret_cast<uintptr_t>(header_v2()->payload_interface_ids.Get()); + if (!payload_end) + payload_end = reinterpret_cast<uintptr_t>(data() + data_num_bytes()); + DCHECK_GE(payload_end, payload); + num_bytes = payload_end - payload; + } + } + DCHECK_LE(num_bytes, std::numeric_limits<uint32_t>::max()); + return static_cast<uint32_t>(num_bytes); +} + +uint32_t Message::payload_num_interface_ids() const { + auto* array_pointer = + version() < 2 ? nullptr : header_v2()->payload_interface_ids.Get(); + return array_pointer ? static_cast<uint32_t>(array_pointer->size()) : 0; +} + +const uint32_t* Message::payload_interface_ids() const { + auto* array_pointer = + version() < 2 ? nullptr : header_v2()->payload_interface_ids.Get(); + return array_pointer ? array_pointer->storage() : nullptr; +} + +ScopedMessageHandle Message::TakeMojoMessage() { + // If there are associated endpoints transferred, + // SerializeAssociatedEndpointHandles() must be called before this method. + DCHECK(associated_endpoint_handles_.empty()); + + if (handles_.empty()) // Fast path for the common case: No handles. + return buffer_->TakeMessage(); + + // Allocate a new message with space for the handles, then copy the buffer + // contents into it. + // + // TODO(rockot): We could avoid this copy by extending GetSerializedSize() + // behavior to collect handles. It's unoptimized for now because it's much + // more common to have messages with no handles. + ScopedMessageHandle new_message; + MojoResult rv = AllocMessage( + data_num_bytes(), + handles_.empty() ? nullptr + : reinterpret_cast<const MojoHandle*>(handles_.data()), + handles_.size(), + MOJO_ALLOC_MESSAGE_FLAG_NONE, + &new_message); + CHECK_EQ(rv, MOJO_RESULT_OK); + handles_.clear(); + + void* new_buffer = nullptr; + rv = GetMessageBuffer(new_message.get(), &new_buffer); + CHECK_EQ(rv, MOJO_RESULT_OK); + + memcpy(new_buffer, data(), data_num_bytes()); + buffer_.reset(); + + return new_message; +} + +void Message::NotifyBadMessage(const std::string& error) { + DCHECK(buffer_); + buffer_->NotifyBadMessage(error); +} + +void Message::CloseHandles() { + for (std::vector<Handle>::iterator it = handles_.begin(); + it != handles_.end(); ++it) { + if (it->is_valid()) + CloseRaw(*it); + } +} + +void Message::SerializeAssociatedEndpointHandles( + AssociatedGroupController* group_controller) { + if (associated_endpoint_handles_.empty()) + return; + + DCHECK_GE(version(), 2u); + DCHECK(header_v2()->payload_interface_ids.is_null()); + + size_t size = associated_endpoint_handles_.size(); + auto* data = internal::Array_Data<uint32_t>::New(size, buffer()); + header_v2()->payload_interface_ids.Set(data); + + for (size_t i = 0; i < size; ++i) { + ScopedInterfaceEndpointHandle& handle = associated_endpoint_handles_[i]; + + DCHECK(handle.pending_association()); + data->storage()[i] = + group_controller->AssociateInterface(std::move(handle)); + } + associated_endpoint_handles_.clear(); +} + +bool Message::DeserializeAssociatedEndpointHandles( + AssociatedGroupController* group_controller) { + associated_endpoint_handles_.clear(); + + uint32_t num_ids = payload_num_interface_ids(); + if (num_ids == 0) + return true; + + associated_endpoint_handles_.reserve(num_ids); + uint32_t* ids = header_v2()->payload_interface_ids.Get()->storage(); + bool result = true; + for (uint32_t i = 0; i < num_ids; ++i) { + auto handle = group_controller->CreateLocalEndpointHandle(ids[i]); + if (IsValidInterfaceId(ids[i]) && !handle.is_valid()) { + // |ids[i]| itself is valid but handle creation failed. In that case, mark + // deserialization as failed but continue to deserialize the rest of + // handles. + result = false; + } + + associated_endpoint_handles_.push_back(std::move(handle)); + ids[i] = kInvalidInterfaceId; + } + return result; +} + +PassThroughFilter::PassThroughFilter() {} + +PassThroughFilter::~PassThroughFilter() {} + +bool PassThroughFilter::Accept(Message* message) { return true; } + +SyncMessageResponseContext::SyncMessageResponseContext() + : outer_context_(current()) { + g_tls_sync_response_context.Get().Set(this); +} + +SyncMessageResponseContext::~SyncMessageResponseContext() { + DCHECK_EQ(current(), this); + g_tls_sync_response_context.Get().Set(outer_context_); +} + +// static +SyncMessageResponseContext* SyncMessageResponseContext::current() { + return g_tls_sync_response_context.Get().Get(); +} + +void SyncMessageResponseContext::ReportBadMessage(const std::string& error) { + GetBadMessageCallback().Run(error); +} + +const ReportBadMessageCallback& +SyncMessageResponseContext::GetBadMessageCallback() { + if (bad_message_callback_.is_null()) { + bad_message_callback_ = + base::Bind(&DoNotifyBadMessage, base::Passed(&response_)); + } + return bad_message_callback_; +} + +MojoResult ReadMessage(MessagePipeHandle handle, Message* message) { + MojoResult rv; + + std::vector<Handle> handles; + ScopedMessageHandle mojo_message; + uint32_t num_bytes = 0, num_handles = 0; + rv = ReadMessageNew(handle, + &mojo_message, + &num_bytes, + nullptr, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (rv == MOJO_RESULT_RESOURCE_EXHAUSTED) { + DCHECK_GT(num_handles, 0u); + handles.resize(num_handles); + rv = ReadMessageNew(handle, + &mojo_message, + &num_bytes, + reinterpret_cast<MojoHandle*>(handles.data()), + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + } + + if (rv != MOJO_RESULT_OK) + return rv; + + message->InitializeFromMojoMessage( + std::move(mojo_message), num_bytes, &handles); + return MOJO_RESULT_OK; +} + +void ReportBadMessage(const std::string& error) { + internal::MessageDispatchContext* context = + internal::MessageDispatchContext::current(); + DCHECK(context); + context->GetBadMessageCallback().Run(error); +} + +ReportBadMessageCallback GetBadMessageCallback() { + internal::MessageDispatchContext* context = + internal::MessageDispatchContext::current(); + DCHECK(context); + return context->GetBadMessageCallback(); +} + +namespace internal { + +MessageHeaderV2::MessageHeaderV2() = default; + +MessageDispatchContext::MessageDispatchContext(Message* message) + : outer_context_(current()), message_(message) { + g_tls_message_dispatch_context.Get().Set(this); +} + +MessageDispatchContext::~MessageDispatchContext() { + DCHECK_EQ(current(), this); + g_tls_message_dispatch_context.Get().Set(outer_context_); +} + +// static +MessageDispatchContext* MessageDispatchContext::current() { + return g_tls_message_dispatch_context.Get().Get(); +} + +const ReportBadMessageCallback& +MessageDispatchContext::GetBadMessageCallback() { + if (bad_message_callback_.is_null()) { + bad_message_callback_ = + base::Bind(&DoNotifyBadMessage, base::Passed(message_)); + } + return bad_message_callback_; +} + +// static +void SyncMessageResponseSetup::SetCurrentSyncResponseMessage(Message* message) { + SyncMessageResponseContext* context = SyncMessageResponseContext::current(); + if (context) + context->response_ = std::move(*message); +} + +} // namespace internal + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_buffer.cc b/mojo/public/cpp/bindings/lib/message_buffer.cc new file mode 100644 index 0000000000..cc12ef6e31 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_buffer.cc @@ -0,0 +1,52 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/message_buffer.h" + +#include <limits> + +#include "mojo/public/cpp/bindings/lib/serialization_util.h" + +namespace mojo { +namespace internal { + +MessageBuffer::MessageBuffer(size_t capacity, bool zero_initialized) { + DCHECK_LE(capacity, std::numeric_limits<uint32_t>::max()); + + MojoResult rv = AllocMessage(capacity, nullptr, 0, + MOJO_ALLOC_MESSAGE_FLAG_NONE, &message_); + CHECK_EQ(rv, MOJO_RESULT_OK); + + void* buffer = nullptr; + if (capacity != 0) { + rv = GetMessageBuffer(message_.get(), &buffer); + CHECK_EQ(rv, MOJO_RESULT_OK); + + if (zero_initialized) + memset(buffer, 0, capacity); + } + Initialize(buffer, capacity); +} + +MessageBuffer::MessageBuffer(ScopedMessageHandle message, uint32_t num_bytes) { + message_ = std::move(message); + + void* buffer = nullptr; + if (num_bytes != 0) { + MojoResult rv = GetMessageBuffer(message_.get(), &buffer); + CHECK_EQ(rv, MOJO_RESULT_OK); + } + Initialize(buffer, num_bytes); +} + +MessageBuffer::~MessageBuffer() {} + +void MessageBuffer::NotifyBadMessage(const std::string& error) { + DCHECK(message_.is_valid()); + MojoResult result = mojo::NotifyBadMessage(message_.get(), error); + DCHECK_EQ(result, MOJO_RESULT_OK); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_buffer.h b/mojo/public/cpp/bindings/lib/message_buffer.h new file mode 100644 index 0000000000..96d5140f77 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_buffer.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_ + +#include <stdint.h> + +#include <utility> + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/system/message.h" + +namespace mojo { +namespace internal { + +// A fixed-size Buffer using a Mojo message object for storage. +class MessageBuffer : public Buffer { + public: + // Initializes this buffer to carry a fixed byte capacity and no handles. + MessageBuffer(size_t capacity, bool zero_initialized); + + // Initializes this buffer from an existing Mojo MessageHandle. + MessageBuffer(ScopedMessageHandle message, uint32_t num_bytes); + + ~MessageBuffer(); + + ScopedMessageHandle TakeMessage() { return std::move(message_); } + + void NotifyBadMessage(const std::string& error); + + private: + ScopedMessageHandle message_; + + DISALLOW_COPY_AND_ASSIGN(MessageBuffer); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_LIB_MESSAGE_BUFFER_H_ diff --git a/mojo/public/cpp/bindings/lib/message_builder.cc b/mojo/public/cpp/bindings/lib/message_builder.cc new file mode 100644 index 0000000000..6806a73213 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_builder.cc @@ -0,0 +1,69 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/message_builder.h" + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/message_internal.h" + +namespace mojo { +namespace internal { + +template <typename Header> +void Allocate(Buffer* buf, Header** header) { + *header = static_cast<Header*>(buf->Allocate(sizeof(Header))); + (*header)->num_bytes = sizeof(Header); +} + +MessageBuilder::MessageBuilder(uint32_t name, + uint32_t flags, + size_t payload_size, + size_t payload_interface_id_count) { + if (payload_interface_id_count > 0) { + // Version 2 + InitializeMessage( + sizeof(MessageHeaderV2) + Align(payload_size) + + ArrayDataTraits<uint32_t>::GetStorageSize( + static_cast<uint32_t>(payload_interface_id_count))); + + MessageHeaderV2* header; + Allocate(message_.buffer(), &header); + header->version = 2; + header->name = name; + header->flags = flags; + // The payload immediately follows the header. + header->payload.Set(header + 1); + } else if (flags & + (Message::kFlagExpectsResponse | Message::kFlagIsResponse)) { + // Version 1 + InitializeMessage(sizeof(MessageHeaderV1) + payload_size); + + MessageHeaderV1* header; + Allocate(message_.buffer(), &header); + header->version = 1; + header->name = name; + header->flags = flags; + } else { + InitializeMessage(sizeof(MessageHeader) + payload_size); + + MessageHeader* header; + Allocate(message_.buffer(), &header); + header->version = 0; + header->name = name; + header->flags = flags; + } +} + +MessageBuilder::~MessageBuilder() { +} + +void MessageBuilder::InitializeMessage(size_t size) { + message_.Initialize(static_cast<uint32_t>(Align(size)), + true /* zero_initialized */); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_builder.h b/mojo/public/cpp/bindings/lib/message_builder.h new file mode 100644 index 0000000000..8a4d5c4690 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_builder.h @@ -0,0 +1,45 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +class Message; + +namespace internal { + +class Buffer; + +class MOJO_CPP_BINDINGS_EXPORT MessageBuilder { + public: + MessageBuilder(uint32_t name, + uint32_t flags, + size_t payload_size, + size_t payload_interface_id_count); + ~MessageBuilder(); + + Buffer* buffer() { return message_.buffer(); } + Message* message() { return &message_; } + + private: + void InitializeMessage(size_t size); + + Message message_; + + DISALLOW_COPY_AND_ASSIGN(MessageBuilder); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_BUILDER_H_ diff --git a/mojo/public/cpp/bindings/lib/message_header_validator.cc b/mojo/public/cpp/bindings/lib/message_header_validator.cc new file mode 100644 index 0000000000..9f8c6278c0 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_header_validator.cc @@ -0,0 +1,133 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/message_header_validator.h" + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +namespace mojo { +namespace { + +// TODO(yzshen): Define a mojom struct for message header and use the generated +// validation and data view code. +bool IsValidMessageHeader(const internal::MessageHeader* header, + internal::ValidationContext* validation_context) { + // NOTE: Our goal is to preserve support for future extension of the message + // header. If we encounter fields we do not understand, we must ignore them. + + // Extra validation of the struct header: + do { + if (header->version == 0) { + if (header->num_bytes == sizeof(internal::MessageHeader)) + break; + } else if (header->version == 1) { + if (header->num_bytes == sizeof(internal::MessageHeaderV1)) + break; + } else if (header->version == 2) { + if (header->num_bytes == sizeof(internal::MessageHeaderV2)) + break; + } else if (header->version > 2) { + if (header->num_bytes >= sizeof(internal::MessageHeaderV2)) + break; + } + internal::ReportValidationError( + validation_context, + internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } while (false); + + // Validate flags (allow unknown bits): + + // These flags require a RequestID. + constexpr uint32_t kRequestIdFlags = + Message::kFlagExpectsResponse | Message::kFlagIsResponse; + if (header->version == 0 && (header->flags & kRequestIdFlags)) { + internal::ReportValidationError( + validation_context, + internal::VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID); + return false; + } + + // These flags are mutually exclusive. + if ((header->flags & kRequestIdFlags) == kRequestIdFlags) { + internal::ReportValidationError( + validation_context, + internal::VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + + if (header->version < 2) + return true; + + auto* header_v2 = static_cast<const internal::MessageHeaderV2*>(header); + // For the payload pointer: + // - Check that the pointer can be safely decoded. + // - Claim one byte that the pointer points to. It makes sure not only the + // address is within the message, but also the address precedes the array + // storing interface IDs (which is important for safely calculating the + // payload size). + // - Validation of the payload contents will be done separately based on the + // payload type. + if (!header_v2->payload.is_null() && + (!internal::ValidatePointer(header_v2->payload, validation_context) || + !validation_context->ClaimMemory(header_v2->payload.Get(), 1))) { + return false; + } + + const internal::ContainerValidateParams validate_params(0, false, nullptr); + if (!internal::ValidateContainer(header_v2->payload_interface_ids, + validation_context, &validate_params)) { + return false; + } + + if (!header_v2->payload_interface_ids.is_null()) { + size_t num_ids = header_v2->payload_interface_ids.Get()->size(); + const uint32_t* ids = header_v2->payload_interface_ids.Get()->storage(); + for (size_t i = 0; i < num_ids; ++i) { + if (!IsValidInterfaceId(ids[i]) || IsMasterInterfaceId(ids[i])) { + internal::ReportValidationError( + validation_context, + internal::VALIDATION_ERROR_ILLEGAL_INTERFACE_ID); + return false; + } + } + } + + return true; +} + +} // namespace + +MessageHeaderValidator::MessageHeaderValidator() + : MessageHeaderValidator("MessageHeaderValidator") {} + +MessageHeaderValidator::MessageHeaderValidator(const std::string& description) + : description_(description) { +} + +void MessageHeaderValidator::SetDescription(const std::string& description) { + description_ = description; +} + +bool MessageHeaderValidator::Accept(Message* message) { + // Pass 0 as number of handles and associated endpoint handles because we + // don't expect any in the header, even if |message| contains handles. + internal::ValidationContext validation_context( + message->data(), message->data_num_bytes(), 0, 0, message, description_); + + if (!internal::ValidateStructHeaderAndClaimMemory(message->data(), + &validation_context)) + return false; + + if (!IsValidMessageHeader(message->header(), &validation_context)) + return false; + + return true; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/message_internal.h b/mojo/public/cpp/bindings/lib/message_internal.h new file mode 100644 index 0000000000..6693198f81 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/message_internal.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ + +#include <stdint.h> + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" + +namespace mojo { + +class Message; + +namespace internal { + +template <typename T> +class Array_Data; + +#pragma pack(push, 1) + +struct MessageHeader : internal::StructHeader { + // Interface ID for identifying multiple interfaces running on the same + // message pipe. + uint32_t interface_id; + // Message name, which is scoped to the interface that the message belongs to. + uint32_t name; + // 0 or either of the enum values defined above. + uint32_t flags; + // Unused padding to make the struct size a multiple of 8 bytes. + uint32_t padding; +}; +static_assert(sizeof(MessageHeader) == 24, "Bad sizeof(MessageHeader)"); + +struct MessageHeaderV1 : MessageHeader { + // Only used if either kFlagExpectsResponse or kFlagIsResponse is set in + // order to match responses with corresponding requests. + uint64_t request_id; +}; +static_assert(sizeof(MessageHeaderV1) == 32, "Bad sizeof(MessageHeaderV1)"); + +struct MessageHeaderV2 : MessageHeaderV1 { + MessageHeaderV2(); + GenericPointer payload; + Pointer<Array_Data<uint32_t>> payload_interface_ids; +}; +static_assert(sizeof(MessageHeaderV2) == 48, "Bad sizeof(MessageHeaderV2)"); + +#pragma pack(pop) + +class MOJO_CPP_BINDINGS_EXPORT MessageDispatchContext { + public: + explicit MessageDispatchContext(Message* message); + ~MessageDispatchContext(); + + static MessageDispatchContext* current(); + + const base::Callback<void(const std::string&)>& GetBadMessageCallback(); + + private: + MessageDispatchContext* outer_context_; + Message* message_; + base::Callback<void(const std::string&)> bad_message_callback_; + + DISALLOW_COPY_AND_ASSIGN(MessageDispatchContext); +}; + +class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseSetup { + public: + static void SetCurrentSyncResponseMessage(Message* message); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MESSAGE_INTERNAL_H_ diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.cc b/mojo/public/cpp/bindings/lib/multiplex_router.cc new file mode 100644 index 0000000000..ff7c678289 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/multiplex_router.cc @@ -0,0 +1,960 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" + +#include <stdint.h> + +#include <utility> + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/interface_endpoint_controller.h" +#include "mojo/public/cpp/bindings/lib/may_auto_lock.h" +#include "mojo/public/cpp/bindings/sync_event_watcher.h" + +namespace mojo { +namespace internal { + +// InterfaceEndpoint stores the information of an interface endpoint registered +// with the router. +// No one other than the router's |endpoints_| and |tasks_| should hold refs to +// this object. +class MultiplexRouter::InterfaceEndpoint + : public base::RefCountedThreadSafe<InterfaceEndpoint>, + public InterfaceEndpointController { + public: + InterfaceEndpoint(MultiplexRouter* router, InterfaceId id) + : router_(router), + id_(id), + closed_(false), + peer_closed_(false), + handle_created_(false), + client_(nullptr) {} + + // --------------------------------------------------------------------------- + // The following public methods are safe to call from any threads without + // locking. + + InterfaceId id() const { return id_; } + + // --------------------------------------------------------------------------- + // The following public methods are called under the router's lock. + + bool closed() const { return closed_; } + void set_closed() { + router_->AssertLockAcquired(); + closed_ = true; + } + + bool peer_closed() const { return peer_closed_; } + void set_peer_closed() { + router_->AssertLockAcquired(); + peer_closed_ = true; + } + + bool handle_created() const { return handle_created_; } + void set_handle_created() { + router_->AssertLockAcquired(); + handle_created_ = true; + } + + const base::Optional<DisconnectReason>& disconnect_reason() const { + return disconnect_reason_; + } + void set_disconnect_reason( + const base::Optional<DisconnectReason>& disconnect_reason) { + router_->AssertLockAcquired(); + disconnect_reason_ = disconnect_reason; + } + + base::SingleThreadTaskRunner* task_runner() const { + return task_runner_.get(); + } + + InterfaceEndpointClient* client() const { return client_; } + + void AttachClient(InterfaceEndpointClient* client, + scoped_refptr<base::SingleThreadTaskRunner> runner) { + router_->AssertLockAcquired(); + DCHECK(!client_); + DCHECK(!closed_); + DCHECK(runner->BelongsToCurrentThread()); + + task_runner_ = std::move(runner); + client_ = client; + } + + // This method must be called on the same thread as the corresponding + // AttachClient() call. + void DetachClient() { + router_->AssertLockAcquired(); + DCHECK(client_); + DCHECK(task_runner_->BelongsToCurrentThread()); + DCHECK(!closed_); + + task_runner_ = nullptr; + client_ = nullptr; + sync_watcher_.reset(); + } + + void SignalSyncMessageEvent() { + router_->AssertLockAcquired(); + if (sync_message_event_signaled_) + return; + sync_message_event_signaled_ = true; + if (sync_message_event_) + sync_message_event_->Signal(); + } + + void ResetSyncMessageSignal() { + router_->AssertLockAcquired(); + if (!sync_message_event_signaled_) + return; + sync_message_event_signaled_ = false; + if (sync_message_event_) + sync_message_event_->Reset(); + } + + // --------------------------------------------------------------------------- + // The following public methods (i.e., InterfaceEndpointController + // implementation) are called by the client on the same thread as the + // AttachClient() call. They are called outside of the router's lock. + + bool SendMessage(Message* message) override { + DCHECK(task_runner_->BelongsToCurrentThread()); + message->set_interface_id(id_); + return router_->connector_.Accept(message); + } + + void AllowWokenUpBySyncWatchOnSameThread() override { + DCHECK(task_runner_->BelongsToCurrentThread()); + + EnsureSyncWatcherExists(); + sync_watcher_->AllowWokenUpBySyncWatchOnSameThread(); + } + + bool SyncWatch(const bool* should_stop) override { + DCHECK(task_runner_->BelongsToCurrentThread()); + + EnsureSyncWatcherExists(); + return sync_watcher_->SyncWatch(should_stop); + } + + private: + friend class base::RefCountedThreadSafe<InterfaceEndpoint>; + + ~InterfaceEndpoint() override { + router_->AssertLockAcquired(); + + DCHECK(!client_); + DCHECK(closed_); + DCHECK(peer_closed_); + DCHECK(!sync_watcher_); + } + + void OnSyncEventSignaled() { + DCHECK(task_runner_->BelongsToCurrentThread()); + scoped_refptr<MultiplexRouter> router_protector(router_); + + MayAutoLock locker(&router_->lock_); + scoped_refptr<InterfaceEndpoint> self_protector(this); + + bool more_to_process = router_->ProcessFirstSyncMessageForEndpoint(id_); + + if (!more_to_process) + ResetSyncMessageSignal(); + + // Currently there are no queued sync messages and the peer has closed so + // there won't be incoming sync messages in the future. + if (!more_to_process && peer_closed_) { + // If a SyncWatch() call (or multiple ones) of this interface endpoint is + // on the call stack, resetting the sync watcher will allow it to exit + // when the call stack unwinds to that frame. + sync_watcher_.reset(); + } + } + + void EnsureSyncWatcherExists() { + DCHECK(task_runner_->BelongsToCurrentThread()); + if (sync_watcher_) + return; + + { + MayAutoLock locker(&router_->lock_); + if (!sync_message_event_) { + sync_message_event_.emplace( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + if (sync_message_event_signaled_) + sync_message_event_->Signal(); + } + } + sync_watcher_.reset( + new SyncEventWatcher(&sync_message_event_.value(), + base::Bind(&InterfaceEndpoint::OnSyncEventSignaled, + base::Unretained(this)))); + } + + // --------------------------------------------------------------------------- + // The following members are safe to access from any threads. + + MultiplexRouter* const router_; + const InterfaceId id_; + + // --------------------------------------------------------------------------- + // The following members are accessed under the router's lock. + + // Whether the endpoint has been closed. + bool closed_; + // Whether the peer endpoint has been closed. + bool peer_closed_; + + // Whether there is already a ScopedInterfaceEndpointHandle created for this + // endpoint. + bool handle_created_; + + base::Optional<DisconnectReason> disconnect_reason_; + + // The task runner on which |client_|'s methods can be called. + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + // Not owned. It is null if no client is attached to this endpoint. + InterfaceEndpointClient* client_; + + // An event used to signal that sync messages are available. The event is + // initialized under the router's lock and remains unchanged afterwards. It + // may be accessed outside of the router's lock later. + base::Optional<base::WaitableEvent> sync_message_event_; + bool sync_message_event_signaled_ = false; + + // --------------------------------------------------------------------------- + // The following members are only valid while a client is attached. They are + // used exclusively on the client's thread. They may be accessed outside of + // the router's lock. + + std::unique_ptr<SyncEventWatcher> sync_watcher_; + + DISALLOW_COPY_AND_ASSIGN(InterfaceEndpoint); +}; + +// MessageWrapper objects are always destroyed under the router's lock. On +// destruction, if the message it wrappers contains +// ScopedInterfaceEndpointHandles (which cannot be destructed under the +// router's lock), the wrapper unlocks to clean them up. +class MultiplexRouter::MessageWrapper { + public: + MessageWrapper() = default; + + MessageWrapper(MultiplexRouter* router, Message message) + : router_(router), value_(std::move(message)) {} + + MessageWrapper(MessageWrapper&& other) + : router_(other.router_), value_(std::move(other.value_)) {} + + ~MessageWrapper() { + if (value_.associated_endpoint_handles()->empty()) + return; + + router_->AssertLockAcquired(); + { + MayAutoUnlock unlocker(&router_->lock_); + value_.mutable_associated_endpoint_handles()->clear(); + } + } + + MessageWrapper& operator=(MessageWrapper&& other) { + router_ = other.router_; + value_ = std::move(other.value_); + return *this; + } + + Message& value() { return value_; } + + private: + MultiplexRouter* router_ = nullptr; + Message value_; + + DISALLOW_COPY_AND_ASSIGN(MessageWrapper); +}; + +struct MultiplexRouter::Task { + public: + // Doesn't take ownership of |message| but takes its contents. + static std::unique_ptr<Task> CreateMessageTask( + MessageWrapper message_wrapper) { + Task* task = new Task(MESSAGE); + task->message_wrapper = std::move(message_wrapper); + return base::WrapUnique(task); + } + static std::unique_ptr<Task> CreateNotifyErrorTask( + InterfaceEndpoint* endpoint) { + Task* task = new Task(NOTIFY_ERROR); + task->endpoint_to_notify = endpoint; + return base::WrapUnique(task); + } + + ~Task() {} + + bool IsMessageTask() const { return type == MESSAGE; } + bool IsNotifyErrorTask() const { return type == NOTIFY_ERROR; } + + MessageWrapper message_wrapper; + scoped_refptr<InterfaceEndpoint> endpoint_to_notify; + + enum Type { MESSAGE, NOTIFY_ERROR }; + Type type; + + private: + explicit Task(Type in_type) : type(in_type) {} + + DISALLOW_COPY_AND_ASSIGN(Task); +}; + +MultiplexRouter::MultiplexRouter( + ScopedMessagePipeHandle message_pipe, + Config config, + bool set_interface_id_namesapce_bit, + scoped_refptr<base::SingleThreadTaskRunner> runner) + : set_interface_id_namespace_bit_(set_interface_id_namesapce_bit), + task_runner_(runner), + header_validator_(nullptr), + filters_(this), + connector_(std::move(message_pipe), + config == MULTI_INTERFACE ? Connector::MULTI_THREADED_SEND + : Connector::SINGLE_THREADED_SEND, + std::move(runner)), + control_message_handler_(this), + control_message_proxy_(&connector_), + next_interface_id_value_(1), + posted_to_process_tasks_(false), + encountered_error_(false), + paused_(false), + testing_mode_(false) { + DCHECK(task_runner_->BelongsToCurrentThread()); + + if (config == MULTI_INTERFACE) + lock_.emplace(); + + if (config == SINGLE_INTERFACE_WITH_SYNC_METHODS || + config == MULTI_INTERFACE) { + // Always participate in sync handle watching in multi-interface mode, + // because even if it doesn't expect sync requests during sync handle + // watching, it may still need to dispatch messages to associated endpoints + // on a different thread. + connector_.AllowWokenUpBySyncWatchOnSameThread(); + } + connector_.set_incoming_receiver(&filters_); + connector_.set_connection_error_handler( + base::Bind(&MultiplexRouter::OnPipeConnectionError, + base::Unretained(this))); + + std::unique_ptr<MessageHeaderValidator> header_validator = + base::MakeUnique<MessageHeaderValidator>(); + header_validator_ = header_validator.get(); + filters_.Append(std::move(header_validator)); +} + +MultiplexRouter::~MultiplexRouter() { + MayAutoLock locker(&lock_); + + sync_message_tasks_.clear(); + tasks_.clear(); + + for (auto iter = endpoints_.begin(); iter != endpoints_.end();) { + InterfaceEndpoint* endpoint = iter->second.get(); + // Increment the iterator before calling UpdateEndpointStateMayRemove() + // because it may remove the corresponding value from the map. + ++iter; + + if (!endpoint->closed()) { + // This happens when a NotifyPeerEndpointClosed message been received, but + // the interface ID hasn't been used to create local endpoint handle. + DCHECK(!endpoint->client()); + DCHECK(endpoint->peer_closed()); + UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED); + } else { + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + } + } + + DCHECK(endpoints_.empty()); +} + +void MultiplexRouter::SetMasterInterfaceName(const char* name) { + DCHECK(thread_checker_.CalledOnValidThread()); + header_validator_->SetDescription( + std::string(name) + " [master] MessageHeaderValidator"); + control_message_handler_.SetDescription( + std::string(name) + " [master] PipeControlMessageHandler"); + connector_.SetWatcherHeapProfilerTag(name); +} + +InterfaceId MultiplexRouter::AssociateInterface( + ScopedInterfaceEndpointHandle handle_to_send) { + if (!handle_to_send.pending_association()) + return kInvalidInterfaceId; + + uint32_t id = 0; + { + MayAutoLock locker(&lock_); + do { + if (next_interface_id_value_ >= kInterfaceIdNamespaceMask) + next_interface_id_value_ = 1; + id = next_interface_id_value_++; + if (set_interface_id_namespace_bit_) + id |= kInterfaceIdNamespaceMask; + } while (base::ContainsKey(endpoints_, id)); + + InterfaceEndpoint* endpoint = new InterfaceEndpoint(this, id); + endpoints_[id] = endpoint; + if (encountered_error_) + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + endpoint->set_handle_created(); + } + + if (!NotifyAssociation(&handle_to_send, id)) { + // The peer handle of |handle_to_send|, which is supposed to join this + // associated group, has been closed. + { + MayAutoLock locker(&lock_); + InterfaceEndpoint* endpoint = FindEndpoint(id); + if (endpoint) + UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED); + } + + control_message_proxy_.NotifyPeerEndpointClosed( + id, handle_to_send.disconnect_reason()); + } + return id; +} + +ScopedInterfaceEndpointHandle MultiplexRouter::CreateLocalEndpointHandle( + InterfaceId id) { + if (!IsValidInterfaceId(id)) + return ScopedInterfaceEndpointHandle(); + + MayAutoLock locker(&lock_); + bool inserted = false; + InterfaceEndpoint* endpoint = FindOrInsertEndpoint(id, &inserted); + if (inserted) { + DCHECK(!endpoint->handle_created()); + + if (encountered_error_) + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + } else { + // If the endpoint already exist, it is because we have received a + // notification that the peer endpoint has closed. + CHECK(!endpoint->closed()); + CHECK(endpoint->peer_closed()); + + if (endpoint->handle_created()) + return ScopedInterfaceEndpointHandle(); + } + + endpoint->set_handle_created(); + return CreateScopedInterfaceEndpointHandle(id); +} + +void MultiplexRouter::CloseEndpointHandle( + InterfaceId id, + const base::Optional<DisconnectReason>& reason) { + if (!IsValidInterfaceId(id)) + return; + + MayAutoLock locker(&lock_); + DCHECK(base::ContainsKey(endpoints_, id)); + InterfaceEndpoint* endpoint = endpoints_[id].get(); + DCHECK(!endpoint->client()); + DCHECK(!endpoint->closed()); + UpdateEndpointStateMayRemove(endpoint, ENDPOINT_CLOSED); + + if (!IsMasterInterfaceId(id) || reason) { + MayAutoUnlock unlocker(&lock_); + control_message_proxy_.NotifyPeerEndpointClosed(id, reason); + } + + ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr); +} + +InterfaceEndpointController* MultiplexRouter::AttachEndpointClient( + const ScopedInterfaceEndpointHandle& handle, + InterfaceEndpointClient* client, + scoped_refptr<base::SingleThreadTaskRunner> runner) { + const InterfaceId id = handle.id(); + + DCHECK(IsValidInterfaceId(id)); + DCHECK(client); + + MayAutoLock locker(&lock_); + DCHECK(base::ContainsKey(endpoints_, id)); + + InterfaceEndpoint* endpoint = endpoints_[id].get(); + endpoint->AttachClient(client, std::move(runner)); + + if (endpoint->peer_closed()) + tasks_.push_back(Task::CreateNotifyErrorTask(endpoint)); + ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr); + + return endpoint; +} + +void MultiplexRouter::DetachEndpointClient( + const ScopedInterfaceEndpointHandle& handle) { + const InterfaceId id = handle.id(); + + DCHECK(IsValidInterfaceId(id)); + + MayAutoLock locker(&lock_); + DCHECK(base::ContainsKey(endpoints_, id)); + + InterfaceEndpoint* endpoint = endpoints_[id].get(); + endpoint->DetachClient(); +} + +void MultiplexRouter::RaiseError() { + if (task_runner_->BelongsToCurrentThread()) { + connector_.RaiseError(); + } else { + task_runner_->PostTask(FROM_HERE, + base::Bind(&MultiplexRouter::RaiseError, this)); + } +} + +void MultiplexRouter::CloseMessagePipe() { + DCHECK(thread_checker_.CalledOnValidThread()); + connector_.CloseMessagePipe(); + // CloseMessagePipe() above won't trigger connection error handler. + // Explicitly call OnPipeConnectionError() so that associated endpoints will + // get notified. + OnPipeConnectionError(); +} + +void MultiplexRouter::PauseIncomingMethodCallProcessing() { + DCHECK(thread_checker_.CalledOnValidThread()); + connector_.PauseIncomingMethodCallProcessing(); + + MayAutoLock locker(&lock_); + paused_ = true; + + for (auto iter = endpoints_.begin(); iter != endpoints_.end(); ++iter) + iter->second->ResetSyncMessageSignal(); +} + +void MultiplexRouter::ResumeIncomingMethodCallProcessing() { + DCHECK(thread_checker_.CalledOnValidThread()); + connector_.ResumeIncomingMethodCallProcessing(); + + MayAutoLock locker(&lock_); + paused_ = false; + + for (auto iter = endpoints_.begin(); iter != endpoints_.end(); ++iter) { + auto sync_iter = sync_message_tasks_.find(iter->first); + if (iter->second->peer_closed() || + (sync_iter != sync_message_tasks_.end() && + !sync_iter->second.empty())) { + iter->second->SignalSyncMessageEvent(); + } + } + + ProcessTasks(NO_DIRECT_CLIENT_CALLS, nullptr); +} + +bool MultiplexRouter::HasAssociatedEndpoints() const { + DCHECK(thread_checker_.CalledOnValidThread()); + MayAutoLock locker(&lock_); + + if (endpoints_.size() > 1) + return true; + if (endpoints_.size() == 0) + return false; + + return !base::ContainsKey(endpoints_, kMasterInterfaceId); +} + +void MultiplexRouter::EnableTestingMode() { + DCHECK(thread_checker_.CalledOnValidThread()); + MayAutoLock locker(&lock_); + + testing_mode_ = true; + connector_.set_enforce_errors_from_incoming_receiver(false); +} + +bool MultiplexRouter::Accept(Message* message) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!message->DeserializeAssociatedEndpointHandles(this)) + return false; + + scoped_refptr<MultiplexRouter> protector(this); + MayAutoLock locker(&lock_); + + DCHECK(!paused_); + + ClientCallBehavior client_call_behavior = + connector_.during_sync_handle_watcher_callback() + ? ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES + : ALLOW_DIRECT_CLIENT_CALLS; + + bool processed = + tasks_.empty() && ProcessIncomingMessage(message, client_call_behavior, + connector_.task_runner()); + + if (!processed) { + // Either the task queue is not empty or we cannot process the message + // directly. In both cases, there is no need to call ProcessTasks(). + tasks_.push_back( + Task::CreateMessageTask(MessageWrapper(this, std::move(*message)))); + Task* task = tasks_.back().get(); + + if (task->message_wrapper.value().has_flag(Message::kFlagIsSync)) { + InterfaceId id = task->message_wrapper.value().interface_id(); + sync_message_tasks_[id].push_back(task); + InterfaceEndpoint* endpoint = FindEndpoint(id); + if (endpoint) + endpoint->SignalSyncMessageEvent(); + } + } else if (!tasks_.empty()) { + // Processing the message may result in new tasks (for error notification) + // being added to the queue. In this case, we have to attempt to process the + // tasks. + ProcessTasks(client_call_behavior, connector_.task_runner()); + } + + // Always return true. If we see errors during message processing, we will + // explicitly call Connector::RaiseError() to disconnect the message pipe. + return true; +} + +bool MultiplexRouter::OnPeerAssociatedEndpointClosed( + InterfaceId id, + const base::Optional<DisconnectReason>& reason) { + DCHECK(!IsMasterInterfaceId(id) || reason); + + MayAutoLock locker(&lock_); + InterfaceEndpoint* endpoint = FindOrInsertEndpoint(id, nullptr); + + if (reason) + endpoint->set_disconnect_reason(reason); + + // It is possible that this endpoint has been set as peer closed. That is + // because when the message pipe is closed, all the endpoints are updated with + // PEER_ENDPOINT_CLOSED. We continue to process remaining tasks in the queue, + // as long as there are refs keeping the router alive. If there is a + // PeerAssociatedEndpointClosedEvent control message in the queue, we will get + // here and see that the endpoint has been marked as peer closed. + if (!endpoint->peer_closed()) { + if (endpoint->client()) + tasks_.push_back(Task::CreateNotifyErrorTask(endpoint)); + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + } + + // No need to trigger a ProcessTasks() because it is already on the stack. + + return true; +} + +void MultiplexRouter::OnPipeConnectionError() { + DCHECK(thread_checker_.CalledOnValidThread()); + + scoped_refptr<MultiplexRouter> protector(this); + MayAutoLock locker(&lock_); + + encountered_error_ = true; + + for (auto iter = endpoints_.begin(); iter != endpoints_.end();) { + InterfaceEndpoint* endpoint = iter->second.get(); + // Increment the iterator before calling UpdateEndpointStateMayRemove() + // because it may remove the corresponding value from the map. + ++iter; + + if (endpoint->client()) + tasks_.push_back(Task::CreateNotifyErrorTask(endpoint)); + + UpdateEndpointStateMayRemove(endpoint, PEER_ENDPOINT_CLOSED); + } + + ProcessTasks(connector_.during_sync_handle_watcher_callback() + ? ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES + : ALLOW_DIRECT_CLIENT_CALLS, + connector_.task_runner()); +} + +void MultiplexRouter::ProcessTasks( + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner) { + AssertLockAcquired(); + + if (posted_to_process_tasks_) + return; + + while (!tasks_.empty() && !paused_) { + std::unique_ptr<Task> task(std::move(tasks_.front())); + tasks_.pop_front(); + + InterfaceId id = kInvalidInterfaceId; + bool sync_message = + task->IsMessageTask() && !task->message_wrapper.value().IsNull() && + task->message_wrapper.value().has_flag(Message::kFlagIsSync); + if (sync_message) { + id = task->message_wrapper.value().interface_id(); + auto& sync_message_queue = sync_message_tasks_[id]; + DCHECK_EQ(task.get(), sync_message_queue.front()); + sync_message_queue.pop_front(); + } + + bool processed = + task->IsNotifyErrorTask() + ? ProcessNotifyErrorTask(task.get(), client_call_behavior, + current_task_runner) + : ProcessIncomingMessage(&task->message_wrapper.value(), + client_call_behavior, current_task_runner); + + if (!processed) { + if (sync_message) { + auto& sync_message_queue = sync_message_tasks_[id]; + sync_message_queue.push_front(task.get()); + } + tasks_.push_front(std::move(task)); + break; + } else { + if (sync_message) { + auto iter = sync_message_tasks_.find(id); + if (iter != sync_message_tasks_.end() && iter->second.empty()) + sync_message_tasks_.erase(iter); + } + } + } +} + +bool MultiplexRouter::ProcessFirstSyncMessageForEndpoint(InterfaceId id) { + AssertLockAcquired(); + + auto iter = sync_message_tasks_.find(id); + if (iter == sync_message_tasks_.end()) + return false; + + if (paused_) + return true; + + MultiplexRouter::Task* task = iter->second.front(); + iter->second.pop_front(); + + DCHECK(task->IsMessageTask()); + MessageWrapper message_wrapper = std::move(task->message_wrapper); + + // Note: after this call, |task| and |iter| may be invalidated. + bool processed = ProcessIncomingMessage( + &message_wrapper.value(), ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES, + nullptr); + DCHECK(processed); + + iter = sync_message_tasks_.find(id); + if (iter == sync_message_tasks_.end()) + return false; + + if (iter->second.empty()) { + sync_message_tasks_.erase(iter); + return false; + } + + return true; +} + +bool MultiplexRouter::ProcessNotifyErrorTask( + Task* task, + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner) { + DCHECK(!current_task_runner || current_task_runner->BelongsToCurrentThread()); + DCHECK(!paused_); + + AssertLockAcquired(); + InterfaceEndpoint* endpoint = task->endpoint_to_notify.get(); + if (!endpoint->client()) + return true; + + if (client_call_behavior != ALLOW_DIRECT_CLIENT_CALLS || + endpoint->task_runner() != current_task_runner) { + MaybePostToProcessTasks(endpoint->task_runner()); + return false; + } + + DCHECK(endpoint->task_runner()->BelongsToCurrentThread()); + + InterfaceEndpointClient* client = endpoint->client(); + base::Optional<DisconnectReason> disconnect_reason( + endpoint->disconnect_reason()); + + { + // We must unlock before calling into |client| because it may call this + // object within NotifyError(). Holding the lock will lead to deadlock. + // + // It is safe to call into |client| without the lock. Because |client| is + // always accessed on the same thread, including DetachEndpointClient(). + MayAutoUnlock unlocker(&lock_); + client->NotifyError(disconnect_reason); + } + return true; +} + +bool MultiplexRouter::ProcessIncomingMessage( + Message* message, + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner) { + DCHECK(!current_task_runner || current_task_runner->BelongsToCurrentThread()); + DCHECK(!paused_); + DCHECK(message); + AssertLockAcquired(); + + if (message->IsNull()) { + // This is a sync message and has been processed during sync handle + // watching. + return true; + } + + if (PipeControlMessageHandler::IsPipeControlMessage(message)) { + bool result = false; + + { + MayAutoUnlock unlocker(&lock_); + result = control_message_handler_.Accept(message); + } + + if (!result) + RaiseErrorInNonTestingMode(); + + return true; + } + + InterfaceId id = message->interface_id(); + DCHECK(IsValidInterfaceId(id)); + + InterfaceEndpoint* endpoint = FindEndpoint(id); + if (!endpoint || endpoint->closed()) + return true; + + if (!endpoint->client()) { + // We need to wait until a client is attached in order to dispatch further + // messages. + return false; + } + + bool can_direct_call; + if (message->has_flag(Message::kFlagIsSync)) { + can_direct_call = client_call_behavior != NO_DIRECT_CLIENT_CALLS && + endpoint->task_runner()->BelongsToCurrentThread(); + } else { + can_direct_call = client_call_behavior == ALLOW_DIRECT_CLIENT_CALLS && + endpoint->task_runner() == current_task_runner; + } + + if (!can_direct_call) { + MaybePostToProcessTasks(endpoint->task_runner()); + return false; + } + + DCHECK(endpoint->task_runner()->BelongsToCurrentThread()); + + InterfaceEndpointClient* client = endpoint->client(); + bool result = false; + { + // We must unlock before calling into |client| because it may call this + // object within HandleIncomingMessage(). Holding the lock will lead to + // deadlock. + // + // It is safe to call into |client| without the lock. Because |client| is + // always accessed on the same thread, including DetachEndpointClient(). + MayAutoUnlock unlocker(&lock_); + result = client->HandleIncomingMessage(message); + } + if (!result) + RaiseErrorInNonTestingMode(); + + return true; +} + +void MultiplexRouter::MaybePostToProcessTasks( + base::SingleThreadTaskRunner* task_runner) { + AssertLockAcquired(); + if (posted_to_process_tasks_) + return; + + posted_to_process_tasks_ = true; + posted_to_task_runner_ = task_runner; + task_runner->PostTask( + FROM_HERE, base::Bind(&MultiplexRouter::LockAndCallProcessTasks, this)); +} + +void MultiplexRouter::LockAndCallProcessTasks() { + // There is no need to hold a ref to this class in this case because this is + // always called using base::Bind(), which holds a ref. + MayAutoLock locker(&lock_); + posted_to_process_tasks_ = false; + scoped_refptr<base::SingleThreadTaskRunner> runner( + std::move(posted_to_task_runner_)); + ProcessTasks(ALLOW_DIRECT_CLIENT_CALLS, runner.get()); +} + +void MultiplexRouter::UpdateEndpointStateMayRemove( + InterfaceEndpoint* endpoint, + EndpointStateUpdateType type) { + if (type == ENDPOINT_CLOSED) { + endpoint->set_closed(); + } else { + endpoint->set_peer_closed(); + // If the interface endpoint is performing a sync watch, this makes sure + // it is notified and eventually exits the sync watch. + endpoint->SignalSyncMessageEvent(); + } + if (endpoint->closed() && endpoint->peer_closed()) + endpoints_.erase(endpoint->id()); +} + +void MultiplexRouter::RaiseErrorInNonTestingMode() { + AssertLockAcquired(); + if (!testing_mode_) + RaiseError(); +} + +MultiplexRouter::InterfaceEndpoint* MultiplexRouter::FindOrInsertEndpoint( + InterfaceId id, + bool* inserted) { + AssertLockAcquired(); + // Either |inserted| is nullptr or it points to a boolean initialized as + // false. + DCHECK(!inserted || !*inserted); + + InterfaceEndpoint* endpoint = FindEndpoint(id); + if (!endpoint) { + endpoint = new InterfaceEndpoint(this, id); + endpoints_[id] = endpoint; + if (inserted) + *inserted = true; + } + + return endpoint; +} + +MultiplexRouter::InterfaceEndpoint* MultiplexRouter::FindEndpoint( + InterfaceId id) { + AssertLockAcquired(); + auto iter = endpoints_.find(id); + return iter != endpoints_.end() ? iter->second.get() : nullptr; +} + +void MultiplexRouter::AssertLockAcquired() { +#if DCHECK_IS_ON() + if (lock_) + lock_->AssertAcquired(); +#endif +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/multiplex_router.h b/mojo/public/cpp/bindings/lib/multiplex_router.h new file mode 100644 index 0000000000..cac138bcb7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/multiplex_router.h @@ -0,0 +1,275 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_ + +#include <stdint.h> + +#include <deque> +#include <map> +#include <memory> +#include <string> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/connector.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/bindings/pipe_control_message_handler.h" +#include "mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h" +#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace mojo { + +namespace internal { + +// MultiplexRouter supports routing messages for multiple interfaces over a +// single message pipe. +// +// It is created on the thread where the master interface of the message pipe +// lives. Although it is ref-counted, it is guarateed to be destructed on the +// same thread. +// Some public methods are only allowed to be called on the creating thread; +// while the others are safe to call from any threads. Please see the method +// comments for more details. +// +// NOTE: CloseMessagePipe() or PassMessagePipe() MUST be called on |runner|'s +// thread before this object is destroyed. +class MOJO_CPP_BINDINGS_EXPORT MultiplexRouter + : NON_EXPORTED_BASE(public MessageReceiver), + public AssociatedGroupController, + NON_EXPORTED_BASE(public PipeControlMessageHandlerDelegate) { + public: + enum Config { + // There is only the master interface running on this router. Please note + // that because of interface versioning, the other side of the message pipe + // may use a newer master interface definition which passes associated + // interfaces. In that case, this router may still receive pipe control + // messages or messages targetting associated interfaces. + SINGLE_INTERFACE, + // Similar to the mode above, there is only the master interface running on + // this router. Besides, the master interface has sync methods. + SINGLE_INTERFACE_WITH_SYNC_METHODS, + // There may be associated interfaces running on this router. + MULTI_INTERFACE + }; + + // If |set_interface_id_namespace_bit| is true, the interface IDs generated by + // this router will have the highest bit set. + MultiplexRouter(ScopedMessagePipeHandle message_pipe, + Config config, + bool set_interface_id_namespace_bit, + scoped_refptr<base::SingleThreadTaskRunner> runner); + + // Sets the master interface name for this router. Only used when reporting + // message header or control message validation errors. + // |name| must be a string literal. + void SetMasterInterfaceName(const char* name); + + // --------------------------------------------------------------------------- + // The following public methods are safe to call from any threads. + + // AssociatedGroupController implementation: + InterfaceId AssociateInterface( + ScopedInterfaceEndpointHandle handle_to_send) override; + ScopedInterfaceEndpointHandle CreateLocalEndpointHandle( + InterfaceId id) override; + void CloseEndpointHandle( + InterfaceId id, + const base::Optional<DisconnectReason>& reason) override; + InterfaceEndpointController* AttachEndpointClient( + const ScopedInterfaceEndpointHandle& handle, + InterfaceEndpointClient* endpoint_client, + scoped_refptr<base::SingleThreadTaskRunner> runner) override; + void DetachEndpointClient( + const ScopedInterfaceEndpointHandle& handle) override; + void RaiseError() override; + + // --------------------------------------------------------------------------- + // The following public methods are called on the creating thread. + + // Please note that this method shouldn't be called unless it results from an + // explicit request of the user of bindings (e.g., the user sets an + // InterfacePtr to null or closes a Binding). + void CloseMessagePipe(); + + // Extracts the underlying message pipe. + ScopedMessagePipeHandle PassMessagePipe() { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!HasAssociatedEndpoints()); + return connector_.PassMessagePipe(); + } + + // Blocks the current thread until the first incoming message, or |deadline|. + bool WaitForIncomingMessage(MojoDeadline deadline) { + DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.WaitForIncomingMessage(deadline); + } + + // See Binding for details of pause/resume. + void PauseIncomingMethodCallProcessing(); + void ResumeIncomingMethodCallProcessing(); + + // Whether there are any associated interfaces running currently. + bool HasAssociatedEndpoints() const; + + // Sets this object to testing mode. + // In testing mode, the object doesn't disconnect the underlying message pipe + // when it receives unexpected or invalid messages. + void EnableTestingMode(); + + // Is the router bound to a message pipe handle? + bool is_valid() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.is_valid(); + } + + // TODO(yzshen): consider removing this getter. + MessagePipeHandle handle() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return connector_.handle(); + } + + bool SimulateReceivingMessageForTesting(Message* message) { + return filters_.Accept(message); + } + + private: + class InterfaceEndpoint; + class MessageWrapper; + struct Task; + + ~MultiplexRouter() override; + + // MessageReceiver implementation: + bool Accept(Message* message) override; + + // PipeControlMessageHandlerDelegate implementation: + bool OnPeerAssociatedEndpointClosed( + InterfaceId id, + const base::Optional<DisconnectReason>& reason) override; + + void OnPipeConnectionError(); + + // Specifies whether we are allowed to directly call into + // InterfaceEndpointClient (given that we are already on the same thread as + // the client). + enum ClientCallBehavior { + // Don't call any InterfaceEndpointClient methods directly. + NO_DIRECT_CLIENT_CALLS, + // Only call InterfaceEndpointClient::HandleIncomingMessage directly to + // handle sync messages. + ALLOW_DIRECT_CLIENT_CALLS_FOR_SYNC_MESSAGES, + // Allow to call any InterfaceEndpointClient methods directly. + ALLOW_DIRECT_CLIENT_CALLS + }; + + // Processes enqueued tasks (incoming messages and error notifications). + // |current_task_runner| is only used when |client_call_behavior| is + // ALLOW_DIRECT_CLIENT_CALLS to determine whether we are on the right task + // runner to make client calls for async messages or connection error + // notifications. + // + // Note: Because calling into InterfaceEndpointClient may lead to destruction + // of this object, if direct calls are allowed, the caller needs to hold on to + // a ref outside of |lock_| before calling this method. + void ProcessTasks(ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner); + + // Processes the first queued sync message for the endpoint corresponding to + // |id|; returns whether there are more sync messages for that endpoint in the + // queue. + // + // This method is only used by enpoints during sync watching. Therefore, not + // all sync messages are handled by it. + bool ProcessFirstSyncMessageForEndpoint(InterfaceId id); + + // Returns true to indicate that |task|/|message| has been processed. + bool ProcessNotifyErrorTask( + Task* task, + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner); + bool ProcessIncomingMessage( + Message* message, + ClientCallBehavior client_call_behavior, + base::SingleThreadTaskRunner* current_task_runner); + + void MaybePostToProcessTasks(base::SingleThreadTaskRunner* task_runner); + void LockAndCallProcessTasks(); + + // Updates the state of |endpoint|. If both the endpoint and its peer have + // been closed, removes it from |endpoints_|. + // NOTE: The method may invalidate |endpoint|. + enum EndpointStateUpdateType { ENDPOINT_CLOSED, PEER_ENDPOINT_CLOSED }; + void UpdateEndpointStateMayRemove(InterfaceEndpoint* endpoint, + EndpointStateUpdateType type); + + void RaiseErrorInNonTestingMode(); + + InterfaceEndpoint* FindOrInsertEndpoint(InterfaceId id, bool* inserted); + InterfaceEndpoint* FindEndpoint(InterfaceId id); + + void AssertLockAcquired(); + + // Whether to set the namespace bit when generating interface IDs. Please see + // comments of kInterfaceIdNamespaceMask. + const bool set_interface_id_namespace_bit_; + + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + // Owned by |filters_| below. + MessageHeaderValidator* header_validator_; + + FilterChain filters_; + Connector connector_; + + base::ThreadChecker thread_checker_; + + // Protects the following members. + // Not set in Config::SINGLE_INTERFACE* mode. + mutable base::Optional<base::Lock> lock_; + PipeControlMessageHandler control_message_handler_; + + // NOTE: It is unsafe to call into this object while holding |lock_|. + PipeControlMessageProxy control_message_proxy_; + + std::map<InterfaceId, scoped_refptr<InterfaceEndpoint>> endpoints_; + uint32_t next_interface_id_value_; + + std::deque<std::unique_ptr<Task>> tasks_; + // It refers to tasks in |tasks_| and doesn't own any of them. + std::map<InterfaceId, std::deque<Task*>> sync_message_tasks_; + + bool posted_to_process_tasks_; + scoped_refptr<base::SingleThreadTaskRunner> posted_to_task_runner_; + + bool encountered_error_; + + bool paused_; + + bool testing_mode_; + + DISALLOW_COPY_AND_ASSIGN(MultiplexRouter); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_MULTIPLEX_ROUTER_H_ diff --git a/mojo/public/cpp/bindings/lib/native_enum_data.h b/mojo/public/cpp/bindings/lib/native_enum_data.h new file mode 100644 index 0000000000..dcafce2815 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_enum_data.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_ + +namespace mojo { +namespace internal { + +class ValidationContext; + +class NativeEnum_Data { + public: + static bool const kIsExtensible = true; + + static bool IsKnownValue(int32_t value) { return false; } + + static bool Validate(int32_t value, + ValidationContext* validation_context) { return true; } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_DATA_H_ diff --git a/mojo/public/cpp/bindings/lib/native_enum_serialization.h b/mojo/public/cpp/bindings/lib/native_enum_serialization.h new file mode 100644 index 0000000000..4faf957c58 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_enum_serialization.h @@ -0,0 +1,82 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <type_traits> + +#include "base/logging.h" +#include "base/pickle.h" +#include "ipc/ipc_param_traits.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/native_enum.h" + +namespace mojo { +namespace internal { + +template <typename MaybeConstUserType> +struct NativeEnumSerializerImpl { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = IPC::ParamTraits<UserType>; + + // IPC_ENUM_TRAITS* macros serialize enum as int, make sure that fits into + // mojo native-only enum. + static_assert(sizeof(NativeEnum) >= sizeof(int), + "Cannot store the serialization result in NativeEnum."); + + static void Serialize(UserType input, int32_t* output) { + base::Pickle pickle; + Traits::Write(&pickle, input); + + CHECK_GE(sizeof(int32_t), pickle.payload_size()); + *output = 0; + memcpy(reinterpret_cast<char*>(output), pickle.payload(), + pickle.payload_size()); + } + + struct PickleData { + uint32_t payload_size; + int32_t value; + }; + static_assert(sizeof(PickleData) == 8, "PickleData size mismatch."); + + static bool Deserialize(int32_t input, UserType* output) { + PickleData data = {sizeof(int32_t), input}; + base::Pickle pickle_view(reinterpret_cast<const char*>(&data), + sizeof(PickleData)); + base::PickleIterator iter(pickle_view); + return Traits::Read(&pickle_view, &iter, output); + } +}; + +struct UnmappedNativeEnumSerializerImpl { + static void Serialize(NativeEnum input, int32_t* output) { + *output = static_cast<int32_t>(input); + } + static bool Deserialize(int32_t input, NativeEnum* output) { + *output = static_cast<NativeEnum>(input); + return true; + } +}; + +template <> +struct NativeEnumSerializerImpl<NativeEnum> + : public UnmappedNativeEnumSerializerImpl {}; + +template <> +struct NativeEnumSerializerImpl<const NativeEnum> + : public UnmappedNativeEnumSerializerImpl {}; + +template <typename MaybeConstUserType> +struct Serializer<NativeEnum, MaybeConstUserType> + : public NativeEnumSerializerImpl<MaybeConstUserType> {}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_ENUM_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/native_struct.cc b/mojo/public/cpp/bindings/lib/native_struct.cc new file mode 100644 index 0000000000..7b1a1a6c59 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct.cc @@ -0,0 +1,34 @@ +// 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. + +#include "mojo/public/cpp/bindings/native_struct.h" + +#include "mojo/public/cpp/bindings/lib/hash_util.h" + +namespace mojo { + +// static +NativeStructPtr NativeStruct::New() { + return NativeStructPtr(base::in_place); +} + +NativeStruct::NativeStruct() {} + +NativeStruct::~NativeStruct() {} + +NativeStructPtr NativeStruct::Clone() const { + NativeStructPtr rv(New()); + rv->data = data; + return rv; +} + +bool NativeStruct::Equals(const NativeStruct& other) const { + return data == other.data; +} + +size_t NativeStruct::Hash(size_t seed) const { + return internal::Hash(seed, data); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/native_struct_data.cc b/mojo/public/cpp/bindings/lib/native_struct_data.cc new file mode 100644 index 0000000000..0e5d245692 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct_data.cc @@ -0,0 +1,22 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/native_struct_data.h" + +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" + +namespace mojo { +namespace internal { + +// static +bool NativeStruct_Data::Validate(const void* data, + ValidationContext* validation_context) { + const ContainerValidateParams data_validate_params(0, false, nullptr); + return Array_Data<uint8_t>::Validate(data, validation_context, + &data_validate_params); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/native_struct_data.h b/mojo/public/cpp/bindings/lib/native_struct_data.h new file mode 100644 index 0000000000..1c7cd81c77 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct_data.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_ + +#include <vector> + +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace internal { + +class ValidationContext; + +class MOJO_CPP_BINDINGS_EXPORT NativeStruct_Data { + public: + static bool Validate(const void* data, ValidationContext* validation_context); + + // Unlike normal structs, the memory layout is exactly the same as an array + // of uint8_t. + Array_Data<uint8_t> data; + + private: + NativeStruct_Data() = delete; + ~NativeStruct_Data() = delete; +}; + +static_assert(sizeof(Array_Data<uint8_t>) == sizeof(NativeStruct_Data), + "Mismatched NativeStruct_Data and Array_Data<uint8_t> size"); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_DATA_H_ diff --git a/mojo/public/cpp/bindings/lib/native_struct_serialization.cc b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc new file mode 100644 index 0000000000..fa0dbf3803 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.cc @@ -0,0 +1,61 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h" + +#include "mojo/public/cpp/bindings/lib/serialization.h" + +namespace mojo { +namespace internal { + +// static +size_t UnmappedNativeStructSerializerImpl::PrepareToSerialize( + const NativeStructPtr& input, + SerializationContext* context) { + if (!input) + return 0; + return internal::PrepareToSerialize<ArrayDataView<uint8_t>>(input->data, + context); +} + +// static +void UnmappedNativeStructSerializerImpl::Serialize( + const NativeStructPtr& input, + Buffer* buffer, + NativeStruct_Data** output, + SerializationContext* context) { + if (!input) { + *output = nullptr; + return; + } + + Array_Data<uint8_t>* data = nullptr; + const ContainerValidateParams params(0, false, nullptr); + internal::Serialize<ArrayDataView<uint8_t>>(input->data, buffer, &data, + ¶ms, context); + *output = reinterpret_cast<NativeStruct_Data*>(data); +} + +// static +bool UnmappedNativeStructSerializerImpl::Deserialize( + NativeStruct_Data* input, + NativeStructPtr* output, + SerializationContext* context) { + Array_Data<uint8_t>* data = reinterpret_cast<Array_Data<uint8_t>*>(input); + + NativeStructPtr result(NativeStruct::New()); + if (!internal::Deserialize<ArrayDataView<uint8_t>>(data, &result->data, + context)) { + output = nullptr; + return false; + } + if (!result->data) + *output = nullptr; + else + result.Swap(output); + return true; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/native_struct_serialization.h b/mojo/public/cpp/bindings/lib/native_struct_serialization.h new file mode 100644 index 0000000000..457435b955 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/native_struct_serialization.h @@ -0,0 +1,134 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <limits> + +#include "base/logging.h" +#include "base/pickle.h" +#include "ipc/ipc_param_traits.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/native_struct_data.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/native_struct.h" +#include "mojo/public/cpp/bindings/native_struct_data_view.h" + +namespace mojo { +namespace internal { + +template <typename MaybeConstUserType> +struct NativeStructSerializerImpl { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = IPC::ParamTraits<UserType>; + + static size_t PrepareToSerialize(MaybeConstUserType& value, + SerializationContext* context) { + base::PickleSizer sizer; + Traits::GetSize(&sizer, value); + return Align(sizer.payload_size() + sizeof(ArrayHeader)); + } + + static void Serialize(MaybeConstUserType& value, + Buffer* buffer, + NativeStruct_Data** out, + SerializationContext* context) { + base::Pickle pickle; + Traits::Write(&pickle, value); + +#if DCHECK_IS_ON() + base::PickleSizer sizer; + Traits::GetSize(&sizer, value); + DCHECK_EQ(sizer.payload_size(), pickle.payload_size()); +#endif + + size_t total_size = pickle.payload_size() + sizeof(ArrayHeader); + DCHECK_LT(total_size, std::numeric_limits<uint32_t>::max()); + + // Allocate a uint8 array, initialize its header, and copy the Pickle in. + ArrayHeader* header = + reinterpret_cast<ArrayHeader*>(buffer->Allocate(total_size)); + header->num_bytes = static_cast<uint32_t>(total_size); + header->num_elements = static_cast<uint32_t>(pickle.payload_size()); + memcpy(reinterpret_cast<char*>(header) + sizeof(ArrayHeader), + pickle.payload(), pickle.payload_size()); + + *out = reinterpret_cast<NativeStruct_Data*>(header); + } + + static bool Deserialize(NativeStruct_Data* data, + UserType* out, + SerializationContext* context) { + if (!data) + return false; + + // Construct a temporary base::Pickle view over the array data. Note that + // the Array_Data is laid out like this: + // + // [num_bytes (4 bytes)] [num_elements (4 bytes)] [elements...] + // + // and base::Pickle expects to view data like this: + // + // [payload_size (4 bytes)] [header bytes ...] [payload...] + // + // Because ArrayHeader's num_bytes includes the length of the header and + // Pickle's payload_size does not, we need to adjust the stored value + // momentarily so Pickle can view the data. + ArrayHeader* header = reinterpret_cast<ArrayHeader*>(data); + DCHECK_GE(header->num_bytes, sizeof(ArrayHeader)); + header->num_bytes -= sizeof(ArrayHeader); + + { + // Construct a view over the full Array_Data, including our hacked up + // header. Pickle will infer from this that the header is 8 bytes long, + // and the payload will contain all of the pickled bytes. + base::Pickle pickle_view(reinterpret_cast<const char*>(header), + header->num_bytes + sizeof(ArrayHeader)); + base::PickleIterator iter(pickle_view); + if (!Traits::Read(&pickle_view, &iter, out)) + return false; + } + + // Return the header to its original state. + header->num_bytes += sizeof(ArrayHeader); + + return true; + } +}; + +struct MOJO_CPP_BINDINGS_EXPORT UnmappedNativeStructSerializerImpl { + static size_t PrepareToSerialize(const NativeStructPtr& input, + SerializationContext* context); + static void Serialize(const NativeStructPtr& input, + Buffer* buffer, + NativeStruct_Data** output, + SerializationContext* context); + static bool Deserialize(NativeStruct_Data* input, + NativeStructPtr* output, + SerializationContext* context); +}; + +template <> +struct NativeStructSerializerImpl<NativeStructPtr> + : public UnmappedNativeStructSerializerImpl {}; + +template <> +struct NativeStructSerializerImpl<const NativeStructPtr> + : public UnmappedNativeStructSerializerImpl {}; + +template <typename MaybeConstUserType> +struct Serializer<NativeStructDataView, MaybeConstUserType> + : public NativeStructSerializerImpl<MaybeConstUserType> {}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_NATIVE_STRUCT_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc new file mode 100644 index 0000000000..d451c05a5f --- /dev/null +++ b/mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc @@ -0,0 +1,90 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/pipe_control_message_handler.h" + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" +#include "mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h" +#include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h" + +namespace mojo { + +PipeControlMessageHandler::PipeControlMessageHandler( + PipeControlMessageHandlerDelegate* delegate) + : delegate_(delegate) {} + +PipeControlMessageHandler::~PipeControlMessageHandler() {} + +void PipeControlMessageHandler::SetDescription(const std::string& description) { + description_ = description; +} + +// static +bool PipeControlMessageHandler::IsPipeControlMessage(const Message* message) { + return !IsValidInterfaceId(message->interface_id()); +} + +bool PipeControlMessageHandler::Accept(Message* message) { + if (!Validate(message)) + return false; + + if (message->name() == pipe_control::kRunOrClosePipeMessageId) + return RunOrClosePipe(message); + + NOTREACHED(); + return false; +} + +bool PipeControlMessageHandler::Validate(Message* message) { + internal::ValidationContext validation_context(message->payload(), + message->payload_num_bytes(), + 0, 0, message, description_); + + if (message->name() == pipe_control::kRunOrClosePipeMessageId) { + if (!internal::ValidateMessageIsRequestWithoutResponse( + message, &validation_context)) { + return false; + } + return internal::ValidateMessagePayload< + pipe_control::internal::RunOrClosePipeMessageParams_Data>( + message, &validation_context); + } + + return false; +} + +bool PipeControlMessageHandler::RunOrClosePipe(Message* message) { + internal::SerializationContext context; + pipe_control::internal::RunOrClosePipeMessageParams_Data* params = + reinterpret_cast< + pipe_control::internal::RunOrClosePipeMessageParams_Data*>( + message->mutable_payload()); + pipe_control::RunOrClosePipeMessageParamsPtr params_ptr; + internal::Deserialize<pipe_control::RunOrClosePipeMessageParamsDataView>( + params, ¶ms_ptr, &context); + + if (params_ptr->input->is_peer_associated_endpoint_closed_event()) { + const auto& event = + params_ptr->input->get_peer_associated_endpoint_closed_event(); + + base::Optional<DisconnectReason> reason; + if (event->disconnect_reason) { + reason.emplace(event->disconnect_reason->custom_reason, + event->disconnect_reason->description); + } + return delegate_->OnPeerAssociatedEndpointClosed(event->id, reason); + } + + DVLOG(1) << "Unsupported command in a RunOrClosePipe message pipe control " + << "message. Closing the pipe."; + return false; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc new file mode 100644 index 0000000000..1029c2c491 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc @@ -0,0 +1,68 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/pipe_control_message_proxy.h" + +#include <stddef.h> +#include <utility> + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h" + +namespace mojo { +namespace { + +Message ConstructRunOrClosePipeMessage( + pipe_control::RunOrClosePipeInputPtr input_ptr) { + internal::SerializationContext context; + + auto params_ptr = pipe_control::RunOrClosePipeMessageParams::New(); + params_ptr->input = std::move(input_ptr); + + size_t size = internal::PrepareToSerialize< + pipe_control::RunOrClosePipeMessageParamsDataView>(params_ptr, &context); + internal::MessageBuilder builder(pipe_control::kRunOrClosePipeMessageId, 0, + size, 0); + + pipe_control::internal::RunOrClosePipeMessageParams_Data* params = nullptr; + internal::Serialize<pipe_control::RunOrClosePipeMessageParamsDataView>( + params_ptr, builder.buffer(), ¶ms, &context); + builder.message()->set_interface_id(kInvalidInterfaceId); + return std::move(*builder.message()); +} + +} // namespace + +PipeControlMessageProxy::PipeControlMessageProxy(MessageReceiver* receiver) + : receiver_(receiver) {} + +void PipeControlMessageProxy::NotifyPeerEndpointClosed( + InterfaceId id, + const base::Optional<DisconnectReason>& reason) { + Message message(ConstructPeerEndpointClosedMessage(id, reason)); + ignore_result(receiver_->Accept(&message)); +} + +// static +Message PipeControlMessageProxy::ConstructPeerEndpointClosedMessage( + InterfaceId id, + const base::Optional<DisconnectReason>& reason) { + auto event = pipe_control::PeerAssociatedEndpointClosedEvent::New(); + event->id = id; + if (reason) { + event->disconnect_reason = pipe_control::DisconnectReason::New(); + event->disconnect_reason->custom_reason = reason->custom_reason; + event->disconnect_reason->description = reason->description; + } + + auto input = pipe_control::RunOrClosePipeInput::New(); + input->set_peer_associated_endpoint_closed_event(std::move(event)); + + return ConstructRunOrClosePipeMessage(std::move(input)); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc new file mode 100644 index 0000000000..c1345079a5 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc @@ -0,0 +1,382 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "mojo/public/cpp/bindings/associated_group_controller.h" +#include "mojo/public/cpp/bindings/lib/may_auto_lock.h" + +namespace mojo { + +// ScopedInterfaceEndpointHandle::State ---------------------------------------- + +// State could be called from multiple threads. +class ScopedInterfaceEndpointHandle::State + : public base::RefCountedThreadSafe<State> { + public: + State() = default; + + State(InterfaceId id, + scoped_refptr<AssociatedGroupController> group_controller) + : id_(id), group_controller_(group_controller) {} + + void InitPendingState(scoped_refptr<State> peer) { + DCHECK(!lock_); + DCHECK(!pending_association_); + + lock_.emplace(); + pending_association_ = true; + peer_state_ = std::move(peer); + } + + void Close(const base::Optional<DisconnectReason>& reason) { + scoped_refptr<AssociatedGroupController> cached_group_controller; + InterfaceId cached_id = kInvalidInterfaceId; + scoped_refptr<State> cached_peer_state; + + { + internal::MayAutoLock locker(&lock_); + + if (!association_event_handler_.is_null()) { + association_event_handler_.Reset(); + runner_ = nullptr; + } + + if (!pending_association_) { + if (IsValidInterfaceId(id_)) { + // Intentionally keep |group_controller_| unchanged. + // That is because the callback created by + // CreateGroupControllerGetter() could still be used after this point, + // potentially from another thread. We would like it to continue + // returning the same group controller. + // + // Imagine there is a ThreadSafeForwarder A: + // (1) On the IO thread, A's underlying associated interface pointer + // is closed. + // (2) On the proxy thread, the user makes a call on A to pass an + // associated request B_asso_req. The callback returned by + // CreateGroupControllerGetter() is used to associate B_asso_req. + // (3) On the proxy thread, the user immediately binds B_asso_ptr_info + // to B_asso_ptr and makes calls on it. + // + // If we reset |group_controller_| in step (1), step (2) won't be able + // to associate B_asso_req. Therefore, in step (3) B_asso_ptr won't be + // able to serialize associated endpoints or send message because it + // is still in "pending_association" state and doesn't have a group + // controller. + // + // We could "address" this issue by ignoring messages if there isn't a + // group controller. But the side effect is that we cannot detect + // programming errors of "using associated interface pointer before + // sending associated request". + + cached_group_controller = group_controller_; + cached_id = id_; + id_ = kInvalidInterfaceId; + } + } else { + pending_association_ = false; + cached_peer_state = std::move(peer_state_); + } + } + + if (cached_group_controller) { + cached_group_controller->CloseEndpointHandle(cached_id, reason); + } else if (cached_peer_state) { + cached_peer_state->OnPeerClosedBeforeAssociation(reason); + } + } + + void SetAssociationEventHandler(AssociationEventCallback handler) { + internal::MayAutoLock locker(&lock_); + + if (!pending_association_ && !IsValidInterfaceId(id_)) + return; + + association_event_handler_ = std::move(handler); + if (association_event_handler_.is_null()) { + runner_ = nullptr; + return; + } + + runner_ = base::ThreadTaskRunnerHandle::Get(); + if (!pending_association_) { + runner_->PostTask( + FROM_HERE, + base::Bind( + &ScopedInterfaceEndpointHandle::State::RunAssociationEventHandler, + this, runner_, ASSOCIATED)); + } else if (!peer_state_) { + runner_->PostTask( + FROM_HERE, + base::Bind( + &ScopedInterfaceEndpointHandle::State::RunAssociationEventHandler, + this, runner_, PEER_CLOSED_BEFORE_ASSOCIATION)); + } + } + + bool NotifyAssociation( + InterfaceId id, + scoped_refptr<AssociatedGroupController> peer_group_controller) { + scoped_refptr<State> cached_peer_state; + { + internal::MayAutoLock locker(&lock_); + + DCHECK(pending_association_); + pending_association_ = false; + cached_peer_state = std::move(peer_state_); + } + + if (cached_peer_state) { + cached_peer_state->OnAssociated(id, std::move(peer_group_controller)); + return true; + } + return false; + } + + bool is_valid() const { + internal::MayAutoLock locker(&lock_); + return pending_association_ || IsValidInterfaceId(id_); + } + + bool pending_association() const { + internal::MayAutoLock locker(&lock_); + return pending_association_; + } + + InterfaceId id() const { + internal::MayAutoLock locker(&lock_); + return id_; + } + + AssociatedGroupController* group_controller() const { + internal::MayAutoLock locker(&lock_); + return group_controller_.get(); + } + + const base::Optional<DisconnectReason>& disconnect_reason() const { + internal::MayAutoLock locker(&lock_); + return disconnect_reason_; + } + + private: + friend class base::RefCountedThreadSafe<State>; + + ~State() { + DCHECK(!pending_association_); + DCHECK(!IsValidInterfaceId(id_)); + } + + // Called by the peer, maybe from a different thread. + void OnAssociated(InterfaceId id, + scoped_refptr<AssociatedGroupController> group_controller) { + AssociationEventCallback handler; + { + internal::MayAutoLock locker(&lock_); + + // There may be race between Close() of endpoint A and + // NotifyPeerAssociation() of endpoint A_peer on different threads. + // Therefore, it is possible that endpoint A has been closed but it + // still gets OnAssociated() call from its peer. + if (!pending_association_) + return; + + pending_association_ = false; + peer_state_ = nullptr; + id_ = id; + group_controller_ = std::move(group_controller); + + if (!association_event_handler_.is_null()) { + if (runner_->BelongsToCurrentThread()) { + handler = std::move(association_event_handler_); + runner_ = nullptr; + } else { + runner_->PostTask(FROM_HERE, + base::Bind(&ScopedInterfaceEndpointHandle::State:: + RunAssociationEventHandler, + this, runner_, ASSOCIATED)); + } + } + } + + if (!handler.is_null()) + std::move(handler).Run(ASSOCIATED); + } + + // Called by the peer, maybe from a different thread. + void OnPeerClosedBeforeAssociation( + const base::Optional<DisconnectReason>& reason) { + AssociationEventCallback handler; + { + internal::MayAutoLock locker(&lock_); + + // There may be race between Close()/NotifyPeerAssociation() of endpoint + // A and Close() of endpoint A_peer on different threads. + // Therefore, it is possible that endpoint A is not in pending association + // state but still gets OnPeerClosedBeforeAssociation() call from its + // peer. + if (!pending_association_) + return; + + disconnect_reason_ = reason; + // NOTE: This handle itself is still pending. + peer_state_ = nullptr; + + if (!association_event_handler_.is_null()) { + if (runner_->BelongsToCurrentThread()) { + handler = std::move(association_event_handler_); + runner_ = nullptr; + } else { + runner_->PostTask( + FROM_HERE, + base::Bind(&ScopedInterfaceEndpointHandle::State:: + RunAssociationEventHandler, + this, runner_, PEER_CLOSED_BEFORE_ASSOCIATION)); + } + } + } + + if (!handler.is_null()) + std::move(handler).Run(PEER_CLOSED_BEFORE_ASSOCIATION); + } + + void RunAssociationEventHandler( + scoped_refptr<base::SingleThreadTaskRunner> posted_to_runner, + AssociationEvent event) { + AssociationEventCallback handler; + + { + internal::MayAutoLock locker(&lock_); + if (posted_to_runner == runner_) { + runner_ = nullptr; + handler = std::move(association_event_handler_); + } + } + + if (!handler.is_null()) + std::move(handler).Run(event); + } + + // Protects the following members if the handle is initially set to pending + // association. + mutable base::Optional<base::Lock> lock_; + + bool pending_association_ = false; + base::Optional<DisconnectReason> disconnect_reason_; + + scoped_refptr<State> peer_state_; + + AssociationEventCallback association_event_handler_; + scoped_refptr<base::SingleThreadTaskRunner> runner_; + + InterfaceId id_ = kInvalidInterfaceId; + scoped_refptr<AssociatedGroupController> group_controller_; + + DISALLOW_COPY_AND_ASSIGN(State); +}; + +// ScopedInterfaceEndpointHandle ----------------------------------------------- + +// static +void ScopedInterfaceEndpointHandle::CreatePairPendingAssociation( + ScopedInterfaceEndpointHandle* handle0, + ScopedInterfaceEndpointHandle* handle1) { + ScopedInterfaceEndpointHandle result0; + ScopedInterfaceEndpointHandle result1; + result0.state_->InitPendingState(result1.state_); + result1.state_->InitPendingState(result0.state_); + + *handle0 = std::move(result0); + *handle1 = std::move(result1); +} + +ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle() + : state_(new State) {} + +ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle( + ScopedInterfaceEndpointHandle&& other) + : state_(new State) { + state_.swap(other.state_); +} + +ScopedInterfaceEndpointHandle::~ScopedInterfaceEndpointHandle() { + state_->Close(base::nullopt); +} + +ScopedInterfaceEndpointHandle& ScopedInterfaceEndpointHandle::operator=( + ScopedInterfaceEndpointHandle&& other) { + reset(); + state_.swap(other.state_); + return *this; +} + +bool ScopedInterfaceEndpointHandle::is_valid() const { + return state_->is_valid(); +} + +bool ScopedInterfaceEndpointHandle::pending_association() const { + return state_->pending_association(); +} + +InterfaceId ScopedInterfaceEndpointHandle::id() const { + return state_->id(); +} + +AssociatedGroupController* ScopedInterfaceEndpointHandle::group_controller() + const { + return state_->group_controller(); +} + +const base::Optional<DisconnectReason>& +ScopedInterfaceEndpointHandle::disconnect_reason() const { + return state_->disconnect_reason(); +} + +void ScopedInterfaceEndpointHandle::SetAssociationEventHandler( + AssociationEventCallback handler) { + state_->SetAssociationEventHandler(std::move(handler)); +} + +void ScopedInterfaceEndpointHandle::reset() { + ResetInternal(base::nullopt); +} + +void ScopedInterfaceEndpointHandle::ResetWithReason( + uint32_t custom_reason, + const std::string& description) { + ResetInternal(DisconnectReason(custom_reason, description)); +} + +ScopedInterfaceEndpointHandle::ScopedInterfaceEndpointHandle( + InterfaceId id, + scoped_refptr<AssociatedGroupController> group_controller) + : state_(new State(id, std::move(group_controller))) { + DCHECK(!IsValidInterfaceId(state_->id()) || state_->group_controller()); +} + +bool ScopedInterfaceEndpointHandle::NotifyAssociation( + InterfaceId id, + scoped_refptr<AssociatedGroupController> peer_group_controller) { + return state_->NotifyAssociation(id, peer_group_controller); +} + +void ScopedInterfaceEndpointHandle::ResetInternal( + const base::Optional<DisconnectReason>& reason) { + scoped_refptr<State> new_state(new State); + state_->Close(reason); + state_.swap(new_state); +} + +base::Callback<AssociatedGroupController*()> +ScopedInterfaceEndpointHandle::CreateGroupControllerGetter() const { + // We allow this callback to be run on any thread. If this handle is created + // in non-pending state, we don't have a lock but it should still be safe + // because the group controller never changes. + return base::Bind(&State::group_controller, state_); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/serialization.h b/mojo/public/cpp/bindings/lib/serialization.h new file mode 100644 index 0000000000..2a7d288d55 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization.h @@ -0,0 +1,107 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_ + +#include <string.h> + +#include "mojo/public/cpp/bindings/array_traits_carray.h" +#include "mojo/public/cpp/bindings/array_traits_stl.h" +#include "mojo/public/cpp/bindings/lib/array_serialization.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" +#include "mojo/public/cpp/bindings/lib/handle_interface_serialization.h" +#include "mojo/public/cpp/bindings/lib/map_serialization.h" +#include "mojo/public/cpp/bindings/lib/native_enum_serialization.h" +#include "mojo/public/cpp/bindings/lib/native_struct_serialization.h" +#include "mojo/public/cpp/bindings/lib/string_serialization.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/map_traits_stl.h" +#include "mojo/public/cpp/bindings/string_traits_stl.h" +#include "mojo/public/cpp/bindings/string_traits_string16.h" +#include "mojo/public/cpp/bindings/string_traits_string_piece.h" + +namespace mojo { +namespace internal { + +template <typename MojomType, typename DataArrayType, typename UserType> +DataArrayType StructSerializeImpl(UserType* input) { + static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value, + "Unexpected type."); + + SerializationContext context; + size_t size = PrepareToSerialize<MojomType>(*input, &context); + DCHECK_EQ(size, Align(size)); + + DataArrayType result(size); + if (size == 0) + return result; + + void* result_buffer = &result.front(); + // The serialization logic requires that the buffer is 8-byte aligned. If the + // result buffer is not properly aligned, we have to do an extra copy. In + // practice, this should never happen for std::vector. + bool need_copy = !IsAligned(result_buffer); + + if (need_copy) { + // calloc sets the memory to all zero. + result_buffer = calloc(size, 1); + DCHECK(IsAligned(result_buffer)); + } + + Buffer buffer; + buffer.Initialize(result_buffer, size); + typename MojomTypeTraits<MojomType>::Data* data = nullptr; + Serialize<MojomType>(*input, &buffer, &data, &context); + + if (need_copy) { + memcpy(&result.front(), result_buffer, size); + free(result_buffer); + } + + return result; +} + +template <typename MojomType, typename DataArrayType, typename UserType> +bool StructDeserializeImpl(const DataArrayType& input, + UserType* output, + bool (*validate_func)(const void*, + ValidationContext*)) { + static_assert(BelongsTo<MojomType, MojomTypeCategory::STRUCT>::value, + "Unexpected type."); + using DataType = typename MojomTypeTraits<MojomType>::Data; + + // TODO(sammc): Use DataArrayType::empty() once WTF::Vector::empty() exists. + void* input_buffer = + input.size() == 0 + ? nullptr + : const_cast<void*>(reinterpret_cast<const void*>(&input.front())); + + // Please see comments in StructSerializeImpl. + bool need_copy = !IsAligned(input_buffer); + + if (need_copy) { + input_buffer = malloc(input.size()); + DCHECK(IsAligned(input_buffer)); + memcpy(input_buffer, &input.front(), input.size()); + } + + ValidationContext validation_context(input_buffer, input.size(), 0, 0); + bool result = false; + if (validate_func(input_buffer, &validation_context)) { + auto data = reinterpret_cast<DataType*>(input_buffer); + SerializationContext context; + result = Deserialize<MojomType>(data, output, &context); + } + + if (need_copy) + free(input_buffer); + + return result; +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/serialization_context.cc b/mojo/public/cpp/bindings/lib/serialization_context.cc new file mode 100644 index 0000000000..e2fd5c6e18 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization_context.cc @@ -0,0 +1,57 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/serialization_context.h" + +#include <limits> + +#include "base/logging.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace internal { + +SerializedHandleVector::SerializedHandleVector() {} + +SerializedHandleVector::~SerializedHandleVector() { + for (auto handle : handles_) { + if (handle.is_valid()) { + MojoResult rv = MojoClose(handle.value()); + DCHECK_EQ(rv, MOJO_RESULT_OK); + } + } +} + +Handle_Data SerializedHandleVector::AddHandle(mojo::Handle handle) { + Handle_Data data; + if (!handle.is_valid()) { + data.value = kEncodedInvalidHandleValue; + } else { + DCHECK_LT(handles_.size(), std::numeric_limits<uint32_t>::max()); + data.value = static_cast<uint32_t>(handles_.size()); + handles_.push_back(handle); + } + return data; +} + +mojo::Handle SerializedHandleVector::TakeHandle( + const Handle_Data& encoded_handle) { + if (!encoded_handle.is_valid()) + return mojo::Handle(); + DCHECK_LT(encoded_handle.value, handles_.size()); + return FetchAndReset(&handles_[encoded_handle.value]); +} + +void SerializedHandleVector::Swap(std::vector<mojo::Handle>* other) { + handles_.swap(*other); +} + +SerializationContext::SerializationContext() {} + +SerializationContext::~SerializationContext() { + DCHECK(!custom_contexts || custom_contexts->empty()); +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/serialization_context.h b/mojo/public/cpp/bindings/lib/serialization_context.h new file mode 100644 index 0000000000..a34fe3d4ed --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization_context.h @@ -0,0 +1,77 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_ + +#include <stddef.h> + +#include <memory> +#include <queue> +#include <vector> + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace internal { + +// A container for handles during serialization/deserialization. +class MOJO_CPP_BINDINGS_EXPORT SerializedHandleVector { + public: + SerializedHandleVector(); + ~SerializedHandleVector(); + + size_t size() const { return handles_.size(); } + + // Adds a handle to the handle list and returns its index for encoding. + Handle_Data AddHandle(mojo::Handle handle); + + // Takes a handle from the list of serialized handle data. + mojo::Handle TakeHandle(const Handle_Data& encoded_handle); + + // Takes a handle from the list of serialized handle data and returns it in + // |*out_handle| as a specific scoped handle type. + template <typename T> + ScopedHandleBase<T> TakeHandleAs(const Handle_Data& encoded_handle) { + return MakeScopedHandle(T(TakeHandle(encoded_handle).value())); + } + + // Swaps all owned handles out with another Handle vector. + void Swap(std::vector<mojo::Handle>* other); + + private: + // Handles are owned by this object. + std::vector<mojo::Handle> handles_; + + DISALLOW_COPY_AND_ASSIGN(SerializedHandleVector); +}; + +// Context information for serialization/deserialization routines. +struct MOJO_CPP_BINDINGS_EXPORT SerializationContext { + SerializationContext(); + + ~SerializationContext(); + + // Opaque context pointers returned by StringTraits::SetUpContext(). + std::unique_ptr<std::queue<void*>> custom_contexts; + + // Stashes handles encoded in a message by index. + SerializedHandleVector handles; + + // The number of ScopedInterfaceEndpointHandles that need to be serialized. + // It is calculated by PrepareToSerialize(). + uint32_t associated_endpoint_count = 0; + + // Stashes ScopedInterfaceEndpointHandles encoded in a message by index. + std::vector<ScopedInterfaceEndpointHandle> associated_endpoint_handles; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_BINDINGS_SERIALIZATION_CONTEXT_H_ diff --git a/mojo/public/cpp/bindings/lib/serialization_forward.h b/mojo/public/cpp/bindings/lib/serialization_forward.h new file mode 100644 index 0000000000..55c9982ccc --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization_forward.h @@ -0,0 +1,123 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_ + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/array_traits.h" +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" +#include "mojo/public/cpp/bindings/map_traits.h" +#include "mojo/public/cpp/bindings/string_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/union_traits.h" + +// This file is included by serialization implementation files to avoid circular +// includes. +// Users of the serialization funtions should include serialization.h (and also +// wtf_serialization.h if necessary). + +namespace mojo { +namespace internal { + +template <typename MojomType, typename MaybeConstUserType> +struct Serializer; + +template <typename T> +struct IsOptionalWrapper { + static const bool value = IsSpecializationOf< + base::Optional, + typename std::remove_const< + typename std::remove_reference<T>::type>::type>::value; +}; + +// PrepareToSerialize() must be matched by a Serialize() for the same input +// later. Moreover, within the same SerializationContext if PrepareToSerialize() +// is called for |input_1|, ..., |input_n|, Serialize() must be called for +// those objects in the exact same order. +template <typename MojomType, + typename InputUserType, + typename... Args, + typename std::enable_if< + !IsOptionalWrapper<InputUserType>::value>::type* = nullptr> +size_t PrepareToSerialize(InputUserType&& input, Args&&... args) { + return Serializer<MojomType, + typename std::remove_reference<InputUserType>::type>:: + PrepareToSerialize(std::forward<InputUserType>(input), + std::forward<Args>(args)...); +} + +template <typename MojomType, + typename InputUserType, + typename... Args, + typename std::enable_if< + !IsOptionalWrapper<InputUserType>::value>::type* = nullptr> +void Serialize(InputUserType&& input, Args&&... args) { + Serializer<MojomType, typename std::remove_reference<InputUserType>::type>:: + Serialize(std::forward<InputUserType>(input), + std::forward<Args>(args)...); +} + +template <typename MojomType, + typename DataType, + typename InputUserType, + typename... Args, + typename std::enable_if< + !IsOptionalWrapper<InputUserType>::value>::type* = nullptr> +bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) { + return Serializer<MojomType, InputUserType>::Deserialize( + std::forward<DataType>(input), output, std::forward<Args>(args)...); +} + +// Specialization that unwraps base::Optional<>. +template <typename MojomType, + typename InputUserType, + typename... Args, + typename std::enable_if< + IsOptionalWrapper<InputUserType>::value>::type* = nullptr> +size_t PrepareToSerialize(InputUserType&& input, Args&&... args) { + if (!input) + return 0; + return PrepareToSerialize<MojomType>(*input, std::forward<Args>(args)...); +} + +template <typename MojomType, + typename InputUserType, + typename DataType, + typename... Args, + typename std::enable_if< + IsOptionalWrapper<InputUserType>::value>::type* = nullptr> +void Serialize(InputUserType&& input, + Buffer* buffer, + DataType** output, + Args&&... args) { + if (!input) { + *output = nullptr; + return; + } + Serialize<MojomType>(*input, buffer, output, std::forward<Args>(args)...); +} + +template <typename MojomType, + typename DataType, + typename InputUserType, + typename... Args, + typename std::enable_if< + IsOptionalWrapper<InputUserType>::value>::type* = nullptr> +bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) { + if (!input) { + *output = base::nullopt; + return true; + } + if (!*output) + output->emplace(); + return Deserialize<MojomType>(std::forward<DataType>(input), &output->value(), + std::forward<Args>(args)...); +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_FORWARD_H_ diff --git a/mojo/public/cpp/bindings/lib/serialization_util.h b/mojo/public/cpp/bindings/lib/serialization_util.h new file mode 100644 index 0000000000..4820a014ec --- /dev/null +++ b/mojo/public/cpp/bindings/lib/serialization_util.h @@ -0,0 +1,213 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <queue> + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" + +namespace mojo { +namespace internal { + +template <typename T> +struct HasIsNullMethod { + template <typename U> + static char Test(decltype(U::IsNull)*); + template <typename U> + static int Test(...); + static const bool value = sizeof(Test<T>(0)) == sizeof(char); + + private: + EnsureTypeIsComplete<T> check_t_; +}; + +template < + typename Traits, + typename UserType, + typename std::enable_if<HasIsNullMethod<Traits>::value>::type* = nullptr> +bool CallIsNullIfExists(const UserType& input) { + return Traits::IsNull(input); +} + +template < + typename Traits, + typename UserType, + typename std::enable_if<!HasIsNullMethod<Traits>::value>::type* = nullptr> +bool CallIsNullIfExists(const UserType& input) { + return false; +} +template <typename T> +struct HasSetToNullMethod { + template <typename U> + static char Test(decltype(U::SetToNull)*); + template <typename U> + static int Test(...); + static const bool value = sizeof(Test<T>(0)) == sizeof(char); + + private: + EnsureTypeIsComplete<T> check_t_; +}; + +template < + typename Traits, + typename UserType, + typename std::enable_if<HasSetToNullMethod<Traits>::value>::type* = nullptr> +bool CallSetToNullIfExists(UserType* output) { + Traits::SetToNull(output); + return true; +} + +template <typename Traits, + typename UserType, + typename std::enable_if<!HasSetToNullMethod<Traits>::value>::type* = + nullptr> +bool CallSetToNullIfExists(UserType* output) { + LOG(ERROR) << "A null value is received. But the Struct/Array/StringTraits " + << "class doesn't define a SetToNull() function and therefore is " + << "unable to deserialize the value."; + return false; +} + +template <typename T> +struct HasSetUpContextMethod { + template <typename U> + static char Test(decltype(U::SetUpContext)*); + template <typename U> + static int Test(...); + static const bool value = sizeof(Test<T>(0)) == sizeof(char); + + private: + EnsureTypeIsComplete<T> check_t_; +}; + +template <typename Traits, + bool has_context = HasSetUpContextMethod<Traits>::value> +struct CustomContextHelper; + +template <typename Traits> +struct CustomContextHelper<Traits, true> { + template <typename MaybeConstUserType> + static void* SetUp(MaybeConstUserType& input, SerializationContext* context) { + void* custom_context = Traits::SetUpContext(input); + if (!context->custom_contexts) + context->custom_contexts.reset(new std::queue<void*>()); + context->custom_contexts->push(custom_context); + return custom_context; + } + + static void* GetNext(SerializationContext* context) { + void* custom_context = context->custom_contexts->front(); + context->custom_contexts->pop(); + return custom_context; + } + + template <typename MaybeConstUserType> + static void TearDown(MaybeConstUserType& input, void* custom_context) { + Traits::TearDownContext(input, custom_context); + } +}; + +template <typename Traits> +struct CustomContextHelper<Traits, false> { + template <typename MaybeConstUserType> + static void* SetUp(MaybeConstUserType& input, SerializationContext* context) { + return nullptr; + } + + static void* GetNext(SerializationContext* context) { return nullptr; } + + template <typename MaybeConstUserType> + static void TearDown(MaybeConstUserType& input, void* custom_context) { + DCHECK(!custom_context); + } +}; + +template <typename ReturnType, typename ParamType, typename InputUserType> +ReturnType CallWithContext(ReturnType (*f)(ParamType, void*), + InputUserType&& input, + void* context) { + return f(std::forward<InputUserType>(input), context); +} + +template <typename ReturnType, typename ParamType, typename InputUserType> +ReturnType CallWithContext(ReturnType (*f)(ParamType), + InputUserType&& input, + void* context) { + return f(std::forward<InputUserType>(input)); +} + +template <typename T, typename MaybeConstUserType> +struct HasGetBeginMethod { + template <typename U> + static char Test(decltype(U::GetBegin(std::declval<MaybeConstUserType&>()))*); + template <typename U> + static int Test(...); + static const bool value = sizeof(Test<T>(0)) == sizeof(char); + + private: + EnsureTypeIsComplete<T> check_t_; +}; + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + HasGetBeginMethod<Traits, MaybeConstUserType>::value>::type* = nullptr> +decltype(Traits::GetBegin(std::declval<MaybeConstUserType&>())) +CallGetBeginIfExists(MaybeConstUserType& input) { + return Traits::GetBegin(input); +} + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + !HasGetBeginMethod<Traits, MaybeConstUserType>::value>::type* = nullptr> +size_t CallGetBeginIfExists(MaybeConstUserType& input) { + return 0; +} + +template <typename T, typename MaybeConstUserType> +struct HasGetDataMethod { + template <typename U> + static char Test(decltype(U::GetData(std::declval<MaybeConstUserType&>()))*); + template <typename U> + static int Test(...); + static const bool value = sizeof(Test<T>(0)) == sizeof(char); + + private: + EnsureTypeIsComplete<T> check_t_; +}; + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + HasGetDataMethod<Traits, MaybeConstUserType>::value>::type* = nullptr> +decltype(Traits::GetData(std::declval<MaybeConstUserType&>())) +CallGetDataIfExists(MaybeConstUserType& input) { + return Traits::GetData(input); +} + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + !HasGetDataMethod<Traits, MaybeConstUserType>::value>::type* = nullptr> +void* CallGetDataIfExists(MaybeConstUserType& input) { + return nullptr; +} + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_SERIALIZATION_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/string_serialization.h b/mojo/public/cpp/bindings/lib/string_serialization.h new file mode 100644 index 0000000000..6e0c758576 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/string_serialization.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ + +#include <stddef.h> +#include <string.h> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/string_data_view.h" +#include "mojo/public/cpp/bindings/string_traits.h" + +namespace mojo { +namespace internal { + +template <typename MaybeConstUserType> +struct Serializer<StringDataView, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = StringTraits<UserType>; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) + return 0; + + void* custom_context = CustomContextHelper<Traits>::SetUp(input, context); + return Align(sizeof(String_Data) + + CallWithContext(Traits::GetSize, input, custom_context)); + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buffer, + String_Data** output, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) { + *output = nullptr; + return; + } + + void* custom_context = CustomContextHelper<Traits>::GetNext(context); + + String_Data* result = String_Data::New( + CallWithContext(Traits::GetSize, input, custom_context), buffer); + if (result) { + memcpy(result->storage(), + CallWithContext(Traits::GetData, input, custom_context), + CallWithContext(Traits::GetSize, input, custom_context)); + } + *output = result; + + CustomContextHelper<Traits>::TearDown(input, custom_context); + } + + static bool Deserialize(String_Data* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists<Traits>(output); + return Traits::Read(StringDataView(input, context), output); + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_STRING_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/lib/string_traits_string16.cc b/mojo/public/cpp/bindings/lib/string_traits_string16.cc new file mode 100644 index 0000000000..95ff6ccf25 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/string_traits_string16.cc @@ -0,0 +1,42 @@ +// 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. + +#include "mojo/public/cpp/bindings/string_traits_string16.h" + +#include <string> + +#include "base/strings/utf_string_conversions.h" + +namespace mojo { + +// static +void* StringTraits<base::string16>::SetUpContext(const base::string16& input) { + return new std::string(base::UTF16ToUTF8(input)); +} + +// static +void StringTraits<base::string16>::TearDownContext(const base::string16& input, + void* context) { + delete static_cast<std::string*>(context); +} + +// static +size_t StringTraits<base::string16>::GetSize(const base::string16& input, + void* context) { + return static_cast<std::string*>(context)->size(); +} + +// static +const char* StringTraits<base::string16>::GetData(const base::string16& input, + void* context) { + return static_cast<std::string*>(context)->data(); +} + +// static +bool StringTraits<base::string16>::Read(StringDataView input, + base::string16* output) { + return base::UTF8ToUTF16(input.storage(), input.size(), output); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/string_traits_wtf.cc b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc new file mode 100644 index 0000000000..203f6f5903 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/string_traits_wtf.cc @@ -0,0 +1,84 @@ +// 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. + +#include "mojo/public/cpp/bindings/string_traits_wtf.h" + +#include <string.h> + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "third_party/WebKit/Source/wtf/text/StringUTF8Adaptor.h" + +namespace mojo { +namespace { + +struct UTF8AdaptorInfo { + explicit UTF8AdaptorInfo(const WTF::String& input) : utf8_adaptor(input) { +#if DCHECK_IS_ON() + original_size_in_bytes = input.charactersSizeInBytes(); +#endif + } + + ~UTF8AdaptorInfo() {} + + WTF::StringUTF8Adaptor utf8_adaptor; + +#if DCHECK_IS_ON() + // For sanity check only. + size_t original_size_in_bytes; +#endif +}; + +UTF8AdaptorInfo* ToAdaptor(const WTF::String& input, void* context) { + UTF8AdaptorInfo* adaptor = static_cast<UTF8AdaptorInfo*>(context); + +#if DCHECK_IS_ON() + DCHECK_EQ(adaptor->original_size_in_bytes, input.charactersSizeInBytes()); +#endif + return adaptor; +} + +} // namespace + +// static +void StringTraits<WTF::String>::SetToNull(WTF::String* output) { + if (output->isNull()) + return; + + WTF::String result; + output->swap(result); +} + +// static +void* StringTraits<WTF::String>::SetUpContext(const WTF::String& input) { + return new UTF8AdaptorInfo(input); +} + +// static +void StringTraits<WTF::String>::TearDownContext(const WTF::String& input, + void* context) { + delete ToAdaptor(input, context); +} + +// static +size_t StringTraits<WTF::String>::GetSize(const WTF::String& input, + void* context) { + return ToAdaptor(input, context)->utf8_adaptor.length(); +} + +// static +const char* StringTraits<WTF::String>::GetData(const WTF::String& input, + void* context) { + return ToAdaptor(input, context)->utf8_adaptor.data(); +} + +// static +bool StringTraits<WTF::String>::Read(StringDataView input, + WTF::String* output) { + WTF::String result = WTF::String::fromUTF8(input.storage(), input.size()); + output->swap(result); + return true; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc new file mode 100644 index 0000000000..585a8f094c --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_call_restrictions.cc @@ -0,0 +1,93 @@ +// 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. + +#include "mojo/public/cpp/bindings/sync_call_restrictions.h" + +#if ENABLE_SYNC_CALL_RESTRICTIONS + +#include "base/debug/leak_annotations.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { + +namespace { + +class SyncCallSettings { + public: + static SyncCallSettings* current(); + + bool allowed() const { + return scoped_allow_count_ > 0 || system_defined_value_; + } + + void IncreaseScopedAllowCount() { scoped_allow_count_++; } + void DecreaseScopedAllowCount() { + DCHECK_LT(0u, scoped_allow_count_); + scoped_allow_count_--; + } + + private: + SyncCallSettings(); + ~SyncCallSettings(); + + bool system_defined_value_ = true; + size_t scoped_allow_count_ = 0; +}; + +base::LazyInstance<base::ThreadLocalPointer<SyncCallSettings>>::DestructorAtExit + g_sync_call_settings = LAZY_INSTANCE_INITIALIZER; + +// static +SyncCallSettings* SyncCallSettings::current() { + SyncCallSettings* result = g_sync_call_settings.Pointer()->Get(); + if (!result) { + result = new SyncCallSettings(); + ANNOTATE_LEAKING_OBJECT_PTR(result); + DCHECK_EQ(result, g_sync_call_settings.Pointer()->Get()); + } + return result; +} + +SyncCallSettings::SyncCallSettings() { + MojoResult result = MojoGetProperty(MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED, + &system_defined_value_); + DCHECK_EQ(MOJO_RESULT_OK, result); + + DCHECK(!g_sync_call_settings.Pointer()->Get()); + g_sync_call_settings.Pointer()->Set(this); +} + +SyncCallSettings::~SyncCallSettings() { + g_sync_call_settings.Pointer()->Set(nullptr); +} + +} // namespace + +// static +void SyncCallRestrictions::AssertSyncCallAllowed() { + if (!SyncCallSettings::current()->allowed()) { + LOG(FATAL) << "Mojo sync calls are not allowed in this process because " + << "they can lead to jank and deadlock. If you must make an " + << "exception, please see " + << "SyncCallRestrictions::ScopedAllowSyncCall and consult " + << "mojo/OWNERS."; + } +} + +// static +void SyncCallRestrictions::IncreaseScopedAllowCount() { + SyncCallSettings::current()->IncreaseScopedAllowCount(); +} + +// static +void SyncCallRestrictions::DecreaseScopedAllowCount() { + SyncCallSettings::current()->DecreaseScopedAllowCount(); +} + +} // namespace mojo + +#endif // ENABLE_SYNC_CALL_RESTRICTIONS diff --git a/mojo/public/cpp/bindings/lib/sync_event_watcher.cc b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc new file mode 100644 index 0000000000..b1c97e3691 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_event_watcher.cc @@ -0,0 +1,67 @@ +// 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. + +#include "mojo/public/cpp/bindings/sync_event_watcher.h" + +#include "base/logging.h" + +namespace mojo { + +SyncEventWatcher::SyncEventWatcher(base::WaitableEvent* event, + const base::Closure& callback) + : event_(event), + callback_(callback), + registry_(SyncHandleRegistry::current()), + destroyed_(new base::RefCountedData<bool>(false)) {} + +SyncEventWatcher::~SyncEventWatcher() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (registered_) + registry_->UnregisterEvent(event_); + destroyed_->data = true; +} + +void SyncEventWatcher::AllowWokenUpBySyncWatchOnSameThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); +} + +bool SyncEventWatcher::SyncWatch(const bool* should_stop) { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); + if (!registered_) { + DecrementRegisterCount(); + return false; + } + + // This object may be destroyed during the Wait() call. So we have to preserve + // the boolean that Wait uses. + auto destroyed = destroyed_; + const bool* should_stop_array[] = {should_stop, &destroyed->data}; + bool result = registry_->Wait(should_stop_array, 2); + + // This object has been destroyed. + if (destroyed->data) + return false; + + DecrementRegisterCount(); + return result; +} + +void SyncEventWatcher::IncrementRegisterCount() { + register_request_count_++; + if (!registered_) + registered_ = registry_->RegisterEvent(event_, callback_); +} + +void SyncEventWatcher::DecrementRegisterCount() { + DCHECK_GT(register_request_count_, 0u); + register_request_count_--; + if (register_request_count_ == 0 && registered_) { + registry_->UnregisterEvent(event_); + registered_ = false; + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/sync_handle_registry.cc b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc new file mode 100644 index 0000000000..fd3df396ec --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_handle_registry.cc @@ -0,0 +1,135 @@ +// 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. + +#include "mojo/public/cpp/bindings/sync_handle_registry.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "base/threading/thread_local.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { +namespace { + +base::LazyInstance<base::ThreadLocalPointer<SyncHandleRegistry>>::Leaky + g_current_sync_handle_watcher = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// static +scoped_refptr<SyncHandleRegistry> SyncHandleRegistry::current() { + scoped_refptr<SyncHandleRegistry> result( + g_current_sync_handle_watcher.Pointer()->Get()); + if (!result) { + result = new SyncHandleRegistry(); + DCHECK_EQ(result.get(), g_current_sync_handle_watcher.Pointer()->Get()); + } + return result; +} + +bool SyncHandleRegistry::RegisterHandle(const Handle& handle, + MojoHandleSignals handle_signals, + const HandleCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (base::ContainsKey(handles_, handle)) + return false; + + MojoResult result = wait_set_.AddHandle(handle, handle_signals); + if (result != MOJO_RESULT_OK) + return false; + + handles_[handle] = callback; + return true; +} + +void SyncHandleRegistry::UnregisterHandle(const Handle& handle) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!base::ContainsKey(handles_, handle)) + return; + + MojoResult result = wait_set_.RemoveHandle(handle); + DCHECK_EQ(MOJO_RESULT_OK, result); + handles_.erase(handle); +} + +bool SyncHandleRegistry::RegisterEvent(base::WaitableEvent* event, + const base::Closure& callback) { + auto result = events_.insert({event, callback}); + DCHECK(result.second); + MojoResult rv = wait_set_.AddEvent(event); + if (rv == MOJO_RESULT_OK) + return true; + DCHECK_EQ(MOJO_RESULT_ALREADY_EXISTS, rv); + return false; +} + +void SyncHandleRegistry::UnregisterEvent(base::WaitableEvent* event) { + auto it = events_.find(event); + DCHECK(it != events_.end()); + events_.erase(it); + MojoResult rv = wait_set_.RemoveEvent(event); + DCHECK_EQ(MOJO_RESULT_OK, rv); +} + +bool SyncHandleRegistry::Wait(const bool* should_stop[], size_t count) { + DCHECK(thread_checker_.CalledOnValidThread()); + + size_t num_ready_handles; + Handle ready_handle; + MojoResult ready_handle_result; + + scoped_refptr<SyncHandleRegistry> preserver(this); + while (true) { + for (size_t i = 0; i < count; ++i) + if (*should_stop[i]) + return true; + + // TODO(yzshen): Theoretically it can reduce sync call re-entrancy if we + // give priority to the handle that is waiting for sync response. + base::WaitableEvent* ready_event = nullptr; + num_ready_handles = 1; + wait_set_.Wait(&ready_event, &num_ready_handles, &ready_handle, + &ready_handle_result); + if (num_ready_handles) { + DCHECK_EQ(1u, num_ready_handles); + const auto iter = handles_.find(ready_handle); + iter->second.Run(ready_handle_result); + } + + if (ready_event) { + const auto iter = events_.find(ready_event); + DCHECK(iter != events_.end()); + iter->second.Run(); + } + }; + + return false; +} + +SyncHandleRegistry::SyncHandleRegistry() { + DCHECK(!g_current_sync_handle_watcher.Pointer()->Get()); + g_current_sync_handle_watcher.Pointer()->Set(this); +} + +SyncHandleRegistry::~SyncHandleRegistry() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // This object may be destructed after the thread local storage slot used by + // |g_current_sync_handle_watcher| is reset during thread shutdown. + // For example, another slot in the thread local storage holds a referrence to + // this object, and that slot is cleaned up after + // |g_current_sync_handle_watcher|. + if (!g_current_sync_handle_watcher.Pointer()->Get()) + return; + + // If this breaks, it is likely that the global variable is bulit into and + // accessed from multiple modules. + DCHECK_EQ(this, g_current_sync_handle_watcher.Pointer()->Get()); + + g_current_sync_handle_watcher.Pointer()->Set(nullptr); +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc new file mode 100644 index 0000000000..f20af56b20 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/sync_handle_watcher.cc @@ -0,0 +1,76 @@ +// 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. + +#include "mojo/public/cpp/bindings/sync_handle_watcher.h" + +#include "base/logging.h" + +namespace mojo { + +SyncHandleWatcher::SyncHandleWatcher( + const Handle& handle, + MojoHandleSignals handle_signals, + const SyncHandleRegistry::HandleCallback& callback) + : handle_(handle), + handle_signals_(handle_signals), + callback_(callback), + registered_(false), + register_request_count_(0), + registry_(SyncHandleRegistry::current()), + destroyed_(new base::RefCountedData<bool>(false)) {} + +SyncHandleWatcher::~SyncHandleWatcher() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (registered_) + registry_->UnregisterHandle(handle_); + + destroyed_->data = true; +} + +void SyncHandleWatcher::AllowWokenUpBySyncWatchOnSameThread() { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); +} + +bool SyncHandleWatcher::SyncWatch(const bool* should_stop) { + DCHECK(thread_checker_.CalledOnValidThread()); + IncrementRegisterCount(); + if (!registered_) { + DecrementRegisterCount(); + return false; + } + + // This object may be destroyed during the Wait() call. So we have to preserve + // the boolean that Wait uses. + auto destroyed = destroyed_; + const bool* should_stop_array[] = {should_stop, &destroyed->data}; + bool result = registry_->Wait(should_stop_array, 2); + + // This object has been destroyed. + if (destroyed->data) + return false; + + DecrementRegisterCount(); + return result; +} + +void SyncHandleWatcher::IncrementRegisterCount() { + register_request_count_++; + if (!registered_) { + registered_ = + registry_->RegisterHandle(handle_, handle_signals_, callback_); + } +} + +void SyncHandleWatcher::DecrementRegisterCount() { + DCHECK_GT(register_request_count_, 0u); + + register_request_count_--; + if (register_request_count_ == 0 && registered_) { + registry_->UnregisterHandle(handle_); + registered_ = false; + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/template_util.h b/mojo/public/cpp/bindings/lib/template_util.h new file mode 100644 index 0000000000..5151123ac0 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/template_util.h @@ -0,0 +1,120 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ + +#include <type_traits> + +namespace mojo { +namespace internal { + +template <class T, T v> +struct IntegralConstant { + static const T value = v; +}; + +template <class T, T v> +const T IntegralConstant<T, v>::value; + +typedef IntegralConstant<bool, true> TrueType; +typedef IntegralConstant<bool, false> FalseType; + +template <class T> +struct IsConst : FalseType {}; +template <class T> +struct IsConst<const T> : TrueType {}; + +template <class T> +struct IsPointer : FalseType {}; +template <class T> +struct IsPointer<T*> : TrueType {}; + +template <bool B, typename T = void> +struct EnableIf {}; + +template <typename T> +struct EnableIf<true, T> { + typedef T type; +}; + +// Types YesType and NoType are guaranteed such that sizeof(YesType) < +// sizeof(NoType). +typedef char YesType; + +struct NoType { + YesType dummy[2]; +}; + +// A helper template to determine if given type is non-const move-only-type, +// i.e. if a value of the given type should be passed via std::move() in a +// destructive way. +template <typename T> +struct IsMoveOnlyType { + static const bool value = std::is_constructible<T, T&&>::value && + !std::is_constructible<T, const T&>::value; +}; + +// This goop is a trick used to implement a template that can be used to +// determine if a given class is the base class of another given class. +template <typename, typename> +struct IsSame { + static bool const value = false; +}; +template <typename A> +struct IsSame<A, A> { + static bool const value = true; +}; + +template <typename T> +struct EnsureTypeIsComplete { + // sizeof() cannot be applied to incomplete types, this line will fail + // compilation if T is forward declaration. + using CheckSize = char (*)[sizeof(T)]; +}; + +template <typename Base, typename Derived> +struct IsBaseOf { + private: + static Derived* CreateDerived(); + static char(&Check(Base*))[1]; + static char(&Check(...))[2]; + + EnsureTypeIsComplete<Base> check_base_; + EnsureTypeIsComplete<Derived> check_derived_; + + public: + static bool const value = sizeof Check(CreateDerived()) == 1 && + !IsSame<Base const, void const>::value; +}; + +template <class T> +struct RemovePointer { + typedef T type; +}; +template <class T> +struct RemovePointer<T*> { + typedef T type; +}; + +template <template <typename...> class Template, typename T> +struct IsSpecializationOf : FalseType {}; + +template <template <typename...> class Template, typename... Args> +struct IsSpecializationOf<Template, Template<Args...>> : TrueType {}; + +template <bool B, typename T, typename F> +struct Conditional { + typedef T type; +}; + +template <typename T, typename F> +struct Conditional<false, T, F> { + typedef F type; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_TEMPLATE_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/union_accessor.h b/mojo/public/cpp/bindings/lib/union_accessor.h new file mode 100644 index 0000000000..821aede595 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/union_accessor.h @@ -0,0 +1,33 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_ + +namespace mojo { +namespace internal { + +// When serializing and deserializing Unions, it is necessary to access +// the private fields and methods of the Union. This allows us to do that +// without leaking those same fields and methods in the Union interface. +// All Union wrappers are friends of this class allowing such access. +template <typename U> +class UnionAccessor { + public: + explicit UnionAccessor(U* u) : u_(u) {} + + typename U::Union_* data() { return &(u_->data_); } + + typename U::Tag* tag() { return &(u_->tag_); } + + void SwitchActive(typename U::Tag new_tag) { u_->SwitchActive(new_tag); } + + private: + U* u_; +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_UNION_ACCESSOR_H_ diff --git a/mojo/public/cpp/bindings/lib/validate_params.h b/mojo/public/cpp/bindings/lib/validate_params.h new file mode 100644 index 0000000000..c0ee8e02a7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validate_params.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_ + +#include <stdint.h> + +#include "base/macros.h" + +namespace mojo { +namespace internal { + +class ValidationContext; + +using ValidateEnumFunc = bool (*)(int32_t, ValidationContext*); + +class ContainerValidateParams { + public: + // Validates a map. A map is validated as a pair of arrays, one for the keys + // and one for the values. Both arguments must be non-null. + // + // ContainerValidateParams takes ownership of |in_key_validate params| and + // |in_element_validate params|. + ContainerValidateParams(ContainerValidateParams* in_key_validate_params, + ContainerValidateParams* in_element_validate_params) + : key_validate_params(in_key_validate_params), + element_validate_params(in_element_validate_params) { + DCHECK(in_key_validate_params) + << "Map validate params require key validate params"; + DCHECK(in_element_validate_params) + << "Map validate params require element validate params"; + } + + // Validates an array. + // + // ContainerValidateParams takes ownership of |in_element_validate params|. + ContainerValidateParams(uint32_t in_expected_num_elements, + bool in_element_is_nullable, + ContainerValidateParams* in_element_validate_params) + : expected_num_elements(in_expected_num_elements), + element_is_nullable(in_element_is_nullable), + element_validate_params(in_element_validate_params) {} + + // Validates an array of enums. + ContainerValidateParams(uint32_t in_expected_num_elements, + ValidateEnumFunc in_validate_enum_func) + : expected_num_elements(in_expected_num_elements), + validate_enum_func(in_validate_enum_func) {} + + ~ContainerValidateParams() { + if (element_validate_params) + delete element_validate_params; + if (key_validate_params) + delete key_validate_params; + } + + // If |expected_num_elements| is not 0, the array is expected to have exactly + // that number of elements. + uint32_t expected_num_elements = 0; + + // Whether the elements are nullable. + bool element_is_nullable = false; + + // Validation information for the map key array. May contain other + // ArrayValidateParams e.g. if the keys are strings. + ContainerValidateParams* key_validate_params = nullptr; + + // For arrays: validation information for elements. It is either a pointer to + // another instance of ArrayValidateParams (if elements are arrays or maps), + // or nullptr. + // + // For maps: validation information for the whole value array. May contain + // other ArrayValidateParams e.g. if the values are arrays or maps. + ContainerValidateParams* element_validate_params = nullptr; + + // Validation function for enum elements. + ValidateEnumFunc validate_enum_func = nullptr; + + private: + DISALLOW_COPY_AND_ASSIGN(ContainerValidateParams); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATE_PARAMS_H_ diff --git a/mojo/public/cpp/bindings/lib/validation_context.cc b/mojo/public/cpp/bindings/lib/validation_context.cc new file mode 100644 index 0000000000..ad0a3646eb --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_context.cc @@ -0,0 +1,50 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/validation_context.h" + +#include "base/logging.h" + +namespace mojo { +namespace internal { + +ValidationContext::ValidationContext(const void* data, + size_t data_num_bytes, + size_t num_handles, + size_t num_associated_endpoint_handles, + Message* message, + const base::StringPiece& description, + int stack_depth) + : message_(message), + description_(description), + data_begin_(reinterpret_cast<uintptr_t>(data)), + data_end_(data_begin_ + data_num_bytes), + handle_begin_(0), + handle_end_(static_cast<uint32_t>(num_handles)), + associated_endpoint_handle_begin_(0), + associated_endpoint_handle_end_( + static_cast<uint32_t>(num_associated_endpoint_handles)), + stack_depth_(stack_depth) { + // Check whether the calculation of |data_end_| or static_cast from size_t to + // uint32_t causes overflow. + // They shouldn't happen but they do, set the corresponding range to empty. + if (data_end_ < data_begin_) { + NOTREACHED(); + data_end_ = data_begin_; + } + if (handle_end_ < num_handles) { + NOTREACHED(); + handle_end_ = 0; + } + if (associated_endpoint_handle_end_ < num_associated_endpoint_handles) { + NOTREACHED(); + associated_endpoint_handle_end_ = 0; + } +} + +ValidationContext::~ValidationContext() { +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/validation_context.h b/mojo/public/cpp/bindings/lib/validation_context.h new file mode 100644 index 0000000000..ed6c6542e7 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_context.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_CONTEXT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_CONTEXT_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/strings/string_piece.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" + +static const int kMaxRecursionDepth = 100; + +namespace mojo { + +class Message; + +namespace internal { + +// ValidationContext is used when validating object sizes, pointers and handle +// indices in the payload of incoming messages. +class MOJO_CPP_BINDINGS_EXPORT ValidationContext { + public: + // [data, data + data_num_bytes) specifies the initial valid memory range. + // [0, num_handles) specifies the initial valid range of handle indices. + // [0, num_associated_endpoint_handles) specifies the initial valid range of + // associated endpoint handle indices. + // + // If provided, |message| and |description| provide additional information + // to use when reporting validation errors. In addition if |message| is + // provided, the MojoNotifyBadMessage API will be used to notify the system of + // such errors. + ValidationContext(const void* data, + size_t data_num_bytes, + size_t num_handles, + size_t num_associated_endpoint_handles, + Message* message = nullptr, + const base::StringPiece& description = "", + int stack_depth = 0); + + ~ValidationContext(); + + // Claims the specified memory range. + // The method succeeds if the range is valid to claim. (Please see + // the comments for IsValidRange().) + // On success, the valid memory range is shrinked to begin right after the end + // of the claimed range. + bool ClaimMemory(const void* position, uint32_t num_bytes) { + uintptr_t begin = reinterpret_cast<uintptr_t>(position); + uintptr_t end = begin + num_bytes; + + if (!InternalIsValidRange(begin, end)) + return false; + + data_begin_ = end; + return true; + } + + // Claims the specified encoded handle (which is basically a handle index). + // The method succeeds if: + // - |encoded_handle|'s value is |kEncodedInvalidHandleValue|. + // - the handle is contained inside the valid range of handle indices. In this + // case, the valid range is shinked to begin right after the claimed handle. + bool ClaimHandle(const Handle_Data& encoded_handle) { + uint32_t index = encoded_handle.value; + if (index == kEncodedInvalidHandleValue) + return true; + + if (index < handle_begin_ || index >= handle_end_) + return false; + + // |index| + 1 shouldn't overflow, because |index| is not the max value of + // uint32_t (it is less than |handle_end_|). + handle_begin_ = index + 1; + return true; + } + + // Claims the specified encoded associated endpoint handle. + // The method succeeds if: + // - |encoded_handle|'s value is |kEncodedInvalidHandleValue|. + // - the handle is contained inside the valid range of associated endpoint + // handle indices. In this case, the valid range is shinked to begin right + // after the claimed handle. + bool ClaimAssociatedEndpointHandle( + const AssociatedEndpointHandle_Data& encoded_handle) { + uint32_t index = encoded_handle.value; + if (index == kEncodedInvalidHandleValue) + return true; + + if (index < associated_endpoint_handle_begin_ || + index >= associated_endpoint_handle_end_) + return false; + + // |index| + 1 shouldn't overflow, because |index| is not the max value of + // uint32_t (it is less than |associated_endpoint_handle_end_|). + associated_endpoint_handle_begin_ = index + 1; + return true; + } + + // Returns true if the specified range is not empty, and the range is + // contained inside the valid memory range. + bool IsValidRange(const void* position, uint32_t num_bytes) const { + uintptr_t begin = reinterpret_cast<uintptr_t>(position); + uintptr_t end = begin + num_bytes; + + return InternalIsValidRange(begin, end); + } + + // This object should be created on the stack once every time we recurse down + // into a subfield during validation to make sure we don't recurse too deep + // and blow the stack. + class ScopedDepthTracker { + public: + // |ctx| must outlive this object. + explicit ScopedDepthTracker(ValidationContext* ctx) : ctx_(ctx) { + ++ctx_->stack_depth_; + } + + ~ScopedDepthTracker() { --ctx_->stack_depth_; } + + private: + ValidationContext* ctx_; + + DISALLOW_COPY_AND_ASSIGN(ScopedDepthTracker); + }; + + // Returns true if the recursion depth limit has been reached. + bool ExceedsMaxDepth() WARN_UNUSED_RESULT { + return stack_depth_ > kMaxRecursionDepth; + } + + Message* message() const { return message_; } + const base::StringPiece& description() const { return description_; } + + private: + bool InternalIsValidRange(uintptr_t begin, uintptr_t end) const { + return end > begin && begin >= data_begin_ && end <= data_end_; + } + + Message* const message_; + const base::StringPiece description_; + + // [data_begin_, data_end_) is the valid memory range. + uintptr_t data_begin_; + uintptr_t data_end_; + + // [handle_begin_, handle_end_) is the valid handle index range. + uint32_t handle_begin_; + uint32_t handle_end_; + + // [associated_endpoint_handle_begin_, associated_endpoint_handle_end_) is the + // valid associated endpoint handle index range. + uint32_t associated_endpoint_handle_begin_; + uint32_t associated_endpoint_handle_end_; + + int stack_depth_; + + DISALLOW_COPY_AND_ASSIGN(ValidationContext); +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_CONTEXT_H_ diff --git a/mojo/public/cpp/bindings/lib/validation_errors.cc b/mojo/public/cpp/bindings/lib/validation_errors.cc new file mode 100644 index 0000000000..904f5e4c72 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_errors.cc @@ -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. + +#include "mojo/public/cpp/bindings/lib/validation_errors.h" + +#include "base/strings/stringprintf.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace internal { +namespace { + +ValidationErrorObserverForTesting* g_validation_error_observer = nullptr; +SerializationWarningObserverForTesting* g_serialization_warning_observer = + nullptr; +bool g_suppress_logging = false; + +} // namespace + +const char* ValidationErrorToString(ValidationError error) { + switch (error) { + case VALIDATION_ERROR_NONE: + return "VALIDATION_ERROR_NONE"; + case VALIDATION_ERROR_MISALIGNED_OBJECT: + return "VALIDATION_ERROR_MISALIGNED_OBJECT"; + case VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE: + return "VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE"; + case VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER: + return "VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER"; + case VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER: + return "VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER"; + case VALIDATION_ERROR_ILLEGAL_HANDLE: + return "VALIDATION_ERROR_ILLEGAL_HANDLE"; + case VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE: + return "VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE"; + case VALIDATION_ERROR_ILLEGAL_POINTER: + return "VALIDATION_ERROR_ILLEGAL_POINTER"; + case VALIDATION_ERROR_UNEXPECTED_NULL_POINTER: + return "VALIDATION_ERROR_UNEXPECTED_NULL_POINTER"; + case VALIDATION_ERROR_ILLEGAL_INTERFACE_ID: + return "VALIDATION_ERROR_ILLEGAL_INTERFACE_ID"; + case VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID: + return "VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID"; + case VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS: + return "VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS"; + case VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID: + return "VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID"; + case VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD: + return "VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD"; + case VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP: + return "VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP"; + case VALIDATION_ERROR_UNKNOWN_UNION_TAG: + return "VALIDATION_ERROR_UNKNOWN_UNION_TAG"; + case VALIDATION_ERROR_UNKNOWN_ENUM_VALUE: + return "VALIDATION_ERROR_UNKNOWN_ENUM_VALUE"; + case VALIDATION_ERROR_DESERIALIZATION_FAILED: + return "VALIDATION_ERROR_DESERIALIZATION_FAILED"; + case VALIDATION_ERROR_MAX_RECURSION_DEPTH: + return "VALIDATION_ERROR_MAX_RECURSION_DEPTH"; + } + + return "Unknown error"; +} + +void ReportValidationError(ValidationContext* context, + ValidationError error, + const char* description) { + if (g_validation_error_observer) { + g_validation_error_observer->set_last_error(error); + return; + } + + if (description) { + if (!g_suppress_logging) { + LOG(ERROR) << "Invalid message: " << ValidationErrorToString(error) + << " (" << description << ")"; + } + if (context->message()) { + context->message()->NotifyBadMessage( + base::StringPrintf("Validation failed for %s [%s (%s)]", + context->description().data(), + ValidationErrorToString(error), description)); + } + } else { + if (!g_suppress_logging) + LOG(ERROR) << "Invalid message: " << ValidationErrorToString(error); + if (context->message()) { + context->message()->NotifyBadMessage( + base::StringPrintf("Validation failed for %s [%s]", + context->description().data(), + ValidationErrorToString(error))); + } + } +} + +void ReportValidationErrorForMessage( + mojo::Message* message, + ValidationError error, + const char* description) { + ValidationContext validation_context(nullptr, 0, 0, 0, message, description); + ReportValidationError(&validation_context, error); +} + +ScopedSuppressValidationErrorLoggingForTests + ::ScopedSuppressValidationErrorLoggingForTests() + : was_suppressed_(g_suppress_logging) { + g_suppress_logging = true; +} + +ScopedSuppressValidationErrorLoggingForTests + ::~ScopedSuppressValidationErrorLoggingForTests() { + g_suppress_logging = was_suppressed_; +} + +ValidationErrorObserverForTesting::ValidationErrorObserverForTesting( + const base::Closure& callback) + : last_error_(VALIDATION_ERROR_NONE), callback_(callback) { + DCHECK(!g_validation_error_observer); + g_validation_error_observer = this; +} + +ValidationErrorObserverForTesting::~ValidationErrorObserverForTesting() { + DCHECK(g_validation_error_observer == this); + g_validation_error_observer = nullptr; +} + +bool ReportSerializationWarning(ValidationError error) { + if (g_serialization_warning_observer) { + g_serialization_warning_observer->set_last_warning(error); + return true; + } + + return false; +} + +SerializationWarningObserverForTesting::SerializationWarningObserverForTesting() + : last_warning_(VALIDATION_ERROR_NONE) { + DCHECK(!g_serialization_warning_observer); + g_serialization_warning_observer = this; +} + +SerializationWarningObserverForTesting:: + ~SerializationWarningObserverForTesting() { + DCHECK(g_serialization_warning_observer == this); + g_serialization_warning_observer = nullptr; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/validation_errors.h b/mojo/public/cpp/bindings/lib/validation_errors.h new file mode 100644 index 0000000000..122418d9e3 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_errors.h @@ -0,0 +1,167 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ + +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" + +namespace mojo { + +class Message; + +namespace internal { + +enum ValidationError { + // There is no validation error. + VALIDATION_ERROR_NONE, + // An object (struct or array) is not 8-byte aligned. + VALIDATION_ERROR_MISALIGNED_OBJECT, + // An object is not contained inside the message data, or it overlaps other + // objects. + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE, + // A struct header doesn't make sense, for example: + // - |num_bytes| is smaller than the size of the struct header. + // - |num_bytes| and |version| don't match. + // TODO(yzshen): Consider splitting it into two different error codes. Because + // the former indicates someone is misbehaving badly whereas the latter could + // be due to an inappropriately-modified .mojom file. + VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER, + // An array header doesn't make sense, for example: + // - |num_bytes| is smaller than the size of the header plus the size required + // to store |num_elements| elements. + // - For fixed-size arrays, |num_elements| is different than the specified + // size. + VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + // An encoded handle is illegal. + VALIDATION_ERROR_ILLEGAL_HANDLE, + // A non-nullable handle field is set to invalid handle. + VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + // An encoded pointer is illegal. + VALIDATION_ERROR_ILLEGAL_POINTER, + // A non-nullable pointer field is set to null. + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + // An interface ID is illegal. + VALIDATION_ERROR_ILLEGAL_INTERFACE_ID, + // A non-nullable interface ID field is set to invalid. + VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID, + // |flags| in the message header is invalid. The flags are either + // inconsistent with one another, inconsistent with other parts of the + // message, or unexpected for the message receiver. For example the + // receiver is expecting a request message but the flags indicate that + // the message is a response message. + VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS, + // |flags| in the message header indicates that a request ID is required but + // there isn't one. + VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID, + // The |name| field in a message header contains an unexpected value. + VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD, + // Two parallel arrays which are supposed to represent a map have different + // lengths. + VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP, + // Attempted to deserialize a tagged union with an unknown tag. + VALIDATION_ERROR_UNKNOWN_UNION_TAG, + // A value of a non-extensible enum type is unknown. + VALIDATION_ERROR_UNKNOWN_ENUM_VALUE, + // Message deserialization failure, for example due to rejection by custom + // validation logic. + VALIDATION_ERROR_DESERIALIZATION_FAILED, + // The message contains a too deeply nested value, for example a recursively + // defined field which runtime value is too large. + VALIDATION_ERROR_MAX_RECURSION_DEPTH, +}; + +MOJO_CPP_BINDINGS_EXPORT const char* ValidationErrorToString( + ValidationError error); + +MOJO_CPP_BINDINGS_EXPORT void ReportValidationError( + ValidationContext* context, + ValidationError error, + const char* description = nullptr); + +MOJO_CPP_BINDINGS_EXPORT void ReportValidationErrorForMessage( + mojo::Message* message, + ValidationError error, + const char* description = nullptr); + +// This class may be used by tests to suppress validation error logging. This is +// not thread-safe and must only be instantiated on the main thread with no +// other threads using Mojo bindings at the time of construction or destruction. +class MOJO_CPP_BINDINGS_EXPORT ScopedSuppressValidationErrorLoggingForTests { + public: + ScopedSuppressValidationErrorLoggingForTests(); + ~ScopedSuppressValidationErrorLoggingForTests(); + + private: + const bool was_suppressed_; + + DISALLOW_COPY_AND_ASSIGN(ScopedSuppressValidationErrorLoggingForTests); +}; + +// Only used by validation tests and when there is only one thread doing message +// validation. +class MOJO_CPP_BINDINGS_EXPORT ValidationErrorObserverForTesting { + public: + explicit ValidationErrorObserverForTesting(const base::Closure& callback); + ~ValidationErrorObserverForTesting(); + + ValidationError last_error() const { return last_error_; } + void set_last_error(ValidationError error) { + last_error_ = error; + callback_.Run(); + } + + private: + ValidationError last_error_; + base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ValidationErrorObserverForTesting); +}; + +// Used only by MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING. Don't use it directly. +// +// The function returns true if the error is recorded (by a +// SerializationWarningObserverForTesting object), false otherwise. +MOJO_CPP_BINDINGS_EXPORT bool ReportSerializationWarning(ValidationError error); + +// Only used by serialization tests and when there is only one thread doing +// message serialization. +class MOJO_CPP_BINDINGS_EXPORT SerializationWarningObserverForTesting { + public: + SerializationWarningObserverForTesting(); + ~SerializationWarningObserverForTesting(); + + ValidationError last_warning() const { return last_warning_; } + void set_last_warning(ValidationError error) { last_warning_ = error; } + + private: + ValidationError last_warning_; + + DISALLOW_COPY_AND_ASSIGN(SerializationWarningObserverForTesting); +}; + +} // namespace internal +} // namespace mojo + +// In debug build, logs a serialization warning if |condition| evaluates to +// true: +// - if there is a SerializationWarningObserverForTesting object alive, +// records |error| in it; +// - otherwise, logs a fatal-level message. +// |error| is the validation error that will be triggered by the receiver +// of the serialzation result. +// +// In non-debug build, does nothing (not even compiling |condition|). +#define MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING(condition, error, \ + description) \ + DLOG_IF(FATAL, (condition) && !ReportSerializationWarning(error)) \ + << "The outgoing message will trigger " \ + << ValidationErrorToString(error) << " at the receiving side (" \ + << description << ")."; + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_ERRORS_H_ diff --git a/mojo/public/cpp/bindings/lib/validation_util.cc b/mojo/public/cpp/bindings/lib/validation_util.cc new file mode 100644 index 0000000000..7614df5cbc --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_util.cc @@ -0,0 +1,210 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +#include <stdint.h> + +#include <limits> + +#include "mojo/public/cpp/bindings/lib/message_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +namespace mojo { +namespace internal { + +bool ValidateStructHeaderAndClaimMemory(const void* data, + ValidationContext* validation_context) { + if (!IsAligned(data)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MISALIGNED_OBJECT); + return false; + } + if (!validation_context->IsValidRange(data, sizeof(StructHeader))) { + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + const StructHeader* header = static_cast<const StructHeader*>(data); + + if (header->num_bytes < sizeof(StructHeader)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + + if (!validation_context->ClaimMemory(data, header->num_bytes)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + return true; +} + +bool ValidateNonInlinedUnionHeaderAndClaimMemory( + const void* data, + ValidationContext* validation_context) { + if (!IsAligned(data)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MISALIGNED_OBJECT); + return false; + } + + if (!validation_context->ClaimMemory(data, kUnionDataSize) || + *static_cast<const uint32_t*>(data) != kUnionDataSize) { + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE); + return false; + } + + return true; +} + +bool ValidateMessageIsRequestWithoutResponse( + const Message* message, + ValidationContext* validation_context) { + if (message->has_flag(Message::kFlagIsResponse) || + message->has_flag(Message::kFlagExpectsResponse)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + return true; +} + +bool ValidateMessageIsRequestExpectingResponse( + const Message* message, + ValidationContext* validation_context) { + if (message->has_flag(Message::kFlagIsResponse) || + !message->has_flag(Message::kFlagExpectsResponse)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + return true; +} + +bool ValidateMessageIsResponse(const Message* message, + ValidationContext* validation_context) { + if (message->has_flag(Message::kFlagExpectsResponse) || + !message->has_flag(Message::kFlagIsResponse)) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS); + return false; + } + return true; +} + +bool IsHandleOrInterfaceValid(const AssociatedInterface_Data& input) { + return input.handle.is_valid(); +} + +bool IsHandleOrInterfaceValid(const AssociatedEndpointHandle_Data& input) { + return input.is_valid(); +} + +bool IsHandleOrInterfaceValid(const Interface_Data& input) { + return input.handle.is_valid(); +} + +bool IsHandleOrInterfaceValid(const Handle_Data& input) { + return input.is_valid(); +} + +bool ValidateHandleOrInterfaceNonNullable( + const AssociatedInterface_Data& input, + const char* error_message, + ValidationContext* validation_context) { + if (IsHandleOrInterfaceValid(input)) + return true; + + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID, + error_message); + return false; +} + +bool ValidateHandleOrInterfaceNonNullable( + const AssociatedEndpointHandle_Data& input, + const char* error_message, + ValidationContext* validation_context) { + if (IsHandleOrInterfaceValid(input)) + return true; + + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID, + error_message); + return false; +} + +bool ValidateHandleOrInterfaceNonNullable( + const Interface_Data& input, + const char* error_message, + ValidationContext* validation_context) { + if (IsHandleOrInterfaceValid(input)) + return true; + + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + error_message); + return false; +} + +bool ValidateHandleOrInterfaceNonNullable( + const Handle_Data& input, + const char* error_message, + ValidationContext* validation_context) { + if (IsHandleOrInterfaceValid(input)) + return true; + + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + error_message); + return false; +} + +bool ValidateHandleOrInterface(const AssociatedInterface_Data& input, + ValidationContext* validation_context) { + if (validation_context->ClaimAssociatedEndpointHandle(input.handle)) + return true; + + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_INTERFACE_ID); + return false; +} + +bool ValidateHandleOrInterface(const AssociatedEndpointHandle_Data& input, + ValidationContext* validation_context) { + if (validation_context->ClaimAssociatedEndpointHandle(input)) + return true; + + ReportValidationError(validation_context, + VALIDATION_ERROR_ILLEGAL_INTERFACE_ID); + return false; +} + +bool ValidateHandleOrInterface(const Interface_Data& input, + ValidationContext* validation_context) { + if (validation_context->ClaimHandle(input.handle)) + return true; + + ReportValidationError(validation_context, VALIDATION_ERROR_ILLEGAL_HANDLE); + return false; +} + +bool ValidateHandleOrInterface(const Handle_Data& input, + ValidationContext* validation_context) { + if (validation_context->ClaimHandle(input)) + return true; + + ReportValidationError(validation_context, VALIDATION_ERROR_ILLEGAL_HANDLE); + return false; +} + +} // namespace internal +} // namespace mojo diff --git a/mojo/public/cpp/bindings/lib/validation_util.h b/mojo/public/cpp/bindings/lib/validation_util.h new file mode 100644 index 0000000000..ea5a991668 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/validation_util.h @@ -0,0 +1,206 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_ + +#include <stdint.h> + +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace internal { + +// Checks whether decoding the pointer will overflow and produce a pointer +// smaller than |offset|. +inline bool ValidateEncodedPointer(const uint64_t* offset) { + // - Make sure |*offset| is no more than 32-bits. + // - Cast |offset| to uintptr_t so overflow behavior is well defined across + // 32-bit and 64-bit systems. + return *offset <= std::numeric_limits<uint32_t>::max() && + (reinterpret_cast<uintptr_t>(offset) + + static_cast<uint32_t>(*offset) >= + reinterpret_cast<uintptr_t>(offset)); +} + +template <typename T> +bool ValidatePointer(const Pointer<T>& input, + ValidationContext* validation_context) { + bool result = ValidateEncodedPointer(&input.offset); + if (!result) + ReportValidationError(validation_context, VALIDATION_ERROR_ILLEGAL_POINTER); + + return result; +} + +// Validates that |data| contains a valid struct header, in terms of alignment +// and size (i.e., the |num_bytes| field of the header is sufficient for storing +// the header itself). Besides, it checks that the memory range +// [data, data + num_bytes) is not marked as occupied by other objects in +// |validation_context|. On success, the memory range is marked as occupied. +// Note: Does not verify |version| or that |num_bytes| is correct for the +// claimed version. +MOJO_CPP_BINDINGS_EXPORT bool ValidateStructHeaderAndClaimMemory( + const void* data, + ValidationContext* validation_context); + +// Validates that |data| contains a valid union header, in terms of alignment +// and size. It checks that the memory range [data, data + kUnionDataSize) is +// not marked as occupied by other objects in |validation_context|. On success, +// the memory range is marked as occupied. +MOJO_CPP_BINDINGS_EXPORT bool ValidateNonInlinedUnionHeaderAndClaimMemory( + const void* data, + ValidationContext* validation_context); + +// Validates that the message is a request which doesn't expect a response. +MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsRequestWithoutResponse( + const Message* message, + ValidationContext* validation_context); + +// Validates that the message is a request expecting a response. +MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsRequestExpectingResponse( + const Message* message, + ValidationContext* validation_context); + +// Validates that the message is a response. +MOJO_CPP_BINDINGS_EXPORT bool ValidateMessageIsResponse( + const Message* message, + ValidationContext* validation_context); + +// Validates that the message payload is a valid struct of type ParamsType. +template <typename ParamsType> +bool ValidateMessagePayload(const Message* message, + ValidationContext* validation_context) { + return ParamsType::Validate(message->payload(), validation_context); +} + +// The following Validate.*NonNullable() functions validate that the given +// |input| is not null/invalid. +template <typename T> +bool ValidatePointerNonNullable(const T& input, + const char* error_message, + ValidationContext* validation_context) { + if (input.offset) + return true; + + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + error_message); + return false; +} + +template <typename T> +bool ValidateInlinedUnionNonNullable(const T& input, + const char* error_message, + ValidationContext* validation_context) { + if (!input.is_null()) + return true; + + ReportValidationError(validation_context, + VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + error_message); + return false; +} + +MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid( + const AssociatedInterface_Data& input); +MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid( + const AssociatedEndpointHandle_Data& input); +MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid( + const Interface_Data& input); +MOJO_CPP_BINDINGS_EXPORT bool IsHandleOrInterfaceValid( + const Handle_Data& input); + +MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable( + const AssociatedInterface_Data& input, + const char* error_message, + ValidationContext* validation_context); +MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable( + const AssociatedEndpointHandle_Data& input, + const char* error_message, + ValidationContext* validation_context); +MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable( + const Interface_Data& input, + const char* error_message, + ValidationContext* validation_context); +MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterfaceNonNullable( + const Handle_Data& input, + const char* error_message, + ValidationContext* validation_context); + +template <typename T> +bool ValidateContainer(const Pointer<T>& input, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + ValidationContext::ScopedDepthTracker depth_tracker(validation_context); + if (validation_context->ExceedsMaxDepth()) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MAX_RECURSION_DEPTH); + return false; + } + return ValidatePointer(input, validation_context) && + T::Validate(input.Get(), validation_context, validate_params); +} + +template <typename T> +bool ValidateStruct(const Pointer<T>& input, + ValidationContext* validation_context) { + ValidationContext::ScopedDepthTracker depth_tracker(validation_context); + if (validation_context->ExceedsMaxDepth()) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MAX_RECURSION_DEPTH); + return false; + } + return ValidatePointer(input, validation_context) && + T::Validate(input.Get(), validation_context); +} + +template <typename T> +bool ValidateInlinedUnion(const T& input, + ValidationContext* validation_context) { + ValidationContext::ScopedDepthTracker depth_tracker(validation_context); + if (validation_context->ExceedsMaxDepth()) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MAX_RECURSION_DEPTH); + return false; + } + return T::Validate(&input, validation_context, true); +} + +template <typename T> +bool ValidateNonInlinedUnion(const Pointer<T>& input, + ValidationContext* validation_context) { + ValidationContext::ScopedDepthTracker depth_tracker(validation_context); + if (validation_context->ExceedsMaxDepth()) { + ReportValidationError(validation_context, + VALIDATION_ERROR_MAX_RECURSION_DEPTH); + return false; + } + return ValidatePointer(input, validation_context) && + T::Validate(input.Get(), validation_context, false); +} + +MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface( + const AssociatedInterface_Data& input, + ValidationContext* validation_context); +MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface( + const AssociatedEndpointHandle_Data& input, + ValidationContext* validation_context); +MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface( + const Interface_Data& input, + ValidationContext* validation_context); +MOJO_CPP_BINDINGS_EXPORT bool ValidateHandleOrInterface( + const Handle_Data& input, + ValidationContext* validation_context); + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_VALIDATION_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h b/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h new file mode 100644 index 0000000000..cb24bc46ee --- /dev/null +++ b/mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h @@ -0,0 +1,78 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_CLONE_EQUALS_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_CLONE_EQUALS_UTIL_H_ + +#include <type_traits> + +#include "mojo/public/cpp/bindings/clone_traits.h" +#include "mojo/public/cpp/bindings/lib/equals_traits.h" +#include "third_party/WebKit/Source/wtf/HashMap.h" +#include "third_party/WebKit/Source/wtf/Optional.h" +#include "third_party/WebKit/Source/wtf/Vector.h" +#include "third_party/WebKit/Source/wtf/text/WTFString.h" + +namespace mojo { + +template <typename T> +struct CloneTraits<WTF::Vector<T>, false> { + static WTF::Vector<T> Clone(const WTF::Vector<T>& input) { + WTF::Vector<T> result; + result.reserveCapacity(input.size()); + for (const auto& element : input) + result.push_back(mojo::Clone(element)); + + return result; + } +}; + +template <typename K, typename V> +struct CloneTraits<WTF::HashMap<K, V>, false> { + static WTF::HashMap<K, V> Clone(const WTF::HashMap<K, V>& input) { + WTF::HashMap<K, V> result; + auto input_end = input.end(); + for (auto it = input.begin(); it != input_end; ++it) + result.add(mojo::Clone(it->key), mojo::Clone(it->value)); + return result; + } +}; + +namespace internal { + +template <typename T> +struct EqualsTraits<WTF::Vector<T>, false> { + static bool Equals(const WTF::Vector<T>& a, const WTF::Vector<T>& b) { + if (a.size() != b.size()) + return false; + for (size_t i = 0; i < a.size(); ++i) { + if (!internal::Equals(a[i], b[i])) + return false; + } + return true; + } +}; + +template <typename K, typename V> +struct EqualsTraits<WTF::HashMap<K, V>, false> { + static bool Equals(const WTF::HashMap<K, V>& a, const WTF::HashMap<K, V>& b) { + if (a.size() != b.size()) + return false; + + auto a_end = a.end(); + auto b_end = b.end(); + + for (auto iter = a.begin(); iter != a_end; ++iter) { + auto b_iter = b.find(iter->key); + if (b_iter == b_end || !internal::Equals(iter->value, b_iter->value)) + return false; + } + return true; + } +}; + +} // namespace internal +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_CLONE_EQUALS_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/wtf_hash_util.h b/mojo/public/cpp/bindings/lib/wtf_hash_util.h new file mode 100644 index 0000000000..cc590da67a --- /dev/null +++ b/mojo/public/cpp/bindings/lib/wtf_hash_util.h @@ -0,0 +1,132 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_HASH_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_HASH_UTIL_H_ + +#include <type_traits> + +#include "mojo/public/cpp/bindings/lib/hash_util.h" +#include "mojo/public/cpp/bindings/struct_ptr.h" +#include "third_party/WebKit/Source/wtf/HashFunctions.h" +#include "third_party/WebKit/Source/wtf/text/StringHash.h" +#include "third_party/WebKit/Source/wtf/text/WTFString.h" + +namespace mojo { +namespace internal { + +template <typename T> +size_t WTFHashCombine(size_t seed, const T& value) { + // Based on proposal in: + // http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf + // + // TODO(tibell): We'd like to use WTF::DefaultHash instead of std::hash, but + // there is no general template specialization of DefaultHash for enums + // and there can't be an instance for bool. + return seed ^ (std::hash<T>()(value) + (seed << 6) + (seed >> 2)); +} + +template <typename T, bool has_hash_method = HasHashMethod<T>::value> +struct WTFHashTraits; + +template <typename T> +size_t WTFHash(size_t seed, const T& value); + +template <typename T> +struct WTFHashTraits<T, true> { + static size_t Hash(size_t seed, const T& value) { return value.Hash(seed); } +}; + +template <typename T> +struct WTFHashTraits<T, false> { + static size_t Hash(size_t seed, const T& value) { + return WTFHashCombine(seed, value); + } +}; + +template <> +struct WTFHashTraits<WTF::String, false> { + static size_t Hash(size_t seed, const WTF::String& value) { + return HashCombine(seed, WTF::StringHash::hash(value)); + } +}; + +template <typename T> +size_t WTFHash(size_t seed, const T& value) { + return WTFHashTraits<T>::Hash(seed, value); +} + +template <typename T> +struct StructPtrHashFn { + static unsigned hash(const StructPtr<T>& value) { + return value.Hash(kHashSeed); + } + static bool equal(const StructPtr<T>& left, const StructPtr<T>& right) { + return left.Equals(right); + } + static const bool safeToCompareToEmptyOrDeleted = false; +}; + +template <typename T> +struct InlinedStructPtrHashFn { + static unsigned hash(const InlinedStructPtr<T>& value) { + return value.Hash(kHashSeed); + } + static bool equal(const InlinedStructPtr<T>& left, + const InlinedStructPtr<T>& right) { + return left.Equals(right); + } + static const bool safeToCompareToEmptyOrDeleted = false; +}; + +} // namespace internal +} // namespace mojo + +namespace WTF { + +template <typename T> +struct DefaultHash<mojo::StructPtr<T>> { + using Hash = mojo::internal::StructPtrHashFn<T>; +}; + +template <typename T> +struct HashTraits<mojo::StructPtr<T>> + : public GenericHashTraits<mojo::StructPtr<T>> { + static const bool hasIsEmptyValueFunction = true; + static bool isEmptyValue(const mojo::StructPtr<T>& value) { + return value.is_null(); + } + static void constructDeletedValue(mojo::StructPtr<T>& slot, bool) { + mojo::internal::StructPtrWTFHelper<T>::ConstructDeletedValue(slot); + } + static bool isDeletedValue(const mojo::StructPtr<T>& value) { + return mojo::internal::StructPtrWTFHelper<T>::IsHashTableDeletedValue( + value); + } +}; + +template <typename T> +struct DefaultHash<mojo::InlinedStructPtr<T>> { + using Hash = mojo::internal::InlinedStructPtrHashFn<T>; +}; + +template <typename T> +struct HashTraits<mojo::InlinedStructPtr<T>> + : public GenericHashTraits<mojo::InlinedStructPtr<T>> { + static const bool hasIsEmptyValueFunction = true; + static bool isEmptyValue(const mojo::InlinedStructPtr<T>& value) { + return value.is_null(); + } + static void constructDeletedValue(mojo::InlinedStructPtr<T>& slot, bool) { + mojo::internal::InlinedStructPtrWTFHelper<T>::ConstructDeletedValue(slot); + } + static bool isDeletedValue(const mojo::InlinedStructPtr<T>& value) { + return mojo::internal::InlinedStructPtrWTFHelper< + T>::IsHashTableDeletedValue(value); + } +}; + +} // namespace WTF + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_HASH_UTIL_H_ diff --git a/mojo/public/cpp/bindings/lib/wtf_serialization.h b/mojo/public/cpp/bindings/lib/wtf_serialization.h new file mode 100644 index 0000000000..0f112b9143 --- /dev/null +++ b/mojo/public/cpp/bindings/lib/wtf_serialization.h @@ -0,0 +1,12 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_SERIALIZATION_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_SERIALIZATION_H_ + +#include "mojo/public/cpp/bindings/array_traits_wtf_vector.h" +#include "mojo/public/cpp/bindings/map_traits_wtf_hash_map.h" +#include "mojo/public/cpp/bindings/string_traits_wtf.h" + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_WTF_SERIALIZATION_H_ diff --git a/mojo/public/cpp/bindings/map.h b/mojo/public/cpp/bindings/map.h new file mode 100644 index 0000000000..c1ba0756a3 --- /dev/null +++ b/mojo/public/cpp/bindings/map.h @@ -0,0 +1,42 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MAP_H_ + +#include <map> +#include <unordered_map> +#include <utility> + +namespace mojo { + +// TODO(yzshen): These conversion functions should be removed and callsites +// should be revisited and changed to use the same map type. +template <typename Key, typename Value> +std::unordered_map<Key, Value> MapToUnorderedMap( + const std::map<Key, Value>& input) { + return std::unordered_map<Key, Value>(input.begin(), input.end()); +} + +template <typename Key, typename Value> +std::unordered_map<Key, Value> MapToUnorderedMap(std::map<Key, Value>&& input) { + return std::unordered_map<Key, Value>(std::make_move_iterator(input.begin()), + std::make_move_iterator(input.end())); +} + +template <typename Key, typename Value> +std::map<Key, Value> UnorderedMapToMap( + const std::unordered_map<Key, Value>& input) { + return std::map<Key, Value>(input.begin(), input.end()); +} + +template <typename Key, typename Value> +std::map<Key, Value> UnorderedMapToMap(std::unordered_map<Key, Value>&& input) { + return std::map<Key, Value>(std::make_move_iterator(input.begin()), + std::make_move_iterator(input.end())); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_H_ diff --git a/mojo/public/cpp/bindings/map_data_view.h b/mojo/public/cpp/bindings/map_data_view.h new file mode 100644 index 0000000000..a65bb9eca1 --- /dev/null +++ b/mojo/public/cpp/bindings/map_data_view.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_DATA_VIEW_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MAP_DATA_VIEW_H_ + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/array_data_view.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/map_data_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/lib/serialization_forward.h" + +namespace mojo { + +template <typename K, typename V> +class MapDataView { + public: + using Data_ = typename internal::MojomTypeTraits<MapDataView<K, V>>::Data; + + MapDataView() {} + + MapDataView(Data_* data, internal::SerializationContext* context) + : keys_(data ? data->keys.Get() : nullptr, context), + values_(data ? data->values.Get() : nullptr, context) {} + + bool is_null() const { + DCHECK_EQ(keys_.is_null(), values_.is_null()); + return keys_.is_null(); + } + + size_t size() const { + DCHECK_EQ(keys_.size(), values_.size()); + return keys_.size(); + } + + ArrayDataView<K>& keys() { return keys_; } + const ArrayDataView<K>& keys() const { return keys_; } + + template <typename U> + bool ReadKeys(U* output) { + return internal::Deserialize<ArrayDataView<K>>(keys_.data_, output, + keys_.context_); + } + + ArrayDataView<V>& values() { return values_; } + const ArrayDataView<V>& values() const { return values_; } + + template <typename U> + bool ReadValues(U* output) { + return internal::Deserialize<ArrayDataView<V>>(values_.data_, output, + values_.context_); + } + + private: + ArrayDataView<K> keys_; + ArrayDataView<V> values_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_DATA_VIEW_H_ diff --git a/mojo/public/cpp/bindings/map_traits.h b/mojo/public/cpp/bindings/map_traits.h new file mode 100644 index 0000000000..5c0d8b2846 --- /dev/null +++ b/mojo/public/cpp/bindings/map_traits.h @@ -0,0 +1,56 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_H_ + +namespace mojo { + +// This must be specialized for any type |T| to be serialized/deserialized as +// a mojom map. +// +// Usually you would like to do a partial specialization for a map template. +// Imagine you want to specialize it for CustomMap<>, you need to implement: +// +// template <typename K, typename V> +// struct MapTraits<CustomMap<K, V>> { +// using Key = K; +// using Value = V; +// +// // These two methods are optional. Please see comments in struct_traits.h +// static bool IsNull(const CustomMap<K, V>& input); +// static void SetToNull(CustomMap<K, V>* output); +// +// static size_t GetSize(const CustomMap<K, V>& input); +// +// static CustomConstIterator GetBegin(const CustomMap<K, V>& input); +// static CustomIterator GetBegin(CustomMap<K, V>& input); +// +// static void AdvanceIterator(CustomConstIterator& iterator); +// static void AdvanceIterator(CustomIterator& iterator); +// +// static const K& GetKey(CustomIterator& iterator); +// static const K& GetKey(CustomConstIterator& iterator); +// +// static V& GetValue(CustomIterator& iterator); +// static const V& GetValue(CustomConstIterator& iterator); +// +// // Returning false results in deserialization failure and causes the +// // message pipe receiving it to be disconnected. |IK| and |IV| are +// // separate input key/value template parameters that allows for the +// // the key/value types to be forwarded. +// template <typename IK, typename IV> +// static bool Insert(CustomMap<K, V>& input, +// IK&& key, +// IV&& value); +// +// static void SetToEmpty(CustomMap<K, V>* output); +// }; +// +template <typename T> +struct MapTraits; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/map_traits_stl.h b/mojo/public/cpp/bindings/map_traits_stl.h new file mode 100644 index 0000000000..83a4399ce0 --- /dev/null +++ b/mojo/public/cpp/bindings/map_traits_stl.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_STL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_STL_H_ + +#include <map> +#include <unordered_map> + +#include "mojo/public/cpp/bindings/map_traits.h" + +namespace mojo { + +template <typename K, typename V> +struct MapTraits<std::map<K, V>> { + using Key = K; + using Value = V; + using Iterator = typename std::map<K, V>::iterator; + using ConstIterator = typename std::map<K, V>::const_iterator; + + static bool IsNull(const std::map<K, V>& input) { + // std::map<> is always converted to non-null mojom map. + return false; + } + + static void SetToNull(std::map<K, V>* output) { + // std::map<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const std::map<K, V>& input) { return input.size(); } + + static ConstIterator GetBegin(const std::map<K, V>& input) { + return input.begin(); + } + static Iterator GetBegin(std::map<K, V>& input) { return input.begin(); } + + static void AdvanceIterator(ConstIterator& iterator) { iterator++; } + static void AdvanceIterator(Iterator& iterator) { iterator++; } + + static const K& GetKey(Iterator& iterator) { return iterator->first; } + static const K& GetKey(ConstIterator& iterator) { return iterator->first; } + + static V& GetValue(Iterator& iterator) { return iterator->second; } + static const V& GetValue(ConstIterator& iterator) { return iterator->second; } + + static bool Insert(std::map<K, V>& input, const K& key, V&& value) { + input.insert(std::make_pair(key, std::forward<V>(value))); + return true; + } + static bool Insert(std::map<K, V>& input, const K& key, const V& value) { + input.insert(std::make_pair(key, value)); + return true; + } + + static void SetToEmpty(std::map<K, V>* output) { output->clear(); } +}; + +template <typename K, typename V> +struct MapTraits<std::unordered_map<K, V>> { + using Key = K; + using Value = V; + using Iterator = typename std::unordered_map<K, V>::iterator; + using ConstIterator = typename std::unordered_map<K, V>::const_iterator; + + static bool IsNull(const std::unordered_map<K, V>& input) { + // std::unordered_map<> is always converted to non-null mojom map. + return false; + } + + static void SetToNull(std::unordered_map<K, V>* output) { + // std::unordered_map<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const std::unordered_map<K, V>& input) { + return input.size(); + } + + static ConstIterator GetBegin(const std::unordered_map<K, V>& input) { + return input.begin(); + } + static Iterator GetBegin(std::unordered_map<K, V>& input) { + return input.begin(); + } + + static void AdvanceIterator(ConstIterator& iterator) { iterator++; } + static void AdvanceIterator(Iterator& iterator) { iterator++; } + + static const K& GetKey(Iterator& iterator) { return iterator->first; } + static const K& GetKey(ConstIterator& iterator) { return iterator->first; } + + static V& GetValue(Iterator& iterator) { return iterator->second; } + static const V& GetValue(ConstIterator& iterator) { return iterator->second; } + + template <typename IK, typename IV> + static bool Insert(std::unordered_map<K, V>& input, IK&& key, IV&& value) { + input.insert( + std::make_pair(std::forward<IK>(key), std::forward<IV>(value))); + return true; + } + + static void SetToEmpty(std::unordered_map<K, V>* output) { output->clear(); } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_STL_H_ diff --git a/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h b/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h new file mode 100644 index 0000000000..dd68b3686a --- /dev/null +++ b/mojo/public/cpp/bindings/map_traits_wtf_hash_map.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_WTF_HASH_MAP_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_WTF_HASH_MAP_H_ + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/map_traits.h" +#include "third_party/WebKit/Source/wtf/HashMap.h" + +namespace mojo { + +template <typename K, typename V> +struct MapTraits<WTF::HashMap<K, V>> { + using Key = K; + using Value = V; + using Iterator = typename WTF::HashMap<K, V>::iterator; + using ConstIterator = typename WTF::HashMap<K, V>::const_iterator; + + static bool IsNull(const WTF::HashMap<K, V>& input) { + // WTF::HashMap<> is always converted to non-null mojom map. + return false; + } + + static void SetToNull(WTF::HashMap<K, V>* output) { + // WTF::HashMap<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const WTF::HashMap<K, V>& input) { + return input.size(); + } + + static ConstIterator GetBegin(const WTF::HashMap<K, V>& input) { + return input.begin(); + } + static Iterator GetBegin(WTF::HashMap<K, V>& input) { return input.begin(); } + + static void AdvanceIterator(ConstIterator& iterator) { ++iterator; } + static void AdvanceIterator(Iterator& iterator) { ++iterator; } + + static const K& GetKey(Iterator& iterator) { return iterator->key; } + static const K& GetKey(ConstIterator& iterator) { return iterator->key; } + + static V& GetValue(Iterator& iterator) { return iterator->value; } + static const V& GetValue(ConstIterator& iterator) { return iterator->value; } + + template <typename IK, typename IV> + static bool Insert(WTF::HashMap<K, V>& input, IK&& key, IV&& value) { + if (!WTF::HashMap<K, V>::isValidKey(key)) { + LOG(ERROR) << "The key value is disallowed by WTF::HashMap"; + return false; + } + input.insert(std::forward<IK>(key), std::forward<IV>(value)); + return true; + } + + static void SetToEmpty(WTF::HashMap<K, V>* output) { output->clear(); } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MAP_TRAITS_WTF_HASH_MAP_H_ diff --git a/mojo/public/cpp/bindings/message.h b/mojo/public/cpp/bindings/message.h new file mode 100644 index 0000000000..48e6900306 --- /dev/null +++ b/mojo/public/cpp/bindings/message.h @@ -0,0 +1,302 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <limits> +#include <memory> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/message_buffer.h" +#include "mojo/public/cpp/bindings/lib/message_internal.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/system/message.h" + +namespace mojo { + +class AssociatedGroupController; + +using ReportBadMessageCallback = base::Callback<void(const std::string& error)>; + +// Message is a holder for the data and handles to be sent over a MessagePipe. +// Message owns its data and handles, but a consumer of Message is free to +// mutate the data and handles. The message's data is comprised of a header +// followed by payload. +class MOJO_CPP_BINDINGS_EXPORT Message { + public: + static const uint32_t kFlagExpectsResponse = 1 << 0; + static const uint32_t kFlagIsResponse = 1 << 1; + static const uint32_t kFlagIsSync = 1 << 2; + + Message(); + Message(Message&& other); + + ~Message(); + + Message& operator=(Message&& other); + + // Resets the Message to an uninitialized state. Upon reset, the Message + // exists as if it were default-constructed: it has no data buffer and owns no + // handles. + void Reset(); + + // Indicates whether this Message is uninitialized. + bool IsNull() const { return !buffer_; } + + // Initializes a Message with enough space for |capacity| bytes. + void Initialize(size_t capacity, bool zero_initialized); + + // Initializes a Message from an existing Mojo MessageHandle. + void InitializeFromMojoMessage(ScopedMessageHandle message, + uint32_t num_bytes, + std::vector<Handle>* handles); + + uint32_t data_num_bytes() const { + return static_cast<uint32_t>(buffer_->size()); + } + + // Access the raw bytes of the message. + const uint8_t* data() const { + return static_cast<const uint8_t*>(buffer_->data()); + } + + uint8_t* mutable_data() { return static_cast<uint8_t*>(buffer_->data()); } + + // Access the header. + const internal::MessageHeader* header() const { + return static_cast<const internal::MessageHeader*>(buffer_->data()); + } + internal::MessageHeader* header() { + return static_cast<internal::MessageHeader*>(buffer_->data()); + } + + const internal::MessageHeaderV1* header_v1() const { + DCHECK_GE(version(), 1u); + return static_cast<const internal::MessageHeaderV1*>(buffer_->data()); + } + internal::MessageHeaderV1* header_v1() { + DCHECK_GE(version(), 1u); + return static_cast<internal::MessageHeaderV1*>(buffer_->data()); + } + + const internal::MessageHeaderV2* header_v2() const { + DCHECK_GE(version(), 2u); + return static_cast<const internal::MessageHeaderV2*>(buffer_->data()); + } + internal::MessageHeaderV2* header_v2() { + DCHECK_GE(version(), 2u); + return static_cast<internal::MessageHeaderV2*>(buffer_->data()); + } + + uint32_t version() const { return header()->version; } + + uint32_t interface_id() const { return header()->interface_id; } + void set_interface_id(uint32_t id) { header()->interface_id = id; } + + uint32_t name() const { return header()->name; } + bool has_flag(uint32_t flag) const { return !!(header()->flags & flag); } + + // Access the request_id field (if present). + uint64_t request_id() const { return header_v1()->request_id; } + void set_request_id(uint64_t request_id) { + header_v1()->request_id = request_id; + } + + // Access the payload. + const uint8_t* payload() const; + uint8_t* mutable_payload() { return const_cast<uint8_t*>(payload()); } + uint32_t payload_num_bytes() const; + + uint32_t payload_num_interface_ids() const; + const uint32_t* payload_interface_ids() const; + + // Access the handles. + const std::vector<Handle>* handles() const { return &handles_; } + std::vector<Handle>* mutable_handles() { return &handles_; } + + const std::vector<ScopedInterfaceEndpointHandle>* + associated_endpoint_handles() const { + return &associated_endpoint_handles_; + } + std::vector<ScopedInterfaceEndpointHandle>* + mutable_associated_endpoint_handles() { + return &associated_endpoint_handles_; + } + + // Access the underlying Buffer interface. + internal::Buffer* buffer() { return buffer_.get(); } + + // Takes a scoped MessageHandle which may be passed to |WriteMessageNew()| for + // transmission. Note that this invalidates this Message object, taking + // ownership of its internal storage and any attached handles. + ScopedMessageHandle TakeMojoMessage(); + + // Notifies the system that this message is "bad," in this case meaning it was + // rejected by bindings validation code. + void NotifyBadMessage(const std::string& error); + + // Serializes |associated_endpoint_handles_| into the payload_interface_ids + // field. + void SerializeAssociatedEndpointHandles( + AssociatedGroupController* group_controller); + + // Deserializes |associated_endpoint_handles_| from the payload_interface_ids + // field. + bool DeserializeAssociatedEndpointHandles( + AssociatedGroupController* group_controller); + + private: + void CloseHandles(); + + std::unique_ptr<internal::MessageBuffer> buffer_; + std::vector<Handle> handles_; + std::vector<ScopedInterfaceEndpointHandle> associated_endpoint_handles_; + + DISALLOW_COPY_AND_ASSIGN(Message); +}; + +class MessageReceiver { + public: + virtual ~MessageReceiver() {} + + // The receiver may mutate the given message. Returns true if the message + // was accepted and false otherwise, indicating that the message was invalid + // or malformed. + virtual bool Accept(Message* message) WARN_UNUSED_RESULT = 0; +}; + +class MessageReceiverWithResponder : public MessageReceiver { + public: + ~MessageReceiverWithResponder() override {} + + // A variant on Accept that registers a MessageReceiver (known as the + // responder) to handle the response message generated from the given + // message. The responder's Accept method may be called during + // AcceptWithResponder or some time after its return. + virtual bool AcceptWithResponder(Message* message, + std::unique_ptr<MessageReceiver> responder) + WARN_UNUSED_RESULT = 0; +}; + +// A MessageReceiver that is also able to provide status about the state +// of the underlying MessagePipe to which it will be forwarding messages +// received via the |Accept()| call. +class MessageReceiverWithStatus : public MessageReceiver { + public: + ~MessageReceiverWithStatus() override {} + + // Returns |true| if this MessageReceiver is currently bound to a MessagePipe, + // the pipe has not been closed, and the pipe has not encountered an error. + virtual bool IsValid() = 0; + + // DCHECKs if this MessageReceiver is currently bound to a MessagePipe, the + // pipe has not been closed, and the pipe has not encountered an error. + // This function may be called on any thread. + virtual void DCheckInvalid(const std::string& message) = 0; +}; + +// An alternative to MessageReceiverWithResponder for cases in which it +// is necessary for the implementor of this interface to know about the status +// of the MessagePipe which will carry the responses. +class MessageReceiverWithResponderStatus : public MessageReceiver { + public: + ~MessageReceiverWithResponderStatus() override {} + + // A variant on Accept that registers a MessageReceiverWithStatus (known as + // the responder) to handle the response message generated from the given + // message. Any of the responder's methods (Accept or IsValid) may be called + // during AcceptWithResponder or some time after its return. + virtual bool AcceptWithResponder(Message* message, + std::unique_ptr<MessageReceiverWithStatus> + responder) WARN_UNUSED_RESULT = 0; +}; + +class MOJO_CPP_BINDINGS_EXPORT PassThroughFilter + : NON_EXPORTED_BASE(public MessageReceiver) { + public: + PassThroughFilter(); + ~PassThroughFilter() override; + + // MessageReceiver: + bool Accept(Message* message) override; + + private: + DISALLOW_COPY_AND_ASSIGN(PassThroughFilter); +}; + +namespace internal { +class SyncMessageResponseSetup; +} + +// An object which should be constructed on the stack immediately before making +// a sync request for which the caller wishes to perform custom validation of +// the response value(s). It is illegal to make more than one sync call during +// the lifetime of the topmost SyncMessageResponseContext, but it is legal to +// nest contexts to support reentrancy. +// +// Usage should look something like: +// +// SyncMessageResponseContext response_context; +// foo_interface->SomeSyncCall(&response_value); +// if (response_value.IsBad()) +// response_context.ReportBadMessage("Bad response_value!"); +// +class MOJO_CPP_BINDINGS_EXPORT SyncMessageResponseContext { + public: + SyncMessageResponseContext(); + ~SyncMessageResponseContext(); + + static SyncMessageResponseContext* current(); + + void ReportBadMessage(const std::string& error); + + const ReportBadMessageCallback& GetBadMessageCallback(); + + private: + friend class internal::SyncMessageResponseSetup; + + SyncMessageResponseContext* outer_context_; + Message response_; + ReportBadMessageCallback bad_message_callback_; + + DISALLOW_COPY_AND_ASSIGN(SyncMessageResponseContext); +}; + +// Read a single message from the pipe. The caller should have created the +// Message, but not called Initialize(). Returns MOJO_RESULT_SHOULD_WAIT if +// the caller should wait on the handle to become readable. Returns +// MOJO_RESULT_OK if the message was read successfully and should be +// dispatched, otherwise returns an error code if something went wrong. +// +// NOTE: The message hasn't been validated and may be malformed! +MojoResult ReadMessage(MessagePipeHandle handle, Message* message); + +// Reports the currently dispatching Message as bad. Note that this is only +// legal to call from directly within the stack frame of a message dispatch. If +// you need to do asynchronous work before you can determine the legitimacy of +// a message, use TakeBadMessageCallback() and retain its result until you're +// ready to invoke or discard it. +MOJO_CPP_BINDINGS_EXPORT +void ReportBadMessage(const std::string& error); + +// Acquires a callback which may be run to report the currently dispatching +// Message as bad. Note that this is only legal to call from directly within the +// stack frame of a message dispatch, but the returned callback may be called +// exactly once any time thereafter to report the message as bad. This may only +// be called once per message. +MOJO_CPP_BINDINGS_EXPORT +ReportBadMessageCallback GetBadMessageCallback(); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_H_ diff --git a/mojo/public/cpp/bindings/message_header_validator.h b/mojo/public/cpp/bindings/message_header_validator.h new file mode 100644 index 0000000000..50c19dbe04 --- /dev/null +++ b/mojo/public/cpp/bindings/message_header_validator.h @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_HEADER_VALIDATOR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_HEADER_VALIDATOR_H_ + +#include "base/compiler_specific.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +class MOJO_CPP_BINDINGS_EXPORT MessageHeaderValidator + : NON_EXPORTED_BASE(public MessageReceiver) { + public: + MessageHeaderValidator(); + explicit MessageHeaderValidator(const std::string& description); + + // Sets the description associated with this validator. Used for reporting + // more detailed validation errors. + void SetDescription(const std::string& description); + + bool Accept(Message* message) override; + + private: + std::string description_; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_MESSAGE_HEADER_VALIDATOR_H_ diff --git a/mojo/public/cpp/bindings/native_enum.h b/mojo/public/cpp/bindings/native_enum.h new file mode 100644 index 0000000000..08b43b78bf --- /dev/null +++ b/mojo/public/cpp/bindings/native_enum.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_NATIVE_ENUM_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_NATIVE_ENUM_H_ + +#include <functional> + +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/native_enum_data.h" + +namespace mojo { + +// Native-only enums correspond to "[Native] enum Foo;" definitions in mojom. +enum class NativeEnum : int32_t {}; + +} // namespace mojo + +namespace std { + +template <> +struct hash<mojo::NativeEnum> + : public mojo::internal::EnumHashImpl<mojo::NativeEnum> {}; + +} // namespace std + +#endif // MOJO_PUBLIC_CPP_BINDINGS_NATIVE_ENUM_H_ diff --git a/mojo/public/cpp/bindings/native_struct.h b/mojo/public/cpp/bindings/native_struct.h new file mode 100644 index 0000000000..ac27250bcc --- /dev/null +++ b/mojo/public/cpp/bindings/native_struct.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_ + +#include <vector> + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/lib/native_struct_data.h" +#include "mojo/public/cpp/bindings/struct_ptr.h" +#include "mojo/public/cpp/bindings/type_converter.h" + +namespace mojo { + +class NativeStruct; +using NativeStructPtr = StructPtr<NativeStruct>; + +// Native-only structs correspond to "[Native] struct Foo;" definitions in +// mojom. +class MOJO_CPP_BINDINGS_EXPORT NativeStruct { + public: + using Data_ = internal::NativeStruct_Data; + + static NativeStructPtr New(); + + template <typename U> + static NativeStructPtr From(const U& u) { + return TypeConverter<NativeStructPtr, U>::Convert(u); + } + + template <typename U> + U To() const { + return TypeConverter<U, NativeStruct>::Convert(*this); + } + + NativeStruct(); + ~NativeStruct(); + + NativeStructPtr Clone() const; + bool Equals(const NativeStruct& other) const; + size_t Hash(size_t seed) const; + + base::Optional<std::vector<uint8_t>> data; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_H_ diff --git a/mojo/public/cpp/bindings/native_struct_data_view.h b/mojo/public/cpp/bindings/native_struct_data_view.h new file mode 100644 index 0000000000..613bd7a0b0 --- /dev/null +++ b/mojo/public/cpp/bindings/native_struct_data_view.h @@ -0,0 +1,36 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_ + +#include "mojo/public/cpp/bindings/lib/native_struct_data.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" + +namespace mojo { + +class NativeStructDataView { + public: + using Data_ = internal::NativeStruct_Data; + + NativeStructDataView() {} + + NativeStructDataView(Data_* data, internal::SerializationContext* context) + : data_(data) {} + + bool is_null() const { return !data_; } + + size_t size() const { return data_->data.size(); } + + uint8_t operator[](size_t index) const { return data_->data.at(index); } + + const uint8_t* data() const { return data_->data.storage(); } + + private: + Data_* data_ = nullptr; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_NATIVE_STRUCT_DATA_VIEW_H_ diff --git a/mojo/public/cpp/bindings/pipe_control_message_handler.h b/mojo/public/cpp/bindings/pipe_control_message_handler.h new file mode 100644 index 0000000000..a5c04da627 --- /dev/null +++ b/mojo/public/cpp/bindings/pipe_control_message_handler.h @@ -0,0 +1,55 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +class PipeControlMessageHandlerDelegate; + +// Handler for messages defined in pipe_control_messages.mojom. +class MOJO_CPP_BINDINGS_EXPORT PipeControlMessageHandler + : NON_EXPORTED_BASE(public MessageReceiver) { + public: + explicit PipeControlMessageHandler( + PipeControlMessageHandlerDelegate* delegate); + ~PipeControlMessageHandler() override; + + // Sets the description for this handler. Used only when reporting validation + // errors. + void SetDescription(const std::string& description); + + // NOTE: |message| must have passed message header validation. + static bool IsPipeControlMessage(const Message* message); + + // MessageReceiver implementation: + + // NOTE: |message| must: + // - have passed message header validation; and + // - be a pipe control message (i.e., IsPipeControlMessage() returns true). + // If the method returns false, the message pipe should be closed. + bool Accept(Message* message) override; + + private: + // |message| must have passed message header validation. + bool Validate(Message* message); + bool RunOrClosePipe(Message* message); + + std::string description_; + PipeControlMessageHandlerDelegate* const delegate_; + + DISALLOW_COPY_AND_ASSIGN(PipeControlMessageHandler); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_H_ diff --git a/mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h b/mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h new file mode 100644 index 0000000000..16fd918d5d --- /dev/null +++ b/mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h @@ -0,0 +1,29 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_DELEGATE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_DELEGATE_H_ + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/interface_id.h" + +namespace mojo { + +class PipeControlMessageHandlerDelegate { + public: + // The implementation of the following methods should return false if the + // notification is unexpected. In that case, the user of this delegate is + // expected to close the message pipe. + virtual bool OnPeerAssociatedEndpointClosed( + InterfaceId id, + const base::Optional<DisconnectReason>& reason) = 0; + + protected: + virtual ~PipeControlMessageHandlerDelegate() {} +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_HANDLER_DELEGATE_H_ diff --git a/mojo/public/cpp/bindings/pipe_control_message_proxy.h b/mojo/public/cpp/bindings/pipe_control_message_proxy.h new file mode 100644 index 0000000000..52c408f827 --- /dev/null +++ b/mojo/public/cpp/bindings/pipe_control_message_proxy.h @@ -0,0 +1,45 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_PROXY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_PROXY_H_ + +#include "base/macros.h" +#include "base/optional.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/interface_id.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { + +class MessageReceiver; + +// Proxy for request messages defined in pipe_control_messages.mojom. +// +// NOTE: This object may be used from multiple threads. +class MOJO_CPP_BINDINGS_EXPORT PipeControlMessageProxy { + public: + // Doesn't take ownership of |receiver|. If This PipeControlMessageProxy will + // be used from multiple threads, |receiver| must be thread-safe. + explicit PipeControlMessageProxy(MessageReceiver* receiver); + + void NotifyPeerEndpointClosed(InterfaceId id, + const base::Optional<DisconnectReason>& reason); + + static Message ConstructPeerEndpointClosedMessage( + InterfaceId id, + const base::Optional<DisconnectReason>& reason); + + private: + // Not owned. + MessageReceiver* receiver_; + + DISALLOW_COPY_AND_ASSIGN(PipeControlMessageProxy); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_PIPE_CONTROL_MESSAGE_PROXY_H_ diff --git a/mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h b/mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h new file mode 100644 index 0000000000..4d40cdfc12 --- /dev/null +++ b/mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_RAW_PTR_IMPL_REF_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_RAW_PTR_IMPL_REF_TRAITS_H_ + +namespace mojo { + +// Default traits for a binding's implementation reference type. This +// corresponds to a raw pointer. +template <typename Interface> +struct RawPtrImplRefTraits { + using PointerType = Interface*; + + static bool IsNull(PointerType ptr) { return !ptr; } + static Interface* GetRawPointer(PointerType* ptr) { return *ptr; } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_LIB_RAW_PTR_IMPL_REF_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h b/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h new file mode 100644 index 0000000000..16527cf747 --- /dev/null +++ b/mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h @@ -0,0 +1,123 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_SCOPED_INTERFACE_ENDPOINT_HANDLE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_SCOPED_INTERFACE_ENDPOINT_HANDLE_H_ + +#include <string> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/disconnect_reason.h" +#include "mojo/public/cpp/bindings/interface_id.h" + +namespace mojo { + +class AssociatedGroupController; + +// ScopedInterfaceEndpointHandle refers to one end of an interface, either the +// implementation side or the client side. +// Threading: At any given time, a ScopedInterfaceEndpointHandle should only +// be accessed from a single thread. +class MOJO_CPP_BINDINGS_EXPORT ScopedInterfaceEndpointHandle { + public: + // Creates a pair of handles representing the two endpoints of an interface, + // which are not yet associated with a message pipe. + static void CreatePairPendingAssociation( + ScopedInterfaceEndpointHandle* handle0, + ScopedInterfaceEndpointHandle* handle1); + + // Creates an invalid endpoint handle. + ScopedInterfaceEndpointHandle(); + + ScopedInterfaceEndpointHandle(ScopedInterfaceEndpointHandle&& other); + + ~ScopedInterfaceEndpointHandle(); + + ScopedInterfaceEndpointHandle& operator=( + ScopedInterfaceEndpointHandle&& other); + + bool is_valid() const; + + // Returns true if the interface hasn't associated with a message pipe. + bool pending_association() const; + + // Returns kInvalidInterfaceId when in pending association state or the handle + // is invalid. + InterfaceId id() const; + + // Returns null when in pending association state or the handle is invalid. + AssociatedGroupController* group_controller() const; + + // Returns the disconnect reason if the peer handle is closed before + // association and specifies a custom disconnect reason. + const base::Optional<DisconnectReason>& disconnect_reason() const; + + enum AssociationEvent { + // The interface has been associated with a message pipe. + ASSOCIATED, + // The peer of this object has been closed before association. + PEER_CLOSED_BEFORE_ASSOCIATION + }; + + using AssociationEventCallback = base::OnceCallback<void(AssociationEvent)>; + // Note: + // - |handler| won't run if the handle is invalid. Otherwise, |handler| is run + // on the calling thread asynchronously, even if the interface has already + // been associated or the peer has been closed before association. + // - |handler| won't be called after this object is destroyed or reset. + // - A null |handler| can be used to cancel the previous callback. + void SetAssociationEventHandler(AssociationEventCallback handler); + + void reset(); + void ResetWithReason(uint32_t custom_reason, const std::string& description); + + private: + friend class AssociatedGroupController; + friend class AssociatedGroup; + + class State; + + // Used by AssociatedGroupController. + ScopedInterfaceEndpointHandle( + InterfaceId id, + scoped_refptr<AssociatedGroupController> group_controller); + + // Used by AssociatedGroupController. + // The peer of this handle will join |peer_group_controller|. + bool NotifyAssociation( + InterfaceId id, + scoped_refptr<AssociatedGroupController> peer_group_controller); + + void ResetInternal(const base::Optional<DisconnectReason>& reason); + + // Used by AssociatedGroup. + // It is safe to run the returned callback on any thread, or after this handle + // is destroyed. + // The return value of the getter: + // - If the getter is retrieved when the handle is invalid, the return value + // of the getter will always be null. + // - If the getter is retrieved when the handle is valid and non-pending, + // the return value of the getter will be non-null and remain unchanged + // even if the handle is later reset. + // - If the getter is retrieved when the handle is valid but pending + // asssociation, the return value of the getter will initially be null, + // change to non-null when the handle is associated, and remain unchanged + // ever since. + base::Callback<AssociatedGroupController*()> CreateGroupControllerGetter() + const; + + scoped_refptr<State> state_; + + DISALLOW_COPY_AND_ASSIGN(ScopedInterfaceEndpointHandle); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_SCOPED_INTERFACE_ENDPOINT_HANDLE_H_ diff --git a/mojo/public/cpp/bindings/string_data_view.h b/mojo/public/cpp/bindings/string_data_view.h new file mode 100644 index 0000000000..2b091b45f8 --- /dev/null +++ b/mojo/public/cpp/bindings/string_data_view.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_DATA_VIEW_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRING_DATA_VIEW_H_ + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization_context.h" + +namespace mojo { + +// Access to the contents of a serialized string. +class StringDataView { + public: + StringDataView() {} + + StringDataView(internal::String_Data* data, + internal::SerializationContext* context) + : data_(data) {} + + bool is_null() const { return !data_; } + + const char* storage() const { return data_->storage(); } + + size_t size() const { return data_->size(); } + + private: + internal::String_Data* data_ = nullptr; +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_DATA_VIEW_H_ diff --git a/mojo/public/cpp/bindings/string_traits.h b/mojo/public/cpp/bindings/string_traits.h new file mode 100644 index 0000000000..7d3075a579 --- /dev/null +++ b/mojo/public/cpp/bindings/string_traits.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_H_ + +#include "mojo/public/cpp/bindings/string_data_view.h" + +namespace mojo { + +// This must be specialized for any type |T| to be serialized/deserialized as +// a mojom string. +// +// Imagine you want to specialize it for CustomString, usually you need to +// implement: +// +// template <T> +// struct StringTraits<CustomString> { +// // These two methods are optional. Please see comments in struct_traits.h +// static bool IsNull(const CustomString& input); +// static void SetToNull(CustomString* output); +// +// static size_t GetSize(const CustomString& input); +// static const char* GetData(const CustomString& input); +// +// // The caller guarantees that |!input.is_null()|. +// static bool Read(StringDataView input, CustomString* output); +// }; +// +// In some cases, you may need to do conversion before you can return the size +// and data as 8-bit characters for serialization. (For example, CustomString is +// UTF-16 string). In that case, you can add two optional methods: +// +// static void* SetUpContext(const CustomString& input); +// static void TearDownContext(const CustomString& input, void* context); +// +// And then you append a second parameter, void* context, to GetSize() and +// GetData(): +// +// static size_t GetSize(const CustomString& input, void* context); +// static const char* GetData(const CustomString& input, void* context); +// +// If a CustomString instance is not null, the serialization code will call +// SetUpContext() at the beginning, and pass the resulting context pointer to +// GetSize()/GetData(). After serialization is done, it calls TearDownContext() +// so that you can do any necessary cleanup. +// +template <typename T> +struct StringTraits; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/string_traits_stl.h b/mojo/public/cpp/bindings/string_traits_stl.h new file mode 100644 index 0000000000..f6cc8ad098 --- /dev/null +++ b/mojo/public/cpp/bindings/string_traits_stl.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STL_H_ + +#include <string> + +#include "mojo/public/cpp/bindings/string_traits.h" + +namespace mojo { + +template <> +struct StringTraits<std::string> { + static bool IsNull(const std::string& input) { + // std::string is always converted to non-null mojom string. + return false; + } + + static void SetToNull(std::string* output) { + // std::string doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const std::string& input) { return input.size(); } + + static const char* GetData(const std::string& input) { return input.data(); } + + static bool Read(StringDataView input, std::string* output) { + output->assign(input.storage(), input.size()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STL_H_ diff --git a/mojo/public/cpp/bindings/string_traits_string16.h b/mojo/public/cpp/bindings/string_traits_string16.h new file mode 100644 index 0000000000..f96973ad91 --- /dev/null +++ b/mojo/public/cpp/bindings/string_traits_string16.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_ + +#include "base/strings/string16.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/string_traits.h" + +namespace mojo { + +template <> +struct MOJO_CPP_BINDINGS_EXPORT StringTraits<base::string16> { + static bool IsNull(const base::string16& input) { + // base::string16 is always converted to non-null mojom string. + return false; + } + + static void SetToNull(base::string16* output) { + // Convert null to an "empty" base::string16. + output->clear(); + } + + static void* SetUpContext(const base::string16& input); + static void TearDownContext(const base::string16& input, void* context); + + static size_t GetSize(const base::string16& input, void* context); + static const char* GetData(const base::string16& input, void* context); + + static bool Read(StringDataView input, base::string16* output); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING16_H_ diff --git a/mojo/public/cpp/bindings/string_traits_string_piece.h b/mojo/public/cpp/bindings/string_traits_string_piece.h new file mode 100644 index 0000000000..af6be893ac --- /dev/null +++ b/mojo/public/cpp/bindings/string_traits_string_piece.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING_PIECE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING_PIECE_H_ + +#include "base/strings/string_piece.h" +#include "mojo/public/cpp/bindings/string_traits.h" + +namespace mojo { + +template <> +struct StringTraits<base::StringPiece> { + static bool IsNull(const base::StringPiece& input) { + // base::StringPiece is always converted to non-null mojom string. We could + // have let StringPiece containing a null data pointer map to null mojom + // string, but StringPiece::empty() returns true in this case. It seems + // confusing to mix the concept of empty and null strings, especially + // because they mean different things in mojom. + return false; + } + + static void SetToNull(base::StringPiece* output) { + // Convert null to an "empty" base::StringPiece. + output->set(nullptr, 0); + } + + static size_t GetSize(const base::StringPiece& input) { return input.size(); } + + static const char* GetData(const base::StringPiece& input) { + return input.data(); + } + + static bool Read(StringDataView input, base::StringPiece* output) { + output->set(input.storage(), input.size()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_STRING_PIECE_H_ diff --git a/mojo/public/cpp/bindings/string_traits_wtf.h b/mojo/public/cpp/bindings/string_traits_wtf.h new file mode 100644 index 0000000000..238c2eb119 --- /dev/null +++ b/mojo/public/cpp/bindings/string_traits_wtf.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_WTF_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_WTF_H_ + +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/string_traits.h" +#include "third_party/WebKit/Source/wtf/text/WTFString.h" + +namespace mojo { + +template <> +struct StringTraits<WTF::String> { + static bool IsNull(const WTF::String& input) { return input.isNull(); } + static void SetToNull(WTF::String* output); + + static void* SetUpContext(const WTF::String& input); + static void TearDownContext(const WTF::String& input, void* context); + + static size_t GetSize(const WTF::String& input, void* context); + + static const char* GetData(const WTF::String& input, void* context); + + static bool Read(StringDataView input, WTF::String* output); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRING_TRAITS_WTF_H_ diff --git a/mojo/public/cpp/bindings/strong_associated_binding.h b/mojo/public/cpp/bindings/strong_associated_binding.h new file mode 100644 index 0000000000..a1e299bb2d --- /dev/null +++ b/mojo/public/cpp/bindings/strong_associated_binding.h @@ -0,0 +1,125 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_H_ + +#include <memory> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +template <typename Interface> +class StrongAssociatedBinding; + +template <typename Interface> +using StrongAssociatedBindingPtr = + base::WeakPtr<StrongAssociatedBinding<Interface>>; + +// This connects an interface implementation strongly to an associated pipe. +// When a connection error is detected the implementation is deleted. If the +// task runner that a StrongAssociatedBinding is bound on is stopped, the +// connection error handler will not be invoked and the implementation will not +// be deleted. +// +// To use, call StrongAssociatedBinding<T>::Create() (see below) or the helper +// MakeStrongAssociatedBinding function: +// +// mojo::MakeStrongAssociatedBinding(base::MakeUnique<FooImpl>(), +// std::move(foo_request)); +// +template <typename Interface> +class StrongAssociatedBinding { + public: + // Create a new StrongAssociatedBinding instance. The instance owns itself, + // cleaning up only in the event of a pipe connection error. Returns a WeakPtr + // to the new StrongAssociatedBinding instance. + static StrongAssociatedBindingPtr<Interface> Create( + std::unique_ptr<Interface> impl, + AssociatedInterfaceRequest<Interface> request) { + StrongAssociatedBinding* binding = + new StrongAssociatedBinding(std::move(impl), std::move(request)); + return binding->weak_factory_.GetWeakPtr(); + } + + // Note: The error handler must not delete the interface implementation. + // + // This method may only be called after this StrongAssociatedBinding has been + // bound to a message pipe. + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(binding_.is_bound()); + connection_error_handler_ = error_handler; + connection_error_with_reason_handler_.Reset(); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(binding_.is_bound()); + connection_error_with_reason_handler_ = error_handler; + connection_error_handler_.Reset(); + } + + // Forces the binding to close. This destroys the StrongBinding instance. + void Close() { delete this; } + + Interface* impl() { return impl_.get(); } + + // Sends a message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting() { binding_.FlushForTesting(); } + + private: + StrongAssociatedBinding(std::unique_ptr<Interface> impl, + AssociatedInterfaceRequest<Interface> request) + : impl_(std::move(impl)), + binding_(impl_.get(), std::move(request)), + weak_factory_(this) { + binding_.set_connection_error_with_reason_handler(base::Bind( + &StrongAssociatedBinding::OnConnectionError, base::Unretained(this))); + } + + ~StrongAssociatedBinding() {} + + void OnConnectionError(uint32_t custom_reason, + const std::string& description) { + if (!connection_error_handler_.is_null()) + connection_error_handler_.Run(); + else if (!connection_error_with_reason_handler_.is_null()) + connection_error_with_reason_handler_.Run(custom_reason, description); + Close(); + } + + std::unique_ptr<Interface> impl_; + base::Closure connection_error_handler_; + ConnectionErrorWithReasonCallback connection_error_with_reason_handler_; + AssociatedBinding<Interface> binding_; + base::WeakPtrFactory<StrongAssociatedBinding> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(StrongAssociatedBinding); +}; + +template <typename Interface, typename Impl> +StrongAssociatedBindingPtr<Interface> MakeStrongAssociatedBinding( + std::unique_ptr<Impl> impl, + AssociatedInterfaceRequest<Interface> request) { + return StrongAssociatedBinding<Interface>::Create(std::move(impl), + std::move(request)); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_ASSOCIATED_BINDING_H_ diff --git a/mojo/public/cpp/bindings/strong_binding.h b/mojo/public/cpp/bindings/strong_binding.h new file mode 100644 index 0000000000..f4b4a061cd --- /dev/null +++ b/mojo/public/cpp/bindings/strong_binding.h @@ -0,0 +1,125 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_ + +#include <memory> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/connection_error_callback.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +template <typename Interface> +class StrongBinding; + +template <typename Interface> +using StrongBindingPtr = base::WeakPtr<StrongBinding<Interface>>; + +// This connects an interface implementation strongly to a pipe. When a +// connection error is detected the implementation is deleted. If the task +// runner that a StrongBinding is bound on is stopped, the connection error +// handler will not be invoked and the implementation will not be deleted. +// +// To use, call StrongBinding<T>::Create() (see below) or the helper +// MakeStrongBinding function: +// +// mojo::MakeStrongBinding(base::MakeUnique<FooImpl>(), +// std::move(foo_request)); +// +template <typename Interface> +class StrongBinding { + public: + // Create a new StrongBinding instance. The instance owns itself, cleaning up + // only in the event of a pipe connection error. Returns a WeakPtr to the new + // StrongBinding instance. + static StrongBindingPtr<Interface> Create( + std::unique_ptr<Interface> impl, + InterfaceRequest<Interface> request) { + StrongBinding* binding = + new StrongBinding(std::move(impl), std::move(request)); + return binding->weak_factory_.GetWeakPtr(); + } + + // Note: The error handler must not delete the interface implementation. + // + // This method may only be called after this StrongBinding has been bound to a + // message pipe. + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(binding_.is_bound()); + connection_error_handler_ = error_handler; + connection_error_with_reason_handler_.Reset(); + } + + void set_connection_error_with_reason_handler( + const ConnectionErrorWithReasonCallback& error_handler) { + DCHECK(binding_.is_bound()); + connection_error_with_reason_handler_ = error_handler; + connection_error_handler_.Reset(); + } + + // Forces the binding to close. This destroys the StrongBinding instance. + void Close() { delete this; } + + Interface* impl() { return impl_.get(); } + + // Sends a message on the underlying message pipe and runs the current + // message loop until its response is received. This can be used in tests to + // verify that no message was sent on a message pipe in response to some + // stimulus. + void FlushForTesting() { binding_.FlushForTesting(); } + + private: + StrongBinding(std::unique_ptr<Interface> impl, + InterfaceRequest<Interface> request) + : impl_(std::move(impl)), + binding_(impl_.get(), std::move(request)), + weak_factory_(this) { + binding_.set_connection_error_with_reason_handler( + base::Bind(&StrongBinding::OnConnectionError, base::Unretained(this))); + } + + ~StrongBinding() {} + + void OnConnectionError(uint32_t custom_reason, + const std::string& description) { + if (!connection_error_handler_.is_null()) + connection_error_handler_.Run(); + else if (!connection_error_with_reason_handler_.is_null()) + connection_error_with_reason_handler_.Run(custom_reason, description); + Close(); + } + + std::unique_ptr<Interface> impl_; + base::Closure connection_error_handler_; + ConnectionErrorWithReasonCallback connection_error_with_reason_handler_; + Binding<Interface> binding_; + base::WeakPtrFactory<StrongBinding> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(StrongBinding); +}; + +template <typename Interface, typename Impl> +StrongBindingPtr<Interface> MakeStrongBinding( + std::unique_ptr<Impl> impl, + InterfaceRequest<Interface> request) { + return StrongBinding<Interface>::Create(std::move(impl), std::move(request)); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_H_ diff --git a/mojo/public/cpp/bindings/strong_binding_set.h b/mojo/public/cpp/bindings/strong_binding_set.h new file mode 100644 index 0000000000..f6bcd5259c --- /dev/null +++ b/mojo/public/cpp/bindings/strong_binding_set.h @@ -0,0 +1,26 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_SET_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_SET_H_ + +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h" + +namespace mojo { + +// This class manages a set of bindings. When the pipe a binding is bound to is +// disconnected, the binding is automatically destroyed and removed from the +// set, and the interface implementation is deleted. When the StrongBindingSet +// is destructed, all outstanding bindings in the set are destroyed and all the +// bound interface implementations are automatically deleted. +template <typename Interface, typename ContextType = void> +using StrongBindingSet = + BindingSetBase<Interface, + Binding<Interface, UniquePtrImplRefTraits<Interface>>, + ContextType>; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRONG_BINDING_SET_H_ diff --git a/mojo/public/cpp/bindings/struct_ptr.h b/mojo/public/cpp/bindings/struct_ptr.h new file mode 100644 index 0000000000..b135312e39 --- /dev/null +++ b/mojo/public/cpp/bindings/struct_ptr.h @@ -0,0 +1,283 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ + +#include <functional> +#include <memory> +#include <new> + +#include "base/logging.h" +#include "base/macros.h" +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/hash_util.h" +#include "mojo/public/cpp/bindings/type_converter.h" + +namespace mojo { +namespace internal { + +constexpr size_t kHashSeed = 31; + +template <typename Struct> +class StructPtrWTFHelper; + +template <typename Struct> +class InlinedStructPtrWTFHelper; + +} // namespace internal + +// Smart pointer wrapping a mojom structure with move-only semantics. +template <typename S> +class StructPtr { + public: + using Struct = S; + + StructPtr() = default; + StructPtr(decltype(nullptr)) {} + + ~StructPtr() = default; + + StructPtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + StructPtr(StructPtr&& other) { Take(&other); } + StructPtr& operator=(StructPtr&& other) { + Take(&other); + return *this; + } + + template <typename... Args> + StructPtr(base::in_place_t, Args&&... args) + : ptr_(new Struct(std::forward<Args>(args)...)) {} + + template <typename U> + U To() const { + return TypeConverter<U, StructPtr>::Convert(*this); + } + + void reset() { ptr_.reset(); } + + bool is_null() const { return !ptr_; } + + Struct& operator*() const { + DCHECK(ptr_); + return *ptr_; + } + Struct* operator->() const { + DCHECK(ptr_); + return ptr_.get(); + } + Struct* get() const { return ptr_.get(); } + + void Swap(StructPtr* other) { std::swap(ptr_, other->ptr_); } + + // Please note that calling this method will fail compilation if the value + // type |Struct| doesn't have a Clone() method defined (which usually means + // that it contains Mojo handles). + StructPtr Clone() const { return is_null() ? StructPtr() : ptr_->Clone(); } + + // Compares the pointees (which might both be null). + // TODO(tibell): Get rid of Equals in favor of the operator. Same for Hash. + bool Equals(const StructPtr& other) const { + if (is_null() || other.is_null()) + return is_null() && other.is_null(); + return ptr_->Equals(*other.ptr_); + } + + // Hashes based on the pointee (which might be null). + size_t Hash(size_t seed) const { + if (is_null()) + return internal::HashCombine(seed, 0); + return ptr_->Hash(seed); + } + + explicit operator bool() const { return !is_null(); } + + private: + friend class internal::StructPtrWTFHelper<Struct>; + void Take(StructPtr* other) { + reset(); + Swap(other); + } + + std::unique_ptr<Struct> ptr_; + + DISALLOW_COPY_AND_ASSIGN(StructPtr); +}; + +template <typename T> +bool operator==(const StructPtr<T>& lhs, const StructPtr<T>& rhs) { + return lhs.Equals(rhs); +} +template <typename T> +bool operator!=(const StructPtr<T>& lhs, const StructPtr<T>& rhs) { + return !(lhs == rhs); +} + +// Designed to be used when Struct is small and copyable. +template <typename S> +class InlinedStructPtr { + public: + using Struct = S; + + InlinedStructPtr() : state_(NIL) {} + InlinedStructPtr(decltype(nullptr)) : state_(NIL) {} + + ~InlinedStructPtr() {} + + InlinedStructPtr& operator=(decltype(nullptr)) { + reset(); + return *this; + } + + InlinedStructPtr(InlinedStructPtr&& other) : state_(NIL) { Take(&other); } + InlinedStructPtr& operator=(InlinedStructPtr&& other) { + Take(&other); + return *this; + } + + template <typename... Args> + InlinedStructPtr(base::in_place_t, Args&&... args) + : value_(std::forward<Args>(args)...), state_(VALID) {} + + template <typename U> + U To() const { + return TypeConverter<U, InlinedStructPtr>::Convert(*this); + } + + void reset() { + state_ = NIL; + value_. ~Struct(); + new (&value_) Struct(); + } + + bool is_null() const { return state_ == NIL; } + + Struct& operator*() const { + DCHECK(state_ == VALID); + return value_; + } + Struct* operator->() const { + DCHECK(state_ == VALID); + return &value_; + } + Struct* get() const { return &value_; } + + void Swap(InlinedStructPtr* other) { + std::swap(value_, other->value_); + std::swap(state_, other->state_); + } + + InlinedStructPtr Clone() const { + return is_null() ? InlinedStructPtr() : value_.Clone(); + } + + // Compares the pointees (which might both be null). + bool Equals(const InlinedStructPtr& other) const { + if (is_null() || other.is_null()) + return is_null() && other.is_null(); + return value_.Equals(other.value_); + } + + // Hashes based on the pointee (which might be null). + size_t Hash(size_t seed) const { + if (is_null()) + return internal::HashCombine(seed, 0); + return value_.Hash(seed); + } + + explicit operator bool() const { return !is_null(); } + + private: + friend class internal::InlinedStructPtrWTFHelper<Struct>; + void Take(InlinedStructPtr* other) { + reset(); + Swap(other); + } + + enum State { + VALID, + NIL, + DELETED, // For use in WTF::HashMap only + }; + + mutable Struct value_; + State state_; + + DISALLOW_COPY_AND_ASSIGN(InlinedStructPtr); +}; + +template <typename T> +bool operator==(const InlinedStructPtr<T>& lhs, + const InlinedStructPtr<T>& rhs) { + return lhs.Equals(rhs); +} +template <typename T> +bool operator!=(const InlinedStructPtr<T>& lhs, + const InlinedStructPtr<T>& rhs) { + return !(lhs == rhs); +} + +namespace internal { + +template <typename Struct> +class StructPtrWTFHelper { + public: + static bool IsHashTableDeletedValue(const StructPtr<Struct>& value) { + return value.ptr_.get() == reinterpret_cast<Struct*>(1u); + } + + static void ConstructDeletedValue(mojo::StructPtr<Struct>& slot) { + // |slot| refers to a previous, real value that got deleted and had its + // destructor run, so this is the first time the "deleted value" has its + // constructor called. + // + // Dirty trick: implant an invalid pointer in |ptr_|. Destructor isn't + // called for deleted buckets, so this is okay. + new (&slot) StructPtr<Struct>(); + slot.ptr_.reset(reinterpret_cast<Struct*>(1u)); + } +}; + +template <typename Struct> +class InlinedStructPtrWTFHelper { + public: + static bool IsHashTableDeletedValue(const InlinedStructPtr<Struct>& value) { + return value.state_ == InlinedStructPtr<Struct>::DELETED; + } + + static void ConstructDeletedValue(mojo::InlinedStructPtr<Struct>& slot) { + // |slot| refers to a previous, real value that got deleted and had its + // destructor run, so this is the first time the "deleted value" has its + // constructor called. + new (&slot) InlinedStructPtr<Struct>(); + slot.state_ = InlinedStructPtr<Struct>::DELETED; + } +}; + +} // namespace internal +} // namespace mojo + +namespace std { + +template <typename T> +struct hash<mojo::StructPtr<T>> { + size_t operator()(const mojo::StructPtr<T>& value) const { + return value.Hash(mojo::internal::kHashSeed); + } +}; + +template <typename T> +struct hash<mojo::InlinedStructPtr<T>> { + size_t operator()(const mojo::InlinedStructPtr<T>& value) const { + return value.Hash(mojo::internal::kHashSeed); + } +}; + +} // namespace std + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRUCT_PTR_H_ diff --git a/mojo/public/cpp/bindings/struct_traits.h b/mojo/public/cpp/bindings/struct_traits.h new file mode 100644 index 0000000000..6cc070fc48 --- /dev/null +++ b/mojo/public/cpp/bindings/struct_traits.h @@ -0,0 +1,165 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_STRUCT_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_STRUCT_TRAITS_H_ + +namespace mojo { + +// This must be specialized for any type |T| to be serialized/deserialized as +// a mojom struct. |DataViewType| is the corresponding data view type of the +// mojom struct. For example, if the mojom struct is example.Foo, +// |DataViewType| will be example::FooDataView, which can also be referred to by +// example::Foo::DataView (in chromium) and example::blink::Foo::DataView (in +// blink). +// +// Each specialization needs to implement a few things: +// 1. Static getters for each field in the Mojom type. These should be +// of the form: +// +// static <return type> <field name>(const T& input); +// +// and should return a serializable form of the named field as extracted +// from |input|. +// +// Serializable form of a field: +// Value or reference of the same type used in the generated stuct +// wrapper type, or the following alternatives: +// - string: +// Value or reference of any type that has a StringTraits defined. +// Supported by default: base::StringPiece, std::string, +// WTF::String (in blink). +// +// - array: +// Value or reference of any type that has an ArrayTraits defined. +// Supported by default: std::vector, CArray, WTF::Vector (in blink) +// +// - map: +// Value or reference of any type that has a MapTraits defined. +// Supported by default: std::map, std::unordered_map, +// WTF::HashMap (in blink). +// +// - struct: +// Value or reference of any type that has a StructTraits defined. +// +// - enum: +// Value of any type that has an EnumTraits defined. +// +// For any nullable string/struct/array/map/union field you could also +// return value or reference of base::Optional<T>/WTF::Optional<T>, if T +// has the right *Traits defined. +// +// During serialization, getters for string/struct/array/map/union fields +// are called twice (one for size calculation and one for actual +// serialization). If you want to return a value (as opposed to a +// reference) from these getters, you have to be sure that constructing and +// copying the returned object is really cheap. +// +// Getters for fields of other types are called once. +// +// 2. A static Read() method to set the contents of a |T| instance from a +// DataViewType. +// +// static bool Read(DataViewType data, T* output); +// +// The generated DataViewType provides a convenient, inexpensive view of a +// serialized struct's field data. The caller guarantees that +// |!data.is_null()|. +// +// Returning false indicates invalid incoming data and causes the message +// pipe receiving it to be disconnected. Therefore, you can do custom +// validation for |T| in this method. +// +// 3. [Optional] A static IsNull() method indicating whether a given |T| +// instance is null: +// +// static bool IsNull(const T& input); +// +// If this method returns true, it is guaranteed that none of the getters +// (described in section 1) will be called for the same |input|. So you +// don't have to check whether |input| is null in those getters. +// +// If it is not defined, |T| instances are always considered non-null. +// +// [Optional] A static SetToNull() method to set the contents of a given +// |T| instance to null. +// +// static void SetToNull(T* output); +// +// When a null serialized struct is received, the deserialization code +// calls this method instead of Read(). +// +// NOTE: It is to set |*output|'s contents to a null state, not to set the +// |output| pointer itself to null. "Null state" means whatever state you +// think it makes sense to map a null serialized struct to. +// +// If it is not defined, null is not allowed to be converted to |T|. In +// that case, an incoming null value is considered invalid and causes the +// message pipe to be disconnected. +// +// 4. [Optional] As mentioned above, getters for string/struct/array/map/union +// fields are called multiple times (twice to be exact). If you need to do +// some expensive calculation/conversion, you probably want to cache the +// result across multiple calls. You can introduce an arbitrary context +// object by adding two optional methods: +// static void* SetUpContext(const T& input); +// static void TearDownContext(const T& input, void* context); +// +// And then you append a second parameter, void* context, to getters: +// static <return type> <field name>(const T& input, void* context); +// +// If a T instance is not null, the serialization code will call +// SetUpContext() at the beginning, and pass the resulting context pointer +// to getters. After serialization is done, it calls TearDownContext() so +// that you can do any necessary cleanup. +// +// In the description above, methods having an |input| parameter define it as +// const reference of T. Actually, it can be a non-const reference of T too. +// E.g., if T contains Mojo handles or interfaces whose ownership needs to be +// transferred. Correspondingly, it requies you to always give non-const T +// reference/value to the Mojo bindings for serialization: +// - if T is used in the "type_mappings" section of a typemap config file, +// you need to declare it as pass-by-value: +// type_mappings = [ "MojomType=T[move_only]" ] +// or +// type_mappings = [ "MojomType=T[copyable_pass_by_value]" ] +// +// - if another type U's StructTraits/UnionTraits has a getter for T, it +// needs to return non-const reference/value. +// +// EXAMPLE: +// +// Mojom definition: +// struct Bar {}; +// struct Foo { +// int32 f_integer; +// string f_string; +// array<string> f_string_array; +// Bar f_bar; +// }; +// +// StructTraits for Foo: +// template <> +// struct StructTraits<FooDataView, CustomFoo> { +// // Optional methods dealing with null: +// static bool IsNull(const CustomFoo& input); +// static void SetToNull(CustomFoo* output); +// +// // Field getters: +// static int32_t f_integer(const CustomFoo& input); +// static const std::string& f_string(const CustomFoo& input); +// static const std::vector<std::string>& f_string_array( +// const CustomFoo& input); +// // Assuming there is a StructTraits<Bar, CustomBar> defined. +// static const CustomBar& f_bar(const CustomFoo& input); +// +// static bool Read(FooDataView data, CustomFoo* output); +// }; +// +template <typename DataViewType, typename T> +struct StructTraits; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_STRUCT_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/sync_call_restrictions.h b/mojo/public/cpp/bindings/sync_call_restrictions.h new file mode 100644 index 0000000000..5529042784 --- /dev/null +++ b/mojo/public/cpp/bindings/sync_call_restrictions.h @@ -0,0 +1,108 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_CALL_RESTRICTIONS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_CALL_RESTRICTIONS_H_ + +#include "base/macros.h" +#include "base/threading/thread_restrictions.h" +#include "mojo/public/cpp/bindings/bindings_export.h" + +#if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define ENABLE_SYNC_CALL_RESTRICTIONS 1 +#else +#define ENABLE_SYNC_CALL_RESTRICTIONS 0 +#endif + +namespace leveldb { +class LevelDBMojoProxy; +} + +namespace prefs { +class PersistentPrefStoreClient; +} + +namespace ui { +class Gpu; +} + +namespace views { +class ClipboardMus; +} + +namespace mojo { + +// In some processes, sync calls are disallowed. For example, in the browser +// process we don't want any sync calls to child processes for performance, +// security and stability reasons. SyncCallRestrictions helps to enforce such +// rules. +// +// Before processing a sync call, the bindings call +// SyncCallRestrictions::AssertSyncCallAllowed() to check whether sync calls are +// allowed. By default, it is determined by the mojo system property +// MOJO_PROPERTY_SYNC_CALL_ALLOWED. If the default setting says no but you have +// a very compelling reason to disregard that (which should be very very rare), +// you can override it by constructing a ScopedAllowSyncCall object, which +// allows making sync calls on the current thread during its lifetime. +class MOJO_CPP_BINDINGS_EXPORT SyncCallRestrictions { + public: +#if ENABLE_SYNC_CALL_RESTRICTIONS + // Checks whether the current thread is allowed to make sync calls, and causes + // a DCHECK if not. + static void AssertSyncCallAllowed(); +#else + // Inline the empty definitions of functions so that they can be compiled out. + static void AssertSyncCallAllowed() {} +#endif + + private: + // DO NOT ADD ANY OTHER FRIEND STATEMENTS, talk to mojo/OWNERS first. + // BEGIN ALLOWED USAGE. + friend class ui::Gpu; // http://crbug.com/620058 + // LevelDBMojoProxy makes same-process sync calls from the DB thread. + friend class leveldb::LevelDBMojoProxy; + // Pref service connection is sync at startup. + friend class prefs::PersistentPrefStoreClient; + + // END ALLOWED USAGE. + + // BEGIN USAGE THAT NEEDS TO BE FIXED. + // In the non-mus case, we called blocking OS functions in the ui::Clipboard + // implementation which weren't caught by sync call restrictions. Our blocking + // calls to mus, however, are. + friend class views::ClipboardMus; + // END USAGE THAT NEEDS TO BE FIXED. + +#if ENABLE_SYNC_CALL_RESTRICTIONS + static void IncreaseScopedAllowCount(); + static void DecreaseScopedAllowCount(); +#else + static void IncreaseScopedAllowCount() {} + static void DecreaseScopedAllowCount() {} +#endif + + // If a process is configured to disallow sync calls in general, constructing + // a ScopedAllowSyncCall object temporarily allows making sync calls on the + // current thread. Doing this is almost always incorrect, which is why we + // limit who can use this through friend. If you find yourself needing to use + // this, talk to mojo/OWNERS. + class ScopedAllowSyncCall { + public: + ScopedAllowSyncCall() { IncreaseScopedAllowCount(); } + ~ScopedAllowSyncCall() { DecreaseScopedAllowCount(); } + + private: +#if ENABLE_SYNC_CALL_RESTRICTIONS + base::ThreadRestrictions::ScopedAllowWait allow_wait_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ScopedAllowSyncCall); + }; + + DISALLOW_IMPLICIT_CONSTRUCTORS(SyncCallRestrictions); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_CALL_RESTRICTIONS_H_ diff --git a/mojo/public/cpp/bindings/sync_event_watcher.h b/mojo/public/cpp/bindings/sync_event_watcher.h new file mode 100644 index 0000000000..6e254844e9 --- /dev/null +++ b/mojo/public/cpp/bindings/sync_event_watcher.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_ + +#include <stddef.h> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/sync_handle_registry.h" + +namespace mojo { + +// SyncEventWatcher supports waiting on a base::WaitableEvent to signal while +// also allowing other SyncEventWatchers and SyncHandleWatchers on the same +// thread to wake up as needed. +// +// This class is not thread safe. +class MOJO_CPP_BINDINGS_EXPORT SyncEventWatcher { + public: + SyncEventWatcher(base::WaitableEvent* event, const base::Closure& callback); + + ~SyncEventWatcher(); + + // Registers |event_| with SyncHandleRegistry, so that when others perform + // sync watching on the same thread, |event_| will be watched along with them. + void AllowWokenUpBySyncWatchOnSameThread(); + + // Waits on |event_| plus all other events and handles registered with this + // thread's SyncHandleRegistry, running callbacks synchronously for any ready + // events and handles. + // This method: + // - returns true when |should_stop| is set to true; + // - return false when any error occurs, including this object being + // destroyed during a callback. + bool SyncWatch(const bool* should_stop); + + private: + void IncrementRegisterCount(); + void DecrementRegisterCount(); + + base::WaitableEvent* const event_; + const base::Closure callback_; + + // Whether |event_| has been registered with SyncHandleRegistry. + bool registered_ = false; + + // If non-zero, |event_| should be registered with SyncHandleRegistry. + size_t register_request_count_ = 0; + + scoped_refptr<SyncHandleRegistry> registry_; + + scoped_refptr<base::RefCountedData<bool>> destroyed_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(SyncEventWatcher); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_EVENT_WATCHER_H_ diff --git a/mojo/public/cpp/bindings/sync_handle_registry.h b/mojo/public/cpp/bindings/sync_handle_registry.h new file mode 100644 index 0000000000..afb3b56bf4 --- /dev/null +++ b/mojo/public/cpp/bindings/sync_handle_registry.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_ + +#include <map> +#include <unordered_map> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/wait_set.h" + +namespace mojo { + +// SyncHandleRegistry is a thread-local storage to register handles that want to +// be watched together. +// +// This class is not thread safe. +class MOJO_CPP_BINDINGS_EXPORT SyncHandleRegistry + : public base::RefCounted<SyncHandleRegistry> { + public: + // Returns a thread-local object. + static scoped_refptr<SyncHandleRegistry> current(); + + using HandleCallback = base::Callback<void(MojoResult)>; + bool RegisterHandle(const Handle& handle, + MojoHandleSignals handle_signals, + const HandleCallback& callback); + + void UnregisterHandle(const Handle& handle); + + // Registers a |base::WaitableEvent| which can be used to wake up + // Wait() before any handle signals. |event| is not owned, and if it signals + // during Wait(), |callback| is invoked. Returns |true| if registered + // successfully or |false| if |event| was already registered. + bool RegisterEvent(base::WaitableEvent* event, const base::Closure& callback); + + void UnregisterEvent(base::WaitableEvent* event); + + // Waits on all the registered handles and events and runs callbacks + // synchronously for any that become ready. + // The method: + // - returns true when any element of |should_stop| is set to true; + // - returns false when any error occurs. + bool Wait(const bool* should_stop[], size_t count); + + private: + friend class base::RefCounted<SyncHandleRegistry>; + + SyncHandleRegistry(); + ~SyncHandleRegistry(); + + WaitSet wait_set_; + std::map<Handle, HandleCallback> handles_; + std::map<base::WaitableEvent*, base::Closure> events_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(SyncHandleRegistry); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_REGISTRY_H_ diff --git a/mojo/public/cpp/bindings/sync_handle_watcher.h b/mojo/public/cpp/bindings/sync_handle_watcher.h new file mode 100644 index 0000000000..eff73dd66e --- /dev/null +++ b/mojo/public/cpp/bindings/sync_handle_watcher.h @@ -0,0 +1,75 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_WATCHER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_WATCHER_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/bindings_export.h" +#include "mojo/public/cpp/bindings/sync_handle_registry.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { + +// SyncHandleWatcher supports watching a handle synchronously. It also supports +// registering the handle with a thread-local storage (SyncHandleRegistry), so +// that when other SyncHandleWatcher instances on the same thread perform sync +// handle watching, this handle will be watched together. +// +// SyncHandleWatcher is used for sync methods. While a sync call is waiting for +// response, we would like to block the thread. On the other hand, we need +// incoming sync method requests on the same thread to be able to reenter. We +// also need master interface endpoints to continue dispatching messages for +// associated endpoints on different threads. +// +// This class is not thread safe. +class MOJO_CPP_BINDINGS_EXPORT SyncHandleWatcher { + public: + // Note: |handle| must outlive this object. + SyncHandleWatcher(const Handle& handle, + MojoHandleSignals handle_signals, + const SyncHandleRegistry::HandleCallback& callback); + + ~SyncHandleWatcher(); + + // Registers |handle_| with SyncHandleRegistry, so that when others perform + // sync handle watching on the same thread, |handle_| will be watched + // together. + void AllowWokenUpBySyncWatchOnSameThread(); + + // Waits on |handle_| plus all handles registered with SyncHandleRegistry and + // runs callbacks synchronously for those ready handles. + // This method: + // - returns true when |should_stop| is set to true; + // - return false when any error occurs, including this object being + // destroyed during a callback. + bool SyncWatch(const bool* should_stop); + + private: + void IncrementRegisterCount(); + void DecrementRegisterCount(); + + const Handle handle_; + const MojoHandleSignals handle_signals_; + SyncHandleRegistry::HandleCallback callback_; + + // Whether |handle_| has been registered with SyncHandleRegistry. + bool registered_; + // If non-zero, |handle_| should be registered with SyncHandleRegistry. + size_t register_request_count_; + + scoped_refptr<SyncHandleRegistry> registry_; + + scoped_refptr<base::RefCountedData<bool>> destroyed_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(SyncHandleWatcher); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_SYNC_HANDLE_WATCHER_H_ diff --git a/mojo/public/cpp/bindings/tests/BUILD.gn b/mojo/public/cpp/bindings/tests/BUILD.gn new file mode 100644 index 0000000000..668ca6da90 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/BUILD.gn @@ -0,0 +1,146 @@ +# 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. + +source_set("tests") { + testonly = true + + sources = [ + "associated_interface_unittest.cc", + "bind_task_runner_unittest.cc", + "binding_callback_unittest.cc", + "binding_set_unittest.cc", + "binding_unittest.cc", + "buffer_unittest.cc", + "connector_unittest.cc", + "constant_unittest.cc", + "container_test_util.cc", + "container_test_util.h", + "data_view_unittest.cc", + "equals_unittest.cc", + "handle_passing_unittest.cc", + "hash_unittest.cc", + "interface_ptr_unittest.cc", + "map_unittest.cc", + "message_queue.cc", + "message_queue.h", + "multiplex_router_unittest.cc", + "report_bad_message_unittest.cc", + "request_response_unittest.cc", + "router_test_util.cc", + "router_test_util.h", + "sample_service_unittest.cc", + "serialization_warning_unittest.cc", + "struct_unittest.cc", + "sync_method_unittest.cc", + "type_conversion_unittest.cc", + "union_unittest.cc", + "validation_context_unittest.cc", + "validation_unittest.cc", + "variant_test_util.h", + ] + + deps = [ + ":mojo_public_bindings_test_utils", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + "//mojo/public/cpp/test_support:test_utils", + "//mojo/public/interfaces/bindings/tests:test_associated_interfaces", + "//mojo/public/interfaces/bindings/tests:test_export_component", + "//mojo/public/interfaces/bindings/tests:test_export_component2", + "//mojo/public/interfaces/bindings/tests:test_exported_import", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/interfaces/bindings/tests:test_struct_traits_interfaces", + "//testing/gtest", + ] + + data = [ + "//mojo/public/interfaces/bindings/tests/data/validation/", + ] + + if (is_ios) { + assert_no_deps = [ "//third_party/WebKit/*" ] + } else { + sources += [ + "pickle_unittest.cc", + "struct_traits_unittest.cc", + ] + + deps += [ "//mojo/public/interfaces/bindings/tests:test_interfaces_blink" ] + } +} + +if (!is_ios) { + source_set("for_blink_tests") { + testonly = true + + sources = [ + "container_test_util.cc", + "container_test_util.h", + "variant_test_util.h", + "wtf_hash_unittest.cc", + "wtf_map_unittest.cc", + "wtf_types_unittest.cc", + ] + + deps = [ + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + "//mojo/public/interfaces/bindings/tests:test_export_blink_component", + "//mojo/public/interfaces/bindings/tests:test_exported_import_blink", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/interfaces/bindings/tests:test_interfaces_blink", + "//mojo/public/interfaces/bindings/tests:test_wtf_types", + "//mojo/public/interfaces/bindings/tests:test_wtf_types_blink", + "//testing/gtest", + ] + } +} + +source_set("struct_with_traits_impl") { + sources = [ + "struct_with_traits_impl.cc", + "struct_with_traits_impl.h", + ] + + deps = [ + "//base", + "//mojo/public/cpp/system:system", + ] +} + +source_set("perftests") { + testonly = true + + sources = [ + "bindings_perftest.cc", + ] + + if (!is_ios) { + sources += [ "e2e_perftest.cc" ] + } + + deps = [ + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + "//mojo/public/cpp/test_support:test_utils", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//testing/gtest", + ] +} + +source_set("mojo_public_bindings_test_utils") { + sources = [ + "validation_test_input_parser.cc", + "validation_test_input_parser.h", + ] + + deps = [ + "//mojo/public/c/system", + ] +} diff --git a/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc new file mode 100644 index 0000000000..be225e4761 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/associated_interface_unittest.cc @@ -0,0 +1,1189 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> +#include <algorithm> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using mojo::internal::MultiplexRouter; + +class IntegerSenderImpl : public IntegerSender { + public: + explicit IntegerSenderImpl(AssociatedInterfaceRequest<IntegerSender> request) + : binding_(this, std::move(request)) {} + + ~IntegerSenderImpl() override {} + + void set_notify_send_method_called( + const base::Callback<void(int32_t)>& callback) { + notify_send_method_called_ = callback; + } + + void Echo(int32_t value, const EchoCallback& callback) override { + callback.Run(value); + } + void Send(int32_t value) override { notify_send_method_called_.Run(value); } + + AssociatedBinding<IntegerSender>* binding() { return &binding_; } + + void set_connection_error_handler(const base::Closure& handler) { + binding_.set_connection_error_handler(handler); + } + + private: + AssociatedBinding<IntegerSender> binding_; + base::Callback<void(int32_t)> notify_send_method_called_; +}; + +class IntegerSenderConnectionImpl : public IntegerSenderConnection { + public: + explicit IntegerSenderConnectionImpl( + InterfaceRequest<IntegerSenderConnection> request) + : binding_(this, std::move(request)) {} + + ~IntegerSenderConnectionImpl() override {} + + void GetSender(AssociatedInterfaceRequest<IntegerSender> sender) override { + IntegerSenderImpl* sender_impl = new IntegerSenderImpl(std::move(sender)); + sender_impl->set_connection_error_handler( + base::Bind(&DeleteSender, sender_impl)); + } + + void AsyncGetSender(const AsyncGetSenderCallback& callback) override { + IntegerSenderAssociatedPtrInfo ptr_info; + auto request = MakeRequest(&ptr_info); + GetSender(std::move(request)); + callback.Run(std::move(ptr_info)); + } + + Binding<IntegerSenderConnection>* binding() { return &binding_; } + + private: + static void DeleteSender(IntegerSenderImpl* sender) { delete sender; } + + Binding<IntegerSenderConnection> binding_; +}; + +class AssociatedInterfaceTest : public testing::Test { + public: + AssociatedInterfaceTest() {} + ~AssociatedInterfaceTest() override { base::RunLoop().RunUntilIdle(); } + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + template <typename T> + AssociatedInterfacePtrInfo<T> EmulatePassingAssociatedPtrInfo( + AssociatedInterfacePtrInfo<T> ptr_info, + scoped_refptr<MultiplexRouter> source, + scoped_refptr<MultiplexRouter> target) { + ScopedInterfaceEndpointHandle handle = ptr_info.PassHandle(); + CHECK(handle.pending_association()); + auto id = source->AssociateInterface(std::move(handle)); + return AssociatedInterfacePtrInfo<T>(target->CreateLocalEndpointHandle(id), + ptr_info.version()); + } + + void CreateRouterPair(scoped_refptr<MultiplexRouter>* router0, + scoped_refptr<MultiplexRouter>* router1) { + MessagePipe pipe; + *router0 = new MultiplexRouter(std::move(pipe.handle0), + MultiplexRouter::MULTI_INTERFACE, true, + base::ThreadTaskRunnerHandle::Get()); + *router1 = new MultiplexRouter(std::move(pipe.handle1), + MultiplexRouter::MULTI_INTERFACE, false, + base::ThreadTaskRunnerHandle::Get()); + } + + void CreateIntegerSenderWithExistingRouters( + scoped_refptr<MultiplexRouter> router0, + IntegerSenderAssociatedPtrInfo* ptr_info0, + scoped_refptr<MultiplexRouter> router1, + IntegerSenderAssociatedRequest* request1) { + *request1 = MakeRequest(ptr_info0); + *ptr_info0 = EmulatePassingAssociatedPtrInfo(std::move(*ptr_info0), router1, + router0); + } + + void CreateIntegerSender(IntegerSenderAssociatedPtrInfo* ptr_info, + IntegerSenderAssociatedRequest* request) { + scoped_refptr<MultiplexRouter> router0; + scoped_refptr<MultiplexRouter> router1; + CreateRouterPair(&router0, &router1); + CreateIntegerSenderWithExistingRouters(router1, ptr_info, router0, request); + } + + // Okay to call from any thread. + void QuitRunLoop(base::RunLoop* run_loop) { + if (loop_.task_runner()->BelongsToCurrentThread()) { + run_loop->Quit(); + } else { + loop_.task_runner()->PostTask( + FROM_HERE, + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(run_loop))); + } + } + + private: + base::MessageLoop loop_; +}; + +void DoSetFlagAndRunClosure(bool* flag, const base::Closure& closure) { + *flag = true; + closure.Run(); +} + +void DoExpectValueSetFlagAndRunClosure(int32_t expected_value, + bool* flag, + const base::Closure& closure, + int32_t value) { + EXPECT_EQ(expected_value, value); + DoSetFlagAndRunClosure(flag, closure); +} + +base::Closure SetFlagAndRunClosure(bool* flag, const base::Closure& closure) { + return base::Bind(&DoSetFlagAndRunClosure, flag, closure); +} + +base::Callback<void(int32_t)> ExpectValueSetFlagAndRunClosure( + int32_t expected_value, + bool* flag, + const base::Closure& closure) { + return base::Bind( + &DoExpectValueSetFlagAndRunClosure, expected_value, flag, closure); +} + +void Fail() { + FAIL() << "Unexpected connection error"; +} + +TEST_F(AssociatedInterfaceTest, InterfacesAtBothEnds) { + // Bind to the same pipe two associated interfaces, whose implementation lives + // at different ends. Test that the two don't interfere with each other. + + scoped_refptr<MultiplexRouter> router0; + scoped_refptr<MultiplexRouter> router1; + CreateRouterPair(&router0, &router1); + + AssociatedInterfaceRequest<IntegerSender> request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0, &request); + + IntegerSenderImpl impl0(std::move(request)); + AssociatedInterfacePtr<IntegerSender> ptr0; + ptr0.Bind(std::move(ptr_info)); + + CreateIntegerSenderWithExistingRouters(router0, &ptr_info, router1, &request); + + IntegerSenderImpl impl1(std::move(request)); + AssociatedInterfacePtr<IntegerSender> ptr1; + ptr1.Bind(std::move(ptr_info)); + + base::RunLoop run_loop, run_loop2; + bool ptr0_callback_run = false; + ptr0->Echo(123, ExpectValueSetFlagAndRunClosure(123, &ptr0_callback_run, + run_loop.QuitClosure())); + + bool ptr1_callback_run = false; + ptr1->Echo(456, ExpectValueSetFlagAndRunClosure(456, &ptr1_callback_run, + run_loop2.QuitClosure())); + + run_loop.Run(); + run_loop2.Run(); + EXPECT_TRUE(ptr0_callback_run); + EXPECT_TRUE(ptr1_callback_run); + + bool ptr0_error_callback_run = false; + base::RunLoop run_loop3; + ptr0.set_connection_error_handler( + SetFlagAndRunClosure(&ptr0_error_callback_run, run_loop3.QuitClosure())); + + impl0.binding()->Close(); + run_loop3.Run(); + EXPECT_TRUE(ptr0_error_callback_run); + + bool impl1_error_callback_run = false; + base::RunLoop run_loop4; + impl1.binding()->set_connection_error_handler( + SetFlagAndRunClosure(&impl1_error_callback_run, run_loop4.QuitClosure())); + + ptr1.reset(); + run_loop4.Run(); + EXPECT_TRUE(impl1_error_callback_run); +} + +class TestSender { + public: + TestSender() + : sender_thread_("TestSender"), + next_sender_(nullptr), + max_value_to_send_(-1) { + sender_thread_.Start(); + } + + // The following three methods are called on the corresponding sender thread. + void SetUp(IntegerSenderAssociatedPtrInfo ptr_info, + TestSender* next_sender, + int32_t max_value_to_send) { + CHECK(sender_thread_.task_runner()->BelongsToCurrentThread()); + + ptr_.Bind(std::move(ptr_info)); + next_sender_ = next_sender ? next_sender : this; + max_value_to_send_ = max_value_to_send; + } + + void Send(int32_t value) { + CHECK(sender_thread_.task_runner()->BelongsToCurrentThread()); + + if (value > max_value_to_send_) + return; + + ptr_->Send(value); + + next_sender_->sender_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestSender::Send, base::Unretained(next_sender_), ++value)); + } + + void TearDown() { + CHECK(sender_thread_.task_runner()->BelongsToCurrentThread()); + + ptr_.reset(); + } + + base::Thread* sender_thread() { return &sender_thread_; } + + private: + base::Thread sender_thread_; + TestSender* next_sender_; + int32_t max_value_to_send_; + + AssociatedInterfacePtr<IntegerSender> ptr_; +}; + +class TestReceiver { + public: + TestReceiver() : receiver_thread_("TestReceiver"), expected_calls_(0) { + receiver_thread_.Start(); + } + + void SetUp(AssociatedInterfaceRequest<IntegerSender> request0, + AssociatedInterfaceRequest<IntegerSender> request1, + size_t expected_calls, + const base::Closure& notify_finish) { + CHECK(receiver_thread_.task_runner()->BelongsToCurrentThread()); + + impl0_.reset(new IntegerSenderImpl(std::move(request0))); + impl0_->set_notify_send_method_called( + base::Bind(&TestReceiver::SendMethodCalled, base::Unretained(this))); + impl1_.reset(new IntegerSenderImpl(std::move(request1))); + impl1_->set_notify_send_method_called( + base::Bind(&TestReceiver::SendMethodCalled, base::Unretained(this))); + + expected_calls_ = expected_calls; + notify_finish_ = notify_finish; + } + + void TearDown() { + CHECK(receiver_thread_.task_runner()->BelongsToCurrentThread()); + + impl0_.reset(); + impl1_.reset(); + } + + base::Thread* receiver_thread() { return &receiver_thread_; } + const std::vector<int32_t>& values() const { return values_; } + + private: + void SendMethodCalled(int32_t value) { + values_.push_back(value); + + if (values_.size() >= expected_calls_) + notify_finish_.Run(); + } + + base::Thread receiver_thread_; + size_t expected_calls_; + + std::unique_ptr<IntegerSenderImpl> impl0_; + std::unique_ptr<IntegerSenderImpl> impl1_; + + std::vector<int32_t> values_; + + base::Closure notify_finish_; +}; + +class NotificationCounter { + public: + NotificationCounter(size_t total_count, const base::Closure& notify_finish) + : total_count_(total_count), + current_count_(0), + notify_finish_(notify_finish) {} + + ~NotificationCounter() {} + + // Okay to call from any thread. + void OnGotNotification() { + bool finshed = false; + { + base::AutoLock locker(lock_); + CHECK_LT(current_count_, total_count_); + current_count_++; + finshed = current_count_ == total_count_; + } + + if (finshed) + notify_finish_.Run(); + } + + private: + base::Lock lock_; + const size_t total_count_; + size_t current_count_; + base::Closure notify_finish_; +}; + +TEST_F(AssociatedInterfaceTest, MultiThreadAccess) { + // Set up four associated interfaces on a message pipe. Use the inteface + // pointers on four threads in parallel; run the interface implementations on + // two threads. Test that multi-threaded access works. + + const int32_t kMaxValue = 1000; + MessagePipe pipe; + scoped_refptr<MultiplexRouter> router0; + scoped_refptr<MultiplexRouter> router1; + CreateRouterPair(&router0, &router1); + + AssociatedInterfaceRequest<IntegerSender> requests[4]; + IntegerSenderAssociatedPtrInfo ptr_infos[4]; + for (size_t i = 0; i < 4; ++i) { + CreateIntegerSenderWithExistingRouters(router1, &ptr_infos[i], router0, + &requests[i]); + } + + TestSender senders[4]; + for (size_t i = 0; i < 4; ++i) { + senders[i].sender_thread()->task_runner()->PostTask( + FROM_HERE, base::Bind(&TestSender::SetUp, base::Unretained(&senders[i]), + base::Passed(&ptr_infos[i]), nullptr, + kMaxValue * (i + 1) / 4)); + } + + base::RunLoop run_loop; + TestReceiver receivers[2]; + NotificationCounter counter( + 2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + for (size_t i = 0; i < 2; ++i) { + receivers[i].receiver_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestReceiver::SetUp, base::Unretained(&receivers[i]), + base::Passed(&requests[2 * i]), + base::Passed(&requests[2 * i + 1]), + static_cast<size_t>(kMaxValue / 2), + base::Bind(&NotificationCounter::OnGotNotification, + base::Unretained(&counter)))); + } + + for (size_t i = 0; i < 4; ++i) { + senders[i].sender_thread()->task_runner()->PostTask( + FROM_HERE, base::Bind(&TestSender::Send, base::Unretained(&senders[i]), + kMaxValue * i / 4 + 1)); + } + + run_loop.Run(); + + for (size_t i = 0; i < 4; ++i) { + base::RunLoop run_loop; + senders[i].sender_thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])), + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + run_loop.Run(); + } + + for (size_t i = 0; i < 2; ++i) { + base::RunLoop run_loop; + receivers[i].receiver_thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])), + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + run_loop.Run(); + } + + EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[0].values().size()); + EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[1].values().size()); + + std::vector<int32_t> all_values; + all_values.insert(all_values.end(), receivers[0].values().begin(), + receivers[0].values().end()); + all_values.insert(all_values.end(), receivers[1].values().begin(), + receivers[1].values().end()); + + std::sort(all_values.begin(), all_values.end()); + for (size_t i = 0; i < all_values.size(); ++i) + ASSERT_EQ(static_cast<int32_t>(i + 1), all_values[i]); +} + +TEST_F(AssociatedInterfaceTest, FIFO) { + // Set up four associated interfaces on a message pipe. Use the inteface + // pointers on four threads; run the interface implementations on two threads. + // Take turns to make calls using the four pointers. Test that FIFO-ness is + // preserved. + + const int32_t kMaxValue = 100; + MessagePipe pipe; + scoped_refptr<MultiplexRouter> router0; + scoped_refptr<MultiplexRouter> router1; + CreateRouterPair(&router0, &router1); + + AssociatedInterfaceRequest<IntegerSender> requests[4]; + IntegerSenderAssociatedPtrInfo ptr_infos[4]; + for (size_t i = 0; i < 4; ++i) { + CreateIntegerSenderWithExistingRouters(router1, &ptr_infos[i], router0, + &requests[i]); + } + + TestSender senders[4]; + for (size_t i = 0; i < 4; ++i) { + senders[i].sender_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestSender::SetUp, base::Unretained(&senders[i]), + base::Passed(&ptr_infos[i]), + base::Unretained(&senders[(i + 1) % 4]), kMaxValue)); + } + + base::RunLoop run_loop; + TestReceiver receivers[2]; + NotificationCounter counter( + 2, base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + for (size_t i = 0; i < 2; ++i) { + receivers[i].receiver_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestReceiver::SetUp, base::Unretained(&receivers[i]), + base::Passed(&requests[2 * i]), + base::Passed(&requests[2 * i + 1]), + static_cast<size_t>(kMaxValue / 2), + base::Bind(&NotificationCounter::OnGotNotification, + base::Unretained(&counter)))); + } + + senders[0].sender_thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestSender::Send, base::Unretained(&senders[0]), 1)); + + run_loop.Run(); + + for (size_t i = 0; i < 4; ++i) { + base::RunLoop run_loop; + senders[i].sender_thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestSender::TearDown, base::Unretained(&senders[i])), + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + run_loop.Run(); + } + + for (size_t i = 0; i < 2; ++i) { + base::RunLoop run_loop; + receivers[i].receiver_thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestReceiver::TearDown, base::Unretained(&receivers[i])), + base::Bind(&AssociatedInterfaceTest::QuitRunLoop, + base::Unretained(this), base::Unretained(&run_loop))); + run_loop.Run(); + } + + EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[0].values().size()); + EXPECT_EQ(static_cast<size_t>(kMaxValue / 2), receivers[1].values().size()); + + for (size_t i = 0; i < 2; ++i) { + for (size_t j = 1; j < receivers[i].values().size(); ++j) + EXPECT_LT(receivers[i].values()[j - 1], receivers[i].values()[j]); + } +} + +void CaptureInt32(int32_t* storage, + const base::Closure& closure, + int32_t value) { + *storage = value; + closure.Run(); +} + +void CaptureSenderPtrInfo(IntegerSenderAssociatedPtr* storage, + const base::Closure& closure, + IntegerSenderAssociatedPtrInfo info) { + storage->Bind(std::move(info)); + closure.Run(); +} + +TEST_F(AssociatedInterfaceTest, PassAssociatedInterfaces) { + IntegerSenderConnectionPtr connection_ptr; + IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr)); + + IntegerSenderAssociatedPtr sender0; + connection_ptr->GetSender(MakeRequest(&sender0)); + + int32_t echoed_value = 0; + base::RunLoop run_loop; + sender0->Echo(123, base::Bind(&CaptureInt32, &echoed_value, + run_loop.QuitClosure())); + run_loop.Run(); + EXPECT_EQ(123, echoed_value); + + IntegerSenderAssociatedPtr sender1; + base::RunLoop run_loop2; + connection_ptr->AsyncGetSender( + base::Bind(&CaptureSenderPtrInfo, &sender1, run_loop2.QuitClosure())); + run_loop2.Run(); + EXPECT_TRUE(sender1); + + base::RunLoop run_loop3; + sender1->Echo(456, base::Bind(&CaptureInt32, &echoed_value, + run_loop3.QuitClosure())); + run_loop3.Run(); + EXPECT_EQ(456, echoed_value); +} + +TEST_F(AssociatedInterfaceTest, BindingWaitAndPauseWhenNoAssociatedInterfaces) { + IntegerSenderConnectionPtr connection_ptr; + IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr)); + + IntegerSenderAssociatedPtr sender0; + connection_ptr->GetSender(MakeRequest(&sender0)); + + EXPECT_FALSE(connection.binding()->HasAssociatedInterfaces()); + // There are no associated interfaces running on the pipe yet. It is okay to + // pause. + connection.binding()->PauseIncomingMethodCallProcessing(); + connection.binding()->ResumeIncomingMethodCallProcessing(); + + // There are no associated interfaces running on the pipe yet. It is okay to + // wait. + EXPECT_TRUE(connection.binding()->WaitForIncomingMethodCall()); + + // The previous wait has dispatched the GetSender request message, therefore + // an associated interface has been set up on the pipe. It is not allowed to + // wait or pause. + EXPECT_TRUE(connection.binding()->HasAssociatedInterfaces()); +} + +class PingServiceImpl : public PingService { + public: + explicit PingServiceImpl(PingServiceAssociatedRequest request) + : binding_(this, std::move(request)) {} + ~PingServiceImpl() override {} + + AssociatedBinding<PingService>& binding() { return binding_; } + + void set_ping_handler(const base::Closure& handler) { + ping_handler_ = handler; + } + + // PingService: + void Ping(const PingCallback& callback) override { + if (!ping_handler_.is_null()) + ping_handler_.Run(); + callback.Run(); + } + + private: + AssociatedBinding<PingService> binding_; + base::Closure ping_handler_; +}; + +class PingProviderImpl : public AssociatedPingProvider { + public: + explicit PingProviderImpl(AssociatedPingProviderRequest request) + : binding_(this, std::move(request)) {} + ~PingProviderImpl() override {} + + // AssociatedPingProvider: + void GetPing(PingServiceAssociatedRequest request) override { + ping_services_.emplace_back(new PingServiceImpl(std::move(request))); + + if (expected_bindings_count_ > 0 && + ping_services_.size() == expected_bindings_count_ && + !quit_waiting_.is_null()) { + expected_bindings_count_ = 0; + base::ResetAndReturn(&quit_waiting_).Run(); + } + } + + std::vector<std::unique_ptr<PingServiceImpl>>& ping_services() { + return ping_services_; + } + + void WaitForBindings(size_t count) { + DCHECK(quit_waiting_.is_null()); + + expected_bindings_count_ = count; + base::RunLoop loop; + quit_waiting_ = loop.QuitClosure(); + loop.Run(); + } + + private: + Binding<AssociatedPingProvider> binding_; + std::vector<std::unique_ptr<PingServiceImpl>> ping_services_; + size_t expected_bindings_count_ = 0; + base::Closure quit_waiting_; +}; + +class CallbackFilter : public MessageReceiver { + public: + explicit CallbackFilter(const base::Closure& callback) + : callback_(callback) {} + ~CallbackFilter() override {} + + static std::unique_ptr<CallbackFilter> Wrap(const base::Closure& callback) { + return base::MakeUnique<CallbackFilter>(callback); + } + + // MessageReceiver: + bool Accept(Message* message) override { + callback_.Run(); + return true; + } + + private: + const base::Closure callback_; +}; + +// Verifies that filters work as expected on associated bindings, i.e. that +// they're notified in order, before dispatch; and that each associated +// binding in a group operates with its own set of filters. +TEST_F(AssociatedInterfaceTest, BindingWithFilters) { + AssociatedPingProviderPtr provider; + PingProviderImpl provider_impl(MakeRequest(&provider)); + + PingServiceAssociatedPtr ping_a, ping_b; + provider->GetPing(MakeRequest(&ping_a)); + provider->GetPing(MakeRequest(&ping_b)); + provider_impl.WaitForBindings(2); + + ASSERT_EQ(2u, provider_impl.ping_services().size()); + PingServiceImpl& ping_a_impl = *provider_impl.ping_services()[0]; + PingServiceImpl& ping_b_impl = *provider_impl.ping_services()[1]; + + int a_status, b_status; + auto handler_helper = [] (int* a_status, int* b_status, int expected_a_status, + int new_a_status, int expected_b_status, + int new_b_status) { + EXPECT_EQ(expected_a_status, *a_status); + EXPECT_EQ(expected_b_status, *b_status); + *a_status = new_a_status; + *b_status = new_b_status; + }; + auto create_handler = [&] (int expected_a_status, int new_a_status, + int expected_b_status, int new_b_status) { + return base::Bind(handler_helper, &a_status, &b_status, expected_a_status, + new_a_status, expected_b_status, new_b_status); + }; + + ping_a_impl.binding().AddFilter( + CallbackFilter::Wrap(create_handler(0, 1, 0, 0))); + ping_a_impl.binding().AddFilter( + CallbackFilter::Wrap(create_handler(1, 2, 0, 0))); + ping_a_impl.set_ping_handler(create_handler(2, 3, 0, 0)); + + ping_b_impl.binding().AddFilter( + CallbackFilter::Wrap(create_handler(3, 3, 0, 1))); + ping_b_impl.binding().AddFilter( + CallbackFilter::Wrap(create_handler(3, 3, 1, 2))); + ping_b_impl.set_ping_handler(create_handler(3, 3, 2, 3)); + + for (int i = 0; i < 10; ++i) { + a_status = 0; + b_status = 0; + + { + base::RunLoop loop; + ping_a->Ping(loop.QuitClosure()); + loop.Run(); + } + + EXPECT_EQ(3, a_status); + EXPECT_EQ(0, b_status); + + { + base::RunLoop loop; + ping_b->Ping(loop.QuitClosure()); + loop.Run(); + } + + EXPECT_EQ(3, a_status); + EXPECT_EQ(3, b_status); + } +} + +TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTesting) { + AssociatedInterfaceRequest<IntegerSender> request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + IntegerSenderImpl impl0(std::move(request)); + AssociatedInterfacePtr<IntegerSender> ptr0; + ptr0.Bind(std::move(ptr_info)); + ptr0.set_connection_error_handler(base::Bind(&Fail)); + + bool ptr0_callback_run = false; + ptr0->Echo(123, ExpectValueSetFlagAndRunClosure( + 123, &ptr0_callback_run, base::Bind(&base::DoNothing))); + ptr0.FlushForTesting(); + EXPECT_TRUE(ptr0_callback_run); +} + +void SetBool(bool* value) { + *value = true; +} + +template <typename T> +void SetBoolWithUnusedParameter(bool* value, T unused) { + *value = true; +} + +TEST_F(AssociatedInterfaceTest, AssociatedPtrFlushForTestingWithClosedPeer) { + AssociatedInterfaceRequest<IntegerSender> request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + AssociatedInterfacePtr<IntegerSender> ptr0; + ptr0.Bind(std::move(ptr_info)); + bool called = false; + ptr0.set_connection_error_handler(base::Bind(&SetBool, &called)); + request = nullptr; + + ptr0.FlushForTesting(); + EXPECT_TRUE(called); + ptr0.FlushForTesting(); +} + +TEST_F(AssociatedInterfaceTest, AssociatedBindingFlushForTesting) { + AssociatedInterfaceRequest<IntegerSender> request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + IntegerSenderImpl impl0(std::move(request)); + impl0.set_connection_error_handler(base::Bind(&Fail)); + AssociatedInterfacePtr<IntegerSender> ptr0; + ptr0.Bind(std::move(ptr_info)); + + bool ptr0_callback_run = false; + ptr0->Echo(123, ExpectValueSetFlagAndRunClosure( + 123, &ptr0_callback_run, base::Bind(&base::DoNothing))); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + impl0.binding()->FlushForTesting(); + impl0.binding()->FlushForTesting(); + EXPECT_TRUE(ptr0_callback_run); +} + +TEST_F(AssociatedInterfaceTest, + AssociatedBindingFlushForTestingWithClosedPeer) { + scoped_refptr<MultiplexRouter> router0; + scoped_refptr<MultiplexRouter> router1; + CreateRouterPair(&router0, &router1); + + AssociatedInterfaceRequest<IntegerSender> request; + { + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSenderWithExistingRouters(router1, &ptr_info, router0, + &request); + } + + IntegerSenderImpl impl(std::move(request)); + bool called = false; + impl.set_connection_error_handler(base::Bind(&SetBool, &called)); + impl.binding()->FlushForTesting(); + EXPECT_TRUE(called); + impl.binding()->FlushForTesting(); +} + +TEST_F(AssociatedInterfaceTest, BindingFlushForTesting) { + IntegerSenderConnectionPtr ptr; + IntegerSenderConnectionImpl impl(MakeRequest(&ptr)); + bool called = false; + ptr->AsyncGetSender(base::Bind( + &SetBoolWithUnusedParameter<IntegerSenderAssociatedPtrInfo>, &called)); + EXPECT_FALSE(called); + impl.binding()->set_connection_error_handler(base::Bind(&Fail)); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + impl.binding()->FlushForTesting(); + impl.binding()->FlushForTesting(); + EXPECT_TRUE(called); +} + +TEST_F(AssociatedInterfaceTest, BindingFlushForTestingWithClosedPeer) { + IntegerSenderConnectionPtr ptr; + IntegerSenderConnectionImpl impl(MakeRequest(&ptr)); + bool called = false; + impl.binding()->set_connection_error_handler(base::Bind(&SetBool, &called)); + ptr.reset(); + EXPECT_FALSE(called); + impl.binding()->FlushForTesting(); + EXPECT_TRUE(called); + impl.binding()->FlushForTesting(); +} + +TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTesting) { + IntegerSenderConnectionPtr ptr; + auto binding = + MakeStrongBinding(base::MakeUnique<IntegerSenderConnectionImpl>( + IntegerSenderConnectionRequest{}), + MakeRequest(&ptr)); + bool called = false; + IntegerSenderAssociatedPtr sender_ptr; + ptr->GetSender(MakeRequest(&sender_ptr)); + sender_ptr->Echo(1, base::Bind(&SetBoolWithUnusedParameter<int>, &called)); + EXPECT_FALSE(called); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + ASSERT_TRUE(binding); + binding->FlushForTesting(); + ASSERT_TRUE(binding); + binding->FlushForTesting(); + EXPECT_TRUE(called); +} + +TEST_F(AssociatedInterfaceTest, StrongBindingFlushForTestingWithClosedPeer) { + IntegerSenderConnectionPtr ptr; + bool called = false; + auto binding = + MakeStrongBinding(base::MakeUnique<IntegerSenderConnectionImpl>( + IntegerSenderConnectionRequest{}), + MakeRequest(&ptr)); + binding->set_connection_error_handler(base::Bind(&SetBool, &called)); + ptr.reset(); + EXPECT_FALSE(called); + ASSERT_TRUE(binding); + binding->FlushForTesting(); + EXPECT_TRUE(called); + ASSERT_FALSE(binding); +} + +TEST_F(AssociatedInterfaceTest, PtrFlushForTesting) { + IntegerSenderConnectionPtr ptr; + IntegerSenderConnectionImpl impl(MakeRequest(&ptr)); + bool called = false; + ptr.set_connection_error_handler(base::Bind(&Fail)); + ptr->AsyncGetSender(base::Bind( + &SetBoolWithUnusedParameter<IntegerSenderAssociatedPtrInfo>, &called)); + EXPECT_FALSE(called); + ptr.FlushForTesting(); + EXPECT_TRUE(called); +} + +TEST_F(AssociatedInterfaceTest, PtrFlushForTestingWithClosedPeer) { + IntegerSenderConnectionPtr ptr; + MakeRequest(&ptr); + bool called = false; + ptr.set_connection_error_handler(base::Bind(&SetBool, &called)); + EXPECT_FALSE(called); + ptr.FlushForTesting(); + EXPECT_TRUE(called); + ptr.FlushForTesting(); +} + +TEST_F(AssociatedInterfaceTest, AssociatedBindingConnectionErrorWithReason) { + AssociatedInterfaceRequest<IntegerSender> request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + IntegerSenderImpl impl(std::move(request)); + AssociatedInterfacePtr<IntegerSender> ptr; + ptr.Bind(std::move(ptr_info)); + + base::RunLoop run_loop; + impl.binding()->set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(123u, custom_reason); + EXPECT_EQ("farewell", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(123u, "farewell"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, + PendingAssociatedBindingConnectionErrorWithReason) { + // Test that AssociatedBinding is notified with connection error when the + // interface hasn't associated with a message pipe and the peer is closed. + + IntegerSenderAssociatedPtr ptr; + IntegerSenderImpl impl(MakeRequest(&ptr)); + + base::RunLoop run_loop; + impl.binding()->set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(123u, custom_reason); + EXPECT_EQ("farewell", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(123u, "farewell"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, AssociatedPtrConnectionErrorWithReason) { + AssociatedInterfaceRequest<IntegerSender> request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + IntegerSenderImpl impl(std::move(request)); + AssociatedInterfacePtr<IntegerSender> ptr; + ptr.Bind(std::move(ptr_info)); + + base::RunLoop run_loop; + ptr.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(456u, custom_reason); + EXPECT_EQ("farewell", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + impl.binding()->CloseWithReason(456u, "farewell"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, PendingAssociatedPtrConnectionErrorWithReason) { + // Test that AssociatedInterfacePtr is notified with connection error when the + // interface hasn't associated with a message pipe and the peer is closed. + + IntegerSenderAssociatedPtr ptr; + auto request = MakeRequest(&ptr); + + base::RunLoop run_loop; + ptr.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(456u, custom_reason); + EXPECT_EQ("farewell", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + request.ResetWithReason(456u, "farewell"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, AssociatedRequestResetWithReason) { + AssociatedInterfaceRequest<IntegerSender> request; + IntegerSenderAssociatedPtrInfo ptr_info; + CreateIntegerSender(&ptr_info, &request); + + AssociatedInterfacePtr<IntegerSender> ptr; + ptr.Bind(std::move(ptr_info)); + + base::RunLoop run_loop; + ptr.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(789u, custom_reason); + EXPECT_EQ("long time no see", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + request.ResetWithReason(789u, "long time no see"); + + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, ThreadSafeAssociatedInterfacePtr) { + IntegerSenderConnectionPtr connection_ptr; + IntegerSenderConnectionImpl connection(MakeRequest(&connection_ptr)); + + IntegerSenderAssociatedPtr sender; + connection_ptr->GetSender(MakeRequest(&sender)); + + scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr> thread_safe_sender = + ThreadSafeIntegerSenderAssociatedPtr::Create(std::move(sender)); + + { + // Test the thread safe pointer can be used from the interface ptr thread. + int32_t echoed_value = 0; + base::RunLoop run_loop; + (*thread_safe_sender) + ->Echo(123, base::Bind(&CaptureInt32, &echoed_value, + run_loop.QuitClosure())); + run_loop.Run(); + EXPECT_EQ(123, echoed_value); + } + + // Test the thread safe pointer can be used from another thread. + base::RunLoop run_loop; + base::Thread other_thread("service test thread"); + other_thread.Start(); + + auto run_method = base::Bind( + [](const scoped_refptr<base::TaskRunner>& main_task_runner, + const base::Closure& quit_closure, + const scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr>& + thread_safe_sender) { + auto done_callback = base::Bind( + [](const scoped_refptr<base::TaskRunner>& main_task_runner, + const base::Closure& quit_closure, + base::PlatformThreadId thread_id, int32_t result) { + EXPECT_EQ(123, result); + // Validate the callback is invoked on the calling thread. + EXPECT_EQ(thread_id, base::PlatformThread::CurrentId()); + // Notify the run_loop to quit. + main_task_runner->PostTask(FROM_HERE, quit_closure); + }); + (*thread_safe_sender) + ->Echo(123, + base::Bind(done_callback, main_task_runner, quit_closure, + base::PlatformThread::CurrentId())); + }, + base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(), + thread_safe_sender); + other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method); + + // Block until the method callback is called on the background thread. + run_loop.Run(); +} + +struct ForwarderTestContext { + IntegerSenderConnectionPtr connection_ptr; + std::unique_ptr<IntegerSenderConnectionImpl> interface_impl; + IntegerSenderAssociatedRequest sender_request; +}; + +TEST_F(AssociatedInterfaceTest, + ThreadSafeAssociatedInterfacePtrWithTaskRunner) { + // Start the thread from where we'll bind the interface pointer. + base::Thread other_thread("service test thread"); + other_thread.Start(); + const scoped_refptr<base::SingleThreadTaskRunner>& other_thread_task_runner = + other_thread.message_loop()->task_runner(); + + ForwarderTestContext* context = new ForwarderTestContext(); + IntegerSenderAssociatedPtrInfo sender_info; + base::WaitableEvent sender_info_bound_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + auto setup = [](base::WaitableEvent* sender_info_bound_event, + IntegerSenderAssociatedPtrInfo* sender_info, + ForwarderTestContext* context) { + context->interface_impl = base::MakeUnique<IntegerSenderConnectionImpl>( + MakeRequest(&context->connection_ptr)); + + auto sender_request = MakeRequest(sender_info); + context->connection_ptr->GetSender(std::move(sender_request)); + + // Unblock the main thread as soon as |sender_info| is set. + sender_info_bound_event->Signal(); + }; + other_thread_task_runner->PostTask( + FROM_HERE, + base::Bind(setup, &sender_info_bound_event, &sender_info, context)); + sender_info_bound_event.Wait(); + + // Create a ThreadSafeAssociatedPtr that binds on the background thread and is + // associated with |connection_ptr| there. + scoped_refptr<ThreadSafeIntegerSenderAssociatedPtr> thread_safe_ptr = + ThreadSafeIntegerSenderAssociatedPtr::Create(std::move(sender_info), + other_thread_task_runner); + + // Issue a call on the thread-safe ptr immediately. Note that this may happen + // before the interface is bound on the background thread, and that must be + // OK. + { + auto echo_callback = + base::Bind([](const base::Closure& quit_closure, int32_t result) { + EXPECT_EQ(123, result); + quit_closure.Run(); + }); + base::RunLoop run_loop; + (*thread_safe_ptr) + ->Echo(123, base::Bind(echo_callback, run_loop.QuitClosure())); + + // Block until the method callback is called. + run_loop.Run(); + } + + other_thread_task_runner->DeleteSoon(FROM_HERE, context); + + // Reset the pointer now so the InterfacePtr associated resources can be + // deleted before the background thread's message loop is invalidated. + thread_safe_ptr = nullptr; +} + +class DiscardingAssociatedPingProviderProvider + : public AssociatedPingProviderProvider { + public: + void GetPingProvider( + AssociatedPingProviderAssociatedRequest request) override {} +}; + +TEST_F(AssociatedInterfaceTest, CloseWithoutBindingAssociatedRequest) { + DiscardingAssociatedPingProviderProvider ping_provider_provider; + mojo::Binding<AssociatedPingProviderProvider> binding( + &ping_provider_provider); + auto provider_provider = binding.CreateInterfacePtrAndBind(); + AssociatedPingProviderAssociatedPtr provider; + provider_provider->GetPingProvider(mojo::MakeRequest(&provider)); + PingServiceAssociatedPtr ping; + provider->GetPing(mojo::MakeRequest(&ping)); + base::RunLoop run_loop; + ping.set_connection_error_handler(run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(AssociatedInterfaceTest, GetIsolatedInterface) { + IntegerSenderAssociatedPtr sender; + GetIsolatedInterface(MakeRequest(&sender).PassHandle()); + sender->Send(42); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc new file mode 100644 index 0000000000..569eb518c6 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc @@ -0,0 +1,395 @@ +// 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. + +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class TestTaskRunner : public base::SingleThreadTaskRunner { + public: + TestTaskRunner() + : thread_id_(base::PlatformThread::CurrentRef()), + quit_called_(false), + task_ready_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED) {} + + bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here, + base::OnceClosure task, + base::TimeDelta delay) override { + NOTREACHED(); + return false; + } + + bool PostDelayedTask(const tracked_objects::Location& from_here, + base::OnceClosure task, + base::TimeDelta delay) override { + { + base::AutoLock locker(lock_); + tasks_.push(std::move(task)); + } + task_ready_.Signal(); + return true; + } + bool RunsTasksOnCurrentThread() const override { + return base::PlatformThread::CurrentRef() == thread_id_; + } + + // Only quits when Quit() is called. + void Run() { + DCHECK(RunsTasksOnCurrentThread()); + quit_called_ = false; + + while (true) { + { + base::AutoLock locker(lock_); + while (!tasks_.empty()) { + auto task = std::move(tasks_.front()); + tasks_.pop(); + + { + base::AutoUnlock unlocker(lock_); + std::move(task).Run(); + if (quit_called_) + return; + } + } + } + task_ready_.Wait(); + } + } + + void Quit() { + DCHECK(RunsTasksOnCurrentThread()); + quit_called_ = true; + } + + // Waits until one task is ready and runs it. + void RunOneTask() { + DCHECK(RunsTasksOnCurrentThread()); + + while (true) { + { + base::AutoLock locker(lock_); + if (!tasks_.empty()) { + auto task = std::move(tasks_.front()); + tasks_.pop(); + + { + base::AutoUnlock unlocker(lock_); + std::move(task).Run(); + return; + } + } + } + task_ready_.Wait(); + } + } + + private: + ~TestTaskRunner() override {} + + const base::PlatformThreadRef thread_id_; + bool quit_called_; + base::WaitableEvent task_ready_; + + // Protect |tasks_|. + base::Lock lock_; + std::queue<base::OnceClosure> tasks_; + + DISALLOW_COPY_AND_ASSIGN(TestTaskRunner); +}; + +template <typename BindingType, typename RequestType> +class IntegerSenderImpl : public IntegerSender { + public: + IntegerSenderImpl(RequestType request, + scoped_refptr<base::SingleThreadTaskRunner> runner) + : binding_(this, std::move(request), std::move(runner)) {} + + ~IntegerSenderImpl() override {} + + using EchoHandler = base::Callback<void(int32_t, const EchoCallback&)>; + + void set_echo_handler(const EchoHandler& handler) { echo_handler_ = handler; } + + void Echo(int32_t value, const EchoCallback& callback) override { + if (echo_handler_.is_null()) + callback.Run(value); + else + echo_handler_.Run(value, callback); + } + void Send(int32_t value) override { NOTREACHED(); } + + BindingType* binding() { return &binding_; } + + private: + BindingType binding_; + EchoHandler echo_handler_; +}; + +class IntegerSenderConnectionImpl : public IntegerSenderConnection { + public: + using SenderType = IntegerSenderImpl<AssociatedBinding<IntegerSender>, + IntegerSenderAssociatedRequest>; + + explicit IntegerSenderConnectionImpl( + IntegerSenderConnectionRequest request, + scoped_refptr<base::SingleThreadTaskRunner> runner, + scoped_refptr<base::SingleThreadTaskRunner> sender_runner) + : binding_(this, std::move(request), std::move(runner)), + sender_runner_(std::move(sender_runner)) {} + + ~IntegerSenderConnectionImpl() override {} + + void set_get_sender_notification(const base::Closure& notification) { + get_sender_notification_ = notification; + } + void GetSender(IntegerSenderAssociatedRequest sender) override { + sender_impl_.reset(new SenderType(std::move(sender), sender_runner_)); + get_sender_notification_.Run(); + } + + void AsyncGetSender(const AsyncGetSenderCallback& callback) override { + NOTREACHED(); + } + + Binding<IntegerSenderConnection>* binding() { return &binding_; } + + SenderType* sender_impl() { return sender_impl_.get(); } + + private: + Binding<IntegerSenderConnection> binding_; + std::unique_ptr<SenderType> sender_impl_; + scoped_refptr<base::SingleThreadTaskRunner> sender_runner_; + base::Closure get_sender_notification_; +}; + +class BindTaskRunnerTest : public testing::Test { + protected: + void SetUp() override { + binding_task_runner_ = scoped_refptr<TestTaskRunner>(new TestTaskRunner); + ptr_task_runner_ = scoped_refptr<TestTaskRunner>(new TestTaskRunner); + + auto request = MakeRequest(&ptr_, ptr_task_runner_); + impl_.reset(new ImplType(std::move(request), binding_task_runner_)); + } + + base::MessageLoop loop_; + scoped_refptr<TestTaskRunner> binding_task_runner_; + scoped_refptr<TestTaskRunner> ptr_task_runner_; + + IntegerSenderPtr ptr_; + using ImplType = + IntegerSenderImpl<Binding<IntegerSender>, IntegerSenderRequest>; + std::unique_ptr<ImplType> impl_; +}; + +class AssociatedBindTaskRunnerTest : public testing::Test { + protected: + void SetUp() override { + connection_binding_task_runner_ = + scoped_refptr<TestTaskRunner>(new TestTaskRunner); + connection_ptr_task_runner_ = + scoped_refptr<TestTaskRunner>(new TestTaskRunner); + sender_binding_task_runner_ = + scoped_refptr<TestTaskRunner>(new TestTaskRunner); + sender_ptr_task_runner_ = scoped_refptr<TestTaskRunner>(new TestTaskRunner); + + auto connection_request = + MakeRequest(&connection_ptr_, connection_ptr_task_runner_); + connection_impl_.reset(new IntegerSenderConnectionImpl( + std::move(connection_request), connection_binding_task_runner_, + sender_binding_task_runner_)); + + connection_impl_->set_get_sender_notification( + base::Bind(&AssociatedBindTaskRunnerTest::QuitTaskRunner, + base::Unretained(this))); + + connection_ptr_->GetSender( + MakeRequest(&sender_ptr_, sender_ptr_task_runner_)); + connection_binding_task_runner_->Run(); + } + + void QuitTaskRunner() { + connection_binding_task_runner_->Quit(); + } + + base::MessageLoop loop_; + scoped_refptr<TestTaskRunner> connection_binding_task_runner_; + scoped_refptr<TestTaskRunner> connection_ptr_task_runner_; + scoped_refptr<TestTaskRunner> sender_binding_task_runner_; + scoped_refptr<TestTaskRunner> sender_ptr_task_runner_; + + IntegerSenderConnectionPtr connection_ptr_; + std::unique_ptr<IntegerSenderConnectionImpl> connection_impl_; + IntegerSenderAssociatedPtr sender_ptr_; +}; + +void DoSetFlagAndQuitTaskRunner(bool* flag, + scoped_refptr<TestTaskRunner> task_runner) { + *flag = true; + if (task_runner) + task_runner->Quit(); +} + +void DoExpectValueSetFlagAndQuitTaskRunner( + int32_t expected_value, + bool* flag, + scoped_refptr<TestTaskRunner> task_runner, + int32_t value) { + EXPECT_EQ(expected_value, value); + DoSetFlagAndQuitTaskRunner(flag, task_runner); +} + +void DoExpectValueSetFlagForwardValueAndQuitTaskRunner( + int32_t expected_value, + bool* flag, + scoped_refptr<TestTaskRunner> task_runner, + int32_t value, + const IntegerSender::EchoCallback& callback) { + EXPECT_EQ(expected_value, value); + *flag = true; + callback.Run(value); + task_runner->Quit(); +} + +base::Closure SetFlagAndQuitTaskRunner( + bool* flag, + scoped_refptr<TestTaskRunner> task_runner) { + return base::Bind(&DoSetFlagAndQuitTaskRunner, flag, task_runner); +} + +base::Callback<void(int32_t)> ExpectValueSetFlagAndQuitTaskRunner( + int32_t expected_value, + bool* flag, + scoped_refptr<TestTaskRunner> task_runner) { + return base::Bind(&DoExpectValueSetFlagAndQuitTaskRunner, expected_value, + flag, task_runner); +} + +TEST_F(BindTaskRunnerTest, MethodCall) { + bool echo_called = false; + impl_->set_echo_handler( + base::Bind(&DoExpectValueSetFlagForwardValueAndQuitTaskRunner, + 1024, &echo_called, binding_task_runner_)); + bool echo_replied = false; + ptr_->Echo(1024, ExpectValueSetFlagAndQuitTaskRunner(1024, &echo_replied, + ptr_task_runner_)); + binding_task_runner_->Run(); + EXPECT_TRUE(echo_called); + ptr_task_runner_->Run(); + EXPECT_TRUE(echo_replied); +} + +TEST_F(BindTaskRunnerTest, BindingConnectionError) { + bool connection_error_called = false; + impl_->binding()->set_connection_error_handler( + SetFlagAndQuitTaskRunner(&connection_error_called, binding_task_runner_)); + ptr_.reset(); + binding_task_runner_->Run(); + EXPECT_TRUE(connection_error_called); +} + +TEST_F(BindTaskRunnerTest, PtrConnectionError) { + bool connection_error_called = false; + ptr_.set_connection_error_handler( + SetFlagAndQuitTaskRunner(&connection_error_called, ptr_task_runner_)); + impl_->binding()->Close(); + ptr_task_runner_->Run(); + EXPECT_TRUE(connection_error_called); +} + +void ExpectValueSetFlagAndForward(int32_t expected_value, + bool* flag, + int32_t value, + const IntegerSender::EchoCallback& callback) { + EXPECT_EQ(expected_value, value); + *flag = true; + callback.Run(value); +} + +TEST_F(AssociatedBindTaskRunnerTest, MethodCall) { + bool echo_called = false; + connection_impl_->sender_impl()->set_echo_handler( + base::Bind(&ExpectValueSetFlagAndForward, 1024, &echo_called)); + + bool echo_replied = false; + sender_ptr_->Echo( + 1024, ExpectValueSetFlagAndQuitTaskRunner(1024, &echo_replied, nullptr)); + + // The Echo request first arrives at the master endpoint's task runner, and + // then is forwarded to the associated endpoint's task runner. + connection_binding_task_runner_->RunOneTask(); + sender_binding_task_runner_->RunOneTask(); + EXPECT_TRUE(echo_called); + + // Similarly, the Echo response arrives at the master endpoint's task runner + // and then is forwarded to the associated endpoint's task runner. + connection_ptr_task_runner_->RunOneTask(); + sender_ptr_task_runner_->RunOneTask(); + EXPECT_TRUE(echo_replied); +} + +TEST_F(AssociatedBindTaskRunnerTest, BindingConnectionError) { + bool sender_impl_error = false; + connection_impl_->sender_impl()->binding()->set_connection_error_handler( + SetFlagAndQuitTaskRunner(&sender_impl_error, + sender_binding_task_runner_)); + bool connection_impl_error = false; + connection_impl_->binding()->set_connection_error_handler( + SetFlagAndQuitTaskRunner(&connection_impl_error, + connection_binding_task_runner_)); + bool sender_ptr_error = false; + sender_ptr_.set_connection_error_handler( + SetFlagAndQuitTaskRunner(&sender_ptr_error, sender_ptr_task_runner_)); + connection_ptr_.reset(); + sender_ptr_task_runner_->Run(); + EXPECT_TRUE(sender_ptr_error); + connection_binding_task_runner_->Run(); + EXPECT_TRUE(connection_impl_error); + sender_binding_task_runner_->Run(); + EXPECT_TRUE(sender_impl_error); +} + +TEST_F(AssociatedBindTaskRunnerTest, PtrConnectionError) { + bool sender_impl_error = false; + connection_impl_->sender_impl()->binding()->set_connection_error_handler( + SetFlagAndQuitTaskRunner(&sender_impl_error, + sender_binding_task_runner_)); + bool connection_ptr_error = false; + connection_ptr_.set_connection_error_handler( + SetFlagAndQuitTaskRunner(&connection_ptr_error, + connection_ptr_task_runner_)); + bool sender_ptr_error = false; + sender_ptr_.set_connection_error_handler( + SetFlagAndQuitTaskRunner(&sender_ptr_error, sender_ptr_task_runner_)); + connection_impl_->binding()->Close(); + sender_binding_task_runner_->Run(); + EXPECT_TRUE(sender_impl_error); + connection_ptr_task_runner_->Run(); + EXPECT_TRUE(connection_ptr_error); + sender_ptr_task_runner_->Run(); + EXPECT_TRUE(sender_ptr_error); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc b/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc new file mode 100644 index 0000000000..43122ceb4f --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_callback_unittest.cc @@ -0,0 +1,338 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/test/gtest_util.h" +#include "build/build_config.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +/////////////////////////////////////////////////////////////////////////////// +// +// The tests in this file are designed to test the interaction between a +// Callback and its associated Binding. If a Callback is deleted before +// being used we DCHECK fail--unless the associated Binding has already +// been closed or deleted. This contract must be explained to the Mojo +// application developer. For example it is the developer's responsibility to +// ensure that the Binding is destroyed before an unused Callback is destroyed. +// +/////////////////////////////////////////////////////////////////////////////// + +namespace mojo { +namespace test { +namespace { + +void SaveValue(int32_t* storage, const base::Closure& closure, int32_t value) { + *storage = value; + if (!closure.is_null()) + closure.Run(); +} + +base::Callback<void(int32_t)> BindValueSaver(int32_t* last_value_seen, + const base::Closure& closure) { + return base::Bind(&SaveValue, last_value_seen, closure); +} + +// An implementation of sample::Provider used on the server side. +// It only implements one of the methods: EchoInt(). +// All it does is save the values and Callbacks it sees. +class InterfaceImpl : public sample::Provider { + public: + InterfaceImpl() + : last_server_value_seen_(0), + callback_saved_(new EchoIntCallback) {} + + ~InterfaceImpl() override { + if (callback_saved_) { + delete callback_saved_; + } + } + + // Run's the callback previously saved from the last invocation + // of |EchoInt()|. + bool RunCallback() { + if (callback_saved_) { + callback_saved_->Run(last_server_value_seen_); + return true; + } + return false; + } + + // Delete's the previously saved callback. + void DeleteCallback() { + delete callback_saved_; + callback_saved_ = nullptr; + } + + // sample::Provider implementation + + // Saves its two input values in member variables and does nothing else. + void EchoInt(int32_t x, const EchoIntCallback& callback) override { + last_server_value_seen_ = x; + *callback_saved_ = callback; + if (!closure_.is_null()) { + closure_.Run(); + closure_.Reset(); + } + } + + void EchoString(const std::string& a, + const EchoStringCallback& callback) override { + CHECK(false) << "Not implemented."; + } + + void EchoStrings(const std::string& a, + const std::string& b, + const EchoStringsCallback& callback) override { + CHECK(false) << "Not implemented."; + } + + void EchoMessagePipeHandle( + ScopedMessagePipeHandle a, + const EchoMessagePipeHandleCallback& callback) override { + CHECK(false) << "Not implemented."; + } + + void EchoEnum(sample::Enum a, const EchoEnumCallback& callback) override { + CHECK(false) << "Not implemented."; + } + + void resetLastServerValueSeen() { last_server_value_seen_ = 0; } + + int32_t last_server_value_seen() const { return last_server_value_seen_; } + + void set_closure(const base::Closure& closure) { closure_ = closure; } + + private: + int32_t last_server_value_seen_; + EchoIntCallback* callback_saved_; + base::Closure closure_; +}; + +class BindingCallbackTest : public testing::Test { + public: + BindingCallbackTest() {} + ~BindingCallbackTest() override {} + + protected: + int32_t last_client_callback_value_seen_; + sample::ProviderPtr interface_ptr_; + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +// Tests that the InterfacePtr and the Binding can communicate with each +// other normally. +TEST_F(BindingCallbackTest, Basic) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding<sample::Provider> binding(&server_impl, MakeRequest(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop, run_loop2; + server_impl.set_closure(run_loop.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, + run_loop2.QuitClosure())); + run_loop.Run(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now run the Callback. + server_impl.RunCallback(); + run_loop2.Run(); + + // Check that the client has now seen the correct value. + EXPECT_EQ(7, last_client_callback_value_seen_); + + // Initialize the test values again. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method again. + base::RunLoop run_loop3, run_loop4; + server_impl.set_closure(run_loop3.QuitClosure()); + interface_ptr_->EchoInt( + 13, + BindValueSaver(&last_client_callback_value_seen_, + run_loop4.QuitClosure())); + run_loop3.Run(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(13, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now run the Callback again. + server_impl.RunCallback(); + run_loop4.Run(); + + // Check that the client has now seen the correct value again. + EXPECT_EQ(13, last_client_callback_value_seen_); +} + +// Tests that running the Callback after the Binding has been deleted +// results in a clean failure. +TEST_F(BindingCallbackTest, DeleteBindingThenRunCallback) { + // Create the ServerImpl. + InterfaceImpl server_impl; + base::RunLoop run_loop; + { + // Create the binding in an inner scope so it can be deleted first. + Binding<sample::Provider> binding(&server_impl, + MakeRequest(&interface_ptr_)); + interface_ptr_.set_connection_error_handler(run_loop.QuitClosure()); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop2; + server_impl.set_closure(run_loop2.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop2.Run(); + } + // The binding has now been destroyed and the pipe is closed. + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now try to run the Callback. This should do nothing since the pipe + // is closed. + EXPECT_TRUE(server_impl.RunCallback()); + PumpMessages(); + + // Check that the client has still not seen the correct value. + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Attempt to invoke the method again and confirm that an error was + // encountered. + interface_ptr_->EchoInt( + 13, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop.Run(); + EXPECT_TRUE(interface_ptr_.encountered_error()); +} + +// Tests that deleting a Callback without running it after the corresponding +// binding has already been deleted does not result in a crash. +TEST_F(BindingCallbackTest, DeleteBindingThenDeleteCallback) { + // Create the ServerImpl. + InterfaceImpl server_impl; + { + // Create the binding in an inner scope so it can be deleted first. + Binding<sample::Provider> binding(&server_impl, + MakeRequest(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop; + server_impl.set_closure(run_loop.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop.Run(); + } + // The binding has now been destroyed and the pipe is closed. + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Delete the callback without running it. This should not + // cause a problem because the insfrastructure can detect that the + // binding has already been destroyed and the pipe is closed. + server_impl.DeleteCallback(); +} + +// Tests that closing a Binding allows us to delete a callback +// without running it without encountering a crash. +TEST_F(BindingCallbackTest, CloseBindingBeforeDeletingCallback) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding<sample::Provider> binding(&server_impl, MakeRequest(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop; + server_impl.set_closure(run_loop.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop.Run(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + // Now close the Binding. + binding.Close(); + + // Delete the callback without running it. This should not + // cause a crash because the insfrastructure can detect that the + // binding has already been closed. + server_impl.DeleteCallback(); + + // Check that the client has still not seen the correct value. + EXPECT_EQ(0, last_client_callback_value_seen_); +} + +// Tests that deleting a Callback without using it before the +// Binding has been destroyed or closed results in a DCHECK. +TEST_F(BindingCallbackTest, DeleteCallbackBeforeBindingDeathTest) { + // Create the ServerImpl and the Binding. + InterfaceImpl server_impl; + Binding<sample::Provider> binding(&server_impl, MakeRequest(&interface_ptr_)); + + // Initialize the test values. + server_impl.resetLastServerValueSeen(); + last_client_callback_value_seen_ = 0; + + // Invoke the Echo method. + base::RunLoop run_loop; + server_impl.set_closure(run_loop.QuitClosure()); + interface_ptr_->EchoInt( + 7, + BindValueSaver(&last_client_callback_value_seen_, base::Closure())); + run_loop.Run(); + + // Check that server saw the correct value, but the client has not yet. + EXPECT_EQ(7, server_impl.last_server_value_seen()); + EXPECT_EQ(0, last_client_callback_value_seen_); + + EXPECT_DCHECK_DEATH(server_impl.DeleteCallback()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/binding_set_unittest.cc b/mojo/public/cpp/bindings/tests/binding_set_unittest.cc new file mode 100644 index 0000000000..07acfbebe0 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_set_unittest.cc @@ -0,0 +1,416 @@ +// 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. + +#include <memory> +#include <utility> + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/associated_binding_set.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/strong_binding_set.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class BindingSetTest : public testing::Test { + public: + BindingSetTest() {} + ~BindingSetTest() override {} + + base::MessageLoop& loop() { return loop_; } + + private: + base::MessageLoop loop_; + + DISALLOW_COPY_AND_ASSIGN(BindingSetTest); +}; + +template <typename BindingSetType, typename ContextType> +void ExpectContextHelper(BindingSetType* binding_set, + ContextType expected_context) { + EXPECT_EQ(expected_context, binding_set->dispatch_context()); +} + +template <typename BindingSetType, typename ContextType> +base::Closure ExpectContext(BindingSetType* binding_set, + ContextType expected_context) { + return base::Bind( + &ExpectContextHelper<BindingSetType, ContextType>, binding_set, + expected_context); +} + +base::Closure Sequence(const base::Closure& first, + const base::Closure& second) { + return base::Bind( + [] (const base::Closure& first, const base::Closure& second) { + first.Run(); + second.Run(); + }, first, second); +} + +class PingImpl : public PingService { + public: + PingImpl() {} + ~PingImpl() override {} + + void set_ping_handler(const base::Closure& handler) { + ping_handler_ = handler; + } + + private: + // PingService: + void Ping(const PingCallback& callback) override { + if (!ping_handler_.is_null()) + ping_handler_.Run(); + callback.Run(); + } + + base::Closure ping_handler_; +}; + +TEST_F(BindingSetTest, BindingSetContext) { + PingImpl impl; + + BindingSet<PingService, int> bindings; + PingServicePtr ping_a, ping_b; + bindings.AddBinding(&impl, MakeRequest(&ping_a), 1); + bindings.AddBinding(&impl, MakeRequest(&ping_b), 2); + + { + impl.set_ping_handler(ExpectContext(&bindings, 1)); + base::RunLoop loop; + ping_a->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + impl.set_ping_handler(ExpectContext(&bindings, 2)); + base::RunLoop loop; + ping_b->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_connection_error_handler( + Sequence(ExpectContext(&bindings, 1), loop.QuitClosure())); + ping_a.reset(); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_connection_error_handler( + Sequence(ExpectContext(&bindings, 2), loop.QuitClosure())); + ping_b.reset(); + loop.Run(); + } + + EXPECT_TRUE(bindings.empty()); +} + +TEST_F(BindingSetTest, BindingSetConnectionErrorWithReason) { + PingImpl impl; + PingServicePtr ptr; + BindingSet<PingService> bindings; + bindings.AddBinding(&impl, MakeRequest(&ptr)); + + base::RunLoop run_loop; + bindings.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(1024u, custom_reason); + EXPECT_EQ("bye", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(1024u, "bye"); +} + +class PingProviderImpl : public AssociatedPingProvider, public PingService { + public: + PingProviderImpl() {} + ~PingProviderImpl() override {} + + void set_new_ping_context(int context) { new_ping_context_ = context; } + + void set_new_ping_handler(const base::Closure& handler) { + new_ping_handler_ = handler; + } + + void set_ping_handler(const base::Closure& handler) { + ping_handler_ = handler; + } + + AssociatedBindingSet<PingService, int>& ping_bindings() { + return ping_bindings_; + } + + private: + // AssociatedPingProvider: + void GetPing(PingServiceAssociatedRequest request) override { + ping_bindings_.AddBinding(this, std::move(request), new_ping_context_); + if (!new_ping_handler_.is_null()) + new_ping_handler_.Run(); + } + + // PingService: + void Ping(const PingCallback& callback) override { + if (!ping_handler_.is_null()) + ping_handler_.Run(); + callback.Run(); + } + + AssociatedBindingSet<PingService, int> ping_bindings_; + int new_ping_context_ = -1; + base::Closure ping_handler_; + base::Closure new_ping_handler_; +}; + +TEST_F(BindingSetTest, AssociatedBindingSetContext) { + AssociatedPingProviderPtr provider; + PingProviderImpl impl; + Binding<AssociatedPingProvider> binding(&impl, MakeRequest(&provider)); + + PingServiceAssociatedPtr ping_a; + { + base::RunLoop loop; + impl.set_new_ping_context(1); + impl.set_new_ping_handler(loop.QuitClosure()); + provider->GetPing(MakeRequest(&ping_a)); + loop.Run(); + } + + PingServiceAssociatedPtr ping_b; + { + base::RunLoop loop; + impl.set_new_ping_context(2); + impl.set_new_ping_handler(loop.QuitClosure()); + provider->GetPing(MakeRequest(&ping_b)); + loop.Run(); + } + + { + impl.set_ping_handler(ExpectContext(&impl.ping_bindings(), 1)); + base::RunLoop loop; + ping_a->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + impl.set_ping_handler(ExpectContext(&impl.ping_bindings(), 2)); + base::RunLoop loop; + ping_b->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + base::RunLoop loop; + impl.ping_bindings().set_connection_error_handler( + Sequence(ExpectContext(&impl.ping_bindings(), 1), loop.QuitClosure())); + ping_a.reset(); + loop.Run(); + } + + { + base::RunLoop loop; + impl.ping_bindings().set_connection_error_handler( + Sequence(ExpectContext(&impl.ping_bindings(), 2), loop.QuitClosure())); + ping_b.reset(); + loop.Run(); + } + + EXPECT_TRUE(impl.ping_bindings().empty()); +} + +TEST_F(BindingSetTest, MasterInterfaceBindingSetContext) { + AssociatedPingProviderPtr provider_a, provider_b; + PingProviderImpl impl; + BindingSet<AssociatedPingProvider, int> bindings; + + bindings.AddBinding(&impl, MakeRequest(&provider_a), 1); + bindings.AddBinding(&impl, MakeRequest(&provider_b), 2); + + { + PingServiceAssociatedPtr ping; + base::RunLoop loop; + impl.set_new_ping_handler( + Sequence(ExpectContext(&bindings, 1), loop.QuitClosure())); + provider_a->GetPing(MakeRequest(&ping)); + loop.Run(); + } + + { + PingServiceAssociatedPtr ping; + base::RunLoop loop; + impl.set_new_ping_handler( + Sequence(ExpectContext(&bindings, 2), loop.QuitClosure())); + provider_b->GetPing(MakeRequest(&ping)); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_connection_error_handler( + Sequence(ExpectContext(&bindings, 1), loop.QuitClosure())); + provider_a.reset(); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_connection_error_handler( + Sequence(ExpectContext(&bindings, 2), loop.QuitClosure())); + provider_b.reset(); + loop.Run(); + } + + EXPECT_TRUE(bindings.empty()); +} + +TEST_F(BindingSetTest, PreDispatchHandler) { + PingImpl impl; + + BindingSet<PingService, int> bindings; + PingServicePtr ping_a, ping_b; + bindings.AddBinding(&impl, MakeRequest(&ping_a), 1); + bindings.AddBinding(&impl, MakeRequest(&ping_b), 2); + + { + bindings.set_pre_dispatch_handler(base::Bind([] (const int& context) { + EXPECT_EQ(1, context); + })); + base::RunLoop loop; + ping_a->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + bindings.set_pre_dispatch_handler(base::Bind([] (const int& context) { + EXPECT_EQ(2, context); + })); + base::RunLoop loop; + ping_b->Ping(loop.QuitClosure()); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_pre_dispatch_handler( + base::Bind([](base::RunLoop* loop, const int& context) { + EXPECT_EQ(1, context); + loop->Quit(); + }, &loop)); + ping_a.reset(); + loop.Run(); + } + + { + base::RunLoop loop; + bindings.set_pre_dispatch_handler( + base::Bind([](base::RunLoop* loop, const int& context) { + EXPECT_EQ(2, context); + loop->Quit(); + }, &loop)); + ping_b.reset(); + loop.Run(); + } + + EXPECT_TRUE(bindings.empty()); +} + +TEST_F(BindingSetTest, AssociatedBindingSetConnectionErrorWithReason) { + AssociatedPingProviderPtr master_ptr; + PingProviderImpl master_impl; + Binding<AssociatedPingProvider> master_binding(&master_impl, &master_ptr); + + base::RunLoop run_loop; + master_impl.ping_bindings().set_connection_error_with_reason_handler( + base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(2048u, custom_reason); + EXPECT_EQ("bye", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + PingServiceAssociatedPtr ptr; + master_ptr->GetPing(MakeRequest(&ptr)); + + ptr.ResetWithReason(2048u, "bye"); + + run_loop.Run(); +} + +class PingInstanceCounter : public PingService { + public: + PingInstanceCounter() { ++instance_count; } + ~PingInstanceCounter() override { --instance_count; } + + void Ping(const PingCallback& callback) override {} + + static int instance_count; +}; +int PingInstanceCounter::instance_count = 0; + +TEST_F(BindingSetTest, StrongBinding_Destructor) { + PingServicePtr ping_a, ping_b; + auto bindings = base::MakeUnique<StrongBindingSet<PingService>>(); + + bindings->AddBinding(base::MakeUnique<PingInstanceCounter>(), + mojo::MakeRequest(&ping_a)); + EXPECT_EQ(1, PingInstanceCounter::instance_count); + + bindings->AddBinding(base::MakeUnique<PingInstanceCounter>(), + mojo::MakeRequest(&ping_b)); + EXPECT_EQ(2, PingInstanceCounter::instance_count); + + bindings.reset(); + EXPECT_EQ(0, PingInstanceCounter::instance_count); +} + +TEST_F(BindingSetTest, StrongBinding_ConnectionError) { + PingServicePtr ping_a, ping_b; + StrongBindingSet<PingService> bindings; + bindings.AddBinding(base::MakeUnique<PingInstanceCounter>(), + mojo::MakeRequest(&ping_a)); + bindings.AddBinding(base::MakeUnique<PingInstanceCounter>(), + mojo::MakeRequest(&ping_b)); + EXPECT_EQ(2, PingInstanceCounter::instance_count); + + ping_a.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, PingInstanceCounter::instance_count); + + ping_b.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(0, PingInstanceCounter::instance_count); +} + +TEST_F(BindingSetTest, StrongBinding_RemoveBinding) { + PingServicePtr ping_a, ping_b; + StrongBindingSet<PingService> bindings; + BindingId binding_id_a = bindings.AddBinding( + base::MakeUnique<PingInstanceCounter>(), mojo::MakeRequest(&ping_a)); + BindingId binding_id_b = bindings.AddBinding( + base::MakeUnique<PingInstanceCounter>(), mojo::MakeRequest(&ping_b)); + EXPECT_EQ(2, PingInstanceCounter::instance_count); + + EXPECT_TRUE(bindings.RemoveBinding(binding_id_a)); + EXPECT_EQ(1, PingInstanceCounter::instance_count); + + EXPECT_TRUE(bindings.RemoveBinding(binding_id_b)); + EXPECT_EQ(0, PingInstanceCounter::instance_count); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/binding_unittest.cc b/mojo/public/cpp/bindings/tests/binding_unittest.cc new file mode 100644 index 0000000000..e76993bb68 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/binding_unittest.cc @@ -0,0 +1,611 @@ +// 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. + +// Note: This file tests both binding.h (mojo::Binding) and strong_binding.h +// (mojo::StrongBinding). + +#include "mojo/public/cpp/bindings/binding.h" + +#include <stdint.h> +#include <utility> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class BindingTestBase : public testing::Test { + public: + BindingTestBase() {} + ~BindingTestBase() override {} + + base::MessageLoop& loop() { return loop_; } + + private: + base::MessageLoop loop_; + + DISALLOW_COPY_AND_ASSIGN(BindingTestBase); +}; + +class ServiceImpl : public sample::Service { + public: + explicit ServiceImpl(bool* was_deleted = nullptr) + : was_deleted_(was_deleted) {} + ~ServiceImpl() override { + if (was_deleted_) + *was_deleted_ = true; + } + + private: + // sample::Service implementation + void Frobinate(sample::FooPtr foo, + BazOptions options, + sample::PortPtr port, + const FrobinateCallback& callback) override { + callback.Run(1); + } + void GetPort(InterfaceRequest<sample::Port> port) override {} + + bool* const was_deleted_; + + DISALLOW_COPY_AND_ASSIGN(ServiceImpl); +}; + +template <typename... Args> +void DoSetFlagAndRunClosure(bool* flag, + const base::Closure& closure, + Args... args) { + *flag = true; + if (!closure.is_null()) + closure.Run(); +} + +template <typename... Args> +base::Callback<void(Args...)> SetFlagAndRunClosure( + bool* flag, + const base::Closure& callback = base::Closure()) { + return base::Bind(&DoSetFlagAndRunClosure<Args...>, flag, callback); +} + +// BindingTest ----------------------------------------------------------------- + +using BindingTest = BindingTestBase; + +TEST_F(BindingTest, Close) { + bool called = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + base::RunLoop run_loop; + ptr.set_connection_error_handler( + SetFlagAndRunClosure(&called, run_loop.QuitClosure())); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, std::move(request)); + + binding.Close(); + EXPECT_FALSE(called); + run_loop.Run(); + EXPECT_TRUE(called); +} + +// Tests that destroying a mojo::Binding closes the bound message pipe handle. +TEST_F(BindingTest, DestroyClosesMessagePipe) { + bool encountered_error = false; + ServiceImpl impl; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + base::RunLoop run_loop; + ptr.set_connection_error_handler( + SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure())); + bool called = false; + base::RunLoop run_loop2; + { + Binding<sample::Service> binding(&impl, std::move(request)); + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called, + run_loop2.QuitClosure())); + run_loop2.Run(); + EXPECT_TRUE(called); + EXPECT_FALSE(encountered_error); + } + // Now that the Binding is out of scope we should detect an error on the other + // end of the pipe. + run_loop.Run(); + EXPECT_TRUE(encountered_error); + + // And calls should fail. + called = false; + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called, + run_loop2.QuitClosure())); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(called); +} + +// Tests that the binding's connection error handler gets called when the other +// end is closed. +TEST_F(BindingTest, ConnectionError) { + bool called = false; + { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding<sample::Service> binding(&impl, MakeRequest(&ptr)); + base::RunLoop run_loop; + binding.set_connection_error_handler( + SetFlagAndRunClosure(&called, run_loop.QuitClosure())); + ptr.reset(); + EXPECT_FALSE(called); + run_loop.Run(); + EXPECT_TRUE(called); + // We want to make sure that it isn't called again during destruction. + called = false; + } + EXPECT_FALSE(called); +} + +// Tests that calling Close doesn't result in the connection error handler being +// called. +TEST_F(BindingTest, CloseDoesntCallConnectionErrorHandler) { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding<sample::Service> binding(&impl, MakeRequest(&ptr)); + bool called = false; + binding.set_connection_error_handler(SetFlagAndRunClosure(&called)); + binding.Close(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(called); + + // We can also close the other end, and the error handler still won't be + // called. + ptr.reset(); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(called); +} + +class ServiceImplWithBinding : public ServiceImpl { + public: + ServiceImplWithBinding(bool* was_deleted, + const base::Closure& closure, + InterfaceRequest<sample::Service> request) + : ServiceImpl(was_deleted), + binding_(this, std::move(request)), + closure_(closure) { + binding_.set_connection_error_handler( + base::Bind(&ServiceImplWithBinding::OnConnectionError, + base::Unretained(this))); + } + + private: + ~ServiceImplWithBinding() override{ + closure_.Run(); + } + + void OnConnectionError() { delete this; } + + Binding<sample::Service> binding_; + base::Closure closure_; + + DISALLOW_COPY_AND_ASSIGN(ServiceImplWithBinding); +}; + +// Tests that the binding may be deleted in the connection error handler. +TEST_F(BindingTest, SelfDeleteOnConnectionError) { + bool was_deleted = false; + sample::ServicePtr ptr; + // This should delete itself on connection error. + base::RunLoop run_loop; + new ServiceImplWithBinding(&was_deleted, run_loop.QuitClosure(), + MakeRequest(&ptr)); + ptr.reset(); + EXPECT_FALSE(was_deleted); + run_loop.Run(); + EXPECT_TRUE(was_deleted); +} + +// Tests that explicitly calling Unbind followed by rebinding works. +TEST_F(BindingTest, Unbind) { + ServiceImpl impl; + sample::ServicePtr ptr; + Binding<sample::Service> binding(&impl, MakeRequest(&ptr)); + + bool called = false; + base::RunLoop run_loop; + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called, + run_loop.QuitClosure())); + run_loop.Run(); + EXPECT_TRUE(called); + + called = false; + auto request = binding.Unbind(); + EXPECT_FALSE(binding.is_bound()); + // All calls should fail when not bound... + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called, + run_loop.QuitClosure())); + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(called); + + called = false; + binding.Bind(std::move(request)); + EXPECT_TRUE(binding.is_bound()); + // ...and should succeed again when the rebound. + base::RunLoop run_loop2; + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called, + run_loop2.QuitClosure())); + run_loop2.Run(); + EXPECT_TRUE(called); +} + +class IntegerAccessorImpl : public sample::IntegerAccessor { + public: + IntegerAccessorImpl() {} + ~IntegerAccessorImpl() override {} + + private: + // sample::IntegerAccessor implementation. + void GetInteger(const GetIntegerCallback& callback) override { + callback.Run(1, sample::Enum::VALUE); + } + void SetInteger(int64_t data, sample::Enum type) override {} + + DISALLOW_COPY_AND_ASSIGN(IntegerAccessorImpl); +}; + +TEST_F(BindingTest, SetInterfacePtrVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding<sample::IntegerAccessor> binding(&impl, &ptr); + EXPECT_EQ(3u, ptr.version()); +} + +TEST_F(BindingTest, PauseResume) { + bool called = false; + base::RunLoop run_loop; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, std::move(request)); + binding.PauseIncomingMethodCallProcessing(); + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called, + run_loop.QuitClosure())); + EXPECT_FALSE(called); + base::RunLoop().RunUntilIdle(); + // Frobinate() should not be called as the binding is paused. + EXPECT_FALSE(called); + + // Resume the binding, which should trigger processing. + binding.ResumeIncomingMethodCallProcessing(); + run_loop.Run(); + EXPECT_TRUE(called); +} + +// Verifies the connection error handler is not run while a binding is paused. +TEST_F(BindingTest, ErrorHandleNotRunWhilePaused) { + bool called = false; + base::RunLoop run_loop; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, std::move(request)); + binding.set_connection_error_handler( + SetFlagAndRunClosure(&called, run_loop.QuitClosure())); + binding.PauseIncomingMethodCallProcessing(); + + ptr.reset(); + base::RunLoop().RunUntilIdle(); + // The connection error handle should not be called as the binding is paused. + EXPECT_FALSE(called); + + // Resume the binding, which should trigger the error handler. + binding.ResumeIncomingMethodCallProcessing(); + run_loop.Run(); + EXPECT_TRUE(called); +} + +class PingServiceImpl : public test::PingService { + public: + PingServiceImpl() {} + ~PingServiceImpl() override {} + + // test::PingService: + void Ping(const PingCallback& callback) override { + if (!ping_handler_.is_null()) + ping_handler_.Run(); + callback.Run(); + } + + void set_ping_handler(const base::Closure& handler) { + ping_handler_ = handler; + } + + private: + base::Closure ping_handler_; + + DISALLOW_COPY_AND_ASSIGN(PingServiceImpl); +}; + +class CallbackFilter : public MessageReceiver { + public: + explicit CallbackFilter(const base::Closure& callback) + : callback_(callback) {} + ~CallbackFilter() override {} + + static std::unique_ptr<CallbackFilter> Wrap(const base::Closure& callback) { + return base::MakeUnique<CallbackFilter>(callback); + } + + // MessageReceiver: + bool Accept(Message* message) override { + callback_.Run(); + return true; + } + + private: + const base::Closure callback_; +}; + +// Verifies that message filters are notified in the order they were added and +// are always notified before a message is dispatched. +TEST_F(BindingTest, MessageFilter) { + test::PingServicePtr ptr; + PingServiceImpl impl; + mojo::Binding<test::PingService> binding(&impl, MakeRequest(&ptr)); + + int status = 0; + auto handler_helper = [] (int* status, int expected_status, int new_status) { + EXPECT_EQ(expected_status, *status); + *status = new_status; + }; + auto create_handler = [&] (int expected_status, int new_status) { + return base::Bind(handler_helper, &status, expected_status, new_status); + }; + + binding.AddFilter(CallbackFilter::Wrap(create_handler(0, 1))); + binding.AddFilter(CallbackFilter::Wrap(create_handler(1, 2))); + impl.set_ping_handler(create_handler(2, 3)); + + for (int i = 0; i < 10; ++i) { + status = 0; + base::RunLoop loop; + ptr->Ping(loop.QuitClosure()); + loop.Run(); + EXPECT_EQ(3, status); + } +} + +void Fail() { + FAIL() << "Unexpected connection error"; +} + +TEST_F(BindingTest, FlushForTesting) { + bool called = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, std::move(request)); + binding.set_connection_error_handler(base::Bind(&Fail)); + + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called)); + EXPECT_FALSE(called); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + binding.FlushForTesting(); + binding.FlushForTesting(); + EXPECT_TRUE(called); +} + +TEST_F(BindingTest, FlushForTestingWithClosedPeer) { + bool called = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, std::move(request)); + binding.set_connection_error_handler(SetFlagAndRunClosure(&called)); + ptr.reset(); + + EXPECT_FALSE(called); + binding.FlushForTesting(); + EXPECT_TRUE(called); + binding.FlushForTesting(); +} + +TEST_F(BindingTest, ConnectionErrorWithReason) { + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ServiceImpl impl; + Binding<sample::Service> binding(&impl, std::move(request)); + + base::RunLoop run_loop; + binding.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(1234u, custom_reason); + EXPECT_EQ("hello", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(1234u, "hello"); + + run_loop.Run(); +} + +template <typename T> +struct WeakPtrImplRefTraits { + using PointerType = base::WeakPtr<T>; + + static bool IsNull(const base::WeakPtr<T>& ptr) { return !ptr; } + static T* GetRawPointer(base::WeakPtr<T>* ptr) { return ptr->get(); } +}; + +template <typename T> +using WeakBinding = Binding<T, WeakPtrImplRefTraits<T>>; + +TEST_F(BindingTest, CustomImplPointerType) { + PingServiceImpl impl; + base::WeakPtrFactory<test::PingService> weak_factory(&impl); + + test::PingServicePtr proxy; + WeakBinding<test::PingService> binding(weak_factory.GetWeakPtr(), + MakeRequest(&proxy)); + + { + // Ensure the binding is functioning. + base::RunLoop run_loop; + proxy->Ping(run_loop.QuitClosure()); + run_loop.Run(); + } + + { + // Attempt to dispatch another message after the WeakPtr is invalidated. + base::Closure assert_not_reached = base::Bind([] { NOTREACHED(); }); + impl.set_ping_handler(assert_not_reached); + proxy->Ping(assert_not_reached); + + // The binding will close its end of the pipe which will trigger a + // connection error on |proxy|. + base::RunLoop run_loop; + proxy.set_connection_error_handler(run_loop.QuitClosure()); + weak_factory.InvalidateWeakPtrs(); + run_loop.Run(); + } +} + +// StrongBindingTest ----------------------------------------------------------- + +using StrongBindingTest = BindingTestBase; + +// Tests that destroying a mojo::StrongBinding closes the bound message pipe +// handle but does *not* destroy the implementation object. +TEST_F(StrongBindingTest, DestroyClosesMessagePipe) { + base::RunLoop run_loop; + bool encountered_error = false; + bool was_deleted = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + ptr.set_connection_error_handler( + SetFlagAndRunClosure(&encountered_error, run_loop.QuitClosure())); + bool called = false; + base::RunLoop run_loop2; + + auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted), + std::move(request)); + ptr->Frobinate( + nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called, run_loop2.QuitClosure())); + run_loop2.Run(); + EXPECT_TRUE(called); + EXPECT_FALSE(encountered_error); + binding->Close(); + + // Now that the StrongBinding is closed we should detect an error on the other + // end of the pipe. + run_loop.Run(); + EXPECT_TRUE(encountered_error); + + // Destroying the StrongBinding also destroys the impl. + ASSERT_TRUE(was_deleted); +} + +// Tests the typical case, where the implementation object owns the +// StrongBinding (and should be destroyed on connection error). +TEST_F(StrongBindingTest, ConnectionErrorDestroysImpl) { + sample::ServicePtr ptr; + bool was_deleted = false; + // Will delete itself. + base::RunLoop run_loop; + new ServiceImplWithBinding(&was_deleted, run_loop.QuitClosure(), + MakeRequest(&ptr)); + + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE(was_deleted); + + ptr.reset(); + EXPECT_FALSE(was_deleted); + run_loop.Run(); + EXPECT_TRUE(was_deleted); +} + +TEST_F(StrongBindingTest, FlushForTesting) { + bool called = false; + bool was_deleted = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted), + std::move(request)); + binding->set_connection_error_handler(base::Bind(&Fail)); + + ptr->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + SetFlagAndRunClosure<int32_t>(&called)); + EXPECT_FALSE(called); + // Because the flush is sent from the binding, it only guarantees that the + // request has been received, not the response. The second flush waits for the + // response to be received. + ASSERT_TRUE(binding); + binding->FlushForTesting(); + ASSERT_TRUE(binding); + binding->FlushForTesting(); + EXPECT_TRUE(called); + EXPECT_FALSE(was_deleted); + ptr.reset(); + ASSERT_TRUE(binding); + binding->set_connection_error_handler(base::Closure()); + binding->FlushForTesting(); + EXPECT_TRUE(was_deleted); +} + +TEST_F(StrongBindingTest, FlushForTestingWithClosedPeer) { + bool called = false; + bool was_deleted = false; + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + auto binding = MakeStrongBinding(base::MakeUnique<ServiceImpl>(&was_deleted), + std::move(request)); + binding->set_connection_error_handler(SetFlagAndRunClosure(&called)); + ptr.reset(); + + EXPECT_FALSE(called); + EXPECT_FALSE(was_deleted); + ASSERT_TRUE(binding); + binding->FlushForTesting(); + EXPECT_TRUE(called); + EXPECT_TRUE(was_deleted); + ASSERT_FALSE(binding); +} + +TEST_F(StrongBindingTest, ConnectionErrorWithReason) { + sample::ServicePtr ptr; + auto request = MakeRequest(&ptr); + auto binding = + MakeStrongBinding(base::MakeUnique<ServiceImpl>(), std::move(request)); + base::RunLoop run_loop; + binding->set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(5678u, custom_reason); + EXPECT_EQ("hello", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + ptr.ResetWithReason(5678u, "hello"); + + run_loop.Run(); +} + +} // namespace +} // mojo diff --git a/mojo/public/cpp/bindings/tests/bindings_perftest.cc b/mojo/public/cpp/bindings/tests/bindings_perftest.cc new file mode 100644 index 0000000000..65b3c8c1d4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/bindings_perftest.cc @@ -0,0 +1,286 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <utility> + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const double kMojoTicksPerSecond = 1000000.0; + +double MojoTicksToSeconds(MojoTimeTicks ticks) { + return ticks / kMojoTicksPerSecond; +} + +class PingServiceImpl : public test::PingService { + public: + PingServiceImpl() {} + ~PingServiceImpl() override {} + + // |PingService| methods: + void Ping(const PingCallback& callback) override; + + private: + DISALLOW_COPY_AND_ASSIGN(PingServiceImpl); +}; + +void PingServiceImpl::Ping(const PingCallback& callback) { + callback.Run(); +} + +class PingPongTest { + public: + explicit PingPongTest(test::PingServicePtr service); + + void Run(unsigned int iterations); + + private: + void OnPingDone(); + + test::PingServicePtr service_; + unsigned int iterations_to_run_; + unsigned int current_iterations_; + + base::Closure quit_closure_; + + DISALLOW_COPY_AND_ASSIGN(PingPongTest); +}; + +PingPongTest::PingPongTest(test::PingServicePtr service) + : service_(std::move(service)) {} + +void PingPongTest::Run(unsigned int iterations) { + iterations_to_run_ = iterations; + current_iterations_ = 0; + + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + service_->Ping(base::Bind(&PingPongTest::OnPingDone, base::Unretained(this))); + run_loop.Run(); +} + +void PingPongTest::OnPingDone() { + current_iterations_++; + if (current_iterations_ >= iterations_to_run_) { + quit_closure_.Run(); + return; + } + + service_->Ping(base::Bind(&PingPongTest::OnPingDone, base::Unretained(this))); +} + +struct BoundPingService { + BoundPingService() : binding(&impl) { binding.Bind(MakeRequest(&service)); } + + PingServiceImpl impl; + test::PingServicePtr service; + Binding<test::PingService> binding; +}; + +class MojoBindingsPerftest : public testing::Test { + public: + MojoBindingsPerftest() {} + + protected: + base::MessageLoop loop_; +}; + +TEST_F(MojoBindingsPerftest, InProcessPingPong) { + test::PingServicePtr service; + PingServiceImpl impl; + Binding<test::PingService> binding(&impl, MakeRequest(&service)); + PingPongTest test(std::move(service)); + + { + const unsigned int kIterations = 100000; + const MojoTimeTicks start_time = MojoGetTimeTicksNow(); + test.Run(kIterations); + const MojoTimeTicks end_time = MojoGetTimeTicksNow(); + test::LogPerfResult( + "InProcessPingPong", "0_Inactive", + kIterations / MojoTicksToSeconds(end_time - start_time), + "pings/second"); + } + + { + const size_t kNumInactiveServices = 1000; + BoundPingService* inactive_services = + new BoundPingService[kNumInactiveServices]; + + const unsigned int kIterations = 10000; + const MojoTimeTicks start_time = MojoGetTimeTicksNow(); + test.Run(kIterations); + const MojoTimeTicks end_time = MojoGetTimeTicksNow(); + test::LogPerfResult( + "InProcessPingPong", "1000_Inactive", + kIterations / MojoTicksToSeconds(end_time - start_time), + "pings/second"); + + delete[] inactive_services; + } +} + +class PingPongPaddle : public MessageReceiverWithResponderStatus { + public: + PingPongPaddle(MessageReceiver* sender) : sender_(sender) {} + + void set_sender(MessageReceiver* sender) { sender_ = sender; } + + bool Accept(Message* message) override { + uint32_t count = message->header()->name; + if (!quit_closure_.is_null()) { + count++; + if (count >= expected_count_) { + end_time_ = base::TimeTicks::Now(); + quit_closure_.Run(); + return true; + } + } + + internal::MessageBuilder builder(count, 0, 8, 0); + bool result = sender_->Accept(builder.message()); + DCHECK(result); + return true; + } + + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override { + NOTREACHED(); + return true; + } + + base::TimeDelta Serve(uint32_t expected_count) { + base::RunLoop run_loop; + + expected_count_ = expected_count; + quit_closure_ = run_loop.QuitClosure(); + + start_time_ = base::TimeTicks::Now(); + internal::MessageBuilder builder(0, 0, 8, 0); + bool result = sender_->Accept(builder.message()); + DCHECK(result); + + run_loop.Run(); + + return end_time_ - start_time_; + } + + private: + base::TimeTicks start_time_; + base::TimeTicks end_time_; + uint32_t expected_count_ = 0; + MessageReceiver* sender_; + base::Closure quit_closure_; +}; + +TEST_F(MojoBindingsPerftest, MultiplexRouterPingPong) { + MessagePipe pipe; + scoped_refptr<internal::MultiplexRouter> router0( + new internal::MultiplexRouter(std::move(pipe.handle0), + internal::MultiplexRouter::SINGLE_INTERFACE, + true, base::ThreadTaskRunnerHandle::Get())); + scoped_refptr<internal::MultiplexRouter> router1( + new internal::MultiplexRouter( + std::move(pipe.handle1), internal::MultiplexRouter::SINGLE_INTERFACE, + false, base::ThreadTaskRunnerHandle::Get())); + + PingPongPaddle paddle0(nullptr); + PingPongPaddle paddle1(nullptr); + + InterfaceEndpointClient client0( + router0->CreateLocalEndpointHandle(kMasterInterfaceId), &paddle0, nullptr, + false, base::ThreadTaskRunnerHandle::Get(), 0u); + InterfaceEndpointClient client1( + router1->CreateLocalEndpointHandle(kMasterInterfaceId), &paddle1, nullptr, + false, base::ThreadTaskRunnerHandle::Get(), 0u); + + paddle0.set_sender(&client0); + paddle1.set_sender(&client1); + + static const uint32_t kWarmUpIterations = 1000; + static const uint32_t kTestIterations = 1000000; + + paddle0.Serve(kWarmUpIterations); + + base::TimeDelta duration = paddle0.Serve(kTestIterations); + + test::LogPerfResult("MultiplexRouterPingPong", nullptr, + kTestIterations / duration.InSecondsF(), "pings/second"); +} + +class CounterReceiver : public MessageReceiverWithResponderStatus { + public: + bool Accept(Message* message) override { + counter_++; + return true; + } + + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override { + NOTREACHED(); + return true; + } + + uint32_t counter() const { return counter_; } + + void Reset() { counter_ = 0; } + + private: + uint32_t counter_ = 0; +}; + +TEST_F(MojoBindingsPerftest, MultiplexRouterDispatchCost) { + MessagePipe pipe; + scoped_refptr<internal::MultiplexRouter> router(new internal::MultiplexRouter( + std::move(pipe.handle0), internal::MultiplexRouter::SINGLE_INTERFACE, + true, base::ThreadTaskRunnerHandle::Get())); + CounterReceiver receiver; + InterfaceEndpointClient client( + router->CreateLocalEndpointHandle(kMasterInterfaceId), &receiver, nullptr, + false, base::ThreadTaskRunnerHandle::Get(), 0u); + + static const uint32_t kIterations[] = {1000, 3000000}; + + for (size_t i = 0; i < 2; ++i) { + receiver.Reset(); + base::TimeTicks start_time = base::TimeTicks::Now(); + for (size_t j = 0; j < kIterations[i]; ++j) { + internal::MessageBuilder builder(0, 0, 8, 0); + bool result = + router->SimulateReceivingMessageForTesting(builder.message()); + DCHECK(result); + } + + base::TimeTicks end_time = base::TimeTicks::Now(); + base::TimeDelta duration = end_time - start_time; + CHECK_EQ(kIterations[i], receiver.counter()); + + if (i == 1) { + test::LogPerfResult("MultiplexRouterDispatchCost", nullptr, + kIterations[i] / duration.InSecondsF(), + "times/second"); + } + } +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/blink_typemaps.gni b/mojo/public/cpp/bindings/tests/blink_typemaps.gni new file mode 100644 index 0000000000..b71dcf8d46 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/blink_typemaps.gni @@ -0,0 +1,8 @@ +# 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. + +typemaps = [ + "//mojo/public/cpp/bindings/tests/rect_blink.typemap", + "//mojo/public/cpp/bindings/tests/test_native_types_blink.typemap", +] diff --git a/mojo/public/cpp/bindings/tests/buffer_unittest.cc b/mojo/public/cpp/bindings/tests/buffer_unittest.cc new file mode 100644 index 0000000000..d75bdd0785 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/buffer_unittest.cc @@ -0,0 +1,93 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> + +#include <limits> + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +bool IsZero(void* p_buf, size_t size) { + char* buf = reinterpret_cast<char*>(p_buf); + for (size_t i = 0; i < size; ++i) { + if (buf[i] != 0) + return false; + } + return true; +} + +// Tests that FixedBuffer allocates memory aligned to 8 byte boundaries. +TEST(FixedBufferTest, Alignment) { + internal::FixedBufferForTesting buf(internal::Align(10) * 2); + ASSERT_EQ(buf.size(), 16u * 2); + + void* a = buf.Allocate(10); + ASSERT_TRUE(a); + EXPECT_TRUE(IsZero(a, 10)); + EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(a) % 8); + + void* b = buf.Allocate(10); + ASSERT_TRUE(b); + EXPECT_TRUE(IsZero(b, 10)); + EXPECT_EQ(0, reinterpret_cast<ptrdiff_t>(b) % 8); + + // Any more allocations would result in an assert, but we can't test that. +} + +// Tests that FixedBufferForTesting::Leak passes ownership to the caller. +TEST(FixedBufferTest, Leak) { + void* ptr = nullptr; + void* buf_ptr = nullptr; + { + internal::FixedBufferForTesting buf(8); + ASSERT_EQ(8u, buf.size()); + + ptr = buf.Allocate(8); + ASSERT_TRUE(ptr); + buf_ptr = buf.Leak(); + + // The buffer should point to the first element allocated. + // TODO(mpcomplete): Is this a reasonable expectation? + EXPECT_EQ(ptr, buf_ptr); + + // The FixedBufferForTesting should be empty now. + EXPECT_EQ(0u, buf.size()); + EXPECT_FALSE(buf.Leak()); + } + + // Since we called Leak, ptr is still writable after FixedBufferForTesting + // went out of scope. + memset(ptr, 1, 8); + free(buf_ptr); +} + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +TEST(FixedBufferTest, TooBig) { + internal::FixedBufferForTesting buf(24); + + // A little bit too large. + EXPECT_EQ(reinterpret_cast<void*>(0), buf.Allocate(32)); + + // Move the cursor forward. + EXPECT_NE(reinterpret_cast<void*>(0), buf.Allocate(16)); + + // A lot too large. + EXPECT_EQ(reinterpret_cast<void*>(0), + buf.Allocate(std::numeric_limits<size_t>::max() - 1024u)); + + // A lot too large, leading to possible integer overflow. + EXPECT_EQ(reinterpret_cast<void*>(0), + buf.Allocate(std::numeric_limits<size_t>::max() - 8u)); +} +#endif + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/chromium_typemaps.gni b/mojo/public/cpp/bindings/tests/chromium_typemaps.gni new file mode 100644 index 0000000000..1da7cbfa3e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/chromium_typemaps.gni @@ -0,0 +1,9 @@ +# 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. + +typemaps = [ + "//mojo/public/cpp/bindings/tests/rect_chromium.typemap", + "//mojo/public/cpp/bindings/tests/struct_with_traits.typemap", + "//mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap", +] diff --git a/mojo/public/cpp/bindings/tests/connector_unittest.cc b/mojo/public/cpp/bindings/tests/connector_unittest.cc new file mode 100644 index 0000000000..74ecb7a9ee --- /dev/null +++ b/mojo/public/cpp/bindings/tests/connector_unittest.cc @@ -0,0 +1,599 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/connector.h" + +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/callback_helpers.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/tests/message_queue.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class MessageAccumulator : public MessageReceiver { + public: + MessageAccumulator() {} + explicit MessageAccumulator(const base::Closure& closure) + : closure_(closure) {} + + bool Accept(Message* message) override { + queue_.Push(message); + if (!closure_.is_null()) + base::ResetAndReturn(&closure_).Run(); + return true; + } + + bool IsEmpty() const { return queue_.IsEmpty(); } + + void Pop(Message* message) { queue_.Pop(message); } + + void set_closure(const base::Closure& closure) { closure_ = closure; } + + size_t size() const { return queue_.size(); } + + private: + MessageQueue queue_; + base::Closure closure_; +}; + +class ConnectorDeletingMessageAccumulator : public MessageAccumulator { + public: + ConnectorDeletingMessageAccumulator(Connector** connector) + : connector_(connector) {} + + bool Accept(Message* message) override { + delete *connector_; + *connector_ = nullptr; + return MessageAccumulator::Accept(message); + } + + private: + Connector** connector_; +}; + +class ReentrantMessageAccumulator : public MessageAccumulator { + public: + ReentrantMessageAccumulator(Connector* connector) + : connector_(connector), number_of_calls_(0) {} + + bool Accept(Message* message) override { + if (!MessageAccumulator::Accept(message)) + return false; + number_of_calls_++; + if (number_of_calls_ == 1) { + return connector_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + } + return true; + } + + int number_of_calls() { return number_of_calls_; } + + private: + Connector* connector_; + int number_of_calls_; +}; + +class ConnectorTest : public testing::Test { + public: + ConnectorTest() {} + + void SetUp() override { + CreateMessagePipe(nullptr, &handle0_, &handle1_); + } + + void TearDown() override {} + + void AllocMessage(const char* text, Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::MessageBuilder builder(1, 0, payload_size, 0); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + + *message = std::move(*builder.message()); + } + + protected: + ScopedMessagePipeHandle handle0_; + ScopedMessagePipeHandle handle1_; + + private: + base::MessageLoop loop_; +}; + +TEST_F(ConnectorTest, Basic) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + base::RunLoop run_loop; + MessageAccumulator accumulator(run_loop.QuitClosure()); + connector1.set_incoming_receiver(&accumulator); + + run_loop.Run(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_Synchronous) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + connector1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_EarlyIncomingReceiver) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + base::RunLoop run_loop; + MessageAccumulator accumulator(run_loop.QuitClosure()); + connector1.set_incoming_receiver(&accumulator); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + run_loop.Run(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, Basic_TwoMessages) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < arraysize(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + for (size_t i = 0; i < arraysize(kText); ++i) { + if (accumulator.IsEmpty()) { + base::RunLoop run_loop; + accumulator.set_closure(run_loop.QuitClosure()); + run_loop.Run(); + } + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[i]), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + } +} + +TEST_F(ConnectorTest, Basic_TwoMessages_Synchronous) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < arraysize(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + MessageAccumulator accumulator; + connector1.set_incoming_receiver(&accumulator); + + connector1.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[0]), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + + ASSERT_TRUE(accumulator.IsEmpty()); +} + +TEST_F(ConnectorTest, WriteToClosedPipe) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + // Close the other end of the pipe. + handle1_.reset(); + + // Not observed yet because we haven't spun the message loop yet. + EXPECT_FALSE(connector0.encountered_error()); + + // Write failures are not reported. + bool ok = connector0.Accept(&message); + EXPECT_TRUE(ok); + + // Still not observed. + EXPECT_FALSE(connector0.encountered_error()); + + // Spin the message loop, and then we should start observing the closed pipe. + base::RunLoop run_loop; + connector0.set_connection_error_handler(run_loop.QuitClosure()); + run_loop.Run(); + + EXPECT_TRUE(connector0.encountered_error()); +} + +TEST_F(ConnectorTest, MessageWithHandles) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message1; + AllocMessage(kText, &message1); + + MessagePipe pipe; + message1.mutable_handles()->push_back(pipe.handle0.release()); + + connector0.Accept(&message1); + + // The message should have been transferred, releasing the handles. + EXPECT_TRUE(message1.handles()->empty()); + + base::RunLoop run_loop; + MessageAccumulator accumulator(run_loop.QuitClosure()); + connector1.set_incoming_receiver(&accumulator); + + run_loop.Run(); + + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + ASSERT_EQ(1U, message_received.handles()->size()); + + // Now send a message to the transferred handle and confirm it's sent through + // to the orginal pipe. + // TODO(vtl): Do we need a better way of "downcasting" the handle types? + ScopedMessagePipeHandle smph; + smph.reset(MessagePipeHandle(message_received.handles()->front().value())); + message_received.mutable_handles()->front() = Handle(); + // |smph| now owns this handle. + + Connector connector_received(std::move(smph), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector_original(std::move(pipe.handle1), + Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + Message message2; + AllocMessage(kText, &message2); + + connector_received.Accept(&message2); + base::RunLoop run_loop2; + MessageAccumulator accumulator2(run_loop2.QuitClosure()); + connector_original.set_incoming_receiver(&accumulator2); + run_loop2.Run(); + + ASSERT_FALSE(accumulator2.IsEmpty()); + + accumulator2.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithError) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + // Close the other end of the pipe. + handle1_.reset(); + ASSERT_FALSE(connector0.WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE)); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithDeletion) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector* connector1 = + new Connector(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + + ConnectorDeletingMessageAccumulator accumulator(&connector1); + connector1->set_incoming_receiver(&accumulator); + + connector1->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + ASSERT_FALSE(connector1); + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); +} + +TEST_F(ConnectorTest, WaitForIncomingMessageWithReentrancy) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char* kText[] = {"hello", "world"}; + + for (size_t i = 0; i < arraysize(kText); ++i) { + Message message; + AllocMessage(kText[i], &message); + + connector0.Accept(&message); + } + + ReentrantMessageAccumulator accumulator(&connector1); + connector1.set_incoming_receiver(&accumulator); + + for (size_t i = 0; i < arraysize(kText); ++i) { + if (accumulator.IsEmpty()) { + base::RunLoop run_loop; + accumulator.set_closure(run_loop.QuitClosure()); + run_loop.Run(); + } + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText[i]), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + } + + ASSERT_EQ(2, accumulator.number_of_calls()); +} + +void ForwardErrorHandler(bool* called, const base::Closure& callback) { + *called = true; + callback.Run(); +} + +TEST_F(ConnectorTest, RaiseError) { + base::RunLoop run_loop, run_loop2; + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + bool error_handler_called0 = false; + connector0.set_connection_error_handler( + base::Bind(&ForwardErrorHandler, &error_handler_called0, + run_loop.QuitClosure())); + + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + bool error_handler_called1 = false; + connector1.set_connection_error_handler( + base::Bind(&ForwardErrorHandler, &error_handler_called1, + run_loop2.QuitClosure())); + + const char kText[] = "hello world"; + + Message message; + AllocMessage(kText, &message); + + connector0.Accept(&message); + connector0.RaiseError(); + + base::RunLoop run_loop3; + MessageAccumulator accumulator(run_loop3.QuitClosure()); + connector1.set_incoming_receiver(&accumulator); + + run_loop3.Run(); + + // Messages sent prior to RaiseError() still arrive at the other end. + ASSERT_FALSE(accumulator.IsEmpty()); + + Message message_received; + accumulator.Pop(&message_received); + + EXPECT_EQ( + std::string(kText), + std::string(reinterpret_cast<const char*>(message_received.payload()))); + + run_loop.Run(); + run_loop2.Run(); + + // Connection error handler is called at both sides. + EXPECT_TRUE(error_handler_called0); + EXPECT_TRUE(error_handler_called1); + + // The error flag is set at both sides. + EXPECT_TRUE(connector0.encountered_error()); + EXPECT_TRUE(connector1.encountered_error()); + + // The message pipe handle is valid at both sides. + EXPECT_TRUE(connector0.is_valid()); + EXPECT_TRUE(connector1.is_valid()); +} + +void PauseConnectorAndRunClosure(Connector* connector, + const base::Closure& closure) { + connector->PauseIncomingMethodCallProcessing(); + closure.Run(); +} + +TEST_F(ConnectorTest, PauseWithQueuedMessages) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + // Queue up two messages. + Message message; + AllocMessage(kText, &message); + connector0.Accept(&message); + AllocMessage(kText, &message); + connector0.Accept(&message); + + base::RunLoop run_loop; + // Configure the accumulator such that it pauses after the first message is + // received. + MessageAccumulator accumulator( + base::Bind(&PauseConnectorAndRunClosure, &connector1, + run_loop.QuitClosure())); + connector1.set_incoming_receiver(&accumulator); + + run_loop.Run(); + + // As we paused after the first message we should only have gotten one + // message. + ASSERT_EQ(1u, accumulator.size()); +} + +void AccumulateWithNestedLoop(MessageAccumulator* accumulator, + const base::Closure& closure) { + base::RunLoop nested_run_loop; + base::MessageLoop::ScopedNestableTaskAllower allow( + base::MessageLoop::current()); + accumulator->set_closure(nested_run_loop.QuitClosure()); + nested_run_loop.Run(); + closure.Run(); +} + +TEST_F(ConnectorTest, ProcessWhenNested) { + Connector connector0(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + Connector connector1(std::move(handle1_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()); + + const char kText[] = "hello world"; + + // Queue up two messages. + Message message; + AllocMessage(kText, &message); + connector0.Accept(&message); + AllocMessage(kText, &message); + connector0.Accept(&message); + + base::RunLoop run_loop; + MessageAccumulator accumulator; + // When the accumulator gets the first message it spins a nested message + // loop. The loop is quit when another message is received. + accumulator.set_closure(base::Bind(&AccumulateWithNestedLoop, &accumulator, + run_loop.QuitClosure())); + connector1.set_incoming_receiver(&accumulator); + + run_loop.Run(); + + ASSERT_EQ(2u, accumulator.size()); +} + +TEST_F(ConnectorTest, DestroyOnDifferentThreadAfterClose) { + std::unique_ptr<Connector> connector( + new Connector(std::move(handle0_), Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get())); + + connector->CloseMessagePipe(); + + base::Thread another_thread("ThreadForDestroyingConnector"); + another_thread.Start(); + + base::RunLoop run_loop; + another_thread.task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind( + [](std::unique_ptr<Connector> connector) { connector.reset(); }, + base::Passed(std::move(connector))), + run_loop.QuitClosure()); + + run_loop.Run(); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/constant_unittest.cc b/mojo/public/cpp/bindings/tests/constant_unittest.cc new file mode 100644 index 0000000000..caa6464cf4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/constant_unittest.cc @@ -0,0 +1,60 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <cmath> + +#include "base/strings/string_piece.h" +#include "mojo/public/interfaces/bindings/tests/test_constants.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +TEST(ConstantTest, GlobalConstants) { + // Compile-time constants. + static_assert(kBoolValue == true, ""); + static_assert(kInt8Value == -2, ""); + static_assert(kUint8Value == 128U, ""); + static_assert(kInt16Value == -233, ""); + static_assert(kUint16Value == 44204U, ""); + static_assert(kInt32Value == -44204, ""); + static_assert(kUint32Value == 4294967295U, ""); + static_assert(kInt64Value == -9223372036854775807, ""); + static_assert(kUint64Value == 9999999999999999999ULL, ""); + static_assert(kDoubleValue == 3.14159, ""); + static_assert(kFloatValue == 2.71828f, ""); + + EXPECT_EQ(base::StringPiece(kStringValue), "test string contents"); + EXPECT_TRUE(std::isnan(kDoubleNaN)); + EXPECT_TRUE(std::isinf(kDoubleInfinity)); + EXPECT_TRUE(std::isinf(kDoubleNegativeInfinity)); + EXPECT_NE(kDoubleInfinity, kDoubleNegativeInfinity); + EXPECT_TRUE(std::isnan(kFloatNaN)); + EXPECT_TRUE(std::isinf(kFloatInfinity)); + EXPECT_TRUE(std::isinf(kFloatNegativeInfinity)); + EXPECT_NE(kFloatInfinity, kFloatNegativeInfinity); +} + +TEST(ConstantTest, StructConstants) { + // Compile-time constants. + static_assert(StructWithConstants::kInt8Value == 5U, ""); + static_assert(StructWithConstants::kFloatValue == 765.432f, ""); + + EXPECT_EQ(base::StringPiece(StructWithConstants::kStringValue), + "struct test string contents"); +} + +TEST(ConstantTest, InterfaceConstants) { + // Compile-time constants. + static_assert(InterfaceWithConstants::kUint32Value == 20100722, ""); + static_assert(InterfaceWithConstants::kDoubleValue == 12.34567, ""); + + EXPECT_EQ(base::StringPiece(InterfaceWithConstants::kStringValue), + "interface test string contents"); + EXPECT_EQ(base::StringPiece(InterfaceWithConstants::Name_), + "mojo::test::InterfaceWithConstants"); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/container_test_util.cc b/mojo/public/cpp/bindings/tests/container_test_util.cc new file mode 100644 index 0000000000..a53d351e0c --- /dev/null +++ b/mojo/public/cpp/bindings/tests/container_test_util.cc @@ -0,0 +1,52 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> + +#include "mojo/public/cpp/bindings/tests/container_test_util.h" + +namespace mojo { + +size_t CopyableType::num_instances_ = 0; +size_t MoveOnlyType::num_instances_ = 0; + +CopyableType::CopyableType() : copied_(false), ptr_(this) { + num_instances_++; +} + +CopyableType::CopyableType(const CopyableType& other) + : copied_(true), ptr_(other.ptr()) { + num_instances_++; +} + +CopyableType& CopyableType::operator=(const CopyableType& other) { + copied_ = true; + ptr_ = other.ptr(); + return *this; +} + +CopyableType::~CopyableType() { + num_instances_--; +} + +MoveOnlyType::MoveOnlyType() : moved_(false), ptr_(this) { + num_instances_++; +} + +MoveOnlyType::MoveOnlyType(MoveOnlyType&& other) + : moved_(true), ptr_(other.ptr()) { + num_instances_++; +} + +MoveOnlyType& MoveOnlyType::operator=(MoveOnlyType&& other) { + moved_ = true; + ptr_ = other.ptr(); + return *this; +} + +MoveOnlyType::~MoveOnlyType() { + num_instances_--; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/container_test_util.h b/mojo/public/cpp/bindings/tests/container_test_util.h new file mode 100644 index 0000000000..f709c1561e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/container_test_util.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ + +#include <stddef.h> + +#include "base/macros.h" + +namespace mojo { + +class CopyableType { + public: + CopyableType(); + CopyableType(const CopyableType& other); + CopyableType& operator=(const CopyableType& other); + ~CopyableType(); + + bool copied() const { return copied_; } + static size_t num_instances() { return num_instances_; } + CopyableType* ptr() const { return ptr_; } + void ResetCopied() { copied_ = false; } + + private: + bool copied_; + static size_t num_instances_; + CopyableType* ptr_; +}; + +class MoveOnlyType { + public: + typedef MoveOnlyType Data_; + MoveOnlyType(); + MoveOnlyType(MoveOnlyType&& other); + MoveOnlyType& operator=(MoveOnlyType&& other); + ~MoveOnlyType(); + + bool moved() const { return moved_; } + static size_t num_instances() { return num_instances_; } + MoveOnlyType* ptr() const { return ptr_; } + void ResetMoved() { moved_ = false; } + + private: + bool moved_; + static size_t num_instances_; + MoveOnlyType* ptr_; + + DISALLOW_COPY_AND_ASSIGN(MoveOnlyType); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_CONTAINER_TEST_UTIL_H_ diff --git a/mojo/public/cpp/bindings/tests/data_view_unittest.cc b/mojo/public/cpp/bindings/tests/data_view_unittest.cc new file mode 100644 index 0000000000..0ebfda5d12 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/data_view_unittest.cc @@ -0,0 +1,303 @@ +// 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. + +#include <memory> +#include <string> +#include <unordered_map> +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/interfaces/bindings/tests/test_data_view.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace data_view { +namespace { + +class DataViewTest : public testing::Test { + private: + base::MessageLoop message_loop_; +}; + +struct DataViewHolder { + std::unique_ptr<TestStructDataView> data_view; + std::unique_ptr<mojo::internal::FixedBufferForTesting> buf; + mojo::internal::SerializationContext context; +}; + +std::unique_ptr<DataViewHolder> SerializeTestStruct(TestStructPtr input) { + std::unique_ptr<DataViewHolder> result(new DataViewHolder); + + size_t size = mojo::internal::PrepareToSerialize<TestStructDataView>( + input, &result->context); + + result->buf.reset(new mojo::internal::FixedBufferForTesting(size)); + internal::TestStruct_Data* data = nullptr; + mojo::internal::Serialize<TestStructDataView>(input, result->buf.get(), &data, + &result->context); + + result->data_view.reset(new TestStructDataView(data, &result->context)); + return result; +} + +class TestInterfaceImpl : public TestInterface { + public: + explicit TestInterfaceImpl(TestInterfaceRequest request) + : binding_(this, std::move(request)) {} + ~TestInterfaceImpl() override {} + + // TestInterface implementation: + void Echo(int32_t value, const EchoCallback& callback) override { + callback.Run(value); + } + + private: + Binding<TestInterface> binding_; +}; + +} // namespace + +TEST_F(DataViewTest, String) { + TestStructPtr obj(TestStruct::New()); + obj->f_string = "hello"; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + StringDataView string_data_view; + data_view.GetFStringDataView(&string_data_view); + + ASSERT_FALSE(string_data_view.is_null()); + EXPECT_EQ(std::string("hello"), + std::string(string_data_view.storage(), string_data_view.size())); +} + +TEST_F(DataViewTest, NestedStruct) { + TestStructPtr obj(TestStruct::New()); + obj->f_struct = NestedStruct::New(); + obj->f_struct->f_int32 = 42; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + NestedStructDataView struct_data_view; + data_view.GetFStructDataView(&struct_data_view); + + ASSERT_FALSE(struct_data_view.is_null()); + EXPECT_EQ(42, struct_data_view.f_int32()); +} + +TEST_F(DataViewTest, NativeStruct) { + TestStructPtr obj(TestStruct::New()); + obj->f_native_struct = NativeStruct::New(); + obj->f_native_struct->data = std::vector<uint8_t>({3, 2, 1}); + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + NativeStructDataView struct_data_view; + data_view.GetFNativeStructDataView(&struct_data_view); + + ASSERT_FALSE(struct_data_view.is_null()); + ASSERT_EQ(3u, struct_data_view.size()); + EXPECT_EQ(3, struct_data_view[0]); + EXPECT_EQ(2, struct_data_view[1]); + EXPECT_EQ(1, struct_data_view[2]); + EXPECT_EQ(3, *struct_data_view.data()); +} + +TEST_F(DataViewTest, BoolArray) { + TestStructPtr obj(TestStruct::New()); + obj->f_bool_array = {true, false}; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView<bool> array_data_view; + data_view.GetFBoolArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(2u, array_data_view.size()); + EXPECT_TRUE(array_data_view[0]); + EXPECT_FALSE(array_data_view[1]); +} + +TEST_F(DataViewTest, IntegerArray) { + TestStructPtr obj(TestStruct::New()); + obj->f_int32_array = {1024, 128}; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView<int32_t> array_data_view; + data_view.GetFInt32ArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(2u, array_data_view.size()); + EXPECT_EQ(1024, array_data_view[0]); + EXPECT_EQ(128, array_data_view[1]); + EXPECT_EQ(1024, *array_data_view.data()); +} + +TEST_F(DataViewTest, EnumArray) { + TestStructPtr obj(TestStruct::New()); + obj->f_enum_array = {TestEnum::VALUE_1, TestEnum::VALUE_0}; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView<TestEnum> array_data_view; + data_view.GetFEnumArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(2u, array_data_view.size()); + EXPECT_EQ(TestEnum::VALUE_1, array_data_view[0]); + EXPECT_EQ(TestEnum::VALUE_0, array_data_view[1]); + EXPECT_EQ(TestEnum::VALUE_0, *(array_data_view.data() + 1)); + + TestEnum output; + ASSERT_TRUE(array_data_view.Read(0, &output)); + EXPECT_EQ(TestEnum::VALUE_1, output); +} + +TEST_F(DataViewTest, InterfaceArray) { + TestInterfacePtr ptr; + TestInterfaceImpl impl(MakeRequest(&ptr)); + + TestStructPtr obj(TestStruct::New()); + obj->f_interface_array.push_back(std::move(ptr)); + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView<TestInterfacePtrDataView> array_data_view; + data_view.GetFInterfaceArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(1u, array_data_view.size()); + + TestInterfacePtr ptr2 = array_data_view.Take<TestInterfacePtr>(0); + ASSERT_TRUE(ptr2); + int32_t result = 0; + ASSERT_TRUE(ptr2->Echo(42, &result)); + EXPECT_EQ(42, result); +} + +TEST_F(DataViewTest, NestedArray) { + TestStructPtr obj(TestStruct::New()); + obj->f_nested_array = {{3, 4}, {2}}; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView<ArrayDataView<int32_t>> array_data_view; + data_view.GetFNestedArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(2u, array_data_view.size()); + + ArrayDataView<int32_t> nested_array_data_view; + array_data_view.GetDataView(0, &nested_array_data_view); + ASSERT_FALSE(nested_array_data_view.is_null()); + ASSERT_EQ(2u, nested_array_data_view.size()); + EXPECT_EQ(4, nested_array_data_view[1]); + + std::vector<int32_t> vec; + ASSERT_TRUE(array_data_view.Read(1, &vec)); + ASSERT_EQ(1u, vec.size()); + EXPECT_EQ(2, vec[0]); +} + +TEST_F(DataViewTest, StructArray) { + NestedStructPtr nested_struct(NestedStruct::New()); + nested_struct->f_int32 = 42; + + TestStructPtr obj(TestStruct::New()); + obj->f_struct_array.push_back(std::move(nested_struct)); + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView<NestedStructDataView> array_data_view; + data_view.GetFStructArrayDataView(&array_data_view); + + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(1u, array_data_view.size()); + + NestedStructDataView struct_data_view; + array_data_view.GetDataView(0, &struct_data_view); + ASSERT_FALSE(struct_data_view.is_null()); + EXPECT_EQ(42, struct_data_view.f_int32()); + + NestedStructPtr nested_struct2; + ASSERT_TRUE(array_data_view.Read(0, &nested_struct2)); + ASSERT_TRUE(nested_struct2); + EXPECT_EQ(42, nested_struct2->f_int32); +} + +TEST_F(DataViewTest, Map) { + TestStructPtr obj(TestStruct::New()); + obj->f_map["1"] = 1; + obj->f_map["2"] = 2; + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + MapDataView<StringDataView, int32_t> map_data_view; + data_view.GetFMapDataView(&map_data_view); + + ASSERT_FALSE(map_data_view.is_null()); + ASSERT_EQ(2u, map_data_view.size()); + + ASSERT_FALSE(map_data_view.keys().is_null()); + ASSERT_EQ(2u, map_data_view.keys().size()); + + ASSERT_FALSE(map_data_view.values().is_null()); + ASSERT_EQ(2u, map_data_view.values().size()); + + std::vector<std::string> keys; + ASSERT_TRUE(map_data_view.ReadKeys(&keys)); + std::vector<int32_t> values; + ASSERT_TRUE(map_data_view.ReadValues(&values)); + + std::unordered_map<std::string, int32_t> map; + for (size_t i = 0; i < 2; ++i) + map[keys[i]] = values[i]; + + EXPECT_EQ(1, map["1"]); + EXPECT_EQ(2, map["2"]); +} + +TEST_F(DataViewTest, UnionArray) { + TestUnionPtr union_ptr(TestUnion::New()); + union_ptr->set_f_int32(1024); + + TestStructPtr obj(TestStruct::New()); + obj->f_union_array.push_back(std::move(union_ptr)); + + auto data_view_holder = SerializeTestStruct(std::move(obj)); + auto& data_view = *data_view_holder->data_view; + + ArrayDataView<TestUnionDataView> array_data_view; + data_view.GetFUnionArrayDataView(&array_data_view); + ASSERT_FALSE(array_data_view.is_null()); + ASSERT_EQ(1u, array_data_view.size()); + + TestUnionDataView union_data_view; + array_data_view.GetDataView(0, &union_data_view); + ASSERT_FALSE(union_data_view.is_null()); + + TestUnionPtr union_ptr2; + ASSERT_TRUE(array_data_view.Read(0, &union_ptr2)); + ASSERT_TRUE(union_ptr2->is_f_int32()); + EXPECT_EQ(1024, union_ptr2->get_f_int32()); +} + +} // namespace data_view +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/e2e_perftest.cc b/mojo/public/cpp/bindings/tests/e2e_perftest.cc new file mode 100644 index 0000000000..bc69e0f727 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/e2e_perftest.cc @@ -0,0 +1,204 @@ +// 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. + +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/test/perf_time_logger.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/interfaces/bindings/tests/ping_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +class EchoServiceImpl : public test::EchoService { + public: + explicit EchoServiceImpl(const base::Closure& quit_closure); + ~EchoServiceImpl() override; + + // |EchoService| methods: + void Echo(const std::string& test_data, + const EchoCallback& callback) override; + + private: + const base::Closure quit_closure_; +}; + +EchoServiceImpl::EchoServiceImpl(const base::Closure& quit_closure) + : quit_closure_(quit_closure) {} + +EchoServiceImpl::~EchoServiceImpl() { + quit_closure_.Run(); +} + +void EchoServiceImpl::Echo(const std::string& test_data, + const EchoCallback& callback) { + callback.Run(test_data); +} + +class PingPongTest { + public: + explicit PingPongTest(test::EchoServicePtr service); + + void RunTest(int iterations, int batch_size, int message_size); + + private: + void DoPing(); + void OnPingDone(const std::string& reply); + + test::EchoServicePtr service_; + const base::Callback<void(const std::string&)> ping_done_callback_; + + int iterations_; + int batch_size_; + std::string message_; + + int current_iterations_; + int calls_outstanding_; + + base::Closure quit_closure_; +}; + +PingPongTest::PingPongTest(test::EchoServicePtr service) + : service_(std::move(service)), + ping_done_callback_( + base::Bind(&PingPongTest::OnPingDone, base::Unretained(this))) {} + +void PingPongTest::RunTest(int iterations, int batch_size, int message_size) { + iterations_ = iterations; + batch_size_ = batch_size; + message_ = std::string(message_size, 'a'); + current_iterations_ = 0; + calls_outstanding_ = 0; + + base::MessageLoop::current()->SetNestableTasksAllowed(true); + base::RunLoop run_loop; + quit_closure_ = run_loop.QuitClosure(); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&PingPongTest::DoPing, base::Unretained(this))); + run_loop.Run(); +} + +void PingPongTest::DoPing() { + DCHECK_EQ(0, calls_outstanding_); + current_iterations_++; + if (current_iterations_ > iterations_) { + quit_closure_.Run(); + return; + } + + calls_outstanding_ = batch_size_; + for (int i = 0; i < batch_size_; i++) { + service_->Echo(message_, ping_done_callback_); + } +} + +void PingPongTest::OnPingDone(const std::string& reply) { + DCHECK_GT(calls_outstanding_, 0); + calls_outstanding_--; + + if (!calls_outstanding_) + DoPing(); +} + +class MojoE2EPerftest : public edk::test::MojoTestBase { + public: + void RunTestOnTaskRunner(base::TaskRunner* runner, + MojoHandle client_mp, + const std::string& test_name) { + if (runner == base::ThreadTaskRunnerHandle::Get().get()) { + RunTests(client_mp, test_name); + } else { + base::RunLoop run_loop; + runner->PostTaskAndReply( + FROM_HERE, base::Bind(&MojoE2EPerftest::RunTests, + base::Unretained(this), client_mp, test_name), + run_loop.QuitClosure()); + run_loop.Run(); + } + } + + protected: + base::MessageLoop message_loop_; + + private: + void RunTests(MojoHandle client_mp, const std::string& test_name) { + const int kMessages = 10000; + const int kBatchSizes[] = {1, 10, 100}; + const int kMessageSizes[] = {8, 64, 512, 4096, 65536}; + + test::EchoServicePtr service; + service.Bind(InterfacePtrInfo<test::EchoService>( + ScopedMessagePipeHandle(MessagePipeHandle(client_mp)), + service.version())); + PingPongTest test(std::move(service)); + + for (int batch_size : kBatchSizes) { + for (int message_size : kMessageSizes) { + int num_messages = kMessages; + if (message_size == 65536) + num_messages /= 10; + std::string sub_test_name = base::StringPrintf( + "%s/%dx%d/%dbytes", test_name.c_str(), num_messages / batch_size, + batch_size, message_size); + base::PerfTimeLogger timer(sub_test_name.c_str()); + test.RunTest(num_messages / batch_size, batch_size, message_size); + } + } + } +}; + +void CreateAndRunService(InterfaceRequest<test::EchoService> request, + const base::Closure& cb) { + MakeStrongBinding(base::MakeUnique<EchoServiceImpl>(cb), std::move(request)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(PingService, MojoE2EPerftest, mp) { + MojoHandle service_mp; + EXPECT_EQ("hello", ReadMessageWithHandles(mp, &service_mp, 1)); + + InterfaceRequest<test::EchoService> request; + request.Bind(ScopedMessagePipeHandle(MessagePipeHandle(service_mp))); + base::RunLoop run_loop; + edk::GetIOTaskRunner()->PostTask( + FROM_HERE, + base::Bind(&CreateAndRunService, base::Passed(&request), + base::Bind(base::IgnoreResult(&base::TaskRunner::PostTask), + message_loop_.task_runner(), FROM_HERE, + run_loop.QuitClosure()))); + run_loop.Run(); +} + +TEST_F(MojoE2EPerftest, MultiProcessEchoMainThread) { + RUN_CHILD_ON_PIPE(PingService, mp) + MojoHandle client_mp, service_mp; + CreateMessagePipe(&client_mp, &service_mp); + WriteMessageWithHandles(mp, "hello", &service_mp, 1); + RunTestOnTaskRunner(message_loop_.task_runner().get(), client_mp, + "MultiProcessEchoMainThread"); + END_CHILD() +} + +TEST_F(MojoE2EPerftest, MultiProcessEchoIoThread) { + RUN_CHILD_ON_PIPE(PingService, mp) + MojoHandle client_mp, service_mp; + CreateMessagePipe(&client_mp, &service_mp); + WriteMessageWithHandles(mp, "hello", &service_mp, 1); + RunTestOnTaskRunner(edk::GetIOTaskRunner().get(), client_mp, + "MultiProcessEchoIoThread"); + END_CHILD() +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/equals_unittest.cc b/mojo/public/cpp/bindings/tests/equals_unittest.cc new file mode 100644 index 0000000000..6483baf8f0 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/equals_unittest.cc @@ -0,0 +1,122 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <utility> + +#include "base/message_loop/message_loop.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +namespace { + +RectPtr CreateRect() { + return Rect::New(1, 2, 3, 4); +} + +using EqualsTest = testing::Test; + +} // namespace + +TEST_F(EqualsTest, NullStruct) { + RectPtr r1; + RectPtr r2; + EXPECT_TRUE(r1.Equals(r2)); + EXPECT_TRUE(r2.Equals(r1)); + + r1 = CreateRect(); + EXPECT_FALSE(r1.Equals(r2)); + EXPECT_FALSE(r2.Equals(r1)); +} + +TEST_F(EqualsTest, Struct) { + RectPtr r1(CreateRect()); + RectPtr r2(r1.Clone()); + EXPECT_TRUE(r1.Equals(r2)); + r2->y = 1; + EXPECT_FALSE(r1.Equals(r2)); + r2.reset(); + EXPECT_FALSE(r1.Equals(r2)); +} + +TEST_F(EqualsTest, StructNested) { + RectPairPtr p1(RectPair::New(CreateRect(), CreateRect())); + RectPairPtr p2(p1.Clone()); + EXPECT_TRUE(p1.Equals(p2)); + p2->second->width = 0; + EXPECT_FALSE(p1.Equals(p2)); + p2->second.reset(); + EXPECT_FALSE(p1.Equals(p2)); +} + +TEST_F(EqualsTest, Array) { + std::vector<RectPtr> rects; + rects.push_back(CreateRect()); + NamedRegionPtr n1(NamedRegion::New(std::string("n1"), std::move(rects))); + NamedRegionPtr n2(n1.Clone()); + EXPECT_TRUE(n1.Equals(n2)); + + n2->rects = base::nullopt; + EXPECT_FALSE(n1.Equals(n2)); + n2->rects.emplace(); + EXPECT_FALSE(n1.Equals(n2)); + + n2->rects->push_back(CreateRect()); + n2->rects->push_back(CreateRect()); + EXPECT_FALSE(n1.Equals(n2)); + + n2->rects->resize(1); + (*n2->rects)[0]->width = 0; + EXPECT_FALSE(n1.Equals(n2)); + + (*n2->rects)[0] = CreateRect(); + EXPECT_TRUE(n1.Equals(n2)); +} + +TEST_F(EqualsTest, InterfacePtr) { + base::MessageLoop message_loop; + + SomeInterfacePtr inf1; + SomeInterfacePtr inf2; + + EXPECT_TRUE(inf1.Equals(inf1)); + EXPECT_TRUE(inf1.Equals(inf2)); + + auto inf1_request = MakeRequest(&inf1); + ALLOW_UNUSED_LOCAL(inf1_request); + + EXPECT_TRUE(inf1.Equals(inf1)); + EXPECT_FALSE(inf1.Equals(inf2)); + + auto inf2_request = MakeRequest(&inf2); + ALLOW_UNUSED_LOCAL(inf2_request); + + EXPECT_FALSE(inf1.Equals(inf2)); +} + +TEST_F(EqualsTest, InterfaceRequest) { + base::MessageLoop message_loop; + + InterfaceRequest<SomeInterface> req1; + InterfaceRequest<SomeInterface> req2; + + EXPECT_TRUE(req1.Equals(req1)); + EXPECT_TRUE(req1.Equals(req2)); + + SomeInterfacePtr inf1; + req1 = MakeRequest(&inf1); + + EXPECT_TRUE(req1.Equals(req1)); + EXPECT_FALSE(req1.Equals(req2)); + + SomeInterfacePtr inf2; + req2 = MakeRequest(&inf2); + + EXPECT_FALSE(req1.Equals(req2)); +} + +} // test +} // mojo diff --git a/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc new file mode 100644 index 0000000000..ef977af935 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/handle_passing_unittest.cc @@ -0,0 +1,356 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> +#include <utility> + +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/system/wait.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/sample_factory.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +const char kText1[] = "hello"; +const char kText2[] = "world"; + +void RecordString(std::string* storage, + const base::Closure& closure, + const std::string& str) { + *storage = str; + closure.Run(); +} + +base::Callback<void(const std::string&)> MakeStringRecorder( + std::string* storage, + const base::Closure& closure) { + return base::Bind(&RecordString, storage, closure); +} + +class ImportedInterfaceImpl : public imported::ImportedInterface { + public: + ImportedInterfaceImpl( + InterfaceRequest<imported::ImportedInterface> request, + const base::Closure& closure) + : binding_(this, std::move(request)), closure_(closure) {} + + void DoSomething() override { + do_something_count_++; + closure_.Run(); + } + + static int do_something_count() { return do_something_count_; } + + private: + static int do_something_count_; + Binding<ImportedInterface> binding_; + base::Closure closure_; +}; +int ImportedInterfaceImpl::do_something_count_ = 0; + +class SampleNamedObjectImpl : public sample::NamedObject { + public: + SampleNamedObjectImpl() {} + + void SetName(const std::string& name) override { name_ = name; } + + void GetName(const GetNameCallback& callback) override { + callback.Run(name_); + } + + private: + std::string name_; +}; + +class SampleFactoryImpl : public sample::Factory { + public: + explicit SampleFactoryImpl(InterfaceRequest<sample::Factory> request) + : binding_(this, std::move(request)) {} + + void DoStuff(sample::RequestPtr request, + ScopedMessagePipeHandle pipe, + const DoStuffCallback& callback) override { + std::string text1; + if (pipe.is_valid()) + EXPECT_TRUE(ReadTextMessage(pipe.get(), &text1)); + + std::string text2; + if (request->pipe.is_valid()) { + EXPECT_TRUE(ReadTextMessage(request->pipe.get(), &text2)); + + // Ensure that simply accessing request->pipe does not close it. + EXPECT_TRUE(request->pipe.is_valid()); + } + + ScopedMessagePipeHandle pipe0; + if (!text2.empty()) { + CreateMessagePipe(nullptr, &pipe0, &pipe1_); + EXPECT_TRUE(WriteTextMessage(pipe1_.get(), text2)); + } + + sample::ResponsePtr response(sample::Response::New(2, std::move(pipe0))); + callback.Run(std::move(response), text1); + + if (request->obj) + request->obj->DoSomething(); + } + + void DoStuff2(ScopedDataPipeConsumerHandle pipe, + const DoStuff2Callback& callback) override { + // Read the data from the pipe, writing the response (as a string) to + // DidStuff2(). + ASSERT_TRUE(pipe.is_valid()); + uint32_t data_size = 0; + + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + mojo::Wait(pipe.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); + ASSERT_TRUE(state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); + ASSERT_EQ(MOJO_RESULT_OK, + ReadDataRaw( + pipe.get(), nullptr, &data_size, MOJO_READ_DATA_FLAG_QUERY)); + ASSERT_NE(0, static_cast<int>(data_size)); + char data[64]; + ASSERT_LT(static_cast<int>(data_size), 64); + ASSERT_EQ( + MOJO_RESULT_OK, + ReadDataRaw( + pipe.get(), data, &data_size, MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + callback.Run(data); + } + + void CreateNamedObject( + InterfaceRequest<sample::NamedObject> object_request) override { + EXPECT_TRUE(object_request.is_pending()); + MakeStrongBinding(base::MakeUnique<SampleNamedObjectImpl>(), + std::move(object_request)); + } + + // These aren't called or implemented, but exist here to test that the + // methods are generated with the correct argument types for imported + // interfaces. + void RequestImportedInterface( + InterfaceRequest<imported::ImportedInterface> imported, + const RequestImportedInterfaceCallback& callback) override {} + void TakeImportedInterface( + imported::ImportedInterfacePtr imported, + const TakeImportedInterfaceCallback& callback) override {} + + private: + ScopedMessagePipeHandle pipe1_; + Binding<sample::Factory> binding_; +}; + +class HandlePassingTest : public testing::Test { + public: + HandlePassingTest() {} + + void TearDown() override { PumpMessages(); } + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +void DoStuff(bool* got_response, + std::string* got_text_reply, + const base::Closure& closure, + sample::ResponsePtr response, + const std::string& text_reply) { + *got_text_reply = text_reply; + + if (response->pipe.is_valid()) { + std::string text2; + EXPECT_TRUE(ReadTextMessage(response->pipe.get(), &text2)); + + // Ensure that simply accessing response.pipe does not close it. + EXPECT_TRUE(response->pipe.is_valid()); + + EXPECT_EQ(std::string(kText2), text2); + + // Do some more tests of handle passing: + ScopedMessagePipeHandle p = std::move(response->pipe); + EXPECT_TRUE(p.is_valid()); + EXPECT_FALSE(response->pipe.is_valid()); + } + + *got_response = true; + closure.Run(); +} + +void DoStuff2(bool* got_response, + std::string* got_text_reply, + const base::Closure& closure, + const std::string& text_reply) { + *got_response = true; + *got_text_reply = text_reply; + closure.Run(); +} + +TEST_F(HandlePassingTest, Basic) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + MessagePipe pipe0; + EXPECT_TRUE(WriteTextMessage(pipe0.handle1.get(), kText1)); + + MessagePipe pipe1; + EXPECT_TRUE(WriteTextMessage(pipe1.handle1.get(), kText2)); + + imported::ImportedInterfacePtr imported; + base::RunLoop run_loop; + ImportedInterfaceImpl imported_impl(MakeRequest(&imported), + run_loop.QuitClosure()); + + sample::RequestPtr request(sample::Request::New( + 1, std::move(pipe1.handle0), base::nullopt, std::move(imported))); + bool got_response = false; + std::string got_text_reply; + base::RunLoop run_loop2; + factory->DoStuff(std::move(request), std::move(pipe0.handle0), + base::Bind(&DoStuff, &got_response, &got_text_reply, + run_loop2.QuitClosure())); + + EXPECT_FALSE(got_response); + int count_before = ImportedInterfaceImpl::do_something_count(); + + run_loop.Run(); + run_loop2.Run(); + + EXPECT_TRUE(got_response); + EXPECT_EQ(kText1, got_text_reply); + EXPECT_EQ(1, ImportedInterfaceImpl::do_something_count() - count_before); +} + +TEST_F(HandlePassingTest, PassInvalid) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + sample::RequestPtr request( + sample::Request::New(1, ScopedMessagePipeHandle(), base::nullopt, + imported::ImportedInterfacePtr())); + bool got_response = false; + std::string got_text_reply; + base::RunLoop run_loop; + factory->DoStuff(std::move(request), ScopedMessagePipeHandle(), + base::Bind(&DoStuff, &got_response, &got_text_reply, + run_loop.QuitClosure())); + + EXPECT_FALSE(got_response); + + run_loop.Run(); + + EXPECT_TRUE(got_response); +} + +// Verifies DataPipeConsumer can be passed and read from. +TEST_F(HandlePassingTest, DataPipe) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + // Writes a string to a data pipe and passes the data pipe (consumer) to the + // factory. + ScopedDataPipeProducerHandle producer_handle; + ScopedDataPipeConsumerHandle consumer_handle; + MojoCreateDataPipeOptions options = {sizeof(MojoCreateDataPipeOptions), + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + 1, + 1024}; + ASSERT_EQ(MOJO_RESULT_OK, + CreateDataPipe(&options, &producer_handle, &consumer_handle)); + std::string expected_text_reply = "got it"; + // +1 for \0. + uint32_t data_size = static_cast<uint32_t>(expected_text_reply.size() + 1); + ASSERT_EQ(MOJO_RESULT_OK, + WriteDataRaw(producer_handle.get(), + expected_text_reply.c_str(), + &data_size, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + + bool got_response = false; + std::string got_text_reply; + base::RunLoop run_loop; + factory->DoStuff2(std::move(consumer_handle), + base::Bind(&DoStuff2, &got_response, &got_text_reply, + run_loop.QuitClosure())); + + EXPECT_FALSE(got_response); + + run_loop.Run(); + + EXPECT_TRUE(got_response); + EXPECT_EQ(expected_text_reply, got_text_reply); +} + +TEST_F(HandlePassingTest, PipesAreClosed) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + MessagePipe extra_pipe; + + MojoHandle handle0_value = extra_pipe.handle0.get().value(); + MojoHandle handle1_value = extra_pipe.handle1.get().value(); + + { + std::vector<ScopedMessagePipeHandle> pipes(2); + pipes[0] = std::move(extra_pipe.handle0); + pipes[1] = std::move(extra_pipe.handle1); + + sample::RequestPtr request(sample::Request::New()); + request->more_pipes = std::move(pipes); + + factory->DoStuff(std::move(request), ScopedMessagePipeHandle(), + sample::Factory::DoStuffCallback()); + } + + // We expect the pipes to have been closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle0_value)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handle1_value)); +} + +TEST_F(HandlePassingTest, CreateNamedObject) { + sample::FactoryPtr factory; + SampleFactoryImpl factory_impl(MakeRequest(&factory)); + + sample::NamedObjectPtr object1; + EXPECT_FALSE(object1); + + InterfaceRequest<sample::NamedObject> object1_request(&object1); + EXPECT_TRUE(object1_request.is_pending()); + factory->CreateNamedObject(std::move(object1_request)); + EXPECT_FALSE(object1_request.is_pending()); // We've passed the request. + + ASSERT_TRUE(object1); + object1->SetName("object1"); + + sample::NamedObjectPtr object2; + factory->CreateNamedObject(MakeRequest(&object2)); + object2->SetName("object2"); + + base::RunLoop run_loop, run_loop2; + std::string name1; + object1->GetName(MakeStringRecorder(&name1, run_loop.QuitClosure())); + + std::string name2; + object2->GetName(MakeStringRecorder(&name2, run_loop2.QuitClosure())); + + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(std::string("object1"), name1); + EXPECT_EQ(std::string("object2"), name2); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/hash_unittest.cc b/mojo/public/cpp/bindings/tests/hash_unittest.cc new file mode 100644 index 0000000000..9ce1f5bc7b --- /dev/null +++ b/mojo/public/cpp/bindings/tests/hash_unittest.cc @@ -0,0 +1,35 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/hash_util.h" + +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using HashTest = testing::Test; + +TEST_F(HashTest, NestedStruct) { + // Just check that this template instantiation compiles. + ASSERT_EQ( + ::mojo::internal::Hash(::mojo::internal::kHashSeed, + SimpleNestedStruct::New(ContainsOther::New(1))), + ::mojo::internal::Hash(::mojo::internal::kHashSeed, + SimpleNestedStruct::New(ContainsOther::New(1)))); +} + +TEST_F(HashTest, UnmappedNativeStruct) { + // Just check that this template instantiation compiles. + ASSERT_EQ(::mojo::internal::Hash(::mojo::internal::kHashSeed, + UnmappedNativeStruct::New()), + ::mojo::internal::Hash(::mojo::internal::kHashSeed, + UnmappedNativeStruct::New())); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc new file mode 100644 index 0000000000..431a844250 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc @@ -0,0 +1,937 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/threading/thread.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h" +#include "mojo/public/interfaces/bindings/tests/math_calculator.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "mojo/public/interfaces/bindings/tests/scoping.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +typedef base::Callback<void(double)> CalcCallback; + +class MathCalculatorImpl : public math::Calculator { + public: + explicit MathCalculatorImpl(InterfaceRequest<math::Calculator> request) + : total_(0.0), binding_(this, std::move(request)) {} + ~MathCalculatorImpl() override {} + + void Clear(const CalcCallback& callback) override { + total_ = 0.0; + callback.Run(total_); + } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + Binding<math::Calculator>* binding() { return &binding_; } + + private: + double total_; + Binding<math::Calculator> binding_; +}; + +class MathCalculatorUI { + public: + explicit MathCalculatorUI(math::CalculatorPtr calculator) + : calculator_(std::move(calculator)), + output_(0.0) {} + + bool encountered_error() const { return calculator_.encountered_error(); } + void set_connection_error_handler(const base::Closure& closure) { + calculator_.set_connection_error_handler(closure); + } + + void Add(double value, const base::Closure& closure) { + calculator_->Add( + value, + base::Bind(&MathCalculatorUI::Output, base::Unretained(this), closure)); + } + + void Multiply(double value, const base::Closure& closure) { + calculator_->Multiply( + value, + base::Bind(&MathCalculatorUI::Output, base::Unretained(this), closure)); + } + + double GetOutput() const { return output_; } + + math::CalculatorPtr& GetInterfacePtr() { return calculator_; } + + private: + void Output(const base::Closure& closure, double output) { + output_ = output; + if (!closure.is_null()) + closure.Run(); + } + + math::CalculatorPtr calculator_; + double output_; + base::Closure closure_; +}; + +class SelfDestructingMathCalculatorUI { + public: + explicit SelfDestructingMathCalculatorUI(math::CalculatorPtr calculator) + : calculator_(std::move(calculator)), nesting_level_(0) { + ++num_instances_; + } + + void BeginTest(bool nested, const base::Closure& closure) { + nesting_level_ = nested ? 2 : 1; + calculator_->Add( + 1.0, + base::Bind(&SelfDestructingMathCalculatorUI::Output, + base::Unretained(this), closure)); + } + + static int num_instances() { return num_instances_; } + + void Output(const base::Closure& closure, double value) { + if (--nesting_level_ > 0) { + // Add some more and wait for re-entrant call to Output! + calculator_->Add( + 1.0, + base::Bind(&SelfDestructingMathCalculatorUI::Output, + base::Unretained(this), closure)); + } else { + closure.Run(); + delete this; + } + } + + private: + ~SelfDestructingMathCalculatorUI() { --num_instances_; } + + math::CalculatorPtr calculator_; + int nesting_level_; + static int num_instances_; +}; + +// static +int SelfDestructingMathCalculatorUI::num_instances_ = 0; + +class ReentrantServiceImpl : public sample::Service { + public: + ~ReentrantServiceImpl() override {} + + explicit ReentrantServiceImpl(InterfaceRequest<sample::Service> request) + : call_depth_(0), + max_call_depth_(0), + binding_(this, std::move(request)) {} + + int max_call_depth() { return max_call_depth_; } + + void Frobinate(sample::FooPtr foo, + sample::Service::BazOptions baz, + sample::PortPtr port, + const sample::Service::FrobinateCallback& callback) override { + max_call_depth_ = std::max(++call_depth_, max_call_depth_); + if (call_depth_ == 1) { + EXPECT_TRUE(binding_.WaitForIncomingMethodCall()); + } + call_depth_--; + callback.Run(5); + } + + void GetPort(mojo::InterfaceRequest<sample::Port> port) override {} + + private: + int call_depth_; + int max_call_depth_; + Binding<sample::Service> binding_; +}; + +class IntegerAccessorImpl : public sample::IntegerAccessor { + public: + IntegerAccessorImpl() : integer_(0) {} + ~IntegerAccessorImpl() override {} + + int64_t integer() const { return integer_; } + + void set_closure(const base::Closure& closure) { closure_ = closure; } + + private: + // sample::IntegerAccessor implementation. + void GetInteger(const GetIntegerCallback& callback) override { + callback.Run(integer_, sample::Enum::VALUE); + } + void SetInteger(int64_t data, sample::Enum type) override { + integer_ = data; + if (!closure_.is_null()) { + closure_.Run(); + closure_.Reset(); + } + } + + int64_t integer_; + base::Closure closure_; +}; + +class InterfacePtrTest : public testing::Test { + public: + InterfacePtrTest() {} + ~InterfacePtrTest() override { base::RunLoop().RunUntilIdle(); } + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +void SetFlagAndRunClosure(bool* flag, const base::Closure& closure) { + *flag = true; + closure.Run(); +} + +void IgnoreValueAndRunClosure(const base::Closure& closure, int32_t value) { + closure.Run(); +} + +void ExpectValueAndRunClosure(uint32_t expected_value, + const base::Closure& closure, + uint32_t value) { + EXPECT_EQ(expected_value, value); + closure.Run(); +} + +TEST_F(InterfacePtrTest, IsBound) { + math::CalculatorPtr calc; + EXPECT_FALSE(calc.is_bound()); + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + EXPECT_TRUE(calc.is_bound()); +} + +TEST_F(InterfacePtrTest, EndToEnd) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + + // Suppose this is instantiated in a process that has pipe1_. + MathCalculatorUI calculator_ui(std::move(calc)); + + base::RunLoop run_loop, run_loop2; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + calculator_ui.Multiply(5.0, run_loop2.QuitClosure()); + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); +} + +TEST_F(InterfacePtrTest, EndToEnd_Synchronous) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + + // Suppose this is instantiated in a process that has pipe1_. + MathCalculatorUI calculator_ui(std::move(calc)); + + EXPECT_EQ(0.0, calculator_ui.GetOutput()); + + base::RunLoop run_loop; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + EXPECT_EQ(0.0, calculator_ui.GetOutput()); + calc_impl.binding()->WaitForIncomingMethodCall(); + run_loop.Run(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + + base::RunLoop run_loop2; + calculator_ui.Multiply(5.0, run_loop2.QuitClosure()); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + calc_impl.binding()->WaitForIncomingMethodCall(); + run_loop2.Run(); + EXPECT_EQ(10.0, calculator_ui.GetOutput()); +} + +TEST_F(InterfacePtrTest, Movable) { + math::CalculatorPtr a; + math::CalculatorPtr b; + MathCalculatorImpl calc_impl(MakeRequest(&b)); + + EXPECT_TRUE(!a); + EXPECT_FALSE(!b); + + a = std::move(b); + + EXPECT_FALSE(!a); + EXPECT_TRUE(!b); +} + +TEST_F(InterfacePtrTest, Resettable) { + math::CalculatorPtr a; + + EXPECT_TRUE(!a); + + MessagePipe pipe; + + // Save this so we can test it later. + Handle handle = pipe.handle0.get(); + + a = MakeProxy( + InterfacePtrInfo<math::Calculator>(std::move(pipe.handle0), 0u)); + + EXPECT_FALSE(!a); + + a.reset(); + + EXPECT_TRUE(!a); + EXPECT_FALSE(a.internal_state()->is_bound()); + + // Test that handle was closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, CloseRaw(handle)); +} + +TEST_F(InterfacePtrTest, BindInvalidHandle) { + math::CalculatorPtr ptr; + EXPECT_FALSE(ptr.get()); + EXPECT_FALSE(ptr); + + ptr.Bind(InterfacePtrInfo<math::Calculator>()); + EXPECT_FALSE(ptr.get()); + EXPECT_FALSE(ptr); +} + +TEST_F(InterfacePtrTest, EncounteredError) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(MakeRequest(&proxy)); + + MathCalculatorUI calculator_ui(std::move(proxy)); + + base::RunLoop run_loop; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + run_loop.Run(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + calculator_ui.Multiply(5.0, base::Closure()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + // Close the server. + calc_impl.binding()->Close(); + + // The state change isn't picked up locally yet. + base::RunLoop run_loop2; + calculator_ui.set_connection_error_handler(run_loop2.QuitClosure()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + run_loop2.Run(); + + // OK, now we see the error. + EXPECT_TRUE(calculator_ui.encountered_error()); +} + +TEST_F(InterfacePtrTest, EncounteredErrorCallback) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(MakeRequest(&proxy)); + + bool encountered_error = false; + base::RunLoop run_loop; + proxy.set_connection_error_handler( + base::Bind(&SetFlagAndRunClosure, &encountered_error, + run_loop.QuitClosure())); + + MathCalculatorUI calculator_ui(std::move(proxy)); + + base::RunLoop run_loop2; + calculator_ui.Add(2.0, run_loop2.QuitClosure()); + run_loop2.Run(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + calculator_ui.Multiply(5.0, base::Closure()); + EXPECT_FALSE(calculator_ui.encountered_error()); + + // Close the server. + calc_impl.binding()->Close(); + + // The state change isn't picked up locally yet. + EXPECT_FALSE(calculator_ui.encountered_error()); + + run_loop.Run(); + + // OK, now we see the error. + EXPECT_TRUE(calculator_ui.encountered_error()); + + // We should have also been able to observe the error through the error + // handler. + EXPECT_TRUE(encountered_error); +} + +TEST_F(InterfacePtrTest, DestroyInterfacePtrOnMethodResponse) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(MakeRequest(&proxy)); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); + + SelfDestructingMathCalculatorUI* impl = + new SelfDestructingMathCalculatorUI(std::move(proxy)); + base::RunLoop run_loop; + impl->BeginTest(false, run_loop.QuitClosure()); + run_loop.Run(); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); +} + +TEST_F(InterfacePtrTest, NestedDestroyInterfacePtrOnMethodResponse) { + math::CalculatorPtr proxy; + MathCalculatorImpl calc_impl(MakeRequest(&proxy)); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); + + SelfDestructingMathCalculatorUI* impl = + new SelfDestructingMathCalculatorUI(std::move(proxy)); + base::RunLoop run_loop; + impl->BeginTest(true, run_loop.QuitClosure()); + run_loop.Run(); + + EXPECT_EQ(0, SelfDestructingMathCalculatorUI::num_instances()); +} + +TEST_F(InterfacePtrTest, ReentrantWaitForIncomingMethodCall) { + sample::ServicePtr proxy; + ReentrantServiceImpl impl(MakeRequest(&proxy)); + + base::RunLoop run_loop, run_loop2; + proxy->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + base::Bind(&IgnoreValueAndRunClosure, + run_loop.QuitClosure())); + proxy->Frobinate(nullptr, sample::Service::BazOptions::REGULAR, nullptr, + base::Bind(&IgnoreValueAndRunClosure, + run_loop2.QuitClosure())); + + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(2, impl.max_call_depth()); +} + +TEST_F(InterfacePtrTest, QueryVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding<sample::IntegerAccessor> binding(&impl, MakeRequest(&ptr)); + + EXPECT_EQ(0u, ptr.version()); + + base::RunLoop run_loop; + ptr.QueryVersion(base::Bind(&ExpectValueAndRunClosure, 3u, + run_loop.QuitClosure())); + run_loop.Run(); + + EXPECT_EQ(3u, ptr.version()); +} + +TEST_F(InterfacePtrTest, RequireVersion) { + IntegerAccessorImpl impl; + sample::IntegerAccessorPtr ptr; + Binding<sample::IntegerAccessor> binding(&impl, MakeRequest(&ptr)); + + EXPECT_EQ(0u, ptr.version()); + + ptr.RequireVersion(1u); + EXPECT_EQ(1u, ptr.version()); + base::RunLoop run_loop; + impl.set_closure(run_loop.QuitClosure()); + ptr->SetInteger(123, sample::Enum::VALUE); + run_loop.Run(); + EXPECT_FALSE(ptr.encountered_error()); + EXPECT_EQ(123, impl.integer()); + + ptr.RequireVersion(3u); + EXPECT_EQ(3u, ptr.version()); + base::RunLoop run_loop2; + impl.set_closure(run_loop2.QuitClosure()); + ptr->SetInteger(456, sample::Enum::VALUE); + run_loop2.Run(); + EXPECT_FALSE(ptr.encountered_error()); + EXPECT_EQ(456, impl.integer()); + + // Require a version that is not supported by the impl side. + ptr.RequireVersion(4u); + // This value is set to the input of RequireVersion() synchronously. + EXPECT_EQ(4u, ptr.version()); + base::RunLoop run_loop3; + ptr.set_connection_error_handler(run_loop3.QuitClosure()); + ptr->SetInteger(789, sample::Enum::VALUE); + run_loop3.Run(); + EXPECT_TRUE(ptr.encountered_error()); + // The call to SetInteger() after RequireVersion(4u) is ignored. + EXPECT_EQ(456, impl.integer()); +} + +class StrongMathCalculatorImpl : public math::Calculator { + public: + StrongMathCalculatorImpl(bool* destroyed) : destroyed_(destroyed) {} + ~StrongMathCalculatorImpl() override { *destroyed_ = true; } + + // math::Calculator implementation. + void Clear(const CalcCallback& callback) override { callback.Run(total_); } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + private: + double total_ = 0.0; + bool* destroyed_; +}; + +TEST(StrongConnectorTest, Math) { + base::MessageLoop loop; + + bool error_received = false; + bool destroyed = false; + math::CalculatorPtr calc; + base::RunLoop run_loop; + + auto binding = + MakeStrongBinding(base::MakeUnique<StrongMathCalculatorImpl>(&destroyed), + MakeRequest(&calc)); + binding->set_connection_error_handler(base::Bind( + &SetFlagAndRunClosure, &error_received, run_loop.QuitClosure())); + + { + // Suppose this is instantiated in a process that has the other end of the + // message pipe. + MathCalculatorUI calculator_ui(std::move(calc)); + + base::RunLoop run_loop, run_loop2; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + calculator_ui.Multiply(5.0, run_loop2.QuitClosure()); + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); + EXPECT_FALSE(error_received); + EXPECT_FALSE(destroyed); + } + // Destroying calculator_ui should close the pipe and generate an error on the + // other + // end which will destroy the instance since it is strongly bound. + + run_loop.Run(); + EXPECT_TRUE(error_received); + EXPECT_TRUE(destroyed); +} + +class WeakMathCalculatorImpl : public math::Calculator { + public: + WeakMathCalculatorImpl(ScopedMessagePipeHandle handle, + bool* error_received, + bool* destroyed, + const base::Closure& closure) + : error_received_(error_received), + destroyed_(destroyed), + closure_(closure), + binding_(this, std::move(handle)) { + binding_.set_connection_error_handler( + base::Bind(&SetFlagAndRunClosure, error_received_, closure_)); + } + ~WeakMathCalculatorImpl() override { *destroyed_ = true; } + + void Clear(const CalcCallback& callback) override { callback.Run(total_); } + + void Add(double value, const CalcCallback& callback) override { + total_ += value; + callback.Run(total_); + } + + void Multiply(double value, const CalcCallback& callback) override { + total_ *= value; + callback.Run(total_); + } + + private: + double total_ = 0.0; + bool* error_received_; + bool* destroyed_; + base::Closure closure_; + + Binding<math::Calculator> binding_; +}; + +TEST(WeakConnectorTest, Math) { + base::MessageLoop loop; + + bool error_received = false; + bool destroyed = false; + MessagePipe pipe; + base::RunLoop run_loop; + WeakMathCalculatorImpl impl(std::move(pipe.handle0), &error_received, + &destroyed, run_loop.QuitClosure()); + + math::CalculatorPtr calc; + calc.Bind(InterfacePtrInfo<math::Calculator>(std::move(pipe.handle1), 0u)); + + { + // Suppose this is instantiated in a process that has the other end of the + // message pipe. + MathCalculatorUI calculator_ui(std::move(calc)); + + base::RunLoop run_loop, run_loop2; + calculator_ui.Add(2.0, run_loop.QuitClosure()); + calculator_ui.Multiply(5.0, run_loop2.QuitClosure()); + run_loop.Run(); + run_loop2.Run(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); + EXPECT_FALSE(error_received); + EXPECT_FALSE(destroyed); + // Destroying calculator_ui should close the pipe and generate an error on + // the other + // end which will destroy the instance since it is strongly bound. + } + + run_loop.Run(); + EXPECT_TRUE(error_received); + EXPECT_FALSE(destroyed); +} + +class CImpl : public C { + public: + CImpl(bool* d_called, const base::Closure& closure) + : d_called_(d_called), closure_(closure) {} + ~CImpl() override {} + + private: + void D() override { + *d_called_ = true; + closure_.Run(); + } + + bool* d_called_; + base::Closure closure_; +}; + +class BImpl : public B { + public: + BImpl(bool* d_called, const base::Closure& closure) + : d_called_(d_called), closure_(closure) {} + ~BImpl() override {} + + private: + void GetC(InterfaceRequest<C> c) override { + MakeStrongBinding(base::MakeUnique<CImpl>(d_called_, closure_), + std::move(c)); + } + + bool* d_called_; + base::Closure closure_; +}; + +class AImpl : public A { + public: + AImpl(InterfaceRequest<A> request, const base::Closure& closure) + : d_called_(false), binding_(this, std::move(request)), + closure_(closure) {} + ~AImpl() override {} + + bool d_called() const { return d_called_; } + + private: + void GetB(InterfaceRequest<B> b) override { + MakeStrongBinding(base::MakeUnique<BImpl>(&d_called_, closure_), + std::move(b)); + } + + bool d_called_; + Binding<A> binding_; + base::Closure closure_; +}; + +TEST_F(InterfacePtrTest, Scoping) { + APtr a; + base::RunLoop run_loop; + AImpl a_impl(MakeRequest(&a), run_loop.QuitClosure()); + + EXPECT_FALSE(a_impl.d_called()); + + { + BPtr b; + a->GetB(MakeRequest(&b)); + CPtr c; + b->GetC(MakeRequest(&c)); + c->D(); + } + + // While B & C have fallen out of scope, the pipes will remain until they are + // flushed. + EXPECT_FALSE(a_impl.d_called()); + run_loop.Run(); + EXPECT_TRUE(a_impl.d_called()); +} + +class PingTestImpl : public sample::PingTest { + public: + explicit PingTestImpl(InterfaceRequest<sample::PingTest> request) + : binding_(this, std::move(request)) {} + ~PingTestImpl() override {} + + private: + // sample::PingTest: + void Ping(const PingCallback& callback) override { callback.Run(); } + + Binding<sample::PingTest> binding_; +}; + +// Tests that FuseProxy does what it's supposed to do. +TEST_F(InterfacePtrTest, Fusion) { + sample::PingTestPtr proxy; + PingTestImpl impl(MakeRequest(&proxy)); + + // Create another PingTest pipe. + sample::PingTestPtr ptr; + sample::PingTestRequest request(&ptr); + + // Fuse the new pipe to the one hanging off |impl|. + EXPECT_TRUE(FuseInterface(std::move(request), proxy.PassInterface())); + + // Ping! + bool called = false; + base::RunLoop loop; + ptr->Ping(base::Bind(&SetFlagAndRunClosure, &called, loop.QuitClosure())); + loop.Run(); + EXPECT_TRUE(called); +} + +void Fail() { + FAIL() << "Unexpected connection error"; +} + +TEST_F(InterfacePtrTest, FlushForTesting) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + calc.set_connection_error_handler(base::Bind(&Fail)); + + MathCalculatorUI calculator_ui(std::move(calc)); + + calculator_ui.Add(2.0, base::Bind(&base::DoNothing)); + calculator_ui.GetInterfacePtr().FlushForTesting(); + EXPECT_EQ(2.0, calculator_ui.GetOutput()); + + calculator_ui.Multiply(5.0, base::Bind(&base::DoNothing)); + calculator_ui.GetInterfacePtr().FlushForTesting(); + + EXPECT_EQ(10.0, calculator_ui.GetOutput()); +} + +void SetBool(bool* value) { + *value = true; +} + +TEST_F(InterfacePtrTest, FlushForTestingWithClosedPeer) { + math::CalculatorPtr calc; + MakeRequest(&calc); + bool called = false; + calc.set_connection_error_handler(base::Bind(&SetBool, &called)); + calc.FlushForTesting(); + EXPECT_TRUE(called); + calc.FlushForTesting(); +} + +TEST_F(InterfacePtrTest, ConnectionErrorWithReason) { + math::CalculatorPtr calc; + MathCalculatorImpl calc_impl(MakeRequest(&calc)); + + base::RunLoop run_loop; + calc.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(42u, custom_reason); + EXPECT_EQ("hey", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + calc_impl.binding()->CloseWithReason(42u, "hey"); + + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, InterfaceRequestResetWithReason) { + math::CalculatorPtr calc; + auto request = MakeRequest(&calc); + + base::RunLoop run_loop; + calc.set_connection_error_with_reason_handler(base::Bind( + [](const base::Closure& quit_closure, uint32_t custom_reason, + const std::string& description) { + EXPECT_EQ(88u, custom_reason); + EXPECT_EQ("greetings", description); + quit_closure.Run(); + }, + run_loop.QuitClosure())); + + request.ResetWithReason(88u, "greetings"); + + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, CallbackIsPassedInterfacePtr) { + sample::PingTestPtr ptr; + sample::PingTestRequest request(&ptr); + + base::RunLoop run_loop; + + // Make a call with the proxy's lifetime bound to the response callback. + sample::PingTest* raw_proxy = ptr.get(); + ptr.set_connection_error_handler(run_loop.QuitClosure()); + raw_proxy->Ping( + base::Bind([](sample::PingTestPtr ptr) {}, base::Passed(&ptr))); + + // Trigger an error on |ptr|. This will ultimately lead to the proxy's + // response callbacks being destroyed, which will in turn lead to the proxy + // being destroyed. This should not crash. + request.PassMessagePipe(); + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, ConnectionErrorHandlerOwnsInterfacePtr) { + sample::PingTestPtr* ptr = new sample::PingTestPtr; + sample::PingTestRequest request(ptr); + + base::RunLoop run_loop; + + // Make a call with |ptr|'s lifetime bound to the connection error handler + // callback. + ptr->set_connection_error_handler(base::Bind( + [](const base::Closure& quit, sample::PingTestPtr* ptr) { + ptr->reset(); + quit.Run(); + }, + run_loop.QuitClosure(), base::Owned(ptr))); + + // Trigger an error on |ptr|. In the error handler |ptr| is reset. This + // shouldn't immediately destroy the callback (and |ptr| that it owns), before + // the callback is completed. + request.PassMessagePipe(); + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, ThreadSafeInterfacePointer) { + math::CalculatorPtr ptr; + MathCalculatorImpl calc_impl(MakeRequest(&ptr)); + scoped_refptr<math::ThreadSafeCalculatorPtr> thread_safe_ptr = + math::ThreadSafeCalculatorPtr::Create(std::move(ptr)); + + base::RunLoop run_loop; + + // Create and start the thread from where we'll call the interface pointer. + base::Thread other_thread("service test thread"); + other_thread.Start(); + + auto run_method = base::Bind( + [](const scoped_refptr<base::TaskRunner>& main_task_runner, + const base::Closure& quit_closure, + const scoped_refptr<math::ThreadSafeCalculatorPtr>& thread_safe_ptr) { + auto calc_callback = base::Bind( + [](const scoped_refptr<base::TaskRunner>& main_task_runner, + const base::Closure& quit_closure, + base::PlatformThreadId thread_id, + double result) { + EXPECT_EQ(123, result); + // Validate the callback is invoked on the calling thread. + EXPECT_EQ(thread_id, base::PlatformThread::CurrentId()); + // Notify the run_loop to quit. + main_task_runner->PostTask(FROM_HERE, quit_closure); + }); + (*thread_safe_ptr)->Add( + 123, base::Bind(calc_callback, main_task_runner, quit_closure, + base::PlatformThread::CurrentId())); + }, + base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(), + thread_safe_ptr); + other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method); + + // Block until the method callback is called on the background thread. + run_loop.Run(); +} + +TEST_F(InterfacePtrTest, ThreadSafeInterfacePointerWithTaskRunner) { + // Create and start the thread from where we'll bind the interface pointer. + base::Thread other_thread("service test thread"); + other_thread.Start(); + const scoped_refptr<base::SingleThreadTaskRunner>& other_thread_task_runner = + other_thread.message_loop()->task_runner(); + + math::CalculatorPtr ptr; + math::CalculatorRequest request(&ptr); + + // Create a ThreadSafeInterfacePtr that we'll bind from a different thread. + scoped_refptr<math::ThreadSafeCalculatorPtr> thread_safe_ptr = + math::ThreadSafeCalculatorPtr::Create(ptr.PassInterface(), + other_thread_task_runner); + ASSERT_TRUE(thread_safe_ptr); + + MathCalculatorImpl* math_calc_impl = nullptr; + { + base::RunLoop run_loop; + auto run_method = base::Bind( + [](const scoped_refptr<base::TaskRunner>& main_task_runner, + const base::Closure& quit_closure, + const scoped_refptr<math::ThreadSafeCalculatorPtr>& thread_safe_ptr, + math::CalculatorRequest request, + MathCalculatorImpl** math_calc_impl) { + math::CalculatorPtr ptr; + // In real life, the implementation would have a legitimate owner. + *math_calc_impl = new MathCalculatorImpl(std::move(request)); + main_task_runner->PostTask(FROM_HERE, quit_closure); + }, + base::SequencedTaskRunnerHandle::Get(), run_loop.QuitClosure(), + thread_safe_ptr, base::Passed(&request), &math_calc_impl); + other_thread.message_loop()->task_runner()->PostTask(FROM_HERE, run_method); + run_loop.Run(); + } + + { + // The interface ptr is bound, we can call methods on it. + auto calc_callback = + base::Bind([](const base::Closure& quit_closure, double result) { + EXPECT_EQ(123, result); + quit_closure.Run(); + }); + base::RunLoop run_loop; + (*thread_safe_ptr) + ->Add(123, base::Bind(calc_callback, run_loop.QuitClosure())); + // Block until the method callback is called. + run_loop.Run(); + } + + other_thread_task_runner->DeleteSoon(FROM_HERE, math_calc_impl); + + // Reset the pointer now so the InterfacePtr associated resources can be + // deleted before the background thread's message loop is invalidated. + thread_safe_ptr = nullptr; +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/map_unittest.cc b/mojo/public/cpp/bindings/tests/map_unittest.cc new file mode 100644 index 0000000000..8d630a5862 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/map_unittest.cc @@ -0,0 +1,46 @@ +// 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. + +#include <stddef.h> +#include <stdint.h> +#include <unordered_map> +#include <utility> + +#include "mojo/public/cpp/bindings/tests/rect_chromium.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +TEST(MapTest, StructKey) { + std::unordered_map<RectPtr, int32_t> map; + map.insert(std::make_pair(Rect::New(1, 2, 3, 4), 123)); + + RectPtr key = Rect::New(1, 2, 3, 4); + ASSERT_NE(map.end(), map.find(key)); + ASSERT_EQ(123, map.find(key)->second); + + map.erase(key); + ASSERT_EQ(0u, map.size()); +} + +TEST(MapTest, TypemappedStructKey) { + std::unordered_map<ContainsHashablePtr, int32_t> map; + map.insert( + std::make_pair(ContainsHashable::New(RectChromium(1, 2, 3, 4)), 123)); + + ContainsHashablePtr key = ContainsHashable::New(RectChromium(1, 2, 3, 4)); + ASSERT_NE(map.end(), map.find(key)); + ASSERT_EQ(123, map.find(key)->second); + + map.erase(key); + ASSERT_EQ(0u, map.size()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/message_queue.cc b/mojo/public/cpp/bindings/tests/message_queue.cc new file mode 100644 index 0000000000..32ed76328c --- /dev/null +++ b/mojo/public/cpp/bindings/tests/message_queue.cc @@ -0,0 +1,39 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/message_queue.h" + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace test { + +MessageQueue::MessageQueue() { +} + +MessageQueue::~MessageQueue() { +} + +bool MessageQueue::IsEmpty() const { + return queue_.empty(); +} + +void MessageQueue::Push(Message* message) { + queue_.emplace(std::move(*message)); +} + +void MessageQueue::Pop(Message* message) { + DCHECK(!queue_.empty()); + *message = std::move(queue_.front()); + Pop(); +} + +void MessageQueue::Pop() { + DCHECK(!queue_.empty()); + queue_.pop(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/message_queue.h b/mojo/public/cpp/bindings/tests/message_queue.h new file mode 100644 index 0000000000..8f13f7ab6d --- /dev/null +++ b/mojo/public/cpp/bindings/tests/message_queue.h @@ -0,0 +1,44 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ + +#include <queue> + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace test { + +// A queue for Message objects. +class MessageQueue { + public: + MessageQueue(); + ~MessageQueue(); + + bool IsEmpty() const; + + // This method copies the message data and steals ownership of its handles. + void Push(Message* message); + + // Removes the next message from the queue, copying its data and transferring + // ownership of its handles to the given |message|. + void Pop(Message* message); + + size_t size() const { return queue_.size(); } + + private: + void Pop(); + + std::queue<Message> queue_; + + DISALLOW_COPY_AND_ASSIGN(MessageQueue); +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MESSAGE_QUEUE_H_ diff --git a/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h b/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h new file mode 100644 index 0000000000..b3bbe27421 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/mojo_test_blink_export.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_TEST_BLINK_IMPLEMENTATION) +#define MOJO_TEST_BLINK_EXPORT __declspec(dllexport) +#else +#define MOJO_TEST_BLINK_EXPORT __declspec(dllimport) +#endif // defined(MOJO_TEST_BLINK_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_TEST_BLINK_IMPLEMENTATION) +#define MOJO_TEST_BLINK_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_TEST_BLINK_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_TEST_BLINK_EXPORT +#endif + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_BLINK_EXPORT_H_ diff --git a/mojo/public/cpp/bindings/tests/mojo_test_export.h b/mojo/public/cpp/bindings/tests/mojo_test_export.h new file mode 100644 index 0000000000..a48a1ba8ae --- /dev/null +++ b/mojo/public/cpp/bindings/tests/mojo_test_export.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_TEST_IMPLEMENTATION) +#define MOJO_TEST_EXPORT __declspec(dllexport) +#else +#define MOJO_TEST_EXPORT __declspec(dllimport) +#endif // defined(MOJO_TEST_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_TEST_IMPLEMENTATION) +#define MOJO_TEST_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_TEST_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_TEST_EXPORT +#endif + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_MOJO_TEST_EXPORT_H_ diff --git a/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc new file mode 100644 index 0000000000..89509283c4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc @@ -0,0 +1,314 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/lib/multiplex_router.h" + +#include <utility> + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/interface_endpoint_client.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" +#include "mojo/public/cpp/bindings/tests/message_queue.h" +#include "mojo/public/cpp/bindings/tests/router_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using mojo::internal::MultiplexRouter; + +class MultiplexRouterTest : public testing::Test { + public: + MultiplexRouterTest() {} + + void SetUp() override { + MessagePipe pipe; + router0_ = new MultiplexRouter(std::move(pipe.handle0), + MultiplexRouter::MULTI_INTERFACE, false, + base::ThreadTaskRunnerHandle::Get()); + router1_ = new MultiplexRouter(std::move(pipe.handle1), + MultiplexRouter::MULTI_INTERFACE, true, + base::ThreadTaskRunnerHandle::Get()); + ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&endpoint0_, + &endpoint1_); + auto id = router0_->AssociateInterface(std::move(endpoint1_)); + endpoint1_ = router1_->CreateLocalEndpointHandle(id); + } + + void TearDown() override {} + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + protected: + scoped_refptr<MultiplexRouter> router0_; + scoped_refptr<MultiplexRouter> router1_; + ScopedInterfaceEndpointHandle endpoint0_; + ScopedInterfaceEndpointHandle endpoint1_; + + private: + base::MessageLoop loop_; +}; + +TEST_F(MultiplexRouterTest, BasicRequestResponse) { + InterfaceEndpointClient client0(std::move(endpoint0_), nullptr, + base::MakeUnique<PassThroughFilter>(), false, + base::ThreadTaskRunnerHandle::Get(), 0u); + ResponseGenerator generator; + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::MakeUnique<PassThroughFilter>(), false, + base::ThreadTaskRunnerHandle::Get(), 0u); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + base::RunLoop run_loop; + client0.AcceptWithResponder( + &request, base::MakeUnique<MessageAccumulator>(&message_queue, + run_loop.QuitClosure())); + + run_loop.Run(); + + EXPECT_FALSE(message_queue.IsEmpty()); + + Message response; + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); + + // Send a second message on the pipe. + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + base::RunLoop run_loop2; + client0.AcceptWithResponder( + &request2, base::MakeUnique<MessageAccumulator>(&message_queue, + run_loop2.QuitClosure())); + + run_loop2.Run(); + + EXPECT_FALSE(message_queue.IsEmpty()); + + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); +} + +TEST_F(MultiplexRouterTest, BasicRequestResponse_Synchronous) { + InterfaceEndpointClient client0(std::move(endpoint0_), nullptr, + base::MakeUnique<PassThroughFilter>(), false, + base::ThreadTaskRunnerHandle::Get(), 0u); + ResponseGenerator generator; + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::MakeUnique<PassThroughFilter>(), false, + base::ThreadTaskRunnerHandle::Get(), 0u); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + client0.AcceptWithResponder( + &request, base::MakeUnique<MessageAccumulator>(&message_queue)); + + router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + EXPECT_FALSE(message_queue.IsEmpty()); + + Message response; + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); + + // Send a second message on the pipe. + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + client0.AcceptWithResponder( + &request2, base::MakeUnique<MessageAccumulator>(&message_queue)); + + router1_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + router0_->WaitForIncomingMessage(MOJO_DEADLINE_INDEFINITE); + + EXPECT_FALSE(message_queue.IsEmpty()); + + message_queue.Pop(&response); + + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); +} + +// Tests MultiplexRouter using the LazyResponseGenerator. The responses will not +// be sent until after the requests have been accepted. +TEST_F(MultiplexRouterTest, LazyResponses) { + InterfaceEndpointClient client0( + std::move(endpoint0_), nullptr, base::WrapUnique(new PassThroughFilter()), + false, base::ThreadTaskRunnerHandle::Get(), 0u); + base::RunLoop run_loop; + LazyResponseGenerator generator(run_loop.QuitClosure()); + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::WrapUnique(new PassThroughFilter()), + false, base::ThreadTaskRunnerHandle::Get(), + 0u); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + base::RunLoop run_loop2; + client0.AcceptWithResponder( + &request, base::MakeUnique<MessageAccumulator>(&message_queue, + run_loop2.QuitClosure())); + run_loop.Run(); + + // The request has been received but the response has not been sent yet. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Send the response. + EXPECT_TRUE(generator.responder_is_valid()); + generator.CompleteWithResponse(); + run_loop2.Run(); + + // Check the response. + EXPECT_FALSE(message_queue.IsEmpty()); + Message response; + message_queue.Pop(&response); + EXPECT_EQ(std::string("hello world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); + + // Send a second message on the pipe. + base::RunLoop run_loop3; + generator.set_closure(run_loop3.QuitClosure()); + Message request2; + AllocRequestMessage(1, "hello again", &request2); + + base::RunLoop run_loop4; + client0.AcceptWithResponder( + &request2, base::MakeUnique<MessageAccumulator>(&message_queue, + run_loop4.QuitClosure())); + run_loop3.Run(); + + // The request has been received but the response has not been sent yet. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Send the second response. + EXPECT_TRUE(generator.responder_is_valid()); + generator.CompleteWithResponse(); + run_loop4.Run(); + + // Check the second response. + EXPECT_FALSE(message_queue.IsEmpty()); + message_queue.Pop(&response); + EXPECT_EQ(std::string("hello again world!"), + std::string(reinterpret_cast<const char*>(response.payload()))); +} + +void ForwardErrorHandler(bool* called, const base::Closure& callback) { + *called = true; + callback.Run(); +} + +// Tests that if the receiving application destroys the responder_ without +// sending a response, then we trigger connection error at both sides. Moreover, +// both sides still appear to have a valid message pipe handle bound. +TEST_F(MultiplexRouterTest, MissingResponses) { + base::RunLoop run_loop0, run_loop1; + InterfaceEndpointClient client0( + std::move(endpoint0_), nullptr, base::WrapUnique(new PassThroughFilter()), + false, base::ThreadTaskRunnerHandle::Get(), 0u); + bool error_handler_called0 = false; + client0.set_connection_error_handler( + base::Bind(&ForwardErrorHandler, &error_handler_called0, + run_loop0.QuitClosure())); + + base::RunLoop run_loop3; + LazyResponseGenerator generator(run_loop3.QuitClosure()); + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::WrapUnique(new PassThroughFilter()), + false, base::ThreadTaskRunnerHandle::Get(), + 0u); + bool error_handler_called1 = false; + client1.set_connection_error_handler( + base::Bind(&ForwardErrorHandler, &error_handler_called1, + run_loop1.QuitClosure())); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + client0.AcceptWithResponder( + &request, base::MakeUnique<MessageAccumulator>(&message_queue)); + run_loop3.Run(); + + // The request has been received but no response has been sent. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Destroy the responder MessagerReceiver but don't send any response. + generator.CompleteWithoutResponse(); + run_loop0.Run(); + run_loop1.Run(); + + // Check that no response was received. + EXPECT_TRUE(message_queue.IsEmpty()); + + // Connection error handler is called at both sides. + EXPECT_TRUE(error_handler_called0); + EXPECT_TRUE(error_handler_called1); + + // The error flag is set at both sides. + EXPECT_TRUE(client0.encountered_error()); + EXPECT_TRUE(client1.encountered_error()); + + // The message pipe handle is valid at both sides. + EXPECT_TRUE(router0_->is_valid()); + EXPECT_TRUE(router1_->is_valid()); +} + +TEST_F(MultiplexRouterTest, LateResponse) { + // Test that things won't blow up if we try to send a message to a + // MessageReceiver, which was given to us via AcceptWithResponder, + // after the router has gone away. + + base::RunLoop run_loop; + LazyResponseGenerator generator(run_loop.QuitClosure()); + { + InterfaceEndpointClient client0( + std::move(endpoint0_), nullptr, base::MakeUnique<PassThroughFilter>(), + false, base::ThreadTaskRunnerHandle::Get(), 0u); + InterfaceEndpointClient client1(std::move(endpoint1_), &generator, + base::MakeUnique<PassThroughFilter>(), + false, base::ThreadTaskRunnerHandle::Get(), + 0u); + + Message request; + AllocRequestMessage(1, "hello", &request); + + MessageQueue message_queue; + client0.AcceptWithResponder( + &request, base::MakeUnique<MessageAccumulator>(&message_queue)); + + run_loop.Run(); + + EXPECT_TRUE(generator.has_responder()); + } + + EXPECT_FALSE(generator.responder_is_valid()); + generator.CompleteWithResponse(); // This should end up doing nothing. +} + +// TODO(yzshen): add more tests. + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/pickle_unittest.cc b/mojo/public/cpp/bindings/tests/pickle_unittest.cc new file mode 100644 index 0000000000..a5947ce9ed --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickle_unittest.cc @@ -0,0 +1,403 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/tests/pickled_types_blink.h" +#include "mojo/public/cpp/bindings/tests/pickled_types_chromium.h" +#include "mojo/public/cpp/bindings/tests/variant_test_util.h" +#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template <typename T> +void DoExpectResult(int foo, int bar, const base::Closure& callback, T actual) { + EXPECT_EQ(foo, actual.foo()); + EXPECT_EQ(bar, actual.bar()); + callback.Run(); +} + +template <typename T> +base::Callback<void(T)> ExpectResult(const T& t, + const base::Closure& callback) { + return base::Bind(&DoExpectResult<T>, t.foo(), t.bar(), callback); +} + +template <typename T> +void DoFail(const std::string& reason, T) { + EXPECT_TRUE(false) << reason; +} + +template <typename T> +base::Callback<void(T)> Fail(const std::string& reason) { + return base::Bind(&DoFail<T>, reason); +} + +template <typename T> +void DoExpectEnumResult(T expected, const base::Closure& callback, T actual) { + EXPECT_EQ(expected, actual); + callback.Run(); +} + +template <typename T> +base::Callback<void(T)> ExpectEnumResult(T t, const base::Closure& callback) { + return base::Bind(&DoExpectEnumResult<T>, t, callback); +} + +template <typename T> +void DoEnumFail(const std::string& reason, T) { + EXPECT_TRUE(false) << reason; +} + +template <typename T> +base::Callback<void(T)> EnumFail(const std::string& reason) { + return base::Bind(&DoEnumFail<T>, reason); +} + +template <typename T> +void ExpectError(InterfacePtr<T>* proxy, const base::Closure& callback) { + proxy->set_connection_error_handler(callback); +} + +template <typename Func, typename Arg> +void RunSimpleLambda(Func func, Arg arg) { func(std::move(arg)); } + +template <typename Arg, typename Func> +base::Callback<void(Arg)> BindSimpleLambda(Func func) { + return base::Bind(&RunSimpleLambda<Func, Arg>, func); +} + +// This implements the generated Chromium variant of PicklePasser. +class ChromiumPicklePasserImpl : public PicklePasser { + public: + ChromiumPicklePasserImpl() {} + + // mojo::test::PicklePasser: + void PassPickledStruct(PickledStructChromium pickle, + const PassPickledStructCallback& callback) override { + callback.Run(std::move(pickle)); + } + + void PassPickledEnum(PickledEnumChromium pickle, + const PassPickledEnumCallback& callback) override { + callback.Run(pickle); + } + + void PassPickleContainer( + PickleContainerPtr container, + const PassPickleContainerCallback& callback) override { + callback.Run(std::move(container)); + } + + void PassPickles(std::vector<PickledStructChromium> pickles, + const PassPicklesCallback& callback) override { + callback.Run(std::move(pickles)); + } + + void PassPickleArrays( + std::vector<std::vector<PickledStructChromium>> pickle_arrays, + const PassPickleArraysCallback& callback) override { + callback.Run(std::move(pickle_arrays)); + } +}; + +// This implements the generated Blink variant of PicklePasser. +class BlinkPicklePasserImpl : public blink::PicklePasser { + public: + BlinkPicklePasserImpl() {} + + // mojo::test::blink::PicklePasser: + void PassPickledStruct(PickledStructBlink pickle, + const PassPickledStructCallback& callback) override { + callback.Run(std::move(pickle)); + } + + void PassPickledEnum(PickledEnumBlink pickle, + const PassPickledEnumCallback& callback) override { + callback.Run(pickle); + } + + void PassPickleContainer( + blink::PickleContainerPtr container, + const PassPickleContainerCallback& callback) override { + callback.Run(std::move(container)); + } + + void PassPickles(WTF::Vector<PickledStructBlink> pickles, + const PassPicklesCallback& callback) override { + callback.Run(std::move(pickles)); + } + + void PassPickleArrays( + WTF::Vector<WTF::Vector<PickledStructBlink>> pickle_arrays, + const PassPickleArraysCallback& callback) override { + callback.Run(std::move(pickle_arrays)); + } +}; + +// A test which runs both Chromium and Blink implementations of the +// PicklePasser service. +class PickleTest : public testing::Test { + public: + PickleTest() {} + + template <typename ProxyType = PicklePasser> + InterfacePtr<ProxyType> ConnectToChromiumService() { + InterfacePtr<ProxyType> proxy; + InterfaceRequest<ProxyType> request(&proxy); + chromium_bindings_.AddBinding( + &chromium_service_, + ConvertInterfaceRequest<PicklePasser>(std::move(request))); + return proxy; + } + + template <typename ProxyType = blink::PicklePasser> + InterfacePtr<ProxyType> ConnectToBlinkService() { + InterfacePtr<ProxyType> proxy; + InterfaceRequest<ProxyType> request(&proxy); + blink_bindings_.AddBinding( + &blink_service_, + ConvertInterfaceRequest<blink::PicklePasser>(std::move(request))); + return proxy; + } + + private: + base::MessageLoop loop_; + ChromiumPicklePasserImpl chromium_service_; + BindingSet<PicklePasser> chromium_bindings_; + BlinkPicklePasserImpl blink_service_; + BindingSet<blink::PicklePasser> blink_bindings_; +}; + +} // namespace + +TEST_F(PickleTest, ChromiumProxyToChromiumService) { + auto chromium_proxy = ConnectToChromiumService(); + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(1, 2), + ExpectResult(PickledStructChromium(1, 2), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(4, 5), + ExpectResult(PickledStructChromium(4, 5), loop.QuitClosure())); + loop.Run(); + } + + { + base::RunLoop loop; + chromium_proxy->PassPickledEnum( + PickledEnumChromium::VALUE_1, + ExpectEnumResult(PickledEnumChromium::VALUE_1, loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(PickleTest, ChromiumProxyToBlinkService) { + auto chromium_proxy = ConnectToBlinkService<PicklePasser>(); + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(1, 2), + ExpectResult(PickledStructChromium(1, 2), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(4, 5), + ExpectResult(PickledStructChromium(4, 5), loop.QuitClosure())); + loop.Run(); + } + // The Blink service should drop our connection because the + // PickledStructBlink ParamTraits deserializer rejects negative values. + { + base::RunLoop loop; + chromium_proxy->PassPickledStruct( + PickledStructChromium(-1, -1), + Fail<PickledStructChromium>("Blink service should reject this.")); + ExpectError(&chromium_proxy, loop.QuitClosure()); + loop.Run(); + } + + chromium_proxy = ConnectToBlinkService<PicklePasser>(); + { + base::RunLoop loop; + chromium_proxy->PassPickledEnum( + PickledEnumChromium::VALUE_0, + ExpectEnumResult(PickledEnumChromium::VALUE_0, loop.QuitClosure())); + loop.Run(); + } + + // The Blink service should drop our connection because the + // PickledEnumBlink ParamTraits deserializer rejects this value. + { + base::RunLoop loop; + chromium_proxy->PassPickledEnum( + PickledEnumChromium::VALUE_2, + EnumFail<PickledEnumChromium>("Blink service should reject this.")); + ExpectError(&chromium_proxy, loop.QuitClosure()); + loop.Run(); + } +} + +TEST_F(PickleTest, BlinkProxyToBlinkService) { + auto blink_proxy = ConnectToBlinkService(); + { + base::RunLoop loop; + blink_proxy->PassPickledStruct( + PickledStructBlink(1, 1), + ExpectResult(PickledStructBlink(1, 1), loop.QuitClosure())); + loop.Run(); + } + + { + base::RunLoop loop; + blink_proxy->PassPickledEnum( + PickledEnumBlink::VALUE_0, + ExpectEnumResult(PickledEnumBlink::VALUE_0, loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(PickleTest, BlinkProxyToChromiumService) { + auto blink_proxy = ConnectToChromiumService<blink::PicklePasser>(); + { + base::RunLoop loop; + blink_proxy->PassPickledStruct( + PickledStructBlink(1, 1), + ExpectResult(PickledStructBlink(1, 1), loop.QuitClosure())); + loop.Run(); + } + + { + base::RunLoop loop; + blink_proxy->PassPickledEnum( + PickledEnumBlink::VALUE_1, + ExpectEnumResult(PickledEnumBlink::VALUE_1, loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(PickleTest, PickleArray) { + auto proxy = ConnectToChromiumService(); + auto pickles = std::vector<PickledStructChromium>(2); + pickles[0].set_foo(1); + pickles[0].set_bar(2); + pickles[0].set_baz(100); + pickles[1].set_foo(3); + pickles[1].set_bar(4); + pickles[1].set_baz(100); + { + base::RunLoop run_loop; + // Verify that the array of pickled structs can be serialized and + // deserialized intact. This ensures that the ParamTraits are actually used + // rather than doing a byte-for-byte copy of the element data, beacuse the + // |baz| field should never be serialized. + proxy->PassPickles(std::move(pickles), + BindSimpleLambda<std::vector<PickledStructChromium>>( + [&](std::vector<PickledStructChromium> passed) { + ASSERT_EQ(2u, passed.size()); + EXPECT_EQ(1, passed[0].foo()); + EXPECT_EQ(2, passed[0].bar()); + EXPECT_EQ(0, passed[0].baz()); + EXPECT_EQ(3, passed[1].foo()); + EXPECT_EQ(4, passed[1].bar()); + EXPECT_EQ(0, passed[1].baz()); + run_loop.Quit(); + })); + run_loop.Run(); + } +} + +TEST_F(PickleTest, PickleArrayArray) { + auto proxy = ConnectToChromiumService(); + auto pickle_arrays = std::vector<std::vector<PickledStructChromium>>(2); + for (size_t i = 0; i < 2; ++i) + pickle_arrays[i] = std::vector<PickledStructChromium>(2); + + pickle_arrays[0][0].set_foo(1); + pickle_arrays[0][0].set_bar(2); + pickle_arrays[0][0].set_baz(100); + pickle_arrays[0][1].set_foo(3); + pickle_arrays[0][1].set_bar(4); + pickle_arrays[0][1].set_baz(100); + pickle_arrays[1][0].set_foo(5); + pickle_arrays[1][0].set_bar(6); + pickle_arrays[1][0].set_baz(100); + pickle_arrays[1][1].set_foo(7); + pickle_arrays[1][1].set_bar(8); + pickle_arrays[1][1].set_baz(100); + { + base::RunLoop run_loop; + // Verify that the array-of-arrays serializes and deserializes properly. + proxy->PassPickleArrays( + std::move(pickle_arrays), + BindSimpleLambda<std::vector<std::vector<PickledStructChromium>>>( + [&](std::vector<std::vector<PickledStructChromium>> passed) { + ASSERT_EQ(2u, passed.size()); + ASSERT_EQ(2u, passed[0].size()); + ASSERT_EQ(2u, passed[1].size()); + EXPECT_EQ(1, passed[0][0].foo()); + EXPECT_EQ(2, passed[0][0].bar()); + EXPECT_EQ(0, passed[0][0].baz()); + EXPECT_EQ(3, passed[0][1].foo()); + EXPECT_EQ(4, passed[0][1].bar()); + EXPECT_EQ(0, passed[0][1].baz()); + EXPECT_EQ(5, passed[1][0].foo()); + EXPECT_EQ(6, passed[1][0].bar()); + EXPECT_EQ(0, passed[1][0].baz()); + EXPECT_EQ(7, passed[1][1].foo()); + EXPECT_EQ(8, passed[1][1].bar()); + EXPECT_EQ(0, passed[1][1].baz()); + run_loop.Quit(); + })); + run_loop.Run(); + } +} + +TEST_F(PickleTest, PickleContainer) { + auto proxy = ConnectToChromiumService(); + PickleContainerPtr pickle_container = PickleContainer::New(); + pickle_container->f_struct.set_foo(42); + pickle_container->f_struct.set_bar(43); + pickle_container->f_struct.set_baz(44); + pickle_container->f_enum = PickledEnumChromium::VALUE_1; + EXPECT_TRUE(pickle_container.Equals(pickle_container)); + EXPECT_FALSE(pickle_container.Equals(PickleContainer::New())); + { + base::RunLoop run_loop; + proxy->PassPickleContainer(std::move(pickle_container), + BindSimpleLambda<PickleContainerPtr>( + [&](PickleContainerPtr passed) { + ASSERT_FALSE(passed.is_null()); + EXPECT_EQ(42, passed->f_struct.foo()); + EXPECT_EQ(43, passed->f_struct.bar()); + EXPECT_EQ(0, passed->f_struct.baz()); + EXPECT_EQ(PickledEnumChromium::VALUE_1, + passed->f_enum); + run_loop.Quit(); + })); + run_loop.Run(); + } +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/pickled_types_blink.cc b/mojo/public/cpp/bindings/tests/pickled_types_blink.cc new file mode 100644 index 0000000000..7e556507bb --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickled_types_blink.cc @@ -0,0 +1,67 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/pickled_types_blink.h" + +#include "base/logging.h" +#include "base/pickle.h" + +namespace mojo { +namespace test { + +PickledStructBlink::PickledStructBlink() {} + +PickledStructBlink::PickledStructBlink(int foo, int bar) + : foo_(foo), bar_(bar) { + DCHECK_GE(foo_, 0); + DCHECK_GE(bar_, 0); +} + +PickledStructBlink::~PickledStructBlink() {} + +} // namespace test +} // namespace mojo + +namespace IPC { + +void ParamTraits<mojo::test::PickledStructBlink>::GetSize( + base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt(); + sizer->AddInt(); +} + +void ParamTraits<mojo::test::PickledStructBlink>::Write(base::Pickle* m, + const param_type& p) { + m->WriteInt(p.foo()); + m->WriteInt(p.bar()); +} + +bool ParamTraits<mojo::test::PickledStructBlink>::Read( + const base::Pickle* m, + base::PickleIterator* iter, + param_type* p) { + int foo, bar; + if (!iter->ReadInt(&foo) || !iter->ReadInt(&bar) || foo < 0 || bar < 0) + return false; + + p->set_foo(foo); + p->set_bar(bar); + return true; +} + +#include "ipc/param_traits_size_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) +#include "ipc/param_traits_write_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) +#include "ipc/param_traits_read_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) +#include "ipc/param_traits_log_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) + +} // namespace IPC diff --git a/mojo/public/cpp/bindings/tests/pickled_types_blink.h b/mojo/public/cpp/bindings/tests/pickled_types_blink.h new file mode 100644 index 0000000000..37e9e70578 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickled_types_blink.h @@ -0,0 +1,88 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_ + +#include <stddef.h> + +#include <string> + +#include "base/logging.h" +#include "base/macros.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_param_traits.h" + +namespace base { +class Pickle; +class PickleIterator; +class PickleSizer; +} + +namespace mojo { +namespace test { + +// Implementation of types with IPC::ParamTraits for consumers in Blink. + +enum class PickledEnumBlink { VALUE_0, VALUE_1 }; + +// To make things slightly more interesting, this variation of the type doesn't +// support negative values. It'll DCHECK if you try to construct it with any, +// and it will fail deserialization if negative values are decoded. +class PickledStructBlink { + public: + PickledStructBlink(); + PickledStructBlink(int foo, int bar); + PickledStructBlink(PickledStructBlink&& other) = default; + ~PickledStructBlink(); + + PickledStructBlink& operator=(PickledStructBlink&& other) = default; + + int foo() const { return foo_; } + void set_foo(int foo) { + DCHECK_GE(foo, 0); + foo_ = foo; + } + + int bar() const { return bar_; } + void set_bar(int bar) { + DCHECK_GE(bar, 0); + bar_ = bar; + } + + // The |baz| field should never be serialized. + int baz() const { return baz_; } + void set_baz(int baz) { baz_ = baz; } + + private: + int foo_ = 0; + int bar_ = 0; + int baz_ = 0; + + DISALLOW_COPY_AND_ASSIGN(PickledStructBlink); +}; + +} // namespace test +} // namespace mojo + +namespace IPC { + +template <> +struct ParamTraits<mojo::test::PickledStructBlink> { + using param_type = mojo::test::PickledStructBlink; + + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l) {} +}; + +} // namespace IPC + +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumBlink, + mojo::test::PickledEnumBlink::VALUE_1) + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_BLINK_H_ diff --git a/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc b/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc new file mode 100644 index 0000000000..9957c9a4d0 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickled_types_chromium.cc @@ -0,0 +1,69 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/pickled_types_chromium.h" + +#include "base/pickle.h" + +namespace mojo { +namespace test { + +PickledStructChromium::PickledStructChromium() {} + +PickledStructChromium::PickledStructChromium(int foo, int bar) + : foo_(foo), bar_(bar) {} + +PickledStructChromium::~PickledStructChromium() {} + +bool operator==(const PickledStructChromium& a, + const PickledStructChromium& b) { + return a.foo() == b.foo() && a.bar() == b.bar() && a.baz() == b.baz(); +} + +} // namespace test +} // namespace mojo + +namespace IPC { + +void ParamTraits<mojo::test::PickledStructChromium>::GetSize( + base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt(); + sizer->AddInt(); +} + +void ParamTraits<mojo::test::PickledStructChromium>::Write( + base::Pickle* m, + const param_type& p) { + m->WriteInt(p.foo()); + m->WriteInt(p.bar()); +} + +bool ParamTraits<mojo::test::PickledStructChromium>::Read( + const base::Pickle* m, + base::PickleIterator* iter, + param_type* p) { + int foo, bar; + if (!iter->ReadInt(&foo) || !iter->ReadInt(&bar)) + return false; + + p->set_foo(foo); + p->set_bar(bar); + return true; +} + +#include "ipc/param_traits_size_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) +#include "ipc/param_traits_write_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) +#include "ipc/param_traits_read_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) +#include "ipc/param_traits_log_macros.h" +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) + +} // namespace IPC diff --git a/mojo/public/cpp/bindings/tests/pickled_types_chromium.h b/mojo/public/cpp/bindings/tests/pickled_types_chromium.h new file mode 100644 index 0000000000..d9287b62e7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/pickled_types_chromium.h @@ -0,0 +1,81 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_ + +#include <stddef.h> + +#include <string> + +#include "base/macros.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_param_traits.h" + +namespace base { +class Pickle; +class PickleIterator; +class PickleSizer; +} + +namespace mojo { +namespace test { + +// Implementation of types with IPC::ParamTraits for consumers in the greater +// Chromium tree. + +enum class PickledEnumChromium { VALUE_0, VALUE_1, VALUE_2 }; + +class PickledStructChromium { + public: + PickledStructChromium(); + PickledStructChromium(int foo, int bar); + PickledStructChromium(PickledStructChromium&& other) = default; + ~PickledStructChromium(); + + PickledStructChromium& operator=(PickledStructChromium&& other) = default; + + int foo() const { return foo_; } + void set_foo(int foo) { foo_ = foo; } + + int bar() const { return bar_; } + void set_bar(int bar) { bar_ = bar; } + + // The |baz| field should never be serialized. + int baz() const { return baz_; } + void set_baz(int baz) { baz_ = baz; } + + private: + int foo_ = 0; + int bar_ = 0; + int baz_ = 0; + + DISALLOW_COPY_AND_ASSIGN(PickledStructChromium); +}; + +bool operator==(const PickledStructChromium& a, const PickledStructChromium& b); + +} // namespace test +} // namespace mojo + +namespace IPC { + +template <> +struct ParamTraits<mojo::test::PickledStructChromium> { + using param_type = mojo::test::PickledStructChromium; + + static void GetSize(base::PickleSizer* sizer, const param_type& p); + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l) {} +}; + +} // namespace IPC + +IPC_ENUM_TRAITS_MAX_VALUE(mojo::test::PickledEnumChromium, + mojo::test::PickledEnumChromium::VALUE_2) + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_PICKLED_TYPES_CHROMIUM_H_ diff --git a/mojo/public/cpp/bindings/tests/rect_blink.h b/mojo/public/cpp/bindings/tests/rect_blink.h new file mode 100644 index 0000000000..7335989593 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_blink.h @@ -0,0 +1,83 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_ + +#include "base/logging.h" + +namespace mojo { +namespace test { + +// An implementation of a hypothetical Rect type specifically for consumers in +// in Blink. Unlike the Chromium variant (see rect_chromium.h) this does not +// support negative origin coordinates and is not copyable. +class RectBlink { + public: + RectBlink() {} + RectBlink(int x, int y, int width, int height) : + x_(x), y_(y), width_(width), height_(height) { + DCHECK_GE(x_, 0); + DCHECK_GE(y_, 0); + DCHECK_GE(width_, 0); + DCHECK_GE(height_, 0); + } + ~RectBlink() {} + + int x() const { return x_; } + void setX(int x) { + DCHECK_GE(x, 0); + x_ = x; + } + + int y() const { return y_; } + void setY(int y) { + DCHECK_GE(y, 0); + y_ = y; + } + + int width() const { return width_; } + void setWidth(int width) { + DCHECK_GE(width, 0); + width_ = width; + } + + int height() const { return height_; } + void setHeight(int height) { + DCHECK_GE(height, 0); + height_ = height; + } + + int computeArea() const { return width_ * height_; } + + bool operator==(const RectBlink& other) const { + return (x() == other.x() && y() == other.y() && width() == other.width() && + height() == other.height()); + } + bool operator!=(const RectBlink& other) const { return !(*this == other); } + + private: + int x_ = 0; + int y_ = 0; + int width_ = 0; + int height_ = 0; +}; + +} // namespace test +} // namespace mojo + +namespace std { + +template <> +struct hash<mojo::test::RectBlink> { + size_t operator()(const mojo::test::RectBlink& value) { + // Terrible hash function: + return (std::hash<int>()(value.x()) ^ std::hash<int>()(value.y()) ^ + std::hash<int>()(value.width()) ^ std::hash<int>()(value.height())); + } +}; + +} // namespace std + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_H_ diff --git a/mojo/public/cpp/bindings/tests/rect_blink.typemap b/mojo/public/cpp/bindings/tests/rect_blink.typemap new file mode 100644 index 0000000000..657ea1a6ca --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_blink.typemap @@ -0,0 +1,18 @@ +# 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. + +mojom = "//mojo/public/interfaces/bindings/tests/rect.mojom" +public_headers = [ + "//mojo/public/cpp/bindings/tests/rect_blink.h", + "//mojo/public/cpp/bindings/tests/shared_rect.h", +] +traits_headers = [ + "//mojo/public/cpp/bindings/tests/rect_blink_traits.h", + "//mojo/public/cpp/bindings/tests/shared_rect_traits.h", +] + +type_mappings = [ + "mojo.test.TypemappedRect=mojo::test::RectBlink[hashable]", + "mojo.test.SharedTypemappedRect=mojo::test::SharedRect", +] diff --git a/mojo/public/cpp/bindings/tests/rect_blink_traits.h b/mojo/public/cpp/bindings/tests/rect_blink_traits.h new file mode 100644 index 0000000000..7258739a84 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_blink_traits.h @@ -0,0 +1,35 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_ + +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/tests/rect_blink.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom-blink.h" + +namespace mojo { + +template <> +struct StructTraits<test::TypemappedRectDataView, test::RectBlink> { + static int x(const test::RectBlink& r) { return r.x(); } + static int y(const test::RectBlink& r) { return r.y(); } + static int width(const test::RectBlink& r) { return r.width(); } + static int height(const test::RectBlink& r) { return r.height(); } + + static bool Read(test::TypemappedRectDataView r, test::RectBlink* out) { + if (r.x() < 0 || r.y() < 0 || r.width() < 0 || r.height() < 0) { + return false; + } + out->setX(r.x()); + out->setY(r.y()); + out->setWidth(r.width()); + out->setHeight(r.height()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_BLINK_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/tests/rect_chromium.h b/mojo/public/cpp/bindings/tests/rect_chromium.h new file mode 100644 index 0000000000..d2e0a3e635 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_chromium.h @@ -0,0 +1,87 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_ + +#include "base/logging.h" + +namespace mojo { +namespace test { + +// An implementation of a hypothetical Rect type specifically for consumers in +// in Chromium. +class RectChromium { + public: + RectChromium() {} + RectChromium(const RectChromium& other) + : x_(other.x_), + y_(other.y_), + width_(other.width_), + height_(other.height_) {} + RectChromium(int x, int y, int width, int height) : + x_(x), y_(y), width_(width), height_(height) { + DCHECK_GE(width_, 0); + DCHECK_GE(height_, 0); + } + ~RectChromium() {} + + RectChromium& operator=(const RectChromium& other) { + x_ = other.x_; + y_ = other.y_; + width_ = other.width_; + height_ = other.height_; + return *this; + } + + int x() const { return x_; } + void set_x(int x) { x_ = x; } + + int y() const { return y_; } + void set_y(int y) { y_ = y; } + + int width() const { return width_; } + void set_width(int width) { + DCHECK_GE(width, 0); + width_ = width; + } + + int height() const { return height_; } + void set_height(int height) { + DCHECK_GE(height, 0); + height_ = height; + } + + int GetArea() const { return width_ * height_; } + + bool operator==(const RectChromium& other) const { + return (x() == other.x() && y() == other.y() && width() == other.width() && + height() == other.height()); + } + bool operator!=(const RectChromium& other) const { return !(*this == other); } + + private: + int x_ = 0; + int y_ = 0; + int width_ = 0; + int height_ = 0; +}; + +} // namespace test +} // namespace mojo + +namespace std { + +template <> +struct hash<mojo::test::RectChromium> { + size_t operator()(const mojo::test::RectChromium& value) { + // Terrible hash function: + return (std::hash<int>()(value.x()) ^ std::hash<int>()(value.y()) ^ + std::hash<int>()(value.width()) ^ std::hash<int>()(value.height())); + } +}; + +} // namespace std + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_H_ diff --git a/mojo/public/cpp/bindings/tests/rect_chromium.typemap b/mojo/public/cpp/bindings/tests/rect_chromium.typemap new file mode 100644 index 0000000000..7e5df8401a --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_chromium.typemap @@ -0,0 +1,18 @@ +# 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. + +mojom = "//mojo/public/interfaces/bindings/tests/rect.mojom" +public_headers = [ + "//mojo/public/cpp/bindings/tests/rect_chromium.h", + "//mojo/public/cpp/bindings/tests/shared_rect.h", +] +traits_headers = [ + "//mojo/public/cpp/bindings/tests/rect_chromium_traits.h", + "//mojo/public/cpp/bindings/tests/shared_rect_traits.h", +] + +type_mappings = [ + "mojo.test.TypemappedRect=mojo::test::RectChromium[hashable]", + "mojo.test.SharedTypemappedRect=mojo::test::SharedRect", +] diff --git a/mojo/public/cpp/bindings/tests/rect_chromium_traits.h b/mojo/public/cpp/bindings/tests/rect_chromium_traits.h new file mode 100644 index 0000000000..b446d7d34a --- /dev/null +++ b/mojo/public/cpp/bindings/tests/rect_chromium_traits.h @@ -0,0 +1,34 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_ + +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/tests/rect_chromium.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom.h" + +namespace mojo { + +template <> +struct StructTraits<test::TypemappedRectDataView, test::RectChromium> { + static int x(const test::RectChromium& r) { return r.x(); } + static int y(const test::RectChromium& r) { return r.y(); } + static int width(const test::RectChromium& r) { return r.width(); } + static int height(const test::RectChromium& r) { return r.height(); } + + static bool Read(test::TypemappedRectDataView r, test::RectChromium* out) { + if (r.width() < 0 || r.height() < 0) + return false; + out->set_x(r.x()); + out->set_y(r.y()); + out->set_width(r.width()); + out->set_height(r.height()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_RECT_CHROMIUM_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc b/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc new file mode 100644 index 0000000000..1bf3f7a4b7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc @@ -0,0 +1,194 @@ +// 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. + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/interfaces/bindings/tests/test_bad_messages.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class TestBadMessagesImpl : public TestBadMessages { + public: + TestBadMessagesImpl() : binding_(this) {} + ~TestBadMessagesImpl() override {} + + void BindImpl(TestBadMessagesRequest request) { + binding_.Bind(std::move(request)); + } + + const ReportBadMessageCallback& bad_message_callback() { + return bad_message_callback_; + } + + private: + // TestBadMessages: + void RejectEventually(const RejectEventuallyCallback& callback) override { + bad_message_callback_ = GetBadMessageCallback(); + callback.Run(); + } + + void RequestResponse(const RequestResponseCallback& callback) override { + callback.Run(); + } + + void RejectSync(const RejectSyncCallback& callback) override { + callback.Run(); + ReportBadMessage("go away"); + } + + void RequestResponseSync( + const RequestResponseSyncCallback& callback) override { + callback.Run(); + } + + ReportBadMessageCallback bad_message_callback_; + mojo::Binding<TestBadMessages> binding_; + + DISALLOW_COPY_AND_ASSIGN(TestBadMessagesImpl); +}; + +class ReportBadMessageTest : public testing::Test { + public: + ReportBadMessageTest() {} + + void SetUp() override { + mojo::edk::SetDefaultProcessErrorCallback( + base::Bind(&ReportBadMessageTest::OnProcessError, + base::Unretained(this))); + + impl_.BindImpl(MakeRequest(&proxy_)); + } + + void TearDown() override { + mojo::edk::SetDefaultProcessErrorCallback( + mojo::edk::ProcessErrorCallback()); + } + + TestBadMessages* proxy() { return proxy_.get(); } + + TestBadMessagesImpl* impl() { return &impl_; } + + void SetErrorHandler(const base::Closure& handler) { + error_handler_ = handler; + } + + private: + void OnProcessError(const std::string& error) { + if (!error_handler_.is_null()) + error_handler_.Run(); + } + + TestBadMessagesPtr proxy_; + TestBadMessagesImpl impl_; + base::Closure error_handler_; + base::MessageLoop message_loop; +}; + +TEST_F(ReportBadMessageTest, Request) { + // Verify that basic immediate error reporting works. + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + EXPECT_TRUE(proxy()->RejectSync()); + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, RequestAsync) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + // This should capture a bad message reporting callback in the impl. + base::RunLoop loop; + proxy()->RejectEventually(loop.QuitClosure()); + loop.Run(); + + EXPECT_FALSE(error); + + // Now we can run the callback and it should trigger a bad message report. + DCHECK(!impl()->bad_message_callback().is_null()); + impl()->bad_message_callback().Run("bad!"); + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, Response) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + base::RunLoop loop; + proxy()->RequestResponse( + base::Bind([] (const base::Closure& quit) { + // Report a bad message inside the response callback. This should + // trigger the error handler. + ReportBadMessage("no way!"); + quit.Run(); + }, + loop.QuitClosure())); + loop.Run(); + + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, ResponseAsync) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + ReportBadMessageCallback bad_message_callback; + base::RunLoop loop; + proxy()->RequestResponse( + base::Bind([] (const base::Closure& quit, + ReportBadMessageCallback* callback) { + // Capture the bad message callback inside the response callback. + *callback = GetBadMessageCallback(); + quit.Run(); + }, + loop.QuitClosure(), &bad_message_callback)); + loop.Run(); + + EXPECT_FALSE(error); + + // Invoking this callback should report a bad message and trigger the error + // handler immediately. + bad_message_callback.Run("this message is bad and should feel bad"); + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, ResponseSync) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + SyncMessageResponseContext context; + proxy()->RequestResponseSync(); + + EXPECT_FALSE(error); + context.ReportBadMessage("i don't like this response"); + EXPECT_TRUE(error); +} + +TEST_F(ReportBadMessageTest, ResponseSyncDeferred) { + bool error = false; + SetErrorHandler(base::Bind([] (bool* flag) { *flag = true; }, &error)); + + ReportBadMessageCallback bad_message_callback; + { + SyncMessageResponseContext context; + proxy()->RequestResponseSync(); + bad_message_callback = context.GetBadMessageCallback(); + } + + EXPECT_FALSE(error); + bad_message_callback.Run("nope nope nope"); + EXPECT_TRUE(error); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/request_response_unittest.cc b/mojo/public/cpp/bindings/tests/request_response_unittest.cc new file mode 100644 index 0000000000..43b8f0dc90 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/request_response_unittest.cc @@ -0,0 +1,157 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> +#include <utility> + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/sample_import.mojom.h" +#include "mojo/public/interfaces/bindings/tests/sample_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +class ProviderImpl : public sample::Provider { + public: + explicit ProviderImpl(InterfaceRequest<sample::Provider> request) + : binding_(this, std::move(request)) {} + + void EchoString(const std::string& a, + const EchoStringCallback& callback) override { + EchoStringCallback callback_copy; + // Make sure operator= is used. + callback_copy = callback; + callback_copy.Run(a); + } + + void EchoStrings(const std::string& a, + const std::string& b, + const EchoStringsCallback& callback) override { + callback.Run(a, b); + } + + void EchoMessagePipeHandle( + ScopedMessagePipeHandle a, + const EchoMessagePipeHandleCallback& callback) override { + callback.Run(std::move(a)); + } + + void EchoEnum(sample::Enum a, const EchoEnumCallback& callback) override { + callback.Run(a); + } + + void EchoInt(int32_t a, const EchoIntCallback& callback) override { + callback.Run(a); + } + + Binding<sample::Provider> binding_; +}; + +void RecordString(std::string* storage, + const base::Closure& closure, + const std::string& str) { + *storage = str; + closure.Run(); +} + +void RecordStrings(std::string* storage, + const base::Closure& closure, + const std::string& a, + const std::string& b) { + *storage = a + b; + closure.Run(); +} + +void WriteToMessagePipe(const char* text, + const base::Closure& closure, + ScopedMessagePipeHandle handle) { + WriteTextMessage(handle.get(), text); + closure.Run(); +} + +void RecordEnum(sample::Enum* storage, + const base::Closure& closure, + sample::Enum value) { + *storage = value; + closure.Run(); +} + +class RequestResponseTest : public testing::Test { + public: + RequestResponseTest() {} + ~RequestResponseTest() override { base::RunLoop().RunUntilIdle(); } + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + private: + base::MessageLoop loop_; +}; + +TEST_F(RequestResponseTest, EchoString) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(MakeRequest(&provider)); + + std::string buf; + base::RunLoop run_loop; + provider->EchoString("hello", + base::Bind(&RecordString, &buf, run_loop.QuitClosure())); + + run_loop.Run(); + + EXPECT_EQ(std::string("hello"), buf); +} + +TEST_F(RequestResponseTest, EchoStrings) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(MakeRequest(&provider)); + + std::string buf; + base::RunLoop run_loop; + provider->EchoStrings("hello", " world", base::Bind(&RecordStrings, &buf, + run_loop.QuitClosure())); + + run_loop.Run(); + + EXPECT_EQ(std::string("hello world"), buf); +} + +TEST_F(RequestResponseTest, EchoMessagePipeHandle) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(MakeRequest(&provider)); + + MessagePipe pipe2; + base::RunLoop run_loop; + provider->EchoMessagePipeHandle( + std::move(pipe2.handle1), + base::Bind(&WriteToMessagePipe, "hello", run_loop.QuitClosure())); + + run_loop.Run(); + + std::string value; + ReadTextMessage(pipe2.handle0.get(), &value); + + EXPECT_EQ(std::string("hello"), value); +} + +TEST_F(RequestResponseTest, EchoEnum) { + sample::ProviderPtr provider; + ProviderImpl provider_impl(MakeRequest(&provider)); + + sample::Enum value; + base::RunLoop run_loop; + provider->EchoEnum(sample::Enum::VALUE, + base::Bind(&RecordEnum, &value, run_loop.QuitClosure())); + run_loop.Run(); + + EXPECT_EQ(sample::Enum::VALUE, value); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/router_test_util.cc b/mojo/public/cpp/bindings/tests/router_test_util.cc new file mode 100644 index 0000000000..9bab1cb360 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/router_test_util.cc @@ -0,0 +1,111 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/router_test_util.h" + +#include <stddef.h> +#include <stdint.h> +#include <string.h> + +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/tests/message_queue.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +void AllocRequestMessage(uint32_t name, const char* text, Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::MessageBuilder builder(name, Message::kFlagExpectsResponse, + payload_size, 0); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + *message = std::move(*builder.message()); +} + +void AllocResponseMessage(uint32_t name, + const char* text, + uint64_t request_id, + Message* message) { + size_t payload_size = strlen(text) + 1; // Plus null terminator. + internal::MessageBuilder builder(name, Message::kFlagIsResponse, payload_size, + 0); + builder.message()->set_request_id(request_id); + memcpy(builder.buffer()->Allocate(payload_size), text, payload_size); + *message = std::move(*builder.message()); +} + +MessageAccumulator::MessageAccumulator(MessageQueue* queue, + const base::Closure& closure) + : queue_(queue), closure_(closure) {} + +MessageAccumulator::~MessageAccumulator() {} + +bool MessageAccumulator::Accept(Message* message) { + queue_->Push(message); + if (!closure_.is_null()) { + closure_.Run(); + closure_.Reset(); + } + return true; +} + +ResponseGenerator::ResponseGenerator() {} + +bool ResponseGenerator::Accept(Message* message) { + return false; +} + +bool ResponseGenerator::AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) { + EXPECT_TRUE(message->has_flag(Message::kFlagExpectsResponse)); + + bool result = SendResponse(message->name(), message->request_id(), + reinterpret_cast<const char*>(message->payload()), + responder.get()); + EXPECT_TRUE(responder->IsValid()); + return result; +} + +bool ResponseGenerator::SendResponse(uint32_t name, + uint64_t request_id, + const char* request_string, + MessageReceiver* responder) { + Message response; + std::string response_string(request_string); + response_string += " world!"; + AllocResponseMessage(name, response_string.c_str(), request_id, &response); + + return responder->Accept(&response); +} + +LazyResponseGenerator::LazyResponseGenerator(const base::Closure& closure) + : responder_(nullptr), name_(0), request_id_(0), closure_(closure) {} + +LazyResponseGenerator::~LazyResponseGenerator() = default; + +bool LazyResponseGenerator::AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) { + name_ = message->name(); + request_id_ = message->request_id(); + request_string_ = + std::string(reinterpret_cast<const char*>(message->payload())); + responder_ = std::move(responder); + if (!closure_.is_null()) { + closure_.Run(); + closure_.Reset(); + } + return true; +} + +void LazyResponseGenerator::Complete(bool send_response) { + if (send_response) { + SendResponse(name_, request_id_, request_string_.c_str(), responder_.get()); + } + responder_ = nullptr; +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/router_test_util.h b/mojo/public/cpp/bindings/tests/router_test_util.h new file mode 100644 index 0000000000..dd6aff63da --- /dev/null +++ b/mojo/public/cpp/bindings/tests/router_test_util.h @@ -0,0 +1,92 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_ + +#include <stdint.h> + +#include <string> + +#include "base/callback.h" +#include "mojo/public/cpp/bindings/message.h" + +namespace mojo { +namespace test { + +class MessageQueue; + +void AllocRequestMessage(uint32_t name, const char* text, Message* message); +void AllocResponseMessage(uint32_t name, + const char* text, + uint64_t request_id, + Message* message); + +class MessageAccumulator : public MessageReceiver { + public: + MessageAccumulator(MessageQueue* queue, + const base::Closure& closure = base::Closure()); + ~MessageAccumulator() override; + + bool Accept(Message* message) override; + + private: + MessageQueue* queue_; + base::Closure closure_; +}; + +class ResponseGenerator : public MessageReceiverWithResponderStatus { + public: + ResponseGenerator(); + + bool Accept(Message* message) override; + + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override; + bool SendResponse(uint32_t name, + uint64_t request_id, + const char* request_string, + MessageReceiver* responder); +}; + +class LazyResponseGenerator : public ResponseGenerator { + public: + explicit LazyResponseGenerator( + const base::Closure& closure = base::Closure()); + + ~LazyResponseGenerator() override; + + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiverWithStatus> responder) override; + + bool has_responder() const { return !!responder_; } + + bool responder_is_valid() const { return responder_->IsValid(); } + + void set_closure(const base::Closure& closure) { closure_ = closure; } + + // Sends the response and delete the responder. + void CompleteWithResponse() { Complete(true); } + + // Deletes the responder without sending a response. + void CompleteWithoutResponse() { Complete(false); } + + private: + // Completes the request handling by deleting responder_. Optionally + // also sends a response. + void Complete(bool send_response); + + std::unique_ptr<MessageReceiverWithStatus> responder_; + uint32_t name_; + uint64_t request_id_; + std::string request_string_; + base::Closure closure_; +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_ROUTER_TEST_UTIL_H_ diff --git a/mojo/public/cpp/bindings/tests/sample_service_unittest.cc b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc new file mode 100644 index 0000000000..1f95a27a5e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/sample_service_unittest.cc @@ -0,0 +1,362 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> +#include <algorithm> +#include <ostream> +#include <string> +#include <utility> + +#include "mojo/public/interfaces/bindings/tests/sample_service.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { + +template <> +struct TypeConverter<int32_t, sample::BarPtr> { + static int32_t Convert(const sample::BarPtr& bar) { + return static_cast<int32_t>(bar->alpha) << 16 | + static_cast<int32_t>(bar->beta) << 8 | + static_cast<int32_t>(bar->gamma); + } +}; + +} // namespace mojo + +namespace sample { +namespace { + +// Set this variable to true to print the message in hex. +bool g_dump_message_as_hex = false; + +// Set this variable to true to print the message in human readable form. +bool g_dump_message_as_text = false; + +// Make a sample |Foo|. +FooPtr MakeFoo() { + std::string name("foopy"); + + BarPtr bar(Bar::New(20, 40, 60, Bar::Type::VERTICAL)); + + std::vector<BarPtr> extra_bars(3); + for (size_t i = 0; i < extra_bars.size(); ++i) { + Bar::Type type = i % 2 == 0 ? Bar::Type::VERTICAL : Bar::Type::HORIZONTAL; + uint8_t base = static_cast<uint8_t>(i * 100); + extra_bars[i] = Bar::New(base, base + 20, base + 40, type); + } + + std::vector<uint8_t> data(10); + for (size_t i = 0; i < data.size(); ++i) + data[i] = static_cast<uint8_t>(data.size() - i); + + std::vector<mojo::ScopedDataPipeConsumerHandle> input_streams(2); + std::vector<mojo::ScopedDataPipeProducerHandle> output_streams(2); + for (size_t i = 0; i < input_streams.size(); ++i) { + MojoCreateDataPipeOptions options; + options.struct_size = sizeof(MojoCreateDataPipeOptions); + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + options.element_num_bytes = 1; + options.capacity_num_bytes = 1024; + mojo::ScopedDataPipeProducerHandle producer; + mojo::ScopedDataPipeConsumerHandle consumer; + mojo::CreateDataPipe(&options, &producer, &consumer); + input_streams[i] = std::move(consumer); + output_streams[i] = std::move(producer); + } + + std::vector<std::vector<bool>> array_of_array_of_bools(2); + for (size_t i = 0; i < 2; ++i) { + std::vector<bool> array_of_bools(2); + for (size_t j = 0; j < 2; ++j) + array_of_bools[j] = j; + array_of_array_of_bools[i] = std::move(array_of_bools); + } + + mojo::MessagePipe pipe; + return Foo::New(name, 1, 2, false, true, false, std::move(bar), + std::move(extra_bars), std::move(data), + std::move(pipe.handle1), std::move(input_streams), + std::move(output_streams), std::move(array_of_array_of_bools), + base::nullopt, base::nullopt); +} + +// Check that the given |Foo| is identical to the one made by |MakeFoo()|. +void CheckFoo(const Foo& foo) { + const std::string kName("foopy"); + EXPECT_EQ(kName.size(), foo.name.size()); + for (size_t i = 0; i < std::min(kName.size(), foo.name.size()); i++) { + // Test both |operator[]| and |at|. + EXPECT_EQ(kName[i], foo.name.at(i)) << i; + EXPECT_EQ(kName[i], foo.name[i]) << i; + } + EXPECT_EQ(kName, foo.name); + + EXPECT_EQ(1, foo.x); + EXPECT_EQ(2, foo.y); + EXPECT_FALSE(foo.a); + EXPECT_TRUE(foo.b); + EXPECT_FALSE(foo.c); + + EXPECT_EQ(20, foo.bar->alpha); + EXPECT_EQ(40, foo.bar->beta); + EXPECT_EQ(60, foo.bar->gamma); + EXPECT_EQ(Bar::Type::VERTICAL, foo.bar->type); + + EXPECT_EQ(3u, foo.extra_bars->size()); + for (size_t i = 0; i < foo.extra_bars->size(); i++) { + uint8_t base = static_cast<uint8_t>(i * 100); + Bar::Type type = i % 2 == 0 ? Bar::Type::VERTICAL : Bar::Type::HORIZONTAL; + EXPECT_EQ(base, (*foo.extra_bars)[i]->alpha) << i; + EXPECT_EQ(base + 20, (*foo.extra_bars)[i]->beta) << i; + EXPECT_EQ(base + 40, (*foo.extra_bars)[i]->gamma) << i; + EXPECT_EQ(type, (*foo.extra_bars)[i]->type) << i; + } + + EXPECT_EQ(10u, foo.data->size()); + for (size_t i = 0; i < foo.data->size(); ++i) { + EXPECT_EQ(static_cast<uint8_t>(foo.data->size() - i), (*foo.data)[i]) << i; + } + + EXPECT_TRUE(foo.input_streams); + EXPECT_EQ(2u, foo.input_streams->size()); + + EXPECT_TRUE(foo.output_streams); + EXPECT_EQ(2u, foo.output_streams->size()); + + EXPECT_EQ(2u, foo.array_of_array_of_bools->size()); + for (size_t i = 0; i < foo.array_of_array_of_bools->size(); ++i) { + EXPECT_EQ(2u, (*foo.array_of_array_of_bools)[i].size()); + for (size_t j = 0; j < (*foo.array_of_array_of_bools)[i].size(); ++j) { + EXPECT_EQ(bool(j), (*foo.array_of_array_of_bools)[i][j]); + } + } +} + +void PrintSpacer(int depth) { + for (int i = 0; i < depth; ++i) + std::cout << " "; +} + +void Print(int depth, const char* name, bool value) { + PrintSpacer(depth); + std::cout << name << ": " << (value ? "true" : "false") << std::endl; +} + +void Print(int depth, const char* name, int32_t value) { + PrintSpacer(depth); + std::cout << name << ": " << value << std::endl; +} + +void Print(int depth, const char* name, uint8_t value) { + PrintSpacer(depth); + std::cout << name << ": " << uint32_t(value) << std::endl; +} + +template <typename H> +void Print(int depth, + const char* name, + const mojo::ScopedHandleBase<H>& value) { + PrintSpacer(depth); + std::cout << name << ": 0x" << std::hex << value.get().value() << std::endl; +} + +void Print(int depth, const char* name, const std::string& str) { + PrintSpacer(depth); + std::cout << name << ": \"" << str << "\"" << std::endl; +} + +void Print(int depth, const char* name, const BarPtr& bar) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + if (!bar.is_null()) { + ++depth; + Print(depth, "alpha", bar->alpha); + Print(depth, "beta", bar->beta); + Print(depth, "gamma", bar->gamma); + Print(depth, "packed", bar.To<int32_t>()); + --depth; + } +} + +template <typename T> +void Print(int depth, const char* name, const std::vector<T>& array) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + ++depth; + for (size_t i = 0; i < array.size(); ++i) { + std::stringstream buf; + buf << i; + Print(depth, buf.str().data(), array.at(i)); + } + --depth; +} + +template <typename T> +void Print(int depth, + const char* name, + const base::Optional<std::vector<T>>& array) { + if (array) + Print(depth, name, *array); + else + Print(depth, name, std::vector<T>()); +} + +void Print(int depth, const char* name, const FooPtr& foo) { + PrintSpacer(depth); + std::cout << name << ":" << std::endl; + if (!foo.is_null()) { + ++depth; + Print(depth, "name", foo->name); + Print(depth, "x", foo->x); + Print(depth, "y", foo->y); + Print(depth, "a", foo->a); + Print(depth, "b", foo->b); + Print(depth, "c", foo->c); + Print(depth, "bar", foo->bar); + Print(depth, "extra_bars", foo->extra_bars); + Print(depth, "data", foo->data); + Print(depth, "source", foo->source); + Print(depth, "input_streams", foo->input_streams); + Print(depth, "output_streams", foo->output_streams); + Print(depth, "array_of_array_of_bools", foo->array_of_array_of_bools); + --depth; + } +} + +void DumpHex(const uint8_t* bytes, uint32_t num_bytes) { + for (uint32_t i = 0; i < num_bytes; ++i) { + std::cout << std::setw(2) << std::setfill('0') << std::hex + << uint32_t(bytes[i]); + + if (i % 16 == 15) { + std::cout << std::endl; + continue; + } + + if (i % 2 == 1) + std::cout << " "; + if (i % 8 == 7) + std::cout << " "; + } +} + +class ServiceImpl : public Service { + public: + void Frobinate(FooPtr foo, + BazOptions baz, + PortPtr port, + const Service::FrobinateCallback& callback) override { + // Users code goes here to handle the incoming Frobinate message. + + // We mainly check that we're given the expected arguments. + EXPECT_FALSE(foo.is_null()); + if (!foo.is_null()) + CheckFoo(*foo); + EXPECT_EQ(BazOptions::EXTRA, baz); + + if (g_dump_message_as_text) { + // Also dump the Foo structure and all of its members. + std::cout << "Frobinate:" << std::endl; + int depth = 1; + Print(depth, "foo", foo); + Print(depth, "baz", static_cast<int32_t>(baz)); + Print(depth, "port", port.get()); + } + callback.Run(5); + } + + void GetPort(mojo::InterfaceRequest<Port> port_request) override {} +}; + +class ServiceProxyImpl : public ServiceProxy { + public: + explicit ServiceProxyImpl(mojo::MessageReceiverWithResponder* receiver) + : ServiceProxy(receiver) {} +}; + +class SimpleMessageReceiver : public mojo::MessageReceiverWithResponder { + public: + bool Accept(mojo::Message* message) override { + // Imagine some IPC happened here. + + if (g_dump_message_as_hex) { + DumpHex(reinterpret_cast<const uint8_t*>(message->data()), + message->data_num_bytes()); + } + + // In the receiving process, an implementation of ServiceStub is known to + // the system. It receives the incoming message. + ServiceImpl impl; + + ServiceStub<> stub; + stub.set_sink(&impl); + return stub.Accept(message); + } + + bool AcceptWithResponder( + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiver> responder) override { + return false; + } +}; + +using BindingsSampleTest = testing::Test; + +TEST_F(BindingsSampleTest, Basic) { + SimpleMessageReceiver receiver; + + // User has a proxy to a Service somehow. + Service* service = new ServiceProxyImpl(&receiver); + + // User constructs a message to send. + + // Notice that it doesn't matter in what order the structs / arrays are + // allocated. Here, the various members of Foo are allocated before Foo is + // allocated. + + FooPtr foo = MakeFoo(); + CheckFoo(*foo); + + PortPtr port; + service->Frobinate(std::move(foo), Service::BazOptions::EXTRA, + std::move(port), Service::FrobinateCallback()); + + delete service; +} + +TEST_F(BindingsSampleTest, DefaultValues) { + DefaultsTestPtr defaults(DefaultsTest::New()); + EXPECT_EQ(-12, defaults->a0); + EXPECT_EQ(kTwelve, defaults->a1); + EXPECT_EQ(1234, defaults->a2); + EXPECT_EQ(34567U, defaults->a3); + EXPECT_EQ(123456, defaults->a4); + EXPECT_EQ(3456789012U, defaults->a5); + EXPECT_EQ(-111111111111LL, defaults->a6); + EXPECT_EQ(9999999999999999999ULL, defaults->a7); + EXPECT_EQ(0x12345, defaults->a8); + EXPECT_EQ(-0x12345, defaults->a9); + EXPECT_EQ(1234, defaults->a10); + EXPECT_TRUE(defaults->a11); + EXPECT_FALSE(defaults->a12); + EXPECT_FLOAT_EQ(123.25f, defaults->a13); + EXPECT_DOUBLE_EQ(1234567890.123, defaults->a14); + EXPECT_DOUBLE_EQ(1E10, defaults->a15); + EXPECT_DOUBLE_EQ(-1.2E+20, defaults->a16); + EXPECT_DOUBLE_EQ(1.23E-20, defaults->a17); + EXPECT_TRUE(defaults->a18.empty()); + EXPECT_TRUE(defaults->a19.empty()); + EXPECT_EQ(Bar::Type::BOTH, defaults->a20); + EXPECT_TRUE(defaults->a21.is_null()); + ASSERT_FALSE(defaults->a22.is_null()); + EXPECT_EQ(imported::Shape::RECTANGLE, defaults->a22->shape); + EXPECT_EQ(imported::Color::BLACK, defaults->a22->color); + EXPECT_EQ(0xFFFFFFFFFFFFFFFFULL, defaults->a23); + EXPECT_EQ(0x123456789, defaults->a24); + EXPECT_EQ(-0x123456789, defaults->a25); +} + +} // namespace +} // namespace sample diff --git a/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc new file mode 100644 index 0000000000..275f10f9e7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc @@ -0,0 +1,251 @@ +// 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. + +// Serialization warnings are only recorded when DLOG is enabled. +#if !defined(NDEBUG) || defined(DCHECK_ALWAYS_ON) + +#include <stddef.h> +#include <utility> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using mojo::internal::ContainerValidateParams; + +// Creates an array of arrays of handles (2 X 3) for testing. +std::vector<base::Optional<std::vector<ScopedHandle>>> +CreateTestNestedHandleArray() { + std::vector<base::Optional<std::vector<ScopedHandle>>> array(2); + for (size_t i = 0; i < array.size(); ++i) { + std::vector<ScopedHandle> nested_array(3); + for (size_t j = 0; j < nested_array.size(); ++j) { + MessagePipe pipe; + nested_array[j] = ScopedHandle::From(std::move(pipe.handle1)); + } + array[i].emplace(std::move(nested_array)); + } + + return array; +} + +class SerializationWarningTest : public testing::Test { + public: + ~SerializationWarningTest() override {} + + protected: + template <typename T> + void TestWarning(T obj, mojo::internal::ValidationError expected_warning) { + using MojomType = typename T::Struct::DataView; + + warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE); + + mojo::internal::SerializationContext context; + mojo::internal::FixedBufferForTesting buf( + mojo::internal::PrepareToSerialize<MojomType>(obj, &context)); + typename mojo::internal::MojomTypeTraits<MojomType>::Data* data; + mojo::internal::Serialize<MojomType>(obj, &buf, &data, &context); + + EXPECT_EQ(expected_warning, warning_observer_.last_warning()); + } + + template <typename MojomType, typename T> + void TestArrayWarning(T obj, + mojo::internal::ValidationError expected_warning, + const ContainerValidateParams* validate_params) { + warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE); + + mojo::internal::SerializationContext context; + mojo::internal::FixedBufferForTesting buf( + mojo::internal::PrepareToSerialize<MojomType>(obj, &context)); + typename mojo::internal::MojomTypeTraits<MojomType>::Data* data; + mojo::internal::Serialize<MojomType>(obj, &buf, &data, validate_params, + &context); + + EXPECT_EQ(expected_warning, warning_observer_.last_warning()); + } + + template <typename T> + void TestUnionWarning(T obj, + mojo::internal::ValidationError expected_warning) { + using MojomType = typename T::Struct::DataView; + + warning_observer_.set_last_warning(mojo::internal::VALIDATION_ERROR_NONE); + + mojo::internal::SerializationContext context; + mojo::internal::FixedBufferForTesting buf( + mojo::internal::PrepareToSerialize<MojomType>(obj, false, &context)); + typename mojo::internal::MojomTypeTraits<MojomType>::Data* data; + mojo::internal::Serialize<MojomType>(obj, &buf, &data, false, &context); + + EXPECT_EQ(expected_warning, warning_observer_.last_warning()); + } + + mojo::internal::SerializationWarningObserverForTesting warning_observer_; +}; + +TEST_F(SerializationWarningTest, HandleInStruct) { + Struct2Ptr test_struct(Struct2::New()); + EXPECT_FALSE(test_struct->hdl.is_valid()); + + TestWarning(std::move(test_struct), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE); + + test_struct = Struct2::New(); + MessagePipe pipe; + test_struct->hdl = ScopedHandle::From(std::move(pipe.handle1)); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, StructInStruct) { + Struct3Ptr test_struct(Struct3::New()); + EXPECT_TRUE(!test_struct->struct_1); + + TestWarning(std::move(test_struct), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct3::New(); + test_struct->struct_1 = Struct1::New(); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, ArrayOfStructsInStruct) { + Struct4Ptr test_struct(Struct4::New()); + test_struct->data.resize(1); + + TestWarning(std::move(test_struct), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); + + test_struct = Struct4::New(); + test_struct->data.resize(0); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); + + test_struct = Struct4::New(); + test_struct->data.resize(1); + test_struct->data[0] = Struct1::New(); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, FixedArrayOfStructsInStruct) { + Struct5Ptr test_struct(Struct5::New()); + test_struct->pair.resize(1); + test_struct->pair[0] = Struct1::New(); + + TestWarning(std::move(test_struct), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER); + + test_struct = Struct5::New(); + test_struct->pair.resize(2); + test_struct->pair[0] = Struct1::New(); + test_struct->pair[1] = Struct1::New(); + + TestWarning(std::move(test_struct), mojo::internal::VALIDATION_ERROR_NONE); +} + +TEST_F(SerializationWarningTest, ArrayOfArraysOfHandles) { + using MojomType = ArrayDataView<ArrayDataView<ScopedHandle>>; + auto test_array = CreateTestNestedHandleArray(); + test_array[0] = base::nullopt; + (*test_array[1])[0] = ScopedHandle(); + + ContainerValidateParams validate_params_0( + 0, true, new ContainerValidateParams(0, true, nullptr)); + TestArrayWarning<MojomType>(std::move(test_array), + mojo::internal::VALIDATION_ERROR_NONE, + &validate_params_0); + + test_array = CreateTestNestedHandleArray(); + test_array[0] = base::nullopt; + ContainerValidateParams validate_params_1( + 0, false, new ContainerValidateParams(0, true, nullptr)); + TestArrayWarning<MojomType>( + std::move(test_array), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + &validate_params_1); + + test_array = CreateTestNestedHandleArray(); + (*test_array[1])[0] = ScopedHandle(); + ContainerValidateParams validate_params_2( + 0, true, new ContainerValidateParams(0, false, nullptr)); + TestArrayWarning<MojomType>( + std::move(test_array), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, + &validate_params_2); +} + +TEST_F(SerializationWarningTest, ArrayOfStrings) { + using MojomType = ArrayDataView<StringDataView>; + + std::vector<std::string> test_array(3); + for (size_t i = 0; i < test_array.size(); ++i) + test_array[i] = "hello"; + + ContainerValidateParams validate_params_0( + 0, true, new ContainerValidateParams(0, false, nullptr)); + TestArrayWarning<MojomType>(std::move(test_array), + mojo::internal::VALIDATION_ERROR_NONE, + &validate_params_0); + + std::vector<base::Optional<std::string>> optional_test_array(3); + ContainerValidateParams validate_params_1( + 0, false, new ContainerValidateParams(0, false, nullptr)); + TestArrayWarning<MojomType>( + std::move(optional_test_array), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + &validate_params_1); + + test_array = std::vector<std::string>(2); + ContainerValidateParams validate_params_2( + 3, true, new ContainerValidateParams(0, false, nullptr)); + TestArrayWarning<MojomType>( + std::move(test_array), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER, + &validate_params_2); +} + +TEST_F(SerializationWarningTest, StructInUnion) { + DummyStructPtr dummy(nullptr); + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + TestUnionWarning(std::move(obj), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); +} + +TEST_F(SerializationWarningTest, UnionInUnion) { + PodUnionPtr pod(nullptr); + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + TestUnionWarning(std::move(obj), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER); +} + +TEST_F(SerializationWarningTest, HandleInUnion) { + ScopedMessagePipeHandle pipe; + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe)); + + TestUnionWarning(std::move(handle), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE); +} + +} // namespace +} // namespace test +} // namespace mojo + +#endif diff --git a/mojo/public/cpp/bindings/tests/shared_rect.h b/mojo/public/cpp/bindings/tests/shared_rect.h new file mode 100644 index 0000000000..c0a4771c14 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/shared_rect.h @@ -0,0 +1,43 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_ + +#include "base/logging.h" + +namespace mojo { +namespace test { + +// An implementation of a hypothetical Rect type specifically for consumers in +// both Chromium and Blink. +class SharedRect { + public: + SharedRect() {} + SharedRect(int x, int y, int width, int height) + : x_(x), y_(y), width_(width), height_(height) {} + + int x() const { return x_; } + void set_x(int x) { x_ = x; } + + int y() const { return y_; } + void set_y(int y) { y_ = y; } + + int width() const { return width_; } + void set_width(int width) { width_ = width; } + + int height() const { return height_; } + void set_height(int height) { height_ = height; } + + private: + int x_ = 0; + int y_ = 0; + int width_ = 0; + int height_ = 0; +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_H_ diff --git a/mojo/public/cpp/bindings/tests/shared_rect_traits.h b/mojo/public/cpp/bindings/tests/shared_rect_traits.h new file mode 100644 index 0000000000..bbf04d5f6e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/shared_rect_traits.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_ + +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/tests/shared_rect.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom-shared.h" + +namespace mojo { + +template <> +struct StructTraits<test::SharedTypemappedRectDataView, test::SharedRect> { + static int x(const test::SharedRect& r) { return r.x(); } + static int y(const test::SharedRect& r) { return r.y(); } + static int width(const test::SharedRect& r) { return r.width(); } + static int height(const test::SharedRect& r) { return r.height(); } + + static bool Read(test::SharedTypemappedRectDataView r, + test::SharedRect* out) { + out->set_x(r.x()); + out->set_y(r.y()); + out->set_width(r.width()); + out->set_height(r.height()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_SHARED_RECT_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc new file mode 100644 index 0000000000..77b448a215 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_traits_unittest.cc @@ -0,0 +1,553 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/tests/rect_blink.h" +#include "mojo/public/cpp/bindings/tests/rect_chromium.h" +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h" +#include "mojo/public/cpp/bindings/tests/variant_test_util.h" +#include "mojo/public/cpp/system/wait.h" +#include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_native_types.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template <typename T> +void DoExpectResult(const T& expected, + const base::Closure& callback, + const T& actual) { + EXPECT_EQ(expected.x(), actual.x()); + EXPECT_EQ(expected.y(), actual.y()); + EXPECT_EQ(expected.width(), actual.width()); + EXPECT_EQ(expected.height(), actual.height()); + callback.Run(); +} + +template <typename T> +base::Callback<void(const T&)> ExpectResult(const T& r, + const base::Closure& callback) { + return base::Bind(&DoExpectResult<T>, r, callback); +} + +template <typename T> +void DoFail(const std::string& reason, const T&) { + EXPECT_TRUE(false) << reason; +} + +template <typename T> +base::Callback<void(const T&)> Fail(const std::string& reason) { + return base::Bind(&DoFail<T>, reason); +} + +template <typename T> +void ExpectError(InterfacePtr<T> *proxy, const base::Closure& callback) { + proxy->set_connection_error_handler(callback); +} + +// This implements the generated Chromium variant of RectService. +class ChromiumRectServiceImpl : public RectService { + public: + ChromiumRectServiceImpl() {} + + // mojo::test::RectService: + void AddRect(const RectChromium& r) override { + if (r.GetArea() > largest_rect_.GetArea()) + largest_rect_ = r; + } + + void GetLargestRect(const GetLargestRectCallback& callback) override { + callback.Run(largest_rect_); + } + + void PassSharedRect(const SharedRect& r, + const PassSharedRectCallback& callback) override { + callback.Run(r); + } + + private: + RectChromium largest_rect_; +}; + +// This implements the generated Blink variant of RectService. +class BlinkRectServiceImpl : public blink::RectService { + public: + BlinkRectServiceImpl() {} + + // mojo::test::blink::RectService: + void AddRect(const RectBlink& r) override { + if (r.computeArea() > largest_rect_.computeArea()) { + largest_rect_.setX(r.x()); + largest_rect_.setY(r.y()); + largest_rect_.setWidth(r.width()); + largest_rect_.setHeight(r.height()); + } + } + + void GetLargestRect(const GetLargestRectCallback& callback) override { + callback.Run(largest_rect_); + } + + void PassSharedRect(const SharedRect& r, + const PassSharedRectCallback& callback) override { + callback.Run(r); + } + + private: + RectBlink largest_rect_; +}; + +// A test which runs both Chromium and Blink implementations of a RectService. +class StructTraitsTest : public testing::Test, + public TraitsTestService { + public: + StructTraitsTest() {} + + protected: + void BindToChromiumService(RectServiceRequest request) { + chromium_bindings_.AddBinding(&chromium_service_, std::move(request)); + } + void BindToChromiumService(blink::RectServiceRequest request) { + chromium_bindings_.AddBinding( + &chromium_service_, + ConvertInterfaceRequest<RectService>(std::move(request))); + } + + void BindToBlinkService(blink::RectServiceRequest request) { + blink_bindings_.AddBinding(&blink_service_, std::move(request)); + } + void BindToBlinkService(RectServiceRequest request) { + blink_bindings_.AddBinding( + &blink_service_, + ConvertInterfaceRequest<blink::RectService>(std::move(request))); + } + + TraitsTestServicePtr GetTraitsTestProxy() { + return traits_test_bindings_.CreateInterfacePtrAndBind(this); + } + + private: + // TraitsTestService: + void EchoStructWithTraits( + const StructWithTraitsImpl& s, + const EchoStructWithTraitsCallback& callback) override { + callback.Run(s); + } + + void EchoTrivialStructWithTraits( + TrivialStructWithTraitsImpl s, + const EchoTrivialStructWithTraitsCallback& callback) override { + callback.Run(s); + } + + void EchoMoveOnlyStructWithTraits( + MoveOnlyStructWithTraitsImpl s, + const EchoMoveOnlyStructWithTraitsCallback& callback) override { + callback.Run(std::move(s)); + } + + void EchoNullableMoveOnlyStructWithTraits( + base::Optional<MoveOnlyStructWithTraitsImpl> s, + const EchoNullableMoveOnlyStructWithTraitsCallback& callback) override { + callback.Run(std::move(s)); + } + + void EchoEnumWithTraits(EnumWithTraitsImpl e, + const EchoEnumWithTraitsCallback& callback) override { + callback.Run(e); + } + + void EchoStructWithTraitsForUniquePtr( + std::unique_ptr<int> e, + const EchoStructWithTraitsForUniquePtrCallback& callback) override { + callback.Run(std::move(e)); + } + + void EchoNullableStructWithTraitsForUniquePtr( + std::unique_ptr<int> e, + const EchoNullableStructWithTraitsForUniquePtrCallback& callback) + override { + callback.Run(std::move(e)); + } + + void EchoUnionWithTraits( + std::unique_ptr<test::UnionWithTraitsBase> u, + const EchoUnionWithTraitsCallback& callback) override { + callback.Run(std::move(u)); + } + + base::MessageLoop loop_; + + ChromiumRectServiceImpl chromium_service_; + BindingSet<RectService> chromium_bindings_; + + BlinkRectServiceImpl blink_service_; + BindingSet<blink::RectService> blink_bindings_; + + BindingSet<TraitsTestService> traits_test_bindings_; +}; + +} // namespace + +TEST_F(StructTraitsTest, ChromiumProxyToChromiumService) { + RectServicePtr chromium_proxy; + BindToChromiumService(MakeRequest(&chromium_proxy)); + { + base::RunLoop loop; + chromium_proxy->AddRect(RectChromium(1, 1, 4, 5)); + chromium_proxy->AddRect(RectChromium(-1, -1, 2, 2)); + chromium_proxy->GetLargestRect( + ExpectResult(RectChromium(1, 1, 4, 5), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + chromium_proxy->PassSharedRect( + {1, 2, 3, 4}, + ExpectResult(SharedRect({1, 2, 3, 4}), loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(StructTraitsTest, ChromiumToBlinkService) { + RectServicePtr chromium_proxy; + BindToBlinkService(MakeRequest(&chromium_proxy)); + { + base::RunLoop loop; + chromium_proxy->AddRect(RectChromium(1, 1, 4, 5)); + chromium_proxy->AddRect(RectChromium(2, 2, 5, 5)); + chromium_proxy->GetLargestRect( + ExpectResult(RectChromium(2, 2, 5, 5), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + chromium_proxy->PassSharedRect( + {1, 2, 3, 4}, + ExpectResult(SharedRect({1, 2, 3, 4}), loop.QuitClosure())); + loop.Run(); + } + // The Blink service should drop our connection because RectBlink's + // deserializer rejects negative origins. + { + base::RunLoop loop; + ExpectError(&chromium_proxy, loop.QuitClosure()); + chromium_proxy->AddRect(RectChromium(-1, -1, 2, 2)); + chromium_proxy->GetLargestRect( + Fail<RectChromium>("The pipe should have been closed.")); + loop.Run(); + } +} + +TEST_F(StructTraitsTest, BlinkProxyToBlinkService) { + blink::RectServicePtr blink_proxy; + BindToBlinkService(MakeRequest(&blink_proxy)); + { + base::RunLoop loop; + blink_proxy->AddRect(RectBlink(1, 1, 4, 5)); + blink_proxy->AddRect(RectBlink(10, 10, 20, 20)); + blink_proxy->GetLargestRect( + ExpectResult(RectBlink(10, 10, 20, 20), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + blink_proxy->PassSharedRect( + {4, 3, 2, 1}, + ExpectResult(SharedRect({4, 3, 2, 1}), loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(StructTraitsTest, BlinkProxyToChromiumService) { + blink::RectServicePtr blink_proxy; + BindToChromiumService(MakeRequest(&blink_proxy)); + { + base::RunLoop loop; + blink_proxy->AddRect(RectBlink(1, 1, 4, 5)); + blink_proxy->AddRect(RectBlink(10, 10, 2, 2)); + blink_proxy->GetLargestRect( + ExpectResult(RectBlink(1, 1, 4, 5), loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + blink_proxy->PassSharedRect( + {4, 3, 2, 1}, + ExpectResult(SharedRect({4, 3, 2, 1}), loop.QuitClosure())); + loop.Run(); + } +} + +void ExpectStructWithTraits(const StructWithTraitsImpl& expected, + const base::Closure& closure, + const StructWithTraitsImpl& passed) { + EXPECT_EQ(expected.get_enum(), passed.get_enum()); + EXPECT_EQ(expected.get_bool(), passed.get_bool()); + EXPECT_EQ(expected.get_uint32(), passed.get_uint32()); + EXPECT_EQ(expected.get_uint64(), passed.get_uint64()); + EXPECT_EQ(expected.get_string(), passed.get_string()); + EXPECT_EQ(expected.get_string_array(), passed.get_string_array()); + EXPECT_EQ(expected.get_struct(), passed.get_struct()); + EXPECT_EQ(expected.get_struct_array(), passed.get_struct_array()); + EXPECT_EQ(expected.get_struct_map(), passed.get_struct_map()); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoStructWithTraits) { + StructWithTraitsImpl input; + input.set_enum(EnumWithTraitsImpl::CUSTOM_VALUE_1); + input.set_bool(true); + input.set_uint32(7); + input.set_uint64(42); + input.set_string("hello world!"); + input.get_mutable_string_array().assign({"hello", "world!"}); + input.get_mutable_string_set().insert("hello"); + input.get_mutable_string_set().insert("world!"); + input.get_mutable_struct().value = 42; + input.get_mutable_struct_array().resize(2); + input.get_mutable_struct_array()[0].value = 1; + input.get_mutable_struct_array()[1].value = 2; + input.get_mutable_struct_map()["hello"] = NestedStructWithTraitsImpl(1024); + input.get_mutable_struct_map()["world"] = NestedStructWithTraitsImpl(2048); + + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + proxy->EchoStructWithTraits( + input, + base::Bind(&ExpectStructWithTraits, input, loop.QuitClosure())); + loop.Run(); +} + +TEST_F(StructTraitsTest, CloneStructWithTraitsContainer) { + StructWithTraitsContainerPtr container = StructWithTraitsContainer::New(); + container->f_struct.set_uint32(7); + container->f_struct.set_uint64(42); + StructWithTraitsContainerPtr cloned_container = container.Clone(); + EXPECT_EQ(7u, cloned_container->f_struct.get_uint32()); + EXPECT_EQ(42u, cloned_container->f_struct.get_uint64()); +} + +void ExpectTrivialStructWithTraits(TrivialStructWithTraitsImpl expected, + const base::Closure& closure, + TrivialStructWithTraitsImpl passed) { + EXPECT_EQ(expected.value, passed.value); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoTrivialStructWithTraits) { + TrivialStructWithTraitsImpl input; + input.value = 42; + + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + proxy->EchoTrivialStructWithTraits( + input, + base::Bind(&ExpectTrivialStructWithTraits, input, loop.QuitClosure())); + loop.Run(); +} + +void CaptureMessagePipe(ScopedMessagePipeHandle* storage, + const base::Closure& closure, + MoveOnlyStructWithTraitsImpl passed) { + storage->reset(MessagePipeHandle( + passed.get_mutable_handle().release().value())); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoMoveOnlyStructWithTraits) { + MessagePipe mp; + MoveOnlyStructWithTraitsImpl input; + input.get_mutable_handle().reset(mp.handle0.release()); + + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + ScopedMessagePipeHandle received; + proxy->EchoMoveOnlyStructWithTraits( + std::move(input), + base::Bind(&CaptureMessagePipe, &received, loop.QuitClosure())); + loop.Run(); + + ASSERT_TRUE(received.is_valid()); + + // Verify that the message pipe handle is correctly passed. + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(mp.handle1.get(), kHello, kHelloSize, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, Wait(received.get(), MOJO_HANDLE_SIGNAL_READABLE)); + + char buffer[10] = {0}; + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(received.get(), buffer, &buffer_size, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, buffer_size); + EXPECT_STREQ(kHello, buffer); +} + +void CaptureNullableMoveOnlyStructWithTraitsImpl( + base::Optional<MoveOnlyStructWithTraitsImpl>* storage, + const base::Closure& closure, + base::Optional<MoveOnlyStructWithTraitsImpl> passed) { + *storage = std::move(passed); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoNullableMoveOnlyStructWithTraits) { + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + base::Optional<MoveOnlyStructWithTraitsImpl> received; + proxy->EchoNullableMoveOnlyStructWithTraits( + base::nullopt, base::Bind(&CaptureNullableMoveOnlyStructWithTraitsImpl, + &received, loop.QuitClosure())); + loop.Run(); + + EXPECT_FALSE(received); +} + +void ExpectEnumWithTraits(EnumWithTraitsImpl expected_value, + const base::Closure& closure, + EnumWithTraitsImpl value) { + EXPECT_EQ(expected_value, value); + closure.Run(); +} + +TEST_F(StructTraitsTest, EchoEnumWithTraits) { + base::RunLoop loop; + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + proxy->EchoEnumWithTraits( + EnumWithTraitsImpl::CUSTOM_VALUE_1, + base::Bind(&ExpectEnumWithTraits, EnumWithTraitsImpl::CUSTOM_VALUE_1, + loop.QuitClosure())); + loop.Run(); +} + +TEST_F(StructTraitsTest, SerializeStructWithTraits) { + StructWithTraitsImpl input; + input.set_enum(EnumWithTraitsImpl::CUSTOM_VALUE_1); + input.set_bool(true); + input.set_uint32(7); + input.set_uint64(42); + input.set_string("hello world!"); + input.get_mutable_string_array().assign({ "hello", "world!" }); + input.get_mutable_string_set().insert("hello"); + input.get_mutable_string_set().insert("world!"); + input.get_mutable_struct().value = 42; + input.get_mutable_struct_array().resize(2); + input.get_mutable_struct_array()[0].value = 1; + input.get_mutable_struct_array()[1].value = 2; + input.get_mutable_struct_map()["hello"] = NestedStructWithTraitsImpl(1024); + input.get_mutable_struct_map()["world"] = NestedStructWithTraitsImpl(2048); + + auto data = StructWithTraits::Serialize(&input); + StructWithTraitsImpl output; + ASSERT_TRUE(StructWithTraits::Deserialize(std::move(data), &output)); + + EXPECT_EQ(input.get_enum(), output.get_enum()); + EXPECT_EQ(input.get_bool(), output.get_bool()); + EXPECT_EQ(input.get_uint32(), output.get_uint32()); + EXPECT_EQ(input.get_uint64(), output.get_uint64()); + EXPECT_EQ(input.get_string(), output.get_string()); + EXPECT_EQ(input.get_string_array(), output.get_string_array()); + EXPECT_EQ(input.get_string_set(), output.get_string_set()); + EXPECT_EQ(input.get_struct(), output.get_struct()); + EXPECT_EQ(input.get_struct_array(), output.get_struct_array()); + EXPECT_EQ(input.get_struct_map(), output.get_struct_map()); +} + +void ExpectUniquePtr(std::unique_ptr<int> expected, + const base::Closure& closure, + std::unique_ptr<int> value) { + ASSERT_EQ(!expected, !value); + if (expected) + EXPECT_EQ(*expected, *value); + closure.Run(); +} + +TEST_F(StructTraitsTest, TypemapUniquePtr) { + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + { + base::RunLoop loop; + proxy->EchoStructWithTraitsForUniquePtr( + base::MakeUnique<int>(12345), + base::Bind(&ExpectUniquePtr, base::Passed(base::MakeUnique<int>(12345)), + loop.QuitClosure())); + loop.Run(); + } + { + base::RunLoop loop; + proxy->EchoNullableStructWithTraitsForUniquePtr( + nullptr, base::Bind(&ExpectUniquePtr, nullptr, loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(StructTraitsTest, EchoUnionWithTraits) { + TraitsTestServicePtr proxy = GetTraitsTestProxy(); + + { + std::unique_ptr<test::UnionWithTraitsBase> input( + new test::UnionWithTraitsInt32(1234)); + base::RunLoop loop; + proxy->EchoUnionWithTraits( + std::move(input), + base::Bind( + [](const base::Closure& quit_closure, + std::unique_ptr<test::UnionWithTraitsBase> passed) { + ASSERT_EQ(test::UnionWithTraitsBase::Type::INT32, passed->type()); + EXPECT_EQ(1234, + static_cast<test::UnionWithTraitsInt32*>(passed.get()) + ->value()); + quit_closure.Run(); + + }, + loop.QuitClosure())); + loop.Run(); + } + + { + std::unique_ptr<test::UnionWithTraitsBase> input( + new test::UnionWithTraitsStruct(4321)); + base::RunLoop loop; + proxy->EchoUnionWithTraits( + std::move(input), + base::Bind( + [](const base::Closure& quit_closure, + std::unique_ptr<test::UnionWithTraitsBase> passed) { + ASSERT_EQ(test::UnionWithTraitsBase::Type::STRUCT, + passed->type()); + EXPECT_EQ(4321, + static_cast<test::UnionWithTraitsStruct*>(passed.get()) + ->get_struct() + .value); + quit_closure.Run(); + + }, + loop.QuitClosure())); + loop.Run(); + } +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_unittest.cc b/mojo/public/cpp/bindings/tests/struct_unittest.cc new file mode 100644 index 0000000000..a687052706 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_unittest.cc @@ -0,0 +1,526 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> +#include <string.h> +#include <utility> + +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/interfaces/bindings/tests/test_export2.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +RectPtr MakeRect(int32_t factor = 1) { + return Rect::New(1 * factor, 2 * factor, 10 * factor, 20 * factor); +} + +void CheckRect(const Rect& rect, int32_t factor = 1) { + EXPECT_EQ(1 * factor, rect.x); + EXPECT_EQ(2 * factor, rect.y); + EXPECT_EQ(10 * factor, rect.width); + EXPECT_EQ(20 * factor, rect.height); +} + +MultiVersionStructPtr MakeMultiVersionStruct() { + MessagePipe pipe; + return MultiVersionStruct::New(123, MakeRect(5), std::string("hello"), + std::vector<int8_t>{10, 9, 8}, + std::move(pipe.handle0), false, 42); +} + +template <typename U, typename T> +U SerializeAndDeserialize(T input) { + using InputMojomType = typename T::Struct::DataView; + using OutputMojomType = typename U::Struct::DataView; + + using InputDataType = + typename mojo::internal::MojomTypeTraits<InputMojomType>::Data*; + using OutputDataType = + typename mojo::internal::MojomTypeTraits<OutputMojomType>::Data*; + + mojo::internal::SerializationContext context; + size_t size = + mojo::internal::PrepareToSerialize<InputMojomType>(input, &context); + mojo::internal::FixedBufferForTesting buf(size + 32); + InputDataType data; + mojo::internal::Serialize<InputMojomType>(input, &buf, &data, &context); + + // Set the subsequent area to a special value, so that we can find out if we + // mistakenly access the area. + void* subsequent_area = buf.Allocate(32); + memset(subsequent_area, 0xAA, 32); + + OutputDataType output_data = reinterpret_cast<OutputDataType>(data); + + U output; + mojo::internal::Deserialize<OutputMojomType>(output_data, &output, &context); + return std::move(output); +} + +using StructTest = testing::Test; + +} // namespace + +TEST_F(StructTest, Rect) { + RectPtr rect; + EXPECT_TRUE(rect.is_null()); + EXPECT_TRUE(!rect); + EXPECT_FALSE(rect); + + rect = nullptr; + EXPECT_TRUE(rect.is_null()); + EXPECT_TRUE(!rect); + EXPECT_FALSE(rect); + + rect = MakeRect(); + EXPECT_FALSE(rect.is_null()); + EXPECT_FALSE(!rect); + EXPECT_TRUE(rect); + + RectPtr null_rect = nullptr; + EXPECT_TRUE(null_rect.is_null()); + EXPECT_TRUE(!null_rect); + EXPECT_FALSE(null_rect); + + CheckRect(*rect); +} + +TEST_F(StructTest, Clone) { + NamedRegionPtr region; + + NamedRegionPtr clone_region = region.Clone(); + EXPECT_TRUE(clone_region.is_null()); + + region = NamedRegion::New(); + clone_region = region.Clone(); + EXPECT_FALSE(clone_region->name); + EXPECT_FALSE(clone_region->rects); + + region->name.emplace("hello world"); + clone_region = region.Clone(); + EXPECT_EQ(region->name, clone_region->name); + + region->rects.emplace(2); + (*region->rects)[1] = MakeRect(); + clone_region = region.Clone(); + EXPECT_EQ(2u, clone_region->rects->size()); + EXPECT_TRUE((*clone_region->rects)[0].is_null()); + CheckRect(*(*clone_region->rects)[1]); + + // NoDefaultFieldValues contains handles, so Clone() is not available, but + // NoDefaultFieldValuesPtr should still compile. + NoDefaultFieldValuesPtr no_default_field_values(NoDefaultFieldValues::New()); + EXPECT_FALSE(no_default_field_values->f13.is_valid()); +} + +// Serialization test of a struct with no pointer or handle members. +TEST_F(StructTest, Serialization_Basic) { + RectPtr rect(MakeRect()); + + size_t size = mojo::internal::PrepareToSerialize<RectDataView>(rect, nullptr); + EXPECT_EQ(8U + 16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::Rect_Data* data; + mojo::internal::Serialize<RectDataView>(rect, &buf, &data, nullptr); + + RectPtr rect2; + mojo::internal::Deserialize<RectDataView>(data, &rect2, nullptr); + + CheckRect(*rect2); +} + +// Construction of a struct with struct pointers from null. +TEST_F(StructTest, Construction_StructPointers) { + RectPairPtr pair; + EXPECT_TRUE(pair.is_null()); + + pair = RectPair::New(); + EXPECT_FALSE(pair.is_null()); + EXPECT_TRUE(pair->first.is_null()); + EXPECT_TRUE(pair->first.is_null()); + + pair = nullptr; + EXPECT_TRUE(pair.is_null()); +} + +// Serialization test of a struct with struct pointers. +TEST_F(StructTest, Serialization_StructPointers) { + RectPairPtr pair(RectPair::New(MakeRect(), MakeRect())); + + size_t size = + mojo::internal::PrepareToSerialize<RectPairDataView>(pair, nullptr); + EXPECT_EQ(8U + 16U + 2 * (8U + 16U), size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::RectPair_Data* data; + mojo::internal::Serialize<RectPairDataView>(pair, &buf, &data, nullptr); + + RectPairPtr pair2; + mojo::internal::Deserialize<RectPairDataView>(data, &pair2, nullptr); + + CheckRect(*pair2->first); + CheckRect(*pair2->second); +} + +// Serialization test of a struct with an array member. +TEST_F(StructTest, Serialization_ArrayPointers) { + std::vector<RectPtr> rects; + for (size_t i = 0; i < 4; ++i) + rects.push_back(MakeRect(static_cast<int32_t>(i) + 1)); + + NamedRegionPtr region( + NamedRegion::New(std::string("region"), std::move(rects))); + + size_t size = + mojo::internal::PrepareToSerialize<NamedRegionDataView>(region, nullptr); + EXPECT_EQ(8U + // header + 8U + // name pointer + 8U + // rects pointer + 8U + // name header + 8U + // name payload (rounded up) + 8U + // rects header + 4 * 8U + // rects payload (four pointers) + 4 * (8U + // rect header + 16U), // rect payload (four ints) + size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::NamedRegion_Data* data; + mojo::internal::Serialize<NamedRegionDataView>(region, &buf, &data, nullptr); + + NamedRegionPtr region2; + mojo::internal::Deserialize<NamedRegionDataView>(data, ®ion2, nullptr); + + EXPECT_EQ("region", *region2->name); + + EXPECT_EQ(4U, region2->rects->size()); + for (size_t i = 0; i < region2->rects->size(); ++i) + CheckRect(*(*region2->rects)[i], static_cast<int32_t>(i) + 1); +} + +// Serialization test of a struct with null array pointers. +TEST_F(StructTest, Serialization_NullArrayPointers) { + NamedRegionPtr region(NamedRegion::New()); + EXPECT_FALSE(region->name); + EXPECT_FALSE(region->rects); + + size_t size = + mojo::internal::PrepareToSerialize<NamedRegionDataView>(region, nullptr); + EXPECT_EQ(8U + // header + 8U + // name pointer + 8U, // rects pointer + size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::NamedRegion_Data* data; + mojo::internal::Serialize<NamedRegionDataView>(region, &buf, &data, nullptr); + + NamedRegionPtr region2; + mojo::internal::Deserialize<NamedRegionDataView>(data, ®ion2, nullptr); + + EXPECT_FALSE(region2->name); + EXPECT_FALSE(region2->rects); +} + +// Tests deserializing structs as a newer version. +TEST_F(StructTest, Versioning_OldToNew) { + { + MultiVersionStructV0Ptr input(MultiVersionStructV0::New(123)); + MultiVersionStructPtr expected_output(MultiVersionStruct::New(123)); + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV1Ptr input(MultiVersionStructV1::New(123, MakeRect(5))); + MultiVersionStructPtr expected_output( + MultiVersionStruct::New(123, MakeRect(5))); + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV3Ptr input( + MultiVersionStructV3::New(123, MakeRect(5), std::string("hello"))); + MultiVersionStructPtr expected_output( + MultiVersionStruct::New(123, MakeRect(5), std::string("hello"))); + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructV5Ptr input(MultiVersionStructV5::New( + 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8})); + MultiVersionStructPtr expected_output(MultiVersionStruct::New( + 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8})); + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MessagePipe pipe; + MultiVersionStructV7Ptr input(MultiVersionStructV7::New( + 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8}, + std::move(pipe.handle0), false)); + + MultiVersionStructPtr expected_output(MultiVersionStruct::New( + 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8})); + // Save the raw handle value separately so that we can compare later. + MojoHandle expected_handle = input->f_message_pipe.get().value(); + + MultiVersionStructPtr output = + SerializeAndDeserialize<MultiVersionStructPtr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_EQ(expected_handle, output->f_message_pipe.get().value()); + output->f_message_pipe.reset(); + EXPECT_TRUE(output->Equals(*expected_output)); + } +} + +// Tests deserializing structs as an older version. +TEST_F(StructTest, Versioning_NewToOld) { + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV7Ptr expected_output(MultiVersionStructV7::New( + 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8})); + // Save the raw handle value separately so that we can compare later. + MojoHandle expected_handle = input->f_message_pipe.get().value(); + + MultiVersionStructV7Ptr output = + SerializeAndDeserialize<MultiVersionStructV7Ptr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_EQ(expected_handle, output->f_message_pipe.get().value()); + output->f_message_pipe.reset(); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV5Ptr expected_output(MultiVersionStructV5::New( + 123, MakeRect(5), std::string("hello"), std::vector<int8_t>{10, 9, 8})); + + MultiVersionStructV5Ptr output = + SerializeAndDeserialize<MultiVersionStructV5Ptr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV3Ptr expected_output( + MultiVersionStructV3::New(123, MakeRect(5), std::string("hello"))); + + MultiVersionStructV3Ptr output = + SerializeAndDeserialize<MultiVersionStructV3Ptr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV1Ptr expected_output( + MultiVersionStructV1::New(123, MakeRect(5))); + + MultiVersionStructV1Ptr output = + SerializeAndDeserialize<MultiVersionStructV1Ptr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } + + { + MultiVersionStructPtr input = MakeMultiVersionStruct(); + MultiVersionStructV0Ptr expected_output(MultiVersionStructV0::New(123)); + + MultiVersionStructV0Ptr output = + SerializeAndDeserialize<MultiVersionStructV0Ptr>(std::move(input)); + EXPECT_TRUE(output); + EXPECT_TRUE(output->Equals(*expected_output)); + } +} + +// Serialization test for native struct. +TEST_F(StructTest, Serialization_NativeStruct) { + using Data = mojo::internal::NativeStruct_Data; + { + // Serialization of a null native struct. + NativeStructPtr native; + size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>( + native, nullptr); + EXPECT_EQ(0u, size); + mojo::internal::FixedBufferForTesting buf(size); + + Data* data = nullptr; + mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf, + &data, nullptr); + + EXPECT_EQ(nullptr, data); + + NativeStructPtr output_native; + mojo::internal::Deserialize<NativeStructDataView>(data, &output_native, + nullptr); + EXPECT_TRUE(output_native.is_null()); + } + + { + // Serialization of a native struct with null data. + NativeStructPtr native(NativeStruct::New()); + size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>( + native, nullptr); + EXPECT_EQ(0u, size); + mojo::internal::FixedBufferForTesting buf(size); + + Data* data = nullptr; + mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf, + &data, nullptr); + + EXPECT_EQ(nullptr, data); + + NativeStructPtr output_native; + mojo::internal::Deserialize<NativeStructDataView>(data, &output_native, + nullptr); + EXPECT_TRUE(output_native.is_null()); + } + + { + NativeStructPtr native(NativeStruct::New()); + native->data = std::vector<uint8_t>{'X', 'Y'}; + + size_t size = mojo::internal::PrepareToSerialize<NativeStructDataView>( + native, nullptr); + EXPECT_EQ(16u, size); + mojo::internal::FixedBufferForTesting buf(size); + + Data* data = nullptr; + mojo::internal::Serialize<NativeStructDataView>(std::move(native), &buf, + &data, nullptr); + + EXPECT_NE(nullptr, data); + + NativeStructPtr output_native; + mojo::internal::Deserialize<NativeStructDataView>(data, &output_native, + nullptr); + ASSERT_TRUE(output_native); + ASSERT_FALSE(output_native->data->empty()); + EXPECT_EQ(2u, output_native->data->size()); + EXPECT_EQ('X', (*output_native->data)[0]); + EXPECT_EQ('Y', (*output_native->data)[1]); + } +} + +TEST_F(StructTest, Serialization_PublicAPI) { + { + // A null struct pointer. + RectPtr null_struct; + auto data = Rect::Serialize(&null_struct); + EXPECT_TRUE(data.empty()); + + // Initialize it to non-null. + RectPtr output(Rect::New()); + ASSERT_TRUE(Rect::Deserialize(data, &output)); + EXPECT_TRUE(output.is_null()); + } + + { + // A struct with no fields. + EmptyStructPtr empty_struct(EmptyStruct::New()); + auto data = EmptyStruct::Serialize(&empty_struct); + EXPECT_FALSE(data.empty()); + + EmptyStructPtr output; + ASSERT_TRUE(EmptyStruct::Deserialize(data, &output)); + EXPECT_FALSE(output.is_null()); + } + + { + // A simple struct. + RectPtr rect = MakeRect(); + RectPtr cloned_rect = rect.Clone(); + auto data = Rect::Serialize(&rect); + + RectPtr output; + ASSERT_TRUE(Rect::Deserialize(data, &output)); + EXPECT_TRUE(output.Equals(cloned_rect)); + } + + { + // A struct containing other objects. + std::vector<RectPtr> rects; + for (size_t i = 0; i < 3; ++i) + rects.push_back(MakeRect(static_cast<int32_t>(i) + 1)); + NamedRegionPtr region( + NamedRegion::New(std::string("region"), std::move(rects))); + + NamedRegionPtr cloned_region = region.Clone(); + auto data = NamedRegion::Serialize(®ion); + + // Make sure that the serialized result gets pointers encoded properly. + auto cloned_data = data; + NamedRegionPtr output; + ASSERT_TRUE(NamedRegion::Deserialize(cloned_data, &output)); + EXPECT_TRUE(output.Equals(cloned_region)); + } + + { + // Deserialization failure. + RectPtr rect = MakeRect(); + auto data = Rect::Serialize(&rect); + + NamedRegionPtr output; + EXPECT_FALSE(NamedRegion::Deserialize(data, &output)); + } + + { + // A struct from another component. + auto pair = test_export2::StringPair::New("hello", "world"); + auto data = test_export2::StringPair::Serialize(&pair); + + test_export2::StringPairPtr output; + ASSERT_TRUE(test_export2::StringPair::Deserialize(data, &output)); + EXPECT_TRUE(output.Equals(pair)); + } +} + +TEST_F(StructTest, VersionedStructConstructor) { + auto reordered = ReorderedStruct::New(123, 456, 789); + EXPECT_EQ(123, reordered->a); + EXPECT_EQ(456, reordered->b); + EXPECT_EQ(789, reordered->c); + + reordered = ReorderedStruct::New(123, 456); + EXPECT_EQ(123, reordered->a); + EXPECT_EQ(6, reordered->b); + EXPECT_EQ(456, reordered->c); + + reordered = ReorderedStruct::New(123); + EXPECT_EQ(3, reordered->a); + EXPECT_EQ(6, reordered->b); + EXPECT_EQ(123, reordered->c); + + reordered = ReorderedStruct::New(); + EXPECT_EQ(3, reordered->a); + EXPECT_EQ(6, reordered->b); + EXPECT_EQ(1, reordered->c); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits.typemap b/mojo/public/cpp/bindings/tests/struct_with_traits.typemap new file mode 100644 index 0000000000..752ce44b58 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits.typemap @@ -0,0 +1,26 @@ +# 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. + +mojom = "//mojo/public/interfaces/bindings/tests/struct_with_traits.mojom" +public_headers = + [ "//mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" ] +traits_headers = + [ "//mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h" ] +sources = [ + "//mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc", +] +deps = [ + "//mojo/public/cpp/bindings/tests:struct_with_traits_impl", + "//mojo/public/cpp/system:system", +] + +type_mappings = [ + "mojo.test.EnumWithTraits=mojo::test::EnumWithTraitsImpl", + "mojo.test.StructWithTraits=mojo::test::StructWithTraitsImpl", + "mojo.test.NestedStructWithTraits=mojo::test::NestedStructWithTraitsImpl", + "mojo.test.TrivialStructWithTraits=mojo::test::TrivialStructWithTraitsImpl[copyable_pass_by_value]", + "mojo.test.MoveOnlyStructWithTraits=mojo::test::MoveOnlyStructWithTraitsImpl[move_only]", + "mojo.test.StructWithTraitsForUniquePtr=std::unique_ptr<int>[move_only,nullable_is_same_type]", + "mojo.test.UnionWithTraits=std::unique_ptr<mojo::test::UnionWithTraitsBase>[move_only,nullable_is_same_type]", +] diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc new file mode 100644 index 0000000000..cbdd4bfde7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc @@ -0,0 +1,36 @@ +// 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. + +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" + +namespace mojo { +namespace test { + +NestedStructWithTraitsImpl::NestedStructWithTraitsImpl() {} +NestedStructWithTraitsImpl::NestedStructWithTraitsImpl(int32_t in_value) + : value(in_value) {} + +StructWithTraitsImpl::StructWithTraitsImpl() {} + +StructWithTraitsImpl::~StructWithTraitsImpl() {} + +StructWithTraitsImpl::StructWithTraitsImpl(const StructWithTraitsImpl& other) = + default; + +MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl() {} + +MoveOnlyStructWithTraitsImpl::MoveOnlyStructWithTraitsImpl( + MoveOnlyStructWithTraitsImpl&& other) = default; + +MoveOnlyStructWithTraitsImpl::~MoveOnlyStructWithTraitsImpl() {} + +MoveOnlyStructWithTraitsImpl& MoveOnlyStructWithTraitsImpl::operator=( + MoveOnlyStructWithTraitsImpl&& other) = default; + +UnionWithTraitsInt32::~UnionWithTraitsInt32() {} + +UnionWithTraitsStruct::~UnionWithTraitsStruct() {} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h new file mode 100644 index 0000000000..7b007cc083 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl.h @@ -0,0 +1,168 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_ + +#include <stdint.h> + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { +namespace test { + +struct NestedStructWithTraitsImpl { + public: + NestedStructWithTraitsImpl(); + explicit NestedStructWithTraitsImpl(int32_t in_value); + + bool operator==(const NestedStructWithTraitsImpl& other) const { + return value == other.value; + } + + int32_t value = 0; +}; + +enum class EnumWithTraitsImpl { CUSTOM_VALUE_0 = 10, CUSTOM_VALUE_1 = 11 }; + +// A type which knows how to look like a mojo::test::StructWithTraits mojom type +// by way of mojo::StructTraits. +class StructWithTraitsImpl { + public: + StructWithTraitsImpl(); + ~StructWithTraitsImpl(); + + StructWithTraitsImpl(const StructWithTraitsImpl& other); + + void set_enum(EnumWithTraitsImpl value) { enum_ = value; } + EnumWithTraitsImpl get_enum() const { return enum_; } + + void set_bool(bool value) { bool_ = value; } + bool get_bool() const { return bool_; } + + void set_uint32(uint32_t value) { uint32_ = value; } + uint32_t get_uint32() const { return uint32_; } + + void set_uint64(uint64_t value) { uint64_ = value; } + uint64_t get_uint64() const { return uint64_; } + + void set_string(std::string value) { string_ = value; } + base::StringPiece get_string_as_string_piece() const { return string_; } + const std::string& get_string() const { return string_; } + + const std::vector<std::string>& get_string_array() const { + return string_array_; + } + std::vector<std::string>& get_mutable_string_array() { return string_array_; } + + const std::set<std::string>& get_string_set() const { + return string_set_; + } + std::set<std::string>& get_mutable_string_set() { return string_set_; } + + const NestedStructWithTraitsImpl& get_struct() const { return struct_; } + NestedStructWithTraitsImpl& get_mutable_struct() { return struct_; } + + const std::vector<NestedStructWithTraitsImpl>& get_struct_array() const { + return struct_array_; + } + std::vector<NestedStructWithTraitsImpl>& get_mutable_struct_array() { + return struct_array_; + } + + const std::map<std::string, NestedStructWithTraitsImpl>& get_struct_map() + const { + return struct_map_; + } + std::map<std::string, NestedStructWithTraitsImpl>& get_mutable_struct_map() { + return struct_map_; + } + + private: + EnumWithTraitsImpl enum_ = EnumWithTraitsImpl::CUSTOM_VALUE_0; + bool bool_ = false; + uint32_t uint32_ = 0; + uint64_t uint64_ = 0; + std::string string_; + std::vector<std::string> string_array_; + std::set<std::string> string_set_; + NestedStructWithTraitsImpl struct_; + std::vector<NestedStructWithTraitsImpl> struct_array_; + std::map<std::string, NestedStructWithTraitsImpl> struct_map_; +}; + +// A type which knows how to look like a mojo::test::TrivialStructWithTraits +// mojom type by way of mojo::StructTraits. +struct TrivialStructWithTraitsImpl { + int32_t value; +}; + +// A type which knows how to look like a mojo::test::MoveOnlyStructWithTraits +// mojom type by way of mojo::StructTraits. +class MoveOnlyStructWithTraitsImpl { + public: + MoveOnlyStructWithTraitsImpl(); + MoveOnlyStructWithTraitsImpl(MoveOnlyStructWithTraitsImpl&& other); + ~MoveOnlyStructWithTraitsImpl(); + + ScopedHandle& get_mutable_handle() { return handle_; } + + MoveOnlyStructWithTraitsImpl& operator=(MoveOnlyStructWithTraitsImpl&& other); + + private: + ScopedHandle handle_; + DISALLOW_COPY_AND_ASSIGN(MoveOnlyStructWithTraitsImpl); +}; + +class UnionWithTraitsBase { + public: + enum class Type { INT32, STRUCT }; + + virtual ~UnionWithTraitsBase() {} + + Type type() const { return type_; } + + protected: + Type type_ = Type::INT32; +}; + +class UnionWithTraitsInt32 : public UnionWithTraitsBase { + public: + UnionWithTraitsInt32() {} + explicit UnionWithTraitsInt32(int32_t value) : value_(value) {} + + ~UnionWithTraitsInt32() override; + + int32_t value() const { return value_; } + void set_value(int32_t value) { value_ = value; } + + private: + int32_t value_ = 0; +}; + +class UnionWithTraitsStruct : public UnionWithTraitsBase { + public: + UnionWithTraitsStruct() { type_ = Type::STRUCT; } + explicit UnionWithTraitsStruct(int32_t value) : struct_(value) { + type_ = Type::STRUCT; + } + ~UnionWithTraitsStruct() override; + + NestedStructWithTraitsImpl& get_mutable_struct() { return struct_; } + const NestedStructWithTraitsImpl& get_struct() const { return struct_; } + + private: + NestedStructWithTraitsImpl struct_; +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_H_ diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc new file mode 100644 index 0000000000..6b770b1a49 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc @@ -0,0 +1,137 @@ +// 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. + +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h" + +namespace mojo { +namespace { + +struct Context { + int32_t value; +}; + +} // namespace + +// static +void* StructTraits<test::NestedStructWithTraitsDataView, + test::NestedStructWithTraitsImpl>:: + SetUpContext(const test::NestedStructWithTraitsImpl& input) { + Context* context = new Context; + context->value = input.value; + return context; +} + +// static +void StructTraits<test::NestedStructWithTraitsDataView, + test::NestedStructWithTraitsImpl>:: + TearDownContext(const test::NestedStructWithTraitsImpl& input, + void* context) { + Context* context_obj = static_cast<Context*>(context); + CHECK_EQ(context_obj->value, input.value); + delete context_obj; +} + +// static +int32_t StructTraits<test::NestedStructWithTraitsDataView, + test::NestedStructWithTraitsImpl>:: + value(const test::NestedStructWithTraitsImpl& input, void* context) { + Context* context_obj = static_cast<Context*>(context); + CHECK_EQ(context_obj->value, input.value); + return input.value; +} + +// static +bool StructTraits<test::NestedStructWithTraitsDataView, + test::NestedStructWithTraitsImpl>:: + Read(test::NestedStructWithTraits::DataView data, + test::NestedStructWithTraitsImpl* output) { + output->value = data.value(); + return true; +} + +test::EnumWithTraits +EnumTraits<test::EnumWithTraits, test::EnumWithTraitsImpl>::ToMojom( + test::EnumWithTraitsImpl input) { + switch (input) { + case test::EnumWithTraitsImpl::CUSTOM_VALUE_0: + return test::EnumWithTraits::VALUE_0; + case test::EnumWithTraitsImpl::CUSTOM_VALUE_1: + return test::EnumWithTraits::VALUE_1; + }; + + NOTREACHED(); + return test::EnumWithTraits::VALUE_0; +} + +bool EnumTraits<test::EnumWithTraits, test::EnumWithTraitsImpl>::FromMojom( + test::EnumWithTraits input, + test::EnumWithTraitsImpl* output) { + switch (input) { + case test::EnumWithTraits::VALUE_0: + *output = test::EnumWithTraitsImpl::CUSTOM_VALUE_0; + return true; + case test::EnumWithTraits::VALUE_1: + *output = test::EnumWithTraitsImpl::CUSTOM_VALUE_1; + return true; + }; + + return false; +} + +// static +bool StructTraits<test::StructWithTraitsDataView, test::StructWithTraitsImpl>:: + Read(test::StructWithTraits::DataView data, + test::StructWithTraitsImpl* out) { + test::EnumWithTraitsImpl f_enum; + if (!data.ReadFEnum(&f_enum)) + return false; + out->set_enum(f_enum); + + out->set_bool(data.f_bool()); + out->set_uint32(data.f_uint32()); + out->set_uint64(data.f_uint64()); + + base::StringPiece f_string; + std::string f_string2; + if (!data.ReadFString(&f_string) || !data.ReadFString2(&f_string2) || + f_string != f_string2) { + return false; + } + out->set_string(f_string2); + + if (!data.ReadFStringArray(&out->get_mutable_string_array())) + return false; + + // We can't deserialize as a std::set, so we have to manually copy from the + // data view. + ArrayDataView<StringDataView> string_set_data_view; + data.GetFStringSetDataView(&string_set_data_view); + for (size_t i = 0; i < string_set_data_view.size(); ++i) { + std::string value; + string_set_data_view.Read(i, &value); + out->get_mutable_string_set().insert(value); + } + + if (!data.ReadFStruct(&out->get_mutable_struct())) + return false; + + if (!data.ReadFStructArray(&out->get_mutable_struct_array())) + return false; + + if (!data.ReadFStructMap(&out->get_mutable_struct_map())) + return false; + + return true; +} + +// static +bool StructTraits<test::MoveOnlyStructWithTraitsDataView, + test::MoveOnlyStructWithTraitsImpl>:: + Read(test::MoveOnlyStructWithTraits::DataView data, + test::MoveOnlyStructWithTraitsImpl* out) { + out->get_mutable_handle() = data.TakeFHandle(); + return true; +} + +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h new file mode 100644 index 0000000000..adcad8aa9e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h @@ -0,0 +1,196 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include "base/strings/string_piece.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/tests/struct_with_traits_impl.h" +#include "mojo/public/interfaces/bindings/tests/struct_with_traits.mojom.h" + +namespace mojo { + +template <> +struct StructTraits<test::NestedStructWithTraitsDataView, + test::NestedStructWithTraitsImpl> { + static void* SetUpContext(const test::NestedStructWithTraitsImpl& input); + static void TearDownContext(const test::NestedStructWithTraitsImpl& input, + void* context); + + static int32_t value(const test::NestedStructWithTraitsImpl& input, + void* context); + + static bool Read(test::NestedStructWithTraitsDataView data, + test::NestedStructWithTraitsImpl* output); +}; + +template <> +struct EnumTraits<test::EnumWithTraits, test::EnumWithTraitsImpl> { + static test::EnumWithTraits ToMojom(test::EnumWithTraitsImpl input); + static bool FromMojom(test::EnumWithTraits input, + test::EnumWithTraitsImpl* output); +}; + +template <> +struct StructTraits<test::StructWithTraitsDataView, + test::StructWithTraitsImpl> { + // Deserialization to test::StructTraitsImpl. + static bool Read(test::StructWithTraitsDataView data, + test::StructWithTraitsImpl* out); + + // Fields in test::StructWithTraits. + // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom. + static test::EnumWithTraitsImpl f_enum( + const test::StructWithTraitsImpl& value) { + return value.get_enum(); + } + + static bool f_bool(const test::StructWithTraitsImpl& value) { + return value.get_bool(); + } + + static uint32_t f_uint32(const test::StructWithTraitsImpl& value) { + return value.get_uint32(); + } + + static uint64_t f_uint64(const test::StructWithTraitsImpl& value) { + return value.get_uint64(); + } + + static base::StringPiece f_string(const test::StructWithTraitsImpl& value) { + return value.get_string_as_string_piece(); + } + + static const std::string& f_string2(const test::StructWithTraitsImpl& value) { + return value.get_string(); + } + + static const std::vector<std::string>& f_string_array( + const test::StructWithTraitsImpl& value) { + return value.get_string_array(); + } + + static const std::set<std::string>& f_string_set( + const test::StructWithTraitsImpl& value) { + return value.get_string_set(); + } + + static const test::NestedStructWithTraitsImpl& f_struct( + const test::StructWithTraitsImpl& value) { + return value.get_struct(); + } + + static const std::vector<test::NestedStructWithTraitsImpl>& f_struct_array( + const test::StructWithTraitsImpl& value) { + return value.get_struct_array(); + } + + static const std::map<std::string, test::NestedStructWithTraitsImpl>& + f_struct_map(const test::StructWithTraitsImpl& value) { + return value.get_struct_map(); + } +}; + +template <> +struct StructTraits<test::TrivialStructWithTraitsDataView, + test::TrivialStructWithTraitsImpl> { + // Deserialization to test::TrivialStructTraitsImpl. + static bool Read(test::TrivialStructWithTraitsDataView data, + test::TrivialStructWithTraitsImpl* out) { + out->value = data.value(); + return true; + } + + // Fields in test::TrivialStructWithTraits. + // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom. + static int32_t value(test::TrivialStructWithTraitsImpl& input) { + return input.value; + } +}; + +template <> +struct StructTraits<test::MoveOnlyStructWithTraitsDataView, + test::MoveOnlyStructWithTraitsImpl> { + // Deserialization to test::MoveOnlyStructTraitsImpl. + static bool Read(test::MoveOnlyStructWithTraitsDataView data, + test::MoveOnlyStructWithTraitsImpl* out); + + // Fields in test::MoveOnlyStructWithTraits. + // See src/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom. + static ScopedHandle f_handle(test::MoveOnlyStructWithTraitsImpl& value) { + return std::move(value.get_mutable_handle()); + } +}; + +template <> +struct StructTraits<test::StructWithTraitsForUniquePtrDataView, + std::unique_ptr<int>> { + static bool IsNull(const std::unique_ptr<int>& data) { return !data; } + static void SetToNull(std::unique_ptr<int>* data) { data->reset(); } + + static int f_int32(const std::unique_ptr<int>& data) { return *data; } + + static bool Read(test::StructWithTraitsForUniquePtrDataView data, + std::unique_ptr<int>* out) { + out->reset(new int(data.f_int32())); + return true; + } +}; + +template <> +struct UnionTraits<test::UnionWithTraitsDataView, + std::unique_ptr<test::UnionWithTraitsBase>> { + static bool IsNull(const std::unique_ptr<test::UnionWithTraitsBase>& data) { + return !data; + } + static void SetToNull(std::unique_ptr<test::UnionWithTraitsBase>* data) { + data->reset(); + } + + static test::UnionWithTraitsDataView::Tag GetTag( + const std::unique_ptr<test::UnionWithTraitsBase>& data) { + if (data->type() == test::UnionWithTraitsBase::Type::INT32) + return test::UnionWithTraitsDataView::Tag::F_INT32; + + return test::UnionWithTraitsDataView::Tag::F_STRUCT; + } + + static int32_t f_int32( + const std::unique_ptr<test::UnionWithTraitsBase>& data) { + return static_cast<test::UnionWithTraitsInt32*>(data.get())->value(); + } + + static const test::NestedStructWithTraitsImpl& f_struct( + const std::unique_ptr<test::UnionWithTraitsBase>& data) { + return static_cast<test::UnionWithTraitsStruct*>(data.get())->get_struct(); + } + + static bool Read(test::UnionWithTraitsDataView data, + std::unique_ptr<test::UnionWithTraitsBase>* out) { + switch (data.tag()) { + case test::UnionWithTraitsDataView::Tag::F_INT32: { + out->reset(new test::UnionWithTraitsInt32(data.f_int32())); + return true; + } + case test::UnionWithTraitsDataView::Tag::F_STRUCT: { + auto* struct_object = new test::UnionWithTraitsStruct(); + out->reset(struct_object); + return data.ReadFStruct(&struct_object->get_mutable_struct()); + } + } + + NOTREACHED(); + return false; + } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_STRUCT_WITH_TRAITS_IMPL_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/tests/sync_method_unittest.cc b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc new file mode 100644 index 0000000000..084e080ad3 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/sync_method_unittest.cc @@ -0,0 +1,831 @@ +// 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. + +#include <utility> + +#include "base/bind.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread.h" +#include "mojo/public/cpp/bindings/associated_binding.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/interfaces/bindings/tests/test_sync_methods.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template <typename... Args> +struct LambdaBinder { + using CallbackType = base::Callback<void(Args...)>; + + template <typename Func> + static void RunLambda(Func func, Args... args) { + func(std::move(args)...); + } + + template <typename Func> + static CallbackType BindLambda(Func func) { + return base::Bind(&LambdaBinder::RunLambda<Func>, func); + } +}; + +class TestSyncCommonImpl { + public: + TestSyncCommonImpl() {} + + using PingHandler = base::Callback<void(const base::Callback<void()>&)>; + using PingBinder = LambdaBinder<const base::Callback<void()>&>; + template <typename Func> + void set_ping_handler(Func handler) { + ping_handler_ = PingBinder::BindLambda(handler); + } + + using EchoHandler = + base::Callback<void(int32_t, const base::Callback<void(int32_t)>&)>; + using EchoBinder = + LambdaBinder<int32_t, const base::Callback<void(int32_t)>&>; + template <typename Func> + void set_echo_handler(Func handler) { + echo_handler_ = EchoBinder::BindLambda(handler); + } + + using AsyncEchoHandler = + base::Callback<void(int32_t, const base::Callback<void(int32_t)>&)>; + using AsyncEchoBinder = + LambdaBinder<int32_t, const base::Callback<void(int32_t)>&>; + template <typename Func> + void set_async_echo_handler(Func handler) { + async_echo_handler_ = AsyncEchoBinder::BindLambda(handler); + } + + using SendInterfaceHandler = base::Callback<void(TestSyncAssociatedPtrInfo)>; + using SendInterfaceBinder = LambdaBinder<TestSyncAssociatedPtrInfo>; + template <typename Func> + void set_send_interface_handler(Func handler) { + send_interface_handler_ = SendInterfaceBinder::BindLambda(handler); + } + + using SendRequestHandler = base::Callback<void(TestSyncAssociatedRequest)>; + using SendRequestBinder = LambdaBinder<TestSyncAssociatedRequest>; + template <typename Func> + void set_send_request_handler(Func handler) { + send_request_handler_ = SendRequestBinder::BindLambda(handler); + } + + void PingImpl(const base::Callback<void()>& callback) { + if (ping_handler_.is_null()) { + callback.Run(); + return; + } + ping_handler_.Run(callback); + } + void EchoImpl(int32_t value, const base::Callback<void(int32_t)>& callback) { + if (echo_handler_.is_null()) { + callback.Run(value); + return; + } + echo_handler_.Run(value, callback); + } + void AsyncEchoImpl(int32_t value, + const base::Callback<void(int32_t)>& callback) { + if (async_echo_handler_.is_null()) { + callback.Run(value); + return; + } + async_echo_handler_.Run(value, callback); + } + void SendInterfaceImpl(TestSyncAssociatedPtrInfo ptr) { + send_interface_handler_.Run(std::move(ptr)); + } + void SendRequestImpl(TestSyncAssociatedRequest request) { + send_request_handler_.Run(std::move(request)); + } + + private: + PingHandler ping_handler_; + EchoHandler echo_handler_; + AsyncEchoHandler async_echo_handler_; + SendInterfaceHandler send_interface_handler_; + SendRequestHandler send_request_handler_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncCommonImpl); +}; + +class TestSyncImpl : public TestSync, public TestSyncCommonImpl { + public: + explicit TestSyncImpl(TestSyncRequest request) + : binding_(this, std::move(request)) {} + + // TestSync implementation: + void Ping(const PingCallback& callback) override { PingImpl(callback); } + void Echo(int32_t value, const EchoCallback& callback) override { + EchoImpl(value, callback); + } + void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override { + AsyncEchoImpl(value, callback); + } + + Binding<TestSync>* binding() { return &binding_; } + + private: + Binding<TestSync> binding_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncImpl); +}; + +class TestSyncMasterImpl : public TestSyncMaster, public TestSyncCommonImpl { + public: + explicit TestSyncMasterImpl(TestSyncMasterRequest request) + : binding_(this, std::move(request)) {} + + // TestSyncMaster implementation: + void Ping(const PingCallback& callback) override { PingImpl(callback); } + void Echo(int32_t value, const EchoCallback& callback) override { + EchoImpl(value, callback); + } + void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override { + AsyncEchoImpl(value, callback); + } + void SendInterface(TestSyncAssociatedPtrInfo ptr) override { + SendInterfaceImpl(std::move(ptr)); + } + void SendRequest(TestSyncAssociatedRequest request) override { + SendRequestImpl(std::move(request)); + } + + Binding<TestSyncMaster>* binding() { return &binding_; } + + private: + Binding<TestSyncMaster> binding_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncMasterImpl); +}; + +class TestSyncAssociatedImpl : public TestSync, public TestSyncCommonImpl { + public: + explicit TestSyncAssociatedImpl(TestSyncAssociatedRequest request) + : binding_(this, std::move(request)) {} + + // TestSync implementation: + void Ping(const PingCallback& callback) override { PingImpl(callback); } + void Echo(int32_t value, const EchoCallback& callback) override { + EchoImpl(value, callback); + } + void AsyncEcho(int32_t value, const AsyncEchoCallback& callback) override { + AsyncEchoImpl(value, callback); + } + + AssociatedBinding<TestSync>* binding() { return &binding_; } + + private: + AssociatedBinding<TestSync> binding_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncAssociatedImpl); +}; + +template <typename Interface> +struct ImplTraits; + +template <> +struct ImplTraits<TestSync> { + using Type = TestSyncImpl; +}; + +template <> +struct ImplTraits<TestSyncMaster> { + using Type = TestSyncMasterImpl; +}; + +template <typename Interface> +using ImplTypeFor = typename ImplTraits<Interface>::Type; + +// A wrapper for either an InterfacePtr or scoped_refptr<ThreadSafeInterfacePtr> +// that exposes the InterfacePtr interface. +template <typename Interface> +class PtrWrapper { + public: + explicit PtrWrapper(InterfacePtr<Interface> ptr) : ptr_(std::move(ptr)) {} + + explicit PtrWrapper( + scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr) + : thread_safe_ptr_(thread_safe_ptr) {} + + PtrWrapper(PtrWrapper&& other) = default; + + Interface* operator->() { + return thread_safe_ptr_ ? thread_safe_ptr_->get() : ptr_.get(); + } + + void set_connection_error_handler(const base::Closure& error_handler) { + DCHECK(!thread_safe_ptr_); + ptr_.set_connection_error_handler(error_handler); + } + + void reset() { + ptr_ = nullptr; + thread_safe_ptr_ = nullptr; + } + + private: + InterfacePtr<Interface> ptr_; + scoped_refptr<ThreadSafeInterfacePtr<Interface>> thread_safe_ptr_; + + DISALLOW_COPY_AND_ASSIGN(PtrWrapper); +}; + +// The type parameter for SyncMethodCommonTests for varying the Interface and +// whether to use InterfacePtr or ThreadSafeInterfacePtr. +template <typename InterfaceT, bool use_thread_safe_ptr> +struct TestParams { + using Interface = InterfaceT; + static const bool kIsThreadSafeInterfacePtrTest = use_thread_safe_ptr; + + static PtrWrapper<InterfaceT> Wrap(InterfacePtr<Interface> ptr) { + if (kIsThreadSafeInterfacePtrTest) { + return PtrWrapper<Interface>( + ThreadSafeInterfacePtr<Interface>::Create(std::move(ptr))); + } else { + return PtrWrapper<Interface>(std::move(ptr)); + } + } +}; + +template <typename Interface> +class TestSyncServiceThread { + public: + TestSyncServiceThread() + : thread_("TestSyncServiceThread"), ping_called_(false) { + thread_.Start(); + } + + void SetUp(InterfaceRequest<Interface> request) { + CHECK(thread_.task_runner()->BelongsToCurrentThread()); + impl_.reset(new ImplTypeFor<Interface>(std::move(request))); + impl_->set_ping_handler( + [this](const typename Interface::PingCallback& callback) { + { + base::AutoLock locker(lock_); + ping_called_ = true; + } + callback.Run(); + }); + } + + void TearDown() { + CHECK(thread_.task_runner()->BelongsToCurrentThread()); + impl_.reset(); + } + + base::Thread* thread() { return &thread_; } + bool ping_called() const { + base::AutoLock locker(lock_); + return ping_called_; + } + + private: + base::Thread thread_; + + std::unique_ptr<ImplTypeFor<Interface>> impl_; + + mutable base::Lock lock_; + bool ping_called_; + + DISALLOW_COPY_AND_ASSIGN(TestSyncServiceThread); +}; + +class SyncMethodTest : public testing::Test { + public: + SyncMethodTest() {} + ~SyncMethodTest() override { base::RunLoop().RunUntilIdle(); } + + protected: + base::MessageLoop loop_; +}; + +template <typename T> +class SyncMethodCommonTest : public SyncMethodTest { + public: + SyncMethodCommonTest() {} + ~SyncMethodCommonTest() override {} +}; + +class SyncMethodAssociatedTest : public SyncMethodTest { + public: + SyncMethodAssociatedTest() {} + ~SyncMethodAssociatedTest() override {} + + protected: + void SetUp() override { + master_impl_.reset(new TestSyncMasterImpl(MakeRequest(&master_ptr_))); + + asso_request_ = MakeRequest(&asso_ptr_info_); + opposite_asso_request_ = MakeRequest(&opposite_asso_ptr_info_); + + master_impl_->set_send_interface_handler( + [this](TestSyncAssociatedPtrInfo ptr) { + opposite_asso_ptr_info_ = std::move(ptr); + }); + base::RunLoop run_loop; + master_impl_->set_send_request_handler( + [this, &run_loop](TestSyncAssociatedRequest request) { + asso_request_ = std::move(request); + run_loop.Quit(); + }); + + master_ptr_->SendInterface(std::move(opposite_asso_ptr_info_)); + master_ptr_->SendRequest(std::move(asso_request_)); + run_loop.Run(); + } + + void TearDown() override { + asso_ptr_info_ = TestSyncAssociatedPtrInfo(); + asso_request_ = TestSyncAssociatedRequest(); + opposite_asso_ptr_info_ = TestSyncAssociatedPtrInfo(); + opposite_asso_request_ = TestSyncAssociatedRequest(); + + master_ptr_ = nullptr; + master_impl_.reset(); + } + + InterfacePtr<TestSyncMaster> master_ptr_; + std::unique_ptr<TestSyncMasterImpl> master_impl_; + + // An associated interface whose binding lives at the |master_impl_| side. + TestSyncAssociatedPtrInfo asso_ptr_info_; + TestSyncAssociatedRequest asso_request_; + + // An associated interface whose binding lives at the |master_ptr_| side. + TestSyncAssociatedPtrInfo opposite_asso_ptr_info_; + TestSyncAssociatedRequest opposite_asso_request_; +}; + +void SetFlagAndRunClosure(bool* flag, const base::Closure& closure) { + *flag = true; + closure.Run(); +} + +void ExpectValueAndRunClosure(int32_t expected_value, + const base::Closure& closure, + int32_t value) { + EXPECT_EQ(expected_value, value); + closure.Run(); +} + +template <typename Func> +void CallAsyncEchoCallback(Func func, int32_t value) { + func(value); +} + +template <typename Func> +TestSync::AsyncEchoCallback BindAsyncEchoCallback(Func func) { + return base::Bind(&CallAsyncEchoCallback<Func>, func); +} + +// TestSync (without associated interfaces) and TestSyncMaster (with associated +// interfaces) exercise MultiplexRouter with different configurations. +// Each test is run once with an InterfacePtr and once with a +// ThreadSafeInterfacePtr to ensure that they behave the same with respect to +// sync calls. +using InterfaceTypes = testing::Types<TestParams<TestSync, true>, + TestParams<TestSync, false>, + TestParams<TestSyncMaster, true>, + TestParams<TestSyncMaster, false>>; +TYPED_TEST_CASE(SyncMethodCommonTest, InterfaceTypes); + +TYPED_TEST(SyncMethodCommonTest, CallSyncMethodAsynchronously) { + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + base::RunLoop run_loop; + ptr->Echo(123, base::Bind(&ExpectValueAndRunClosure, 123, + run_loop.QuitClosure())); + run_loop.Run(); +} + +TYPED_TEST(SyncMethodCommonTest, BasicSyncCalls) { + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + InterfaceRequest<Interface> request = MakeRequest(&interface_ptr); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + TestSyncServiceThread<Interface> service_thread; + service_thread.thread()->task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestSyncServiceThread<Interface>::SetUp, + base::Unretained(&service_thread), base::Passed(&request))); + ASSERT_TRUE(ptr->Ping()); + ASSERT_TRUE(service_thread.ping_called()); + + int32_t output_value = -1; + ASSERT_TRUE(ptr->Echo(42, &output_value)); + ASSERT_EQ(42, output_value); + + base::RunLoop run_loop; + service_thread.thread()->task_runner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&TestSyncServiceThread<Interface>::TearDown, + base::Unretained(&service_thread)), + run_loop.QuitClosure()); + run_loop.Run(); +} + +TYPED_TEST(SyncMethodCommonTest, ReenteredBySyncMethodBinding) { + // Test that an interface pointer waiting for a sync call response can be + // reentered by a binding serving sync methods on the same thread. + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + // The binding lives on the same thread as the interface pointer. + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + int32_t output_value = -1; + ASSERT_TRUE(ptr->Echo(42, &output_value)); + EXPECT_EQ(42, output_value); +} + +TYPED_TEST(SyncMethodCommonTest, InterfacePtrDestroyedDuringSyncCall) { + // Test that it won't result in crash or hang if an interface pointer is + // destroyed while it is waiting for a sync call response. + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + impl.set_ping_handler([&ptr](const TestSync::PingCallback& callback) { + ptr.reset(); + callback.Run(); + }); + ASSERT_FALSE(ptr->Ping()); +} + +TYPED_TEST(SyncMethodCommonTest, BindingDestroyedDuringSyncCall) { + // Test that it won't result in crash or hang if a binding is + // closed (and therefore the message pipe handle is closed) while the + // corresponding interface pointer is waiting for a sync call response. + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + impl.set_ping_handler([&impl](const TestSync::PingCallback& callback) { + impl.binding()->Close(); + callback.Run(); + }); + ASSERT_FALSE(ptr->Ping()); +} + +TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithInOrderResponses) { + // Test that we can call a sync method on an interface ptr, while there is + // already a sync call ongoing. The responses arrive in order. + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + // The same variable is used to store the output of the two sync calls, in + // order to test that responses are handled in the correct order. + int32_t result_value = -1; + + bool first_call = true; + impl.set_echo_handler([&first_call, &ptr, &result_value]( + int32_t value, const TestSync::EchoCallback& callback) { + if (first_call) { + first_call = false; + ASSERT_TRUE(ptr->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); + } + callback.Run(value); + }); + + ASSERT_TRUE(ptr->Echo(123, &result_value)); + EXPECT_EQ(123, result_value); +} + +TYPED_TEST(SyncMethodCommonTest, NestedSyncCallsWithOutOfOrderResponses) { + // Test that we can call a sync method on an interface ptr, while there is + // already a sync call ongoing. The responses arrive out of order. + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + // The same variable is used to store the output of the two sync calls, in + // order to test that responses are handled in the correct order. + int32_t result_value = -1; + + bool first_call = true; + impl.set_echo_handler([&first_call, &ptr, &result_value]( + int32_t value, const TestSync::EchoCallback& callback) { + callback.Run(value); + if (first_call) { + first_call = false; + ASSERT_TRUE(ptr->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); + } + }); + + ASSERT_TRUE(ptr->Echo(123, &result_value)); + EXPECT_EQ(123, result_value); +} + +TYPED_TEST(SyncMethodCommonTest, AsyncResponseQueuedDuringSyncCall) { + // Test that while an interface pointer is waiting for the response to a sync + // call, async responses are queued until the sync call completes. + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + int32_t async_echo_request_value = -1; + TestSync::AsyncEchoCallback async_echo_request_callback; + base::RunLoop run_loop1; + impl.set_async_echo_handler( + [&async_echo_request_value, &async_echo_request_callback, &run_loop1]( + int32_t value, const TestSync::AsyncEchoCallback& callback) { + async_echo_request_value = value; + async_echo_request_callback = callback; + run_loop1.Quit(); + }); + + bool async_echo_response_dispatched = false; + base::RunLoop run_loop2; + ptr->AsyncEcho( + 123, + BindAsyncEchoCallback( + [&async_echo_response_dispatched, &run_loop2](int32_t result) { + async_echo_response_dispatched = true; + EXPECT_EQ(123, result); + run_loop2.Quit(); + })); + // Run until the AsyncEcho request reaches the service side. + run_loop1.Run(); + + impl.set_echo_handler( + [&async_echo_request_value, &async_echo_request_callback]( + int32_t value, const TestSync::EchoCallback& callback) { + // Send back the async response first. + EXPECT_FALSE(async_echo_request_callback.is_null()); + async_echo_request_callback.Run(async_echo_request_value); + + callback.Run(value); + }); + + int32_t result_value = -1; + ASSERT_TRUE(ptr->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); + + // Although the AsyncEcho response arrives before the Echo response, it should + // be queued and not yet dispatched. + EXPECT_FALSE(async_echo_response_dispatched); + + // Run until the AsyncEcho response is dispatched. + run_loop2.Run(); + + EXPECT_TRUE(async_echo_response_dispatched); +} + +TYPED_TEST(SyncMethodCommonTest, AsyncRequestQueuedDuringSyncCall) { + // Test that while an interface pointer is waiting for the response to a sync + // call, async requests for a binding running on the same thread are queued + // until the sync call completes. + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> interface_ptr; + ImplTypeFor<Interface> impl(MakeRequest(&interface_ptr)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + bool async_echo_request_dispatched = false; + impl.set_async_echo_handler([&async_echo_request_dispatched]( + int32_t value, const TestSync::AsyncEchoCallback& callback) { + async_echo_request_dispatched = true; + callback.Run(value); + }); + + bool async_echo_response_dispatched = false; + base::RunLoop run_loop; + ptr->AsyncEcho( + 123, + BindAsyncEchoCallback( + [&async_echo_response_dispatched, &run_loop](int32_t result) { + async_echo_response_dispatched = true; + EXPECT_EQ(123, result); + run_loop.Quit(); + })); + + impl.set_echo_handler([&async_echo_request_dispatched]( + int32_t value, const TestSync::EchoCallback& callback) { + // Although the AsyncEcho request is sent before the Echo request, it + // shouldn't be dispatched yet at this point, because there is an ongoing + // sync call on the same thread. + EXPECT_FALSE(async_echo_request_dispatched); + callback.Run(value); + }); + + int32_t result_value = -1; + ASSERT_TRUE(ptr->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); + + // Although the AsyncEcho request is sent before the Echo request, it + // shouldn't be dispatched yet. + EXPECT_FALSE(async_echo_request_dispatched); + + // Run until the AsyncEcho response is dispatched. + run_loop.Run(); + + EXPECT_TRUE(async_echo_response_dispatched); +} + +TYPED_TEST(SyncMethodCommonTest, + QueuedMessagesProcessedBeforeErrorNotification) { + // Test that while an interface pointer is waiting for the response to a sync + // call, async responses are queued. If the message pipe is disconnected + // before the queued messages are processed, the connection error + // notification is delayed until all the queued messages are processed. + + // ThreadSafeInterfacePtr doesn't guarantee that messages are delivered before + // error notifications, so skip it for this test. + if (TypeParam::kIsThreadSafeInterfacePtrTest) + return; + + using Interface = typename TypeParam::Interface; + InterfacePtr<Interface> ptr; + ImplTypeFor<Interface> impl(MakeRequest(&ptr)); + + int32_t async_echo_request_value = -1; + TestSync::AsyncEchoCallback async_echo_request_callback; + base::RunLoop run_loop1; + impl.set_async_echo_handler( + [&async_echo_request_value, &async_echo_request_callback, &run_loop1]( + int32_t value, const TestSync::AsyncEchoCallback& callback) { + async_echo_request_value = value; + async_echo_request_callback = callback; + run_loop1.Quit(); + }); + + bool async_echo_response_dispatched = false; + bool connection_error_dispatched = false; + base::RunLoop run_loop2; + ptr->AsyncEcho( + 123, + BindAsyncEchoCallback( + [&async_echo_response_dispatched, &connection_error_dispatched, &ptr, + &run_loop2](int32_t result) { + async_echo_response_dispatched = true; + // At this point, error notification should not be dispatched + // yet. + EXPECT_FALSE(connection_error_dispatched); + EXPECT_FALSE(ptr.encountered_error()); + EXPECT_EQ(123, result); + run_loop2.Quit(); + })); + // Run until the AsyncEcho request reaches the service side. + run_loop1.Run(); + + impl.set_echo_handler( + [&impl, &async_echo_request_value, &async_echo_request_callback]( + int32_t value, const TestSync::EchoCallback& callback) { + // Send back the async response first. + EXPECT_FALSE(async_echo_request_callback.is_null()); + async_echo_request_callback.Run(async_echo_request_value); + + impl.binding()->Close(); + }); + + base::RunLoop run_loop3; + ptr.set_connection_error_handler( + base::Bind(&SetFlagAndRunClosure, &connection_error_dispatched, + run_loop3.QuitClosure())); + + int32_t result_value = -1; + ASSERT_FALSE(ptr->Echo(456, &result_value)); + EXPECT_EQ(-1, result_value); + ASSERT_FALSE(connection_error_dispatched); + EXPECT_FALSE(ptr.encountered_error()); + + // Although the AsyncEcho response arrives before the Echo response, it should + // be queued and not yet dispatched. + EXPECT_FALSE(async_echo_response_dispatched); + + // Run until the AsyncEcho response is dispatched. + run_loop2.Run(); + + EXPECT_TRUE(async_echo_response_dispatched); + + // Run until the error notification is dispatched. + run_loop3.Run(); + + ASSERT_TRUE(connection_error_dispatched); + EXPECT_TRUE(ptr.encountered_error()); +} + +TYPED_TEST(SyncMethodCommonTest, InvalidMessageDuringSyncCall) { + // Test that while an interface pointer is waiting for the response to a sync + // call, an invalid incoming message will disconnect the message pipe, cause + // the sync call to return false, and run the connection error handler + // asynchronously. + + using Interface = typename TypeParam::Interface; + MessagePipe pipe; + + InterfacePtr<Interface> interface_ptr; + interface_ptr.Bind(InterfacePtrInfo<Interface>(std::move(pipe.handle0), 0u)); + auto ptr = TypeParam::Wrap(std::move(interface_ptr)); + + MessagePipeHandle raw_binding_handle = pipe.handle1.get(); + ImplTypeFor<Interface> impl(MakeRequest<Interface>(std::move(pipe.handle1))); + + impl.set_echo_handler([&raw_binding_handle]( + int32_t value, const TestSync::EchoCallback& callback) { + // Write a 1-byte message, which is considered invalid. + char invalid_message = 0; + MojoResult result = + WriteMessageRaw(raw_binding_handle, &invalid_message, 1u, nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE); + ASSERT_EQ(MOJO_RESULT_OK, result); + callback.Run(value); + }); + + bool connection_error_dispatched = false; + base::RunLoop run_loop; + // ThreadSafeInterfacePtr doesn't support setting connection error handlers. + if (!TypeParam::kIsThreadSafeInterfacePtrTest) { + ptr.set_connection_error_handler(base::Bind(&SetFlagAndRunClosure, + &connection_error_dispatched, + run_loop.QuitClosure())); + } + + int32_t result_value = -1; + ASSERT_FALSE(ptr->Echo(456, &result_value)); + EXPECT_EQ(-1, result_value); + ASSERT_FALSE(connection_error_dispatched); + + if (!TypeParam::kIsThreadSafeInterfacePtrTest) { + run_loop.Run(); + ASSERT_TRUE(connection_error_dispatched); + } +} + +TEST_F(SyncMethodAssociatedTest, ReenteredBySyncMethodAssoBindingOfSameRouter) { + // Test that an interface pointer waiting for a sync call response can be + // reentered by an associated binding serving sync methods on the same thread. + // The associated binding belongs to the same MultiplexRouter as the waiting + // interface pointer. + + TestSyncAssociatedImpl opposite_asso_impl(std::move(opposite_asso_request_)); + TestSyncAssociatedPtr opposite_asso_ptr; + opposite_asso_ptr.Bind(std::move(opposite_asso_ptr_info_)); + + master_impl_->set_echo_handler([&opposite_asso_ptr]( + int32_t value, const TestSyncMaster::EchoCallback& callback) { + int32_t result_value = -1; + + ASSERT_TRUE(opposite_asso_ptr->Echo(123, &result_value)); + EXPECT_EQ(123, result_value); + callback.Run(value); + }); + + int32_t result_value = -1; + ASSERT_TRUE(master_ptr_->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); +} + +TEST_F(SyncMethodAssociatedTest, + ReenteredBySyncMethodAssoBindingOfDifferentRouter) { + // Test that an interface pointer waiting for a sync call response can be + // reentered by an associated binding serving sync methods on the same thread. + // The associated binding belongs to a different MultiplexRouter as the + // waiting interface pointer. + + TestSyncAssociatedImpl asso_impl(std::move(asso_request_)); + TestSyncAssociatedPtr asso_ptr; + asso_ptr.Bind(std::move(asso_ptr_info_)); + + master_impl_->set_echo_handler( + [&asso_ptr](int32_t value, const TestSyncMaster::EchoCallback& callback) { + int32_t result_value = -1; + + ASSERT_TRUE(asso_ptr->Echo(123, &result_value)); + EXPECT_EQ(123, result_value); + callback.Run(value); + }); + + int32_t result_value = -1; + ASSERT_TRUE(master_ptr_->Echo(456, &result_value)); + EXPECT_EQ(456, result_value); +} + +// TODO(yzshen): Add more tests related to associated interfaces. + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap b/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap new file mode 100644 index 0000000000..1bdfbbc1a7 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/test_native_types_blink.typemap @@ -0,0 +1,17 @@ +# 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. + +mojom = "//mojo/public/interfaces/bindings/tests/test_native_types.mojom" +public_headers = [ "//mojo/public/cpp/bindings/tests/pickled_types_blink.h" ] +sources = [ + "//mojo/public/cpp/bindings/tests/pickled_types_blink.cc", +] +deps = [ + "//ipc", +] + +type_mappings = [ + "mojo.test.PickledEnum=mojo::test::PickledEnumBlink", + "mojo.test.PickledStruct=mojo::test::PickledStructBlink[move_only]", +] diff --git a/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap b/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap new file mode 100644 index 0000000000..50e8076a50 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap @@ -0,0 +1,17 @@ +# 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. + +mojom = "//mojo/public/interfaces/bindings/tests/test_native_types.mojom" +public_headers = [ "//mojo/public/cpp/bindings/tests/pickled_types_chromium.h" ] +sources = [ + "//mojo/public/cpp/bindings/tests/pickled_types_chromium.cc", +] +deps = [ + "//ipc", +] + +type_mappings = [ + "mojo.test.PickledEnum=mojo::test::PickledEnumChromium", + "mojo.test.PickledStruct=mojo::test::PickledStructChromium[move_only]", +] diff --git a/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc new file mode 100644 index 0000000000..b0124aa0e2 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/type_conversion_unittest.cc @@ -0,0 +1,161 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +struct RedmondRect { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +}; + +struct RedmondNamedRegion { + std::string name; + std::vector<RedmondRect> rects; +}; + +bool AreEqualRectArrays(const std::vector<test::RectPtr>& rects1, + const std::vector<test::RectPtr>& rects2) { + if (rects1.size() != rects2.size()) + return false; + + for (size_t i = 0; i < rects1.size(); ++i) { + if (rects1[i]->x != rects2[i]->x || rects1[i]->y != rects2[i]->y || + rects1[i]->width != rects2[i]->width || + rects1[i]->height != rects2[i]->height) { + return false; + } + } + + return true; +} + +} // namespace + +template <> +struct TypeConverter<test::RectPtr, RedmondRect> { + static test::RectPtr Convert(const RedmondRect& input) { + return test::Rect::New(input.left, input.top, input.right - input.left, + input.bottom - input.top); + } +}; + +template <> +struct TypeConverter<RedmondRect, test::RectPtr> { + static RedmondRect Convert(const test::RectPtr& input) { + RedmondRect rect; + rect.left = input->x; + rect.top = input->y; + rect.right = input->x + input->width; + rect.bottom = input->y + input->height; + return rect; + } +}; + +template <> +struct TypeConverter<test::NamedRegionPtr, RedmondNamedRegion> { + static test::NamedRegionPtr Convert(const RedmondNamedRegion& input) { + return test::NamedRegion::New( + input.name, ConvertTo<std::vector<test::RectPtr>>(input.rects)); + } +}; + +template <> +struct TypeConverter<RedmondNamedRegion, test::NamedRegionPtr> { + static RedmondNamedRegion Convert(const test::NamedRegionPtr& input) { + RedmondNamedRegion region; + if (input->name) + region.name = input->name.value(); + if (input->rects) { + region.rects.reserve(input->rects->size()); + for (const auto& element : *input->rects) + region.rects.push_back(element.To<RedmondRect>()); + } + return region; + } +}; + +namespace test { +namespace { + +TEST(TypeConversionTest, CustomTypeConverter) { + RectPtr rect(Rect::New(10, 20, 50, 45)); + + RedmondRect rr = rect.To<RedmondRect>(); + EXPECT_EQ(10, rr.left); + EXPECT_EQ(20, rr.top); + EXPECT_EQ(60, rr.right); + EXPECT_EQ(65, rr.bottom); + + RectPtr rect2(Rect::From(rr)); + EXPECT_EQ(rect->x, rect2->x); + EXPECT_EQ(rect->y, rect2->y); + EXPECT_EQ(rect->width, rect2->width); + EXPECT_EQ(rect->height, rect2->height); +} + +TEST(TypeConversionTest, CustomTypeConverter_Array_Null) { + std::vector<RectPtr> rects; + + auto redmond_rects = ConvertTo<std::vector<RedmondRect>>(rects); + + EXPECT_TRUE(redmond_rects.empty()); +} + +TEST(TypeConversionTest, CustomTypeConverter_Array) { + const RedmondRect kBase = {10, 20, 30, 40}; + + std::vector<RectPtr> rects(10); + for (size_t i = 0; i < rects.size(); ++i) { + RedmondRect rr = kBase; + rr.left += static_cast<int32_t>(i); + rr.top += static_cast<int32_t>(i); + rects[i] = Rect::From(rr); + } + + auto redmond_rects = ConvertTo<std::vector<RedmondRect>>(rects); + + auto rects2 = ConvertTo<std::vector<RectPtr>>(redmond_rects); + EXPECT_TRUE(AreEqualRectArrays(rects, rects2)); +} + +TEST(TypeConversionTest, CustomTypeConverter_Nested) { + RedmondNamedRegion redmond_region; + redmond_region.name = "foopy"; + + const RedmondRect kBase = {10, 20, 30, 40}; + + for (size_t i = 0; i < 10; ++i) { + RedmondRect rect = kBase; + rect.left += static_cast<int32_t>(i); + rect.top += static_cast<int32_t>(i); + redmond_region.rects.push_back(rect); + } + + // Round-trip through generated struct and TypeConverter. + + NamedRegionPtr copy = NamedRegion::From(redmond_region); + RedmondNamedRegion redmond_region2 = copy.To<RedmondNamedRegion>(); + + EXPECT_EQ(redmond_region.name, redmond_region2.name); + EXPECT_EQ(redmond_region.rects.size(), redmond_region2.rects.size()); + for (size_t i = 0; i < redmond_region.rects.size(); ++i) { + EXPECT_EQ(redmond_region.rects[i].left, redmond_region2.rects[i].left); + EXPECT_EQ(redmond_region.rects[i].top, redmond_region2.rects[i].top); + EXPECT_EQ(redmond_region.rects[i].right, redmond_region2.rects[i].right); + EXPECT_EQ(redmond_region.rects[i].bottom, redmond_region2.rects[i].bottom); + } +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/union_unittest.cc b/mojo/public/cpp/bindings/tests/union_unittest.cc new file mode 100644 index 0000000000..bdf27dfff3 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/union_unittest.cc @@ -0,0 +1,1246 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> +#include <utility> +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom.h" +#include "mojo/public/interfaces/bindings/tests/test_unions.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { + +TEST(UnionTest, PlainOldDataGetterSetter) { + PodUnionPtr pod(PodUnion::New()); + + pod->set_f_int8(10); + EXPECT_EQ(10, pod->get_f_int8()); + EXPECT_TRUE(pod->is_f_int8()); + EXPECT_FALSE(pod->is_f_int8_other()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT8); + + pod->set_f_uint8(11); + EXPECT_EQ(11, pod->get_f_uint8()); + EXPECT_TRUE(pod->is_f_uint8()); + EXPECT_FALSE(pod->is_f_int8()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT8); + + pod->set_f_int16(12); + EXPECT_EQ(12, pod->get_f_int16()); + EXPECT_TRUE(pod->is_f_int16()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT16); + + pod->set_f_uint16(13); + EXPECT_EQ(13, pod->get_f_uint16()); + EXPECT_TRUE(pod->is_f_uint16()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT16); + + pod->set_f_int32(14); + EXPECT_EQ(14, pod->get_f_int32()); + EXPECT_TRUE(pod->is_f_int32()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT32); + + pod->set_f_uint32(static_cast<uint32_t>(15)); + EXPECT_EQ(static_cast<uint32_t>(15), pod->get_f_uint32()); + EXPECT_TRUE(pod->is_f_uint32()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT32); + + pod->set_f_int64(16); + EXPECT_EQ(16, pod->get_f_int64()); + EXPECT_TRUE(pod->is_f_int64()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_INT64); + + pod->set_f_uint64(static_cast<uint64_t>(17)); + EXPECT_EQ(static_cast<uint64_t>(17), pod->get_f_uint64()); + EXPECT_TRUE(pod->is_f_uint64()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_UINT64); + + pod->set_f_float(1.5); + EXPECT_EQ(1.5, pod->get_f_float()); + EXPECT_TRUE(pod->is_f_float()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_FLOAT); + + pod->set_f_double(1.9); + EXPECT_EQ(1.9, pod->get_f_double()); + EXPECT_TRUE(pod->is_f_double()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_DOUBLE); + + pod->set_f_bool(true); + EXPECT_TRUE(pod->get_f_bool()); + pod->set_f_bool(false); + EXPECT_FALSE(pod->get_f_bool()); + EXPECT_TRUE(pod->is_f_bool()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_BOOL); + + pod->set_f_enum(AnEnum::SECOND); + EXPECT_EQ(AnEnum::SECOND, pod->get_f_enum()); + EXPECT_TRUE(pod->is_f_enum()); + EXPECT_EQ(pod->which(), PodUnion::Tag::F_ENUM); +} + +TEST(UnionTest, PodEquals) { + PodUnionPtr pod1(PodUnion::New()); + PodUnionPtr pod2(PodUnion::New()); + + pod1->set_f_int8(10); + pod2->set_f_int8(10); + EXPECT_TRUE(pod1.Equals(pod2)); + + pod2->set_f_int8(11); + EXPECT_FALSE(pod1.Equals(pod2)); + + pod2->set_f_int8_other(10); + EXPECT_FALSE(pod1.Equals(pod2)); +} + +TEST(UnionTest, PodClone) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + PodUnionPtr pod_clone = pod.Clone(); + EXPECT_EQ(10, pod_clone->get_f_int8()); + EXPECT_TRUE(pod_clone->is_f_int8()); + EXPECT_EQ(pod_clone->which(), PodUnion::Tag::F_INT8); +} + +TEST(UnionTest, PodSerialization) { + PodUnionPtr pod1(PodUnion::New()); + pod1->set_f_int8(10); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<PodUnionDataView>( + pod1, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize<PodUnionDataView>(pod1, &buf, &data, false, + &context); + + PodUnionPtr pod2; + mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, &context); + + EXPECT_EQ(10, pod2->get_f_int8()); + EXPECT_TRUE(pod2->is_f_int8()); + EXPECT_EQ(pod2->which(), PodUnion::Tag::F_INT8); +} + +TEST(UnionTest, EnumSerialization) { + PodUnionPtr pod1(PodUnion::New()); + pod1->set_f_enum(AnEnum::SECOND); + + size_t size = mojo::internal::PrepareToSerialize<PodUnionDataView>( + pod1, false, nullptr); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize<PodUnionDataView>(pod1, &buf, &data, false, + nullptr); + + PodUnionPtr pod2; + mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, nullptr); + + EXPECT_EQ(AnEnum::SECOND, pod2->get_f_enum()); + EXPECT_TRUE(pod2->is_f_enum()); + EXPECT_EQ(pod2->which(), PodUnion::Tag::F_ENUM); +} + +TEST(UnionTest, PodValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + size_t size = + mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_TRUE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, SerializeNotNull) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(0); + size_t size = + mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr); + EXPECT_FALSE(data->is_null()); +} + +TEST(UnionTest, SerializeIsNullInlined) { + PodUnionPtr pod; + size_t size = + mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr); + EXPECT_EQ(16U, size); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + + // Check that dirty output buffers are handled correctly by serialization. + data->size = 16U; + data->tag = PodUnion::Tag::F_UINT16; + data->data.f_f_int16 = 20; + + mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, true, nullptr); + EXPECT_TRUE(data->is_null()); + + PodUnionPtr pod2; + mojo::internal::Deserialize<PodUnionDataView>(data, &pod2, nullptr); + EXPECT_TRUE(pod2.is_null()); +} + +TEST(UnionTest, SerializeIsNullNotInlined) { + PodUnionPtr pod; + size_t size = + mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr); + EXPECT_EQ(16U, size); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr); + EXPECT_EQ(nullptr, data); +} + +TEST(UnionTest, NullValidation) { + void* buf = nullptr; + mojo::internal::ValidationContext validation_context(buf, 0, 0, 0); + EXPECT_TRUE(internal::PodUnion_Data::Validate( + buf, &validation_context, false)); +} + +TEST(UnionTest, OutOfAlignmentValidation) { + size_t size = sizeof(internal::PodUnion_Data); + // Get an aligned object and shift the alignment. + mojo::internal::FixedBufferForTesting aligned_buf(size + 1); + void* raw_buf = aligned_buf.Leak(); + char* buf = reinterpret_cast<char*>(raw_buf) + 1; + + internal::PodUnion_Data* data = + reinterpret_cast<internal::PodUnion_Data*>(buf); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_FALSE(internal::PodUnion_Data::Validate( + buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, OOBValidation) { + size_t size = sizeof(internal::PodUnion_Data) - 1; + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnknownTagValidation) { + size_t size = sizeof(internal::PodUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = internal::PodUnion_Data::New(&buf); + data->tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(0xFFFFFF); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnknownEnumValueValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_enum(static_cast<AnEnum>(0xFFFF)); + + size_t size = + mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_FALSE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnknownExtensibleEnumValueValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_extensible_enum(static_cast<AnExtensibleEnum>(0xFFFF)); + + size_t size = + mojo::internal::PrepareToSerialize<PodUnionDataView>(pod, false, nullptr); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::PodUnion_Data* data = nullptr; + mojo::internal::Serialize<PodUnionDataView>(pod, &buf, &data, false, nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_TRUE( + internal::PodUnion_Data::Validate(raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StringGetterSetter) { + ObjectUnionPtr pod(ObjectUnion::New()); + + std::string hello("hello world"); + pod->set_f_string(hello); + EXPECT_EQ(hello, pod->get_f_string()); + EXPECT_TRUE(pod->is_f_string()); + EXPECT_EQ(pod->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, StringEquals) { + ObjectUnionPtr pod1(ObjectUnion::New()); + ObjectUnionPtr pod2(ObjectUnion::New()); + + pod1->set_f_string("hello world"); + pod2->set_f_string("hello world"); + EXPECT_TRUE(pod1.Equals(pod2)); + + pod2->set_f_string("hello universe"); + EXPECT_FALSE(pod1.Equals(pod2)); +} + +TEST(UnionTest, StringClone) { + ObjectUnionPtr pod(ObjectUnion::New()); + + std::string hello("hello world"); + pod->set_f_string(hello); + ObjectUnionPtr pod_clone = pod.Clone(); + EXPECT_EQ(hello, pod_clone->get_f_string()); + EXPECT_TRUE(pod_clone->is_f_string()); + EXPECT_EQ(pod_clone->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, StringSerialization) { + ObjectUnionPtr pod1(ObjectUnion::New()); + + std::string hello("hello world"); + pod1->set_f_string(hello); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + pod1, false, nullptr); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(pod1, &buf, &data, false, + nullptr); + + ObjectUnionPtr pod2; + mojo::internal::Deserialize<ObjectUnionDataView>(data, &pod2, nullptr); + EXPECT_EQ(hello, pod2->get_f_string()); + EXPECT_TRUE(pod2->is_f_string()); + EXPECT_EQ(pod2->which(), ObjectUnion::Tag::F_STRING); +} + +TEST(UnionTest, NullStringValidation) { + size_t size = sizeof(internal::ObjectUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + data->data.unknown = 0x0; + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StringPointerOverflowValidation) { + size_t size = sizeof(internal::ObjectUnion_Data); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + data->data.unknown = 0xFFFFFFFFFFFFFFFF; + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StringValidateOOB) { + size_t size = 32; + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = internal::ObjectUnion_Data::New(&buf); + data->tag = internal::ObjectUnion_Data::ObjectUnion_Tag::F_STRING; + + data->data.f_f_string.offset = 8; + char* ptr = reinterpret_cast<char*>(&data->data.f_f_string); + mojo::internal::ArrayHeader* array_header = + reinterpret_cast<mojo::internal::ArrayHeader*>(ptr + *ptr); + array_header->num_bytes = 20; // This should go out of bounds. + array_header->num_elements = 20; + mojo::internal::ValidationContext validation_context(data, 32, 0, 0); + void* raw_buf = buf.Leak(); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +// TODO(azani): Move back in array_unittest.cc when possible. +// Array tests +TEST(UnionTest, PodUnionInArray) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union_array.emplace(2); + small_struct->pod_union_array.value()[0] = PodUnion::New(); + small_struct->pod_union_array.value()[1] = PodUnion::New(); + + small_struct->pod_union_array.value()[0]->set_f_int8(10); + small_struct->pod_union_array.value()[1]->set_f_int16(12); + + EXPECT_EQ(10, small_struct->pod_union_array.value()[0]->get_f_int8()); + EXPECT_EQ(12, small_struct->pod_union_array.value()[1]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInArraySerialization) { + std::vector<PodUnionPtr> array(2); + array[0] = PodUnion::New(); + array[1] = PodUnion::New(); + + array[0]->set_f_int8(10); + array[1]->set_f_int16(12); + EXPECT_EQ(2U, array.size()); + + size_t size = + mojo::internal::PrepareToSerialize<ArrayDataView<PodUnionDataView>>( + array, nullptr); + EXPECT_EQ(40U, size); + + mojo::internal::FixedBufferForTesting buf(size); + mojo::internal::Array_Data<internal::PodUnion_Data>* data; + mojo::internal::ContainerValidateParams validate_params(0, false, nullptr); + mojo::internal::Serialize<ArrayDataView<PodUnionDataView>>( + array, &buf, &data, &validate_params, nullptr); + + std::vector<PodUnionPtr> array2; + mojo::internal::Deserialize<ArrayDataView<PodUnionDataView>>(data, &array2, + nullptr); + + EXPECT_EQ(2U, array2.size()); + + EXPECT_EQ(10, array2[0]->get_f_int8()); + EXPECT_EQ(12, array2[1]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInArraySerializationWithNull) { + std::vector<PodUnionPtr> array(2); + array[0] = PodUnion::New(); + + array[0]->set_f_int8(10); + EXPECT_EQ(2U, array.size()); + + size_t size = + mojo::internal::PrepareToSerialize<ArrayDataView<PodUnionDataView>>( + array, nullptr); + EXPECT_EQ(40U, size); + + mojo::internal::FixedBufferForTesting buf(size); + mojo::internal::Array_Data<internal::PodUnion_Data>* data; + mojo::internal::ContainerValidateParams validate_params(0, true, nullptr); + mojo::internal::Serialize<ArrayDataView<PodUnionDataView>>( + array, &buf, &data, &validate_params, nullptr); + + std::vector<PodUnionPtr> array2; + mojo::internal::Deserialize<ArrayDataView<PodUnionDataView>>(data, &array2, + nullptr); + + EXPECT_EQ(2U, array2.size()); + + EXPECT_EQ(10, array2[0]->get_f_int8()); + EXPECT_TRUE(array2[1].is_null()); +} + +TEST(UnionTest, ObjectUnionInArraySerialization) { + std::vector<ObjectUnionPtr> array(2); + array[0] = ObjectUnion::New(); + array[1] = ObjectUnion::New(); + + array[0]->set_f_string("hello"); + array[1]->set_f_string("world"); + EXPECT_EQ(2U, array.size()); + + size_t size = + mojo::internal::PrepareToSerialize<ArrayDataView<ObjectUnionDataView>>( + array, nullptr); + EXPECT_EQ(72U, size); + + mojo::internal::FixedBufferForTesting buf(size); + + mojo::internal::Array_Data<internal::ObjectUnion_Data>* data; + mojo::internal::ContainerValidateParams validate_params(0, false, nullptr); + mojo::internal::Serialize<ArrayDataView<ObjectUnionDataView>>( + array, &buf, &data, &validate_params, nullptr); + + std::vector<char> new_buf; + new_buf.resize(size); + + void* raw_buf = buf.Leak(); + memcpy(new_buf.data(), raw_buf, size); + free(raw_buf); + + data = + reinterpret_cast<mojo::internal::Array_Data<internal::ObjectUnion_Data>*>( + new_buf.data()); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + ASSERT_TRUE(mojo::internal::Array_Data<internal::ObjectUnion_Data>::Validate( + data, &validation_context, &validate_params)); + + std::vector<ObjectUnionPtr> array2; + mojo::internal::Deserialize<ArrayDataView<ObjectUnionDataView>>(data, &array2, + nullptr); + + EXPECT_EQ(2U, array2.size()); + + EXPECT_EQ("hello", array2[0]->get_f_string()); + EXPECT_EQ("world", array2[1]->get_f_string()); +} + +// TODO(azani): Move back in struct_unittest.cc when possible. +// Struct tests +TEST(UnionTest, Clone_Union) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int8(10); + + SmallStructPtr clone = small_struct.Clone(); + EXPECT_EQ(10, clone->pod_union->get_f_int8()); +} + +// Serialization test of a struct with a union of plain old data. +TEST(UnionTest, Serialization_UnionOfPods) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>( + small_struct, &context); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data, + &context); + + SmallStructPtr deserialized; + mojo::internal::Deserialize<SmallStructDataView>(data, &deserialized, + &context); + + EXPECT_EQ(10, deserialized->pod_union->get_f_int32()); +} + +// Serialization test of a struct with a union of structs. +TEST(UnionTest, Serialization_UnionOfObjects) { + SmallObjStructPtr obj_struct(SmallObjStruct::New()); + obj_struct->obj_union = ObjectUnion::New(); + std::string hello("hello world"); + obj_struct->obj_union->set_f_string(hello); + + size_t size = mojo::internal::PrepareToSerialize<SmallObjStructDataView>( + obj_struct, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallObjStruct_Data* data = nullptr; + mojo::internal::Serialize<SmallObjStructDataView>(obj_struct, &buf, &data, + nullptr); + + SmallObjStructPtr deserialized; + mojo::internal::Deserialize<SmallObjStructDataView>(data, &deserialized, + nullptr); + + EXPECT_EQ(hello, deserialized->obj_union->get_f_string()); +} + +// Validation test of a struct with a union. +TEST(UnionTest, Validation_UnionsInStruct) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>( + small_struct, &context); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_TRUE(internal::SmallStruct_Data::Validate( + raw_buf, &validation_context)); + free(raw_buf); +} + +// Validation test of a struct union fails due to unknown union tag. +TEST(UnionTest, Validation_PodUnionInStruct_Failure) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union = PodUnion::New(); + small_struct->pod_union->set_f_int32(10); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>( + small_struct, &context); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data, + &context); + data->pod_union.tag = static_cast<internal::PodUnion_Data::PodUnion_Tag>(100); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_FALSE(internal::SmallStruct_Data::Validate( + raw_buf, &validation_context)); + free(raw_buf); +} + +// Validation fails due to non-nullable null union in struct. +TEST(UnionTest, Validation_NullUnion_Failure) { + SmallStructNonNullableUnionPtr small_struct( + SmallStructNonNullableUnion::New()); + + size_t size = + mojo::internal::PrepareToSerialize<SmallStructNonNullableUnionDataView>( + small_struct, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStructNonNullableUnion_Data* data = + internal::SmallStructNonNullableUnion_Data::New(&buf); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_FALSE(internal::SmallStructNonNullableUnion_Data::Validate( + raw_buf, &validation_context)); + free(raw_buf); +} + +// Validation passes with nullable null union. +TEST(UnionTest, Validation_NullableUnion) { + SmallStructPtr small_struct(SmallStruct::New()); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<SmallStructDataView>( + small_struct, &context); + + mojo::internal::FixedBufferForTesting buf(size); + internal::SmallStruct_Data* data = nullptr; + mojo::internal::Serialize<SmallStructDataView>(small_struct, &buf, &data, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_TRUE(internal::SmallStruct_Data::Validate( + raw_buf, &validation_context)); + free(raw_buf); +} + +// TODO(azani): Move back in map_unittest.cc when possible. +// Map Tests +TEST(UnionTest, PodUnionInMap) { + SmallStructPtr small_struct(SmallStruct::New()); + small_struct->pod_union_map.emplace(); + small_struct->pod_union_map.value()["one"] = PodUnion::New(); + small_struct->pod_union_map.value()["two"] = PodUnion::New(); + + small_struct->pod_union_map.value()["one"]->set_f_int8(8); + small_struct->pod_union_map.value()["two"]->set_f_int16(16); + + EXPECT_EQ(8, small_struct->pod_union_map.value()["one"]->get_f_int8()); + EXPECT_EQ(16, small_struct->pod_union_map.value()["two"]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInMapSerialization) { + using MojomType = MapDataView<StringDataView, PodUnionDataView>; + + std::unordered_map<std::string, PodUnionPtr> map; + map.insert(std::make_pair("one", PodUnion::New())); + map.insert(std::make_pair("two", PodUnion::New())); + + map["one"]->set_f_int8(8); + map["two"]->set_f_int16(16); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<MojomType>(map, &context); + EXPECT_EQ(120U, size); + + mojo::internal::FixedBufferForTesting buf(size); + + typename mojo::internal::MojomTypeTraits<MojomType>::Data* data; + mojo::internal::ContainerValidateParams validate_params( + new mojo::internal::ContainerValidateParams(0, false, nullptr), + new mojo::internal::ContainerValidateParams(0, false, nullptr)); + mojo::internal::Serialize<MojomType>(map, &buf, &data, &validate_params, + &context); + + std::unordered_map<std::string, PodUnionPtr> map2; + mojo::internal::Deserialize<MojomType>(data, &map2, &context); + + EXPECT_EQ(8, map2["one"]->get_f_int8()); + EXPECT_EQ(16, map2["two"]->get_f_int16()); +} + +TEST(UnionTest, PodUnionInMapSerializationWithNull) { + using MojomType = MapDataView<StringDataView, PodUnionDataView>; + + std::unordered_map<std::string, PodUnionPtr> map; + map.insert(std::make_pair("one", PodUnion::New())); + map.insert(std::make_pair("two", nullptr)); + + map["one"]->set_f_int8(8); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<MojomType>(map, &context); + EXPECT_EQ(120U, size); + + mojo::internal::FixedBufferForTesting buf(size); + typename mojo::internal::MojomTypeTraits<MojomType>::Data* data; + mojo::internal::ContainerValidateParams validate_params( + new mojo::internal::ContainerValidateParams(0, false, nullptr), + new mojo::internal::ContainerValidateParams(0, true, nullptr)); + mojo::internal::Serialize<MojomType>(map, &buf, &data, &validate_params, + &context); + + std::unordered_map<std::string, PodUnionPtr> map2; + mojo::internal::Deserialize<MojomType>(data, &map2, &context); + + EXPECT_EQ(8, map2["one"]->get_f_int8()); + EXPECT_TRUE(map2["two"].is_null()); +} + +TEST(UnionTest, StructInUnionGetterSetterPasser) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + EXPECT_EQ(8, obj->get_f_dummy()->f_int8); +} + +TEST(UnionTest, StructInUnionSerialization) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + ObjectUnionPtr obj2; + mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr); + EXPECT_EQ(8, obj2->get_f_dummy()->f_int8); +} + +TEST(UnionTest, StructInUnionValidation) { + DummyStructPtr dummy(DummyStruct::New()); + dummy->f_int8 = 8; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StructInUnionValidationNonNullable) { + mojo::internal::SerializationWarningObserverForTesting suppress_warning; + + DummyStructPtr dummy(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_dummy(std::move(dummy)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, StructInUnionValidationNullable) { + DummyStructPtr dummy(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_nullable(std::move(dummy)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, ArrayInUnionGetterSetter) { + std::vector<int8_t> array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(std::move(array)); + + EXPECT_EQ(8, obj->get_f_array_int8()[0]); + EXPECT_EQ(9, obj->get_f_array_int8()[1]); +} + +TEST(UnionTest, ArrayInUnionSerialization) { + std::vector<int8_t> array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(std::move(array)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + ObjectUnionPtr obj2; + mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr); + + EXPECT_EQ(8, obj2->get_f_array_int8()[0]); + EXPECT_EQ(9, obj2->get_f_array_int8()[1]); +} + +TEST(UnionTest, ArrayInUnionValidation) { + std::vector<int8_t> array(2); + array[0] = 8; + array[1] = 9; + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_array_int8(std::move(array)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, MapInUnionGetterSetter) { + std::unordered_map<std::string, int8_t> map; + map.insert({"one", 1}); + map.insert({"two", 2}); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(std::move(map)); + + EXPECT_EQ(1, obj->get_f_map_int8()["one"]); + EXPECT_EQ(2, obj->get_f_map_int8()["two"]); +} + +TEST(UnionTest, MapInUnionSerialization) { + std::unordered_map<std::string, int8_t> map; + map.insert({"one", 1}); + map.insert({"two", 2}); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(std::move(map)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, &context); + EXPECT_EQ(112U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + &context); + + ObjectUnionPtr obj2; + mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, &context); + + EXPECT_EQ(1, obj2->get_f_map_int8()["one"]); + EXPECT_EQ(2, obj2->get_f_map_int8()["two"]); +} + +TEST(UnionTest, MapInUnionValidation) { + std::unordered_map<std::string, int8_t> map; + map.insert({"one", 1}); + map.insert({"two", 2}); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_map_int8(std::move(map)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, &context); + EXPECT_EQ(112U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnionInUnionGetterSetter) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + EXPECT_EQ(10, obj->get_f_pod_union()->get_f_int8()); +} + +TEST(UnionTest, UnionInUnionSerialization) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + ObjectUnionPtr obj2; + mojo::internal::Deserialize<ObjectUnionDataView>(data, &obj2, nullptr); + EXPECT_EQ(10, obj2->get_f_pod_union()->get_f_int8()); +} + +TEST(UnionTest, UnionInUnionValidation) { + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int8(10); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + EXPECT_EQ(32U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_TRUE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, UnionInUnionValidationNonNullable) { + mojo::internal::SerializationWarningObserverForTesting suppress_warning; + + PodUnionPtr pod(nullptr); + + ObjectUnionPtr obj(ObjectUnion::New()); + obj->set_f_pod_union(std::move(pod)); + + size_t size = mojo::internal::PrepareToSerialize<ObjectUnionDataView>( + obj, false, nullptr); + + mojo::internal::FixedBufferForTesting buf(size); + internal::ObjectUnion_Data* data = nullptr; + mojo::internal::Serialize<ObjectUnionDataView>(obj, &buf, &data, false, + nullptr); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 0, 0); + EXPECT_FALSE(internal::ObjectUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, HandleInUnionGetterSetter) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe1)); + + std::string golden("hello world"); + WriteTextMessage(pipe0.get(), golden); + + std::string actual; + ReadTextMessage(handle->get_f_message_pipe().get(), &actual); + + EXPECT_EQ(golden, actual); +} + +TEST(UnionTest, HandleInUnionSerialization) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe1)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>( + handle, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false, + &context); + EXPECT_EQ(1U, context.handles.size()); + + HandleUnionPtr handle2(HandleUnion::New()); + mojo::internal::Deserialize<HandleUnionDataView>(data, &handle2, &context); + + std::string golden("hello world"); + WriteTextMessage(pipe0.get(), golden); + + std::string actual; + ReadTextMessage(handle2->get_f_message_pipe().get(), &actual); + + EXPECT_EQ(golden, actual); +} + +TEST(UnionTest, HandleInUnionValidation) { + ScopedMessagePipeHandle pipe0; + ScopedMessagePipeHandle pipe1; + + CreateMessagePipe(nullptr, &pipe0, &pipe1); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe1)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>( + handle, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 1, 0); + EXPECT_TRUE(internal::HandleUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +TEST(UnionTest, HandleInUnionValidationNull) { + mojo::internal::SerializationWarningObserverForTesting suppress_warning; + + ScopedMessagePipeHandle pipe; + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_message_pipe(std::move(pipe)); + + mojo::internal::SerializationContext context; + size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>( + handle, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false, + &context); + + void* raw_buf = buf.Leak(); + mojo::internal::ValidationContext validation_context( + data, static_cast<uint32_t>(size), 1, 0); + EXPECT_FALSE(internal::HandleUnion_Data::Validate( + raw_buf, &validation_context, false)); + free(raw_buf); +} + +class SmallCacheImpl : public SmallCache { + public: + explicit SmallCacheImpl(const base::Closure& closure) + : int_value_(0), closure_(closure) {} + ~SmallCacheImpl() override {} + int64_t int_value() const { return int_value_; } + + private: + void SetIntValue(int64_t int_value) override { + int_value_ = int_value; + closure_.Run(); + } + void GetIntValue(const GetIntValueCallback& callback) override { + callback.Run(int_value_); + } + + int64_t int_value_; + base::Closure closure_; +}; + +TEST(UnionTest, InterfaceInUnion) { + base::MessageLoop message_loop; + base::RunLoop run_loop; + SmallCacheImpl impl(run_loop.QuitClosure()); + SmallCachePtr ptr; + Binding<SmallCache> bindings(&impl, MakeRequest(&ptr)); + + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_small_cache(std::move(ptr)); + + handle->get_f_small_cache()->SetIntValue(10); + run_loop.Run(); + EXPECT_EQ(10, impl.int_value()); +} + +TEST(UnionTest, InterfaceInUnionSerialization) { + base::MessageLoop message_loop; + base::RunLoop run_loop; + SmallCacheImpl impl(run_loop.QuitClosure()); + SmallCachePtr ptr; + Binding<SmallCache> bindings(&impl, MakeRequest(&ptr)); + + mojo::internal::SerializationContext context; + HandleUnionPtr handle(HandleUnion::New()); + handle->set_f_small_cache(std::move(ptr)); + size_t size = mojo::internal::PrepareToSerialize<HandleUnionDataView>( + handle, false, &context); + EXPECT_EQ(16U, size); + + mojo::internal::FixedBufferForTesting buf(size); + internal::HandleUnion_Data* data = nullptr; + mojo::internal::Serialize<HandleUnionDataView>(handle, &buf, &data, false, + &context); + EXPECT_EQ(1U, context.handles.size()); + + HandleUnionPtr handle2(HandleUnion::New()); + mojo::internal::Deserialize<HandleUnionDataView>(data, &handle2, &context); + + handle2->get_f_small_cache()->SetIntValue(10); + run_loop.Run(); + EXPECT_EQ(10, impl.int_value()); +} + +class UnionInterfaceImpl : public UnionInterface { + public: + UnionInterfaceImpl() {} + ~UnionInterfaceImpl() override {} + + private: + void Echo(PodUnionPtr in, const EchoCallback& callback) override { + callback.Run(std::move(in)); + } +}; + +void ExpectInt16(int16_t value, PodUnionPtr out) { + EXPECT_EQ(value, out->get_f_int16()); +} + +TEST(UnionTest, UnionInInterface) { + base::MessageLoop message_loop; + UnionInterfaceImpl impl; + UnionInterfacePtr ptr; + Binding<UnionInterface> bindings(&impl, MakeRequest(&ptr)); + + PodUnionPtr pod(PodUnion::New()); + pod->set_f_int16(16); + + ptr->Echo(std::move(pod), base::Bind(&ExpectInt16, 16)); + base::RunLoop().RunUntilIdle(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/validation_context_unittest.cc b/mojo/public/cpp/bindings/tests/validation_context_unittest.cc new file mode 100644 index 0000000000..9ce9d60e49 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_context_unittest.cc @@ -0,0 +1,297 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include <limits> + +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/system/core.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +using Handle_Data = mojo::internal::Handle_Data; +using AssociatedEndpointHandle_Data = + mojo::internal::AssociatedEndpointHandle_Data; + +const void* ToPtr(uintptr_t ptr) { + return reinterpret_cast<const void*>(ptr); +} + +#if defined(NDEBUG) && !defined(DCHECK_ALWAYS_ON) +TEST(ValidationContextTest, ConstructorRangeOverflow) { + { + // Test memory range overflow. + internal::ValidationContext context( + ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 5000, 0, 0); + + EXPECT_FALSE(context.IsValidRange( + ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1)); + EXPECT_FALSE(context.ClaimMemory( + ToPtr(std::numeric_limits<uintptr_t>::max() - 3000), 1)); + } + + if (sizeof(size_t) <= sizeof(uint32_t)) + return; + + { + // Test handle index range overflow. + size_t num_handles = + static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 5; + internal::ValidationContext context(ToPtr(0), 0, num_handles, 0); + + EXPECT_FALSE(context.ClaimHandle(Handle_Data(0))); + EXPECT_FALSE(context.ClaimHandle( + Handle_Data(std::numeric_limits<uint32_t>::max() - 1))); + + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + size_t num_associated_endpoint_handles = + static_cast<size_t>(std::numeric_limits<uint32_t>::max()) + 5; + internal::ValidationContext context(ToPtr(0), 0, 0, + num_associated_endpoint_handles); + + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + EXPECT_FALSE( + context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data( + std::numeric_limits<uint32_t>::max() - 1))); + + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + } +} +#endif + +TEST(ValidationContextTest, IsValidRange) { + { + internal::ValidationContext context(ToPtr(1234), 100, 0, 0); + + // Basics. + EXPECT_FALSE(context.IsValidRange(ToPtr(100), 5)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1230), 50)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1234), 5)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1240), 50)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1234), 100)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 101)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1240), 100)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1333), 5)); + EXPECT_FALSE(context.IsValidRange(ToPtr(2234), 5)); + + // ClaimMemory() updates the valid range. + EXPECT_TRUE(context.ClaimMemory(ToPtr(1254), 10)); + + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 1)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1254), 10)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1263), 1)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1263), 10)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1264), 10)); + EXPECT_TRUE(context.IsValidRange(ToPtr(1264), 70)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1264), 71)); + } + + { + internal::ValidationContext context(ToPtr(1234), 100, 0, 0); + // Should return false for empty ranges. + EXPECT_FALSE(context.IsValidRange(ToPtr(0), 0)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1200), 0)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 0)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1240), 0)); + EXPECT_FALSE(context.IsValidRange(ToPtr(2234), 0)); + } + + { + // The valid memory range is empty. + internal::ValidationContext context(ToPtr(1234), 0, 0, 0); + + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 1)); + EXPECT_FALSE(context.IsValidRange(ToPtr(1234), 0)); + } + + { + internal::ValidationContext context( + ToPtr(std::numeric_limits<uintptr_t>::max() - 2000), 1000, 0, 0); + + // Test overflow. + EXPECT_FALSE(context.IsValidRange( + ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 4000)); + EXPECT_FALSE(context.IsValidRange( + ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), + std::numeric_limits<uint32_t>::max())); + + // This should be fine. + EXPECT_TRUE(context.IsValidRange( + ToPtr(std::numeric_limits<uintptr_t>::max() - 1500), 200)); + } +} + +TEST(ValidationContextTest, ClaimHandle) { + { + internal::ValidationContext context(ToPtr(0), 0, 10, 0); + + // Basics. + EXPECT_TRUE(context.ClaimHandle(Handle_Data(0))); + EXPECT_FALSE(context.ClaimHandle(Handle_Data(0))); + + EXPECT_TRUE(context.ClaimHandle(Handle_Data(9))); + EXPECT_FALSE(context.ClaimHandle(Handle_Data(10))); + + // Should fail because it is smaller than the max index that has been + // claimed. + EXPECT_FALSE(context.ClaimHandle(Handle_Data(8))); + + // Should return true for invalid handle. + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + // No handle to claim. + internal::ValidationContext context(ToPtr(0), 0, 0, 0); + + EXPECT_FALSE(context.ClaimHandle(Handle_Data(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + // Test the case that |num_handles| is the same value as + // |internal::kEncodedInvalidHandleValue|. + EXPECT_EQ(internal::kEncodedInvalidHandleValue, + std::numeric_limits<uint32_t>::max()); + internal::ValidationContext context( + ToPtr(0), 0, std::numeric_limits<uint32_t>::max(), 0); + + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(std::numeric_limits<uint32_t>::max() - 1))); + EXPECT_FALSE(context.ClaimHandle( + Handle_Data(std::numeric_limits<uint32_t>::max() - 1))); + EXPECT_FALSE(context.ClaimHandle(Handle_Data(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE(context.ClaimHandle( + Handle_Data(internal::kEncodedInvalidHandleValue))); + } +} + +TEST(ValidationContextTest, ClaimAssociatedEndpointHandle) { + { + internal::ValidationContext context(ToPtr(0), 0, 0, 10); + + // Basics. + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(9))); + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(10))); + + // Should fail because it is smaller than the max index that has been + // claimed. + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(8))); + + // Should return true for invalid handle. + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + // No handle to claim. + internal::ValidationContext context(ToPtr(0), 0, 0, 0); + + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + } + + { + // Test the case that |num_associated_endpoint_handles| is the same value as + // |internal::kEncodedInvalidHandleValue|. + EXPECT_EQ(internal::kEncodedInvalidHandleValue, + std::numeric_limits<uint32_t>::max()); + internal::ValidationContext context(ToPtr(0), 0, 0, + std::numeric_limits<uint32_t>::max()); + + EXPECT_TRUE( + context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data( + std::numeric_limits<uint32_t>::max() - 1))); + EXPECT_FALSE( + context.ClaimAssociatedEndpointHandle(AssociatedEndpointHandle_Data( + std::numeric_limits<uint32_t>::max() - 1))); + EXPECT_FALSE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(0))); + + // Should still return true for invalid handle. + EXPECT_TRUE(context.ClaimAssociatedEndpointHandle( + AssociatedEndpointHandle_Data(internal::kEncodedInvalidHandleValue))); + } +} + +TEST(ValidationContextTest, ClaimMemory) { + { + internal::ValidationContext context(ToPtr(1000), 2000, 0, 0); + + // Basics. + EXPECT_FALSE(context.ClaimMemory(ToPtr(500), 100)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(800), 300)); + EXPECT_TRUE(context.ClaimMemory(ToPtr(1000), 100)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(1099), 100)); + EXPECT_TRUE(context.ClaimMemory(ToPtr(1100), 200)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(2000), 1001)); + EXPECT_TRUE(context.ClaimMemory(ToPtr(2000), 500)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(2000), 500)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(1400), 100)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(3000), 1)); + EXPECT_TRUE(context.ClaimMemory(ToPtr(2500), 500)); + } + + { + // No memory to claim. + internal::ValidationContext context(ToPtr(10000), 0, 0, 0); + + EXPECT_FALSE(context.ClaimMemory(ToPtr(10000), 1)); + EXPECT_FALSE(context.ClaimMemory(ToPtr(10000), 0)); + } + + { + internal::ValidationContext context( + ToPtr(std::numeric_limits<uintptr_t>::max() - 1000), 500, 0, 0); + + // Test overflow. + EXPECT_FALSE(context.ClaimMemory( + ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 4000)); + EXPECT_FALSE( + context.ClaimMemory(ToPtr(std::numeric_limits<uintptr_t>::max() - 750), + std::numeric_limits<uint32_t>::max())); + + // This should be fine. + EXPECT_TRUE(context.ClaimMemory( + ToPtr(std::numeric_limits<uintptr_t>::max() - 750), 200)); + } +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc b/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc new file mode 100644 index 0000000000..f3097372a4 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.cc @@ -0,0 +1,412 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" + +#include <assert.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <limits> +#include <map> +#include <set> +#include <utility> + +#include "mojo/public/c/system/macros.h" + +namespace mojo { +namespace test { +namespace { + +class ValidationTestInputParser { + public: + ValidationTestInputParser(const std::string& input, + std::vector<uint8_t>* data, + size_t* num_handles, + std::string* error_message); + ~ValidationTestInputParser(); + + bool Run(); + + private: + struct DataType; + + typedef std::pair<const char*, const char*> Range; + + typedef bool (ValidationTestInputParser::*ParseDataFunc)( + const DataType& type, + const std::string& value_string); + + struct DataType { + const char* name; + size_t name_size; + size_t data_size; + ParseDataFunc parse_data_func; + }; + + // A dist4/8 item that hasn't been matched with an anchr item. + struct PendingDistanceItem { + // Where this data item is located in |data_|. + size_t pos; + // Either 4 or 8 (bytes). + size_t data_size; + }; + + bool GetNextItem(Range* range); + + bool ParseItem(const Range& range); + + bool ParseUnsignedInteger(const DataType& type, + const std::string& value_string); + bool ParseSignedInteger(const DataType& type, + const std::string& value_string); + bool ParseFloat(const DataType& type, const std::string& value_string); + bool ParseDouble(const DataType& type, const std::string& value_string); + bool ParseBinarySequence(const DataType& type, + const std::string& value_string); + bool ParseDistance(const DataType& type, const std::string& value_string); + bool ParseAnchor(const DataType& type, const std::string& value_string); + bool ParseHandles(const DataType& type, const std::string& value_string); + + bool StartsWith(const Range& range, const char* prefix, size_t prefix_length); + + bool ConvertToUnsignedInteger(const std::string& value_string, + unsigned long long int* value); + + template <typename T> + void AppendData(T data) { + size_t pos = data_->size(); + data_->resize(pos + sizeof(T)); + memcpy(&(*data_)[pos], &data, sizeof(T)); + } + + template <typename TargetType, typename InputType> + bool ConvertAndAppendData(InputType value) { + if (value > std::numeric_limits<TargetType>::max() || + value < std::numeric_limits<TargetType>::min()) { + return false; + } + AppendData(static_cast<TargetType>(value)); + return true; + } + + template <typename TargetType, typename InputType> + bool ConvertAndFillData(size_t pos, InputType value) { + if (value > std::numeric_limits<TargetType>::max() || + value < std::numeric_limits<TargetType>::min()) { + return false; + } + TargetType target_value = static_cast<TargetType>(value); + assert(pos + sizeof(TargetType) <= data_->size()); + memcpy(&(*data_)[pos], &target_value, sizeof(TargetType)); + return true; + } + + static const DataType kDataTypes[]; + static const size_t kDataTypeCount; + + const std::string& input_; + size_t input_cursor_; + + std::vector<uint8_t>* data_; + size_t* num_handles_; + std::string* error_message_; + + std::map<std::string, PendingDistanceItem> pending_distance_items_; + std::set<std::string> anchors_; +}; + +#define DATA_TYPE(name, data_size, parse_data_func) \ + { name, sizeof(name) - 1, data_size, parse_data_func } + +const ValidationTestInputParser::DataType + ValidationTestInputParser::kDataTypes[] = { + DATA_TYPE("[u1]", 1, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u2]", 2, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u4]", 4, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[u8]", 8, &ValidationTestInputParser::ParseUnsignedInteger), + DATA_TYPE("[s1]", 1, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s2]", 2, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s4]", 4, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[s8]", 8, &ValidationTestInputParser::ParseSignedInteger), + DATA_TYPE("[b]", 1, &ValidationTestInputParser::ParseBinarySequence), + DATA_TYPE("[f]", 4, &ValidationTestInputParser::ParseFloat), + DATA_TYPE("[d]", 8, &ValidationTestInputParser::ParseDouble), + DATA_TYPE("[dist4]", 4, &ValidationTestInputParser::ParseDistance), + DATA_TYPE("[dist8]", 8, &ValidationTestInputParser::ParseDistance), + DATA_TYPE("[anchr]", 0, &ValidationTestInputParser::ParseAnchor), + DATA_TYPE("[handles]", 0, &ValidationTestInputParser::ParseHandles)}; + +const size_t ValidationTestInputParser::kDataTypeCount = + sizeof(ValidationTestInputParser::kDataTypes) / + sizeof(ValidationTestInputParser::kDataTypes[0]); + +ValidationTestInputParser::ValidationTestInputParser(const std::string& input, + std::vector<uint8_t>* data, + size_t* num_handles, + std::string* error_message) + : input_(input), + input_cursor_(0), + data_(data), + num_handles_(num_handles), + error_message_(error_message) { + assert(data_); + assert(num_handles_); + assert(error_message_); + data_->clear(); + *num_handles_ = 0; + error_message_->clear(); +} + +ValidationTestInputParser::~ValidationTestInputParser() { +} + +bool ValidationTestInputParser::Run() { + Range range; + bool result = true; + while (result && GetNextItem(&range)) + result = ParseItem(range); + + if (!result) { + *error_message_ = + "Error occurred when parsing " + std::string(range.first, range.second); + } else if (!pending_distance_items_.empty()) { + // We have parsed all the contents in |input_| successfully, but there are + // unmatched dist4/8 items. + *error_message_ = "Error occurred when matching [dist4/8] and [anchr]."; + result = false; + } + if (!result) { + data_->clear(); + *num_handles_ = 0; + } else { + assert(error_message_->empty()); + } + + return result; +} + +bool ValidationTestInputParser::GetNextItem(Range* range) { + const char kWhitespaceChars[] = " \t\n\r"; + const char kItemDelimiters[] = " \t\n\r/"; + const char kEndOfLineChars[] = "\n\r"; + while (true) { + // Skip leading whitespaces. + // If there are no non-whitespace characters left, |input_cursor_| will be + // set to std::npos. + input_cursor_ = input_.find_first_not_of(kWhitespaceChars, input_cursor_); + + if (input_cursor_ >= input_.size()) + return false; + + if (StartsWith( + Range(&input_[0] + input_cursor_, &input_[0] + input_.size()), + "//", + 2)) { + // Skip contents until the end of the line. + input_cursor_ = input_.find_first_of(kEndOfLineChars, input_cursor_); + } else { + range->first = &input_[0] + input_cursor_; + input_cursor_ = input_.find_first_of(kItemDelimiters, input_cursor_); + range->second = input_cursor_ >= input_.size() + ? &input_[0] + input_.size() + : &input_[0] + input_cursor_; + return true; + } + } + return false; +} + +bool ValidationTestInputParser::ParseItem(const Range& range) { + for (size_t i = 0; i < kDataTypeCount; ++i) { + if (StartsWith(range, kDataTypes[i].name, kDataTypes[i].name_size)) { + return (this->*kDataTypes[i].parse_data_func)( + kDataTypes[i], + std::string(range.first + kDataTypes[i].name_size, range.second)); + } + } + + // "[u1]" is optional. + return ParseUnsignedInteger(kDataTypes[0], + std::string(range.first, range.second)); +} + +bool ValidationTestInputParser::ParseUnsignedInteger( + const DataType& type, + const std::string& value_string) { + unsigned long long int value; + if (!ConvertToUnsignedInteger(value_string, &value)) + return false; + + switch (type.data_size) { + case 1: + return ConvertAndAppendData<uint8_t>(value); + case 2: + return ConvertAndAppendData<uint16_t>(value); + case 4: + return ConvertAndAppendData<uint32_t>(value); + case 8: + return ConvertAndAppendData<uint64_t>(value); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseSignedInteger( + const DataType& type, + const std::string& value_string) { + long long int value; + if (sscanf(value_string.c_str(), "%lli", &value) != 1) + return false; + + switch (type.data_size) { + case 1: + return ConvertAndAppendData<int8_t>(value); + case 2: + return ConvertAndAppendData<int16_t>(value); + case 4: + return ConvertAndAppendData<int32_t>(value); + case 8: + return ConvertAndAppendData<int64_t>(value); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseFloat(const DataType& type, + const std::string& value_string) { + static_assert(sizeof(float) == 4, "sizeof(float) is not 4"); + + float value; + if (sscanf(value_string.c_str(), "%f", &value) != 1) + return false; + + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseDouble(const DataType& type, + const std::string& value_string) { + static_assert(sizeof(double) == 8, "sizeof(double) is not 8"); + + double value; + if (sscanf(value_string.c_str(), "%lf", &value) != 1) + return false; + + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseBinarySequence( + const DataType& type, + const std::string& value_string) { + if (value_string.size() != 8) + return false; + + uint8_t value = 0; + for (std::string::const_iterator iter = value_string.begin(); + iter != value_string.end(); + ++iter) { + value <<= 1; + if (*iter == '1') + value++; + else if (*iter != '0') + return false; + } + AppendData(value); + return true; +} + +bool ValidationTestInputParser::ParseDistance(const DataType& type, + const std::string& value_string) { + if (pending_distance_items_.find(value_string) != + pending_distance_items_.end()) + return false; + + PendingDistanceItem item = {data_->size(), type.data_size}; + data_->resize(data_->size() + type.data_size); + pending_distance_items_[value_string] = item; + + return true; +} + +bool ValidationTestInputParser::ParseAnchor(const DataType& type, + const std::string& value_string) { + if (anchors_.find(value_string) != anchors_.end()) + return false; + anchors_.insert(value_string); + + std::map<std::string, PendingDistanceItem>::iterator iter = + pending_distance_items_.find(value_string); + if (iter == pending_distance_items_.end()) + return false; + + PendingDistanceItem dist_item = iter->second; + pending_distance_items_.erase(iter); + + size_t distance = data_->size() - dist_item.pos; + switch (dist_item.data_size) { + case 4: + return ConvertAndFillData<uint32_t>(dist_item.pos, distance); + case 8: + return ConvertAndFillData<uint64_t>(dist_item.pos, distance); + default: + assert(false); + return false; + } +} + +bool ValidationTestInputParser::ParseHandles(const DataType& type, + const std::string& value_string) { + // It should be the first item. + if (!data_->empty()) + return false; + + unsigned long long int value; + if (!ConvertToUnsignedInteger(value_string, &value)) + return false; + + if (value > std::numeric_limits<size_t>::max()) + return false; + + *num_handles_ = static_cast<size_t>(value); + return true; +} + +bool ValidationTestInputParser::StartsWith(const Range& range, + const char* prefix, + size_t prefix_length) { + if (static_cast<size_t>(range.second - range.first) < prefix_length) + return false; + + return memcmp(range.first, prefix, prefix_length) == 0; +} + +bool ValidationTestInputParser::ConvertToUnsignedInteger( + const std::string& value_string, + unsigned long long int* value) { + const char* format = nullptr; + if (value_string.find_first_of("xX") != std::string::npos) + format = "%llx"; + else + format = "%llu"; + return sscanf(value_string.c_str(), format, value) == 1; +} + +} // namespace + +bool ParseValidationTestInput(const std::string& input, + std::vector<uint8_t>* data, + size_t* num_handles, + std::string* error_message) { + ValidationTestInputParser parser(input, data, num_handles, error_message); + return parser.Run(); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/validation_test_input_parser.h b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h new file mode 100644 index 0000000000..d08f359c17 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_test_input_parser.h @@ -0,0 +1,121 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ + +#include <stddef.h> +#include <stdint.h> + +#include <string> +#include <vector> + +namespace mojo { +namespace test { + +// Input Format of Mojo Message Validation Tests. +// +// Data items are separated by whitespaces: +// - ' ' (0x20) space; +// - '\t' (0x09) horizontal tab; +// - '\n' (0x0a) newline; +// - '\r' (0x0d) carriage return. +// A comment starts with //, extending to the end of the line. +// Each data item is of the format [<type>]<value>. The types defined and the +// corresponding value formats are described below. +// +// Type: u1 / u2 / u4 / u8 +// Description: Little-endian 1/2/4/8-byte unsigned integer. +// Value Format: +// - Decimal integer: 0|[1-9][0-9]* +// - Hexadecimal integer: 0[xX][0-9a-fA-F]+ +// - The type prefix (including the square brackets) of 1-byte unsigned +// integer is optional. +// +// Type: s1 / s2 / s4 / s8 +// Description: Little-endian 1/2/4/8-byte signed integer. +// Value Format: +// - Decimal integer: [-+]?(0|[1-9][0-9]*) +// - Hexadecimal integer: [-+]?0[xX][0-9a-fA-F]+ +// +// Type: b +// Description: Binary sequence of 1 byte. +// Value Format: [01]{8} +// +// Type: f / d +// Description: Little-endian IEEE-754 format of float (4 bytes) and double (8 +// bytes). +// Value Format: [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)? +// +// Type: dist4 / dist8 +// Description: Little-endian 4/8-byte unsigned integer. The actual value is set +// to the byte distance from the location of this integer to the location of the +// anchr item with the same ID. A dist8 and anchr pair can be used to easily +// represent an encoded pointer. A dist4 and anchr pair can be used to easily +// calculate struct/array size. +// Value Format: The value is an ID: [0-9a-zA-Z_]+ +// +// Type: anchr +// Description: Mark an anchor location. It doesn’t translate into any actual +// data. +// Value Format: The value is an ID of the same format as that of dist4/8. +// +// Type: handles +// Description: The number of handles that are associated with the message. This +// special item is not part of the message data. If specified, it should be the +// first item. +// Value Format: The same format as u1/2/4/8. +// +// EXAMPLE: +// +// Suppose you have the following Mojo types defined: +// struct Bar { +// int32_t a; +// bool b; +// bool c; +// }; +// struct Foo { +// Bar x; +// uint32_t y; +// }; +// +// The following describes a valid message whose payload is a Foo struct: +// // message header +// [dist4]message_header // num_bytes +// [u4]3 // version +// [u4]0 // type +// [u4]1 // flags +// [u8]1234 // request_id +// [anchr]message_header +// +// // payload +// [dist4]foo // num_bytes +// [u4]2 // version +// [dist8]bar_ptr // x +// [u4]0xABCD // y +// [u4]0 // padding +// [anchr]foo +// +// [anchr]bar_ptr +// [dist4]bar // num_bytes +// [u4]3 // version +// [s4]-1 // a +// [b]00000010 // b and c +// 0 0 0 // padding +// [anchr]bar + +// Parses validation test input. +// On success, |data| and |num_handles| store the parsing result, +// |error_message| is cleared; on failure, |error_message| is set to a message +// describing the error, |data| is cleared and |num_handles| set to 0. +// Note: For now, this method only works on little-endian platforms. +bool ParseValidationTestInput(const std::string& input, + std::vector<uint8_t>* data, + size_t* num_handles, + std::string* error_message); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_VALIDATION_TEST_INPUT_PARSER_H_ diff --git a/mojo/public/cpp/bindings/tests/validation_unittest.cc b/mojo/public/cpp/bindings/tests/validation_unittest.cc new file mode 100644 index 0000000000..7af7396d4e --- /dev/null +++ b/mojo/public/cpp/bindings/tests/validation_unittest.cc @@ -0,0 +1,498 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <algorithm> +#include <string> +#include <utility> +#include <vector> + +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/c/system/macros.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/connector.h" +#include "mojo/public/cpp/bindings/filter_chain.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/message_header_validator.h" +#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/test_support/test_support.h" +#include "mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom.h" +#include "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +template <typename T> +void Append(std::vector<uint8_t>* data_vector, T data) { + size_t pos = data_vector->size(); + data_vector->resize(pos + sizeof(T)); + memcpy(&(*data_vector)[pos], &data, sizeof(T)); +} + +bool TestInputParser(const std::string& input, + bool expected_result, + const std::vector<uint8_t>& expected_data, + size_t expected_num_handles) { + std::vector<uint8_t> data; + size_t num_handles; + std::string error_message; + + bool result = + ParseValidationTestInput(input, &data, &num_handles, &error_message); + if (expected_result) { + if (result && error_message.empty() && expected_data == data && + expected_num_handles == num_handles) { + return true; + } + + // Compare with an empty string instead of checking |error_message.empty()|, + // so that the message will be printed out if the two are not equal. + EXPECT_EQ(std::string(), error_message); + EXPECT_EQ(expected_data, data); + EXPECT_EQ(expected_num_handles, num_handles); + return false; + } + + EXPECT_FALSE(error_message.empty()); + return !result && !error_message.empty(); +} + +std::vector<std::string> GetMatchingTests(const std::vector<std::string>& names, + const std::string& prefix) { + const std::string suffix = ".data"; + std::vector<std::string> tests; + for (size_t i = 0; i < names.size(); ++i) { + if (names[i].size() >= suffix.size() && + names[i].substr(0, prefix.size()) == prefix && + names[i].substr(names[i].size() - suffix.size()) == suffix) + tests.push_back(names[i].substr(0, names[i].size() - suffix.size())); + } + return tests; +} + +bool ReadFile(const std::string& path, std::string* result) { + FILE* fp = OpenSourceRootRelativeFile(path.c_str()); + if (!fp) { + ADD_FAILURE() << "File not found: " << path; + return false; + } + fseek(fp, 0, SEEK_END); + size_t size = static_cast<size_t>(ftell(fp)); + if (size == 0) { + result->clear(); + fclose(fp); + return true; + } + fseek(fp, 0, SEEK_SET); + result->resize(size); + size_t size_read = fread(&result->at(0), 1, size, fp); + fclose(fp); + return size == size_read; +} + +bool ReadAndParseDataFile(const std::string& path, + std::vector<uint8_t>* data, + size_t* num_handles) { + std::string input; + if (!ReadFile(path, &input)) + return false; + + std::string error_message; + if (!ParseValidationTestInput(input, data, num_handles, &error_message)) { + ADD_FAILURE() << error_message; + return false; + } + + return true; +} + +bool ReadResultFile(const std::string& path, std::string* result) { + if (!ReadFile(path, result)) + return false; + + // Result files are new-line delimited text files. Remove any CRs. + result->erase(std::remove(result->begin(), result->end(), '\r'), + result->end()); + + // Remove trailing LFs. + size_t pos = result->find_last_not_of('\n'); + if (pos == std::string::npos) + result->clear(); + else + result->resize(pos + 1); + + return true; +} + +std::string GetPath(const std::string& root, const std::string& suffix) { + return "mojo/public/interfaces/bindings/tests/data/validation/" + root + + suffix; +} + +// |message| should be a newly created object. +bool ReadTestCase(const std::string& test, + Message* message, + std::string* expected) { + std::vector<uint8_t> data; + size_t num_handles; + if (!ReadAndParseDataFile(GetPath(test, ".data"), &data, &num_handles) || + !ReadResultFile(GetPath(test, ".expected"), expected)) { + return false; + } + + message->Initialize(static_cast<uint32_t>(data.size()), + false /* zero_initialized */); + if (!data.empty()) + memcpy(message->mutable_data(), &data[0], data.size()); + message->mutable_handles()->resize(num_handles); + + return true; +} + +void RunValidationTests(const std::string& prefix, + MessageReceiver* test_message_receiver) { + std::vector<std::string> names = + EnumerateSourceRootRelativeDirectory(GetPath("", "")); + std::vector<std::string> tests = GetMatchingTests(names, prefix); + ASSERT_FALSE(tests.empty()); + + for (size_t i = 0; i < tests.size(); ++i) { + Message message; + std::string expected; + ASSERT_TRUE(ReadTestCase(tests[i], &message, &expected)); + + std::string result; + base::RunLoop run_loop; + mojo::internal::ValidationErrorObserverForTesting observer( + run_loop.QuitClosure()); + ignore_result(test_message_receiver->Accept(&message)); + if (expected != "PASS") // Observer only gets called on errors. + run_loop.Run(); + if (observer.last_error() == mojo::internal::VALIDATION_ERROR_NONE) + result = "PASS"; + else + result = mojo::internal::ValidationErrorToString(observer.last_error()); + + EXPECT_EQ(expected, result) << "failed test: " << tests[i]; + } +} + +class DummyMessageReceiver : public MessageReceiver { + public: + bool Accept(Message* message) override { + return true; // Any message is OK. + } +}; + +class ValidationTest : public testing::Test { + public: + ValidationTest() {} + + protected: + base::MessageLoop loop_; +}; + +class ValidationIntegrationTest : public ValidationTest { + public: + ValidationIntegrationTest() : test_message_receiver_(nullptr) {} + + ~ValidationIntegrationTest() override {} + + void SetUp() override { + ScopedMessagePipeHandle tester_endpoint; + ASSERT_EQ(MOJO_RESULT_OK, + CreateMessagePipe(nullptr, &tester_endpoint, &testee_endpoint_)); + test_message_receiver_ = + new TestMessageReceiver(this, std::move(tester_endpoint)); + } + + void TearDown() override { + delete test_message_receiver_; + test_message_receiver_ = nullptr; + + // Make sure that the other end receives the OnConnectionError() + // notification. + PumpMessages(); + } + + MessageReceiver* test_message_receiver() { return test_message_receiver_; } + + ScopedMessagePipeHandle testee_endpoint() { + return std::move(testee_endpoint_); + } + + private: + class TestMessageReceiver : public MessageReceiver { + public: + TestMessageReceiver(ValidationIntegrationTest* owner, + ScopedMessagePipeHandle handle) + : owner_(owner), + connector_(std::move(handle), + mojo::Connector::SINGLE_THREADED_SEND, + base::ThreadTaskRunnerHandle::Get()) { + connector_.set_enforce_errors_from_incoming_receiver(false); + } + ~TestMessageReceiver() override {} + + bool Accept(Message* message) override { + return connector_.Accept(message); + } + + public: + ValidationIntegrationTest* owner_; + mojo::Connector connector_; + }; + + void PumpMessages() { base::RunLoop().RunUntilIdle(); } + + TestMessageReceiver* test_message_receiver_; + ScopedMessagePipeHandle testee_endpoint_; +}; + +class IntegrationTestInterfaceImpl : public IntegrationTestInterface { + public: + ~IntegrationTestInterfaceImpl() override {} + + void Method0(BasicStructPtr param0, + const Method0Callback& callback) override { + callback.Run(std::vector<uint8_t>()); + } +}; + +TEST_F(ValidationTest, InputParser) { + { + // The parser, as well as Append() defined above, assumes that this code is + // running on a little-endian platform. Test whether that is true. + uint16_t x = 1; + ASSERT_EQ(1, *(reinterpret_cast<char*>(&x))); + } + { + // Test empty input. + std::string input; + std::vector<uint8_t> expected; + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + // Test input that only consists of comments and whitespaces. + std::string input = " \t // hello world \n\r \t// the answer is 42 "; + std::vector<uint8_t> expected; + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = + "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" + "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; + std::vector<uint8_t> expected; + Append(&expected, static_cast<uint8_t>(0x10)); + Append(&expected, static_cast<uint16_t>(65535)); + Append(&expected, static_cast<uint32_t>(65536)); + Append(&expected, static_cast<uint64_t>(0xffffffffffffffff)); + Append(&expected, static_cast<uint8_t>(0)); + Append(&expected, static_cast<uint8_t>(0xff)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; + std::vector<uint8_t> expected; + Append(&expected, -static_cast<int64_t>(0x800)); + Append(&expected, static_cast<int8_t>(-128)); + Append(&expected, static_cast<int16_t>(0)); + Append(&expected, static_cast<int32_t>(-40)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; + std::vector<uint8_t> expected; + Append(&expected, static_cast<uint8_t>(11)); + Append(&expected, static_cast<uint8_t>(128)); + Append(&expected, static_cast<uint8_t>(0)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[f]+.3e9 [d]-10.03"; + std::vector<uint8_t> expected; + Append(&expected, +.3e9f); + Append(&expected, -10.03); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; + std::vector<uint8_t> expected; + Append(&expected, static_cast<uint32_t>(14)); + Append(&expected, static_cast<uint8_t>(0)); + Append(&expected, static_cast<uint64_t>(9)); + Append(&expected, static_cast<uint8_t>(0)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 0)); + } + { + std::string input = "// This message has handles! \n[handles]50 [u8]2"; + std::vector<uint8_t> expected; + Append(&expected, static_cast<uint64_t>(2)); + + EXPECT_TRUE(TestInputParser(input, true, expected, 50)); + } + + // Test some failure cases. + { + const char* 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", + nullptr}; + + for (size_t i = 0; error_inputs[i]; ++i) { + std::vector<uint8_t> expected; + if (!TestInputParser(error_inputs[i], false, expected, 0)) + ADD_FAILURE() << "Unexpected test result for: " << error_inputs[i]; + } + } +} + +TEST_F(ValidationTest, Conformance) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append<mojo::MessageHeaderValidator>(); + validators.Append<ConformanceTestInterface::RequestValidator_>(); + + RunValidationTests("conformance_", &validators); +} + +TEST_F(ValidationTest, AssociatedConformace) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append<mojo::MessageHeaderValidator>(); + validators.Append<AssociatedConformanceTestInterface::RequestValidator_>(); + + RunValidationTests("associated_conformance_", &validators); +} + +// This test is similar to Conformance test but its goal is specifically +// do bounds-check testing of message validation. For example we test the +// detection of off-by-one errors in method ordinals. +TEST_F(ValidationTest, BoundsCheck) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append<mojo::MessageHeaderValidator>(); + validators.Append<BoundsCheckTestInterface::RequestValidator_>(); + + RunValidationTests("boundscheck_", &validators); +} + +// This test is similar to the Conformance test but for responses. +TEST_F(ValidationTest, ResponseConformance) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append<mojo::MessageHeaderValidator>(); + validators.Append<ConformanceTestInterface::ResponseValidator_>(); + + RunValidationTests("resp_conformance_", &validators); +} + +// This test is similar to the BoundsCheck test but for responses. +TEST_F(ValidationTest, ResponseBoundsCheck) { + DummyMessageReceiver dummy_receiver; + mojo::FilterChain validators(&dummy_receiver); + validators.Append<mojo::MessageHeaderValidator>(); + validators.Append<BoundsCheckTestInterface::ResponseValidator_>(); + + RunValidationTests("resp_boundscheck_", &validators); +} + +// Test that InterfacePtr<X> applies the correct validators and they don't +// conflict with each other: +// - MessageHeaderValidator +// - X::ResponseValidator_ +TEST_F(ValidationIntegrationTest, InterfacePtr) { + IntegrationTestInterfacePtr interface_ptr = MakeProxy( + InterfacePtrInfo<IntegrationTestInterface>(testee_endpoint(), 0u)); + interface_ptr.internal_state()->EnableTestingMode(); + + RunValidationTests("integration_intf_resp", test_message_receiver()); + RunValidationTests("integration_msghdr", test_message_receiver()); +} + +// Test that Binding<X> applies the correct validators and they don't +// conflict with each other: +// - MessageHeaderValidator +// - X::RequestValidator_ +TEST_F(ValidationIntegrationTest, Binding) { + IntegrationTestInterfaceImpl interface_impl; + Binding<IntegrationTestInterface> binding( + &interface_impl, + MakeRequest<IntegrationTestInterface>(testee_endpoint())); + binding.EnableTestingMode(); + + RunValidationTests("integration_intf_rqst", test_message_receiver()); + RunValidationTests("integration_msghdr", test_message_receiver()); +} + +// Test pointer validation (specifically, that the encoded offset is 32-bit) +TEST_F(ValidationTest, ValidateEncodedPointer) { + uint64_t offset; + + offset = 0ULL; + EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset)); + + offset = 1ULL; + EXPECT_TRUE(mojo::internal::ValidateEncodedPointer(&offset)); + + // offset must be <= 32-bit. + offset = std::numeric_limits<uint32_t>::max() + 1ULL; + EXPECT_FALSE(mojo::internal::ValidateEncodedPointer(&offset)); +} + +// Tests the IsKnownEnumValue() function generated for BasicEnum. +TEST(EnumValueValidationTest, BasicEnum) { + // BasicEnum can have -3,0,1,10 as possible integral values. + EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-4))); + EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(-3))); + EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-2))); + EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(-1))); + EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(0))); + EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(1))); + EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(2))); + EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(9))); + // In the mojom, we represent this value as hex (0xa). + EXPECT_TRUE(IsKnownEnumValue(static_cast<BasicEnum>(10))); + EXPECT_FALSE(IsKnownEnumValue(static_cast<BasicEnum>(11))); +} + +// Tests the IsKnownEnumValue() method generated for StructWithEnum. +TEST(EnumValueValidationTest, EnumWithin) { + // StructWithEnum::EnumWithin can have [0,4] as possible integral values. + EXPECT_FALSE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(-1))); + EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(0))); + EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(1))); + EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(2))); + EXPECT_TRUE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(3))); + EXPECT_FALSE(IsKnownEnumValue(static_cast<StructWithEnum::EnumWithin>(4))); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/variant_test_util.h b/mojo/public/cpp/bindings/tests/variant_test_util.h new file mode 100644 index 0000000000..3f6b1f1b5f --- /dev/null +++ b/mojo/public/cpp/bindings/tests/variant_test_util.h @@ -0,0 +1,32 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_ + +#include <string.h> + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace mojo { +namespace test { + +// Converts a request of Interface1 to a request of Interface0. Interface0 and +// Interface1 are expected to be two variants of the same mojom interface. +// In real-world use cases, users shouldn't need to worry about this. Because it +// is rare to deal with two variants of the same interface in the same app. +template <typename Interface0, typename Interface1> +InterfaceRequest<Interface0> ConvertInterfaceRequest( + InterfaceRequest<Interface1> request) { + DCHECK_EQ(0, strcmp(Interface0::Name_, Interface1::Name_)); + InterfaceRequest<Interface0> result; + result.Bind(request.PassMessagePipe()); + return result; +} + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TESTS_VARIANT_TEST_UTIL_H_ diff --git a/mojo/public/cpp/bindings/tests/versioning_apptest.cc b/mojo/public/cpp/bindings/tests/versioning_apptest.cc new file mode 100644 index 0000000000..95a89c0114 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/versioning_apptest.cc @@ -0,0 +1,123 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "mojo/public/interfaces/bindings/tests/versioning_test_client.mojom.h" +#include "services/service_manager/public/cpp/application_test_base.h" +#include "services/service_manager/public/cpp/connector.h" + +namespace mojo { +namespace test { +namespace versioning { + +class VersioningApplicationTest : public ApplicationTestBase { + public: + VersioningApplicationTest() : ApplicationTestBase() {} + ~VersioningApplicationTest() override {} + + protected: + // ApplicationTestBase overrides. + void SetUp() override { + ApplicationTestBase::SetUp(); + + connector()->BindInterface("versioning_test_service", &database_); + } + + HumanResourceDatabasePtr database_; + + private: + DISALLOW_COPY_AND_ASSIGN(VersioningApplicationTest); +}; + +TEST_F(VersioningApplicationTest, Struct) { + // The service side uses a newer version of Employee defintion. + // The returned struct should be truncated. + EmployeePtr employee(Employee::New()); + employee->employee_id = 1; + employee->name = "Homer Simpson"; + employee->department = DEPARTMENT_DEV; + + database_->QueryEmployee(1, true, + [&employee](EmployeePtr returned_employee, + Array<uint8_t> returned_finger_print) { + EXPECT_TRUE(employee->Equals(*returned_employee)); + EXPECT_FALSE(returned_finger_print.is_null()); + }); + database_.WaitForIncomingResponse(); + + // Passing a struct of older version to the service side works. + EmployeePtr new_employee(Employee::New()); + new_employee->employee_id = 2; + new_employee->name = "Marge Simpson"; + new_employee->department = DEPARTMENT_SALES; + + database_->AddEmployee(new_employee.Clone(), + [](bool success) { EXPECT_TRUE(success); }); + database_.WaitForIncomingResponse(); + + database_->QueryEmployee( + 2, false, [&new_employee](EmployeePtr returned_employee, + Array<uint8_t> returned_finger_print) { + EXPECT_TRUE(new_employee->Equals(*returned_employee)); + EXPECT_TRUE(returned_finger_print.is_null()); + }); + database_.WaitForIncomingResponse(); +} + +TEST_F(VersioningApplicationTest, QueryVersion) { + EXPECT_EQ(0u, database_.version()); + database_.QueryVersion([](uint32_t version) { EXPECT_EQ(1u, version); }); + database_.WaitForIncomingResponse(); + EXPECT_EQ(1u, database_.version()); +} + +TEST_F(VersioningApplicationTest, RequireVersion) { + EXPECT_EQ(0u, database_.version()); + + database_.RequireVersion(1); + EXPECT_EQ(1u, database_.version()); + database_->QueryEmployee(3, false, + [](EmployeePtr returned_employee, + Array<uint8_t> returned_finger_print) {}); + database_.WaitForIncomingResponse(); + EXPECT_FALSE(database_.encountered_error()); + + // Requiring a version higher than what the service side implements will close + // the pipe. + database_.RequireVersion(3); + EXPECT_EQ(3u, database_.version()); + database_->QueryEmployee(1, false, + [](EmployeePtr returned_employee, + Array<uint8_t> returned_finger_print) {}); + database_.WaitForIncomingResponse(); + EXPECT_TRUE(database_.encountered_error()); +} + +TEST_F(VersioningApplicationTest, CallNonexistentMethod) { + EXPECT_EQ(0u, database_.version()); + + Array<uint8_t> new_finger_print(128); + for (size_t i = 0; i < 128; ++i) + new_finger_print[i] = i + 13; + + // Although the client side doesn't know whether the service side supports + // version 1, calling a version 1 method succeeds as long as the service side + // supports version 1. + database_->AttachFingerPrint(1, new_finger_print.Clone(), + [](bool success) { EXPECT_TRUE(success); }); + database_.WaitForIncomingResponse(); + + // Calling a version 2 method (which the service side doesn't support) closes + // the pipe. + database_->ListEmployeeIds([](Array<uint64_t> ids) { EXPECT_TRUE(false); }); + database_.WaitForIncomingResponse(); + EXPECT_TRUE(database_.encountered_error()); +} + +} // namespace versioning +} // namespace examples +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/versioning_test_service.cc b/mojo/public/cpp/bindings/tests/versioning_test_service.cc new file mode 100644 index 0000000000..313a6249e2 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/versioning_test_service.cc @@ -0,0 +1,127 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stdint.h> + +#include <map> +#include <utility> + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/strong_binding.h" +#include "mojo/public/interfaces/bindings/tests/versioning_test_service.mojom.h" +#include "services/service_manager/public/c/main.h" +#include "services/service_manager/public/cpp/interface_factory.h" +#include "services/service_manager/public/cpp/service.h" +#include "services/service_manager/public/cpp/service_runner.h" + +namespace mojo { +namespace test { +namespace versioning { + +struct EmployeeInfo { + public: + EmployeeInfo() {} + + EmployeePtr employee; + Array<uint8_t> finger_print; + + private: + DISALLOW_COPY_AND_ASSIGN(EmployeeInfo); +}; + +class HumanResourceDatabaseImpl : public HumanResourceDatabase { + public: + explicit HumanResourceDatabaseImpl( + InterfaceRequest<HumanResourceDatabase> request) + : strong_binding_(this, std::move(request)) { + // Pretend that there is already some data in the system. + EmployeeInfo* info = new EmployeeInfo(); + employees_[1] = info; + info->employee = Employee::New(); + info->employee->employee_id = 1; + info->employee->name = "Homer Simpson"; + info->employee->department = DEPARTMENT_DEV; + info->employee->birthday = Date::New(); + info->employee->birthday->year = 1955; + info->employee->birthday->month = 5; + info->employee->birthday->day = 12; + info->finger_print.resize(1024); + for (uint32_t i = 0; i < 1024; ++i) + info->finger_print[i] = i; + } + + ~HumanResourceDatabaseImpl() override { + for (auto iter = employees_.begin(); iter != employees_.end(); ++iter) + delete iter->second; + } + + void AddEmployee(EmployeePtr employee, + const AddEmployeeCallback& callback) override { + uint64_t id = employee->employee_id; + if (employees_.find(id) == employees_.end()) + employees_[id] = new EmployeeInfo(); + employees_[id]->employee = std::move(employee); + callback.Run(true); + } + + void QueryEmployee(uint64_t id, + bool retrieve_finger_print, + const QueryEmployeeCallback& callback) override { + if (employees_.find(id) == employees_.end()) { + callback.Run(nullptr, Array<uint8_t>()); + return; + } + callback.Run(employees_[id]->employee.Clone(), + retrieve_finger_print ? employees_[id]->finger_print.Clone() + : Array<uint8_t>()); + } + + void AttachFingerPrint(uint64_t id, + Array<uint8_t> finger_print, + const AttachFingerPrintCallback& callback) override { + if (employees_.find(id) == employees_.end()) { + callback.Run(false); + return; + } + employees_[id]->finger_print = std::move(finger_print); + callback.Run(true); + } + + private: + std::map<uint64_t, EmployeeInfo*> employees_; + + StrongBinding<HumanResourceDatabase> strong_binding_; +}; + +class HumanResourceSystemServer + : public service_manager::Service, + public InterfaceFactory<HumanResourceDatabase> { + public: + HumanResourceSystemServer() {} + + // service_manager::Service implementation. + bool OnConnect(Connection* connection) override { + connection->AddInterface<HumanResourceDatabase>(this); + return true; + } + + // InterfaceFactory<HumanResourceDatabase> implementation. + void Create(Connection* connection, + InterfaceRequest<HumanResourceDatabase> request) override { + // It will be deleted automatically when the underlying pipe encounters a + // connection error. + new HumanResourceDatabaseImpl(std::move(request)); + } +}; + +} // namespace versioning +} // namespace test +} // namespace mojo + +MojoResult ServiceMain(MojoHandle request) { + mojo::ServiceRunner runner( + new mojo::test::versioning::HumanResourceSystemServer()); + + return runner.Run(request); +} diff --git a/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc new file mode 100644 index 0000000000..959d25b368 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc @@ -0,0 +1,60 @@ +// 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. + +#include "mojo/public/cpp/bindings/lib/wtf_hash_util.h" + +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom-blink.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/wtf/HashFunctions.h" + +namespace mojo { +namespace test { +namespace { + +using WTFHashTest = testing::Test; + +TEST_F(WTFHashTest, NestedStruct) { + // Just check that this template instantiation compiles. + ASSERT_EQ(::mojo::internal::Hash( + ::mojo::internal::kHashSeed, + blink::SimpleNestedStruct::New(blink::ContainsOther::New(1))), + ::mojo::internal::Hash( + ::mojo::internal::kHashSeed, + blink::SimpleNestedStruct::New(blink::ContainsOther::New(1)))); +} + +TEST_F(WTFHashTest, UnmappedNativeStruct) { + // Just check that this template instantiation compiles. + ASSERT_EQ(::mojo::internal::Hash(::mojo::internal::kHashSeed, + blink::UnmappedNativeStruct::New()), + ::mojo::internal::Hash(::mojo::internal::kHashSeed, + blink::UnmappedNativeStruct::New())); +} + +TEST_F(WTFHashTest, Enum) { + // Just check that this template instantiation compiles. + + // Top-level. + ASSERT_EQ(WTF::DefaultHash<blink::TopLevelEnum>::Hash().hash( + blink::TopLevelEnum::E0), + WTF::DefaultHash<blink::TopLevelEnum>::Hash().hash( + blink::TopLevelEnum::E0)); + + // Nested in struct. + ASSERT_EQ(WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().hash( + blink::TestWTFStruct::NestedEnum::E0), + WTF::DefaultHash<blink::TestWTFStruct::NestedEnum>::Hash().hash( + blink::TestWTFStruct::NestedEnum::E0)); + + // Nested in interface. + ASSERT_EQ(WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().hash( + blink::TestWTF::NestedEnum::E0), + WTF::DefaultHash<blink::TestWTF::NestedEnum>::Hash().hash( + blink::TestWTF::NestedEnum::E0)); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc new file mode 100644 index 0000000000..dc40143168 --- /dev/null +++ b/mojo/public/cpp/bindings/tests/wtf_map_unittest.cc @@ -0,0 +1,41 @@ +// 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. + +#include "mojo/public/cpp/bindings/tests/rect_blink.h" +#include "mojo/public/interfaces/bindings/tests/rect.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_structs.mojom-blink.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace test { +namespace { + +TEST(WTFMapTest, StructKey) { + WTF::HashMap<blink::RectPtr, int32_t> map; + map.insert(blink::Rect::New(1, 2, 3, 4), 123); + + blink::RectPtr key = blink::Rect::New(1, 2, 3, 4); + ASSERT_NE(map.end(), map.find(key)); + ASSERT_EQ(123, map.find(key)->value); + + map.erase(key); + ASSERT_EQ(0u, map.size()); +} + +TEST(WTFMapTest, TypemappedStructKey) { + WTF::HashMap<blink::ContainsHashablePtr, int32_t> map; + map.insert(blink::ContainsHashable::New(RectBlink(1, 2, 3, 4)), 123); + + blink::ContainsHashablePtr key = + blink::ContainsHashable::New(RectBlink(1, 2, 3, 4)); + ASSERT_NE(map.end(), map.find(key)); + ASSERT_EQ(123, map.find(key)->value); + + map.erase(key); + ASSERT_EQ(0u, map.size()); +} + +} // namespace +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc b/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc new file mode 100644 index 0000000000..363ef7cdab --- /dev/null +++ b/mojo/public/cpp/bindings/tests/wtf_types_unittest.cc @@ -0,0 +1,245 @@ +// 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. + +#include "base/bind.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/fixed_buffer.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/wtf_serialization.h" +#include "mojo/public/cpp/bindings/tests/variant_test_util.h" +#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom-blink.h" +#include "mojo/public/interfaces/bindings/tests/test_wtf_types.mojom.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/wtf/text/StringHash.h" + +namespace mojo { +namespace test { +namespace { + +const char kHelloWorld[] = "hello world"; + +// Replace the "o"s in "hello world" with "o"s with acute. +const char kUTF8HelloWorld[] = "hell\xC3\xB3 w\xC3\xB3rld"; + +class TestWTFImpl : public TestWTF { + public: + explicit TestWTFImpl(TestWTFRequest request) + : binding_(this, std::move(request)) {} + + // mojo::test::TestWTF implementation: + void EchoString(const base::Optional<std::string>& str, + const EchoStringCallback& callback) override { + callback.Run(str); + } + + void EchoStringArray( + const base::Optional<std::vector<base::Optional<std::string>>>& arr, + const EchoStringArrayCallback& callback) override { + callback.Run(std::move(arr)); + } + + void EchoStringMap( + const base::Optional< + std::unordered_map<std::string, base::Optional<std::string>>>& + str_map, + const EchoStringMapCallback& callback) override { + callback.Run(std::move(str_map)); + } + + private: + Binding<TestWTF> binding_; +}; + +class WTFTypesTest : public testing::Test { + public: + WTFTypesTest() {} + + private: + base::MessageLoop loop_; +}; + +WTF::Vector<WTF::String> ConstructStringArray() { + WTF::Vector<WTF::String> strs(4); + // strs[0] is null. + // strs[1] is empty. + strs[1] = ""; + strs[2] = kHelloWorld; + strs[3] = WTF::String::fromUTF8(kUTF8HelloWorld); + + return strs; +} + +WTF::HashMap<WTF::String, WTF::String> ConstructStringMap() { + WTF::HashMap<WTF::String, WTF::String> str_map; + // A null string as value. + str_map.insert("0", WTF::String()); + str_map.insert("1", kHelloWorld); + str_map.insert("2", WTF::String::fromUTF8(kUTF8HelloWorld)); + + return str_map; +} + +void ExpectString(const WTF::String& expected_string, + const base::Closure& closure, + const WTF::String& string) { + EXPECT_EQ(expected_string, string); + closure.Run(); +} + +void ExpectStringArray(WTF::Optional<WTF::Vector<WTF::String>>* expected_arr, + const base::Closure& closure, + const WTF::Optional<WTF::Vector<WTF::String>>& arr) { + EXPECT_EQ(*expected_arr, arr); + closure.Run(); +} + +void ExpectStringMap( + WTF::Optional<WTF::HashMap<WTF::String, WTF::String>>* expected_map, + const base::Closure& closure, + const WTF::Optional<WTF::HashMap<WTF::String, WTF::String>>& map) { + EXPECT_EQ(*expected_map, map); + closure.Run(); +} + +} // namespace + +TEST_F(WTFTypesTest, Serialization_WTFVectorToWTFVector) { + using MojomType = ArrayDataView<StringDataView>; + + WTF::Vector<WTF::String> strs = ConstructStringArray(); + auto cloned_strs = strs; + + mojo::internal::SerializationContext context; + size_t size = + mojo::internal::PrepareToSerialize<MojomType>(cloned_strs, &context); + + mojo::internal::FixedBufferForTesting buf(size); + typename mojo::internal::MojomTypeTraits<MojomType>::Data* data; + mojo::internal::ContainerValidateParams validate_params( + 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr)); + mojo::internal::Serialize<MojomType>(cloned_strs, &buf, &data, + &validate_params, &context); + + WTF::Vector<WTF::String> strs2; + mojo::internal::Deserialize<MojomType>(data, &strs2, &context); + + EXPECT_EQ(strs, strs2); +} + +TEST_F(WTFTypesTest, Serialization_WTFVectorToStlVector) { + using MojomType = ArrayDataView<StringDataView>; + + WTF::Vector<WTF::String> strs = ConstructStringArray(); + auto cloned_strs = strs; + + mojo::internal::SerializationContext context; + size_t size = + mojo::internal::PrepareToSerialize<MojomType>(cloned_strs, &context); + + mojo::internal::FixedBufferForTesting buf(size); + typename mojo::internal::MojomTypeTraits<MojomType>::Data* data; + mojo::internal::ContainerValidateParams validate_params( + 0, true, new mojo::internal::ContainerValidateParams(0, false, nullptr)); + mojo::internal::Serialize<MojomType>(cloned_strs, &buf, &data, + &validate_params, &context); + + std::vector<base::Optional<std::string>> strs2; + mojo::internal::Deserialize<MojomType>(data, &strs2, &context); + + ASSERT_EQ(4u, strs2.size()); + EXPECT_FALSE(strs2[0]); + EXPECT_EQ("", *strs2[1]); + EXPECT_EQ(kHelloWorld, *strs2[2]); + EXPECT_EQ(kUTF8HelloWorld, *strs2[3]); +} + +TEST_F(WTFTypesTest, Serialization_PublicAPI) { + blink::TestWTFStructPtr input(blink::TestWTFStruct::New(kHelloWorld, 42)); + + blink::TestWTFStructPtr cloned_input = input.Clone(); + + auto data = blink::TestWTFStruct::Serialize(&input); + + blink::TestWTFStructPtr output; + ASSERT_TRUE(blink::TestWTFStruct::Deserialize(std::move(data), &output)); + EXPECT_TRUE(cloned_input.Equals(output)); +} + +TEST_F(WTFTypesTest, SendString) { + blink::TestWTFPtr ptr; + TestWTFImpl impl(ConvertInterfaceRequest<TestWTF>(MakeRequest(&ptr))); + + WTF::Vector<WTF::String> strs = ConstructStringArray(); + + for (size_t i = 0; i < strs.size(); ++i) { + base::RunLoop loop; + // Test that a WTF::String is unchanged after the following conversion: + // - serialized; + // - deserialized as base::Optional<std::string>; + // - serialized; + // - deserialized as WTF::String. + ptr->EchoString(strs[i], + base::Bind(&ExpectString, strs[i], loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(WTFTypesTest, SendStringArray) { + blink::TestWTFPtr ptr; + TestWTFImpl impl(ConvertInterfaceRequest<TestWTF>(MakeRequest(&ptr))); + + WTF::Optional<WTF::Vector<WTF::String>> arrs[3]; + // arrs[0] is empty. + arrs[0].emplace(); + // arrs[1] is null. + arrs[2] = ConstructStringArray(); + + for (size_t i = 0; i < arraysize(arrs); ++i) { + base::RunLoop loop; + // Test that a WTF::Optional<WTF::Vector<WTF::String>> is unchanged after + // the following conversion: + // - serialized; + // - deserialized as + // base::Optional<std::vector<base::Optional<std::string>>>; + // - serialized; + // - deserialized as WTF::Optional<WTF::Vector<WTF::String>>. + ptr->EchoStringArray( + arrs[i], base::Bind(&ExpectStringArray, base::Unretained(&arrs[i]), + loop.QuitClosure())); + loop.Run(); + } +} + +TEST_F(WTFTypesTest, SendStringMap) { + blink::TestWTFPtr ptr; + TestWTFImpl impl(ConvertInterfaceRequest<TestWTF>(MakeRequest(&ptr))); + + WTF::Optional<WTF::HashMap<WTF::String, WTF::String>> maps[3]; + // maps[0] is empty. + maps[0].emplace(); + // maps[1] is null. + maps[2] = ConstructStringMap(); + + for (size_t i = 0; i < arraysize(maps); ++i) { + base::RunLoop loop; + // Test that a WTF::Optional<WTF::HashMap<WTF::String, WTF::String>> is + // unchanged after the following conversion: + // - serialized; + // - deserialized as base::Optional< + // std::unordered_map<std::string, base::Optional<std::string>>>; + // - serialized; + // - deserialized as WTF::Optional<WTF::HashMap<WTF::String, + // WTF::String>>. + ptr->EchoStringMap(maps[i], + base::Bind(&ExpectStringMap, base::Unretained(&maps[i]), + loop.QuitClosure())); + loop.Run(); + } +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/bindings/thread_safe_interface_ptr.h b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h new file mode 100644 index 0000000000..740687f379 --- /dev/null +++ b/mojo/public/cpp/bindings/thread_safe_interface_ptr.h @@ -0,0 +1,394 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_THREAD_SAFE_INTERFACE_PTR_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_THREAD_SAFE_INTERFACE_PTR_H_ + +#include <memory> + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/stl_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/cpp/bindings/associated_group.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/message.h" +#include "mojo/public/cpp/bindings/sync_call_restrictions.h" +#include "mojo/public/cpp/bindings/sync_event_watcher.h" + +// ThreadSafeInterfacePtr wraps a non-thread-safe InterfacePtr and proxies +// messages to it. Async calls are posted to the thread that the InteracePtr is +// bound to, and the responses are posted back. Sync calls are dispatched +// directly if the call is made on the thread that the wrapped InterfacePtr is +// bound to, or posted otherwise. It's important to be aware that sync calls +// block both the calling thread and the InterfacePtr thread. That means that +// you cannot make sync calls through a ThreadSafeInterfacePtr if the +// underlying InterfacePtr is bound to a thread that cannot block, like the IO +// thread. + +namespace mojo { + +// Instances of this class may be used from any thread to serialize |Interface| +// messages and forward them elsewhere. In general you should use one of the +// ThreadSafeInterfacePtrBase helper aliases defined below, but this type may be +// useful if you need/want to manually manage the lifetime of the underlying +// proxy object which will be used to ultimately send messages. +template <typename Interface> +class ThreadSafeForwarder : public MessageReceiverWithResponder { + public: + using ProxyType = typename Interface::Proxy_; + using ForwardMessageCallback = base::Callback<void(Message)>; + using ForwardMessageWithResponderCallback = + base::Callback<void(Message, std::unique_ptr<MessageReceiver>)>; + + // Constructs a ThreadSafeForwarder through which Messages are forwarded to + // |forward| or |forward_with_responder| by posting to |task_runner|. + // + // Any message sent through this forwarding interface will dispatch its reply, + // if any, back to the thread which called the corresponding interface method. + ThreadSafeForwarder( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner, + const ForwardMessageCallback& forward, + const ForwardMessageWithResponderCallback& forward_with_responder, + const AssociatedGroup& associated_group) + : proxy_(this), + task_runner_(task_runner), + forward_(forward), + forward_with_responder_(forward_with_responder), + associated_group_(associated_group), + sync_calls_(new InProgressSyncCalls()) {} + + ~ThreadSafeForwarder() override { + // If there are ongoing sync calls signal their completion now. + base::AutoLock l(sync_calls_->lock); + for (const auto& pending_response : sync_calls_->pending_responses) + pending_response->event.Signal(); + } + + ProxyType& proxy() { return proxy_; } + + private: + // MessageReceiverWithResponder implementation: + bool Accept(Message* message) override { + if (!message->associated_endpoint_handles()->empty()) { + // If this DCHECK fails, it is likely because: + // - This is a non-associated interface pointer setup using + // PtrWrapper::BindOnTaskRunner( + // InterfacePtrInfo<InterfaceType> ptr_info); + // Please see the TODO in that method. + // - This is an associated interface which hasn't been associated with a + // message pipe. In other words, the corresponding + // AssociatedInterfaceRequest hasn't been sent. + DCHECK(associated_group_.GetController()); + message->SerializeAssociatedEndpointHandles( + associated_group_.GetController()); + } + task_runner_->PostTask(FROM_HERE, + base::Bind(forward_, base::Passed(message))); + return true; + } + + bool AcceptWithResponder( + Message* message, + std::unique_ptr<MessageReceiver> responder) override { + if (!message->associated_endpoint_handles()->empty()) { + // Please see comment for the DCHECK in the previous method. + DCHECK(associated_group_.GetController()); + message->SerializeAssociatedEndpointHandles( + associated_group_.GetController()); + } + + // Async messages are always posted (even if |task_runner_| runs tasks on + // this thread) to guarantee that two async calls can't be reordered. + if (!message->has_flag(Message::kFlagIsSync)) { + auto reply_forwarder = + base::MakeUnique<ForwardToCallingThread>(std::move(responder)); + task_runner_->PostTask( + FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message), + base::Passed(&reply_forwarder))); + return true; + } + + SyncCallRestrictions::AssertSyncCallAllowed(); + + // If the InterfacePtr is bound to this thread, dispatch it directly. + if (task_runner_->RunsTasksOnCurrentThread()) { + forward_with_responder_.Run(std::move(*message), std::move(responder)); + return true; + } + + // If the InterfacePtr is bound on another thread, post the call. + // TODO(yzshen, watk): We block both this thread and the InterfacePtr + // thread. Ideally only this thread would block. + auto response = make_scoped_refptr(new SyncResponseInfo()); + auto response_signaler = base::MakeUnique<SyncResponseSignaler>(response); + task_runner_->PostTask( + FROM_HERE, base::Bind(forward_with_responder_, base::Passed(message), + base::Passed(&response_signaler))); + + // Save the pending SyncResponseInfo so that if the sync call deletes + // |this|, we can signal the completion of the call to return from + // SyncWatch(). + auto sync_calls = sync_calls_; + { + base::AutoLock l(sync_calls->lock); + sync_calls->pending_responses.push_back(response.get()); + } + + auto assign_true = [](bool* b) { *b = true; }; + bool event_signaled = false; + SyncEventWatcher watcher(&response->event, + base::Bind(assign_true, &event_signaled)); + watcher.SyncWatch(&event_signaled); + + { + base::AutoLock l(sync_calls->lock); + base::Erase(sync_calls->pending_responses, response.get()); + } + + if (response->received) + ignore_result(responder->Accept(&response->message)); + + return true; + } + + // Data that we need to share between the threads involved in a sync call. + struct SyncResponseInfo + : public base::RefCountedThreadSafe<SyncResponseInfo> { + Message message; + bool received = false; + base::WaitableEvent event{base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED}; + + private: + friend class base::RefCountedThreadSafe<SyncResponseInfo>; + }; + + // A MessageReceiver that signals |response| when it either accepts the + // response message, or is destructed. + class SyncResponseSignaler : public MessageReceiver { + public: + explicit SyncResponseSignaler(scoped_refptr<SyncResponseInfo> response) + : response_(response) {} + + ~SyncResponseSignaler() override { + // If Accept() was not called we must still notify the waiter that the + // sync call is finished. + if (response_) + response_->event.Signal(); + } + + bool Accept(Message* message) { + response_->message = std::move(*message); + response_->received = true; + response_->event.Signal(); + response_ = nullptr; + return true; + } + + private: + scoped_refptr<SyncResponseInfo> response_; + }; + + // A record of the pending sync responses for canceling pending sync calls + // when the owning ThreadSafeForwarder is destructed. + struct InProgressSyncCalls + : public base::RefCountedThreadSafe<InProgressSyncCalls> { + // |lock| protects access to |pending_responses|. + base::Lock lock; + std::vector<SyncResponseInfo*> pending_responses; + }; + + class ForwardToCallingThread : public MessageReceiver { + public: + explicit ForwardToCallingThread(std::unique_ptr<MessageReceiver> responder) + : responder_(std::move(responder)), + caller_task_runner_(base::ThreadTaskRunnerHandle::Get()) {} + + private: + bool Accept(Message* message) { + // The current instance will be deleted when this method returns, so we + // have to relinquish the responder's ownership so it does not get + // deleted. + caller_task_runner_->PostTask( + FROM_HERE, + base::Bind(&ForwardToCallingThread::CallAcceptAndDeleteResponder, + base::Passed(std::move(responder_)), + base::Passed(std::move(*message)))); + return true; + } + + static void CallAcceptAndDeleteResponder( + std::unique_ptr<MessageReceiver> responder, + Message message) { + ignore_result(responder->Accept(&message)); + } + + std::unique_ptr<MessageReceiver> responder_; + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_; + }; + + ProxyType proxy_; + const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + const ForwardMessageCallback forward_; + const ForwardMessageWithResponderCallback forward_with_responder_; + AssociatedGroup associated_group_; + scoped_refptr<InProgressSyncCalls> sync_calls_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSafeForwarder); +}; + +template <typename InterfacePtrType> +class ThreadSafeInterfacePtrBase + : public base::RefCountedThreadSafe< + ThreadSafeInterfacePtrBase<InterfacePtrType>> { + public: + using InterfaceType = typename InterfacePtrType::InterfaceType; + using PtrInfoType = typename InterfacePtrType::PtrInfoType; + + explicit ThreadSafeInterfacePtrBase( + std::unique_ptr<ThreadSafeForwarder<InterfaceType>> forwarder) + : forwarder_(std::move(forwarder)) {} + + // Creates a ThreadSafeInterfacePtrBase wrapping an underlying non-thread-safe + // InterfacePtrType which is bound to the calling thread. All messages sent + // via this thread-safe proxy will internally be sent by first posting to this + // (the calling) thread's TaskRunner. + static scoped_refptr<ThreadSafeInterfacePtrBase> Create( + InterfacePtrType interface_ptr) { + scoped_refptr<PtrWrapper> wrapper = + new PtrWrapper(std::move(interface_ptr)); + return new ThreadSafeInterfacePtrBase(wrapper->CreateForwarder()); + } + + // Creates a ThreadSafeInterfacePtrBase which binds the underlying + // non-thread-safe InterfacePtrType on the specified TaskRunner. All messages + // sent via this thread-safe proxy will internally be sent by first posting to + // that TaskRunner. + static scoped_refptr<ThreadSafeInterfacePtrBase> Create( + PtrInfoType ptr_info, + const scoped_refptr<base::SingleThreadTaskRunner>& bind_task_runner) { + scoped_refptr<PtrWrapper> wrapper = new PtrWrapper(bind_task_runner); + wrapper->BindOnTaskRunner(std::move(ptr_info)); + return new ThreadSafeInterfacePtrBase(wrapper->CreateForwarder()); + } + + InterfaceType* get() { return &forwarder_->proxy(); } + InterfaceType* operator->() { return get(); } + InterfaceType& operator*() { return *get(); } + + private: + friend class base::RefCountedThreadSafe< + ThreadSafeInterfacePtrBase<InterfacePtrType>>; + + struct PtrWrapperDeleter; + + // Helper class which owns an |InterfacePtrType| instance on an appropriate + // thread. This is kept alive as long its bound within some + // ThreadSafeForwarder's callbacks. + class PtrWrapper + : public base::RefCountedThreadSafe<PtrWrapper, PtrWrapperDeleter> { + public: + explicit PtrWrapper(InterfacePtrType ptr) + : PtrWrapper(base::ThreadTaskRunnerHandle::Get()) { + ptr_ = std::move(ptr); + associated_group_ = *ptr_.internal_state()->associated_group(); + } + + explicit PtrWrapper( + const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) + : task_runner_(task_runner) {} + + void BindOnTaskRunner(AssociatedInterfacePtrInfo<InterfaceType> ptr_info) { + associated_group_ = AssociatedGroup(ptr_info.handle()); + task_runner_->PostTask(FROM_HERE, base::Bind(&PtrWrapper::Bind, this, + base::Passed(&ptr_info))); + } + + void BindOnTaskRunner(InterfacePtrInfo<InterfaceType> ptr_info) { + // TODO(yzhsen): At the momment we don't have a group controller + // available. That means the user won't be able to pass associated + // endpoints on this interface (at least not immediately). In order to fix + // this, we need to create a MultiplexRouter immediately and bind it to + // the interface pointer on the |task_runner_|. Therefore, MultiplexRouter + // should be able to be created on a thread different than the one that it + // is supposed to listen on. crbug.com/682334 + task_runner_->PostTask(FROM_HERE, base::Bind(&PtrWrapper::Bind, this, + base::Passed(&ptr_info))); + } + + std::unique_ptr<ThreadSafeForwarder<InterfaceType>> CreateForwarder() { + return base::MakeUnique<ThreadSafeForwarder<InterfaceType>>( + task_runner_, base::Bind(&PtrWrapper::Accept, this), + base::Bind(&PtrWrapper::AcceptWithResponder, this), + associated_group_); + } + + private: + friend struct PtrWrapperDeleter; + + ~PtrWrapper() {} + + void Bind(PtrInfoType ptr_info) { + DCHECK(task_runner_->RunsTasksOnCurrentThread()); + ptr_.Bind(std::move(ptr_info)); + } + + void Accept(Message message) { + ptr_.internal_state()->ForwardMessage(std::move(message)); + } + + void AcceptWithResponder(Message message, + std::unique_ptr<MessageReceiver> responder) { + ptr_.internal_state()->ForwardMessageWithResponder(std::move(message), + std::move(responder)); + } + + void DeleteOnCorrectThread() const { + if (!task_runner_->RunsTasksOnCurrentThread()) { + // NOTE: This is only called when there are no more references to + // |this|, so binding it unretained is both safe and necessary. + task_runner_->PostTask(FROM_HERE, + base::Bind(&PtrWrapper::DeleteOnCorrectThread, + base::Unretained(this))); + } else { + delete this; + } + } + + InterfacePtrType ptr_; + const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + AssociatedGroup associated_group_; + + DISALLOW_COPY_AND_ASSIGN(PtrWrapper); + }; + + struct PtrWrapperDeleter { + static void Destruct(const PtrWrapper* interface_ptr) { + interface_ptr->DeleteOnCorrectThread(); + } + }; + + ~ThreadSafeInterfacePtrBase() {} + + const std::unique_ptr<ThreadSafeForwarder<InterfaceType>> forwarder_; + + DISALLOW_COPY_AND_ASSIGN(ThreadSafeInterfacePtrBase); +}; + +template <typename Interface> +using ThreadSafeAssociatedInterfacePtr = + ThreadSafeInterfacePtrBase<AssociatedInterfacePtr<Interface>>; + +template <typename Interface> +using ThreadSafeInterfacePtr = + ThreadSafeInterfacePtrBase<InterfacePtr<Interface>>; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_THREAD_SAFE_INTERFACE_PTR_H_ diff --git a/mojo/public/cpp/bindings/type_converter.h b/mojo/public/cpp/bindings/type_converter.h new file mode 100644 index 0000000000..395eeb4ffe --- /dev/null +++ b/mojo/public/cpp/bindings/type_converter.h @@ -0,0 +1,116 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ + +#include <stdint.h> + +#include <vector> + +namespace mojo { + +// NOTE: TypeConverter is deprecated. Please consider StructTraits / +// UnionTraits / EnumTraits / ArrayTraits / MapTraits / StringTraits if you +// would like to convert between custom types and the wire format of mojom +// types. +// +// Specialize the following class: +// template <typename T, typename U> struct TypeConverter; +// to perform type conversion for Mojom-defined structs and arrays. Here, T is +// the target type; U is the input type. +// +// Specializations should implement the following interfaces: +// namespace mojo { +// template <> +// struct TypeConverter<X, Y> { +// static X Convert(const Y& input); +// }; +// template <> +// struct TypeConverter<Y, X> { +// static Y Convert(const X& input); +// }; +// } +// +// EXAMPLE: +// +// Suppose you have the following Mojom-defined struct: +// +// module geometry { +// struct Point { +// int32_t x; +// int32_t y; +// }; +// } +// +// Now, imagine you wanted to write a TypeConverter specialization for +// gfx::Point. It might look like this: +// +// namespace mojo { +// template <> +// struct TypeConverter<geometry::PointPtr, gfx::Point> { +// static geometry::PointPtr Convert(const gfx::Point& input) { +// geometry::PointPtr result; +// result->x = input.x(); +// result->y = input.y(); +// return result; +// } +// }; +// template <> +// struct TypeConverter<gfx::Point, geometry::PointPtr> { +// static gfx::Point Convert(const geometry::PointPtr& input) { +// return input ? gfx::Point(input->x, input->y) : gfx::Point(); +// } +// }; +// } +// +// With the above TypeConverter defined, it is possible to write code like this: +// +// void AcceptPoint(const geometry::PointPtr& input) { +// // With an explicit cast using the .To<> method. +// gfx::Point pt = input.To<gfx::Point>(); +// +// // With an explicit cast using the static From() method. +// geometry::PointPtr output = geometry::Point::From(pt); +// +// // Inferring the input type using the ConvertTo helper function. +// gfx::Point pt2 = ConvertTo<gfx::Point>(input); +// } +// +template <typename T, typename U> +struct TypeConverter; + +template <typename T, typename U> +inline T ConvertTo(const U& obj); + +// The following specialization is useful when you are converting between +// Array<POD> and std::vector<POD>. +template <typename T> +struct TypeConverter<T, T> { + static T Convert(const T& obj) { return obj; } +}; + +template <typename T, typename Container> +struct TypeConverter<std::vector<T>, Container> { + static std::vector<T> Convert(const Container& container) { + std::vector<T> output; + output.reserve(container.size()); + for (const auto& obj : container) { + output.push_back(ConvertTo<T>(obj)); + } + return output; + } +}; + +// The following helper function is useful for shorthand. The compiler can infer +// the input type, so you can write: +// OutputType out = ConvertTo<OutputType>(input); +template <typename T, typename U> +inline T ConvertTo(const U& obj) { + return TypeConverter<T, U>::Convert(obj); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_TYPE_CONVERTER_H_ diff --git a/mojo/public/cpp/bindings/union_traits.h b/mojo/public/cpp/bindings/union_traits.h new file mode 100644 index 0000000000..292ee58f27 --- /dev/null +++ b/mojo/public/cpp/bindings/union_traits.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_UNION_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_UNION_TRAITS_H_ + +namespace mojo { + +// This must be specialized for any type |T| to be serialized/deserialized as +// a mojom union. |DataViewType| is the corresponding data view type of the +// mojom union. For example, if the mojom union is example.Foo, |DataViewType| +// will be example::FooDataView, which can also be referred to by +// example::Foo::DataView (in chromium) and example::blink::Foo::DataView (in +// blink). +// +// Similar to StructTraits, each specialization of UnionTraits implements the +// following methods: +// 1. Getters for each field in the Mojom type. +// 2. Read() method. +// 3. [Optional] IsNull() and SetToNull(). +// 4. [Optional] SetUpContext() and TearDownContext(). +// Please see the documentation of StructTraits for details of these methods. +// +// Unlike StructTraits, there is one more method to implement: +// 5. A static GetTag() method indicating which field is the current active +// field for serialization: +// +// static DataViewType::Tag GetTag(const T& input); +// +// During serialization, only the field getter corresponding to this tag +// will be called. +// +template <typename DataViewType, typename T> +struct UnionTraits; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_UNION_TRAITS_H_ diff --git a/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h b/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h new file mode 100644 index 0000000000..f1ac097396 --- /dev/null +++ b/mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_BINDINGS_UNIQUE_PTR_IMPL_REF_TRAITS_H_ +#define MOJO_PUBLIC_CPP_BINDINGS_UNIQUE_PTR_IMPL_REF_TRAITS_H_ + +namespace mojo { + +// Traits for a binding's implementation reference type. +// This corresponds to a unique_ptr reference type. +template <typename Interface> +struct UniquePtrImplRefTraits { + using PointerType = std::unique_ptr<Interface>; + + static bool IsNull(const PointerType& ptr) { return !ptr; } + static Interface* GetRawPointer(PointerType* ptr) { return ptr->get(); } +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_BINDINGS_UNIQUE_PTR_IMPL_REF_TRAITS_H_ diff --git a/mojo/public/cpp/system/BUILD.gn b/mojo/public/cpp/system/BUILD.gn new file mode 100644 index 0000000000..35087ef6f1 --- /dev/null +++ b/mojo/public/cpp/system/BUILD.gn @@ -0,0 +1,57 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Deletes libsystem.dylib from the build dir, since it shadows +# /usr/lib/libSystem.dylib on macOS. +# TODO(thakis): Remove this after a while. +action("clean_up_old_dylib") { + script = "//build/rm.py" + stamp = "$target_gen_dir/clean_up_stamp" + outputs = [ + stamp, + ] + args = [ + "--stamp", + rebase_path(stamp, root_build_dir), + "-f", + "libsystem.dylib", + ] +} + +component("system") { + output_name = "mojo_public_system_cpp" + + sources = [ + "buffer.cc", + "buffer.h", + "core.h", + "data_pipe.h", + "functions.h", + "handle.h", + "handle_signals_state.h", + "message.h", + "message_pipe.h", + "platform_handle.cc", + "platform_handle.h", + "simple_watcher.cc", + "simple_watcher.h", + "system_export.h", + "wait.cc", + "wait.h", + "wait_set.cc", + "wait_set.h", + "watcher.cc", + "watcher.h", + ] + + public_deps = [ + "//base", + "//mojo/public/c/system", + ] + deps = [ + ":clean_up_old_dylib", + ] + + defines = [ "MOJO_CPP_SYSTEM_IMPLEMENTATION" ] +} diff --git a/mojo/public/cpp/system/README.md b/mojo/public/cpp/system/README.md new file mode 100644 index 0000000000..782744f0b1 --- /dev/null +++ b/mojo/public/cpp/system/README.md @@ -0,0 +1,396 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo C++ System API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview +The Mojo C++ System API provides a convenient set of helper classes and +functions for working with Mojo primitives. Unlike the low-level +[C API](/mojo/public/c/system) (upon which this is built) this library takes +advantage of C++ language features and common STL and `//base` types to provide +a slightly more idiomatic interface to the Mojo system layer, making it +generally easier to use. + +This document provides a brief guide to API usage with example code snippets. +For a detailed API references please consult the headers in +[//mojo/public/cpp/system](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/). + +Note that all API symbols referenced in this document are implicitly in the +top-level `mojo` namespace. + +## Scoped, Typed Handles + +All types of Mojo handles in the C API are simply opaque, integral `MojoHandle` +values. The C++ API has more strongly typed wrappers defined for different +handle types: `MessagePipeHandle`, `SharedBufferHandle`, +`DataPipeConsumerHandle`, `DataPipeProducerHandle`, and `WatcherHandle`. + +Each of these also has a corresponding, move-only, scoped type for safer usage: +`ScopedMessagePipeHandle`, `ScopedSharedBufferHandle`, and so on. When a scoped +handle type is destroyed, its handle is automatically closed via `MojoClose`. +When working with raw handles you should **always** prefer to use one of the +scoped types for ownership. + +Similar to `std::unique_ptr`, scoped handle types expose a `get()` method to get +at the underlying unscoped handle type as well as the `->` operator to +dereference the scoper and make calls directly on the underlying handle type. + +## Message Pipes + +There are two ways to create a new message pipe using the C++ API. You may +construct a `MessagePipe` object: + +``` cpp +mojo::MessagePipe pipe; + +// NOTE: Because pipes are bi-directional there is no implicit semantic +// difference between |handle0| or |handle1| here. They're just two ends of a +// pipe. The choice to treat one as a "client" and one as a "server" is entirely +// a the API user's decision. +mojo::ScopedMessagePipeHandle client = std::move(pipe.handle0); +mojo::ScopedMessagePipeHandle server = std::move(pipe.handle1); +``` + +or you may call `CreateMessagePipe`: + +``` cpp +mojo::ScopedMessagePipeHandle client; +mojo::ScopedMessagePipeHandle server; +mojo::CreateMessagePipe(nullptr, &client, &server); +``` + +There are also some helper functions for constructing message objects and +reading/writing them on pipes using the library's more strongly-typed C++ +handles: + +``` cpp +mojo::ScopedMessageHandle message; +mojo::AllocMessage(6, nullptr, 0, MOJO_ALLOC_MESSAGE_FLAG_NONE, &message); + +void *buffer; +mojo::GetMessageBuffer(message.get(), &buffer); + +const std::string kMessage = "hello"; +std::copy(kMessage.begin(), kMessage.end(), static_cast<char*>(buffer)); + +mojo::WriteMessageNew(client.get(), std::move(message), + MOJO_WRITE_MESSAGE_FLAG_NONE); + +// Some time later... + +mojo::ScopedMessageHandle received_message; +uint32_t num_bytes; +mojo::ReadMessageNew(server.get(), &received_message, &num_bytes, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE); +``` + +See [message_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/message_pipe.h) +for detailed C++ message pipe API documentation. + +## Data Pipes + +Similar to [Message Pipes](#Message-Pipes), the C++ library has some simple +helpers for more strongly-typed data pipe usage: + +``` cpp +mojo::DataPipe pipe; +mojo::ScopedDataPipeProducerHandle producer = std::move(pipe.producer); +mojo::ScopedDataPipeConsumerHandle consumer = std::move(pipe.consumer); + +// Or alternatively: +mojo::ScopedDataPipeProducerHandle producer; +mojo::ScopedDataPipeConsumerHandle consumer; +mojo::CreateDataPipe(null, &producer, &consumer); +``` + +// Reads from a data pipe. See |MojoReadData()| for complete documentation. +inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags); +} + +// Begins a two-phase read +C++ helpers which correspond directly to the +[Data Pipe C API](/mojo/public/c/system#Data-Pipes) for immediate and two-phase +I/O are provided as well. For example: + +``` cpp +uint32_t num_bytes = 7; +mojo::WriteDataRaw(producer.get(), "hihihi", + &num_bytes, MOJO_WRITE_DATA_FLAG_NONE); + +// Some time later... + +char buffer[64]; +uint32_t num_bytes = 64; +mojo::ReadDataRaw(consumer.get(), buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); +``` + +See [data_pipe.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/data_pipe.h) +for detailed C++ data pipe API documentation. + +## Shared Buffers + +A new shared buffers can be allocated like so: + +``` cpp +mojo::ScopedSharedBufferHandle buffer = + mojo::ScopedSharedBufferHandle::Create(4096); +``` + +This new handle can be cloned arbitrarily many times by using the underlying +handle's `Clone` method: + +``` cpp +mojo::ScopedSharedBufferHandle another_handle = buffer->Clone(); +mojo::ScopedSharedBufferHandle read_only_handle = + buffer->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY); +``` + +And finally the library also provides a scoper for mapping the shared buffer's +memory: + +``` cpp +mojo::ScopedSharedBufferMapping mapping = buffer->Map(64); +static_cast<int*>(mapping.get()) = 42; + +mojo::ScopedSharedBufferMapping another_mapping = buffer->MapAtOffset(64, 4); +static_cast<int*>(mapping.get()) = 43; +``` + +When `mapping` and `another_mapping` are destroyed, they automatically unmap +their respective memory regions. + +See [buffer.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/buffer.h) +for detailed C++ shared buffer API documentation. + +## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) + +The C++ library provides several helpers for wrapping system handle types. +These are specifically useful when working with a few `//base` types, namely +`base::PlatformFile` and `base::SharedMemoryHandle`. See +[platform_handle.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/platform_handle.h) +for detailed C++ platform handle API documentation. + +## Signals & Watchers + +For an introduction to the concepts of handle signals and watchers, check out +the C API's documentation on [Signals & Watchers](/mojo/public/c/system#Signals-Watchers). + +### Querying Signals + +Any C++ handle type's last known signaling state can be queried by calling the +`QuerySignalsState` method on the handle: + +``` cpp +mojo::MessagePipe message_pipe; +mojo::DataPipe data_pipe; +mojo::HandleSignalsState a = message_pipe.handle0->QuerySignalsState(); +mojo::HandleSignalsState b = data_pipe.consumer->QuerySignalsState(); +``` + +The `HandleSignalsState` is a thin wrapper interface around the C API's +`MojoHandleSignalsState` structure with convenient accessors for testing +the signal bitmasks. Whereas when using the C API you might write: + +``` c +struct MojoHandleSignalsState state; +MojoQueryHandleSignalsState(handle0, &state); +if (state.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE) { + // ... +} +``` + +the C++ API equivalent would be: + +``` cpp +if (message_pipe.handle0->QuerySignalsState().readable()) { + // ... +} +``` + +### Watching Handles + +The [`mojo::SimpleWatcher`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/simple_watcher.h) +class serves as a convenient helper for using the [low-level watcher API](/mojo/public/c/system#Signals-Watchers) +to watch a handle for signaling state changes. A `SimpleWatcher` is bound to a +single thread and always dispatches its notifications on a +`base::SingleThreadTaskRunner`. + +`SimpleWatcher` has two possible modes of operation, selected at construction +time by the `mojo::SimpleWatcher::ArmingPolicy` enum: + +* `MANUAL` mode requires the user to manually call `Arm` and/or `ArmOrNotify` + before any notifications will fire regarding the state of the watched handle. + Every time the notification callback is run, the `SimpleWatcher` must be + rearmed again before the next one can fire. See + [Arming a Watcher](/mojo/public/c/system#Arming-a-Watcher) and the + documentation in `SimpleWatcher`'s header. + +* `AUTOMATIC` mode ensures that the `SimpleWatcher` always either is armed or + has a pending notification task queued for execution. + +`AUTOMATIC` mode is more convenient but can result in redundant notification +tasks, especially if the provided callback does not make a strong effort to +return the watched handle to an uninteresting signaling state (by *e.g.*, +reading all its available messages when notified of readability.) + +Example usage: + +``` cpp +class PipeReader { + public: + PipeReader(mojo::ScopedMessagePipeHandle pipe) + : pipe_(std::move(pipe)), + watcher_(mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) { + // NOTE: base::Unretained is safe because the callback can never be run + // after SimpleWatcher destruction. + watcher_.Watch(pipe_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&PipeReader::OnReadable, base::Unretained(this))); + } + + ~PipeReader() {} + + private: + void OnReadable(MojoResult result) { + while (result == MOJO_RESULT_OK) { + mojo::ScopedMessageHandle message; + uint32_t num_bytes; + result = mojo::ReadMessageNew(pipe_.get(), &message, &num_bytes, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE); + DCHECK_EQ(result, MOJO_RESULT_OK); + messages_.emplace_back(std::move(message)); + } + } + + mojo::ScopedMessagePipeHandle pipe_; + mojo::SimpleWatcher watcher_; + std::vector<mojo::ScopedMessageHandle> messages_; +}; + +mojo::MessagePipe pipe; +PipeReader reader(std::move(pipe.handle0)); + +// Written messages will asynchronously end up in |reader.messages_|. +WriteABunchOfStuff(pipe.handle1.get()); +``` + +## Synchronous Waiting + +The C++ System API defines some utilities to block a calling thread while +waiting for one or more handles to change signaling state in an interesting way. +These threads combine usage of the [low-level Watcher API](/mojo/public/c/system#Signals-Watchers) +with common synchronization primitives (namely `base::WaitableEvent`.) + +While these API features should be used sparingly, they are sometimes necessary. + +See the documentation in +[wait.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait.h) +and [wait_set.h](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h) +for a more detailed API reference. + +### Waiting On a Single Handle + +The `mojo::Wait` function simply blocks the calling thread until a given signal +mask is either partially satisfied or fully unsatisfiable on a given handle. + +``` cpp +mojo::MessagePipe pipe; +mojo::WriteMessageRaw(pipe.handle0.get(), "hey", 3, nullptr, nullptr, + MOJO_WRITE_MESSAGE_FLAG_NONE); +MojoResult result = mojo::Wait(pipe.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); +DCHECK_EQ(result, MOJO_RESULT_OK); + +// Guaranteed to succeed because we know |handle1| is readable now. +mojo::ScopedMessageHandle message; +uint32_t num_bytes; +mojo::ReadMessageNew(pipe.handle1.get(), &num_bytes, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE); +``` + +`mojo::Wait` is most typically useful in limited testing scenarios. + +### Waiting On Multiple Handles + +`mojo::WaitMany` provides a simple API to wait on multiple handles +simultaneously, returning when any handle's given signal mask is either +partially satisfied or fully unsatisfiable. + +``` cpp +mojo::MessagePipe a, b; +GoDoSomethingWithPipes(std:move(a.handle1), std::move(b.handle1)); + +mojo::MessagePipeHandle handles[2] = {a.handle0.get(), b.handle0.get()}; +MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; +size_t ready_index; +MojoResult result = mojo::WaitMany(handles, signals, 2, &ready_index); +if (ready_index == 0) { + // a.handle0 was ready. +} else { + // b.handle0 was ready. +} +``` + +Similar to `mojo::Wait`, `mojo::WaitMany` is primarily useful in testing. When +waiting on multiple handles in production code, you should almost always instead +use a more efficient and more flexible `mojo::WaitSet` as described in the next +section. + +### Waiting On Handles and Events Simultaneously + +Typically when waiting on one or more handles to signal, the set of handles and +conditions being waited upon do not change much between consecutive blocking +waits. It's also often useful to be able to interrupt the blocking operation +as efficiently as possible. + +[`mojo::WaitSet`](https://cs.chromium.org/chromium/src/mojo/public/cpp/system/wait_set.h) +is designed with these conditions in mind. A `WaitSet` maintains a persistent +set of (not-owned) Mojo handles and `base::WaitableEvent`s, which may be +explicitly added to or removed from the set at any time. + +The `WaitSet` may be waited upon repeatedly, each time blocking the calling +thread until either one of the handles attains an interesting signaling state or +one of the events is signaled. For example let's suppose we want to wait up to 5 +seconds for either one of two handles to become readable: + +``` cpp +base::WaitableEvent timeout_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); +mojo::MessagePipe a, b; + +GoDoStuffWithPipes(std::move(a.handle1), std::move(b.handle1)); + +mojo::WaitSet wait_set; +wait_set.AddHandle(a.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); +wait_set.AddHandle(b.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); +wait_set.AddEvent(&timeout_event); + +// Ensure the Wait() lasts no more than 5 seconds. +bg_thread->task_runner()->PostDelayedTask( + FROM_HERE, + base::Bind([](base::WaitableEvent* e) { e->Signal(); }, &timeout_event); + base::TimeDelta::FromSeconds(5)); + +base::WaitableEvent* ready_event = nullptr; +size_t num_ready_handles = 1; +mojo::Handle ready_handle; +MojoResult ready_result; +wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); + +// The apex of thread-safety. +bg_thread->Stop(); + +if (ready_event) { + // The event signaled... +} + +if (num_ready_handles > 0) { + // At least one of the handles signaled... + // NOTE: This and the above condition are not mutually exclusive. If handle + // signaling races with timeout, both things might be true. +} +``` diff --git a/mojo/public/cpp/system/buffer.cc b/mojo/public/cpp/system/buffer.cc new file mode 100644 index 0000000000..49f45d8498 --- /dev/null +++ b/mojo/public/cpp/system/buffer.cc @@ -0,0 +1,46 @@ +// 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. + +#include "mojo/public/cpp/system/buffer.h" + +namespace mojo { + +// static +ScopedSharedBufferHandle SharedBufferHandle::Create(uint64_t num_bytes) { + MojoCreateSharedBufferOptions options = { + sizeof(options), MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE}; + SharedBufferHandle handle; + MojoCreateSharedBuffer(&options, num_bytes, handle.mutable_value()); + return MakeScopedHandle(handle); +} + +ScopedSharedBufferHandle SharedBufferHandle::Clone( + SharedBufferHandle::AccessMode access_mode) const { + ScopedSharedBufferHandle result; + if (!is_valid()) + return result; + + MojoDuplicateBufferHandleOptions options = { + sizeof(options), MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}; + if (access_mode == AccessMode::READ_ONLY) + options.flags |= MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; + SharedBufferHandle result_handle; + MojoDuplicateBufferHandle(value(), &options, result_handle.mutable_value()); + result.reset(result_handle); + return result; +} + +ScopedSharedBufferMapping SharedBufferHandle::Map(uint64_t size) const { + return MapAtOffset(size, 0); +} + +ScopedSharedBufferMapping SharedBufferHandle::MapAtOffset( + uint64_t size, + uint64_t offset) const { + void* buffer = nullptr; + MojoMapBuffer(value(), offset, size, &buffer, MOJO_MAP_BUFFER_FLAG_NONE); + return ScopedSharedBufferMapping(buffer); +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/buffer.h b/mojo/public/cpp/system/buffer.h new file mode 100644 index 0000000000..1ae923cb75 --- /dev/null +++ b/mojo/public/cpp/system/buffer.h @@ -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. + +// This file provides a C++ wrapping around the Mojo C API for shared buffers, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/buffer.h" for complete documentation of the +// API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ + +#include <stdint.h> + +#include <memory> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { +namespace internal { + +struct Unmapper { + void operator()(void* buffer) { + MojoResult result = MojoUnmapBuffer(buffer); + DCHECK_EQ(MOJO_RESULT_OK, result); + } +}; + +} // namespace internal + +using ScopedSharedBufferMapping = std::unique_ptr<void, internal::Unmapper>; + +class SharedBufferHandle; + +typedef ScopedHandleBase<SharedBufferHandle> ScopedSharedBufferHandle; + +// A strongly-typed representation of a |MojoHandle| referring to a shared +// buffer. +class MOJO_CPP_SYSTEM_EXPORT SharedBufferHandle + : NON_EXPORTED_BASE(public Handle) { + public: + enum class AccessMode { + READ_WRITE, + READ_ONLY, + }; + + SharedBufferHandle() {} + explicit SharedBufferHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. + + // Creates a new SharedBufferHandle. Returns an invalid handle on failure. + static ScopedSharedBufferHandle Create(uint64_t num_bytes); + + // Clones this shared buffer handle. If |access_mode| is READ_ONLY or this is + // a read-only handle, the new handle will be read-only. On failure, this will + // return an empty result. + ScopedSharedBufferHandle Clone(AccessMode = AccessMode::READ_WRITE) const; + + // Maps |size| bytes of this shared buffer. On failure, this will return a + // null mapping. + ScopedSharedBufferMapping Map(uint64_t size) const; + + // Maps |size| bytes of this shared buffer, starting |offset| bytes into the + // buffer. On failure, this will return a null mapping. + ScopedSharedBufferMapping MapAtOffset(uint64_t size, uint64_t offset) const; +}; + +static_assert(sizeof(SharedBufferHandle) == sizeof(Handle), + "Bad size for C++ SharedBufferHandle"); +static_assert(sizeof(ScopedSharedBufferHandle) == sizeof(SharedBufferHandle), + "Bad size for C++ ScopedSharedBufferHandle"); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_BUFFER_H_ diff --git a/mojo/public/cpp/system/core.h b/mojo/public/cpp/system/core.h new file mode 100644 index 0000000000..f1d18d977f --- /dev/null +++ b/mojo/public/cpp/system/core.h @@ -0,0 +1,14 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ + +#include "mojo/public/cpp/system/buffer.h" +#include "mojo/public/cpp/system/data_pipe.h" +#include "mojo/public/cpp/system/functions.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/message_pipe.h" + +#endif // MOJO_PUBLIC_CPP_SYSTEM_CORE_H_ diff --git a/mojo/public/cpp/system/data_pipe.h b/mojo/public/cpp/system/data_pipe.h new file mode 100644 index 0000000000..0dbc3c74e5 --- /dev/null +++ b/mojo/public/cpp/system/data_pipe.h @@ -0,0 +1,163 @@ +// 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 provides a C++ wrapping around the Mojo C API for data pipes, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/data_pipe.h" for complete documentation of +// the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { + +// A strongly-typed representation of a |MojoHandle| to the producer end of a +// data pipe. +class DataPipeProducerHandle : public Handle { + public: + DataPipeProducerHandle() {} + explicit DataPipeProducerHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(DataPipeProducerHandle) == sizeof(Handle), + "Bad size for C++ DataPipeProducerHandle"); + +typedef ScopedHandleBase<DataPipeProducerHandle> ScopedDataPipeProducerHandle; +static_assert(sizeof(ScopedDataPipeProducerHandle) == + sizeof(DataPipeProducerHandle), + "Bad size for C++ ScopedDataPipeProducerHandle"); + +// A strongly-typed representation of a |MojoHandle| to the consumer end of a +// data pipe. +class DataPipeConsumerHandle : public Handle { + public: + DataPipeConsumerHandle() {} + explicit DataPipeConsumerHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(DataPipeConsumerHandle) == sizeof(Handle), + "Bad size for C++ DataPipeConsumerHandle"); + +typedef ScopedHandleBase<DataPipeConsumerHandle> ScopedDataPipeConsumerHandle; +static_assert(sizeof(ScopedDataPipeConsumerHandle) == + sizeof(DataPipeConsumerHandle), + "Bad size for C++ ScopedDataPipeConsumerHandle"); + +// Creates a new data pipe. See |MojoCreateDataPipe()| for complete +// documentation. +inline MojoResult CreateDataPipe( + const MojoCreateDataPipeOptions* options, + ScopedDataPipeProducerHandle* data_pipe_producer, + ScopedDataPipeConsumerHandle* data_pipe_consumer) { + DCHECK(data_pipe_producer); + DCHECK(data_pipe_consumer); + DataPipeProducerHandle producer_handle; + DataPipeConsumerHandle consumer_handle; + MojoResult rv = MojoCreateDataPipe(options, + producer_handle.mutable_value(), + consumer_handle.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + data_pipe_producer->reset(producer_handle); + data_pipe_consumer->reset(consumer_handle); + return rv; +} + +// Writes to a data pipe. See |MojoWriteData| for complete documentation. +inline MojoResult WriteDataRaw(DataPipeProducerHandle data_pipe_producer, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + return MojoWriteData(data_pipe_producer.value(), elements, num_bytes, flags); +} + +// Begins a two-phase write to a data pipe. See |MojoBeginWriteData()| for +// complete documentation. +inline MojoResult BeginWriteDataRaw(DataPipeProducerHandle data_pipe_producer, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + return MojoBeginWriteData( + data_pipe_producer.value(), buffer, buffer_num_bytes, flags); +} + +// Completes a two-phase write to a data pipe. See |MojoEndWriteData()| for +// complete documentation. +inline MojoResult EndWriteDataRaw(DataPipeProducerHandle data_pipe_producer, + uint32_t num_bytes_written) { + return MojoEndWriteData(data_pipe_producer.value(), num_bytes_written); +} + +// Reads from a data pipe. See |MojoReadData()| for complete documentation. +inline MojoResult ReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + return MojoReadData(data_pipe_consumer.value(), elements, num_bytes, flags); +} + +// Begins a two-phase read from a data pipe. See |MojoBeginReadData()| for +// complete documentation. +inline MojoResult BeginReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + return MojoBeginReadData( + data_pipe_consumer.value(), buffer, buffer_num_bytes, flags); +} + +// Completes a two-phase read from a data pipe. See |MojoEndReadData()| for +// complete documentation. +inline MojoResult EndReadDataRaw(DataPipeConsumerHandle data_pipe_consumer, + uint32_t num_bytes_read) { + return MojoEndReadData(data_pipe_consumer.value(), num_bytes_read); +} + +// A wrapper class that automatically creates a data pipe and owns both handles. +// TODO(vtl): Make an even more friendly version? (Maybe templatized for a +// particular type instead of some "element"? Maybe functions that take +// vectors?) +class DataPipe { + public: + DataPipe(); + explicit DataPipe(const MojoCreateDataPipeOptions& options); + ~DataPipe(); + + ScopedDataPipeProducerHandle producer_handle; + ScopedDataPipeConsumerHandle consumer_handle; +}; + +inline DataPipe::DataPipe() { + MojoResult result = + CreateDataPipe(nullptr, &producer_handle, &consumer_handle); + ALLOW_UNUSED_LOCAL(result); + DCHECK_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); +} + +inline DataPipe::~DataPipe() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_DATA_PIPE_H_ diff --git a/mojo/public/cpp/system/functions.h b/mojo/public/cpp/system/functions.h new file mode 100644 index 0000000000..31edf57ab5 --- /dev/null +++ b/mojo/public/cpp/system/functions.h @@ -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. + +// This file provides a C++ wrapping around the standalone functions of the Mojo +// C API, replacing the prefix of "Mojo" with a "mojo" namespace. +// +// Please see "mojo/public/c/system/functions.h" for complete documentation of +// the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ + +#include "mojo/public/c/system/functions.h" + +namespace mojo { + +// Returns the current |MojoTimeTicks| value. See |MojoGetTimeTicksNow()| for +// complete documentation. +inline MojoTimeTicks GetTimeTicksNow() { + return MojoGetTimeTicksNow(); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_FUNCTIONS_H_ diff --git a/mojo/public/cpp/system/handle.h b/mojo/public/cpp/system/handle.h new file mode 100644 index 0000000000..781944eb76 --- /dev/null +++ b/mojo/public/cpp/system/handle.h @@ -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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ + +#include <stdint.h> +#include <limits> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle_signals_state.h" + +namespace mojo { + +// OVERVIEW +// +// |Handle| and |...Handle|: +// +// |Handle| is a simple, copyable wrapper for the C type |MojoHandle| (which is +// just an integer). Its purpose is to increase type-safety, not provide +// lifetime management. For the same purpose, we have trivial *subclasses* of +// |Handle|, e.g., |MessagePipeHandle| and |DataPipeProducerHandle|. |Handle| +// and its subclasses impose *no* extra overhead over using |MojoHandle|s +// directly. +// +// Note that though we provide constructors for |Handle|/|...Handle| from a +// |MojoHandle|, we do not provide, e.g., a constructor for |MessagePipeHandle| +// from a |Handle|. This is for type safety: If we did, you'd then be able to +// construct a |MessagePipeHandle| from, e.g., a |DataPipeProducerHandle| (since +// it's a |Handle|). +// +// |ScopedHandleBase| and |Scoped...Handle|: +// +// |ScopedHandleBase<HandleType>| is a templated scoped wrapper, for the handle +// types above (in the same sense that a C++11 |unique_ptr<T>| is a scoped +// wrapper for a |T*|). It provides lifetime management, closing its owned +// handle on destruction. It also provides (emulated) move semantics, again +// along the lines of C++11's |unique_ptr| (and exactly like Chromium's +// |scoped_ptr|). +// +// |ScopedHandle| is just (a typedef of) a |ScopedHandleBase<Handle>|. +// Similarly, |ScopedMessagePipeHandle| is just a +// |ScopedHandleBase<MessagePipeHandle>|. Etc. Note that a +// |ScopedMessagePipeHandle| is *not* a (subclass of) |ScopedHandle|. +// +// Wrapper functions: +// +// We provide simple wrappers for the |Mojo...()| functions (in +// mojo/public/c/system/core.h -- see that file for details on individual +// functions). +// +// The general guideline is functions that imply ownership transfer of a handle +// should take (or produce) an appropriate |Scoped...Handle|, while those that +// don't take a |...Handle|. For example, |CreateMessagePipe()| has two +// |ScopedMessagePipe| "out" parameters, whereas |Wait()| and |WaitMany()| take +// |Handle| parameters. Some, have both: e.g., |DuplicatedBuffer()| takes a +// suitable (unscoped) handle (e.g., |SharedBufferHandle|) "in" parameter and +// produces a suitable scoped handle (e.g., |ScopedSharedBufferHandle| a.k.a. +// |ScopedHandleBase<SharedBufferHandle>|) as an "out" parameter. +// +// An exception are some of the |...Raw()| functions. E.g., |CloseRaw()| takes a +// |Handle|, leaving the user to discard the wrapper. +// +// ScopedHandleBase ------------------------------------------------------------ + +// Scoper for the actual handle types defined further below. It's move-only, +// like the C++11 |unique_ptr|. +template <class HandleType> +class ScopedHandleBase { + public: + using RawHandleType = HandleType; + + ScopedHandleBase() {} + explicit ScopedHandleBase(HandleType handle) : handle_(handle) {} + ~ScopedHandleBase() { CloseIfNecessary(); } + + template <class CompatibleHandleType> + explicit ScopedHandleBase(ScopedHandleBase<CompatibleHandleType> other) + : handle_(other.release()) {} + + // Move-only constructor and operator=. + ScopedHandleBase(ScopedHandleBase&& other) : handle_(other.release()) {} + ScopedHandleBase& operator=(ScopedHandleBase&& other) { + if (&other != this) { + CloseIfNecessary(); + handle_ = other.release(); + } + return *this; + } + + const HandleType& get() const { return handle_; } + const HandleType* operator->() const { return &handle_; } + + template <typename PassedHandleType> + static ScopedHandleBase<HandleType> From( + ScopedHandleBase<PassedHandleType> other) { + static_assert( + sizeof(static_cast<PassedHandleType*>(static_cast<HandleType*>(0))), + "HandleType is not a subtype of PassedHandleType"); + return ScopedHandleBase<HandleType>( + static_cast<HandleType>(other.release().value())); + } + + void swap(ScopedHandleBase& other) { handle_.swap(other.handle_); } + + HandleType release() WARN_UNUSED_RESULT { + HandleType rv; + rv.swap(handle_); + return rv; + } + + void reset(HandleType handle = HandleType()) { + CloseIfNecessary(); + handle_ = handle; + } + + bool is_valid() const { return handle_.is_valid(); } + + bool operator==(const ScopedHandleBase& other) const { + return handle_.value() == other.get().value(); + } + + private: + void CloseIfNecessary() { + if (handle_.is_valid()) + handle_.Close(); + } + + HandleType handle_; + + DISALLOW_COPY_AND_ASSIGN(ScopedHandleBase); +}; + +template <typename HandleType> +inline ScopedHandleBase<HandleType> MakeScopedHandle(HandleType handle) { + return ScopedHandleBase<HandleType>(handle); +} + +// Handle ---------------------------------------------------------------------- + +const MojoHandle kInvalidHandleValue = MOJO_HANDLE_INVALID; + +// Wrapper base class for |MojoHandle|. +class Handle { + public: + Handle() : value_(kInvalidHandleValue) {} + explicit Handle(MojoHandle value) : value_(value) {} + ~Handle() {} + + void swap(Handle& other) { + MojoHandle temp = value_; + value_ = other.value_; + other.value_ = temp; + } + + bool is_valid() const { return value_ != kInvalidHandleValue; } + + const MojoHandle& value() const { return value_; } + MojoHandle* mutable_value() { return &value_; } + void set_value(MojoHandle value) { value_ = value; } + + void Close() { + DCHECK(is_valid()); + MojoResult result = MojoClose(value_); + ALLOW_UNUSED_LOCAL(result); + DCHECK_EQ(MOJO_RESULT_OK, result); + } + + HandleSignalsState QuerySignalsState() const { + HandleSignalsState signals_state; + MojoResult result = MojoQueryHandleSignalsState( + value_, static_cast<MojoHandleSignalsState*>(&signals_state)); + DCHECK_EQ(MOJO_RESULT_OK, result); + return signals_state; + } + + private: + MojoHandle value_; + + // Copying and assignment allowed. +}; + +// Should have zero overhead. +static_assert(sizeof(Handle) == sizeof(MojoHandle), "Bad size for C++ Handle"); + +// The scoper should also impose no more overhead. +typedef ScopedHandleBase<Handle> ScopedHandle; +static_assert(sizeof(ScopedHandle) == sizeof(Handle), + "Bad size for C++ ScopedHandle"); + +// |Close()| takes ownership of the handle, since it'll invalidate it. +// Note: There's nothing to do, since the argument will be destroyed when it +// goes out of scope. +template <class HandleType> +inline void Close(ScopedHandleBase<HandleType> /*handle*/) { +} + +// Most users should typically use |Close()| (above) instead. +inline MojoResult CloseRaw(Handle handle) { + return MojoClose(handle.value()); +} + +// Strict weak ordering, so that |Handle|s can be used as keys in |std::map|s, +inline bool operator<(const Handle a, const Handle b) { + return a.value() < b.value(); +} + +// Comparison, so that |Handle|s can be used as keys in hash maps. +inline bool operator==(const Handle a, const Handle b) { + return a.value() == b.value(); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_H_ diff --git a/mojo/public/cpp/system/handle_signals_state.h b/mojo/public/cpp/system/handle_signals_state.h new file mode 100644 index 0000000000..9e2430f15a --- /dev/null +++ b/mojo/public/cpp/system/handle_signals_state.h @@ -0,0 +1,83 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_ + +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { + +// A convenience wrapper around the MojoHandleSignalsState struct. +struct MOJO_CPP_SYSTEM_EXPORT HandleSignalsState final + : public MojoHandleSignalsState { + HandleSignalsState() { + satisfied_signals = MOJO_HANDLE_SIGNAL_NONE; + satisfiable_signals = MOJO_HANDLE_SIGNAL_NONE; + } + + HandleSignalsState(MojoHandleSignals satisfied, + MojoHandleSignals satisfiable) { + satisfied_signals = satisfied; + satisfiable_signals = satisfiable; + } + + bool operator==(const HandleSignalsState& other) const { + return satisfied_signals == other.satisfied_signals && + satisfiable_signals == other.satisfiable_signals; + } + + // TODO(rockot): Remove this in favor of operator==. + bool equals(const HandleSignalsState& other) const { + return satisfied_signals == other.satisfied_signals && + satisfiable_signals == other.satisfiable_signals; + } + + bool satisfies(MojoHandleSignals signals) const { + return !!(satisfied_signals & signals); + } + + bool can_satisfy(MojoHandleSignals signals) const { + return !!(satisfiable_signals & signals); + } + + // The handle is currently readable. May apply to a message pipe handle or + // data pipe consumer handle. + bool readable() const { return satisfies(MOJO_HANDLE_SIGNAL_READABLE); } + + // The handle is currently writable. May apply to a message pipe handle or + // data pipe producer handle. + bool writable() const { return satisfies(MOJO_HANDLE_SIGNAL_WRITABLE); } + + // The handle's peer is closed. May apply to any message pipe or data pipe + // handle. + bool peer_closed() const { return satisfies(MOJO_HANDLE_SIGNAL_PEER_CLOSED); } + + // The handle will never be |readable()| again. + bool never_readable() const { + return !can_satisfy(MOJO_HANDLE_SIGNAL_READABLE); + } + + // The handle will never be |writable()| again. + bool never_writable() const { + return !can_satisfy(MOJO_HANDLE_SIGNAL_WRITABLE); + } + + // The handle can never indicate |peer_closed()|. Never true for message pipe + // or data pipe handles (they can always signal peer closure), but always true + // for other types of handles (they have no peer.) + bool never_peer_closed() const { + return !can_satisfy(MOJO_HANDLE_SIGNAL_PEER_CLOSED); + } + + // (Copy and assignment allowed.) +}; + +static_assert(sizeof(HandleSignalsState) == sizeof(MojoHandleSignalsState), + "HandleSignalsState should add no overhead"); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_HANDLE_SIGNALS_STATE_H_ diff --git a/mojo/public/cpp/system/message.cc b/mojo/public/cpp/system/message.cc new file mode 100644 index 0000000000..09d8d46e6d --- /dev/null +++ b/mojo/public/cpp/system/message.cc @@ -0,0 +1,13 @@ +// 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. + +#include "mojo/public/cpp/system/message.h" + +namespace mojo { + +ScopedMessageHandle::~ScopedMessageHandle() { + +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/message.h b/mojo/public/cpp/system/message.h new file mode 100644 index 0000000000..d4406ee808 --- /dev/null +++ b/mojo/public/cpp/system/message.h @@ -0,0 +1,83 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_ + +#include <limits> + +#include "base/macros.h" +#include "base/strings/string_piece.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/cpp/system/handle.h" + +namespace mojo { + +const MojoMessageHandle kInvalidMessageHandleValue = + MOJO_MESSAGE_HANDLE_INVALID; + +// Handle wrapper base class for a |MojoMessageHandle|. +class MessageHandle { + public: + MessageHandle() : value_(kInvalidMessageHandleValue) {} + explicit MessageHandle(MojoMessageHandle value) : value_(value) {} + ~MessageHandle() {} + + void swap(MessageHandle& other) { + MojoMessageHandle temp = value_; + value_ = other.value_; + other.value_ = temp; + } + + bool is_valid() const { return value_ != kInvalidMessageHandleValue; } + + const MojoMessageHandle& value() const { return value_; } + MojoMessageHandle* mutable_value() { return &value_; } + void set_value(MojoMessageHandle value) { value_ = value; } + + void Close() { + DCHECK(is_valid()); + MojoResult result = MojoFreeMessage(value_); + ALLOW_UNUSED_LOCAL(result); + DCHECK_EQ(MOJO_RESULT_OK, result); + } + + private: + MojoMessageHandle value_; +}; + +using ScopedMessageHandle = ScopedHandleBase<MessageHandle>; + +inline MojoResult AllocMessage(size_t num_bytes, + const MojoHandle* handles, + size_t num_handles, + MojoAllocMessageFlags flags, + ScopedMessageHandle* handle) { + DCHECK_LE(num_bytes, std::numeric_limits<uint32_t>::max()); + DCHECK_LE(num_handles, std::numeric_limits<uint32_t>::max()); + MojoMessageHandle raw_handle; + MojoResult rv = MojoAllocMessage(static_cast<uint32_t>(num_bytes), handles, + static_cast<uint32_t>(num_handles), flags, + &raw_handle); + if (rv != MOJO_RESULT_OK) + return rv; + + handle->reset(MessageHandle(raw_handle)); + return MOJO_RESULT_OK; +} + +inline MojoResult GetMessageBuffer(MessageHandle message, void** buffer) { + DCHECK(message.is_valid()); + return MojoGetMessageBuffer(message.value(), buffer); +} + +inline MojoResult NotifyBadMessage(MessageHandle message, + const base::StringPiece& error) { + DCHECK(message.is_valid()); + return MojoNotifyBadMessage(message.value(), error.data(), error.size()); +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_H_ diff --git a/mojo/public/cpp/system/message_pipe.h b/mojo/public/cpp/system/message_pipe.h new file mode 100644 index 0000000000..7fbe43f7f4 --- /dev/null +++ b/mojo/public/cpp/system/message_pipe.h @@ -0,0 +1,158 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a C++ wrapping around the Mojo C API for message pipes, +// replacing the prefix of "Mojo" with a "mojo" namespace, and using more +// strongly-typed representations of |MojoHandle|s. +// +// Please see "mojo/public/c/system/message_pipe.h" for complete documentation +// of the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/message.h" + +namespace mojo { + +// A strongly-typed representation of a |MojoHandle| to one end of a message +// pipe. +class MessagePipeHandle : public Handle { + public: + MessagePipeHandle() {} + explicit MessagePipeHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(MessagePipeHandle) == sizeof(Handle), + "Bad size for C++ MessagePipeHandle"); + +typedef ScopedHandleBase<MessagePipeHandle> ScopedMessagePipeHandle; +static_assert(sizeof(ScopedMessagePipeHandle) == sizeof(MessagePipeHandle), + "Bad size for C++ ScopedMessagePipeHandle"); + +// Creates a message pipe. See |MojoCreateMessagePipe()| for complete +// documentation. +inline MojoResult CreateMessagePipe(const MojoCreateMessagePipeOptions* options, + ScopedMessagePipeHandle* message_pipe0, + ScopedMessagePipeHandle* message_pipe1) { + DCHECK(message_pipe0); + DCHECK(message_pipe1); + MessagePipeHandle handle0; + MessagePipeHandle handle1; + MojoResult rv = MojoCreateMessagePipe( + options, handle0.mutable_value(), handle1.mutable_value()); + // Reset even on failure (reduces the chances that a "stale"/incorrect handle + // will be used). + message_pipe0->reset(handle0); + message_pipe1->reset(handle1); + return rv; +} + +// The following "...Raw" versions fully expose the underlying API, and don't +// help with ownership of handles (especially when writing messages). It is +// expected that in most cases these methods will be called through generated +// bindings anyway. +// TODO(vtl): Write friendlier versions of these functions (using scoped +// handles and/or vectors) if there is a demonstrated need for them. + +// Writes to a message pipe. If handles are attached, on success the handles +// will no longer be valid (the receiver will receive equivalent, but logically +// different, handles). See |MojoWriteMessage()| for complete documentation. +inline MojoResult WriteMessageRaw(MessagePipeHandle message_pipe, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + return MojoWriteMessage( + message_pipe.value(), bytes, num_bytes, handles, num_handles, flags); +} + +// Reads from a message pipe. See |MojoReadMessage()| for complete +// documentation. +inline MojoResult ReadMessageRaw(MessagePipeHandle message_pipe, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return MojoReadMessage( + message_pipe.value(), bytes, num_bytes, handles, num_handles, flags); +} + +// Writes to a message pipe. Takes ownership of |message| and any attached +// handles. +inline MojoResult WriteMessageNew(MessagePipeHandle message_pipe, + ScopedMessageHandle message, + MojoWriteMessageFlags flags) { + return MojoWriteMessageNew( + message_pipe.value(), message.release().value(), flags); +} + +// Reads from a message pipe. See |MojoReadMessageNew()| for complete +// documentation. +inline MojoResult ReadMessageNew(MessagePipeHandle message_pipe, + ScopedMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + MojoMessageHandle raw_message; + MojoResult rv = MojoReadMessageNew(message_pipe.value(), &raw_message, + num_bytes, handles, num_handles, flags); + if (rv != MOJO_RESULT_OK) + return rv; + + message->reset(MessageHandle(raw_message)); + return MOJO_RESULT_OK; +} + +// Fuses two message pipes together at the given handles. See +// |MojoFuseMessagePipes()| for complete documentation. +inline MojoResult FuseMessagePipes(ScopedMessagePipeHandle message_pipe0, + ScopedMessagePipeHandle message_pipe1) { + return MojoFuseMessagePipes(message_pipe0.release().value(), + message_pipe1.release().value()); +} + +// A wrapper class that automatically creates a message pipe and owns both +// handles. +class MessagePipe { + public: + MessagePipe(); + explicit MessagePipe(const MojoCreateMessagePipeOptions& options); + ~MessagePipe(); + + ScopedMessagePipeHandle handle0; + ScopedMessagePipeHandle handle1; +}; + +inline MessagePipe::MessagePipe() { + MojoResult result = CreateMessagePipe(nullptr, &handle0, &handle1); + DCHECK_EQ(MOJO_RESULT_OK, result); + DCHECK(handle0.is_valid()); + DCHECK(handle1.is_valid()); +} + +inline MessagePipe::MessagePipe(const MojoCreateMessagePipeOptions& options) { + MojoResult result = CreateMessagePipe(&options, &handle0, &handle1); + DCHECK_EQ(MOJO_RESULT_OK, result); + DCHECK(handle0.is_valid()); + DCHECK(handle1.is_valid()); +} + +inline MessagePipe::~MessagePipe() { +} + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_MESSAGE_PIPE_H_ diff --git a/mojo/public/cpp/system/platform_handle.cc b/mojo/public/cpp/system/platform_handle.cc new file mode 100644 index 0000000000..42e4abac83 --- /dev/null +++ b/mojo/public/cpp/system/platform_handle.cc @@ -0,0 +1,178 @@ +// 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. + +#include "mojo/public/cpp/system/platform_handle.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include <mach/mach.h> +#include "base/mac/mach_logging.h" +#endif + +namespace mojo { + +namespace { + +uint64_t PlatformHandleValueFromPlatformFile(base::PlatformFile file) { +#if defined(OS_WIN) + return reinterpret_cast<uint64_t>(file); +#else + return static_cast<uint64_t>(file); +#endif +} + +base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) { +#if defined(OS_WIN) + return reinterpret_cast<base::PlatformFile>(value); +#else + return static_cast<base::PlatformFile>(value); +#endif +} + +} // namespace + +ScopedHandle WrapPlatformFile(base::PlatformFile platform_file) { + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + platform_handle.type = kPlatformFileHandleType; + platform_handle.value = PlatformHandleValueFromPlatformFile(platform_file); + + MojoHandle mojo_handle; + MojoResult result = MojoWrapPlatformHandle(&platform_handle, &mojo_handle); + CHECK_EQ(result, MOJO_RESULT_OK); + + return ScopedHandle(Handle(mojo_handle)); +} + +MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file) { + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + MojoResult result = MojoUnwrapPlatformHandle(handle.release().value(), + &platform_handle); + if (result != MOJO_RESULT_OK) + return result; + + if (platform_handle.type == MOJO_PLATFORM_HANDLE_TYPE_INVALID) { + *file = base::kInvalidPlatformFile; + } else { + CHECK_EQ(platform_handle.type, kPlatformFileHandleType); + *file = PlatformFileFromPlatformHandleValue(platform_handle.value); + } + + return MOJO_RESULT_OK; +} + +ScopedSharedBufferHandle WrapSharedMemoryHandle( + const base::SharedMemoryHandle& memory_handle, + size_t size, + bool read_only) { +#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS)) + if (memory_handle.fd == base::kInvalidPlatformFile) + return ScopedSharedBufferHandle(); +#else + if (!memory_handle.IsValid()) + return ScopedSharedBufferHandle(); +#endif + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + platform_handle.type = kPlatformSharedBufferHandleType; +#if defined(OS_MACOSX) && !defined(OS_IOS) + platform_handle.value = + static_cast<uint64_t>(memory_handle.GetMemoryObject()); +#elif defined(OS_POSIX) + platform_handle.value = PlatformHandleValueFromPlatformFile(memory_handle.fd); +#elif defined(OS_WIN) + platform_handle.value = + PlatformHandleValueFromPlatformFile(memory_handle.GetHandle()); +#endif + + MojoPlatformSharedBufferHandleFlags flags = + MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE; + if (read_only) + flags |= MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY; + + MojoHandle mojo_handle; + MojoResult result = MojoWrapPlatformSharedBufferHandle( + &platform_handle, size, flags, &mojo_handle); + CHECK_EQ(result, MOJO_RESULT_OK); + + return ScopedSharedBufferHandle(SharedBufferHandle(mojo_handle)); +} + +MojoResult UnwrapSharedMemoryHandle(ScopedSharedBufferHandle handle, + base::SharedMemoryHandle* memory_handle, + size_t* size, + bool* read_only) { + if (!handle.is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + + MojoPlatformSharedBufferHandleFlags flags; + size_t num_bytes; + MojoResult result = MojoUnwrapPlatformSharedBufferHandle( + handle.release().value(), &platform_handle, &num_bytes, &flags); + if (result != MOJO_RESULT_OK) + return result; + + if (size) + *size = num_bytes; + + if (read_only) + *read_only = flags & MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT); + *memory_handle = base::SharedMemoryHandle( + static_cast<mach_port_t>(platform_handle.value), num_bytes, + base::GetCurrentProcId()); +#elif defined(OS_POSIX) + CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR); + *memory_handle = base::SharedMemoryHandle( + static_cast<int>(platform_handle.value), false); +#elif defined(OS_WIN) + CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE); + *memory_handle = base::SharedMemoryHandle( + reinterpret_cast<HANDLE>(platform_handle.value), + base::GetCurrentProcId()); +#endif + + return MOJO_RESULT_OK; +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +ScopedHandle WrapMachPort(mach_port_t port) { + kern_return_t kr = + mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, 1); + MACH_LOG_IF(ERROR, kr != KERN_SUCCESS, kr) + << "MachPortAttachmentMac mach_port_mod_refs"; + if (kr != KERN_SUCCESS) + return ScopedHandle(); + + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + platform_handle.type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT; + platform_handle.value = static_cast<uint64_t>(port); + + MojoHandle mojo_handle; + MojoResult result = MojoWrapPlatformHandle(&platform_handle, &mojo_handle); + CHECK_EQ(result, MOJO_RESULT_OK); + + return ScopedHandle(Handle(mojo_handle)); +} + +MojoResult UnwrapMachPort(ScopedHandle handle, mach_port_t* port) { + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + MojoResult result = + MojoUnwrapPlatformHandle(handle.release().value(), &platform_handle); + if (result != MOJO_RESULT_OK) + return result; + + CHECK_EQ(platform_handle.type, MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT); + *port = static_cast<mach_port_t>(platform_handle.value); + return MOJO_RESULT_OK; +} +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +} // namespace mojo diff --git a/mojo/public/cpp/system/platform_handle.h b/mojo/public/cpp/system/platform_handle.h new file mode 100644 index 0000000000..801264efce --- /dev/null +++ b/mojo/public/cpp/system/platform_handle.h @@ -0,0 +1,92 @@ +// 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. + +// This file provides a C++ wrapping around the Mojo C API for platform handles, +// replacing the prefix of "Mojo" with a "mojo" namespace. +// +// Please see "mojo/public/c/system/platform_handle.h" for complete +// documentation of the API. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_ + +#include <stdint.h> + +#include "base/compiler_specific.h" +#include "base/files/file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/shared_memory_handle.h" +#include "base/process/process_handle.h" +#include "mojo/public/c/system/platform_handle.h" +#include "mojo/public/cpp/system/buffer.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +#if defined(OS_WIN) +#include <windows.h> +#endif + +namespace mojo { + +#if defined(OS_POSIX) +const MojoPlatformHandleType kPlatformFileHandleType = + MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; + +#if defined(OS_MACOSX) && !defined(OS_IOS) +const MojoPlatformHandleType kPlatformSharedBufferHandleType = + MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT; +#else +const MojoPlatformHandleType kPlatformSharedBufferHandleType = + MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +#elif defined(OS_WIN) +const MojoPlatformHandleType kPlatformFileHandleType = + MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE; + +const MojoPlatformHandleType kPlatformSharedBufferHandleType = + MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE; +#endif // defined(OS_POSIX) + +// Wraps a PlatformFile as a Mojo handle. Takes ownership of the file object. +MOJO_CPP_SYSTEM_EXPORT +ScopedHandle WrapPlatformFile(base::PlatformFile platform_file); + +// Unwraps a PlatformFile from a Mojo handle. +MOJO_CPP_SYSTEM_EXPORT +MojoResult UnwrapPlatformFile(ScopedHandle handle, base::PlatformFile* file); + +// Wraps a base::SharedMemoryHandle as a Mojo handle. Takes ownership of the +// SharedMemoryHandle. Note that |read_only| is only an indicator of whether +// |memory_handle| only supports read-only mapping. It does NOT have any +// influence on the access control of the shared buffer object. +MOJO_CPP_SYSTEM_EXPORT +ScopedSharedBufferHandle WrapSharedMemoryHandle( + const base::SharedMemoryHandle& memory_handle, + size_t size, + bool read_only); + +// Unwraps a base::SharedMemoryHandle from a Mojo handle. The caller assumes +// responsibility for the lifetime of the SharedMemoryHandle. +MOJO_CPP_SYSTEM_EXPORT MojoResult +UnwrapSharedMemoryHandle(ScopedSharedBufferHandle handle, + base::SharedMemoryHandle* memory_handle, + size_t* size, + bool* read_only); + +#if defined(OS_MACOSX) && !defined(OS_IOS) +// Wraps a mach_port_t as a Mojo handle. This takes a reference to the +// Mach port. +MOJO_CPP_SYSTEM_EXPORT ScopedHandle WrapMachPort(mach_port_t port); + +// Unwraps a mach_port_t from a Mojo handle. The caller gets ownership of the +// Mach port. +MOJO_CPP_SYSTEM_EXPORT MojoResult UnwrapMachPort(ScopedHandle handle, + mach_port_t* port); +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_PLATFORM_HANDLE_H_ diff --git a/mojo/public/cpp/system/simple_watcher.cc b/mojo/public/cpp/system/simple_watcher.cc new file mode 100644 index 0000000000..ae96faa395 --- /dev/null +++ b/mojo/public/cpp/system/simple_watcher.cc @@ -0,0 +1,279 @@ +// 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. + +#include "mojo/public/cpp/system/simple_watcher.h" + +#include "base/bind.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/single_thread_task_runner.h" +#include "base/synchronization/lock.h" +#include "base/trace_event/heap_profiler.h" +#include "mojo/public/c/system/watcher.h" + +namespace mojo { + +// Thread-safe Context object used to dispatch watch notifications from a +// arbitrary threads. +class SimpleWatcher::Context : public base::RefCountedThreadSafe<Context> { + public: + // Creates a |Context| instance for a new watch on |watcher|, to watch + // |handle| for |signals|. + static scoped_refptr<Context> Create( + base::WeakPtr<SimpleWatcher> watcher, + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + WatcherHandle watcher_handle, + Handle handle, + MojoHandleSignals signals, + int watch_id, + MojoResult* watch_result) { + scoped_refptr<Context> context = + new Context(watcher, task_runner, watch_id); + + // If MojoWatch succeeds, it assumes ownership of a reference to |context|. + // In that case, this reference is balanced in CallNotify() when |result| is + // |MOJO_RESULT_CANCELLED|. + context->AddRef(); + + *watch_result = MojoWatch(watcher_handle.value(), handle.value(), signals, + context->value()); + if (*watch_result != MOJO_RESULT_OK) { + // Balanced by the AddRef() above since watching failed. + context->Release(); + return nullptr; + } + + return context; + } + + static void CallNotify(uintptr_t context_value, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + auto* context = reinterpret_cast<Context*>(context_value); + context->Notify(result, signals_state, flags); + + // That was the last notification for the context. We can release the ref + // owned by the watch, which may in turn delete the Context. + if (result == MOJO_RESULT_CANCELLED) + context->Release(); + } + + uintptr_t value() const { return reinterpret_cast<uintptr_t>(this); } + + void DisableCancellationNotifications() { + base::AutoLock lock(lock_); + enable_cancellation_notifications_ = false; + } + + private: + friend class base::RefCountedThreadSafe<Context>; + + Context(base::WeakPtr<SimpleWatcher> weak_watcher, + scoped_refptr<base::SingleThreadTaskRunner> task_runner, + int watch_id) + : weak_watcher_(weak_watcher), + task_runner_(task_runner), + watch_id_(watch_id) {} + ~Context() {} + + void Notify(MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + if (result == MOJO_RESULT_CANCELLED) { + // The SimpleWatcher may have explicitly cancelled this watch, so we don't + // bother dispatching the notification - it would be ignored anyway. + // + // TODO(rockot): This shouldn't really be necessary, but there are already + // instances today where bindings object may be bound and subsequently + // closed due to pipe error, all before the thread's TaskRunner has been + // properly initialized. + base::AutoLock lock(lock_); + if (!enable_cancellation_notifications_) + return; + } + + if ((flags & MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM) && + task_runner_->RunsTasksOnCurrentThread() && weak_watcher_ && + weak_watcher_->is_default_task_runner_) { + // System notifications will trigger from the task runner passed to + // mojo::edk::InitIPCSupport(). In Chrome this happens to always be the + // default task runner for the IO thread. + weak_watcher_->OnHandleReady(watch_id_, result); + } else { + task_runner_->PostTask( + FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, weak_watcher_, + watch_id_, result)); + } + } + + const base::WeakPtr<SimpleWatcher> weak_watcher_; + const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + const int watch_id_; + + base::Lock lock_; + bool enable_cancellation_notifications_ = true; + + DISALLOW_COPY_AND_ASSIGN(Context); +}; + +SimpleWatcher::SimpleWatcher(const tracked_objects::Location& from_here, + ArmingPolicy arming_policy, + scoped_refptr<base::SingleThreadTaskRunner> runner) + : arming_policy_(arming_policy), + task_runner_(std::move(runner)), + is_default_task_runner_(task_runner_ == + base::ThreadTaskRunnerHandle::Get()), + heap_profiler_tag_(from_here.file_name()), + weak_factory_(this) { + MojoResult rv = CreateWatcher(&Context::CallNotify, &watcher_handle_); + DCHECK_EQ(MOJO_RESULT_OK, rv); + DCHECK(task_runner_->BelongsToCurrentThread()); +} + +SimpleWatcher::~SimpleWatcher() { + if (IsWatching()) + Cancel(); +} + +bool SimpleWatcher::IsWatching() const { + DCHECK(thread_checker_.CalledOnValidThread()); + return context_ != nullptr; +} + +MojoResult SimpleWatcher::Watch(Handle handle, + MojoHandleSignals signals, + const ReadyCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!IsWatching()); + DCHECK(!callback.is_null()); + + callback_ = callback; + handle_ = handle; + watch_id_ += 1; + + MojoResult watch_result = MOJO_RESULT_UNKNOWN; + context_ = Context::Create(weak_factory_.GetWeakPtr(), task_runner_, + watcher_handle_.get(), handle_, signals, watch_id_, + &watch_result); + if (!context_) { + handle_.set_value(kInvalidHandleValue); + callback_.Reset(); + DCHECK_EQ(MOJO_RESULT_INVALID_ARGUMENT, watch_result); + return watch_result; + } + + if (arming_policy_ == ArmingPolicy::AUTOMATIC) + ArmOrNotify(); + + return MOJO_RESULT_OK; +} + +void SimpleWatcher::Cancel() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // The watcher may have already been cancelled if the handle was closed. + if (!context_) + return; + + // Prevent the cancellation notification from being dispatched to + // OnHandleReady() when cancellation is explicit. See the note in the + // implementation of DisableCancellationNotifications() above. + context_->DisableCancellationNotifications(); + + handle_.set_value(kInvalidHandleValue); + callback_.Reset(); + + // Ensure |context_| is unset by the time we call MojoCancelWatch, as may + // re-enter the notification callback and we want to ensure |context_| is + // unset by then. + scoped_refptr<Context> context; + std::swap(context, context_); + MojoResult rv = + MojoCancelWatch(watcher_handle_.get().value(), context->value()); + + // It's possible this cancellation could race with a handle closure + // notification, in which case the watch may have already been implicitly + // cancelled. + DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND); +} + +MojoResult SimpleWatcher::Arm(MojoResult* ready_result) { + DCHECK(thread_checker_.CalledOnValidThread()); + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult local_ready_result; + MojoHandleSignalsState ready_state; + MojoResult rv = + MojoArmWatcher(watcher_handle_.get().value(), &num_ready_contexts, + &ready_context, &local_ready_result, &ready_state); + if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + DCHECK(context_); + DCHECK_EQ(1u, num_ready_contexts); + DCHECK_EQ(context_->value(), ready_context); + if (ready_result) + *ready_result = local_ready_result; + } + + return rv; +} + +void SimpleWatcher::ArmOrNotify() { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Already cancelled, nothing to do. + if (!IsWatching()) + return; + + MojoResult ready_result; + MojoResult rv = Arm(&ready_result); + if (rv == MOJO_RESULT_OK) + return; + + DCHECK_EQ(MOJO_RESULT_FAILED_PRECONDITION, rv); + task_runner_->PostTask(FROM_HERE, base::Bind(&SimpleWatcher::OnHandleReady, + weak_factory_.GetWeakPtr(), + watch_id_, ready_result)); +} + +void SimpleWatcher::OnHandleReady(int watch_id, MojoResult result) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // This notification may be for a previously watched context, in which case + // we just ignore it. + if (watch_id != watch_id_) + return; + + ReadyCallback callback = callback_; + if (result == MOJO_RESULT_CANCELLED) { + // Implicit cancellation due to someone closing the watched handle. We clear + // the SimppleWatcher's state before dispatching this. + context_ = nullptr; + handle_.set_value(kInvalidHandleValue); + callback_.Reset(); + } + + // NOTE: It's legal for |callback| to delete |this|. + if (!callback.is_null()) { + TRACE_HEAP_PROFILER_API_SCOPED_TASK_EXECUTION event(heap_profiler_tag_); + + base::WeakPtr<SimpleWatcher> weak_self = weak_factory_.GetWeakPtr(); + callback.Run(result); + if (!weak_self) + return; + + if (unsatisfiable_) + return; + + // Prevent |MOJO_RESULT_FAILED_PRECONDITION| task spam by only notifying + // at most once in AUTOMATIC arming mode. + if (result == MOJO_RESULT_FAILED_PRECONDITION) + unsatisfiable_ = true; + + if (arming_policy_ == ArmingPolicy::AUTOMATIC && IsWatching()) + ArmOrNotify(); + } +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/simple_watcher.h b/mojo/public/cpp/system/simple_watcher.h new file mode 100644 index 0000000000..9001884c97 --- /dev/null +++ b/mojo/public/cpp/system/simple_watcher.h @@ -0,0 +1,215 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_ + +#include "base/callback.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/thread_checker.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/system_export.h" +#include "mojo/public/cpp/system/watcher.h" + +namespace base { +class SingleThreadTaskRunner; +} + +namespace mojo { + +// This provides a convenient thread-bound watcher implementation to safely +// watch a single handle, dispatching state change notifications to an arbitrary +// SingleThreadTaskRunner running on the same thread as the SimpleWatcher. +// +// SimpleWatcher exposes the concept of "arming" from the low-level Watcher API. +// In general, a SimpleWatcher must be "armed" in order to dispatch a single +// notification, and must then be rearmed before it will dispatch another. For +// more details, see the documentation for ArmingPolicy and the Arm() and +// ArmOrNotify() methods below. +class MOJO_CPP_SYSTEM_EXPORT SimpleWatcher { + public: + // A callback to be called any time a watched handle changes state in some + // interesting way. The |result| argument indicates one of the following + // conditions depending on its value: + // + // |MOJO_RESULT_OK|: One or more of the signals being watched is satisfied. + // + // |MOJO_RESULT_FAILED_PRECONDITION|: None of the signals being watched can + // ever be satisfied again. + // + // |MOJO_RESULT_CANCELLED|: The watched handle has been closed. No further + // notifications will be fired, as this equivalent to an implicit + // CancelWatch(). + // + // Note that unlike the first two conditions, this callback may be invoked + // with |MOJO_RESULT_CANCELLED| even while the SimpleWatcher is disarmed. + using ReadyCallback = base::Callback<void(MojoResult result)>; + + // Selects how this SimpleWatcher is to be armed. + enum class ArmingPolicy { + // The SimpleWatcher is armed automatically on Watch() and rearmed again + // after every invocation of the ReadyCallback. There is no need to manually + // call Arm() on a SimpleWatcher using this policy. This mode is equivalent + // to calling ArmOrNotify() once after Watch() and once again after every + // dispatched notification in MANUAL mode. + // + // This provides a reasonable approximation of edge-triggered behavior, + // mitigating (but not completely eliminating) the potential for redundant + // notifications. + // + // NOTE: It is important when using AUTOMATIC policy that your ReadyCallback + // always attempt to change the state of the handle (e.g. read available + // messages on a message pipe.) Otherwise this will result in a potentially + // large number of avoidable redundant tasks. + // + // For perfect edge-triggered behavior, use MANUAL policy and manually Arm() + // the SimpleWatcher as soon as it becomes possible to do so again. + AUTOMATIC, + + // The SimpleWatcher is never armed automatically. Arm() or ArmOrNotify() + // must be called manually before any non-cancellation notification can be + // dispatched to the ReadyCallback. See the documentation for Arm() and + // ArmNotify() methods below for more details. + MANUAL, + }; + + SimpleWatcher(const tracked_objects::Location& from_here, + ArmingPolicy arming_policy, + scoped_refptr<base::SingleThreadTaskRunner> runner = + base::ThreadTaskRunnerHandle::Get()); + ~SimpleWatcher(); + + // Indicates if the SimpleWatcher is currently watching a handle. + bool IsWatching() const; + + // Starts watching |handle|. A SimpleWatcher may only watch one handle at a + // time, but it is safe to call this more than once as long as the previous + // watch has been cancelled (i.e. |IsWatching()| returns |false|.) + // + // If |handle| is not a valid watchable (message or data pipe) handle or + // |signals| is not a valid set of signals to watch, this returns + // |MOJO_RESULT_INVALID_ARGUMENT|. + // + // Otherwise |MOJO_RESULT_OK| is returned and the handle will be watched until + // either |handle| is closed, the SimpleWatcher is destroyed, or Cancel() is + // explicitly called. + // + // Once the watch is started, |callback| may be called at any time on the + // current thread until |Cancel()| is called or the handle is closed. Note + // that |callback| can be called for results other than + // |MOJO_RESULT_CANCELLED| only if the SimpleWatcher is currently armed. Use + // ArmingPolicy to configure how a SimpleWatcher is armed. + // + // |MOJO_RESULT_CANCELLED| may be dispatched even while the SimpleWatcher + // is disarmed, and no further notifications will be dispatched after that. + // + // Destroying the SimpleWatcher implicitly calls |Cancel()|. + MojoResult Watch(Handle handle, + MojoHandleSignals signals, + const ReadyCallback& callback); + + // Cancels the current watch. Once this returns, the ReadyCallback previously + // passed to |Watch()| will never be called again for this SimpleWatcher. + // + // Note that when cancelled with an explicit call to |Cancel()| the + // ReadyCallback will not be invoked with a |MOJO_RESULT_CANCELLED| result. + void Cancel(); + + // Manually arms the SimpleWatcher. + // + // Arming the SimpleWatcher allows it to fire a single notification regarding + // some future relevant change in the watched handle's state. It's only valid + // to call Arm() while a handle is being watched (see Watch() above.) + // + // SimpleWatcher is always disarmed immediately before invoking its + // ReadyCallback and must be rearmed again before another notification can + // fire. + // + // If the watched handle already meets the watched signaling conditions - + // i.e., if it would have notified immediately once armed - the SimpleWatcher + // is NOT armed, and this call fails with a return value of + // |MOJO_RESULT_FAILED_PRECONDITION|. In that case, what would have been the + // result code for that immediate notification is instead placed in + // |*ready_result| if |ready_result| is non-null. + // + // If the watcher is successfully armed, this returns |MOJO_RESULT_OK| and + // |ready_result| is ignored. + MojoResult Arm(MojoResult* ready_result = nullptr); + + // Manually arms the SimpleWatcher OR posts a task to invoke the ReadyCallback + // with the ready result of the failed arming attempt. + // + // This is meant as a convenient helper for a common usage of Arm(), and it + // ensures that the ReadyCallback will be invoked asynchronously again as soon + // as the watch's conditions are satisfied, assuming the SimpleWatcher isn't + // cancelled first. + // + // Unlike Arm() above, this can never fail. + void ArmOrNotify(); + + Handle handle() const { return handle_; } + ReadyCallback ready_callback() const { return callback_; } + + // Sets the tag used by the heap profiler. + // |tag| must be a const string literal. + void set_heap_profiler_tag(const char* heap_profiler_tag) { + heap_profiler_tag_ = heap_profiler_tag; + } + + private: + class Context; + + void OnHandleReady(int watch_id, MojoResult result); + + base::ThreadChecker thread_checker_; + + // The policy used to determine how this SimpleWatcher is armed. + const ArmingPolicy arming_policy_; + + // The TaskRunner of this SimpleWatcher's owning thread. This field is safe to + // access from any thread. + const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; + + // Whether |task_runner_| is the same as base::ThreadTaskRunnerHandle::Get() + // for the thread. + const bool is_default_task_runner_; + + ScopedWatcherHandle watcher_handle_; + + // A thread-safe context object corresponding to the currently active watch, + // if any. + scoped_refptr<Context> context_; + + // Fields below must only be accessed on the SimpleWatcher's owning thread. + + // The handle currently under watch. Not owned. + Handle handle_; + + // A simple counter to disambiguate notifications from multiple watch contexts + // in the event that this SimpleWatcher cancels and watches multiple times. + int watch_id_ = 0; + + // The callback to call when the handle is signaled. + ReadyCallback callback_; + + // Tracks if the SimpleWatcher has already notified of unsatisfiability. This + // is used to prevent redundant notifications in AUTOMATIC mode. + bool unsatisfiable_ = false; + + // Tag used to ID memory allocations that originated from notifications in + // this watcher. + const char* heap_profiler_tag_ = nullptr; + + base::WeakPtrFactory<SimpleWatcher> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SimpleWatcher); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_SIMPLE_WATCHER_H_ diff --git a/mojo/public/cpp/system/system_export.h b/mojo/public/cpp/system/system_export.h new file mode 100644 index 0000000000..c9bb140db3 --- /dev/null +++ b/mojo/public/cpp/system/system_export.h @@ -0,0 +1,34 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_CPP_SYSTEM_IMPLEMENTATION) +#define MOJO_CPP_SYSTEM_EXPORT __declspec(dllexport) +#else +#define MOJO_CPP_SYSTEM_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_CPP_SYSTEM_IMPLEMENTATION) +#define MOJO_CPP_SYSTEM_EXPORT __attribute((visibility("default"))) +#else +#define MOJO_CPP_SYSTEM_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) + +#define MOJO_CPP_SYSTEM_EXPORT + +#endif // defined(COMPONENT_BUILD) + +#endif // MOJO_PUBLIC_CPP_SYSTEM_SYSTEM_EXPORT_H_ diff --git a/mojo/public/cpp/system/tests/BUILD.gn b/mojo/public/cpp/system/tests/BUILD.gn new file mode 100644 index 0000000000..705d009c9c --- /dev/null +++ b/mojo/public/cpp/system/tests/BUILD.gn @@ -0,0 +1,23 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +source_set("tests") { + testonly = true + + sources = [ + "core_unittest.cc", + "handle_signals_state_unittest.cc", + "simple_watcher_unittest.cc", + "wait_set_unittest.cc", + "wait_unittest.cc", + ] + + deps = [ + "//base", + "//mojo/public/c/system/tests", + "//mojo/public/cpp/system", + "//mojo/public/cpp/test_support:test_utils", + "//testing/gtest", + ] +} diff --git a/mojo/public/cpp/system/tests/core_unittest.cc b/mojo/public/cpp/system/tests/core_unittest.cc new file mode 100644 index 0000000000..40a94f008f --- /dev/null +++ b/mojo/public/cpp/system/tests/core_unittest.cc @@ -0,0 +1,510 @@ +// 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 tests the C++ Mojo system core wrappers. +// TODO(vtl): Maybe rename "CoreCppTest" -> "CoreTest" if/when this gets +// compiled into a different binary from the C API tests. + +#include "mojo/public/cpp/system/core.h" + +#include <stddef.h> +#include <stdint.h> +#include <map> +#include <utility> + +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +const MojoHandleSignals kSignalReadableWritable = + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE; + +const MojoHandleSignals kSignalAll = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; + +TEST(CoreCppTest, GetTimeTicksNow) { + const MojoTimeTicks start = GetTimeTicksNow(); + EXPECT_NE(static_cast<MojoTimeTicks>(0), start) + << "GetTimeTicksNow should return nonzero value"; +} + +TEST(CoreCppTest, Basic) { + // Basic |Handle| implementation: + { + EXPECT_EQ(MOJO_HANDLE_INVALID, kInvalidHandleValue); + + Handle h0; + EXPECT_EQ(kInvalidHandleValue, h0.value()); + EXPECT_EQ(kInvalidHandleValue, *h0.mutable_value()); + EXPECT_FALSE(h0.is_valid()); + + Handle h1(static_cast<MojoHandle>(123)); + EXPECT_EQ(static_cast<MojoHandle>(123), h1.value()); + EXPECT_EQ(static_cast<MojoHandle>(123), *h1.mutable_value()); + EXPECT_TRUE(h1.is_valid()); + *h1.mutable_value() = static_cast<MojoHandle>(456); + EXPECT_EQ(static_cast<MojoHandle>(456), h1.value()); + EXPECT_TRUE(h1.is_valid()); + + h1.swap(h0); + EXPECT_EQ(static_cast<MojoHandle>(456), h0.value()); + EXPECT_TRUE(h0.is_valid()); + EXPECT_FALSE(h1.is_valid()); + + h1.set_value(static_cast<MojoHandle>(789)); + h0.swap(h1); + EXPECT_EQ(static_cast<MojoHandle>(789), h0.value()); + EXPECT_TRUE(h0.is_valid()); + EXPECT_EQ(static_cast<MojoHandle>(456), h1.value()); + EXPECT_TRUE(h1.is_valid()); + + // Make sure copy constructor works. + Handle h2(h0); + EXPECT_EQ(static_cast<MojoHandle>(789), h2.value()); + // And assignment. + h2 = h1; + EXPECT_EQ(static_cast<MojoHandle>(456), h2.value()); + + // Make sure that we can put |Handle|s into |std::map|s. + h0 = Handle(static_cast<MojoHandle>(987)); + h1 = Handle(static_cast<MojoHandle>(654)); + h2 = Handle(static_cast<MojoHandle>(321)); + Handle h3; + std::map<Handle, int> handle_to_int; + handle_to_int[h0] = 0; + handle_to_int[h1] = 1; + handle_to_int[h2] = 2; + handle_to_int[h3] = 3; + + EXPECT_EQ(4u, handle_to_int.size()); + EXPECT_FALSE(handle_to_int.find(h0) == handle_to_int.end()); + EXPECT_EQ(0, handle_to_int[h0]); + EXPECT_FALSE(handle_to_int.find(h1) == handle_to_int.end()); + EXPECT_EQ(1, handle_to_int[h1]); + EXPECT_FALSE(handle_to_int.find(h2) == handle_to_int.end()); + EXPECT_EQ(2, handle_to_int[h2]); + EXPECT_FALSE(handle_to_int.find(h3) == handle_to_int.end()); + EXPECT_EQ(3, handle_to_int[h3]); + EXPECT_TRUE(handle_to_int.find(Handle(static_cast<MojoHandle>(13579))) == + handle_to_int.end()); + + // TODO(vtl): With C++11, support |std::unordered_map|s, etc. (Or figure out + // how to support the variations of |hash_map|.) + } + + // |Handle|/|ScopedHandle| functions: + { + ScopedHandle h; + + EXPECT_EQ(kInvalidHandleValue, h.get().value()); + + // This should be a no-op. + Close(std::move(h)); + + // It should still be invalid. + EXPECT_EQ(kInvalidHandleValue, h.get().value()); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + Wait(h.get(), ~MOJO_HANDLE_SIGNAL_NONE)); + + std::vector<Handle> wh; + wh.push_back(h.get()); + std::vector<MojoHandleSignals> sigs; + sigs.push_back(~MOJO_HANDLE_SIGNAL_NONE); + size_t result_index; + MojoResult rv = WaitMany(wh.data(), sigs.data(), wh.size(), &result_index); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, rv); + } + + // |MakeScopedHandle| (just compilation tests): + { + EXPECT_FALSE(MakeScopedHandle(Handle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(MessagePipeHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(DataPipeProducerHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(DataPipeConsumerHandle()).is_valid()); + EXPECT_FALSE(MakeScopedHandle(SharedBufferHandle()).is_valid()); + } + + // |MessagePipeHandle|/|ScopedMessagePipeHandle| functions: + { + MessagePipeHandle h_invalid; + EXPECT_FALSE(h_invalid.is_valid()); + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + WriteMessageRaw( + h_invalid, nullptr, 0, nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); + char buffer[10] = {0}; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WriteMessageRaw(h_invalid, + buffer, + sizeof(buffer), + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + ReadMessageRaw(h_invalid, + nullptr, + nullptr, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + ReadMessageRaw(h_invalid, + buffer, + &buffer_size, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + + // Basic tests of waiting and closing. + MojoHandle hv0 = kInvalidHandleValue; + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + EXPECT_FALSE(h0.get().is_valid()); + EXPECT_FALSE(h1.get().is_valid()); + + CreateMessagePipe(nullptr, &h0, &h1); + EXPECT_TRUE(h0.get().is_valid()); + EXPECT_TRUE(h1.get().is_valid()); + EXPECT_NE(h0.get().value(), h1.get().value()); + // Save the handle values, so we can check that things got closed + // correctly. + hv0 = h0.get().value(); + MojoHandle hv1 = h1.get().value(); + MojoHandleSignalsState state = h0->QuerySignalsState(); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + std::vector<Handle> wh; + wh.push_back(h0.get()); + wh.push_back(h1.get()); + std::vector<MojoHandleSignals> sigs; + sigs.push_back(MOJO_HANDLE_SIGNAL_READABLE); + sigs.push_back(MOJO_HANDLE_SIGNAL_WRITABLE); + std::vector<MojoHandleSignalsState> states(sigs.size()); + + size_t result_index; + MojoResult rv = WaitMany(wh.data(), sigs.data(), wh.size(), &result_index, + states.data()); + EXPECT_EQ(MOJO_RESULT_OK, rv); + EXPECT_EQ(1u, result_index); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[0].satisfied_signals); + EXPECT_EQ(kSignalAll, states[0].satisfiable_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, states[1].satisfied_signals); + EXPECT_EQ(kSignalAll, states[1].satisfiable_signals); + + // Test closing |h1| explicitly. + Close(std::move(h1)); + EXPECT_FALSE(h1.get().is_valid()); + + // Make sure |h1| is closed. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + Wait(Handle(hv1), ~MOJO_HANDLE_SIGNAL_NONE)); + + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); + + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + } + // |hv0| should have been closed when |h0| went out of scope, so this close + // should fail. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0)); + + // Actually test writing/reading messages. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h0.get(), + kHello, + kHelloSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + MojoHandleSignalsState state; + EXPECT_EQ(MOJO_RESULT_OK, + Wait(h1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); + EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + char buffer[10] = {0}; + uint32_t buffer_size = static_cast<uint32_t>(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(h1.get(), + buffer, + &buffer_size, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, buffer_size); + EXPECT_STREQ(kHello, buffer); + + // Send a handle over the previously-establish message pipe. Use the + // |MessagePipe| wrapper (to test it), which automatically creates a + // message pipe. + MessagePipe mp; + + // Write a message to |mp.handle0|, before we send |mp.handle1|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(mp.handle0.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |mp.handle1| over |h1| to |h0|. + MojoHandle handles[5]; + handles[0] = mp.handle1.release().value(); + EXPECT_NE(kInvalidHandleValue, handles[0]); + EXPECT_FALSE(mp.handle1.get().is_valid()); + uint32_t handles_count = 1; + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + handles, + handles_count, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |handles[0]| should actually be invalid now. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(handles[0])); + + // Read "hello" and the sent handle. + EXPECT_EQ(MOJO_RESULT_OK, + Wait(h0.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); + EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + memset(buffer, 0, sizeof(buffer)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + for (size_t i = 0; i < arraysize(handles); i++) + handles[i] = kInvalidHandleValue; + handles_count = static_cast<uint32_t>(arraysize(handles)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(h0.get(), + buffer, + &buffer_size, + handles, + &handles_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kHelloSize, buffer_size); + EXPECT_STREQ(kHello, buffer); + EXPECT_EQ(1u, handles_count); + EXPECT_NE(kInvalidHandleValue, handles[0]); + + // Read from the sent/received handle. + mp.handle1.reset(MessagePipeHandle(handles[0])); + // Save |handles[0]| to check that it gets properly closed. + hv0 = handles[0]; + + EXPECT_EQ(MOJO_RESULT_OK, + Wait(mp.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &state)); + EXPECT_EQ(kSignalReadableWritable, state.satisfied_signals); + EXPECT_EQ(kSignalAll, state.satisfiable_signals); + + memset(buffer, 0, sizeof(buffer)); + buffer_size = static_cast<uint32_t>(sizeof(buffer)); + for (size_t i = 0; i < arraysize(handles); i++) + handles[i] = kInvalidHandleValue; + handles_count = static_cast<uint32_t>(arraysize(handles)); + EXPECT_EQ(MOJO_RESULT_OK, + ReadMessageRaw(mp.handle1.get(), + buffer, + &buffer_size, + handles, + &handles_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(kWorldSize, buffer_size); + EXPECT_STREQ(kWorld, buffer); + EXPECT_EQ(0u, handles_count); + } + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(hv0)); + } + + // TODO(vtl): Test |CloseRaw()|. + // TODO(vtl): Test |reset()| more thoroughly? +} + +TEST(CoreCppTest, TearDownWithMessagesEnqueued) { + // Tear down a message pipe which still has a message enqueued, with the + // message also having a valid message pipe handle. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + // Send a handle over the previously-establish message pipe. + ScopedMessagePipeHandle h2; + ScopedMessagePipeHandle h3; + if (CreateMessagePipe(nullptr, &h2, &h3) != MOJO_RESULT_OK) + CreateMessagePipe(nullptr, &h2, &h3); // Must be old EDK. + + // Write a message to |h2|, before we send |h3|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h2.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // And also a message to |h3|. + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h3.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |h3| over |h1| to |h0|. + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + MojoHandle h3_value; + h3_value = h3.release().value(); + EXPECT_NE(kInvalidHandleValue, h3_value); + EXPECT_FALSE(h3.get().is_valid()); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + &h3_value, + 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |h3_value| should actually be invalid now. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value())); + } + + // Do this in a different order: make the enqueued message pipe handle only + // half-alive. + { + ScopedMessagePipeHandle h0; + ScopedMessagePipeHandle h1; + CreateMessagePipe(nullptr, &h0, &h1); + + // Send a handle over the previously-establish message pipe. + ScopedMessagePipeHandle h2; + ScopedMessagePipeHandle h3; + if (CreateMessagePipe(nullptr, &h2, &h3) != MOJO_RESULT_OK) + CreateMessagePipe(nullptr, &h2, &h3); // Must be old EDK. + + // Write a message to |h2|, before we send |h3|. + const char kWorld[] = "world!"; + const uint32_t kWorldSize = static_cast<uint32_t>(sizeof(kWorld)); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h2.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // And also a message to |h3|. + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h3.get(), + kWorld, + kWorldSize, + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send |h3| over |h1| to |h0|. + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast<uint32_t>(sizeof(kHello)); + MojoHandle h3_value; + h3_value = h3.release().value(); + EXPECT_NE(kInvalidHandleValue, h3_value); + EXPECT_FALSE(h3.get().is_valid()); + EXPECT_EQ(MOJO_RESULT_OK, + WriteMessageRaw(h1.get(), + kHello, + kHelloSize, + &h3_value, + 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + // |h3_value| should actually be invalid now. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(h3_value)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h2.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h0.release().value())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h1.release().value())); + } +} + +TEST(CoreCppTest, ScopedHandleMoveCtor) { + ScopedSharedBufferHandle buffer1 = SharedBufferHandle::Create(1024); + EXPECT_TRUE(buffer1.is_valid()); + + ScopedSharedBufferHandle buffer2 = SharedBufferHandle::Create(1024); + EXPECT_TRUE(buffer2.is_valid()); + + // If this fails to close buffer1, ScopedHandleBase::CloseIfNecessary() will + // assert. + buffer1 = std::move(buffer2); + + EXPECT_TRUE(buffer1.is_valid()); + EXPECT_FALSE(buffer2.is_valid()); +} + +TEST(CoreCppTest, BasicSharedBuffer) { + ScopedSharedBufferHandle h0 = SharedBufferHandle::Create(100); + ASSERT_TRUE(h0.is_valid()); + + // Map everything. + ScopedSharedBufferMapping mapping = h0->Map(100); + ASSERT_TRUE(mapping); + static_cast<char*>(mapping.get())[50] = 'x'; + + // Duplicate |h0| to |h1|. + ScopedSharedBufferHandle h1 = + h0->Clone(SharedBufferHandle::AccessMode::READ_ONLY); + ASSERT_TRUE(h1.is_valid()); + + // Close |h0|. + h0.reset(); + + // The mapping should still be good. + static_cast<char*>(mapping.get())[51] = 'y'; + + // Unmap it. + mapping.reset(); + + // Map half of |h1|. + mapping = h1->MapAtOffset(50, 50); + ASSERT_TRUE(mapping); + + // It should have what we wrote. + EXPECT_EQ('x', static_cast<char*>(mapping.get())[0]); + EXPECT_EQ('y', static_cast<char*>(mapping.get())[1]); + + // Unmap it. + mapping.reset(); + h1.reset(); + + // Creating a 1 EB shared buffer should fail without crashing. + EXPECT_FALSE(SharedBufferHandle::Create(1ULL << 60).is_valid()); +} + +// TODO(vtl): Write data pipe tests. + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc new file mode 100644 index 0000000000..82f538e17a --- /dev/null +++ b/mojo/public/cpp/system/tests/handle_signals_state_unittest.cc @@ -0,0 +1,42 @@ +// 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. + +#include "mojo/public/cpp/system/handle_signals_state.h" + +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +using HandleSignalsStateTest = testing::Test; + +TEST_F(HandleSignalsStateTest, SanityCheck) { + // There's not much to test here. Just a quick sanity check to make sure the + // code compiles and the helper methods do what they're supposed to do. + + HandleSignalsState empty_signals(MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_NONE); + EXPECT_FALSE(empty_signals.readable()); + EXPECT_FALSE(empty_signals.writable()); + EXPECT_FALSE(empty_signals.peer_closed()); + EXPECT_TRUE(empty_signals.never_readable()); + EXPECT_TRUE(empty_signals.never_writable()); + EXPECT_TRUE(empty_signals.never_peer_closed()); + + HandleSignalsState all_signals( + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + EXPECT_TRUE(all_signals.readable()); + EXPECT_TRUE(all_signals.writable()); + EXPECT_TRUE(all_signals.peer_closed()); + EXPECT_FALSE(all_signals.never_readable()); + EXPECT_FALSE(all_signals.never_writable()); + EXPECT_FALSE(all_signals.never_peer_closed()); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/simple_watcher_unittest.cc b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc new file mode 100644 index 0000000000..795f262c4e --- /dev/null +++ b/mojo/public/cpp/system/tests/simple_watcher_unittest.cc @@ -0,0 +1,277 @@ +// 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. + +#include "mojo/public/cpp/system/simple_watcher.h" + +#include <memory> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +template <typename Handler> +void RunResultHandler(Handler f, MojoResult result) { + f(result); +} + +template <typename Handler> +SimpleWatcher::ReadyCallback OnReady(Handler f) { + return base::Bind(&RunResultHandler<Handler>, f); +} + +SimpleWatcher::ReadyCallback NotReached() { + return OnReady([](MojoResult) { NOTREACHED(); }); +} + +class SimpleWatcherTest : public testing::Test { + public: + SimpleWatcherTest() {} + ~SimpleWatcherTest() override {} + + private: + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(SimpleWatcherTest); +}; + +TEST_F(SimpleWatcherTest, WatchBasic) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + bool notified = false; + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([&](MojoResult result) { + EXPECT_EQ(MOJO_RESULT_OK, result); + notified = true; + run_loop.Quit(); + }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + run_loop.Run(); + EXPECT_TRUE(notified); + + b_watcher.Cancel(); +} + +TEST_F(SimpleWatcherTest, WatchUnsatisfiable) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + a.reset(); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, b_watcher.Arm()); +} + +TEST_F(SimpleWatcherTest, WatchInvalidHandle) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + a.reset(); + b.reset(); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_FALSE(b_watcher.IsWatching()); +} + +TEST_F(SimpleWatcherTest, Cancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_TRUE(b_watcher.IsWatching()); + b_watcher.Cancel(); + EXPECT_FALSE(b_watcher.IsWatching()); + + // This should never trigger the watcher. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(SimpleWatcherTest, CancelOnClose) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop run_loop; + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([&](MojoResult result) { + EXPECT_EQ(MOJO_RESULT_CANCELLED, result); + run_loop.Quit(); + }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + // This should trigger the watcher above. + b.reset(); + + run_loop.Run(); + + EXPECT_FALSE(b_watcher.IsWatching()); +} + +TEST_F(SimpleWatcherTest, CancelOnDestruction) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + base::RunLoop run_loop; + { + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ( + MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, NotReached())); + EXPECT_TRUE(b_watcher.IsWatching()); + + // |b_watcher| should be cancelled when it goes out of scope. + } + + // This should never trigger the watcher above. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, + run_loop.QuitClosure()); + run_loop.Run(); +} + +TEST_F(SimpleWatcherTest, CloseAndCancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + OnReady([](MojoResult result) { FAIL(); }))); + EXPECT_TRUE(b_watcher.IsWatching()); + + // This should trigger the watcher above... + b.reset(); + // ...but the watcher is cancelled first. + b_watcher.Cancel(); + + EXPECT_FALSE(b_watcher.IsWatching()); + + base::RunLoop().RunUntilIdle(); +} + +TEST_F(SimpleWatcherTest, UnarmedCancel) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + base::RunLoop loop; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_CANCELLED); + loop->Quit(); + }, + &loop))); + + // This message write will not wake up the watcher since the watcher isn't + // armed. Instead, the cancellation will dispatch due to the reset below. + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + b.reset(); + loop.Run(); +} + +TEST_F(SimpleWatcherTest, ManualArming) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + SimpleWatcher b_watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + base::RunLoop loop; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher.Watch(b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + loop->Quit(); + }, + &loop))); + EXPECT_EQ(MOJO_RESULT_OK, b_watcher.Arm()); + + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + loop.Run(); +} + +TEST_F(SimpleWatcherTest, ManualArmOrNotifyWhileSignaled) { + ScopedMessagePipeHandle a, b; + CreateMessagePipe(nullptr, &a, &b); + + base::RunLoop loop1; + SimpleWatcher b_watcher1(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + bool notified1 = false; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher1.Watch( + b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, bool* notified, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + *notified = true; + loop->Quit(); + }, + &loop1, ¬ified1))); + + base::RunLoop loop2; + SimpleWatcher b_watcher2(FROM_HERE, SimpleWatcher::ArmingPolicy::MANUAL); + bool notified2 = false; + EXPECT_EQ(MOJO_RESULT_OK, + b_watcher2.Watch( + b.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind( + [](base::RunLoop* loop, bool* notified, MojoResult result) { + EXPECT_EQ(result, MOJO_RESULT_OK); + *notified = true; + loop->Quit(); + }, + &loop2, ¬ified2))); + + // First ensure that |b| is readable. + EXPECT_EQ(MOJO_RESULT_OK, b_watcher1.Arm()); + EXPECT_EQ(MOJO_RESULT_OK, WriteMessageRaw(a.get(), "hello", 5, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + loop1.Run(); + + EXPECT_TRUE(notified1); + EXPECT_FALSE(notified2); + notified1 = false; + + // Now verify that ArmOrNotify results in a notification. + b_watcher2.ArmOrNotify(); + loop2.Run(); + + EXPECT_FALSE(notified1); + EXPECT_TRUE(notified2); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/wait_set_unittest.cc b/mojo/public/cpp/system/tests/wait_set_unittest.cc new file mode 100644 index 0000000000..d60cb45924 --- /dev/null +++ b/mojo/public/cpp/system/tests/wait_set_unittest.cc @@ -0,0 +1,376 @@ +// 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. + +#include "mojo/public/cpp/system/wait_set.h" + +#include <set> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/memory/ptr_util.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +using WaitSetTest = testing::Test; + +void WriteMessage(const ScopedMessagePipeHandle& handle, + const base::StringPiece& message) { + MojoResult rv = WriteMessageRaw(handle.get(), message.data(), + static_cast<uint32_t>(message.size()), + nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_OK, rv); +} + +std::string ReadMessage(const ScopedMessagePipeHandle& handle) { + uint32_t num_bytes = 0; + uint32_t num_handles = 0; + MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv); + CHECK_EQ(0u, num_handles); + + std::vector<char> buffer(num_bytes); + rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_OK, rv); + return std::string(buffer.data(), buffer.size()); +} + +class ThreadedRunner : public base::SimpleThread { + public: + explicit ThreadedRunner(const base::Closure& callback) + : SimpleThread("ThreadedRunner"), callback_(callback) {} + ~ThreadedRunner() override { Join(); } + + void Run() override { callback_.Run(); } + + private: + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadedRunner); +}; + +TEST_F(WaitSetTest, Satisfied) { + WaitSet wait_set; + MessagePipe p; + + const char kTestMessage1[] = "hello wake up"; + + // Watch only one handle and write to the other. + + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + WriteMessage(p.handle0, kTestMessage1); + + size_t num_ready_handles = 2; + Handle ready_handles[2]; + MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN}; + HandleSignalsState hss[2]; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle1.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + + wait_set.RemoveHandle(p.handle1.get()); + + // Now watch only the other handle and write to the first one. + + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + WriteMessage(p.handle1, kTestMessage1); + + num_ready_handles = 2; + ready_results[0] = MOJO_RESULT_UNKNOWN; + ready_results[1] = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle0.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + + // Now wait on both of them. + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + + num_ready_handles = 2; + ready_results[0] = MOJO_RESULT_UNKNOWN; + ready_results[1] = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + EXPECT_EQ(2u, num_ready_handles); + EXPECT_TRUE((ready_handles[0] == p.handle0.get() && + ready_handles[1] == p.handle1.get()) || + (ready_handles[0] == p.handle1.get() && + ready_handles[1] == p.handle0.get())); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[1]); + EXPECT_TRUE(hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed()); + + // Wait on both again, but with only enough output space for one result. + num_ready_handles = 1; + ready_results[0] = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_TRUE(ready_handles[0] == p.handle0.get() || + ready_handles[0] == p.handle1.get()); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Remove the ready handle from the set and wait one more time. + EXPECT_EQ(MOJO_RESULT_OK, wait_set.RemoveHandle(ready_handles[0])); + + num_ready_handles = 1; + ready_results[0] = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results, hss); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_TRUE(ready_handles[0] == p.handle0.get() || + ready_handles[0] == p.handle1.get()); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + EXPECT_EQ(MOJO_RESULT_OK, wait_set.RemoveHandle(ready_handles[0])); + + // The wait set should be empty now. Nothing to wait on. + num_ready_handles = 2; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(0u, num_ready_handles); +} + +TEST_F(WaitSetTest, Unsatisfiable) { + MessagePipe p, q; + WaitSet wait_set; + + wait_set.AddHandle(q.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(q.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + + size_t num_ready_handles = 2; + Handle ready_handles[2]; + MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN}; + + p.handle1.reset(); + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle0.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); +} + +TEST_F(WaitSetTest, CloseWhileWaiting) { + MessagePipe p; + WaitSet wait_set; + + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + + const Handle handle0_value = p.handle0.get(); + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then close the handle. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle0)); + close_after_delay.Start(); + + size_t num_ready_handles = 2; + Handle ready_handles[2]; + MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN}; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(handle0_value, ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_results[0]); + + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value)); +} + +TEST_F(WaitSetTest, CloseBeforeWaiting) { + MessagePipe p; + WaitSet wait_set; + + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + + Handle handle0_value = p.handle0.get(); + Handle handle1_value = p.handle1.get(); + + p.handle0.reset(); + p.handle1.reset(); + + // Ensure that the WaitSet user is always made aware of all cancellations even + // if they happen while not waiting, or they have to be returned over the span + // of multiple Wait() calls due to insufficient output storage. + + size_t num_ready_handles = 1; + Handle ready_handle; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_TRUE(ready_handle == handle0_value || ready_handle == handle1_value); + EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_result); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value)); + + wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_TRUE(ready_handle == handle0_value || ready_handle == handle1_value); + EXPECT_EQ(MOJO_RESULT_CANCELLED, ready_result); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, wait_set.RemoveHandle(handle0_value)); + + // Nothing more to wait on. + wait_set.Wait(nullptr, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(0u, num_ready_handles); +} + +TEST_F(WaitSetTest, SatisfiedThenUnsatisfied) { + MessagePipe p; + WaitSet wait_set; + + wait_set.AddHandle(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + + const char kTestMessage1[] = "testing testing testing"; + WriteMessage(p.handle0, kTestMessage1); + + size_t num_ready_handles = 2; + Handle ready_handles[2]; + MojoResult ready_results[2] = {MOJO_RESULT_UNKNOWN, MOJO_RESULT_UNKNOWN}; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle1.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1)); + + ThreadedRunner write_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then write a message. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + WriteMessage(*handle, "wakey wakey"); + }, + &p.handle1)); + write_after_delay.Start(); + + num_ready_handles = 2; + wait_set.Wait(nullptr, &num_ready_handles, ready_handles, ready_results); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(p.handle0.get(), ready_handles[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); +} + +TEST_F(WaitSetTest, EventOnly) { + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::SIGNALED); + WaitSet wait_set; + wait_set.AddEvent(&event); + + base::WaitableEvent* ready_event = nullptr; + size_t num_ready_handles = 1; + Handle ready_handle; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(0u, num_ready_handles); + EXPECT_EQ(&event, ready_event); +} + +TEST_F(WaitSetTest, EventAndHandle) { + const char kTestMessage[] = "hello hello"; + + MessagePipe p; + WriteMessage(p.handle0, kTestMessage); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WaitSet wait_set; + wait_set.AddHandle(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddEvent(&event); + + base::WaitableEvent* ready_event = nullptr; + size_t num_ready_handles = 1; + Handle ready_handle; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(nullptr, ready_event); + EXPECT_EQ(p.handle1.get(), ready_handle); + EXPECT_EQ(MOJO_RESULT_OK, ready_result); + + EXPECT_EQ(kTestMessage, ReadMessage(p.handle1)); + + ThreadedRunner signal_after_delay(base::Bind( + [](base::WaitableEvent* event) { + // Wait a little while, then close the handle. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + event->Signal(); + }, + &event)); + signal_after_delay.Start(); + + wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, &ready_result); + EXPECT_EQ(0u, num_ready_handles); + EXPECT_EQ(&event, ready_event); +} + +TEST_F(WaitSetTest, NoStarvation) { + const char kTestMessage[] = "wait for it"; + const size_t kNumTestPipes = 50; + const size_t kNumTestEvents = 10; + + // Create a bunch of handles and events which are always ready and add them + // to a shared WaitSet. + + WaitSet wait_set; + + MessagePipe pipes[kNumTestPipes]; + for (size_t i = 0; i < kNumTestPipes; ++i) { + WriteMessage(pipes[i].handle0, kTestMessage); + Wait(pipes[i].handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + + WriteMessage(pipes[i].handle1, kTestMessage); + Wait(pipes[i].handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + + wait_set.AddHandle(pipes[i].handle0.get(), MOJO_HANDLE_SIGNAL_READABLE); + wait_set.AddHandle(pipes[i].handle1.get(), MOJO_HANDLE_SIGNAL_READABLE); + } + + std::vector<std::unique_ptr<base::WaitableEvent>> events(kNumTestEvents); + for (auto& event_ptr : events) { + event_ptr = base::MakeUnique<base::WaitableEvent>( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + event_ptr->Signal(); + wait_set.AddEvent(event_ptr.get()); + } + + // Now verify that all handle and event signals are deteceted within a finite + // number of consecutive Wait() calls. Do it a few times for good measure. + for (size_t i = 0; i < 3; ++i) { + std::set<base::WaitableEvent*> ready_events; + std::set<Handle> ready_handles; + while (ready_events.size() < kNumTestEvents || + ready_handles.size() < kNumTestPipes * 2) { + base::WaitableEvent* ready_event = nullptr; + size_t num_ready_handles = 1; + Handle ready_handle; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + wait_set.Wait(&ready_event, &num_ready_handles, &ready_handle, + &ready_result); + if (ready_event) + ready_events.insert(ready_event); + + if (num_ready_handles) { + EXPECT_EQ(1u, num_ready_handles); + EXPECT_EQ(MOJO_RESULT_OK, ready_result); + ready_handles.insert(ready_handle); + } + } + } +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/tests/wait_unittest.cc b/mojo/public/cpp/system/tests/wait_unittest.cc new file mode 100644 index 0000000000..1d9d3c69bc --- /dev/null +++ b/mojo/public/cpp/system/tests/wait_unittest.cc @@ -0,0 +1,321 @@ +// 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. + +#include "mojo/public/cpp/system/wait.h" + +#include <string> +#include <vector> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/strings/string_piece.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "base/time/time.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle_signals_state.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace { + +using WaitTest = testing::Test; +using WaitManyTest = testing::Test; + +void WriteMessage(const ScopedMessagePipeHandle& handle, + const base::StringPiece& message) { + MojoResult rv = WriteMessageRaw(handle.get(), message.data(), + static_cast<uint32_t>(message.size()), + nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_OK, rv); +} + +std::string ReadMessage(const ScopedMessagePipeHandle& handle) { + uint32_t num_bytes = 0; + uint32_t num_handles = 0; + MojoResult rv = ReadMessageRaw(handle.get(), nullptr, &num_bytes, nullptr, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, rv); + CHECK_EQ(0u, num_handles); + + std::vector<char> buffer(num_bytes); + rv = ReadMessageRaw(handle.get(), buffer.data(), &num_bytes, nullptr, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE); + CHECK_EQ(MOJO_RESULT_OK, rv); + return std::string(buffer.data(), buffer.size()); +} + +class ThreadedRunner : public base::SimpleThread { + public: + explicit ThreadedRunner(const base::Closure& callback) + : SimpleThread("ThreadedRunner"), callback_(callback) {} + ~ThreadedRunner() override { Join(); } + + void Run() override { callback_.Run(); } + + private: + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadedRunner); +}; + +TEST_F(WaitTest, InvalidArguments) { + Handle invalid_handle; + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + Wait(invalid_handle, MOJO_HANDLE_SIGNAL_READABLE)); + + MessagePipe p; + Handle valid_handles[2] = {p.handle0.get(), p.handle1.get()}; + Handle invalid_handles[2]; + MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_NONE, + MOJO_HANDLE_SIGNAL_NONE}; + size_t result_index = 0; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WaitMany(invalid_handles, signals, 2, &result_index)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WaitMany(nullptr, signals, 2, &result_index)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WaitMany(valid_handles, nullptr, 2, &result_index)); +} + +TEST_F(WaitTest, Basic) { + MessagePipe p; + + // Write to one end of the pipe and wait on the other. + const char kTestMessage1[] = "how about a nice game of chess?"; + WriteMessage(p.handle0, kTestMessage1); + EXPECT_EQ(MOJO_RESULT_OK, Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE)); + + // And make sure we can also grab the handle signals state (with both the C + // and C++ library structs.) + + MojoHandleSignalsState c_hss = {0, 0}; + EXPECT_EQ(MOJO_RESULT_OK, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &c_hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + c_hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + c_hss.satisfiable_signals); + + HandleSignalsState hss; + EXPECT_EQ(MOJO_RESULT_OK, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(hss.readable() && hss.writable() && !hss.peer_closed()); + EXPECT_FALSE(hss.never_readable() || hss.never_writable() || + hss.never_peer_closed()); + + // Now close the writing end and wait for peer closure. + + p.handle0.reset(); + EXPECT_EQ(MOJO_RESULT_OK, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + + // Still readable as there's still a message queued. No longer writable as + // peer closure has been detected. + EXPECT_TRUE(hss.readable() && hss.peer_closed() && !hss.writable()); + EXPECT_TRUE(hss.never_writable() && !hss.never_readable() && + !hss.never_peer_closed()); + + // Read the message and wait for readable again. Waiting should fail since + // there are no more messages and the peer is closed. + EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1)); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + + // Sanity check the signals state again. + EXPECT_TRUE(hss.peer_closed() && !hss.readable() && !hss.writable()); + EXPECT_TRUE(hss.never_readable() && hss.never_writable() && + !hss.never_peer_closed()); +} + +TEST_F(WaitTest, DelayedWrite) { + MessagePipe p; + + ThreadedRunner write_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then write a message. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + WriteMessage(*handle, "wakey wakey"); + }, + &p.handle0)); + write_after_delay.Start(); + + HandleSignalsState hss; + EXPECT_EQ(MOJO_RESULT_OK, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(hss.readable() && hss.writable() && !hss.peer_closed()); + EXPECT_TRUE(!hss.never_readable() && !hss.never_writable() && + !hss.never_peer_closed()); +} + +TEST_F(WaitTest, DelayedPeerClosure) { + MessagePipe p; + + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then close the handle. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle0)); + close_after_delay.Start(); + + HandleSignalsState hss; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + Wait(p.handle1.get(), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(!hss.readable() && !hss.writable() && hss.peer_closed()); + EXPECT_TRUE(hss.never_readable() && hss.never_writable() && + !hss.never_peer_closed()); +} + +TEST_F(WaitTest, CloseWhileWaiting) { + MessagePipe p; + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle0)); + close_after_delay.Start(); + EXPECT_EQ(MOJO_RESULT_CANCELLED, + Wait(p.handle0.get(), MOJO_HANDLE_SIGNAL_READABLE)); +} + +TEST_F(WaitManyTest, Basic) { + MessagePipe p; + + const char kTestMessage1[] = "hello"; + WriteMessage(p.handle0, kTestMessage1); + + // Wait for either handle to become readable. Wait twice, just to verify that + // we can use either the C or C++ signaling state structure for the last + // argument. + + Handle handles[2] = {p.handle0.get(), p.handle1.get()}; + MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + size_t result_index = 0; + MojoHandleSignalsState c_hss[2]; + HandleSignalsState hss[2]; + + EXPECT_EQ(MOJO_RESULT_OK, + WaitMany(handles, signals, 2, &result_index, c_hss)); + EXPECT_EQ(1u, result_index); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, c_hss[0].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + c_hss[0].satisfiable_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + c_hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + c_hss[1].satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss)); + EXPECT_EQ(1u, result_index); + EXPECT_TRUE(!hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + EXPECT_TRUE(!hss[0].never_readable() && !hss[0].never_writable() && + !hss[0].never_peer_closed()); + EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed()); + EXPECT_TRUE(!hss[1].never_readable() && !hss[1].never_writable() && + !hss[1].never_peer_closed()); + + // Close the writer and read the message. Try to wait again, and it should + // fail due to the conditions being unsatisfiable. + + EXPECT_EQ(kTestMessage1, ReadMessage(p.handle1)); + p.handle0.reset(); + + // handles[0] is invalid. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + WaitMany(handles, signals, 2, &result_index, hss)); + handles[0] = handles[1]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitMany(handles, signals, 1, &result_index, hss)); + EXPECT_EQ(0u, result_index); + EXPECT_TRUE(!hss[0].readable() && !hss[0].writable() && hss[0].peer_closed()); + EXPECT_TRUE(hss[0].never_readable() && hss[0].never_writable() && + !hss[0].never_peer_closed()); +} + +TEST_F(WaitManyTest, CloseWhileWaiting) { + MessagePipe p, q; + + Handle handles[3] = {q.handle0.get(), q.handle1.get(), p.handle1.get()}; + MojoHandleSignals signals[3] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle1)); + close_after_delay.Start(); + + size_t result_index = 0; + EXPECT_EQ(MOJO_RESULT_CANCELLED, + WaitMany(handles, signals, 3, &result_index)); + EXPECT_EQ(2u, result_index); +} + +TEST_F(WaitManyTest, DelayedWrite) { + MessagePipe p; + + ThreadedRunner write_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then write a message. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + WriteMessage(*handle, "wakey wakey"); + }, + &p.handle0)); + write_after_delay.Start(); + + Handle handles[2] = {p.handle0.get(), p.handle1.get()}; + MojoHandleSignals signals[2] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + size_t result_index = 0; + HandleSignalsState hss[2]; + EXPECT_EQ(MOJO_RESULT_OK, WaitMany(handles, signals, 2, &result_index, hss)); + EXPECT_EQ(1u, result_index); + EXPECT_TRUE(!hss[0].readable() && hss[0].writable() && !hss[0].peer_closed()); + EXPECT_TRUE(!hss[0].never_readable() && !hss[0].never_writable() && + !hss[0].never_peer_closed()); + EXPECT_TRUE(hss[1].readable() && hss[1].writable() && !hss[1].peer_closed()); + EXPECT_TRUE(!hss[1].never_readable() && !hss[1].never_writable() && + !hss[1].never_peer_closed()); +} + +TEST_F(WaitManyTest, DelayedPeerClosure) { + MessagePipe p, q; + + ThreadedRunner close_after_delay(base::Bind( + [](ScopedMessagePipeHandle* handle) { + // Wait a little while, then close the handle. + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); + handle->reset(); + }, + &p.handle0)); + close_after_delay.Start(); + + Handle handles[3] = {q.handle0.get(), q.handle1.get(), p.handle1.get()}; + MojoHandleSignals signals[3] = {MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE, + MOJO_HANDLE_SIGNAL_READABLE}; + size_t result_index = 0; + HandleSignalsState hss[3]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitMany(handles, signals, 3, &result_index, hss)); + EXPECT_EQ(2u, result_index); + EXPECT_TRUE(!hss[2].readable() && !hss[2].writable() && hss[2].peer_closed()); + EXPECT_TRUE(hss[2].never_readable() && hss[2].never_writable() && + !hss[2].never_peer_closed()); +} + +} // namespace +} // namespace mojo diff --git a/mojo/public/cpp/system/wait.cc b/mojo/public/cpp/system/wait.cc new file mode 100644 index 0000000000..e4e124f25c --- /dev/null +++ b/mojo/public/cpp/system/wait.cc @@ -0,0 +1,200 @@ +// 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. + +#include "mojo/public/cpp/system/wait.h" + +#include <memory> +#include <vector> + +#include "base/memory/ptr_util.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/waitable_event.h" +#include "mojo/public/c/system/watcher.h" +#include "mojo/public/cpp/system/watcher.h" + +namespace mojo { +namespace { + +class WatchContext : public base::RefCountedThreadSafe<WatchContext> { + public: + WatchContext() + : event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED) {} + + base::WaitableEvent& event() { return event_; } + MojoResult wait_result() const { return wait_result_; } + MojoHandleSignalsState wait_state() const { return wait_state_; } + uintptr_t context_value() const { return reinterpret_cast<uintptr_t>(this); } + + static void OnNotification(uintptr_t context_value, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + auto* context = reinterpret_cast<WatchContext*>(context_value); + context->Notify(result, state); + if (result == MOJO_RESULT_CANCELLED) { + // Balanced in Wait() or WaitMany(). + context->Release(); + } + } + + private: + friend class base::RefCountedThreadSafe<WatchContext>; + + ~WatchContext() {} + + void Notify(MojoResult result, MojoHandleSignalsState state) { + if (wait_result_ == MOJO_RESULT_UNKNOWN) { + wait_result_ = result; + wait_state_ = state; + } + event_.Signal(); + } + + base::WaitableEvent event_; + + // NOTE: Although these are modified in Notify() which may be called from any + // thread, Notify() is guaranteed to never run concurrently with itself. + // Furthermore, they are only modified once, before |event_| signals; so there + // is no need for a WatchContext user to synchronize access to these fields + // apart from waiting on |event()|. + MojoResult wait_result_ = MOJO_RESULT_UNKNOWN; + MojoHandleSignalsState wait_state_ = {0, 0}; + + DISALLOW_COPY_AND_ASSIGN(WatchContext); +}; + +} // namespace + +MojoResult Wait(Handle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* signals_state) { + ScopedWatcherHandle watcher; + MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher); + DCHECK_EQ(MOJO_RESULT_OK, rv); + + scoped_refptr<WatchContext> context = new WatchContext; + + // Balanced in WatchContext::OnNotification if MojoWatch() is successful. + // Otherwise balanced immediately below. + context->AddRef(); + + rv = MojoWatch(watcher.get().value(), handle.value(), signals, + context->context_value()); + if (rv == MOJO_RESULT_INVALID_ARGUMENT) { + // Balanced above. + context->Release(); + return rv; + } + DCHECK_EQ(MOJO_RESULT_OK, rv); + + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult ready_result; + MojoHandleSignalsState ready_state; + rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts, + &ready_context, &ready_result, &ready_state); + if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + DCHECK_EQ(1u, num_ready_contexts); + if (signals_state) + *signals_state = ready_state; + return ready_result; + } + + // Wait for the first notification only. + context->event().Wait(); + + ready_result = context->wait_result(); + DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result); + + if (signals_state) + *signals_state = context->wait_state(); + + return ready_result; +} + +MojoResult WaitMany(const Handle* handles, + const MojoHandleSignals* signals, + size_t num_handles, + size_t* result_index, + MojoHandleSignalsState* signals_states) { + if (!handles || !signals) + return MOJO_RESULT_INVALID_ARGUMENT; + + ScopedWatcherHandle watcher; + MojoResult rv = CreateWatcher(&WatchContext::OnNotification, &watcher); + DCHECK_EQ(MOJO_RESULT_OK, rv); + + std::vector<scoped_refptr<WatchContext>> contexts(num_handles); + std::vector<base::WaitableEvent*> events(num_handles); + for (size_t i = 0; i < num_handles; ++i) { + contexts[i] = new WatchContext(); + + // Balanced in WatchContext::OnNotification if MojoWatch() is successful. + // Otherwise balanced immediately below. + contexts[i]->AddRef(); + + MojoResult rv = MojoWatch(watcher.get().value(), handles[i].value(), + signals[i], contexts[i]->context_value()); + if (rv == MOJO_RESULT_INVALID_ARGUMENT) { + if (result_index) + *result_index = i; + + // Balanced above. + contexts[i]->Release(); + + return MOJO_RESULT_INVALID_ARGUMENT; + } + + events[i] = &contexts[i]->event(); + } + + uint32_t num_ready_contexts = 1; + uintptr_t ready_context = 0; + MojoResult ready_result = MOJO_RESULT_UNKNOWN; + MojoHandleSignalsState ready_state{0, 0}; + rv = MojoArmWatcher(watcher.get().value(), &num_ready_contexts, + &ready_context, &ready_result, &ready_state); + + size_t index = num_handles; + if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + DCHECK_EQ(1u, num_ready_contexts); + + // Most commonly we only watch a small number of handles. Just scan for + // the right index. + for (size_t i = 0; i < num_handles; ++i) { + if (contexts[i]->context_value() == ready_context) { + index = i; + break; + } + } + } else { + DCHECK_EQ(MOJO_RESULT_OK, rv); + + // Wait for one of the contexts to signal. First one wins. + index = base::WaitableEvent::WaitMany(events.data(), events.size()); + ready_result = contexts[index]->wait_result(); + ready_state = contexts[index]->wait_state(); + } + + DCHECK_NE(MOJO_RESULT_UNKNOWN, ready_result); + DCHECK_LT(index, num_handles); + + if (result_index) + *result_index = index; + + if (signals_states) { + for (size_t i = 0; i < num_handles; ++i) { + if (i == index) { + signals_states[i] = ready_state; + } else { + signals_states[i] = handles[i].QuerySignalsState(); + } + } + } + + return ready_result; +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/wait.h b/mojo/public/cpp/system/wait.h new file mode 100644 index 0000000000..808e44fc25 --- /dev/null +++ b/mojo/public/cpp/system/wait.h @@ -0,0 +1,75 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_ + +#include <stddef.h> + +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { + +// Blocks the calling thread, waiting for one or more signals in |signals| to be +// become satisfied -- or for all of them to become unsatisfiable -- on the +// given Handle. +// +// If |signals_state| is non-null, |handle| is valid, the wait is not cancelled +// (see return values below), the last known signaling state of |handle| is +// written to |*signals_state| before returning. +// +// Return values: +// |MOJO_RESULT_OK| if one or more signals in |signals| has been raised on +// |handle| . +// |MOJO_RESULT_FAILED_PRECONDITION| if the state of |handle| changes such +// that no signals in |signals| can ever be raised again. +// |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle. +// |MOJO_RESULT_CANCELLED| if the wait was cancelled because |handle| was +// closed by some other thread while waiting. +MOJO_CPP_SYSTEM_EXPORT MojoResult +Wait(Handle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* signals_state = nullptr); + +// Waits on |handles[0]|, ..., |handles[num_handles-1]| until: +// - At least one handle satisfies a signal indicated in its respective +// |signals[0]|, ..., |signals[num_handles-1]|. +// - It becomes known that no signal in some |signals[i]| will ever be +// satisfied. +// +// This means that |WaitMany()| behaves as if |Wait()| were called on each +// handle/signals pair simultaneously, completing when the first |Wait()| would +// complete. +// +// If |signals_states| is non-null, all other arguments are valid, and the wait +// is not cancelled (see return values below), the last known signaling state of +// each Handle |handles[i]| is written to its corresponding entry in +// |signals_states[i]| before returning. +// +// Returns values: +// |MOJO_RESULT_OK| if one of the Handles in |handles| had one or more of its +// correpsonding signals satisfied. |*result_index| contains the index +// of the Handle in question if |result_index| is non-null. +// |MOJO_RESULT_FAILED_PRECONDITION| if one of the Handles in |handles| +// changes state such that its corresponding signals become permanently +// unsatisfiable. |*result_index| contains the index of the handle in +// question if |result_index| is non-null. +// |MOJO_RESULT_INVALID_ARGUMENT| if any Handle in |handles| is invalid, +// or if either |handles| or |signals| is null. +// |MOJO_RESULT_CANCELLED| if the wait was cancelled because a handle in +// |handles| was closed by some other thread while waiting. +// |*result_index| contains the index of the closed Handle if +// |result_index| is non-null. +MOJO_CPP_SYSTEM_EXPORT MojoResult +WaitMany(const Handle* handles, + const MojoHandleSignals* signals, + size_t num_handles, + size_t* result_index = nullptr, + MojoHandleSignalsState* signals_states = nullptr); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_WAIT_H_ diff --git a/mojo/public/cpp/system/wait_set.cc b/mojo/public/cpp/system/wait_set.cc new file mode 100644 index 0000000000..1728f81b95 --- /dev/null +++ b/mojo/public/cpp/system/wait_set.cc @@ -0,0 +1,371 @@ +// 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. + +#include "mojo/public/cpp/system/wait_set.h" + +#include <algorithm> +#include <limits> +#include <map> +#include <set> +#include <vector> + +#include "base/containers/stack_container.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "mojo/public/cpp/system/watcher.h" + +namespace mojo { + +class WaitSet::State : public base::RefCountedThreadSafe<State> { + public: + State() + : handle_event_(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED) { + MojoResult rv = CreateWatcher(&Context::OnNotification, &watcher_handle_); + DCHECK_EQ(MOJO_RESULT_OK, rv); + } + + void ShutDown() { + // NOTE: This may immediately invoke Notify for every context. + watcher_handle_.reset(); + } + + MojoResult AddEvent(base::WaitableEvent* event) { + auto result = user_events_.insert(event); + if (result.second) + return MOJO_RESULT_OK; + return MOJO_RESULT_ALREADY_EXISTS; + } + + MojoResult RemoveEvent(base::WaitableEvent* event) { + auto it = user_events_.find(event); + if (it == user_events_.end()) + return MOJO_RESULT_NOT_FOUND; + user_events_.erase(it); + return MOJO_RESULT_OK; + } + + MojoResult AddHandle(Handle handle, MojoHandleSignals signals) { + DCHECK(watcher_handle_.is_valid()); + + scoped_refptr<Context> context = new Context(this, handle); + + { + base::AutoLock lock(lock_); + + if (handle_to_context_.count(handle)) + return MOJO_RESULT_ALREADY_EXISTS; + DCHECK(!contexts_.count(context->context_value())); + + handle_to_context_[handle] = context; + contexts_[context->context_value()] = context; + } + + // Balanced in State::Notify() with MOJO_RESULT_CANCELLED if + // MojoWatch() succeeds. Otherwise balanced immediately below. + context->AddRef(); + + // This can notify immediately if the watcher is already armed. Don't hold + // |lock_| while calling it. + MojoResult rv = MojoWatch(watcher_handle_.get().value(), handle.value(), + signals, context->context_value()); + if (rv == MOJO_RESULT_INVALID_ARGUMENT) { + base::AutoLock lock(lock_); + handle_to_context_.erase(handle); + contexts_.erase(context->context_value()); + + // Balanced above. + context->Release(); + return rv; + } + DCHECK_EQ(MOJO_RESULT_OK, rv); + + return rv; + } + + MojoResult RemoveHandle(Handle handle) { + DCHECK(watcher_handle_.is_valid()); + + scoped_refptr<Context> context; + { + base::AutoLock lock(lock_); + auto it = handle_to_context_.find(handle); + if (it == handle_to_context_.end()) + return MOJO_RESULT_NOT_FOUND; + + context = std::move(it->second); + handle_to_context_.erase(it); + + // Ensure that we never return this handle as a ready result again. Note + // that it's removal from |handle_to_context_| above ensures it will never + // be added back to this map. + ready_handles_.erase(handle); + } + + // NOTE: This may enter the notification callback immediately, so don't hold + // |lock_| while calling it. + MojoResult rv = MojoCancelWatch(watcher_handle_.get().value(), + context->context_value()); + + // We don't really care whether or not this succeeds. In either case, the + // context was or will imminently be cancelled and moved from |contexts_| + // to |cancelled_contexts_|. + DCHECK(rv == MOJO_RESULT_OK || rv == MOJO_RESULT_NOT_FOUND); + + { + // Always clear |cancelled_contexts_| in case it's accumulated any more + // entries since the last time we ran. + base::AutoLock lock(lock_); + cancelled_contexts_.clear(); + } + + return rv; + } + + void Wait(base::WaitableEvent** ready_event, + size_t* num_ready_handles, + Handle* ready_handles, + MojoResult* ready_results, + MojoHandleSignalsState* signals_states) { + DCHECK(watcher_handle_.is_valid()); + DCHECK(num_ready_handles); + DCHECK(ready_handles); + DCHECK(ready_results); + { + base::AutoLock lock(lock_); + if (ready_handles_.empty()) { + // No handles are currently in the ready set. Make sure the event is + // reset and try to arm the watcher. + handle_event_.Reset(); + + DCHECK_LE(*num_ready_handles, std::numeric_limits<uint32_t>::max()); + uint32_t num_ready_contexts = static_cast<uint32_t>(*num_ready_handles); + + base::StackVector<uintptr_t, 4> ready_contexts; + ready_contexts.container().resize(num_ready_contexts); + base::StackVector<MojoHandleSignalsState, 4> ready_states; + MojoHandleSignalsState* out_states = signals_states; + if (!out_states) { + // If the caller didn't provide a buffer for signal states, we provide + // our own locally. MojoArmWatcher() requires one if we want to handle + // arming failure properly. + ready_states.container().resize(num_ready_contexts); + out_states = ready_states.container().data(); + } + MojoResult rv = MojoArmWatcher( + watcher_handle_.get().value(), &num_ready_contexts, + ready_contexts.container().data(), ready_results, out_states); + + if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + // Simulate the handles becoming ready. We do this in lieu of + // returning the results immediately so as to avoid potentially + // starving user events. i.e., we always want to call WaitMany() + // below. + handle_event_.Signal(); + for (size_t i = 0; i < num_ready_contexts; ++i) { + auto it = contexts_.find(ready_contexts.container()[i]); + DCHECK(it != contexts_.end()); + ready_handles_[it->second->handle()] = {ready_results[i], + out_states[i]}; + } + } else if (rv == MOJO_RESULT_NOT_FOUND) { + // Nothing to watch. If there are no user events, always signal to + // avoid deadlock. + if (user_events_.empty()) + handle_event_.Signal(); + } else { + // Watcher must be armed now. No need to manually signal. + DCHECK_EQ(MOJO_RESULT_OK, rv); + } + } + } + + // Build a local contiguous array of events to wait on. These are rotated + // across Wait() calls to avoid starvation, by virtue of the fact that + // WaitMany guarantees left-to-right priority when multiple events are + // signaled. + + base::StackVector<base::WaitableEvent*, 4> events; + events.container().resize(user_events_.size() + 1); + if (waitable_index_shift_ > user_events_.size()) + waitable_index_shift_ = 0; + + size_t dest_index = waitable_index_shift_++; + events.container()[dest_index] = &handle_event_; + for (auto* e : user_events_) { + dest_index = (dest_index + 1) % events.container().size(); + events.container()[dest_index] = e; + } + + size_t index = base::WaitableEvent::WaitMany(events.container().data(), + events.container().size()); + base::AutoLock lock(lock_); + + // Pop as many handles as we can out of the ready set and return them. Note + // that we do this regardless of which event signaled, as there may be + // ready handles in any case and they may be interesting to the caller. + *num_ready_handles = std::min(*num_ready_handles, ready_handles_.size()); + for (size_t i = 0; i < *num_ready_handles; ++i) { + auto it = ready_handles_.begin(); + ready_handles[i] = it->first; + ready_results[i] = it->second.result; + if (signals_states) + signals_states[i] = it->second.signals_state; + ready_handles_.erase(it); + } + + // If the caller cares, let them know which user event unblocked us, if any. + if (ready_event) { + if (events.container()[index] == &handle_event_) + *ready_event = nullptr; + else + *ready_event = events.container()[index]; + } + } + + private: + friend class base::RefCountedThreadSafe<State>; + + class Context : public base::RefCountedThreadSafe<Context> { + public: + Context(scoped_refptr<State> state, Handle handle) + : state_(state), handle_(handle) {} + + Handle handle() const { return handle_; } + + uintptr_t context_value() const { + return reinterpret_cast<uintptr_t>(this); + } + + static void OnNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState signals_state, + MojoWatcherNotificationFlags flags) { + reinterpret_cast<Context*>(context)->Notify(result, signals_state); + } + + private: + friend class base::RefCountedThreadSafe<Context>; + + ~Context() {} + + void Notify(MojoResult result, MojoHandleSignalsState signals_state) { + state_->Notify(handle_, result, signals_state, this); + } + + const scoped_refptr<State> state_; + const Handle handle_; + + DISALLOW_COPY_AND_ASSIGN(Context); + }; + + ~State() {} + + void Notify(Handle handle, + MojoResult result, + MojoHandleSignalsState signals_state, + Context* context) { + base::AutoLock lock(lock_); + + // This could be a cancellation notification following an explicit + // RemoveHandle(), in which case we really don't care and don't want to + // add it to the ready set. Only update and signal if that's not the case. + if (!handle_to_context_.count(handle)) { + DCHECK_EQ(MOJO_RESULT_CANCELLED, result); + } else { + ready_handles_[handle] = {result, signals_state}; + handle_event_.Signal(); + } + + // Whether it's an implicit or explicit cancellation, erase from |contexts_| + // and append to |cancelled_contexts_|. + if (result == MOJO_RESULT_CANCELLED) { + contexts_.erase(context->context_value()); + handle_to_context_.erase(handle); + + // NOTE: We retain a context ref in |cancelled_contexts_| to ensure that + // this Context's heap address is not reused too soon. For example, it + // would otherwise be possible for the user to call AddHandle() from the + // WaitSet's thread immediately after this notification has fired on + // another thread, potentially reusing the same heap address for the newly + // added Context; and then they may call RemoveHandle() for this handle + // (not knowing its context has just been implicitly cancelled) and + // cause the new Context to be incorrectly removed from |contexts_|. + // + // This vector is cleared on the WaitSet's own thread every time + // RemoveHandle is called. + cancelled_contexts_.emplace_back(make_scoped_refptr(context)); + + // Balanced in State::AddHandle(). + context->Release(); + } + } + + struct ReadyState { + ReadyState() = default; + ReadyState(MojoResult result, MojoHandleSignalsState signals_state) + : result(result), signals_state(signals_state) {} + ~ReadyState() = default; + + MojoResult result = MOJO_RESULT_UNKNOWN; + MojoHandleSignalsState signals_state = {0, 0}; + }; + + // Not guarded by lock. Must only be accessed from the WaitSet's owning + // thread. + ScopedWatcherHandle watcher_handle_; + + base::Lock lock_; + std::map<uintptr_t, scoped_refptr<Context>> contexts_; + std::map<Handle, scoped_refptr<Context>> handle_to_context_; + std::map<Handle, ReadyState> ready_handles_; + std::vector<scoped_refptr<Context>> cancelled_contexts_; + std::set<base::WaitableEvent*> user_events_; + + // Event signaled any time a handle notification is received. + base::WaitableEvent handle_event_; + + // Offset by which to rotate the current set of waitable objects. This is used + // to guard against event starvation, as base::WaitableEvent::WaitMany gives + // preference to events in left-to-right order. + size_t waitable_index_shift_ = 0; + + DISALLOW_COPY_AND_ASSIGN(State); +}; + +WaitSet::WaitSet() : state_(new State) {} + +WaitSet::~WaitSet() { + state_->ShutDown(); +} + +MojoResult WaitSet::AddEvent(base::WaitableEvent* event) { + return state_->AddEvent(event); +} + +MojoResult WaitSet::RemoveEvent(base::WaitableEvent* event) { + return state_->RemoveEvent(event); +} + +MojoResult WaitSet::AddHandle(Handle handle, MojoHandleSignals signals) { + return state_->AddHandle(handle, signals); +} + +MojoResult WaitSet::RemoveHandle(Handle handle) { + return state_->RemoveHandle(handle); +} + +void WaitSet::Wait(base::WaitableEvent** ready_event, + size_t* num_ready_handles, + Handle* ready_handles, + MojoResult* ready_results, + MojoHandleSignalsState* signals_states) { + state_->Wait(ready_event, num_ready_handles, ready_handles, ready_results, + signals_states); +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/wait_set.h b/mojo/public/cpp/system/wait_set.h new file mode 100644 index 0000000000..5047a86a48 --- /dev/null +++ b/mojo/public/cpp/system/wait_set.h @@ -0,0 +1,124 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_ + +#include <stddef.h> +#include <stdint.h> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace base { +class WaitableEvent; +} + +namespace mojo { + +// WaitSet provides an efficient means of blocking a thread on any number of +// events and Mojo handle state changes. +// +// Unlike WaitMany(), which incurs some extra setup cost for every call, a +// WaitSet maintains some persistent accounting of the handles added or removed +// from the set. A blocking wait operation (see the Wait() method below) can +// then be performed multiple times for the same set of events and handles with +// minimal additional setup per call. +// +// WaitSet is NOT thread-safe, so naturally handles and events may not be added +// to or removed from the set while waiting. +class MOJO_CPP_SYSTEM_EXPORT WaitSet { + public: + WaitSet(); + ~WaitSet(); + + // Adds |event| to the set of events to wait on. If successful, any future + // Wait() on this WaitSet will wake up if the event is signaled. + // + // |event| is not owned. + // + // Return values: + // |MOJO_RESULT_OK| if |event| has been successfully added. + // |MOJO_RESULT_ALREADY_EXISTS| if |event| is already in this WaitSet. + MojoResult AddEvent(base::WaitableEvent* event); + + // Removes |event| from the set of events to wait on. + // + // Return values: + // |MOJO_RESULT_OK| if |event| has been successfully added. + // |MOJO_RESULT_NOT_FOUND| if |event| was not in the set. + MojoResult RemoveEvent(base::WaitableEvent* event); + + // Adds |handle| to the set of handles to wait on. If successful, any future + // Wait() on this WaitSet will wake up in the event that one or more signals + // in |signals| becomes satisfied on |handle| or all of them become + // permanently unsatisfiable. + // + // Return values: + // |MOJO_RESULT_OK| if |handle| has been successfully added. + // |MOJO_RESULT_ALREADY_EXISTS| if |handle| is already in this WaitSet. + // |MOJO_RESULT_INVALID_ARGUMENT| if |handle| is not a valid handle. + MojoResult AddHandle(Handle handle, MojoHandleSignals signals); + + // Removes |handle| from the set of handles to wait on. Future calls to + // Wait() will be unaffected by the state of this handle. + // + // Return values: + // |MOJO_RESULT_OK| if |handle| has been successfully removed. + // |MOJO_RESULT_NOT_FOUND| if |handle| was not in the set. + MojoResult RemoveHandle(Handle handle); + + // Waits on the current set of handles, waking up when one more of them meets + // the signaling conditions which were specified when they were added via + // AddHandle() above. + // + // |*num_ready_handles| on input must specify the number of entries available + // for output storage in |ready_handles| and |ready_result| (which must both + // be non-null). If |signals_states| is non-null it must also point to enough + // storage for |*num_ready_handles| MojoHandleSignalsState structures. + // + // Upon return, |*num_ready_handles| will contain the total number of handles + // whose information is stored in the given output buffers. + // + // If |ready_event| is non-null and the Wait() was unblocked by a user event + // signaling, the address of the event which signaled will be placed in + // |*ready_event|. Note that this is not necessarily exclusive to one or more + // handles also being ready. If |ready_event| is non-null and no user event + // was signaled for this Wait(), |*ready_event| will be null upon return. + // + // Every entry in |ready_handles| on output corresponds to one of the handles + // whose signaling state termianted the Wait() operation. Every corresponding + // entry in |ready_results| indicates the status of a ready handle according + // to the following result codes: + // |MOJO_RESULT_OK| one or more signals for the handle has been satisfied. + // |MOJO_RESULT_FAILED_PRECONDITION| all of the signals for the handle have + // become permanently unsatisfiable. + // |MOJO_RESULT_CANCELLED| if the handle has been closed from another + // thread. NOTE: It is important to recognize that this means the + // corresponding value in |ready_handles| is either invalid, or valid + // but referring to a different handle (i.e. has already been reused) by + // the time Wait() returns. The handle in question is automatically + // removed from the WaitSet. + void Wait(base::WaitableEvent** ready_event, + size_t* num_ready_handles, + Handle* ready_handles, + MojoResult* ready_results, + MojoHandleSignalsState* signals_states = nullptr); + + private: + class State; + + // Thread-safe state associated with this WaitSet. Used to aggregate + // notifications from watched handles. + scoped_refptr<State> state_; + + DISALLOW_COPY_AND_ASSIGN(WaitSet); +}; + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_WAIT_SET_H_ diff --git a/mojo/public/cpp/system/watcher.cc b/mojo/public/cpp/system/watcher.cc new file mode 100644 index 0000000000..0c62ba8e20 --- /dev/null +++ b/mojo/public/cpp/system/watcher.cc @@ -0,0 +1,20 @@ +// 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. + +#include "mojo/public/cpp/system/watcher.h" + +#include "mojo/public/c/system/functions.h" + +namespace mojo { + +MojoResult CreateWatcher(MojoWatcherCallback callback, + ScopedWatcherHandle* watcher_handle) { + MojoHandle handle; + MojoResult rv = MojoCreateWatcher(callback, &handle); + if (rv == MOJO_RESULT_OK) + watcher_handle->reset(WatcherHandle(handle)); + return rv; +} + +} // namespace mojo diff --git a/mojo/public/cpp/system/watcher.h b/mojo/public/cpp/system/watcher.h new file mode 100644 index 0000000000..d0a257814d --- /dev/null +++ b/mojo/public/cpp/system/watcher.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_ +#define MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_ + +#include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/system_export.h" + +namespace mojo { + +// A strongly-typed representation of a |MojoHandle| for a watcher. +class WatcherHandle : public Handle { + public: + WatcherHandle() = default; + explicit WatcherHandle(MojoHandle value) : Handle(value) {} + + // Copying and assignment allowed. +}; + +static_assert(sizeof(WatcherHandle) == sizeof(Handle), + "Bad size for C++ WatcherHandle"); + +typedef ScopedHandleBase<WatcherHandle> ScopedWatcherHandle; +static_assert(sizeof(ScopedWatcherHandle) == sizeof(WatcherHandle), + "Bad size for C++ ScopedWatcherHandle"); + +MOJO_CPP_SYSTEM_EXPORT MojoResult +CreateWatcher(MojoWatcherCallback callback, + ScopedWatcherHandle* watcher_handle); + +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_SYSTEM_WATCHER_H_ diff --git a/mojo/public/cpp/test_support/BUILD.gn b/mojo/public/cpp/test_support/BUILD.gn new file mode 100644 index 0000000000..efa1712fff --- /dev/null +++ b/mojo/public/cpp/test_support/BUILD.gn @@ -0,0 +1,19 @@ +# 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. + +static_library("test_utils") { + testonly = true + + sources = [ + "lib/test_support.cc", + "lib/test_utils.cc", + "test_utils.h", + ] + + deps = [ + "//mojo/public/c/test_support", + "//mojo/public/cpp/system", + "//testing/gtest", + ] +} diff --git a/mojo/public/cpp/test_support/lib/test_support.cc b/mojo/public/cpp/test_support/lib/test_support.cc new file mode 100644 index 0000000000..0b6035b9c2 --- /dev/null +++ b/mojo/public/cpp/test_support/lib/test_support.cc @@ -0,0 +1,26 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/test_support/test_support.h" + +#include <stdlib.h> + +namespace mojo { +namespace test { + +std::vector<std::string> EnumerateSourceRootRelativeDirectory( + const std::string& relative_path) { + char** names = MojoTestSupportEnumerateSourceRootRelativeDirectory( + relative_path.c_str()); + std::vector<std::string> results; + for (char** ptr = names; *ptr != nullptr; ++ptr) { + results.push_back(*ptr); + free(*ptr); + } + free(names); + return results; +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/test_support/lib/test_utils.cc b/mojo/public/cpp/test_support/lib/test_utils.cc new file mode 100644 index 0000000000..7fe6f02788 --- /dev/null +++ b/mojo/public/cpp/test_support/lib/test_utils.cc @@ -0,0 +1,100 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/cpp/test_support/test_utils.h" + +#include <stddef.h> +#include <stdint.h> + +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/wait.h" +#include "mojo/public/cpp/test_support/test_support.h" + +namespace mojo { +namespace test { + +bool WriteTextMessage(const MessagePipeHandle& handle, + const std::string& text) { + MojoResult rv = WriteMessageRaw(handle, + text.data(), + static_cast<uint32_t>(text.size()), + nullptr, + 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + return rv == MOJO_RESULT_OK; +} + +bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text) { + MojoResult rv; + bool did_wait = false; + + uint32_t num_bytes = 0, num_handles = 0; + for (;;) { + rv = ReadMessageRaw(handle, + nullptr, + &num_bytes, + nullptr, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + if (rv == MOJO_RESULT_SHOULD_WAIT) { + if (did_wait) { + assert(false); // Looping endlessly!? + return false; + } + rv = Wait(handle, MOJO_HANDLE_SIGNAL_READABLE); + if (rv != MOJO_RESULT_OK) + return false; + did_wait = true; + } else { + assert(!num_handles); + break; + } + } + + text->resize(num_bytes); + rv = ReadMessageRaw(handle, + &text->at(0), + &num_bytes, + nullptr, + &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE); + return rv == MOJO_RESULT_OK; +} + +bool DiscardMessage(const MessagePipeHandle& handle) { + MojoResult rv = ReadMessageRaw(handle, + nullptr, + nullptr, + nullptr, + nullptr, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD); + return rv == MOJO_RESULT_OK; +} + +void IterateAndReportPerf(const char* test_name, + const char* sub_test_name, + PerfTestSingleIteration single_iteration, + void* closure) { + // TODO(vtl): These should be specifiable using command-line flags. + static const size_t kGranularity = 100; + static const MojoTimeTicks kPerftestTimeMicroseconds = 3 * 1000000; + + const MojoTimeTicks start_time = GetTimeTicksNow(); + MojoTimeTicks end_time; + size_t iterations = 0; + do { + for (size_t i = 0; i < kGranularity; i++) + (*single_iteration)(closure); + iterations += kGranularity; + + end_time = GetTimeTicksNow(); + } while (end_time - start_time < kPerftestTimeMicroseconds); + + MojoTestSupportLogPerfResult(test_name, sub_test_name, + 1000000.0 * iterations / (end_time - start_time), + "iterations/second"); +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/cpp/test_support/test_support.h b/mojo/public/cpp/test_support/test_support.h new file mode 100644 index 0000000000..9a536e649f --- /dev/null +++ b/mojo/public/cpp/test_support/test_support.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ +#define MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ + +#include <string> +#include <vector> + +#include "mojo/public/c/test_support/test_support.h" + +namespace mojo { +namespace test { + +inline void LogPerfResult(const char* test_name, + const char* sub_test_name, + double value, + const char* units) { + MojoTestSupportLogPerfResult(test_name, sub_test_name, value, units); +} + +// Opens text file relative to the source root for reading. +inline FILE* OpenSourceRootRelativeFile(const std::string& relative_path) { + return MojoTestSupportOpenSourceRootRelativeFile(relative_path.c_str()); +} + +// Returns the list of regular files in a directory relative to the source root. +std::vector<std::string> EnumerateSourceRootRelativeDirectory( + const std::string& relative_path); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_SUPPORT_H_ diff --git a/mojo/public/cpp/test_support/test_utils.h b/mojo/public/cpp/test_support/test_utils.h new file mode 100644 index 0000000000..6fd5a9ea25 --- /dev/null +++ b/mojo/public/cpp/test_support/test_utils.h @@ -0,0 +1,40 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ +#define MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ + +#include <string> + +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace test { + +// Writes a message to |handle| with message data |text|. Returns true on +// success. +bool WriteTextMessage(const MessagePipeHandle& handle, const std::string& text); + +// Reads a message from |handle|, putting its contents into |*text|. Returns +// true on success. (This blocks if necessary and will call |MojoReadMessage()| +// multiple times, e.g., to query the size of the message.) +bool ReadTextMessage(const MessagePipeHandle& handle, std::string* text); + +// Discards a message from |handle|. Returns true on success. (This does not +// block. It will fail if no message is available to discard.) +bool DiscardMessage(const MessagePipeHandle& handle); + +// Run |single_iteration| an appropriate number of times and report its +// performance appropriately. (This actually runs |single_iteration| for a fixed +// amount of time and reports the number of iterations per unit time.) +typedef void (*PerfTestSingleIteration)(void* closure); +void IterateAndReportPerf(const char* test_name, + const char* sub_test_name, + PerfTestSingleIteration single_iteration, + void* closure); + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_CPP_TEST_SUPPORT_TEST_UTILS_H_ diff --git a/mojo/public/interfaces/BUILD.gn b/mojo/public/interfaces/BUILD.gn new file mode 100644 index 0000000000..fb11ec2250 --- /dev/null +++ b/mojo/public/interfaces/BUILD.gn @@ -0,0 +1,9 @@ +# 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. + +group("interfaces") { + deps = [ + "bindings", + ] +} diff --git a/mojo/public/interfaces/bindings/BUILD.gn b/mojo/public/interfaces/bindings/BUILD.gn new file mode 100644 index 0000000000..c2cadcd736 --- /dev/null +++ b/mojo/public/interfaces/bindings/BUILD.gn @@ -0,0 +1,29 @@ +# 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. + +import("../../tools/bindings/mojom.gni") + +mojom("bindings") { + visibility = [] + sources = [ + "interface_control_messages.mojom", + "pipe_control_messages.mojom", + ] + + export_class_attribute = "MOJO_CPP_BINDINGS_EXPORT" + export_define = "MOJO_CPP_BINDINGS_IMPLEMENTATION" + export_header = "mojo/public/cpp/bindings/bindings_export.h" +} + +# TODO(yzshen): Remove this target and use the one above once +# |use_new_js_bindings| becomes true by default. +mojom("new_bindings") { + visibility = [] + sources = [ + "new_bindings/interface_control_messages.mojom", + "new_bindings/pipe_control_messages.mojom", + ] + + use_new_js_bindings = true +} diff --git a/mojo/public/interfaces/bindings/interface_control_messages.mojom b/mojo/public/interfaces/bindings/interface_control_messages.mojom new file mode 100644 index 0000000000..0a1904206a --- /dev/null +++ b/mojo/public/interfaces/bindings/interface_control_messages.mojom @@ -0,0 +1,67 @@ +// 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. + +[JavaPackage="org.chromium.mojo.bindings.interfacecontrol"] +module mojo.interface_control; + +// For each user-defined interface, some control functions are provided by the +// interface endpoints at both sides. + +//////////////////////////////////////////////////////////////////////////////// +// Run@0xFFFFFFFF(RunInput input) => (RunOutput? output); +// +// This control function runs the input command. If the command is not +// supported, |output| is set to null; otherwise |output| stores the result, +// whose type depends on the input. + +const uint32 kRunMessageId = 0xFFFFFFFF; + +struct RunMessageParams { + RunInput input; +}; +union RunInput { + QueryVersion query_version; + FlushForTesting flush_for_testing; +}; + +struct RunResponseMessageParams { + RunOutput? output; +}; +union RunOutput { + QueryVersionResult query_version_result; +}; + +// Queries the max supported version of the user-defined interface. +// Sent by the interface client side. +struct QueryVersion { +}; +struct QueryVersionResult { + uint32 version; +}; + +// Sent by either side of the interface. +struct FlushForTesting { +}; + +//////////////////////////////////////////////////////////////////////////////// +// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input); +// +// This control function runs the input command. If the operation fails or the +// command is not supported, the message pipe is closed. + +const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE; + +struct RunOrClosePipeMessageParams { + RunOrClosePipeInput input; +}; +union RunOrClosePipeInput { + RequireVersion require_version; +}; + +// If the specified version of the user-defined interface is not supported, the +// function fails and the pipe is closed. +// Sent by the interface client side. +struct RequireVersion { + uint32 version; +}; diff --git a/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom new file mode 100644 index 0000000000..e03ffd6589 --- /dev/null +++ b/mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom @@ -0,0 +1,67 @@ +// 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. + +[JavaPackage="org.chromium.mojo.bindings.interfacecontrol"] +module mojo.interface_control2; + +// For each user-defined interface, some control functions are provided by the +// interface endpoints at both sides. + +//////////////////////////////////////////////////////////////////////////////// +// Run@0xFFFFFFFF(RunInput input) => (RunOutput? output); +// +// This control function runs the input command. If the command is not +// supported, |output| is set to null; otherwise |output| stores the result, +// whose type depends on the input. + +const uint32 kRunMessageId = 0xFFFFFFFF; + +struct RunMessageParams { + RunInput input; +}; +union RunInput { + QueryVersion query_version; + FlushForTesting flush_for_testing; +}; + +struct RunResponseMessageParams { + RunOutput? output; +}; +union RunOutput { + QueryVersionResult query_version_result; +}; + +// Queries the max supported version of the user-defined interface. +// Sent by the interface client side. +struct QueryVersion { +}; +struct QueryVersionResult { + uint32 version; +}; + +// Sent by either side of the interface. +struct FlushForTesting { +}; + +//////////////////////////////////////////////////////////////////////////////// +// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input); +// +// This control function runs the input command. If the operation fails or the +// command is not supported, the message pipe is closed. + +const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE; + +struct RunOrClosePipeMessageParams { + RunOrClosePipeInput input; +}; +union RunOrClosePipeInput { + RequireVersion require_version; +}; + +// If the specified version of the user-defined interface is not supported, the +// function fails and the pipe is closed. +// Sent by the interface client side. +struct RequireVersion { + uint32 version; +}; diff --git a/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom new file mode 100644 index 0000000000..69975fc1c0 --- /dev/null +++ b/mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom @@ -0,0 +1,46 @@ +// 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. + +[JavaPackage="org.chromium.mojo.bindings.pipecontrol"] +module mojo.pipe_control2; + +// For each message pipe running user-defined interfaces, some control +// functions are provided and used by the routers at both ends of the pipe, so +// that they can coordinate to manage interface endpoints. +// All these control messages will have the interface ID field in the message +// header set to invalid. + +//////////////////////////////////////////////////////////////////////////////// +// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input); +// +// This control function runs the input command. If the operation fails or the +// command is not supported, the message pipe is closed. + +const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE; + +struct RunOrClosePipeMessageParams { + RunOrClosePipeInput input; +}; + +union RunOrClosePipeInput { + PeerAssociatedEndpointClosedEvent peer_associated_endpoint_closed_event; +}; + +// A user-defined reason about why the interface is disconnected. +struct DisconnectReason { + uint32 custom_reason; + string description; +}; + +// An event to notify that an interface endpoint set up at the message sender +// side has been closed. +// +// This event is omitted if the endpoint belongs to the master interface and +// there is no disconnect reason specified. +struct PeerAssociatedEndpointClosedEvent { + // The interface ID. + uint32 id; + DisconnectReason? disconnect_reason; +}; + diff --git a/mojo/public/interfaces/bindings/pipe_control_messages.mojom b/mojo/public/interfaces/bindings/pipe_control_messages.mojom new file mode 100644 index 0000000000..74e9cc7657 --- /dev/null +++ b/mojo/public/interfaces/bindings/pipe_control_messages.mojom @@ -0,0 +1,46 @@ +// 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. + +[JavaPackage="org.chromium.mojo.bindings.pipecontrol"] +module mojo.pipe_control; + +// For each message pipe running user-defined interfaces, some control +// functions are provided and used by the routers at both ends of the pipe, so +// that they can coordinate to manage interface endpoints. +// All these control messages will have the interface ID field in the message +// header set to invalid. + +//////////////////////////////////////////////////////////////////////////////// +// RunOrClosePipe@0xFFFFFFFE(RunOrClosePipeInput input); +// +// This control function runs the input command. If the operation fails or the +// command is not supported, the message pipe is closed. + +const uint32 kRunOrClosePipeMessageId = 0xFFFFFFFE; + +struct RunOrClosePipeMessageParams { + RunOrClosePipeInput input; +}; + +union RunOrClosePipeInput { + PeerAssociatedEndpointClosedEvent peer_associated_endpoint_closed_event; +}; + +// A user-defined reason about why the interface is disconnected. +struct DisconnectReason { + uint32 custom_reason; + string description; +}; + +// An event to notify that an interface endpoint set up at the message sender +// side has been closed. +// +// This event is omitted if the endpoint belongs to the master interface and +// there is no disconnect reason specified. +struct PeerAssociatedEndpointClosedEvent { + // The interface ID. + uint32 id; + DisconnectReason? disconnect_reason; +}; + diff --git a/mojo/public/interfaces/bindings/tests/BUILD.gn b/mojo/public/interfaces/bindings/tests/BUILD.gn new file mode 100644 index 0000000000..e496eb656c --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/BUILD.gn @@ -0,0 +1,204 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("../../../tools/bindings/mojom.gni") + +mojom("test_interfaces") { + testonly = true + sources = [ + "math_calculator.mojom", + "no_module.mojom", + "ping_service.mojom", + "rect.mojom", + "regression_tests.mojom", + "sample_factory.mojom", + "sample_interfaces.mojom", + "sample_service.mojom", + "scoping.mojom", + "serialization_test_structs.mojom", + "test_bad_messages.mojom", + "test_constants.mojom", + "test_data_view.mojom", + "test_native_types.mojom", + "test_structs.mojom", + "test_sync_methods.mojom", + "test_unions.mojom", + "validation_test_interfaces.mojom", + ] + public_deps = [ + ":echo", + ":test_mojom_import", + ":test_mojom_import2", + ] +} + +component("test_export_component") { + testonly = true + deps = [ + ":test_export", + ] +} + +if (!is_ios) { + component("test_export_blink_component") { + testonly = true + deps = [ + ":test_export_blink", + ] + } +} + +mojom("test_export") { + testonly = true + sources = [ + "test_export.mojom", + ] + export_class_attribute = "MOJO_TEST_EXPORT" + export_define = "MOJO_TEST_IMPLEMENTATION=1" + export_header = "mojo/public/cpp/bindings/tests/mojo_test_export.h" + if (!is_ios) { + export_class_attribute_blink = "MOJO_TEST_BLINK_EXPORT" + export_define_blink = "MOJO_TEST_BLINK_IMPLEMENTATION=1" + export_header_blink = + "mojo/public/cpp/bindings/tests/mojo_test_blink_export.h" + } + visibility = [ ":test_export_component" ] + if (!is_ios) { + visibility_blink = [ ":test_export_blink_component" ] + } +} + +mojom("test_exported_import") { + testonly = true + sources = [ + "test_import.mojom", + ] + public_deps = [ + ":test_export", + ] + + overridden_deps = [ ":test_export" ] + component_deps = [ ":test_export_component" ] + if (!is_ios) { + overridden_deps_blink = [ ":test_export" ] + component_deps_blink = [ ":test_export_blink_component" ] + } +} + +# Used to test that it is okay to call mojom::Foo::Serialize()/Deserialize() +# even if the mojom target is linked into another component. +# +# We don't use |test_export_component| for this test because +# //mojo/public/cpp/bindings/tests depends on both |test_export_component| and +# |test_exported_import| and therefore actually get the shared cpp sources of +# test_export.mojom from |test_exported_import|. +component("test_export_component2") { + testonly = true + public_deps = [ + ":test_export2", + ] +} + +mojom("test_export2") { + testonly = true + sources = [ + "test_export2.mojom", + ] + export_class_attribute = "MOJO_TEST_EXPORT" + export_define = "MOJO_TEST_IMPLEMENTATION=1" + export_header = "mojo/public/cpp/bindings/tests/mojo_test_export.h" + visibility = [ ":test_export_component2" ] +} + +mojom("test_mojom_import") { + testonly = true + sources = [ + "sample_import.mojom", + ] +} + +mojom("test_mojom_import_wrapper") { + testonly = true + public_deps = [ + ":test_mojom_import", + ] +} + +mojom("test_mojom_import_wrapper_wrapper") { + testonly = true + public_deps = [ + ":test_mojom_import_wrapper", + ] +} + +mojom("test_mojom_import2") { + testonly = true + sources = [ + "sample_import2.mojom", + ] + public_deps = [ + ":test_mojom_import", + ":test_mojom_import_wrapper_wrapper", + ] +} + +mojom("test_struct_traits_interfaces") { + testonly = true + sources = [ + "struct_with_traits.mojom", + ] +} + +mojom("test_associated_interfaces") { + # These files are not included in the test_interfaces target because + # associated interfaces are not supported by all bindings languages yet. + testonly = true + sources = [ + "test_associated_interfaces.mojom", + "validation_test_associated_interfaces.mojom", + ] + + public_deps = [ + ":test_interfaces", + ] +} + +mojom("versioning_test_service_interfaces") { + testonly = true + sources = [ + "versioning_test_service.mojom", + ] +} + +mojom("versioning_test_client_interfaces") { + testonly = true + sources = [ + "versioning_test_client.mojom", + ] +} + +mojom("test_wtf_types") { + testonly = true + + sources = [ + "test_wtf_types.mojom", + ] +} + +mojom("test_no_sources") { + testonly = true + + public_deps = [ + ":test_interfaces", + ] +} + +mojom("echo") { + testonly = true + sources = [ + "echo.mojom", + "echo_import.mojom", + ] + use_new_js_bindings = true +} diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.data new file mode 100644 index 0000000000..b797feaa2d --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.data @@ -0,0 +1,26 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method0_params // num_bytes +[u4]0 // version +[u4]1 // associated interface pointer: interface ID index +[u4]1 // associated interface pointer: version +[anchr]method0_params + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]3 // num_elements : It is okay to have IDs that are not + // referred to. +[u4]4 +[u4]5 +[u4]8 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data new file mode 100644 index 0000000000..a36d8073c0 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data @@ -0,0 +1,24 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method0_params // num_bytes +[u4]0 // version +[u4]1 // associated interface pointer: interface ID index +[u4]1 // associated interface pointer: version +[anchr]method0_params + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]2 // num_elements +[u4]3 +[u4]0xFFFFFFFF // Unexpected invalid interface ID. +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected new file mode 100644 index 0000000000..420b4210e2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_INTERFACE_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.data new file mode 100644 index 0000000000..e3fa5bb4f5 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.data @@ -0,0 +1,24 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method0_params // num_bytes +[u4]0 // version +[u4]1 // associated interface pointer: interface ID index +[u4]1 // associated interface pointer: version +[anchr]method0_params + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]2 // num_elements +[u4]3 +[u4]0 // Unexpected master interface ID. +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected new file mode 100644 index 0000000000..420b4210e2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_INTERFACE_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data new file mode 100644 index 0000000000..f9e62015c9 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data @@ -0,0 +1,27 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method0_params // num_bytes +[u4]0 // version +[u4]1111 // associated interface pointer: The interface ID index + // is out of range. +[u4]1 // associated interface pointer: version +[anchr]method0_params + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]3 // num_elements : It is okay to have IDs that are not + // referred to. +[u4]4 +[u4]5 +[u4]8 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected new file mode 100644 index 0000000000..420b4210e2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_INTERFACE_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data new file mode 100644 index 0000000000..b785ed1b48 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data @@ -0,0 +1,25 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method0_params // num_bytes +[u4]0 // version +[u4]0xFFFFFFFF // associated interface pointer: Unexpected invalid + // interface ID index. +[u4]1 // associated interface pointer: version +[anchr]method0_params + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]2 // num_elements +[u4]3 +[u4]4 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.expected new file mode 100644 index 0000000000..d8eda1f573 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.data new file mode 100644 index 0000000000..efa21623f5 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.data @@ -0,0 +1,26 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]1 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method1_params // num_bytes +[u4]0 // version +[u4]1 // associated interface request: interface ID index +[u4]0 // padding +[anchr]method1_params + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]3 // num_elements : It is okay to have IDs that are not + // referred to. +[u4]4 +[u4]5 +[u4]8 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.data new file mode 100644 index 0000000000..5a66aad82d --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.data @@ -0,0 +1,27 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]1 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method1_params // num_bytes +[u4]0 // version +[u4]0xFFFFFFFF // associated interface request: Unexpected invalid + // interface ID index. +[u4]0 // padding +[anchr]method1_params + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]3 // num_elements : It is okay to have IDs that are not + // referred to. +[u4]4 +[u4]5 +[u4]8 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.expected new file mode 100644 index 0000000000..d8eda1f573 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.data new file mode 100644 index 0000000000..ab29603ec1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.data @@ -0,0 +1,18 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]2 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[u8]0 // payload_interface_ids: This array is a nullable field. +[anchr]message_header + +[anchr]payload +[dist4]method2_params // num_bytes +[u4]0 // version +[u4]0xFFFFFFFF // associated interface pointer: Invalid interface ID + // index. +[u4]1 // associated interface pointer: version +[anchr]method2_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.data new file mode 100644 index 0000000000..6cb71d374f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.data @@ -0,0 +1,36 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[dist4]associated_interface_array // num_bytes +[u4]2 // num_elements +[u4]2 // interface ID index +[u4]14 // version +[u4]2 // interface ID index: The same value as the + // one above. +[u4]18 // version +[anchr]associated_interface_array + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]4 // num_elements : It is okay to have IDs that are not + // referred to. +[u4]4 +[u4]5 +[u4]8 +[u4]19 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.expected new file mode 100644 index 0000000000..420b4210e2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_INTERFACE_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.data new file mode 100644 index 0000000000..13df01e049 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.data @@ -0,0 +1,35 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[dist4]associated_interface_array // num_bytes +[u4]2 // num_elements +[u4]2 // interface ID index +[u4]14 // version +[u4]3 // interface ID index +[u4]18 // version +[anchr]associated_interface_array + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]4 // num_elements : It is okay to have IDs that are not + // referred to. +[u4]4 +[u4]5 +[u4]8 +[u4]19 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.data new file mode 100644 index 0000000000..2e163be160 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.data @@ -0,0 +1,36 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[dist4]associated_interface_array // num_bytes +[u4]2 // num_elements +[u4]2 // interface ID index +[u4]14 // version +[u4]0xFFFFFFFF // interface ID index: Unexpected invalid + // value. +[u4]18 // version +[anchr]associated_interface_array + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]4 // num_elements : It is okay to have IDs that are not + // referred to. +[u4]4 +[u4]5 +[u4]8 +[u4]19 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.expected new file mode 100644 index 0000000000..d8eda1f573 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.data b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.data new file mode 100644 index 0000000000..4a63003e19 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.data @@ -0,0 +1,38 @@ +[dist4]message_header // num_bytes +[u4]2 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // request_id +[dist8]payload +[dist8]payload_interface_ids +[anchr]message_header + +[anchr]payload +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[dist4]associated_interface_array // num_bytes +[u4]3 // num_elements +[u4]2 // interface ID index +[u4]14 // version +[u4]3 // interface ID index +[u4]0 // version +[u4]0 // interface ID index : It is smaller than + // the first element above, which is wrong. +[u4]18 // version +[anchr]associated_interface_array + +[anchr]payload_interface_ids +[dist4]interface_id_array // num_bytes +[u4]4 // num_elements : It is okay to have IDs that are not + // referred to. +[u4]4 +[u4]5 +[u4]8 +[u4]19 +[anchr]interface_id_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.expected new file mode 100644 index 0000000000..420b4210e2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_INTERFACE_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.data b/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.data new file mode 100644 index 0000000000..30032a172b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.data @@ -0,0 +1,7 @@ +[dist4]message_header // num_bytes +[u4]0 // version number +[u4]0 // interface ID +[u4]2 // There is no Method2 +[u4]0 // flags +[u4]0 // padding +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.expected b/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.expected new file mode 100644 index 0000000000..a32d895c31 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data new file mode 100644 index 0000000000..68899f4650 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data @@ -0,0 +1,2 @@ +[u4]24 // num_bytes: Bigger than the total size of the message. +[u4]0 // version diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data new file mode 100644 index 0000000000..21e7fbc02f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data @@ -0,0 +1 @@ +0x00 diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.data new file mode 100644 index 0000000000..dfb2dd262d --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.data @@ -0,0 +1,8 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0x80000000 // name +[u4]3 // flags: This combination is illegal. +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.expected new file mode 100644 index 0000000000..c33fde327b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data new file mode 100644 index 0000000000..27804a8390 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data @@ -0,0 +1,8 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0x80000000 // name +[u4]1 // flags: This is a response message which expects to + // have a request ID. +[u4]0 // padding +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected new file mode 100644 index 0000000000..083db1ad27 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.data new file mode 100644 index 0000000000..6302baeec1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.data @@ -0,0 +1,7 @@ +[dist4]message_header // num_bytes +[u4]0 // version number +[u4]0 // interface ID +[u4]9999 // There is no Method9999. +[u4]0 // flags +[u4]0 // padding +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.expected new file mode 100644 index 0000000000..a32d895c31 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data new file mode 100644 index 0000000000..2fd0fcd452 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data @@ -0,0 +1,6 @@ +[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow. +[u4]0 // version +[u4]0 // interface ID +[u4]0x80000000 // name +[u4]0 // flags +[u4]0 // padding diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data new file mode 100644 index 0000000000..f58eca94df --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data @@ -0,0 +1,4 @@ +[dist4]message_header // num_bytes: Less than the minimal size of message + // header. +[u4]0 // version +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data new file mode 100644 index 0000000000..e98f66f147 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data @@ -0,0 +1,6 @@ +[u4]0 // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0x80000000 // name +[u4]0 // flags +[u4]0 // padding diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.data new file mode 100644 index 0000000000..df9e418105 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.data @@ -0,0 +1,9 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0x80000000 // name +[u4]0 // flags +[u4]0 // padding +[u8]0 // Extra bytes that result in mismatched |num_bytes| and + // |version|. +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.data new file mode 100644 index 0000000000..e2c574eea5 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.data @@ -0,0 +1,10 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0x80000000 // name +[u4]1 // flags +[u4]0 // padding +[u8]0 // request_id +[u8]0 // Extra bytes that result in mismatched |num_bytes| and + // |version|. +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.data new file mode 100644 index 0000000000..f7a321b6f8 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.data @@ -0,0 +1,7 @@ +[dist4]message_header // num_bytes +[u4]8 // version: |num_bytes| is too small for |version|. +[u4]0 // interface ID +[u4]0x80000000 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data new file mode 100644 index 0000000000..841da5e360 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data @@ -0,0 +1,13 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method0_params // num_bytes +[u4]0 // version +[f]-1 // param0 +[u4]0 // padding +[anchr]method0_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data new file mode 100644 index 0000000000..cff6a3066c --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data @@ -0,0 +1,11 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[u4]16 // num_bytes: Incomplete struct. +[u4]0 // version +[f]-1 // param0 diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data new file mode 100644 index 0000000000..3f03ab2d04 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data @@ -0,0 +1,9 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[u4]16 // num_bytes: Incomplete struct header. diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.data new file mode 100644 index 0000000000..7aee806966 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.data @@ -0,0 +1,8 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0 // name +[u4]2 // flags: kMessageIsResponse is set in a request. +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.expected new file mode 100644 index 0000000000..c33fde327b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.data new file mode 100644 index 0000000000..5448c5f91b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.data @@ -0,0 +1,9 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0 // name +[u4]1 // flags: kMessageExpectsResponse is set in a request + // for a method that does not take a response. +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.expected new file mode 100644 index 0000000000..c33fde327b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data new file mode 100644 index 0000000000..4a3e2fea2e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data @@ -0,0 +1,12 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow. +[u4]0 // version +[f]-1 // param0 +[u4]0 // padding diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data new file mode 100644 index 0000000000..fa4c555096 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data @@ -0,0 +1,11 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method0_params // num_bytes: Less than the minimal size that we expect. +[u4]0 // version +[anchr]method0_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data new file mode 100644 index 0000000000..d62206ed15 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data @@ -0,0 +1,12 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]0 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[u4]4 // num_bytes: Less than the size of struct header. +[u4]0 // version +[f]-1 // param0 +[u4]0 // padding diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.data new file mode 100644 index 0000000000..5dca2fe238 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.data @@ -0,0 +1,48 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]10 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method10_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method10_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[dist8]key_array_ptr +[dist8]value_array_ptr +[anchr]map_data_struct_header + +[anchr]key_array_ptr +[dist4]key_array_member // num_bytes +[u4]2 // num_elements +[dist8]key_string_1 +[dist8]key_string_2 +[anchr]key_array_member + +[anchr]key_string_1 +[dist4]key_string_1_member // num_bytes +[u4]5 // num_elements +0 1 2 3 4 +[anchr]key_string_1_member + +[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment + +[anchr]key_string_2 +[dist4]key_string_2_member // num_bytes +[u4]5 // num_elements +5 6 7 8 9 +[anchr]key_string_2_member + +[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment + +[anchr]value_array_ptr +[dist4]value_array_member // num_bytes +[u4]2 // num_elements +1 2 +[anchr]value_array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.data new file mode 100644 index 0000000000..f64fbc388b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.data @@ -0,0 +1,48 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]10 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method10_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method10_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[dist8]key_array_ptr +[dist8]value_array_ptr +[anchr]map_data_struct_header + +[anchr]key_array_ptr +[dist4]key_array_member // num_bytes +[u4]2 // num_elements +[dist8]key_string_1 +[dist8]key_string_2 +[anchr]key_array_member + +[anchr]key_string_1 +[dist4]key_string_1_member // num_bytes +[u4]5 // num_elements +0 1 2 3 4 +[anchr]key_string_1_member + +[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment + +[anchr]key_string_2 +[dist4]key_string_2_member // num_bytes +[u4]5 // num_elements +0 1 2 3 4 +[anchr]key_string_2_member + +[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment + +[anchr]value_array_ptr +[dist4]value_array_member // num_bytes +[u4]2 // num_elements +1 2 +[anchr]value_array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.data new file mode 100644 index 0000000000..3a99dc2ab8 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.data @@ -0,0 +1,25 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]10 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method10_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method10_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[u8]0 // null keys array +[dist8]value_array_ptr +[anchr]map_data_struct_header + +[anchr]value_array_ptr +[dist4]value_array_member // num_bytes +[u4]2 // num_elements +1 2 +[anchr]value_array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.data new file mode 100644 index 0000000000..459d806dac --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.data @@ -0,0 +1,40 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]10 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method10_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method10_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[dist8]key_array_ptr +[u8]0 // null values array +[anchr]map_data_struct_header + +[anchr]key_array_ptr +[dist4]key_array_member // num_bytes +[u4]2 // num_elements +[dist8]key_string_1 +[dist8]key_string_2 +[anchr]key_array_member + +[anchr]key_string_1 +[dist4]key_string_1_member // num_bytes +[u4]5 // num_elements +0 1 2 3 4 +[anchr]key_string_1_member + +[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment + +[anchr]key_string_2 +[dist4]key_string_2_member // num_bytes +[u4]5 // num_elements +5 6 7 8 9 +[anchr]key_string_2_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.data new file mode 100644 index 0000000000..9127a26c25 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.data @@ -0,0 +1,40 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]10 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method10_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method10_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[dist8]key_array_ptr +[dist8]value_array_ptr +[anchr]map_data_struct_header + +[anchr]key_array_ptr +[dist4]key_array_member // num_bytes +[u4]2 // num_elements +[dist8]key_string_1 +[u8]0 // one null key +[anchr]key_array_member + +[anchr]key_string_1 +[dist4]key_string_1_member // num_bytes +[u4]5 // num_elements +0 1 2 3 4 +[anchr]key_string_1_member + +[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment + +[anchr]value_array_ptr +[dist4]value_array_member // num_bytes +[u4]2 // num_elements +1 2 +[anchr]value_array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.data new file mode 100644 index 0000000000..a2f903859f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.data @@ -0,0 +1,48 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]10 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method10_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method10_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[dist8]key_array_ptr +[dist8]value_array_ptr +[anchr]map_data_struct_header + +[anchr]key_array_ptr +[dist4]key_array_member // num_bytes +[u4]2 // num_elements +[dist8]key_string_1 +[dist8]key_string_2 +[anchr]key_array_member + +[anchr]key_string_1 +[dist4]key_string_1_member // num_bytes +[u4]5 // num_elements +0 1 2 3 4 +[anchr]key_string_1_member + +[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment + +[anchr]key_string_2 +[dist4]key_string_2_member // num_bytes +[u4]5 // num_elements +5 6 7 8 9 +[anchr]key_string_2_member + +[u4]0 [u4]0 [u1]0 [u1]0 [u1]0 // manual padding for array alignment + +[anchr]value_array_ptr +[dist4]value_array_member // num_bytes +[u4]1 // num_elements +1 // unequal size +[anchr]value_array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.expected new file mode 100644 index 0000000000..2798d4861e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.data new file mode 100644 index 0000000000..781079bc39 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.data @@ -0,0 +1,19 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]11 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method11_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method11_params + +[anchr]param0_ptr +[dist4]struct_g // num_bytes +[u4]0 // version +[s4]123 // i +[u4]0 // padding +[anchr]struct_g diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.data new file mode 100644 index 0000000000..b9ab5bff51 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.data @@ -0,0 +1,20 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]11 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method11_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method11_params + +[anchr]param0_ptr +[dist4]struct_g // num_bytes +[u4]1 // version +[s4]123 // i +[u4]0 // padding +[u8]0 // struct_a +[anchr]struct_g diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.data new file mode 100644 index 0000000000..7d61446321 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.data @@ -0,0 +1,20 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]11 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method11_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method11_params + +[anchr]param0_ptr +[dist4]struct_g // num_bytes +[u4]2 // version +[s4]123 // i +[u4]0 // padding +[u8]0 // struct_a +[anchr]struct_g diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.data new file mode 100644 index 0000000000..3c3ee12717 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.data @@ -0,0 +1,28 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]11 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method11_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method11_params + +[anchr]param0_ptr +[dist4]struct_g // num_bytes +[u4]3 // version +[s4]123 // i +[b]00000001 // b +0 0 0 // padding +[u8]0 // struct_a +[dist8]str_ptr // str +[anchr]struct_g + +[anchr]str_ptr +[dist4]string // num_bytes +[u4]2 // num_elements +0 1 +[anchr]string diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.data new file mode 100644 index 0000000000..2e9fde6eeb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.data @@ -0,0 +1,30 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]11 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method11_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method11_params + +[anchr]param0_ptr +[dist4]struct_g // num_bytes +[u4]5 // version: Newer than what the validator knows. + // It is okay that the size is the same as the latest + // version that the validator knows. +[s4]123 // i +[b]00000001 // b +0 0 0 // padding +[u8]0 // struct_a +[dist8]str_ptr // str +[anchr]struct_g + +[anchr]str_ptr +[dist4]string // num_bytes +[u4]2 // num_elements +0 1 +[anchr]string diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.data new file mode 100644 index 0000000000..9a956267e8 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.data @@ -0,0 +1,30 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]11 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method11_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method11_params + +[anchr]param0_ptr +[dist4]struct_g // num_bytes +[u4]5 // version: Newer than what the validator knows. +[s4]123 // i +[b]00000001 // b +0 0 0 // padding +[u8]0 // struct_a +[dist8]str_ptr // str +[u8]0 // unknown contents +[u8]0 // unknown contents +[anchr]struct_g + +[anchr]str_ptr +[dist4]string // num_bytes +[u4]2 // num_elements +0 1 +[anchr]string diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.data new file mode 100644 index 0000000000..c2e5a8da66 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.data @@ -0,0 +1,21 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]11 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method11_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method11_params + +[anchr]param0_ptr +[dist4]struct_g // num_bytes: The size is too big for the version. +[u4]1 // version +[s4]123 // i +[u4]0 // padding +[u8]0 // struct_a +[u8]0 // Unexpected contents that cause the mismatch. +[anchr]struct_g diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.data new file mode 100644 index 0000000000..edfe5fa8b1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.data @@ -0,0 +1,19 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]11 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method11_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method11_params + +[anchr]param0_ptr +[dist4]struct_g // num_bytes: The size is too small for the version. +[u4]2 // version +[s4]123 // i +[u4]0 // padding +[anchr]struct_g diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.data new file mode 100644 index 0000000000..13135aecad --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.data @@ -0,0 +1,9 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]12 // name +[u4]0 // flags: kMessageExpectsResponse is not set but + // expected. +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.expected new file mode 100644 index 0000000000..c33fde327b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.data new file mode 100644 index 0000000000..51973be114 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.data @@ -0,0 +1,17 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]13 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method13_params // num_bytes +[u4]0 // version +[u4]0xFFFFFFFF // param0 +[u4]1234 +[u4]65535 // param1 +[u4]0xFFFFFFFF // param2 +[u4]3242 +[u4]0 // padding +[anchr]method13_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.data new file mode 100644 index 0000000000..b739731093 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.data @@ -0,0 +1,19 @@ +[handles]2 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]13 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method13_params // num_bytes +[u4]0 // version +[u4]0 // param0 +[u4]1234 +[u4]65535 // param1 +[u4]1 // param2 +[u4]3242 +[u4]0 // padding +[anchr]method13_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.data new file mode 100644 index 0000000000..14448497f1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.data @@ -0,0 +1,13 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]14 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method14_params // num_bytes +[u4]0 // version +[u4]0 // param0 +[u4]1 // param1 +[anchr]method14_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.data new file mode 100644 index 0000000000..50b9ea33b0 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.data @@ -0,0 +1,13 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]14 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method14_params // num_bytes +[u4]0 // version +[u4]0 // param0 +[u4]0xFFFFFFFF // param1: Unknown value is okay for extensible enum. +[anchr]method14_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.data new file mode 100644 index 0000000000..567f23b254 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.data @@ -0,0 +1,14 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]14 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method14_params // num_bytes +[u4]0 // version +[u4]0xFFFFFFFF // param0: Unknown value is not allowed for + // non-extensible enum. +[u4]2 // param1 +[anchr]method14_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.expected new file mode 100644 index 0000000000..9ef4ce3fdd --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNKNOWN_ENUM_VALUE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.data new file mode 100644 index 0000000000..21af8a3df1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.data @@ -0,0 +1,22 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]15 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method15_params // num_bytes +[u4]0 // version +[dist8]enum_array_0 // param0 +[u8]0 // param1 +[anchr]method15_params + +[anchr]enum_array_0 +[dist4]enum_array_0_member // num_bytes +[u4]0 // num_elements +[anchr]enum_array_0_member + +[u8]0x5678 // This is not part of the array above (which is + // empty), so enum validation shouldn't be done on + // it. diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.data new file mode 100644 index 0000000000..c418d8994c --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.data @@ -0,0 +1,27 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]15 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method15_params // num_bytes +[u4]0 // version +[dist8]enum_array_0 // param0 +[dist8]enum_array_1 // param1 +[anchr]method15_params + +[anchr]enum_array_0 +[dist4]enum_array_0_member // num_bytes +[u4]2 // num_elements +[u4]0 +[u4]1 +[anchr]enum_array_0_member + +[anchr]enum_array_1 +[dist4]enum_array_1_member // num_bytes +[u4]2 // num_elements +[u4]0 +[u4]1 +[anchr]enum_array_1_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.data new file mode 100644 index 0000000000..b6be6d93c6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.data @@ -0,0 +1,20 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]15 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method15_params // num_bytes +[u4]0 // version +[u8]0 // param0 +[dist8]enum_array_1 // param1 +[anchr]method15_params + +[anchr]enum_array_1 +[dist4]enum_array_1_member // num_bytes +[u4]2 // num_elements +[u4]0 +[u4]0x1234 // Unknown value is okay for extensible enum. +[anchr]enum_array_1_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.data new file mode 100644 index 0000000000..0a46e0a4a2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.data @@ -0,0 +1,21 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]15 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method15_params // num_bytes +[u4]0 // version +[dist8]enum_array_0 // param0 +[u8]0 // param1 +[anchr]method15_params + +[anchr]enum_array_0 +[dist4]enum_array_0_member // num_bytes +[u4]2 // num_elements +[u4]1 +[u4]0x5678 // Unknown value is not allowed for non-extensible + // enum. +[anchr]enum_array_0_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.expected new file mode 100644 index 0000000000..9ef4ce3fdd --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNKNOWN_ENUM_VALUE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.data new file mode 100644 index 0000000000..0425ea72f6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.data @@ -0,0 +1,34 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]16 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method16_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method16_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[dist8]key_array_ptr +[dist8]value_array_ptr +[anchr]map_data_struct_header + +[anchr]key_array_ptr +[dist4]key_array_member // num_bytes +[u4]2 // num_elements +[u4]0x5678 // Unknown value is not allowed for non-extensible + // enum. +[u4]1 +[anchr]key_array_member + +[anchr]value_array_ptr +[dist4]value_array_member // num_bytes +[u4]2 // num_elements +[u4]1 +[u4]2 +[anchr]value_array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.expected new file mode 100644 index 0000000000..9ef4ce3fdd --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNKNOWN_ENUM_VALUE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.data new file mode 100644 index 0000000000..2c2ea26d93 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.data @@ -0,0 +1,34 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]16 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method16_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method16_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[dist8]key_array_ptr +[dist8]value_array_ptr +[anchr]map_data_struct_header + +[anchr]key_array_ptr +[dist4]key_array_member // num_bytes +[u4]2 // num_elements +[u4]1 +[u4]2 +[anchr]key_array_member + +[anchr]value_array_ptr +[dist4]value_array_member // num_bytes +[u4]2 // num_elements +[u4]0x5678 // Unknown value is not allowed for non-extensible + // enum. +[u4]1 +[anchr]value_array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.expected new file mode 100644 index 0000000000..9ef4ce3fdd --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNKNOWN_ENUM_VALUE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.data new file mode 100644 index 0000000000..48807ab36f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.data @@ -0,0 +1,23 @@ +[handles]10 // Larger than the number of handles that we know about is okay. + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]17 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method17_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method17_params + +[anchr]param0_ptr +[dist4]interface_array // num_bytes +[u4]2 // num_elements +[u4]4 // handle +[u4]14 // version +[u4]5 // handle +[u4]18 // version +[anchr]interface_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.data new file mode 100644 index 0000000000..e549a10e3a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.data @@ -0,0 +1,24 @@ +[handles]10 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]17 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method17_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method17_params + +[anchr]param0_ptr +[dist4]interface_array // num_bytes +[u4]2 // num_elements +[u4]4 // handle +[u4]14 // version +[u4]10 // handle: It is outside of the valid encoded handle + // range [0, 10) +[u4]18 // version +[anchr]interface_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.expected new file mode 100644 index 0000000000..eef8e38a84 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_HANDLE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.data new file mode 100644 index 0000000000..1ce1d92fe5 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.data @@ -0,0 +1,23 @@ +[handles]10 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]17 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method17_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method17_params + +[anchr]param0_ptr +[dist4]interface_array // num_bytes +[u4]2 // num_elements +[u4]4 // handle +[u4]14 // version +[u4]0xFFFFFFFF // handle: An unexpected invalid handle. +[u4]18 // version +[anchr]interface_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.expected new file mode 100644 index 0000000000..6768236f6e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.data new file mode 100644 index 0000000000..663796d50e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.data @@ -0,0 +1,14 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]18 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method18_params // num_bytes +[u4]0 // version +[u4]0 // param0: Size 0 indicating the inlined union is null. +[u4]0 // param0: Tag field ignored. +[u8]0 // param0: Payload field ignored. +[anchr]method18_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.data new file mode 100644 index 0000000000..96e52d5ee2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.data @@ -0,0 +1,612 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]19 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method0_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method0_params + +[anchr]param0_ptr +[dist4]struct_a1 // num_bytes +[u4]0 // version +[dist8]struct_a2_ptr // struct_a2 +[anchr]struct_a1 + +[anchr]struct_a2_ptr +[dist4]struct_a2 // num_bytes +[u4]0 // version +[dist8]struct_a3_ptr // struct_a2 +[anchr]struct_a2 + +[anchr]struct_a3_ptr +[dist4]struct_a3 // num_bytes +[u4]0 // version +[dist8]struct_a4_ptr // struct_a3 +[anchr]struct_a3 + +[anchr]struct_a4_ptr +[dist4]struct_a4 // num_bytes +[u4]0 // version +[dist8]struct_a5_ptr // struct_a4 +[anchr]struct_a4 + +[anchr]struct_a5_ptr +[dist4]struct_a5 // num_bytes +[u4]0 // version +[dist8]struct_a6_ptr // struct_a5 +[anchr]struct_a5 + +[anchr]struct_a6_ptr +[dist4]struct_a6 // num_bytes +[u4]0 // version +[dist8]struct_a7_ptr // struct_a6 +[anchr]struct_a6 + +[anchr]struct_a7_ptr +[dist4]struct_a7 // num_bytes +[u4]0 // version +[dist8]struct_a8_ptr // struct_a7 +[anchr]struct_a7 + +[anchr]struct_a8_ptr +[dist4]struct_a8 // num_bytes +[u4]0 // version +[dist8]struct_a9_ptr // struct_a8 +[anchr]struct_a8 + +[anchr]struct_a9_ptr +[dist4]struct_a9 // num_bytes +[u4]0 // version +[dist8]struct_a10_ptr // struct_a9 +[anchr]struct_a9 + +[anchr]struct_a10_ptr +[dist4]struct_a10 // num_bytes +[u4]0 // version +[dist8]struct_a11_ptr // struct_a10 +[anchr]struct_a10 + +[anchr]struct_a11_ptr +[dist4]struct_a11 // num_bytes +[u4]0 // version +[dist8]struct_a12_ptr // struct_a11 +[anchr]struct_a11 + +[anchr]struct_a12_ptr +[dist4]struct_a12 // num_bytes +[u4]0 // version +[dist8]struct_a13_ptr // struct_a12 +[anchr]struct_a12 + +[anchr]struct_a13_ptr +[dist4]struct_a13 // num_bytes +[u4]0 // version +[dist8]struct_a14_ptr // struct_a13 +[anchr]struct_a13 + +[anchr]struct_a14_ptr +[dist4]struct_a14 // num_bytes +[u4]0 // version +[dist8]struct_a15_ptr // struct_a14 +[anchr]struct_a14 + +[anchr]struct_a15_ptr +[dist4]struct_a15 // num_bytes +[u4]0 // version +[dist8]struct_a16_ptr // struct_a15 +[anchr]struct_a15 + +[anchr]struct_a16_ptr +[dist4]struct_a16 // num_bytes +[u4]0 // version +[dist8]struct_a17_ptr // struct_a16 +[anchr]struct_a16 + +[anchr]struct_a17_ptr +[dist4]struct_a17 // num_bytes +[u4]0 // version +[dist8]struct_a18_ptr // struct_a17 +[anchr]struct_a17 + +[anchr]struct_a18_ptr +[dist4]struct_a18 // num_bytes +[u4]0 // version +[dist8]struct_a19_ptr // struct_a18 +[anchr]struct_a18 + +[anchr]struct_a19_ptr +[dist4]struct_a19 // num_bytes +[u4]0 // version +[dist8]struct_a20_ptr // struct_a19 +[anchr]struct_a19 + +[anchr]struct_a20_ptr +[dist4]struct_a20 // num_bytes +[u4]0 // version +[dist8]struct_a21_ptr // struct_a20 +[anchr]struct_a20 + +[anchr]struct_a21_ptr +[dist4]struct_a21 // num_bytes +[u4]0 // version +[dist8]struct_a22_ptr // struct_a21 +[anchr]struct_a21 + +[anchr]struct_a22_ptr +[dist4]struct_a22 // num_bytes +[u4]0 // version +[dist8]struct_a23_ptr // struct_a22 +[anchr]struct_a22 + +[anchr]struct_a23_ptr +[dist4]struct_a23 // num_bytes +[u4]0 // version +[dist8]struct_a24_ptr // struct_a23 +[anchr]struct_a23 + +[anchr]struct_a24_ptr +[dist4]struct_a24 // num_bytes +[u4]0 // version +[dist8]struct_a25_ptr // struct_a24 +[anchr]struct_a24 + +[anchr]struct_a25_ptr +[dist4]struct_a25 // num_bytes +[u4]0 // version +[dist8]struct_a26_ptr // struct_a25 +[anchr]struct_a25 + +[anchr]struct_a26_ptr +[dist4]struct_a26 // num_bytes +[u4]0 // version +[dist8]struct_a27_ptr // struct_a26 +[anchr]struct_a26 + +[anchr]struct_a27_ptr +[dist4]struct_a27 // num_bytes +[u4]0 // version +[dist8]struct_a28_ptr // struct_a27 +[anchr]struct_a27 + +[anchr]struct_a28_ptr +[dist4]struct_a28 // num_bytes +[u4]0 // version +[dist8]struct_a29_ptr // struct_a28 +[anchr]struct_a28 + +[anchr]struct_a29_ptr +[dist4]struct_a29 // num_bytes +[u4]0 // version +[dist8]struct_a30_ptr // struct_a29 +[anchr]struct_a29 + +[anchr]struct_a30_ptr +[dist4]struct_a30 // num_bytes +[u4]0 // version +[dist8]struct_a31_ptr // struct_a30 +[anchr]struct_a30 + +[anchr]struct_a31_ptr +[dist4]struct_a31 // num_bytes +[u4]0 // version +[dist8]struct_a32_ptr // struct_a31 +[anchr]struct_a31 + +[anchr]struct_a32_ptr +[dist4]struct_a32 // num_bytes +[u4]0 // version +[dist8]struct_a33_ptr // struct_a32 +[anchr]struct_a32 + +[anchr]struct_a33_ptr +[dist4]struct_a33 // num_bytes +[u4]0 // version +[dist8]struct_a34_ptr // struct_a33 +[anchr]struct_a33 + +[anchr]struct_a34_ptr +[dist4]struct_a34 // num_bytes +[u4]0 // version +[dist8]struct_a35_ptr // struct_a34 +[anchr]struct_a34 + +[anchr]struct_a35_ptr +[dist4]struct_a35 // num_bytes +[u4]0 // version +[dist8]struct_a36_ptr // struct_a35 +[anchr]struct_a35 + +[anchr]struct_a36_ptr +[dist4]struct_a36 // num_bytes +[u4]0 // version +[dist8]struct_a37_ptr // struct_a36 +[anchr]struct_a36 + +[anchr]struct_a37_ptr +[dist4]struct_a37 // num_bytes +[u4]0 // version +[dist8]struct_a38_ptr // struct_a37 +[anchr]struct_a37 + +[anchr]struct_a38_ptr +[dist4]struct_a38 // num_bytes +[u4]0 // version +[dist8]struct_a39_ptr // struct_a38 +[anchr]struct_a38 + +[anchr]struct_a39_ptr +[dist4]struct_a39 // num_bytes +[u4]0 // version +[dist8]struct_a40_ptr // struct_a39 +[anchr]struct_a39 + +[anchr]struct_a40_ptr +[dist4]struct_a40 // num_bytes +[u4]0 // version +[dist8]struct_a41_ptr // struct_a40 +[anchr]struct_a40 + +[anchr]struct_a41_ptr +[dist4]struct_a41 // num_bytes +[u4]0 // version +[dist8]struct_a42_ptr // struct_a41 +[anchr]struct_a41 + +[anchr]struct_a42_ptr +[dist4]struct_a42 // num_bytes +[u4]0 // version +[dist8]struct_a43_ptr // struct_a42 +[anchr]struct_a42 + +[anchr]struct_a43_ptr +[dist4]struct_a43 // num_bytes +[u4]0 // version +[dist8]struct_a44_ptr // struct_a43 +[anchr]struct_a43 + +[anchr]struct_a44_ptr +[dist4]struct_a44 // num_bytes +[u4]0 // version +[dist8]struct_a45_ptr // struct_a44 +[anchr]struct_a44 + +[anchr]struct_a45_ptr +[dist4]struct_a45 // num_bytes +[u4]0 // version +[dist8]struct_a46_ptr // struct_a45 +[anchr]struct_a45 + +[anchr]struct_a46_ptr +[dist4]struct_a46 // num_bytes +[u4]0 // version +[dist8]struct_a47_ptr // struct_a46 +[anchr]struct_a46 + +[anchr]struct_a47_ptr +[dist4]struct_a47 // num_bytes +[u4]0 // version +[dist8]struct_a48_ptr // struct_a47 +[anchr]struct_a47 + +[anchr]struct_a48_ptr +[dist4]struct_a48 // num_bytes +[u4]0 // version +[dist8]struct_a49_ptr // struct_a48 +[anchr]struct_a48 + +[anchr]struct_a49_ptr +[dist4]struct_a49 // num_bytes +[u4]0 // version +[dist8]struct_a50_ptr // struct_a49 +[anchr]struct_a49 + +[anchr]struct_a50_ptr +[dist4]struct_a50 // num_bytes +[u4]0 // version +[dist8]struct_a51_ptr // struct_a50 +[anchr]struct_a50 + +[anchr]struct_a51_ptr +[dist4]struct_a51 // num_bytes +[u4]0 // version +[dist8]struct_a52_ptr // struct_a51 +[anchr]struct_a51 + +[anchr]struct_a52_ptr +[dist4]struct_a52 // num_bytes +[u4]0 // version +[dist8]struct_a53_ptr // struct_a52 +[anchr]struct_a52 + +[anchr]struct_a53_ptr +[dist4]struct_a53 // num_bytes +[u4]0 // version +[dist8]struct_a54_ptr // struct_a53 +[anchr]struct_a53 + +[anchr]struct_a54_ptr +[dist4]struct_a54 // num_bytes +[u4]0 // version +[dist8]struct_a55_ptr // struct_a54 +[anchr]struct_a54 + +[anchr]struct_a55_ptr +[dist4]struct_a55 // num_bytes +[u4]0 // version +[dist8]struct_a56_ptr // struct_a55 +[anchr]struct_a55 + +[anchr]struct_a56_ptr +[dist4]struct_a56 // num_bytes +[u4]0 // version +[dist8]struct_a57_ptr // struct_a56 +[anchr]struct_a56 + +[anchr]struct_a57_ptr +[dist4]struct_a57 // num_bytes +[u4]0 // version +[dist8]struct_a58_ptr // struct_a57 +[anchr]struct_a57 + +[anchr]struct_a58_ptr +[dist4]struct_a58 // num_bytes +[u4]0 // version +[dist8]struct_a59_ptr // struct_a58 +[anchr]struct_a58 + +[anchr]struct_a59_ptr +[dist4]struct_a59 // num_bytes +[u4]0 // version +[dist8]struct_a60_ptr // struct_a59 +[anchr]struct_a59 + +[anchr]struct_a60_ptr +[dist4]struct_a60 // num_bytes +[u4]0 // version +[dist8]struct_a61_ptr // struct_a60 +[anchr]struct_a60 + +[anchr]struct_a61_ptr +[dist4]struct_a61 // num_bytes +[u4]0 // version +[dist8]struct_a62_ptr // struct_a61 +[anchr]struct_a61 + +[anchr]struct_a62_ptr +[dist4]struct_a62 // num_bytes +[u4]0 // version +[dist8]struct_a63_ptr // struct_a62 +[anchr]struct_a62 + +[anchr]struct_a63_ptr +[dist4]struct_a63 // num_bytes +[u4]0 // version +[dist8]struct_a64_ptr // struct_a63 +[anchr]struct_a63 + +[anchr]struct_a64_ptr +[dist4]struct_a64 // num_bytes +[u4]0 // version +[dist8]struct_a65_ptr // struct_a64 +[anchr]struct_a64 + +[anchr]struct_a65_ptr +[dist4]struct_a65 // num_bytes +[u4]0 // version +[dist8]struct_a66_ptr // struct_a65 +[anchr]struct_a65 + +[anchr]struct_a66_ptr +[dist4]struct_a66 // num_bytes +[u4]0 // version +[dist8]struct_a67_ptr // struct_a66 +[anchr]struct_a66 + +[anchr]struct_a67_ptr +[dist4]struct_a67 // num_bytes +[u4]0 // version +[dist8]struct_a68_ptr // struct_a67 +[anchr]struct_a67 + +[anchr]struct_a68_ptr +[dist4]struct_a68 // num_bytes +[u4]0 // version +[dist8]struct_a69_ptr // struct_a68 +[anchr]struct_a68 + +[anchr]struct_a69_ptr +[dist4]struct_a69 // num_bytes +[u4]0 // version +[dist8]struct_a70_ptr // struct_a69 +[anchr]struct_a69 + +[anchr]struct_a70_ptr +[dist4]struct_a70 // num_bytes +[u4]0 // version +[dist8]struct_a71_ptr // struct_a70 +[anchr]struct_a70 + +[anchr]struct_a71_ptr +[dist4]struct_a71 // num_bytes +[u4]0 // version +[dist8]struct_a72_ptr // struct_a71 +[anchr]struct_a71 + +[anchr]struct_a72_ptr +[dist4]struct_a72 // num_bytes +[u4]0 // version +[dist8]struct_a73_ptr // struct_a72 +[anchr]struct_a72 + +[anchr]struct_a73_ptr +[dist4]struct_a73 // num_bytes +[u4]0 // version +[dist8]struct_a74_ptr // struct_a73 +[anchr]struct_a73 + +[anchr]struct_a74_ptr +[dist4]struct_a74 // num_bytes +[u4]0 // version +[dist8]struct_a75_ptr // struct_a74 +[anchr]struct_a74 + +[anchr]struct_a75_ptr +[dist4]struct_a75 // num_bytes +[u4]0 // version +[dist8]struct_a76_ptr // struct_a75 +[anchr]struct_a75 + +[anchr]struct_a76_ptr +[dist4]struct_a76 // num_bytes +[u4]0 // version +[dist8]struct_a77_ptr // struct_a76 +[anchr]struct_a76 + +[anchr]struct_a77_ptr +[dist4]struct_a77 // num_bytes +[u4]0 // version +[dist8]struct_a78_ptr // struct_a77 +[anchr]struct_a77 + +[anchr]struct_a78_ptr +[dist4]struct_a78 // num_bytes +[u4]0 // version +[dist8]struct_a79_ptr // struct_a78 +[anchr]struct_a78 + +[anchr]struct_a79_ptr +[dist4]struct_a79 // num_bytes +[u4]0 // version +[dist8]struct_a80_ptr // struct_a79 +[anchr]struct_a79 + +[anchr]struct_a80_ptr +[dist4]struct_a80 // num_bytes +[u4]0 // version +[dist8]struct_a81_ptr // struct_a80 +[anchr]struct_a80 + +[anchr]struct_a81_ptr +[dist4]struct_a81 // num_bytes +[u4]0 // version +[dist8]struct_a82_ptr // struct_a81 +[anchr]struct_a81 + +[anchr]struct_a82_ptr +[dist4]struct_a82 // num_bytes +[u4]0 // version +[dist8]struct_a83_ptr // struct_a82 +[anchr]struct_a82 + +[anchr]struct_a83_ptr +[dist4]struct_a83 // num_bytes +[u4]0 // version +[dist8]struct_a84_ptr // struct_a83 +[anchr]struct_a83 + +[anchr]struct_a84_ptr +[dist4]struct_a84 // num_bytes +[u4]0 // version +[dist8]struct_a85_ptr // struct_a84 +[anchr]struct_a84 + +[anchr]struct_a85_ptr +[dist4]struct_a85 // num_bytes +[u4]0 // version +[dist8]struct_a86_ptr // struct_a85 +[anchr]struct_a85 + +[anchr]struct_a86_ptr +[dist4]struct_a86 // num_bytes +[u4]0 // version +[dist8]struct_a87_ptr // struct_a86 +[anchr]struct_a86 + +[anchr]struct_a87_ptr +[dist4]struct_a87 // num_bytes +[u4]0 // version +[dist8]struct_a88_ptr // struct_a87 +[anchr]struct_a87 + +[anchr]struct_a88_ptr +[dist4]struct_a88 // num_bytes +[u4]0 // version +[dist8]struct_a89_ptr // struct_a88 +[anchr]struct_a88 + +[anchr]struct_a89_ptr +[dist4]struct_a89 // num_bytes +[u4]0 // version +[dist8]struct_a90_ptr // struct_a89 +[anchr]struct_a89 + +[anchr]struct_a90_ptr +[dist4]struct_a90 // num_bytes +[u4]0 // version +[dist8]struct_a91_ptr // struct_a90 +[anchr]struct_a90 + +[anchr]struct_a91_ptr +[dist4]struct_a91 // num_bytes +[u4]0 // version +[dist8]struct_a92_ptr // struct_a91 +[anchr]struct_a91 + +[anchr]struct_a92_ptr +[dist4]struct_a92 // num_bytes +[u4]0 // version +[dist8]struct_a93_ptr // struct_a92 +[anchr]struct_a92 + +[anchr]struct_a93_ptr +[dist4]struct_a93 // num_bytes +[u4]0 // version +[dist8]struct_a94_ptr // struct_a93 +[anchr]struct_a93 + +[anchr]struct_a94_ptr +[dist4]struct_a94 // num_bytes +[u4]0 // version +[dist8]struct_a95_ptr // struct_a94 +[anchr]struct_a94 + +[anchr]struct_a95_ptr +[dist4]struct_a95 // num_bytes +[u4]0 // version +[dist8]struct_a96_ptr // struct_a95 +[anchr]struct_a95 + +[anchr]struct_a96_ptr +[dist4]struct_a96 // num_bytes +[u4]0 // version +[dist8]struct_a97_ptr // struct_a96 +[anchr]struct_a96 + +[anchr]struct_a97_ptr +[dist4]struct_a97 // num_bytes +[u4]0 // version +[dist8]struct_a98_ptr // struct_a97 +[anchr]struct_a97 + +[anchr]struct_a98_ptr +[dist4]struct_a98 // num_bytes +[u4]0 // version +[dist8]struct_a99_ptr // struct_a98 +[anchr]struct_a98 + +[anchr]struct_a99_ptr +[dist4]struct_a99 // num_bytes +[u4]0 // version +[dist8]struct_a100_ptr // struct_a99 +[anchr]struct_a99 + +[anchr]struct_a100_ptr +[dist4]struct_a100 // num_bytes +[u4]0 // version +[u8]0 // struct_a100 +[anchr]struct_a100 diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.expected new file mode 100644 index 0000000000..81d6cd84e4 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MAX_RECURSION_DEPTH
\ No newline at end of file diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data new file mode 100644 index 0000000000..b6c201a948 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data @@ -0,0 +1,18 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]1 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method1_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method1_params + +[anchr]param0_ptr +[dist4]struct_a // num_bytes +[u4]0 // version +[u8]1234 // i +[anchr]struct_a diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data new file mode 100644 index 0000000000..ec39b71d03 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data @@ -0,0 +1,20 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]1 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method1_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method1_params + +[u1]0 // Causes the following struct to be misaligned. + +[anchr]param0_ptr +[dist4]struct_a // num_bytes +[u4]0 // version +[u8]1234 // i +[anchr]struct_a diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected new file mode 100644 index 0000000000..acca999b3f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MISALIGNED_OBJECT diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data new file mode 100644 index 0000000000..6d9205093b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data @@ -0,0 +1,13 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]1 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method1_params // num_bytes +[u4]0 // version +[u8]0xFFFFFFFFFFFFFFFF // param0: Test whether decoding the pointer causes + // overflow. +[anchr]method1_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected new file mode 100644 index 0000000000..23abb8cbb7 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data new file mode 100644 index 0000000000..569733bcb3 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data @@ -0,0 +1,12 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]1 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method1_params // num_bytes +[u4]0 // version +[u8]0 // param0: An unexpected null pointer. +[anchr]method1_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.data new file mode 100644 index 0000000000..8ec608bcd1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.data @@ -0,0 +1,57 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]20 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method20_params // num_bytes +[u4]0 // version +[dist8]map_data_ptr // param0 +[anchr]method20_params + +[anchr]map_data_ptr +[dist4]map_data_struct_header // num_bytes +[u4]0 // version +[dist8]key_array_ptr +[dist8]value_array_ptr +[anchr]map_data_struct_header + +[anchr]key_array_ptr +[dist4]key_array_member // num_bytes +[u4]2 // num_elements +[dist8]key_struct_b_1 +[dist8]key_struct_b_2 +[anchr]key_array_member + +[anchr]key_struct_b_1 +[dist4]key_struct_b_1_member // num_bytes +[u4]0 // version +[dist8]key_struct_a_1 // struct_a +[anchr]key_struct_b_1_member + +[anchr]key_struct_a_1 +[dist4]key_struct_a_1_member // num_bytes +[u4]0 // version +[u8]1234 // i +[anchr]key_struct_a_1_member + +[anchr]key_struct_b_2 +[dist4]key_struct_b_2_member // num_bytes +[u4]0 // version +[dist8]key_struct_a_2 // struct_a +[anchr]key_struct_b_2_member + +[anchr]key_struct_a_2 +[dist4]key_struct_a_2_member // num_bytes +[u4]0 // version +[u8]5678 // i +[anchr]key_struct_a_2_member + +[anchr]value_array_ptr +[dist4]value_array_member // num_bytes +[u4]2 // num_elements +[u1]1 +[u1]2 +[anchr]value_array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.data new file mode 100644 index 0000000000..d3ae88e3e6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.data @@ -0,0 +1,13 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]21 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method21_params // num_bytes +[u4]0 // version +[u4]7 // param0. All values are valid for an extensible enum. +[u4]0 // padding +[anchr]method21_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.data new file mode 100644 index 0000000000..414785ceb1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.data @@ -0,0 +1,14 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]22 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method21_params // num_bytes +[u4]0 // version +[u4]0 // param0. No values are valid for an empty + // non-extensible enum. +[u4]0 // padding +[anchr]method21_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.expected new file mode 100644 index 0000000000..9ef4ce3fdd --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNKNOWN_ENUM_VALUE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data new file mode 100644 index 0000000000..40719f5708 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data @@ -0,0 +1,34 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]2 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method2_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method2_params + +[anchr]param0_ptr +[dist4]struct_b // num_bytes +[u4]0 // version +[dist8]struct_a_ptr // struct_a +[anchr]struct_b + +[u8]0 // Having extra bytes in the middle is okay if the following objects are + // still properly alignmented. + +[anchr]struct_a_ptr +[dist4]struct_a_member // num_bytes +[u4]0 // version +[u8]12345 // i +[anchr]struct_a_member + +[anchr]param1_ptr +[dist4]struct_a_param // num_bytes +[u4]0 // version +[u8]67890 // i +[anchr]struct_a_param diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data new file mode 100644 index 0000000000..ef6525b008 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data @@ -0,0 +1,27 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]2 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method2_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method2_params + +[anchr]param0_ptr +[dist4]struct_b // num_bytes +[u4]0 // version +[dist8]struct_a_ptr // struct_a +[anchr]struct_b + +// There are two pointers pointing to the same struct. +[anchr]struct_a_ptr +[anchr]param1_ptr +[dist4]struct_a // num_bytes +[u4]0 // version +[u8]12345 // i +[anchr]struct_a diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data new file mode 100644 index 0000000000..58b25a1aa9 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data @@ -0,0 +1,32 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]2 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method2_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method2_params + +[anchr]param0_ptr +[dist4]struct_b // num_bytes +[u4]0 // version +[dist8]struct_a_ptr // struct_a +[anchr]struct_b + +[anchr]struct_a_ptr +[dist4]struct_a_member // num_bytes +[u4]0 // version + +[anchr]param1_ptr +// The following |num_bytes| and |version| fields are also the |i| field of the +// previous struct. +[dist4]struct_a_param // num_bytes +[u4]0 // version +[anchr]struct_a_member +[u8]67890 // i +[anchr]struct_a_param diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data new file mode 100644 index 0000000000..3038ed8097 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data @@ -0,0 +1,34 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]2 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method2_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method2_params + +[anchr]param0_ptr +[dist4]struct_b // num_bytes +[u4]0 // version +[dist8]struct_a_ptr // struct_a +[anchr]struct_b + +// The following two structs are arranged in wrong order. + +[anchr]param1_ptr +[dist4]struct_a_param // num_bytes +[u4]0 // version +[u8]67890 // i +[anchr]struct_a_param + +[anchr]struct_a_ptr +[dist4]struct_a_member // num_bytes +[u4]0 // version +[u8]12345 // i +[anchr]struct_a_member + diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data new file mode 100644 index 0000000000..681463663f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data @@ -0,0 +1,18 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[u4]0xFFFFFFFF // num_bytes: Test whether a huge value will cause overflow. +[u4]12 // num_elements +[b]01010101 +[b]00001111 diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data new file mode 100644 index 0000000000..45021c0634 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data @@ -0,0 +1,18 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[u4]7 // num_bytes: Less than the size of array header. +[u4]12 // num_elements +[b]01010101 +[b]00001111 diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected new file mode 100644 index 0000000000..5a1ec4ef20 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data new file mode 100644 index 0000000000..3d3870298f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data @@ -0,0 +1,20 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[dist4]array // num_bytes: Less than the size needed (array header + 12 boolean + // values). +[u4]12 // num_elements +[b]01010101 +[anchr]array +[b]00001111 diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected new file mode 100644 index 0000000000..5a1ec4ef20 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data new file mode 100644 index 0000000000..2f9e091b04 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data @@ -0,0 +1,13 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[u8]0xFFFFFFFFFFFFFFFF // param0: Test whether decoding the pointer causes + // overflow. +[anchr]method3_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected new file mode 100644 index 0000000000..23abb8cbb7 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data new file mode 100644 index 0000000000..ad26763f5f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data @@ -0,0 +1,19 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[dist4]array // num_bytes +[u4]12 // num_elements +[b]01010101 +[b]00001111 +[anchr]array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data new file mode 100644 index 0000000000..d7734589ce --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data @@ -0,0 +1,16 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[u4]16 // num_bytes +[u1]0 // num_elements: Incomplete array. diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data new file mode 100644 index 0000000000..ca462a5758 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data @@ -0,0 +1,15 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[anchr]param0_ptr +[u4]16 // num_bytes: Incomplete array header. diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data new file mode 100644 index 0000000000..5adfbbaa97 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data @@ -0,0 +1,21 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method3_params + +[u2]0 // Causes the following array to be misaligned. + +[anchr]param0_ptr +[dist4]array // num_bytes +[u4]12 // num_elements +[b]01010101 +[b]00001111 +[anchr]array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected new file mode 100644 index 0000000000..acca999b3f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MISALIGNED_OBJECT diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data new file mode 100644 index 0000000000..0f96c4bd58 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data @@ -0,0 +1,12 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]3 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method3_params // num_bytes +[u4]0 // version +[u8]0 // param0: An unexpected null pointer. +[anchr]method3_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data new file mode 100644 index 0000000000..84943d29ec --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data @@ -0,0 +1,34 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]4 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method4_params // num_bytes: Larger than what we know is okay. +[u4]3 // version: Larger than what we know is okay. +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[u8]0 // unknown +[anchr]method4_params + +[anchr]param0_ptr +[dist4]struct_c // num_bytes +[u4]0 // version +[dist8]array_ptr // array +[anchr]struct_c + +[anchr]array_ptr +[dist4]array_member // num_bytes +[u4]3 // num_elements +0 1 2 +[anchr]array_member + +[u4]0 [u1]0 // Padding to make the next array aligned properly. + +[anchr]param1_ptr +[dist4]array_param // num_bytes +[u4]10 // num_elements +0 1 2 3 4 5 6 7 8 9 +[anchr]array_param diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data new file mode 100644 index 0000000000..2f84185552 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data @@ -0,0 +1,26 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]4 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method4_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method4_params + +[anchr]param0_ptr +[dist4]struct_c // num_bytes +[u4]0 // version +[dist8]array_ptr // array +[anchr]struct_c + +[anchr]param1_ptr +[anchr]array_ptr +[dist4]array_member // num_bytes +[u4]3 // num_elements +0 1 2 +[anchr]array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data new file mode 100644 index 0000000000..d863e64a12 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data @@ -0,0 +1,32 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]4 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method4_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method4_params + +[anchr]param0_ptr +[dist4]struct_c // num_bytes +[u4]0 // version +[dist8]array_ptr // array +[anchr]struct_c + +[anchr]array_ptr +[dist4]array_member // num_bytes +[u4]3 // num_elements + +[anchr]param1_ptr +// The first three bytes of |num_bytes| are also the elements of the previous +// array. +[dist4]array_param // num_bytes +[u4]10 // num_elements +0 1 2 3 4 5 6 7 8 9 +[anchr]array_param +[anchr]array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data new file mode 100644 index 0000000000..b61423a6d7 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data @@ -0,0 +1,35 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]4 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method4_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method4_params + +[anchr]param0_ptr +[dist4]struct_c // num_bytes +[u4]0 // version +[dist8]array_ptr // array +[anchr]struct_c + +// The following two arrays are arranged in wrong order. + +[anchr]param1_ptr +[dist4]array_param // num_bytes +[u4]10 // num_elements +0 1 2 3 4 5 6 7 8 9 +[anchr]array_param + +[u4]0 [u2]0 // Padding to make the next array aligned properly. + +[anchr]array_ptr +[dist4]array_member // num_bytes +[u4]3 // num_elements +0 1 2 +[anchr]array_member diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected new file mode 100644 index 0000000000..779df88cf6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data new file mode 100644 index 0000000000..dcec8952d1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data @@ -0,0 +1,37 @@ +[handles]10 // Larger than the number of handles that we know about is okay. + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]5 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method5_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[u4]4 // param1 +[u4]0 // padding +[anchr]method5_params + +[anchr]param0_ptr +[dist4]struct_e // num_bytes +[u4]0 // version +[dist8]struct_d_ptr // struct_d +[u4]3 // data_pipe_consumer +[u4]0 // padding +[anchr]struct_e + +[anchr]struct_d_ptr +[dist4]struct_d // num_bytes +[u4]0 // version +[dist8]message_pipes_ptr // message_pipes +[anchr]struct_d + +[anchr]message_pipes_ptr +[dist4]message_pipe_array // num_bytes +[u4]2 // num_elements +[u4]0 +[u4]1 +[anchr]message_pipe_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data new file mode 100644 index 0000000000..d4a82ed174 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data @@ -0,0 +1,38 @@ +[handles]10 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]5 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method5_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[u4]10 // param1: It is outside of the valid encoded handle + // range [0, 10). +[u4]0 // padding +[anchr]method5_params + +[anchr]param0_ptr +[dist4]struct_e // num_bytes +[u4]0 // version +[dist8]struct_d_ptr // struct_d +[u4]3 // data_pipe_consumer +[u4]0 // padding +[anchr]struct_e + +[anchr]struct_d_ptr +[dist4]struct_d // num_bytes +[u4]0 // version +[dist8]message_pipes_ptr // message_pipes +[anchr]struct_d + +[anchr]message_pipes_ptr +[dist4]message_pipe_array // num_bytes +[u4]2 // num_elements +[u4]0 +[u4]1 +[anchr]message_pipe_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected new file mode 100644 index 0000000000..eef8e38a84 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_HANDLE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data new file mode 100644 index 0000000000..9ee7a48c9c --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data @@ -0,0 +1,37 @@ +[handles]10 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]5 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method5_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[u4]4 // param1 +[u4]0 // padding +[anchr]method5_params + +[anchr]param0_ptr +[dist4]struct_e // num_bytes +[u4]0 // version +[dist8]struct_d_ptr // struct_d +[u4]4 // data_pipe_consumer: The same value as |param1| above. +[u4]0 // padding +[anchr]struct_e + +[anchr]struct_d_ptr +[dist4]struct_d // num_bytes +[u4]0 // version +[dist8]message_pipes_ptr // message_pipes +[anchr]struct_d + +[anchr]message_pipes_ptr +[dist4]message_pipe_array // num_bytes +[u4]2 // num_elements +[u4]0 +[u4]1 +[anchr]message_pipe_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected new file mode 100644 index 0000000000..eef8e38a84 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_HANDLE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data new file mode 100644 index 0000000000..cb01caeb1a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data @@ -0,0 +1,37 @@ +[handles]10 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]5 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method5_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[u4]4 // param1 +[u4]0 // padding +[anchr]method5_params + +[anchr]param0_ptr +[dist4]struct_e // num_bytes +[u4]0 // version +[dist8]struct_d_ptr // struct_d +[u4]3 // data_pipe_consumer +[u4]0 // padding +[anchr]struct_e + +[anchr]struct_d_ptr +[dist4]struct_d // num_bytes +[u4]0 // version +[dist8]message_pipes_ptr // message_pipes +[anchr]struct_d + +[anchr]message_pipes_ptr +[dist4]message_pipe_array // num_bytes +[u4]2 // num_elements +[u4]1 // The two message pipe handles have the same value. +[u4]1 +[anchr]message_pipe_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected new file mode 100644 index 0000000000..eef8e38a84 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_HANDLE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data new file mode 100644 index 0000000000..b06ae0a46b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data @@ -0,0 +1,36 @@ +[handles]5 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]5 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method5_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[u4]4 // param1 +[u4]0 // padding +[anchr]method5_params + +[anchr]param0_ptr +[dist4]struct_e // num_bytes +[u4]0 // version +[dist8]struct_d_ptr // struct_d +[s4]-1 // data_pipe_consumer: An unexpected invalid handle. +[u4]0 // padding +[anchr]struct_e + +[anchr]struct_d_ptr +[dist4]struct_d // num_bytes +[u4]0 // version +[dist8]message_pipes_ptr // message_pipes +[anchr]struct_d + +[anchr]message_pipes_ptr +[dist4]message_pipe_array // num_bytes +[u4]1 // num_elements +[u4]2 +[anchr]message_pipe_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected new file mode 100644 index 0000000000..6768236f6e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data new file mode 100644 index 0000000000..f641de0561 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data @@ -0,0 +1,38 @@ +[handles]10 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]5 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method5_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[u4]9 // param1 +[u4]0 // padding +[anchr]method5_params + +[anchr]param0_ptr +[dist4]struct_e // num_bytes +[u4]0 // version +[dist8]struct_d_ptr // struct_d +[u4]1 // data_pipe_consumer: It is smaller than those handles + // in |message_pipe_array|, which is wrong. +[u4]0 // padding +[anchr]struct_e + +[anchr]struct_d_ptr +[dist4]struct_d // num_bytes +[u4]0 // version +[dist8]message_pipes_ptr // message_pipes +[anchr]struct_d + +[anchr]message_pipes_ptr +[dist4]message_pipe_array // num_bytes +[u4]2 // num_elements +[u4]3 +[u4]4 +[anchr]message_pipe_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected new file mode 100644 index 0000000000..eef8e38a84 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_ILLEGAL_HANDLE diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data new file mode 100644 index 0000000000..fb3f862f4c --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data @@ -0,0 +1,24 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]6 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method6_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method6_params + +[anchr]param0_ptr +[dist4]array_param // num_bytes +[u4]1 // num_elements +[dist8]element_ptr +[anchr]array_param + +[anchr]element_ptr +[dist4]array_element // num_bytes +[u4]10 // num_elements +0 1 2 3 4 5 6 7 8 9 +[anchr]array_element diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data new file mode 100644 index 0000000000..c8cacf0b2e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data @@ -0,0 +1,24 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]6 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method6_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method6_params + +[anchr]param0_ptr +[dist4]array_param // num_bytes +[u4]1 // num_elements +[dist8]element_ptr +[anchr]array_param + +[anchr]element_ptr +[dist4]array_element // num_bytes: It is insufficient to store 12 elements. +[u4]12 // num_elements +0 1 2 3 4 5 6 7 8 9 +[anchr]array_element diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected new file mode 100644 index 0000000000..5a1ec4ef20 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data new file mode 100644 index 0000000000..c9726763e1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data @@ -0,0 +1,40 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]7 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method7_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method7_params + +[anchr]param0_ptr +[dist4]struct_f // num_bytes +[u4]0 // version +[dist8]array_ptr // fixed_size_array +[anchr]struct_f + +[anchr]array_ptr +[dist4]array_member // num_bytes +[u4]3 // num_elements +0 1 2 +[anchr]array_member + +[u4]0 [u1]0 // Padding to make the next array aligned properly. + +[anchr]param1_ptr +[dist4]array_param // num_bytes +[u4]2 // num_elements +[u8]0 // A null pointer, which is okay. +[dist8]array_element_ptr +[anchr]array_param + +[anchr]array_element_ptr +[dist4]array_element // num_bytes +[u4]3 // num_elements +0 1 2 +[anchr]array_element diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data new file mode 100644 index 0000000000..4d25cf24a2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data @@ -0,0 +1,26 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]7 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method7_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method7_params + +[anchr]param0_ptr +[dist4]struct_f // num_bytes +[u4]0 // version +[u8]0 // fixed_size_array: An unexpected null pointer. +[anchr]struct_f + +[anchr]param1_ptr +[dist4]array_param // num_bytes +[u4]2 // num_elements +[u8]0 // A null pointer, which is okay. +[u8]0 // A null pointer, which is okay. +[anchr]array_param diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.data new file mode 100644 index 0000000000..cee6e1404f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.data @@ -0,0 +1,34 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]7 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method7_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method7_params + +[anchr]param0_ptr +[dist4]struct_f // num_bytes +[u4]0 // version +[dist8]array_ptr // fixed_size_array +[anchr]struct_f + +[anchr]array_ptr +[dist4]array_member // num_bytes +[u4]2 // num_elements: Too few elements. +0 1 +[anchr]array_member + +[u4]0 [u1]0 [u1]0 // Padding for alignment of next array. + +[anchr]param1_ptr +[dist4]array_param // num_bytes +[u4]2 // num_elements +[u8]0 // A null pointer, which is okay. +[u8]0 // A null pointer, which is okay. +[anchr]array_param diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.expected new file mode 100644 index 0000000000..5a1ec4ef20 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.data new file mode 100644 index 0000000000..3095a73893 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.data @@ -0,0 +1,40 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]7 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method7_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[dist8]param1_ptr // param1 +[anchr]method7_params + +[anchr]param0_ptr +[dist4]struct_f // num_bytes +[u4]0 // version +[dist8]array_ptr // fixed_size_array +[anchr]struct_f + +[anchr]array_ptr +[dist4]array_member // num_bytes +[u4]3 // num_elements +0 1 3 +[anchr]array_member + +[u4]0 [u1]0 // Padding for alignment of next array. + +[anchr]param1_ptr +[dist4]array_param // num_bytes +[u4]2 // num_elements +[dist8]array_element_ptr +[u8]0 // A null pointer, which is okay. +[anchr]array_param + +[anchr]array_element_ptr +[dist4]array_element // num_bytes +[u4]4 // num_elements: Too many elements. +0 1 2 3 +[anchr]array_element diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.expected new file mode 100644 index 0000000000..5a1ec4ef20 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data new file mode 100644 index 0000000000..b19e141b0b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data @@ -0,0 +1,21 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]8 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method8_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method8_params + +[anchr]param0_ptr +[dist4]array_param // num_bytes +[u4]0x20000001 // num_elements: The corresponding array size should be + // 0x20000001 * 8 + 8 = 0x100000010 which is + // 2^32 + 16 (base-10), while |num_bytes| is a 32-bit + // unsigned integer and its value is 16. +[u8]0 +[anchr]array_param diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected new file mode 100644 index 0000000000..5a1ec4ef20 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data new file mode 100644 index 0000000000..08c4bc3121 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data @@ -0,0 +1,32 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]8 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method8_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method8_params + +[anchr]param0_ptr +[dist4]array_param // num_bytes +[u4]3 // num_elements +[u8]0 // A null pointer, which is okay. +[dist8]nested_array_ptr +[u8]0 // A null pointer, which is okay. +[anchr]array_param + +[anchr]nested_array_ptr +[dist4]nested_array // num_bytes +[u4]1 // num_elements +[dist8]string_ptr +[anchr]nested_array + +[anchr]string_ptr +[dist4]string // num_bytes +[u4]5 // num_elements +0 1 2 3 4 +[anchr]string diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data new file mode 100644 index 0000000000..03f2a10cb5 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data @@ -0,0 +1,12 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]8 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method8_params // num_bytes +[u4]0 // version +[u8]0 // param0: An unexpected null pointer. +[anchr]method8_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data new file mode 100644 index 0000000000..b1b4462051 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data @@ -0,0 +1,33 @@ +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]8 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method8_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method8_params + +[anchr]param0_ptr +[dist4]array_param // num_bytes +[u4]3 // num_elements +[u8]0 // A null pointer, which is okay. +[dist8]nested_array_ptr +[u8]0 // A null pointer, which is okay. +[anchr]array_param + +[anchr]nested_array_ptr +[dist4]nested_array // num_bytes +[u4]2 // num_elements +[dist8]string_ptr +[u8]0 // An unexpected null pointer. +[anchr]nested_array + +[anchr]string_ptr +[dist4]string // num_bytes +[u4]5 // num_elements +0 1 2 3 4 +[anchr]string diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data new file mode 100644 index 0000000000..6ed00092c6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data @@ -0,0 +1,36 @@ +[handles]4 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]9 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method9_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method9_params + +[anchr]param0_ptr +[dist4]array_param // num_bytes +[u4]2 // num_elements +[dist8]nested_array_ptr_0 +[dist8]nested_array_ptr_1 +[anchr]array_param + +[anchr]nested_array_ptr_0 +[dist4]nested_array_0 // num_bytes +[u4]2 // num_elements +[u4]0 +[s4]-1 // An invalid handle, which is okay. +[anchr]nested_array_0 + +[anchr]nested_array_ptr_1 +[dist4]nested_array_1 // num_bytes +[u4]3 // num_elements +[u4]2 +[s4]-1 // An invalid handle, which is okay. +[u4]3 +[anchr]nested_array_1 diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data new file mode 100644 index 0000000000..90feced0b6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data @@ -0,0 +1,14 @@ +[handles]4 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]9 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method9_params // num_bytes +[u4]0 // version +[u8]0 // param0: A null pointer, which is okay. +[anchr]method9_params diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data new file mode 100644 index 0000000000..e87fbcba31 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data @@ -0,0 +1,27 @@ +[handles]4 + +[dist4]message_header // num_bytes +[u4]0 // version +[u4]0 // interface ID +[u4]9 // name +[u4]0 // flags +[u4]0 // padding +[anchr]message_header + +[dist4]method9_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method9_params + +[anchr]param0_ptr +[dist4]array_param // num_bytes +[u4]2 // num_elements +[dist8]nested_array_ptr_0 +[u8]0 // An unexpected null pointer. +[anchr]array_param + +[anchr]nested_array_ptr_0 +[dist4]nested_array_0 // num_bytes +[u4]1 // num_elements +[u4]0 +[anchr]nested_array_0 diff --git a/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected new file mode 100644 index 0000000000..95d8db01bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_NULL_POINTER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.data new file mode 100644 index 0000000000..7da356fc5e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.data @@ -0,0 +1,19 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0 // name +[u4]2 // flags kMessageIsResponse +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header + +[dist4]method0_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method0_params + +[anchr]param0_ptr +[dist4]uint8_array // num_bytes +[u4]1 // num_elements +[u1]0 +[anchr]uint8_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.data new file mode 100644 index 0000000000..bdcfc46853 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.data @@ -0,0 +1,19 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0 // name +[u4]2 // flags kMessageIsResponse +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header + +[dist4]method0_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method0_params + +[anchr]param0_ptr +[dist4]uint8_array // num_bytes +[u4]2 // num_elements: The size is too small to hold 2 elements. +[u1]0 +[anchr]uint8_array diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.expected new file mode 100644 index 0000000000..5a1ec4ef20 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.data new file mode 100644 index 0000000000..a1fe69d82e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.data @@ -0,0 +1,20 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0 // name +[u4]1 // flags kMessageExpectsResponse +[u4]0 // padding +[u8]7 // request_id +[anchr]message_header + +[dist4]method0_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method0_params + +[anchr]param0_ptr +[dist4]basic_struct // num_bytes +[u4]0 // version +[s4]-1 // a +[u4]0 // padding +[anchr]basic_struct diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.expected new file mode 100644 index 0000000000..7ef22e9a43 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.expected @@ -0,0 +1 @@ +PASS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.data new file mode 100644 index 0000000000..e689adb670 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.data @@ -0,0 +1,17 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0 // name +[u4]1 // flags kMessageExpectsResponse +[u4]0 // padding +[u8]7 // request_id +[anchr]message_header + +[dist4]method0_params // num_bytes +[u4]0 // version +[dist8]param0_ptr // param0 +[anchr]method0_params + +[anchr]param0_ptr +[u4]0 // num_bytes: The struct size is too small. +[u4]0 // version diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.expected new file mode 100644 index 0000000000..25aceeea5a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data new file mode 100644 index 0000000000..7198afa30e --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data @@ -0,0 +1,8 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]0xffffffff // name +[u4]3 // flags: This combination is illegal. +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected new file mode 100644 index 0000000000..c33fde327b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.data b/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.data new file mode 100644 index 0000000000..72835098f7 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.data @@ -0,0 +1,8 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]1 // name: Method1 does not have a response message. +[u4]2 // flags: kMessageIsResponse +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.expected b/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.expected new file mode 100644 index 0000000000..65a48b3c06 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD
\ No newline at end of file diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.data b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.data new file mode 100644 index 0000000000..d1d8d3f157 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.data @@ -0,0 +1,8 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]12 // name +[u4]0 // flags: kMessageIsResponse is not set in a response. +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.expected b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.expected new file mode 100644 index 0000000000..c33fde327b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.data b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.data new file mode 100644 index 0000000000..091b68c235 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.data @@ -0,0 +1,8 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]12 // name +[u4]1 // flags: kMessageExpectsResponse is set in a response. +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.expected b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.expected new file mode 100644 index 0000000000..c33fde327b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.data b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.data new file mode 100644 index 0000000000..0eda287e32 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.data @@ -0,0 +1,8 @@ +[dist4]message_header // num_bytes +[u4]1 // version +[u4]0 // interface ID +[u4]11 // name: Method11 does not have a response message. +[u4]2 // flags: kMessageIsResponse +[u4]0 // padding +[u8]1 // request_id +[anchr]message_header diff --git a/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.expected b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.expected new file mode 100644 index 0000000000..65a48b3c06 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.expected @@ -0,0 +1 @@ +VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD
\ No newline at end of file diff --git a/mojo/public/interfaces/bindings/tests/echo.mojom b/mojo/public/interfaces/bindings/tests/echo.mojom new file mode 100644 index 0000000000..56c6063010 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/echo.mojom @@ -0,0 +1,12 @@ +// 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. + +module test.echo.mojom; + +import "echo_import.mojom"; + +interface Echo { + EchoPoint(test.echo_import.mojom.Point point) + => (test.echo_import.mojom.Point result); +}; diff --git a/mojo/public/interfaces/bindings/tests/echo_import.mojom b/mojo/public/interfaces/bindings/tests/echo_import.mojom new file mode 100644 index 0000000000..a024ce2ff1 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/echo_import.mojom @@ -0,0 +1,10 @@ +// 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. + +module test.echo_import.mojom; + +struct Point { + int32 x; + int32 y; +}; diff --git a/mojo/public/interfaces/bindings/tests/math_calculator.mojom b/mojo/public/interfaces/bindings/tests/math_calculator.mojom new file mode 100644 index 0000000000..7d1b171e1a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/math_calculator.mojom @@ -0,0 +1,12 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.math"] +module math; + +interface Calculator { + Clear@0() => (double value@0); + Add@1(double value@0) => (double value@0); + Multiply@2(double value@0) => (double value@0); +}; diff --git a/mojo/public/interfaces/bindings/tests/no_module.mojom b/mojo/public/interfaces/bindings/tests/no_module.mojom new file mode 100644 index 0000000000..f380011290 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/no_module.mojom @@ -0,0 +1,9 @@ +// 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. + +// Entities without module + +enum EnumWithoutModule { + A +}; diff --git a/mojo/public/interfaces/bindings/tests/ping_service.mojom b/mojo/public/interfaces/bindings/tests/ping_service.mojom new file mode 100644 index 0000000000..ba6ad3d66a --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/ping_service.mojom @@ -0,0 +1,14 @@ +// 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. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.ping"] +module mojo.test; + +interface PingService { + Ping() => (); +}; + +interface EchoService { + Echo(string test_data) => (string echo_data); +}; diff --git a/mojo/public/interfaces/bindings/tests/rect.mojom b/mojo/public/interfaces/bindings/tests/rect.mojom new file mode 100644 index 0000000000..4ecc4d9660 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/rect.mojom @@ -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. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.test_structs"] +module mojo.test; + +struct Rect { + int32 x; + int32 y; + int32 width; + int32 height; +}; + +// A copy of Rect that is typemapped differently in the chromium and blink +// variants. +struct TypemappedRect { + int32 x; + int32 y; + int32 width; + int32 height; +}; + +// A copy of Rect that is typemapped to the same custom type in the chromium and +// blink variants. +struct SharedTypemappedRect { + int32 x; + int32 y; + int32 width; + int32 height; +};
\ No newline at end of file diff --git a/mojo/public/interfaces/bindings/tests/regression_tests.mojom b/mojo/public/interfaces/bindings/tests/regression_tests.mojom new file mode 100644 index 0000000000..d87a3ad65f --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/regression_tests.mojom @@ -0,0 +1,76 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Module containing entities for regression tests of the generator. Entities +// must never be modified, instead new entity must be added to add new tests. +[JavaPackage="org.chromium.mojo.bindings.test.mojom.regression_tests"] +module regression_tests; + +interface CheckMethodWithEmptyResponse { +WithouParameterAndEmptyResponse() => (); +WithParameterAndEmptyResponse(bool b) => (); +}; + +interface CheckNameCollision { +WithNameCollision(bool message, bool response) => (bool message, bool response); +}; + +enum EnumWithReference { + k_STEREO_AND_KEYBOARD_MIC = 30, + k_MAX = k_STEREO_AND_KEYBOARD_MIC +}; + +enum EnumWithLowercase { + PlanarF16, + PlanarF32 +}; + +enum EnumWithNumbers { + k_2_1 = 4 +}; + +enum EnumWithK { + K = 0 +}; + +struct Edge { + Vertex? v; +}; + +struct Vertex { + EmptyStruct? e; +}; + +struct EmptyStruct { +}; + +struct A { + B? b; +}; + +struct B { + A? a; +}; + +// Previously, a field or parameter called |handles| would be shadowed by a +// method parameter in generated C++ bindings code. +struct HandlesNameCollisionStruct { + EmptyStruct handles; +}; + +struct HandlesHandleNameCollisionStruct { + handle handles; +}; + +union HandlesNameCollisionUnion { + int32 handles; +}; + +struct HandlesUnionNameCollisionStruct { + HandlesNameCollisionUnion handles; +}; + +interface HandlesNameCollisionInterface { + Method(EmptyStruct handles) => (handle handles); +}; diff --git a/mojo/public/interfaces/bindings/tests/sample_factory.mojom b/mojo/public/interfaces/bindings/tests/sample_factory.mojom new file mode 100644 index 0000000000..ade3bf37d6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/sample_factory.mojom @@ -0,0 +1,41 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample"] +module sample; + +import "sample_import.mojom"; + +// This sample shows how handles to MessagePipes can be sent as both parameters +// to methods as well as fields on structs. + +struct Request { + int32 x; + handle<message_pipe>? pipe; + array<handle<message_pipe>>? more_pipes; + + // Interfaces can be used as members. + imported.ImportedInterface? obj; +}; + +struct Response { + int32 x; + handle<message_pipe>? pipe; +}; + +interface NamedObject { + SetName(string name); + GetName() => (string name); +}; + +interface Factory { + DoStuff(Request request, handle<message_pipe>? pipe) => + (Response response, string text); + DoStuff2(handle<data_pipe_consumer> pipe) => (string text); + CreateNamedObject(NamedObject& obj); + RequestImportedInterface( + imported.ImportedInterface& obj) => (imported.ImportedInterface& obj); + TakeImportedInterface( + imported.ImportedInterface obj) => (imported.ImportedInterface obj); +}; diff --git a/mojo/public/interfaces/bindings/tests/sample_import.mojom b/mojo/public/interfaces/bindings/tests/sample_import.mojom new file mode 100644 index 0000000000..d28cb7b4db --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/sample_import.mojom @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.imported"] +module imported; + +// This sample just defines some types that are imported into +// sample_service.mojom, to show how import works. + +enum Shape { + RECTANGLE = 1, + CIRCLE, + TRIANGLE, + LAST = TRIANGLE, +}; + +// These enum values should not interfere with those of Shape above. +enum AnotherShape { + RECTANGLE = 10, + CIRCLE, + TRIANGLE, +}; + +enum YetAnotherShape { + RECTANGLE = 20, + CIRCLE, + TRIANGLE, +}; + +struct Point { + int32 x; + int32 y; +}; + +interface ImportedInterface { + DoSomething(); +}; diff --git a/mojo/public/interfaces/bindings/tests/sample_import2.mojom b/mojo/public/interfaces/bindings/tests/sample_import2.mojom new file mode 100644 index 0000000000..ca4e81c0bb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/sample_import2.mojom @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.imported"] +module imported; + +import "sample_import.mojom"; + +// This sample adds more types and constants to the "imported" namespace, +// to test a bug with importing multiple modules with the same namespace. + +enum Color { + RED, + BLACK, +}; + +struct Size { + int32 width; + int32 height; +}; + +struct Thing { + imported.Shape shape = RECTANGLE; + imported.Color color = Color.BLACK; + Point location; + Size size; +}; diff --git a/mojo/public/interfaces/bindings/tests/sample_interfaces.mojom b/mojo/public/interfaces/bindings/tests/sample_interfaces.mojom new file mode 100644 index 0000000000..5960d75665 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/sample_interfaces.mojom @@ -0,0 +1,32 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample", + JavaConstantsClassName="InterfaceConstants", + Foo = "hello world"] +module sample; + +const uint64 kLong = 4405; + +enum Enum { + VALUE +}; + +interface PingTest { + Ping() => (); +}; + +interface Provider { + EchoString(string a) => (string a); + EchoStrings(string a, string b) => (string a, string b); + EchoMessagePipeHandle(handle<message_pipe> a) => (handle<message_pipe> a); + EchoEnum(Enum a) => (Enum a); + EchoInt(int32 a) => (int32 a); +}; + +interface IntegerAccessor { + GetInteger() => (int64 data, [MinVersion=2] Enum type); + [MinVersion=1] + SetInteger(int64 data, [MinVersion=3] Enum type); +}; diff --git a/mojo/public/interfaces/bindings/tests/sample_service.mojom b/mojo/public/interfaces/bindings/tests/sample_service.mojom new file mode 100644 index 0000000000..761cb91a9b --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/sample_service.mojom @@ -0,0 +1,112 @@ + +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.sample"] +module sample; + +import "sample_import.mojom"; +import "sample_import2.mojom"; + +const uint8 kTwelve = 12; + +struct Bar { + enum Type { + VERTICAL = 1, + HORIZONTAL, + BOTH, + INVALID + }; + uint8 alpha@0 = 0xff; + uint8 beta@1; + uint8 gamma@2; + Type type@3 = sample.Bar.Type.VERTICAL; +}; + +struct Foo { + const string kFooby = "Fooby"; + string name@8 = kFooby; + int32 x@0; + int32 y@1; + bool a@2 = true; + bool b@3; + bool c@4; + Bar? bar@5; + array<Bar>? extra_bars@7; + array<uint8>? data@6; + handle<message_pipe>? source@9; + array<handle<data_pipe_consumer>>? input_streams@10; + array<handle<data_pipe_producer>>? output_streams@11; + array<array<bool>>? array_of_array_of_bools@12; + array<array<array<string>>>? multi_array_of_strings@13; + array<bool>? array_of_bools@14; +}; + +struct DefaultsTest { + int8 a0@0 = -12; + uint8 a1@1 = sample.kTwelve; + int16 a2@2 = 1234; + uint16 a3@3 = 34567; + int32 a4@4 = 123456; + uint32 a5@5 = 3456789012; + int64 a6@6 = -111111111111; + uint64 a7@7 = 9999999999999999999; + int32 a8@8 = 0x12345; + int32 a9@9 = -0x12345; + int32 a10@10 = +1234; + bool a11@11 = true; + bool a12@12 = false; + float a13@13 = 123.25; + double a14@14 = 1234567890.123; + double a15@15 = 1E10; + double a16@16 = -1.2E+20; + double a17@17 = +1.23E-20; + + // TODO(vtl): Add tests for default vs null when those are implemented (for + // structs, arrays, and strings). + array<uint8> a18@18; + string a19@19; + + Bar.Type a20@20 = BOTH; + imported.Point a21@21; + imported.Thing a22@22 = default; + + uint64 a23@23 = 0xFFFFFFFFFFFFFFFF; + int64 a24@24 = 0x123456789; + int64 a25@25 = -0x123456789; + + double a26@26 = double.INFINITY; + double a27@27 = double.NEGATIVE_INFINITY; + double a28@28 = double.NAN; + float a29@29 = float.INFINITY; + float a30@30 = float.NEGATIVE_INFINITY; + float a31@31 = float.NAN; +}; + +struct StructWithHoleV1 { + int32 v1 = 1; + int64 v2 = 2; +}; + +struct StructWithHoleV2 { + int32 v1 = 1; + int64 v2 = 2; + int32 v3 = 3; +}; + +interface Service { + enum BazOptions { + REGULAR = 0, + EXTRA + }; + const uint8 kFavoriteBaz = 1; + Frobinate@0(Foo? foo@0, BazOptions baz@1, Port? port@2) => (int32 result@0); + GetPort@1(Port& port @0); +}; + +// This interface is referenced above where it is defined. It also refers to +// itself from a method. +interface Port { + PostMessageToPort@0(string message_text@0, Port port@1); +}; diff --git a/mojo/public/interfaces/bindings/tests/scoping.mojom b/mojo/public/interfaces/bindings/tests/scoping.mojom new file mode 100644 index 0000000000..2e9edb10b4 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/scoping.mojom @@ -0,0 +1,17 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test; + +interface A { + GetB(B& b); +}; + +interface B { + GetC(C& c); +}; + +interface C { + D(); +}; diff --git a/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom b/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom new file mode 100644 index 0000000000..1239e163cb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom @@ -0,0 +1,36 @@ +// 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. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.mojo"] +module mojo.test; + +struct Struct1 { + uint8 i; +}; + +struct Struct2 { + handle hdl; +}; + +struct Struct3 { + Struct1 struct_1; +}; + +struct Struct4 { + array<Struct1> data; +}; + +struct Struct5 { + array<Struct1, 2> pair; +}; + +struct Struct6 { + string str; +}; + +struct StructOfNullables { + handle? hdl; + Struct1? struct_1; + string? str; +}; diff --git a/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom b/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom new file mode 100644 index 0000000000..b50409ee88 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/struct_with_traits.mojom @@ -0,0 +1,83 @@ +// 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. + +module mojo.test; + +// TODO(yzshen): Rename *WithTraits* types to something more readable. + +struct NestedStructWithTraits { + int32 value; +}; + +enum EnumWithTraits { + VALUE_0, + VALUE_1 +}; + +struct StructWithTraits { + EnumWithTraits f_enum; + bool f_bool; + uint32 f_uint32; + uint64 f_uint64; + string f_string; + string f_string2; + array<string> f_string_array; + array<string> f_string_set; + NestedStructWithTraits f_struct; + array<NestedStructWithTraits> f_struct_array; + map<string, NestedStructWithTraits> f_struct_map; +}; + +// Test that this container can be cloned. +struct StructWithTraitsContainer { + StructWithTraits f_struct; +}; + +// Maps to a pass-by-value trivial struct. +struct TrivialStructWithTraits { + int32 value; +}; + +// Maps to a move-only struct. +struct MoveOnlyStructWithTraits { + handle f_handle; +}; + +// The custom type for MoveOnlyStructWithTraits is not clonable. Test that +// this container can compile as long as Clone() is not used. +struct MoveOnlyStructWithTraitsContainer { + MoveOnlyStructWithTraits f_struct; +}; + +struct StructWithTraitsForUniquePtr { + int32 f_int32; +}; + +union UnionWithTraits { + int32 f_int32; + NestedStructWithTraits f_struct; +}; + +interface TraitsTestService { + EchoStructWithTraits(StructWithTraits s) => (StructWithTraits passed); + + EchoTrivialStructWithTraits(TrivialStructWithTraits s) => + (TrivialStructWithTraits passed); + + EchoMoveOnlyStructWithTraits(MoveOnlyStructWithTraits s) => + (MoveOnlyStructWithTraits passed); + + EchoNullableMoveOnlyStructWithTraits(MoveOnlyStructWithTraits? s) => + (MoveOnlyStructWithTraits? passed); + + EchoEnumWithTraits(EnumWithTraits e) => (EnumWithTraits passed); + + EchoStructWithTraitsForUniquePtr(StructWithTraitsForUniquePtr e) => ( + StructWithTraitsForUniquePtr passed); + + EchoNullableStructWithTraitsForUniquePtr(StructWithTraitsForUniquePtr? e) => ( + StructWithTraitsForUniquePtr? passed); + + EchoUnionWithTraits(UnionWithTraits u) => (UnionWithTraits passed); +}; diff --git a/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom new file mode 100644 index 0000000000..adc4e7e809 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom @@ -0,0 +1,54 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test; + +import "mojo/public/interfaces/bindings/tests/ping_service.mojom"; + +interface FooInterface {}; + +struct StructContainsAssociated { + associated FooInterface? foo_interface; + associated FooInterface& foo_request; + array<associated FooInterface> foo_interfaces; + array<associated FooInterface&> foo_requests; +}; + +union UnionContainsAssociated { + associated FooInterface foo_interface; + associated FooInterface& foo_request; + array<associated FooInterface> foo_interfaces; + array<associated FooInterface&> foo_requests; +}; + +interface InterfacePassesAssociated { + PassFoo(associated FooInterface foo_interface, + associated FooInterface& foo_request) => + (associated FooInterface foo_interface, + associated FooInterface& foo_request); + + PassStruct(StructContainsAssociated foo_struct) => + (StructContainsAssociated foo_struct); + + PassUnion(UnionContainsAssociated foo_union) => + (UnionContainsAssociated foo_union); +}; + +interface IntegerSender { + Echo(int32 value) => (int32 value); + Send(int32 value); +}; + +interface IntegerSenderConnection { + GetSender(associated IntegerSender& sender); + AsyncGetSender() => (associated IntegerSender sender); +}; + +interface AssociatedPingProvider { + GetPing(associated PingService& request); +}; + +interface AssociatedPingProviderProvider { + GetPingProvider(associated AssociatedPingProvider& request); +}; diff --git a/mojo/public/interfaces/bindings/tests/test_bad_messages.mojom b/mojo/public/interfaces/bindings/tests/test_bad_messages.mojom new file mode 100644 index 0000000000..dcd594754d --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_bad_messages.mojom @@ -0,0 +1,13 @@ +// 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. + +module mojo.test; + +interface TestBadMessages { + RejectEventually() => (); + RequestResponse() => (); + + [Sync] RejectSync() => (); + [Sync] RequestResponseSync() => (); +}; diff --git a/mojo/public/interfaces/bindings/tests/test_constants.mojom b/mojo/public/interfaces/bindings/tests/test_constants.mojom new file mode 100644 index 0000000000..272e6035a6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_constants.mojom @@ -0,0 +1,57 @@ +// 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. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.test_constants"] +module mojo.test; + +// Integral types. +const bool kBoolValue = true; + +const int8 kInt8Value = -2; + +// In the range of (MAX_INT8, MAX_UINT8]. +const uint8 kUint8Value = 128; + +// In the range of [MIN_INT16, MIN_INT8). +const int16 kInt16Value = -233; + +// In the range of (MAX_INT16, MAX_UINT16]. +const uint16 kUint16Value = 44204; + +// In the range of [MIN_INT32, MIN_INT16). +const int32 kInt32Value = -44204; + +// In the range of (MAX_INT32, MAX_UINT32]. +const uint32 kUint32Value = 4294967295; + +// In the range of [MIN_INT64, MIN_INT32). +const int64 kInt64Value = -9223372036854775807; + +// In the range of (MAX_INT64, MAX_UINT64]. +const uint64 kUint64Value = 9999999999999999999; + +// Floating point types. +const double kDoubleValue = 3.14159; +const double kDoubleInfinity = double.INFINITY; +const double kDoubleNegativeInfinity = double.NEGATIVE_INFINITY; +const double kDoubleNaN = double.NAN; + +const float kFloatValue = 2.71828; +const float kFloatInfinity = float.INFINITY; +const float kFloatNegativeInfinity = float.NEGATIVE_INFINITY; +const float kFloatNaN = float.NAN; + +const string kStringValue = "test string contents"; + +struct StructWithConstants { + const int8 kInt8Value = 5; + const float kFloatValue = 765.432; + const string kStringValue = "struct test string contents"; +}; + +interface InterfaceWithConstants { + const uint32 kUint32Value = 20100722; + const double kDoubleValue = 12.34567; + const string kStringValue = "interface test string contents"; +}; diff --git a/mojo/public/interfaces/bindings/tests/test_data_view.mojom b/mojo/public/interfaces/bindings/tests/test_data_view.mojom new file mode 100644 index 0000000000..1fe8c6a8e2 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_data_view.mojom @@ -0,0 +1,41 @@ +// 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. + +module mojo.test.data_view; + +enum TestEnum { + VALUE_0, + VALUE_1 +}; + +interface TestInterface { + [Sync] + Echo(int32 value) => (int32 out_value); +}; + +struct NestedStruct { + int32 f_int32; +}; + +[Native] +struct TestNativeStruct; + +union TestUnion { + bool f_bool; + int32 f_int32; +}; + +struct TestStruct { + string f_string; + NestedStruct? f_struct; + TestNativeStruct? f_native_struct; + array<bool> f_bool_array; + array<int32> f_int32_array; + array<TestEnum> f_enum_array; + array<TestInterface> f_interface_array; + array<array<int32>> f_nested_array; + array<NestedStruct> f_struct_array; + array<TestUnion> f_union_array; + map<string, int32> f_map; +}; diff --git a/mojo/public/interfaces/bindings/tests/test_export.mojom b/mojo/public/interfaces/bindings/tests/test_export.mojom new file mode 100644 index 0000000000..319a15b036 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_export.mojom @@ -0,0 +1,20 @@ +// 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. + +module mojo.test.test_export; + +struct StringPair { + string s1; + string s2; +}; + +// This is a regression test. On Windows, if we export the generated class *and* +// not explicitly disallow copy constructor and assign operator, compilation +// will fail because it tries to use copy constructor of +// InlinedStructPtr<StringPair>. +struct StringPairContainer { + array<StringPair> pairs; +}; + +interface ExportedInterface {}; diff --git a/mojo/public/interfaces/bindings/tests/test_export2.mojom b/mojo/public/interfaces/bindings/tests/test_export2.mojom new file mode 100644 index 0000000000..011395cccb --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_export2.mojom @@ -0,0 +1,10 @@ +// 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. + +module mojo.test.test_export2; + +struct StringPair { + string s1; + string s2; +}; diff --git a/mojo/public/interfaces/bindings/tests/test_import.mojom b/mojo/public/interfaces/bindings/tests/test_import.mojom new file mode 100644 index 0000000000..42014c6809 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_import.mojom @@ -0,0 +1,11 @@ +// 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. + +module mojo.test.test_import; + +import "mojo/public/interfaces/bindings/tests/test_export.mojom"; + +struct ImportingStruct { + mojo.test.test_export.StringPair strings; +}; diff --git a/mojo/public/interfaces/bindings/tests/test_native_types.mojom b/mojo/public/interfaces/bindings/tests/test_native_types.mojom new file mode 100644 index 0000000000..3df43182a3 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_native_types.mojom @@ -0,0 +1,38 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test; + +import "mojo/public/interfaces/bindings/tests/rect.mojom"; + +// Used to verify that structs can be declared with no body in mojom. + +[Native] +struct PickledStruct; + +[Native] +enum PickledEnum; + +struct PickleContainer { + PickledStruct f_struct; + PickledEnum f_enum; +}; + +interface PicklePasser { + PassPickledStruct(PickledStruct pickle) => (PickledStruct passed); + PassPickledEnum(PickledEnum pickle) => (PickledEnum passed); + PassPickleContainer(PickleContainer container) => (PickleContainer passed); + PassPickles(array<PickledStruct> pickles) => (array<PickledStruct> passed); + PassPickleArrays(array<array<PickledStruct>> pickle_arrays) + => (array<array<PickledStruct>> passed); +}; + +// Used to verify support for native serialization of mojom-defined structs +// using StrucTraits with different variants of the Rect type from rect.mojom. + +interface RectService { + AddRect(TypemappedRect r); + GetLargestRect() => (TypemappedRect largest); + PassSharedRect(SharedTypemappedRect r) => (SharedTypemappedRect passed); +}; diff --git a/mojo/public/interfaces/bindings/tests/test_structs.mojom b/mojo/public/interfaces/bindings/tests/test_structs.mojom new file mode 100644 index 0000000000..03a0a20581 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_structs.mojom @@ -0,0 +1,414 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.test_structs"] +module mojo.test; + +import "mojo/public/interfaces/bindings/tests/rect.mojom"; + +struct NamedRegion { + string? name; + array<Rect>? rects; +}; + +struct RectPair { + Rect? first; + Rect? second; +}; + +struct EmptyStruct { +}; + +[Native] +struct UnmappedNativeStruct; + +// Used to verify that struct fields which don't specify a default are +// initialized to: false for bool, 0 for numbers, and null for strings, +// handles, and structs. The "?" nullable suffix shouldn't have any +// impact on initial field values. + +struct NoDefaultFieldValues { + bool f0; + int8 f1; + uint8 f2; + int16 f3; + uint16 f4; + int32 f5; + uint32 f6; + int64 f7; + uint64 f8; + float f9; + double f10; + string f11; + string? f12; + handle<message_pipe> f13; + handle<data_pipe_consumer> f14; + handle<data_pipe_producer> f15; + handle<message_pipe>? f16; + handle<data_pipe_consumer>? f17; + handle<data_pipe_producer>? f18; + handle f19; + handle? f20; + handle<shared_buffer> f21; + handle<shared_buffer>? f22; + array<string> f23; + array<string?> f24; + array<string>? f25; + array<string?>? f26; + EmptyStruct f27; + EmptyStruct? f28; +}; + +// Used to verify that struct fields with an explicit default value +// are initialized correctly. The "?" nullable suffix shouldn't have any +// impact on initial field values. + +struct DefaultFieldValues { + const string kFoo = "foo"; + bool f0 = true; + int8 f1 = 100; + uint8 f2 = 100; + int16 f3 = 100; + uint16 f4 = 100; + int32 f5 = 100; + uint32 f6 = 100; + int64 f7 = 100; + uint64 f8 = 100; + float f9 = 100; + float f10 = 100.0; + double f11 = 100; + double f12 = 100.0; + string f13 = kFoo; + string? f14 = kFoo; + Rect f15 = default; + Rect? f16 = default; +}; + +// Used to verify that the code generated for enum and const values defined +// within a struct is correct. Assuming that a constant's value can be a literal +// or another constant and that enum values can either be an integer constant or +// another value from the same enum type. + +struct ScopedConstants { + const int32 TEN = 10; + const int32 ALSO_TEN = TEN; + enum EType { + E0, + E1, + E2 = 10, + E3 = E2, + E4, + }; + EType f0 = E0; // 0 + EType f1 = E1; // 1 + EType f2 = E2; // 10 + EType f3 = E3; // 10 + EType f4 = E4; // 11 + int32 f5 = TEN; + int32 f6 = ALSO_TEN; +}; + +// Used to verify that all possible Map key field types can be encoded and +// decoded successfully. + +struct MapKeyTypes { + // TODO(yzshen): WTF::HashMap doesn't support bool as key. + // map<bool, bool> f0; + map<int8, int8> f1; + map<uint8, uint8> f2; + map<int16, int16> f3; + map<uint16, uint16> f4; + map<int32, int32> f5; + map<uint32, uint32> f6; + map<int64, int64> f7; + map<uint64, uint64> f8; + map<float, float> f9; + map<double, double> f10; + map<string, string> f11; + // TODO(tibell): JS/Java don't support struct as key. + // map<Rect, Rect> f12; +}; + +// Used to verify that various map value types can be encoded and decoded +// successfully. + +struct MapValueTypes { + map<string, array<string>> f0; + map<string, array<string>?> f1; + map<string, array<string?>> f2; + map<string, array<string, 2>> f3; + map<string, array<array<string, 2>?>> f4; + map<string, array<array<string, 2>, 1>> f5; + map<string, Rect?> f6; + map<string, map<string, string>> f7; + map<string, array<map<string, string>>> f8; + map<string, handle> f9; + map<string, array<handle>> f10; + map<string, map<string, handle>> f11; +}; + +// Used to verify that various array types can be encoded and decoded +// successfully. + +struct ArrayValueTypes { + array<int8> f0; + array<int16> f1; + array<int32> f2; + array<int64> f3; + array<float> f4; + array<double> f5; + array<SomeInterface> f6; + array<SomeInterface&> f7; +}; + +// Used to verify that various float and double values can be encoded and +// decoded correctly. + +struct FloatNumberValues { + const double V0 = double.INFINITY; + const double V1 = double.NEGATIVE_INFINITY; + const double V2 = double.NAN; + const float V3 = float.INFINITY; + const float V4 = float.NEGATIVE_INFINITY; + const float V5 = float.NAN; + const float V6 = 0; + const double V7 = 1234567890.123; + const double V8 = 1.2E+20; + const double V9 = -1.2E+20; + + double f0 = V0; + double f1 = V1; + double f2 = V2; + float f3 = V3; + float f4 = V4; + float f5 = V5; + float f6 = V6; + double f7 = V7; + double f8 = V8; + double f9 = V9; +}; + +// Used to verify that various signed integer values can be encoded and +// decoded correctly. + +struct IntegerNumberValues { + const int8 V0 = -128; // Minimum + const int8 V1 = -1; // -1 + const int8 V2 = 0; // 0 + const int8 V3 = 42; // An arbitrary valid value. + const int8 V4 = 127; // Maximum + + const int16 V5 = -32768; // ... + const int16 V6 = -1; + const int16 V7 = 0; + const int16 V8 = 12345; + const int16 V9 = 32767; + + const int32 V10 = -2147483648; + const int32 V11 = -1; + const int32 V12 = 0; + const int32 V13 = 1234567890; + const int32 V14 = 2147483647; + + // The limits for JavaScript integers are +/- (2^53 - 1). + const int64 V15 = -9007199254740991; // Number.MIN_SAFE_INTEGER + const int64 V16 = -1; + const int64 V17 = 0; + const int64 V18 = 1234567890123456; + const int64 V19 = 9007199254740991; // Number.MAX_SAFE_INTEGER + + int8 f0 = V0; + int8 f1 = V1; + int8 f2 = V2; + int8 f3 = V3; + int8 f4 = V4; + + int16 f5 = V5; + int16 f6 = V6; + int16 f7 = V7; + int16 f8 = V8; + int16 f9 = V9; + + int32 f10 = V10; + int32 f11 = V11; + int32 f12 = V12; + int32 f13 = V13; + int32 f14 = V14; + + int64 f15 = V15; + int64 f16 = V16; + int64 f17 = V17; + int64 f18 = V18; + int64 f19 = V19; +}; + +// Used to verify that various unsigned integer values can be encoded and +// decoded correctly. + +struct UnsignedNumberValues { + const uint8 V0 = 0; // Minimum = 0. + const uint8 V1 = 42; // An arbitrary valid value. + const uint8 V2 = 0xFF; // Maximum + + const uint16 V3 = 0; // ... + const uint16 V4 = 12345; + const uint16 V5 = 0xFFFF; + + const uint32 V6 = 0; + const uint32 V7 = 1234567890; + const uint32 V8 = 0xFFFFFFFF; + + // The limits for JavaScript integers are +/- (2^53 - 1). + const uint64 V9 = 0; + const uint64 V10 = 1234567890123456; + const uint64 V11 = 9007199254740991; // Number.MAX_SAFE_INTEGER + + uint8 f0 = V0; + uint8 f1 = V1; + uint8 f2 = V2; + + uint16 f3 = V3; + uint16 f4 = V4; + uint16 f5 = V5; + + uint32 f6 = V6; + uint32 f7 = V7; + uint32 f8 = V8; + + uint64 f9 = V9; + uint64 f10 = V10; + uint64 f11 = V11; +}; + +// Used to verify that various (packed) boolean array values can be encoded +// and decoded correctly. + +struct BitArrayValues { + array<bool, 1> f0; + array<bool, 7> f1; + array<bool, 9> f2; + array<bool> f3; + array<array<bool>> f4; + array<array<bool>?> f5; + array<array<bool, 2>?> f6; +}; + +// Used to verify that different versions can be decoded correctly. + +struct MultiVersionStruct { + [MinVersion=0] + int32 f_int32; + [MinVersion=1] + Rect? f_rect; + [MinVersion=3] + string? f_string; + [MinVersion=5] + array<int8>? f_array; + [MinVersion=7] + handle<message_pipe>? f_message_pipe; + [MinVersion=7] + bool f_bool; + [MinVersion=9] + int16 f_int16; +}; + +struct MultiVersionStructV0 { + [MinVersion=0] + int32 f_int32; +}; + +struct MultiVersionStructV1 { + [MinVersion=0] + int32 f_int32; + [MinVersion=1] + Rect? f_rect; +}; + +struct MultiVersionStructV3 { + [MinVersion=0] + int32 f_int32; + [MinVersion=1] + Rect? f_rect; + [MinVersion=3] + string? f_string; +}; + +struct MultiVersionStructV5 { + [MinVersion=0] + int32 f_int32; + [MinVersion=1] + Rect? f_rect; + [MinVersion=3] + string? f_string; + [MinVersion=5] + array<int8>? f_array; +}; + +struct MultiVersionStructV7 { + [MinVersion=0] + int32 f_int32; + [MinVersion=1] + Rect? f_rect; + [MinVersion=3] + string? f_string; + [MinVersion=5] + array<int8>? f_array; + [MinVersion=7] + handle<message_pipe>? f_message_pipe; + [MinVersion=7] + bool f_bool; +}; + +// A struct where the fields are not sorted by their ordinals. +struct ReorderedStruct { + [MinVersion=2] + int32 a@3 = 3; + [MinVersion=4] + int32 b@6 = 6; + [MinVersion=1] + int32 c@1 = 1; +}; + +// Used to verify that interfaces that are struct members can be defined in the +// same file. + +interface SomeInterface { + SomeMethod(RectPair pair) => (RectPair other_pair); +}; + +struct ContainsInterface { + SomeInterface some_interface; +}; + +// Verify that a field can be called |other|. + +struct ContainsOther { + int32 other; +}; + +// Used to verify that structs can contain interface requests. + +struct ContainsInterfaceRequest { + SomeInterface& request; +}; + +// Used to verify that boolean fields are correctly serialized/deserialized. + +struct SingleBoolStruct { + bool value; +}; + +// Used to verify that structs containing typemapped types can be hashed (if the +// typemapped type itself is hashable). + +struct ContainsHashable { + TypemappedRect rect; +}; + +// Used to test that nested structs can be hashed. The nested struct mustn't be +// nullable. + +struct SimpleNestedStruct { + ContainsOther nested; +}; diff --git a/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom b/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom new file mode 100644 index 0000000000..3b8cfe6388 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_sync_methods.mojom @@ -0,0 +1,44 @@ +// 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. + +module mojo.test; + +interface TestSyncCodeGeneration { + [Sync] + NoInput() => (int32 result); + + [Sync] + NoOutput(int32 value) => (); + + [Sync] + NoInOut() => (); + + [Sync] + HaveInOut(int32 value1, int32 value2) => (int32 result1, int32 result2); +}; + +interface TestSync { + [Sync] + Ping() => (); + + [Sync] + Echo(int32 value) => (int32 result); + + AsyncEcho(int32 value) => (int32 result); +}; + +// Test sync method support with associated interfaces. +interface TestSyncMaster { + [Sync] + Ping() => (); + + [Sync] + Echo(int32 value) => (int32 result); + + AsyncEcho(int32 value) => (int32 result); + + SendInterface(associated TestSync ptr); + + SendRequest(associated TestSync& request); +}; diff --git a/mojo/public/interfaces/bindings/tests/test_unions.mojom b/mojo/public/interfaces/bindings/tests/test_unions.mojom new file mode 100644 index 0000000000..41e1ed6ace --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_unions.mojom @@ -0,0 +1,105 @@ +// 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. + +module mojo.test; + +enum AnEnum { + FIRST, SECOND +}; + +[Extensible] +enum AnExtensibleEnum { + FIRST, SECOND, THIRD +}; + +union PodUnion { + int8 f_int8; + int8 f_int8_other; + uint8 f_uint8; + int16 f_int16; + uint16 f_uint16; + int32 f_int32; + uint32 f_uint32; + int64 f_int64; + uint64 f_uint64; + float f_float; + double f_double; + bool f_bool; + AnEnum f_enum; + AnExtensibleEnum f_extensible_enum; +}; + +union ObjectUnion { + int8 f_int8; + string f_string; + DummyStruct f_dummy; + DummyStruct? f_nullable; + array<int8> f_array_int8; + map<string, int8> f_map_int8; + PodUnion f_pod_union; + // Test that Clone() is defined after SmallStruct is declared. + array<SmallStruct> f_small_structs; +}; + +union HandleUnion { + handle f_handle; + handle<message_pipe> f_message_pipe; + handle<data_pipe_consumer> f_data_pipe_consumer; + handle<data_pipe_producer> f_data_pipe_producer; + handle<shared_buffer> f_shared_buffer; + SmallCache f_small_cache; + SmallCache& f_small_cache_request; +}; + +struct WrapperStruct { + ObjectUnion? object_union; + PodUnion? pod_union; + HandleUnion? handle_union; +}; + +struct DummyStruct { + int8 f_int8; +}; + +struct SmallStruct { + DummyStruct? dummy_struct; + PodUnion? pod_union; + array<PodUnion>? pod_union_array; + array<PodUnion?>? nullable_pod_union_array; + array<DummyStruct>? s_array; + map<string, PodUnion>? pod_union_map; + map<string, PodUnion?>? nullable_pod_union_map; +}; + +struct SmallStructNonNullableUnion { + PodUnion pod_union; +}; + +struct SmallObjStruct { + ObjectUnion obj_union; + int8 f_int8; +}; + +interface SmallCache { + SetIntValue(int64 int_value); + GetIntValue() => (int64 int_value); +}; + +interface UnionInterface { + Echo(PodUnion in_val) => (PodUnion out_val); +}; + +struct TryNonNullStruct { + DummyStruct? nullable; + DummyStruct non_nullable; +}; + +union OldUnion { + int8 f_int8; +}; + +union NewUnion { + int8 f_int8; + int16 f_int16; +}; diff --git a/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom b/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom new file mode 100644 index 0000000000..183f184ef3 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/test_wtf_types.mojom @@ -0,0 +1,50 @@ +// 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. + +module mojo.test; + +struct TestWTFCodeGeneration { + string str; + string? nullable_str; + array<string> strs; + array<string?> nullable_strs; + array<array<int32>> arrays; + array<bool> bools; + array<handle<message_pipe>> handles; + map<string, string?> str_map; + map<int32, array<int32>> array_map; + map<int32, handle<message_pipe>> handle_map; + array<map<string, string?>> str_maps; +}; + +union TestWTFCodeGeneration2 { + string str; + array<string> strs; + map<string, string?> str_map; +}; + +struct TestWTFStruct { + enum NestedEnum { + E0, + E1, + }; + string str; + int32 integer; +}; + +interface TestWTF { + enum NestedEnum { + E0, + E1, + }; + EchoString(string? str) => (string? str); + EchoStringArray(array<string?>? arr) => (array<string?>? arr); + EchoStringMap(map<string, string?>? str_map) + => (map<string, string?>? str_map); +}; + +enum TopLevelEnum { + E0, + E1, +}; diff --git a/mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom b/mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom new file mode 100644 index 0000000000..2fa77ffc9d --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom @@ -0,0 +1,18 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test; + +// Associated interfaces are not supported by all language bindings yet. +// Eventually these definitions should live in validation_test_interfaces.mojom. + +interface InterfaceX {}; + +interface AssociatedConformanceTestInterface { + Method0(associated InterfaceX param0); + Method1(associated InterfaceX& param0); + Method2(associated InterfaceX? param0); + Method3(array<associated InterfaceX> param0); +}; + diff --git a/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom b/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom new file mode 100644 index 0000000000..ab69045e53 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom @@ -0,0 +1,135 @@ +// 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. + + +[JavaPackage="org.chromium.mojo.bindings.test.mojom.mojo"] +module mojo.test; + +struct StructA { + uint64 i; +}; + +struct StructB { + StructA struct_a; +}; + +struct StructC { + array<uint8> data; +}; + +struct StructD { + array<handle<message_pipe>> message_pipes; +}; + +struct StructE { + StructD struct_d; + handle<data_pipe_consumer> data_pipe_consumer; +}; + +struct StructF { + array<uint8, 3> fixed_size_array; +}; + +struct StructG { + int32 i; + [MinVersion=1] + StructA? struct_a; + [MinVersion=3] + string? str; + [MinVersion=3] + bool b; +}; + +interface InterfaceA { +}; + +enum EnumA { + ENUM_A_0, + ENUM_A_1 +}; + +[Extensible] +enum EnumB { + ENUM_B_0, + ENUM_B_1, + ENUM_B_2 +}; + +// A non-extensible enum with no values is valid, but about as useless as +// you would expect: it will fail validation for all values. +enum EmptyEnum {}; + +[Extensible] +enum ExtensibleEmptyEnum {}; + +union UnionA { + StructA struct_a; + bool b; +}; + +// This interface is used for testing bounds-checking in the mojom +// binding code. If you add a method please update the files +// ./data/validation/boundscheck_*. If you add a response please update +// ./data/validation/resp_boundscheck_*. +interface BoundsCheckTestInterface { + Method0(uint8 param0) => (uint8 param0); + Method1(uint8 param0); +}; + +interface ConformanceTestInterface { + Method0(float param0); + Method1(StructA param0); + Method2(StructB param0, StructA param1); + Method3(array<bool> param0); + Method4(StructC param0, array<uint8> param1); + Method5(StructE param0, handle<data_pipe_producer> param1); + Method6(array<array<uint8>> param0); + Method7(StructF param0, array<array<uint8, 3>?, 2> param1); + Method8(array<array<string>?> param0); + Method9(array<array<handle?>>? param0); + Method10(map<string, uint8> param0); + Method11(StructG param0); + Method12(float param0) => (float param0); + Method13(InterfaceA? param0, uint32 param1, InterfaceA? param2); + Method14(EnumA param0, EnumB param1); + Method15(array<EnumA>? param0, array<EnumB>? param1); + Method16(map<EnumA, EnumA>? param0); + Method17(array<InterfaceA> param0); + Method18(UnionA? param0); + Method19(Recursive recursive); + Method20(map<StructB, uint8> param0); + Method21(ExtensibleEmptyEnum param0); + Method22(EmptyEnum param0); +}; + +struct BasicStruct { + int32 a; +}; + +interface IntegrationTestInterface { + Method0(BasicStruct param0) => (array<uint8> param0); +}; + +// An enum generates a enum-value validation function, so we want to test it. +// E.g., valid enum values for this enum should be: -3, 0, 1, 10 +enum BasicEnum { + A, + B, + C = A, + D = -3, + E = 0xA +}; + +// The enum validation function should be generated within the scope of this +// struct. +struct StructWithEnum { + enum EnumWithin { + A, B, C, D + }; +}; + +// This is used to test that deeply recursive structures don't blow the stack. +struct Recursive { + Recursive? recursive; +}; diff --git a/mojo/public/interfaces/bindings/tests/versioning_test_client.mojom b/mojo/public/interfaces/bindings/tests/versioning_test_client.mojom new file mode 100644 index 0000000000..f0136db9b5 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/versioning_test_client.mojom @@ -0,0 +1,34 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test.versioning; + +// versioning_test_service.mojom and versioning_test_client.mojom contain +// different versions of Mojom definitions for a fictitious human resource +// management system. They are used to test the versioning mechanism. + +enum Department { + SALES, + DEV +}; + +struct Employee { + uint64 employee_id; + string name; + Department department; +}; + +interface HumanResourceDatabase { + AddEmployee(Employee employee) => (bool success); + + QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print) + => (Employee? employee, [MinVersion=1] array<uint8>? finger_print); + + [MinVersion=1] + AttachFingerPrint(uint64 id, array<uint8> finger_print) + => (bool success); + + [MinVersion=2] + ListEmployeeIds() => (array<uint64>? ids); +}; diff --git a/mojo/public/interfaces/bindings/tests/versioning_test_service.mojom b/mojo/public/interfaces/bindings/tests/versioning_test_service.mojom new file mode 100644 index 0000000000..59b38324a6 --- /dev/null +++ b/mojo/public/interfaces/bindings/tests/versioning_test_service.mojom @@ -0,0 +1,38 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +module mojo.test.versioning; + +// versioning_test_service.mojom and versioning_test_client.mojom contain +// different versions of Mojom definitions for a fictitious human resource +// management system. They are used to test the versioning mechanism. + +enum Department { + SALES, + DEV +}; + +struct Date { + uint16 year; + uint8 month; + uint8 day; +}; + +struct Employee { + uint64 employee_id; + string name; + Department department; + [MinVersion=1] Date? birthday; +}; + +interface HumanResourceDatabase { + AddEmployee(Employee employee) => (bool success); + + QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print) + => (Employee? employee, [MinVersion=1] array<uint8>? finger_print); + + [MinVersion=1] + AttachFingerPrint(uint64 id, array<uint8> finger_print) + => (bool success); +}; diff --git a/mojo/public/java/BUILD.gn b/mojo/public/java/BUILD.gn new file mode 100644 index 0000000000..078064114e --- /dev/null +++ b/mojo/public/java/BUILD.gn @@ -0,0 +1,64 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/android/rules.gni") + +android_library("system_java") { + java_files = [ + "system/src/org/chromium/mojo/system/Core.java", + "system/src/org/chromium/mojo/system/DataPipe.java", + "system/src/org/chromium/mojo/system/Flags.java", + "system/src/org/chromium/mojo/system/Handle.java", + "system/src/org/chromium/mojo/system/InvalidHandle.java", + "system/src/org/chromium/mojo/system/MessagePipeHandle.java", + "system/src/org/chromium/mojo/system/MojoException.java", + "system/src/org/chromium/mojo/system/MojoResult.java", + "system/src/org/chromium/mojo/system/Pair.java", + "system/src/org/chromium/mojo/system/ResultAnd.java", + "system/src/org/chromium/mojo/system/SharedBufferHandle.java", + "system/src/org/chromium/mojo/system/UntypedHandle.java", + "system/src/org/chromium/mojo/system/RunLoop.java", + "system/src/org/chromium/mojo/system/Watcher.java", + ] +} + +android_library("bindings_java") { + java_files = [ + "bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java", + "bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java", + "bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java", + "bindings/src/org/chromium/mojo/bindings/BindingsHelper.java", + "bindings/src/org/chromium/mojo/bindings/Callbacks.java", + "bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java", + "bindings/src/org/chromium/mojo/bindings/Connector.java", + "bindings/src/org/chromium/mojo/bindings/DataHeader.java", + "bindings/src/org/chromium/mojo/bindings/Decoder.java", + "bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java", + "bindings/src/org/chromium/mojo/bindings/DeserializationException.java", + "bindings/src/org/chromium/mojo/bindings/Encoder.java", + "bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java", + "bindings/src/org/chromium/mojo/bindings/HandleOwner.java", + "bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java", + "bindings/src/org/chromium/mojo/bindings/Interface.java", + "bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java", + "bindings/src/org/chromium/mojo/bindings/MessageHeader.java", + "bindings/src/org/chromium/mojo/bindings/Message.java", + "bindings/src/org/chromium/mojo/bindings/MessageReceiver.java", + "bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java", + "bindings/src/org/chromium/mojo/bindings/RouterImpl.java", + "bindings/src/org/chromium/mojo/bindings/Router.java", + "bindings/src/org/chromium/mojo/bindings/SerializationException.java", + "bindings/src/org/chromium/mojo/bindings/ServiceMessage.java", + "bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java", + "bindings/src/org/chromium/mojo/bindings/Struct.java", + "bindings/src/org/chromium/mojo/bindings/Union.java", + ] + + deps = [ + ":system_java", + "//base:base_java", + ] + + srcjar_deps = [ "../interfaces/bindings:bindings_java_sources" ] +} diff --git a/mojo/public/java/bindings/README.md b/mojo/public/java/bindings/README.md new file mode 100644 index 0000000000..821a230ed9 --- /dev/null +++ b/mojo/public/java/bindings/README.md @@ -0,0 +1,12 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java Bindings API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +This document provides a brief guide to API usage with example code snippets. +For a detailed API references please consult the class definitions in +[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/bindings/src/org/chromium/mojo/bindings/) + +TODO: Make the contents of this document less non-existent. diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java new file mode 100644 index 0000000000..ee8f6310a1 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java new file mode 100644 index 0000000000..1b07cd11ba --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java new file mode 100644 index 0000000000..8a83be928e --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java @@ -0,0 +1,116 @@ +// 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; + + /** + * 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); + } + + /** + * @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."); + } + super.finalize(); + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java new file mode 100644 index 0000000000..f77399d1b4 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java new file mode 100644 index 0000000000..c6b14c1494 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java new file mode 100644 index 0000000000..f601fb81b5 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java new file mode 100644 index 0000000000..2aa5ea690c --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java @@ -0,0 +1,214 @@ +// 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) { + mErrorHandler.onConnectionError(exception); + } + } + + /** + * 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) { + // TODO(qsr) Allow usage of a pool of pre-allocated buffer for performance. + ResultAnd<ReadMessageResult> result = + handle.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE); + if (result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED) { + return new ResultAnd<Boolean>(result.getMojoResult(), false); + } + ReadMessageResult readResult = result.getValue(); + assert readResult != null; + ByteBuffer buffer = ByteBuffer.allocateDirect(readResult.getMessageSize()); + result = handle.readMessage( + buffer, readResult.getHandlesCount(), MessagePipeHandle.ReadFlags.NONE); + if (receiver != null && result.getMojoResult() == MojoResult.OK) { + boolean accepted = receiver.accept(new Message(buffer, result.getValue().getHandles())); + return new ResultAnd<Boolean>(result.getMojoResult(), accepted); + } + return new ResultAnd<Boolean>(result.getMojoResult(), false); + } +} diff --git a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java new file mode 100644 index 0000000000..96acec9438 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java new file mode 100644 index 0000000000..64ff1c08a1 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java new file mode 100644 index 0000000000..2ee2ae7493 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java new file mode 100644 index 0000000000..eeb511c520 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java new file mode 100644 index 0000000000..3c86a8daa4 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java new file mode 100644 index 0000000000..bb49cbc4bd --- /dev/null +++ b/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(NOTIFY_BUFFER, 0, 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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java new file mode 100644 index 0000000000..60fc33ba4e --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java new file mode 100644 index 0000000000..2699ab899a --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java @@ -0,0 +1,425 @@ +// 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; + +/** + * 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; + } + try { + callback.call(mVersion); + } catch (RuntimeException e) { + // TODO(lhchavez): Remove this hack. See b/28986534 for details. + android.util.Log.wtf("org.chromium.mojo.bindings.Interface", + "Uncaught runtime exception", e); + } + } + }); + } + + /** + * @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(); + } + + } + + /** + * 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); + } + + /** + * 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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java new file mode 100644 index 0000000000..51f543d567 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java new file mode 100644 index 0000000000..61899b4467 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java new file mode 100644 index 0000000000..996c457338 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java @@ -0,0 +1,69 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +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> mHandle; + + /** + * 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) { + assert buffer.isDirect(); + mBuffer = buffer; + mHandle = handles; + } + + /** + * The data of the message. + */ + public ByteBuffer getData() { + return mBuffer; + } + + /** + * The handles of the message. + */ + public List<? extends Handle> getHandles() { + return mHandle; + } + + /** + * 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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java new file mode 100644 index 0000000000..771d22b5c3 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java new file mode 100644 index 0000000000..4223297914 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java new file mode 100644 index 0000000000..e24a5586c6 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java new file mode 100644 index 0000000000..ba19ae5ac4 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java new file mode 100644 index 0000000000..aebc9e21c8 --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java @@ -0,0 +1,274 @@ +// 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. + // TODO(lhchavez): Remove this hack. See b/28986534 for details. + synchronized (mResponders) { + 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; + // TODO(lhchavez): Remove this hack. See b/28986534 for details. + synchronized (mResponders) { + 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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java new file mode 100644 index 0000000000..d4f550227f --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java new file mode 100644 index 0000000000..313dc6aea3 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java new file mode 100644 index 0000000000..118c991f7b --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java new file mode 100644 index 0000000000..14f4e1e726 --- /dev/null +++ b/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/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java new file mode 100644 index 0000000000..90b40ea57b --- /dev/null +++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.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.bindings; + +import org.chromium.mojo.system.Core; + +/** + * Base class for all mojo unions. + */ +public abstract class Union { + /** + * 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/mojo/public/java/system/README.md b/mojo/public/java/system/README.md new file mode 100644 index 0000000000..3213e4c790 --- /dev/null +++ b/mojo/public/java/system/README.md @@ -0,0 +1,25 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Java System API +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +This document provides a brief guide to Java Mojo bindings usage with example +code snippets. + +For a detailed API references please consult the class definitions in +[this directory](https://cs.chromium.org/chromium/src/mojo/public/java/system/src/org/chromium/mojo/system/). + +*TODO: Make the contents of this document less non-existent.* + +## Message Pipes + +## Data Pipes + +## Shared Buffers + +## Native Platform Handles (File Descriptors, Windows Handles, *etc.*) + +## Watchers + diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/Core.java b/mojo/public/java/system/src/org/chromium/mojo/system/Core.java new file mode 100644 index 0000000000..40e4be365d --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java b/mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java new file mode 100644 index 0000000000..4deaf0975d --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java b/mojo/public/java/system/src/org/chromium/mojo/system/Flags.java new file mode 100644 index 0000000000..30ff07f710 --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java b/mojo/public/java/system/src/org/chromium/mojo/system/Handle.java new file mode 100644 index 0000000000..903f36d677 --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java new file mode 100644 index 0000000000..f8b99c6d66 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java @@ -0,0 +1,219 @@ +// 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(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags) + */ + @Override + public ResultAnd<ReadMessageResult> readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) { + throw new MojoException(MojoResult.INVALID_ARGUMENT); + } + +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java new file mode 100644 index 0000000000..deb6ac0f01 --- /dev/null +++ b/mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java @@ -0,0 +1,224 @@ +// 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; + private static final int FLAG_MAY_DISCARD = 1 << 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); + } + + /** + * Change the may-discard bit of this flag. If set, if the message is unable to be read for + * whatever reason (e.g., the caller-supplied buffer is too small), discard the message + * (i.e., simply dequeue it). + * + * @param mayDiscard the new value of the may-discard bit. + * @return this. + */ + public ReadFlags setMayDiscard(boolean mayDiscard) { + return setFlag(FLAG_MAY_DISCARD, mayDiscard); + } + + /** + * @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, the size in bytes of the message, otherwise the size in bytes of + * the next message. + */ + private int mMessageSize; + /** + * If a message was read, the number of handles contained in the message, otherwise the + * number of handles contained in the next message. + */ + private int mHandlesCount; + /** + * If a message was read, the handles contained in the message, undefined otherwise. + */ + private List<UntypedHandle> mHandles; + + /** + * @return the messageSize + */ + public int getMessageSize() { + return mMessageSize; + } + + /** + * @param messageSize the messageSize to set + */ + public void setMessageSize(int messageSize) { + mMessageSize = messageSize; + } + + /** + * @return the handlesCount + */ + public int getHandlesCount() { + return mHandlesCount; + } + + /** + * @param handlesCount the handlesCount to set + */ + public void setHandlesCount(int handlesCount) { + mHandlesCount = handlesCount; + } + + /** + * @return the handles + */ + public List<UntypedHandle> getHandles() { + return mHandles; + } + + /** + * @param handles the handles to set + */ + public void setHandles(List<UntypedHandle> handles) { + mHandles = handles; + } + } + + /** + * @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( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags); +} diff --git a/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java b/mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java new file mode 100644 index 0000000000..4e0e3e9597 --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java b/mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java new file mode 100644 index 0000000000..2602cb5e1e --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java b/mojo/public/java/system/src/org/chromium/mojo/system/Pair.java new file mode 100644 index 0000000000..2ead0204f9 --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java b/mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java new file mode 100644 index 0000000000..656d0d64e4 --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java b/mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java new file mode 100644 index 0000000000..4038b2954e --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java new file mode 100644 index 0000000000..df317d134d --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java b/mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java new file mode 100644 index 0000000000..199b0a1c04 --- /dev/null +++ b/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/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java b/mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java new file mode 100644 index 0000000000..9c70161067 --- /dev/null +++ b/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/mojo/public/js/BUILD.gn b/mojo/public/js/BUILD.gn new file mode 100644 index 0000000000..5ed57a1328 --- /dev/null +++ b/mojo/public/js/BUILD.gn @@ -0,0 +1,93 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +interfaces_bindings_gen_dir = "$root_gen_dir/mojo/public/interfaces/bindings" + +source_set("js") { + sources = [ + "constants.cc", + "constants.h", + ] +} + +group("bindings") { + data = [ + "$interfaces_bindings_gen_dir/interface_control_messages.mojom.js", + "$interfaces_bindings_gen_dir/pipe_control_messages.mojom.js", + "bindings.js", + "buffer.js", + "codec.js", + "connector.js", + "core.js", + "interface_types.js", + "lib/control_message_handler.js", + "lib/control_message_proxy.js", + "lib/interface_endpoint_client.js", + "lib/interface_endpoint_handle.js", + "lib/pipe_control_message_handler.js", + "lib/pipe_control_message_proxy.js", + "router.js", + "support.js", + "threading.js", + "unicode.js", + "validator.js", + ] + + deps = [ + ":new_bindings", + "//mojo/public/interfaces/bindings:bindings__generator", + ] +} + +action("new_bindings") { + new_bindings_js_files = [ + # This must be the first file in the list, because it initializes global + # variable |mojoBindings| that the others need to refer to. + "new_bindings/base.js", + + "$interfaces_bindings_gen_dir/new_bindings/interface_control_messages.mojom.js", + "new_bindings/bindings.js", + "new_bindings/buffer.js", + "new_bindings/codec.js", + "new_bindings/connector.js", + "new_bindings/interface_types.js", + "new_bindings/lib/control_message_handler.js", + "new_bindings/lib/control_message_proxy.js", + "new_bindings/router.js", + "new_bindings/unicode.js", + "new_bindings/validator.js", + ] + compiled_file = "$target_gen_dir/mojo_bindings.js" + + # TODO(yzshen): Eventually we would like to use Closure Compiler to minify the + # bindings instead of simply concatenating the files. + script = "//v8/tools/concatenate-files.py" + + sources = new_bindings_js_files + outputs = [ + compiled_file, + ] + + args = rebase_path(new_bindings_js_files) + args += [ rebase_path(compiled_file) ] + + deps = [ + "//mojo/public/interfaces/bindings:new_bindings__generator", + ] +} + +group("tests") { + testonly = true + + data = [ + "//mojo/public/interfaces/bindings/tests/data/validation/", + "tests/core_unittest.js", + "tests/validation_test_input_parser.js", + "tests/validation_unittest.js", + ] + + public_deps = [ + ":bindings", + ] +} diff --git a/mojo/public/js/README.md b/mojo/public/js/README.md new file mode 100644 index 0000000000..b6eafe9c82 --- /dev/null +++ b/mojo/public/js/README.md @@ -0,0 +1,7 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo JavaScript System and Bindings APIs +This document is a subset of the [Mojo documentation](/mojo). + +**NOTE:** The JavaScript APIs are currently in flux and will stabilize soon. +More info forthcoming ASAP! + +TODO: Make the contents of this document less non-existent. diff --git a/mojo/public/js/bindings.js b/mojo/public/js/bindings.js new file mode 100644 index 0000000000..a944e2f7aa --- /dev/null +++ b/mojo/public/js/bindings.js @@ -0,0 +1,322 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/bindings", [ + "mojo/public/js/core", + "mojo/public/js/interface_types", + "mojo/public/js/lib/interface_endpoint_client", + "mojo/public/js/router", +], function(core, types, interfaceEndpointClient, router) { + + var InterfaceEndpointClient = interfaceEndpointClient.InterfaceEndpointClient; + + // --------------------------------------------------------------------------- + + function makeRequest(interfacePtr) { + var pipe = core.createMessagePipe(); + interfacePtr.ptr.bind(new types.InterfacePtrInfo(pipe.handle0, 0)); + return new types.InterfaceRequest(pipe.handle1); + } + + // --------------------------------------------------------------------------- + + // Operations used to setup/configure an interface pointer. Exposed as the + // |ptr| field of generated interface pointer classes. + // |ptrInfoOrHandle| could be omitted and passed into bind() later. + function InterfacePtrController(interfaceType, ptrInfoOrHandle) { + this.version = 0; + + this.interfaceType_ = interfaceType; + this.router_ = null; + this.interfaceEndpointClient_ = null; + this.proxy_ = null; + + // |router_| and |interfaceEndpointClient_| are lazily initialized. + // |handle_| is valid between bind() and + // the initialization of |router_| and |interfaceEndpointClient_|. + this.handle_ = null; + + if (ptrInfoOrHandle) + this.bind(ptrInfoOrHandle); + } + + InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) { + this.reset(); + + if (ptrInfoOrHandle instanceof types.InterfacePtrInfo) { + this.version = ptrInfoOrHandle.version; + this.handle_ = ptrInfoOrHandle.handle; + } else { + this.handle_ = ptrInfoOrHandle; + } + }; + + InterfacePtrController.prototype.isBound = function() { + return this.router_ !== null || this.handle_ !== null; + }; + + // Although users could just discard the object, reset() closes the pipe + // immediately. + InterfacePtrController.prototype.reset = function() { + this.version = 0; + if (this.interfaceEndpointClient_) { + this.interfaceEndpointClient_.close(); + this.interfaceEndpointClient_ = null; + } + if (this.router_) { + this.router_.close(); + this.router_ = null; + + this.proxy_ = null; + } + if (this.handle_) { + core.close(this.handle_); + this.handle_ = null; + } + }; + + InterfacePtrController.prototype.resetWithReason = function(reason) { + this.configureProxyIfNecessary_(); + this.interfaceEndpointClient_.close(reason); + this.interfaceEndpointClient_ = null; + this.reset(); + }; + + InterfacePtrController.prototype.setConnectionErrorHandler = function( + callback) { + if (!this.isBound()) + throw new Error("Cannot set connection error handler if not bound."); + + this.configureProxyIfNecessary_(); + this.interfaceEndpointClient_.setConnectionErrorHandler(callback); + }; + + InterfacePtrController.prototype.passInterface = function() { + var result; + if (this.router_) { + // TODO(yzshen): Fix Router interface to support extracting handle. + result = new types.InterfacePtrInfo( + this.router_.connector_.handle_, this.version); + this.router_.connector_.handle_ = null; + } else { + // This also handles the case when this object is not bound. + result = new types.InterfacePtrInfo(this.handle_, this.version); + this.handle_ = null; + } + + this.reset(); + return result; + }; + + InterfacePtrController.prototype.getProxy = function() { + this.configureProxyIfNecessary_(); + return this.proxy_; + }; + + InterfacePtrController.prototype.waitForNextMessageForTesting = function() { + this.configureProxyIfNecessary_(); + this.router_.waitForNextMessageForTesting(); + }; + + InterfacePtrController.prototype.configureProxyIfNecessary_ = function() { + if (!this.handle_) + return; + + this.router_ = new router.Router(this.handle_); + this.handle_ = null; + + this.interfaceEndpointClient_ = new InterfaceEndpointClient( + this.router_.createLocalEndpointHandle(types.kMasterInterfaceId), + this.router_); + + this.interfaceEndpointClient_ .setPayloadValidators([ + this.interfaceType_.validateResponse]); + this.proxy_ = new this.interfaceType_.proxyClass( + this.interfaceEndpointClient_); + }; + + InterfacePtrController.prototype.queryVersion = function() { + function onQueryVersion(version) { + this.version = version; + return version; + } + + this.configureProxyIfNecessary_(); + return this.interfaceEndpointClient_.queryVersion().then( + onQueryVersion.bind(this)); + }; + + InterfacePtrController.prototype.requireVersion = function(version) { + this.configureProxyIfNecessary_(); + + if (this.version >= version) { + return; + } + this.version = version; + this.interfaceEndpointClient_.requireVersion(version); + }; + + // --------------------------------------------------------------------------- + + // |request| could be omitted and passed into bind() later. + // + // Example: + // + // // FooImpl implements mojom.Foo. + // function FooImpl() { ... } + // FooImpl.prototype.fooMethod1 = function() { ... } + // FooImpl.prototype.fooMethod2 = function() { ... } + // + // var fooPtr = new mojom.FooPtr(); + // var request = makeRequest(fooPtr); + // var binding = new Binding(mojom.Foo, new FooImpl(), request); + // fooPtr.fooMethod1(); + function Binding(interfaceType, impl, requestOrHandle) { + this.interfaceType_ = interfaceType; + this.impl_ = impl; + this.router_ = null; + this.interfaceEndpointClient_ = null; + this.stub_ = null; + + if (requestOrHandle) + this.bind(requestOrHandle); + } + + Binding.prototype.isBound = function() { + return this.router_ !== null; + }; + + Binding.prototype.createInterfacePtrAndBind = function() { + var ptr = new this.interfaceType_.ptrClass(); + // TODO(yzshen): Set the version of the interface pointer. + this.bind(makeRequest(ptr)); + return ptr; + }; + + Binding.prototype.bind = function(requestOrHandle) { + this.close(); + + var handle = requestOrHandle instanceof types.InterfaceRequest ? + requestOrHandle.handle : requestOrHandle; + if (!core.isHandle(handle)) + return; + + this.router_ = new router.Router(handle); + + this.stub_ = new this.interfaceType_.stubClass(this.impl_); + this.interfaceEndpointClient_ = new InterfaceEndpointClient( + this.router_.createLocalEndpointHandle(types.kMasterInterfaceId), + this.router_, this.interfaceType_.kVersion); + this.interfaceEndpointClient_.setIncomingReceiver(this.stub_); + this.interfaceEndpointClient_ .setPayloadValidators([ + this.interfaceType_.validateRequest]); + }; + + Binding.prototype.close = function() { + if (!this.isBound()) + return; + + if (this.interfaceEndpointClient_) { + this.interfaceEndpointClient_.close(); + this.interfaceEndpointClient_ = null; + } + + this.router_.close(); + this.router_ = null; + this.stub_ = null; + }; + + Binding.prototype.closeWithReason = function(reason) { + if (this.interfaceEndpointClient_) { + this.interfaceEndpointClient_.close(reason); + this.interfaceEndpointClient_ = null; + } + this.close(); + }; + + Binding.prototype.setConnectionErrorHandler + = function(callback) { + if (!this.isBound()) { + throw new Error("Cannot set connection error handler if not bound."); + } + this.interfaceEndpointClient_.setConnectionErrorHandler(callback); + }; + + Binding.prototype.unbind = function() { + if (!this.isBound()) + return new types.InterfaceRequest(null); + + var result = new types.InterfaceRequest(this.router_.connector_.handle_); + this.router_.connector_.handle_ = null; + this.close(); + return result; + }; + + Binding.prototype.waitForNextMessageForTesting = function() { + this.router_.waitForNextMessageForTesting(); + }; + + // --------------------------------------------------------------------------- + + function BindingSetEntry(bindingSet, interfaceType, impl, requestOrHandle, + bindingId) { + this.bindingSet_ = bindingSet; + this.bindingId_ = bindingId; + this.binding_ = new Binding(interfaceType, impl, requestOrHandle); + + this.binding_.setConnectionErrorHandler(function() { + this.bindingSet_.onConnectionError(bindingId); + }.bind(this)); + } + + BindingSetEntry.prototype.close = function() { + this.binding_.close(); + }; + + function BindingSet(interfaceType) { + this.interfaceType_ = interfaceType; + this.nextBindingId_ = 0; + this.bindings_ = new Map(); + this.errorHandler_ = null; + } + + BindingSet.prototype.isEmpty = function() { + return this.bindings_.size == 0; + }; + + BindingSet.prototype.addBinding = function(impl, requestOrHandle) { + this.bindings_.set( + this.nextBindingId_, + new BindingSetEntry(this, this.interfaceType_, impl, requestOrHandle, + this.nextBindingId_)); + ++this.nextBindingId_; + }; + + BindingSet.prototype.closeAllBindings = function() { + for (var entry of this.bindings_.values()) + entry.close(); + this.bindings_.clear(); + }; + + BindingSet.prototype.setConnectionErrorHandler = function(callback) { + this.errorHandler_ = callback; + }; + + BindingSet.prototype.onConnectionError = function(bindingId) { + this.bindings_.delete(bindingId); + + if (this.errorHandler_) + this.errorHandler_(); + }; + + var exports = {}; + exports.InterfacePtrInfo = types.InterfacePtrInfo; + exports.InterfaceRequest = types.InterfaceRequest; + exports.makeRequest = makeRequest; + exports.InterfacePtrController = InterfacePtrController; + exports.Binding = Binding; + exports.BindingSet = BindingSet; + + return exports; +}); diff --git a/mojo/public/js/buffer.js b/mojo/public/js/buffer.js new file mode 100644 index 0000000000..e35f69513f --- /dev/null +++ b/mojo/public/js/buffer.js @@ -0,0 +1,156 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/buffer", function() { + + var kHostIsLittleEndian = (function () { + var endianArrayBuffer = new ArrayBuffer(2); + var endianUint8Array = new Uint8Array(endianArrayBuffer); + var endianUint16Array = new Uint16Array(endianArrayBuffer); + endianUint16Array[0] = 1; + return endianUint8Array[0] == 1; + })(); + + var kHighWordMultiplier = 0x100000000; + + function Buffer(sizeOrArrayBuffer) { + if (sizeOrArrayBuffer instanceof ArrayBuffer) + this.arrayBuffer = sizeOrArrayBuffer; + else + this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer); + + this.dataView = new DataView(this.arrayBuffer); + this.next = 0; + } + + Object.defineProperty(Buffer.prototype, "byteLength", { + get: function() { return this.arrayBuffer.byteLength; } + }); + + Buffer.prototype.alloc = function(size) { + var pointer = this.next; + this.next += size; + if (this.next > this.byteLength) { + var newSize = (1.5 * (this.byteLength + size)) | 0; + this.grow(newSize); + } + return pointer; + }; + + function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) { + (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer)); + } + + Buffer.prototype.grow = function(size) { + var newArrayBuffer = new ArrayBuffer(size); + copyArrayBuffer(newArrayBuffer, this.arrayBuffer); + this.arrayBuffer = newArrayBuffer; + this.dataView = new DataView(this.arrayBuffer); + }; + + Buffer.prototype.trim = function() { + this.arrayBuffer = this.arrayBuffer.slice(0, this.next); + this.dataView = new DataView(this.arrayBuffer); + }; + + Buffer.prototype.getUint8 = function(offset) { + return this.dataView.getUint8(offset); + } + Buffer.prototype.getUint16 = function(offset) { + return this.dataView.getUint16(offset, kHostIsLittleEndian); + } + Buffer.prototype.getUint32 = function(offset) { + return this.dataView.getUint32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getUint64 = function(offset) { + var lo, hi; + if (kHostIsLittleEndian) { + lo = this.dataView.getUint32(offset, kHostIsLittleEndian); + hi = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } else { + hi = this.dataView.getUint32(offset, kHostIsLittleEndian); + lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } + return lo + hi * kHighWordMultiplier; + } + + Buffer.prototype.getInt8 = function(offset) { + return this.dataView.getInt8(offset); + } + Buffer.prototype.getInt16 = function(offset) { + return this.dataView.getInt16(offset, kHostIsLittleEndian); + } + Buffer.prototype.getInt32 = function(offset) { + return this.dataView.getInt32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getInt64 = function(offset) { + var lo, hi; + if (kHostIsLittleEndian) { + lo = this.dataView.getUint32(offset, kHostIsLittleEndian); + hi = this.dataView.getInt32(offset + 4, kHostIsLittleEndian); + } else { + hi = this.dataView.getInt32(offset, kHostIsLittleEndian); + lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } + return lo + hi * kHighWordMultiplier; + } + + Buffer.prototype.getFloat32 = function(offset) { + return this.dataView.getFloat32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getFloat64 = function(offset) { + return this.dataView.getFloat64(offset, kHostIsLittleEndian); + } + + Buffer.prototype.setUint8 = function(offset, value) { + this.dataView.setUint8(offset, value); + } + Buffer.prototype.setUint16 = function(offset, value) { + this.dataView.setUint16(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setUint32 = function(offset, value) { + this.dataView.setUint32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setUint64 = function(offset, value) { + var hi = (value / kHighWordMultiplier) | 0; + if (kHostIsLittleEndian) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian); + } else { + this.dataView.setInt32(offset, hi, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian); + } + } + + Buffer.prototype.setInt8 = function(offset, value) { + this.dataView.setInt8(offset, value); + } + Buffer.prototype.setInt16 = function(offset, value) { + this.dataView.setInt16(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setInt32 = function(offset, value) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setInt64 = function(offset, value) { + var hi = Math.floor(value / kHighWordMultiplier); + if (kHostIsLittleEndian) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian); + } else { + this.dataView.setInt32(offset, hi, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian); + } + } + + Buffer.prototype.setFloat32 = function(offset, value) { + this.dataView.setFloat32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setFloat64 = function(offset, value) { + this.dataView.setFloat64(offset, value, kHostIsLittleEndian); + } + + var exports = {}; + exports.Buffer = Buffer; + return exports; +}); diff --git a/mojo/public/js/codec.js b/mojo/public/js/codec.js new file mode 100644 index 0000000000..ce58a8cfd4 --- /dev/null +++ b/mojo/public/js/codec.js @@ -0,0 +1,926 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/codec", [ + "mojo/public/js/buffer", + "mojo/public/js/interface_types", + "mojo/public/js/unicode", +], function(buffer, types, unicode) { + + var kErrorUnsigned = "Passing negative value to unsigned"; + var kErrorArray = "Passing non Array for array type"; + var kErrorString = "Passing non String for string type"; + var kErrorMap = "Passing non Map for map type"; + + // Memory ------------------------------------------------------------------- + + var kAlignment = 8; + + function align(size) { + return size + (kAlignment - (size % kAlignment)) % kAlignment; + } + + function isAligned(offset) { + return offset >= 0 && (offset % kAlignment) === 0; + } + + // Constants ---------------------------------------------------------------- + + var kArrayHeaderSize = 8; + var kStructHeaderSize = 8; + var kMessageHeaderSize = 24; + var kMessageWithRequestIDHeaderSize = 32; + var kMapStructPayloadSize = 16; + + var kStructHeaderNumBytesOffset = 0; + var kStructHeaderVersionOffset = 4; + + var kEncodedInvalidHandleValue = 0xFFFFFFFF; + + // Decoder ------------------------------------------------------------------ + + function Decoder(buffer, handles, base) { + this.buffer = buffer; + this.handles = handles; + this.base = base; + this.next = base; + } + + Decoder.prototype.align = function() { + this.next = align(this.next); + }; + + Decoder.prototype.skip = function(offset) { + this.next += offset; + }; + + Decoder.prototype.readInt8 = function() { + var result = this.buffer.getInt8(this.next); + this.next += 1; + return result; + }; + + Decoder.prototype.readUint8 = function() { + var result = this.buffer.getUint8(this.next); + this.next += 1; + return result; + }; + + Decoder.prototype.readInt16 = function() { + var result = this.buffer.getInt16(this.next); + this.next += 2; + return result; + }; + + Decoder.prototype.readUint16 = function() { + var result = this.buffer.getUint16(this.next); + this.next += 2; + return result; + }; + + Decoder.prototype.readInt32 = function() { + var result = this.buffer.getInt32(this.next); + this.next += 4; + return result; + }; + + Decoder.prototype.readUint32 = function() { + var result = this.buffer.getUint32(this.next); + this.next += 4; + return result; + }; + + Decoder.prototype.readInt64 = function() { + var result = this.buffer.getInt64(this.next); + this.next += 8; + return result; + }; + + Decoder.prototype.readUint64 = function() { + var result = this.buffer.getUint64(this.next); + this.next += 8; + return result; + }; + + Decoder.prototype.readFloat = function() { + var result = this.buffer.getFloat32(this.next); + this.next += 4; + return result; + }; + + Decoder.prototype.readDouble = function() { + var result = this.buffer.getFloat64(this.next); + this.next += 8; + return result; + }; + + Decoder.prototype.decodePointer = function() { + // TODO(abarth): To correctly decode a pointer, we need to know the real + // base address of the array buffer. + var offsetPointer = this.next; + var offset = this.readUint64(); + if (!offset) + return 0; + return offsetPointer + offset; + }; + + Decoder.prototype.decodeAndCreateDecoder = function(pointer) { + return new Decoder(this.buffer, this.handles, pointer); + }; + + Decoder.prototype.decodeHandle = function() { + return this.handles[this.readUint32()] || null; + }; + + Decoder.prototype.decodeString = function() { + var numberOfBytes = this.readUint32(); + var numberOfElements = this.readUint32(); + var base = this.next; + this.next += numberOfElements; + return unicode.decodeUtf8String( + new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements)); + }; + + Decoder.prototype.decodeArray = function(cls) { + var numberOfBytes = this.readUint32(); + var numberOfElements = this.readUint32(); + var val = new Array(numberOfElements); + if (cls === PackedBool) { + var byte; + for (var i = 0; i < numberOfElements; ++i) { + if (i % 8 === 0) + byte = this.readUint8(); + val[i] = (byte & (1 << i % 8)) ? true : false; + } + } else { + for (var i = 0; i < numberOfElements; ++i) { + val[i] = cls.decode(this); + } + } + return val; + }; + + Decoder.prototype.decodeStruct = function(cls) { + return cls.decode(this); + }; + + Decoder.prototype.decodeStructPointer = function(cls) { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return cls.decode(this.decodeAndCreateDecoder(pointer)); + }; + + Decoder.prototype.decodeArrayPointer = function(cls) { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return this.decodeAndCreateDecoder(pointer).decodeArray(cls); + }; + + Decoder.prototype.decodeStringPointer = function() { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return this.decodeAndCreateDecoder(pointer).decodeString(); + }; + + Decoder.prototype.decodeMap = function(keyClass, valueClass) { + this.skip(4); // numberOfBytes + this.skip(4); // version + var keys = this.decodeArrayPointer(keyClass); + var values = this.decodeArrayPointer(valueClass); + var val = new Map(); + for (var i = 0; i < keys.length; i++) + val.set(keys[i], values[i]); + return val; + }; + + Decoder.prototype.decodeMapPointer = function(keyClass, valueClass) { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + var decoder = this.decodeAndCreateDecoder(pointer); + return decoder.decodeMap(keyClass, valueClass); + }; + + // Encoder ------------------------------------------------------------------ + + function Encoder(buffer, handles, base) { + this.buffer = buffer; + this.handles = handles; + this.base = base; + this.next = base; + } + + Encoder.prototype.align = function() { + this.next = align(this.next); + }; + + Encoder.prototype.skip = function(offset) { + this.next += offset; + }; + + Encoder.prototype.writeInt8 = function(val) { + this.buffer.setInt8(this.next, val); + this.next += 1; + }; + + Encoder.prototype.writeUint8 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.setUint8(this.next, val); + this.next += 1; + }; + + Encoder.prototype.writeInt16 = function(val) { + this.buffer.setInt16(this.next, val); + this.next += 2; + }; + + Encoder.prototype.writeUint16 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.setUint16(this.next, val); + this.next += 2; + }; + + Encoder.prototype.writeInt32 = function(val) { + this.buffer.setInt32(this.next, val); + this.next += 4; + }; + + Encoder.prototype.writeUint32 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.setUint32(this.next, val); + this.next += 4; + }; + + Encoder.prototype.writeInt64 = function(val) { + this.buffer.setInt64(this.next, val); + this.next += 8; + }; + + Encoder.prototype.writeUint64 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.setUint64(this.next, val); + this.next += 8; + }; + + Encoder.prototype.writeFloat = function(val) { + this.buffer.setFloat32(this.next, val); + this.next += 4; + }; + + Encoder.prototype.writeDouble = function(val) { + this.buffer.setFloat64(this.next, val); + this.next += 8; + }; + + Encoder.prototype.encodePointer = function(pointer) { + if (!pointer) + return this.writeUint64(0); + // TODO(abarth): To correctly encode a pointer, we need to know the real + // base address of the array buffer. + var offset = pointer - this.next; + this.writeUint64(offset); + }; + + Encoder.prototype.createAndEncodeEncoder = function(size) { + var pointer = this.buffer.alloc(align(size)); + this.encodePointer(pointer); + return new Encoder(this.buffer, this.handles, pointer); + }; + + Encoder.prototype.encodeHandle = function(handle) { + if (handle) { + this.handles.push(handle); + this.writeUint32(this.handles.length - 1); + } else { + this.writeUint32(kEncodedInvalidHandleValue); + } + }; + + Encoder.prototype.encodeString = function(val) { + var base = this.next + kArrayHeaderSize; + var numberOfElements = unicode.encodeUtf8String( + val, new Uint8Array(this.buffer.arrayBuffer, base)); + var numberOfBytes = kArrayHeaderSize + numberOfElements; + this.writeUint32(numberOfBytes); + this.writeUint32(numberOfElements); + this.next += numberOfElements; + }; + + Encoder.prototype.encodeArray = + function(cls, val, numberOfElements, encodedSize) { + if (numberOfElements === undefined) + numberOfElements = val.length; + if (encodedSize === undefined) + encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements; + + this.writeUint32(encodedSize); + this.writeUint32(numberOfElements); + + if (cls === PackedBool) { + var byte = 0; + for (i = 0; i < numberOfElements; ++i) { + if (val[i]) + byte |= (1 << i % 8); + if (i % 8 === 7 || i == numberOfElements - 1) { + Uint8.encode(this, byte); + byte = 0; + } + } + } else { + for (var i = 0; i < numberOfElements; ++i) + cls.encode(this, val[i]); + } + }; + + Encoder.prototype.encodeStruct = function(cls, val) { + return cls.encode(this, val); + }; + + Encoder.prototype.encodeStructPointer = function(cls, val) { + if (val == null) { + // Also handles undefined, since undefined == null. + this.encodePointer(val); + return; + } + var encoder = this.createAndEncodeEncoder(cls.encodedSize); + cls.encode(encoder, val); + }; + + Encoder.prototype.encodeArrayPointer = function(cls, val) { + if (val == null) { + // Also handles undefined, since undefined == null. + this.encodePointer(val); + return; + } + + var numberOfElements = val.length; + if (!Number.isSafeInteger(numberOfElements) || numberOfElements < 0) + throw new Error(kErrorArray); + + var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ? + Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements); + var encoder = this.createAndEncodeEncoder(encodedSize); + encoder.encodeArray(cls, val, numberOfElements, encodedSize); + }; + + Encoder.prototype.encodeStringPointer = function(val) { + if (val == null) { + // Also handles undefined, since undefined == null. + this.encodePointer(val); + return; + } + // Only accepts string primivites, not String Objects like new String("foo") + if (typeof(val) !== "string") { + throw new Error(kErrorString); + } + var encodedSize = kArrayHeaderSize + unicode.utf8Length(val); + var encoder = this.createAndEncodeEncoder(encodedSize); + encoder.encodeString(val); + }; + + Encoder.prototype.encodeMap = function(keyClass, valueClass, val) { + var keys = new Array(val.size); + var values = new Array(val.size); + var i = 0; + val.forEach(function(value, key) { + values[i] = value; + keys[i++] = key; + }); + this.writeUint32(kStructHeaderSize + kMapStructPayloadSize); + this.writeUint32(0); // version + this.encodeArrayPointer(keyClass, keys); + this.encodeArrayPointer(valueClass, values); + } + + Encoder.prototype.encodeMapPointer = function(keyClass, valueClass, val) { + if (val == null) { + // Also handles undefined, since undefined == null. + this.encodePointer(val); + return; + } + if (!(val instanceof Map)) { + throw new Error(kErrorMap); + } + var encodedSize = kStructHeaderSize + kMapStructPayloadSize; + var encoder = this.createAndEncodeEncoder(encodedSize); + encoder.encodeMap(keyClass, valueClass, val); + }; + + // Message ------------------------------------------------------------------ + + var kMessageInterfaceIdOffset = kStructHeaderSize; + var kMessageNameOffset = kMessageInterfaceIdOffset + 4; + var kMessageFlagsOffset = kMessageNameOffset + 4; + var kMessageRequestIDOffset = kMessageFlagsOffset + 8; + + var kMessageExpectsResponse = 1 << 0; + var kMessageIsResponse = 1 << 1; + + function Message(buffer, handles) { + this.buffer = buffer; + this.handles = handles; + } + + Message.prototype.getHeaderNumBytes = function() { + return this.buffer.getUint32(kStructHeaderNumBytesOffset); + }; + + Message.prototype.getHeaderVersion = function() { + return this.buffer.getUint32(kStructHeaderVersionOffset); + }; + + Message.prototype.getName = function() { + return this.buffer.getUint32(kMessageNameOffset); + }; + + Message.prototype.getFlags = function() { + return this.buffer.getUint32(kMessageFlagsOffset); + }; + + Message.prototype.getInterfaceId = function() { + return this.buffer.getUint32(kMessageInterfaceIdOffset); + }; + + Message.prototype.isResponse = function() { + return (this.getFlags() & kMessageIsResponse) != 0; + }; + + Message.prototype.expectsResponse = function() { + return (this.getFlags() & kMessageExpectsResponse) != 0; + }; + + Message.prototype.setRequestID = function(requestID) { + // TODO(darin): Verify that space was reserved for this field! + this.buffer.setUint64(kMessageRequestIDOffset, requestID); + }; + + Message.prototype.setInterfaceId = function(interfaceId) { + this.buffer.setUint32(kMessageInterfaceIdOffset, interfaceId); + }; + + + // MessageBuilder ----------------------------------------------------------- + + function MessageBuilder(messageName, payloadSize) { + // Currently, we don't compute the payload size correctly ahead of time. + // Instead, we resize the buffer at the end. + var numberOfBytes = kMessageHeaderSize + payloadSize; + this.buffer = new buffer.Buffer(numberOfBytes); + this.handles = []; + var encoder = this.createEncoder(kMessageHeaderSize); + encoder.writeUint32(kMessageHeaderSize); + encoder.writeUint32(0); // version. + encoder.writeUint32(0); // interface ID. + encoder.writeUint32(messageName); + encoder.writeUint32(0); // flags. + encoder.writeUint32(0); // padding. + } + + MessageBuilder.prototype.createEncoder = function(size) { + var pointer = this.buffer.alloc(size); + return new Encoder(this.buffer, this.handles, pointer); + }; + + MessageBuilder.prototype.encodeStruct = function(cls, val) { + cls.encode(this.createEncoder(cls.encodedSize), val); + }; + + MessageBuilder.prototype.finish = function() { + // TODO(abarth): Rather than resizing the buffer at the end, we could + // compute the size we need ahead of time, like we do in C++. + this.buffer.trim(); + var message = new Message(this.buffer, this.handles); + this.buffer = null; + this.handles = null; + this.encoder = null; + return message; + }; + + // MessageWithRequestIDBuilder ----------------------------------------------- + + function MessageWithRequestIDBuilder(messageName, payloadSize, flags, + requestID) { + // Currently, we don't compute the payload size correctly ahead of time. + // Instead, we resize the buffer at the end. + var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize; + this.buffer = new buffer.Buffer(numberOfBytes); + this.handles = []; + var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize); + encoder.writeUint32(kMessageWithRequestIDHeaderSize); + encoder.writeUint32(1); // version. + encoder.writeUint32(0); // interface ID. + encoder.writeUint32(messageName); + encoder.writeUint32(flags); + encoder.writeUint32(0); // padding. + encoder.writeUint64(requestID); + } + + MessageWithRequestIDBuilder.prototype = + Object.create(MessageBuilder.prototype); + + MessageWithRequestIDBuilder.prototype.constructor = + MessageWithRequestIDBuilder; + + // MessageReader ------------------------------------------------------------ + + function MessageReader(message) { + this.decoder = new Decoder(message.buffer, message.handles, 0); + var messageHeaderSize = this.decoder.readUint32(); + this.payloadSize = message.buffer.byteLength - messageHeaderSize; + var version = this.decoder.readUint32(); + var interface_id = this.decoder.readUint32(); + this.messageName = this.decoder.readUint32(); + this.flags = this.decoder.readUint32(); + // Skip the padding. + this.decoder.skip(4); + if (version >= 1) + this.requestID = this.decoder.readUint64(); + this.decoder.skip(messageHeaderSize - this.decoder.next); + } + + MessageReader.prototype.decodeStruct = function(cls) { + return cls.decode(this.decoder); + }; + + // Built-in types ----------------------------------------------------------- + + // This type is only used with ArrayOf(PackedBool). + function PackedBool() { + } + + function Int8() { + } + + Int8.encodedSize = 1; + + Int8.decode = function(decoder) { + return decoder.readInt8(); + }; + + Int8.encode = function(encoder, val) { + encoder.writeInt8(val); + }; + + Uint8.encode = function(encoder, val) { + encoder.writeUint8(val); + }; + + function Uint8() { + } + + Uint8.encodedSize = 1; + + Uint8.decode = function(decoder) { + return decoder.readUint8(); + }; + + Uint8.encode = function(encoder, val) { + encoder.writeUint8(val); + }; + + function Int16() { + } + + Int16.encodedSize = 2; + + Int16.decode = function(decoder) { + return decoder.readInt16(); + }; + + Int16.encode = function(encoder, val) { + encoder.writeInt16(val); + }; + + function Uint16() { + } + + Uint16.encodedSize = 2; + + Uint16.decode = function(decoder) { + return decoder.readUint16(); + }; + + Uint16.encode = function(encoder, val) { + encoder.writeUint16(val); + }; + + function Int32() { + } + + Int32.encodedSize = 4; + + Int32.decode = function(decoder) { + return decoder.readInt32(); + }; + + Int32.encode = function(encoder, val) { + encoder.writeInt32(val); + }; + + function Uint32() { + } + + Uint32.encodedSize = 4; + + Uint32.decode = function(decoder) { + return decoder.readUint32(); + }; + + Uint32.encode = function(encoder, val) { + encoder.writeUint32(val); + }; + + function Int64() { + } + + Int64.encodedSize = 8; + + Int64.decode = function(decoder) { + return decoder.readInt64(); + }; + + Int64.encode = function(encoder, val) { + encoder.writeInt64(val); + }; + + function Uint64() { + } + + Uint64.encodedSize = 8; + + Uint64.decode = function(decoder) { + return decoder.readUint64(); + }; + + Uint64.encode = function(encoder, val) { + encoder.writeUint64(val); + }; + + function String() { + }; + + String.encodedSize = 8; + + String.decode = function(decoder) { + return decoder.decodeStringPointer(); + }; + + String.encode = function(encoder, val) { + encoder.encodeStringPointer(val); + }; + + function NullableString() { + } + + NullableString.encodedSize = String.encodedSize; + + NullableString.decode = String.decode; + + NullableString.encode = String.encode; + + function Float() { + } + + Float.encodedSize = 4; + + Float.decode = function(decoder) { + return decoder.readFloat(); + }; + + Float.encode = function(encoder, val) { + encoder.writeFloat(val); + }; + + function Double() { + } + + Double.encodedSize = 8; + + Double.decode = function(decoder) { + return decoder.readDouble(); + }; + + Double.encode = function(encoder, val) { + encoder.writeDouble(val); + }; + + function Enum(cls) { + this.cls = cls; + } + + Enum.prototype.encodedSize = 4; + + Enum.prototype.decode = function(decoder) { + return decoder.readInt32(); + }; + + Enum.prototype.encode = function(encoder, val) { + encoder.writeInt32(val); + }; + + function PointerTo(cls) { + this.cls = cls; + } + + PointerTo.prototype.encodedSize = 8; + + PointerTo.prototype.decode = function(decoder) { + var pointer = decoder.decodePointer(); + if (!pointer) { + return null; + } + return this.cls.decode(decoder.decodeAndCreateDecoder(pointer)); + }; + + PointerTo.prototype.encode = function(encoder, val) { + if (!val) { + encoder.encodePointer(val); + return; + } + var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize); + this.cls.encode(objectEncoder, val); + }; + + function NullablePointerTo(cls) { + PointerTo.call(this, cls); + } + + NullablePointerTo.prototype = Object.create(PointerTo.prototype); + + function ArrayOf(cls, length) { + this.cls = cls; + this.length = length || 0; + } + + ArrayOf.prototype.encodedSize = 8; + + ArrayOf.prototype.dimensions = function() { + return [this.length].concat( + (this.cls instanceof ArrayOf) ? this.cls.dimensions() : []); + } + + ArrayOf.prototype.decode = function(decoder) { + return decoder.decodeArrayPointer(this.cls); + }; + + ArrayOf.prototype.encode = function(encoder, val) { + encoder.encodeArrayPointer(this.cls, val); + }; + + function NullableArrayOf(cls) { + ArrayOf.call(this, cls); + } + + NullableArrayOf.prototype = Object.create(ArrayOf.prototype); + + function Handle() { + } + + Handle.encodedSize = 4; + + Handle.decode = function(decoder) { + return decoder.decodeHandle(); + }; + + Handle.encode = function(encoder, val) { + encoder.encodeHandle(val); + }; + + function NullableHandle() { + } + + NullableHandle.encodedSize = Handle.encodedSize; + + NullableHandle.decode = Handle.decode; + + NullableHandle.encode = Handle.encode; + + function Interface(cls) { + this.cls = cls; + } + + Interface.prototype.encodedSize = 8; + + Interface.prototype.decode = function(decoder) { + var interfacePtrInfo = new types.InterfacePtrInfo( + decoder.decodeHandle(), decoder.readUint32()); + var interfacePtr = new this.cls(); + interfacePtr.ptr.bind(interfacePtrInfo); + return interfacePtr; + }; + + Interface.prototype.encode = function(encoder, val) { + var interfacePtrInfo = + val ? val.ptr.passInterface() : new types.InterfacePtrInfo(null, 0); + encoder.encodeHandle(interfacePtrInfo.handle); + encoder.writeUint32(interfacePtrInfo.version); + }; + + function NullableInterface(cls) { + Interface.call(this, cls); + } + + NullableInterface.prototype = Object.create(Interface.prototype); + + function InterfaceRequest() { + } + + InterfaceRequest.encodedSize = 4; + + InterfaceRequest.decode = function(decoder) { + return new types.InterfaceRequest(decoder.decodeHandle()); + }; + + InterfaceRequest.encode = function(encoder, val) { + encoder.encodeHandle(val ? val.handle : null); + }; + + function NullableInterfaceRequest() { + } + + NullableInterfaceRequest.encodedSize = InterfaceRequest.encodedSize; + + NullableInterfaceRequest.decode = InterfaceRequest.decode; + + NullableInterfaceRequest.encode = InterfaceRequest.encode; + + function MapOf(keyClass, valueClass) { + this.keyClass = keyClass; + this.valueClass = valueClass; + } + + MapOf.prototype.encodedSize = 8; + + MapOf.prototype.decode = function(decoder) { + return decoder.decodeMapPointer(this.keyClass, this.valueClass); + }; + + MapOf.prototype.encode = function(encoder, val) { + encoder.encodeMapPointer(this.keyClass, this.valueClass, val); + }; + + function NullableMapOf(keyClass, valueClass) { + MapOf.call(this, keyClass, valueClass); + } + + NullableMapOf.prototype = Object.create(MapOf.prototype); + + var exports = {}; + exports.align = align; + exports.isAligned = isAligned; + exports.Message = Message; + exports.MessageBuilder = MessageBuilder; + exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder; + exports.MessageReader = MessageReader; + exports.kArrayHeaderSize = kArrayHeaderSize; + exports.kMapStructPayloadSize = kMapStructPayloadSize; + exports.kStructHeaderSize = kStructHeaderSize; + exports.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue; + exports.kMessageHeaderSize = kMessageHeaderSize; + exports.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize; + exports.kMessageExpectsResponse = kMessageExpectsResponse; + exports.kMessageIsResponse = kMessageIsResponse; + exports.Int8 = Int8; + exports.Uint8 = Uint8; + exports.Int16 = Int16; + exports.Uint16 = Uint16; + exports.Int32 = Int32; + exports.Uint32 = Uint32; + exports.Int64 = Int64; + exports.Uint64 = Uint64; + exports.Float = Float; + exports.Double = Double; + exports.String = String; + exports.Enum = Enum; + exports.NullableString = NullableString; + exports.PointerTo = PointerTo; + exports.NullablePointerTo = NullablePointerTo; + exports.ArrayOf = ArrayOf; + exports.NullableArrayOf = NullableArrayOf; + exports.PackedBool = PackedBool; + exports.Handle = Handle; + exports.NullableHandle = NullableHandle; + exports.Interface = Interface; + exports.NullableInterface = NullableInterface; + exports.InterfaceRequest = InterfaceRequest; + exports.NullableInterfaceRequest = NullableInterfaceRequest; + exports.MapOf = MapOf; + exports.NullableMapOf = NullableMapOf; + return exports; +}); diff --git a/mojo/public/js/connector.js b/mojo/public/js/connector.js new file mode 100644 index 0000000000..012e3c7c07 --- /dev/null +++ b/mojo/public/js/connector.js @@ -0,0 +1,113 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/connector", [ + "mojo/public/js/buffer", + "mojo/public/js/codec", + "mojo/public/js/core", + "mojo/public/js/support", +], function(buffer, codec, core, support) { + + function Connector(handle) { + if (!core.isHandle(handle)) + throw new Error("Connector: not a handle " + handle); + this.handle_ = handle; + this.dropWrites_ = false; + this.error_ = false; + this.incomingReceiver_ = null; + this.readWatcher_ = null; + this.errorHandler_ = null; + + if (handle) { + this.readWatcher_ = support.watch(handle, + core.HANDLE_SIGNAL_READABLE, + this.readMore_.bind(this)); + } + } + + Connector.prototype.close = function() { + if (this.readWatcher_) { + support.cancelWatch(this.readWatcher_); + this.readWatcher_ = null; + } + if (this.handle_ != null) { + core.close(this.handle_); + this.handle_ = null; + } + }; + + Connector.prototype.accept = function(message) { + if (this.error_) + return false; + + if (this.dropWrites_) + return true; + + var result = core.writeMessage(this.handle_, + new Uint8Array(message.buffer.arrayBuffer), + message.handles, + core.WRITE_MESSAGE_FLAG_NONE); + switch (result) { + case core.RESULT_OK: + // The handles were successfully transferred, so we don't own them + // anymore. + message.handles = []; + break; + case core.RESULT_FAILED_PRECONDITION: + // There's no point in continuing to write to this pipe since the other + // end is gone. Avoid writing any future messages. Hide write failures + // from the caller since we'd like them to continue consuming any + // backlog of incoming messages before regarding the message pipe as + // closed. + this.dropWrites_ = true; + break; + default: + // This particular write was rejected, presumably because of bad input. + // The pipe is not necessarily in a bad state. + return false; + } + return true; + }; + + Connector.prototype.setIncomingReceiver = function(receiver) { + this.incomingReceiver_ = receiver; + }; + + Connector.prototype.setErrorHandler = function(handler) { + this.errorHandler_ = handler; + }; + + Connector.prototype.waitForNextMessageForTesting = function() { + var wait = core.wait(this.handle_, core.HANDLE_SIGNAL_READABLE); + this.readMore_(wait.result); + }; + + Connector.prototype.readMore_ = function(result) { + for (;;) { + var read = core.readMessage(this.handle_, + core.READ_MESSAGE_FLAG_NONE); + if (this.handle_ == null) // The connector has been closed. + return; + if (read.result == core.RESULT_SHOULD_WAIT) + return; + if (read.result != core.RESULT_OK) { + // TODO(wangjimmy): Add a handleError method to swap the handle to be + // closed with a dummy handle in the case when + // read.result != MOJO_RESULT_FAILED_PRECONDITION + this.error_ = true; + if (this.errorHandler_) + this.errorHandler_.onError(); + return; + } + var messageBuffer = new buffer.Buffer(read.buffer); + var message = new codec.Message(messageBuffer, read.handles); + if (this.incomingReceiver_) + this.incomingReceiver_.accept(message); + } + }; + + var exports = {}; + exports.Connector = Connector; + return exports; +}); diff --git a/mojo/public/js/constants.cc b/mojo/public/js/constants.cc new file mode 100644 index 0000000000..a0ce7d4d1d --- /dev/null +++ b/mojo/public/js/constants.cc @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/js/constants.h" + +namespace mojo { + +const char kBindingsModuleName[] = "mojo/public/js/bindings"; +const char kBufferModuleName[] = "mojo/public/js/buffer"; +const char kCodecModuleName[] = "mojo/public/js/codec"; +const char kConnectorModuleName[] = "mojo/public/js/connector"; +const char kControlMessageHandlerModuleName[] = + "mojo/public/js/lib/control_message_handler"; +const char kControlMessageProxyModuleName[] = + "mojo/public/js/lib/control_message_proxy"; +const char kInterfaceControlMessagesMojom[] = + "mojo/public/interfaces/bindings/interface_control_messages.mojom"; +const char kInterfaceEndpointClientModuleName[] = + "mojo/public/js/lib/interface_endpoint_client"; +const char kInterfaceEndpointHandleModuleName[] = + "mojo/public/js/lib/interface_endpoint_handle"; +const char kInterfaceTypesModuleName[] = "mojo/public/js/interface_types"; +const char kPipeControlMessageHandlerModuleName[] = + "mojo/public/js/lib/pipe_control_message_handler"; +const char kPipeControlMessageProxyModuleName[] = + "mojo/public/js/lib/pipe_control_message_proxy"; +const char kPipeControlMessagesMojom[] = + "mojo/public/interfaces/bindings/pipe_control_messages.mojom"; +const char kRouterModuleName[] = "mojo/public/js/router"; +const char kUnicodeModuleName[] = "mojo/public/js/unicode"; +const char kValidatorModuleName[] = "mojo/public/js/validator"; +} // namespace mojo diff --git a/mojo/public/js/constants.h b/mojo/public/js/constants.h new file mode 100644 index 0000000000..f561d739b9 --- /dev/null +++ b/mojo/public/js/constants.h @@ -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. + +#ifndef MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_ +#define MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_ + +namespace mojo { + +// JavaScript module names: +extern const char kBindingsModuleName[]; +extern const char kBufferModuleName[]; +extern const char kCodecModuleName[]; +extern const char kConnectorModuleName[]; +extern const char kControlMessageHandlerModuleName[]; +extern const char kControlMessageProxyModuleName[]; +extern const char kInterfaceControlMessagesMojom[]; +extern const char kInterfaceEndpointClientModuleName[]; +extern const char kInterfaceEndpointHandleModuleName[]; +extern const char kInterfaceTypesModuleName[]; +extern const char kPipeControlMessageHandlerModuleName[]; +extern const char kPipeControlMessageProxyModuleName[]; +extern const char kPipeControlMessagesMojom[]; +extern const char kRouterModuleName[]; +extern const char kUnicodeModuleName[]; +extern const char kValidatorModuleName[]; + +} // namespace mojo + +#endif // MOJO_PUBLIC_JS_BINDINGS_CONSTANTS_H_ diff --git a/mojo/public/js/core.js b/mojo/public/js/core.js new file mode 100644 index 0000000000..b2c4ee2733 --- /dev/null +++ b/mojo/public/js/core.js @@ -0,0 +1,304 @@ +// 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. + +// Module "mojo/public/js/core" +// +// Note: This file is for documentation purposes only. The code here is not +// actually executed. The real module is implemented natively in Mojo. +// +// This module provides the JavaScript bindings for mojo/public/c/system/core.h. +// Refer to that file for more detailed documentation for equivalent methods. + +while (1); + +/** + * MojoHandle: An opaque handles to a Mojo object (e.g. a message pipe). + */ +var kInvalidHandle; + +/** + * MojoResult {number}: Result codes for Mojo operations. + * See core.h for more information. + */ +var RESULT_OK; +var RESULT_CANCELLED; +var RESULT_UNKNOWN; +var RESULT_INVALID_ARGUMENT; +var RESULT_DEADLINE_EXCEEDED; +var RESULT_NOT_FOUND; +var RESULT_ALREADY_EXISTS; +var RESULT_PERMISSION_DENIED; +var RESULT_RESOURCE_EXHAUSTED; +var RESULT_FAILED_PRECONDITION; +var RESULT_ABORTED; +var RESULT_OUT_OF_RANGE; +var RESULT_UNIMPLEMENTED; +var RESULT_INTERNAL; +var RESULT_UNAVAILABLE; +var RESULT_DATA_LOSS; +var RESULT_BUSY; +var RESULT_SHOULD_WAIT; + +/** + * MojoHandleSignals: Used to specify signals that can be waited on for a handle + *(and which can be triggered), e.g., the ability to read or write to + * the handle. + * See core.h for more information. + */ +var HANDLE_SIGNAL_NONE; +var HANDLE_SIGNAL_READABLE; +var HANDLE_SIGNAL_WRITABLE; +var HANDLE_SIGNAL_PEER_CLOSED; + +/** + * MojoCreateDataMessageOptions: Used to specify creation parameters for a data + * pipe to |createDataMessage()|. + * See core.h for more information. + */ +dictionary MojoCreateDataMessageOptions { + MojoCreateDataMessageOptionsFlags flags; // See below. +}; + +// MojoCreateDataMessageOptionsFlags +var CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE; + +/* + * MojoWriteMessageFlags: Used to specify different modes to |writeMessage()|. + * See core.h for more information. + */ +var WRITE_MESSAGE_FLAG_NONE; + +/** + * MojoReadMessageFlags: Used to specify different modes to |readMessage()|. + * See core.h for more information. + */ +var READ_MESSAGE_FLAG_NONE; +var READ_MESSAGE_FLAG_MAY_DISCARD; + +/** + * MojoCreateDataPipeOptions: Used to specify creation parameters for a data + * pipe to |createDataPipe()|. + * See core.h for more information. + */ +dictionary MojoCreateDataPipeOptions { + MojoCreateDataPipeOptionsFlags flags; // See below. + int32 elementNumBytes; // The size of an element, in bytes. + int32 capacityNumBytes; // The capacity of the data pipe, in bytes. +}; + +// MojoCreateDataPipeOptionsFlags +var CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + +/* + * MojoWriteDataFlags: Used to specify different modes to |writeData()|. + * See core.h for more information. + */ +var WRITE_DATA_FLAG_NONE; +var WRITE_DATA_FLAG_ALL_OR_NONE; + +/** + * MojoReadDataFlags: Used to specify different modes to |readData()|. + * See core.h for more information. + */ +var READ_DATA_FLAG_NONE; +var READ_DATA_FLAG_ALL_OR_NONE; +var READ_DATA_FLAG_DISCARD; +var READ_DATA_FLAG_QUERY; +var READ_DATA_FLAG_PEEK; + +/** + * MojoCreateSharedBufferOptionsFlags: Used to specify options to + * |createSharedBuffer()|. + * See core.h for more information. + */ +var CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + +/** + * MojoDuplicateBufferHandleOptionsFlags: Used to specify options to + * |duplicateBufferHandle()|. + * See core.h for more information. + */ +var DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE; +var DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; + +/** + * MojoMapBufferFlags: Used to specify options to |mapBuffer()|. + * See core.h for more information. + */ +var MAP_BUFFER_FLAG_NONE; + +/** + * Closes the given |handle|. See MojoClose for more info. + * @param {MojoHandle} Handle to close. + * @return {MojoResult} Result code. + */ +function close(handle) { [native code] } + +/** + * Queries the last known signaling state of |handle|. + * + * @param {MojoHandle} handle Handle to query. + * @return {object} An object of the form { + * result, // MOJO_RESULT_OK or MOJO_RESULT_INVALID_ARGUMENT + * satisfiedSignals, // MojoHandleSignals (see above) + * satisfiableSignals, // MojoHandleSignals + * } + */ +function queryHandleSignalsState(handle) { [native code] } + +/** + * Waits on the given handle until a signal indicated by |signals| is + * satisfied or an error occurs. + * + * @param {MojoHandle} handle Handle to wait on. + * @param {MojoHandleSignals} signals Specifies the condition to wait for. + * @return {MojoResult} Result code. + */ +function wait(handle, signals) { [native code] } + +/** + * Creates a message pipe. This function always succeeds. + * See MojoCreateMessagePipe for more information on message pipes. + * + * @param {MojoCreateMessagePipeOptions} optionsDict Options to control the + * message pipe parameters. May be null. + * @return {MessagePipe} An object of the form { + * handle0, + * handle1, + * } + * where |handle0| and |handle1| are MojoHandles to each end of the channel. + */ +function createMessagePipe(optionsDict) { [native code] } + +/** + * Writes a message to the message pipe endpoint given by |handle|. See + * MojoWriteMessage for more information, including return codes. + * + * @param {MojoHandle} handle The endpoint to write to. + * @param {ArrayBufferView} buffer The message data. May be empty. + * @param {Array.MojoHandle} handlesArray Any handles to attach. Handles are + * transferred on success and will no longer be valid. May be empty. + * @param {MojoWriteMessageFlags} flags Flags. + * @return {MojoResult} Result code. + */ +function writeMessage(handle, buffer, handlesArray, flags) { [native code] } + +/** + * Reads a message from the message pipe endpoint given by |handle|. See + * MojoReadMessage for more information, including return codes. + * + * @param {MojoHandle} handle The endpoint to read from. + * @param {MojoReadMessageFlags} flags Flags. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * buffer, // An ArrayBufferView of the message data (only on success). + * handles // An array of MojoHandles transferred, if any. + * } + */ +function readMessage(handle, flags) { [native code] } + +/** + * Creates a data pipe, which is a unidirectional communication channel for + * unframed data, with the given options. See MojoCreateDataPipe for more + * more information, including return codes. + * + * @param {MojoCreateDataPipeOptions} optionsDict Options to control the data + * pipe parameters. May be null. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * producerHandle, // MojoHandle to use with writeData (only on success). + * consumerHandle, // MojoHandle to use with readData (only on success). + * } + */ +function createDataPipe(optionsDict) { [native code] } + +/** + * Writes the given data to the data pipe producer given by |handle|. See + * MojoWriteData for more information, including return codes. + * + * @param {MojoHandle} handle A producerHandle returned by createDataPipe. + * @param {ArrayBufferView} buffer The data to write. + * @param {MojoWriteDataFlags} flags Flags. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * numBytes, // The number of bytes written. + * } + */ +function writeData(handle, buffer, flags) { [native code] } + +/** + * Reads data from the data pipe consumer given by |handle|. May also + * be used to discard data. See MojoReadData for more information, including + * return codes. + * + * @param {MojoHandle} handle A consumerHandle returned by createDataPipe. + * @param {MojoReadDataFlags} flags Flags. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * buffer, // An ArrayBufferView of the data read (only on success). + * } + */ +function readData(handle, flags) { [native code] } + +/** + * True if the argument is a message or data pipe handle. + * + * @param {value} an arbitrary JS value. + * @return true or false + */ +function isHandle(value) { [native code] } + +/** + * Creates shared buffer of specified size |num_bytes|. + * See MojoCreateSharedBuffer for more information including error codes. + * + * @param {number} num_bytes Size of the memory to be allocated for shared + * @param {MojoCreateSharedBufferOptionsFlags} flags Flags. + * buffer. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * handle, // An MojoHandle for shared buffer (only on success). + * } + */ +function createSharedBuffer(num_bytes, flags) { [native code] } + +/** + * Duplicates the |buffer_handle| to a shared buffer. Duplicated handle can be + * sent to another process over message pipe. See MojoDuplicateBufferHandle for + * more information including error codes. + * + * @param {MojoHandle} buffer_handle MojoHandle. + * @param {MojoCreateSharedBufferOptionsFlags} flags Flags. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * handle, // A duplicated MojoHandle for shared buffer (only on success). + * } + */ +function duplicateBufferHandle(buffer_handle, flags) { [native code] } + +/** + * Maps the part (at offset |offset| of length |num_bytes|) of the buffer given + * by |buffer_handle| into ArrayBuffer memory |buffer|, with options specified + * by |flags|. See MojoMapBuffer for more information including error codes. + * + * @param {MojoHandle} buffer_handle A sharedBufferHandle returned by + * createSharedBuffer. + * @param {number} offset Offset. + * @param {number} num_bytes Size of the memory to be mapped. + * @param {MojoMapBufferFlags} flags Flags. + * @return {object} An object of the form { + * result, // |RESULT_OK| on success, error code otherwise. + * buffer, // An ArrayBuffer (only on success). + * } + */ +function mapBuffer(buffer_handle, offset, num_bytes, flags) { [native code] } + +/** + * Unmaps buffer that was mapped using mapBuffer. + * See MojoUnmapBuffer for more information including error codes. + * + * @param {ArrayBuffer} buffer ArrayBuffer. + * @return {MojoResult} Result code. + */ +function unmapBuffer(buffer) { [native code] } diff --git a/mojo/public/js/interface_types.js b/mojo/public/js/interface_types.js new file mode 100644 index 0000000000..e8ed37ae64 --- /dev/null +++ b/mojo/public/js/interface_types.js @@ -0,0 +1,70 @@ +// 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. + +define("mojo/public/js/interface_types", [ + "mojo/public/js/core", +], function(core) { + + // Constants ---------------------------------------------------------------- + var kInterfaceIdNamespaceMask = 0x80000000; + var kMasterInterfaceId = 0x00000000; + var kInvalidInterfaceId = 0xFFFFFFFF; + + // --------------------------------------------------------------------------- + + function InterfacePtrInfo(handle, version) { + this.handle = handle; + this.version = version; + } + + InterfacePtrInfo.prototype.isValid = function() { + return core.isHandle(this.handle); + }; + + InterfacePtrInfo.prototype.close = function() { + if (!this.isValid()) + return; + + core.close(this.handle); + this.handle = null; + this.version = 0; + }; + + // --------------------------------------------------------------------------- + + function InterfaceRequest(handle) { + this.handle = handle; + } + + InterfaceRequest.prototype.isValid = function() { + return core.isHandle(this.handle); + }; + + InterfaceRequest.prototype.close = function() { + if (!this.isValid()) + return; + + core.close(this.handle); + this.handle = null; + }; + + function isMasterInterfaceId(interfaceId) { + return interfaceId === kMasterInterfaceId; + } + + function isValidInterfaceId(interfaceId) { + return interfaceId !== kInvalidInterfaceId; + } + + var exports = {}; + exports.InterfacePtrInfo = InterfacePtrInfo; + exports.InterfaceRequest = InterfaceRequest; + exports.isMasterInterfaceId = isMasterInterfaceId; + exports.isValidInterfaceId = isValidInterfaceId; + exports.kInvalidInterfaceId = kInvalidInterfaceId; + exports.kMasterInterfaceId = kMasterInterfaceId; + exports.kInterfaceIdNamespaceMask = kInterfaceIdNamespaceMask; + + return exports; +}); diff --git a/mojo/public/js/lib/control_message_handler.js b/mojo/public/js/lib/control_message_handler.js new file mode 100644 index 0000000000..5da306e371 --- /dev/null +++ b/mojo/public/js/lib/control_message_handler.js @@ -0,0 +1,111 @@ +// 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. + +define("mojo/public/js/lib/control_message_handler", [ + "mojo/public/interfaces/bindings/interface_control_messages.mojom", + "mojo/public/js/codec", + "mojo/public/js/validator", +], function(controlMessages, codec, validator) { + + var Validator = validator.Validator; + + function validateControlRequestWithResponse(message) { + var messageValidator = new Validator(message); + var error = messageValidator.validateMessageIsRequestExpectingResponse(); + if (error !== validator.validationError.NONE) { + throw error; + } + + if (message.getName() != controlMessages.kRunMessageId) { + throw new Error("Control message name is not kRunMessageId"); + } + + // Validate payload. + error = controlMessages.RunMessageParams.validate(messageValidator, + message.getHeaderNumBytes()); + if (error != validator.validationError.NONE) { + throw error; + } + } + + function validateControlRequestWithoutResponse(message) { + var messageValidator = new Validator(message); + var error = messageValidator.validateMessageIsRequestWithoutResponse(); + if (error != validator.validationError.NONE) { + throw error; + } + + if (message.getName() != controlMessages.kRunOrClosePipeMessageId) { + throw new Error("Control message name is not kRunOrClosePipeMessageId"); + } + + // Validate payload. + error = controlMessages.RunOrClosePipeMessageParams.validate( + messageValidator, message.getHeaderNumBytes()); + if (error != validator.validationError.NONE) { + throw error; + } + } + + function runOrClosePipe(message, interface_version) { + var reader = new codec.MessageReader(message); + var runOrClosePipeMessageParams = reader.decodeStruct( + controlMessages.RunOrClosePipeMessageParams); + return interface_version >= + runOrClosePipeMessageParams.input.require_version.version; + } + + function run(message, responder, interface_version) { + var reader = new codec.MessageReader(message); + var runMessageParams = + reader.decodeStruct(controlMessages.RunMessageParams); + var runOutput = null; + + if (runMessageParams.input.query_version) { + runOutput = new controlMessages.RunOutput(); + runOutput.query_version_result = new + controlMessages.QueryVersionResult({'version': interface_version}); + } + + var runResponseMessageParams = new + controlMessages.RunResponseMessageParams(); + runResponseMessageParams.output = runOutput; + + var messageName = controlMessages.kRunMessageId; + var payloadSize = controlMessages.RunResponseMessageParams.encodedSize; + var requestID = reader.requestID; + var builder = new codec.MessageWithRequestIDBuilder(messageName, + payloadSize, codec.kMessageIsResponse, requestID); + builder.encodeStruct(controlMessages.RunResponseMessageParams, + runResponseMessageParams); + responder.accept(builder.finish()); + return true; + } + + function isControlMessage(message) { + return message.getName() == controlMessages.kRunMessageId || + message.getName() == controlMessages.kRunOrClosePipeMessageId; + } + + function ControlMessageHandler(interface_version) { + this.interface_version_ = interface_version; + } + + ControlMessageHandler.prototype.accept = function(message) { + validateControlRequestWithoutResponse(message); + return runOrClosePipe(message, this.interface_version_); + }; + + ControlMessageHandler.prototype.acceptWithResponder = function(message, + responder) { + validateControlRequestWithResponse(message); + return run(message, responder, this.interface_version_); + }; + + var exports = {}; + exports.ControlMessageHandler = ControlMessageHandler; + exports.isControlMessage = isControlMessage; + + return exports; +}); diff --git a/mojo/public/js/lib/control_message_proxy.js b/mojo/public/js/lib/control_message_proxy.js new file mode 100644 index 0000000000..b6f1d3c83c --- /dev/null +++ b/mojo/public/js/lib/control_message_proxy.js @@ -0,0 +1,104 @@ +// 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. + +define("mojo/public/js/lib/control_message_proxy", [ + "mojo/public/interfaces/bindings/interface_control_messages.mojom", + "mojo/public/js/codec", + "mojo/public/js/validator", +], function(controlMessages, codec, validator) { + + var Validator = validator.Validator; + + function constructRunOrClosePipeMessage(runOrClosePipeInput) { + var runOrClosePipeMessageParams = new + controlMessages.RunOrClosePipeMessageParams(); + runOrClosePipeMessageParams.input = runOrClosePipeInput; + + var messageName = controlMessages.kRunOrClosePipeMessageId; + var payloadSize = controlMessages.RunOrClosePipeMessageParams.encodedSize; + var builder = new codec.MessageBuilder(messageName, payloadSize); + builder.encodeStruct(controlMessages.RunOrClosePipeMessageParams, + runOrClosePipeMessageParams); + var message = builder.finish(); + return message; + } + + function validateControlResponse(message) { + var messageValidator = new Validator(message); + var error = messageValidator.validateMessageIsResponse(); + if (error != validator.validationError.NONE) { + throw error; + } + + if (message.getName() != controlMessages.kRunMessageId) { + throw new Error("Control message name is not kRunMessageId"); + } + + // Validate payload. + error = controlMessages.RunResponseMessageParams.validate( + messageValidator, message.getHeaderNumBytes()); + if (error != validator.validationError.NONE) { + throw error; + } + } + + function acceptRunResponse(message) { + validateControlResponse(message); + + var reader = new codec.MessageReader(message); + var runResponseMessageParams = reader.decodeStruct( + controlMessages.RunResponseMessageParams); + + return Promise.resolve(runResponseMessageParams); + } + + /** + * Sends the given run message through the receiver. + * Accepts the response message from the receiver and decodes the message + * struct to RunResponseMessageParams. + * + * @param {Router} receiver. + * @param {RunMessageParams} runMessageParams to be sent via a message. + * @return {Promise} that resolves to a RunResponseMessageParams. + */ + function sendRunMessage(receiver, runMessageParams) { + var messageName = controlMessages.kRunMessageId; + var payloadSize = controlMessages.RunMessageParams.encodedSize; + // |requestID| is set to 0, but is later properly set by Router. + var builder = new codec.MessageWithRequestIDBuilder(messageName, + payloadSize, codec.kMessageExpectsResponse, 0); + builder.encodeStruct(controlMessages.RunMessageParams, runMessageParams); + var message = builder.finish(); + + return receiver.acceptAndExpectResponse(message).then(acceptRunResponse); + } + + function ControlMessageProxy(receiver) { + this.receiver_ = receiver; + } + + ControlMessageProxy.prototype.queryVersion = function() { + var runMessageParams = new controlMessages.RunMessageParams(); + runMessageParams.input = new controlMessages.RunInput(); + runMessageParams.input.query_version = new controlMessages.QueryVersion(); + + return sendRunMessage(this.receiver_, runMessageParams).then(function( + runResponseMessageParams) { + return runResponseMessageParams.output.query_version_result.version; + }); + }; + + ControlMessageProxy.prototype.requireVersion = function(version) { + var runOrClosePipeInput = new controlMessages.RunOrClosePipeInput(); + runOrClosePipeInput.require_version = new controlMessages.RequireVersion({ + 'version': version}); + var message = constructRunOrClosePipeMessage(runOrClosePipeInput); + this.receiver_.accept(message); + }; + + var exports = {}; + exports.ControlMessageProxy = ControlMessageProxy; + + return exports; +}); diff --git a/mojo/public/js/lib/interface_endpoint_client.js b/mojo/public/js/lib/interface_endpoint_client.js new file mode 100644 index 0000000000..631c52ec91 --- /dev/null +++ b/mojo/public/js/lib/interface_endpoint_client.js @@ -0,0 +1,232 @@ +// 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. + +define("mojo/public/js/lib/interface_endpoint_client", [ + "console", + "mojo/public/js/codec", + "mojo/public/js/lib/control_message_handler", + "mojo/public/js/lib/control_message_proxy", + "mojo/public/js/lib/interface_endpoint_handle", + "mojo/public/js/validator", + "timer", +], function(console, + codec, + controlMessageHandler, + controlMessageProxy, + interfaceEndpointHandle, + validator, + timer) { + + var ControlMessageHandler = controlMessageHandler.ControlMessageHandler; + var ControlMessageProxy = controlMessageProxy.ControlMessageProxy; + var MessageReader = codec.MessageReader; + var Validator = validator.Validator; + var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle; + + function InterfaceEndpointClient(interfaceEndpointHandle, receiver, + interfaceVersion) { + this.controller_ = null; + this.encounteredError_ = false; + this.handle_ = interfaceEndpointHandle; + this.incomingReceiver_ = receiver; + + if (interfaceVersion !== undefined) { + this.controlMessageHandler_ = new ControlMessageHandler( + interfaceVersion); + } else { + this.controlMessageProxy_ = new ControlMessageProxy(this); + } + + this.nextRequestID_ = 0; + this.completers_ = new Map(); + this.payloadValidators_ = []; + this.connectionErrorHandler_ = null; + + if (interfaceEndpointHandle.pendingAssociation()) { + interfaceEndpointHandle.setAssociationEventHandler( + this.onAssociationEvent.bind(this)); + } else { + this.initControllerIfNecessary_(); + } + } + + InterfaceEndpointClient.prototype.initControllerIfNecessary_ = function() { + if (this.controller_ || this.handle_.pendingAssociation()) { + return; + } + + this.controller_ = this.handle_.groupController().attachEndpointClient( + this.handle_, this); + }; + + InterfaceEndpointClient.prototype.onAssociationEvent = function( + associationEvent) { + if (associationEvent === + InterfaceEndpointHandle.AssociationEvent.ASSOCIATED) { + this.initControllerIfNecessary_(); + } else if (associationEvent === + InterfaceEndpointHandle.AssociationEvent + .PEER_CLOSED_BEFORE_ASSOCIATION) { + timer.createOneShot(0, this.notifyError.bind(this, + this.handle_.disconnectReason())); + } + }; + + InterfaceEndpointClient.prototype.passHandle = function() { + if (!this.handle_.isValid()) { + return new InterfaceEndpointHandle(); + } + + // Used to clear the previously set callback. + this.handle_.setAssociationEventHandler(undefined); + + if (this.controller_) { + this.controller_ = null; + this.handle_.groupController().detachEndpointClient(this.handle_); + } + var handle = this.handle_; + this.handle_ = null; + return handle; + }; + + InterfaceEndpointClient.prototype.close = function(reason) { + var handle = this.passHandle(); + handle.reset(reason); + }; + + InterfaceEndpointClient.prototype.accept = function(message) { + if (this.encounteredError_) { + return false; + } + + this.initControllerIfNecessary_(); + return this.controller_.sendMessage(message); + }; + + InterfaceEndpointClient.prototype.acceptAndExpectResponse = function( + message) { + if (this.encounteredError_) { + return Promise.reject(); + } + + this.initControllerIfNecessary_(); + + // Reserve 0 in case we want it to convey special meaning in the future. + var requestID = this.nextRequestID_++; + if (requestID === 0) + requestID = this.nextRequestID_++; + + message.setRequestID(requestID); + var result = this.controller_.sendMessage(message); + if (!result) + return Promise.reject(Error("Connection error")); + + var completer = {}; + this.completers_.set(requestID, completer); + return new Promise(function(resolve, reject) { + completer.resolve = resolve; + completer.reject = reject; + }); + }; + + InterfaceEndpointClient.prototype.setPayloadValidators = function( + payloadValidators) { + this.payloadValidators_ = payloadValidators; + }; + + InterfaceEndpointClient.prototype.setIncomingReceiver = function(receiver) { + this.incomingReceiver_ = receiver; + }; + + InterfaceEndpointClient.prototype.setConnectionErrorHandler = function( + handler) { + this.connectionErrorHandler_ = handler; + }; + + InterfaceEndpointClient.prototype.handleIncomingMessage_ = function( + message) { + var noError = validator.validationError.NONE; + var messageValidator = new Validator(message); + var err = noError; + for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i) + err = this.payloadValidators_[i](messageValidator); + + if (err == noError) { + return this.handleValidIncomingMessage_(message); + } else { + validator.reportValidationError(err); + return false; + } + }; + + InterfaceEndpointClient.prototype.handleValidIncomingMessage_ = function( + message) { + if (validator.isTestingMode()) { + return true; + } + + if (this.encounteredError_) { + return false; + } + + var ok = false; + + if (message.expectsResponse()) { + if (controlMessageHandler.isControlMessage(message) && + this.controlMessageHandler_) { + ok = this.controlMessageHandler_.acceptWithResponder(message, this); + } else if (this.incomingReceiver_) { + ok = this.incomingReceiver_.acceptWithResponder(message, this); + } + } else if (message.isResponse()) { + var reader = new MessageReader(message); + var requestID = reader.requestID; + var completer = this.completers_.get(requestID); + if (completer) { + this.completers_.delete(requestID); + completer.resolve(message); + ok = true; + } else { + console.log("Unexpected response with request ID: " + requestID); + } + } else { + if (controlMessageHandler.isControlMessage(message) && + this.controlMessageHandler_) { + ok = this.controlMessageHandler_.accept(message); + } else if (this.incomingReceiver_) { + ok = this.incomingReceiver_.accept(message); + } + } + return ok; + }; + + InterfaceEndpointClient.prototype.notifyError = function(reason) { + if (this.encounteredError_) { + return; + } + this.encounteredError_ = true; + + this.completers_.forEach(function(value) { + value.reject(); + }); + this.completers_.clear(); // Drop any responders. + + if (this.connectionErrorHandler_) { + this.connectionErrorHandler_(reason); + } + }; + + InterfaceEndpointClient.prototype.queryVersion = function() { + return this.controlMessageProxy_.queryVersion(); + }; + + InterfaceEndpointClient.prototype.requireVersion = function(version) { + this.controlMessageProxy_.requireVersion(version); + }; + + var exports = {}; + exports.InterfaceEndpointClient = InterfaceEndpointClient; + + return exports; +}); diff --git a/mojo/public/js/lib/interface_endpoint_handle.js b/mojo/public/js/lib/interface_endpoint_handle.js new file mode 100644 index 0000000000..f48b89ba85 --- /dev/null +++ b/mojo/public/js/lib/interface_endpoint_handle.js @@ -0,0 +1,158 @@ +// 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. + +define("mojo/public/js/lib/interface_endpoint_handle", [ + "mojo/public/js/interface_types", + "timer", +], function(types, timer) { + + var AssociationEvent = { + // The interface has been associated with a message pipe. + ASSOCIATED: 'associated', + // The peer of this object has been closed before association. + PEER_CLOSED_BEFORE_ASSOCIATION: 'peer_closed_before_association' + }; + + function State(interfaceId, associatedGroupController) { + if (interfaceId === undefined) { + interfaceId = types.kInvalidInterfaceId; + } + + this.interfaceId = interfaceId; + this.associatedGroupController = associatedGroupController; + this.pendingAssociation = false; + this.disconnectReason = null; + this.peerState_ = null; + this.associationEventHandler_ = null; + } + + State.prototype.initPendingState = function(peer) { + this.pendingAssociation = true; + this.peerState_ = peer; + }; + + State.prototype.isValid = function() { + return this.pendingAssociation || + types.isValidInterfaceId(this.interfaceId); + }; + + State.prototype.close = function(disconnectReason) { + var cachedGroupController; + var cachedPeerState; + var cachedId = types.kInvalidInterfaceId; + + if (!this.pendingAssociation) { + if (types.isValidInterfaceId(this.interfaceId)) { + cachedGroupController = this.associatedGroupController; + this.associatedGroupController = null; + cachedId = this.interfaceId; + this.interfaceId = types.kInvalidInterfaceId; + } + } else { + this.pendingAssociation = false; + cachedPeerState = this.peerState_; + this.peerState_ = null; + } + + if (cachedGroupController) { + cachedGroupController.closeEndpointHandle(cachedId, + disconnectReason); + } else if (cachedPeerState) { + cachedPeerState.onPeerClosedBeforeAssociation(disconnectReason); + } + }; + + State.prototype.runAssociationEventHandler = function(associationEvent) { + if (this.associationEventHandler_) { + var handler = this.associationEventHandler_; + this.associationEventHandler_ = null; + handler(associationEvent); + } + }; + + State.prototype.setAssociationEventHandler = function(handler) { + if (!this.pendingAssociation && + !types.isValidInterfaceId(this.interfaceId)) { + return; + } + + if (!handler) { + this.associationEventHandler_ = null; + return; + } + + this.associationEventHandler_ = handler; + if (!this.pendingAssociation) { + timer.createOneShot(0, this.runAssociationEventHandler.bind(this, + AssociationEvent.ASSOCIATED)); + } else if (!this.peerState_) { + timer.createOneShot(0, this.runAssociationEventHandler.bind(this, + AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION)); + } + }; + + State.prototype.onAssociated = function(interfaceId, + associatedGroupController) { + if (!this.pendingAssociation) { + return; + } + + this.pendingAssociation = false; + this.peerState_ = null; + this.interfaceId = interfaceId; + this.associatedGroupController = associatedGroupController; + this.runAssociationEventHandler(AssociationEvent.ASSOCIATED); + }; + + State.prototype.onPeerClosedBeforeAssociation = function(disconnectReason) { + if (!this.pendingAssociation) { + return; + } + + this.peerState_ = null; + this.disconnectReason = disconnectReason; + + this.runAssociationEventHandler( + AssociationEvent.PEER_CLOSED_BEFORE_ASSOCIATION); + }; + + function InterfaceEndpointHandle(interfaceId, associatedGroupController) { + this.state_ = new State(interfaceId, associatedGroupController); + } + + InterfaceEndpointHandle.prototype.isValid = function() { + return this.state_.isValid(); + }; + + InterfaceEndpointHandle.prototype.pendingAssociation = function() { + return this.state_.pendingAssociation; + }; + + InterfaceEndpointHandle.prototype.id = function() { + return this.state_.interfaceId; + }; + + InterfaceEndpointHandle.prototype.groupController = function() { + return this.state_.associatedGroupController; + }; + + InterfaceEndpointHandle.prototype.disconnectReason = function() { + return this.state_.disconnectReason; + }; + + InterfaceEndpointHandle.prototype.setAssociationEventHandler = function( + handler) { + this.state_.setAssociationEventHandler(handler); + }; + + InterfaceEndpointHandle.prototype.reset = function(reason) { + this.state_.close(reason); + this.state_ = new State(); + }; + + var exports = {}; + exports.InterfaceEndpointHandle = InterfaceEndpointHandle; + + return exports; +}); diff --git a/mojo/public/js/lib/pipe_control_message_handler.js b/mojo/public/js/lib/pipe_control_message_handler.js new file mode 100644 index 0000000000..2eb45d1626 --- /dev/null +++ b/mojo/public/js/lib/pipe_control_message_handler.js @@ -0,0 +1,61 @@ +// 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. + +define("mojo/public/js/lib/pipe_control_message_handler", [ + "mojo/public/interfaces/bindings/pipe_control_messages.mojom", + "mojo/public/js/codec", + "mojo/public/js/interface_types", + "mojo/public/js/validator", +], function(pipeControlMessages, codec, types, validator) { + + var Validator = validator.Validator; + + function validateControlRequestWithoutResponse(message) { + var messageValidator = new Validator(message); + var error = messageValidator.validateMessageIsRequestWithoutResponse(); + if (error != validator.validationError.NONE) { + throw error; + } + + if (message.getName() != pipeControlMessages.kRunOrClosePipeMessageId) { + throw new Error("Control message name is not kRunOrClosePipeMessageId"); + } + + // Validate payload. + error = pipeControlMessages.RunOrClosePipeMessageParams.validate( + messageValidator, message.getHeaderNumBytes()); + if (error != validator.validationError.NONE) { + throw error; + } + } + + function runOrClosePipe(message, delegate) { + var reader = new codec.MessageReader(message); + var runOrClosePipeMessageParams = reader.decodeStruct( + pipeControlMessages.RunOrClosePipeMessageParams); + var event = runOrClosePipeMessageParams.input + .peer_associated_endpoint_closed_event; + return delegate.onPeerAssociatedEndpointClosed(event.id, + event.disconnect_reason); + } + + function isPipeControlMessage(message) { + return !types.isValidInterfaceId(message.getInterfaceId()); + } + + function PipeControlMessageHandler(delegate) { + this.delegate_ = delegate; + } + + PipeControlMessageHandler.prototype.accept = function(message) { + validateControlRequestWithoutResponse(message); + return runOrClosePipe(message, this.delegate_); + }; + + var exports = {}; + exports.PipeControlMessageHandler = PipeControlMessageHandler; + exports.isPipeControlMessage = isPipeControlMessage; + + return exports; +}); diff --git a/mojo/public/js/lib/pipe_control_message_proxy.js b/mojo/public/js/lib/pipe_control_message_proxy.js new file mode 100644 index 0000000000..4b8e7a20ea --- /dev/null +++ b/mojo/public/js/lib/pipe_control_message_proxy.js @@ -0,0 +1,56 @@ +// 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. + +define("mojo/public/js/lib/pipe_control_message_proxy", [ + "mojo/public/interfaces/bindings/pipe_control_messages.mojom", + "mojo/public/js/codec", + "mojo/public/js/interface_types", +], function(pipeControlMessages, codec, types) { + + function constructRunOrClosePipeMessage(runOrClosePipeInput) { + var runOrClosePipeMessageParams = new + pipeControlMessages.RunOrClosePipeMessageParams(); + runOrClosePipeMessageParams.input = runOrClosePipeInput; + + var messageName = pipeControlMessages.kRunOrClosePipeMessageId; + var payloadSize = + pipeControlMessages.RunOrClosePipeMessageParams.encodedSize; + + var builder = new codec.MessageBuilder(messageName, payloadSize); + builder.encodeStruct(pipeControlMessages.RunOrClosePipeMessageParams, + runOrClosePipeMessageParams); + var message = builder.finish(); + message.setInterfaceId(types.kInvalidInterfaceId); + return message; + } + + function PipeControlMessageProxy(receiver) { + this.receiver_ = receiver; + } + + PipeControlMessageProxy.prototype.notifyPeerEndpointClosed = function( + interfaceId, reason) { + var message = this.constructPeerEndpointClosedMessage(interfaceId, reason); + this.receiver_.accept(message); + }; + + PipeControlMessageProxy.prototype.constructPeerEndpointClosedMessage = + function(interfaceId, reason) { + var event = new pipeControlMessages.PeerAssociatedEndpointClosedEvent(); + event.id = interfaceId; + if (reason) { + event.disconnect_reason = new pipeControlMessages.DisconnectReason({ + custom_reason: reason.custom_reason, + description: reason.description}); + } + var runOrClosePipeInput = new pipeControlMessages.RunOrClosePipeInput(); + runOrClosePipeInput.peer_associated_endpoint_closed_event = event; + return constructRunOrClosePipeMessage(runOrClosePipeInput); + }; + + var exports = {}; + exports.PipeControlMessageProxy = PipeControlMessageProxy; + + return exports; +}); diff --git a/mojo/public/js/new_bindings/base.js b/mojo/public/js/new_bindings/base.js new file mode 100644 index 0000000000..db72d489db --- /dev/null +++ b/mojo/public/js/new_bindings/base.js @@ -0,0 +1,111 @@ +// 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. + +'use strict'; + +if (mojo && mojo.internal) { + throw new Error('The Mojo bindings library has been initialized.'); +} + +var mojo = mojo || {}; +mojo.internal = {}; +mojo.internal.global = this; +mojo.config = { + // Whether to automatically load mojom dependencies. + // For example, if foo.mojom imports bar.mojom, |autoLoadMojomDeps| set to + // true means that loading foo.mojom.js will insert a <script> tag to load + // bar.mojom.js, if it hasn't been loaded. + // + // The URL of bar.mojom.js is determined by the relative path of bar.mojom + // (relative to the position of foo.mojom at build time) and the URL of + // foo.mojom.js. For exmple, if at build time the two mojom files are + // located at: + // a/b/c/foo.mojom + // a/b/d/bar.mojom + // and the URL of foo.mojom.js is: + // http://example.org/scripts/b/c/foo.mojom.js + // then the URL of bar.mojom.js will be: + // http://example.org/scripts/b/d/bar.mojom.js + // + // If you would like bar.mojom.js to live at a different location, you need + // to turn off |autoLoadMojomDeps| before loading foo.mojom.js, and manually + // load bar.mojom.js yourself. Similarly, you need to turn off the option if + // you merge bar.mojom.js and foo.mojom.js into a single file. + // + // Performance tip: Avoid loading the same mojom.js file multiple times. + // Assume that |autoLoadMojomDeps| is set to true: + // <!-- No duplicate loading; recommended. --> + // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> + // + // <!-- No duplicate loading, although unnecessary. --> + // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script> + // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> + // + // <!-- Load bar.mojom.js twice; should be avoided. --> + // <script src="http://example.org/scripts/b/c/foo.mojom.js"></script> + // <script src="http://example.org/scripts/b/d/bar.mojom.js"></script> + autoLoadMojomDeps: true +}; + +(function() { + var internal = mojo.internal; + + var LoadState = { + PENDING_LOAD: 1, + LOADED: 2 + }; + + var mojomRegistry = new Map(); + + function exposeNamespace(namespace) { + var current = internal.global; + var parts = namespace.split('.'); + + for (var part; parts.length && (part = parts.shift());) { + if (!current[part]) { + current[part] = {}; + } + current = current[part]; + } + + return current; + } + + function isMojomPendingLoad(id) { + return mojomRegistry.get(id) === LoadState.PENDING_LOAD; + } + + function isMojomLoaded(id) { + return mojomRegistry.get(id) === LoadState.LOADED; + } + + function markMojomPendingLoad(id) { + if (isMojomLoaded(id)) { + throw new Error('The following mojom file has been loaded: ' + id); + } + + mojomRegistry.set(id, LoadState.PENDING_LOAD); + } + + function markMojomLoaded(id) { + mojomRegistry.set(id, LoadState.LOADED); + } + + function loadMojomIfNecessary(id, url) { + if (mojomRegistry.has(id)) { + return; + } + + markMojomPendingLoad(id); + internal.global.document.write('<script type="text/javascript" src="' + + url + '"></script>'); + } + + internal.exposeNamespace = exposeNamespace; + internal.isMojomPendingLoad = isMojomPendingLoad; + internal.isMojomLoaded = isMojomLoaded; + internal.markMojomPendingLoad = markMojomPendingLoad; + internal.markMojomLoaded = markMojomLoaded; + internal.loadMojomIfNecessary = loadMojomIfNecessary; +})(); diff --git a/mojo/public/js/new_bindings/bindings.js b/mojo/public/js/new_bindings/bindings.js new file mode 100644 index 0000000000..5b3b66ecea --- /dev/null +++ b/mojo/public/js/new_bindings/bindings.js @@ -0,0 +1,275 @@ +// 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. + +(function() { + var internal = mojo.internal; + // --------------------------------------------------------------------------- + + function makeRequest(interfacePtr) { + var pipe = Mojo.createMessagePipe(); + interfacePtr.ptr.bind(new mojo.InterfacePtrInfo(pipe.handle0, 0)); + return new mojo.InterfaceRequest(pipe.handle1); + } + + // --------------------------------------------------------------------------- + + // Operations used to setup/configure an interface pointer. Exposed as the + // |ptr| field of generated interface pointer classes. + // |ptrInfoOrHandle| could be omitted and passed into bind() later. + function InterfacePtrController(interfaceType, ptrInfoOrHandle) { + this.version = 0; + + this.interfaceType_ = interfaceType; + this.router_ = null; + this.proxy_ = null; + + // |router_| is lazily initialized. |handle_| is valid between bind() and + // the initialization of |router_|. + this.handle_ = null; + this.controlMessageProxy_ = null; + + if (ptrInfoOrHandle) + this.bind(ptrInfoOrHandle); + } + + InterfacePtrController.prototype.bind = function(ptrInfoOrHandle) { + this.reset(); + + if (ptrInfoOrHandle instanceof mojo.InterfacePtrInfo) { + this.version = ptrInfoOrHandle.version; + this.handle_ = ptrInfoOrHandle.handle; + } else { + this.handle_ = ptrInfoOrHandle; + } + }; + + InterfacePtrController.prototype.isBound = function() { + return this.router_ !== null || this.handle_ !== null; + }; + + // Although users could just discard the object, reset() closes the pipe + // immediately. + InterfacePtrController.prototype.reset = function() { + this.version = 0; + if (this.router_) { + this.router_.close(); + this.router_ = null; + + this.proxy_ = null; + } + if (this.handle_) { + this.handle_.close(); + this.handle_ = null; + } + }; + + InterfacePtrController.prototype.setConnectionErrorHandler + = function(callback) { + if (!this.isBound()) + throw new Error("Cannot set connection error handler if not bound."); + + this.configureProxyIfNecessary_(); + this.router_.setErrorHandler(callback); + }; + + InterfacePtrController.prototype.passInterface = function() { + var result; + if (this.router_) { + // TODO(yzshen): Fix Router interface to support extracting handle. + result = new mojo.InterfacePtrInfo( + this.router_.connector_.handle_, this.version); + this.router_.connector_.handle_ = null; + } else { + // This also handles the case when this object is not bound. + result = new mojo.InterfacePtrInfo(this.handle_, this.version); + this.handle_ = null; + } + + this.reset(); + return result; + }; + + InterfacePtrController.prototype.getProxy = function() { + this.configureProxyIfNecessary_(); + return this.proxy_; + }; + + InterfacePtrController.prototype.enableTestingMode = function() { + this.configureProxyIfNecessary_(); + return this.router_.enableTestingMode(); + }; + + InterfacePtrController.prototype.configureProxyIfNecessary_ = function() { + if (!this.handle_) + return; + + this.router_ = new internal.Router(this.handle_); + this.handle_ = null; + this.router_ .setPayloadValidators([this.interfaceType_.validateResponse]); + + this.controlMessageProxy_ = new internal.ControlMessageProxy(this.router_); + + this.proxy_ = new this.interfaceType_.proxyClass(this.router_); + }; + + InterfacePtrController.prototype.queryVersion = function() { + function onQueryVersion(version) { + this.version = version; + return version; + } + + this.configureProxyIfNecessary_(); + return this.controlMessageProxy_.queryVersion().then( + onQueryVersion.bind(this)); + }; + + InterfacePtrController.prototype.requireVersion = function(version) { + this.configureProxyIfNecessary_(); + + if (this.version >= version) { + return; + } + this.version = version; + this.controlMessageProxy_.requireVersion(version); + }; + + // --------------------------------------------------------------------------- + + // |request| could be omitted and passed into bind() later. + // + // Example: + // + // // FooImpl implements mojom.Foo. + // function FooImpl() { ... } + // FooImpl.prototype.fooMethod1 = function() { ... } + // FooImpl.prototype.fooMethod2 = function() { ... } + // + // var fooPtr = new mojom.FooPtr(); + // var request = makeRequest(fooPtr); + // var binding = new Binding(mojom.Foo, new FooImpl(), request); + // fooPtr.fooMethod1(); + function Binding(interfaceType, impl, requestOrHandle) { + this.interfaceType_ = interfaceType; + this.impl_ = impl; + this.router_ = null; + this.stub_ = null; + + if (requestOrHandle) + this.bind(requestOrHandle); + } + + Binding.prototype.isBound = function() { + return this.router_ !== null; + }; + + Binding.prototype.createInterfacePtrAndBind = function() { + var ptr = new this.interfaceType_.ptrClass(); + // TODO(yzshen): Set the version of the interface pointer. + this.bind(makeRequest(ptr)); + return ptr; + } + + Binding.prototype.bind = function(requestOrHandle) { + this.close(); + + var handle = requestOrHandle instanceof mojo.InterfaceRequest ? + requestOrHandle.handle : requestOrHandle; + if (!(handle instanceof MojoHandle)) + return; + + this.stub_ = new this.interfaceType_.stubClass(this.impl_); + this.router_ = new internal.Router(handle, this.interfaceType_.kVersion); + this.router_.setIncomingReceiver(this.stub_); + this.router_ .setPayloadValidators([this.interfaceType_.validateRequest]); + }; + + Binding.prototype.close = function() { + if (!this.isBound()) + return; + + this.router_.close(); + this.router_ = null; + this.stub_ = null; + }; + + Binding.prototype.setConnectionErrorHandler + = function(callback) { + if (!this.isBound()) + throw new Error("Cannot set connection error handler if not bound."); + this.router_.setErrorHandler(callback); + }; + + Binding.prototype.unbind = function() { + if (!this.isBound()) + return new mojo.InterfaceRequest(null); + + var result = new mojo.InterfaceRequest(this.router_.connector_.handle_); + this.router_.connector_.handle_ = null; + this.close(); + return result; + }; + + Binding.prototype.enableTestingMode = function() { + return this.router_.enableTestingMode(); + }; + + // --------------------------------------------------------------------------- + + function BindingSetEntry(bindingSet, interfaceType, impl, requestOrHandle, + bindingId) { + this.bindingSet_ = bindingSet; + this.bindingId_ = bindingId; + this.binding_ = new Binding(interfaceType, impl, requestOrHandle); + + this.binding_.setConnectionErrorHandler(function() { + this.bindingSet_.onConnectionError(bindingId); + }.bind(this)); + } + + BindingSetEntry.prototype.close = function() { + this.binding_.close(); + }; + + function BindingSet(interfaceType) { + this.interfaceType_ = interfaceType; + this.nextBindingId_ = 0; + this.bindings_ = new Map(); + this.errorHandler_ = null; + } + + BindingSet.prototype.isEmpty = function() { + return this.bindings_.size == 0; + }; + + BindingSet.prototype.addBinding = function(impl, requestOrHandle) { + this.bindings_.set( + this.nextBindingId_, + new BindingSetEntry(this, this.interfaceType_, impl, requestOrHandle, + this.nextBindingId_)); + ++this.nextBindingId_; + }; + + BindingSet.prototype.closeAllBindings = function() { + for (var entry of this.bindings_.values()) + entry.close(); + this.bindings_.clear(); + }; + + BindingSet.prototype.setConnectionErrorHandler = function(callback) { + this.errorHandler_ = callback; + }; + + BindingSet.prototype.onConnectionError = function(bindingId) { + this.bindings_.delete(bindingId); + + if (this.errorHandler_) + this.errorHandler_(); + }; + + + mojo.makeRequest = makeRequest; + mojo.Binding = Binding; + mojo.BindingSet = BindingSet; + mojo.InterfacePtrController = InterfacePtrController; +})(); diff --git a/mojo/public/js/new_bindings/buffer.js b/mojo/public/js/new_bindings/buffer.js new file mode 100644 index 0000000000..c44058bd0f --- /dev/null +++ b/mojo/public/js/new_bindings/buffer.js @@ -0,0 +1,155 @@ +// 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. + +(function() { + var internal = mojo.internal; + + var kHostIsLittleEndian = (function () { + var endianArrayBuffer = new ArrayBuffer(2); + var endianUint8Array = new Uint8Array(endianArrayBuffer); + var endianUint16Array = new Uint16Array(endianArrayBuffer); + endianUint16Array[0] = 1; + return endianUint8Array[0] == 1; + })(); + + var kHighWordMultiplier = 0x100000000; + + function Buffer(sizeOrArrayBuffer) { + if (sizeOrArrayBuffer instanceof ArrayBuffer) + this.arrayBuffer = sizeOrArrayBuffer; + else + this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer); + + this.dataView = new DataView(this.arrayBuffer); + this.next = 0; + } + + Object.defineProperty(Buffer.prototype, "byteLength", { + get: function() { return this.arrayBuffer.byteLength; } + }); + + Buffer.prototype.alloc = function(size) { + var pointer = this.next; + this.next += size; + if (this.next > this.byteLength) { + var newSize = (1.5 * (this.byteLength + size)) | 0; + this.grow(newSize); + } + return pointer; + }; + + function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) { + (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer)); + } + + Buffer.prototype.grow = function(size) { + var newArrayBuffer = new ArrayBuffer(size); + copyArrayBuffer(newArrayBuffer, this.arrayBuffer); + this.arrayBuffer = newArrayBuffer; + this.dataView = new DataView(this.arrayBuffer); + }; + + Buffer.prototype.trim = function() { + this.arrayBuffer = this.arrayBuffer.slice(0, this.next); + this.dataView = new DataView(this.arrayBuffer); + }; + + Buffer.prototype.getUint8 = function(offset) { + return this.dataView.getUint8(offset); + } + Buffer.prototype.getUint16 = function(offset) { + return this.dataView.getUint16(offset, kHostIsLittleEndian); + } + Buffer.prototype.getUint32 = function(offset) { + return this.dataView.getUint32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getUint64 = function(offset) { + var lo, hi; + if (kHostIsLittleEndian) { + lo = this.dataView.getUint32(offset, kHostIsLittleEndian); + hi = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } else { + hi = this.dataView.getUint32(offset, kHostIsLittleEndian); + lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } + return lo + hi * kHighWordMultiplier; + } + + Buffer.prototype.getInt8 = function(offset) { + return this.dataView.getInt8(offset); + } + Buffer.prototype.getInt16 = function(offset) { + return this.dataView.getInt16(offset, kHostIsLittleEndian); + } + Buffer.prototype.getInt32 = function(offset) { + return this.dataView.getInt32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getInt64 = function(offset) { + var lo, hi; + if (kHostIsLittleEndian) { + lo = this.dataView.getUint32(offset, kHostIsLittleEndian); + hi = this.dataView.getInt32(offset + 4, kHostIsLittleEndian); + } else { + hi = this.dataView.getInt32(offset, kHostIsLittleEndian); + lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } + return lo + hi * kHighWordMultiplier; + } + + Buffer.prototype.getFloat32 = function(offset) { + return this.dataView.getFloat32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getFloat64 = function(offset) { + return this.dataView.getFloat64(offset, kHostIsLittleEndian); + } + + Buffer.prototype.setUint8 = function(offset, value) { + this.dataView.setUint8(offset, value); + } + Buffer.prototype.setUint16 = function(offset, value) { + this.dataView.setUint16(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setUint32 = function(offset, value) { + this.dataView.setUint32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setUint64 = function(offset, value) { + var hi = (value / kHighWordMultiplier) | 0; + if (kHostIsLittleEndian) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian); + } else { + this.dataView.setInt32(offset, hi, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian); + } + } + + Buffer.prototype.setInt8 = function(offset, value) { + this.dataView.setInt8(offset, value); + } + Buffer.prototype.setInt16 = function(offset, value) { + this.dataView.setInt16(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setInt32 = function(offset, value) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setInt64 = function(offset, value) { + var hi = Math.floor(value / kHighWordMultiplier); + if (kHostIsLittleEndian) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian); + } else { + this.dataView.setInt32(offset, hi, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian); + } + } + + Buffer.prototype.setFloat32 = function(offset, value) { + this.dataView.setFloat32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setFloat64 = function(offset, value) { + this.dataView.setFloat64(offset, value, kHostIsLittleEndian); + } + + internal.Buffer = Buffer; +})(); diff --git a/mojo/public/js/new_bindings/codec.js b/mojo/public/js/new_bindings/codec.js new file mode 100644 index 0000000000..339fc169da --- /dev/null +++ b/mojo/public/js/new_bindings/codec.js @@ -0,0 +1,917 @@ +// 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. + +(function() { + var internal = mojo.internal; + + var kErrorUnsigned = "Passing negative value to unsigned"; + var kErrorArray = "Passing non Array for array type"; + var kErrorString = "Passing non String for string type"; + var kErrorMap = "Passing non Map for map type"; + + // Memory ------------------------------------------------------------------- + + var kAlignment = 8; + + function align(size) { + return size + (kAlignment - (size % kAlignment)) % kAlignment; + } + + function isAligned(offset) { + return offset >= 0 && (offset % kAlignment) === 0; + } + + // Constants ---------------------------------------------------------------- + + var kArrayHeaderSize = 8; + var kStructHeaderSize = 8; + var kMessageHeaderSize = 24; + var kMessageWithRequestIDHeaderSize = 32; + var kMapStructPayloadSize = 16; + + var kStructHeaderNumBytesOffset = 0; + var kStructHeaderVersionOffset = 4; + + var kEncodedInvalidHandleValue = 0xFFFFFFFF; + + // Decoder ------------------------------------------------------------------ + + function Decoder(buffer, handles, base) { + this.buffer = buffer; + this.handles = handles; + this.base = base; + this.next = base; + } + + Decoder.prototype.align = function() { + this.next = align(this.next); + }; + + Decoder.prototype.skip = function(offset) { + this.next += offset; + }; + + Decoder.prototype.readInt8 = function() { + var result = this.buffer.getInt8(this.next); + this.next += 1; + return result; + }; + + Decoder.prototype.readUint8 = function() { + var result = this.buffer.getUint8(this.next); + this.next += 1; + return result; + }; + + Decoder.prototype.readInt16 = function() { + var result = this.buffer.getInt16(this.next); + this.next += 2; + return result; + }; + + Decoder.prototype.readUint16 = function() { + var result = this.buffer.getUint16(this.next); + this.next += 2; + return result; + }; + + Decoder.prototype.readInt32 = function() { + var result = this.buffer.getInt32(this.next); + this.next += 4; + return result; + }; + + Decoder.prototype.readUint32 = function() { + var result = this.buffer.getUint32(this.next); + this.next += 4; + return result; + }; + + Decoder.prototype.readInt64 = function() { + var result = this.buffer.getInt64(this.next); + this.next += 8; + return result; + }; + + Decoder.prototype.readUint64 = function() { + var result = this.buffer.getUint64(this.next); + this.next += 8; + return result; + }; + + Decoder.prototype.readFloat = function() { + var result = this.buffer.getFloat32(this.next); + this.next += 4; + return result; + }; + + Decoder.prototype.readDouble = function() { + var result = this.buffer.getFloat64(this.next); + this.next += 8; + return result; + }; + + Decoder.prototype.decodePointer = function() { + // TODO(abarth): To correctly decode a pointer, we need to know the real + // base address of the array buffer. + var offsetPointer = this.next; + var offset = this.readUint64(); + if (!offset) + return 0; + return offsetPointer + offset; + }; + + Decoder.prototype.decodeAndCreateDecoder = function(pointer) { + return new Decoder(this.buffer, this.handles, pointer); + }; + + Decoder.prototype.decodeHandle = function() { + return this.handles[this.readUint32()] || null; + }; + + Decoder.prototype.decodeString = function() { + var numberOfBytes = this.readUint32(); + var numberOfElements = this.readUint32(); + var base = this.next; + this.next += numberOfElements; + return internal.decodeUtf8String( + new Uint8Array(this.buffer.arrayBuffer, base, numberOfElements)); + }; + + Decoder.prototype.decodeArray = function(cls) { + var numberOfBytes = this.readUint32(); + var numberOfElements = this.readUint32(); + var val = new Array(numberOfElements); + if (cls === PackedBool) { + var byte; + for (var i = 0; i < numberOfElements; ++i) { + if (i % 8 === 0) + byte = this.readUint8(); + val[i] = (byte & (1 << i % 8)) ? true : false; + } + } else { + for (var i = 0; i < numberOfElements; ++i) { + val[i] = cls.decode(this); + } + } + return val; + }; + + Decoder.prototype.decodeStruct = function(cls) { + return cls.decode(this); + }; + + Decoder.prototype.decodeStructPointer = function(cls) { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return cls.decode(this.decodeAndCreateDecoder(pointer)); + }; + + Decoder.prototype.decodeArrayPointer = function(cls) { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return this.decodeAndCreateDecoder(pointer).decodeArray(cls); + }; + + Decoder.prototype.decodeStringPointer = function() { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + return this.decodeAndCreateDecoder(pointer).decodeString(); + }; + + Decoder.prototype.decodeMap = function(keyClass, valueClass) { + this.skip(4); // numberOfBytes + this.skip(4); // version + var keys = this.decodeArrayPointer(keyClass); + var values = this.decodeArrayPointer(valueClass); + var val = new Map(); + for (var i = 0; i < keys.length; i++) + val.set(keys[i], values[i]); + return val; + }; + + Decoder.prototype.decodeMapPointer = function(keyClass, valueClass) { + var pointer = this.decodePointer(); + if (!pointer) { + return null; + } + var decoder = this.decodeAndCreateDecoder(pointer); + return decoder.decodeMap(keyClass, valueClass); + }; + + // Encoder ------------------------------------------------------------------ + + function Encoder(buffer, handles, base) { + this.buffer = buffer; + this.handles = handles; + this.base = base; + this.next = base; + } + + Encoder.prototype.align = function() { + this.next = align(this.next); + }; + + Encoder.prototype.skip = function(offset) { + this.next += offset; + }; + + Encoder.prototype.writeInt8 = function(val) { + this.buffer.setInt8(this.next, val); + this.next += 1; + }; + + Encoder.prototype.writeUint8 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.setUint8(this.next, val); + this.next += 1; + }; + + Encoder.prototype.writeInt16 = function(val) { + this.buffer.setInt16(this.next, val); + this.next += 2; + }; + + Encoder.prototype.writeUint16 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.setUint16(this.next, val); + this.next += 2; + }; + + Encoder.prototype.writeInt32 = function(val) { + this.buffer.setInt32(this.next, val); + this.next += 4; + }; + + Encoder.prototype.writeUint32 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.setUint32(this.next, val); + this.next += 4; + }; + + Encoder.prototype.writeInt64 = function(val) { + this.buffer.setInt64(this.next, val); + this.next += 8; + }; + + Encoder.prototype.writeUint64 = function(val) { + if (val < 0) { + throw new Error(kErrorUnsigned); + } + this.buffer.setUint64(this.next, val); + this.next += 8; + }; + + Encoder.prototype.writeFloat = function(val) { + this.buffer.setFloat32(this.next, val); + this.next += 4; + }; + + Encoder.prototype.writeDouble = function(val) { + this.buffer.setFloat64(this.next, val); + this.next += 8; + }; + + Encoder.prototype.encodePointer = function(pointer) { + if (!pointer) + return this.writeUint64(0); + // TODO(abarth): To correctly encode a pointer, we need to know the real + // base address of the array buffer. + var offset = pointer - this.next; + this.writeUint64(offset); + }; + + Encoder.prototype.createAndEncodeEncoder = function(size) { + var pointer = this.buffer.alloc(align(size)); + this.encodePointer(pointer); + return new Encoder(this.buffer, this.handles, pointer); + }; + + Encoder.prototype.encodeHandle = function(handle) { + if (handle) { + this.handles.push(handle); + this.writeUint32(this.handles.length - 1); + } else { + this.writeUint32(kEncodedInvalidHandleValue); + } + }; + + Encoder.prototype.encodeString = function(val) { + var base = this.next + kArrayHeaderSize; + var numberOfElements = internal.encodeUtf8String( + val, new Uint8Array(this.buffer.arrayBuffer, base)); + var numberOfBytes = kArrayHeaderSize + numberOfElements; + this.writeUint32(numberOfBytes); + this.writeUint32(numberOfElements); + this.next += numberOfElements; + }; + + Encoder.prototype.encodeArray = + function(cls, val, numberOfElements, encodedSize) { + if (numberOfElements === undefined) + numberOfElements = val.length; + if (encodedSize === undefined) + encodedSize = kArrayHeaderSize + cls.encodedSize * numberOfElements; + + this.writeUint32(encodedSize); + this.writeUint32(numberOfElements); + + if (cls === PackedBool) { + var byte = 0; + for (i = 0; i < numberOfElements; ++i) { + if (val[i]) + byte |= (1 << i % 8); + if (i % 8 === 7 || i == numberOfElements - 1) { + Uint8.encode(this, byte); + byte = 0; + } + } + } else { + for (var i = 0; i < numberOfElements; ++i) + cls.encode(this, val[i]); + } + }; + + Encoder.prototype.encodeStruct = function(cls, val) { + return cls.encode(this, val); + }; + + Encoder.prototype.encodeStructPointer = function(cls, val) { + if (val == null) { + // Also handles undefined, since undefined == null. + this.encodePointer(val); + return; + } + var encoder = this.createAndEncodeEncoder(cls.encodedSize); + cls.encode(encoder, val); + }; + + Encoder.prototype.encodeArrayPointer = function(cls, val) { + if (val == null) { + // Also handles undefined, since undefined == null. + this.encodePointer(val); + return; + } + + var numberOfElements = val.length; + if (!Number.isSafeInteger(numberOfElements) || numberOfElements < 0) + throw new Error(kErrorArray); + + var encodedSize = kArrayHeaderSize + ((cls === PackedBool) ? + Math.ceil(numberOfElements / 8) : cls.encodedSize * numberOfElements); + var encoder = this.createAndEncodeEncoder(encodedSize); + encoder.encodeArray(cls, val, numberOfElements, encodedSize); + }; + + Encoder.prototype.encodeStringPointer = function(val) { + if (val == null) { + // Also handles undefined, since undefined == null. + this.encodePointer(val); + return; + } + // Only accepts string primivites, not String Objects like new String("foo") + if (typeof(val) !== "string") { + throw new Error(kErrorString); + } + var encodedSize = kArrayHeaderSize + internal.utf8Length(val); + var encoder = this.createAndEncodeEncoder(encodedSize); + encoder.encodeString(val); + }; + + Encoder.prototype.encodeMap = function(keyClass, valueClass, val) { + var keys = new Array(val.size); + var values = new Array(val.size); + var i = 0; + val.forEach(function(value, key) { + values[i] = value; + keys[i++] = key; + }); + this.writeUint32(kStructHeaderSize + kMapStructPayloadSize); + this.writeUint32(0); // version + this.encodeArrayPointer(keyClass, keys); + this.encodeArrayPointer(valueClass, values); + } + + Encoder.prototype.encodeMapPointer = function(keyClass, valueClass, val) { + if (val == null) { + // Also handles undefined, since undefined == null. + this.encodePointer(val); + return; + } + if (!(val instanceof Map)) { + throw new Error(kErrorMap); + } + var encodedSize = kStructHeaderSize + kMapStructPayloadSize; + var encoder = this.createAndEncodeEncoder(encodedSize); + encoder.encodeMap(keyClass, valueClass, val); + }; + + // Message ------------------------------------------------------------------ + + var kMessageInterfaceIdOffset = kStructHeaderSize; + var kMessageNameOffset = kMessageInterfaceIdOffset + 4; + var kMessageFlagsOffset = kMessageNameOffset + 4; + var kMessageRequestIDOffset = kMessageFlagsOffset + 8; + + var kMessageExpectsResponse = 1 << 0; + var kMessageIsResponse = 1 << 1; + + function Message(buffer, handles) { + this.buffer = buffer; + this.handles = handles; + } + + Message.prototype.getHeaderNumBytes = function() { + return this.buffer.getUint32(kStructHeaderNumBytesOffset); + }; + + Message.prototype.getHeaderVersion = function() { + return this.buffer.getUint32(kStructHeaderVersionOffset); + }; + + Message.prototype.getName = function() { + return this.buffer.getUint32(kMessageNameOffset); + }; + + Message.prototype.getFlags = function() { + return this.buffer.getUint32(kMessageFlagsOffset); + }; + + Message.prototype.isResponse = function() { + return (this.getFlags() & kMessageIsResponse) != 0; + }; + + Message.prototype.expectsResponse = function() { + return (this.getFlags() & kMessageExpectsResponse) != 0; + }; + + Message.prototype.setRequestID = function(requestID) { + // TODO(darin): Verify that space was reserved for this field! + this.buffer.setUint64(kMessageRequestIDOffset, requestID); + }; + + + // MessageBuilder ----------------------------------------------------------- + + function MessageBuilder(messageName, payloadSize) { + // Currently, we don't compute the payload size correctly ahead of time. + // Instead, we resize the buffer at the end. + var numberOfBytes = kMessageHeaderSize + payloadSize; + this.buffer = new internal.Buffer(numberOfBytes); + this.handles = []; + var encoder = this.createEncoder(kMessageHeaderSize); + encoder.writeUint32(kMessageHeaderSize); + encoder.writeUint32(0); // version. + encoder.writeUint32(0); // interface ID. + encoder.writeUint32(messageName); + encoder.writeUint32(0); // flags. + encoder.writeUint32(0); // padding. + } + + MessageBuilder.prototype.createEncoder = function(size) { + var pointer = this.buffer.alloc(size); + return new Encoder(this.buffer, this.handles, pointer); + }; + + MessageBuilder.prototype.encodeStruct = function(cls, val) { + cls.encode(this.createEncoder(cls.encodedSize), val); + }; + + MessageBuilder.prototype.finish = function() { + // TODO(abarth): Rather than resizing the buffer at the end, we could + // compute the size we need ahead of time, like we do in C++. + this.buffer.trim(); + var message = new Message(this.buffer, this.handles); + this.buffer = null; + this.handles = null; + this.encoder = null; + return message; + }; + + // MessageWithRequestIDBuilder ----------------------------------------------- + + function MessageWithRequestIDBuilder(messageName, payloadSize, flags, + requestID) { + // Currently, we don't compute the payload size correctly ahead of time. + // Instead, we resize the buffer at the end. + var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize; + this.buffer = new internal.Buffer(numberOfBytes); + this.handles = []; + var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize); + encoder.writeUint32(kMessageWithRequestIDHeaderSize); + encoder.writeUint32(1); // version. + encoder.writeUint32(0); // interface ID. + encoder.writeUint32(messageName); + encoder.writeUint32(flags); + encoder.writeUint32(0); // padding. + encoder.writeUint64(requestID); + } + + MessageWithRequestIDBuilder.prototype = + Object.create(MessageBuilder.prototype); + + MessageWithRequestIDBuilder.prototype.constructor = + MessageWithRequestIDBuilder; + + // MessageReader ------------------------------------------------------------ + + function MessageReader(message) { + this.decoder = new Decoder(message.buffer, message.handles, 0); + var messageHeaderSize = this.decoder.readUint32(); + this.payloadSize = message.buffer.byteLength - messageHeaderSize; + var version = this.decoder.readUint32(); + var interface_id = this.decoder.readUint32(); + if (interface_id != 0) { + throw new Error("Receiving non-zero interface ID. Associated interfaces " + + "are not yet supported."); + } + this.messageName = this.decoder.readUint32(); + this.flags = this.decoder.readUint32(); + // Skip the padding. + this.decoder.skip(4); + if (version >= 1) + this.requestID = this.decoder.readUint64(); + this.decoder.skip(messageHeaderSize - this.decoder.next); + } + + MessageReader.prototype.decodeStruct = function(cls) { + return cls.decode(this.decoder); + }; + + // Built-in types ----------------------------------------------------------- + + // This type is only used with ArrayOf(PackedBool). + function PackedBool() { + } + + function Int8() { + } + + Int8.encodedSize = 1; + + Int8.decode = function(decoder) { + return decoder.readInt8(); + }; + + Int8.encode = function(encoder, val) { + encoder.writeInt8(val); + }; + + Uint8.encode = function(encoder, val) { + encoder.writeUint8(val); + }; + + function Uint8() { + } + + Uint8.encodedSize = 1; + + Uint8.decode = function(decoder) { + return decoder.readUint8(); + }; + + Uint8.encode = function(encoder, val) { + encoder.writeUint8(val); + }; + + function Int16() { + } + + Int16.encodedSize = 2; + + Int16.decode = function(decoder) { + return decoder.readInt16(); + }; + + Int16.encode = function(encoder, val) { + encoder.writeInt16(val); + }; + + function Uint16() { + } + + Uint16.encodedSize = 2; + + Uint16.decode = function(decoder) { + return decoder.readUint16(); + }; + + Uint16.encode = function(encoder, val) { + encoder.writeUint16(val); + }; + + function Int32() { + } + + Int32.encodedSize = 4; + + Int32.decode = function(decoder) { + return decoder.readInt32(); + }; + + Int32.encode = function(encoder, val) { + encoder.writeInt32(val); + }; + + function Uint32() { + } + + Uint32.encodedSize = 4; + + Uint32.decode = function(decoder) { + return decoder.readUint32(); + }; + + Uint32.encode = function(encoder, val) { + encoder.writeUint32(val); + }; + + function Int64() { + } + + Int64.encodedSize = 8; + + Int64.decode = function(decoder) { + return decoder.readInt64(); + }; + + Int64.encode = function(encoder, val) { + encoder.writeInt64(val); + }; + + function Uint64() { + } + + Uint64.encodedSize = 8; + + Uint64.decode = function(decoder) { + return decoder.readUint64(); + }; + + Uint64.encode = function(encoder, val) { + encoder.writeUint64(val); + }; + + function String() { + }; + + String.encodedSize = 8; + + String.decode = function(decoder) { + return decoder.decodeStringPointer(); + }; + + String.encode = function(encoder, val) { + encoder.encodeStringPointer(val); + }; + + function NullableString() { + } + + NullableString.encodedSize = String.encodedSize; + + NullableString.decode = String.decode; + + NullableString.encode = String.encode; + + function Float() { + } + + Float.encodedSize = 4; + + Float.decode = function(decoder) { + return decoder.readFloat(); + }; + + Float.encode = function(encoder, val) { + encoder.writeFloat(val); + }; + + function Double() { + } + + Double.encodedSize = 8; + + Double.decode = function(decoder) { + return decoder.readDouble(); + }; + + Double.encode = function(encoder, val) { + encoder.writeDouble(val); + }; + + function Enum(cls) { + this.cls = cls; + } + + Enum.prototype.encodedSize = 4; + + Enum.prototype.decode = function(decoder) { + return decoder.readInt32(); + }; + + Enum.prototype.encode = function(encoder, val) { + encoder.writeInt32(val); + }; + + function PointerTo(cls) { + this.cls = cls; + } + + PointerTo.prototype.encodedSize = 8; + + PointerTo.prototype.decode = function(decoder) { + var pointer = decoder.decodePointer(); + if (!pointer) { + return null; + } + return this.cls.decode(decoder.decodeAndCreateDecoder(pointer)); + }; + + PointerTo.prototype.encode = function(encoder, val) { + if (!val) { + encoder.encodePointer(val); + return; + } + var objectEncoder = encoder.createAndEncodeEncoder(this.cls.encodedSize); + this.cls.encode(objectEncoder, val); + }; + + function NullablePointerTo(cls) { + PointerTo.call(this, cls); + } + + NullablePointerTo.prototype = Object.create(PointerTo.prototype); + + function ArrayOf(cls, length) { + this.cls = cls; + this.length = length || 0; + } + + ArrayOf.prototype.encodedSize = 8; + + ArrayOf.prototype.dimensions = function() { + return [this.length].concat( + (this.cls instanceof ArrayOf) ? this.cls.dimensions() : []); + } + + ArrayOf.prototype.decode = function(decoder) { + return decoder.decodeArrayPointer(this.cls); + }; + + ArrayOf.prototype.encode = function(encoder, val) { + encoder.encodeArrayPointer(this.cls, val); + }; + + function NullableArrayOf(cls) { + ArrayOf.call(this, cls); + } + + NullableArrayOf.prototype = Object.create(ArrayOf.prototype); + + function Handle() { + } + + Handle.encodedSize = 4; + + Handle.decode = function(decoder) { + return decoder.decodeHandle(); + }; + + Handle.encode = function(encoder, val) { + encoder.encodeHandle(val); + }; + + function NullableHandle() { + } + + NullableHandle.encodedSize = Handle.encodedSize; + + NullableHandle.decode = Handle.decode; + + NullableHandle.encode = Handle.encode; + + function Interface(cls) { + this.cls = cls; + } + + Interface.prototype.encodedSize = 8; + + Interface.prototype.decode = function(decoder) { + var interfacePtrInfo = new mojo.InterfacePtrInfo( + decoder.decodeHandle(), decoder.readUint32()); + var interfacePtr = new this.cls(); + interfacePtr.ptr.bind(interfacePtrInfo); + return interfacePtr; + }; + + Interface.prototype.encode = function(encoder, val) { + var interfacePtrInfo = + val ? val.ptr.passInterface() : new mojo.InterfacePtrInfo(null, 0); + encoder.encodeHandle(interfacePtrInfo.handle); + encoder.writeUint32(interfacePtrInfo.version); + }; + + function NullableInterface(cls) { + Interface.call(this, cls); + } + + NullableInterface.prototype = Object.create(Interface.prototype); + + function InterfaceRequest() { + } + + InterfaceRequest.encodedSize = 4; + + InterfaceRequest.decode = function(decoder) { + return new mojo.InterfaceRequest(decoder.decodeHandle()); + }; + + InterfaceRequest.encode = function(encoder, val) { + encoder.encodeHandle(val ? val.handle : null); + }; + + function NullableInterfaceRequest() { + } + + NullableInterfaceRequest.encodedSize = InterfaceRequest.encodedSize; + + NullableInterfaceRequest.decode = InterfaceRequest.decode; + + NullableInterfaceRequest.encode = InterfaceRequest.encode; + + function MapOf(keyClass, valueClass) { + this.keyClass = keyClass; + this.valueClass = valueClass; + } + + MapOf.prototype.encodedSize = 8; + + MapOf.prototype.decode = function(decoder) { + return decoder.decodeMapPointer(this.keyClass, this.valueClass); + }; + + MapOf.prototype.encode = function(encoder, val) { + encoder.encodeMapPointer(this.keyClass, this.valueClass, val); + }; + + function NullableMapOf(keyClass, valueClass) { + MapOf.call(this, keyClass, valueClass); + } + + NullableMapOf.prototype = Object.create(MapOf.prototype); + + internal.align = align; + internal.isAligned = isAligned; + internal.Message = Message; + internal.MessageBuilder = MessageBuilder; + internal.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder; + internal.MessageReader = MessageReader; + internal.kArrayHeaderSize = kArrayHeaderSize; + internal.kMapStructPayloadSize = kMapStructPayloadSize; + internal.kStructHeaderSize = kStructHeaderSize; + internal.kEncodedInvalidHandleValue = kEncodedInvalidHandleValue; + internal.kMessageHeaderSize = kMessageHeaderSize; + internal.kMessageWithRequestIDHeaderSize = kMessageWithRequestIDHeaderSize; + internal.kMessageExpectsResponse = kMessageExpectsResponse; + internal.kMessageIsResponse = kMessageIsResponse; + internal.Int8 = Int8; + internal.Uint8 = Uint8; + internal.Int16 = Int16; + internal.Uint16 = Uint16; + internal.Int32 = Int32; + internal.Uint32 = Uint32; + internal.Int64 = Int64; + internal.Uint64 = Uint64; + internal.Float = Float; + internal.Double = Double; + internal.String = String; + internal.Enum = Enum; + internal.NullableString = NullableString; + internal.PointerTo = PointerTo; + internal.NullablePointerTo = NullablePointerTo; + internal.ArrayOf = ArrayOf; + internal.NullableArrayOf = NullableArrayOf; + internal.PackedBool = PackedBool; + internal.Handle = Handle; + internal.NullableHandle = NullableHandle; + internal.Interface = Interface; + internal.NullableInterface = NullableInterface; + internal.InterfaceRequest = InterfaceRequest; + internal.NullableInterfaceRequest = NullableInterfaceRequest; + internal.MapOf = MapOf; + internal.NullableMapOf = NullableMapOf; +})(); diff --git a/mojo/public/js/new_bindings/connector.js b/mojo/public/js/new_bindings/connector.js new file mode 100644 index 0000000000..7fa4822f89 --- /dev/null +++ b/mojo/public/js/new_bindings/connector.js @@ -0,0 +1,104 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +(function() { + var internal = mojo.internal; + + function Connector(handle) { + if (!(handle instanceof MojoHandle)) + throw new Error("Connector: not a handle " + handle); + this.handle_ = handle; + this.dropWrites_ = false; + this.error_ = false; + this.incomingReceiver_ = null; + this.readWatcher_ = null; + this.errorHandler_ = null; + + if (handle) { + this.readWatcher_ = handle.watch({readable: true}, + this.readMore_.bind(this)); + } + } + + Connector.prototype.close = function() { + if (this.readWatcher_) { + this.readWatcher_.cancel(); + this.readWatcher_ = null; + } + if (this.handle_ != null) { + this.handle_.close(); + this.handle_ = null; + } + }; + + Connector.prototype.accept = function(message) { + if (this.error_) + return false; + + if (this.dropWrites_) + return true; + + var result = this.handle_.writeMessage( + new Uint8Array(message.buffer.arrayBuffer), message.handles); + switch (result) { + case Mojo.RESULT_OK: + // The handles were successfully transferred, so we don't own them + // anymore. + message.handles = []; + break; + case Mojo.RESULT_FAILED_PRECONDITION: + // There's no point in continuing to write to this pipe since the other + // end is gone. Avoid writing any future messages. Hide write failures + // from the caller since we'd like them to continue consuming any + // backlog of incoming messages before regarding the message pipe as + // closed. + this.dropWrites_ = true; + break; + default: + // This particular write was rejected, presumably because of bad input. + // The pipe is not necessarily in a bad state. + return false; + } + return true; + }; + + Connector.prototype.setIncomingReceiver = function(receiver) { + this.incomingReceiver_ = receiver; + }; + + Connector.prototype.setErrorHandler = function(handler) { + this.errorHandler_ = handler; + }; + + Connector.prototype.encounteredError = function() { + return this.error_; + }; + + Connector.prototype.waitForNextMessageForTesting = function() { + // TODO(yzshen): Change the tests that use this method. + throw new Error("Not supported!"); + }; + + Connector.prototype.readMore_ = function(result) { + for (;;) { + var read = this.handle_.readMessage(); + if (this.handle_ == null) // The connector has been closed. + return; + if (read.result == Mojo.RESULT_SHOULD_WAIT) + return; + if (read.result != Mojo.RESULT_OK) { + this.error_ = true; + if (this.errorHandler_) + this.errorHandler_.onError(read.result); + return; + } + var messageBuffer = new internal.Buffer(read.buffer); + var message = new internal.Message(messageBuffer, read.handles); + if (this.incomingReceiver_) + this.incomingReceiver_.accept(message); + } + }; + + internal.Connector = Connector; +})(); diff --git a/mojo/public/js/new_bindings/interface_types.js b/mojo/public/js/new_bindings/interface_types.js new file mode 100644 index 0000000000..c52f6c7e55 --- /dev/null +++ b/mojo/public/js/new_bindings/interface_types.js @@ -0,0 +1,46 @@ +// 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. + +(function() { + // --------------------------------------------------------------------------- + + function InterfacePtrInfo(handle, version) { + this.handle = handle; + this.version = version; + } + + InterfacePtrInfo.prototype.isValid = function() { + return this.handle instanceof MojoHandle; + }; + + InterfacePtrInfo.prototype.close = function() { + if (!this.isValid()) + return; + + this.handle.close(); + this.handle = null; + this.version = 0; + }; + + // --------------------------------------------------------------------------- + + function InterfaceRequest(handle) { + this.handle = handle; + } + + InterfaceRequest.prototype.isValid = function() { + return this.handle instanceof MojoHandle; + }; + + InterfaceRequest.prototype.close = function() { + if (!this.isValid()) + return; + + this.handle.close(); + this.handle = null; + }; + + mojo.InterfacePtrInfo = InterfacePtrInfo; + mojo.InterfaceRequest = InterfaceRequest; +})(); diff --git a/mojo/public/js/new_bindings/lib/control_message_handler.js b/mojo/public/js/new_bindings/lib/control_message_handler.js new file mode 100644 index 0000000000..3f122fb379 --- /dev/null +++ b/mojo/public/js/new_bindings/lib/control_message_handler.js @@ -0,0 +1,106 @@ +// 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. + +(function() { + var internal = mojo.internal; + + function validateControlRequestWithResponse(message) { + var messageValidator = new internal.Validator(message); + var error = messageValidator.validateMessageIsRequestExpectingResponse(); + if (error !== internal.validationError.NONE) { + throw error; + } + + if (message.getName() != mojo.interface_control2.kRunMessageId) { + throw new Error("Control message name is not kRunMessageId"); + } + + // Validate payload. + error = mojo.interface_control2.RunMessageParams.validate(messageValidator, + message.getHeaderNumBytes()); + if (error != internal.validationError.NONE) { + throw error; + } + } + + function validateControlRequestWithoutResponse(message) { + var messageValidator = new internal.Validator(message); + var error = messageValidator.validateMessageIsRequestWithoutResponse(); + if (error != internal.validationError.NONE) { + throw error; + } + + if (message.getName() != mojo.interface_control2.kRunOrClosePipeMessageId) { + throw new Error("Control message name is not kRunOrClosePipeMessageId"); + } + + // Validate payload. + error = mojo.interface_control2.RunOrClosePipeMessageParams.validate( + messageValidator, message.getHeaderNumBytes()); + if (error != internal.validationError.NONE) { + throw error; + } + } + + function runOrClosePipe(message, interface_version) { + var reader = new internal.MessageReader(message); + var runOrClosePipeMessageParams = reader.decodeStruct( + mojo.interface_control2.RunOrClosePipeMessageParams); + return interface_version >= + runOrClosePipeMessageParams.input.require_version.version; + } + + function run(message, responder, interface_version) { + var reader = new internal.MessageReader(message); + var runMessageParams = + reader.decodeStruct(mojo.interface_control2.RunMessageParams); + var runOutput = null; + + if (runMessageParams.input.query_version) { + runOutput = new mojo.interface_control2.RunOutput(); + runOutput.query_version_result = new + mojo.interface_control2.QueryVersionResult( + {'version': interface_version}); + } + + var runResponseMessageParams = new + mojo.interface_control2.RunResponseMessageParams(); + runResponseMessageParams.output = runOutput; + + var messageName = mojo.interface_control2.kRunMessageId; + var payloadSize = + mojo.interface_control2.RunResponseMessageParams.encodedSize; + var requestID = reader.requestID; + var builder = new internal.MessageWithRequestIDBuilder(messageName, + payloadSize, internal.kMessageIsResponse, requestID); + builder.encodeStruct(mojo.interface_control2.RunResponseMessageParams, + runResponseMessageParams); + responder.accept(builder.finish()); + return true; + } + + function isInterfaceControlMessage(message) { + return message.getName() == mojo.interface_control2.kRunMessageId || + message.getName() == + mojo.interface_control2.kRunOrClosePipeMessageId; + } + + function ControlMessageHandler(interface_version) { + this.interface_version = interface_version; + } + + ControlMessageHandler.prototype.accept = function(message) { + validateControlRequestWithoutResponse(message); + return runOrClosePipe(message, this.interface_version); + }; + + ControlMessageHandler.prototype.acceptWithResponder = function(message, + responder) { + validateControlRequestWithResponse(message); + return run(message, responder, this.interface_version); + }; + + internal.ControlMessageHandler = ControlMessageHandler; + internal.isInterfaceControlMessage = isInterfaceControlMessage; +})(); diff --git a/mojo/public/js/new_bindings/lib/control_message_proxy.js b/mojo/public/js/new_bindings/lib/control_message_proxy.js new file mode 100644 index 0000000000..1d57557ae2 --- /dev/null +++ b/mojo/public/js/new_bindings/lib/control_message_proxy.js @@ -0,0 +1,97 @@ +// 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. + +(function() { + var internal = mojo.internal; + + function sendRunOrClosePipeMessage(receiver, runOrClosePipeMessageParams) { + var messageName = mojo.interface_control2.kRunOrClosePipeMessageId; + var payloadSize = + mojo.interface_control2.RunOrClosePipeMessageParams.encodedSize; + var builder = new internal.MessageBuilder(messageName, payloadSize); + builder.encodeStruct(mojo.interface_control2.RunOrClosePipeMessageParams, + runOrClosePipeMessageParams); + var message = builder.finish(); + receiver.accept(message); + } + + function validateControlResponse(message) { + var messageValidator = new internal.Validator(message); + var error = messageValidator.validateMessageIsResponse(); + if (error != internal.validationError.NONE) { + throw error; + } + + if (message.getName() != mojo.interface_control2.kRunMessageId) { + throw new Error("Control message name is not kRunMessageId"); + } + + // Validate payload. + error = mojo.interface_control2.RunResponseMessageParams.validate( + messageValidator, message.getHeaderNumBytes()); + if (error != internal.validationError.NONE) { + throw error; + } + } + + function acceptRunResponse(message) { + validateControlResponse(message); + + var reader = new internal.MessageReader(message); + var runResponseMessageParams = reader.decodeStruct( + mojo.interface_control2.RunResponseMessageParams); + + return Promise.resolve(runResponseMessageParams); + } + + /** + * Sends the given run message through the receiver. + * Accepts the response message from the receiver and decodes the message + * struct to RunResponseMessageParams. + * + * @param {Router} receiver. + * @param {RunMessageParams} runMessageParams to be sent via a message. + * @return {Promise} that resolves to a RunResponseMessageParams. + */ + function sendRunMessage(receiver, runMessageParams) { + var messageName = mojo.interface_control2.kRunMessageId; + var payloadSize = mojo.interface_control2.RunMessageParams.encodedSize; + // |requestID| is set to 0, but is later properly set by Router. + var builder = new internal.MessageWithRequestIDBuilder(messageName, + payloadSize, internal.kMessageExpectsResponse, 0); + builder.encodeStruct(mojo.interface_control2.RunMessageParams, + runMessageParams); + var message = builder.finish(); + + return receiver.acceptAndExpectResponse(message).then(acceptRunResponse); + } + + function ControlMessageProxy(receiver) { + this.receiver = receiver; + } + + ControlMessageProxy.prototype.queryVersion = function() { + var runMessageParams = new mojo.interface_control2.RunMessageParams(); + runMessageParams.input = new mojo.interface_control2.RunInput(); + runMessageParams.input.query_version = + new mojo.interface_control2.QueryVersion(); + + return sendRunMessage(this.receiver, runMessageParams).then(function( + runResponseMessageParams) { + return runResponseMessageParams.output.query_version_result.version; + }); + }; + + ControlMessageProxy.prototype.requireVersion = function(version) { + var runOrClosePipeMessageParams = new + mojo.interface_control2.RunOrClosePipeMessageParams(); + runOrClosePipeMessageParams.input = new + mojo.interface_control2.RunOrClosePipeInput(); + runOrClosePipeMessageParams.input.require_version = new + mojo.interface_control2.RequireVersion({'version': version}); + sendRunOrClosePipeMessage(this.receiver, runOrClosePipeMessageParams); + }; + + internal.ControlMessageProxy = ControlMessageProxy; +})(); diff --git a/mojo/public/js/new_bindings/router.js b/mojo/public/js/new_bindings/router.js new file mode 100644 index 0000000000..1272407c1e --- /dev/null +++ b/mojo/public/js/new_bindings/router.js @@ -0,0 +1,190 @@ +// 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. + +(function() { + var internal = mojo.internal; + + function Router(handle, interface_version, connectorFactory) { + if (!(handle instanceof MojoHandle)) + throw new Error("Router constructor: Not a handle"); + if (connectorFactory === undefined) + connectorFactory = internal.Connector; + this.connector_ = new connectorFactory(handle); + this.incomingReceiver_ = null; + this.errorHandler_ = null; + this.nextRequestID_ = 0; + this.completers_ = new Map(); + this.payloadValidators_ = []; + this.testingController_ = null; + + if (interface_version !== undefined) { + this.controlMessageHandler_ = new + internal.ControlMessageHandler(interface_version); + } + + this.connector_.setIncomingReceiver({ + accept: this.handleIncomingMessage_.bind(this), + }); + this.connector_.setErrorHandler({ + onError: this.handleConnectionError_.bind(this), + }); + } + + Router.prototype.close = function() { + this.completers_.clear(); // Drop any responders. + this.connector_.close(); + this.testingController_ = null; + }; + + Router.prototype.accept = function(message) { + this.connector_.accept(message); + }; + + Router.prototype.reject = function(message) { + // TODO(mpcomplete): no way to trasmit errors over a Connection. + }; + + Router.prototype.acceptAndExpectResponse = function(message) { + // Reserve 0 in case we want it to convey special meaning in the future. + var requestID = this.nextRequestID_++; + if (requestID == 0) + requestID = this.nextRequestID_++; + + message.setRequestID(requestID); + var result = this.connector_.accept(message); + if (!result) + return Promise.reject(Error("Connection error")); + + var completer = {}; + this.completers_.set(requestID, completer); + return new Promise(function(resolve, reject) { + completer.resolve = resolve; + completer.reject = reject; + }); + }; + + Router.prototype.setIncomingReceiver = function(receiver) { + this.incomingReceiver_ = receiver; + }; + + Router.prototype.setPayloadValidators = function(payloadValidators) { + this.payloadValidators_ = payloadValidators; + }; + + Router.prototype.setErrorHandler = function(handler) { + this.errorHandler_ = handler; + }; + + Router.prototype.encounteredError = function() { + return this.connector_.encounteredError(); + }; + + Router.prototype.enableTestingMode = function() { + this.testingController_ = new RouterTestingController(this.connector_); + return this.testingController_; + }; + + Router.prototype.handleIncomingMessage_ = function(message) { + var noError = internal.validationError.NONE; + var messageValidator = new internal.Validator(message); + var err = messageValidator.validateMessageHeader(); + for (var i = 0; err === noError && i < this.payloadValidators_.length; ++i) + err = this.payloadValidators_[i](messageValidator); + + if (err == noError) + this.handleValidIncomingMessage_(message); + else + this.handleInvalidIncomingMessage_(message, err); + }; + + Router.prototype.handleValidIncomingMessage_ = function(message) { + if (this.testingController_) + return; + + if (message.expectsResponse()) { + if (internal.isInterfaceControlMessage(message)) { + if (this.controlMessageHandler_) { + this.controlMessageHandler_.acceptWithResponder(message, this); + } else { + this.close(); + } + } else if (this.incomingReceiver_) { + this.incomingReceiver_.acceptWithResponder(message, this); + } else { + // 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. + this.close(); + } + } else if (message.isResponse()) { + var reader = new internal.MessageReader(message); + var requestID = reader.requestID; + var completer = this.completers_.get(requestID); + if (completer) { + this.completers_.delete(requestID); + completer.resolve(message); + } else { + console.log("Unexpected response with request ID: " + requestID); + } + } else { + if (internal.isInterfaceControlMessage(message)) { + if (this.controlMessageHandler_) { + var ok = this.controlMessageHandler_.accept(message); + if (ok) return; + } + this.close(); + } else if (this.incomingReceiver_) { + this.incomingReceiver_.accept(message); + } + } + }; + + Router.prototype.handleInvalidIncomingMessage_ = function(message, error) { + if (!this.testingController_) { + // TODO(yzshen): Consider notifying the embedder. + // TODO(yzshen): This should also trigger connection error handler. + // Consider making accept() return a boolean and let the connector deal + // with this, as the C++ code does. + console.log("Invalid message: " + internal.validationError[error]); + + this.close(); + return; + } + + this.testingController_.onInvalidIncomingMessage(error); + }; + + Router.prototype.handleConnectionError_ = function(result) { + this.completers_.forEach(function(value) { + value.reject(result); + }); + if (this.errorHandler_) + this.errorHandler_(); + this.close(); + }; + + // The RouterTestingController is used in unit tests. It defeats valid message + // handling and delgates invalid message handling. + + function RouterTestingController(connector) { + this.connector_ = connector; + this.invalidMessageHandler_ = null; + } + + RouterTestingController.prototype.waitForNextMessage = function() { + this.connector_.waitForNextMessageForTesting(); + }; + + RouterTestingController.prototype.setInvalidIncomingMessageHandler = + function(callback) { + this.invalidMessageHandler_ = callback; + }; + + RouterTestingController.prototype.onInvalidIncomingMessage = + function(error) { + if (this.invalidMessageHandler_) + this.invalidMessageHandler_(error); + }; + + internal.Router = Router; +})(); diff --git a/mojo/public/js/new_bindings/unicode.js b/mojo/public/js/new_bindings/unicode.js new file mode 100644 index 0000000000..6ed8839c3f --- /dev/null +++ b/mojo/public/js/new_bindings/unicode.js @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Defines functions for translating between JavaScript strings and UTF8 strings + * stored in ArrayBuffers. There is much room for optimization in this code if + * it proves necessary. + */ +(function() { + var internal = mojo.internal; + + /** + * Decodes the UTF8 string from the given buffer. + * @param {ArrayBufferView} buffer The buffer containing UTF8 string data. + * @return {string} The corresponding JavaScript string. + */ + function decodeUtf8String(buffer) { + return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer))); + } + + /** + * Encodes the given JavaScript string into UTF8. + * @param {string} str The string to encode. + * @param {ArrayBufferView} outputBuffer The buffer to contain the result. + * Should be pre-allocated to hold enough space. Use |utf8Length| to determine + * how much space is required. + * @return {number} The number of bytes written to |outputBuffer|. + */ + function encodeUtf8String(str, outputBuffer) { + var utf8String = unescape(encodeURIComponent(str)); + if (outputBuffer.length < utf8String.length) + throw new Error("Buffer too small for encodeUtf8String"); + for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++) + outputBuffer[i] = utf8String.charCodeAt(i); + return i; + } + + /** + * Returns the number of bytes that a UTF8 encoding of the JavaScript string + * |str| would occupy. + */ + function utf8Length(str) { + var utf8String = unescape(encodeURIComponent(str)); + return utf8String.length; + } + + internal.decodeUtf8String = decodeUtf8String; + internal.encodeUtf8String = encodeUtf8String; + internal.utf8Length = utf8Length; +})(); diff --git a/mojo/public/js/new_bindings/validator.js b/mojo/public/js/new_bindings/validator.js new file mode 100644 index 0000000000..610112b58e --- /dev/null +++ b/mojo/public/js/new_bindings/validator.js @@ -0,0 +1,511 @@ +// 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. + +(function() { + var internal = mojo.internal; + + var validationError = { + NONE: 'VALIDATION_ERROR_NONE', + MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT', + ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE', + UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER', + UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER', + ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE', + UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE', + ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER', + UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER', + MESSAGE_HEADER_INVALID_FLAGS: + 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS', + MESSAGE_HEADER_MISSING_REQUEST_ID: + 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID', + DIFFERENT_SIZED_ARRAYS_IN_MAP: + 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP', + INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE', + UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION', + UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE', + }; + + var NULL_MOJO_POINTER = "NULL_MOJO_POINTER"; + + function isEnumClass(cls) { + return cls instanceof internal.Enum; + } + + function isStringClass(cls) { + return cls === internal.String || cls === internal.NullableString; + } + + function isHandleClass(cls) { + return cls === internal.Handle || cls === internal.NullableHandle; + } + + function isInterfaceClass(cls) { + return cls instanceof internal.Interface; + } + + function isInterfaceRequestClass(cls) { + return cls === internal.InterfaceRequest || + cls === internal.NullableInterfaceRequest; + } + + function isNullable(type) { + return type === internal.NullableString || + type === internal.NullableHandle || + type === internal.NullableInterface || + type === internal.NullableInterfaceRequest || + type instanceof internal.NullableArrayOf || + type instanceof internal.NullablePointerTo; + } + + function Validator(message) { + this.message = message; + this.offset = 0; + this.handleIndex = 0; + } + + Object.defineProperty(Validator.prototype, "offsetLimit", { + get: function() { return this.message.buffer.byteLength; } + }); + + Object.defineProperty(Validator.prototype, "handleIndexLimit", { + get: function() { return this.message.handles.length; } + }); + + // True if we can safely allocate a block of bytes from start to + // to start + numBytes. + Validator.prototype.isValidRange = function(start, numBytes) { + // Only positive JavaScript integers that are less than 2^53 + // (Number.MAX_SAFE_INTEGER) can be represented exactly. + if (start < this.offset || numBytes <= 0 || + !Number.isSafeInteger(start) || + !Number.isSafeInteger(numBytes)) + return false; + + var newOffset = start + numBytes; + if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit) + return false; + + return true; + }; + + Validator.prototype.claimRange = function(start, numBytes) { + if (this.isValidRange(start, numBytes)) { + this.offset = start + numBytes; + return true; + } + return false; + }; + + Validator.prototype.claimHandle = function(index) { + if (index === internal.kEncodedInvalidHandleValue) + return true; + + if (index < this.handleIndex || index >= this.handleIndexLimit) + return false; + + // This is safe because handle indices are uint32. + this.handleIndex = index + 1; + return true; + }; + + Validator.prototype.validateEnum = function(offset, enumClass) { + // Note: Assumes that enums are always 32 bits! But this matches + // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay. + var value = this.message.buffer.getInt32(offset); + return enumClass.validate(value); + } + + Validator.prototype.validateHandle = function(offset, nullable) { + var index = this.message.buffer.getUint32(offset); + + if (index === internal.kEncodedInvalidHandleValue) + return nullable ? + validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE; + + if (!this.claimHandle(index)) + return validationError.ILLEGAL_HANDLE; + + return validationError.NONE; + }; + + Validator.prototype.validateInterface = function(offset, nullable) { + return this.validateHandle(offset, nullable); + }; + + Validator.prototype.validateInterfaceRequest = function(offset, nullable) { + return this.validateHandle(offset, nullable); + }; + + Validator.prototype.validateStructHeader = function(offset, minNumBytes) { + if (!internal.isAligned(offset)) + return validationError.MISALIGNED_OBJECT; + + if (!this.isValidRange(offset, internal.kStructHeaderSize)) + return validationError.ILLEGAL_MEMORY_RANGE; + + var numBytes = this.message.buffer.getUint32(offset); + + if (numBytes < minNumBytes) + return validationError.UNEXPECTED_STRUCT_HEADER; + + if (!this.claimRange(offset, numBytes)) + return validationError.ILLEGAL_MEMORY_RANGE; + + return validationError.NONE; + }; + + Validator.prototype.validateStructVersion = function(offset, versionSizes) { + var numBytes = this.message.buffer.getUint32(offset); + var version = this.message.buffer.getUint32(offset + 4); + + if (version <= versionSizes[versionSizes.length - 1].version) { + // Scan in reverse order to optimize for more recent versionSizes. + for (var i = versionSizes.length - 1; i >= 0; --i) { + if (version >= versionSizes[i].version) { + if (numBytes == versionSizes[i].numBytes) + break; + return validationError.UNEXPECTED_STRUCT_HEADER; + } + } + } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) { + return validationError.UNEXPECTED_STRUCT_HEADER; + } + + return validationError.NONE; + }; + + Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) { + var structVersion = this.message.buffer.getUint32(offset + 4); + return fieldVersion <= structVersion; + }; + + Validator.prototype.validateMessageHeader = function() { + + var err = this.validateStructHeader(0, internal.kMessageHeaderSize); + if (err != validationError.NONE) + return err; + + var numBytes = this.message.getHeaderNumBytes(); + var version = this.message.getHeaderVersion(); + + var validVersionAndNumBytes = + (version == 0 && numBytes == internal.kMessageHeaderSize) || + (version == 1 && + numBytes == internal.kMessageWithRequestIDHeaderSize) || + (version > 1 && + numBytes >= internal.kMessageWithRequestIDHeaderSize); + if (!validVersionAndNumBytes) + return validationError.UNEXPECTED_STRUCT_HEADER; + + var expectsResponse = this.message.expectsResponse(); + var isResponse = this.message.isResponse(); + + if (version == 0 && (expectsResponse || isResponse)) + return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID; + + if (isResponse && expectsResponse) + return validationError.MESSAGE_HEADER_INVALID_FLAGS; + + return validationError.NONE; + }; + + Validator.prototype.validateMessageIsRequestWithoutResponse = function() { + if (this.message.isResponse() || this.message.expectsResponse()) { + return validationError.MESSAGE_HEADER_INVALID_FLAGS; + } + return validationError.NONE; + }; + + Validator.prototype.validateMessageIsRequestExpectingResponse = function() { + if (this.message.isResponse() || !this.message.expectsResponse()) { + return validationError.MESSAGE_HEADER_INVALID_FLAGS; + } + return validationError.NONE; + }; + + Validator.prototype.validateMessageIsResponse = function() { + if (this.message.expectsResponse() || !this.message.isResponse()) { + return validationError.MESSAGE_HEADER_INVALID_FLAGS; + } + return validationError.NONE; + }; + + // Returns the message.buffer relative offset this pointer "points to", + // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the + // pointer's value is not valid. + Validator.prototype.decodePointer = function(offset) { + var pointerValue = this.message.buffer.getUint64(offset); + if (pointerValue === 0) + return NULL_MOJO_POINTER; + var bufferOffset = offset + pointerValue; + return Number.isSafeInteger(bufferOffset) ? bufferOffset : null; + }; + + Validator.prototype.decodeUnionSize = function(offset) { + return this.message.buffer.getUint32(offset); + }; + + Validator.prototype.decodeUnionTag = function(offset) { + return this.message.buffer.getUint32(offset + 4); + }; + + Validator.prototype.validateArrayPointer = function( + offset, elementSize, elementType, nullable, expectedDimensionSizes, + currentDimension) { + var arrayOffset = this.decodePointer(offset); + if (arrayOffset === null) + return validationError.ILLEGAL_POINTER; + + if (arrayOffset === NULL_MOJO_POINTER) + return nullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; + + return this.validateArray(arrayOffset, elementSize, elementType, + expectedDimensionSizes, currentDimension); + }; + + Validator.prototype.validateStructPointer = function( + offset, structClass, nullable) { + var structOffset = this.decodePointer(offset); + if (structOffset === null) + return validationError.ILLEGAL_POINTER; + + if (structOffset === NULL_MOJO_POINTER) + return nullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; + + return structClass.validate(this, structOffset); + }; + + Validator.prototype.validateUnion = function( + offset, unionClass, nullable) { + var size = this.message.buffer.getUint32(offset); + if (size == 0) { + return nullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_UNION; + } + + return unionClass.validate(this, offset); + }; + + Validator.prototype.validateNestedUnion = function( + offset, unionClass, nullable) { + var unionOffset = this.decodePointer(offset); + if (unionOffset === null) + return validationError.ILLEGAL_POINTER; + + if (unionOffset === NULL_MOJO_POINTER) + return nullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_UNION; + + return this.validateUnion(unionOffset, unionClass, nullable); + }; + + // This method assumes that the array at arrayPointerOffset has + // been validated. + + Validator.prototype.arrayLength = function(arrayPointerOffset) { + var arrayOffset = this.decodePointer(arrayPointerOffset); + return this.message.buffer.getUint32(arrayOffset + 4); + }; + + Validator.prototype.validateMapPointer = function( + offset, mapIsNullable, keyClass, valueClass, valueIsNullable) { + // Validate the implicit map struct: + // struct {array<keyClass> keys; array<valueClass> values}; + var structOffset = this.decodePointer(offset); + if (structOffset === null) + return validationError.ILLEGAL_POINTER; + + if (structOffset === NULL_MOJO_POINTER) + return mapIsNullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; + + var mapEncodedSize = internal.kStructHeaderSize + + internal.kMapStructPayloadSize; + var err = this.validateStructHeader(structOffset, mapEncodedSize); + if (err !== validationError.NONE) + return err; + + // Validate the keys array. + var keysArrayPointerOffset = structOffset + internal.kStructHeaderSize; + err = this.validateArrayPointer( + keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0); + if (err !== validationError.NONE) + return err; + + // Validate the values array. + var valuesArrayPointerOffset = keysArrayPointerOffset + 8; + var valuesArrayDimensions = [0]; // Validate the actual length below. + if (valueClass instanceof internal.ArrayOf) + valuesArrayDimensions = + valuesArrayDimensions.concat(valueClass.dimensions()); + var err = this.validateArrayPointer(valuesArrayPointerOffset, + valueClass.encodedSize, + valueClass, + valueIsNullable, + valuesArrayDimensions, + 0); + if (err !== validationError.NONE) + return err; + + // Validate the lengths of the keys and values arrays. + var keysArrayLength = this.arrayLength(keysArrayPointerOffset); + var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset); + if (keysArrayLength != valuesArrayLength) + return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP; + + return validationError.NONE; + }; + + Validator.prototype.validateStringPointer = function(offset, nullable) { + return this.validateArrayPointer( + offset, internal.Uint8.encodedSize, internal.Uint8, nullable, [0], 0); + }; + + // Similar to Array_Data<T>::Validate() + // mojo/public/cpp/bindings/lib/array_internal.h + + Validator.prototype.validateArray = + function (offset, elementSize, elementType, expectedDimensionSizes, + currentDimension) { + if (!internal.isAligned(offset)) + return validationError.MISALIGNED_OBJECT; + + if (!this.isValidRange(offset, internal.kArrayHeaderSize)) + return validationError.ILLEGAL_MEMORY_RANGE; + + var numBytes = this.message.buffer.getUint32(offset); + var numElements = this.message.buffer.getUint32(offset + 4); + + // Note: this computation is "safe" because elementSize <= 8 and + // numElements is a uint32. + var elementsTotalSize = (elementType === internal.PackedBool) ? + Math.ceil(numElements / 8) : (elementSize * numElements); + + if (numBytes < internal.kArrayHeaderSize + elementsTotalSize) + return validationError.UNEXPECTED_ARRAY_HEADER; + + if (expectedDimensionSizes[currentDimension] != 0 && + numElements != expectedDimensionSizes[currentDimension]) { + return validationError.UNEXPECTED_ARRAY_HEADER; + } + + if (!this.claimRange(offset, numBytes)) + return validationError.ILLEGAL_MEMORY_RANGE; + + // Validate the array's elements if they are pointers or handles. + + var elementsOffset = offset + internal.kArrayHeaderSize; + var nullable = isNullable(elementType); + + if (isHandleClass(elementType)) + return this.validateHandleElements(elementsOffset, numElements, nullable); + if (isInterfaceClass(elementType)) + return this.validateInterfaceElements( + elementsOffset, numElements, nullable); + if (isInterfaceRequestClass(elementType)) + return this.validateInterfaceRequestElements( + elementsOffset, numElements, nullable); + if (isStringClass(elementType)) + return this.validateArrayElements( + elementsOffset, numElements, internal.Uint8, nullable, [0], 0); + if (elementType instanceof internal.PointerTo) + return this.validateStructElements( + elementsOffset, numElements, elementType.cls, nullable); + if (elementType instanceof internal.ArrayOf) + return this.validateArrayElements( + elementsOffset, numElements, elementType.cls, nullable, + expectedDimensionSizes, currentDimension + 1); + if (isEnumClass(elementType)) + return this.validateEnumElements(elementsOffset, numElements, + elementType.cls); + + return validationError.NONE; + }; + + // Note: the |offset + i * elementSize| computation in the validateFooElements + // methods below is "safe" because elementSize <= 8, offset and + // numElements are uint32, and 0 <= i < numElements. + + Validator.prototype.validateHandleElements = + function(offset, numElements, nullable) { + var elementSize = internal.Handle.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateHandle(elementOffset, nullable); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + Validator.prototype.validateInterfaceElements = + function(offset, numElements, nullable) { + var elementSize = internal.Interface.prototype.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateInterface(elementOffset, nullable); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + Validator.prototype.validateInterfaceRequestElements = + function(offset, numElements, nullable) { + var elementSize = internal.InterfaceRequest.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateInterfaceRequest(elementOffset, nullable); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + // The elementClass parameter is the element type of the element arrays. + Validator.prototype.validateArrayElements = + function(offset, numElements, elementClass, nullable, + expectedDimensionSizes, currentDimension) { + var elementSize = internal.PointerTo.prototype.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateArrayPointer( + elementOffset, elementClass.encodedSize, elementClass, nullable, + expectedDimensionSizes, currentDimension); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + Validator.prototype.validateStructElements = + function(offset, numElements, structClass, nullable) { + var elementSize = internal.PointerTo.prototype.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = + this.validateStructPointer(elementOffset, structClass, nullable); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + Validator.prototype.validateEnumElements = + function(offset, numElements, enumClass) { + var elementSize = internal.Enum.prototype.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateEnum(elementOffset, enumClass); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + internal.validationError = validationError; + internal.Validator = Validator; +})(); diff --git a/mojo/public/js/router.js b/mojo/public/js/router.js new file mode 100644 index 0000000000..89d9a2f66b --- /dev/null +++ b/mojo/public/js/router.js @@ -0,0 +1,269 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/router", [ + "mojo/public/js/connector", + "mojo/public/js/core", + "mojo/public/js/interface_types", + "mojo/public/js/lib/interface_endpoint_handle", + "mojo/public/js/lib/pipe_control_message_handler", + "mojo/public/js/lib/pipe_control_message_proxy", + "mojo/public/js/validator", + "timer", +], function(connector, core, types, interfaceEndpointHandle, + controlMessageHandler, controlMessageProxy, validator, timer) { + + var Connector = connector.Connector; + var PipeControlMessageHandler = + controlMessageHandler.PipeControlMessageHandler; + var PipeControlMessageProxy = controlMessageProxy.PipeControlMessageProxy; + var Validator = validator.Validator; + var InterfaceEndpointHandle = interfaceEndpointHandle.InterfaceEndpointHandle; + + /** + * The state of |endpoint|. If both the endpoint and its peer have been + * closed, removes it from |endpoints_|. + * @enum {string} + */ + var EndpointStateUpdateType = { + ENDPOINT_CLOSED: 'endpoint_closed', + PEER_ENDPOINT_CLOSED: 'peer_endpoint_closed' + }; + + function check(condition, output) { + if (!condition) { + // testharness.js does not rethrow errors so the error stack needs to be + // included as a string in the error we throw for debugging layout tests. + throw new Error((new Error()).stack); + } + } + + function InterfaceEndpoint(router, interfaceId) { + this.router_ = router; + this.id = interfaceId; + this.closed = false; + this.peerClosed = false; + this.handleCreated = false; + this.disconnectReason = null; + this.client = null; + } + + InterfaceEndpoint.prototype.sendMessage = function(message) { + message.setInterfaceId(this.id); + return this.router_.connector_.accept(message); + }; + + function Router(handle, setInterfaceIdNamespaceBit) { + if (!core.isHandle(handle)) { + throw new Error("Router constructor: Not a handle"); + } + if (setInterfaceIdNamespaceBit === undefined) { + setInterfaceIdNamespaceBit = false; + } + + this.connector_ = new Connector(handle); + + this.connector_.setIncomingReceiver({ + accept: this.accept.bind(this), + }); + this.connector_.setErrorHandler({ + onError: this.onPipeConnectionError.bind(this), + }); + + this.setInterfaceIdNamespaceBit_ = setInterfaceIdNamespaceBit; + this.controlMessageHandler_ = new PipeControlMessageHandler(this); + this.controlMessageProxy_ = new PipeControlMessageProxy(this.connector_); + this.nextInterfaceIdValue = 1; + this.encounteredError_ = false; + this.endpoints_ = new Map(); + } + + Router.prototype.attachEndpointClient = function( + interfaceEndpointHandle, interfaceEndpointClient) { + check(types.isValidInterfaceId(interfaceEndpointHandle.id())); + check(interfaceEndpointClient); + + var endpoint = this.endpoints_.get(interfaceEndpointHandle.id()); + check(endpoint); + check(!endpoint.client); + check(!endpoint.closed); + endpoint.client = interfaceEndpointClient; + + if (endpoint.peerClosed) { + timer.createOneShot(0, + endpoint.client.notifyError.bind(endpoint.client)); + } + + return endpoint; + }; + + Router.prototype.detachEndpointClient = function( + interfaceEndpointHandle) { + check(types.isValidInterfaceId(interfaceEndpointHandle.id())); + var endpoint = this.endpoints_.get(interfaceEndpointHandle.id()); + check(endpoint); + check(endpoint.client); + check(!endpoint.closed); + + endpoint.client = null; + }; + + Router.prototype.createLocalEndpointHandle = function( + interfaceId) { + if (!types.isValidInterfaceId(interfaceId)) { + return new InterfaceEndpointHandle(); + } + + var endpoint = this.endpoints_.get(interfaceId); + + if (!endpoint) { + endpoint = new InterfaceEndpoint(this, interfaceId); + this.endpoints_.set(interfaceId, endpoint); + + check(!endpoint.handleCreated); + + if (this.encounteredError_) { + this.updateEndpointStateMayRemove(endpoint, + EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); + } + } else { + // If the endpoint already exist, it is because we have received a + // notification that the peer endpoint has closed. + check(!endpoint.closed); + check(endpoint.peerClosed); + + if (endpoint.handleCreated) { + return new InterfaceEndpointHandle(); + } + } + + endpoint.handleCreated = true; + return new InterfaceEndpointHandle(interfaceId, this); + }; + + Router.prototype.accept = function(message) { + var messageValidator = new Validator(message); + var err = messageValidator.validateMessageHeader(); + + var ok = false; + if (err !== validator.validationError.NONE) { + validator.reportValidationError(err); + } else if (controlMessageHandler.isPipeControlMessage(message)) { + ok = this.controlMessageHandler_.accept(message); + } else { + var interfaceId = message.getInterfaceId(); + var endpoint = this.endpoints_.get(interfaceId); + if (!endpoint || endpoint.closed) { + return true; + } + + if (!endpoint.client) { + // We need to wait until a client is attached in order to dispatch + // further messages. + return false; + } + ok = endpoint.client.handleIncomingMessage_(message); + } + + if (!ok) { + this.handleInvalidIncomingMessage_(); + } + return ok; + }; + + Router.prototype.close = function() { + this.connector_.close(); + // Closing the message pipe won't trigger connection error handler. + // Explicitly call onPipeConnectionError() so that associated endpoints + // will get notified. + this.onPipeConnectionError(); + }; + + Router.prototype.waitForNextMessageForTesting = function() { + this.connector_.waitForNextMessageForTesting(); + }; + + Router.prototype.handleInvalidIncomingMessage_ = function(message) { + if (!validator.isTestingMode()) { + // TODO(yzshen): Consider notifying the embedder. + // TODO(yzshen): This should also trigger connection error handler. + // Consider making accept() return a boolean and let the connector deal + // with this, as the C++ code does. + this.close(); + return; + } + }; + + Router.prototype.onPeerAssociatedEndpointClosed = function(interfaceId, + reason) { + check(!types.isMasterInterfaceId(interfaceId) || reason); + + var endpoint = this.endpoints_.get(interfaceId); + if (!endpoint) { + endpoint = new InterfaceEndpoint(this, interfaceId); + this.endpoints_.set(interfaceId, endpoint); + } + + if (reason) { + endpoint.disconnectReason = reason; + } + + if (!endpoint.peerClosed) { + if (endpoint.client) { + timer.createOneShot(0, + endpoint.client.notifyError.bind(endpoint.client, reason)); + } + this.updateEndpointStateMayRemove(endpoint, + EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); + } + return true; + }; + + Router.prototype.onPipeConnectionError = function() { + this.encounteredError_ = true; + + for (var endpoint of this.endpoints_.values()) { + if (endpoint.client) { + timer.createOneShot(0, + endpoint.client.notifyError.bind(endpoint.client, + endpoint.disconnectReason)); + } + this.updateEndpointStateMayRemove(endpoint, + EndpointStateUpdateType.PEER_ENDPOINT_CLOSED); + } + }; + + Router.prototype.closeEndpointHandle = function(interfaceId, reason) { + if (!types.isValidInterfaceId(interfaceId)) { + return; + } + var endpoint = this.endpoints_.get(interfaceId); + check(endpoint); + check(!endpoint.client); + check(!endpoint.closed); + + this.updateEndpointStateMayRemove(endpoint, + EndpointStateUpdateType.ENDPOINT_CLOSED); + + if (!types.isMasterInterfaceId(interfaceId) || reason) { + this.controlMessageProxy_.notifyPeerEndpointClosed(interfaceId, reason); + } + }; + + Router.prototype.updateEndpointStateMayRemove = function(endpoint, + endpointStateUpdateType) { + if (endpointStateUpdateType === EndpointStateUpdateType.ENDPOINT_CLOSED) { + endpoint.closed = true; + } else { + endpoint.peerClosed = true; + } + if (endpoint.closed && endpoint.peerClosed) { + this.endpoints_.delete(endpoint.id); + } + }; + + var exports = {}; + exports.Router = Router; + return exports; +}); diff --git a/mojo/public/js/support.js b/mojo/public/js/support.js new file mode 100644 index 0000000000..7e27504fbe --- /dev/null +++ b/mojo/public/js/support.js @@ -0,0 +1,53 @@ +// 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. + +// Module "mojo/public/js/support" +// +// Note: This file is for documentation purposes only. The code here is not +// actually executed. The real module is implemented natively in Mojo. + +while (1); + +/* @deprecated Please use watch()/cancelWatch() instead of + * asyncWait()/cancelWait(). + * + * Waits on the given handle until the state indicated by |signals| is + * satisfied. + * + * @param {MojoHandle} handle The handle to wait on. + * @param {MojoHandleSignals} signals Specifies the condition to wait for. + * @param {function (mojoResult)} callback Called with the result the wait is + * complete. See MojoWait for possible result codes. + * + * @return {MojoWaitId} A waitId that can be passed to cancelWait to cancel the + * wait. + */ +function asyncWait(handle, signals, callback) { [native code] } + +/* @deprecated Please use watch()/cancelWatch() instead of + * asyncWait()/cancelWait(). + * + * Cancels the asyncWait operation specified by the given |waitId|. + * + * @param {MojoWaitId} waitId The waitId returned by asyncWait. + */ +function cancelWait(waitId) { [native code] } + +/* Begins watching a handle for |signals| to be satisfied or unsatisfiable. + * + * @param {MojoHandle} handle The handle to watch. + * @param {MojoHandleSignals} signals The signals to watch. + * @param {function (mojoResult)} calback Called with a result any time + * the watched signals become satisfied or unsatisfiable. + * + * @param {MojoWatchId} watchId An opaque identifier that identifies this + * watch. + */ +function watch(handle, signals, callback) { [native code] } + +/* Cancels a handle watch initiated by watch(). + * + * @param {MojoWatchId} watchId The watch identifier returned by watch(). + */ +function cancelWatch(watchId) { [native code] } diff --git a/mojo/public/js/tests/core_unittest.js b/mojo/public/js/tests/core_unittest.js new file mode 100644 index 0000000000..86a997f1e7 --- /dev/null +++ b/mojo/public/js/tests/core_unittest.js @@ -0,0 +1,223 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define([ + "gin/test/expect", + "mojo/public/js/core", + "gc", + ], function(expect, core, gc) { + + var HANDLE_SIGNAL_READWRITABLE = core.HANDLE_SIGNAL_WRITABLE | + core.HANDLE_SIGNAL_READABLE; + var HANDLE_SIGNAL_ALL = core.HANDLE_SIGNAL_WRITABLE | + core.HANDLE_SIGNAL_READABLE | + core.HANDLE_SIGNAL_PEER_CLOSED; + + runWithMessagePipe(testNop); + runWithMessagePipe(testReadAndWriteMessage); + runWithMessagePipeWithOptions(testNop); + runWithMessagePipeWithOptions(testReadAndWriteMessage); + runWithDataPipe(testNop); + runWithDataPipe(testReadAndWriteDataPipe); + runWithDataPipeWithOptions(testNop); + runWithDataPipeWithOptions(testReadAndWriteDataPipe); + runWithMessagePipe(testIsHandleMessagePipe); + runWithDataPipe(testIsHandleDataPipe); + runWithSharedBuffer(testSharedBuffer); + gc.collectGarbage(); // should not crash + this.result = "PASS"; + + function runWithMessagePipe(test) { + var pipe = core.createMessagePipe(); + expect(pipe.result).toBe(core.RESULT_OK); + + test(pipe); + + expect(core.close(pipe.handle0)).toBe(core.RESULT_OK); + expect(core.close(pipe.handle1)).toBe(core.RESULT_OK); + } + + function runWithMessagePipeWithOptions(test) { + var pipe = core.createMessagePipe({ + flags: core.CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE + }); + expect(pipe.result).toBe(core.RESULT_OK); + + test(pipe); + + expect(core.close(pipe.handle0)).toBe(core.RESULT_OK); + expect(core.close(pipe.handle1)).toBe(core.RESULT_OK); + } + + function runWithDataPipe(test) { + var pipe = core.createDataPipe(); + expect(pipe.result).toBe(core.RESULT_OK); + + test(pipe); + + expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK); + expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK); + } + + function runWithDataPipeWithOptions(test) { + var pipe = core.createDataPipe({ + flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + elementNumBytes: 1, + capacityNumBytes: 64 + }); + expect(pipe.result).toBe(core.RESULT_OK); + + test(pipe); + + expect(core.close(pipe.producerHandle)).toBe(core.RESULT_OK); + expect(core.close(pipe.consumerHandle)).toBe(core.RESULT_OK); + } + + function runWithSharedBuffer(test) { + let buffer_size = 32; + let sharedBuffer = core.createSharedBuffer(buffer_size, + core.CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE); + + expect(sharedBuffer.result).toBe(core.RESULT_OK); + expect(core.isHandle(sharedBuffer.handle)).toBeTruthy(); + + test(sharedBuffer, buffer_size); + } + + function testNop(pipe) { + } + + function testReadAndWriteMessage(pipe) { + var state0 = core.queryHandleSignalsState(pipe.handle0); + expect(state0.result).toBe(core.RESULT_OK); + expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE); + expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); + + var state1 = core.queryHandleSignalsState(pipe.handle1); + expect(state1.result).toBe(core.RESULT_OK); + expect(state1.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE); + expect(state1.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); + + var senderData = new Uint8Array(42); + for (var i = 0; i < senderData.length; ++i) { + senderData[i] = i * i; + } + + var result = core.writeMessage( + pipe.handle0, senderData, [], + core.WRITE_MESSAGE_FLAG_NONE); + + expect(result).toBe(core.RESULT_OK); + + state0 = core.queryHandleSignalsState(pipe.handle0); + expect(state0.result).toBe(core.RESULT_OK); + expect(state0.satisfiedSignals).toBe(core.HANDLE_SIGNAL_WRITABLE); + expect(state0.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); + + var wait = core.wait(pipe.handle1, core.HANDLE_SIGNAL_READABLE); + expect(wait.result).toBe(core.RESULT_OK); + expect(wait.signalsState.satisfiedSignals).toBe(HANDLE_SIGNAL_READWRITABLE); + expect(wait.signalsState.satisfiableSignals).toBe(HANDLE_SIGNAL_ALL); + + var read = core.readMessage(pipe.handle1, core.READ_MESSAGE_FLAG_NONE); + + expect(read.result).toBe(core.RESULT_OK); + expect(read.buffer.byteLength).toBe(42); + expect(read.handles.length).toBe(0); + + var memory = new Uint8Array(read.buffer); + for (var i = 0; i < memory.length; ++i) + expect(memory[i]).toBe((i * i) & 0xFF); + } + + function testReadAndWriteDataPipe(pipe) { + var senderData = new Uint8Array(42); + for (var i = 0; i < senderData.length; ++i) { + senderData[i] = i * i; + } + + var write = core.writeData( + pipe.producerHandle, senderData, + core.WRITE_DATA_FLAG_ALL_OR_NONE); + + expect(write.result).toBe(core.RESULT_OK); + expect(write.numBytes).toBe(42); + + var wait = core.wait(pipe.consumerHandle, core.HANDLE_SIGNAL_READABLE); + expect(wait.result).toBe(core.RESULT_OK); + var peeked = core.readData( + pipe.consumerHandle, + core.READ_DATA_FLAG_PEEK | core.READ_DATA_FLAG_ALL_OR_NONE); + expect(peeked.result).toBe(core.RESULT_OK); + expect(peeked.buffer.byteLength).toBe(42); + + var peeked_memory = new Uint8Array(peeked.buffer); + for (var i = 0; i < peeked_memory.length; ++i) + expect(peeked_memory[i]).toBe((i * i) & 0xFF); + + var read = core.readData( + pipe.consumerHandle, core.READ_DATA_FLAG_ALL_OR_NONE); + + expect(read.result).toBe(core.RESULT_OK); + expect(read.buffer.byteLength).toBe(42); + + var memory = new Uint8Array(read.buffer); + for (var i = 0; i < memory.length; ++i) + expect(memory[i]).toBe((i * i) & 0xFF); + } + + function testIsHandleMessagePipe(pipe) { + expect(core.isHandle(123).toBeFalsy); + expect(core.isHandle("123").toBeFalsy); + expect(core.isHandle({}).toBeFalsy); + expect(core.isHandle([]).toBeFalsy); + expect(core.isHandle(undefined).toBeFalsy); + expect(core.isHandle(pipe).toBeFalsy); + expect(core.isHandle(pipe.handle0)).toBeTruthy(); + expect(core.isHandle(pipe.handle1)).toBeTruthy(); + expect(core.isHandle(null)).toBeTruthy(); + } + + function testIsHandleDataPipe(pipe) { + expect(core.isHandle(pipe.consumerHandle)).toBeTruthy(); + expect(core.isHandle(pipe.producerHandle)).toBeTruthy(); + } + + function testSharedBuffer(sharedBuffer, buffer_size) { + let offset = 0; + let mappedBuffer0 = core.mapBuffer(sharedBuffer.handle, + offset, + buffer_size, + core.MAP_BUFFER_FLAG_NONE); + + expect(mappedBuffer0.result).toBe(core.RESULT_OK); + + let dupedBufferHandle = core.duplicateBufferHandle(sharedBuffer.handle, + core.DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE); + + expect(dupedBufferHandle.result).toBe(core.RESULT_OK); + expect(core.isHandle(dupedBufferHandle.handle)).toBeTruthy(); + + let mappedBuffer1 = core.mapBuffer(dupedBufferHandle.handle, + offset, + buffer_size, + core.MAP_BUFFER_FLAG_NONE); + + expect(mappedBuffer1.result).toBe(core.RESULT_OK); + + let buffer0 = new Uint8Array(mappedBuffer0.buffer); + let buffer1 = new Uint8Array(mappedBuffer1.buffer); + for(let i = 0; i < buffer0.length; ++i) { + buffer0[i] = i; + expect(buffer1[i]).toBe(i); + } + + expect(core.unmapBuffer(mappedBuffer0.buffer)).toBe(core.RESULT_OK); + expect(core.unmapBuffer(mappedBuffer1.buffer)).toBe(core.RESULT_OK); + + expect(core.close(dupedBufferHandle.handle)).toBe(core.RESULT_OK); + expect(core.close(sharedBuffer.handle)).toBe(core.RESULT_OK); + } + +}); diff --git a/mojo/public/js/tests/validation_test_input_parser.js b/mojo/public/js/tests/validation_test_input_parser.js new file mode 100644 index 0000000000..f5a57f9172 --- /dev/null +++ b/mojo/public/js/tests/validation_test_input_parser.js @@ -0,0 +1,299 @@ +// 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. + +// Support for parsing binary sequences encoded as readable strings +// or ".data" files. The input format is described here: +// mojo/public/cpp/bindings/tests/validation_test_input_parser.h + +define([ + "mojo/public/js/buffer" + ], function(buffer) { + + // Files and Lines represent the raw text from an input string + // or ".data" file. + + function InputError(message, line) { + this.message = message; + this.line = line; + } + + InputError.prototype.toString = function() { + var s = 'Error: ' + this.message; + if (this.line) + s += ', at line ' + + (this.line.number + 1) + ': "' + this.line.contents + '"'; + return s; + } + + function File(contents) { + this.contents = contents; + this.index = 0; + this.lineNumber = 0; + } + + File.prototype.endReached = function() { + return this.index >= this.contents.length; + } + + File.prototype.nextLine = function() { + if (this.endReached()) + return null; + var start = this.index; + var end = this.contents.indexOf('\n', start); + if (end == -1) + end = this.contents.length; + this.index = end + 1; + return new Line(this.contents.substring(start, end), this.lineNumber++); + } + + function Line(contents, number) { + var i = contents.indexOf('//'); + var s = (i == -1) ? contents.trim() : contents.substring(0, i).trim(); + this.contents = contents; + this.items = (s.length > 0) ? s.split(/\s+/) : []; + this.index = 0; + this.number = number; + } + + Line.prototype.endReached = function() { + return this.index >= this.items.length; + } + + var ITEM_TYPE_SIZES = { + u1: 1, u2: 2, u4: 4, u8: 8, s1: 1, s2: 2, s4: 4, s8: 8, b: 1, f: 4, d: 8, + dist4: 4, dist8: 8, anchr: 0, handles: 0 + }; + + function isValidItemType(type) { + return ITEM_TYPE_SIZES[type] !== undefined; + } + + Line.prototype.nextItem = function() { + if (this.endReached()) + return null; + + var itemString = this.items[this.index++]; + var type = 'u1'; + var value = itemString; + + if (itemString.charAt(0) == '[') { + var i = itemString.indexOf(']'); + if (i != -1 && i + 1 < itemString.length) { + type = itemString.substring(1, i); + value = itemString.substring(i + 1); + } else { + throw new InputError('invalid item', this); + } + } + if (!isValidItemType(type)) + throw new InputError('invalid item type', this); + + return new Item(this, type, value); + } + + // The text for each whitespace delimited binary data "item" is represented + // by an Item. + + function Item(line, type, value) { + this.line = line; + this.type = type; + this.value = value; + this.size = ITEM_TYPE_SIZES[type]; + } + + Item.prototype.isFloat = function() { + return this.type == 'f' || this.type == 'd'; + } + + Item.prototype.isInteger = function() { + return ['u1', 'u2', 'u4', 'u8', + 's1', 's2', 's4', 's8'].indexOf(this.type) != -1; + } + + Item.prototype.isNumber = function() { + return this.isFloat() || this.isInteger(); + } + + Item.prototype.isByte = function() { + return this.type == 'b'; + } + + Item.prototype.isDistance = function() { + return this.type == 'dist4' || this.type == 'dist8'; + } + + Item.prototype.isAnchor = function() { + return this.type == 'anchr'; + } + + Item.prototype.isHandles = function() { + return this.type == 'handles'; + } + + // A TestMessage represents the complete binary message loaded from an input + // string or ".data" file. The parseTestMessage() function below constructs + // a TestMessage from a File. + + function TestMessage(byteLength) { + this.index = 0; + this.buffer = new buffer.Buffer(byteLength); + this.distances = {}; + this.handleCount = 0; + } + + function checkItemNumberValue(item, n, min, max) { + if (n < min || n > max) + throw new InputError('invalid item value', item.line); + } + + TestMessage.prototype.addNumber = function(item) { + var n = item.isInteger() ? parseInt(item.value) : parseFloat(item.value); + if (Number.isNaN(n)) + throw new InputError("can't parse item value", item.line); + + switch(item.type) { + case 'u1': + checkItemNumberValue(item, n, 0, 0xFF); + this.buffer.setUint8(this.index, n); + break; + case 'u2': + checkItemNumberValue(item, n, 0, 0xFFFF); + this.buffer.setUint16(this.index, n); + break; + case 'u4': + checkItemNumberValue(item, n, 0, 0xFFFFFFFF); + this.buffer.setUint32(this.index, n); + break; + case 'u8': + checkItemNumberValue(item, n, 0, Number.MAX_SAFE_INTEGER); + this.buffer.setUint64(this.index, n); + break; + case 's1': + checkItemNumberValue(item, n, -128, 127); + this.buffer.setInt8(this.index, n); + break; + case 's2': + checkItemNumberValue(item, n, -32768, 32767); + this.buffer.setInt16(this.index, n); + break; + case 's4': + checkItemNumberValue(item, n, -2147483648, 2147483647); + this.buffer.setInt32(this.index, n); + break; + case 's8': + checkItemNumberValue(item, n, + Number.MIN_SAFE_INTEGER, + Number.MAX_SAFE_INTEGER); + this.buffer.setInt64(this.index, n); + break; + case 'f': + this.buffer.setFloat32(this.index, n); + break; + case 'd': + this.buffer.setFloat64(this.index, n); + break; + + default: + throw new InputError('unrecognized item type', item.line); + } + } + + TestMessage.prototype.addByte = function(item) { + if (!/^[01]{8}$/.test(item.value)) + throw new InputError('invalid byte item value', item.line); + function b(i) { + return (item.value.charAt(7 - i) == '1') ? 1 << i : 0; + } + var n = b(0) | b(1) | b(2) | b(3) | b(4) | b(5) | b(6) | b(7); + this.buffer.setUint8(this.index, n); + } + + TestMessage.prototype.addDistance = function(item) { + if (this.distances[item.value]) + throw new InputError('duplicate distance item', item.line); + this.distances[item.value] = {index: this.index, item: item}; + } + + TestMessage.prototype.addAnchor = function(item) { + var dist = this.distances[item.value]; + if (!dist) + throw new InputError('unmatched anchor item', item.line); + delete this.distances[item.value]; + + var n = this.index - dist.index; + // TODO(hansmuller): validate n + + if (dist.item.type == 'dist4') + this.buffer.setUint32(dist.index, n); + else if (dist.item.type == 'dist8') + this.buffer.setUint64(dist.index, n); + else + throw new InputError('unrecognzed distance item type', dist.item.line); + } + + TestMessage.prototype.addHandles = function(item) { + this.handleCount = parseInt(item.value); + if (Number.isNaN(this.handleCount)) + throw new InputError("can't parse handleCount", item.line); + } + + TestMessage.prototype.addItem = function(item) { + if (item.isNumber()) + this.addNumber(item); + else if (item.isByte()) + this.addByte(item); + else if (item.isDistance()) + this.addDistance(item); + else if (item.isAnchor()) + this.addAnchor(item); + else if (item.isHandles()) + this.addHandles(item); + else + throw new InputError('unrecognized item type', item.line); + + this.index += item.size; + } + + TestMessage.prototype.unanchoredDistances = function() { + var names = null; + for (var name in this.distances) { + if (this.distances.hasOwnProperty(name)) + names = (names === null) ? name : names + ' ' + name; + } + return names; + } + + function parseTestMessage(text) { + var file = new File(text); + var items = []; + var messageLength = 0; + while(!file.endReached()) { + var line = file.nextLine(); + while (!line.endReached()) { + var item = line.nextItem(); + if (item.isHandles() && items.length > 0) + throw new InputError('handles item is not first'); + messageLength += item.size; + items.push(item); + } + } + + var msg = new TestMessage(messageLength); + for (var i = 0; i < items.length; i++) + msg.addItem(items[i]); + + if (messageLength != msg.index) + throw new InputError('failed to compute message length'); + var names = msg.unanchoredDistances(); + if (names) + throw new InputError('no anchors for ' + names, 0); + + return msg; + } + + var exports = {}; + exports.parseTestMessage = parseTestMessage; + exports.InputError = InputError; + return exports; +}); diff --git a/mojo/public/js/tests/validation_unittest.js b/mojo/public/js/tests/validation_unittest.js new file mode 100644 index 0000000000..2a07315436 --- /dev/null +++ b/mojo/public/js/tests/validation_unittest.js @@ -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. + +define([ + "console", + "file", + "gin/test/expect", + "mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom", + "mojo/public/js/bindings", + "mojo/public/js/buffer", + "mojo/public/js/codec", + "mojo/public/js/core", + "mojo/public/js/tests/validation_test_input_parser", + "mojo/public/js/validator", +], function(console, + file, + expect, + testInterface, + bindings, + buffer, + codec, + core, + parser, + validator) { + + var noError = validator.validationError.NONE; + + function checkTestMessageParser() { + function TestMessageParserFailure(message, input) { + this.message = message; + this.input = input; + } + + TestMessageParserFailure.prototype.toString = function() { + return 'Error: ' + this.message + ' for "' + this.input + '"'; + }; + + function checkData(data, expectedData, input) { + if (data.byteLength != expectedData.byteLength) { + var s = "message length (" + data.byteLength + ") doesn't match " + + "expected length: " + expectedData.byteLength; + throw new TestMessageParserFailure(s, input); + } + + for (var i = 0; i < data.byteLength; i++) { + if (data.getUint8(i) != expectedData.getUint8(i)) { + var s = 'message data mismatch at byte offset ' + i; + throw new TestMessageParserFailure(s, input); + } + } + } + + function testFloatItems() { + var input = '[f]+.3e9 [d]-10.03'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(12); + expectedData.setFloat32(0, +.3e9); + expectedData.setFloat64(4, -10.03); + checkData(msg.buffer, expectedData, input); + } + + function testUnsignedIntegerItems() { + var input = '[u1]0x10// hello world !! \n\r \t [u2]65535 \n' + + '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(17); + expectedData.setUint8(0, 0x10); + expectedData.setUint16(1, 65535); + expectedData.setUint32(3, 65536); + expectedData.setUint64(7, 0xFFFFFFFFFFFFF); + expectedData.setUint8(15, 0); + expectedData.setUint8(16, 0xff); + checkData(msg.buffer, expectedData, input); + } + + function testSignedIntegerItems() { + var input = '[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(15); + expectedData.setInt64(0, -0x800); + expectedData.setInt8(8, -128); + expectedData.setInt16(9, 0); + expectedData.setInt32(11, -40); + checkData(msg.buffer, expectedData, input); + } + + function testByteItems() { + var input = '[b]00001011 [b]10000000 // hello world\n [b]00000000'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(3); + expectedData.setUint8(0, 11); + expectedData.setUint8(1, 128); + expectedData.setUint8(2, 0); + checkData(msg.buffer, expectedData, input); + } + + function testAnchors() { + var input = '[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(14); + expectedData.setUint32(0, 14); + expectedData.setUint8(4, 0); + expectedData.setUint64(5, 9); + expectedData.setUint8(13, 0); + checkData(msg.buffer, expectedData, input); + } + + function testHandles() { + var input = '// This message has handles! \n[handles]50 [u8]2'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(8); + expectedData.setUint64(0, 2); + + if (msg.handleCount != 50) { + var s = 'wrong handle count (' + msg.handleCount + ')'; + throw new TestMessageParserFailure(s, input); + } + checkData(msg.buffer, expectedData, input); + } + + function testEmptyInput() { + var msg = parser.parseTestMessage(''); + if (msg.buffer.byteLength != 0) + throw new TestMessageParserFailure('expected empty message', ''); + } + + function testBlankInput() { + var input = ' \t // hello world \n\r \t// the answer is 42 '; + var msg = parser.parseTestMessage(input); + if (msg.buffer.byteLength != 0) + throw new TestMessageParserFailure('expected empty message', input); + } + + function testInvalidInput() { + function parserShouldFail(input) { + try { + parser.parseTestMessage(input); + } catch (e) { + if (e instanceof parser.InputError) + return; + throw new TestMessageParserFailure( + 'unexpected exception ' + e.toString(), input); + } + throw new TestMessageParserFailure("didn't detect invalid input", file); + } + + ['/ 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' + ].forEach(parserShouldFail); + } + + try { + testFloatItems(); + testUnsignedIntegerItems(); + testSignedIntegerItems(); + testByteItems(); + testInvalidInput(); + testEmptyInput(); + testBlankInput(); + testHandles(); + testAnchors(); + } catch (e) { + return e.toString(); + } + return null; + } + + function getMessageTestFiles(prefix) { + var sourceRoot = file.getSourceRootDirectory(); + expect(sourceRoot).not.toBeNull(); + + var testDir = sourceRoot + + "/mojo/public/interfaces/bindings/tests/data/validation/"; + var testFiles = file.getFilesInDirectory(testDir); + expect(testFiles).not.toBeNull(); + expect(testFiles.length).toBeGreaterThan(0); + + // The matching ".data" pathnames with the extension removed. + return testFiles.filter(function(s) { + return s.substr(-5) == ".data" && s.indexOf(prefix) == 0; + }).map(function(s) { + return testDir + s.slice(0, -5); + }); + } + + function readTestMessage(filename) { + var contents = file.readFileToString(filename + ".data"); + expect(contents).not.toBeNull(); + return parser.parseTestMessage(contents); + } + + function readTestExpected(filename) { + var contents = file.readFileToString(filename + ".expected"); + expect(contents).not.toBeNull(); + return contents.trim(); + } + + function checkValidationResult(testFile, err) { + var actualResult = (err === noError) ? "PASS" : err; + var expectedResult = readTestExpected(testFile); + if (actualResult != expectedResult) + console.log("[Test message validation failed: " + testFile + " ]"); + expect(actualResult).toEqual(expectedResult); + } + + function testMessageValidation(prefix, filters) { + var testFiles = getMessageTestFiles(prefix); + expect(testFiles.length).toBeGreaterThan(0); + + for (var i = 0; i < testFiles.length; i++) { + // TODO(hansmuller) Temporarily skipping array pointer overflow tests + // because JS numbers are limited to 53 bits. + // TODO(rudominer): Temporarily skipping 'no-such-method', + // 'invalid_request_flags', and 'invalid_response_flags' until additional + // logic in *RequestValidator and *ResponseValidator is ported from + // cpp to js. + // TODO(crbug/640298): Implement max recursion depth for JS. + // TODO(crbug/628104): Support struct map keys for JS. + if (testFiles[i].indexOf("overflow") != -1 || + testFiles[i].indexOf("conformance_mthd19") != -1 || + testFiles[i].indexOf("conformance_mthd20") != -1 || + testFiles[i].indexOf("no_such_method") != -1 || + testFiles[i].indexOf("invalid_request_flags") != -1 || + testFiles[i].indexOf("invalid_response_flags") != -1) { + console.log("[Skipping " + testFiles[i] + "]"); + continue; + } + + var testMessage = readTestMessage(testFiles[i]); + var handles = new Array(testMessage.handleCount); + var message = new codec.Message(testMessage.buffer, handles); + var messageValidator = new validator.Validator(message); + + var err = messageValidator.validateMessageHeader(); + for (var j = 0; err === noError && j < filters.length; ++j) + err = filters[j](messageValidator); + + checkValidationResult(testFiles[i], err); + } + } + + function testConformanceMessageValidation() { + testMessageValidation("conformance_", [ + testInterface.ConformanceTestInterface.validateRequest]); + } + + function testBoundsCheckMessageValidation() { + testMessageValidation("boundscheck_", [ + testInterface.BoundsCheckTestInterface.validateRequest]); + } + + function testResponseConformanceMessageValidation() { + testMessageValidation("resp_conformance_", [ + testInterface.ConformanceTestInterface.validateResponse]); + } + + function testResponseBoundsCheckMessageValidation() { + testMessageValidation("resp_boundscheck_", [ + testInterface.BoundsCheckTestInterface.validateResponse]); + } + + function testIntegratedMessageValidation(testFilesPattern, endpoint) { + var testFiles = getMessageTestFiles(testFilesPattern); + expect(testFiles.length).toBeGreaterThan(0); + + var testMessagePipe = core.createMessagePipe(); + expect(testMessagePipe.result).toBe(core.RESULT_OK); + + endpoint.bind(testMessagePipe.handle1); + var observer = validator.ValidationErrorObserverForTesting.getInstance(); + + for (var i = 0; i < testFiles.length; i++) { + var testMessage = readTestMessage(testFiles[i]); + var handles = new Array(testMessage.handleCount); + + var writeMessageValue = core.writeMessage( + testMessagePipe.handle0, + new Uint8Array(testMessage.buffer.arrayBuffer), + new Array(testMessage.handleCount), + core.WRITE_MESSAGE_FLAG_NONE); + expect(writeMessageValue).toBe(core.RESULT_OK); + + endpoint.waitForNextMessageForTesting(); + checkValidationResult(testFiles[i], observer.lastError); + observer.reset(); + } + + expect(core.close(testMessagePipe.handle0)).toBe(core.RESULT_OK); + } + + function testIntegratedMessageHeaderValidation() { + testIntegratedMessageValidation( + "integration_msghdr", + new bindings.Binding(testInterface.IntegrationTestInterface, {})); + testIntegratedMessageValidation( + "integration_msghdr", + new testInterface.IntegrationTestInterfacePtr().ptr); + } + + function testIntegratedRequestMessageValidation() { + testIntegratedMessageValidation( + "integration_intf_rqst", + new bindings.Binding(testInterface.IntegrationTestInterface, {})); + } + + function testIntegratedResponseMessageValidation() { + testIntegratedMessageValidation( + "integration_intf_resp", + new testInterface.IntegrationTestInterfacePtr().ptr); + } + + expect(checkTestMessageParser()).toBeNull(); + testConformanceMessageValidation(); + testBoundsCheckMessageValidation(); + testResponseConformanceMessageValidation(); + testResponseBoundsCheckMessageValidation(); + testIntegratedMessageHeaderValidation(); + testIntegratedResponseMessageValidation(); + testIntegratedRequestMessageValidation(); + validator.clearTestingMode(); + + this.result = "PASS"; +}); diff --git a/mojo/public/js/threading.js b/mojo/public/js/threading.js new file mode 100644 index 0000000000..49ab5c9142 --- /dev/null +++ b/mojo/public/js/threading.js @@ -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. + +// Module "mojo/public/js/threading" +// +// Note: This file is for documentation purposes only. The code here is not +// actually executed. The real module is implemented natively in Mojo. +// +// This module provides a way for a Service implemented in JS +// to exit by quitting the current message loop. This module is not +// intended to be used by Mojo JS application started by the JS +// content handler. + +while (1); + +/** + * Quits the current message loop, esssentially: + * base::MessageLoop::current()->QuitNow(); +*/ +function quit() { [native code] } diff --git a/mojo/public/js/unicode.js b/mojo/public/js/unicode.js new file mode 100644 index 0000000000..be2ba0e63c --- /dev/null +++ b/mojo/public/js/unicode.js @@ -0,0 +1,51 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Defines functions for translating between JavaScript strings and UTF8 strings + * stored in ArrayBuffers. There is much room for optimization in this code if + * it proves necessary. + */ +define("mojo/public/js/unicode", function() { + /** + * Decodes the UTF8 string from the given buffer. + * @param {ArrayBufferView} buffer The buffer containing UTF8 string data. + * @return {string} The corresponding JavaScript string. + */ + function decodeUtf8String(buffer) { + return decodeURIComponent(escape(String.fromCharCode.apply(null, buffer))); + } + + /** + * Encodes the given JavaScript string into UTF8. + * @param {string} str The string to encode. + * @param {ArrayBufferView} outputBuffer The buffer to contain the result. + * Should be pre-allocated to hold enough space. Use |utf8Length| to determine + * how much space is required. + * @return {number} The number of bytes written to |outputBuffer|. + */ + function encodeUtf8String(str, outputBuffer) { + var utf8String = unescape(encodeURIComponent(str)); + if (outputBuffer.length < utf8String.length) + throw new Error("Buffer too small for encodeUtf8String"); + for (var i = 0; i < outputBuffer.length && i < utf8String.length; i++) + outputBuffer[i] = utf8String.charCodeAt(i); + return i; + } + + /** + * Returns the number of bytes that a UTF8 encoding of the JavaScript string + * |str| would occupy. + */ + function utf8Length(str) { + var utf8String = unescape(encodeURIComponent(str)); + return utf8String.length; + } + + var exports = {}; + exports.decodeUtf8String = decodeUtf8String; + exports.encodeUtf8String = encodeUtf8String; + exports.utf8Length = utf8Length; + return exports; +}); diff --git a/mojo/public/js/validator.js b/mojo/public/js/validator.js new file mode 100644 index 0000000000..283546d4f1 --- /dev/null +++ b/mojo/public/js/validator.js @@ -0,0 +1,560 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +define("mojo/public/js/validator", [ + "mojo/public/js/codec", +], function(codec) { + + var validationError = { + NONE: 'VALIDATION_ERROR_NONE', + MISALIGNED_OBJECT: 'VALIDATION_ERROR_MISALIGNED_OBJECT', + ILLEGAL_MEMORY_RANGE: 'VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE', + UNEXPECTED_STRUCT_HEADER: 'VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER', + UNEXPECTED_ARRAY_HEADER: 'VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER', + ILLEGAL_HANDLE: 'VALIDATION_ERROR_ILLEGAL_HANDLE', + UNEXPECTED_INVALID_HANDLE: 'VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE', + ILLEGAL_POINTER: 'VALIDATION_ERROR_ILLEGAL_POINTER', + UNEXPECTED_NULL_POINTER: 'VALIDATION_ERROR_UNEXPECTED_NULL_POINTER', + MESSAGE_HEADER_INVALID_FLAGS: + 'VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS', + MESSAGE_HEADER_MISSING_REQUEST_ID: + 'VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID', + DIFFERENT_SIZED_ARRAYS_IN_MAP: + 'VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP', + INVALID_UNION_SIZE: 'VALIDATION_ERROR_INVALID_UNION_SIZE', + UNEXPECTED_NULL_UNION: 'VALIDATION_ERROR_UNEXPECTED_NULL_UNION', + UNKNOWN_ENUM_VALUE: 'VALIDATION_ERROR_UNKNOWN_ENUM_VALUE', + }; + + var NULL_MOJO_POINTER = "NULL_MOJO_POINTER"; + var gValidationErrorObserver = null; + + function reportValidationError(error) { + if (gValidationErrorObserver) { + gValidationErrorObserver.setLastError(error); + } + } + + var ValidationErrorObserverForTesting = (function() { + function Observer() { + this.lastError = validationError.NONE; + this.callback = null; + } + + Observer.prototype.setLastError = function(error) { + this.lastError = error; + if (this.callback) { + this.callback(error); + } + }; + + Observer.prototype.reset = function(error) { + this.lastError = validationError.NONE; + this.callback = null; + }; + + return { + getInstance: function() { + if (!gValidationErrorObserver) { + gValidationErrorObserver = new Observer(); + } + return gValidationErrorObserver; + } + }; + })(); + + function isTestingMode() { + return Boolean(gValidationErrorObserver); + } + + function clearTestingMode() { + gValidationErrorObserver = null; + } + + function isEnumClass(cls) { + return cls instanceof codec.Enum; + } + + function isStringClass(cls) { + return cls === codec.String || cls === codec.NullableString; + } + + function isHandleClass(cls) { + return cls === codec.Handle || cls === codec.NullableHandle; + } + + function isInterfaceClass(cls) { + return cls instanceof codec.Interface; + } + + function isInterfaceRequestClass(cls) { + return cls === codec.InterfaceRequest || + cls === codec.NullableInterfaceRequest; + } + + function isNullable(type) { + return type === codec.NullableString || type === codec.NullableHandle || + type === codec.NullableInterface || + type === codec.NullableInterfaceRequest || + type instanceof codec.NullableArrayOf || + type instanceof codec.NullablePointerTo; + } + + function Validator(message) { + this.message = message; + this.offset = 0; + this.handleIndex = 0; + } + + Object.defineProperty(Validator.prototype, "offsetLimit", { + get: function() { return this.message.buffer.byteLength; } + }); + + Object.defineProperty(Validator.prototype, "handleIndexLimit", { + get: function() { return this.message.handles.length; } + }); + + // True if we can safely allocate a block of bytes from start to + // to start + numBytes. + Validator.prototype.isValidRange = function(start, numBytes) { + // Only positive JavaScript integers that are less than 2^53 + // (Number.MAX_SAFE_INTEGER) can be represented exactly. + if (start < this.offset || numBytes <= 0 || + !Number.isSafeInteger(start) || + !Number.isSafeInteger(numBytes)) + return false; + + var newOffset = start + numBytes; + if (!Number.isSafeInteger(newOffset) || newOffset > this.offsetLimit) + return false; + + return true; + }; + + Validator.prototype.claimRange = function(start, numBytes) { + if (this.isValidRange(start, numBytes)) { + this.offset = start + numBytes; + return true; + } + return false; + }; + + Validator.prototype.claimHandle = function(index) { + if (index === codec.kEncodedInvalidHandleValue) + return true; + + if (index < this.handleIndex || index >= this.handleIndexLimit) + return false; + + // This is safe because handle indices are uint32. + this.handleIndex = index + 1; + return true; + }; + + Validator.prototype.validateEnum = function(offset, enumClass) { + // Note: Assumes that enums are always 32 bits! But this matches + // mojom::generate::pack::PackedField::GetSizeForKind, so it should be okay. + var value = this.message.buffer.getInt32(offset); + return enumClass.validate(value); + } + + Validator.prototype.validateHandle = function(offset, nullable) { + var index = this.message.buffer.getUint32(offset); + + if (index === codec.kEncodedInvalidHandleValue) + return nullable ? + validationError.NONE : validationError.UNEXPECTED_INVALID_HANDLE; + + if (!this.claimHandle(index)) + return validationError.ILLEGAL_HANDLE; + + return validationError.NONE; + }; + + Validator.prototype.validateInterface = function(offset, nullable) { + return this.validateHandle(offset, nullable); + }; + + Validator.prototype.validateInterfaceRequest = function(offset, nullable) { + return this.validateHandle(offset, nullable); + }; + + Validator.prototype.validateStructHeader = function(offset, minNumBytes) { + if (!codec.isAligned(offset)) + return validationError.MISALIGNED_OBJECT; + + if (!this.isValidRange(offset, codec.kStructHeaderSize)) + return validationError.ILLEGAL_MEMORY_RANGE; + + var numBytes = this.message.buffer.getUint32(offset); + + if (numBytes < minNumBytes) + return validationError.UNEXPECTED_STRUCT_HEADER; + + if (!this.claimRange(offset, numBytes)) + return validationError.ILLEGAL_MEMORY_RANGE; + + return validationError.NONE; + }; + + Validator.prototype.validateStructVersion = function(offset, versionSizes) { + var numBytes = this.message.buffer.getUint32(offset); + var version = this.message.buffer.getUint32(offset + 4); + + if (version <= versionSizes[versionSizes.length - 1].version) { + // Scan in reverse order to optimize for more recent versionSizes. + for (var i = versionSizes.length - 1; i >= 0; --i) { + if (version >= versionSizes[i].version) { + if (numBytes == versionSizes[i].numBytes) + break; + return validationError.UNEXPECTED_STRUCT_HEADER; + } + } + } else if (numBytes < versionSizes[versionSizes.length-1].numBytes) { + return validationError.UNEXPECTED_STRUCT_HEADER; + } + + return validationError.NONE; + }; + + Validator.prototype.isFieldInStructVersion = function(offset, fieldVersion) { + var structVersion = this.message.buffer.getUint32(offset + 4); + return fieldVersion <= structVersion; + }; + + // TODO(wangjimmy): Add support for v2 messages. + Validator.prototype.validateMessageHeader = function() { + + var err = this.validateStructHeader(0, codec.kMessageHeaderSize); + if (err != validationError.NONE) + return err; + + var numBytes = this.message.getHeaderNumBytes(); + var version = this.message.getHeaderVersion(); + + var validVersionAndNumBytes = + (version == 0 && numBytes == codec.kMessageHeaderSize) || + (version == 1 && + numBytes == codec.kMessageWithRequestIDHeaderSize) || + (version > 1 && + numBytes >= codec.kMessageWithRequestIDHeaderSize); + if (!validVersionAndNumBytes) + return validationError.UNEXPECTED_STRUCT_HEADER; + + var expectsResponse = this.message.expectsResponse(); + var isResponse = this.message.isResponse(); + + if (version == 0 && (expectsResponse || isResponse)) + return validationError.MESSAGE_HEADER_MISSING_REQUEST_ID; + + if (isResponse && expectsResponse) + return validationError.MESSAGE_HEADER_INVALID_FLAGS; + + return validationError.NONE; + }; + + Validator.prototype.validateMessageIsRequestWithoutResponse = function() { + if (this.message.isResponse() || this.message.expectsResponse()) { + return validationError.MESSAGE_HEADER_INVALID_FLAGS; + } + return validationError.NONE; + }; + + Validator.prototype.validateMessageIsRequestExpectingResponse = function() { + if (this.message.isResponse() || !this.message.expectsResponse()) { + return validationError.MESSAGE_HEADER_INVALID_FLAGS; + } + return validationError.NONE; + }; + + Validator.prototype.validateMessageIsResponse = function() { + if (this.message.expectsResponse() || !this.message.isResponse()) { + return validationError.MESSAGE_HEADER_INVALID_FLAGS; + } + return validationError.NONE; + }; + + // Returns the message.buffer relative offset this pointer "points to", + // NULL_MOJO_POINTER if the pointer represents a null, or JS null if the + // pointer's value is not valid. + Validator.prototype.decodePointer = function(offset) { + var pointerValue = this.message.buffer.getUint64(offset); + if (pointerValue === 0) + return NULL_MOJO_POINTER; + var bufferOffset = offset + pointerValue; + return Number.isSafeInteger(bufferOffset) ? bufferOffset : null; + }; + + Validator.prototype.decodeUnionSize = function(offset) { + return this.message.buffer.getUint32(offset); + }; + + Validator.prototype.decodeUnionTag = function(offset) { + return this.message.buffer.getUint32(offset + 4); + }; + + Validator.prototype.validateArrayPointer = function( + offset, elementSize, elementType, nullable, expectedDimensionSizes, + currentDimension) { + var arrayOffset = this.decodePointer(offset); + if (arrayOffset === null) + return validationError.ILLEGAL_POINTER; + + if (arrayOffset === NULL_MOJO_POINTER) + return nullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; + + return this.validateArray(arrayOffset, elementSize, elementType, + expectedDimensionSizes, currentDimension); + }; + + Validator.prototype.validateStructPointer = function( + offset, structClass, nullable) { + var structOffset = this.decodePointer(offset); + if (structOffset === null) + return validationError.ILLEGAL_POINTER; + + if (structOffset === NULL_MOJO_POINTER) + return nullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; + + return structClass.validate(this, structOffset); + }; + + Validator.prototype.validateUnion = function( + offset, unionClass, nullable) { + var size = this.message.buffer.getUint32(offset); + if (size == 0) { + return nullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_UNION; + } + + return unionClass.validate(this, offset); + }; + + Validator.prototype.validateNestedUnion = function( + offset, unionClass, nullable) { + var unionOffset = this.decodePointer(offset); + if (unionOffset === null) + return validationError.ILLEGAL_POINTER; + + if (unionOffset === NULL_MOJO_POINTER) + return nullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_UNION; + + return this.validateUnion(unionOffset, unionClass, nullable); + }; + + // This method assumes that the array at arrayPointerOffset has + // been validated. + + Validator.prototype.arrayLength = function(arrayPointerOffset) { + var arrayOffset = this.decodePointer(arrayPointerOffset); + return this.message.buffer.getUint32(arrayOffset + 4); + }; + + Validator.prototype.validateMapPointer = function( + offset, mapIsNullable, keyClass, valueClass, valueIsNullable) { + // Validate the implicit map struct: + // struct {array<keyClass> keys; array<valueClass> values}; + var structOffset = this.decodePointer(offset); + if (structOffset === null) + return validationError.ILLEGAL_POINTER; + + if (structOffset === NULL_MOJO_POINTER) + return mapIsNullable ? + validationError.NONE : validationError.UNEXPECTED_NULL_POINTER; + + var mapEncodedSize = codec.kStructHeaderSize + codec.kMapStructPayloadSize; + var err = this.validateStructHeader(structOffset, mapEncodedSize); + if (err !== validationError.NONE) + return err; + + // Validate the keys array. + var keysArrayPointerOffset = structOffset + codec.kStructHeaderSize; + err = this.validateArrayPointer( + keysArrayPointerOffset, keyClass.encodedSize, keyClass, false, [0], 0); + if (err !== validationError.NONE) + return err; + + // Validate the values array. + var valuesArrayPointerOffset = keysArrayPointerOffset + 8; + var valuesArrayDimensions = [0]; // Validate the actual length below. + if (valueClass instanceof codec.ArrayOf) + valuesArrayDimensions = + valuesArrayDimensions.concat(valueClass.dimensions()); + var err = this.validateArrayPointer(valuesArrayPointerOffset, + valueClass.encodedSize, + valueClass, + valueIsNullable, + valuesArrayDimensions, + 0); + if (err !== validationError.NONE) + return err; + + // Validate the lengths of the keys and values arrays. + var keysArrayLength = this.arrayLength(keysArrayPointerOffset); + var valuesArrayLength = this.arrayLength(valuesArrayPointerOffset); + if (keysArrayLength != valuesArrayLength) + return validationError.DIFFERENT_SIZED_ARRAYS_IN_MAP; + + return validationError.NONE; + }; + + Validator.prototype.validateStringPointer = function(offset, nullable) { + return this.validateArrayPointer( + offset, codec.Uint8.encodedSize, codec.Uint8, nullable, [0], 0); + }; + + // Similar to Array_Data<T>::Validate() + // mojo/public/cpp/bindings/lib/array_internal.h + + Validator.prototype.validateArray = + function (offset, elementSize, elementType, expectedDimensionSizes, + currentDimension) { + if (!codec.isAligned(offset)) + return validationError.MISALIGNED_OBJECT; + + if (!this.isValidRange(offset, codec.kArrayHeaderSize)) + return validationError.ILLEGAL_MEMORY_RANGE; + + var numBytes = this.message.buffer.getUint32(offset); + var numElements = this.message.buffer.getUint32(offset + 4); + + // Note: this computation is "safe" because elementSize <= 8 and + // numElements is a uint32. + var elementsTotalSize = (elementType === codec.PackedBool) ? + Math.ceil(numElements / 8) : (elementSize * numElements); + + if (numBytes < codec.kArrayHeaderSize + elementsTotalSize) + return validationError.UNEXPECTED_ARRAY_HEADER; + + if (expectedDimensionSizes[currentDimension] != 0 && + numElements != expectedDimensionSizes[currentDimension]) { + return validationError.UNEXPECTED_ARRAY_HEADER; + } + + if (!this.claimRange(offset, numBytes)) + return validationError.ILLEGAL_MEMORY_RANGE; + + // Validate the array's elements if they are pointers or handles. + + var elementsOffset = offset + codec.kArrayHeaderSize; + var nullable = isNullable(elementType); + + if (isHandleClass(elementType)) + return this.validateHandleElements(elementsOffset, numElements, nullable); + if (isInterfaceClass(elementType)) + return this.validateInterfaceElements( + elementsOffset, numElements, nullable); + if (isInterfaceRequestClass(elementType)) + return this.validateInterfaceRequestElements( + elementsOffset, numElements, nullable); + if (isStringClass(elementType)) + return this.validateArrayElements( + elementsOffset, numElements, codec.Uint8, nullable, [0], 0); + if (elementType instanceof codec.PointerTo) + return this.validateStructElements( + elementsOffset, numElements, elementType.cls, nullable); + if (elementType instanceof codec.ArrayOf) + return this.validateArrayElements( + elementsOffset, numElements, elementType.cls, nullable, + expectedDimensionSizes, currentDimension + 1); + if (isEnumClass(elementType)) + return this.validateEnumElements(elementsOffset, numElements, + elementType.cls); + + return validationError.NONE; + }; + + // Note: the |offset + i * elementSize| computation in the validateFooElements + // methods below is "safe" because elementSize <= 8, offset and + // numElements are uint32, and 0 <= i < numElements. + + Validator.prototype.validateHandleElements = + function(offset, numElements, nullable) { + var elementSize = codec.Handle.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateHandle(elementOffset, nullable); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + Validator.prototype.validateInterfaceElements = + function(offset, numElements, nullable) { + var elementSize = codec.Interface.prototype.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateInterface(elementOffset, nullable); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + Validator.prototype.validateInterfaceRequestElements = + function(offset, numElements, nullable) { + var elementSize = codec.InterfaceRequest.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateInterfaceRequest(elementOffset, nullable); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + // The elementClass parameter is the element type of the element arrays. + Validator.prototype.validateArrayElements = + function(offset, numElements, elementClass, nullable, + expectedDimensionSizes, currentDimension) { + var elementSize = codec.PointerTo.prototype.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateArrayPointer( + elementOffset, elementClass.encodedSize, elementClass, nullable, + expectedDimensionSizes, currentDimension); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + Validator.prototype.validateStructElements = + function(offset, numElements, structClass, nullable) { + var elementSize = codec.PointerTo.prototype.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = + this.validateStructPointer(elementOffset, structClass, nullable); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + Validator.prototype.validateEnumElements = + function(offset, numElements, enumClass) { + var elementSize = codec.Enum.prototype.encodedSize; + for (var i = 0; i < numElements; i++) { + var elementOffset = offset + i * elementSize; + var err = this.validateEnum(elementOffset, enumClass); + if (err != validationError.NONE) + return err; + } + return validationError.NONE; + }; + + var exports = {}; + exports.validationError = validationError; + exports.Validator = Validator; + exports.ValidationErrorObserverForTesting = ValidationErrorObserverForTesting; + exports.reportValidationError = reportValidationError; + exports.isTestingMode = isTestingMode; + exports.clearTestingMode = clearTestingMode; + return exports; +}); diff --git a/mojo/public/tests/test_support_private.cc b/mojo/public/tests/test_support_private.cc new file mode 100644 index 0000000000..00819846c0 --- /dev/null +++ b/mojo/public/tests/test_support_private.cc @@ -0,0 +1,76 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/public/tests/test_support_private.h" + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +static mojo::test::TestSupport* g_test_support = NULL; + +extern "C" { + +void MojoTestSupportLogPerfResult(const char* test_name, + const char* sub_test_name, + double value, + const char* units) { + if (g_test_support) { + g_test_support->LogPerfResult(test_name, sub_test_name, value, units); + } else { + if (sub_test_name) { + printf("[no test runner]\t%s/%s\t%g\t%s\n", test_name, sub_test_name, + value, units); + } else { + printf("[no test runner]\t%s\t%g\t%s\n", test_name, value, units); + } + } +} + +FILE* MojoTestSupportOpenSourceRootRelativeFile(const char* relative_path) { + if (g_test_support) + return g_test_support->OpenSourceRootRelativeFile(relative_path); + printf("[no test runner]\n"); + return NULL; +} + +char** MojoTestSupportEnumerateSourceRootRelativeDirectory( + const char* relative_path) { + if (g_test_support) + return g_test_support->EnumerateSourceRootRelativeDirectory(relative_path); + + printf("[no test runner]\n"); + + // Return empty list: + char** rv = static_cast<char**>(calloc(1, sizeof(char*))); + rv[0] = NULL; + return rv; +} + +} // extern "C" + +namespace mojo { +namespace test { + +TestSupport::~TestSupport() { +} + +// static +void TestSupport::Init(TestSupport* test_support) { + assert(!g_test_support); + g_test_support = test_support; +} + +// static +TestSupport* TestSupport::Get() { + return g_test_support; +} + +// static +void TestSupport::Reset() { + g_test_support = NULL; +} + +} // namespace test +} // namespace mojo diff --git a/mojo/public/tests/test_support_private.h b/mojo/public/tests/test_support_private.h new file mode 100644 index 0000000000..27426055fa --- /dev/null +++ b/mojo/public/tests/test_support_private.h @@ -0,0 +1,37 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_ +#define MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_ + +#include <stdio.h> + +#include "mojo/public/c/test_support/test_support.h" + +namespace mojo { +namespace test { + +// Implementors of the test support APIs can use this interface to install their +// implementation into the mojo_test_support dynamic library. +class TestSupport { + public: + virtual ~TestSupport(); + + static void Init(TestSupport* test_support); + static TestSupport* Get(); + static void Reset(); + + virtual void LogPerfResult(const char* test_name, + const char* sub_test_name, + double value, + const char* units) = 0; + virtual FILE* OpenSourceRootRelativeFile(const char* relative_path) = 0; + virtual char** EnumerateSourceRootRelativeDirectory( + const char* relative_path) = 0; +}; + +} // namespace test +} // namespace mojo + +#endif // MOJO_PUBLIC_TESTS_TEST_SUPPORT_PRIVATE_H_ diff --git a/mojo/public/tools/bindings/BUILD.gn b/mojo/public/tools/bindings/BUILD.gn new file mode 100644 index 0000000000..153d110332 --- /dev/null +++ b/mojo/public/tools/bindings/BUILD.gn @@ -0,0 +1,77 @@ +# 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +action("precompile_templates") { + sources = mojom_generator_sources + sources += [ + "$mojom_generator_root/generators/cpp_templates/enum_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/enum_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_proxy_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_request_validator_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_response_validator_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/interface_stub_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared-internal.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared.cc.tmpl", + "$mojom_generator_root/generators/cpp_templates/module-shared.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/module.cc.tmpl", + "$mojom_generator_root/generators/cpp_templates/module.h.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_data_view_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_data_view_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_traits_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/struct_traits_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_data_view_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_data_view_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_serialization_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_traits_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/union_traits_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/validation_macros.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_class_template_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_declaration.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_definition.tmpl", + "$mojom_generator_root/generators/cpp_templates/wrapper_union_class_template_definition.tmpl", + "$mojom_generator_root/generators/java_templates/constant_definition.tmpl", + "$mojom_generator_root/generators/java_templates/constants.java.tmpl", + "$mojom_generator_root/generators/java_templates/data_types_definition.tmpl", + "$mojom_generator_root/generators/java_templates/enum.java.tmpl", + "$mojom_generator_root/generators/java_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/java_templates/header.java.tmpl", + "$mojom_generator_root/generators/java_templates/interface.java.tmpl", + "$mojom_generator_root/generators/java_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/java_templates/interface_internal.java.tmpl", + "$mojom_generator_root/generators/java_templates/struct.java.tmpl", + "$mojom_generator_root/generators/java_templates/union.java.tmpl", + "$mojom_generator_root/generators/js_templates/enum_definition.tmpl", + "$mojom_generator_root/generators/js_templates/interface_definition.tmpl", + "$mojom_generator_root/generators/js_templates/module.amd.tmpl", + "$mojom_generator_root/generators/js_templates/module_definition.tmpl", + "$mojom_generator_root/generators/js_templates/struct_definition.tmpl", + "$mojom_generator_root/generators/js_templates/union_definition.tmpl", + "$mojom_generator_root/generators/js_templates/validation_macros.tmpl", + ] + script = mojom_generator_script + outputs = [ + "$target_gen_dir/cpp_templates.zip", + "$target_gen_dir/java_templates.zip", + "$target_gen_dir/js_templates.zip", + ] + args = [ + "--use_bundled_pylibs", + "precompile", + "-o", + rebase_path(target_gen_dir), + ] +} diff --git a/mojo/public/tools/bindings/README.md b/mojo/public/tools/bindings/README.md new file mode 100644 index 0000000000..737c7e61df --- /dev/null +++ b/mojo/public/tools/bindings/README.md @@ -0,0 +1,749 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojom IDL and Bindings Generator +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +Mojom is the IDL for Mojo bindings interfaces. Given a `.mojom` file, the +[bindings generator](https://cs.chromium.org/chromium/src/mojo/public/tools/bindings) +outputs bindings for all supported languages: **C++**, **JavaScript**, and +**Java**. + +For a trivial example consider the following hypothetical Mojom file we write to +`//services/widget/public/interfaces/frobinator.mojom`: + +``` +module widget.mojom; + +interface Frobinator { + Frobinate(); +}; +``` + +This defines a single [interface](#Interfaces) named `Frobinator` in a +[module](#Modules) named `widget.mojom` (and thus fully qualified in Mojom as +`widget.mojom.Frobinator`.) Note that many interfaces and/or other types of +definitions may be included in a single Mojom file. + +If we add a corresponding GN target to +`//services/widget/public/interfaces/BUILD.gn`: + +``` +import("mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "frobinator.mojom", + ] +} +``` + +and then build this target: + +``` +ninja -C out/r services/widget/public/interfaces +``` + +we'll find several generated sources in our output directory: + +``` +out/r/gen/services/widget/public/interfaces/frobinator.mojom.cc +out/r/gen/services/widget/public/interfaces/frobinator.mojom.h +out/r/gen/services/widget/public/interfaces/frobinator.mojom.js +out/r/gen/services/widget/public/interfaces/frobinator.mojom.srcjar +... +``` + +Each of these generated source modules includes a set of definitions +representing the Mojom contents within the target language. For more details +regarding the generated outputs please see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +## Mojom Syntax + +Mojom IDL allows developers to define **structs**, **unions**, **interfaces**, +**constants**, and **enums**, all within the context of a **module**. These +definitions are used to generate code in the supported target languages at build +time. + +Mojom files may **import** other Mojom files in order to reference their +definitions. + +### Primitive Types +Mojom supports a few basic data types which may be composed into structs or used +for message parameters. + +| Type | Description +|-------------------------------|-------------------------------------------------------| +| `bool` | Boolean type (`true` or `false`.) +| `int8`, `uint8` | Signed or unsigned 8-bit integer. +| `int16`, `uint16` | Signed or unsigned 16-bit integer. +| `int32`, `uint32` | Signed or unsigned 32-bit integer. +| `int64`, `uint64` | Signed or unsigned 64-bit integer. +| `float`, `double` | 32- or 64-bit floating point number. +| `string` | UTF-8 encoded string. +| `array<T>` | Array of any Mojom type *T*; for example, `array<uint8>` or `array<array<string>>`. +| `array<T, N>` | Fixed-length array of any Mojom type *T*. The parameter *N* must be an integral constant. +| `map<S, T>` | Associated array maping values of type *S* to values of type *T*. *S* may be a `string`, `enum`, or numeric type. +| `handle` | Generic Mojo handle. May be any type of handle, including a wrapped native platform handle. +| `handle<message_pipe>` | Generic message pipe handle. +| `handle<shared_buffer>` | Shared buffer handle. +| `handle<data_pipe_producer>` | Data pipe producer handle. +| `handle<data_pipe_consumer>` | Data pipe consumer handle. +| *`InterfaceType`* | Any user-defined Mojom interface type. This is sugar for a strongly-typed message pipe handle which should eventually be used to make outgoing calls on the interface. +| *`InterfaceType&`* | An interface request for any user-defined Mojom interface type. This is sugar for a more strongly-typed message pipe handle which is expected to receive request messages and should therefore eventually be bound to an implementation of the interface. +| *`associated InterfaceType`* | An associated interface handle. See [Associated Interfaces](#Associated-Interfaces) +| *`associated InterfaceType&`* | An associated interface request. See [Associated Interfaces](#Associated-Interfaces) +| *T*? | An optional (nullable) value. Primitive numeric types (integers, floats, booleans, and enums) are not nullable. All other types are nullable. + +### Modules + +Every Mojom file may optionally specify a single **module** to which it belongs. + +This is used strictly for aggregaging all defined symbols therein within a +common Mojom namespace. The specific impact this has on generated binidngs code +varies for each target language. For example, if the following Mojom is used to +generate bindings: + +``` +module business.stuff; + +interface MoneyGenerator { + GenerateMoney(); +}; +``` + +Generated C++ bindings will define a class interface `MoneyGenerator` in the +`business::stuff` namespace, while Java bindings will define an interface +`MoneyGenerator` in the `org.chromium.business.stuff` package. JavaScript +bindings at this time are unaffected by module declarations. + +**NOTE:** By convention in the Chromium codebase, **all** Mojom files should +declare a module name with at least (and preferrably exactly) one top-level name +as well as an inner `mojom` module suffix. *e.g.*, `chrome.mojom`, +`business.mojom`, *etc.* + +This convention makes it easy to tell which symbols are generated by Mojom when +reading non-Mojom code, and it also avoids namespace collisions in the fairly +common scenario where you have a real C++ or Java `Foo` along with a +corresponding Mojom `Foo` for its serialized representation. + +### Imports + +If your Mojom references definitions from other Mojom files, you must **import** +those files. Import syntax is as follows: + +``` +import "services/widget/public/interfaces/frobinator.mojom"; +``` + +Import paths are always relative to the top-level directory. + +Note that circular imports are **not** supported. + +### Structs + +Structs are defined using the **struct** keyword, and they provide a way to +group related fields together: + +``` cpp +struct StringPair { + string first; + string second; +}; +``` + +Struct fields may be comprised of any of the types listed above in the +[Primitive Types](#Primitive-Types) section. + +Default values may be specified as long as they are constant: + +``` cpp +struct Request { + int32 id = -1; + string details; +}; +``` + +What follows is a fairly +comprehensive example using the supported field types: + +``` cpp +struct StringPair { + string first; + string second; +}; + +enum AnEnum { + YES, + NO +}; + +interface SampleInterface { + DoStuff(); +}; + +struct AllTheThings { + // Note that these types can never be marked nullable! + bool boolean_value; + int8 signed_8bit_value = 42; + uint8 unsigned_8bit_value; + int16 signed_16bit_value; + uint16 unsigned_16bit_value; + int32 signed_32bit_value; + uint32 unsigned_32bit_value; + int64 signed_64bit_value; + uint64 unsigned_64bit_value; + float float_value_32bit; + double float_value_64bit; + AnEnum enum_value = AnEnum.YES; + + // Strings may be nullable. + string? maybe_a_string_maybe_not; + + // Structs may contain other structs. These may also be nullable. + StringPair some_strings; + StringPair? maybe_some_more_strings; + + // In fact structs can also be nested, though in practice you must always make + // such fields nullable -- otherwise messages would need to be infinitely long + // in order to pass validation! + AllTheThings? more_things; + + // Arrays may be templated over any Mojom type, and are always nullable: + array<int32> numbers; + array<int32>? maybe_more_numbers; + + // Arrays of arrays of arrays... are fine. + array<array<array<AnEnum>>> this_works_but_really_plz_stop; + + // The element type may be nullable if it's a type which is allowed to be + // nullable. + array<AllTheThings?> more_maybe_things; + + // Fixed-size arrays get some extra validation on the receiving end to ensure + // that the correct number of elements is always received. + array<uint64, 2> uuid; + + // Maps follow many of the same rules as arrays. Key types may be any + // non-handle, non-collection type, and value types may be any supported + // struct field type. Maps may also be nullable. + map<string, int32> one_map; + map<AnEnum, string>? maybe_another_map; + map<StringPair, AllTheThings?>? maybe_a_pretty_weird_but_valid_map; + map<StringPair, map<int32, array<map<string, string>?>?>?> ridiculous; + + // And finally, all handle types are valid as struct fields and may be + // nullable. Note that interfaces and interface requests (the "Foo" and + // "Foo&" type syntax respectively) are just strongly-typed message pipe + // handles. + handle generic_handle; + handle<data_pipe_consumer> reader; + handle<data_pipe_producer>? maybe_writer; + handle<shared_buffer> dumping_ground; + handle<message_pipe> raw_message_pipe; + SampleInterface? maybe_a_sample_interface_client_pipe; + SampleInterface& non_nullable_sample_interface_request; + SampleInterface&? nullable_sample_interface_request; + associated SampleInterface associated_interface_client; + associated SampleInterface& associated_interface_request; + assocaited SampleInterface&? maybe_another_associated_request; +}; +``` + +For details on how all of these different types translate to usable generated +code, see +[documentation for individual target languages](#Generated-Code-For-Target-Languages). + +### Enumeration Types + +Enumeration types may be defined using the **enum** keyword either directly +within a module or within the namespace of some struct or interface: + +``` +module business.mojom; + +enum Department { + SALES = 0, + DEV, +}; + +struct Employee { + enum Type { + FULL_TIME, + PART_TIME, + }; + + Type type; + // ... +}; +``` + +That that similar to C-style enums, individual values may be explicitly assigned +within an enum definition. By default values are based at zero and incremenet by +1 sequentially. + +The effect of nested definitions on generated bindings varies depending on the +target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages) + +### Constants + +Constants may be defined using the **const** keyword either directly within a +module or within the namespace of some struct or interface: + +``` +module business.mojom; + +const string kServiceName = "business"; + +struct Employee { + const uint64 kInvalidId = 0; + + enum Type { + FULL_TIME, + PART_TIME, + }; + + uint64 id = kInvalidId; + Type type; +}; +``` + +The effect of nested definitions on generated bindings varies depending on the +target language. See [documentation for individual target languages](#Generated-Code-For-Target-Languages) + +### Interfaces + +An **interface** is a logical bundle of parameterized request messages. Each +request message may optionally define a parameterized response message. Here's +syntax to define an interface `Foo` with various kinds of requests: + +``` +interface Foo { + // A request which takes no arguments and expects no response. + MyMessage(); + + // A request which has some arguments and expects no response. + MyOtherMessage(string name, array<uint8> bytes); + + // A request which expects a single-argument response. + MyMessageWithResponse(string command) => (bool success); + + // A request which expects a response with multiple arguments. + MyMessageWithMoarResponse(string a, string b) => (int8 c, int8 d); +}; +``` + +Anything which is a valid struct field type (see [Structs](#Structs)) is also a +valid request or response argument type. The type notation is the same for both. + +### Attributes + +Mojom definitions may have their meaning altered by **attributes**, specified +with a syntax similar to Java or C# attributes. There are a handle of +interesting attributes supported today. + +**`[Sync]`** +: The `Sync` attribute may be specified for any interface method which expects + a response. This makes it so that callers of the method can wait + synchronously for a response. See + [Synchronous Calls](/mojo/public/cpp/bindings#Synchronous-Calls) in the C++ + bindings documentation. Note that sync calls are not currently supported in + other target languages. + +**`[Extensible]`** +: The `Extensible` attribute may be specified for any enum definition. This + essentially disables builtin range validation when receiving values of the + enum type in a message, allowing older bindings to tolerate unrecognized + values from newer versions of the enum. + +**`[Native]`** +: The `Native` attribute may be specified for an empty struct declaration to + provide a nominal bridge between Mojo IPC and legacy `IPC::ParamTraits` or + `IPC_STRUCT_TRAITS*` macros. + See [Using Legacy IPC Traits](/ipc#Using-Legacy-IPC-Traits) for more + details. Note support for this attribute is strictly limited to C++ bindings + generation. + +**`[MinVersion=N]`** +: The `MinVersion` attribute is used to specify the version at which a given + field, enum value, interface method, or method parameter was introduced. + See [Versioning](#Versioning) for more details. + +## Generated Code For Target Languages + +When the bindings generator successfully processes an input Mojom file, it emits +corresponding code for each supported target language. For more details on how +Mojom concepts translate to a given target langauge, please refer to the +bindings API documentation for that language: + +* [C++ Bindings](/mojo/public/cpp/bindings) +* [JavaScript Bindings](/mojo/public/js) +* [Java Bindings](/mojo/public/java/bindings) + +## Message Validation + +Regardless of target language, all interface messages are validated during +deserialization before they are dispatched to a receiving implementation of the +interface. This helps to ensure consitent validation across interfaces without +leaving the burden to developers and security reviewers every time a new message +is added. + +If a message fails validation, it is never dispatched. Instead a **connection +error** is raised on the binding object (see +[C++ Connection Errors](/mojo/public/cpp/bindings#Connection-Errors), +[Java Connection Errors](/mojo/public/java/bindings#Connection-Errors), or +[JavaScript Connection Errors](/mojo/public/js#Connection-Errors) for details.) + +Some baseline level of validation is done automatically for primitive Mojom +types. + +### Non-Nullable Objects + +Mojom fields or parameter values (*e.g.*, structs, interfaces, arrays, *etc.*) +may be marked nullable in Mojom definitions (see +[Primitive Types](#Primitive-Types).) If a field or parameter is **not** marked +nullable but a message is received with a null value in its place, that message +will fail validation. + +### Enums + +Enums declared in Mojom are automatically validated against the range of legal +values. For example if a Mojom declares the enum: + +``` cpp +enum AdvancedBoolean { + TRUE = 0, + FALSE = 1, + FILE_NOT_FOUND = 2, +}; +``` + +and a message is received with the integral value 3 (or anything other than 0, +1, or 2) in place of some `AdvancedBoolean` field or parameter, the message will +fail validation. + +*** note +NOTE: It's possible to avoid this type of validation error by explicitly marking +an enum as [Extensible](#Attributes) if you anticipate your enum being exchanged +between two different versions of the binding interface. See +[Versioning](#Versioning). +*** + +### Other failures + +There are a host of internal validation errors that may occur when a malformed +message is received, but developers should not be concerned with these +specifically; in general they can only result from internal bindings bugs, +compromised processes, or some remote endpoint making a dubious effort to +manually encode their own bindings messages. + +### Custom Validation + +It's also possible for developers to define custom validation logic for specific +Mojom struct types by exploiting the +[type mapping](/mojo/public/cpp/bindings#Type-Mapping) system for C++ bindings. +Messages rejected by custom validation logic trigger the same validation failure +behavior as the built-in type validation routines. + +## Associated Interfaces + +As mentioned in the [Primitive Types](#Primitive-Types) section above, interface +and interface request fields and parameters may be marked as `associated`. This +essentially means that they are piggy-backed on some other interface's message +pipe. + +Because individual interface message pipes operate independently there can be no +relative ordering guarantees among them. Associated interfaces are useful when +one interface needs to guarantee strict FIFO ordering with respect to one or +more other interfaces, as they allow interfaces to share a single pipe. + +Currenly associated interfaces are only supported in generated C++ bindings. +See the documentation for +[C++ Associated Interfaces](/mojo/public/cpp/bindings#Associated-Interfaces). + +## Versioning + +### Overview + +*** note +**NOTE:** You don't need to worry about versioning if you don't care about +backwards compatibility. Specifically, all parts of Chrome are updated +atomically today and there is not yet any possibility of any two Chrome +processes communicating with two different versions of any given Mojom +interface. +*** + +Services extend their interfaces to support new features over time, and clients +want to use those new features when they are available. If services and clients +are not updated at the same time, it's important for them to be able to +communicate with each other using different snapshots (versions) of their +interfaces. + +This document shows how to extend Mojom interfaces in a backwards-compatible +way. Changing interfaces in a non-backwards-compatible way is not discussed, +because in that case communication between different interface versions is +impossible anyway. + +### Versioned Structs + +You can use the `MinVersion` [attribute](#Attributes) to indicate from which +version a struct field is introduced. Assume you have the following struct: + +``` cpp +struct Employee { + uint64 employee_id; + string name; +}; +``` + +and you would like to add a birthday field. You can do: + +``` cpp +struct Employee { + uint64 employee_id; + string name; + [MinVersion=1] Date? birthday; +}; +``` + +By default, fields belong to version 0. New fields must be appended to the +struct definition (*i.e*., existing fields must not change **ordinal value**) +with the `MinVersion` attribute set to a number greater than any previous +existing versions. + +**Ordinal value** refers to the relative positional layout of a struct's fields +(and an interface's methods) when encoded in a message. Implicitly, ordinal +numbers are assigned to fields according to lexical position. In the example +above, `employee_id` has an ordinal value of 0 and `name` has an ordinal value +of 1. + +Ordinal values can be specified explicitly using `**@**` notation, subject to +the following hard constraints: + +* For any given struct or interface, if any field or method explicitly specifies + an ordinal value, all fields or methods must explicitly specify an ordinal + value. +* For an *N*-field struct or *N*-method interface, the set of explicitly + assigned ordinal values must be limited to the range *[0, N-1]*. + +You may reorder fields, but you must ensure that the ordinal values of existing +fields remain unchanged. For example, the following struct remains +backwards-compatible: + +``` cpp +struct Employee { + uint64 employee_id@0; + [MinVersion=1] Date? birthday@2; + string name@1; +}; +``` + +*** note +**NOTE:** Newly added fields of Mojo object or handle types MUST be nullable. +See [Primitive Types](#Primitive-Types). +*** + +### Versioned Interfaces + +There are two dimensions on which an interface can be extended + +**Appending New Parameters To Existing Methods** +: Parameter lists are treated as structs internally, so all the rules of + versioned structs apply to method parameter lists. The only difference is + that the version number is scoped to the whole interface rather than to any + individual parameter list. + + Please note that adding a response to a message which did not previously + expect a response is a not a backwards-compatible change. + +**Appending New Methods** +: Similarly, you can reorder methods with explicit ordinal values as long as + the ordinal values of existing methods are unchanged. + +For example: + +``` cpp +// Old version: +interface HumanResourceDatabase { + AddEmployee(Employee employee) => (bool success); + QueryEmployee(uint64 id) => (Employee? employee); +}; + +// New version: +interface HumanResourceDatabase { + AddEmployee(Employee employee) => (bool success); + + QueryEmployee(uint64 id, [MinVersion=1] bool retrieve_finger_print) + => (Employee? employee, + [MinVersion=1] array<uint8>? finger_print); + + [MinVersion=1] + AttachFingerPrint(uint64 id, array<uint8> finger_print) + => (bool success); +}; +``` + +Similar to [versioned structs](#Versioned-Structs), when you pass the parameter +list of a request or response method to a destination using an older version of +an interface, unrecognized fields are silently discarded. However, if the method +call itself is not recognized, it is considered a validation error and the +receiver will close its end of the interface pipe. For example, if a client on +version 1 of the above interface sends an `AttachFingerPrint` request to an +implementation of version 0, the client will be disconnected. + +Bindings target languages that support versioning expose means to query or +assert the remote version from a client handle (*e.g.*, an +`InterfacePtr<T>` in C++ bindings.) + +See +[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations) +and [Java Versioning Considerations](/mojo/public/java/bindings#Versioning-Considerations) + +### Versioned Enums + +**By default, enums are non-extensible**, which means that generated message +validation code does not expect to see new values in the future. When an unknown +value is seen for a non-extensible enum field or parameter, a validation error +is raised. + +If you want an enum to be extensible in the future, you can apply the +`[Extensible]` [attribute](#Attributes): + +``` cpp +[Extensible] +enum Department { + SALES, + DEV, +}; +``` + +And later you can extend this enum without breaking backwards compatibility: + +``` cpp +[Extensible] +enum Department { + SALES, + DEV, + [MinVersion=1] RESEARCH, +}; +``` + +*** note +**NOTE:** For versioned enum definitions, the use of a `[MinVersion]` attribute +is strictly for documentation purposes. It has no impact on the generated code. +*** + +With extensible enums, bound interface implementations may receive unknown enum +values and will need to deal with them gracefully. See +[C++ Versioning Considerations](/mojo/public/cpp/bindings#Versioning-Considerations) +for details. + +## Grammar Reference + +Below is the (BNF-ish) context-free grammar of the Mojom language: + +``` +MojomFile = StatementList +StatementList = Statement StatementList | Statement +Statement = ModuleStatement | ImportStatement | Definition + +ModuleStatement = AttributeSection "module" Identifier ";" +ImportStatement = "import" StringLiteral ";" +Definition = Struct Union Interface Enum Const + +AttributeSection = "[" AttributeList "]" +AttributeList = <empty> | NonEmptyAttributeList +NonEmptyAttributeList = Attribute + | Attribute "," NonEmptyAttributeList +Attribute = Name + | Name "=" Name + | Name "=" Literal + +Struct = AttributeSection "struct" Name "{" StructBody "}" ";" + | AttributeSection "struct" Name ";" +StructBody = <empty> + | StructBody Const + | StructBody Enum + | StructBody StructField +StructField = AttributeSection TypeSpec Name Orginal Default ";" + +Union = AttributeSection "union" Name "{" UnionBody "}" ";" +UnionBody = <empty> | UnionBody UnionField +UnionField = AttributeSection TypeSpec Name Ordinal ";" + +Interface = AttributeSection "interface" Name "{" InterfaceBody "}" ";" +InterfaceBody = <empty> + | InterfaceBody Const + | InterfaceBody Enum + | InterfaceBody Method +Method = AttributeSection Name Ordinal "(" ParamterList ")" Response ";" +ParameterList = <empty> | NonEmptyParameterList +NonEmptyParameterList = Parameter + | Parameter "," NonEmptyParameterList +Parameter = AttributeSection TypeSpec Name Ordinal +Response = <empty> | "=>" "(" ParameterList ")" + +TypeSpec = TypeName "?" | TypeName +TypeName = BasicTypeName + | Array + | FixedArray + | Map + | InterfaceRequest +BasicTypeName = Identifier | "associated" Identifier | HandleType | NumericType +NumericType = "bool" | "int8" | "uint8" | "int16" | "uint16" | "int32" + | "uint32" | "int64" | "uint64" | "float" | "double" +HandleType = "handle" | "handle" "<" SpecificHandleType ">" +SpecificHandleType = "message_pipe" + | "shared_buffer" + | "data_pipe_consumer" + | "data_pipe_producer" +Array = "array" "<" TypeSpec ">" +FixedArray = "array" "<" TypeSpec "," IntConstDec ">" +Map = "map" "<" Identifier "," TypeSpec ">" +InterfaceRequest = Identifier "&" | "associated" Identifier "&" + +Ordinal = <empty> | OrdinalValue + +Default = <empty> | "=" Constant + +Enum = AttributeSection "enum" Name "{" NonEmptyEnumValueList "}" ";" + | AttributeSection "enum" Name "{" NonEmptyEnumValueList "," "}" ";" +NonEmptyEnumValueList = EnumValue | NonEmptyEnumValueList "," EnumValue +EnumValue = AttributeSection Name + | AttributeSection Name "=" Integer + | AttributeSection Name "=" Identifier + +Const = "const" TypeSpec Name "=" Constant ";" + +Constant = Literal | Identifier ";" + +Identifier = Name | Name "." Identifier + +Literal = Integer | Float | "true" | "false" | "default" | StringLiteral + +Integer = IntConst | "+" IntConst | "-" IntConst +IntConst = IntConstDec | IntConstHex + +Float = FloatConst | "+" FloatConst | "-" FloatConst + +; The rules below are for tokens matched strictly according to the given regexes + +Identifier = /[a-zA-Z_][0-9a-zA-Z_]*/ +IntConstDec = /0|(1-9[0-9]*)/ +IntConstHex = /0[xX][0-9a-fA-F]+/ +OrdinalValue = /@(0|(1-9[0-9]*))/ +FloatConst = ... # Imagine it's close enough to C-style float syntax. +StringLiteral = ... # Imagine it's close enough to C-style string literals, including escapes. +``` + +## Additional Documentation + +[Mojom Message Format](https://docs.google.com/document/d/13pv9cFh5YKuBggDBQ1-AL8VReF-IYpFOFpRfvWFrwio/edit) +: Describes the wire format used by Mojo bindings interfaces over message + pipes. + +[Input Format of Mojom Message Validation Tests](https://docs.google.com/document/d/1-y-2IYctyX2NPaLxJjpJfzVNWCC2SR2MJAD9MpIytHQ/edit) +: Describes a text format used to facilitate bindings message validation + tests. diff --git a/mojo/public/tools/bindings/blink_bindings_configuration.gni b/mojo/public/tools/bindings/blink_bindings_configuration.gni new file mode 100644 index 0000000000..bb0fc432a3 --- /dev/null +++ b/mojo/public/tools/bindings/blink_bindings_configuration.gni @@ -0,0 +1,33 @@ +# 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. + +variant = "blink" + +for_blink = true + +_typemap_imports = [ + "//mojo/public/cpp/bindings/tests/blink_typemaps.gni", + "//third_party/WebKit/Source/platform/mojo/blink_typemaps.gni", + "//third_party/WebKit/public/blink_typemaps.gni", + "//third_party/WebKit/public/public_typemaps.gni", +] +_typemaps = [] + +foreach(typemap_import, _typemap_imports) { + # Avoid reassignment error by assigning to empty scope first. + _imported = { + } + _imported = read_file(typemap_import, "scope") + _typemaps += _imported.typemaps +} + +typemaps = [] +foreach(typemap, _typemaps) { + typemaps += [ { + filename = typemap + config = read_file(typemap, "scope") + } ] +} + +blacklist = [] diff --git a/mojo/public/tools/bindings/chromium_bindings_configuration.gni b/mojo/public/tools/bindings/chromium_bindings_configuration.gni new file mode 100644 index 0000000000..ca36723fb0 --- /dev/null +++ b/mojo/public/tools/bindings/chromium_bindings_configuration.gni @@ -0,0 +1,83 @@ +# 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. + +_typemap_imports = [ + "//ash/public/interfaces/typemaps.gni", + "//cc/ipc/typemaps.gni", + "//chrome/browser/media/router/mojo/typemaps.gni", + "//chrome/common/extensions/typemaps.gni", + "//chrome/common/importer/typemaps.gni", + "//chrome/typemaps.gni", + "//components/arc/common/typemaps.gni", + "//components/metrics/public/cpp/typemaps.gni", + "//components/typemaps.gni", + "//content/common/bluetooth/typemaps.gni", + "//content/common/indexed_db/typemaps.gni", + "//content/common/presentation/typemaps.gni", + "//content/common/typemaps.gni", + "//content/public/common/typemaps.gni", + "//device/bluetooth/public/interfaces/typemaps.gni", + "//device/gamepad/public/interfaces/typemaps.gni", + "//device/generic_sensor/public/interfaces/typemaps.gni", + "//device/usb/public/interfaces/typemaps.gni", + "//extensions/common/typemaps.gni", + "//gpu/ipc/common/typemaps.gni", + "//media/capture/mojo/typemaps.gni", + "//media/mojo/interfaces/typemaps.gni", + "//mojo/common/typemaps.gni", + "//mojo/public/cpp/bindings/tests/chromium_typemaps.gni", + "//net/interfaces/typemaps.gni", + "//services/preferences/public/cpp/typemaps.gni", + "//services/resource_coordinator/public/cpp/typemaps.gni", + "//services/service_manager/public/cpp/typemaps.gni", + "//services/ui/gpu/interfaces/typemaps.gni", + "//services/ui/public/interfaces/cursor/typemaps.gni", + "//services/ui/public/interfaces/ime/typemaps.gni", + "//services/video_capture/public/interfaces/typemaps.gni", + "//skia/public/interfaces/typemaps.gni", + "//third_party/WebKit/public/public_typemaps.gni", + "//ui/base/mojo/typemaps.gni", + "//ui/display/mojo/typemaps.gni", + "//ui/events/devices/mojo/typemaps.gni", + "//ui/events/mojo/typemaps.gni", + "//ui/gfx/typemaps.gni", + "//ui/latency/mojo/typemaps.gni", + "//ui/message_center/mojo/typemaps.gni", + "//url/mojo/typemaps.gni", +] + +_typemap_imports_mac = [ "//content/common/typemaps_mac.gni" ] + +_typemaps = [] +foreach(typemap_import, _typemap_imports) { + # Avoid reassignment error by assigning to empty scope first. + _imported = { + } + _imported = read_file(typemap_import, "scope") + _typemaps += _imported.typemaps +} + +typemaps = [] +foreach(typemap, _typemaps) { + typemaps += [ { + filename = typemap + config = read_file(typemap, "scope") + } ] +} + +_typemaps_mac = [] +foreach(typemap_import, _typemap_imports_mac) { + _imported = { + } + _imported = read_file(typemap_import, "scope") + _typemaps_mac += _imported.typemaps +} + +typemaps_mac = [] +foreach(typemap, _typemaps_mac) { + typemaps_mac += [ { + filename = typemap + config = read_file(typemap, "scope") + } ] +} diff --git a/mojo/public/tools/bindings/format_typemap_generator_args.py b/mojo/public/tools/bindings/format_typemap_generator_args.py new file mode 100755 index 0000000000..5057d6cdac --- /dev/null +++ b/mojo/public/tools/bindings/format_typemap_generator_args.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# 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. + +import sys + +# This utility converts mojom dependencies into their corresponding typemap +# paths and formats them to be consumed by generate_type_mappings.py. + + +def FormatTypemap(typemap_filename): + # A simple typemap is valid Python with a minor alteration. + with open(typemap_filename) as f: + typemap_content = f.read().replace('=\n', '=') + typemap = {} + exec typemap_content in typemap + + for header in typemap.get('public_headers', []): + yield 'public_headers=%s' % header + for header in typemap.get('traits_headers', []): + yield 'traits_headers=%s' % header + for header in typemap.get('type_mappings', []): + yield 'type_mappings=%s' % header + + +def main(): + typemaps = sys.argv[1:] + print ' '.join('--start-typemap %s' % ' '.join(FormatTypemap(typemap)) + for typemap in typemaps) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/mojo/public/tools/bindings/generate_type_mappings.py b/mojo/public/tools/bindings/generate_type_mappings.py new file mode 100755 index 0000000000..824f8045ab --- /dev/null +++ b/mojo/public/tools/bindings/generate_type_mappings.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# 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. +"""Generates a JSON typemap from its command-line arguments and dependencies. + +Each typemap should be specified in an command-line argument of the form +key=value, with an argument of "--start-typemap" preceding each typemap. + +For example, +generate_type_mappings.py --output=foo.typemap --start-typemap \\ + public_headers=foo.h traits_headers=foo_traits.h \\ + type_mappings=mojom.Foo=FooImpl + +generates a foo.typemap containing +{ + "c++": { + "mojom.Foo": { + "typename": "FooImpl", + "traits_headers": [ + "foo_traits.h" + ], + "public_headers": [ + "foo.h" + ] + } + } +} + +Then, +generate_type_mappings.py --dependency foo.typemap --output=bar.typemap \\ + --start-typemap public_headers=bar.h traits_headers=bar_traits.h \\ + type_mappings=mojom.Bar=BarImpl + +generates a bar.typemap containing +{ + "c++": { + "mojom.Bar": { + "typename": "BarImpl", + "traits_headers": [ + "bar_traits.h" + ], + "public_headers": [ + "bar.h" + ] + }, + "mojom.Foo": { + "typename": "FooImpl", + "traits_headers": [ + "foo_traits.h" + ], + "public_headers": [ + "foo.h" + ] + } + } +} +""" + +import argparse +import json +import os +import re + + +def ReadTypemap(path): + with open(path) as f: + return json.load(f)['c++'] + + +def ParseTypemapArgs(args): + typemaps = [s for s in '\n'.join(args).split('--start-typemap\n') if s] + result = {} + for typemap in typemaps: + result.update(ParseTypemap(typemap)) + return result + + +def ParseTypemap(typemap): + values = {'type_mappings': [], 'public_headers': [], 'traits_headers': []} + for line in typemap.split('\n'): + if not line: + continue + key, _, value = line.partition('=') + values[key].append(value.lstrip('/')) + result = {} + mapping_pattern = \ + re.compile(r"""^([^=]+) # mojom type + = + ([^[]+) # native type + (?:\[([^]]+)\])?$ # optional attribute in square brackets + """, re.X) + for typename in values['type_mappings']: + match_result = mapping_pattern.match(typename) + assert match_result, ( + "Cannot parse entry in the \"type_mappings\" section: %s" % typename) + + mojom_type = match_result.group(1) + native_type = match_result.group(2) + attributes = [] + if match_result.group(3): + attributes = match_result.group(3).split(',') + + assert mojom_type not in result, ( + "Cannot map multiple native types (%s, %s) to the same mojom type: %s" % + (result[mojom_type]['typename'], native_type, mojom_type)) + + result[mojom_type] = { + 'typename': native_type, + 'non_copyable_non_movable': 'non_copyable_non_movable' in attributes, + 'move_only': 'move_only' in attributes, + 'copyable_pass_by_value': 'copyable_pass_by_value' in attributes, + 'nullable_is_same_type': 'nullable_is_same_type' in attributes, + 'hashable': 'hashable' in attributes, + 'public_headers': values['public_headers'], + 'traits_headers': values['traits_headers'], + } + return result + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument( + '--dependency', + type=str, + action='append', + default=[], + help=('A path to another JSON typemap to merge into the output. ' + 'This may be repeated to merge multiple typemaps.')) + parser.add_argument('--output', + type=str, + required=True, + help='The path to which to write the generated JSON.') + params, typemap_params = parser.parse_known_args() + typemaps = ParseTypemapArgs(typemap_params) + missing = [path for path in params.dependency if not os.path.exists(path)] + if missing: + raise IOError('Missing dependencies: %s' % ', '.join(missing)) + for path in params.dependency: + typemaps.update(ReadTypemap(path)) + with open(params.output, 'w') as f: + json.dump({'c++': typemaps}, f, indent=2) + + +if __name__ == '__main__': + main() diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl new file mode 100644 index 0000000000..f0d503e5e0 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl @@ -0,0 +1,131 @@ +{#--- + Macro for enum definition, and the declaration of associated functions. +---#} + +{%- macro enum_decl(enum) %} +{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %} +enum class {{enum_name}} : int32_t { +{%- for field in enum.fields %} +{%- if field.value %} + {{field.name}} = {{field.value|expression_to_text}}, +{%- else %} + {{field.name}}, +{%- endif %} +{%- endfor %} +}; + +inline std::ostream& operator<<(std::ostream& os, {{enum_name}} value) { +{%- if enum.fields %} + switch(value) { +{%- for _, values in enum.fields|groupby('numeric_value') %} + case {{enum_name}}::{{values[0].name}}: + return os << "{{enum_name}}:: +{%- if values|length > 1 -%} + {{'{'}} +{%- endif -%} + {{values|map(attribute='name')|join(', ')}} +{%- if values|length > 1 -%} + {{'}'}} +{%- endif -%} + "; +{%- endfor %} + default: + return os << "Unknown {{enum_name}} value: " << static_cast<int32_t>(value); + } +{%- else %} + return os << "Unknown {{enum_name}} value: " << static_cast<int32_t>(value); +{%- endif %} +} + +{#- Returns true if the given enum value exists in this version of enum. #} +inline bool IsKnownEnumValue({{enum_name}} value) { + return {{enum|get_name_for_kind(internal=True, + flatten_nested_kind=True)}}::IsKnownValue( + static_cast<int32_t>(value)); +} +{%- endmacro %} + +{%- macro enum_data_decl(enum) %} +{%- set enum_name = enum|get_name_for_kind(flatten_nested_kind=True) %} +struct {{enum_name}}_Data { + public: + static bool constexpr kIsExtensible = {% if enum.extensible %}true{% else %}false{% endif %}; + + static bool IsKnownValue(int32_t value) { +{%- if enum.fields %} + switch (value) { +{%- for enum_field in enum.fields|groupby('numeric_value') %} + case {{enum_field[0]}}: +{%- endfor %} + return true; + } +{%- endif %} + return false; + } + + static bool Validate(int32_t value, + mojo::internal::ValidationContext* validation_context) { + if (kIsExtensible || IsKnownValue(value)) + return true; + + ReportValidationError(validation_context, + mojo::internal::VALIDATION_ERROR_UNKNOWN_ENUM_VALUE); + return false; + } +}; +{%- endmacro %} + +{%- macro enum_hash(enum) %} +{%- set enum_name = enum|get_qualified_name_for_kind( + flatten_nested_kind=True) %} +template <> +struct hash<{{enum_name}}> + : public mojo::internal::EnumHashImpl<{{enum_name}}> {}; +{%- endmacro %} + +{%- macro enum_hash_blink(enum) %} +{%- set enum_name = enum|get_qualified_name_for_kind( + flatten_nested_kind=True, include_variant=False) %} +{%- set hash_fn_name = enum|wtf_hash_fn_name_for_enum %} +{# We need two unused enum values: #} +{%- set empty_value = -1000000 %} +{%- set deleted_value = -1000001 %} +{%- set empty_value_unused = "false" if empty_value in enum|all_enum_values else "true" %} +{%- set deleted_value_unused = "false" if empty_value in enum|all_enum_values else "true" %} +namespace WTF { +struct {{hash_fn_name}} { + static unsigned hash(const {{enum_name}}& value) { + typedef base::underlying_type<{{enum_name}}>::type utype; + return DefaultHash<utype>::Hash().hash(static_cast<utype>(value)); + } + static bool equal(const {{enum_name}}& left, const {{enum_name}}& right) { + return left == right; + } + static const bool safeToCompareToEmptyOrDeleted = true; +}; + +template <> +struct DefaultHash<{{enum_name}}> { + using Hash = {{hash_fn_name}}; +}; + +template <> +struct HashTraits<{{enum_name}}> + : public GenericHashTraits<{{enum_name}}> { + static_assert({{empty_value_unused}}, + "{{empty_value}} is a reserved enum value"); + static_assert({{deleted_value_unused}}, + "{{deleted_value}} is a reserved enum value"); + static const bool hasIsEmptyValueFunction = true; + static bool isEmptyValue(const {{enum_name}}& value) { + return value == static_cast<{{enum_name}}>({{empty_value}}); + } + static void constructDeletedValue({{enum_name}}& slot, bool) { + slot = static_cast<{{enum_name}}>({{deleted_value}}); + } + static bool isDeletedValue(const {{enum_name}}& value) { + return value == static_cast<{{enum_name}}>({{deleted_value}}); + } +}; +} // namespace WTF +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl new file mode 100644 index 0000000000..d7d0e5d873 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl @@ -0,0 +1,29 @@ +{%- set mojom_type = enum|get_qualified_name_for_kind( + flatten_nested_kind=True) %} + +template <> +struct EnumTraits<{{mojom_type}}, {{mojom_type}}> { + static {{mojom_type}} ToMojom({{mojom_type}} input) { return input; } + static bool FromMojom({{mojom_type}} input, {{mojom_type}}* output) { + *output = input; + return true; + } +}; + +namespace internal { + +template <typename MaybeConstUserType> +struct Serializer<{{mojom_type}}, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = EnumTraits<{{mojom_type}}, UserType>; + + static void Serialize(UserType input, int32_t* output) { + *output = static_cast<int32_t>(Traits::ToMojom(input)); + } + + static bool Deserialize(int32_t input, UserType* output) { + return Traits::FromMojom(static_cast<{{mojom_type}}>(input), output); + } +}; + +} // namespace internal diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl new file mode 100644 index 0000000000..7f6497475a --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl @@ -0,0 +1,65 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +class {{interface.name}}Proxy; + +template <typename ImplRefTraits> +class {{interface.name}}Stub; + +class {{interface.name}}RequestValidator; +{%- if interface|has_callbacks %} +class {{interface.name}}ResponseValidator; +{%- endif %} + +class {{export_attribute}} {{interface.name}} + : public {{interface.name}}InterfaceBase { + public: + static const char Name_[]; + static constexpr uint32_t Version_ = {{interface.version}}; + static constexpr bool PassesAssociatedKinds_ = {% if interface|passes_associated_kinds %}true{% else %}false{% endif %}; + static constexpr bool HasSyncMethods_ = {% if interface|has_sync_methods %}true{% else %}false{% endif %}; + + using Proxy_ = {{interface.name}}Proxy; + + template <typename ImplRefTraits> + using Stub_ = {{interface.name}}Stub<ImplRefTraits>; + + using RequestValidator_ = {{interface.name}}RequestValidator; +{%- if interface|has_callbacks %} + using ResponseValidator_ = {{interface.name}}ResponseValidator; +{%- else %} + using ResponseValidator_ = mojo::PassThroughFilter; +{%- endif %} + +{#--- Metadata #} + enum MethodMinVersions : uint32_t { +{%- for method in interface.methods %} + k{{method.name}}MinVersion = {{method.min_version|default(0, true)}}, +{%- endfor %} + }; + +{#--- Enums #} +{%- for enum in interface.enums %} + using {{enum.name}} = {{enum|get_name_for_kind(flatten_nested_kind=True)}}; +{%- endfor %} + +{#--- Constants #} +{%- for constant in interface.constants %} + static {{constant|format_constant_declaration(nested=True)}}; +{%- endfor %} + +{#--- Methods #} + virtual ~{{interface.name}}() {} + +{%- for method in interface.methods %} +{% if method.response_parameters != None %} +{%- if method.sync %} + // Sync method. This signature is used by the client side; the service side + // should implement the signature with callback below. + virtual bool {{method.name}}({{interface_macros.declare_sync_method_params("", method)}}); +{%- endif %} + + using {{method.name}}Callback = {{interface_macros.declare_callback(method, + for_blink, use_once_callback)}}; +{%- endif %} + virtual void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) = 0; +{%- endfor %} +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl new file mode 100644 index 0000000000..aba18380af --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl @@ -0,0 +1,448 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +{%- import "struct_macros.tmpl" as struct_macros %} + +{%- set class_name = interface.name %} +{%- set proxy_name = interface.name ~ "Proxy" %} +{%- set namespace_as_string = "%s"|format(namespace|replace(".","::")) %} + +{%- macro alloc_params(struct, params, message, description) %} + mojo::internal::SerializationContext serialization_context; + serialization_context.handles.Swap(({{message}})->mutable_handles()); + serialization_context.associated_endpoint_handles.swap( + *({{message}})->mutable_associated_endpoint_handles()); + bool success = true; +{%- for param in struct.packed.packed_fields_in_ordinal_order %} + {{param.field.kind|cpp_wrapper_type}} p_{{param.field.name}}{}; +{%- endfor %} + {{struct.name}}DataView input_data_view({{params}}, &serialization_context); + {{struct_macros.deserialize(struct, "input_data_view", "p_%s", "success")}} + if (!success) { + ReportValidationErrorForMessage( + {{message}}, + mojo::internal::VALIDATION_ERROR_DESERIALIZATION_FAILED, + "{{description}} deserializer"); + return false; + } +{%- endmacro %} + +{%- macro pass_params(parameters) %} +{%- for param in parameters %} +std::move(p_{{param.name}}) +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro build_message(struct, input_pattern, struct_display_name, + serialization_context) -%} + {{struct_macros.serialize(struct, struct_display_name, input_pattern, + "params", "builder.buffer()", + serialization_context)}} + ({{serialization_context}})->handles.Swap( + builder.message()->mutable_handles()); + ({{serialization_context}})->associated_endpoint_handles.swap( + *builder.message()->mutable_associated_endpoint_handles()); +{%- endmacro %} + +{#--- Begin #} +const char {{class_name}}::Name_[] = "{{namespace_as_string}}::{{class_name}}"; + +{#--- Constants #} +{%- for constant in interface.constants %} +{%- if constant.kind|is_string_kind %} +const char {{interface.name}}::{{constant.name}}[] = {{constant|constant_value}}; +{%- endif %} +{%- endfor %} + + +{%- for method in interface.methods %} +{%- if method.sync %} +bool {{class_name}}::{{method.name}}({{interface_macros.declare_sync_method_params("", method)}}) { + NOTREACHED(); + return false; +} +{%- endif %} +{%- endfor %} + +{#--- ForwardToCallback definition #} +{%- for method in interface.methods -%} +{%- if method.response_parameters != None %} +{%- if method.sync %} +class {{class_name}}_{{method.name}}_HandleSyncResponse + : public mojo::MessageReceiver { + public: + {{class_name}}_{{method.name}}_HandleSyncResponse( + bool* result +{%- for param in method.response_parameters -%} + , {{param.kind|cpp_wrapper_type}}* out_{{param.name}} +{%- endfor %}) + : result_(result) +{%- for param in method.response_parameters -%} + , out_{{param.name}}_(out_{{param.name}}) +{%- endfor %} { + DCHECK(!*result_); + } + bool Accept(mojo::Message* message) override; + private: + bool* result_; +{%- for param in method.response_parameters %} + {{param.kind|cpp_wrapper_type}}* out_{{param.name}}_; +{%- endfor -%} + DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_HandleSyncResponse); +}; +bool {{class_name}}_{{method.name}}_HandleSyncResponse::Accept( + mojo::Message* message) { + internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>( + message->mutable_payload()); + +{%- set desc = class_name~"::"~method.name~" response" %} + {{alloc_params(method.response_param_struct, "params", "message", desc)}} + +{%- for param in method.response_parameters %} + *out_{{param.name}}_ = std::move(p_{{param.name}}); +{%- endfor %} + mojo::internal::SyncMessageResponseSetup::SetCurrentSyncResponseMessage( + message); + *result_ = true; + return true; +} +{%- endif %} + +class {{class_name}}_{{method.name}}_ForwardToCallback + : public mojo::MessageReceiver { + public: + {{class_name}}_{{method.name}}_ForwardToCallback( +{%- if use_once_callback %} + {{class_name}}::{{method.name}}Callback callback +{%- else %} + const {{class_name}}::{{method.name}}Callback& callback +{%- endif %} + ) : callback_(std::move(callback)) { + } + bool Accept(mojo::Message* message) override; + private: + {{class_name}}::{{method.name}}Callback callback_; + DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ForwardToCallback); +}; +bool {{class_name}}_{{method.name}}_ForwardToCallback::Accept( + mojo::Message* message) { + internal::{{class_name}}_{{method.name}}_ResponseParams_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_ResponseParams_Data*>( + message->mutable_payload()); + +{%- set desc = class_name~"::"~method.name~" response" %} + {{alloc_params(method.response_param_struct, "params", "message", desc)}} + if (!callback_.is_null()) { + mojo::internal::MessageDispatchContext context(message); + std::move(callback_).Run({{pass_params(method.response_parameters)}}); + } + return true; +} +{%- endif %} +{%- endfor %} + +{{proxy_name}}::{{proxy_name}}(mojo::MessageReceiverWithResponder* receiver) + : receiver_(receiver) { +} + +{#--- Proxy definitions #} + +{%- for method in interface.methods %} +{%- set message_name = + "internal::k%s_%s_Name"|format(interface.name, method.name) %} +{%- set params_struct = method.param_struct %} +{%- set params_description = + "%s.%s request"|format(interface.name, method.name) %} +{%- if method.sync %} +bool {{proxy_name}}::{{method.name}}( + {{interface_macros.declare_sync_method_params("param_", method)}}) { + mojo::internal::SerializationContext serialization_context; + {{struct_macros.get_serialized_size(params_struct, "param_%s", + "&serialization_context")}} + + mojo::internal::MessageBuilder builder( + {{message_name}}, + mojo::Message::kFlagIsSync | mojo::Message::kFlagExpectsResponse, + size, serialization_context.associated_endpoint_count); + + {{build_message(params_struct, "param_%s", params_description, + "&serialization_context")}} + + bool result = false; + std::unique_ptr<mojo::MessageReceiver> responder( + new {{class_name}}_{{method.name}}_HandleSyncResponse( + &result +{%- for param in method.response_parameters -%} + , param_{{param.name}} +{%- endfor %})); + ignore_result(receiver_->AcceptWithResponder(builder.message(), + std::move(responder))); + return result; +} +{%- endif %} + +void {{proxy_name}}::{{method.name}}( + {{interface_macros.declare_request_params("in_", method, use_once_callback)}}) { + mojo::internal::SerializationContext serialization_context; + {{struct_macros.get_serialized_size(params_struct, "in_%s", + "&serialization_context")}} + +{%- if method.response_parameters != None %} + constexpr uint32_t kFlags = mojo::Message::kFlagExpectsResponse; +{%- else %} + constexpr uint32_t kFlags = 0; +{%- endif %} + mojo::internal::MessageBuilder builder( + {{message_name}}, kFlags, size, + serialization_context.associated_endpoint_count); + + {{build_message(params_struct, "in_%s", params_description, + "&serialization_context")}} + +{%- if method.response_parameters != None %} + std::unique_ptr<mojo::MessageReceiver> responder( + new {{class_name}}_{{method.name}}_ForwardToCallback( + std::move(callback))); + ignore_result(receiver_->AcceptWithResponder(builder.message(), + std::move(responder))); +{%- else %} + // This return value may be ignored as false implies the Connector has + // encountered an error, which will be visible through other means. + ignore_result(receiver_->Accept(builder.message())); +{%- endif %} +} +{%- endfor %} + +{#--- ProxyToResponder definition #} +{%- for method in interface.methods -%} +{%- if method.response_parameters != None %} +{%- set message_name = + "internal::k%s_%s_Name"|format(interface.name, method.name) %} +{%- set response_params_struct = method.response_param_struct %} +{%- set params_description = + "%s.%s response"|format(interface.name, method.name) %} +class {{class_name}}_{{method.name}}_ProxyToResponder { + public: + static {{class_name}}::{{method.name}}Callback CreateCallback( + uint64_t request_id, + bool is_sync, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) { + std::unique_ptr<{{class_name}}_{{method.name}}_ProxyToResponder> proxy( + new {{class_name}}_{{method.name}}_ProxyToResponder( + request_id, is_sync, std::move(responder))); + return base::Bind(&{{class_name}}_{{method.name}}_ProxyToResponder::Run, + base::Passed(&proxy)); + } + + ~{{class_name}}_{{method.name}}_ProxyToResponder() { +#if DCHECK_IS_ON() + if (responder_) { + // Is the Service destroying the callback without running it + // and without first closing the pipe? + responder_->DCheckInvalid("The callback passed to " + "{{class_name}}::{{method.name}}() was never run."); + } +#endif + // If the Callback was dropped then deleting the responder will close + // the pipe so the calling application knows to stop waiting for a reply. + responder_ = nullptr; + } + + private: + {{class_name}}_{{method.name}}_ProxyToResponder( + uint64_t request_id, + bool is_sync, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) + : request_id_(request_id), + is_sync_(is_sync), + responder_(std::move(responder)) { + } + + void Run( + {{interface_macros.declare_responder_params( + "in_", method.response_parameters, for_blink)}}); + + uint64_t request_id_; + bool is_sync_; + std::unique_ptr<mojo::MessageReceiverWithStatus> responder_; + + DISALLOW_COPY_AND_ASSIGN({{class_name}}_{{method.name}}_ProxyToResponder); +}; + +void {{class_name}}_{{method.name}}_ProxyToResponder::Run( + {{interface_macros.declare_responder_params( + "in_", method.response_parameters, for_blink)}}) { + mojo::internal::SerializationContext serialization_context; + {{struct_macros.get_serialized_size(response_params_struct, "in_%s", + "&serialization_context")}} + + uint32_t flags = (is_sync_ ? mojo::Message::kFlagIsSync : 0) | + mojo::Message::kFlagIsResponse; + mojo::internal::MessageBuilder builder( + {{message_name}}, flags, size, + serialization_context.associated_endpoint_count); + builder.message()->set_request_id(request_id_); + + {{build_message(response_params_struct, "in_%s", params_description, + "&serialization_context")}} + ignore_result(responder_->Accept(builder.message())); + // TODO(darin): Accept() returning false indicates a malformed message, and + // that may be good reason to close the connection. However, we don't have a + // way to do that from here. We should add a way. + responder_ = nullptr; +} +{%- endif -%} +{%- endfor %} + +{#--- StubDispatch definition #} + +// static +bool {{class_name}}StubDispatch::Accept( + {{interface.name}}* impl, + mojo::Message* message) { +{%- if interface.methods %} + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters == None %} + internal::{{class_name}}_{{method.name}}_Params_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>( + message->mutable_payload()); + +{%- set desc = class_name~"::"~method.name %} + {{alloc_params(method.param_struct, "params", "message", desc)| + indent(4)}} + // A null |impl| means no implementation was bound. + assert(impl); + TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}"); + mojo::internal::MessageDispatchContext context(message); + impl->{{method.name}}({{pass_params(method.parameters)}}); + return true; +{%- else %} + break; +{%- endif %} + } +{%- endfor %} + } +{%- endif %} + return false; +} + +// static +bool {{class_name}}StubDispatch::AcceptWithResponder( + {{interface.name}}* impl, + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) { +{%- if interface.methods %} + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters != None %} + internal::{{class_name}}_{{method.name}}_Params_Data* params = + reinterpret_cast<internal::{{class_name}}_{{method.name}}_Params_Data*>( + message->mutable_payload()); + +{%- set desc = class_name~"::"~method.name %} + {{alloc_params(method.param_struct, "params", "message", desc)| + indent(4)}} + {{class_name}}::{{method.name}}Callback callback = + {{class_name}}_{{method.name}}_ProxyToResponder::CreateCallback( + message->request_id(), + message->has_flag(mojo::Message::kFlagIsSync), + std::move(responder)); + // A null |impl| means no implementation was bound. + assert(impl); + TRACE_EVENT0("mojom", "{{class_name}}::{{method.name}}"); + mojo::internal::MessageDispatchContext context(message); + impl->{{method.name}}( +{%- if method.parameters -%}{{pass_params(method.parameters)}}, {% endif -%}std::move(callback)); + return true; +{%- else %} + break; +{%- endif %} + } +{%- endfor %} + } +{%- endif %} + return false; +} + +{#--- Request validator definitions #} + +bool {{class_name}}RequestValidator::Accept(mojo::Message* message) { + if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) + return true; + + mojo::internal::ValidationContext validation_context( + message->payload(), message->payload_num_bytes(), + message->handles()->size(), message->payload_num_interface_ids(), message, + "{{class_name}} RequestValidator"); + + switch (message->header()->name) { +{%- for method in interface.methods %} + case internal::k{{class_name}}_{{method.name}}_Name: { +{%- if method.response_parameters != None %} + if (!mojo::internal::ValidateMessageIsRequestExpectingResponse( + message, &validation_context)) { + return false; + } +{%- else %} + if (!mojo::internal::ValidateMessageIsRequestWithoutResponse( + message, &validation_context)) { + return false; + } +{%- endif %} + if (!mojo::internal::ValidateMessagePayload< + internal::{{class_name}}_{{method.name}}_Params_Data>( + message, &validation_context)) { + return false; + } + return true; + } +{%- endfor %} + default: + break; + } + + // Unrecognized message. + ReportValidationError( + &validation_context, + mojo::internal::VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD); + return false; +} + +{#--- Response validator definitions #} +{% if interface|has_callbacks %} +bool {{class_name}}ResponseValidator::Accept(mojo::Message* message) { + if (mojo::internal::ControlMessageHandler::IsControlMessage(message)) + return true; + + mojo::internal::ValidationContext validation_context( + message->payload(), message->payload_num_bytes(), + message->handles()->size(), message->payload_num_interface_ids(), message, + "{{class_name}} ResponseValidator"); + + if (!mojo::internal::ValidateMessageIsResponse(message, &validation_context)) + return false; + switch (message->header()->name) { +{%- for method in interface.methods if method.response_parameters != None %} + case internal::k{{class_name}}_{{method.name}}_Name: { + if (!mojo::internal::ValidateMessagePayload< + internal::{{class_name}}_{{method.name}}_ResponseParams_Data>( + message, &validation_context)) { + return false; + } + return true; + } +{%- endfor %} + default: + break; + } + + // Unrecognized message. + ReportValidationError( + &validation_context, + mojo::internal::VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD); + return false; +} +{%- endif -%} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl new file mode 100644 index 0000000000..8649273633 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl @@ -0,0 +1,49 @@ +{%- macro declare_params(prefix, parameters) %} +{%- for param in parameters -%} +{{param.kind|cpp_wrapper_param_type}} {{prefix}}{{param.name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro declare_responder_params(prefix, parameters, for_blink) %} +{%- for param in parameters -%} +{{param.kind|cpp_wrapper_param_type}} {{prefix}}{{param.name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{%- macro declare_callback(method, for_blink, use_once_callback) -%} +{%- if use_once_callback -%} +base::OnceCallback<void( +{%- else -%} +base::Callback<void( +{%- endif -%} +{%- for param in method.response_parameters -%} +{{param.kind|cpp_wrapper_param_type}} +{%- if not loop.last %}, {% endif %} +{%- endfor -%} +)> +{%- endmacro -%} + +{%- macro declare_request_params(prefix, method, use_once_callback) -%} +{{declare_params(prefix, method.parameters)}} +{%- if method.response_parameters != None -%} +{%- if method.parameters %}, {% endif -%} +{%- if use_once_callback -%} +{{method.name}}Callback callback +{%- else -%} +const {{method.name}}Callback& callback +{%- endif -%} +{%- endif -%} +{%- endmacro -%} + +{%- macro declare_sync_method_params(prefix, method) -%} +{{declare_params(prefix, method.parameters)}} +{%- if method.response_parameters %} +{%- if method.parameters %}, {% endif %} +{%- for param in method.response_parameters -%} +{{param.kind|cpp_wrapper_type}}* {{prefix}}{{param.name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endif -%} +{%- endmacro -%} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl new file mode 100644 index 0000000000..0a158ec3e8 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl @@ -0,0 +1,16 @@ +{%- import "interface_macros.tmpl" as interface_macros %} +class {{export_attribute}} {{interface.name}}Proxy + : public {{interface.name}} { + public: + explicit {{interface.name}}Proxy(mojo::MessageReceiverWithResponder* receiver); + +{%- for method in interface.methods %} +{%- if method.sync %} + bool {{method.name}}({{interface_macros.declare_sync_method_params("", method)}}) override; +{%- endif %} + void {{method.name}}({{interface_macros.declare_request_params("", method, use_once_callback)}}) override; +{%- endfor %} + + private: + mojo::MessageReceiverWithResponder* receiver_; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl new file mode 100644 index 0000000000..a00d14886d --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl @@ -0,0 +1,4 @@ +class {{export_attribute}} {{interface.name}}RequestValidator : public NON_EXPORTED_BASE(mojo::MessageReceiver) { + public: + bool Accept(mojo::Message* message) override; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl new file mode 100644 index 0000000000..e2caa02c79 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl @@ -0,0 +1,4 @@ +class {{export_attribute}} {{interface.name}}ResponseValidator : public NON_EXPORTED_BASE(mojo::MessageReceiver) { + public: + bool Accept(mojo::Message* message) override; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl new file mode 100644 index 0000000000..79ab46f337 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl @@ -0,0 +1,41 @@ +class {{export_attribute}} {{interface.name}}StubDispatch { + public: + static bool Accept({{interface.name}}* impl, mojo::Message* message); + static bool AcceptWithResponder( + {{interface.name}}* impl, + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder); +}; + +template <typename ImplRefTraits = + mojo::RawPtrImplRefTraits<{{interface.name}}>> +class {{interface.name}}Stub + : public NON_EXPORTED_BASE(mojo::MessageReceiverWithResponderStatus) { + public: + using ImplPointerType = typename ImplRefTraits::PointerType; + + {{interface.name}}Stub() {} + ~{{interface.name}}Stub() override {} + + void set_sink(ImplPointerType sink) { sink_ = std::move(sink); } + ImplPointerType& sink() { return sink_; } + + bool Accept(mojo::Message* message) override { + if (ImplRefTraits::IsNull(sink_)) + return false; + return {{interface.name}}StubDispatch::Accept( + ImplRefTraits::GetRawPointer(&sink_), message); + } + + bool AcceptWithResponder( + mojo::Message* message, + std::unique_ptr<mojo::MessageReceiverWithStatus> responder) override { + if (ImplRefTraits::IsNull(sink_)) + return false; + return {{interface.name}}StubDispatch::AcceptWithResponder( + ImplRefTraits::GetRawPointer(&sink_), message, std::move(responder)); + } + + private: + ImplPointerType sink_; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl new file mode 100644 index 0000000000..964b25438e --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl @@ -0,0 +1,96 @@ +// 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. + +{%- set header_guard = "%s_SHARED_INTERNAL_H_"|format( + module.path|upper|replace("/","_")|replace(".","_")| + replace("-", "_")) %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include <stdint.h> + +#include "mojo/public/cpp/bindings/lib/array_internal.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/map_data_internal.h" +#include "mojo/public/cpp/bindings/lib/native_enum_data.h" +#include "mojo/public/cpp/bindings/lib/native_struct_data.h" +#include "mojo/public/cpp/bindings/lib/buffer.h" + +{%- for import in imports %} +#include "{{import.module.path}}-shared-internal.h" +{%- endfor %} + +namespace mojo { +namespace internal { +class ValidationContext; +} +} + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +namespace internal { + +{#--- Internal forward declarations #} +{%- for struct in structs %} +{%- if struct|is_native_only_kind %} +using {{struct.name}}_Data = mojo::internal::NativeStruct_Data; +{%- else %} +class {{struct.name}}_Data; +{%- endif %} +{%- endfor %} + +{%- for union in unions %} +class {{union.name}}_Data; +{%- endfor %} + +{#--- Enums #} +{%- from "enum_macros.tmpl" import enum_data_decl -%} +{%- for enum in all_enums %} +{%- if enum|is_native_only_kind %} +using {{enum|get_name_for_kind(flatten_nested_kind=True)}}_Data = + mojo::internal::NativeEnum_Data; +{%- else %} +{{enum_data_decl(enum)}} +{%- endif %} +{%- endfor %} + +#pragma pack(push, 1) + +{#--- Unions must be declared first because they can be members of structs #} +{#--- Union class declarations #} +{%- for union in unions %} +{% include "union_declaration.tmpl" %} +{%- endfor %} + +{#--- Struct class declarations #} +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Interface parameter definitions #} +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %} +constexpr uint32_t {{method_name}} = {{method.ordinal}}; +{%- set struct = method.param_struct %} +{% include "struct_declaration.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method.response_param_struct %} +{% include "struct_declaration.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +#pragma pack(pop) + +} // namespace internal +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +#endif // {{header_guard}} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl new file mode 100644 index 0000000000..645bb692b0 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl @@ -0,0 +1,64 @@ +// 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. + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4065) +#endif + +#include "{{module.path}}-shared.h" + +#include <utility> + +#include "base/logging.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/bindings/lib/validation_util.h" + +{%- for header in extra_traits_headers %} +#include "{{header}}" +{%- endfor %} + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} + +namespace internal { + +{#--- Union definitions #} +{%- for union in unions %} +{% include "union_definition.tmpl" %} +{%- endfor %} + +{#--- Struct definitions #} +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Interface parameter definitions #} +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set method_name = "k%s_%s_Name"|format(interface.name, method.name) %} +{%- set struct = method.param_struct %} +{% include "struct_definition.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method.response_param_struct %} +{% include "struct_definition.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +} // namespace internal + +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl new file mode 100644 index 0000000000..dd13466de1 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl @@ -0,0 +1,212 @@ +// 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. + +{%- set header_guard = "%s_SHARED_H_"|format( + module.path|upper|replace("/","_")|replace(".","_")| + replace("-", "_")) %} + +{%- macro mojom_type_traits(kind) %} +template <> +struct MojomTypeTraits<{{kind|get_qualified_name_for_kind}}DataView> { + using Data = {{kind|get_qualified_name_for_kind(internal=True)}}; +{%- if kind|is_union_kind %} + using DataAsArrayElement = Data; + static constexpr MojomTypeCategory category = MojomTypeCategory::UNION; +{%- else %} + using DataAsArrayElement = Pointer<Data>; + static constexpr MojomTypeCategory category = MojomTypeCategory::STRUCT; +{%- endif %} +}; +{%- endmacro %} + +{%- macro namespace_begin() %} +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +{%- endmacro %} + +{%- macro namespace_end() %} +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} +{%- endmacro %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include <stdint.h> + +#include <functional> +#include <ostream> +#include <type_traits> +#include <utility> + +#include "base/compiler_specific.h" +#include "mojo/public/cpp/bindings/array_data_view.h" +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "mojo/public/cpp/bindings/interface_data_view.h" +#include "mojo/public/cpp/bindings/lib/bindings_internal.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/map_data_view.h" +#include "mojo/public/cpp/bindings/native_enum.h" +#include "mojo/public/cpp/bindings/native_struct_data_view.h" +#include "mojo/public/cpp/bindings/string_data_view.h" +#include "{{module.path}}-shared-internal.h" +{%- for import in imports %} +#include "{{import.module.path}}-shared.h" +{%- endfor %} + +{{namespace_begin()}} + +{#--- Struct Forward Declarations -#} +{%- for struct in structs %} +{%- if struct|is_native_only_kind %} +using {{struct.name}}DataView = mojo::NativeStructDataView; +{%- else %} +class {{struct.name}}DataView; +{%- endif %} +{% endfor %} + +{#--- Union Forward Declarations -#} +{%- for union in unions %} +class {{union.name}}DataView; +{%- endfor %} + +{{namespace_end()}} + +namespace mojo { +namespace internal { + +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{{mojom_type_traits(struct)}} +{%- endif %} +{%- endfor %} + +{%- for union in unions %} +{{mojom_type_traits(union)}} +{%- endfor %} + +} // namespace internal +} // namespace mojo + +{{namespace_begin()}} + +{#--- Enums #} +{%- from "enum_macros.tmpl" import enum_decl%} +{%- for enum in all_enums %} +{%- if enum|is_native_only_kind %} +using {{enum|get_name_for_kind(flatten_nested_kind=True)}} = mojo::NativeEnum; +{%- else %} +{{enum_decl(enum)}} +{%- endif %} +{%- endfor %} + +{#--- Interfaces #} +{%- if interfaces %} +// Interface base classes. They are used for type safety check. +{%- endif %} +{%- for interface in interfaces %} +class {{interface.name}}InterfaceBase {}; + +using {{interface.name}}PtrDataView = + mojo::InterfacePtrDataView<{{interface.name}}InterfaceBase>; +using {{interface.name}}RequestDataView = + mojo::InterfaceRequestDataView<{{interface.name}}InterfaceBase>; +using {{interface.name}}AssociatedPtrInfoDataView = + mojo::AssociatedInterfacePtrInfoDataView<{{interface.name}}InterfaceBase>; +using {{interface.name}}AssociatedRequestDataView = + mojo::AssociatedInterfaceRequestDataView<{{interface.name}}InterfaceBase>; + +{%- endfor %} + +{#--- Structs #} +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_data_view_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Interface parameter definitions #} +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set struct = method.param_struct %} +{% include "struct_data_view_declaration.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method.response_param_struct %} +{% include "struct_data_view_declaration.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +{#--- Unions #} +{%- for union in unions %} +{% include "union_data_view_declaration.tmpl" %} +{%- endfor %} + +{{namespace_end()}} + +namespace std { + +{%- from "enum_macros.tmpl" import enum_hash %} +{%- for enum in all_enums %} +{%- if not enum|is_native_only_kind %} +{{enum_hash(enum)}} +{%- endif %} +{%- endfor %} + +} // namespace std + +namespace mojo { + +{#--- Enum Serialization Helpers -#} +{%- for enum in all_enums %} +{%- if not enum|is_native_only_kind %} +{% include "enum_serialization_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Struct Serialization Helpers -#} +{% for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_serialization_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Union Serialization Helpers -#} +{% if unions %} +{%- for union in unions %} +{% include "union_serialization_declaration.tmpl" %} +{%- endfor %} +{%- endif %} + +} // namespace mojo + +{{namespace_begin()}} + +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_data_view_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{%- for interface in interfaces %} +{%- for method in interface.methods %} +{%- set struct = method.param_struct %} +{% include "struct_data_view_definition.tmpl" %} +{%- if method.response_parameters != None %} +{%- set struct = method.response_param_struct %} +{% include "struct_data_view_definition.tmpl" %} +{%- endif %} +{%- endfor %} +{%- endfor %} + +{%- for union in unions %} +{% include "union_data_view_definition.tmpl" %} +{%- endfor %} + +{{namespace_end()}} + +#endif // {{header_guard}} + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl new file mode 100644 index 0000000000..2c66a85f87 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl @@ -0,0 +1,111 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +{%- if variant -%} +{%- set variant_path = "%s-%s"|format(module.path, variant) -%} +{%- else -%} +{%- set variant_path = module.path -%} +{%- endif %} + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-private-field" +#elif defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4056) +#pragma warning(disable:4065) +#pragma warning(disable:4756) +#endif + +#include "{{variant_path}}.h" + +#include <math.h> +#include <stdint.h> +#include <utility> + +#include "base/logging.h" +#include "base/trace_event/trace_event.h" +#include "mojo/public/cpp/bindings/lib/message_builder.h" +#include "mojo/public/cpp/bindings/lib/serialization_util.h" +#include "mojo/public/cpp/bindings/lib/validate_params.h" +#include "mojo/public/cpp/bindings/lib/validation_context.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/interfaces/bindings/interface_control_messages.mojom.h" + +{%- if for_blink %} +#include "mojo/public/cpp/bindings/lib/wtf_serialization.h" +{%- endif %} + +{%- for header in extra_traits_headers %} +#include "{{header}}" +{%- endfor %} + +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +{%- if variant %} +namespace {{variant}} { +{%- endif %} + +{#--- Constants #} +{%- for constant in module.constants %} +{%- if constant.kind|is_string_kind %} +const char {{constant.name}}[] = {{constant|constant_value}}; +{%- endif %} +{%- endfor %} + +{#--- Struct Constants #} +{%- for struct in structs %} +{%- for constant in struct.constants %} +{%- if constant.kind|is_string_kind %} +const char {{struct.name}}::{{constant.name}}[] = {{constant|constant_value}}; +{%- endif %} +{%- endfor %} +{%- endfor %} + +{#--- Struct builder definitions #} +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{%- include "wrapper_class_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Union builder definitions #} +{%- for union in unions %} +{%- include "wrapper_union_class_definition.tmpl" %} +{%- endfor %} + +{#--- Interface definitions #} +{%- for interface in interfaces %} +{%- include "interface_definition.tmpl" %} +{%- endfor %} + +{%- if variant %} +} // namespace {{variant}} +{%- endif %} +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} + +namespace mojo { + +{#--- Struct Serialization Helpers -#} +{% for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_traits_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Union Serialization Helpers #} +{%- for union in unions %} +{%- include "union_traits_definition.tmpl" %} +{%- endfor %} + +} // namespace mojo + +#if defined(__clang__) +#pragma clang diagnostic pop +#elif defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl new file mode 100644 index 0000000000..804a46b6f8 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl @@ -0,0 +1,236 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +{%- if variant -%} +{%- set variant_path = "%s-%s"|format(module.path, variant) -%} +{%- else -%} +{%- set variant_path = module.path -%} +{%- endif -%} + +{%- set header_guard = "%s_H_"|format( + variant_path|upper|replace("/","_")|replace(".","_")| + replace("-", "_")) %} + +{%- macro namespace_begin() %} +{%- for namespace in namespaces_as_array %} +namespace {{namespace}} { +{%- endfor %} +{%- if variant %} +namespace {{variant}} { +{%- endif %} +{%- endmacro %} + +{%- macro namespace_end() %} +{%- if variant %} +} // namespace {{variant}} +{%- endif %} +{%- for namespace in namespaces_as_array|reverse %} +} // namespace {{namespace}} +{%- endfor %} +{%- endmacro %} + +#ifndef {{header_guard}} +#define {{header_guard}} + +#include <stdint.h> + +#include <limits> +#include <type_traits> +#include <utility> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/optional.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr.h" +#include "mojo/public/cpp/bindings/associated_interface_ptr_info.h" +#include "mojo/public/cpp/bindings/associated_interface_request.h" +#include "mojo/public/cpp/bindings/clone_traits.h" +#include "mojo/public/cpp/bindings/interface_ptr.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "mojo/public/cpp/bindings/lib/equals_traits.h" +#include "mojo/public/cpp/bindings/lib/control_message_handler.h" +#include "mojo/public/cpp/bindings/lib/control_message_proxy.h" +#include "mojo/public/cpp/bindings/lib/serialization.h" +#include "mojo/public/cpp/bindings/lib/union_accessor.h" +#include "mojo/public/cpp/bindings/native_struct.h" +#include "mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h" +#include "mojo/public/cpp/bindings/struct_ptr.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/thread_safe_interface_ptr.h" +#include "mojo/public/cpp/bindings/union_traits.h" +#include "{{module.path}}-shared.h" +{%- for import in imports %} +{%- if variant %} +#include "{{"%s-%s.h"|format(import.module.path, variant)}}" +{%- else %} +#include "{{import.module.path}}.h" +{%- endif %} +{%- endfor %} +{%- if not for_blink %} +#include <string> +#include <vector> +{%- else %} +{# hash_util.h includes template specializations that should be present for + every use of {Inlined}StructPtr. #} +#include "mojo/public/cpp/bindings/lib/wtf_hash_util.h" +#include "third_party/WebKit/Source/wtf/HashFunctions.h" +#include "third_party/WebKit/Source/wtf/Optional.h" +#include "third_party/WebKit/Source/wtf/text/WTFString.h" +{%- endif %} + +{%- for header in extra_public_headers %} +#include "{{header}}" +{%- endfor %} + +{%- if export_header %} +#include "{{export_header}}" +{%- endif %} + +{#--- WTF enum hashing #} +{%- from "enum_macros.tmpl" import enum_hash_blink%} +{%- if for_blink %} +{%- for enum in all_enums %} +{%- if not enum|is_native_only_kind %} +{{enum_hash_blink(enum)}} +{%- endif %} +{%- endfor %} +{%- endif %} + +{{namespace_begin()}} + +{#--- Enums #} +{%- if variant %} +{%- for enum in enums %} +using {{enum.name}} = {{enum.name}}; // Alias for definition in the parent namespace. +{%- endfor %} +{%- endif %} + +{#--- Constants #} +{%- for constant in module.constants %} +{{constant|format_constant_declaration}}; +{%- endfor %} + +{#--- Interface Forward Declarations -#} +{% for interface in interfaces %} +class {{interface.name}}; +using {{interface.name}}Ptr = mojo::InterfacePtr<{{interface.name}}>; +using {{interface.name}}PtrInfo = mojo::InterfacePtrInfo<{{interface.name}}>; +using ThreadSafe{{interface.name}}Ptr = + mojo::ThreadSafeInterfacePtr<{{interface.name}}>; +using {{interface.name}}Request = mojo::InterfaceRequest<{{interface.name}}>; +using {{interface.name}}AssociatedPtr = + mojo::AssociatedInterfacePtr<{{interface.name}}>; +using ThreadSafe{{interface.name}}AssociatedPtr = + mojo::ThreadSafeAssociatedInterfacePtr<{{interface.name}}>; +using {{interface.name}}AssociatedPtrInfo = + mojo::AssociatedInterfacePtrInfo<{{interface.name}}>; +using {{interface.name}}AssociatedRequest = + mojo::AssociatedInterfaceRequest<{{interface.name}}>; +{% endfor %} + +{#--- Struct Forward Declarations -#} +{% for struct in structs %} +{%- if struct|is_native_only_kind %} +using {{struct.name}} = mojo::NativeStruct; +using {{struct.name}}Ptr = mojo::NativeStructPtr; +{%- else %} +class {{struct.name}}; +{%- if struct|should_inline %} +using {{struct.name}}Ptr = mojo::InlinedStructPtr<{{struct.name}}>; +{%- else %} +using {{struct.name}}Ptr = mojo::StructPtr<{{struct.name}}>; +{%- endif %} +{%- endif %} +{% endfor %} + +{#--- Union Forward Declarations -#} +{% for union in unions %} +class {{union.name}}; +{% if union|should_inline_union %} +typedef mojo::InlinedStructPtr<{{union.name}}> {{union.name}}Ptr; +{% else %} +typedef mojo::StructPtr<{{union.name}}> {{union.name}}Ptr; +{% endif %} +{%- endfor %} + +{#--- Interfaces -#} +{% for interface in interfaces %} +{% include "interface_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Proxies -#} +{% for interface in interfaces %} +{% include "interface_proxy_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Stubs -#} +{% for interface in interfaces %} +{% include "interface_stub_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Request Validators -#} +{% for interface in interfaces %} +{% include "interface_request_validator_declaration.tmpl" %} +{%- endfor %} + +{#--- Interface Response Validators -#} +{% for interface in interfaces if interface|has_callbacks %} +{% include "interface_response_validator_declaration.tmpl" %} +{%- endfor %} + +{#--- NOTE: Unions and non-inlined structs may have pointers to inlined structs, + so we need to fully define inlined structs ahead of the others. #} + +{#--- Inlined structs #} +{% for struct in structs %} +{% if struct|should_inline and not struct|is_native_only_kind %} +{% include "wrapper_class_declaration.tmpl" %} +{% endif %} +{%- endfor %} + +{#--- Unions must be declared before non-inlined structs because they can be + members of structs. #} +{#--- Unions #} +{% for union in unions %} +{% include "wrapper_union_class_declaration.tmpl" %} +{%- endfor %} + +{#--- Non-inlined structs #} +{% for struct in structs %} +{% if not struct|should_inline and not struct|is_native_only_kind %} +{% include "wrapper_class_declaration.tmpl" %} +{% endif %} +{%- endfor %} + +{%- for union in unions %} +{% include "wrapper_union_class_template_definition.tmpl" %} +{%- endfor %} + +{%- for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "wrapper_class_template_definition.tmpl" %} +{%- endif %} +{%- endfor %} + +{{namespace_end()}} + +namespace mojo { + +{#--- Struct Serialization Helpers -#} +{% for struct in structs %} +{%- if not struct|is_native_only_kind %} +{% include "struct_traits_declaration.tmpl" %} +{%- endif %} +{%- endfor %} + +{#--- Union Serialization Helpers -#} +{% if unions %} +{%- for union in unions %} +{% include "union_traits_declaration.tmpl" %} +{%- endfor %} +{%- endif %} + +} // namespace mojo + +#endif // {{header_guard}} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl new file mode 100644 index 0000000000..96e0d614d8 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl @@ -0,0 +1,118 @@ +class {{struct.name}}DataView { + public: + {{struct.name}}DataView() {} + + {{struct.name}}DataView( + internal::{{struct.name}}_Data* data, + mojo::internal::SerializationContext* context) +{%- if struct|requires_context_for_data_view %} + : data_(data), context_(context) {} +{%- else %} + : data_(data) {} +{%- endif %} + + bool is_null() const { return !data_; } + +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set kind = pf.field.kind %} +{%- set name = pf.field.name %} +{%- if kind|is_union_kind %} + inline void Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output); + + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) { +{%- if pf.min_version != 0 %} + auto* pointer = data_->header_.version >= {{pf.min_version}} + ? &data_->{{name}} : nullptr; +{%- else %} + auto* pointer = &data_->{{name}}; +{%- endif %} + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + pointer, output, context_); + } + +{%- elif kind|is_object_kind %} + inline void Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output); + + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) { +{%- if pf.min_version != 0 %} + auto* pointer = data_->header_.version >= {{pf.min_version}} + ? data_->{{name}}.Get() : nullptr; +{%- else %} + auto* pointer = data_->{{name}}.Get(); +{%- endif %} + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + pointer, output, context_); + } + +{%- elif kind|is_enum_kind %} + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const { +{%- if pf.min_version != 0 %} + auto data_value = data_->header_.version >= {{pf.min_version}} + ? data_->{{name}} : 0; +{%- else %} + auto data_value = data_->{{name}}; +{%- endif %} + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + data_value, output); + } + + {{kind|cpp_data_view_type}} {{name}}() const { +{%- if pf.min_version != 0 %} + if (data_->header_.version < {{pf.min_version}}) + return {{kind|get_qualified_name_for_kind}}{}; +{%- endif %} + return static_cast<{{kind|cpp_data_view_type}}>(data_->{{name}}); + } + +{%- elif kind|is_any_handle_kind %} + {{kind|cpp_data_view_type}} Take{{name|under_to_camel}}() { + {{kind|cpp_data_view_type}} result; +{%- if pf.min_version != 0 %} + if (data_->header_.version < {{pf.min_version}}) + return result; +{%- endif %} + bool ret = + mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + &data_->{{name}}, &result, context_); + DCHECK(ret); + return result; + } + +{%- elif kind|is_any_interface_kind %} + template <typename UserType> + UserType Take{{name|under_to_camel}}() { + UserType result; +{%- if pf.min_version != 0 %} + if (data_->header_.version < {{pf.min_version}}) + return result; +{%- endif %} + bool ret = + mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + &data_->{{name}}, &result, context_); + DCHECK(ret); + return result; + } + +{%- else %} + {{kind|cpp_data_view_type}} {{name}}() const { +{%- if pf.min_version != 0 %} + if (data_->header_.version < {{pf.min_version}}) + return {{kind|cpp_data_view_type}}{}; +{%- endif %} + return data_->{{name}}; + } + +{%- endif %} +{%- endfor %} + private: + internal::{{struct.name}}_Data* data_ = nullptr; +{%- if struct|requires_context_for_data_view %} + mojo::internal::SerializationContext* context_ = nullptr; +{%- endif %} +}; + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl new file mode 100644 index 0000000000..95311dc124 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl @@ -0,0 +1,30 @@ +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set kind = pf.field.kind %} +{%- set name = pf.field.name %} + +{%- if kind|is_union_kind %} +inline void {{struct.name}}DataView::Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output) { +{%- if pf.min_version != 0 %} + auto pointer = data_->header_.version >= {{pf.min_version}} + ? &data_->{{name}} : nullptr; +{%- else %} + auto pointer = &data_->{{name}}; +{%- endif %} + *output = {{kind|cpp_data_view_type}}(pointer, context_); +} + +{%- elif kind|is_object_kind %} +inline void {{struct.name}}DataView::Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output) { +{%- if pf.min_version != 0 %} + auto pointer = data_->header_.version >= {{pf.min_version}} + ? data_->{{name}}.Get() : nullptr; +{%- else %} + auto pointer = data_->{{name}}.Get(); +{%- endif %} + *output = {{kind|cpp_data_view_type}}(pointer, context_); +} +{%- endif %} +{%- endfor %} + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl new file mode 100644 index 0000000000..156f7742c4 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl @@ -0,0 +1,46 @@ +{%- set class_name = struct.name ~ "_Data" -%} + +class {{class_name}} { + public: + static {{class_name}}* New(mojo::internal::Buffer* buf) { + return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}(); + } + + static bool Validate(const void* data, + mojo::internal::ValidationContext* validation_context); + + mojo::internal::StructHeader header_; +{%- for packed_field in struct.packed.packed_fields %} +{%- set name = packed_field.field.name %} +{%- set kind = packed_field.field.kind %} +{%- if kind.spec == 'b' %} + uint8_t {{name}} : 1; +{%- else %} + {{kind|cpp_field_type}} {{name}}; +{%- endif %} +{%- if not loop.last %} +{%- set next_pf = struct.packed.packed_fields[loop.index0 + 1] %} +{%- set pad = next_pf.offset - (packed_field.offset + packed_field.size) %} +{%- if pad > 0 %} + uint8_t pad{{loop.index0}}_[{{pad}}]; +{%- endif %} +{%- endif %} +{%- endfor %} + +{%- set num_fields = struct.versions[-1].num_fields %} +{%- if num_fields > 0 %} +{%- set last_field = struct.packed.packed_fields[num_fields - 1] %} +{%- set offset = last_field.offset + last_field.size %} +{%- set pad = offset|get_pad(8) %} +{%- if pad > 0 %} + uint8_t padfinal_[{{pad}}]; +{%- endif %} +{%- endif %} + + private: + {{class_name}}() : header_({sizeof(*this), {{struct.versions[-1].version}}}) { + } + ~{{class_name}}() = delete; +}; +static_assert(sizeof({{class_name}}) == {{struct.versions[-1].num_bytes}}, + "Bad sizeof({{class_name}})"); diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl new file mode 100644 index 0000000000..60dca4010e --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl @@ -0,0 +1,70 @@ +{%- import "validation_macros.tmpl" as validation_macros %} +{%- set class_name = struct.name ~ "_Data" %} + +// static +bool {{class_name}}::Validate( + const void* data, + mojo::internal::ValidationContext* validation_context) { + if (!data) + return true; + + if (!ValidateStructHeaderAndClaimMemory(data, validation_context)) + return false; + + // NOTE: The memory backing |object| may be smaller than |sizeof(*object)| if + // the message comes from an older version. + const {{class_name}}* object = static_cast<const {{class_name}}*>(data); + + static constexpr struct { + uint32_t version; + uint32_t num_bytes; + } kVersionSizes[] = { +{%- for version in struct.versions -%} + { {{version.version}}, {{version.num_bytes}} }{% if not loop.last %}, {% endif -%} +{%- endfor -%} + }; + + if (object->header_.version <= + kVersionSizes[arraysize(kVersionSizes) - 1].version) { + // Scan in reverse order to optimize for more recent versions. + for (int i = arraysize(kVersionSizes) - 1; i >= 0; --i) { + if (object->header_.version >= kVersionSizes[i].version) { + if (object->header_.num_bytes == kVersionSizes[i].num_bytes) + break; + + ReportValidationError( + validation_context, + mojo::internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + } + } else if (object->header_.num_bytes < + kVersionSizes[arraysize(kVersionSizes) - 1].num_bytes) { + ReportValidationError( + validation_context, + mojo::internal::VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER); + return false; + } + +{#- Before validating fields introduced at a certain version, we need to add + a version check, which makes sure we skip further validation if |object| + is from an earlier version. |last_checked_version| records the last + version that we have added such version check. #} +{%- set last_checked_version = 0 %} +{%- for packed_field in struct.packed.packed_fields_in_ordinal_order %} +{%- set kind = packed_field.field.kind %} +{%- if kind|is_object_kind or kind|is_any_handle_or_interface_kind or + kind|is_enum_kind %} +{%- if packed_field.min_version > last_checked_version %} +{%- set last_checked_version = packed_field.min_version %} + if (object->header_.version < {{packed_field.min_version}}) + return true; +{%- endif %} +{%- set field_expr = "object->" ~ packed_field.field.name %} +{{validation_macros.validate_field(packed_field.field, field_expr, struct.name, true)}} +{%- endif %} +{%- endfor %} + + return true; +} + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl new file mode 100644 index 0000000000..bb5fb9c496 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl @@ -0,0 +1,161 @@ +{# TODO(yzshen): Make these templates more readable. #} + +{# Computes the serialized size for the specified struct. + |struct| is the struct definition. + |input_field_pattern| should be a pattern that contains one string + placeholder, for example, "input->%s", "p_%s". The placeholder will be + substituted with struct field names to refer to the input fields. + |context| is the name of the serialization context. + |input_may_be_temp| indicates whether any input may be temporary obejcts. + We need to assign temporary objects to local variables before passing it to + Serializer, because it is illegal to pass temporary objects as non-const + references. + This macro is expanded to compute seriailized size for both: + - user-defined structs: the input is an instance of the corresponding struct + wrapper class. + - method parameters/response parameters: the input is a list of + arguments. + It declares |size| of type size_t to store the resulting size. #} +{%- macro get_serialized_size(struct, input_field_pattern, context, + input_may_be_temp=False) -%} + size_t size = sizeof({{struct|get_qualified_name_for_kind(internal=True)}}); +{%- for pf in struct.packed.packed_fields_in_ordinal_order + if pf.field.kind|is_object_kind or pf.field.kind|is_associated_kind %} +{%- set name = pf.field.name -%} +{%- set kind = pf.field.kind -%} +{%- set original_input_field = input_field_pattern|format(name) %} +{%- set input_field = "in_%s"|format(name) if input_may_be_temp + else original_input_field %} +{%- if input_may_be_temp %} + decltype({{original_input_field}}) in_{{name}} = {{original_input_field}}; +{%- endif %} + +{%- set serializer_type = kind|unmapped_type_for_serializer %} +{%- if kind|is_union_kind %} + size += mojo::internal::PrepareToSerialize<{{serializer_type}}>( + {{input_field}}, true, {{context}}); +{%- else %} + size += mojo::internal::PrepareToSerialize<{{serializer_type}}>( + {{input_field}}, {{context}}); +{%- endif %} +{%- endfor %} +{%- endmacro -%} + +{# Serializes the specified struct. + |struct| is the struct definition. + |struct_display_name| is the display name for the struct that can be showed + in error/log messages, for example, "FooStruct", "FooMethod request". + |input_field_pattern| should be a pattern that contains one string + placeholder, for example, "input->%s", "p_%s". The placeholder will be + substituted with struct field names to refer to the input fields. + |output| is the name of the output struct instance. + |buffer| is the name of the Buffer instance used. + |context| is the name of the serialization context. + |input_may_be_temp|: please see the comments of get_serialized_size. + This macro is expanded to do serialization for both: + - user-defined structs: the input is an instance of the corresponding struct + wrapper class. + - method parameters/response parameters: the input is a list of + arguments. #} +{%- macro serialize(struct, struct_display_name, input_field_pattern, output, + buffer, context, input_may_be_temp=False) -%} + auto {{output}} = + {{struct|get_qualified_name_for_kind(internal=True)}}::New({{buffer}}); + ALLOW_UNUSED_LOCAL({{output}}); +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set input_field = input_field_pattern|format(pf.field.name) %} +{%- set name = pf.field.name %} +{%- set kind = pf.field.kind %} +{%- set serializer_type = kind|unmapped_type_for_serializer %} + +{%- if kind|is_object_kind or kind|is_any_handle_or_interface_kind %} +{%- set original_input_field = input_field_pattern|format(name) %} +{%- set input_field = "in_%s"|format(name) if input_may_be_temp + else original_input_field %} +{%- if input_may_be_temp %} + decltype({{original_input_field}}) in_{{name}} = {{original_input_field}}; +{%- endif %} +{%- endif %} + +{%- if kind|is_object_kind %} +{%- if kind|is_array_kind or kind|is_map_kind %} + typename decltype({{output}}->{{name}})::BaseType* {{name}}_ptr; + const mojo::internal::ContainerValidateParams {{name}}_validate_params( + {{kind|get_container_validate_params_ctor_args|indent(10)}}); + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, {{buffer}}, &{{name}}_ptr, &{{name}}_validate_params, + {{context}}); + {{output}}->{{name}}.Set({{name}}_ptr); +{%- elif kind|is_union_kind %} + auto {{name}}_ptr = &{{output}}->{{name}}; + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, {{buffer}}, &{{name}}_ptr, true, {{context}}); +{%- else %} + typename decltype({{output}}->{{name}})::BaseType* {{name}}_ptr; + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, {{buffer}}, &{{name}}_ptr, {{context}}); + {{output}}->{{name}}.Set({{name}}_ptr); +{%- endif %} +{%- if not kind|is_nullable_kind %} + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + {{output}}->{{name}}.is_null(), + mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + "null {{name}} in {{struct_display_name}}"); +{%- endif %} + +{%- elif kind|is_any_handle_or_interface_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, &{{output}}->{{name}}, {{context}}); +{%- if not kind|is_nullable_kind %} + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !mojo::internal::IsHandleOrInterfaceValid({{output}}->{{name}}), +{%- if kind|is_associated_kind %} + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID, +{%- else %} + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, +{%- endif %} + "invalid {{name}} in {{struct_display_name}}"); +{%- endif %} + +{%- elif kind|is_enum_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + {{input_field}}, &{{output}}->{{name}}); + +{%- else %} + {{output}}->{{name}} = {{input_field}}; +{%- endif %} +{%- endfor %} +{%- endmacro -%} + +{# Deserializes the specified struct. + |struct| is the struct definition. + |input| is the name of the input struct data view. It is expected to be + non-null. + |output_field_pattern| should be a pattern that contains one string + placeholder, for example, "result->%s", "p_%s". The placeholder will be + substituted with struct field names to refer to the output fields. + |context| is the name of the serialization context. + |success| is the name of a bool variable to track success of the operation. + This macro is expanded to do deserialization for both: + - user-defined structs: the output is an instance of the corresponding + struct wrapper class. + - method parameters/response parameters: the output is a list of + arguments. #} +{%- macro deserialize(struct, input, output_field_pattern, success) -%} +{%- for pf in struct.packed.packed_fields_in_ordinal_order %} +{%- set output_field = output_field_pattern|format(pf.field.name) %} +{%- set name = pf.field.name %} +{%- set kind = pf.field.kind %} +{%- if kind|is_object_kind or kind|is_enum_kind %} + if (!{{input}}.Read{{name|under_to_camel}}(&{{output_field}})) + {{success}} = false; +{%- elif kind|is_any_handle_kind %} + {{output_field}} = {{input}}.Take{{name|under_to_camel}}(); +{%- elif kind|is_any_interface_kind %} + {{output_field}} = + {{input}}.Take{{name|under_to_camel}}<decltype({{output_field}})>(); +{%- else %} + {{output_field}} = {{input}}.{{name}}(); +{%- endif %} +{%- endfor %} +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl new file mode 100644 index 0000000000..835178beda --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl @@ -0,0 +1,57 @@ +{%- import "struct_macros.tmpl" as struct_macros %} +{%- set data_view = struct|get_qualified_name_for_kind ~ "DataView" %} +{%- set data_type = struct|get_qualified_name_for_kind(internal=True) %} + +namespace internal { + +template <typename MaybeConstUserType> +struct Serializer<{{data_view}}, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = StructTraits<{{data_view}}, UserType>; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) + return 0; + + void* custom_context = CustomContextHelper<Traits>::SetUp(input, context); + ALLOW_UNUSED_LOCAL(custom_context); + + {{struct_macros.get_serialized_size( + struct, "CallWithContext(Traits::%s, input, custom_context)", + "context", True)|indent(2)}} + return size; + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buffer, + {{data_type}}** output, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) { + *output = nullptr; + return; + } + + void* custom_context = CustomContextHelper<Traits>::GetNext(context); + + {{struct_macros.serialize( + struct, struct.name ~ " struct", + "CallWithContext(Traits::%s, input, custom_context)", "result", + "buffer", "context", True)|indent(2)}} + *output = result; + + CustomContextHelper<Traits>::TearDown(input, custom_context); + } + + static bool Deserialize({{data_type}}* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists<Traits>(output); + + {{data_view}} data_view(input, context); + return Traits::Read(data_view, output); + } +}; + +} // namespace internal diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl new file mode 100644 index 0000000000..1b7cf8954b --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl @@ -0,0 +1,32 @@ +{%- set mojom_type = struct|get_qualified_name_for_kind %} + +template <> +struct {{export_attribute}} StructTraits<{{mojom_type}}::DataView, + {{mojom_type}}Ptr> { + static bool IsNull(const {{mojom_type}}Ptr& input) { return !input; } + static void SetToNull({{mojom_type}}Ptr* output) { output->reset(); } + +{%- for field in struct.fields %} +{%- set return_ref = field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} +{# We want the field accessor to be const whenever possible to allow + structs to be used as map keys. + TODO(tibell): Make this check more precise to deal with e.g. + custom types which don't contain handles but require non-const + reference for serialization. #} +{%- set maybe_const = "" if field.kind|contains_handles_or_interfaces else "const" %} +{%- if return_ref %} + static {{maybe_const}} decltype({{mojom_type}}::{{field.name}})& {{field.name}}( + {{maybe_const}} {{mojom_type}}Ptr& input) { + return input->{{field.name}}; + } +{%- else %} + static decltype({{mojom_type}}::{{field.name}}) {{field.name}}( + const {{mojom_type}}Ptr& input) { + return input->{{field.name}}; + } +{%- endif %} +{%- endfor %} + + static bool Read({{mojom_type}}::DataView input, {{mojom_type}}Ptr* output); +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl new file mode 100644 index 0000000000..f84337f5bf --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl @@ -0,0 +1,14 @@ +{%- import "struct_macros.tmpl" as struct_macros %} +{%- set mojom_type = struct|get_qualified_name_for_kind %} + +// static +bool StructTraits<{{mojom_type}}::DataView, {{mojom_type}}Ptr>::Read( + {{mojom_type}}::DataView input, + {{mojom_type}}Ptr* output) { + bool success = true; + {{mojom_type}}Ptr result({{mojom_type}}::New()); + {{struct_macros.deserialize(struct, "input", "result->%s", + "success")|indent(4)}} + *output = std::move(result); + return success; +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl new file mode 100644 index 0000000000..5973ba294b --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl @@ -0,0 +1,92 @@ +class {{union.name}}DataView { + public: + using Tag = internal::{{union.name}}_Data::{{union.name}}_Tag; + + {{union.name}}DataView() {} + + {{union.name}}DataView( + internal::{{union.name}}_Data* data, + mojo::internal::SerializationContext* context) +{%- if union|requires_context_for_data_view %} + : data_(data), context_(context) {} +{%- else %} + : data_(data) {} +{%- endif %} + + bool is_null() const { + // For inlined unions, |data_| is always non-null. In that case we need to + // check |data_->is_null()|. + return !data_ || data_->is_null(); + } + + Tag tag() const { return data_->tag; } + +{%- for field in union.fields %} +{%- set kind = field.kind %} +{%- set name = field.name %} + bool is_{{name}}() const { return data_->tag == Tag::{{name|upper}}; } + +{%- if kind|is_object_kind %} + inline void Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output); + + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) { + DCHECK(is_{{name}}()); + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + data_->data.f_{{name}}.Get(), output, context_); + } + +{%- elif kind|is_enum_kind %} + template <typename UserType> + WARN_UNUSED_RESULT bool Read{{name|under_to_camel}}(UserType* output) const { + DCHECK(is_{{name}}()); + return mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + data_->data.f_{{name}}, output); + } + + {{kind|cpp_data_view_type}} {{name}}() const { + DCHECK(is_{{name}}()); + return static_cast<{{kind|cpp_data_view_type}}>( + data_->data.f_{{name}}); + } + +{%- elif kind|is_any_handle_kind %} + {{kind|cpp_data_view_type}} Take{{name|under_to_camel}}() { + DCHECK(is_{{name}}()); + {{kind|cpp_data_view_type}} result; + bool ret = + mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + &data_->data.f_{{name}}, &result, context_); + DCHECK(ret); + return result; + } + +{%- elif kind|is_any_interface_kind %} + template <typename UserType> + UserType Take{{name|under_to_camel}}() { + DCHECK(is_{{name}}()); + UserType result; + bool ret = + mojo::internal::Deserialize<{{kind|unmapped_type_for_serializer}}>( + &data_->data.f_{{name}}, &result, context_); + DCHECK(ret); + return result; + } + +{%- else %} + {{kind|cpp_data_view_type}} {{name}}() const { + DCHECK(is_{{name}}()); + return data_->data.f_{{name}}; + } + +{%- endif %} +{%- endfor %} + + private: + internal::{{union.name}}_Data* data_ = nullptr; +{%- if union|requires_context_for_data_view %} + mojo::internal::SerializationContext* context_ = nullptr; +{%- endif %} +}; + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl new file mode 100644 index 0000000000..6da9280a73 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl @@ -0,0 +1,12 @@ +{%- for field in union.fields %} +{%- set kind = field.kind %} +{%- set name = field.name %} + +{%- if kind|is_object_kind %} +inline void {{union.name}}DataView::Get{{name|under_to_camel}}DataView( + {{kind|cpp_data_view_type}}* output) { + DCHECK(is_{{name}}()); + *output = {{kind|cpp_data_view_type}}(data_->data.f_{{name}}.Get(), context_); +} +{%- endif %} +{%- endfor %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl new file mode 100644 index 0000000000..005ba76b61 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl @@ -0,0 +1,56 @@ +{%- set class_name = union.name ~ "_Data" -%} +{%- set enum_name = union.name ~ "_Tag" -%} +{%- import "struct_macros.tmpl" as struct_macros %} + +class {{class_name}} { + public: + // Used to identify Mojom Union Data Classes. + typedef void MojomUnionDataType; + + {{class_name}}() {} + // Do nothing in the destructor since it won't be called when it is a + // non-inlined union. + ~{{class_name}}() {} + + static {{class_name}}* New(mojo::internal::Buffer* buf) { + return new (buf->Allocate(sizeof({{class_name}}))) {{class_name}}(); + } + + static bool Validate(const void* data, + mojo::internal::ValidationContext* validation_context, + bool inlined); + + bool is_null() const { return size == 0; } + + void set_null() { + size = 0U; + tag = static_cast<{{enum_name}}>(0); + data.unknown = 0U; + } + + enum class {{enum_name}} : uint32_t { +{% for field in union.fields %} + {{field.name|upper}}, +{%- endfor %} + }; + + // A note on layout: + // "Each non-static data member is allocated as if it were the sole member of + // a struct." - Section 9.5.2 ISO/IEC 14882:2011 (The C++ Spec) + union MOJO_ALIGNAS(8) Union_ { +{%- for field in union.fields %} +{%- if field.kind.spec == 'b' %} + uint8_t f_{{field.name}} : 1; +{%- else %} + {{field.kind|cpp_union_field_type}} f_{{field.name}}; +{%- endif %} +{%- endfor %} + uint64_t unknown; + }; + + uint32_t size; + {{enum_name}} tag; + Union_ data; +}; +static_assert(sizeof({{class_name}}) == mojo::internal::kUnionDataSize, + "Bad sizeof({{class_name}})"); diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl new file mode 100644 index 0000000000..af5ea9f8a8 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl @@ -0,0 +1,47 @@ +{%- import "validation_macros.tmpl" as validation_macros %} +{%- set class_name = union.name ~ "_Data" %} +{%- set enum_name = union.name ~ "_Tag" -%} + +// static +bool {{class_name}}::Validate( + const void* data, + mojo::internal::ValidationContext* validation_context, + bool inlined) { + if (!data) { + DCHECK(!inlined); + return true; + } + + // If it is inlined, the alignment is already enforced by its enclosing + // object. We don't have to validate that. + DCHECK(!inlined || mojo::internal::IsAligned(data)); + + if (!inlined && + !mojo::internal::ValidateNonInlinedUnionHeaderAndClaimMemory( + data, validation_context)) { + return false; + } + + const {{class_name}}* object = static_cast<const {{class_name}}*>(data); + ALLOW_UNUSED_LOCAL(object); + + if (inlined && object->is_null()) + return true; + + switch (object->tag) { +{% for field in union.fields %} + case {{enum_name}}::{{field.name|upper}}: { +{%- set field_expr = "object->data.f_" ~ field.name %} +{{validation_macros.validate_field(field, field_expr, union.name, false)|indent(4)}} + return true; + } +{%- endfor %} + default: { + ReportValidationError( + validation_context, + mojo::internal::VALIDATION_ERROR_UNKNOWN_UNION_TAG, + "unknown tag in {{union.name}}"); + return false; + } + } +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl new file mode 100644 index 0000000000..b589ae9147 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl @@ -0,0 +1,141 @@ +{%- set data_view = union|get_qualified_name_for_kind ~ "DataView" %} +{%- set data_type = union|get_qualified_name_for_kind(internal=True) %} + +namespace internal { + +template <typename MaybeConstUserType> +struct Serializer<{{data_view}}, MaybeConstUserType> { + using UserType = typename std::remove_const<MaybeConstUserType>::type; + using Traits = UnionTraits<{{data_view}}, UserType>; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + bool inlined, + SerializationContext* context) { + size_t size = inlined ? 0 : sizeof({{data_type}}); + + if (CallIsNullIfExists<Traits>(input)) + return size; + + void* custom_context = CustomContextHelper<Traits>::SetUp(input, context); + ALLOW_UNUSED_LOCAL(custom_context); + + switch (CallWithContext(Traits::GetTag, input, custom_context)) { +{%- for field in union.fields %} +{%- set name = field.name %} + case {{data_view}}::Tag::{{name|upper}}: { +{%- if field.kind|is_object_kind or field.kind|is_associated_kind %} +{%- set kind = field.kind %} +{%- set serializer_type = kind|unmapped_type_for_serializer %} + decltype(CallWithContext(Traits::{{name}}, input, custom_context)) + in_{{name}} = CallWithContext(Traits::{{name}}, input, + custom_context); +{%- if kind|is_union_kind %} + size += mojo::internal::PrepareToSerialize<{{serializer_type}}>( + in_{{name}}, false, context); +{%- else %} + size += mojo::internal::PrepareToSerialize<{{serializer_type}}>( + in_{{name}}, context); +{%- endif %} +{%- endif %} + break; + } +{%- endfor %} + } + return size; + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buffer, + {{data_type}}** output, + bool inlined, + SerializationContext* context) { + if (CallIsNullIfExists<Traits>(input)) { + if (inlined) + (*output)->set_null(); + else + *output = nullptr; + return; + } + + void* custom_context = CustomContextHelper<Traits>::GetNext(context); + + if (!inlined) + *output = {{data_type}}::New(buffer); + + {{data_type}}* result = *output; + ALLOW_UNUSED_LOCAL(result); + // TODO(azani): Handle unknown and objects. + // Set the not-null flag. + result->size = kUnionDataSize; + result->tag = CallWithContext(Traits::GetTag, input, custom_context); + switch (result->tag) { +{%- for field in union.fields %} +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- set serializer_type = kind|unmapped_type_for_serializer %} + case {{data_view}}::Tag::{{field.name|upper}}: { + decltype(CallWithContext(Traits::{{name}}, input, custom_context)) + in_{{name}} = CallWithContext(Traits::{{name}}, input, + custom_context); +{%- if kind|is_object_kind %} + typename decltype(result->data.f_{{name}})::BaseType* ptr; +{%- if kind|is_union_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, buffer, &ptr, false, context); +{%- elif kind|is_array_kind or kind|is_map_kind %} + const ContainerValidateParams {{name}}_validate_params( + {{kind|get_container_validate_params_ctor_args|indent(16)}}); + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, buffer, &ptr, &{{name}}_validate_params, context); +{%- else %} + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, buffer, &ptr, context); +{%- endif %} + result->data.f_{{name}}.Set(ptr); +{%- if not kind|is_nullable_kind %} + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !ptr, mojo::internal::VALIDATION_ERROR_UNEXPECTED_NULL_POINTER, + "null {{name}} in {{union.name}} union"); +{%- endif %} + +{%- elif kind|is_any_handle_or_interface_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, &result->data.f_{{name}}, context); +{%- if not kind|is_nullable_kind %} + MOJO_INTERNAL_DLOG_SERIALIZATION_WARNING( + !mojo::internal::IsHandleOrInterfaceValid(result->data.f_{{name}}), +{%- if kind|is_associated_kind %} + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_INTERFACE_ID, +{%- else %} + mojo::internal::VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE, +{%- endif %} + "invalid {{name}} in {{union.name}} union"); +{%- endif %} + +{%- elif kind|is_enum_kind %} + mojo::internal::Serialize<{{serializer_type}}>( + in_{{name}}, &result->data.f_{{name}}); + +{%- else %} + result->data.f_{{name}} = in_{{name}}; +{%- endif %} + break; + } +{%- endfor %} + } + + CustomContextHelper<Traits>::TearDown(input, custom_context); + } + + static bool Deserialize({{data_type}}* input, + UserType* output, + SerializationContext* context) { + if (!input || input->is_null()) + return CallSetToNullIfExists<Traits>(output); + + {{data_view}} data_view(input, context); + return Traits::Read(data_view, output); + } +}; + +} // namespace internal diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl new file mode 100644 index 0000000000..4933e57871 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl @@ -0,0 +1,24 @@ +{%- set mojom_type = union|get_qualified_name_for_kind %} + +template <> +struct {{export_attribute}} UnionTraits<{{mojom_type}}::DataView, + {{mojom_type}}Ptr> { + static bool IsNull(const {{mojom_type}}Ptr& input) { return !input; } + static void SetToNull({{mojom_type}}Ptr* output) { output->reset(); } + + static {{mojom_type}}::Tag GetTag(const {{mojom_type}}Ptr& input) { + return input->which(); + } + +{%- for field in union.fields %} +{%- set maybe_const_in = "" if field.kind|contains_handles_or_interfaces else "const" %} +{%- set maybe_const_out = "" if field.kind|contains_handles_or_interfaces or not field.kind|is_reference_kind else "const" %} +{# We want the field accessor to be const whenever possible to allow + structs to be used as map keys. #} + static {{maybe_const_out}} {{field.kind|cpp_union_trait_getter_return_type}} {{field.name}}({{maybe_const_in}} {{mojom_type}}Ptr& input) { + return input->get_{{field.name}}(); + } +{%- endfor %} + + static bool Read({{mojom_type}}::DataView input, {{mojom_type}}Ptr* output); +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl new file mode 100644 index 0000000000..cde3f95669 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl @@ -0,0 +1,47 @@ +{%- set mojom_type = union|get_qualified_name_for_kind %} + +// static +bool UnionTraits<{{mojom_type}}::DataView, {{mojom_type}}Ptr>::Read( + {{mojom_type}}::DataView input, + {{mojom_type}}Ptr* output) { + *output = {{mojom_type}}::New(); + {{mojom_type}}Ptr& result = *output; + + internal::UnionAccessor<{{mojom_type}}> result_acc(result.get()); + switch (input.tag()) { +{%- for field in union.fields %} + case {{mojom_type}}::Tag::{{field.name|upper}}: { +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- set serializer_type = kind|unmapped_type_for_serializer %} +{%- if kind|is_object_kind %} + result_acc.SwitchActive({{mojom_type}}::Tag::{{name|upper}}); + if (!input.Read{{name|under_to_camel}}(result_acc.data()->{{name}})) + return false; + +{%- elif kind|is_any_handle_kind %} + auto result_{{name}} = input.Take{{name|under_to_camel}}(); + result->set_{{name}}(std::move(result_{{name}})); + +{%- elif kind|is_any_interface_kind %} + auto result_{{name}} = + input.Take{{name|under_to_camel}}<typename std::remove_reference<decltype(result->get_{{name}}())>::type>(); + result->set_{{name}}(std::move(result_{{name}})); + +{%- elif kind|is_enum_kind %} + decltype(result->get_{{name}}()) result_{{name}}; + if (!input.Read{{name|under_to_camel}}(&result_{{name}})) + return false; + result->set_{{name}}(result_{{name}}); + +{%- else %} + result->set_{{name}}(input.{{name}}()); +{%- endif %} + break; + } +{%- endfor %} + default: + return false; + } + return true; +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl new file mode 100644 index 0000000000..a50a585c09 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl @@ -0,0 +1,82 @@ +{#- Validates the specified field, which is supposed to be an object + (struct/array/string/map/union). If it is a union, |union_is_inlined| + indicates whether the union is inlined. (Nested unions are not inlined.) + This macro is expanded by the Validate() method. #} +{%- macro validate_object(field, field_expr, object_name, union_is_inlined) %} +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- if not kind|is_nullable_kind %} +{%- if kind|is_union_kind and union_is_inlined %} + if (!mojo::internal::ValidateInlinedUnionNonNullable( + {{field_expr}}, "null {{name}} field in {{object_name}}", + validation_context)) { + return false; + } +{%- else %} + if (!mojo::internal::ValidatePointerNonNullable( + {{field_expr}}, "null {{name}} field in {{object_name}}", + validation_context)) { + return false; + } +{%- endif %} +{%- endif %} +{%- if kind|is_array_kind or kind|is_string_kind or kind|is_map_kind %} + const mojo::internal::ContainerValidateParams {{name}}_validate_params( + {{kind|get_container_validate_params_ctor_args|indent(6)}}); + if (!mojo::internal::ValidateContainer({{field_expr}}, validation_context, + &{{name}}_validate_params)) { + return false; + } +{%- elif kind|is_struct_kind %} + if (!mojo::internal::ValidateStruct({{field_expr}}, validation_context)) + return false; +{%- elif kind|is_union_kind %} +{%- if union_is_inlined %} + if (!mojo::internal::ValidateInlinedUnion({{field_expr}}, validation_context)) + return false; +{%- else %} + if (!mojo::internal::ValidateNonInlinedUnion({{field_expr}}, + validation_context)) + return false; +{%- endif %} +{%- else %} +#error Not reached! +{%- endif %} +{%- endmacro %} + +{#- Validates the specified field, which is supposed to be a handle, + an interface, an associated interface or an associated interface request. + This macro is expanded by the Validate() method. #} +{%- macro validate_handle_or_interface(field, field_expr, object_name) %} +{%- set name = field.name %} +{%- set kind = field.kind %} +{%- if not kind|is_nullable_kind %} + if (!mojo::internal::ValidateHandleOrInterfaceNonNullable( + {{field_expr}}, + "invalid {{name}} field in {{object_name}}", validation_context)) { + return false; + } +{%- endif %} + if (!mojo::internal::ValidateHandleOrInterface({{field_expr}}, + validation_context)) { + return false; + } +{%- endmacro %} + +{#- Validates the specified field, which is supposed to be an enum. + This macro is expanded by the Validate() method. #} +{%- macro validate_enum(field, field_expr) %} + if (!{{field.kind|get_qualified_name_for_kind(internal=True,flatten_nested_kind=True)}} + ::Validate({{field_expr}}, validation_context)) + return false; +{%- endmacro %} + +{%- macro validate_field(field, field_expr, object_name, union_is_inlined) %} +{%- if field.kind|is_object_kind -%} +{{validate_object(field, field_expr, object_name, union_is_inlined)}} +{%- elif field.kind|is_any_handle_or_interface_kind -%} +{{validate_handle_or_interface(field, field_expr, object_name)}} +{%- elif field.kind|is_enum_kind %} +{{validate_enum(field, field_expr)}} +{%- endif %} +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl new file mode 100644 index 0000000000..7ad9b4e1bc --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl @@ -0,0 +1,94 @@ +class {{export_attribute}} {{struct.name}} { + public: + using DataView = {{struct.name}}DataView; + using Data_ = internal::{{struct.name}}_Data; + +{#--- Enums #} +{%- for enum in struct.enums -%} + using {{enum.name}} = {{enum|get_name_for_kind(flatten_nested_kind=True)}}; +{%- endfor %} + +{#--- Constants #} +{%- for constant in struct.constants %} + static {{constant|format_constant_declaration(nested=True)}}; +{%- endfor %} + + template <typename... Args> + static {{struct.name}}Ptr New(Args&&... args) { + return {{struct.name}}Ptr( + base::in_place, + std::forward<Args>(args)...); + } + + template <typename U> + static {{struct.name}}Ptr From(const U& u) { + return mojo::TypeConverter<{{struct.name}}Ptr, U>::Convert(u); + } + + template <typename U> + U To() const { + return mojo::TypeConverter<U, {{struct.name}}>::Convert(*this); + } + +{% for constructor in struct|struct_constructors %} + {% if constructor.params|length == 1 %}explicit {% endif %}{{struct.name}}( +{%- for field in constructor.params %} +{%- set type = field.kind|cpp_wrapper_param_type %} +{%- set name = field.name %} + {{type}} {{name}} +{%- if not loop.last -%},{%- endif %} +{%- endfor %}); +{% endfor %} + ~{{struct.name}}(); + + // Clone() is a template so it is only instantiated if it is used. Thus, the + // bindings generator does not need to know whether Clone() or copy + // constructor/assignment are available for members. + template <typename StructPtrType = {{struct.name}}Ptr> + {{struct.name}}Ptr Clone() const; + + // Equals() is a template so it is only instantiated if it is used. Thus, the + // bindings generator does not need to know whether Equals() or == operator + // are available for members. + template <typename T, + typename std::enable_if<std::is_same< + T, {{struct.name}}>::value>::type* = nullptr> + bool Equals(const T& other) const; + +{%- if struct|is_hashable %} + size_t Hash(size_t seed) const; +{%- endif %} + +{%- set serialization_result_type = "WTF::Vector<uint8_t>" + if for_blink else "std::vector<uint8_t>" %} + + template <typename UserType> + static {{serialization_result_type}} Serialize(UserType* input) { + return mojo::internal::StructSerializeImpl< + {{struct.name}}::DataView, {{serialization_result_type}}>(input); + } + + template <typename UserType> + static bool Deserialize(const {{serialization_result_type}}& input, + UserType* output) { + return mojo::internal::StructDeserializeImpl< + {{struct.name}}::DataView, {{serialization_result_type}}>( + input, output, Validate); + } + +{#--- Struct members #} +{% for field in struct.fields %} +{%- set type = field.kind|cpp_wrapper_type %} +{%- set name = field.name %} + {{type}} {{name}}; +{%- endfor %} + + private: + static bool Validate(const void* data, + mojo::internal::ValidationContext* validation_context); + +{%- if struct|contains_move_only_members %} + DISALLOW_COPY_AND_ASSIGN({{struct.name}}); +{%- endif %} +}; + diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl new file mode 100644 index 0000000000..ab8c22d49c --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl @@ -0,0 +1,39 @@ +{% for constructor in struct|struct_constructors %} +{{struct.name}}::{{struct.name}}( +{%- for field in constructor.params %} +{%- set type = field.kind|cpp_wrapper_param_type %} +{%- set name = field.name %} + {{type}} {{name}}_in +{%- if not loop.last -%},{%- endif %} +{%- endfor %}) +{%- for field, is_parameter in constructor.fields %} +{%- set name = field.name %} + {% if loop.first %}:{% else %} {% endif %} {{name}}( +{%- if is_parameter -%} +std::move({{name}}_in) +{%- else -%} +{{ field|default_value }} +{%- endif -%} +){% if not loop.last %},{% endif %} +{%- endfor %} {} +{% endfor %} +{{struct.name}}::~{{struct.name}}() = default; + +{%- if struct|is_hashable %} +size_t {{struct.name}}::Hash(size_t seed) const { +{%- for field in struct.fields %} +{%- if for_blink %} + seed = mojo::internal::WTFHash(seed, this->{{field.name}}); +{%- else %} + seed = mojo::internal::Hash(seed, this->{{field.name}}); +{%- endif %} +{%- endfor %} + return seed; +} +{%- endif %} + +bool {{struct.name}}::Validate( + const void* data, + mojo::internal::ValidationContext* validation_context) { + return Data_::Validate(data, validation_context); +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl new file mode 100644 index 0000000000..feb861569f --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl @@ -0,0 +1,20 @@ +template <typename StructPtrType> +{{struct.name}}Ptr {{struct.name}}::Clone() const { + return New( +{%- for field in struct.fields %} + mojo::Clone({{field.name}}) +{%- if not loop.last -%},{%- endif %} +{%- endfor %} + ); +} + +template <typename T, + typename std::enable_if<std::is_same< + T, {{struct.name}}>::value>::type*> +bool {{struct.name}}::Equals(const T& other) const { +{%- for field in struct.fields %} + if (!mojo::internal::Equals(this->{{field.name}}, other.{{field.name}})) + return false; +{%- endfor %} + return true; +} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl new file mode 100644 index 0000000000..8b7cf9e6b1 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl @@ -0,0 +1,80 @@ +class {{export_attribute}} {{union.name}} { + public: + using DataView = {{union.name}}DataView; + using Data_ = internal::{{union.name}}_Data; + using Tag = Data_::{{union.name}}_Tag; + + static {{union.name}}Ptr New(); + + template <typename U> + static {{union.name}}Ptr From(const U& u) { + return mojo::TypeConverter<{{union.name}}Ptr, U>::Convert(u); + } + + template <typename U> + U To() const { + return mojo::TypeConverter<U, {{union.name}}>::Convert(*this); + } + + {{union.name}}(); + ~{{union.name}}(); + + // Clone() is a template so it is only instantiated if it is used. Thus, the + // bindings generator does not need to know whether Clone() or copy + // constructor/assignment are available for members. + template <typename UnionPtrType = {{union.name}}Ptr> + {{union.name}}Ptr Clone() const; + + // Equals() is a template so it is only instantiated if it is used. Thus, the + // bindings generator does not need to know whether Equals() or == operator + // are available for members. + template <typename T, + typename std::enable_if<std::is_same< + T, {{union.name}}>::value>::type* = nullptr> + bool Equals(const T& other) const; + +{%- if union|is_hashable %} + size_t Hash(size_t seed) const; +{%- endif %} + + Tag which() const { + return tag_; + } + +{% for field in union.fields %} + bool is_{{field.name}}() const { return tag_ == Tag::{{field.name|upper}}; } + + {{field.kind|cpp_union_getter_return_type}} get_{{field.name}}() const { + DCHECK(tag_ == Tag::{{field.name|upper}}); +{%- if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + return *(data_.{{field.name}}); +{%- else %} + return data_.{{field.name}}; +{%- endif %} + } + + void set_{{field.name}}({{field.kind|cpp_wrapper_param_type}} {{field.name}}); +{%- endfor %} + + private: + friend class mojo::internal::UnionAccessor<{{union.name}}>; + union Union_ { + Union_() {} + ~Union_() {} + +{%- for field in union.fields %} +{%- if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + {{field.kind|cpp_wrapper_type}}* {{field.name}}; +{%- else %} + {{field.kind|cpp_wrapper_type}} {{field.name}}; +{%- endif %} +{%- endfor %} + }; + void SwitchActive(Tag new_active); + void SetActive(Tag new_active); + void DestroyActive(); + Tag tag_; + Union_ data_; +}; diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl new file mode 100644 index 0000000000..b9e416a9f4 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl @@ -0,0 +1,85 @@ +// static +{{union.name}}Ptr {{union.name}}::New() { + return {{union.name}}Ptr(base::in_place); +} + +{{union.name}}::{{union.name}}() { + // TODO(azani): Implement default values here when/if we support them. + // TODO(azani): Set to UNKNOWN when unknown is implemented. + SetActive(static_cast<Tag>(0)); +} + +{{union.name}}::~{{union.name}}() { + DestroyActive(); +} + +{% for field in union.fields %} +void {{union.name}}::set_{{field.name}}({{field.kind|cpp_wrapper_param_type}} {{field.name}}) { + SwitchActive(Tag::{{field.name|upper}}); +{% if field.kind|is_string_kind %} + *(data_.{{field.name}}) = {{field.name}}; +{% elif field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + *(data_.{{field.name}}) = std::move({{field.name}}); +{%- else %} + data_.{{field.name}} = {{field.name}}; +{%- endif %} +} +{%- endfor %} + +void {{union.name}}::SwitchActive(Tag new_active) { + if (new_active == tag_) { + return; + } + + DestroyActive(); + SetActive(new_active); +} + +void {{union.name}}::SetActive(Tag new_active) { + switch (new_active) { +{% for field in union.fields %} + case Tag::{{field.name|upper}}: +{% if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + data_.{{field.name}} = new {{field.kind|cpp_wrapper_type}}(); +{%- endif %} + break; +{%- endfor %} + } + + tag_ = new_active; +} + +void {{union.name}}::DestroyActive() { + switch (tag_) { +{% for field in union.fields %} + case Tag::{{field.name|upper}}: +{% if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + delete data_.{{field.name}}; +{%- endif %} + break; +{%- endfor %} + } +} + +{%- if union|is_hashable %} +size_t {{union.name}}::Hash(size_t seed) const { + seed = mojo::internal::HashCombine(seed, static_cast<uint32_t>(tag_)); + switch (tag_) { +{% for field in union.fields %} + case Tag::{{field.name|upper}}: +{%- if for_blink %} + return mojo::internal::WTFHash(seed, data_.{{field.name}}); +{%- else %} + return mojo::internal::Hash(seed, data_.{{field.name}}); +{%- endif %} +{%- endfor %} + default: + NOTREACHED(); + return seed; + } +} + +{%- endif %} diff --git a/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl new file mode 100644 index 0000000000..4c4851fa83 --- /dev/null +++ b/mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl @@ -0,0 +1,41 @@ +template <typename UnionPtrType> +{{union.name}}Ptr {{union.name}}::Clone() const { + // Use UnionPtrType to prevent the compiler from trying to compile this + // without being asked. + UnionPtrType rv(New()); + switch (tag_) { +{%- for field in union.fields %} + case Tag::{{field.name|upper}}: +{%- if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + rv->set_{{field.name}}(mojo::Clone(*data_.{{field.name}})); +{%- else %} + rv->set_{{field.name}}(mojo::Clone(data_.{{field.name}})); +{%- endif %} + break; +{%- endfor %} + }; + return rv; +} + +template <typename T, + typename std::enable_if<std::is_same< + T, {{union.name}}>::value>::type*> +bool {{union.name}}::Equals(const T& other) const { + if (tag_ != other.which()) + return false; + + switch (tag_) { +{%- for field in union.fields %} + case Tag::{{field.name|upper}}: +{%- if field.kind|is_object_kind or + field.kind|is_any_handle_or_interface_kind %} + return mojo::internal::Equals(*(data_.{{field.name}}), *(other.data_.{{field.name}})); +{%- else %} + return mojo::internal::Equals(data_.{{field.name}}, other.data_.{{field.name}}); +{%- endif %} +{%- endfor %} + }; + + return false; +} diff --git a/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl new file mode 100644 index 0000000000..db193e29a3 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl @@ -0,0 +1,3 @@ +{% macro constant_def(constant) %} +public static final {{constant.kind|java_type}} {{constant|name}} = {{constant|constant_value}}; +{% endmacro %} diff --git a/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl new file mode 100644 index 0000000000..0a4e29956b --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl @@ -0,0 +1,12 @@ +{% from "constant_definition.tmpl" import constant_def %} +{% include "header.java.tmpl" %} + +public final class {{main_entity}} { +{% for constant in constants %} + + {{constant_def(constant)|indent(4)}} +{% endfor %} + + private {{main_entity}}() {} + +} diff --git a/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl new file mode 100644 index 0000000000..4c0823cce6 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl @@ -0,0 +1,418 @@ +{%- from "constant_definition.tmpl" import constant_def %} +{%- from "enum_definition.tmpl" import enum_def %} + +{%- macro equality(kind, v1, v2, ne=False) -%} +{%- if kind|is_reference_kind -%} +{%- if kind|is_array_kind -%} +{%- if kind.kind|is_reference_kind -%} +{%- if ne %}!{%- endif %}java.util.Arrays.deepEquals({{v1}}, {{v2}}) +{%- else -%} +{%- if ne %}!{%- endif %}java.util.Arrays.equals({{v1}}, {{v2}}) +{%- endif -%} +{%- else -%} +{%- if ne %}!{%- endif %}org.chromium.mojo.bindings.BindingsHelper.equals({{v1}}, {{v2}}) +{%- endif -%} +{%- else -%} +{{v1}} {%- if ne %}!={%- else %}=={%- endif %} {{v2}} +{%- endif -%} +{%- endmacro -%} + +{%- macro hash(kind, v) -%} +{%- if kind|is_array_kind -%} +{%- if kind.kind|is_reference_kind -%} +java.util.Arrays.deepHashCode({{v}}) +{%- else -%} +java.util.Arrays.hashCode({{v}}) +{%- endif -%} +{%- else -%} +org.chromium.mojo.bindings.BindingsHelper.hashCode({{v}}) +{%- endif -%} +{%- endmacro -%} + +{%- macro array_element_size(kind) -%} +{%- if kind|is_union_kind %} +org.chromium.mojo.bindings.BindingsHelper.UNION_SIZE +{%- else -%} +org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE +{%- endif -%} +{%- endmacro -%} + +{%- macro encode(variable, kind, offset, bit, level=0, check_for_null=True) %} +{%- if kind|is_pointer_array_kind or kind|is_union_array_kind %} +{%- set sub_kind = kind.kind %} +{%- if check_for_null %} +if ({{variable}} == null) { + encoder{{level}}.encodeNullPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}}); +} else { +{%- else %} +{ +{%- endif %} +{%- if kind|is_pointer_array_kind %} +{%- set encodePointer = 'encodePointerArray' %} +{%- else %} +{%- set encodePointer = 'encodeUnionArray' %} +{%- endif %} + org.chromium.mojo.bindings.Encoder encoder{{level + 1}} = encoder{{level}}.{{encodePointer}}({{variable}}.length, {{offset}}, {{kind|array_expected_length}}); + for (int i{{level}} = 0; i{{level}} < {{variable}}.length; ++i{{level}}) { + {{encode(variable~'[i'~level~']', sub_kind, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + ' ~ array_element_size(sub_kind) ~ ' * i'~level, 0, level+1)|indent(8)}} + } +} +{%- elif kind|is_map_kind %} +if ({{variable}} == null) { + encoder{{level}}.encodeNullPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}}); +} else { + org.chromium.mojo.bindings.Encoder encoder{{level + 1}} = encoder{{level}}.encoderForMap({{offset}}); + int size{{level}} = {{variable}}.size(); + {{kind.key_kind|java_type}}[] keys{{level}} = {{kind.key_kind|array|new_array('size'~level)}}; + {{kind.value_kind|java_type}}[] values{{level}} = {{kind.value_kind|array|new_array('size'~level)}}; + int index{{level}} = 0; + for (java.util.Map.Entry<{{kind.key_kind|java_type(true)}}, {{kind.value_kind|java_type(true)}}> entry{{level}} : {{variable}}.entrySet()) { + keys{{level}}[index{{level}}] = entry{{level}}.getKey(); + values{{level}}[index{{level}}] = entry{{level}}.getValue(); + ++index{{level}}; + } + {{encode('keys'~level, kind.key_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0, level+1, False)|indent(4)}} + {{encode('values'~level, kind.value_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE', 0, level+1, False)|indent(4)}} +} +{%- else %} +encoder{{level}}.{{kind|encode_method(variable, offset, bit)}}; +{%- endif %} +{%- endmacro %} + +{%- macro decode(variable, kind, offset, bit, level=0) %} +{%- if kind|is_struct_kind or + kind|is_pointer_array_kind or + kind|is_union_array_kind or + kind|is_map_kind %} +org.chromium.mojo.bindings.Decoder decoder{{level+1}} = decoder{{level}}.readPointer({{offset}}, {{kind|is_nullable_kind|java_true_false}}); +{%- if kind|is_struct_kind %} +{{variable}} = {{kind|java_type}}.decode(decoder{{level+1}}); +{%- else %}{# kind|is_pointer_array_kind or is_map_kind #} +{%- if kind|is_nullable_kind %} +if (decoder{{level+1}} == null) { + {{variable}} = null; +} else { +{%- else %} +{ +{%- endif %} +{%- if kind|is_map_kind %} + decoder{{level+1}}.readDataHeaderForMap(); + {{kind.key_kind|java_type}}[] keys{{level}}; + {{kind.value_kind|java_type}}[] values{{level}}; + { + {{decode('keys'~level, kind.key_kind|array, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0, level+1)|indent(8)}} + } + { + {{decode('values'~level, kind.value_kind|array('keys'~level~'.length'), 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + org.chromium.mojo.bindings.BindingsHelper.POINTER_SIZE', 0, level+1)|indent(8)}} + } + {{variable}} = new java.util.HashMap<{{kind.key_kind|java_type(true)}}, {{kind.value_kind|java_type(true)}}>(); + for (int index{{level}} = 0; index{{level}} < keys{{level}}.length; ++index{{level}}) { + {{variable}}.put(keys{{level}}[index{{level}}], values{{level}}[index{{level}}]); + } +{%- else %} + org.chromium.mojo.bindings.DataHeader si{{level+1}} = decoder{{level+1}}.readDataHeaderForPointerArray({{kind|array_expected_length}}); + {{variable}} = {{kind|new_array('si'~(level+1)~'.elementsOrVersion')}}; + for (int i{{level+1}} = 0; i{{level+1}} < si{{level+1}}.elementsOrVersion; ++i{{level+1}}) { + {{decode(variable~'[i'~(level+1)~']', kind.kind, 'org.chromium.mojo.bindings.DataHeader.HEADER_SIZE + ' ~ array_element_size(kind.kind) ~' * i'~(level+1), 0, level+1)|indent(8)}} + } +{%- endif %} +} +{%- endif %} +{%- elif kind|is_union_kind %} +{{variable}} = {{kind|java_type}}.decode(decoder{{level}}, {{offset}}); +{%- else %} +{{variable}} = decoder{{level}}.{{kind|decode_method(offset, bit)}}; +{%- if kind|is_array_kind and kind.kind|is_enum_kind %} +{%- if kind|is_nullable_kind %} +if ({{variable}} != null) { +{%- else %} +{ +{%- endif %} + for (int i{{level}} = 0; i{{level}} < {{variable}}.length; ++i{{level}}) { + {{kind.kind|java_class_for_enum}}.validate({{variable}}[i{{level}}]); + } +} +{%- elif kind|is_enum_kind %} + {{kind|java_class_for_enum}}.validate({{variable}}); +{%- endif %} +{%- endif %} +{%- endmacro %} + +{%- macro struct_def(struct, inner_class=False) %} +{{'static' if inner_class else 'public'}} final class {{struct|name}} extends org.chromium.mojo.bindings.Struct { + + private static final int STRUCT_SIZE = {{struct.versions[-1].num_bytes}}; + private static final org.chromium.mojo.bindings.DataHeader[] VERSION_ARRAY = new org.chromium.mojo.bindings.DataHeader[] { +{%- for version in struct.versions -%} + new org.chromium.mojo.bindings.DataHeader({{version.num_bytes}}, {{version.version}}){%- if not loop.last %}, {%- endif -%} +{%- endfor -%} + }; + private static final org.chromium.mojo.bindings.DataHeader DEFAULT_STRUCT_INFO = VERSION_ARRAY[{{struct.versions|length - 1}}]; +{%- for constant in struct.constants %} + + {{constant_def(constant)|indent(4)}} +{%- endfor %} +{%- for enum in struct.enums %} + + {{enum_def(enum, false)|indent(4)}} +{%- endfor %} +{%- if struct.fields %} + +{%- for field in struct.fields %} + public {{field.kind|java_type}} {{field|name}}; +{%- endfor %} +{%- endif %} + + private {{struct|name}}(int version) { + super(STRUCT_SIZE, version); +{%- for field in struct.fields %} +{%- if field.default %} + {{field|name}} = {{field|default_value}}; +{%- elif field.kind|is_any_handle_kind %} + {{field|name}} = org.chromium.mojo.system.InvalidHandle.INSTANCE; +{%- endif %} +{%- endfor %} + } + + public {{struct|name}}() { + this({{struct.versions[-1].version}}); + } + + public static {{struct|name}} deserialize(org.chromium.mojo.bindings.Message message) { + return decode(new org.chromium.mojo.bindings.Decoder(message)); + } + + /** + * Similar to the method above, but deserializes from a |ByteBuffer| instance. + * + * @throws org.chromium.mojo.bindings.DeserializationException on deserialization failure. + */ + public static {{struct|name}} deserialize(java.nio.ByteBuffer data) { + if (data == null) + return null; + + return deserialize(new org.chromium.mojo.bindings.Message( + data, new java.util.ArrayList<org.chromium.mojo.system.Handle>())); + } + + @SuppressWarnings("unchecked") + public static {{struct|name}} decode(org.chromium.mojo.bindings.Decoder decoder0) { + if (decoder0 == null) { + return null; + } + decoder0.increaseStackDepth(); + {{struct|name}} result; + try { + org.chromium.mojo.bindings.DataHeader mainDataHeader = decoder0.readAndValidateDataHeader(VERSION_ARRAY); + result = new {{struct|name}}(mainDataHeader.elementsOrVersion); +{%- for byte in struct.bytes %} +{%- for packed_field in byte.packed_fields %} + if (mainDataHeader.elementsOrVersion >= {{packed_field.min_version}}) { + {{decode('result.' ~ packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(16)}} + } +{%- endfor %} +{%- endfor %} + } finally { + decoder0.decreaseStackDepth(); + } + return result; + } + + @SuppressWarnings("unchecked") + @Override + protected final void encode(org.chromium.mojo.bindings.Encoder encoder) { +{%- if not struct.bytes %} + encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO); +{%- else %} + org.chromium.mojo.bindings.Encoder encoder0 = encoder.getEncoderAtDataOffset(DEFAULT_STRUCT_INFO); +{%- endif %} +{%- for byte in struct.bytes %} +{%- for packed_field in byte.packed_fields %} + {{encode(packed_field.field|name, packed_field.field.kind, 8+packed_field.offset, packed_field.bit)|indent(8)}} +{%- endfor %} +{%- endfor %} + } + + /** + * @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; +{%- if struct.fields|length %} + {{struct|name}} other = ({{struct|name}}) object; +{%- for field in struct.fields %} + if ({{equality(field.kind, 'this.'~field|name, 'other.'~field|name, True)}}) + return false; +{%- endfor %} +{%- endif %} + return true; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = prime + getClass().hashCode(); +{%- for field in struct.fields %} + result = prime * result + {{hash(field.kind, field|name)}}; +{%- endfor %} + return result; + } +} +{%- endmacro %} + + +{%- macro union_def(union) %} +public final class {{union|name}} extends org.chromium.mojo.bindings.Union { + + public static final class Tag { +{%- for field in union.fields %} + public static final int {{field|ucc}} = {{loop.index0}}; +{%- endfor %} + }; + + private int mTag_ = -1; +{%- for field in union.fields %} + private {{field.kind|java_type}} m{{field|ucc}}; +{%- endfor %} + + public int which() { + return mTag_; + } + + public boolean isUnknown() { + return mTag_ == -1; + } +{%- for field in union.fields %} + + // TODO(rockot): Fix the findbugs error and remove this suppression. + // See http://crbug.com/570386. + @SuppressFBWarnings("EI_EXPOSE_REP2") + public void set{{field|ucc}}({{field.kind|java_type}} {{field|name}}) { + mTag_ = Tag.{{field|ucc}}; + m{{field|ucc}} = {{field|name}}; + } + + // TODO(rockot): Fix the findbugs error and remove this suppression. + // See http://crbug.com/570386. + @SuppressFBWarnings("EI_EXPOSE_REP") + public {{field.kind|java_type}} get{{field|ucc}}() { + assert mTag_ == Tag.{{field|ucc}}; + return m{{field|ucc}}; + } +{%- endfor %} + + + @Override + protected final void encode(org.chromium.mojo.bindings.Encoder encoder0, int offset) { + encoder0.encode(org.chromium.mojo.bindings.BindingsHelper.UNION_SIZE, offset); + encoder0.encode(mTag_, offset + 4); + switch (mTag_) { +{%- for field in union.fields %} + case Tag.{{field|ucc}}: { +{%- if field.kind|is_union_kind %} + if (m{{field|ucc}} == null) { + encoder0.encodeNullPointer(offset + 8, {{field.kind|is_nullable_kind|java_true_false}}); + } else { + m{{field|ucc}}.encode(encoder0.encoderForUnionPointer(offset + 8), 0); + } +{%- else %} + {{encode('m' ~ field|ucc, field.kind, 'offset + 8', 0)|indent(16)}} +{%- endif %} + break; + } +{%- endfor %} + default: { + break; + } + } + } + + public static {{union|name}} deserialize(org.chromium.mojo.bindings.Message message) { + return decode(new org.chromium.mojo.bindings.Decoder(message).decoderForSerializedUnion(), 0); + } + + public static final {{union|name}} decode(org.chromium.mojo.bindings.Decoder decoder0, int offset) { + org.chromium.mojo.bindings.DataHeader dataHeader = decoder0.readDataHeaderForUnion(offset); + if (dataHeader.size == 0) { + return null; + } + {{union|name}} result = new {{union|name}}(); + switch (dataHeader.elementsOrVersion) { +{%- for field in union.fields %} + case Tag.{{field|ucc}}: { +{%- if field.kind|is_union_kind %} + org.chromium.mojo.bindings.Decoder decoder1 = decoder0.readPointer(offset + org.chromium.mojo.bindings.DataHeader.HEADER_SIZE, {{field.kind|is_nullable_kind|java_true_false}}); + if (decoder1 != null) { + result.m{{field|ucc}} = {{field.kind|name}}.decode(decoder1, 0); + } +{%- else %} + {{decode('result.m'~field|ucc, field.kind, 'offset + org.chromium.mojo.bindings.DataHeader.HEADER_SIZE', 0)|indent(16)}} +{%- endif %} + result.mTag_ = Tag.{{field|ucc}}; + break; + } +{%- endfor %} + default: { + break; + } + } + 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; + {{union|name}} other = ({{union|name}}) object; + if (mTag_ != other.mTag_) + return false; + switch (mTag_) { +{%- for field in union.fields %} + case Tag.{{field|ucc}}: + return {{equality(field.kind, 'm'~field|ucc, 'other.m'~field|ucc)}}; +{%- endfor %} + default: + break; + } + return false; + } + + /** + * @see Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = prime + getClass().hashCode(); + result = prime * result + org.chromium.mojo.bindings.BindingsHelper.hashCode(mTag_); + switch (mTag_) { +{%- for field in union.fields %} + case Tag.{{field|ucc}}: { + result = prime * result + {{hash(field.kind, 'm'~field|ucc)}}; + break; + } +{%- endfor %} + default: { + break; + } + } + return result; + } +} +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl new file mode 100644 index 0000000000..7096a18747 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl @@ -0,0 +1,4 @@ +{% from "enum_definition.tmpl" import enum_def %} +{% include "header.java.tmpl" %} + +{{enum_def(enum, true)}} diff --git a/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl new file mode 100644 index 0000000000..d37288ac5d --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl @@ -0,0 +1,42 @@ +{%- macro enum_value(enum, field, index) -%} +{%- if field.value -%} +(int) ({{field.value|expression_to_text('i32')}}) +{%- elif index == 0 -%} +0 +{%- else -%} +{{enum.fields[index - 1]|name}} + 1 +{%- endif -%} +{%- endmacro -%} + +{%- macro enum_def(enum, top_level) -%} +public {{ 'static ' if not top_level }}final class {{enum|name}} { + +{% for field in enum.fields %} + public static final int {{field|name}} = {{enum_value(enum, field, loop.index0)}}; +{% endfor %} + + private static final boolean IS_EXTENSIBLE = {% if enum.extensible %}true{% else %}false{% endif %}; + + public static boolean isKnownValue(int value) { +{%- if enum.fields %} + switch (value) { +{%- for enum_field in enum.fields|groupby('numeric_value') %} + case {{enum_field[0]}}: +{%- endfor %} + return true; + } +{%- endif %} + return false; + } + + public static void validate(int value) { + if (IS_EXTENSIBLE || isKnownValue(value)) + return; + + throw new DeserializationException("Invalid enum value."); + } + + private {{enum|name}}() {} + +} +{%- endmacro -%} diff --git a/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl new file mode 100644 index 0000000000..1d67890452 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/header.java.tmpl @@ -0,0 +1,14 @@ +// 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 is autogenerated by: +// mojo/public/tools/bindings/mojom_bindings_generator.py +// For: +// {{module.path}} +// + +package {{package}}; + +import org.chromium.base.annotations.SuppressFBWarnings; +import org.chromium.mojo.bindings.DeserializationException;
\ No newline at end of file diff --git a/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl new file mode 100644 index 0000000000..a13be3ef60 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl @@ -0,0 +1,4 @@ +{% from "interface_definition.tmpl" import interface_def %} +{% include "header.java.tmpl" %} + +{{ interface_def(interface) }} diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl new file mode 100644 index 0000000000..a723f8c393 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl @@ -0,0 +1,297 @@ +{% from "constant_definition.tmpl" import constant_def %} +{% from "enum_definition.tmpl" import enum_def %} +{% from "data_types_definition.tmpl" import struct_def %} + +{%- macro declare_params(parameters, boxed=false) %} +{%- for param in parameters -%} +{{param.kind|java_type(boxed)}} {{param|name}} +{%- if not loop.last %}, {% endif %} +{%- endfor %} +{%- endmacro %} + +{% macro declare_request_params(method) %} +{{declare_params(method.parameters)}} +{%- if method.response_parameters != None -%} +{%- if method.parameters %}, {% endif %} +{{method|interface_response_name}} callback +{%- endif -%} +{% endmacro %} + +{%- macro declare_callback(method) -%} + +interface {{method|interface_response_name}} extends org.chromium.mojo.bindings.Callbacks.Callback{{method.response_parameters|length}}{% if method.response_parameters %}< +{%- for param in method.response_parameters -%} +{{param.kind|java_type(True)}} +{%- if not loop.last %}, {% endif %} +{%- endfor -%} +>{% endif %} { } +{%- endmacro -%} + +{%- macro run_callback(variable, parameters) -%} +{%- if parameters -%} +{%- for param in parameters -%} +{{variable}}.{{param|name}} +{%- if not loop.last %}, {% endif %} +{%- endfor -%} +{%- endif -%} +{%- endmacro -%} + +{%- macro flags_for_method(method, is_request) -%} +{{flags(method.response_parameters != None, is_request)}} +{%- endmacro -%} + +{%- macro flags(use_response_flag, is_request) -%} +{%- if not use_response_flag -%} +org.chromium.mojo.bindings.MessageHeader.NO_FLAG +{%- elif is_request: -%} +org.chromium.mojo.bindings.MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG +{%- else -%} +org.chromium.mojo.bindings.MessageHeader.MESSAGE_IS_RESPONSE_FLAG +{%- endif -%} +{%- endmacro -%} + +{%- macro manager_class(interface, fully_qualified=False) -%} +{% if fully_qualified %}org.chromium.mojo.bindings.Interface.{% endif %}Manager<{{interface|name}}, {{interface|name}}.Proxy> +{%- endmacro -%} + +{%- macro manager_def(interface) -%} +public static final {{manager_class(interface, True)}} MANAGER = + new {{manager_class(interface, True)}}() { + + public String getName() { + return "{{namespace|replace(".","::")}}::{{interface.name}}"; + } + + public int getVersion() { + return {{interface.version}}; + } + + public Proxy buildProxy(org.chromium.mojo.system.Core core, + org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) { + return new Proxy(core, messageReceiver); + } + + public Stub buildStub(org.chromium.mojo.system.Core core, {{interface|name}} impl) { + return new Stub(core, impl); + } + + public {{interface|name}}[] buildArray(int size) { + return new {{interface|name}}[size]; + } +}; +{%- endmacro -%} + +{%- macro accept_body(interface, with_response) -%} +try { + org.chromium.mojo.bindings.ServiceMessage messageWithHeader = + message.asServiceMessage(); + org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader(); + if (!header.validateHeader({{flags(with_response, True)}})) { + return false; + } + switch(header.getType()) { +{% if with_response %} + case org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants.RUN_MESSAGE_ID: + return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRun( + getCore(), {{interface|name}}_Internal.MANAGER, messageWithHeader, receiver); +{% else %} + case org.chromium.mojo.bindings.interfacecontrol.InterfaceControlMessagesConstants.RUN_OR_CLOSE_PIPE_MESSAGE_ID: + return org.chromium.mojo.bindings.InterfaceControlMessagesHelper.handleRunOrClosePipe( + {{interface|name}}_Internal.MANAGER, messageWithHeader); +{% endif %} +{% for method in interface.methods %} +{% if (with_response and method.response_parameters != None) or + (not with_response and method.response_parameters == None) %} +{% set request_struct = method.param_struct %} +{% if with_response %} +{% set response_struct = method.response_param_struct %} +{% endif %} + case {{method|method_ordinal_name}}: { +{% if method.parameters %} + {{request_struct|name}} data = + {{request_struct|name}}.deserialize(messageWithHeader.getPayload()); +{% else %} + {{request_struct|name}}.deserialize(messageWithHeader.getPayload()); +{% endif %} + try { + getImpl().{{method|name}}({{run_callback('data', method.parameters)}}{% if with_response %}{% if method.parameters %}, {% endif %}new {{response_struct|name}}ProxyToResponder(getCore(), receiver, header.getRequestId()){% endif %}); + } catch (RuntimeException e) { + // TODO(lhchavez): Remove this hack. See b/28814913 for details. + android.util.Log.wtf("{{namespace}}.{{interface.name}}", "Uncaught runtime exception", e); + } + return true; + } +{% endif %} +{% endfor %} + default: + return false; + } +} catch (org.chromium.mojo.bindings.DeserializationException e) { + System.err.println(e.toString()); + return false; +} +{%- endmacro -%} + +{% macro interface_def(interface) %} +public interface {{interface|name}} extends org.chromium.mojo.bindings.Interface { +{% for constant in interface.constants %} + + {{constant_def(constant)|indent(4)}} +{% endfor %} +{% for enum in interface.enums %} + + {{enum_def(enum, false)|indent(4)}} +{% endfor %} + + public interface Proxy extends {{interface|name}}, org.chromium.mojo.bindings.Interface.Proxy { + } + + {{manager_class(interface)}} MANAGER = {{interface|name}}_Internal.MANAGER; +{% for method in interface.methods %} + + void {{method|name}}({{declare_request_params(method)}}); +{% if method.response_parameters != None %} + {{declare_callback(method)|indent(4)}} +{% endif %} +{% endfor %} +} +{% endmacro %} + +{% macro interface_internal_def(interface) %} +class {{interface|name}}_Internal { + + {{manager_def(interface)|indent(4)}} + +{% for method in interface.methods %} + private static final int {{method|method_ordinal_name}} = {{method.ordinal}}; +{% endfor %} + + static final class Proxy extends org.chromium.mojo.bindings.Interface.AbstractProxy implements {{interface|name}}.Proxy { + + Proxy(org.chromium.mojo.system.Core core, + org.chromium.mojo.bindings.MessageReceiverWithResponder messageReceiver) { + super(core, messageReceiver); + } +{% for method in interface.methods %} + + @Override + public void {{method|name}}({{declare_request_params(method)}}) { +{% set request_struct = method.param_struct %} + {{request_struct|name}} _message = new {{request_struct|name}}(); +{% for param in method.parameters %} + _message.{{param|name}} = {{param|name}}; +{% endfor %} +{% if method.response_parameters != None %} + getProxyHandler().getMessageReceiver().acceptWithResponder( + _message.serializeWithHeader( + getProxyHandler().getCore(), + new org.chromium.mojo.bindings.MessageHeader( + {{method|method_ordinal_name}}, + {{flags_for_method(method, True)}}, + 0)), + new {{method.response_param_struct|name}}ForwardToCallback(callback)); +{% else %} + getProxyHandler().getMessageReceiver().accept( + _message.serializeWithHeader( + getProxyHandler().getCore(), + new org.chromium.mojo.bindings.MessageHeader({{method|method_ordinal_name}}))); +{% endif %} + } +{% endfor %} + + } + + static final class Stub extends org.chromium.mojo.bindings.Interface.Stub<{{interface|name}}> { + + Stub(org.chromium.mojo.system.Core core, {{interface|name}} impl) { + super(core, impl); + } + + @Override + public boolean accept(org.chromium.mojo.bindings.Message message) { + {{accept_body(interface, False)|indent(12)}} + } + + @Override + public boolean acceptWithResponder(org.chromium.mojo.bindings.Message message, org.chromium.mojo.bindings.MessageReceiver receiver) { + {{accept_body(interface, True)|indent(12)}} + } + } +{% for method in interface.methods %} + + {{ struct_def(method.param_struct, True)|indent(4) }} +{% if method.response_parameters != None %} +{% set response_struct = method.response_param_struct %} + + {{ struct_def(response_struct, True)|indent(4) }} + + static class {{response_struct|name}}ForwardToCallback extends org.chromium.mojo.bindings.SideEffectFreeCloseable + implements org.chromium.mojo.bindings.MessageReceiver { + private final {{interface|name}}.{{method|interface_response_name}} mCallback; + + {{response_struct|name}}ForwardToCallback({{interface|name}}.{{method|interface_response_name}} callback) { + this.mCallback = callback; + } + + @Override + public boolean accept(org.chromium.mojo.bindings.Message message) { + try { + org.chromium.mojo.bindings.ServiceMessage messageWithHeader = + message.asServiceMessage(); + org.chromium.mojo.bindings.MessageHeader header = messageWithHeader.getHeader(); + if (!header.validateHeader({{method|method_ordinal_name}}, + {{flags_for_method(method, False)}})) { + return false; + } +{% if method.response_parameters|length %} + {{response_struct|name}} response = {{response_struct|name}}.deserialize(messageWithHeader.getPayload()); +{% endif %} + try { + mCallback.call({{run_callback('response', method.response_parameters)}}); + } catch (RuntimeException e) { + // TODO(lhchavez): Remove this hack. See b/28814913 for details. + android.util.Log.wtf("{{namespace}}.{{interface.name}}", "Uncaught runtime exception", e); + } + return true; + } catch (org.chromium.mojo.bindings.DeserializationException e) { + return false; + } + } + } + + static class {{response_struct|name}}ProxyToResponder implements {{interface|name}}.{{method|interface_response_name}} { + + private final org.chromium.mojo.system.Core mCore; + private final org.chromium.mojo.bindings.MessageReceiver mMessageReceiver; + private final long mRequestId; + + {{response_struct|name}}ProxyToResponder( + org.chromium.mojo.system.Core core, + org.chromium.mojo.bindings.MessageReceiver messageReceiver, + long requestId) { + mCore = core; + mMessageReceiver = messageReceiver; + mRequestId = requestId; + } + + @Override + public void call({{declare_params(method.response_parameters, true)}}) { + {{response_struct|name}} _response = new {{response_struct|name}}(); +{% for param in method.response_parameters %} + _response.{{param|name}} = {{param|name}}; +{% endfor %} + org.chromium.mojo.bindings.ServiceMessage _message = + _response.serializeWithHeader( + mCore, + new org.chromium.mojo.bindings.MessageHeader( + {{method|method_ordinal_name}}, + {{flags_for_method(method, False)}}, + mRequestId)); + mMessageReceiver.accept(_message); + } + } +{% endif %} +{% endfor %} + +} +{% endmacro %} diff --git a/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl new file mode 100644 index 0000000000..50c7a7bf94 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl @@ -0,0 +1,4 @@ +{% from "interface_definition.tmpl" import interface_internal_def %} +{% include "header.java.tmpl" %} + +{{ interface_internal_def(interface) }} diff --git a/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl new file mode 100644 index 0000000000..e28ba19c8b --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl @@ -0,0 +1,4 @@ +{% from "data_types_definition.tmpl" import struct_def %} +{% include "header.java.tmpl" %} + +{{ struct_def(struct) }} diff --git a/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl b/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl new file mode 100644 index 0000000000..b8cd4aa2e0 --- /dev/null +++ b/mojo/public/tools/bindings/generators/java_templates/union.java.tmpl @@ -0,0 +1,4 @@ +{% from "data_types_definition.tmpl" import union_def %} +{% include "header.java.tmpl" %} + +{{ union_def(union) }} diff --git a/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl new file mode 100644 index 0000000000..019b1b6383 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl @@ -0,0 +1,33 @@ +{%- macro enum_def(enum_name, enum) -%} + {{enum_name}} = {}; +{%- set prev_enum = 0 %} +{%- for field in enum.fields %} +{%- if field.value %} + {{enum_name}}.{{field.name}} = {{field.value|expression_to_text}}; +{%- elif loop.first %} + {{enum_name}}.{{field.name}} = 0; +{%- else %} + {{enum_name}}.{{field.name}} = {{enum_name}}.{{enum.fields[loop.index0 - 1].name}} + 1; +{%- endif %} +{%- endfor %} + + {{enum_name}}.isKnownEnumValue = function(value) { +{%- if enum.fields %} + switch (value) { +{%- for enum_field in enum.fields|groupby('numeric_value') %} + case {{enum_field[0]}}: +{%- endfor %} + return true; + } +{%- endif %} + return false; + }; + + {{enum_name}}.validate = function(enumValue) { + var isExtensible = {% if enum.extensible %}true{% else %}false{% endif %}; + if (isExtensible || this.isKnownEnumValue(enumValue)) + return validator.validationError.NONE; + + return validator.validationError.UNKNOWN_ENUM_VALUE; + }; +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl new file mode 100644 index 0000000000..11e319c1f7 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl @@ -0,0 +1,198 @@ +{%- for method in interface.methods %} + var k{{interface.name}}_{{method.name}}_Name = {{method.ordinal}}; +{%- endfor %} + + function {{interface.name}}Ptr(handleOrPtrInfo) { + this.ptr = new bindings.InterfacePtrController({{interface.name}}, + handleOrPtrInfo); + } + + function {{interface.name}}Proxy(receiver) { + this.receiver_ = receiver; + } + +{%- for method in interface.methods %} + {{interface.name}}Ptr.prototype.{{method.name|stylize_method}} = function() { + return {{interface.name}}Proxy.prototype.{{method.name|stylize_method}} + .apply(this.ptr.getProxy(), arguments); + }; + + {{interface.name}}Proxy.prototype.{{method.name|stylize_method}} = function( +{%- for parameter in method.parameters -%} +{{parameter.name}}{% if not loop.last %}, {% endif %} +{%- endfor -%} +) { + var params = new {{interface.name}}_{{method.name}}_Params(); +{%- for parameter in method.parameters %} + params.{{parameter.name}} = {{parameter.name}}; +{%- endfor %} + +{%- if method.response_parameters == None %} + var builder = new codec.MessageBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_Params.encodedSize)); + builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params); + var message = builder.finish(); + this.receiver_.accept(message); +{%- else %} + return new Promise(function(resolve, reject) { + var builder = new codec.MessageWithRequestIDBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_Params.encodedSize), + codec.kMessageExpectsResponse, 0); + builder.encodeStruct({{interface.name}}_{{method.name}}_Params, params); + var message = builder.finish(); + this.receiver_.acceptAndExpectResponse(message).then(function(message) { + var reader = new codec.MessageReader(message); + var responseParams = + reader.decodeStruct({{interface.name}}_{{method.name}}_ResponseParams); + resolve(responseParams); + }).catch(function(result) { + reject(Error("Connection error: " + result)); + }); + }.bind(this)); +{%- endif %} + }; +{%- endfor %} + + function {{interface.name}}Stub(delegate) { + this.delegate_ = delegate; + } + +{%- for method in interface.methods %} +{%- set js_method_name = method.name|stylize_method %} + {{interface.name}}Stub.prototype.{{js_method_name}} = function({{method.parameters|map(attribute='name')|join(', ')}}) { + return this.delegate_ && this.delegate_.{{js_method_name}} && this.delegate_.{{js_method_name}}({{method.parameters|map(attribute='name')|join(', ')}}); + } +{%- endfor %} + + {{interface.name}}Stub.prototype.accept = function(message) { + var reader = new codec.MessageReader(message); + switch (reader.messageName) { +{%- for method in interface.methods %} +{%- if method.response_parameters == None %} + case k{{interface.name}}_{{method.name}}_Name: + var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params); + this.{{method.name|stylize_method}}( +{%- for parameter in method.parameters -%} + params.{{parameter.name}}{% if not loop.last %}, {% endif %} +{%- endfor %}); + return true; +{%- endif %} +{%- endfor %} + default: + return false; + } + }; + + {{interface.name}}Stub.prototype.acceptWithResponder = + function(message, responder) { + var reader = new codec.MessageReader(message); + switch (reader.messageName) { +{%- for method in interface.methods %} +{%- if method.response_parameters != None %} + case k{{interface.name}}_{{method.name}}_Name: + var params = reader.decodeStruct({{interface.name}}_{{method.name}}_Params); + this.{{method.name|stylize_method}}( +{%- for parameter in method.parameters -%} +params.{{parameter.name}}{% if not loop.last %}, {% endif -%} +{%- endfor %}).then(function(response) { + var responseParams = + new {{interface.name}}_{{method.name}}_ResponseParams(); +{%- for parameter in method.response_parameters %} + responseParams.{{parameter.name}} = response.{{parameter.name}}; +{%- endfor %} + var builder = new codec.MessageWithRequestIDBuilder( + k{{interface.name}}_{{method.name}}_Name, + codec.align({{interface.name}}_{{method.name}}_ResponseParams.encodedSize), + codec.kMessageIsResponse, reader.requestID); + builder.encodeStruct({{interface.name}}_{{method.name}}_ResponseParams, + responseParams); + var message = builder.finish(); + responder.accept(message); + }); + return true; +{%- endif %} +{%- endfor %} + default: + return false; + } + }; + +{#--- Validation #} + + function validate{{interface.name}}Request(messageValidator) { +{%- if not(interface.methods) %} + return validator.validationError.NONE; +{%- else %} + var message = messageValidator.message; + var paramsClass = null; + switch (message.getName()) { +{%- for method in interface.methods %} + case k{{interface.name}}_{{method.name}}_Name: +{%- if method.response_parameters == None %} + if (!message.expectsResponse() && !message.isResponse()) + paramsClass = {{interface.name}}_{{method.name}}_Params; +{%- else %} + if (message.expectsResponse()) + paramsClass = {{interface.name}}_{{method.name}}_Params; +{%- endif %} + break; +{%- endfor %} + } + if (paramsClass === null) + return validator.validationError.NONE; + return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes()); +{%- endif %} + } + + function validate{{interface.name}}Response(messageValidator) { +{%- if not(interface|has_callbacks) %} + return validator.validationError.NONE; +{%- else %} + var message = messageValidator.message; + var paramsClass = null; + switch (message.getName()) { +{%- for method in interface.methods %} +{%- if method.response_parameters != None %} + case k{{interface.name}}_{{method.name}}_Name: + if (message.isResponse()) + paramsClass = {{interface.name}}_{{method.name}}_ResponseParams; + break; +{%- endif %} +{%- endfor %} + } + if (paramsClass === null) + return validator.validationError.NONE; + return paramsClass.validate(messageValidator, messageValidator.message.getHeaderNumBytes()); +{%- endif %} + } + + var {{interface.name}} = { + name: '{{namespace|replace(".","::")}}::{{interface.name}}', + kVersion: {{interface.version}}, + ptrClass: {{interface.name}}Ptr, + proxyClass: {{interface.name}}Proxy, + stubClass: {{interface.name}}Stub, + validateRequest: validate{{interface.name}}Request, +{%- if interface|has_callbacks %} + validateResponse: validate{{interface.name}}Response, +{%- else %} + validateResponse: null, +{%- endif %} + }; +{#--- Interface Constants #} +{%- for constant in interface.constants %} + {{interface.name}}.{{constant.name}} = {{constant.value|expression_to_text}}, +{%- endfor %} +{#--- Interface Enums #} +{%- from "enum_definition.tmpl" import enum_def -%} +{%- for enum in interface.enums %} + {{ enum_def("%s.%s"|format(interface.name, enum.name), enum) }} +{%- endfor %} + {{interface.name}}Stub.prototype.validator = validate{{interface.name}}Request; +{%- if interface|has_callbacks %} + {{interface.name}}Proxy.prototype.validator = validate{{interface.name}}Response; +{%- else %} + {{interface.name}}Proxy.prototype.validator = null; +{%- endif %} diff --git a/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl new file mode 100644 index 0000000000..3637b196ac --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl @@ -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. + +{%- if use_new_js_bindings %} + +'use strict'; + +(function() { + var mojomId = '{{module.path}}'; + if (mojo.internal.isMojomLoaded(mojomId)) { + console.warn('The following mojom is loaded multiple times: ' + mojomId); + return; + } + mojo.internal.markMojomLoaded(mojomId); + + // TODO(yzshen): Define these aliases to minimize the differences between the + // old/new modes. Remove them when the old mode goes away. + var bindings = mojo; + var codec = mojo.internal; + var validator = mojo.internal; + +{%- for import in imports %} + var {{import.unique_name}} = + mojo.internal.exposeNamespace('{{import.module.namespace}}'); + if (mojo.config.autoLoadMojomDeps) { + mojo.internal.loadMojomIfNecessary( + '{{import.module.path}}', + new URL( + '{{import.module|get_relative_path(module)}}.js', + document.currentScript.src).href); + } +{%- endfor %} + +{% include "module_definition.tmpl" %} +})(); + +{%- else %} + +define("{{module.path}}", [ +{%- if module.path != + "mojo/public/interfaces/bindings/interface_control_messages.mojom" and + module.path != + "mojo/public/interfaces/bindings/pipe_control_messages.mojom" %} + "mojo/public/js/bindings", +{%- endif %} + "mojo/public/js/codec", + "mojo/public/js/core", + "mojo/public/js/validator", +{%- for import in imports %} + "{{import.module.path}}", +{%- endfor %} +], function( +{%- if module.path != + "mojo/public/interfaces/bindings/interface_control_messages.mojom" and + module.path != + "mojo/public/interfaces/bindings/pipe_control_messages.mojom" -%} +bindings, {% endif -%} +codec, core, validator +{%- for import in imports -%} + , {{import.unique_name}} +{%- endfor -%} +) { + +{%- include "module_definition.tmpl" %} + + return exports; +}); + +{%- endif %} diff --git a/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl new file mode 100644 index 0000000000..a119ee9480 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl @@ -0,0 +1,49 @@ +{#--- Constants #} +{%- for constant in module.constants %} + var {{constant.name}} = {{constant.value|expression_to_text}}; +{%- endfor %} + +{#--- Enums #} +{%- from "enum_definition.tmpl" import enum_def %} +{%- for enum in enums %} + var {{ enum_def(enum.name, enum) }} +{%- endfor %} + +{#--- Struct definitions #} +{% for struct in structs %} +{%- include "struct_definition.tmpl" %} +{%- endfor -%} + +{#--- Union definitions #} +{%- from "union_definition.tmpl" import union_def %} +{%- for union in unions %} +{{union_def(union)|indent(2)}} +{%- endfor %} + +{#--- Interface definitions #} +{%- for interface in interfaces -%} +{%- include "interface_definition.tmpl" %} +{%- endfor %} + +{%- if use_new_js_bindings %} + var exports = mojo.internal.exposeNamespace("{{module.namespace}}"); +{%- else %} + var exports = {}; +{%- endif %} + +{%- for constant in module.constants %} + exports.{{constant.name}} = {{constant.name}}; +{%- endfor %} +{%- for enum in enums %} + exports.{{enum.name}} = {{enum.name}}; +{%- endfor %} +{%- for struct in structs if struct.exported %} + exports.{{struct.name}} = {{struct.name}}; +{%- endfor %} +{%- for union in unions %} + exports.{{union.name}} = {{union.name}}; +{%- endfor %} +{%- for interface in interfaces %} + exports.{{interface.name}} = {{interface.name}}; + exports.{{interface.name}}Ptr = {{interface.name}}Ptr; +{%- endfor %} diff --git a/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl new file mode 100644 index 0000000000..e823e46155 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl @@ -0,0 +1,126 @@ +{#--- Begin #} + function {{struct.name}}(values) { + this.initDefaults_(); + this.initFields_(values); + } + +{#--- Enums #} +{%- from "enum_definition.tmpl" import enum_def %} +{% for enum in struct.enums %} + {{enum_def("%s.%s"|format(struct.name, enum.name), enum)}} +{%- endfor %} + +{#--- Constants #} +{% for constant in struct.constants %} + {{struct.name}}.{{constant.name}} = {{constant.value|expression_to_text}}; +{%- endfor %} + +{#--- initDefaults() #} + {{struct.name}}.prototype.initDefaults_ = function() { +{%- for packed_field in struct.packed.packed_fields %} + this.{{packed_field.field.name}} = {{packed_field.field|default_value}}; +{%- endfor %} + }; + +{#--- initFields() #} + {{struct.name}}.prototype.initFields_ = function(fields) { + for(var field in fields) { + if (this.hasOwnProperty(field)) + this[field] = fields[field]; + } + }; + +{#--- Validation #} + + {{struct.name}}.validate = function(messageValidator, offset) { + var err; + err = messageValidator.validateStructHeader(offset, codec.kStructHeaderSize); + if (err !== validator.validationError.NONE) + return err; + + var kVersionSizes = [ +{%- for version in struct.versions %} + {version: {{version.version}}, numBytes: {{version.num_bytes}}}{% if not loop.last %}, + {%- endif -%} +{% endfor %} + ]; + err = messageValidator.validateStructVersion(offset, kVersionSizes); + if (err !== validator.validationError.NONE) + return err; + +{#- Before validating fields introduced at a certain version, we need to add + a version check, which makes sure we skip further validation if |object| + is from an earlier version. |last_checked_version| records the last + version that we have added such version check. #} +{%- from "validation_macros.tmpl" import validate_struct_field %} +{%- set last_checked_version = 0 %} +{%- for packed_field in struct.packed.packed_fields_in_ordinal_order %} +{%- set offset = packed_field|field_offset %} +{%- set field = packed_field.field %} +{%- set name = struct.name ~ '.' ~ field.name %} +{% if field|is_object_field or field|is_any_handle_or_interface_field or + field|is_enum_field %} +{% if packed_field.min_version > last_checked_version %} +{% set last_checked_version = packed_field.min_version %} + // version check {{name}} + if (!messageValidator.isFieldInStructVersion(offset, {{packed_field.min_version}})) + return validator.validationError.NONE; +{%- endif -%} +{{validate_struct_field(field, offset, name)|indent(4)}} +{%- endif %} +{%- endfor %} + + return validator.validationError.NONE; + }; + +{#--- Encoding and decoding #} + + {{struct.name}}.encodedSize = codec.kStructHeaderSize + {{struct.packed|payload_size}}; + + {{struct.name}}.decode = function(decoder) { + var packed; + var val = new {{struct.name}}(); + var numberOfBytes = decoder.readUint32(); + var version = decoder.readUint32(); +{%- for byte in struct.bytes %} +{%- if byte.packed_fields|length >= 1 and + byte.packed_fields[0].field|is_bool_field %} + packed = decoder.readUint8(); +{%- for packed_field in byte.packed_fields %} + val.{{packed_field.field.name}} = (packed >> {{packed_field.bit}}) & 1 ? true : false; +{%- endfor %} +{%- else %} +{%- for packed_field in byte.packed_fields %} + val.{{packed_field.field.name}} = decoder.{{packed_field.field.kind|decode_snippet}}; +{%- endfor %} +{%- endif %} +{%- if byte.is_padding %} + decoder.skip(1); +{%- endif %} +{%- endfor %} + return val; + }; + + {{struct.name}}.encode = function(encoder, val) { + var packed; + encoder.writeUint32({{struct.name}}.encodedSize); + encoder.writeUint32({{struct.versions[-1].version}}); + +{%- for byte in struct.bytes %} +{%- if byte.packed_fields|length >= 1 and + byte.packed_fields[0].field|is_bool_field %} + packed = 0; +{%- for packed_field in byte.packed_fields %} + packed |= (val.{{packed_field.field.name}} & 1) << {{packed_field.bit}} +{%- endfor %} + encoder.writeUint8(packed); +{%- else %} +{%- for packed_field in byte.packed_fields %} + encoder.{{packed_field.field.kind|encode_snippet}}val.{{packed_field.field.name}}); +{%- endfor %} +{%- endif %} +{%- if byte.is_padding %} + encoder.skip(1); +{%- endif %} +{%- endfor %} + }; diff --git a/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl new file mode 100644 index 0000000000..4823febeca --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl @@ -0,0 +1,154 @@ +{%- macro union_def(union) %} +function {{union.name}}(value) { + this.initDefault_(); + this.initValue_(value); +} + +{{tags(union)}} + +{{union.name}}.prototype.initDefault_ = function() { + this.$data = null; + this.$tag = undefined; +} + +{{union.name}}.prototype.initValue_ = function(value) { + if (value == undefined) { + return; + } + + var keys = Object.keys(value); + if (keys.length == 0) { + return; + } + + if (keys.length > 1) { + throw new TypeError("You may set only one member on a union."); + } + + var fields = [ +{%- for field in union.fields %} + "{{field.name}}", +{%- endfor %} + ]; + + if (fields.indexOf(keys[0]) < 0) { + throw new ReferenceError(keys[0] + " is not a {{union.name}} member."); + + } + + this[keys[0]] = value[keys[0]]; +} + +{%- for field in union.fields %} +Object.defineProperty({{union.name}}.prototype, "{{field.name}}", { + get: function() { + if (this.$tag != {{union.name}}.Tags.{{field.name}}) { + throw new ReferenceError( + "{{union.name}}.{{field.name}} is not currently set."); + } + return this.$data; + }, + + set: function(value) { + this.$tag = {{union.name}}.Tags.{{field.name}}; + this.$data = value; + } +}); +{%- endfor %} + +{{encode(union)|indent(2)}} + +{{decode(union)|indent(2)}} + +{{validate(union)|indent(2)}} + +{{union.name}}.encodedSize = 16; +{%- endmacro %} + +{%- macro tags(union) %} +{{union.name}}.Tags = { +{%- for field in union.fields %} + {{field.name}}: {{field.ordinal}}, +{%- endfor %} +}; +{%- endmacro %} + +{%- macro encode(union) %} +{{union.name}}.encode = function(encoder, val) { + if (val == null) { + encoder.writeUint64(0); + encoder.writeUint64(0); + return; + } + if (val.$tag == undefined) { + throw new TypeError("Cannot encode unions with an unknown member set."); + } + + encoder.writeUint32(16); + encoder.writeUint32(val.$tag); + switch (val.$tag) { +{%- for field in union.fields %} + case {{union.name}}.Tags.{{field.name}}: +{%- if field|is_bool_field %} + encoder.writeUint8(val.{{field.name}} ? 1 : 0); +{%- else %} + encoder.{{field.kind|union_encode_snippet}}val.{{field.name}}); +{%- endif %} + break; +{%- endfor %} + } + encoder.align(); +}; +{%- endmacro %} + +{%- macro decode(union) %} +{{union.name}}.decode = function(decoder) { + var size = decoder.readUint32(); + if (size == 0) { + decoder.readUint32(); + decoder.readUint64(); + return null; + } + + var result = new {{union.name}}(); + var tag = decoder.readUint32(); + switch (tag) { +{%- for field in union.fields %} + case {{union.name}}.Tags.{{field.name}}: +{%- if field|is_bool_field %} + result.{{field.name}} = decoder.readUint8() ? true : false; +{%- else %} + result.{{field.name}} = decoder.{{field.kind|union_decode_snippet}}; +{%- endif %} + break; +{%- endfor %} + } + decoder.align(); + + return result; +}; +{%- endmacro %} + +{%- from "validation_macros.tmpl" import validate_union_field %} +{%- macro validate(union) %} +{{union.name}}.validate = function(messageValidator, offset) { + var size = messageValidator.decodeUnionSize(offset); + if (size != 16) { + return validator.validationError.INVALID_UNION_SIZE; + } + + var tag = messageValidator.decodeUnionTag(offset); + var data_offset = offset + 8; + var err; + switch (tag) { +{%- for field in union.fields %} +{%- set name = union.name ~ '.' ~ field.name %} + case {{union.name}}.Tags.{{field.name}}: + {{validate_union_field(field, "data_offset", name)}} + break; +{%- endfor %} + } + + return validator.validationError.NONE; +}; +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl new file mode 100644 index 0000000000..d4e15a7859 --- /dev/null +++ b/mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl @@ -0,0 +1,60 @@ +{% macro _check_err() -%} +if (err !== validator.validationError.NONE) + return err; +{%- endmacro %} + +{%- macro _validate_field(field, offset, name) %} +{%- if field|is_string_pointer_field %} +// validate {{name}} +err = messageValidator.validateStringPointer({{offset}}, {{field|validate_nullable_params}}) +{{_check_err()}} +{%- elif field|is_array_pointer_field %} +// validate {{name}} +err = messageValidator.validateArrayPointer({{offset}}, {{field|validate_array_params}}); +{{_check_err()}} +{%- elif field|is_struct_pointer_field %} +// validate {{name}} +err = messageValidator.validateStructPointer({{offset}}, {{field|validate_struct_params}}); +{{_check_err()}} +{%- elif field|is_map_pointer_field %} +// validate {{name}} +err = messageValidator.validateMapPointer({{offset}}, {{field|validate_map_params}}); +{{_check_err()}} +{%- elif field|is_interface_field %} +// validate {{name}} +err = messageValidator.validateInterface({{offset}}, {{field|validate_nullable_params}}); +{{_check_err()}} +{%- elif field|is_interface_request_field %} +// validate {{name}} +err = messageValidator.validateInterfaceRequest({{offset}}, {{field|validate_nullable_params}}) +{{_check_err()}} +{%- elif field|is_handle_field %} +// validate {{name}} +err = messageValidator.validateHandle({{offset}}, {{field|validate_nullable_params}}) +{{_check_err()}} +{%- elif field|is_enum_field %} +// validate {{name}} +err = messageValidator.validateEnum({{offset}}, {{field|validate_enum_params}}); +{{_check_err()}} +{%- endif %} +{%- endmacro %} + +{%- macro validate_struct_field(field, offset, name) %} +{%- if field|is_union_field %} +// validate {{name}} +err = messageValidator.validateUnion({{offset}}, {{field|validate_union_params}}); +{{_check_err()}} +{%- else %} +{{_validate_field(field, offset, name)}} +{%- endif %} +{%- endmacro %} + +{%- macro validate_union_field(field, offset, name) %} +{%- if field|is_union_field %} +// validate {{name}} +err = messageValidator.validateNestedUnion({{offset}}, {{field|validate_union_params}}); +{{_check_err()}} +{%- else %} +{{_validate_field(field, offset, name)}} +{%- endif %} +{%- endmacro %} diff --git a/mojo/public/tools/bindings/generators/mojom_cpp_generator.py b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py new file mode 100644 index 0000000000..38d222b136 --- /dev/null +++ b/mojo/public/tools/bindings/generators/mojom_cpp_generator.py @@ -0,0 +1,818 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generates C++ source files from a mojom.Module.""" + +import mojom.generate.generator as generator +import mojom.generate.module as mojom +import mojom.generate.pack as pack +from mojom.generate.template_expander import UseJinja + + +_kind_to_cpp_type = { + mojom.BOOL: "bool", + mojom.INT8: "int8_t", + mojom.UINT8: "uint8_t", + mojom.INT16: "int16_t", + mojom.UINT16: "uint16_t", + mojom.INT32: "int32_t", + mojom.UINT32: "uint32_t", + mojom.FLOAT: "float", + mojom.INT64: "int64_t", + mojom.UINT64: "uint64_t", + mojom.DOUBLE: "double", +} + +_kind_to_cpp_literal_suffix = { + mojom.UINT8: "U", + mojom.UINT16: "U", + mojom.UINT32: "U", + mojom.FLOAT: "f", + mojom.UINT64: "ULL", +} + +# TODO(rockot): Get rid of these globals. This requires some refactoring of the +# generator library code so that filters can use the generator as context. +_current_typemap = {} +_for_blink = False +# TODO(rockot, yzshen): The variant handling is kind of a hack currently. Make +# it right. +_variant = None +_export_attribute = None + + +class _NameFormatter(object): + """A formatter for the names of kinds or values.""" + + def __init__(self, token, variant): + self._token = token + self._variant = variant + + def Format(self, separator, prefixed=False, internal=False, + include_variant=False, add_same_module_namespaces=False, + flatten_nested_kind=False): + """Formats the name according to the given configuration. + + Args: + separator: Separator between different parts of the name. + prefixed: Whether a leading separator should be added. + internal: Returns the name in the "internal" namespace. + include_variant: Whether to include variant as namespace. If |internal| is + True, then this flag is ignored and variant is not included. + add_same_module_namespaces: Includes all namespaces even if the token is + from the same module as the current mojom file. + flatten_nested_kind: It is allowed to define enums inside structs and + interfaces. If this flag is set to True, this method concatenates the + parent kind and the nested kind with '_', instead of treating the + parent kind as a scope.""" + + parts = [] + if self._ShouldIncludeNamespace(add_same_module_namespaces): + if prefixed: + parts.append("") + parts.extend(self._GetNamespace()) + if include_variant and self._variant and not internal: + parts.append(self._variant) + parts.extend(self._GetName(internal, flatten_nested_kind)) + return separator.join(parts) + + def FormatForCpp(self, add_same_module_namespaces=False, internal=False, + flatten_nested_kind=False): + return self.Format( + "::", prefixed=True, + add_same_module_namespaces=add_same_module_namespaces, + internal=internal, include_variant=True, + flatten_nested_kind=flatten_nested_kind) + + def FormatForMojom(self): + return self.Format(".", add_same_module_namespaces=True) + + def _MapKindName(self, token, internal): + if not internal: + return token.name + if (mojom.IsStructKind(token) or mojom.IsUnionKind(token) or + mojom.IsEnumKind(token)): + return token.name + "_Data" + return token.name + + def _GetName(self, internal, flatten_nested_kind): + if isinstance(self._token, mojom.EnumValue): + name_parts = _NameFormatter(self._token.enum, self._variant)._GetName( + internal, flatten_nested_kind) + name_parts.append(self._token.name) + return name_parts + + name_parts = [] + if internal: + name_parts.append("internal") + + if (flatten_nested_kind and mojom.IsEnumKind(self._token) and + self._token.parent_kind): + name = "%s_%s" % (self._token.parent_kind.name, + self._MapKindName(self._token, internal)) + name_parts.append(name) + return name_parts + + if self._token.parent_kind: + name_parts.append(self._MapKindName(self._token.parent_kind, internal)) + name_parts.append(self._MapKindName(self._token, internal)) + return name_parts + + def _ShouldIncludeNamespace(self, add_same_module_namespaces): + return add_same_module_namespaces or self._token.imported_from + + def _GetNamespace(self): + if self._token.imported_from: + return NamespaceToArray(self._token.imported_from["namespace"]) + elif hasattr(self._token, "module"): + return NamespaceToArray(self._token.module.namespace) + return [] + + +def ConstantValue(constant): + return ExpressionToText(constant.value, kind=constant.kind) + +# TODO(yzshen): Revisit the default value feature. It was designed prior to +# custom type mapping. +def DefaultValue(field): + if field.default: + if mojom.IsStructKind(field.kind): + assert field.default == "default" + if not IsTypemappedKind(field.kind): + return "%s::New()" % GetNameForKind(field.kind) + return ExpressionToText(field.default, kind=field.kind) + return "" + +def NamespaceToArray(namespace): + return namespace.split(".") if namespace else [] + +def GetNameForKind(kind, internal=False, flatten_nested_kind=False, + add_same_module_namespaces=False): + return _NameFormatter(kind, _variant).FormatForCpp( + internal=internal, flatten_nested_kind=flatten_nested_kind, + add_same_module_namespaces=add_same_module_namespaces) + +def GetQualifiedNameForKind(kind, internal=False, flatten_nested_kind=False, + include_variant=True): + return _NameFormatter( + kind, _variant if include_variant else None).FormatForCpp( + internal=internal, add_same_module_namespaces=True, + flatten_nested_kind=flatten_nested_kind) + + +def GetWtfHashFnNameForEnum(enum): + return _NameFormatter( + enum, None).Format("_", internal=True, add_same_module_namespaces=True, + flatten_nested_kind=True) + "HashFn" + + +def GetFullMojomNameForKind(kind): + return _NameFormatter(kind, _variant).FormatForMojom() + +def IsTypemappedKind(kind): + return hasattr(kind, "name") and \ + GetFullMojomNameForKind(kind) in _current_typemap + +def IsNativeOnlyKind(kind): + return (mojom.IsStructKind(kind) or mojom.IsEnumKind(kind)) and \ + kind.native_only + + +def IsHashableKind(kind): + """Check if the kind can be hashed. + + Args: + kind: {Kind} The kind to check. + + Returns: + {bool} True if a value of this kind can be hashed. + """ + checked = set() + def Check(kind): + if kind.spec in checked: + return True + checked.add(kind.spec) + if mojom.IsNullableKind(kind): + return False + elif mojom.IsStructKind(kind): + if (IsTypemappedKind(kind) and + not _current_typemap[GetFullMojomNameForKind(kind)]["hashable"]): + return False + return all(Check(field.kind) for field in kind.fields) + elif mojom.IsEnumKind(kind): + return not IsTypemappedKind(kind) or _current_typemap[ + GetFullMojomNameForKind(kind)]["hashable"] + elif mojom.IsUnionKind(kind): + return all(Check(field.kind) for field in kind.fields) + elif mojom.IsAnyHandleKind(kind): + return False + elif mojom.IsAnyInterfaceKind(kind): + return False + # TODO(tibell): Arrays and maps could be made hashable. We just don't have a + # use case yet. + elif mojom.IsArrayKind(kind): + return False + elif mojom.IsMapKind(kind): + return False + else: + return True + return Check(kind) + + +def AllEnumValues(enum): + """Return all enum values associated with an enum. + + Args: + enum: {mojom.Enum} The enum type. + + Returns: + {Set[int]} The values. + """ + return set(field.numeric_value for field in enum.fields) + + +def GetNativeTypeName(typemapped_kind): + return _current_typemap[GetFullMojomNameForKind(typemapped_kind)]["typename"] + +def GetCppPodType(kind): + return _kind_to_cpp_type[kind] + +def FormatConstantDeclaration(constant, nested=False): + if mojom.IsStringKind(constant.kind): + if nested: + return "const char %s[]" % constant.name + return "%sextern const char %s[]" % \ + ((_export_attribute + " ") if _export_attribute else "", constant.name) + return "constexpr %s %s = %s" % (GetCppPodType(constant.kind), constant.name, + ConstantValue(constant)) + +def GetCppWrapperType(kind, add_same_module_namespaces=False): + def _AddOptional(type_name): + pattern = "WTF::Optional<%s>" if _for_blink else "base::Optional<%s>" + return pattern % type_name + + if IsTypemappedKind(kind): + type_name = GetNativeTypeName(kind) + if (mojom.IsNullableKind(kind) and + not _current_typemap[GetFullMojomNameForKind(kind)][ + "nullable_is_same_type"]): + type_name = _AddOptional(type_name) + return type_name + if mojom.IsEnumKind(kind): + return GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return "%sPtr" % GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsArrayKind(kind): + pattern = "WTF::Vector<%s>" if _for_blink else "std::vector<%s>" + if mojom.IsNullableKind(kind): + pattern = _AddOptional(pattern) + return pattern % GetCppWrapperType( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsMapKind(kind): + pattern = ("WTF::HashMap<%s, %s>" if _for_blink else + "std::unordered_map<%s, %s>") + if mojom.IsNullableKind(kind): + pattern = _AddOptional(pattern) + return pattern % ( + GetCppWrapperType( + kind.key_kind, + add_same_module_namespaces=add_same_module_namespaces), + GetCppWrapperType( + kind.value_kind, + add_same_module_namespaces=add_same_module_namespaces)) + if mojom.IsInterfaceKind(kind): + return "%sPtr" % GetNameForKind( + kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsInterfaceRequestKind(kind): + return "%sRequest" % GetNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsAssociatedInterfaceKind(kind): + return "%sAssociatedPtrInfo" % GetNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "%sAssociatedRequest" % GetNameForKind( + kind.kind, add_same_module_namespaces=add_same_module_namespaces) + if mojom.IsStringKind(kind): + if _for_blink: + return "WTF::String" + type_name = "std::string" + return _AddOptional(type_name) if mojom.IsNullableKind(kind) else type_name + if mojom.IsGenericHandleKind(kind): + return "mojo::ScopedHandle" + if mojom.IsDataPipeConsumerKind(kind): + return "mojo::ScopedDataPipeConsumerHandle" + if mojom.IsDataPipeProducerKind(kind): + return "mojo::ScopedDataPipeProducerHandle" + if mojom.IsMessagePipeKind(kind): + return "mojo::ScopedMessagePipeHandle" + if mojom.IsSharedBufferKind(kind): + return "mojo::ScopedSharedBufferHandle" + if not kind in _kind_to_cpp_type: + raise Exception("Unrecognized kind %s" % kind.spec) + return _kind_to_cpp_type[kind] + +def IsMoveOnlyKind(kind): + if IsTypemappedKind(kind): + if mojom.IsEnumKind(kind): + return False + return _current_typemap[GetFullMojomNameForKind(kind)]["move_only"] + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return True + if mojom.IsArrayKind(kind): + return IsMoveOnlyKind(kind.kind) + if mojom.IsMapKind(kind): + return IsMoveOnlyKind(kind.value_kind) + if mojom.IsAnyHandleOrInterfaceKind(kind): + return True + return False + +def IsCopyablePassByValue(kind): + if not IsTypemappedKind(kind): + return False + return _current_typemap[GetFullMojomNameForKind(kind)][ + "copyable_pass_by_value"] + +def ShouldPassParamByValue(kind): + return ((not mojom.IsReferenceKind(kind)) or IsMoveOnlyKind(kind) or + IsCopyablePassByValue(kind)) + +def GetCppWrapperParamType(kind): + cpp_wrapper_type = GetCppWrapperType(kind) + return (cpp_wrapper_type if ShouldPassParamByValue(kind) + else "const %s&" % cpp_wrapper_type) + +def GetCppFieldType(kind): + if mojom.IsStructKind(kind): + return ("mojo::internal::Pointer<%s>" % + GetNameForKind(kind, internal=True)) + if mojom.IsUnionKind(kind): + return "%s" % GetNameForKind(kind, internal=True) + if mojom.IsArrayKind(kind): + return ("mojo::internal::Pointer<mojo::internal::Array_Data<%s>>" % + GetCppFieldType(kind.kind)) + if mojom.IsMapKind(kind): + return ("mojo::internal::Pointer<mojo::internal::Map_Data<%s, %s>>" % + (GetCppFieldType(kind.key_kind), GetCppFieldType(kind.value_kind))) + if mojom.IsInterfaceKind(kind): + return "mojo::internal::Interface_Data" + if mojom.IsInterfaceRequestKind(kind): + return "mojo::internal::Handle_Data" + if mojom.IsAssociatedInterfaceKind(kind): + return "mojo::internal::AssociatedInterface_Data" + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "mojo::internal::AssociatedEndpointHandle_Data" + if mojom.IsEnumKind(kind): + return "int32_t" + if mojom.IsStringKind(kind): + return "mojo::internal::Pointer<mojo::internal::String_Data>" + if mojom.IsAnyHandleKind(kind): + return "mojo::internal::Handle_Data" + return _kind_to_cpp_type[kind] + +def GetCppUnionFieldType(kind): + if mojom.IsUnionKind(kind): + return ("mojo::internal::Pointer<%s>" % GetNameForKind(kind, internal=True)) + return GetCppFieldType(kind) + +def GetUnionGetterReturnType(kind): + if mojom.IsReferenceKind(kind): + return "%s&" % GetCppWrapperType(kind) + return GetCppWrapperType(kind) + +def GetUnionTraitGetterReturnType(kind): + """Get field type used in UnionTraits template specialization. + + The type may be qualified as UnionTraits specializations live outside the + namespace where e.g. structs are defined. + + Args: + kind: {Kind} The type of the field. + + Returns: + {str} The C++ type to use for the field. + """ + if mojom.IsReferenceKind(kind): + return "%s&" % GetCppWrapperType(kind, add_same_module_namespaces=True) + return GetCppWrapperType(kind, add_same_module_namespaces=True) + +def GetCppDataViewType(kind, qualified=False): + def _GetName(input_kind): + return _NameFormatter(input_kind, None).FormatForCpp( + add_same_module_namespaces=qualified, flatten_nested_kind=True) + + if mojom.IsEnumKind(kind): + return _GetName(kind) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + return "%sDataView" % _GetName(kind) + if mojom.IsArrayKind(kind): + return "mojo::ArrayDataView<%s>" % GetCppDataViewType(kind.kind, qualified) + if mojom.IsMapKind(kind): + return ("mojo::MapDataView<%s, %s>" % ( + GetCppDataViewType(kind.key_kind, qualified), + GetCppDataViewType(kind.value_kind, qualified))) + if mojom.IsStringKind(kind): + return "mojo::StringDataView" + if mojom.IsInterfaceKind(kind): + return "%sPtrDataView" % _GetName(kind) + if mojom.IsInterfaceRequestKind(kind): + return "%sRequestDataView" % _GetName(kind.kind) + if mojom.IsAssociatedInterfaceKind(kind): + return "%sAssociatedPtrInfoDataView" % _GetName(kind.kind) + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "%sAssociatedRequestDataView" % _GetName(kind.kind) + if mojom.IsGenericHandleKind(kind): + return "mojo::ScopedHandle" + if mojom.IsDataPipeConsumerKind(kind): + return "mojo::ScopedDataPipeConsumerHandle" + if mojom.IsDataPipeProducerKind(kind): + return "mojo::ScopedDataPipeProducerHandle" + if mojom.IsMessagePipeKind(kind): + return "mojo::ScopedMessagePipeHandle" + if mojom.IsSharedBufferKind(kind): + return "mojo::ScopedSharedBufferHandle" + return _kind_to_cpp_type[kind] + +def GetUnmappedTypeForSerializer(kind): + return GetCppDataViewType(kind, qualified=True) + +def TranslateConstants(token, kind): + if isinstance(token, mojom.NamedValue): + return GetNameForKind(token, flatten_nested_kind=True) + + if isinstance(token, mojom.BuiltinValue): + if token.value == "double.INFINITY": + return "std::numeric_limits<double>::infinity()" + if token.value == "float.INFINITY": + return "std::numeric_limits<float>::infinity()" + if token.value == "double.NEGATIVE_INFINITY": + return "-std::numeric_limits<double>::infinity()" + if token.value == "float.NEGATIVE_INFINITY": + return "-std::numeric_limits<float>::infinity()" + if token.value == "double.NAN": + return "std::numeric_limits<double>::quiet_NaN()" + if token.value == "float.NAN": + return "std::numeric_limits<float>::quiet_NaN()" + + if (kind is not None and mojom.IsFloatKind(kind)): + return token if token.isdigit() else token + "f"; + + # Per C++11, 2.14.2, the type of an integer literal is the first of the + # corresponding list in Table 6 in which its value can be represented. In this + # case, the list for decimal constants with no suffix is: + # int, long int, long long int + # The standard considers a program ill-formed if it contains an integer + # literal that cannot be represented by any of the allowed types. + # + # As it turns out, MSVC doesn't bother trying to fall back to long long int, + # so the integral constant -2147483648 causes it grief: it decides to + # represent 2147483648 as an unsigned integer, and then warns that the unary + # minus operator doesn't make sense on unsigned types. Doh! + if kind == mojom.INT32 and token == "-2147483648": + return "(-%d - 1) /* %s */" % ( + 2**31 - 1, "Workaround for MSVC bug; see https://crbug.com/445618") + + return "%s%s" % (token, _kind_to_cpp_literal_suffix.get(kind, "")) + +def ExpressionToText(value, kind=None): + return TranslateConstants(value, kind) + +def RequiresContextForDataView(kind): + for field in kind.fields: + if mojom.IsReferenceKind(field.kind): + return True + return False + +def ShouldInlineStruct(struct): + # TODO(darin): Base this on the size of the wrapper class. + if len(struct.fields) > 4: + return False + for field in struct.fields: + if mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind): + return False + return True + +def ContainsMoveOnlyMembers(struct): + for field in struct.fields: + if IsMoveOnlyKind(field.kind): + return True + return False + +def ShouldInlineUnion(union): + return not any( + mojom.IsReferenceKind(field.kind) and not mojom.IsStringKind(field.kind) + for field in union.fields) + + +class StructConstructor(object): + """Represents a constructor for a generated struct. + + Fields: + fields: {[Field]} All struct fields in order. + params: {[Field]} The fields that are passed as params. + """ + + def __init__(self, fields, params): + self._fields = fields + self._params = set(params) + + @property + def params(self): + return [field for field in self._fields if field in self._params] + + @property + def fields(self): + for field in self._fields: + yield (field, field in self._params) + + +def GetStructConstructors(struct): + """Returns a list of constructors for a struct. + + Params: + struct: {Struct} The struct to return constructors for. + + Returns: + {[StructConstructor]} A list of StructConstructors that should be generated + for |struct|. + """ + if not mojom.IsStructKind(struct): + raise TypeError + # Types that are neither copyable nor movable can't be passed to a struct + # constructor so only generate a default constructor. + if any(IsTypemappedKind(field.kind) and _current_typemap[ + GetFullMojomNameForKind(field.kind)]["non_copyable_non_movable"] + for field in struct.fields): + return [StructConstructor(struct.fields, [])] + + param_counts = [0] + for version in struct.versions: + if param_counts[-1] != version.num_fields: + param_counts.append(version.num_fields) + + ordinal_fields = sorted(struct.fields, key=lambda field: field.ordinal) + return (StructConstructor(struct.fields, ordinal_fields[:param_count]) + for param_count in param_counts) + + +def GetContainerValidateParamsCtorArgs(kind): + if mojom.IsStringKind(kind): + expected_num_elements = 0 + element_is_nullable = False + key_validate_params = "nullptr" + element_validate_params = "nullptr" + enum_validate_func = "nullptr" + elif mojom.IsMapKind(kind): + expected_num_elements = 0 + element_is_nullable = False + key_validate_params = GetNewContainerValidateParams(mojom.Array( + kind=kind.key_kind)) + element_validate_params = GetNewContainerValidateParams(mojom.Array( + kind=kind.value_kind)) + enum_validate_func = "nullptr" + else: # mojom.IsArrayKind(kind) + expected_num_elements = generator.ExpectedArraySize(kind) or 0 + element_is_nullable = mojom.IsNullableKind(kind.kind) + key_validate_params = "nullptr" + element_validate_params = GetNewContainerValidateParams(kind.kind) + if mojom.IsEnumKind(kind.kind): + enum_validate_func = ("%s::Validate" % + GetQualifiedNameForKind(kind.kind, internal=True, + flatten_nested_kind=True)) + else: + enum_validate_func = "nullptr" + + if enum_validate_func == "nullptr": + if key_validate_params == "nullptr": + return "%d, %s, %s" % (expected_num_elements, + "true" if element_is_nullable else "false", + element_validate_params) + else: + return "%s, %s" % (key_validate_params, element_validate_params) + else: + return "%d, %s" % (expected_num_elements, enum_validate_func) + +def GetNewContainerValidateParams(kind): + if (not mojom.IsArrayKind(kind) and not mojom.IsMapKind(kind) and + not mojom.IsStringKind(kind)): + return "nullptr" + + return "new mojo::internal::ContainerValidateParams(%s)" % ( + GetContainerValidateParamsCtorArgs(kind)) + +class Generator(generator.Generator): + + cpp_filters = { + "all_enum_values": AllEnumValues, + "constant_value": ConstantValue, + "contains_handles_or_interfaces": mojom.ContainsHandlesOrInterfaces, + "contains_move_only_members": ContainsMoveOnlyMembers, + "cpp_wrapper_param_type": GetCppWrapperParamType, + "cpp_data_view_type": GetCppDataViewType, + "cpp_field_type": GetCppFieldType, + "cpp_union_field_type": GetCppUnionFieldType, + "cpp_pod_type": GetCppPodType, + "cpp_union_getter_return_type": GetUnionGetterReturnType, + "cpp_union_trait_getter_return_type": GetUnionTraitGetterReturnType, + "cpp_wrapper_type": GetCppWrapperType, + "default_value": DefaultValue, + "expression_to_text": ExpressionToText, + "format_constant_declaration": FormatConstantDeclaration, + "get_container_validate_params_ctor_args": + GetContainerValidateParamsCtorArgs, + "get_name_for_kind": GetNameForKind, + "get_pad": pack.GetPad, + "get_qualified_name_for_kind": GetQualifiedNameForKind, + "has_callbacks": mojom.HasCallbacks, + "has_sync_methods": mojom.HasSyncMethods, + "requires_context_for_data_view": RequiresContextForDataView, + "should_inline": ShouldInlineStruct, + "should_inline_union": ShouldInlineUnion, + "is_array_kind": mojom.IsArrayKind, + "is_enum_kind": mojom.IsEnumKind, + "is_integral_kind": mojom.IsIntegralKind, + "is_native_only_kind": IsNativeOnlyKind, + "is_any_handle_kind": mojom.IsAnyHandleKind, + "is_any_interface_kind": mojom.IsAnyInterfaceKind, + "is_any_handle_or_interface_kind": mojom.IsAnyHandleOrInterfaceKind, + "is_associated_kind": mojom.IsAssociatedKind, + "is_hashable": IsHashableKind, + "is_map_kind": mojom.IsMapKind, + "is_nullable_kind": mojom.IsNullableKind, + "is_object_kind": mojom.IsObjectKind, + "is_reference_kind": mojom.IsReferenceKind, + "is_string_kind": mojom.IsStringKind, + "is_struct_kind": mojom.IsStructKind, + "is_typemapped_kind": IsTypemappedKind, + "is_union_kind": mojom.IsUnionKind, + "passes_associated_kinds": mojom.PassesAssociatedKinds, + "struct_constructors": GetStructConstructors, + "stylize_method": generator.StudlyCapsToCamel, + "under_to_camel": generator.UnderToCamel, + "unmapped_type_for_serializer": GetUnmappedTypeForSerializer, + "wtf_hash_fn_name_for_enum": GetWtfHashFnNameForEnum, + } + + def GetExtraTraitsHeaders(self): + extra_headers = set() + for typemap in self._GetAllUsedTypemaps(): + extra_headers.update(typemap.get("traits_headers", [])) + return sorted(extra_headers) + + def _GetAllUsedTypemaps(self): + """Returns the typemaps for types needed for serialization in this module. + + A type is needed for serialization if it is contained by a struct or union + defined in this module, is a parameter of a message in an interface in + this module or is contained within another type needed for serialization. + """ + used_typemaps = [] + seen_types = set() + def AddKind(kind): + if (mojom.IsIntegralKind(kind) or mojom.IsStringKind(kind) or + mojom.IsDoubleKind(kind) or mojom.IsFloatKind(kind) or + mojom.IsAnyHandleKind(kind) or + mojom.IsInterfaceKind(kind) or + mojom.IsInterfaceRequestKind(kind) or + mojom.IsAssociatedKind(kind)): + pass + elif mojom.IsArrayKind(kind): + AddKind(kind.kind) + elif mojom.IsMapKind(kind): + AddKind(kind.key_kind) + AddKind(kind.value_kind) + else: + name = GetFullMojomNameForKind(kind) + if name in seen_types: + return + seen_types.add(name) + + typemap = _current_typemap.get(name, None) + if typemap: + used_typemaps.append(typemap) + if mojom.IsStructKind(kind) or mojom.IsUnionKind(kind): + for field in kind.fields: + AddKind(field.kind) + + for kind in self.module.structs + self.module.unions: + for field in kind.fields: + AddKind(field.kind) + + for interface in self.module.interfaces: + for method in interface.methods: + for parameter in method.parameters + (method.response_parameters or []): + AddKind(parameter.kind) + + return used_typemaps + + def GetExtraPublicHeaders(self): + all_enums = list(self.module.enums) + for struct in self.module.structs: + all_enums.extend(struct.enums) + for interface in self.module.interfaces: + all_enums.extend(interface.enums) + + types = set(GetFullMojomNameForKind(typename) + for typename in + self.module.structs + all_enums + self.module.unions) + headers = set() + for typename, typemap in self.typemap.iteritems(): + if typename in types: + headers.update(typemap.get("public_headers", [])) + return sorted(headers) + + def _GetDirectlyUsedKinds(self): + for struct in self.module.structs + self.module.unions: + for field in struct.fields: + yield field.kind + + for interface in self.module.interfaces: + for method in interface.methods: + for param in method.parameters + (method.response_parameters or []): + yield param.kind + + def GetJinjaExports(self): + structs = self.GetStructs() + interfaces = self.GetInterfaces() + all_enums = list(self.module.enums) + for struct in structs: + all_enums.extend(struct.enums) + for interface in interfaces: + all_enums.extend(interface.enums) + + return { + "module": self.module, + "namespace": self.module.namespace, + "namespaces_as_array": NamespaceToArray(self.module.namespace), + "imports": self.module.imports, + "kinds": self.module.kinds, + "enums": self.module.enums, + "all_enums": all_enums, + "structs": structs, + "unions": self.GetUnions(), + "interfaces": interfaces, + "variant": self.variant, + "extra_traits_headers": self.GetExtraTraitsHeaders(), + "extra_public_headers": self.GetExtraPublicHeaders(), + "for_blink": self.for_blink, + "use_once_callback": self.use_once_callback, + "export_attribute": self.export_attribute, + "export_header": self.export_header, + } + + @staticmethod + def GetTemplatePrefix(): + return "cpp_templates" + + @classmethod + def GetFilters(cls): + return cls.cpp_filters + + @UseJinja("module.h.tmpl") + def GenerateModuleHeader(self): + return self.GetJinjaExports() + + @UseJinja("module.cc.tmpl") + def GenerateModuleSource(self): + return self.GetJinjaExports() + + @UseJinja("module-shared.h.tmpl") + def GenerateModuleSharedHeader(self): + return self.GetJinjaExports() + + @UseJinja("module-shared-internal.h.tmpl") + def GenerateModuleSharedInternalHeader(self): + return self.GetJinjaExports() + + @UseJinja("module-shared.cc.tmpl") + def GenerateModuleSharedSource(self): + return self.GetJinjaExports() + + def GenerateFiles(self, args): + if self.generate_non_variant_code: + self.Write(self.GenerateModuleSharedHeader(), + self.MatchMojomFilePath("%s-shared.h" % self.module.name)) + self.Write( + self.GenerateModuleSharedInternalHeader(), + self.MatchMojomFilePath("%s-shared-internal.h" % self.module.name)) + self.Write(self.GenerateModuleSharedSource(), + self.MatchMojomFilePath("%s-shared.cc" % self.module.name)) + else: + global _current_typemap + _current_typemap = self.typemap + global _for_blink + _for_blink = self.for_blink + global _use_once_callback + _use_once_callback = self.use_once_callback + global _variant + _variant = self.variant + global _export_attribute + _export_attribute = self.export_attribute + suffix = "-%s" % self.variant if self.variant else "" + self.Write(self.GenerateModuleHeader(), + self.MatchMojomFilePath("%s%s.h" % (self.module.name, suffix))) + self.Write( + self.GenerateModuleSource(), + self.MatchMojomFilePath("%s%s.cc" % (self.module.name, suffix))) diff --git a/mojo/public/tools/bindings/generators/mojom_java_generator.py b/mojo/public/tools/bindings/generators/mojom_java_generator.py new file mode 100644 index 0000000000..c7657ff99a --- /dev/null +++ b/mojo/public/tools/bindings/generators/mojom_java_generator.py @@ -0,0 +1,550 @@ +# 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. + +"""Generates java source files from a mojom.Module.""" + +import argparse +import ast +import contextlib +import os +import re +import shutil +import sys +import tempfile + +from jinja2 import contextfilter + +import mojom.fileutil as fileutil +import mojom.generate.generator as generator +import mojom.generate.module as mojom +from mojom.generate.template_expander import UseJinja + +sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, + os.pardir, os.pardir, os.pardir, os.pardir, + 'build', 'android', 'gyp')) +from util import build_utils + + +GENERATOR_PREFIX = 'java' + +_spec_to_java_type = { + mojom.BOOL.spec: 'boolean', + mojom.DCPIPE.spec: 'org.chromium.mojo.system.DataPipe.ConsumerHandle', + mojom.DOUBLE.spec: 'double', + mojom.DPPIPE.spec: 'org.chromium.mojo.system.DataPipe.ProducerHandle', + mojom.FLOAT.spec: 'float', + mojom.HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle', + mojom.INT16.spec: 'short', + mojom.INT32.spec: 'int', + mojom.INT64.spec: 'long', + mojom.INT8.spec: 'byte', + mojom.MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle', + mojom.NULLABLE_DCPIPE.spec: + 'org.chromium.mojo.system.DataPipe.ConsumerHandle', + mojom.NULLABLE_DPPIPE.spec: + 'org.chromium.mojo.system.DataPipe.ProducerHandle', + mojom.NULLABLE_HANDLE.spec: 'org.chromium.mojo.system.UntypedHandle', + mojom.NULLABLE_MSGPIPE.spec: 'org.chromium.mojo.system.MessagePipeHandle', + mojom.NULLABLE_SHAREDBUFFER.spec: + 'org.chromium.mojo.system.SharedBufferHandle', + mojom.NULLABLE_STRING.spec: 'String', + mojom.SHAREDBUFFER.spec: 'org.chromium.mojo.system.SharedBufferHandle', + mojom.STRING.spec: 'String', + mojom.UINT16.spec: 'short', + mojom.UINT32.spec: 'int', + mojom.UINT64.spec: 'long', + mojom.UINT8.spec: 'byte', +} + +_spec_to_decode_method = { + mojom.BOOL.spec: 'readBoolean', + mojom.DCPIPE.spec: 'readConsumerHandle', + mojom.DOUBLE.spec: 'readDouble', + mojom.DPPIPE.spec: 'readProducerHandle', + mojom.FLOAT.spec: 'readFloat', + mojom.HANDLE.spec: 'readUntypedHandle', + mojom.INT16.spec: 'readShort', + mojom.INT32.spec: 'readInt', + mojom.INT64.spec: 'readLong', + mojom.INT8.spec: 'readByte', + mojom.MSGPIPE.spec: 'readMessagePipeHandle', + mojom.NULLABLE_DCPIPE.spec: 'readConsumerHandle', + mojom.NULLABLE_DPPIPE.spec: 'readProducerHandle', + mojom.NULLABLE_HANDLE.spec: 'readUntypedHandle', + mojom.NULLABLE_MSGPIPE.spec: 'readMessagePipeHandle', + mojom.NULLABLE_SHAREDBUFFER.spec: 'readSharedBufferHandle', + mojom.NULLABLE_STRING.spec: 'readString', + mojom.SHAREDBUFFER.spec: 'readSharedBufferHandle', + mojom.STRING.spec: 'readString', + mojom.UINT16.spec: 'readShort', + mojom.UINT32.spec: 'readInt', + mojom.UINT64.spec: 'readLong', + mojom.UINT8.spec: 'readByte', +} + +_java_primitive_to_boxed_type = { + 'boolean': 'Boolean', + 'byte': 'Byte', + 'double': 'Double', + 'float': 'Float', + 'int': 'Integer', + 'long': 'Long', + 'short': 'Short', +} + + +def NameToComponent(name): + # insert '_' between anything and a Title name (e.g, HTTPEntry2FooBar -> + # HTTP_Entry2_FooBar) + name = re.sub('([^_])([A-Z][^A-Z_]+)', r'\1_\2', name) + # insert '_' between non upper and start of upper blocks (e.g., + # HTTP_Entry2_FooBar -> HTTP_Entry2_Foo_Bar) + name = re.sub('([^A-Z_])([A-Z])', r'\1_\2', name) + return [x.lower() for x in name.split('_')] + +def UpperCamelCase(name): + return ''.join([x.capitalize() for x in NameToComponent(name)]) + +def CamelCase(name): + uccc = UpperCamelCase(name) + return uccc[0].lower() + uccc[1:] + +def ConstantStyle(name): + components = NameToComponent(name) + if components[0] == 'k' and len(components) > 1: + components = components[1:] + # variable cannot starts with a digit. + if components[0][0].isdigit(): + components[0] = '_' + components[0] + return '_'.join([x.upper() for x in components]) + +def GetNameForElement(element): + if (mojom.IsEnumKind(element) or mojom.IsInterfaceKind(element) or + mojom.IsStructKind(element) or mojom.IsUnionKind(element)): + return UpperCamelCase(element.name) + if mojom.IsInterfaceRequestKind(element) or mojom.IsAssociatedKind(element): + return GetNameForElement(element.kind) + if isinstance(element, (mojom.Method, + mojom.Parameter, + mojom.Field)): + return CamelCase(element.name) + if isinstance(element, mojom.EnumValue): + return (GetNameForElement(element.enum) + '.' + + ConstantStyle(element.name)) + if isinstance(element, (mojom.NamedValue, + mojom.Constant, + mojom.EnumField)): + return ConstantStyle(element.name) + raise Exception('Unexpected element: %s' % element) + +def GetInterfaceResponseName(method): + return UpperCamelCase(method.name + 'Response') + +def ParseStringAttribute(attribute): + assert isinstance(attribute, basestring) + return attribute + +def GetJavaTrueFalse(value): + return 'true' if value else 'false' + +def GetArrayNullabilityFlags(kind): + """Returns nullability flags for an array type, see Decoder.java. + + As we have dedicated decoding functions for arrays, we have to pass + nullability information about both the array itself, as well as the array + element type there. + """ + assert mojom.IsArrayKind(kind) + ARRAY_NULLABLE = \ + 'org.chromium.mojo.bindings.BindingsHelper.ARRAY_NULLABLE' + ELEMENT_NULLABLE = \ + 'org.chromium.mojo.bindings.BindingsHelper.ELEMENT_NULLABLE' + NOTHING_NULLABLE = \ + 'org.chromium.mojo.bindings.BindingsHelper.NOTHING_NULLABLE' + + flags_to_set = [] + if mojom.IsNullableKind(kind): + flags_to_set.append(ARRAY_NULLABLE) + if mojom.IsNullableKind(kind.kind): + flags_to_set.append(ELEMENT_NULLABLE) + + if not flags_to_set: + flags_to_set = [NOTHING_NULLABLE] + return ' | '.join(flags_to_set) + + +def AppendEncodeDecodeParams(initial_params, context, kind, bit): + """ Appends standard parameters shared between encode and decode calls. """ + params = list(initial_params) + if (kind == mojom.BOOL): + params.append(str(bit)) + if mojom.IsReferenceKind(kind): + if mojom.IsArrayKind(kind): + params.append(GetArrayNullabilityFlags(kind)) + else: + params.append(GetJavaTrueFalse(mojom.IsNullableKind(kind))) + if mojom.IsArrayKind(kind): + params.append(GetArrayExpectedLength(kind)) + if mojom.IsInterfaceKind(kind): + params.append('%s.MANAGER' % GetJavaType(context, kind)) + if mojom.IsArrayKind(kind) and mojom.IsInterfaceKind(kind.kind): + params.append('%s.MANAGER' % GetJavaType(context, kind.kind)) + return params + + +@contextfilter +def DecodeMethod(context, kind, offset, bit): + def _DecodeMethodName(kind): + if mojom.IsArrayKind(kind): + return _DecodeMethodName(kind.kind) + 's' + if mojom.IsEnumKind(kind): + return _DecodeMethodName(mojom.INT32) + if mojom.IsInterfaceRequestKind(kind): + return 'readInterfaceRequest' + if mojom.IsInterfaceKind(kind): + return 'readServiceInterface' + if mojom.IsAssociatedInterfaceRequestKind(kind): + return 'readAssociatedInterfaceRequestNotSupported' + if mojom.IsAssociatedInterfaceKind(kind): + return 'readAssociatedServiceInterfaceNotSupported' + return _spec_to_decode_method[kind.spec] + methodName = _DecodeMethodName(kind) + params = AppendEncodeDecodeParams([ str(offset) ], context, kind, bit) + return '%s(%s)' % (methodName, ', '.join(params)) + +@contextfilter +def EncodeMethod(context, kind, variable, offset, bit): + params = AppendEncodeDecodeParams( + [ variable, str(offset) ], context, kind, bit) + return 'encode(%s)' % ', '.join(params) + +def GetPackage(module): + if module.attributes and 'JavaPackage' in module.attributes: + return ParseStringAttribute(module.attributes['JavaPackage']) + # Default package. + if module.namespace: + return 'org.chromium.' + module.namespace + return 'org.chromium' + +def GetNameForKind(context, kind): + def _GetNameHierachy(kind): + hierachy = [] + if kind.parent_kind: + hierachy = _GetNameHierachy(kind.parent_kind) + hierachy.append(GetNameForElement(kind)) + return hierachy + + module = context.resolve('module') + elements = [] + if GetPackage(module) != GetPackage(kind.module): + elements += [GetPackage(kind.module)] + elements += _GetNameHierachy(kind) + return '.'.join(elements) + +@contextfilter +def GetJavaClassForEnum(context, kind): + return GetNameForKind(context, kind) + +def GetBoxedJavaType(context, kind, with_generics=True): + unboxed_type = GetJavaType(context, kind, False, with_generics) + if unboxed_type in _java_primitive_to_boxed_type: + return _java_primitive_to_boxed_type[unboxed_type] + return unboxed_type + +@contextfilter +def GetJavaType(context, kind, boxed=False, with_generics=True): + if boxed: + return GetBoxedJavaType(context, kind) + if (mojom.IsStructKind(kind) or + mojom.IsInterfaceKind(kind) or + mojom.IsUnionKind(kind)): + return GetNameForKind(context, kind) + if mojom.IsInterfaceRequestKind(kind): + return ('org.chromium.mojo.bindings.InterfaceRequest<%s>' % + GetNameForKind(context, kind.kind)) + if mojom.IsAssociatedInterfaceKind(kind): + return 'org.chromium.mojo.bindings.AssociatedInterfaceNotSupported' + if mojom.IsAssociatedInterfaceRequestKind(kind): + return 'org.chromium.mojo.bindings.AssociatedInterfaceRequestNotSupported' + if mojom.IsMapKind(kind): + if with_generics: + return 'java.util.Map<%s, %s>' % ( + GetBoxedJavaType(context, kind.key_kind), + GetBoxedJavaType(context, kind.value_kind)) + else: + return 'java.util.Map' + if mojom.IsArrayKind(kind): + return '%s[]' % GetJavaType(context, kind.kind, boxed, with_generics) + if mojom.IsEnumKind(kind): + return 'int' + return _spec_to_java_type[kind.spec] + +@contextfilter +def DefaultValue(context, field): + assert field.default + if isinstance(field.kind, mojom.Struct): + assert field.default == 'default' + return 'new %s()' % GetJavaType(context, field.kind) + return '(%s) %s' % ( + GetJavaType(context, field.kind), + ExpressionToText(context, field.default, kind_spec=field.kind.spec)) + +@contextfilter +def ConstantValue(context, constant): + return '(%s) %s' % ( + GetJavaType(context, constant.kind), + ExpressionToText(context, constant.value, kind_spec=constant.kind.spec)) + +@contextfilter +def NewArray(context, kind, size): + if mojom.IsArrayKind(kind.kind): + return NewArray(context, kind.kind, size) + '[]' + return 'new %s[%s]' % ( + GetJavaType(context, kind.kind, boxed=False, with_generics=False), size) + +@contextfilter +def ExpressionToText(context, token, kind_spec=''): + def _TranslateNamedValue(named_value): + entity_name = GetNameForElement(named_value) + if named_value.parent_kind: + return GetJavaType(context, named_value.parent_kind) + '.' + entity_name + # Handle the case where named_value is a module level constant: + if not isinstance(named_value, mojom.EnumValue): + entity_name = (GetConstantsMainEntityName(named_value.module) + '.' + + entity_name) + if GetPackage(named_value.module) == GetPackage(context.resolve('module')): + return entity_name + return GetPackage(named_value.module) + '.' + entity_name + + if isinstance(token, mojom.NamedValue): + return _TranslateNamedValue(token) + if kind_spec.startswith('i') or kind_spec.startswith('u'): + # Add Long suffix to all integer literals. + number = ast.literal_eval(token.lstrip('+ ')) + if not isinstance(number, (int, long)): + raise ValueError('got unexpected type %r for int literal %r' % ( + type(number), token)) + # If the literal is too large to fit a signed long, convert it to the + # equivalent signed long. + if number >= 2 ** 63: + number -= 2 ** 64 + return '%dL' % number + if isinstance(token, mojom.BuiltinValue): + if token.value == 'double.INFINITY': + return 'java.lang.Double.POSITIVE_INFINITY' + if token.value == 'double.NEGATIVE_INFINITY': + return 'java.lang.Double.NEGATIVE_INFINITY' + if token.value == 'double.NAN': + return 'java.lang.Double.NaN' + if token.value == 'float.INFINITY': + return 'java.lang.Float.POSITIVE_INFINITY' + if token.value == 'float.NEGATIVE_INFINITY': + return 'java.lang.Float.NEGATIVE_INFINITY' + if token.value == 'float.NAN': + return 'java.lang.Float.NaN' + return token + +def GetArrayKind(kind, size = None): + if size is None: + return mojom.Array(kind) + else: + array = mojom.Array(kind, 0) + array.java_map_size = size + return array + +def GetArrayExpectedLength(kind): + if mojom.IsArrayKind(kind) and kind.length is not None: + return getattr(kind, 'java_map_size', str(kind.length)) + else: + return 'org.chromium.mojo.bindings.BindingsHelper.UNSPECIFIED_ARRAY_LENGTH' + +def IsPointerArrayKind(kind): + if not mojom.IsArrayKind(kind): + return False + sub_kind = kind.kind + return mojom.IsObjectKind(sub_kind) and not mojom.IsUnionKind(sub_kind) + +def IsUnionArrayKind(kind): + if not mojom.IsArrayKind(kind): + return False + sub_kind = kind.kind + return mojom.IsUnionKind(sub_kind) + +def GetConstantsMainEntityName(module): + if module.attributes and 'JavaConstantsClassName' in module.attributes: + return ParseStringAttribute(module.attributes['JavaConstantsClassName']) + # This constructs the name of the embedding classes for module level constants + # by extracting the mojom's filename and prepending it to Constants. + return (UpperCamelCase(module.path.split('/')[-1].rsplit('.', 1)[0]) + + 'Constants') + +def GetMethodOrdinalName(method): + return ConstantStyle(method.name) + '_ORDINAL' + +def HasMethodWithResponse(interface): + for method in interface.methods: + if method.response_parameters is not None: + return True + return False + +def HasMethodWithoutResponse(interface): + for method in interface.methods: + if method.response_parameters is None: + return True + return False + +@contextlib.contextmanager +def TempDir(): + dirname = tempfile.mkdtemp() + try: + yield dirname + finally: + shutil.rmtree(dirname) + +class Generator(generator.Generator): + + java_filters = { + 'array_expected_length': GetArrayExpectedLength, + 'array': GetArrayKind, + 'constant_value': ConstantValue, + 'decode_method': DecodeMethod, + 'default_value': DefaultValue, + 'encode_method': EncodeMethod, + 'expression_to_text': ExpressionToText, + 'has_method_without_response': HasMethodWithoutResponse, + 'has_method_with_response': HasMethodWithResponse, + 'interface_response_name': GetInterfaceResponseName, + 'is_array_kind': mojom.IsArrayKind, + 'is_any_handle_kind': mojom.IsAnyHandleKind, + "is_enum_kind": mojom.IsEnumKind, + 'is_interface_request_kind': mojom.IsInterfaceRequestKind, + 'is_map_kind': mojom.IsMapKind, + 'is_nullable_kind': mojom.IsNullableKind, + 'is_pointer_array_kind': IsPointerArrayKind, + 'is_reference_kind': mojom.IsReferenceKind, + 'is_struct_kind': mojom.IsStructKind, + 'is_union_array_kind': IsUnionArrayKind, + 'is_union_kind': mojom.IsUnionKind, + 'java_class_for_enum': GetJavaClassForEnum, + 'java_true_false': GetJavaTrueFalse, + 'java_type': GetJavaType, + 'method_ordinal_name': GetMethodOrdinalName, + 'name': GetNameForElement, + 'new_array': NewArray, + 'ucc': lambda x: UpperCamelCase(x.name), + } + + def GetJinjaExports(self): + return { + 'package': GetPackage(self.module), + } + + @staticmethod + def GetTemplatePrefix(): + return "java_templates" + + @classmethod + def GetFilters(cls): + return cls.java_filters + + def GetJinjaExportsForInterface(self, interface): + exports = self.GetJinjaExports() + exports.update({'interface': interface}) + return exports + + @UseJinja('enum.java.tmpl') + def GenerateEnumSource(self, enum): + exports = self.GetJinjaExports() + exports.update({'enum': enum}) + return exports + + @UseJinja('struct.java.tmpl') + def GenerateStructSource(self, struct): + exports = self.GetJinjaExports() + exports.update({'struct': struct}) + return exports + + @UseJinja('union.java.tmpl') + def GenerateUnionSource(self, union): + exports = self.GetJinjaExports() + exports.update({'union': union}) + return exports + + @UseJinja('interface.java.tmpl') + def GenerateInterfaceSource(self, interface): + return self.GetJinjaExportsForInterface(interface) + + @UseJinja('interface_internal.java.tmpl') + def GenerateInterfaceInternalSource(self, interface): + return self.GetJinjaExportsForInterface(interface) + + @UseJinja('constants.java.tmpl') + def GenerateConstantsSource(self, module): + exports = self.GetJinjaExports() + exports.update({'main_entity': GetConstantsMainEntityName(module), + 'constants': module.constants}) + return exports + + def DoGenerateFiles(self): + fileutil.EnsureDirectoryExists(self.output_dir) + + # Keep this above the others as .GetStructs() changes the state of the + # module, annotating structs with required information. + for struct in self.GetStructs(): + self.Write(self.GenerateStructSource(struct), + '%s.java' % GetNameForElement(struct)) + + for union in self.module.unions: + self.Write(self.GenerateUnionSource(union), + '%s.java' % GetNameForElement(union)) + + for enum in self.module.enums: + self.Write(self.GenerateEnumSource(enum), + '%s.java' % GetNameForElement(enum)) + + for interface in self.GetInterfaces(): + self.Write(self.GenerateInterfaceSource(interface), + '%s.java' % GetNameForElement(interface)) + self.Write(self.GenerateInterfaceInternalSource(interface), + '%s_Internal.java' % GetNameForElement(interface)) + + if self.module.constants: + self.Write(self.GenerateConstantsSource(self.module), + '%s.java' % GetConstantsMainEntityName(self.module)) + + def GenerateFiles(self, unparsed_args): + # TODO(rockot): Support variant output for Java. + if self.variant: + raise Exception("Variants not supported in Java bindings.") + + parser = argparse.ArgumentParser() + parser.add_argument('--java_output_directory', dest='java_output_directory') + args = parser.parse_args(unparsed_args) + package_path = GetPackage(self.module).replace('.', '/') + + # Generate the java files in a temporary directory and place a single + # srcjar in the output directory. + basename = self.MatchMojomFilePath("%s.srcjar" % self.module.name) + zip_filename = os.path.join(self.output_dir, basename) + with TempDir() as temp_java_root: + self.output_dir = os.path.join(temp_java_root, package_path) + self.DoGenerateFiles(); + build_utils.ZipDir(zip_filename, temp_java_root) + + if args.java_output_directory: + # If requested, generate the java files directly into indicated directory. + self.output_dir = os.path.join(args.java_output_directory, package_path) + self.DoGenerateFiles(); + + def GetJinjaParameters(self): + return { + 'lstrip_blocks': True, + 'trim_blocks': True, + } + + def GetGlobals(self): + return { + 'namespace': self.module.namespace, + 'module': self.module, + } diff --git a/mojo/public/tools/bindings/generators/mojom_js_generator.py b/mojo/public/tools/bindings/generators/mojom_js_generator.py new file mode 100644 index 0000000000..ab9635ee30 --- /dev/null +++ b/mojo/public/tools/bindings/generators/mojom_js_generator.py @@ -0,0 +1,425 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Generates JavaScript source files from a mojom.Module.""" + +import mojom.generate.generator as generator +import mojom.generate.module as mojom +import mojom.generate.pack as pack +import os +from mojom.generate.template_expander import UseJinja + +_kind_to_javascript_default_value = { + mojom.BOOL: "false", + mojom.INT8: "0", + mojom.UINT8: "0", + mojom.INT16: "0", + mojom.UINT16: "0", + mojom.INT32: "0", + mojom.UINT32: "0", + mojom.FLOAT: "0", + mojom.HANDLE: "null", + mojom.DCPIPE: "null", + mojom.DPPIPE: "null", + mojom.MSGPIPE: "null", + mojom.SHAREDBUFFER: "null", + mojom.NULLABLE_HANDLE: "null", + mojom.NULLABLE_DCPIPE: "null", + mojom.NULLABLE_DPPIPE: "null", + mojom.NULLABLE_MSGPIPE: "null", + mojom.NULLABLE_SHAREDBUFFER: "null", + mojom.INT64: "0", + mojom.UINT64: "0", + mojom.DOUBLE: "0", + mojom.STRING: "null", + mojom.NULLABLE_STRING: "null" +} + + +def JavaScriptType(kind): + name = [] + if kind.imported_from: + name.append(kind.imported_from["unique_name"]) + if kind.parent_kind: + name.append(kind.parent_kind.name) + name.append(kind.name) + return ".".join(name) + + +def JavaScriptDefaultValue(field): + if field.default: + if mojom.IsStructKind(field.kind): + assert field.default == "default" + return "new %s()" % JavaScriptType(field.kind) + return ExpressionToText(field.default) + if field.kind in mojom.PRIMITIVES: + return _kind_to_javascript_default_value[field.kind] + if mojom.IsStructKind(field.kind): + return "null" + if mojom.IsUnionKind(field.kind): + return "null" + if mojom.IsArrayKind(field.kind): + return "null" + if mojom.IsMapKind(field.kind): + return "null" + if mojom.IsInterfaceKind(field.kind): + return "new %sPtr()" % JavaScriptType(field.kind) + if mojom.IsInterfaceRequestKind(field.kind): + return "new bindings.InterfaceRequest()" + if mojom.IsAssociatedKind(field.kind): + return "null" + if mojom.IsEnumKind(field.kind): + return "0" + raise Exception("No valid default: %s" % field) + + +def JavaScriptPayloadSize(packed): + packed_fields = packed.packed_fields + if not packed_fields: + return 0 + last_field = packed_fields[-1] + offset = last_field.offset + last_field.size + pad = pack.GetPad(offset, 8) + return offset + pad + + +_kind_to_codec_type = { + mojom.BOOL: "codec.Uint8", + mojom.INT8: "codec.Int8", + mojom.UINT8: "codec.Uint8", + mojom.INT16: "codec.Int16", + mojom.UINT16: "codec.Uint16", + mojom.INT32: "codec.Int32", + mojom.UINT32: "codec.Uint32", + mojom.FLOAT: "codec.Float", + mojom.HANDLE: "codec.Handle", + mojom.DCPIPE: "codec.Handle", + mojom.DPPIPE: "codec.Handle", + mojom.MSGPIPE: "codec.Handle", + mojom.SHAREDBUFFER: "codec.Handle", + mojom.NULLABLE_HANDLE: "codec.NullableHandle", + mojom.NULLABLE_DCPIPE: "codec.NullableHandle", + mojom.NULLABLE_DPPIPE: "codec.NullableHandle", + mojom.NULLABLE_MSGPIPE: "codec.NullableHandle", + mojom.NULLABLE_SHAREDBUFFER: "codec.NullableHandle", + mojom.INT64: "codec.Int64", + mojom.UINT64: "codec.Uint64", + mojom.DOUBLE: "codec.Double", + mojom.STRING: "codec.String", + mojom.NULLABLE_STRING: "codec.NullableString", +} + + +def CodecType(kind): + if kind in mojom.PRIMITIVES: + return _kind_to_codec_type[kind] + if mojom.IsStructKind(kind): + pointer_type = "NullablePointerTo" if mojom.IsNullableKind(kind) \ + else "PointerTo" + return "new codec.%s(%s)" % (pointer_type, JavaScriptType(kind)) + if mojom.IsUnionKind(kind): + return JavaScriptType(kind) + if mojom.IsArrayKind(kind): + array_type = "NullableArrayOf" if mojom.IsNullableKind(kind) else "ArrayOf" + array_length = "" if kind.length is None else ", %d" % kind.length + element_type = ElementCodecType(kind.kind) + return "new codec.%s(%s%s)" % (array_type, element_type, array_length) + if mojom.IsInterfaceKind(kind): + return "new codec.%s(%sPtr)" % ( + "NullableInterface" if mojom.IsNullableKind(kind) else "Interface", + JavaScriptType(kind)) + if mojom.IsInterfaceRequestKind(kind): + return "codec.%s" % ( + "NullableInterfaceRequest" if mojom.IsNullableKind(kind) + else "InterfaceRequest") + if mojom.IsAssociatedInterfaceKind(kind): + return "codec.AssociatedInterfaceNotSupported" + if mojom.IsAssociatedInterfaceRequestKind(kind): + return "codec.AssociatedInterfaceRequestNotSupported" + if mojom.IsEnumKind(kind): + return "new codec.Enum(%s)" % JavaScriptType(kind) + if mojom.IsMapKind(kind): + map_type = "NullableMapOf" if mojom.IsNullableKind(kind) else "MapOf" + key_type = ElementCodecType(kind.key_kind) + value_type = ElementCodecType(kind.value_kind) + return "new codec.%s(%s, %s)" % (map_type, key_type, value_type) + raise Exception("No codec type for %s" % kind) + + +def ElementCodecType(kind): + return "codec.PackedBool" if mojom.IsBoolKind(kind) else CodecType(kind) + + +def JavaScriptDecodeSnippet(kind): + if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or + mojom.IsAnyInterfaceKind(kind)): + return "decodeStruct(%s)" % CodecType(kind) + if mojom.IsStructKind(kind): + return "decodeStructPointer(%s)" % JavaScriptType(kind) + if mojom.IsMapKind(kind): + return "decodeMapPointer(%s, %s)" % \ + (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind)) + if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind): + return "decodeArrayPointer(codec.PackedBool)" + if mojom.IsArrayKind(kind): + return "decodeArrayPointer(%s)" % CodecType(kind.kind) + if mojom.IsUnionKind(kind): + return "decodeUnion(%s)" % CodecType(kind) + if mojom.IsEnumKind(kind): + return JavaScriptDecodeSnippet(mojom.INT32) + raise Exception("No decode snippet for %s" % kind) + + +def JavaScriptEncodeSnippet(kind): + if (kind in mojom.PRIMITIVES or mojom.IsUnionKind(kind) or + mojom.IsAnyInterfaceKind(kind)): + return "encodeStruct(%s, " % CodecType(kind) + if mojom.IsUnionKind(kind): + return "encodeStruct(%s, " % JavaScriptType(kind) + if mojom.IsStructKind(kind): + return "encodeStructPointer(%s, " % JavaScriptType(kind) + if mojom.IsMapKind(kind): + return "encodeMapPointer(%s, %s, " % \ + (ElementCodecType(kind.key_kind), ElementCodecType(kind.value_kind)) + if mojom.IsArrayKind(kind) and mojom.IsBoolKind(kind.kind): + return "encodeArrayPointer(codec.PackedBool, "; + if mojom.IsArrayKind(kind): + return "encodeArrayPointer(%s, " % CodecType(kind.kind) + if mojom.IsEnumKind(kind): + return JavaScriptEncodeSnippet(mojom.INT32) + raise Exception("No encode snippet for %s" % kind) + + +def JavaScriptUnionDecodeSnippet(kind): + if mojom.IsUnionKind(kind): + return "decodeStructPointer(%s)" % JavaScriptType(kind) + return JavaScriptDecodeSnippet(kind) + + +def JavaScriptUnionEncodeSnippet(kind): + if mojom.IsUnionKind(kind): + return "encodeStructPointer(%s, " % JavaScriptType(kind) + return JavaScriptEncodeSnippet(kind) + + +def JavaScriptFieldOffset(packed_field): + return "offset + codec.kStructHeaderSize + %s" % packed_field.offset + + +def JavaScriptNullableParam(field): + return "true" if mojom.IsNullableKind(field.kind) else "false" + + +def GetArrayExpectedDimensionSizes(kind): + expected_dimension_sizes = [] + while mojom.IsArrayKind(kind): + expected_dimension_sizes.append(generator.ExpectedArraySize(kind) or 0) + kind = kind.kind + # Strings are serialized as variable-length arrays. + if (mojom.IsStringKind(kind)): + expected_dimension_sizes.append(0) + return expected_dimension_sizes + + +def JavaScriptValidateArrayParams(field): + nullable = JavaScriptNullableParam(field) + element_kind = field.kind.kind + element_size = pack.PackedField.GetSizeForKind(element_kind) + expected_dimension_sizes = GetArrayExpectedDimensionSizes( + field.kind) + element_type = ElementCodecType(element_kind) + return "%s, %s, %s, %s, 0" % \ + (element_size, element_type, nullable, + expected_dimension_sizes) + + +def JavaScriptValidateEnumParams(field): + return JavaScriptType(field.kind) + +def JavaScriptValidateStructParams(field): + nullable = JavaScriptNullableParam(field) + struct_type = JavaScriptType(field.kind) + return "%s, %s" % (struct_type, nullable) + +def JavaScriptValidateUnionParams(field): + nullable = JavaScriptNullableParam(field) + union_type = JavaScriptType(field.kind) + return "%s, %s" % (union_type, nullable) + +def JavaScriptValidateMapParams(field): + nullable = JavaScriptNullableParam(field) + keys_type = ElementCodecType(field.kind.key_kind) + values_kind = field.kind.value_kind; + values_type = ElementCodecType(values_kind) + values_nullable = "true" if mojom.IsNullableKind(values_kind) else "false" + return "%s, %s, %s, %s" % \ + (nullable, keys_type, values_type, values_nullable) + + +def TranslateConstants(token): + if isinstance(token, (mojom.EnumValue, mojom.NamedValue)): + # Both variable and enum constants are constructed like: + # NamespaceUid.Struct[.Enum].CONSTANT_NAME + name = [] + if token.imported_from: + name.append(token.imported_from["unique_name"]) + if token.parent_kind: + name.append(token.parent_kind.name) + if isinstance(token, mojom.EnumValue): + name.append(token.enum.name) + name.append(token.name) + return ".".join(name) + + if isinstance(token, mojom.BuiltinValue): + if token.value == "double.INFINITY" or token.value == "float.INFINITY": + return "Infinity"; + if token.value == "double.NEGATIVE_INFINITY" or \ + token.value == "float.NEGATIVE_INFINITY": + return "-Infinity"; + if token.value == "double.NAN" or token.value == "float.NAN": + return "NaN"; + + return token + + +def ExpressionToText(value): + return TranslateConstants(value) + +def IsArrayPointerField(field): + return mojom.IsArrayKind(field.kind) + +def IsEnumField(field): + return mojom.IsEnumKind(field.kind) + +def IsStringPointerField(field): + return mojom.IsStringKind(field.kind) + +def IsStructPointerField(field): + return mojom.IsStructKind(field.kind) + +def IsMapPointerField(field): + return mojom.IsMapKind(field.kind) + +def IsHandleField(field): + return mojom.IsAnyHandleKind(field.kind) + +def IsInterfaceField(field): + return mojom.IsInterfaceKind(field.kind) + +def IsInterfaceRequestField(field): + return mojom.IsInterfaceRequestKind(field.kind) + +def IsUnionField(field): + return mojom.IsUnionKind(field.kind) + +def IsBoolField(field): + return mojom.IsBoolKind(field.kind) + +def IsObjectField(field): + return mojom.IsObjectKind(field.kind) + +def IsAnyHandleOrInterfaceField(field): + return mojom.IsAnyHandleOrInterfaceKind(field.kind) + +def IsEnumField(field): + return mojom.IsEnumKind(field.kind) + +def GetRelativePath(module, base_module): + return os.path.relpath(module.path, os.path.dirname(base_module.path)) + + +class Generator(generator.Generator): + + js_filters = { + "decode_snippet": JavaScriptDecodeSnippet, + "default_value": JavaScriptDefaultValue, + "encode_snippet": JavaScriptEncodeSnippet, + "expression_to_text": ExpressionToText, + "field_offset": JavaScriptFieldOffset, + "has_callbacks": mojom.HasCallbacks, + "is_any_handle_or_interface_field": IsAnyHandleOrInterfaceField, + "is_array_pointer_field": IsArrayPointerField, + "is_bool_field": IsBoolField, + "is_enum_field": IsEnumField, + "is_handle_field": IsHandleField, + "is_interface_field": IsInterfaceField, + "is_interface_request_field": IsInterfaceRequestField, + "is_map_pointer_field": IsMapPointerField, + "is_object_field": IsObjectField, + "is_string_pointer_field": IsStringPointerField, + "is_struct_pointer_field": IsStructPointerField, + "is_union_field": IsUnionField, + "js_type": JavaScriptType, + "payload_size": JavaScriptPayloadSize, + "get_relative_path": GetRelativePath, + "stylize_method": generator.StudlyCapsToCamel, + "union_decode_snippet": JavaScriptUnionDecodeSnippet, + "union_encode_snippet": JavaScriptUnionEncodeSnippet, + "validate_array_params": JavaScriptValidateArrayParams, + "validate_enum_params": JavaScriptValidateEnumParams, + "validate_map_params": JavaScriptValidateMapParams, + "validate_nullable_params": JavaScriptNullableParam, + "validate_struct_params": JavaScriptValidateStructParams, + "validate_union_params": JavaScriptValidateUnionParams, + } + + def GetParameters(self): + return { + "namespace": self.module.namespace, + "imports": self.GetImports(), + "kinds": self.module.kinds, + "enums": self.module.enums, + "module": self.module, + "structs": self.GetStructs() + self.GetStructsFromMethods(), + "unions": self.GetUnions(), + "use_new_js_bindings": self.use_new_js_bindings, + "interfaces": self.GetInterfaces(), + "imported_interfaces": self.GetImportedInterfaces(), + } + + @staticmethod + def GetTemplatePrefix(): + return "js_templates" + + @classmethod + def GetFilters(cls): + return cls.js_filters + + @UseJinja("module.amd.tmpl") + def GenerateAMDModule(self): + return self.GetParameters() + + def GenerateFiles(self, args): + if self.variant: + raise Exception("Variants not supported in JavaScript bindings.") + + self.Write(self.GenerateAMDModule(), + self.MatchMojomFilePath("%s.js" % self.module.name)) + + def GetImports(self): + used_names = set() + for each_import in self.module.imports: + simple_name = each_import["module_name"].split(".")[0] + + # Since each import is assigned a variable in JS, they need to have unique + # names. + unique_name = simple_name + counter = 0 + while unique_name in used_names: + counter += 1 + unique_name = simple_name + str(counter) + + used_names.add(unique_name) + each_import["unique_name"] = unique_name + "$" + counter += 1 + return self.module.imports + + def GetImportedInterfaces(self): + interface_to_import = {}; + for each_import in self.module.imports: + for each_interface in each_import["module"].interfaces: + name = each_interface.name + interface_to_import[name] = each_import["unique_name"] + "." + name + return interface_to_import; + diff --git a/mojo/public/tools/bindings/mojom.gni b/mojo/public/tools/bindings/mojom.gni new file mode 100644 index 0000000000..4a244fb5b1 --- /dev/null +++ b/mojo/public/tools/bindings/mojom.gni @@ -0,0 +1,661 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +declare_args() { + # Indicates whether typemapping should be supported in this build + # configuration. This may be disabled when building external projects which + # depend on //mojo but which do not need/want all of the Chromium tree + # dependencies that come with typemapping. + # + # Note that (perhaps obviously) a huge amount of Chromium code will not build + # with typemapping disabled, so it is never valid to set this to |false| in + # any Chromium build configuration. + enable_mojom_typemapping = true +} + +mojom_generator_root = "//mojo/public/tools/bindings" +mojom_generator_script = "$mojom_generator_root/mojom_bindings_generator.py" +mojom_generator_sources = [ + "$mojom_generator_root/generators/mojom_cpp_generator.py", + "$mojom_generator_root/generators/mojom_js_generator.py", + "$mojom_generator_root/generators/mojom_java_generator.py", + "$mojom_generator_root/pylib/mojom/__init__.py", + "$mojom_generator_root/pylib/mojom/error.py", + "$mojom_generator_root/pylib/mojom/generate/__init__.py", + "$mojom_generator_root/pylib/mojom/generate/constant_resolver.py", + "$mojom_generator_root/pylib/mojom/generate/generator.py", + "$mojom_generator_root/pylib/mojom/generate/module.py", + "$mojom_generator_root/pylib/mojom/generate/pack.py", + "$mojom_generator_root/pylib/mojom/generate/template_expander.py", + "$mojom_generator_root/pylib/mojom/generate/translate.py", + "$mojom_generator_root/pylib/mojom/parse/__init__.py", + "$mojom_generator_root/pylib/mojom/parse/ast.py", + "$mojom_generator_root/pylib/mojom/parse/lexer.py", + "$mojom_generator_root/pylib/mojom/parse/parser.py", + "$mojom_generator_script", +] + +if (enable_mojom_typemapping) { + if (!is_ios) { + _bindings_configuration_files = [ + "//mojo/public/tools/bindings/chromium_bindings_configuration.gni", + "//mojo/public/tools/bindings/blink_bindings_configuration.gni", + ] + } else { + _bindings_configuration_files = + [ "//mojo/public/tools/bindings/chromium_bindings_configuration.gni" ] + } + _bindings_configurations = [] + foreach(config_file, _bindings_configuration_files) { + _bindings_configurations += [ read_file(config_file, "scope") ] + } + foreach(configuration, _bindings_configurations) { + # Check that the mojom field of each typemap refers to a mojom that exists. + foreach(typemap, configuration.typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + read_file(_typemap_config.mojom, "") + } + if (is_mac && defined(configuration.typemaps_mac)) { + foreach(typemap, configuration.typemaps_mac) { + _typemap_config = { + } + _typemap_config = typemap.config + read_file(_typemap_config.mojom, "") + } + } + } +} else { + _bindings_configuration_files = [] + _bindings_configurations = [ + { + typemaps = [] + }, + { + variant = "blink" + for_blink = true + typemaps = [] + }, + ] +} + +# Generates targets for building C++, JavaScript and Java bindings from mojom +# files. The output files will go under the generated file directory tree with +# the same path as each input file. +# +# Other targets should depend on one of these generated targets (where "foo" +# is the target name): +# +# foo +# C++ and Javascript bindings. Other mojom targets should also depend on +# this target. +# +# foo_blink +# C++ bindings using Blink standard types. +# +# foo_java +# Java bindings. +# +# Parameters: +# +# sources (optional if one of the deps sets listed below is present) +# List of source .mojom files to compile. +# +# deps (optional) +# Note: this can contain only other mojom targets. +# +# DEPRECATED: This is synonymous with public_deps because all mojom +# dependencies must be public by design. Please use public_deps. +# +# public_deps (optional) +# Note: this can contain only other mojom targets. +# +# import_dirs (optional) +# List of import directories that will get added when processing sources. +# +# testonly (optional) +# +# visibility (optional) +# +# visibility_blink (optional) +# The value to use for visibility for the blink variant. If unset, +# |visibility| is used. +# +# use_once_callback (optional) +# If set to true, generated classes will use base::OnceCallback instead of +# base::RepeatingCallback. +# Default value is false. +# TODO(dcheng): +# - Convert everything to use OnceCallback. +# - Remove support for the old mode. +# +# cpp_only (optional) +# If set to true, only the C++ bindings targets will be generated. +# +# use_new_js_bindings (optional) +# If set to true, the generated JS code will use the new module loading +# approach and the core API exposed by Web IDL. +# +# TODO(yzshen): Switch all existing users to use_new_js_bindings=true and +# remove the old mode. +# +# The following parameters are used to support the component build. They are +# needed so that bindings which are linked with a component can use the same +# export settings for classes. The first three are for the chromium variant, and +# the last three are for the blink variant. +# export_class_attribute (optional) +# The attribute to add to the class declaration. e.g. "CONTENT_EXPORT" +# export_define (optional) +# A define to be added to the source_set which is needed by the export +# header. e.g. "CONTENT_IMPLEMENTATION=1" +# export_header (optional) +# A header to be added to the generated bindings to support the component +# build. e.g. "content/common/content_export.h" +# export_class_attribute_blink (optional) +# export_define_blink (optional) +# export_header_blink (optional) +# These three parameters are the blink variants of the previous 3. +# +# The following parameters are used to correct component build dependencies. +# They are needed so mojom-mojom dependencies follow the rule that dependencies +# on a source set in another component are replaced by a dependency on the +# containing component. The first two are for the chromium variant; the other +# two are for the blink variant. +# overridden_deps (optional) +# The list of mojom deps to be overridden. +# component_deps (optional) +# The list of component deps to add to replace overridden_deps. +# overridden_deps_blink (optional) +# component_deps_blink (optional) +# These two parameters are the blink variants of the previous two. +template("mojom") { + assert( + defined(invoker.sources) || defined(invoker.deps) || + defined(invoker.public_deps), + "\"sources\" or \"deps\" must be defined for the $target_name template.") + if (defined(invoker.export_class_attribute) || + defined(invoker.export_define) || defined(invoker.export_header)) { + assert(defined(invoker.export_class_attribute)) + assert(defined(invoker.export_define)) + assert(defined(invoker.export_header)) + } + if (defined(invoker.export_class_attribute_blink) || + defined(invoker.export_define_blink) || + defined(invoker.export_header_blink)) { + assert(defined(invoker.export_class_attribute_blink)) + assert(defined(invoker.export_define_blink)) + assert(defined(invoker.export_header_blink)) + } + if (defined(invoker.overridden_deps) || defined(invoker.component_deps)) { + assert(defined(invoker.overridden_deps)) + assert(defined(invoker.component_deps)) + } + + if (defined(invoker.overridden_deps_blink) || + defined(invoker.component_deps_blink)) { + assert(defined(invoker.overridden_deps_blink)) + assert(defined(invoker.component_deps_blink)) + } + + all_deps = [] + if (defined(invoker.deps)) { + all_deps += invoker.deps + } + if (defined(invoker.public_deps)) { + all_deps += invoker.public_deps + } + + group("${target_name}__is_mojom") { + } + + # Explicitly ensure that all dependencies (invoker.deps and + # invoker.public_deps) are mojom targets. + group("${target_name}__check_deps_are_all_mojom") { + deps = [] + foreach(d, all_deps) { + name = get_label_info(d, "label_no_toolchain") + toolchain = get_label_info(d, "toolchain") + deps += [ "${name}__is_mojom(${toolchain})" ] + } + } + + # Generate code that is shared by different variants. + if (defined(invoker.sources)) { + common_generator_args = [ + "--use_bundled_pylibs", + "generate", + "{{source}}", + "-d", + rebase_path("//", root_build_dir), + "-I", + rebase_path("//", root_build_dir), + "-o", + rebase_path(root_gen_dir), + "--bytecode_path", + rebase_path("$root_gen_dir/mojo/public/tools/bindings"), + ] + + if (defined(invoker.import_dirs)) { + foreach(import_dir, invoker.import_dirs) { + common_generator_args += [ + "-I", + rebase_path(import_dir, root_build_dir), + ] + } + } + + generator_shared_cpp_outputs = [ + "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h", + "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.cc", + "{{source_gen_dir}}/{{source_name_part}}.mojom-shared.h", + ] + generator_shared_target_name = "${target_name}_shared__generator" + action_foreach(generator_shared_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + sources = invoker.sources + deps = [ + "//mojo/public/tools/bindings:precompile_templates", + ] + outputs = generator_shared_cpp_outputs + args = common_generator_args + args += [ + "--generate_non_variant_code", + "-g", + "c++", + ] + depfile = "{{source_gen_dir}}/${generator_shared_target_name}_{{source_name_part}}.d" + args += [ + "--depfile", + depfile, + "--depfile_target", + "{{source_gen_dir}}/{{source_name_part}}.mojom-shared-internal.h", + ] + } + } + + shared_cpp_sources_suffix = "shared_cpp_sources" + shared_cpp_sources_target_name = "${target_name}_${shared_cpp_sources_suffix}" + source_set(shared_cpp_sources_target_name) { + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + deps = [] + if (defined(invoker.sources)) { + sources = + process_file_template(invoker.sources, generator_shared_cpp_outputs) + deps += [ ":$generator_shared_target_name" ] + } + public_deps = [] + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append shared_cpp_sources_suffix + # to get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}_${shared_cpp_sources_suffix}" ] + } + } + + # Generate code for variants. + foreach(bindings_configuration, _bindings_configurations) { + cpp_only = false + if (defined(invoker.cpp_only)) { + cpp_only = invoker.cpp_only + } + variant_suffix = "" + if (defined(bindings_configuration.variant)) { + variant = bindings_configuration.variant + variant_suffix = "_${variant}" + cpp_only = true + } + type_mappings_target_name = "${target_name}${variant_suffix}__type_mappings" + type_mappings_path = + "$target_gen_dir/${target_name}${variant_suffix}__type_mappings" + active_typemaps = [] + enabled_sources = [] + if (defined(invoker.sources)) { + generator_cpp_outputs = [] + generator_js_outputs = [] + generator_java_outputs = [] + variant_dash_suffix = "" + if (defined(variant)) { + variant_dash_suffix = "-${variant}" + } + generator_cpp_outputs += [ + "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc", + "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.h", + ] + enabled_sources = [] + if (defined(bindings_configuration.blacklist)) { + foreach(source, invoker.sources) { + blacklisted = false + foreach(blacklisted_source, bindings_configuration.blacklist) { + if (get_path_info(source, "abspath") == blacklisted_source) { + blacklisted = true + } + } + if (!blacklisted) { + enabled_sources += [ source ] + } + } + } else { + enabled_sources = invoker.sources + } + foreach(source, enabled_sources) { + # TODO(sammc): Use a map instead of a linear scan when GN supports maps. + foreach(typemap, bindings_configuration.typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + if (get_path_info(source, "abspath") == _typemap_config.mojom) { + active_typemaps += [ typemap ] + } + } + if (is_mac && defined(bindings_configuration.typemaps_mac)) { + foreach(typemap, bindings_configuration.typemaps_mac) { + _typemap_config = { + } + _typemap_config = typemap.config + if (get_path_info(source, "abspath") == _typemap_config.mojom) { + active_typemaps += [ typemap ] + } + } + } + } + + if (!cpp_only) { + generator_js_outputs = + [ "{{source_gen_dir}}/{{source_name_part}}.mojom.js" ] + generator_java_outputs = + [ "{{source_gen_dir}}/{{source_name_part}}.mojom.srcjar" ] + } + generator_target_name = "${target_name}${variant_suffix}__generator" + action_foreach(generator_target_name) { + script = mojom_generator_script + inputs = mojom_generator_sources + sources = invoker.sources + deps = [ + ":$type_mappings_target_name", + "//mojo/public/tools/bindings:precompile_templates", + ] + outputs = generator_cpp_outputs + generator_java_outputs + + generator_js_outputs + args = common_generator_args + + if (cpp_only) { + args += [ + "-g", + "c++", + ] + } else { + args += [ + "-g", + "c++,javascript,java", + ] + } + + if (defined(bindings_configuration.variant)) { + args += [ + "--variant", + bindings_configuration.variant, + ] + } + depfile = + "{{source_gen_dir}}/${generator_target_name}_{{source_name_part}}.d" + args += [ + "--depfile", + depfile, + "--depfile_target", + "{{source_gen_dir}}/{{source_name_part}}.mojom${variant_dash_suffix}.cc", + ] + + args += [ + "--typemap", + rebase_path(type_mappings_path, root_build_dir), + ] + + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + args += [ "--for_blink" ] + if (defined(invoker.export_class_attribute_blink)) { + args += [ + "--export_attribute", + invoker.export_class_attribute_blink, + "--export_header", + invoker.export_header_blink, + ] + } + } else { + if (defined(invoker.export_class_attribute)) { + args += [ + "--export_attribute", + invoker.export_class_attribute, + "--export_header", + invoker.export_header, + ] + } + } + + if (defined(invoker.use_once_callback) && invoker.use_once_callback) { + args += [ "--use_once_callback" ] + } + + if (defined(invoker.use_new_js_bindings) && + invoker.use_new_js_bindings) { + args += [ "--use_new_js_bindings" ] + } + } + } + + action(type_mappings_target_name) { + inputs = _bindings_configuration_files + outputs = [ + type_mappings_path, + ] + script = "$mojom_generator_root/generate_type_mappings.py" + deps = [] + args = [ + "--output", + rebase_path(type_mappings_path, root_build_dir), + ] + + foreach(d, all_deps) { + name = get_label_info(d, "label_no_toolchain") + toolchain = get_label_info(d, "toolchain") + dependency_output = "${name}${variant_suffix}__type_mappings" + dependency_target = "${dependency_output}(${toolchain})" + deps += [ dependency_target ] + dependency_output_dir = + get_label_info(dependency_output, "target_gen_dir") + dependency_name = get_label_info(dependency_output, "name") + dependency_path = + rebase_path("$dependency_output_dir/${dependency_name}", + root_build_dir) + args += [ + "--dependency", + dependency_path, + ] + } + + if (enabled_sources != []) { + # TODO(sammc): Pass the typemap description in a file to avoid command + # line length limitations. + typemap_description = [] + foreach(typemap, active_typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + typemap_description += [ "--start-typemap" ] + if (defined(_typemap_config.public_headers)) { + foreach(value, _typemap_config.public_headers) { + typemap_description += [ "public_headers=$value" ] + } + } + if (defined(_typemap_config.traits_headers)) { + foreach(value, _typemap_config.traits_headers) { + typemap_description += [ "traits_headers=$value" ] + } + } + foreach(value, _typemap_config.type_mappings) { + typemap_description += [ "type_mappings=$value" ] + } + + # The typemap configuration files are not actually used as inputs here + # but this establishes a necessary build dependency to ensure that + # typemap changes force a rebuild of affected targets. + inputs += [ typemap.filename ] + } + args += typemap_description + } + } + + source_set("${target_name}${variant_suffix}") { + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink && + defined(invoker.visibility_blink)) { + visibility = invoker.visibility_blink + } else if (defined(invoker.visibility)) { + visibility = invoker.visibility + } + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + if (defined(invoker.sources) && !defined(bindings_configuration.variant)) { + data = process_file_template(enabled_sources, generator_js_outputs) + } + defines = [] + if (defined(invoker.testonly)) { + testonly = invoker.testonly + } + if (defined(invoker.export_define)) { + defines += [ invoker.export_define ] + } + if (defined(invoker.export_define_blink)) { + defines += [ invoker.export_define_blink ] + } + if (enabled_sources != []) { + sources = process_file_template(enabled_sources, generator_cpp_outputs) + } + deps = [ + "//mojo/public/cpp/bindings:struct_traits", + "//mojo/public/interfaces/bindings:bindings__generator", + "//mojo/public/interfaces/bindings:bindings_shared__generator", + ] + public_deps = [ + ":$shared_cpp_sources_target_name", + "//base", + "//mojo/public/cpp/bindings", + ] + if (enabled_sources != []) { + public_deps += [ ":$generator_target_name" ] + } + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix to + # get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps += [ "${full_name}${variant_suffix}" ] + } + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + if (defined(invoker.overridden_deps_blink)) { + foreach(d, invoker.overridden_deps_blink) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix + # to get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps -= [ "${full_name}${variant_suffix}" ] + } + public_deps += invoker.component_deps_blink + } + } else { + if (defined(invoker.overridden_deps)) { + foreach(d, invoker.overridden_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append variant_suffix + # to get the cpp dependency name. + full_name = get_label_info("$d", "label_no_toolchain") + public_deps -= [ "${full_name}${variant_suffix}" ] + } + public_deps += invoker.component_deps + } + } + foreach(typemap, active_typemaps) { + _typemap_config = { + } + _typemap_config = typemap.config + if (defined(_typemap_config.public_headers)) { + sources += _typemap_config.public_headers + } + if (defined(_typemap_config.traits_headers)) { + sources += _typemap_config.traits_headers + } + if (defined(_typemap_config.sources)) { + sources += _typemap_config.sources + } + if (defined(_typemap_config.public_deps)) { + public_deps += _typemap_config.public_deps + } + if (defined(_typemap_config.deps)) { + deps += _typemap_config.deps + } + } + if (defined(bindings_configuration.for_blink) && + bindings_configuration.for_blink) { + public_deps += [ "//mojo/public/cpp/bindings:wtf_support" ] + } + } + + if (!cpp_only && is_android) { + import("//build/config/android/rules.gni") + + java_srcjar_target_name = target_name + "_java_sources" + action(java_srcjar_target_name) { + script = "//mojo/public/tools/gn/zip.py" + inputs = [] + if (enabled_sources != []) { + inputs = + process_file_template(enabled_sources, generator_java_outputs) + } + output = "$target_gen_dir/$target_name.srcjar" + outputs = [ + output, + ] + rebase_inputs = rebase_path(inputs, root_build_dir) + rebase_output = rebase_path(output, root_build_dir) + args = [ + "--zip-inputs=$rebase_inputs", + "--output=$rebase_output", + ] + deps = [] + if (enabled_sources != []) { + deps = [ + ":$generator_target_name", + ] + } + } + + java_target_name = target_name + "_java" + android_library(java_target_name) { + deps = [ + "//base:base_java", + "//mojo/public/java:bindings_java", + "//mojo/public/java:system_java", + ] + + foreach(d, all_deps) { + # Resolve the name, so that a target //mojo/something becomes + # //mojo/something:something and we can append "_java" to get the java + # dependency name. + full_name = get_label_info(d, "label_no_toolchain") + deps += [ "${full_name}_java" ] + } + + srcjar_deps = [ ":$java_srcjar_target_name" ] + run_findbugs_override = false + } + } + } +} diff --git a/mojo/public/tools/bindings/mojom_bindings_generator.py b/mojo/public/tools/bindings/mojom_bindings_generator.py new file mode 100755 index 0000000000..a9650d7764 --- /dev/null +++ b/mojo/public/tools/bindings/mojom_bindings_generator.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""The frontend for the Mojo bindings system.""" + + +import argparse +import imp +import json +import os +import pprint +import re +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +# Manually check for the command-line flag. (This isn't quite right, since it +# ignores, e.g., "--", but it's close enough.) +if "--use_bundled_pylibs" in sys.argv[1:]: + sys.path.insert(0, os.path.join(_GetDirAbove("mojo"), "third_party")) + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + "pylib")) + +from mojom.error import Error +import mojom.fileutil as fileutil +from mojom.generate import translate +from mojom.generate import template_expander +from mojom.parse.parser import Parse + + +_BUILTIN_GENERATORS = { + "c++": "mojom_cpp_generator.py", + "javascript": "mojom_js_generator.py", + "java": "mojom_java_generator.py", +} + + +def LoadGenerators(generators_string): + if not generators_string: + return [] # No generators. + + script_dir = os.path.dirname(os.path.abspath(__file__)) + generators = {} + for generator_name in [s.strip() for s in generators_string.split(",")]: + language = generator_name.lower() + if language in _BUILTIN_GENERATORS: + generator_name = os.path.join(script_dir, "generators", + _BUILTIN_GENERATORS[language]) + else: + print "Unknown generator name %s" % generator_name + sys.exit(1) + generator_module = imp.load_source(os.path.basename(generator_name)[:-3], + generator_name) + generators[language] = generator_module + return generators + + +def MakeImportStackMessage(imported_filename_stack): + """Make a (human-readable) message listing a chain of imports. (Returned + string begins with a newline (if nonempty) and does not end with one.)""" + return ''.join( + reversed(["\n %s was imported by %s" % (a, b) for (a, b) in \ + zip(imported_filename_stack[1:], imported_filename_stack)])) + + +class RelativePath(object): + """Represents a path relative to the source tree.""" + def __init__(self, path, source_root): + self.path = path + self.source_root = source_root + + def relative_path(self): + return os.path.relpath(os.path.abspath(self.path), + os.path.abspath(self.source_root)) + + +def FindImportFile(rel_dir, file_name, search_rel_dirs): + """Finds |file_name| in either |rel_dir| or |search_rel_dirs|. Returns a + RelativePath with first file found, or an arbitrary non-existent file + otherwise.""" + for rel_search_dir in [rel_dir] + search_rel_dirs: + path = os.path.join(rel_search_dir.path, file_name) + if os.path.isfile(path): + return RelativePath(path, rel_search_dir.source_root) + return RelativePath(os.path.join(rel_dir.path, file_name), + rel_dir.source_root) + + +class MojomProcessor(object): + """Parses mojom files and creates ASTs for them. + + Attributes: + _processed_files: {Dict[str, mojom.generate.module.Module]} Mapping from + relative mojom filename paths to the module AST for that mojom file. + """ + def __init__(self, should_generate): + self._should_generate = should_generate + self._processed_files = {} + self._parsed_files = {} + self._typemap = {} + + def LoadTypemaps(self, typemaps): + # Support some very simple single-line comments in typemap JSON. + comment_expr = r"^\s*//.*$" + def no_comments(line): + return not re.match(comment_expr, line) + for filename in typemaps: + with open(filename) as f: + typemaps = json.loads("".join(filter(no_comments, f.readlines()))) + for language, typemap in typemaps.iteritems(): + language_map = self._typemap.get(language, {}) + language_map.update(typemap) + self._typemap[language] = language_map + + def ProcessFile(self, args, remaining_args, generator_modules, filename): + self._ParseFileAndImports(RelativePath(filename, args.depth), + args.import_directories, []) + + return self._GenerateModule(args, remaining_args, generator_modules, + RelativePath(filename, args.depth)) + + def _GenerateModule(self, args, remaining_args, generator_modules, + rel_filename): + # Return the already-generated module. + if rel_filename.path in self._processed_files: + return self._processed_files[rel_filename.path] + tree = self._parsed_files[rel_filename.path] + + dirname, name = os.path.split(rel_filename.path) + + # Process all our imports first and collect the module object for each. + # We use these to generate proper type info. + imports = {} + for parsed_imp in tree.import_list: + rel_import_file = FindImportFile( + RelativePath(dirname, rel_filename.source_root), + parsed_imp.import_filename, args.import_directories) + imports[parsed_imp.import_filename] = self._GenerateModule( + args, remaining_args, generator_modules, rel_import_file) + + module = translate.OrderedModule(tree, name, imports) + + # Set the path as relative to the source root. + module.path = rel_filename.relative_path() + + # Normalize to unix-style path here to keep the generators simpler. + module.path = module.path.replace('\\', '/') + + if self._should_generate(rel_filename.path): + for language, generator_module in generator_modules.iteritems(): + generator = generator_module.Generator( + module, args.output_dir, typemap=self._typemap.get(language, {}), + variant=args.variant, bytecode_path=args.bytecode_path, + for_blink=args.for_blink, + use_once_callback=args.use_once_callback, + use_new_js_bindings=args.use_new_js_bindings, + export_attribute=args.export_attribute, + export_header=args.export_header, + generate_non_variant_code=args.generate_non_variant_code) + filtered_args = [] + if hasattr(generator_module, 'GENERATOR_PREFIX'): + prefix = '--' + generator_module.GENERATOR_PREFIX + '_' + filtered_args = [arg for arg in remaining_args + if arg.startswith(prefix)] + generator.GenerateFiles(filtered_args) + + # Save result. + self._processed_files[rel_filename.path] = module + return module + + def _ParseFileAndImports(self, rel_filename, import_directories, + imported_filename_stack): + # Ignore already-parsed files. + if rel_filename.path in self._parsed_files: + return + + if rel_filename.path in imported_filename_stack: + print "%s: Error: Circular dependency" % rel_filename.path + \ + MakeImportStackMessage(imported_filename_stack + [rel_filename.path]) + sys.exit(1) + + try: + with open(rel_filename.path) as f: + source = f.read() + except IOError as e: + print "%s: Error: %s" % (rel_filename.path, e.strerror) + \ + MakeImportStackMessage(imported_filename_stack + [rel_filename.path]) + sys.exit(1) + + try: + tree = Parse(source, rel_filename.path) + except Error as e: + full_stack = imported_filename_stack + [rel_filename.path] + print str(e) + MakeImportStackMessage(full_stack) + sys.exit(1) + + dirname = os.path.split(rel_filename.path)[0] + for imp_entry in tree.import_list: + import_file_entry = FindImportFile( + RelativePath(dirname, rel_filename.source_root), + imp_entry.import_filename, import_directories) + self._ParseFileAndImports(import_file_entry, import_directories, + imported_filename_stack + [rel_filename.path]) + + self._parsed_files[rel_filename.path] = tree + + +def _Generate(args, remaining_args): + if args.variant == "none": + args.variant = None + + for idx, import_dir in enumerate(args.import_directories): + tokens = import_dir.split(":") + if len(tokens) >= 2: + args.import_directories[idx] = RelativePath(tokens[0], tokens[1]) + else: + args.import_directories[idx] = RelativePath(tokens[0], args.depth) + generator_modules = LoadGenerators(args.generators_string) + + fileutil.EnsureDirectoryExists(args.output_dir) + + processor = MojomProcessor(lambda filename: filename in args.filename) + processor.LoadTypemaps(set(args.typemaps)) + for filename in args.filename: + processor.ProcessFile(args, remaining_args, generator_modules, filename) + if args.depfile: + assert args.depfile_target + with open(args.depfile, 'w') as f: + f.write('%s: %s' % ( + args.depfile_target, + ' '.join(processor._parsed_files.keys()))) + + return 0 + + +def _Precompile(args, _): + generator_modules = LoadGenerators(",".join(_BUILTIN_GENERATORS.keys())) + + template_expander.PrecompileTemplates(generator_modules, args.output_dir) + return 0 + + + +def main(): + parser = argparse.ArgumentParser( + description="Generate bindings from mojom files.") + parser.add_argument("--use_bundled_pylibs", action="store_true", + help="use Python modules bundled in the SDK") + + subparsers = parser.add_subparsers() + generate_parser = subparsers.add_parser( + "generate", description="Generate bindings from mojom files.") + generate_parser.add_argument("filename", nargs="+", + help="mojom input file") + generate_parser.add_argument("-d", "--depth", dest="depth", default=".", + help="depth from source root") + generate_parser.add_argument("-o", "--output_dir", dest="output_dir", + default=".", + help="output directory for generated files") + generate_parser.add_argument("-g", "--generators", + dest="generators_string", + metavar="GENERATORS", + default="c++,javascript,java", + help="comma-separated list of generators") + generate_parser.add_argument( + "-I", dest="import_directories", action="append", metavar="directory", + default=[], + help="add a directory to be searched for import files. The depth from " + "source root can be specified for each import by appending it after " + "a colon") + generate_parser.add_argument("--typemap", action="append", metavar="TYPEMAP", + default=[], dest="typemaps", + help="apply TYPEMAP to generated output") + generate_parser.add_argument("--variant", dest="variant", default=None, + help="output a named variant of the bindings") + generate_parser.add_argument( + "--bytecode_path", type=str, required=True, help=( + "the path from which to load template bytecode; to generate template " + "bytecode, run %s precompile BYTECODE_PATH" % os.path.basename( + sys.argv[0]))) + generate_parser.add_argument("--for_blink", action="store_true", + help="Use WTF types as generated types for mojo " + "string/array/map.") + generate_parser.add_argument( + "--use_once_callback", action="store_true", + help="Use base::OnceCallback instead of base::RepeatingCallback.") + generate_parser.add_argument( + "--use_new_js_bindings", action="store_true", + help="Use the new module loading approach and the core API exposed by " + "Web IDL. This option only affects the JavaScript bindings.") + generate_parser.add_argument( + "--export_attribute", type=str, default="", + help="Optional attribute to specify on class declaration to export it " + "for the component build.") + generate_parser.add_argument( + "--export_header", type=str, default="", + help="Optional header to include in the generated headers to support the " + "component build.") + generate_parser.add_argument( + "--generate_non_variant_code", action="store_true", + help="Generate code that is shared by different variants.") + generate_parser.add_argument( + "--depfile", type=str, + help="A file into which the list of input files will be written.") + generate_parser.add_argument( + "--depfile_target", type=str, + help="The target name to use in the depfile.") + generate_parser.set_defaults(func=_Generate) + + precompile_parser = subparsers.add_parser("precompile", + description="Precompile templates for the mojom bindings generator.") + precompile_parser.add_argument( + "-o", "--output_dir", dest="output_dir", default=".", + help="output directory for precompiled templates") + precompile_parser.set_defaults(func=_Precompile) + + args, remaining_args = parser.parse_known_args() + return args.func(args, remaining_args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py new file mode 100644 index 0000000000..de388561cb --- /dev/null +++ b/mojo/public/tools/bindings/mojom_bindings_generator_unittest.py @@ -0,0 +1,23 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import unittest + +from mojom_bindings_generator import MakeImportStackMessage + + +class MojoBindingsGeneratorTest(unittest.TestCase): + """Tests mojo_bindings_generator.""" + + def testMakeImportStackMessage(self): + """Tests MakeImportStackMessage().""" + self.assertEquals(MakeImportStackMessage(["x"]), "") + self.assertEquals(MakeImportStackMessage(["x", "y"]), + "\n y was imported by x") + self.assertEquals(MakeImportStackMessage(["x", "y", "z"]), + "\n z was imported by y\n y was imported by x") + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/public/tools/bindings/pylib/mojom/__init__.py b/mojo/public/tools/bindings/pylib/mojom/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom/error.py b/mojo/public/tools/bindings/pylib/mojom/error.py new file mode 100644 index 0000000000..99522b9507 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/error.py @@ -0,0 +1,27 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +class Error(Exception): + """Base class for Mojo IDL bindings parser/generator errors.""" + + def __init__(self, filename, message, lineno=None, addenda=None, **kwargs): + """|filename| is the (primary) file which caused the error, |message| is the + error message, |lineno| is the 1-based line number (or |None| if not + applicable/available), and |addenda| is a list of additional lines to append + to the final error message.""" + Exception.__init__(self, **kwargs) + self.filename = filename + self.message = message + self.lineno = lineno + self.addenda = addenda + + def __str__(self): + if self.lineno: + s = "%s:%d: Error: %s" % (self.filename, self.lineno, self.message) + else: + s = "%s: Error: %s" % (self.filename, self.message) + return "\n".join([s] + self.addenda) if self.addenda else s + + def __repr__(self): + return str(self) diff --git a/mojo/public/tools/bindings/pylib/mojom/fileutil.py b/mojo/public/tools/bindings/pylib/mojom/fileutil.py new file mode 100644 index 0000000000..b321e9f543 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/fileutil.py @@ -0,0 +1,18 @@ +# 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. + +import errno +import os.path + +def EnsureDirectoryExists(path, always_try_to_create=False): + """A wrapper for os.makedirs that does not error if the directory already + exists. A different process could be racing to create this directory.""" + + if not os.path.exists(path) or always_try_to_create: + try: + os.makedirs(path) + except OSError as e: + # There may have been a race to create this directory. + if e.errno != errno.EEXIST: + raise diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py b/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py b/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py new file mode 100644 index 0000000000..c8b21f2629 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py @@ -0,0 +1,91 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Resolves the values used for constants and enums.""" + +from itertools import ifilter +import mojom.generate.module as mojom + +def ResolveConstants(module, expression_to_text): + in_progress = set() + computed = set() + + def GetResolvedValue(named_value): + assert isinstance(named_value, (mojom.EnumValue, mojom.ConstantValue)) + if isinstance(named_value, mojom.EnumValue): + field = next(ifilter(lambda field: field.name == named_value.name, + named_value.enum.fields), None) + if not field: + raise RuntimeError( + 'Unable to get computed value for field %s of enum %s' % + (named_value.name, named_value.enum.name)) + if field not in computed: + ResolveEnum(named_value.enum) + return field.resolved_value + else: + ResolveConstant(named_value.constant) + named_value.resolved_value = named_value.constant.resolved_value + return named_value.resolved_value + + def ResolveConstant(constant): + if constant in computed: + return + if constant in in_progress: + raise RuntimeError('Circular dependency for constant: %s' % constant.name) + in_progress.add(constant) + if isinstance(constant.value, (mojom.EnumValue, mojom.ConstantValue)): + resolved_value = GetResolvedValue(constant.value) + else: + resolved_value = expression_to_text(constant.value) + constant.resolved_value = resolved_value + in_progress.remove(constant) + computed.add(constant) + + def ResolveEnum(enum): + def ResolveEnumField(enum, field, default_value): + if field in computed: + return + if field in in_progress: + raise RuntimeError('Circular dependency for enum: %s' % enum.name) + in_progress.add(field) + if field.value: + if isinstance(field.value, mojom.EnumValue): + resolved_value = GetResolvedValue(field.value) + elif isinstance(field.value, str): + resolved_value = int(field.value, 0) + else: + raise RuntimeError('Unexpected value: %s' % field.value) + else: + resolved_value = default_value + field.resolved_value = resolved_value + in_progress.remove(field) + computed.add(field) + + current_value = 0 + for field in enum.fields: + ResolveEnumField(enum, field, current_value) + current_value = field.resolved_value + 1 + + for constant in module.constants: + ResolveConstant(constant) + + for enum in module.enums: + ResolveEnum(enum) + + for struct in module.structs: + for constant in struct.constants: + ResolveConstant(constant) + for enum in struct.enums: + ResolveEnum(enum) + for field in struct.fields: + if isinstance(field.default, (mojom.ConstantValue, mojom.EnumValue)): + field.default.resolved_value = GetResolvedValue(field.default) + + for interface in module.interfaces: + for constant in interface.constants: + ResolveConstant(constant) + for enum in interface.enums: + ResolveEnum(enum) + + return module diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py new file mode 100644 index 0000000000..0e64af78a1 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator.py @@ -0,0 +1,153 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Code shared by the various language-specific code generators.""" + +from functools import partial +import os.path +import re + +import module as mojom +import mojom.fileutil as fileutil +import pack + +def ExpectedArraySize(kind): + if mojom.IsArrayKind(kind): + return kind.length + return None + +def StudlyCapsToCamel(studly): + return studly[0].lower() + studly[1:] + +def UnderToCamel(under): + """Converts underscore_separated strings to CamelCase strings.""" + return ''.join(word.capitalize() for word in under.split('_')) + +def WriteFile(contents, full_path): + # Make sure the containing directory exists. + full_dir = os.path.dirname(full_path) + fileutil.EnsureDirectoryExists(full_dir) + + # Dump the data to disk. + with open(full_path, "w+") as f: + f.write(contents) + +class Generator(object): + # Pass |output_dir| to emit files to disk. Omit |output_dir| to echo all + # files to stdout. + def __init__(self, module, output_dir=None, typemap=None, variant=None, + bytecode_path=None, for_blink=False, use_once_callback=False, + use_new_js_bindings=False, export_attribute=None, + export_header=None, generate_non_variant_code=False): + self.module = module + self.output_dir = output_dir + self.typemap = typemap or {} + self.variant = variant + self.bytecode_path = bytecode_path + self.for_blink = for_blink + self.use_once_callback = use_once_callback + self.use_new_js_bindings = use_new_js_bindings + self.export_attribute = export_attribute + self.export_header = export_header + self.generate_non_variant_code = generate_non_variant_code + + def GetStructsFromMethods(self): + result = [] + for interface in self.module.interfaces: + for method in interface.methods: + result.append(self._GetStructFromMethod(method)) + if method.response_parameters != None: + result.append(self._GetResponseStructFromMethod(method)) + return result + + def GetStructs(self): + return map(partial(self._AddStructComputedData, True), self.module.structs) + + def GetUnions(self): + return map(self._AddUnionComputedData, self.module.unions) + + def GetInterfaces(self): + return map(self._AddInterfaceComputedData, self.module.interfaces) + + # Prepend the filename with a directory that matches the directory of the + # original .mojom file, relative to the import root. + def MatchMojomFilePath(self, filename): + return os.path.join(os.path.dirname(self.module.path), filename) + + def Write(self, contents, filename): + if self.output_dir is None: + print contents + return + full_path = os.path.join(self.output_dir, filename) + WriteFile(contents, full_path) + + def GenerateFiles(self, args): + raise NotImplementedError("Subclasses must override/implement this method") + + def GetJinjaParameters(self): + """Returns default constructor parameters for the jinja environment.""" + return {} + + def GetGlobals(self): + """Returns global mappings for the template generation.""" + return {} + + def _AddStructComputedData(self, exported, struct): + """Adds computed data to the given struct. The data is computed once and + used repeatedly in the generation process.""" + struct.packed = pack.PackedStruct(struct) + struct.bytes = pack.GetByteLayout(struct.packed) + struct.versions = pack.GetVersionInfo(struct.packed) + struct.exported = exported + return struct + + def _AddUnionComputedData(self, union): + """Adds computed data to the given union. The data is computed once and + used repeatedly in the generation process.""" + ordinal = 0 + for field in union.fields: + if field.ordinal is not None: + ordinal = field.ordinal + field.ordinal = ordinal + ordinal += 1 + return union + + def _AddInterfaceComputedData(self, interface): + """Adds computed data to the given interface. The data is computed once and + used repeatedly in the generation process.""" + interface.version = 0 + for method in interface.methods: + if method.min_version is not None: + interface.version = max(interface.version, method.min_version) + + method.param_struct = self._GetStructFromMethod(method) + interface.version = max(interface.version, + method.param_struct.versions[-1].version) + + if method.response_parameters is not None: + method.response_param_struct = self._GetResponseStructFromMethod(method) + interface.version = max( + interface.version, + method.response_param_struct.versions[-1].version) + else: + method.response_param_struct = None + return interface + + def _GetStructFromMethod(self, method): + """Converts a method's parameters into the fields of a struct.""" + params_class = "%s_%s_Params" % (method.interface.name, method.name) + struct = mojom.Struct(params_class, module=method.interface.module) + for param in method.parameters: + struct.AddField(param.name, param.kind, param.ordinal, + attributes=param.attributes) + return self._AddStructComputedData(False, struct) + + def _GetResponseStructFromMethod(self, method): + """Converts a method's response_parameters into the fields of a struct.""" + params_class = "%s_%s_ResponseParams" % (method.interface.name, method.name) + struct = mojom.Struct(params_class, module=method.interface.module) + for param in method.response_parameters: + struct.AddField(param.name, param.kind, param.ordinal, + attributes=param.attributes) + return self._AddStructComputedData(False, struct) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py b/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py new file mode 100644 index 0000000000..9966b0b7f8 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py @@ -0,0 +1,24 @@ +# 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. + +import unittest + +import module as mojom +import generator + +class TestGenerator(unittest.TestCase): + + def testGetUnionsAddsOrdinals(self): + module = mojom.Module() + union = module.AddUnion('a') + union.AddField('a', mojom.BOOL) + union.AddField('b', mojom.BOOL) + union.AddField('c', mojom.BOOL, ordinal=10) + union.AddField('d', mojom.BOOL) + + gen = generator.Generator(module) + union = gen.GetUnions()[0] + ordinals = [field.ordinal for field in union.fields] + + self.assertEquals([0, 1, 10, 11], ordinals) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module.py b/mojo/public/tools/bindings/pylib/mojom/generate/module.py new file mode 100644 index 0000000000..3a5f188e75 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/module.py @@ -0,0 +1,891 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# This module's classes provide an interface to mojo modules. Modules are +# collections of interfaces and structs to be used by mojo ipc clients and +# servers. +# +# A simple interface would be created this way: +# module = mojom.generate.module.Module('Foo') +# interface = module.AddInterface('Bar') +# method = interface.AddMethod('Tat', 0) +# method.AddParameter('baz', 0, mojom.INT32) + + +# We use our own version of __repr__ when displaying the AST, as the +# AST currently doesn't capture which nodes are reference (e.g. to +# types) and which nodes are definitions. This allows us to e.g. print +# the definition of a struct when it's defined inside a module, but +# only print its name when it's referenced in e.g. a method parameter. +def Repr(obj, as_ref=True): + """A version of __repr__ that can distinguish references. + + Sometimes we like to print an object's full representation + (e.g. with its fields) and sometimes we just want to reference an + object that was printed in full elsewhere. This function allows us + to make that distinction. + + Args: + obj: The object whose string representation we compute. + as_ref: If True, use the short reference representation. + + Returns: + A str representation of |obj|. + """ + if hasattr(obj, 'Repr'): + return obj.Repr(as_ref=as_ref) + # Since we cannot implement Repr for existing container types, we + # handle them here. + elif isinstance(obj, list): + if not obj: + return '[]' + else: + return ('[\n%s\n]' % (',\n'.join(' %s' % Repr(elem, as_ref).replace( + '\n', '\n ') for elem in obj))) + elif isinstance(obj, dict): + if not obj: + return '{}' + else: + return ('{\n%s\n}' % (',\n'.join(' %s: %s' % ( + Repr(key, as_ref).replace('\n', '\n '), + Repr(val, as_ref).replace('\n', '\n ')) + for key, val in obj.iteritems()))) + else: + return repr(obj) + + +def GenericRepr(obj, names): + """Compute generic Repr for |obj| based on the attributes in |names|. + + Args: + obj: The object to compute a Repr for. + names: A dict from attribute names to include, to booleans + specifying whether those attributes should be shown as + references or not. + + Returns: + A str representation of |obj|. + """ + def ReprIndent(name, as_ref): + return ' %s=%s' % (name, Repr(getattr(obj, name), as_ref).replace( + '\n', '\n ')) + + return '%s(\n%s\n)' % ( + obj.__class__.__name__, + ',\n'.join(ReprIndent(name, as_ref) + for (name, as_ref) in names.iteritems())) + + +class Kind(object): + """Kind represents a type (e.g. int8, string). + + Attributes: + spec: A string uniquely identifying the type. May be None. + parent_kind: The enclosing type. For example, a struct defined + inside an interface has that interface as its parent. May be None. + """ + def __init__(self, spec=None): + self.spec = spec + self.parent_kind = None + + def Repr(self, as_ref=True): + return '<%s spec=%r>' % (self.__class__.__name__, self.spec) + + def __repr__(self): + # Gives us a decent __repr__ for all kinds. + return self.Repr() + + +class ReferenceKind(Kind): + """ReferenceKind represents pointer and handle types. + + A type is nullable if null (for pointer types) or invalid handle (for handle + types) is a legal value for the type. + + Attributes: + is_nullable: True if the type is nullable. + """ + + def __init__(self, spec=None, is_nullable=False): + assert spec is None or is_nullable == spec.startswith('?') + Kind.__init__(self, spec) + self.is_nullable = is_nullable + self.shared_definition = {} + + def Repr(self, as_ref=True): + return '<%s spec=%r is_nullable=%r>' % (self.__class__.__name__, self.spec, + self.is_nullable) + + def MakeNullableKind(self): + assert not self.is_nullable + + if self == STRING: + return NULLABLE_STRING + if self == HANDLE: + return NULLABLE_HANDLE + if self == DCPIPE: + return NULLABLE_DCPIPE + if self == DPPIPE: + return NULLABLE_DPPIPE + if self == MSGPIPE: + return NULLABLE_MSGPIPE + if self == SHAREDBUFFER: + return NULLABLE_SHAREDBUFFER + + nullable_kind = type(self)() + nullable_kind.shared_definition = self.shared_definition + if self.spec is not None: + nullable_kind.spec = '?' + self.spec + nullable_kind.is_nullable = True + + return nullable_kind + + @classmethod + def AddSharedProperty(cls, name): + """Adds a property |name| to |cls|, which accesses the corresponding item in + |shared_definition|. + + The reason of adding such indirection is to enable sharing definition + between a reference kind and its nullable variation. For example: + a = Struct('test_struct_1') + b = a.MakeNullableKind() + a.name = 'test_struct_2' + print b.name # Outputs 'test_struct_2'. + """ + def Get(self): + return self.shared_definition[name] + + def Set(self, value): + self.shared_definition[name] = value + + setattr(cls, name, property(Get, Set)) + + +# Initialize the set of primitive types. These can be accessed by clients. +BOOL = Kind('b') +INT8 = Kind('i8') +INT16 = Kind('i16') +INT32 = Kind('i32') +INT64 = Kind('i64') +UINT8 = Kind('u8') +UINT16 = Kind('u16') +UINT32 = Kind('u32') +UINT64 = Kind('u64') +FLOAT = Kind('f') +DOUBLE = Kind('d') +STRING = ReferenceKind('s') +HANDLE = ReferenceKind('h') +DCPIPE = ReferenceKind('h:d:c') +DPPIPE = ReferenceKind('h:d:p') +MSGPIPE = ReferenceKind('h:m') +SHAREDBUFFER = ReferenceKind('h:s') +NULLABLE_STRING = ReferenceKind('?s', True) +NULLABLE_HANDLE = ReferenceKind('?h', True) +NULLABLE_DCPIPE = ReferenceKind('?h:d:c', True) +NULLABLE_DPPIPE = ReferenceKind('?h:d:p', True) +NULLABLE_MSGPIPE = ReferenceKind('?h:m', True) +NULLABLE_SHAREDBUFFER = ReferenceKind('?h:s', True) + + +# Collection of all Primitive types +PRIMITIVES = ( + BOOL, + INT8, + INT16, + INT32, + INT64, + UINT8, + UINT16, + UINT32, + UINT64, + FLOAT, + DOUBLE, + STRING, + HANDLE, + DCPIPE, + DPPIPE, + MSGPIPE, + SHAREDBUFFER, + NULLABLE_STRING, + NULLABLE_HANDLE, + NULLABLE_DCPIPE, + NULLABLE_DPPIPE, + NULLABLE_MSGPIPE, + NULLABLE_SHAREDBUFFER +) + + +ATTRIBUTE_MIN_VERSION = 'MinVersion' +ATTRIBUTE_EXTENSIBLE = 'Extensible' +ATTRIBUTE_SYNC = 'Sync' + + +class NamedValue(object): + def __init__(self, module, parent_kind, name): + self.module = module + self.namespace = module.namespace + self.parent_kind = parent_kind + self.name = name + self.imported_from = None + + def GetSpec(self): + return (self.namespace + '.' + + (self.parent_kind and (self.parent_kind.name + '.') or "") + + self.name) + + +class BuiltinValue(object): + def __init__(self, value): + self.value = value + + +class ConstantValue(NamedValue): + def __init__(self, module, parent_kind, constant): + NamedValue.__init__(self, module, parent_kind, constant.name) + self.constant = constant + + +class EnumValue(NamedValue): + def __init__(self, module, enum, field): + NamedValue.__init__(self, module, enum.parent_kind, field.name) + self.enum = enum + + def GetSpec(self): + return (self.namespace + '.' + + (self.parent_kind and (self.parent_kind.name + '.') or "") + + self.enum.name + '.' + self.name) + + +class Constant(object): + def __init__(self, name=None, kind=None, value=None, parent_kind=None): + self.name = name + self.kind = kind + self.value = value + self.parent_kind = parent_kind + + +class Field(object): + def __init__(self, name=None, kind=None, ordinal=None, default=None, + attributes=None): + if self.__class__.__name__ == 'Field': + raise Exception() + self.name = name + self.kind = kind + self.ordinal = ordinal + self.default = default + self.attributes = attributes + + def Repr(self, as_ref=True): + # Fields are only referenced by objects which define them and thus + # they are always displayed as non-references. + return GenericRepr(self, {'name': False, 'kind': True}) + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + +class StructField(Field): pass + + +class UnionField(Field): pass + + +class Struct(ReferenceKind): + """A struct with typed fields. + + Attributes: + name: {str} The name of the struct type. + native_only: {bool} Does the struct have a body (i.e. any fields) or is it + purely a native struct. + module: {Module} The defining module. + imported_from: {dict} Information about where this union was + imported from. + fields: {List[StructField]} The members of the struct. + attributes: {dict} Additional information about the struct, such as + if it's a native struct. + """ + + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('native_only') + ReferenceKind.AddSharedProperty('module') + ReferenceKind.AddSharedProperty('imported_from') + ReferenceKind.AddSharedProperty('fields') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, name=None, module=None, attributes=None): + if name is not None: + spec = 'x:' + name + else: + spec = None + ReferenceKind.__init__(self, spec) + self.name = name + self.native_only = False + self.module = module + self.imported_from = None + self.fields = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r imported_from=%s>' % ( + self.__class__.__name__, self.name, + Repr(self.imported_from, as_ref=True)) + else: + return GenericRepr(self, {'name': False, 'fields': False, + 'imported_from': True}) + + def AddField(self, name, kind, ordinal=None, default=None, attributes=None): + field = StructField(name, kind, ordinal, default, attributes) + self.fields.append(field) + return field + + +class Union(ReferenceKind): + """A union of several kinds. + + Attributes: + name: {str} The name of the union type. + module: {Module} The defining module. + imported_from: {dict} Information about where this union was + imported from. + fields: {List[UnionField]} The members of the union. + attributes: {dict} Additional information about the union, such as + which Java class name to use to represent it in the generated + bindings. + """ + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('module') + ReferenceKind.AddSharedProperty('imported_from') + ReferenceKind.AddSharedProperty('fields') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, name=None, module=None, attributes=None): + if name is not None: + spec = 'x:' + name + else: + spec = None + ReferenceKind.__init__(self, spec) + self.name = name + self.module = module + self.imported_from = None + self.fields = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r fields=%s>' % ( + self.__class__.__name__, self.spec, self.is_nullable, + Repr(self.fields)) + else: + return GenericRepr(self, {'fields': True, 'is_nullable': False}) + + def AddField(self, name, kind, ordinal=None, attributes=None): + field = UnionField(name, kind, ordinal, None, attributes) + self.fields.append(field) + return field + + +class Array(ReferenceKind): + """An array. + + Attributes: + kind: {Kind} The type of the elements. May be None. + length: The number of elements. None if unknown. + """ + + ReferenceKind.AddSharedProperty('kind') + ReferenceKind.AddSharedProperty('length') + + def __init__(self, kind=None, length=None): + if kind is not None: + if length is not None: + spec = 'a%d:%s' % (length, kind.spec) + else: + spec = 'a:%s' % kind.spec + + ReferenceKind.__init__(self, spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + self.length = length + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r kind=%s length=%r>' % ( + self.__class__.__name__, self.spec, self.is_nullable, Repr(self.kind), + self.length) + else: + return GenericRepr(self, {'kind': True, 'length': False, + 'is_nullable': False}) + + +class Map(ReferenceKind): + """A map. + + Attributes: + key_kind: {Kind} The type of the keys. May be None. + value_kind: {Kind} The type of the elements. May be None. + """ + ReferenceKind.AddSharedProperty('key_kind') + ReferenceKind.AddSharedProperty('value_kind') + + def __init__(self, key_kind=None, value_kind=None): + if (key_kind is not None and value_kind is not None): + ReferenceKind.__init__(self, + 'm[' + key_kind.spec + '][' + value_kind.spec + + ']') + if IsNullableKind(key_kind): + raise Exception("Nullable kinds cannot be keys in maps.") + if IsAnyHandleKind(key_kind): + raise Exception("Handles cannot be keys in maps.") + if IsAnyInterfaceKind(key_kind): + raise Exception("Interfaces cannot be keys in maps.") + if IsArrayKind(key_kind): + raise Exception("Arrays cannot be keys in maps.") + else: + ReferenceKind.__init__(self) + + self.key_kind = key_kind + self.value_kind = value_kind + + def Repr(self, as_ref=True): + if as_ref: + return '<%s spec=%r is_nullable=%r key_kind=%s value_kind=%s>' % ( + self.__class__.__name__, self.spec, self.is_nullable, + Repr(self.key_kind), Repr(self.value_kind)) + else: + return GenericRepr(self, {'key_kind': True, 'value_kind': True}) + + +class InterfaceRequest(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + "Interface request requires %r to be an interface." % kind.spec) + ReferenceKind.__init__(self, 'r:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + +class AssociatedInterfaceRequest(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, InterfaceRequest): + raise Exception( + "Associated interface request requires %r to be an interface " + "request." % kind.spec) + assert not kind.is_nullable + ReferenceKind.__init__(self, 'asso:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind.kind if kind is not None else None + + +class Parameter(object): + def __init__(self, name=None, kind=None, ordinal=None, default=None, + attributes=None): + self.name = name + self.ordinal = ordinal + self.kind = kind + self.default = default + self.attributes = attributes + + def Repr(self, as_ref=True): + return '<%s name=%r kind=%s>' % (self.__class__.__name__, self.name, + self.kind.Repr(as_ref=True)) + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + +class Method(object): + def __init__(self, interface, name, ordinal=None, attributes=None): + self.interface = interface + self.name = name + self.ordinal = ordinal + self.parameters = [] + self.response_parameters = None + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r>' % (self.__class__.__name__, self.name) + else: + return GenericRepr(self, {'name': False, 'parameters': True, + 'response_parameters': True}) + + def AddParameter(self, name, kind, ordinal=None, default=None, + attributes=None): + parameter = Parameter(name, kind, ordinal, default, attributes) + self.parameters.append(parameter) + return parameter + + def AddResponseParameter(self, name, kind, ordinal=None, default=None, + attributes=None): + if self.response_parameters == None: + self.response_parameters = [] + parameter = Parameter(name, kind, ordinal, default, attributes) + self.response_parameters.append(parameter) + return parameter + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + @property + def sync(self): + return self.attributes.get(ATTRIBUTE_SYNC) \ + if self.attributes else None + + +class Interface(ReferenceKind): + ReferenceKind.AddSharedProperty('module') + ReferenceKind.AddSharedProperty('name') + ReferenceKind.AddSharedProperty('imported_from') + ReferenceKind.AddSharedProperty('methods') + ReferenceKind.AddSharedProperty('attributes') + + def __init__(self, name=None, module=None, attributes=None): + if name is not None: + spec = 'x:' + name + else: + spec = None + ReferenceKind.__init__(self, spec) + self.module = module + self.name = name + self.imported_from = None + self.methods = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r>' % (self.__class__.__name__, self.name) + else: + return GenericRepr(self, {'name': False, 'attributes': False, + 'methods': False}) + + def AddMethod(self, name, ordinal=None, attributes=None): + method = Method(self, name, ordinal, attributes) + self.methods.append(method) + return method + + # TODO(451323): Remove when the language backends no longer rely on this. + @property + def client(self): + return None + + +class AssociatedInterface(ReferenceKind): + ReferenceKind.AddSharedProperty('kind') + + def __init__(self, kind=None): + if kind is not None: + if not isinstance(kind, Interface): + raise Exception( + "Associated interface requires %r to be an interface." % kind.spec) + assert not kind.is_nullable + ReferenceKind.__init__(self, 'asso:' + kind.spec) + else: + ReferenceKind.__init__(self) + self.kind = kind + + +class EnumField(object): + def __init__(self, name=None, value=None, attributes=None, + numeric_value=None): + self.name = name + self.value = value + self.attributes = attributes + self.numeric_value = numeric_value + + @property + def min_version(self): + return self.attributes.get(ATTRIBUTE_MIN_VERSION) \ + if self.attributes else None + + +class Enum(Kind): + def __init__(self, name=None, module=None, attributes=None): + self.module = module + self.name = name + self.native_only = False + self.imported_from = None + if name is not None: + spec = 'x:' + name + else: + spec = None + Kind.__init__(self, spec) + self.fields = [] + self.attributes = attributes + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r>' % (self.__class__.__name__, self.name) + else: + return GenericRepr(self, {'name': False, 'fields': False}) + + @property + def extensible(self): + return self.attributes.get(ATTRIBUTE_EXTENSIBLE, False) \ + if self.attributes else False + + +class Module(object): + def __init__(self, name=None, namespace=None, attributes=None): + self.name = name + self.path = name + self.namespace = namespace + self.structs = [] + self.unions = [] + self.interfaces = [] + self.kinds = {} + self.attributes = attributes + + def __repr__(self): + # Gives us a decent __repr__ for modules. + return self.Repr() + + def Repr(self, as_ref=True): + if as_ref: + return '<%s name=%r namespace=%r>' % ( + self.__class__.__name__, self.name, self.namespace) + else: + return GenericRepr(self, {'name': False, 'namespace': False, + 'attributes': False, 'structs': False, + 'interfaces': False, 'unions': False}) + + def AddInterface(self, name, attributes=None): + interface = Interface(name, self, attributes) + self.interfaces.append(interface) + return interface + + def AddStruct(self, name, attributes=None): + struct = Struct(name, self, attributes) + self.structs.append(struct) + return struct + + def AddUnion(self, name, attributes=None): + union = Union(name, self, attributes) + self.unions.append(union) + return union + + +def IsBoolKind(kind): + return kind.spec == BOOL.spec + + +def IsFloatKind(kind): + return kind.spec == FLOAT.spec + + +def IsDoubleKind(kind): + return kind.spec == DOUBLE.spec + + +def IsIntegralKind(kind): + return (kind.spec == BOOL.spec or + kind.spec == INT8.spec or + kind.spec == INT16.spec or + kind.spec == INT32.spec or + kind.spec == INT64.spec or + kind.spec == UINT8.spec or + kind.spec == UINT16.spec or + kind.spec == UINT32.spec or + kind.spec == UINT64.spec) + + +def IsStringKind(kind): + return kind.spec == STRING.spec or kind.spec == NULLABLE_STRING.spec + + +def IsGenericHandleKind(kind): + return kind.spec == HANDLE.spec or kind.spec == NULLABLE_HANDLE.spec + + +def IsDataPipeConsumerKind(kind): + return kind.spec == DCPIPE.spec or kind.spec == NULLABLE_DCPIPE.spec + + +def IsDataPipeProducerKind(kind): + return kind.spec == DPPIPE.spec or kind.spec == NULLABLE_DPPIPE.spec + + +def IsMessagePipeKind(kind): + return kind.spec == MSGPIPE.spec or kind.spec == NULLABLE_MSGPIPE.spec + + +def IsSharedBufferKind(kind): + return (kind.spec == SHAREDBUFFER.spec or + kind.spec == NULLABLE_SHAREDBUFFER.spec) + + +def IsStructKind(kind): + return isinstance(kind, Struct) + + +def IsUnionKind(kind): + return isinstance(kind, Union) + + +def IsArrayKind(kind): + return isinstance(kind, Array) + + +def IsInterfaceKind(kind): + return isinstance(kind, Interface) + + +def IsAssociatedInterfaceKind(kind): + return isinstance(kind, AssociatedInterface) + + +def IsInterfaceRequestKind(kind): + return isinstance(kind, InterfaceRequest) + + +def IsAssociatedInterfaceRequestKind(kind): + return isinstance(kind, AssociatedInterfaceRequest) + + +def IsEnumKind(kind): + return isinstance(kind, Enum) + + +def IsReferenceKind(kind): + return isinstance(kind, ReferenceKind) + + +def IsNullableKind(kind): + return IsReferenceKind(kind) and kind.is_nullable + + +def IsMapKind(kind): + return isinstance(kind, Map) + + +def IsObjectKind(kind): + return IsPointerKind(kind) or IsUnionKind(kind) + + +def IsPointerKind(kind): + return (IsStructKind(kind) or IsArrayKind(kind) or IsStringKind(kind) or + IsMapKind(kind)) + + +# Please note that it doesn't include any interface kind. +def IsAnyHandleKind(kind): + return (IsGenericHandleKind(kind) or + IsDataPipeConsumerKind(kind) or + IsDataPipeProducerKind(kind) or + IsMessagePipeKind(kind) or + IsSharedBufferKind(kind)) + + +def IsAnyInterfaceKind(kind): + return (IsInterfaceKind(kind) or IsInterfaceRequestKind(kind) or + IsAssociatedKind(kind)) + + +def IsAnyHandleOrInterfaceKind(kind): + return IsAnyHandleKind(kind) or IsAnyInterfaceKind(kind) + + +def IsAssociatedKind(kind): + return (IsAssociatedInterfaceKind(kind) or + IsAssociatedInterfaceRequestKind(kind)) + + +def HasCallbacks(interface): + for method in interface.methods: + if method.response_parameters != None: + return True + return False + + +# Finds out whether an interface passes associated interfaces and associated +# interface requests. +def PassesAssociatedKinds(interface): + def _ContainsAssociatedKinds(kind, visited_kinds): + if kind in visited_kinds: + # No need to examine the kind again. + return False + visited_kinds.add(kind) + if IsAssociatedKind(kind): + return True + if IsArrayKind(kind): + return _ContainsAssociatedKinds(kind.kind, visited_kinds) + if IsStructKind(kind) or IsUnionKind(kind): + for field in kind.fields: + if _ContainsAssociatedKinds(field.kind, visited_kinds): + return True + if IsMapKind(kind): + # No need to examine the key kind, only primitive kinds and non-nullable + # string are allowed to be key kinds. + return _ContainsAssociatedKinds(kind.value_kind, visited_kinds) + return False + + visited_kinds = set() + for method in interface.methods: + for param in method.parameters: + if _ContainsAssociatedKinds(param.kind, visited_kinds): + return True + if method.response_parameters != None: + for param in method.response_parameters: + if _ContainsAssociatedKinds(param.kind, visited_kinds): + return True + return False + + +def HasSyncMethods(interface): + for method in interface.methods: + if method.sync: + return True + return False + + +def ContainsHandlesOrInterfaces(kind): + """Check if the kind contains any handles. + + This check is recursive so it checks all struct fields, containers elements, + etc. + + Args: + struct: {Kind} The kind to check. + + Returns: + {bool}: True if the kind contains handles. + """ + # We remember the types we already checked to avoid infinite recursion when + # checking recursive (or mutually recursive) types: + checked = set() + def Check(kind): + if kind.spec in checked: + return False + checked.add(kind.spec) + if IsStructKind(kind): + return any(Check(field.kind) for field in kind.fields) + elif IsUnionKind(kind): + return any(Check(field.kind) for field in kind.fields) + elif IsAnyHandleKind(kind): + return True + elif IsAnyInterfaceKind(kind): + return True + elif IsArrayKind(kind): + return Check(kind.kind) + elif IsMapKind(kind): + return Check(kind.key_kind) or Check(kind.value_kind) + else: + return False + return Check(kind) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py new file mode 100644 index 0000000000..a887686e1b --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py @@ -0,0 +1,34 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys + +import test_support + +EXPECT_EQ = test_support.EXPECT_EQ +EXPECT_TRUE = test_support.EXPECT_TRUE +RunTest = test_support.RunTest +ModulesAreEqual = test_support.ModulesAreEqual +BuildTestModule = test_support.BuildTestModule +TestTestModule = test_support.TestTestModule + + +def BuildAndTestModule(): + return TestTestModule(BuildTestModule()) + + +def TestModulesEqual(): + return EXPECT_TRUE(ModulesAreEqual(BuildTestModule(), BuildTestModule())) + + +def Main(args): + errors = 0 + errors += RunTest(BuildAndTestModule) + errors += RunTest(TestModulesEqual) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/pack.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py new file mode 100644 index 0000000000..37dc8f396b --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack.py @@ -0,0 +1,250 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import module as mojom + +# This module provides a mechanism for determining the packed order and offsets +# of a mojom.Struct. +# +# ps = pack.PackedStruct(struct) +# ps.packed_fields will access a list of PackedField objects, each of which +# will have an offset, a size and a bit (for mojom.BOOLs). + +# Size of struct header in bytes: num_bytes [4B] + version [4B]. +HEADER_SIZE = 8 + +class PackedField(object): + kind_to_size = { + mojom.BOOL: 1, + mojom.INT8: 1, + mojom.UINT8: 1, + mojom.INT16: 2, + mojom.UINT16: 2, + mojom.INT32: 4, + mojom.UINT32: 4, + mojom.FLOAT: 4, + mojom.HANDLE: 4, + mojom.MSGPIPE: 4, + mojom.SHAREDBUFFER: 4, + mojom.DCPIPE: 4, + mojom.DPPIPE: 4, + mojom.NULLABLE_HANDLE: 4, + mojom.NULLABLE_MSGPIPE: 4, + mojom.NULLABLE_SHAREDBUFFER: 4, + mojom.NULLABLE_DCPIPE: 4, + mojom.NULLABLE_DPPIPE: 4, + mojom.INT64: 8, + mojom.UINT64: 8, + mojom.DOUBLE: 8, + mojom.STRING: 8, + mojom.NULLABLE_STRING: 8 + } + + @classmethod + def GetSizeForKind(cls, kind): + if isinstance(kind, (mojom.Array, mojom.Map, mojom.Struct, + mojom.Interface, mojom.AssociatedInterface)): + return 8 + if isinstance(kind, mojom.Union): + return 16 + if isinstance(kind, mojom.InterfaceRequest): + kind = mojom.MSGPIPE + if isinstance(kind, mojom.AssociatedInterfaceRequest): + return 4 + if isinstance(kind, mojom.Enum): + # TODO(mpcomplete): what about big enums? + return cls.kind_to_size[mojom.INT32] + if not kind in cls.kind_to_size: + raise Exception("Invalid kind: %s" % kind.spec) + return cls.kind_to_size[kind] + + @classmethod + def GetAlignmentForKind(cls, kind): + if isinstance(kind, (mojom.Interface, mojom.AssociatedInterface)): + return 4 + if isinstance(kind, mojom.Union): + return 8 + return cls.GetSizeForKind(kind) + + def __init__(self, field, index, ordinal): + """ + Args: + field: the original field. + index: the position of the original field in the struct. + ordinal: the ordinal of the field for serialization. + """ + self.field = field + self.index = index + self.ordinal = ordinal + self.size = self.GetSizeForKind(field.kind) + self.alignment = self.GetAlignmentForKind(field.kind) + self.offset = None + self.bit = None + self.min_version = None + + +def GetPad(offset, alignment): + """Returns the pad necessary to reserve space so that |offset + pad| equals to + some multiple of |alignment|.""" + return (alignment - (offset % alignment)) % alignment + + +def GetFieldOffset(field, last_field): + """Returns a 2-tuple of the field offset and bit (for BOOLs).""" + if (field.field.kind == mojom.BOOL and + last_field.field.kind == mojom.BOOL and + last_field.bit < 7): + return (last_field.offset, last_field.bit + 1) + + offset = last_field.offset + last_field.size + pad = GetPad(offset, field.alignment) + return (offset + pad, 0) + + +def GetPayloadSizeUpToField(field): + """Returns the payload size (not including struct header) if |field| is the + last field. + """ + if not field: + return 0 + offset = field.offset + field.size + pad = GetPad(offset, 8) + return offset + pad + + +class PackedStruct(object): + def __init__(self, struct): + self.struct = struct + # |packed_fields| contains all the fields, in increasing offset order. + self.packed_fields = [] + # |packed_fields_in_ordinal_order| refers to the same fields as + # |packed_fields|, but in ordinal order. + self.packed_fields_in_ordinal_order = [] + + # No fields. + if (len(struct.fields) == 0): + return + + # Start by sorting by ordinal. + src_fields = self.packed_fields_in_ordinal_order + ordinal = 0 + for index, field in enumerate(struct.fields): + if field.ordinal is not None: + ordinal = field.ordinal + src_fields.append(PackedField(field, index, ordinal)) + ordinal += 1 + src_fields.sort(key=lambda field: field.ordinal) + + # Set |min_version| for each field. + next_min_version = 0 + for packed_field in src_fields: + if packed_field.field.min_version is None: + assert next_min_version == 0 + else: + assert packed_field.field.min_version >= next_min_version + next_min_version = packed_field.field.min_version + packed_field.min_version = next_min_version + + if (packed_field.min_version != 0 and + mojom.IsReferenceKind(packed_field.field.kind) and + not packed_field.field.kind.is_nullable): + raise Exception("Non-nullable fields are only allowed in version 0 of " + "a struct. %s.%s is defined with [MinVersion=%d]." + % (self.struct.name, packed_field.field.name, + packed_field.min_version)) + + src_field = src_fields[0] + src_field.offset = 0 + src_field.bit = 0 + dst_fields = self.packed_fields + dst_fields.append(src_field) + + # Then find first slot that each field will fit. + for src_field in src_fields[1:]: + last_field = dst_fields[0] + for i in xrange(1, len(dst_fields)): + next_field = dst_fields[i] + offset, bit = GetFieldOffset(src_field, last_field) + if offset + src_field.size <= next_field.offset: + # Found hole. + src_field.offset = offset + src_field.bit = bit + dst_fields.insert(i, src_field) + break + last_field = next_field + if src_field.offset is None: + # Add to end + src_field.offset, src_field.bit = GetFieldOffset(src_field, last_field) + dst_fields.append(src_field) + + +class ByteInfo(object): + def __init__(self): + self.is_padding = False + self.packed_fields = [] + + +def GetByteLayout(packed_struct): + total_payload_size = GetPayloadSizeUpToField( + packed_struct.packed_fields[-1] if packed_struct.packed_fields else None) + bytes = [ByteInfo() for i in xrange(total_payload_size)] + + limit_of_previous_field = 0 + for packed_field in packed_struct.packed_fields: + for i in xrange(limit_of_previous_field, packed_field.offset): + bytes[i].is_padding = True + bytes[packed_field.offset].packed_fields.append(packed_field) + limit_of_previous_field = packed_field.offset + packed_field.size + + for i in xrange(limit_of_previous_field, len(bytes)): + bytes[i].is_padding = True + + for byte in bytes: + # A given byte cannot both be padding and have a fields packed into it. + assert not (byte.is_padding and byte.packed_fields) + + return bytes + + +class VersionInfo(object): + def __init__(self, version, num_fields, num_bytes): + self.version = version + self.num_fields = num_fields + self.num_bytes = num_bytes + + +def GetVersionInfo(packed_struct): + """Get version information for a struct. + + Args: + packed_struct: A PackedStruct instance. + + Returns: + A non-empty list of VersionInfo instances, sorted by version in increasing + order. + Note: The version numbers may not be consecutive. + """ + versions = [] + last_version = 0 + last_num_fields = 0 + last_payload_size = 0 + + for packed_field in packed_struct.packed_fields_in_ordinal_order: + if packed_field.min_version != last_version: + versions.append( + VersionInfo(last_version, last_num_fields, + last_payload_size + HEADER_SIZE)) + last_version = packed_field.min_version + + last_num_fields += 1 + # The fields are iterated in ordinal order here. However, the size of a + # version is determined by the last field of that version in pack order, + # instead of ordinal order. Therefore, we need to calculate the max value. + last_payload_size = max(GetPayloadSizeUpToField(packed_field), + last_payload_size) + + assert len(versions) == 0 or last_num_fields != versions[-1].num_fields + versions.append(VersionInfo(last_version, last_num_fields, + last_payload_size + HEADER_SIZE)) + return versions diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py new file mode 100644 index 0000000000..14f699da34 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py @@ -0,0 +1,193 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys + +import module as mojom +import pack +import test_support + + +EXPECT_EQ = test_support.EXPECT_EQ +EXPECT_TRUE = test_support.EXPECT_TRUE +RunTest = test_support.RunTest + + +def TestOrdinalOrder(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT32, 2) + struct.AddField('testfield2', mojom.INT32, 1) + ps = pack.PackedStruct(struct) + + errors += EXPECT_EQ(2, len(ps.packed_fields)) + errors += EXPECT_EQ('testfield2', ps.packed_fields[0].field.name) + errors += EXPECT_EQ('testfield1', ps.packed_fields[1].field.name) + + return errors + +def TestZeroFields(): + errors = 0 + struct = mojom.Struct('test') + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(0, len(ps.packed_fields)) + return errors + + +def TestOneField(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT8) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(1, len(ps.packed_fields)) + return errors + +# Pass three tuples. +# |kinds| is a sequence of mojom.Kinds that specify the fields that are to +# be created. +# |fields| is the expected order of the resulting fields, with the integer +# "1" first. +# |offsets| is the expected order of offsets, with the integer "0" first. +def TestSequence(kinds, fields, offsets): + errors = 0 + struct = mojom.Struct('test') + index = 1 + for kind in kinds: + struct.AddField("%d" % index, kind) + index += 1 + ps = pack.PackedStruct(struct) + num_fields = len(ps.packed_fields) + errors += EXPECT_EQ(len(kinds), num_fields) + for i in xrange(num_fields): + EXPECT_EQ("%d" % fields[i], ps.packed_fields[i].field.name) + EXPECT_EQ(offsets[i], ps.packed_fields[i].offset) + + return errors + + +def TestPaddingPackedInOrder(): + return TestSequence( + (mojom.INT8, mojom.UINT8, mojom.INT32), + (1, 2, 3), + (0, 1, 4)) + + +def TestPaddingPackedOutOfOrder(): + return TestSequence( + (mojom.INT8, mojom.INT32, mojom.UINT8), + (1, 3, 2), + (0, 1, 4)) + + +def TestPaddingPackedOverflow(): + kinds = (mojom.INT8, mojom.INT32, mojom.INT16, mojom.INT8, mojom.INT8) + # 2 bytes should be packed together first, followed by short, then by int. + fields = (1, 4, 3, 2, 5) + offsets = (0, 1, 2, 4, 8) + return TestSequence(kinds, fields, offsets) + + +def TestNullableTypes(): + kinds = (mojom.STRING.MakeNullableKind(), + mojom.HANDLE.MakeNullableKind(), + mojom.Struct('test_struct').MakeNullableKind(), + mojom.DCPIPE.MakeNullableKind(), + mojom.Array().MakeNullableKind(), + mojom.DPPIPE.MakeNullableKind(), + mojom.Array(length=5).MakeNullableKind(), + mojom.MSGPIPE.MakeNullableKind(), + mojom.Interface('test_inteface').MakeNullableKind(), + mojom.SHAREDBUFFER.MakeNullableKind(), + mojom.InterfaceRequest().MakeNullableKind()) + fields = (1, 2, 4, 3, 5, 6, 8, 7, 9, 10, 11) + offsets = (0, 8, 12, 16, 24, 32, 36, 40, 48, 52, 56) + return TestSequence(kinds, fields, offsets) + + +def TestAllTypes(): + return TestSequence( + (mojom.BOOL, mojom.INT8, mojom.STRING, mojom.UINT8, + mojom.INT16, mojom.DOUBLE, mojom.UINT16, + mojom.INT32, mojom.UINT32, mojom.INT64, + mojom.FLOAT, mojom.STRING, mojom.HANDLE, + mojom.UINT64, mojom.Struct('test'), mojom.Array(), + mojom.STRING.MakeNullableKind()), + (1, 2, 4, 5, 7, 3, 6, 8, 9, 10, 11, 13, 12, 14, 15, 16, 17, 18), + (0, 1, 2, 4, 6, 8, 16, 24, 28, 32, 40, 44, 48, 56, 64, 72, 80, 88)) + + +def TestPaddingPackedOutOfOrderByOrdinal(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('testfield1', mojom.INT8) + struct.AddField('testfield3', mojom.UINT8, 3) + struct.AddField('testfield2', mojom.INT32, 2) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(3, len(ps.packed_fields)) + + # Second byte should be packed in behind first, altering order. + errors += EXPECT_EQ('testfield1', ps.packed_fields[0].field.name) + errors += EXPECT_EQ('testfield3', ps.packed_fields[1].field.name) + errors += EXPECT_EQ('testfield2', ps.packed_fields[2].field.name) + + # Second byte should be packed with first. + errors += EXPECT_EQ(0, ps.packed_fields[0].offset) + errors += EXPECT_EQ(1, ps.packed_fields[1].offset) + errors += EXPECT_EQ(4, ps.packed_fields[2].offset) + + return errors + + +def TestBools(): + errors = 0 + struct = mojom.Struct('test') + struct.AddField('bit0', mojom.BOOL) + struct.AddField('bit1', mojom.BOOL) + struct.AddField('int', mojom.INT32) + struct.AddField('bit2', mojom.BOOL) + struct.AddField('bit3', mojom.BOOL) + struct.AddField('bit4', mojom.BOOL) + struct.AddField('bit5', mojom.BOOL) + struct.AddField('bit6', mojom.BOOL) + struct.AddField('bit7', mojom.BOOL) + struct.AddField('bit8', mojom.BOOL) + ps = pack.PackedStruct(struct) + errors += EXPECT_EQ(10, len(ps.packed_fields)) + + # First 8 bits packed together. + for i in xrange(8): + pf = ps.packed_fields[i] + errors += EXPECT_EQ(0, pf.offset) + errors += EXPECT_EQ("bit%d" % i, pf.field.name) + errors += EXPECT_EQ(i, pf.bit) + + # Ninth bit goes into second byte. + errors += EXPECT_EQ("bit8", ps.packed_fields[8].field.name) + errors += EXPECT_EQ(1, ps.packed_fields[8].offset) + errors += EXPECT_EQ(0, ps.packed_fields[8].bit) + + # int comes last. + errors += EXPECT_EQ("int", ps.packed_fields[9].field.name) + errors += EXPECT_EQ(4, ps.packed_fields[9].offset) + + return errors + + +def Main(args): + errors = 0 + errors += RunTest(TestZeroFields) + errors += RunTest(TestOneField) + errors += RunTest(TestPaddingPackedInOrder) + errors += RunTest(TestPaddingPackedOutOfOrder) + errors += RunTest(TestPaddingPackedOverflow) + errors += RunTest(TestNullableTypes) + errors += RunTest(TestAllTypes) + errors += RunTest(TestPaddingPackedOutOfOrderByOrdinal) + errors += RunTest(TestBools) + + return errors + + +if __name__ == '__main__': + sys.exit(Main(sys.argv[1:])) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py b/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py new file mode 100755 index 0000000000..41f11a2b71 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" Test runner for Mojom """ + +import subprocess +import sys + +def TestMojom(testname, args): + print '\nRunning unit tests for %s.' % testname + try: + args = [sys.executable, testname] + args + subprocess.check_call(args, stdout=sys.stdout) + print 'Succeeded' + return 0 + except subprocess.CalledProcessError as err: + print 'Failed with %s.' % str(err) + return 1 + + +def main(args): + errors = 0 + errors += TestMojom('data_tests.py', ['--test']) + errors += TestMojom('module_tests.py', ['--test']) + errors += TestMojom('pack_tests.py', ['--test']) + + if errors: + print '\nFailed tests.' + return min(errors, 127) # Make sure the return value doesn't "wrap". + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py new file mode 100644 index 0000000000..66f8954012 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py @@ -0,0 +1,67 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Based on third_party/WebKit/Source/build/scripts/template_expander.py. + +import imp +import os.path +import sys + +# Disable lint check for finding modules: +# pylint: disable=F0401 + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("jinja2") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +import jinja2 + + +def ApplyTemplate(mojo_generator, path_to_template, params, **kwargs): + loader = jinja2.ModuleLoader(os.path.join( + mojo_generator.bytecode_path, "%s.zip" % mojo_generator.GetTemplatePrefix( + ))) + final_kwargs = dict(mojo_generator.GetJinjaParameters()) + final_kwargs.update(kwargs) + jinja_env = jinja2.Environment(loader=loader, + keep_trailing_newline=True, + **final_kwargs) + jinja_env.globals.update(mojo_generator.GetGlobals()) + jinja_env.filters.update(mojo_generator.GetFilters()) + template = jinja_env.get_template(path_to_template) + return template.render(params) + + +def UseJinja(path_to_template, **kwargs): + def RealDecorator(generator): + def GeneratorInternal(*args, **kwargs2): + parameters = generator(*args, **kwargs2) + return ApplyTemplate(args[0], path_to_template, parameters, **kwargs) + GeneratorInternal.func_name = generator.func_name + return GeneratorInternal + return RealDecorator + + +def PrecompileTemplates(generator_modules, output_dir): + for module in generator_modules.values(): + generator = module.Generator(None) + jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader([os.path.join( + os.path.dirname(module.__file__), generator.GetTemplatePrefix())])) + jinja_env.filters.update(generator.GetFilters()) + jinja_env.compile_templates( + os.path.join(output_dir, "%s.zip" % generator.GetTemplatePrefix()), + extensions=["tmpl"], + zip="stored", + py_compile=True, + ignore_errors=False) diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py b/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py new file mode 100644 index 0000000000..eb394619d2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/test_support.py @@ -0,0 +1,193 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import sys +import traceback + +import module as mojom + +# Support for writing mojom test cases. +# RunTest(fn) will execute fn, catching any exceptions. fn should return +# the number of errors that are encountered. +# +# EXPECT_EQ(a, b) and EXPECT_TRUE(b) will print error information if the +# expectations are not true and return a non zero value. This allows test cases +# to be written like this +# +# def Foo(): +# errors = 0 +# errors += EXPECT_EQ('test', test()) +# ... +# return errors +# +# RunTest(foo) + +def FieldsAreEqual(field1, field2): + if field1 == field2: + return True + return field1.name == field2.name and \ + KindsAreEqual(field1.kind, field2.kind) and \ + field1.ordinal == field2.ordinal and \ + field1.default == field2.default + + +def KindsAreEqual(kind1, kind2): + if kind1 == kind2: + return True + if kind1.__class__ != kind2.__class__ or kind1.spec != kind2.spec: + return False + if kind1.__class__ == mojom.Kind: + return kind1.spec == kind2.spec + if kind1.__class__ == mojom.Struct: + if kind1.name != kind2.name or \ + kind1.spec != kind2.spec or \ + len(kind1.fields) != len(kind2.fields): + return False + for i in range(len(kind1.fields)): + if not FieldsAreEqual(kind1.fields[i], kind2.fields[i]): + return False + return True + if kind1.__class__ == mojom.Array: + return KindsAreEqual(kind1.kind, kind2.kind) + print 'Unknown Kind class: ', kind1.__class__.__name__ + return False + + +def ParametersAreEqual(parameter1, parameter2): + if parameter1 == parameter2: + return True + return parameter1.name == parameter2.name and \ + parameter1.ordinal == parameter2.ordinal and \ + parameter1.default == parameter2.default and \ + KindsAreEqual(parameter1.kind, parameter2.kind) + + +def MethodsAreEqual(method1, method2): + if method1 == method2: + return True + if method1.name != method2.name or \ + method1.ordinal != method2.ordinal or \ + len(method1.parameters) != len(method2.parameters): + return False + for i in range(len(method1.parameters)): + if not ParametersAreEqual(method1.parameters[i], method2.parameters[i]): + return False + return True + + +def InterfacesAreEqual(interface1, interface2): + if interface1 == interface2: + return True + if interface1.name != interface2.name or \ + len(interface1.methods) != len(interface2.methods): + return False + for i in range(len(interface1.methods)): + if not MethodsAreEqual(interface1.methods[i], interface2.methods[i]): + return False + return True + + +def ModulesAreEqual(module1, module2): + if module1 == module2: + return True + if module1.name != module2.name or \ + module1.namespace != module2.namespace or \ + len(module1.structs) != len(module2.structs) or \ + len(module1.interfaces) != len(module2.interfaces): + return False + for i in range(len(module1.structs)): + if not KindsAreEqual(module1.structs[i], module2.structs[i]): + return False + for i in range(len(module1.interfaces)): + if not InterfacesAreEqual(module1.interfaces[i], module2.interfaces[i]): + return False + return True + + +# Builds and returns a Module suitable for testing/ +def BuildTestModule(): + module = mojom.Module('test', 'testspace') + struct = module.AddStruct('teststruct') + struct.AddField('testfield1', mojom.INT32) + struct.AddField('testfield2', mojom.Array(mojom.INT32), 42) + + interface = module.AddInterface('Server') + method = interface.AddMethod('Foo', 42) + method.AddParameter('foo', mojom.INT32) + method.AddParameter('bar', mojom.Array(struct)) + + return module + + +# Tests if |module| is as built by BuildTestModule(). Returns the number of +# errors +def TestTestModule(module): + errors = 0 + + errors += EXPECT_EQ('test', module.name) + errors += EXPECT_EQ('testspace', module.namespace) + errors += EXPECT_EQ(1, len(module.structs)) + errors += EXPECT_EQ('teststruct', module.structs[0].name) + errors += EXPECT_EQ(2, len(module.structs[0].fields)) + errors += EXPECT_EQ('testfield1', module.structs[0].fields[0].name) + errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[0].kind) + errors += EXPECT_EQ('testfield2', module.structs[0].fields[1].name) + errors += EXPECT_EQ(mojom.Array, module.structs[0].fields[1].kind.__class__) + errors += EXPECT_EQ(mojom.INT32, module.structs[0].fields[1].kind.kind) + + errors += EXPECT_EQ(1, len(module.interfaces)) + errors += EXPECT_EQ('Server', module.interfaces[0].name) + errors += EXPECT_EQ(1, len(module.interfaces[0].methods)) + errors += EXPECT_EQ('Foo', module.interfaces[0].methods[0].name) + errors += EXPECT_EQ(2, len(module.interfaces[0].methods[0].parameters)) + errors += EXPECT_EQ('foo', module.interfaces[0].methods[0].parameters[0].name) + errors += EXPECT_EQ(mojom.INT32, + module.interfaces[0].methods[0].parameters[0].kind) + errors += EXPECT_EQ('bar', module.interfaces[0].methods[0].parameters[1].name) + errors += EXPECT_EQ( + mojom.Array, + module.interfaces[0].methods[0].parameters[1].kind.__class__) + errors += EXPECT_EQ( + module.structs[0], + module.interfaces[0].methods[0].parameters[1].kind.kind) + return errors + + +def PrintFailure(string): + stack = traceback.extract_stack() + frame = stack[len(stack)-3] + sys.stderr.write("ERROR at %s:%d, %s\n" % (frame[0], frame[1], string)) + print "Traceback:" + for line in traceback.format_list(stack[:len(stack)-2]): + sys.stderr.write(line) + + +def EXPECT_EQ(a, b): + if a != b: + PrintFailure("%s != %s" % (a, b)) + return 1 + return 0 + + +def EXPECT_TRUE(a): + if not a: + PrintFailure('Expecting True') + return 1 + return 0 + + +def RunTest(fn): + sys.stdout.write('Running %s...' % fn.__name__) + try: + errors = fn() + except: + traceback.print_exc(sys.stderr) + errors = 1 + if errors == 0: + sys.stdout.write('OK\n') + elif errors == 1: + sys.stdout.write('1 ERROR\n') + else: + sys.stdout.write('%d ERRORS\n' % errors) + return errors diff --git a/mojo/public/tools/bindings/pylib/mojom/generate/translate.py b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py new file mode 100644 index 0000000000..ffad7447a9 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/generate/translate.py @@ -0,0 +1,639 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Convert parse tree to AST. + +This module converts the parse tree to the AST we use for code generation. The +main entry point is OrderedModule, which gets passed the parser +representation of a mojom file. When called it's assumed that all imports have +already been parsed and converted to ASTs before. +""" + +import copy +import re + +import module as mojom +from mojom.parse import ast + +def _DuplicateName(values): + """Returns the 'name' of the first entry in |values| whose 'name' has already + been encountered. If there are no duplicates, returns None.""" + names = set() + for value in values: + if value.name in names: + return value.name + names.add(value.name) + return None + +def _ElemsOfType(elems, elem_type, scope): + """Find all elements of the given type. + + Args: + elems: {Sequence[Any]} Sequence of elems. + elem_type: {Type[C]} Extract all elems of this type. + scope: {str} The name of the surrounding scope (e.g. struct + definition). Used in error messages. + + Returns: + {List[C]} All elems of matching type. + """ + assert isinstance(elem_type, type) + result = [elem for elem in elems if isinstance(elem, elem_type)] + duplicate_name = _DuplicateName(result) + if duplicate_name: + raise Exception('Names in mojom must be unique within a scope. The name ' + '"%s" is used more than once within the scope "%s".' % + (duplicate_name, scope)) + return result + +def _MapKind(kind): + map_to_kind = {'bool': 'b', + 'int8': 'i8', + 'int16': 'i16', + 'int32': 'i32', + 'int64': 'i64', + 'uint8': 'u8', + 'uint16': 'u16', + 'uint32': 'u32', + 'uint64': 'u64', + 'float': 'f', + 'double': 'd', + 'string': 's', + 'handle': 'h', + 'handle<data_pipe_consumer>': 'h:d:c', + 'handle<data_pipe_producer>': 'h:d:p', + 'handle<message_pipe>': 'h:m', + 'handle<shared_buffer>': 'h:s'} + if kind.endswith('?'): + base_kind = _MapKind(kind[0:-1]) + # NOTE: This doesn't rule out enum types. Those will be detected later, when + # cross-reference is established. + reference_kinds = ('m', 's', 'h', 'a', 'r', 'x', 'asso') + if re.split('[^a-z]', base_kind, 1)[0] not in reference_kinds: + raise Exception( + 'A type (spec "%s") cannot be made nullable' % base_kind) + return '?' + base_kind + if kind.endswith('}'): + lbracket = kind.rfind('{') + value = kind[0:lbracket] + return 'm[' + _MapKind(kind[lbracket+1:-1]) + '][' + _MapKind(value) + ']' + if kind.endswith(']'): + lbracket = kind.rfind('[') + typename = kind[0:lbracket] + return 'a' + kind[lbracket+1:-1] + ':' + _MapKind(typename) + if kind.endswith('&'): + return 'r:' + _MapKind(kind[0:-1]) + if kind.startswith('asso<'): + assert kind.endswith('>') + return 'asso:' + _MapKind(kind[5:-1]) + if kind in map_to_kind: + return map_to_kind[kind] + return 'x:' + kind + +def _AttributeListToDict(attribute_list): + if attribute_list is None: + return None + assert isinstance(attribute_list, ast.AttributeList) + # TODO(vtl): Check for duplicate keys here. + return dict([(attribute.key, attribute.value) + for attribute in attribute_list]) + +builtin_values = frozenset([ + "double.INFINITY", + "double.NEGATIVE_INFINITY", + "double.NAN", + "float.INFINITY", + "float.NEGATIVE_INFINITY", + "float.NAN"]) + +def _IsBuiltinValue(value): + return value in builtin_values + +def _LookupKind(kinds, spec, scope): + """Tries to find which Kind a spec refers to, given the scope in which its + referenced. Starts checking from the narrowest scope to most general. For + example, given a struct field like + Foo.Bar x; + Foo.Bar could refer to the type 'Bar' in the 'Foo' namespace, or an inner + type 'Bar' in the struct 'Foo' in the current namespace. + + |scope| is a tuple that looks like (namespace, struct/interface), referring + to the location where the type is referenced.""" + if spec.startswith('x:'): + name = spec[2:] + for i in xrange(len(scope), -1, -1): + test_spec = 'x:' + if i > 0: + test_spec += '.'.join(scope[:i]) + '.' + test_spec += name + kind = kinds.get(test_spec) + if kind: + return kind + + return kinds.get(spec) + +def _LookupValue(values, name, scope, kind): + """Like LookupKind, but for constant values.""" + # If the type is an enum, the value can be specified as a qualified name, in + # which case the form EnumName.ENUM_VALUE must be used. We use the presence + # of a '.' in the requested name to identify this. Otherwise, we prepend the + # enum name. + if isinstance(kind, mojom.Enum) and '.' not in name: + name = '%s.%s' % (kind.spec.split(':', 1)[1], name) + for i in reversed(xrange(len(scope) + 1)): + test_spec = '.'.join(scope[:i]) + if test_spec: + test_spec += '.' + test_spec += name + value = values.get(test_spec) + if value: + return value + + return values.get(name) + +def _FixupExpression(module, value, scope, kind): + """Translates an IDENTIFIER into a built-in value or structured NamedValue + object.""" + if isinstance(value, tuple) and value[0] == 'IDENTIFIER': + # Allow user defined values to shadow builtins. + result = _LookupValue(module.values, value[1], scope, kind) + if result: + if isinstance(result, tuple): + raise Exception('Unable to resolve expression: %r' % value[1]) + return result + if _IsBuiltinValue(value[1]): + return mojom.BuiltinValue(value[1]) + return value + +def _Kind(kinds, spec, scope): + """Convert a type name into a mojom.Kind object. + + As a side-effect this function adds the result to 'kinds'. + + Args: + kinds: {Dict[str, mojom.Kind]} All known kinds up to this point, indexed by + their names. + spec: {str} A name uniquely identifying a type. + scope: {Tuple[str, str]} A tuple that looks like (namespace, + struct/interface), referring to the location where the type is + referenced. + + Returns: + {mojom.Kind} The type corresponding to 'spec'. + """ + kind = _LookupKind(kinds, spec, scope) + if kind: + return kind + + if spec.startswith('?'): + kind = _Kind(kinds, spec[1:], scope).MakeNullableKind() + elif spec.startswith('a:'): + kind = mojom.Array(_Kind(kinds, spec[2:], scope)) + elif spec.startswith('asso:'): + inner_kind = _Kind(kinds, spec[5:], scope) + if isinstance(inner_kind, mojom.InterfaceRequest): + kind = mojom.AssociatedInterfaceRequest(inner_kind) + else: + kind = mojom.AssociatedInterface(inner_kind) + elif spec.startswith('a'): + colon = spec.find(':') + length = int(spec[1:colon]) + kind = mojom.Array(_Kind(kinds, spec[colon+1:], scope), length) + elif spec.startswith('r:'): + kind = mojom.InterfaceRequest(_Kind(kinds, spec[2:], scope)) + elif spec.startswith('m['): + # Isolate the two types from their brackets. + + # It is not allowed to use map as key, so there shouldn't be nested ']'s + # inside the key type spec. + key_end = spec.find(']') + assert key_end != -1 and key_end < len(spec) - 1 + assert spec[key_end+1] == '[' and spec[-1] == ']' + + first_kind = spec[2:key_end] + second_kind = spec[key_end+2:-1] + + kind = mojom.Map(_Kind(kinds, first_kind, scope), + _Kind(kinds, second_kind, scope)) + else: + kind = mojom.Kind(spec) + + kinds[spec] = kind + return kind + +def _KindFromImport(original_kind, imported_from): + """Used with 'import module' - clones the kind imported from the given + module's namespace. Only used with Structs, Unions, Interfaces and Enums.""" + kind = copy.copy(original_kind) + # |shared_definition| is used to store various properties (see + # |AddSharedProperty()| in module.py), including |imported_from|. We don't + # want the copy to share these with the original, so copy it if necessary. + if hasattr(original_kind, 'shared_definition'): + kind.shared_definition = copy.copy(original_kind.shared_definition) + kind.imported_from = imported_from + return kind + +def _Import(module, import_module): + import_item = {} + import_item['module_name'] = import_module.name + import_item['namespace'] = import_module.namespace + import_item['module'] = import_module + + # Copy the struct kinds from our imports into the current module. + importable_kinds = (mojom.Struct, mojom.Union, mojom.Enum, mojom.Interface) + for kind in import_module.kinds.itervalues(): + if (isinstance(kind, importable_kinds) and + kind.imported_from is None): + kind = _KindFromImport(kind, import_item) + module.kinds[kind.spec] = kind + # Ditto for values. + for value in import_module.values.itervalues(): + if value.imported_from is None: + # Values don't have shared definitions (since they're not nullable), so no + # need to do anything special. + value = copy.copy(value) + value.imported_from = import_item + module.values[value.GetSpec()] = value + + return import_item + +def _Struct(module, parsed_struct): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_struct: {ast.Struct} Parsed struct. + + Returns: + {mojom.Struct} AST struct. + """ + struct = mojom.Struct(module=module) + struct.name = parsed_struct.name + struct.native_only = parsed_struct.body is None + struct.spec = 'x:' + module.namespace + '.' + struct.name + module.kinds[struct.spec] = struct + if struct.native_only: + struct.enums = [] + struct.constants = [] + struct.fields_data = [] + else: + struct.enums = map( + lambda enum: _Enum(module, enum, struct), + _ElemsOfType(parsed_struct.body, ast.Enum, parsed_struct.name)) + struct.constants = map( + lambda constant: _Constant(module, constant, struct), + _ElemsOfType(parsed_struct.body, ast.Const, parsed_struct.name)) + # Stash fields parsed_struct here temporarily. + struct.fields_data = _ElemsOfType( + parsed_struct.body, ast.StructField, parsed_struct.name) + struct.attributes = _AttributeListToDict(parsed_struct.attribute_list) + + # Enforce that a [Native] attribute is set to make native-only struct + # declarations more explicit. + if struct.native_only: + if not struct.attributes or not struct.attributes.get('Native', False): + raise Exception("Native-only struct declarations must include a " + + "Native attribute.") + + return struct + +def _Union(module, parsed_union): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_union: {ast.Union} Parsed union. + + Returns: + {mojom.Union} AST union. + """ + union = mojom.Union(module=module) + union.name = parsed_union.name + union.spec = 'x:' + module.namespace + '.' + union.name + module.kinds[union.spec] = union + # Stash fields parsed_union here temporarily. + union.fields_data = _ElemsOfType( + parsed_union.body, ast.UnionField, parsed_union.name) + union.attributes = _AttributeListToDict(parsed_union.attribute_list) + return union + +def _StructField(module, parsed_field, struct): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_field: {ast.StructField} Parsed struct field. + struct: {mojom.Struct} Struct this field belongs to. + + Returns: + {mojom.StructField} AST struct field. + """ + field = mojom.StructField() + field.name = parsed_field.name + field.kind = _Kind( + module.kinds, _MapKind(parsed_field.typename), + (module.namespace, struct.name)) + field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None + field.default = _FixupExpression( + module, parsed_field.default_value, (module.namespace, struct.name), + field.kind) + field.attributes = _AttributeListToDict(parsed_field.attribute_list) + return field + +def _UnionField(module, parsed_field, union): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_field: {ast.UnionField} Parsed union field. + union: {mojom.Union} Union this fields belong to. + + Returns: + {mojom.UnionField} AST union. + """ + field = mojom.UnionField() + field.name = parsed_field.name + field.kind = _Kind( + module.kinds, _MapKind(parsed_field.typename), + (module.namespace, union.name)) + field.ordinal = parsed_field.ordinal.value if parsed_field.ordinal else None + field.default = _FixupExpression( + module, None, (module.namespace, union.name), field.kind) + field.attributes = _AttributeListToDict(parsed_field.attribute_list) + return field + +def _Parameter(module, parsed_param, interface): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_param: {ast.Parameter} Parsed parameter. + union: {mojom.Interface} Interface this parameter belongs to. + + Returns: + {mojom.Parameter} AST parameter. + """ + parameter = mojom.Parameter() + parameter.name = parsed_param.name + parameter.kind = _Kind( + module.kinds, _MapKind(parsed_param.typename), + (module.namespace, interface.name)) + parameter.ordinal = ( + parsed_param.ordinal.value if parsed_param.ordinal else None) + parameter.default = None # TODO(tibell): We never have these. Remove field? + parameter.attributes = _AttributeListToDict(parsed_param.attribute_list) + return parameter + +def _Method(module, parsed_method, interface): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_method: {ast.Method} Parsed method. + interface: {mojom.Interface} Interface this method belongs to. + + Returns: + {mojom.Method} AST method. + """ + method = mojom.Method( + interface, parsed_method.name, + ordinal=parsed_method.ordinal.value if parsed_method.ordinal else None) + method.parameters = map( + lambda parameter: _Parameter(module, parameter, interface), + parsed_method.parameter_list) + if parsed_method.response_parameter_list is not None: + method.response_parameters = map( + lambda parameter: _Parameter(module, parameter, interface), + parsed_method.response_parameter_list) + method.attributes = _AttributeListToDict(parsed_method.attribute_list) + + # Enforce that only methods with response can have a [Sync] attribute. + if method.sync and method.response_parameters is None: + raise Exception("Only methods with response can include a [Sync] " + "attribute. If no response parameters are needed, you " + "could use an empty response parameter list, i.e., " + "\"=> ()\".") + + return method + +def _Interface(module, parsed_iface): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_iface: {ast.Interface} Parsed interface. + + Returns: + {mojom.Interface} AST interface. + """ + interface = mojom.Interface(module=module) + interface.name = parsed_iface.name + interface.spec = 'x:' + module.namespace + '.' + interface.name + module.kinds[interface.spec] = interface + interface.enums = map( + lambda enum: _Enum(module, enum, interface), + _ElemsOfType(parsed_iface.body, ast.Enum, parsed_iface.name)) + interface.constants = map( + lambda constant: _Constant(module, constant, interface), + _ElemsOfType(parsed_iface.body, ast.Const, parsed_iface.name)) + # Stash methods parsed_iface here temporarily. + interface.methods_data = _ElemsOfType( + parsed_iface.body, ast.Method, parsed_iface.name) + interface.attributes = _AttributeListToDict(parsed_iface.attribute_list) + return interface + +def _EnumField(module, enum, parsed_field, parent_kind): + """ + Args: + module: {mojom.Module} Module currently being constructed. + enum: {mojom.Enum} Enum this field belongs to. + parsed_field: {ast.EnumValue} Parsed enum value. + parent_kind: {mojom.Kind} The enclosing type. + + Returns: + {mojom.EnumField} AST enum field. + """ + field = mojom.EnumField() + field.name = parsed_field.name + # TODO(mpcomplete): FixupExpression should be done in the second pass, + # so constants and enums can refer to each other. + # TODO(mpcomplete): But then, what if constants are initialized to an enum? Or + # vice versa? + if parent_kind: + field.value = _FixupExpression( + module, parsed_field.value, (module.namespace, parent_kind.name), enum) + else: + field.value = _FixupExpression( + module, parsed_field.value, (module.namespace, ), enum) + field.attributes = _AttributeListToDict(parsed_field.attribute_list) + value = mojom.EnumValue(module, enum, field) + module.values[value.GetSpec()] = value + return field + +def _ResolveNumericEnumValues(enum_fields): + """ + Given a reference to a list of mojom.EnumField, resolves and assigns their + values to EnumField.numeric_value. + """ + + # map of <name> -> integral value + resolved_enum_values = {} + prev_value = -1 + for field in enum_fields: + # This enum value is +1 the previous enum value (e.g: BEGIN). + if field.value is None: + prev_value += 1 + + # Integral value (e.g: BEGIN = -0x1). + elif type(field.value) is str: + prev_value = int(field.value, 0) + + # Reference to a previous enum value (e.g: INIT = BEGIN). + elif type(field.value) is mojom.EnumValue: + prev_value = resolved_enum_values[field.value.name] + else: + raise Exception("Unresolved enum value.") + + resolved_enum_values[field.name] = prev_value + field.numeric_value = prev_value + +def _Enum(module, parsed_enum, parent_kind): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_enum: {ast.Enum} Parsed enum. + + Returns: + {mojom.Enum} AST enum. + """ + enum = mojom.Enum(module=module) + enum.name = parsed_enum.name + enum.native_only = parsed_enum.enum_value_list is None + name = enum.name + if parent_kind: + name = parent_kind.name + '.' + name + enum.spec = 'x:%s.%s' % (module.namespace, name) + enum.parent_kind = parent_kind + enum.attributes = _AttributeListToDict(parsed_enum.attribute_list) + if enum.native_only: + enum.fields = [] + else: + enum.fields = map( + lambda field: _EnumField(module, enum, field, parent_kind), + parsed_enum.enum_value_list) + _ResolveNumericEnumValues(enum.fields) + + module.kinds[enum.spec] = enum + + # Enforce that a [Native] attribute is set to make native-only enum + # declarations more explicit. + if enum.native_only: + if not enum.attributes or not enum.attributes.get('Native', False): + raise Exception("Native-only enum declarations must include a " + + "Native attribute.") + + return enum + +def _Constant(module, parsed_const, parent_kind): + """ + Args: + module: {mojom.Module} Module currently being constructed. + parsed_const: {ast.Const} Parsed constant. + + Returns: + {mojom.Constant} AST constant. + """ + constant = mojom.Constant() + constant.name = parsed_const.name + if parent_kind: + scope = (module.namespace, parent_kind.name) + else: + scope = (module.namespace, ) + # TODO(mpcomplete): maybe we should only support POD kinds. + constant.kind = _Kind(module.kinds, _MapKind(parsed_const.typename), scope) + constant.parent_kind = parent_kind + constant.value = _FixupExpression(module, parsed_const.value, scope, None) + + value = mojom.ConstantValue(module, parent_kind, constant) + module.values[value.GetSpec()] = value + return constant + +def _Module(tree, name, imports): + """ + Args: + tree: {ast.Mojom} The parse tree. + name: {str} The mojom filename, excluding the path. + imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in + the import list, to already processed modules. Used to process imports. + + Returns: + {mojom.Module} An AST for the mojom. + """ + module = mojom.Module() + module.kinds = {} + for kind in mojom.PRIMITIVES: + module.kinds[kind.spec] = kind + + module.values = {} + + module.name = name + module.namespace = tree.module.name[1] if tree.module else '' + # Imports must come first, because they add to module.kinds which is used + # by by the others. + module.imports = [ + _Import(module, imports[imp.import_filename]) + for imp in tree.import_list] + if tree.module and tree.module.attribute_list: + assert isinstance(tree.module.attribute_list, ast.AttributeList) + # TODO(vtl): Check for duplicate keys here. + module.attributes = dict((attribute.key, attribute.value) + for attribute in tree.module.attribute_list) + + # First pass collects kinds. + module.enums = map( + lambda enum: _Enum(module, enum, None), + _ElemsOfType(tree.definition_list, ast.Enum, name)) + module.structs = map( + lambda struct: _Struct(module, struct), + _ElemsOfType(tree.definition_list, ast.Struct, name)) + module.unions = map( + lambda union: _Union(module, union), + _ElemsOfType(tree.definition_list, ast.Union, name)) + module.interfaces = map( + lambda interface: _Interface(module, interface), + _ElemsOfType(tree.definition_list, ast.Interface, name)) + module.constants = map( + lambda constant: _Constant(module, constant, None), + _ElemsOfType(tree.definition_list, ast.Const, name)) + + # Second pass expands fields and methods. This allows fields and parameters + # to refer to kinds defined anywhere in the mojom. + for struct in module.structs: + struct.fields = map(lambda field: + _StructField(module, field, struct), struct.fields_data) + del struct.fields_data + for union in module.unions: + union.fields = map(lambda field: + _UnionField(module, field, union), union.fields_data) + del union.fields_data + for interface in module.interfaces: + interface.methods = map(lambda method: + _Method(module, method, interface), interface.methods_data) + del interface.methods_data + + return module + +def OrderedModule(tree, name, imports): + """Convert parse tree to AST module. + + Args: + tree: {ast.Mojom} The parse tree. + name: {str} The mojom filename, excluding the path. + imports: {Dict[str, mojom.Module]} Mapping from filenames, as they appear in + the import list, to already processed modules. Used to process imports. + + Returns: + {mojom.Module} An AST for the mojom. + """ + module = _Module(tree, name, imports) + for interface in module.interfaces: + next_ordinal = 0 + for method in interface.methods: + if method.ordinal is None: + method.ordinal = next_ordinal + next_ordinal = method.ordinal + 1 + return module diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py b/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/parse/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/ast.py b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py new file mode 100644 index 0000000000..2c6b5fcdf8 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/parse/ast.py @@ -0,0 +1,410 @@ +# 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. + +"""Node classes for the AST for a Mojo IDL file.""" + +# Note: For convenience of testing, you probably want to define __eq__() methods +# for all node types; it's okay to be slightly lax (e.g., not compare filename +# and lineno). You may also define __repr__() to help with analyzing test +# failures, especially for more complex types. + + +class NodeBase(object): + """Base class for nodes in the AST.""" + + def __init__(self, filename=None, lineno=None): + self.filename = filename + self.lineno = lineno + + def __eq__(self, other): + return type(self) == type(other) + + # Make != the inverse of ==. (Subclasses shouldn't have to override this.) + def __ne__(self, other): + return not self == other + + +# TODO(vtl): Some of this is complicated enough that it should be tested. +class NodeListBase(NodeBase): + """Represents a list of other nodes, all having the same type. (This is meant + to be subclassed, with subclasses defining _list_item_type to be the class (or + classes, in a tuple) of the members of the list.)""" + + def __init__(self, item_or_items=None, **kwargs): + super(NodeListBase, self).__init__(**kwargs) + self.items = [] + if item_or_items is None: + pass + elif isinstance(item_or_items, list): + for item in item_or_items: + assert isinstance(item, self._list_item_type) + self.Append(item) + else: + assert isinstance(item_or_items, self._list_item_type) + self.Append(item_or_items) + + # Support iteration. For everything else, users should just access |items| + # directly. (We intentionally do NOT supply |__len__()| or |__nonzero__()|, so + # |bool(NodeListBase())| is true.) + def __iter__(self): + return self.items.__iter__() + + def __eq__(self, other): + return super(NodeListBase, self).__eq__(other) and \ + self.items == other.items + + # Implement this so that on failure, we get slightly more sensible output. + def __repr__(self): + return self.__class__.__name__ + "([" + \ + ", ".join([repr(elem) for elem in self.items]) + "])" + + def Insert(self, item): + """Inserts item at the front of the list.""" + + assert isinstance(item, self._list_item_type) + self.items.insert(0, item) + self._UpdateFilenameAndLineno() + + def Append(self, item): + """Appends item to the end of the list.""" + + assert isinstance(item, self._list_item_type) + self.items.append(item) + self._UpdateFilenameAndLineno() + + def _UpdateFilenameAndLineno(self): + if self.items: + self.filename = self.items[0].filename + self.lineno = self.items[0].lineno + + +class Definition(NodeBase): + """Represents a definition of anything that has a global name (e.g., enums, + enum values, consts, structs, struct fields, interfaces). (This does not + include parameter definitions.) This class is meant to be subclassed.""" + + def __init__(self, name, **kwargs): + assert isinstance(name, str) + NodeBase.__init__(self, **kwargs) + self.name = name + + +################################################################################ + + +class Attribute(NodeBase): + """Represents an attribute.""" + + def __init__(self, key, value, **kwargs): + assert isinstance(key, str) + super(Attribute, self).__init__(**kwargs) + self.key = key + self.value = value + + def __eq__(self, other): + return super(Attribute, self).__eq__(other) and \ + self.key == other.key and \ + self.value == other.value + + +class AttributeList(NodeListBase): + """Represents a list attributes.""" + + _list_item_type = Attribute + + +class Const(Definition): + """Represents a const definition.""" + + def __init__(self, name, typename, value, **kwargs): + # The typename is currently passed through as a string. + assert isinstance(typename, str) + # The value is either a literal (currently passed through as a string) or a + # "wrapped identifier". + assert isinstance(value, str) or isinstance(value, tuple) + super(Const, self).__init__(name, **kwargs) + self.typename = typename + self.value = value + + def __eq__(self, other): + return super(Const, self).__eq__(other) and \ + self.typename == other.typename and \ + self.value == other.value + + +class Enum(Definition): + """Represents an enum definition.""" + + def __init__(self, name, attribute_list, enum_value_list, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert enum_value_list is None or isinstance(enum_value_list, EnumValueList) + super(Enum, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.enum_value_list = enum_value_list + + def __eq__(self, other): + return super(Enum, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.enum_value_list == other.enum_value_list + + +class EnumValue(Definition): + """Represents a definition of an enum value.""" + + def __init__(self, name, attribute_list, value, **kwargs): + # The optional value is either an int (which is current a string) or a + # "wrapped identifier". + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert value is None or isinstance(value, (str, tuple)) + super(EnumValue, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.value = value + + def __eq__(self, other): + return super(EnumValue, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.value == other.value + + +class EnumValueList(NodeListBase): + """Represents a list of enum value definitions (i.e., the "body" of an enum + definition).""" + + _list_item_type = EnumValue + + +class Import(NodeBase): + """Represents an import statement.""" + + def __init__(self, import_filename, **kwargs): + assert isinstance(import_filename, str) + super(Import, self).__init__(**kwargs) + self.import_filename = import_filename + + def __eq__(self, other): + return super(Import, self).__eq__(other) and \ + self.import_filename == other.import_filename + + +class ImportList(NodeListBase): + """Represents a list (i.e., sequence) of import statements.""" + + _list_item_type = Import + + +class Interface(Definition): + """Represents an interface definition.""" + + def __init__(self, name, attribute_list, body, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert isinstance(body, InterfaceBody) + super(Interface, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.body = body + + def __eq__(self, other): + return super(Interface, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.body == other.body + + +class Method(Definition): + """Represents a method definition.""" + + def __init__(self, name, attribute_list, ordinal, parameter_list, + response_parameter_list, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert ordinal is None or isinstance(ordinal, Ordinal) + assert isinstance(parameter_list, ParameterList) + assert response_parameter_list is None or \ + isinstance(response_parameter_list, ParameterList) + super(Method, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.ordinal = ordinal + self.parameter_list = parameter_list + self.response_parameter_list = response_parameter_list + + def __eq__(self, other): + return super(Method, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.ordinal == other.ordinal and \ + self.parameter_list == other.parameter_list and \ + self.response_parameter_list == other.response_parameter_list + + +# This needs to be declared after |Method|. +class InterfaceBody(NodeListBase): + """Represents the body of (i.e., list of definitions inside) an interface.""" + + _list_item_type = (Const, Enum, Method) + + +class Module(NodeBase): + """Represents a module statement.""" + + def __init__(self, name, attribute_list, **kwargs): + # |name| is either none or a "wrapped identifier". + assert name is None or isinstance(name, tuple) + assert attribute_list is None or isinstance(attribute_list, AttributeList) + super(Module, self).__init__(**kwargs) + self.name = name + self.attribute_list = attribute_list + + def __eq__(self, other): + return super(Module, self).__eq__(other) and \ + self.name == other.name and \ + self.attribute_list == other.attribute_list + + +class Mojom(NodeBase): + """Represents an entire .mojom file. (This is the root node.)""" + + def __init__(self, module, import_list, definition_list, **kwargs): + assert module is None or isinstance(module, Module) + assert isinstance(import_list, ImportList) + assert isinstance(definition_list, list) + super(Mojom, self).__init__(**kwargs) + self.module = module + self.import_list = import_list + self.definition_list = definition_list + + def __eq__(self, other): + return super(Mojom, self).__eq__(other) and \ + self.module == other.module and \ + self.import_list == other.import_list and \ + self.definition_list == other.definition_list + + def __repr__(self): + return "%s(%r, %r, %r)" % (self.__class__.__name__, self.module, + self.import_list, self.definition_list) + + +class Ordinal(NodeBase): + """Represents an ordinal value labeling, e.g., a struct field.""" + + def __init__(self, value, **kwargs): + assert isinstance(value, int) + super(Ordinal, self).__init__(**kwargs) + self.value = value + + def __eq__(self, other): + return super(Ordinal, self).__eq__(other) and \ + self.value == other.value + + +class Parameter(NodeBase): + """Represents a method request or response parameter.""" + + def __init__(self, name, attribute_list, ordinal, typename, **kwargs): + assert isinstance(name, str) + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert ordinal is None or isinstance(ordinal, Ordinal) + assert isinstance(typename, str) + super(Parameter, self).__init__(**kwargs) + self.name = name + self.attribute_list = attribute_list + self.ordinal = ordinal + self.typename = typename + + def __eq__(self, other): + return super(Parameter, self).__eq__(other) and \ + self.name == other.name and \ + self.attribute_list == other.attribute_list and \ + self.ordinal == other.ordinal and \ + self.typename == other.typename + + +class ParameterList(NodeListBase): + """Represents a list of (method request or response) parameters.""" + + _list_item_type = Parameter + + +class Struct(Definition): + """Represents a struct definition.""" + + def __init__(self, name, attribute_list, body, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert isinstance(body, StructBody) or body is None + super(Struct, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.body = body + + def __eq__(self, other): + return super(Struct, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.body == other.body + + +class StructField(Definition): + """Represents a struct field definition.""" + + def __init__(self, name, attribute_list, ordinal, typename, default_value, + **kwargs): + assert isinstance(name, str) + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert ordinal is None or isinstance(ordinal, Ordinal) + assert isinstance(typename, str) + # The optional default value is currently either a value as a string or a + # "wrapped identifier". + assert default_value is None or isinstance(default_value, (str, tuple)) + super(StructField, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.ordinal = ordinal + self.typename = typename + self.default_value = default_value + + def __eq__(self, other): + return super(StructField, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.ordinal == other.ordinal and \ + self.typename == other.typename and \ + self.default_value == other.default_value + + +# This needs to be declared after |StructField|. +class StructBody(NodeListBase): + """Represents the body of (i.e., list of definitions inside) a struct.""" + + _list_item_type = (Const, Enum, StructField) + + +class Union(Definition): + """Represents a union definition.""" + + def __init__(self, name, attribute_list, body, **kwargs): + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert isinstance(body, UnionBody) + super(Union, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.body = body + + def __eq__(self, other): + return super(Union, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.body == other.body + + +class UnionField(Definition): + + def __init__(self, name, attribute_list, ordinal, typename, **kwargs): + assert isinstance(name, str) + assert attribute_list is None or isinstance(attribute_list, AttributeList) + assert ordinal is None or isinstance(ordinal, Ordinal) + assert isinstance(typename, str) + super(UnionField, self).__init__(name, **kwargs) + self.attribute_list = attribute_list + self.ordinal = ordinal + self.typename = typename + + def __eq__(self, other): + return super(UnionField, self).__eq__(other) and \ + self.attribute_list == other.attribute_list and \ + self.ordinal == other.ordinal and \ + self.typename == other.typename + + +class UnionBody(NodeListBase): + + _list_item_type = UnionField diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py new file mode 100644 index 0000000000..06354b1d85 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/parse/lexer.py @@ -0,0 +1,254 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply.lex import TOKEN + +from ..error import Error + + +class LexError(Error): + """Class for errors from the lexer.""" + + def __init__(self, filename, message, lineno): + Error.__init__(self, filename, message, lineno=lineno) + + +# We have methods which look like they could be functions: +# pylint: disable=R0201 +class Lexer(object): + + def __init__(self, filename): + self.filename = filename + + ######################-- PRIVATE --###################### + + ## + ## Internal auxiliary methods + ## + def _error(self, msg, token): + raise LexError(self.filename, msg, token.lineno) + + ## + ## Reserved keywords + ## + keywords = ( + 'HANDLE', + + 'IMPORT', + 'MODULE', + 'STRUCT', + 'UNION', + 'INTERFACE', + 'ENUM', + 'CONST', + 'TRUE', + 'FALSE', + 'DEFAULT', + 'ARRAY', + 'MAP', + 'ASSOCIATED' + ) + + keyword_map = {} + for keyword in keywords: + keyword_map[keyword.lower()] = keyword + + ## + ## All the tokens recognized by the lexer + ## + tokens = keywords + ( + # Identifiers + 'NAME', + + # Constants + 'ORDINAL', + 'INT_CONST_DEC', 'INT_CONST_HEX', + 'FLOAT_CONST', + + # String literals + 'STRING_LITERAL', + + # Operators + 'MINUS', + 'PLUS', + 'AMP', + 'QSTN', + + # Assignment + 'EQUALS', + + # Request / response + 'RESPONSE', + + # Delimiters + 'LPAREN', 'RPAREN', # ( ) + 'LBRACKET', 'RBRACKET', # [ ] + 'LBRACE', 'RBRACE', # { } + 'LANGLE', 'RANGLE', # < > + 'SEMI', # ; + 'COMMA', 'DOT' # , . + ) + + ## + ## Regexes for use in tokens + ## + + # valid C identifiers (K&R2: A.2.3) + identifier = r'[a-zA-Z_][0-9a-zA-Z_]*' + + hex_prefix = '0[xX]' + hex_digits = '[0-9a-fA-F]+' + + # integer constants (K&R2: A.2.5.1) + decimal_constant = '0|([1-9][0-9]*)' + hex_constant = hex_prefix+hex_digits + # Don't allow octal constants (even invalid octal). + octal_constant_disallowed = '0[0-9]+' + + # character constants (K&R2: A.2.5.2) + # Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line + # directives with Windows paths as filenames (..\..\dir\file) + # For the same reason, decimal_escape allows all digit sequences. We want to + # parse all correct code, even if it means to sometimes parse incorrect + # code. + # + simple_escape = r"""([a-zA-Z._~!=&\^\-\\?'"])""" + decimal_escape = r"""(\d+)""" + hex_escape = r"""(x[0-9a-fA-F]+)""" + bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-7])""" + + escape_sequence = \ + r"""(\\("""+simple_escape+'|'+decimal_escape+'|'+hex_escape+'))' + + # string literals (K&R2: A.2.6) + string_char = r"""([^"\\\n]|"""+escape_sequence+')' + string_literal = '"'+string_char+'*"' + bad_string_literal = '"'+string_char+'*'+bad_escape+string_char+'*"' + + # floating constants (K&R2: A.2.5.3) + exponent_part = r"""([eE][-+]?[0-9]+)""" + fractional_constant = r"""([0-9]*\.[0-9]+)|([0-9]+\.)""" + floating_constant = \ + '(((('+fractional_constant+')'+ \ + exponent_part+'?)|([0-9]+'+exponent_part+')))' + + # Ordinals + ordinal = r'@[0-9]+' + missing_ordinal_value = r'@' + # Don't allow ordinal values in octal (even invalid octal, like 09) or + # hexadecimal. + octal_or_hex_ordinal_disallowed = r'@((0[0-9]+)|('+hex_prefix+hex_digits+'))' + + ## + ## Rules for the normal state + ## + t_ignore = ' \t\r' + + # Newlines + def t_NEWLINE(self, t): + r'\n+' + t.lexer.lineno += len(t.value) + + # Operators + t_MINUS = r'-' + t_PLUS = r'\+' + t_AMP = r'&' + t_QSTN = r'\?' + + # = + t_EQUALS = r'=' + + # => + t_RESPONSE = r'=>' + + # Delimiters + t_LPAREN = r'\(' + t_RPAREN = r'\)' + t_LBRACKET = r'\[' + t_RBRACKET = r'\]' + t_LBRACE = r'\{' + t_RBRACE = r'\}' + t_LANGLE = r'<' + t_RANGLE = r'>' + t_COMMA = r',' + t_DOT = r'\.' + t_SEMI = r';' + + t_STRING_LITERAL = string_literal + + # The following floating and integer constants are defined as + # functions to impose a strict order (otherwise, decimal + # is placed before the others because its regex is longer, + # and this is bad) + # + @TOKEN(floating_constant) + def t_FLOAT_CONST(self, t): + return t + + @TOKEN(hex_constant) + def t_INT_CONST_HEX(self, t): + return t + + @TOKEN(octal_constant_disallowed) + def t_OCTAL_CONSTANT_DISALLOWED(self, t): + msg = "Octal values not allowed" + self._error(msg, t) + + @TOKEN(decimal_constant) + def t_INT_CONST_DEC(self, t): + return t + + # unmatched string literals are caught by the preprocessor + + @TOKEN(bad_string_literal) + def t_BAD_STRING_LITERAL(self, t): + msg = "String contains invalid escape code" + self._error(msg, t) + + # Handle ordinal-related tokens in the right order: + @TOKEN(octal_or_hex_ordinal_disallowed) + def t_OCTAL_OR_HEX_ORDINAL_DISALLOWED(self, t): + msg = "Octal and hexadecimal ordinal values not allowed" + self._error(msg, t) + + @TOKEN(ordinal) + def t_ORDINAL(self, t): + return t + + @TOKEN(missing_ordinal_value) + def t_BAD_ORDINAL(self, t): + msg = "Missing ordinal value" + self._error(msg, t) + + @TOKEN(identifier) + def t_NAME(self, t): + t.type = self.keyword_map.get(t.value, "NAME") + return t + + # Ignore C and C++ style comments + def t_COMMENT(self, t): + r'(/\*(.|\n)*?\*/)|(//.*(\n[ \t]*//.*)*)' + t.lexer.lineno += t.value.count("\n") + + def t_error(self, t): + msg = "Illegal character %s" % repr(t.value[0]) + self._error(msg, t) diff --git a/mojo/public/tools/bindings/pylib/mojom/parse/parser.py b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py new file mode 100644 index 0000000000..868fb45f33 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom/parse/parser.py @@ -0,0 +1,461 @@ +# 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. + +"""Generates a syntax tree from a Mojo IDL file.""" + +import imp +import os.path +import sys + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply import lex +from ply import yacc + +from ..error import Error +from . import ast +from .lexer import Lexer + + +_MAX_ORDINAL_VALUE = 0xffffffff +_MAX_ARRAY_SIZE = 0xffffffff + + +class ParseError(Error): + """Class for errors from the parser.""" + + def __init__(self, filename, message, lineno=None, snippet=None): + Error.__init__(self, filename, message, lineno=lineno, + addenda=([snippet] if snippet else None)) + + +# We have methods which look like they could be functions: +# pylint: disable=R0201 +class Parser(object): + + def __init__(self, lexer, source, filename): + self.tokens = lexer.tokens + self.source = source + self.filename = filename + + # Names of functions + # + # In general, we name functions after the left-hand-side of the rule(s) that + # they handle. E.g., |p_foo_bar| for a rule |foo_bar : ...|. + # + # There may be multiple functions handling rules for the same left-hand-side; + # then we name the functions |p_foo_bar_N| (for left-hand-side |foo_bar|), + # where N is a number (numbered starting from 1). Note that using multiple + # functions is actually more efficient than having single functions handle + # multiple rules (and, e.g., distinguishing them by examining |len(p)|). + # + # It's also possible to have a function handling multiple rules with different + # left-hand-sides. We do not do this. + # + # See http://www.dabeaz.com/ply/ply.html#ply_nn25 for more details. + + # TODO(vtl): Get rid of the braces in the module "statement". (Consider + # renaming "module" -> "package".) Then we'll be able to have a single rule + # for root (by making module "optional"). + def p_root_1(self, p): + """root : """ + p[0] = ast.Mojom(None, ast.ImportList(), []) + + def p_root_2(self, p): + """root : root module""" + if p[1].module is not None: + raise ParseError(self.filename, + "Multiple \"module\" statements not allowed:", + p[2].lineno, snippet=self._GetSnippet(p[2].lineno)) + if p[1].import_list.items or p[1].definition_list: + raise ParseError( + self.filename, + "\"module\" statements must precede imports and definitions:", + p[2].lineno, snippet=self._GetSnippet(p[2].lineno)) + p[0] = p[1] + p[0].module = p[2] + + def p_root_3(self, p): + """root : root import""" + if p[1].definition_list: + raise ParseError(self.filename, + "\"import\" statements must precede definitions:", + p[2].lineno, snippet=self._GetSnippet(p[2].lineno)) + p[0] = p[1] + p[0].import_list.Append(p[2]) + + def p_root_4(self, p): + """root : root definition""" + p[0] = p[1] + p[0].definition_list.append(p[2]) + + def p_import(self, p): + """import : IMPORT STRING_LITERAL SEMI""" + # 'eval' the literal to strip the quotes. + # TODO(vtl): This eval is dubious. We should unquote/unescape ourselves. + p[0] = ast.Import(eval(p[2]), filename=self.filename, lineno=p.lineno(2)) + + def p_module(self, p): + """module : attribute_section MODULE identifier_wrapped SEMI""" + p[0] = ast.Module(p[3], p[1], filename=self.filename, lineno=p.lineno(2)) + + def p_definition(self, p): + """definition : struct + | union + | interface + | enum + | const""" + p[0] = p[1] + + def p_attribute_section_1(self, p): + """attribute_section : """ + p[0] = None + + def p_attribute_section_2(self, p): + """attribute_section : LBRACKET attribute_list RBRACKET""" + p[0] = p[2] + + def p_attribute_list_1(self, p): + """attribute_list : """ + p[0] = ast.AttributeList() + + def p_attribute_list_2(self, p): + """attribute_list : nonempty_attribute_list""" + p[0] = p[1] + + def p_nonempty_attribute_list_1(self, p): + """nonempty_attribute_list : attribute""" + p[0] = ast.AttributeList(p[1]) + + def p_nonempty_attribute_list_2(self, p): + """nonempty_attribute_list : nonempty_attribute_list COMMA attribute""" + p[0] = p[1] + p[0].Append(p[3]) + + def p_attribute_1(self, p): + """attribute : NAME EQUALS evaled_literal + | NAME EQUALS NAME""" + p[0] = ast.Attribute(p[1], p[3], filename=self.filename, lineno=p.lineno(1)) + + def p_attribute_2(self, p): + """attribute : NAME""" + p[0] = ast.Attribute(p[1], True, filename=self.filename, lineno=p.lineno(1)) + + def p_evaled_literal(self, p): + """evaled_literal : literal""" + # 'eval' the literal to strip the quotes. Handle keywords "true" and "false" + # specially since they cannot directly be evaluated to python boolean + # values. + if p[1] == "true": + p[0] = True + elif p[1] == "false": + p[0] = False + else: + p[0] = eval(p[1]) + + def p_struct_1(self, p): + """struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI""" + p[0] = ast.Struct(p[3], p[1], p[5]) + + def p_struct_2(self, p): + """struct : attribute_section STRUCT NAME SEMI""" + p[0] = ast.Struct(p[3], p[1], None) + + def p_struct_body_1(self, p): + """struct_body : """ + p[0] = ast.StructBody() + + def p_struct_body_2(self, p): + """struct_body : struct_body const + | struct_body enum + | struct_body struct_field""" + p[0] = p[1] + p[0].Append(p[2]) + + def p_struct_field(self, p): + """struct_field : attribute_section typename NAME ordinal default SEMI""" + p[0] = ast.StructField(p[3], p[1], p[4], p[2], p[5]) + + def p_union(self, p): + """union : attribute_section UNION NAME LBRACE union_body RBRACE SEMI""" + p[0] = ast.Union(p[3], p[1], p[5]) + + def p_union_body_1(self, p): + """union_body : """ + p[0] = ast.UnionBody() + + def p_union_body_2(self, p): + """union_body : union_body union_field""" + p[0] = p[1] + p[1].Append(p[2]) + + def p_union_field(self, p): + """union_field : attribute_section typename NAME ordinal SEMI""" + p[0] = ast.UnionField(p[3], p[1], p[4], p[2]) + + def p_default_1(self, p): + """default : """ + p[0] = None + + def p_default_2(self, p): + """default : EQUALS constant""" + p[0] = p[2] + + def p_interface(self, p): + """interface : attribute_section INTERFACE NAME LBRACE interface_body \ + RBRACE SEMI""" + p[0] = ast.Interface(p[3], p[1], p[5]) + + def p_interface_body_1(self, p): + """interface_body : """ + p[0] = ast.InterfaceBody() + + def p_interface_body_2(self, p): + """interface_body : interface_body const + | interface_body enum + | interface_body method""" + p[0] = p[1] + p[0].Append(p[2]) + + def p_response_1(self, p): + """response : """ + p[0] = None + + def p_response_2(self, p): + """response : RESPONSE LPAREN parameter_list RPAREN""" + p[0] = p[3] + + def p_method(self, p): + """method : attribute_section NAME ordinal LPAREN parameter_list RPAREN \ + response SEMI""" + p[0] = ast.Method(p[2], p[1], p[3], p[5], p[7]) + + def p_parameter_list_1(self, p): + """parameter_list : """ + p[0] = ast.ParameterList() + + def p_parameter_list_2(self, p): + """parameter_list : nonempty_parameter_list""" + p[0] = p[1] + + def p_nonempty_parameter_list_1(self, p): + """nonempty_parameter_list : parameter""" + p[0] = ast.ParameterList(p[1]) + + def p_nonempty_parameter_list_2(self, p): + """nonempty_parameter_list : nonempty_parameter_list COMMA parameter""" + p[0] = p[1] + p[0].Append(p[3]) + + def p_parameter(self, p): + """parameter : attribute_section typename NAME ordinal""" + p[0] = ast.Parameter(p[3], p[1], p[4], p[2], + filename=self.filename, lineno=p.lineno(3)) + + def p_typename(self, p): + """typename : nonnullable_typename QSTN + | nonnullable_typename""" + if len(p) == 2: + p[0] = p[1] + else: + p[0] = p[1] + "?" + + def p_nonnullable_typename(self, p): + """nonnullable_typename : basictypename + | array + | fixed_array + | associative_array + | interfacerequest""" + p[0] = p[1] + + def p_basictypename(self, p): + """basictypename : identifier + | ASSOCIATED identifier + | handletype""" + if len(p) == 2: + p[0] = p[1] + else: + p[0] = "asso<" + p[2] + ">" + + def p_handletype(self, p): + """handletype : HANDLE + | HANDLE LANGLE NAME RANGLE""" + if len(p) == 2: + p[0] = p[1] + else: + if p[3] not in ('data_pipe_consumer', + 'data_pipe_producer', + 'message_pipe', + 'shared_buffer'): + # Note: We don't enable tracking of line numbers for everything, so we + # can't use |p.lineno(3)|. + raise ParseError(self.filename, "Invalid handle type %r:" % p[3], + lineno=p.lineno(1), + snippet=self._GetSnippet(p.lineno(1))) + p[0] = "handle<" + p[3] + ">" + + def p_array(self, p): + """array : ARRAY LANGLE typename RANGLE""" + p[0] = p[3] + "[]" + + def p_fixed_array(self, p): + """fixed_array : ARRAY LANGLE typename COMMA INT_CONST_DEC RANGLE""" + value = int(p[5]) + if value == 0 or value > _MAX_ARRAY_SIZE: + raise ParseError(self.filename, "Fixed array size %d invalid:" % value, + lineno=p.lineno(5), + snippet=self._GetSnippet(p.lineno(5))) + p[0] = p[3] + "[" + p[5] + "]" + + def p_associative_array(self, p): + """associative_array : MAP LANGLE identifier COMMA typename RANGLE""" + p[0] = p[5] + "{" + p[3] + "}" + + def p_interfacerequest(self, p): + """interfacerequest : identifier AMP + | ASSOCIATED identifier AMP""" + if len(p) == 3: + p[0] = p[1] + "&" + else: + p[0] = "asso<" + p[2] + "&>" + + def p_ordinal_1(self, p): + """ordinal : """ + p[0] = None + + def p_ordinal_2(self, p): + """ordinal : ORDINAL""" + value = int(p[1][1:]) + if value > _MAX_ORDINAL_VALUE: + raise ParseError(self.filename, "Ordinal value %d too large:" % value, + lineno=p.lineno(1), + snippet=self._GetSnippet(p.lineno(1))) + p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1)) + + def p_enum_1(self, p): + """enum : attribute_section ENUM NAME LBRACE enum_value_list \ + RBRACE SEMI + | attribute_section ENUM NAME LBRACE nonempty_enum_value_list \ + COMMA RBRACE SEMI""" + p[0] = ast.Enum(p[3], p[1], p[5], filename=self.filename, + lineno=p.lineno(2)) + + def p_enum_2(self, p): + """enum : attribute_section ENUM NAME SEMI""" + p[0] = ast.Enum(p[3], p[1], None, filename=self.filename, + lineno=p.lineno(2)) + + def p_enum_value_list_1(self, p): + """enum_value_list : """ + p[0] = ast.EnumValueList() + + def p_enum_value_list_2(self, p): + """enum_value_list : nonempty_enum_value_list""" + p[0] = p[1] + + def p_nonempty_enum_value_list_1(self, p): + """nonempty_enum_value_list : enum_value""" + p[0] = ast.EnumValueList(p[1]) + + def p_nonempty_enum_value_list_2(self, p): + """nonempty_enum_value_list : nonempty_enum_value_list COMMA enum_value""" + p[0] = p[1] + p[0].Append(p[3]) + + def p_enum_value(self, p): + """enum_value : attribute_section NAME + | attribute_section NAME EQUALS int + | attribute_section NAME EQUALS identifier_wrapped""" + p[0] = ast.EnumValue(p[2], p[1], p[4] if len(p) == 5 else None, + filename=self.filename, lineno=p.lineno(2)) + + def p_const(self, p): + """const : CONST typename NAME EQUALS constant SEMI""" + p[0] = ast.Const(p[3], p[2], p[5]) + + def p_constant(self, p): + """constant : literal + | identifier_wrapped""" + p[0] = p[1] + + def p_identifier_wrapped(self, p): + """identifier_wrapped : identifier""" + p[0] = ('IDENTIFIER', p[1]) + + # TODO(vtl): Make this produce a "wrapped" identifier (probably as an + # |ast.Identifier|, to be added) and get rid of identifier_wrapped. + def p_identifier(self, p): + """identifier : NAME + | NAME DOT identifier""" + p[0] = ''.join(p[1:]) + + def p_literal(self, p): + """literal : int + | float + | TRUE + | FALSE + | DEFAULT + | STRING_LITERAL""" + p[0] = p[1] + + def p_int(self, p): + """int : int_const + | PLUS int_const + | MINUS int_const""" + p[0] = ''.join(p[1:]) + + def p_int_const(self, p): + """int_const : INT_CONST_DEC + | INT_CONST_HEX""" + p[0] = p[1] + + def p_float(self, p): + """float : FLOAT_CONST + | PLUS FLOAT_CONST + | MINUS FLOAT_CONST""" + p[0] = ''.join(p[1:]) + + def p_error(self, e): + if e is None: + # Unexpected EOF. + # TODO(vtl): Can we figure out what's missing? + raise ParseError(self.filename, "Unexpected end of file") + + raise ParseError(self.filename, "Unexpected %r:" % e.value, lineno=e.lineno, + snippet=self._GetSnippet(e.lineno)) + + def _GetSnippet(self, lineno): + return self.source.split('\n')[lineno - 1] + + +def Parse(source, filename): + """Parse source file to AST. + + Args: + source: The source text as a str. + filename: The filename that |source| originates from. + + Returns: + The AST as a mojom.parse.ast.Mojom object. + """ + lexer = Lexer(filename) + parser = Parser(lexer, source, filename) + + lex.lex(object=lexer) + yacc.yacc(module=parser, debug=0, write_tables=0) + + tree = yacc.parse(source) + return tree diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py new file mode 100644 index 0000000000..d56faadb19 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py @@ -0,0 +1,55 @@ +# 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. + +import imp +import os.path +import shutil +import sys +import tempfile +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom import fileutil + + +class FileUtilTest(unittest.TestCase): + + def testEnsureDirectoryExists(self): + """Test that EnsureDirectoryExists fuctions correctly.""" + + temp_dir = tempfile.mkdtemp() + try: + self.assertTrue(os.path.exists(temp_dir)) + + # Directory does not exist, yet. + full = os.path.join(temp_dir, "foo", "bar") + self.assertFalse(os.path.exists(full)) + + # Create the directory. + fileutil.EnsureDirectoryExists(full) + self.assertTrue(os.path.exists(full)) + + # Trying to create it again does not cause an error. + fileutil.EnsureDirectoryExists(full) + self.assertTrue(os.path.exists(full)) + + # Bypass check for directory existence to tickle error handling that + # occurs in response to a race. + fileutil.EnsureDirectoryExists(full, always_try_to_create=True) + self.assertTrue(os.path.exists(full)) + finally: + shutil.rmtree(temp_dir) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py new file mode 100644 index 0000000000..70c92a38fb --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py @@ -0,0 +1,156 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.generate import data +from mojom.generate import module as mojom + + +class DataTest(unittest.TestCase): + + def testStructDataConversion(self): + """Tests that a struct can be converted from data.""" + module = mojom.Module('test_module', 'test_namespace') + struct_data = { + 'name': 'SomeStruct', + 'enums': [], + 'constants': [], + 'fields': [ + {'name': 'field1', 'kind': 'i32'}, + {'name': 'field2', 'kind': 'i32', 'ordinal': 10}, + {'name': 'field3', 'kind': 'i32', 'default': 15}]} + + struct = data.StructFromData(module, struct_data) + struct.fields = map(lambda field: + data.StructFieldFromData(module, field, struct), struct.fields_data) + self.assertEquals(struct_data, data.StructToData(struct)) + + def testUnionDataConversion(self): + """Tests that a union can be converted from data.""" + module = mojom.Module('test_module', 'test_namespace') + union_data = { + 'name': 'SomeUnion', + 'fields': [ + {'name': 'field1', 'kind': 'i32'}, + {'name': 'field2', 'kind': 'i32', 'ordinal': 10}]} + + union = data.UnionFromData(module, union_data) + union.fields = map(lambda field: + data.UnionFieldFromData(module, field, union), union.fields_data) + self.assertEquals(union_data, data.UnionToData(union)) + + def testImportFromDataNoMissingImports(self): + """Tests that unions, structs, interfaces and enums are imported.""" + module = mojom.Module('test_module', 'test_namespace') + imported_module = mojom.Module('import_module', 'import_namespace') + #TODO(azani): Init values in module.py. + #TODO(azani): Test that values are imported. + imported_module.values = {} + imported_data = {'module' : imported_module} + + + struct = mojom.Struct('TestStruct', module=module) + imported_module.kinds[struct.spec] = struct + + union = mojom.Union('TestUnion', module=module) + imported_module.kinds[union.spec] = union + + interface = mojom.Interface('TestInterface', module=module) + imported_module.kinds[interface.spec] = interface + + enum = mojom.Enum('TestEnum', module=module) + imported_module.kinds[enum.spec] = enum + + data.ImportFromData(module, imported_data) + + # Test that the kind was imported. + self.assertIn(struct.spec, module.kinds) + self.assertEquals(struct.name, module.kinds[struct.spec].name) + + self.assertIn(union.spec, module.kinds) + self.assertEquals(union.name, module.kinds[union.spec].name) + + self.assertIn(interface.spec, module.kinds) + self.assertEquals(interface.name, module.kinds[interface.spec].name) + + self.assertIn(enum.spec, module.kinds) + self.assertEquals(enum.name, module.kinds[enum.spec].name) + + # Test that the imported kind is a copy and not the original. + self.assertIsNot(struct, module.kinds[struct.spec]) + self.assertIsNot(union, module.kinds[union.spec]) + self.assertIsNot(interface, module.kinds[interface.spec]) + self.assertIsNot(enum, module.kinds[enum.spec]) + + def testImportFromDataNoExtraneousImports(self): + """Tests that arrays, maps and interface requests are not imported.""" + module = mojom.Module('test_module', 'test_namespace') + imported_module = mojom.Module('import_module', 'import_namespace') + #TODO(azani): Init values in module.py. + imported_module.values = {} + imported_data = {'module' : imported_module} + + array = mojom.Array(mojom.INT16, length=20) + imported_module.kinds[array.spec] = array + + map_kind = mojom.Map(mojom.INT16, mojom.INT16) + imported_module.kinds[map_kind.spec] = map_kind + + interface = mojom.Interface('TestInterface', module=module) + imported_module.kinds[interface.spec] = interface + + interface_req = mojom.InterfaceRequest(interface) + imported_module.kinds[interface_req.spec] = interface_req + + data.ImportFromData(module, imported_data) + + self.assertNotIn(array.spec, module.kinds) + self.assertNotIn(map_kind.spec, module.kinds) + self.assertNotIn(interface_req.spec, module.kinds) + + def testNonInterfaceAsInterfaceRequest(self): + """Tests that a non-interface cannot be used for interface requests.""" + module = mojom.Module('test_module', 'test_namespace') + interface = mojom.Interface('TestInterface', module=module) + method_dict = { + 'name': 'Foo', + 'parameters': [{'name': 'foo', 'kind': 'r:i32'}], + } + with self.assertRaises(Exception) as e: + data.MethodFromData(module, method_dict, interface) + self.assertEquals(e.exception.__str__(), + 'Interface request requires \'i32\' to be an interface.') + + def testNonInterfaceAsAssociatedInterface(self): + """Tests that a non-interface type cannot be used for associated + interfaces.""" + module = mojom.Module('test_module', 'test_namespace') + interface = mojom.Interface('TestInterface', module=module) + method_dict = { + 'name': 'Foo', + 'parameters': [{'name': 'foo', 'kind': 'asso:i32'}], + } + with self.assertRaises(Exception) as e: + data.MethodFromData(module, method_dict, interface) + self.assertEquals( + e.exception.__str__(), + 'Associated interface requires \'i32\' to be an interface.') diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py new file mode 100644 index 0000000000..a684773719 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py @@ -0,0 +1,37 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.generate import generator + + +class StringManipulationTest(unittest.TestCase): + """generator contains some string utilities, this tests only those.""" + + def testUnderToCamel(self): + """Tests UnderToCamel which converts underscore_separated to CamelCase.""" + self.assertEquals("CamelCase", generator.UnderToCamel("camel_case")) + self.assertEquals("CamelCase", generator.UnderToCamel("CAMEL_CASE")) + +if __name__ == "__main__": + unittest.main() + diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py new file mode 100644 index 0000000000..75b7cd5437 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py @@ -0,0 +1,48 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.generate import module as mojom + + +class ModuleTest(unittest.TestCase): + + def testNonInterfaceAsInterfaceRequest(self): + """Tests that a non-interface cannot be used for interface requests.""" + module = mojom.Module('test_module', 'test_namespace') + struct = mojom.Struct('TestStruct', module=module) + with self.assertRaises(Exception) as e: + mojom.InterfaceRequest(struct) + self.assertEquals( + e.exception.__str__(), + 'Interface request requires \'x:TestStruct\' to be an interface.') + + def testNonInterfaceAsAssociatedInterface(self): + """Tests that a non-interface type cannot be used for associated interfaces. + """ + module = mojom.Module('test_module', 'test_namespace') + struct = mojom.Struct('TestStruct', module=module) + with self.assertRaises(Exception) as e: + mojom.AssociatedInterface(struct) + self.assertEquals( + e.exception.__str__(), + 'Associated interface requires \'x:TestStruct\' to be an interface.') diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py new file mode 100644 index 0000000000..75f6d5163e --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py @@ -0,0 +1,136 @@ +# 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. + +import imp +import os.path +import sys +import unittest + + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.generate import pack +from mojom.generate import module as mojom + + +# TODO(yzshen): Move tests in pack_tests.py here. +class PackTest(unittest.TestCase): + def _CheckPackSequence(self, kinds, fields, offsets): + """Checks the pack order and offsets of a sequence of mojom.Kinds. + + Args: + kinds: A sequence of mojom.Kinds that specify the fields that are to be + created. + fields: The expected order of the resulting fields, with the integer "1" + first. + offsets: The expected order of offsets, with the integer "0" first. + """ + struct = mojom.Struct('test') + index = 1 + for kind in kinds: + struct.AddField('%d' % index, kind) + index += 1 + ps = pack.PackedStruct(struct) + num_fields = len(ps.packed_fields) + self.assertEquals(len(kinds), num_fields) + for i in xrange(num_fields): + self.assertEquals('%d' % fields[i], ps.packed_fields[i].field.name) + self.assertEquals(offsets[i], ps.packed_fields[i].offset) + + def testMinVersion(self): + """Tests that |min_version| is properly set for packed fields.""" + struct = mojom.Struct('test') + struct.AddField('field_2', mojom.BOOL, 2) + struct.AddField('field_0', mojom.INT32, 0) + struct.AddField('field_1', mojom.INT64, 1) + ps = pack.PackedStruct(struct) + + self.assertEquals('field_0', ps.packed_fields[0].field.name) + self.assertEquals('field_2', ps.packed_fields[1].field.name) + self.assertEquals('field_1', ps.packed_fields[2].field.name) + + self.assertEquals(0, ps.packed_fields[0].min_version) + self.assertEquals(0, ps.packed_fields[1].min_version) + self.assertEquals(0, ps.packed_fields[2].min_version) + + struct.fields[0].attributes = {'MinVersion': 1} + ps = pack.PackedStruct(struct) + + self.assertEquals(0, ps.packed_fields[0].min_version) + self.assertEquals(1, ps.packed_fields[1].min_version) + self.assertEquals(0, ps.packed_fields[2].min_version) + + def testGetVersionInfoEmptyStruct(self): + """Tests that pack.GetVersionInfo() never returns an empty list, even for + empty structs. + """ + struct = mojom.Struct('test') + ps = pack.PackedStruct(struct) + + versions = pack.GetVersionInfo(ps) + self.assertEquals(1, len(versions)) + self.assertEquals(0, versions[0].version) + self.assertEquals(0, versions[0].num_fields) + self.assertEquals(8, versions[0].num_bytes) + + def testGetVersionInfoComplexOrder(self): + """Tests pack.GetVersionInfo() using a struct whose definition order, + ordinal order and pack order for fields are all different. + """ + struct = mojom.Struct('test') + struct.AddField('field_3', mojom.BOOL, ordinal=3, + attributes={'MinVersion': 3}) + struct.AddField('field_0', mojom.INT32, ordinal=0) + struct.AddField('field_1', mojom.INT64, ordinal=1, + attributes={'MinVersion': 2}) + struct.AddField('field_2', mojom.INT64, ordinal=2, + attributes={'MinVersion': 3}) + ps = pack.PackedStruct(struct) + + versions = pack.GetVersionInfo(ps) + self.assertEquals(3, len(versions)) + + self.assertEquals(0, versions[0].version) + self.assertEquals(1, versions[0].num_fields) + self.assertEquals(16, versions[0].num_bytes) + + self.assertEquals(2, versions[1].version) + self.assertEquals(2, versions[1].num_fields) + self.assertEquals(24, versions[1].num_bytes) + + self.assertEquals(3, versions[2].version) + self.assertEquals(4, versions[2].num_fields) + self.assertEquals(32, versions[2].num_bytes) + + def testInterfaceAlignment(self): + """Tests that interfaces are aligned on 4-byte boundaries, although the size + of an interface is 8 bytes. + """ + kinds = (mojom.INT32, mojom.Interface('test_interface')) + fields = (1, 2) + offsets = (0, 4) + self._CheckPackSequence(kinds, fields, offsets) + + def testAssociatedInterfaceAlignment(self): + """Tests that associated interfaces are aligned on 4-byte boundaries, + although the size of an associated interface is 8 bytes. + """ + kinds = (mojom.INT32, + mojom.AssociatedInterface(mojom.Interface('test_interface'))) + fields = (1, 2) + offsets = (0, 4) + self._CheckPackSequence(kinds, fields, offsets) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py new file mode 100644 index 0000000000..dd28cdd120 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py @@ -0,0 +1,135 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +import mojom.parse.ast as ast + + +class _TestNode(ast.NodeBase): + """Node type for tests.""" + + def __init__(self, value, **kwargs): + super(_TestNode, self).__init__(**kwargs) + self.value = value + + def __eq__(self, other): + return super(_TestNode, self).__eq__(other) and self.value == other.value + + +class _TestNodeList(ast.NodeListBase): + """Node list type for tests.""" + + _list_item_type = _TestNode + + +class ASTTest(unittest.TestCase): + """Tests various AST classes.""" + + def testNodeBase(self): + # Test |__eq__()|; this is only used for testing, where we want to do + # comparison by value and ignore filenames/line numbers (for convenience). + node1 = ast.NodeBase(filename="hello.mojom", lineno=123) + node2 = ast.NodeBase() + self.assertEquals(node1, node2) + self.assertEquals(node2, node1) + + # Check that |__ne__()| just defers to |__eq__()| properly. + self.assertFalse(node1 != node2) + self.assertFalse(node2 != node1) + + # Check that |filename| and |lineno| are set properly (and are None by + # default). + self.assertEquals(node1.filename, "hello.mojom") + self.assertEquals(node1.lineno, 123) + self.assertIsNone(node2.filename) + self.assertIsNone(node2.lineno) + + # |NodeBase|'s |__eq__()| should compare types (and a subclass's |__eq__()| + # should first defer to its superclass's). + node3 = _TestNode(123) + self.assertNotEqual(node1, node3) + self.assertNotEqual(node3, node1) + # Also test |__eq__()| directly. + self.assertFalse(node1 == node3) + self.assertFalse(node3 == node1) + + node4 = _TestNode(123, filename="world.mojom", lineno=123) + self.assertEquals(node4, node3) + node5 = _TestNode(456) + self.assertNotEquals(node5, node4) + + def testNodeListBase(self): + node1 = _TestNode(1, filename="foo.mojom", lineno=1) + # Equal to, but not the same as, |node1|: + node1b = _TestNode(1, filename="foo.mojom", lineno=1) + node2 = _TestNode(2, filename="foo.mojom", lineno=2) + + nodelist1 = _TestNodeList() # Contains: (empty). + self.assertEquals(nodelist1, nodelist1) + self.assertEquals(nodelist1.items, []) + self.assertIsNone(nodelist1.filename) + self.assertIsNone(nodelist1.lineno) + + nodelist2 = _TestNodeList(node1) # Contains: 1. + self.assertEquals(nodelist2, nodelist2) + self.assertEquals(nodelist2.items, [node1]) + self.assertNotEqual(nodelist2, nodelist1) + self.assertEquals(nodelist2.filename, "foo.mojom") + self.assertEquals(nodelist2.lineno, 1) + + nodelist3 = _TestNodeList([node2]) # Contains: 2. + self.assertEquals(nodelist3.items, [node2]) + self.assertNotEqual(nodelist3, nodelist1) + self.assertNotEqual(nodelist3, nodelist2) + self.assertEquals(nodelist3.filename, "foo.mojom") + self.assertEquals(nodelist3.lineno, 2) + + nodelist1.Append(node1b) # Contains: 1. + self.assertEquals(nodelist1.items, [node1]) + self.assertEquals(nodelist1, nodelist2) + self.assertNotEqual(nodelist1, nodelist3) + self.assertEquals(nodelist1.filename, "foo.mojom") + self.assertEquals(nodelist1.lineno, 1) + + nodelist1.Append(node2) # Contains: 1, 2. + self.assertEquals(nodelist1.items, [node1, node2]) + self.assertNotEqual(nodelist1, nodelist2) + self.assertNotEqual(nodelist1, nodelist3) + self.assertEquals(nodelist1.lineno, 1) + + nodelist2.Append(node2) # Contains: 1, 2. + self.assertEquals(nodelist2.items, [node1, node2]) + self.assertEquals(nodelist2, nodelist1) + self.assertNotEqual(nodelist2, nodelist3) + self.assertEquals(nodelist2.lineno, 1) + + nodelist3.Insert(node1) # Contains: 1, 2. + self.assertEquals(nodelist3.items, [node1, node2]) + self.assertEquals(nodelist3, nodelist1) + self.assertEquals(nodelist3, nodelist2) + self.assertEquals(nodelist3.lineno, 1) + + # Test iteration: + i = 1 + for item in nodelist1: + self.assertEquals(item.value, i) + i += 1 diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py new file mode 100644 index 0000000000..6822cbc8d0 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py @@ -0,0 +1,192 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("ply") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party")) +from ply import lex + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +import mojom.parse.lexer + + +# This (monkey-patching LexToken to make comparison value-based) is evil, but +# we'll do it anyway. (I'm pretty sure ply's lexer never cares about comparing +# for object identity.) +def _LexTokenEq(self, other): + return self.type == other.type and self.value == other.value and \ + self.lineno == other.lineno and self.lexpos == other.lexpos +setattr(lex.LexToken, '__eq__', _LexTokenEq) + + +def _MakeLexToken(token_type, value, lineno=1, lexpos=0): + """Makes a LexToken with the given parameters. (Note that lineno is 1-based, + but lexpos is 0-based.)""" + rv = lex.LexToken() + rv.type, rv.value, rv.lineno, rv.lexpos = token_type, value, lineno, lexpos + return rv + + +def _MakeLexTokenForKeyword(keyword, **kwargs): + """Makes a LexToken for the given keyword.""" + return _MakeLexToken(keyword.upper(), keyword.lower(), **kwargs) + + +class LexerTest(unittest.TestCase): + """Tests |mojom.parse.lexer.Lexer|.""" + + def __init__(self, *args, **kwargs): + unittest.TestCase.__init__(self, *args, **kwargs) + # Clone all lexer instances from this one, since making a lexer is slow. + self._zygote_lexer = lex.lex(mojom.parse.lexer.Lexer("my_file.mojom")) + + def testValidKeywords(self): + """Tests valid keywords.""" + self.assertEquals(self._SingleTokenForInput("handle"), + _MakeLexTokenForKeyword("handle")) + self.assertEquals(self._SingleTokenForInput("import"), + _MakeLexTokenForKeyword("import")) + self.assertEquals(self._SingleTokenForInput("module"), + _MakeLexTokenForKeyword("module")) + self.assertEquals(self._SingleTokenForInput("struct"), + _MakeLexTokenForKeyword("struct")) + self.assertEquals(self._SingleTokenForInput("union"), + _MakeLexTokenForKeyword("union")) + self.assertEquals(self._SingleTokenForInput("interface"), + _MakeLexTokenForKeyword("interface")) + self.assertEquals(self._SingleTokenForInput("enum"), + _MakeLexTokenForKeyword("enum")) + self.assertEquals(self._SingleTokenForInput("const"), + _MakeLexTokenForKeyword("const")) + self.assertEquals(self._SingleTokenForInput("true"), + _MakeLexTokenForKeyword("true")) + self.assertEquals(self._SingleTokenForInput("false"), + _MakeLexTokenForKeyword("false")) + self.assertEquals(self._SingleTokenForInput("default"), + _MakeLexTokenForKeyword("default")) + self.assertEquals(self._SingleTokenForInput("array"), + _MakeLexTokenForKeyword("array")) + self.assertEquals(self._SingleTokenForInput("map"), + _MakeLexTokenForKeyword("map")) + self.assertEquals(self._SingleTokenForInput("associated"), + _MakeLexTokenForKeyword("associated")) + + def testValidIdentifiers(self): + """Tests identifiers.""" + self.assertEquals(self._SingleTokenForInput("abcd"), + _MakeLexToken("NAME", "abcd")) + self.assertEquals(self._SingleTokenForInput("AbC_d012_"), + _MakeLexToken("NAME", "AbC_d012_")) + self.assertEquals(self._SingleTokenForInput("_0123"), + _MakeLexToken("NAME", "_0123")) + + def testInvalidIdentifiers(self): + with self.assertRaisesRegexp( + mojom.parse.lexer.LexError, + r"^my_file\.mojom:1: Error: Illegal character '\$'$"): + self._TokensForInput("$abc") + with self.assertRaisesRegexp( + mojom.parse.lexer.LexError, + r"^my_file\.mojom:1: Error: Illegal character '\$'$"): + self._TokensForInput("a$bc") + + def testDecimalIntegerConstants(self): + self.assertEquals(self._SingleTokenForInput("0"), + _MakeLexToken("INT_CONST_DEC", "0")) + self.assertEquals(self._SingleTokenForInput("1"), + _MakeLexToken("INT_CONST_DEC", "1")) + self.assertEquals(self._SingleTokenForInput("123"), + _MakeLexToken("INT_CONST_DEC", "123")) + self.assertEquals(self._SingleTokenForInput("10"), + _MakeLexToken("INT_CONST_DEC", "10")) + + def testValidTokens(self): + """Tests valid tokens (which aren't tested elsewhere).""" + # Keywords tested in |testValidKeywords|. + # NAME tested in |testValidIdentifiers|. + self.assertEquals(self._SingleTokenForInput("@123"), + _MakeLexToken("ORDINAL", "@123")) + self.assertEquals(self._SingleTokenForInput("456"), + _MakeLexToken("INT_CONST_DEC", "456")) + self.assertEquals(self._SingleTokenForInput("0x01aB2eF3"), + _MakeLexToken("INT_CONST_HEX", "0x01aB2eF3")) + self.assertEquals(self._SingleTokenForInput("123.456"), + _MakeLexToken("FLOAT_CONST", "123.456")) + self.assertEquals(self._SingleTokenForInput("\"hello\""), + _MakeLexToken("STRING_LITERAL", "\"hello\"")) + self.assertEquals(self._SingleTokenForInput("+"), + _MakeLexToken("PLUS", "+")) + self.assertEquals(self._SingleTokenForInput("-"), + _MakeLexToken("MINUS", "-")) + self.assertEquals(self._SingleTokenForInput("&"), + _MakeLexToken("AMP", "&")) + self.assertEquals(self._SingleTokenForInput("?"), + _MakeLexToken("QSTN", "?")) + self.assertEquals(self._SingleTokenForInput("="), + _MakeLexToken("EQUALS", "=")) + self.assertEquals(self._SingleTokenForInput("=>"), + _MakeLexToken("RESPONSE", "=>")) + self.assertEquals(self._SingleTokenForInput("("), + _MakeLexToken("LPAREN", "(")) + self.assertEquals(self._SingleTokenForInput(")"), + _MakeLexToken("RPAREN", ")")) + self.assertEquals(self._SingleTokenForInput("["), + _MakeLexToken("LBRACKET", "[")) + self.assertEquals(self._SingleTokenForInput("]"), + _MakeLexToken("RBRACKET", "]")) + self.assertEquals(self._SingleTokenForInput("{"), + _MakeLexToken("LBRACE", "{")) + self.assertEquals(self._SingleTokenForInput("}"), + _MakeLexToken("RBRACE", "}")) + self.assertEquals(self._SingleTokenForInput("<"), + _MakeLexToken("LANGLE", "<")) + self.assertEquals(self._SingleTokenForInput(">"), + _MakeLexToken("RANGLE", ">")) + self.assertEquals(self._SingleTokenForInput(";"), + _MakeLexToken("SEMI", ";")) + self.assertEquals(self._SingleTokenForInput(","), + _MakeLexToken("COMMA", ",")) + self.assertEquals(self._SingleTokenForInput("."), + _MakeLexToken("DOT", ".")) + + def _TokensForInput(self, input_string): + """Gets a list of tokens for the given input string.""" + lexer = self._zygote_lexer.clone() + lexer.input(input_string) + rv = [] + while True: + tok = lexer.token() + if not tok: + return rv + rv.append(tok) + + def _SingleTokenForInput(self, input_string): + """Gets the single token for the given input string. (Raises an exception if + the input string does not result in exactly one token.)""" + toks = self._TokensForInput(input_string) + assert len(toks) == 1 + return toks[0] + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py new file mode 100644 index 0000000000..3f4ca871e3 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py @@ -0,0 +1,1497 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +import mojom.parse.ast as ast +import mojom.parse.lexer as lexer +import mojom.parse.parser as parser + + +class ParserTest(unittest.TestCase): + """Tests |parser.Parse()|.""" + + def testTrivialValidSource(self): + """Tests a trivial, but valid, .mojom source.""" + + source = """\ + // This is a comment. + + module my_module; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + []) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testSourceWithCrLfs(self): + """Tests a .mojom source with CR-LFs instead of LFs.""" + + source = "// This is a comment.\r\n\r\nmodule my_module;\r\n" + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + []) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testUnexpectedEOF(self): + """Tests a "truncated" .mojom source.""" + + source = """\ + // This is a comment. + + module my_module + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom: Error: Unexpected end of file$"): + parser.Parse(source, "my_file.mojom") + + def testCommentLineNumbers(self): + """Tests that line numbers are correctly tracked when comments are + present.""" + + source1 = """\ + // Isolated C++-style comments. + + // Foo. + asdf1 + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'asdf1':\n *asdf1$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + // Consecutive C++-style comments. + // Foo. + // Bar. + + struct Yada { // Baz. + // Quux. + int32 x; + }; + + asdf2 + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:10: Error: Unexpected 'asdf2':\n *asdf2$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + /* Single-line C-style comments. */ + /* Foobar. */ + + /* Baz. */ + asdf3 + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:5: Error: Unexpected 'asdf3':\n *asdf3$"): + parser.Parse(source3, "my_file.mojom") + + source4 = """\ + /* Multi-line C-style comments. + */ + /* + Foo. + Bar. + */ + + /* Baz + Quux. */ + asdf4 + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:10: Error: Unexpected 'asdf4':\n *asdf4$"): + parser.Parse(source4, "my_file.mojom") + + + def testSimpleStruct(self): + """Tests a simple .mojom source that just defines a struct.""" + + source = """\ + module my_module; + + struct MyStruct { + int32 a; + double b; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None, 'int32', None), + ast.StructField('b', None, None, 'double', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testSimpleStructWithoutModule(self): + """Tests a simple struct without an explict module statement.""" + + source = """\ + struct MyStruct { + int32 a; + double b; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None, 'int32', None), + ast.StructField('b', None, None, 'double', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidStructDefinitions(self): + """Tests all types of definitions that can occur in a struct.""" + + source = """\ + struct MyStruct { + enum MyEnum { VALUE }; + const double kMyConst = 1.23; + int32 a; + SomeOtherStruct b; // Invalidity detected at another stage. + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.Enum('MyEnum', + None, + ast.EnumValueList( + ast.EnumValue('VALUE', None, None))), + ast.Const('kMyConst', 'double', '1.23'), + ast.StructField('a', None, None, 'int32', None), + ast.StructField('b', None, None, 'SomeOtherStruct', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidStructDefinitions(self): + """Tests that definitions that aren't allowed in a struct are correctly + detected.""" + + source1 = """\ + struct MyStruct { + MyMethod(int32 a); + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '\(':\n" + r" *MyMethod\(int32 a\);$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + struct MyStruct { + struct MyInnerStruct { + int32 a; + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" + r" *struct MyInnerStruct {$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + struct MyStruct { + interface MyInterface { + MyMethod(int32 a); + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'interface':\n" + r" *interface MyInterface {$"): + parser.Parse(source3, "my_file.mojom") + + def testMissingModuleName(self): + """Tests an (invalid) .mojom with a missing module name.""" + + source1 = """\ + // Missing module name. + module ; + struct MyStruct { + int32 a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected ';':\n *module ;$"): + parser.Parse(source1, "my_file.mojom") + + # Another similar case, but make sure that line-number tracking/reporting + # is correct. + source2 = """\ + module + // This line intentionally left unblank. + + struct MyStruct { + int32 a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source2, "my_file.mojom") + + def testMultipleModuleStatements(self): + """Tests an (invalid) .mojom with multiple module statements.""" + + source = """\ + module foo; + module bar; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Multiple \"module\" statements not " + r"allowed:\n *module bar;$"): + parser.Parse(source, "my_file.mojom") + + def testModuleStatementAfterImport(self): + """Tests an (invalid) .mojom with a module statement after an import.""" + + source = """\ + import "foo.mojom"; + module foo; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: \"module\" statements must precede imports " + r"and definitions:\n *module foo;$"): + parser.Parse(source, "my_file.mojom") + + def testModuleStatementAfterDefinition(self): + """Tests an (invalid) .mojom with a module statement after a definition.""" + + source = """\ + struct MyStruct { + int32 a; + }; + module foo; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: \"module\" statements must precede imports " + r"and definitions:\n *module foo;$"): + parser.Parse(source, "my_file.mojom") + + def testImportStatementAfterDefinition(self): + """Tests an (invalid) .mojom with an import statement after a definition.""" + + source = """\ + struct MyStruct { + int32 a; + }; + import "foo.mojom"; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: \"import\" statements must precede " + r"definitions:\n *import \"foo.mojom\";$"): + parser.Parse(source, "my_file.mojom") + + def testEnums(self): + """Tests that enum statements are correctly parsed.""" + + source = """\ + module my_module; + enum MyEnum1 { VALUE1, VALUE2 }; // No trailing comma. + enum MyEnum2 { + VALUE1 = -1, + VALUE2 = 0, + VALUE3 = + 987, // Check that space is allowed. + VALUE4 = 0xAF12, + VALUE5 = -0x09bcd, + VALUE6 = VALUE5, + VALUE7, // Leave trailing comma. + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Enum( + 'MyEnum1', + None, + ast.EnumValueList([ast.EnumValue('VALUE1', None, None), + ast.EnumValue('VALUE2', None, None)])), + ast.Enum( + 'MyEnum2', + None, + ast.EnumValueList([ast.EnumValue('VALUE1', None, '-1'), + ast.EnumValue('VALUE2', None, '0'), + ast.EnumValue('VALUE3', None, '+987'), + ast.EnumValue('VALUE4', None, '0xAF12'), + ast.EnumValue('VALUE5', None, '-0x09bcd'), + ast.EnumValue('VALUE6', None, ('IDENTIFIER', + 'VALUE5')), + ast.EnumValue('VALUE7', None, None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidEnumInitializers(self): + """Tests that invalid enum initializers are correctly detected.""" + + # No values. + source1 = """\ + enum MyEnum { + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '}':\n" + r" *};$"): + parser.Parse(source1, "my_file.mojom") + + # Floating point value. + source2 = "enum MyEnum { VALUE = 0.123 };" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected '0\.123':\n" + r"enum MyEnum { VALUE = 0\.123 };$"): + parser.Parse(source2, "my_file.mojom") + + # Boolean value. + source2 = "enum MyEnum { VALUE = true };" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected 'true':\n" + r"enum MyEnum { VALUE = true };$"): + parser.Parse(source2, "my_file.mojom") + + def testConsts(self): + """Tests some constants and struct members initialized with them.""" + + source = """\ + module my_module; + + struct MyStruct { + const int8 kNumber = -1; + int8 number@0 = kNumber; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Struct( + 'MyStruct', None, + ast.StructBody( + [ast.Const('kNumber', 'int8', '-1'), + ast.StructField('number', None, ast.Ordinal(0), 'int8', + ('IDENTIFIER', 'kNumber'))]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testNoConditionals(self): + """Tests that ?: is not allowed.""" + + source = """\ + module my_module; + + enum MyEnum { + MY_ENUM_1 = 1 ? 2 : 3 + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected '\?':\n" + r" *MY_ENUM_1 = 1 \? 2 : 3$"): + parser.Parse(source, "my_file.mojom") + + def testSimpleOrdinals(self): + """Tests that (valid) ordinal values are scanned correctly.""" + + source = """\ + module my_module; + + // This isn't actually valid .mojom, but the problem (missing ordinals) + // should be handled at a different level. + struct MyStruct { + int32 a0@0; + int32 a1@1; + int32 a2@2; + int32 a9@9; + int32 a10 @10; + int32 a11 @11; + int32 a29 @29; + int32 a1234567890 @1234567890; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a0', None, ast.Ordinal(0), 'int32', None), + ast.StructField('a1', None, ast.Ordinal(1), 'int32', None), + ast.StructField('a2', None, ast.Ordinal(2), 'int32', None), + ast.StructField('a9', None, ast.Ordinal(9), 'int32', None), + ast.StructField('a10', None, ast.Ordinal(10), 'int32', None), + ast.StructField('a11', None, ast.Ordinal(11), 'int32', None), + ast.StructField('a29', None, ast.Ordinal(29), 'int32', None), + ast.StructField('a1234567890', None, ast.Ordinal(1234567890), + 'int32', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidOrdinals(self): + """Tests that (lexically) invalid ordinals are correctly detected.""" + + source1 = """\ + module my_module; + + struct MyStruct { + int32 a_missing@; + }; + """ + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:4: Error: Missing ordinal value$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + module my_module; + + struct MyStruct { + int32 a_octal@01; + }; + """ + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:4: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + module my_module; struct MyStruct { int32 a_invalid_octal@08; }; + """ + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source3, "my_file.mojom") + + source4 = "module my_module; struct MyStruct { int32 a_hex@0x1aB9; };" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source4, "my_file.mojom") + + source5 = "module my_module; struct MyStruct { int32 a_hex@0X0; };" + with self.assertRaisesRegexp( + lexer.LexError, + r"^my_file\.mojom:1: Error: " + r"Octal and hexadecimal ordinal values not allowed$"): + parser.Parse(source5, "my_file.mojom") + + source6 = """\ + struct MyStruct { + int32 a_too_big@999999999999; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: " + r"Ordinal value 999999999999 too large:\n" + r" *int32 a_too_big@999999999999;$"): + parser.Parse(source6, "my_file.mojom") + + def testNestedNamespace(self): + """Tests that "nested" namespaces work.""" + + source = """\ + module my.mod; + + struct MyStruct { + int32 a; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my.mod'), None), + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody(ast.StructField('a', None, None, 'int32', None)))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidHandleTypes(self): + """Tests (valid) handle types.""" + + source = """\ + struct MyStruct { + handle a; + handle<data_pipe_consumer> b; + handle <data_pipe_producer> c; + handle < message_pipe > d; + handle + < shared_buffer + > e; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None, 'handle', None), + ast.StructField('b', None, None, 'handle<data_pipe_consumer>', + None), + ast.StructField('c', None, None, 'handle<data_pipe_producer>', + None), + ast.StructField('d', None, None, 'handle<message_pipe>', None), + ast.StructField('e', None, None, 'handle<shared_buffer>', + None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidHandleType(self): + """Tests an invalid (unknown) handle type.""" + + source = """\ + struct MyStruct { + handle<wtf_is_this> foo; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: " + r"Invalid handle type 'wtf_is_this':\n" + r" *handle<wtf_is_this> foo;$"): + parser.Parse(source, "my_file.mojom") + + def testValidDefaultValues(self): + """Tests default values that are valid (to the parser).""" + + source = """\ + struct MyStruct { + int16 a0 = 0; + uint16 a1 = 0x0; + uint16 a2 = 0x00; + uint16 a3 = 0x01; + uint16 a4 = 0xcd; + int32 a5 = 12345; + int64 a6 = -12345; + int64 a7 = +12345; + uint32 a8 = 0x12cd3; + uint32 a9 = -0x12cD3; + uint32 a10 = +0x12CD3; + bool a11 = true; + bool a12 = false; + float a13 = 1.2345; + float a14 = -1.2345; + float a15 = +1.2345; + float a16 = 123.; + float a17 = .123; + double a18 = 1.23E10; + double a19 = 1.E-10; + double a20 = .5E+10; + double a21 = -1.23E10; + double a22 = +.123E10; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a0', None, None, 'int16', '0'), + ast.StructField('a1', None, None, 'uint16', '0x0'), + ast.StructField('a2', None, None, 'uint16', '0x00'), + ast.StructField('a3', None, None, 'uint16', '0x01'), + ast.StructField('a4', None, None, 'uint16', '0xcd'), + ast.StructField('a5' , None, None, 'int32', '12345'), + ast.StructField('a6', None, None, 'int64', '-12345'), + ast.StructField('a7', None, None, 'int64', '+12345'), + ast.StructField('a8', None, None, 'uint32', '0x12cd3'), + ast.StructField('a9', None, None, 'uint32', '-0x12cD3'), + ast.StructField('a10', None, None, 'uint32', '+0x12CD3'), + ast.StructField('a11', None, None, 'bool', 'true'), + ast.StructField('a12', None, None, 'bool', 'false'), + ast.StructField('a13', None, None, 'float', '1.2345'), + ast.StructField('a14', None, None, 'float', '-1.2345'), + ast.StructField('a15', None, None, 'float', '+1.2345'), + ast.StructField('a16', None, None, 'float', '123.'), + ast.StructField('a17', None, None, 'float', '.123'), + ast.StructField('a18', None, None, 'double', '1.23E10'), + ast.StructField('a19', None, None, 'double', '1.E-10'), + ast.StructField('a20', None, None, 'double', '.5E+10'), + ast.StructField('a21', None, None, 'double', '-1.23E10'), + ast.StructField('a22', None, None, 'double', '+.123E10')]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidFixedSizeArray(self): + """Tests parsing a fixed size array.""" + + source = """\ + struct MyStruct { + array<int32> normal_array; + array<int32, 1> fixed_size_array_one_entry; + array<int32, 10> fixed_size_array_ten_entries; + array<array<array<int32, 1>>, 2> nested_arrays; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('normal_array', None, None, 'int32[]', None), + ast.StructField('fixed_size_array_one_entry', None, None, + 'int32[1]', None), + ast.StructField('fixed_size_array_ten_entries', None, None, + 'int32[10]', None), + ast.StructField('nested_arrays', None, None, + 'int32[1][][2]', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testValidNestedArray(self): + """Tests parsing a nested array.""" + + source = "struct MyStruct { array<array<int32>> nested_array; };" + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + ast.StructField('nested_array', None, None, 'int32[][]', + None)))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidFixedArraySize(self): + """Tests that invalid fixed array bounds are correctly detected.""" + + source1 = """\ + struct MyStruct { + array<int32, 0> zero_size_array; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Fixed array size 0 invalid:\n" + r" *array<int32, 0> zero_size_array;$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + struct MyStruct { + array<int32, 999999999999> too_big_array; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Fixed array size 999999999999 invalid:\n" + r" *array<int32, 999999999999> too_big_array;$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + struct MyStruct { + array<int32, abcdefg> not_a_number; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'abcdefg':\n" + r" *array<int32, abcdefg> not_a_number;"): + parser.Parse(source3, "my_file.mojom") + + def testValidAssociativeArrays(self): + """Tests that we can parse valid associative array structures.""" + + source1 = "struct MyStruct { map<string, uint8> data; };" + expected1 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('data', None, None, 'uint8{string}', None)]))]) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + source2 = "interface MyInterface { MyMethod(map<string, uint8> a); };" + expected2 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList( + ast.Parameter('a', None, None, 'uint8{string}')), + None)))]) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + source3 = "struct MyStruct { map<string, array<uint8>> data; };" + expected3 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('data', None, None, 'uint8[]{string}', + None)]))]) + self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) + + def testValidMethod(self): + """Tests parsing method declarations.""" + + source1 = "interface MyInterface { MyMethod(int32 a); };" + expected1 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList(ast.Parameter('a', None, None, 'int32')), + None)))]) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + source2 = """\ + interface MyInterface { + MyMethod1@0(int32 a@0, int64 b@1); + MyMethod2@1() => (); + }; + """ + expected2 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + [ast.Method( + 'MyMethod1', + None, + ast.Ordinal(0), + ast.ParameterList([ast.Parameter('a', None, ast.Ordinal(0), + 'int32'), + ast.Parameter('b', None, ast.Ordinal(1), + 'int64')]), + None), + ast.Method( + 'MyMethod2', + None, + ast.Ordinal(1), + ast.ParameterList(), + ast.ParameterList())]))]) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + source3 = """\ + interface MyInterface { + MyMethod(string a) => (int32 a, bool b); + }; + """ + expected3 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList(ast.Parameter('a', None, None, 'string')), + ast.ParameterList([ast.Parameter('a', None, None, 'int32'), + ast.Parameter('b', None, None, + 'bool')]))))]) + self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) + + def testInvalidMethods(self): + """Tests that invalid method declarations are correctly detected.""" + + # No trailing commas. + source1 = """\ + interface MyInterface { + MyMethod(string a,); + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '\)':\n" + r" *MyMethod\(string a,\);$"): + parser.Parse(source1, "my_file.mojom") + + # No leading commas. + source2 = """\ + interface MyInterface { + MyMethod(, string a); + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected ',':\n" + r" *MyMethod\(, string a\);$"): + parser.Parse(source2, "my_file.mojom") + + def testValidInterfaceDefinitions(self): + """Tests all types of definitions that can occur in an interface.""" + + source = """\ + interface MyInterface { + enum MyEnum { VALUE }; + const int32 kMyConst = 123; + MyMethod(int32 x) => (MyEnum y); + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + [ast.Enum('MyEnum', + None, + ast.EnumValueList( + ast.EnumValue('VALUE', None, None))), + ast.Const('kMyConst', 'int32', '123'), + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList(ast.Parameter('x', None, None, 'int32')), + ast.ParameterList(ast.Parameter('y', None, None, + 'MyEnum')))]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidInterfaceDefinitions(self): + """Tests that definitions that aren't allowed in an interface are correctly + detected.""" + + source1 = """\ + interface MyInterface { + struct MyStruct { + int32 a; + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + interface MyInterface { + interface MyInnerInterface { + MyMethod(int32 x); + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'interface':\n" + r" *interface MyInnerInterface {$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + interface MyInterface { + int32 my_field; + }; + """ + # The parser thinks that "int32" is a plausible name for a method, so it's + # "my_field" that gives it away. + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'my_field':\n" + r" *int32 my_field;$"): + parser.Parse(source3, "my_file.mojom") + + def testValidAttributes(self): + """Tests parsing attributes (and attribute lists).""" + + # Note: We use structs because they have (optional) attribute lists. + + # Empty attribute list. + source1 = "[] struct MyStruct {};" + expected1 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct('MyStruct', ast.AttributeList(), ast.StructBody())]) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + # One-element attribute list, with name value. + source2 = "[MyAttribute=MyName] struct MyStruct {};" + expected2 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + ast.AttributeList(ast.Attribute("MyAttribute", "MyName")), + ast.StructBody())]) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + # Two-element attribute list, with one string value and one integer value. + source3 = "[MyAttribute1 = \"hello\", MyAttribute2 = 5] struct MyStruct {};" + expected3 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + ast.AttributeList([ast.Attribute("MyAttribute1", "hello"), + ast.Attribute("MyAttribute2", 5)]), + ast.StructBody())]) + self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) + + # Various places that attribute list is allowed. + source4 = """\ + [Attr0=0] module my_module; + + [Attr1=1] struct MyStruct { + [Attr2=2] int32 a; + }; + [Attr3=3] union MyUnion { + [Attr4=4] int32 a; + }; + [Attr5=5] enum MyEnum { + [Attr6=6] a + }; + [Attr7=7] interface MyInterface { + [Attr8=8] MyMethod([Attr9=9] int32 a) => ([Attr10=10] bool b); + }; + """ + expected4 = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), + ast.AttributeList([ast.Attribute("Attr0", 0)])), + ast.ImportList(), + [ast.Struct( + 'MyStruct', + ast.AttributeList(ast.Attribute("Attr1", 1)), + ast.StructBody( + ast.StructField( + 'a', ast.AttributeList([ast.Attribute("Attr2", 2)]), + None, 'int32', None))), + ast.Union( + 'MyUnion', + ast.AttributeList(ast.Attribute("Attr3", 3)), + ast.UnionBody( + ast.UnionField( + 'a', ast.AttributeList([ast.Attribute("Attr4", 4)]), None, + 'int32'))), + ast.Enum( + 'MyEnum', + ast.AttributeList(ast.Attribute("Attr5", 5)), + ast.EnumValueList( + ast.EnumValue( + 'VALUE', ast.AttributeList([ast.Attribute("Attr6", 6)]), + None))), + ast.Interface( + 'MyInterface', + ast.AttributeList(ast.Attribute("Attr7", 7)), + ast.InterfaceBody( + ast.Method( + 'MyMethod', + ast.AttributeList(ast.Attribute("Attr8", 8)), + None, + ast.ParameterList( + ast.Parameter( + 'a', ast.AttributeList([ast.Attribute("Attr9", 9)]), + None, 'int32')), + ast.ParameterList( + ast.Parameter( + 'b', + ast.AttributeList([ast.Attribute("Attr10", 10)]), + None, 'bool')))))]) + self.assertEquals(parser.Parse(source4, "my_file.mojom"), expected4) + + # TODO(vtl): Boolean attributes don't work yet. (In fact, we just |eval()| + # literal (non-name) values, which is extremely dubious.) + + def testInvalidAttributes(self): + """Tests that invalid attributes and attribute lists are correctly + detected.""" + + # Trailing commas not allowed. + source1 = "[MyAttribute=MyName,] struct MyStruct {};" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected '\]':\n" + r"\[MyAttribute=MyName,\] struct MyStruct {};$"): + parser.Parse(source1, "my_file.mojom") + + # Missing value. + source2 = "[MyAttribute=] struct MyStruct {};" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected '\]':\n" + r"\[MyAttribute=\] struct MyStruct {};$"): + parser.Parse(source2, "my_file.mojom") + + # Missing key. + source3 = "[=MyName] struct MyStruct {};" + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:1: Error: Unexpected '=':\n" + r"\[=MyName\] struct MyStruct {};$"): + parser.Parse(source3, "my_file.mojom") + + def testValidImports(self): + """Tests parsing import statements.""" + + # One import (no module statement). + source1 = "import \"somedir/my.mojom\";" + expected1 = ast.Mojom( + None, + ast.ImportList(ast.Import("somedir/my.mojom")), + []) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + # Two imports (no module statement). + source2 = """\ + import "somedir/my1.mojom"; + import "somedir/my2.mojom"; + """ + expected2 = ast.Mojom( + None, + ast.ImportList([ast.Import("somedir/my1.mojom"), + ast.Import("somedir/my2.mojom")]), + []) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + # Imports with module statement. + source3 = """\ + module my_module; + import "somedir/my1.mojom"; + import "somedir/my2.mojom"; + """ + expected3 = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList([ast.Import("somedir/my1.mojom"), + ast.Import("somedir/my2.mojom")]), + []) + self.assertEquals(parser.Parse(source3, "my_file.mojom"), expected3) + + def testInvalidImports(self): + """Tests that invalid import statements are correctly detected.""" + + source1 = """\ + // Make the error occur on line 2. + import invalid + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'invalid':\n" + r" *import invalid$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + import // Missing string. + struct MyStruct { + int32 a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + import "foo.mojom" // Missing semicolon. + struct MyStruct { + int32 a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source3, "my_file.mojom") + + def testValidNullableTypes(self): + """Tests parsing nullable types.""" + + source = """\ + struct MyStruct { + int32? a; // This is actually invalid, but handled at a different + // level. + string? b; + array<int32> ? c; + array<string ? > ? d; + array<array<int32>?>? e; + array<int32, 1>? f; + array<string?, 1>? g; + some_struct? h; + handle? i; + handle<data_pipe_consumer>? j; + handle<data_pipe_producer>? k; + handle<message_pipe>? l; + handle<shared_buffer>? m; + some_interface&? n; + }; + """ + expected = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None,'int32?', None), + ast.StructField('b', None, None,'string?', None), + ast.StructField('c', None, None,'int32[]?', None), + ast.StructField('d', None, None,'string?[]?', None), + ast.StructField('e', None, None,'int32[]?[]?', None), + ast.StructField('f', None, None,'int32[1]?', None), + ast.StructField('g', None, None,'string?[1]?', None), + ast.StructField('h', None, None,'some_struct?', None), + ast.StructField('i', None, None,'handle?', None), + ast.StructField('j', None, None,'handle<data_pipe_consumer>?', + None), + ast.StructField('k', None, None,'handle<data_pipe_producer>?', + None), + ast.StructField('l', None, None,'handle<message_pipe>?', None), + ast.StructField('m', None, None,'handle<shared_buffer>?', + None), + ast.StructField('n', None, None,'some_interface&?', None)]))]) + self.assertEquals(parser.Parse(source, "my_file.mojom"), expected) + + def testInvalidNullableTypes(self): + """Tests that invalid nullable types are correctly detected.""" + source1 = """\ + struct MyStruct { + string?? a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '\?':\n" + r" *string\?\? a;$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + struct MyStruct { + handle?<data_pipe_consumer> a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '<':\n" + r" *handle\?<data_pipe_consumer> a;$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + struct MyStruct { + some_interface?& a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '&':\n" + r" *some_interface\?& a;$"): + parser.Parse(source3, "my_file.mojom") + + def testSimpleUnion(self): + """Tests a simple .mojom source that just defines a union.""" + source = """\ + module my_module; + + union MyUnion { + int32 a; + double b; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('a', None, None, 'int32'), + ast.UnionField('b', None, None, 'double') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionWithOrdinals(self): + """Test that ordinals are assigned to fields.""" + source = """\ + module my_module; + + union MyUnion { + int32 a @10; + double b @30; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('a', None, ast.Ordinal(10), 'int32'), + ast.UnionField('b', None, ast.Ordinal(30), 'double') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionWithStructMembers(self): + """Test that struct members are accepted.""" + source = """\ + module my_module; + + union MyUnion { + SomeStruct s; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('s', None, None, 'SomeStruct') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionWithArrayMember(self): + """Test that array members are accepted.""" + source = """\ + module my_module; + + union MyUnion { + array<int32> a; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('a', None, None, 'int32[]') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionWithMapMember(self): + """Test that map members are accepted.""" + source = """\ + module my_module; + + union MyUnion { + map<int32, string> m; + }; + """ + expected = ast.Mojom( + ast.Module(('IDENTIFIER', 'my_module'), None), + ast.ImportList(), + [ast.Union( + 'MyUnion', + None, + ast.UnionBody([ + ast.UnionField('m', None, None, 'string{int32}') + ]))]) + actual = parser.Parse(source, "my_file.mojom") + self.assertEquals(actual, expected) + + def testUnionDisallowNestedStruct(self): + """Tests that structs cannot be nested in unions.""" + source = """\ + module my_module; + + union MyUnion { + struct MyStruct { + int32 a; + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'struct':\n" + r" *struct MyStruct {$"): + parser.Parse(source, "my_file.mojom") + + def testUnionDisallowNestedInterfaces(self): + """Tests that interfaces cannot be nested in unions.""" + source = """\ + module my_module; + + union MyUnion { + interface MyInterface { + MyMethod(int32 a); + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'interface':\n" + r" *interface MyInterface {$"): + parser.Parse(source, "my_file.mojom") + + def testUnionDisallowNestedUnion(self): + """Tests that unions cannot be nested in unions.""" + source = """\ + module my_module; + + union MyUnion { + union MyOtherUnion { + int32 a; + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'union':\n" + r" *union MyOtherUnion {$"): + parser.Parse(source, "my_file.mojom") + + def testUnionDisallowNestedEnum(self): + """Tests that enums cannot be nested in unions.""" + source = """\ + module my_module; + + union MyUnion { + enum MyEnum { + A, + }; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:4: Error: Unexpected 'enum':\n" + r" *enum MyEnum {$"): + parser.Parse(source, "my_file.mojom") + + def testValidAssociatedKinds(self): + """Tests parsing associated interfaces and requests.""" + source1 = """\ + struct MyStruct { + associated MyInterface a; + associated MyInterface& b; + associated MyInterface? c; + associated MyInterface&? d; + }; + """ + expected1 = ast.Mojom( + None, + ast.ImportList(), + [ast.Struct( + 'MyStruct', + None, + ast.StructBody( + [ast.StructField('a', None, None,'asso<MyInterface>', None), + ast.StructField('b', None, None,'asso<MyInterface&>', None), + ast.StructField('c', None, None,'asso<MyInterface>?', None), + ast.StructField('d', None, None,'asso<MyInterface&>?', + None)]))]) + self.assertEquals(parser.Parse(source1, "my_file.mojom"), expected1) + + source2 = """\ + interface MyInterface { + MyMethod(associated A a) =>(associated B& b); + };""" + expected2 = ast.Mojom( + None, + ast.ImportList(), + [ast.Interface( + 'MyInterface', + None, + ast.InterfaceBody( + ast.Method( + 'MyMethod', + None, + None, + ast.ParameterList( + ast.Parameter('a', None, None, 'asso<A>')), + ast.ParameterList( + ast.Parameter('b', None, None, 'asso<B&>')))))]) + self.assertEquals(parser.Parse(source2, "my_file.mojom"), expected2) + + def testInvalidAssociatedKinds(self): + """Tests that invalid associated interfaces and requests are correctly + detected.""" + source1 = """\ + struct MyStruct { + associated associated SomeInterface a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'associated':\n" + r" *associated associated SomeInterface a;$"): + parser.Parse(source1, "my_file.mojom") + + source2 = """\ + struct MyStruct { + associated handle a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected 'handle':\n" + r" *associated handle a;$"): + parser.Parse(source2, "my_file.mojom") + + source3 = """\ + struct MyStruct { + associated? MyInterface& a; + }; + """ + with self.assertRaisesRegexp( + parser.ParseError, + r"^my_file\.mojom:2: Error: Unexpected '\?':\n" + r" *associated\? MyInterface& a;$"): + parser.Parse(source3, "my_file.mojom") + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py new file mode 100755 index 0000000000..b160de6690 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +# 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. + +"""Simple testing utility to just run the mojom parser.""" + + +import os.path +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.path.pardir, os.path.pardir)) + +from mojom.parse.parser import Parse, ParseError + + +def main(argv): + if len(argv) < 2: + print "usage: %s filename" % argv[0] + return 0 + + for filename in argv[1:]: + with open(filename) as f: + print "%s:" % filename + try: + print Parse(f.read(), filename) + except ParseError, e: + print e + return 1 + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py new file mode 100755 index 0000000000..899d40e584 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# 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. + +"""Simple testing utility to just run the mojom translate stage.""" + + +import os.path +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), + os.path.pardir, os.path.pardir)) + +from mojom.parse.parser import Parse +from mojom.parse.translate import Translate + + +def main(argv): + if len(argv) < 2: + print "usage: %s filename" % sys.argv[0] + return 1 + + for filename in argv[1:]: + with open(filename) as f: + print "%s:" % filename + print Translate(Parse(f.read(), filename), + os.path.splitext(os.path.basename(filename))[0]) + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py b/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py new file mode 100644 index 0000000000..25203329f4 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py @@ -0,0 +1,80 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import imp +import os.path +import sys +import unittest + +def _GetDirAbove(dirname): + """Returns the directory "above" this file containing |dirname| (which must + also be "above" this file).""" + path = os.path.abspath(__file__) + while True: + path, tail = os.path.split(path) + assert tail + if tail == dirname: + return path + +try: + imp.find_module("mojom") +except ImportError: + sys.path.append(os.path.join(_GetDirAbove("pylib"), "pylib")) +from mojom.parse import ast +from mojom.parse import translate + + +class TranslateTest(unittest.TestCase): + """Tests |parser.Parse()|.""" + + def testSimpleArray(self): + """Tests a simple int32[].""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("int32[]"), "a:i32") + + def testAssociativeArray(self): + """Tests a simple uint8{string}.""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("uint8{string}"), "m[s][u8]") + + def testLeftToRightAssociativeArray(self): + """Makes sure that parsing is done from right to left on the internal kinds + in the presence of an associative array.""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("uint8[]{string}"), "m[s][a:u8]") + + def testTranslateSimpleUnions(self): + """Makes sure that a simple union is translated correctly.""" + tree = ast.Mojom( + None, + ast.ImportList(), + [ast.Union("SomeUnion", None, ast.UnionBody( + [ast.UnionField("a", None, None, "int32"), + ast.UnionField("b", None, None, "string")]))]) + expected = [{ + "name": "SomeUnion", + "fields": [{"kind": "i32", "name": "a"}, + {"kind": "s", "name": "b"}]}] + actual = translate.Translate(tree, "mojom_tree") + self.assertEquals(actual["unions"], expected) + + def testMapTreeForTypeRaisesWithDuplicate(self): + """Verifies _MapTreeForType() raises when passed two values with the same + name.""" + methods = [ast.Method('dup', None, None, ast.ParameterList(), None), + ast.Method('dup', None, None, ast.ParameterList(), None)] + self.assertRaises(Exception, translate._MapTreeForType, + (lambda x: x, methods, '', 'scope')) + + def testAssociatedKinds(self): + """Tests type spec translation of associated interfaces and requests.""" + # pylint: disable=W0212 + self.assertEquals(translate._MapKind("asso<SomeInterface>?"), + "?asso:x:SomeInterface") + self.assertEquals(translate._MapKind("asso<SomeInterface&>?"), + "?asso:r:x:SomeInterface") + + +if __name__ == "__main__": + unittest.main() diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py new file mode 100644 index 0000000000..2a4b17b29b --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py @@ -0,0 +1,32 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import fnmatch +from os import walk +from os.path import join +import sys + + +def FindFiles(top, pattern, **kwargs): + """Finds files under |top| matching the glob pattern |pattern|, returning a + list of paths.""" + matches = [] + for dirpath, _, filenames in walk(top, **kwargs): + for filename in fnmatch.filter(filenames, pattern): + matches.append(join(dirpath, filename)) + return matches + + +def main(argv): + if len(argv) != 3: + print "usage: %s path pattern" % argv[0] + return 1 + + for filename in FindFiles(argv[1], argv[2]): + print filename + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py b/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py new file mode 100644 index 0000000000..20ef461969 --- /dev/null +++ b/mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py @@ -0,0 +1,47 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os.path +from subprocess import check_call +import sys + + +def RunBindingsGenerator(out_dir, root_dir, mojom_file, extra_flags=None): + out_dir = os.path.abspath(out_dir) + root_dir = os.path.abspath(root_dir) + mojom_file = os.path.abspath(mojom_file) + + # The mojom file should be under the root directory somewhere. + assert mojom_file.startswith(root_dir) + mojom_reldir = os.path.dirname(os.path.relpath(mojom_file, root_dir)) + + # TODO(vtl): Abstract out the "main" functions, so that we can just import + # the bindings generator (which would be more portable and easier to use in + # tests). + this_dir = os.path.dirname(os.path.abspath(__file__)) + # We're in src/mojo/public/tools/bindings/pylib/mojom_tests/support; + # mojom_bindings_generator.py is in .../bindings. + bindings_generator = os.path.join(this_dir, os.pardir, os.pardir, os.pardir, + "mojom_bindings_generator.py") + + args = ["python", bindings_generator, + "-o", os.path.join(out_dir, mojom_reldir)] + if extra_flags: + args.extend(extra_flags) + args.append(mojom_file) + + check_call(args) + + +def main(argv): + if len(argv) < 4: + print "usage: %s out_dir root_dir mojom_file [extra_flags]" % argv[0] + return 1 + + RunBindingsGenerator(argv[1], argv[2], argv[3], extra_flags=argv[4:]) + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/mojo/public/tools/chrome_ipc/generate_mojom.py b/mojo/public/tools/chrome_ipc/generate_mojom.py new file mode 100755 index 0000000000..04e933bc41 --- /dev/null +++ b/mojo/public/tools/chrome_ipc/generate_mojom.py @@ -0,0 +1,453 @@ +#! /usr/bin/env python +# 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. +"""A generator of mojom interfaces and typemaps from Chrome IPC messages. + +For example, +generate_mojom.py content/common/file_utilities_messages.h + --output_mojom=content/common/file_utilities.mojom + --output_typemap=content/common/file_utilities.typemap +""" + +import argparse +import logging +import os +import re +import subprocess +import sys + +_MESSAGE_PATTERN = re.compile( + r'(?:\n|^)IPC_(SYNC_)?MESSAGE_(ROUTED|CONTROL)(\d_)?(\d)') +_VECTOR_PATTERN = re.compile(r'std::(vector|set)<(.*)>') +_MAP_PATTERN = re.compile(r'std::map<(.*), *(.*)>') +_NAMESPACE_PATTERN = re.compile(r'([a-z_]*?)::([A-Z].*)') + +_unused_arg_count = 0 + + +def _git_grep(pattern, paths_pattern): + try: + args = ['git', 'grep', '-l', '-e', pattern, '--'] + paths_pattern + result = subprocess.check_output(args).strip().splitlines() + logging.debug('%s => %s', ' '.join(args), result) + return result + except subprocess.CalledProcessError: + logging.debug('%s => []', ' '.join(args)) + return [] + + +def _git_multigrep(patterns, paths): + """Find a list of files that match all of the provided patterns.""" + if isinstance(paths, str): + paths = [paths] + if isinstance(patterns, str): + patterns = [patterns] + for pattern in patterns: + # Search only the files that matched previous patterns. + paths = _git_grep(pattern, paths) + if not paths: + return [] + return paths + + +class Typemap(object): + + def __init__(self, typemap_files): + self._typemap_files = typemap_files + self._custom_mappings = {} + self._new_custom_mappings = {} + self._imports = set() + self._public_includes = set() + self._traits_includes = set() + self._enums = set() + + def load_typemaps(self): + for typemap in self._typemap_files: + self.load_typemap(typemap) + + def load_typemap(self, path): + typemap = {} + with open(path) as f: + content = f.read().replace('=\n', '=') + exec content in typemap + for mapping in typemap['type_mappings']: + mojom, native = mapping.split('=') + self._custom_mappings[native] = {'name': mojom, + 'mojom': typemap['mojom'].strip('/')} + + def generate_typemap(self, output_mojom, input_filename, namespace): + new_mappings = sorted(self._format_new_mappings(namespace)) + if not new_mappings: + return + yield """# 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. +""" + yield 'mojom = "//%s"' % output_mojom + yield 'public_headers = [%s\n]' % ''.join( + '\n "//%s",' % include for include in sorted(self._public_includes)) + yield 'traits_headers = [%s\n]' % ''.join( + '\n "//%s",' % include + for include in sorted(self._traits_includes.union([os.path.normpath( + input_filename)]))) + yield 'deps = [ "//ipc" ]' + yield 'type_mappings = [\n %s\n]' % '\n '.join(new_mappings) + + def _format_new_mappings(self, namespace): + for native, mojom in self._new_custom_mappings.iteritems(): + yield '"%s.%s=::%s",' % (namespace, mojom, native) + + def format_new_types(self): + for native_type, typename in self._new_custom_mappings.iteritems(): + if native_type in self._enums: + yield '[Native]\nenum %s;\n' % typename + else: + yield '[Native]\nstruct %s;\n' % typename + + _BUILTINS = { + 'bool': 'bool', + 'int': 'int32', + 'unsigned': 'uint32', + 'char': 'uint8', + 'unsigned char': 'uint8', + 'short': 'int16', + 'unsigned short': 'uint16', + 'int8_t': 'int8', + 'int16_t': 'int16', + 'int32_t': 'int32', + 'int64_t': 'int64', + 'uint8_t': 'uint8', + 'uint16_t': 'uint16', + 'uint32_t': 'uint32', + 'uint64_t': 'uint64', + 'float': 'float', + 'double': 'double', + 'std::string': 'string', + 'base::string16': 'string', + 'base::FilePath::StringType': 'string', + 'base::SharedMemoryHandle': 'handle<shared_memory>', + 'IPC::PlatformFileForTransit': 'handle', + 'base::FileDescriptor': 'handle', + } + + def lookup_type(self, typename): + try: + return self._BUILTINS[typename] + except KeyError: + pass + + vector_match = _VECTOR_PATTERN.search(typename) + if vector_match: + return 'array<%s>' % self.lookup_type(vector_match.groups()[1].strip()) + map_match = _MAP_PATTERN.search(typename) + if map_match: + return 'map<%s, %s>' % tuple(self.lookup_type(t.strip()) + for t in map_match.groups()) + try: + result = self._custom_mappings[typename]['name'] + mojom = self._custom_mappings[typename].get('mojom', None) + if mojom: + self._imports.add(mojom) + return result + except KeyError: + pass + + match = _NAMESPACE_PATTERN.match(typename) + if match: + namespace, name = match.groups() + else: + namespace = '' + name = typename + namespace = namespace.replace('::', '.') + cpp_name = name + name = name.replace('::', '') + + if name.endswith('Params'): + try: + _, name = name.rsplit('Msg_') + except ValueError: + try: + _, name = name.split('_', 1) + except ValueError: + pass + + if namespace.endswith('.mojom'): + generated_mojom_name = '%s.%s' % (namespace, name) + elif not namespace: + generated_mojom_name = 'mojom.%s' % name + else: + generated_mojom_name = '%s.mojom.%s' % (namespace, name) + + self._new_custom_mappings[typename] = name + self._add_includes(namespace, cpp_name, typename) + generated_mojom_name = name + self._custom_mappings[typename] = {'name': generated_mojom_name} + return generated_mojom_name + + def _add_includes(self, namespace, name, fullname): + name_components = name.split('::') + is_enum = False + for i in xrange(len(name_components)): + subname = '::'.join(name_components[i:]) + extra_names = name_components[:i] + [subname] + patterns = [r'\(struct\|class\|enum\)[A-Z_ ]* %s {' % s + for s in extra_names] + if namespace: + patterns.extend(r'namespace %s' % namespace_component + for namespace_component in namespace.split('.')) + includes = _git_multigrep(patterns, '*.h') + if includes: + if _git_grep(r'enum[A-Z_ ]* %s {' % subname, includes): + self._enums.add(fullname) + is_enum = True + logging.info('%s => public_headers = %s', fullname, includes) + self._public_includes.update(includes) + break + + if is_enum: + patterns = ['IPC_ENUM_TRAITS[A-Z_]*(%s' % fullname] + else: + patterns = [r'\(IPC_STRUCT_TRAITS_BEGIN(\|ParamTraits<\)%s' % fullname] + includes = _git_multigrep( + patterns, + ['*messages.h', '*struct_traits.h', 'ipc/ipc_message_utils.h']) + if includes: + logging.info('%s => traits_headers = %s', fullname, includes) + self._traits_includes.update(includes) + + def format_imports(self): + for import_name in sorted(self._imports): + yield 'import "%s";' % import_name + if self._imports: + yield '' + + +class Argument(object): + + def __init__(self, typename, name): + self.typename = typename.strip() + self.name = name.strip().replace('\n', '').replace(' ', '_').lower() + if not self.name: + global _unused_arg_count + self.name = 'unnamed_arg%d' % _unused_arg_count + _unused_arg_count += 1 + + def format(self, typemaps): + return '%s %s' % (typemaps.lookup_type(self.typename), self.name) + + +class Message(object): + + def __init__(self, match, content): + self.sync = bool(match[0]) + self.routed = match[1] == 'ROUTED' + self.args = [] + self.response_args = [] + if self.sync: + num_expected_args = int(match[2][:-1]) + num_expected_response_args = int(match[3]) + else: + num_expected_args = int(match[3]) + num_expected_response_args = 0 + body = content.split(',') + name = body[0].strip() + try: + self.group, self.name = name.split('Msg_') + except ValueError: + try: + self.group, self.name = name.split('_') + except ValueError: + self.group = 'UnnamedInterface' + self.name = name + self.group = '%s%s' % (self.group, match[1].title()) + args = list(self.parse_args(','.join(body[1:]))) + if len(args) != num_expected_args + num_expected_response_args: + raise Exception('Incorrect number of args parsed for %s' % (name)) + self.args = args[:num_expected_args] + self.response_args = args[num_expected_args:] + + def parse_args(self, args_str): + args_str = args_str.strip() + if not args_str: + return + looking_for_type = False + type_start = 0 + comment_start = None + comment_end = None + type_end = None + angle_bracket_nesting = 0 + i = 0 + while i < len(args_str): + if args_str[i] == ',' and not angle_bracket_nesting: + looking_for_type = True + if type_end is None: + type_end = i + elif args_str[i:i + 2] == '/*': + if type_end is None: + type_end = i + comment_start = i + 2 + comment_end = args_str.index('*/', i + 2) + i = comment_end + 1 + elif args_str[i:i + 2] == '//': + if type_end is None: + type_end = i + comment_start = i + 2 + comment_end = args_str.index('\n', i + 2) + i = comment_end + elif args_str[i] == '<': + angle_bracket_nesting += 1 + elif args_str[i] == '>': + angle_bracket_nesting -= 1 + elif looking_for_type and args_str[i].isalpha(): + if comment_start is not None and comment_end is not None: + yield Argument(args_str[type_start:type_end], + args_str[comment_start:comment_end]) + else: + yield Argument(args_str[type_start:type_end], '') + type_start = i + type_end = None + comment_start = None + comment_end = None + looking_for_type = False + i += 1 + if comment_start is not None and comment_end is not None: + yield Argument(args_str[type_start:type_end], + args_str[comment_start:comment_end]) + else: + yield Argument(args_str[type_start:type_end], '') + + def format(self, typemaps): + result = '%s(%s)' % (self.name, ','.join('\n %s' % arg.format(typemaps) + for arg in self.args)) + if self.sync: + result += ' => (%s)' % (',\n'.join('\n %s' % arg.format(typemaps) + for arg in self.response_args)) + result = '[Sync]\n %s' % result + return '%s;' % result + + +class Generator(object): + + def __init__(self, input_name, output_namespace): + self._input_name = input_name + with open(input_name) as f: + self._content = f.read() + self._namespace = output_namespace + self._typemaps = Typemap(self._find_typemaps()) + self._interface_definitions = [] + + def _get_messages(self): + for m in _MESSAGE_PATTERN.finditer(self._content): + i = m.end() + 1 + while i < len(self._content): + if self._content[i:i + 2] == '/*': + i = self._content.index('*/', i + 2) + 1 + elif self._content[i] == ')': + yield Message(m.groups(), self._content[m.end() + 1:i]) + break + i += 1 + + def _extract_messages(self): + grouped_messages = {} + for m in self._get_messages(): + grouped_messages.setdefault(m.group, []).append(m) + self._typemaps.load_typemaps() + for interface, messages in grouped_messages.iteritems(): + self._interface_definitions.append(self._format_interface(interface, + messages)) + + def count(self): + grouped_messages = {} + for m in self._get_messages(): + grouped_messages.setdefault(m.group, []).append(m) + return sum(len(messages) for messages in grouped_messages.values()) + + def generate_mojom(self): + self._extract_messages() + if not self._interface_definitions: + return + yield """// 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. +""" + yield 'module %s;\n' % self._namespace + for import_statement in self._typemaps.format_imports(): + yield import_statement + for typemap in self._typemaps.format_new_types(): + yield typemap + for interface in self._interface_definitions: + yield interface + yield '' + + def generate_typemap(self, output_mojom, input_filename): + return '\n'.join(self._typemaps.generate_typemap( + output_mojom, input_filename, self._namespace)).strip() + + @staticmethod + def _find_typemaps(): + return subprocess.check_output( + ['git', 'ls-files', '*.typemap']).strip().split('\n') + + def _format_interface(self, name, messages): + return 'interface %s {\n %s\n};' % (name, + '\n '.join(m.format(self._typemaps) + for m in messages)) + + +def parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('input', help='input messages.h file') + parser.add_argument( + '--output_namespace', + default='mojom', + help='the mojom module name to use in the generated mojom file ' + '(default: %(default)s)') + parser.add_argument('--output_mojom', help='output mojom path') + parser.add_argument('--output_typemap', help='output typemap path') + parser.add_argument( + '--count', + action='store_true', + default=False, + help='count the number of messages in the input instead of generating ' + 'a mojom file') + parser.add_argument('-v', + '--verbose', + action='store_true', + help='enable logging') + parser.add_argument('-vv', action='store_true', help='enable debug logging') + return parser.parse_args() + + +def main(): + args = parse_args() + if args.vv: + logging.basicConfig(level=logging.DEBUG) + elif args.verbose: + logging.basicConfig(level=logging.INFO) + generator = Generator(args.input, args.output_namespace) + if args.count: + count = generator.count() + if count: + print '%d %s' % (generator.count(), args.input) + return + mojom = '\n'.join(generator.generate_mojom()).strip() + if not mojom: + return + typemap = generator.generate_typemap(args.output_mojom, args.input) + + if args.output_mojom: + with open(args.output_mojom, 'w') as f: + f.write(mojom) + else: + print mojom + if typemap: + if args.output_typemap: + with open(args.output_typemap, 'w') as f: + f.write(typemap) + else: + print typemap + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/mojo/public/tools/gn/zip.py b/mojo/public/tools/gn/zip.py new file mode 100755 index 0000000000..adc9cb1cba --- /dev/null +++ b/mojo/public/tools/gn/zip.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python +# +# 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. + +# TODO(brettw) bug 582594: merge this with build/android/gn/zip.py and update +# callers to use the existing template rather than invoking this directly. + +"""Archives a set of files. +""" + +import optparse +import os +import sys +import zipfile + +sys.path.append(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir, os.pardir, + "build")) +import gn_helpers + +sys.path.append(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir, os.pardir, + 'build', 'android', 'gyp')) +from util import build_utils + + +def DoZip(inputs, link_inputs, zip_inputs, output, base_dir): + files = [] + with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as outfile: + for f in inputs: + file_name = os.path.relpath(f, base_dir) + files.append(file_name) + build_utils.AddToZipHermetic(outfile, file_name, f) + for f in link_inputs: + realf = os.path.realpath(f) # Resolve symlinks. + file_name = os.path.relpath(realf, base_dir) + files.append(file_name) + build_utils.AddToZipHermetic(outfile, file_name, realf) + for zf_name in zip_inputs: + with zipfile.ZipFile(zf_name, 'r') as zf: + for f in zf.namelist(): + if f not in files: + files.append(f) + build_utils.AddToZipHermetic(outfile, f, data=zf.read(f)) + + +def main(): + parser = optparse.OptionParser() + + parser.add_option('--inputs', + help='GN format list of files to archive.') + parser.add_option('--link-inputs', + help='GN-format list of files to archive. Symbolic links are resolved.') + parser.add_option('--zip-inputs', + help='GN-format list of zip files to re-archive.') + parser.add_option('--output', help='Path to output archive.') + parser.add_option('--base-dir', + help='If provided, the paths in the archive will be ' + 'relative to this directory', default='.') + + options, _ = parser.parse_args() + + inputs = [] + if (options.inputs): + parser = gn_helpers.GNValueParser(options.inputs) + inputs = parser.ParseList() + + link_inputs = [] + if options.link_inputs: + parser = gn_helpers.GNValueParser(options.link_inputs) + link_inputs = parser.ParseList() + + zip_inputs = [] + if options.zip_inputs: + parser = gn_helpers.GNValueParser(options.zip_inputs) + zip_inputs = parser.ParseList() + + output = options.output + base_dir = options.base_dir + + DoZip(inputs, link_inputs, zip_inputs, output, base_dir) + +if __name__ == '__main__': + sys.exit(main()) |