From b1919cfb4a2ec955423e6cf8ccc6afabc8ffb3ed Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Fri, 9 Mar 2018 21:53:52 +0900 Subject: [PATCH 01/22] Remove unnecessary diffs from libchrome. - Update build_config.h. - platform_test.h had a modification to support Mac tests. Because we drop Mac host support, that change is no longer needed. Bug: 73270448 Test: Treehugger. Change-Id: I82affa36a4e055368f08ee7554e38eedc61fa6e1 --- build/build_config.h | 36 ++++++++++++++++++++++++++++++++---- testing/platform_test.h | 11 +++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/build/build_config.h b/build/build_config.h index 80a93d3..219b0e2 100644 --- a/build/build_config.h +++ b/build/build_config.h @@ -6,6 +6,7 @@ // Operating System: // OS_WIN / OS_MACOSX / OS_LINUX / OS_POSIX (MACOSX or LINUX) / // OS_NACL (NACL_SFI or NACL_NONSFI) / OS_NACL_SFI / OS_NACL_NONSFI +// OS_CHROMEOS is set by the build system // Compiler: // COMPILER_MSVC / COMPILER_GCC // Processor: @@ -84,9 +85,10 @@ #endif #elif defined(_WIN32) #define OS_WIN 1 -#define TOOLKIT_VIEWS 1 #elif defined(__FreeBSD__) #define OS_FREEBSD 1 +#elif defined(__NetBSD__) +#define OS_NETBSD 1 #elif defined(__OpenBSD__) #define OS_OPENBSD 1 #elif defined(__sun) @@ -103,15 +105,16 @@ // For access to standard BSD features, use OS_BSD instead of a // more specific macro. -#if defined(OS_FREEBSD) || defined(OS_OPENBSD) +#if defined(OS_FREEBSD) || defined(OS_NETBSD) || defined(OS_OPENBSD) #define OS_BSD 1 #endif // For access to standard POSIXish features, use OS_POSIX instead of a // more specific macro. #if defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_FREEBSD) || \ - defined(OS_OPENBSD) || defined(OS_SOLARIS) || defined(OS_ANDROID) || \ - defined(OS_NACL) || defined(OS_QNX) + defined(OS_NETBSD) || defined(OS_OPENBSD) || defined(OS_SOLARIS) || \ + defined(OS_ANDROID) || defined(OS_OPENBSD) || defined(OS_SOLARIS) || \ + defined(OS_ANDROID) || defined(OS_NACL) || defined(OS_QNX) #define OS_POSIX 1 #endif @@ -144,6 +147,31 @@ #define ARCH_CPU_X86 1 #define ARCH_CPU_32_BITS 1 #define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__s390x__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390X 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__s390__) +#define ARCH_CPU_S390_FAMILY 1 +#define ARCH_CPU_S390 1 +#define ARCH_CPU_31_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__PPC64__) && defined(__BIG_ENDIAN__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 +#elif defined(__PPC64__) && defined(__LITTLE_ENDIAN__) +#define ARCH_CPU_PPC64_FAMILY 1 +#define ARCH_CPU_PPC64 1 +#define ARCH_CPU_64_BITS 1 +#define ARCH_CPU_LITTLE_ENDIAN 1 +#elif defined(__PPC__) +#define ARCH_CPU_PPC_FAMILY 1 +#define ARCH_CPU_PPC 1 +#define ARCH_CPU_32_BITS 1 +#define ARCH_CPU_BIG_ENDIAN 1 #elif defined(__ARMEL__) #define ARCH_CPU_ARM_FAMILY 1 #define ARCH_CPU_ARMEL 1 diff --git a/testing/platform_test.h b/testing/platform_test.h index 04fc845..f993864 100644 --- a/testing/platform_test.h +++ b/testing/platform_test.h @@ -8,11 +8,7 @@ #include #if defined(GTEST_OS_MAC) -#ifdef __OBJC__ -@class NSAutoreleasePool; -#else -class NSAutoreleasePool; -#endif +#include // The purpose of this class us to provide a hook for platform-specific // operations across unit tests. For example, on the Mac, it creates and @@ -27,7 +23,10 @@ class PlatformTest : public testing::Test { PlatformTest(); private: - NSAutoreleasePool* pool_; + // |pool_| is a NSAutoreleasePool, but since this header may be imported from + // files built with Objective-C ARC that forbids explicit usage of + // NSAutoreleasePools, it is declared as id here. + id pool_; }; #else typedef testing::Test PlatformTest; -- GitLab From b912d92fdb4d105106904cbff5424e129e5398e3 Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Mon, 12 Mar 2018 17:15:45 +0900 Subject: [PATCH 02/22] Introduce update_libchrome.py. Now we can uprev the libchrome by the script, except; - patch conflict - new change which needs to be patched, and - client code change. Bug: 72617985 Test: Ran update_libchrome.py, and made sure no diff. Change-Id: I03c0544995a3e68ad90fbf45999ecf01a7c8b0f8 --- libchrome_tools/patch/allocator_shim.patch | 13 ++ libchrome_tools/patch/ashmem.patch | 20 +++ libchrome_tools/patch/build_config.patch | 56 +++++++ libchrome_tools/patch/build_time.patch | 74 ++++++++ libchrome_tools/patch/buildflag_header.patch | 32 ++++ libchrome_tools/patch/compiler_specific.patch | 16 ++ libchrome_tools/patch/dmg_fp.patch | 156 +++++++++++++++++ libchrome_tools/patch/file_posix.patch | 15 ++ libchrome_tools/patch/gmock.patch | 6 + libchrome_tools/patch/gtest.patch | 10 ++ libchrome_tools/patch/hash.patch | 28 ++++ libchrome_tools/patch/lazy_instance.patch | 18 ++ libchrome_tools/patch/libevent.patch | 13 ++ libchrome_tools/patch/logging.patch | 80 +++++++++ libchrome_tools/patch/macros.patch | 69 ++++++++ libchrome_tools/patch/message_loop.patch | 13 ++ libchrome_tools/patch/modp_b64.patch | 19 +++ libchrome_tools/patch/path_service.patch | 92 ++++++++++ libchrome_tools/patch/protobuf.patch | 4 + .../patch/shared_memory_posix.patch | 56 +++++++ libchrome_tools/patch/ssl.patch | 126 ++++++++++++++ libchrome_tools/patch/stack_trace_posix.patch | 45 +++++ libchrome_tools/patch/statfs_f_type.patch | 40 +++++ libchrome_tools/patch/subprocess.patch | 74 ++++++++ libchrome_tools/patch/symbolize.patch | 18 ++ libchrome_tools/patch/task_scheduler.patch | 152 +++++++++++++++++ libchrome_tools/patch/time.patch | 19 +++ libchrome_tools/patch/valgrind.patch | 33 ++++ .../patch/virtual_destructor.patch | 78 +++++++++ libchrome_tools/update_libchrome.py | 158 ++++++++++++++++++ 30 files changed, 1533 insertions(+) create mode 100644 libchrome_tools/patch/allocator_shim.patch create mode 100644 libchrome_tools/patch/ashmem.patch create mode 100644 libchrome_tools/patch/build_config.patch create mode 100644 libchrome_tools/patch/build_time.patch create mode 100644 libchrome_tools/patch/buildflag_header.patch create mode 100644 libchrome_tools/patch/compiler_specific.patch create mode 100644 libchrome_tools/patch/dmg_fp.patch create mode 100644 libchrome_tools/patch/file_posix.patch create mode 100644 libchrome_tools/patch/gmock.patch create mode 100644 libchrome_tools/patch/gtest.patch create mode 100644 libchrome_tools/patch/hash.patch create mode 100644 libchrome_tools/patch/lazy_instance.patch create mode 100644 libchrome_tools/patch/libevent.patch create mode 100644 libchrome_tools/patch/logging.patch create mode 100644 libchrome_tools/patch/macros.patch create mode 100644 libchrome_tools/patch/message_loop.patch create mode 100644 libchrome_tools/patch/modp_b64.patch create mode 100644 libchrome_tools/patch/path_service.patch create mode 100644 libchrome_tools/patch/protobuf.patch create mode 100644 libchrome_tools/patch/shared_memory_posix.patch create mode 100644 libchrome_tools/patch/ssl.patch create mode 100644 libchrome_tools/patch/stack_trace_posix.patch create mode 100644 libchrome_tools/patch/statfs_f_type.patch create mode 100644 libchrome_tools/patch/subprocess.patch create mode 100644 libchrome_tools/patch/symbolize.patch create mode 100644 libchrome_tools/patch/task_scheduler.patch create mode 100644 libchrome_tools/patch/time.patch create mode 100644 libchrome_tools/patch/valgrind.patch create mode 100644 libchrome_tools/patch/virtual_destructor.patch create mode 100644 libchrome_tools/update_libchrome.py diff --git a/libchrome_tools/patch/allocator_shim.patch b/libchrome_tools/patch/allocator_shim.patch new file mode 100644 index 0000000..e87f614 --- /dev/null +++ b/libchrome_tools/patch/allocator_shim.patch @@ -0,0 +1,13 @@ +# Use allocator_shim_override_linker_wrapped_symbols.h for ANDROID. + +--- a/base/allocator/allocator_shim.cc ++++ b/base/allocator/allocator_shim.cc +@@ -299,7 +299,7 @@ ALWAYS_INLINE void ShimFreeDefiniteSize( + #include "base/allocator/allocator_shim_override_cpp_symbols.h" + #endif + +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(ANDROID) + // Android does not support symbol interposition. The way malloc symbols are + // intercepted on Android is by using link-time -wrap flags. + #include "base/allocator/allocator_shim_override_linker_wrapped_symbols.h" diff --git a/libchrome_tools/patch/ashmem.patch b/libchrome_tools/patch/ashmem.patch new file mode 100644 index 0000000..c59a536 --- /dev/null +++ b/libchrome_tools/patch/ashmem.patch @@ -0,0 +1,20 @@ +--- /dev/null ++++ b/third_party/ashmem/ashmem.h +@@ -0,0 +1,17 @@ ++// Copyright (C) 2018 The Android Open Source Project ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++// third_party/ashmem is Android shared memory. Instead of clone it here, ++// use cutils/ashmem.h directly. ++#include diff --git a/libchrome_tools/patch/build_config.patch b/libchrome_tools/patch/build_config.patch new file mode 100644 index 0000000..d22e935 --- /dev/null +++ b/libchrome_tools/patch/build_config.patch @@ -0,0 +1,56 @@ +--- a/build/build_config.h ++++ b/build/build_config.h +@@ -16,6 +16,43 @@ + #ifndef BUILD_BUILD_CONFIG_H_ + #define BUILD_BUILD_CONFIG_H_ + ++// A brief primer on #defines: ++// ++// - __ANDROID__ is automatically defined by the Android toolchain (see ++// https://goo.gl/v61lXa). It's not defined when building host code. ++// - __ANDROID_HOST__ is defined via -D by Android.mk when building host code ++// within an Android checkout. ++// - ANDROID is defined via -D when building code for either Android targets or ++// hosts. Use __ANDROID__ and __ANDROID_HOST__ instead. ++// - OS_ANDROID is a Chrome-specific define used to build Chrome for Android ++// within the NDK. ++ ++// Android targets and hosts don't use tcmalloc. ++#if defined(__ANDROID__) || defined(__ANDROID_HOST__) ++#define NO_TCMALLOC ++#endif // defined(__ANDROID__) || defined(__ANDROID_HOST__) ++ ++// Use the Chrome OS version of the code for both Android targets and Chrome OS builds. ++#if !defined(__ANDROID_HOST__) ++#define OS_CHROMEOS 1 ++#endif // !defined(__ANDROID_HOST__) ++ ++#if defined(__ANDROID__) // Android targets ++ ++#define __linux__ 1 ++#if defined(__BIONIC__) ++#define __UCLIBC__ 1 ++#endif // defined(__BIONIC__) ++ ++#elif !defined(__ANDROID_HOST__) // Chrome OS ++ ++// TODO: Remove these once the GLib MessageLoopForUI isn't being used: ++// https://crbug.com/361635 ++#define USE_GLIB 1 ++#define USE_OZONE 1 ++ ++#endif // defined(__ANDROID__) ++ + // A set of macros to use for platform detection. + #if defined(__native_client__) + // __native_client__ must be first, so that other OS_ defines are not set. +@@ -28,8 +65,7 @@ + #else + #define OS_NACL_SFI + #endif +-#elif defined(ANDROID) +-#define OS_ANDROID 1 ++// Don't set OS_ANDROID; it's only used when building Chrome for Android. + #elif defined(__APPLE__) + // only include TargetConditions after testing ANDROID as some android builds + // on mac don't have this header available and it's not needed unless the target diff --git a/libchrome_tools/patch/build_time.patch b/libchrome_tools/patch/build_time.patch new file mode 100644 index 0000000..cebe8a6 --- /dev/null +++ b/libchrome_tools/patch/build_time.patch @@ -0,0 +1,74 @@ +--- a/base/build_time.cc ++++ b/base/build_time.cc +@@ -4,20 +4,31 @@ + + #include "base/build_time.h" + +-// Imports the generated build date, i.e. BUILD_DATE. +-#include "base/generated_build_date.h" +- + #include "base/logging.h" + #include "base/time/time.h" + ++#ifdef __ANDROID__ ++#include ++#endif ++ + namespace base { + + Time GetBuildTime() { + Time integral_build_time; +- // BUILD_DATE is exactly "Mmm DD YYYY HH:MM:SS". +- // See //build/write_build_date_header.py. "HH:MM:SS" is normally expected to +- // be "05:00:00" but is not enforced here. +- bool result = Time::FromUTCString(BUILD_DATE, &integral_build_time); ++ // The format of __DATE__ and __TIME__ is specified by the ANSI C Standard, ++ // section 6.8.8. ++ // ++ // __DATE__ is exactly "Mmm DD YYYY". ++ // __TIME__ is exactly "hh:mm:ss". ++#if defined(__ANDROID__) ++ char kDateTime[PROPERTY_VALUE_MAX]; ++ property_get("ro.build.date", kDateTime, "Sep 02 2008 08:00:00 PST"); ++#elif defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD) ++ const char kDateTime[] = "Sep 02 2008 08:00:00 PST"; ++#else ++ const char kDateTime[] = __DATE__ " " __TIME__ " PST"; ++#endif ++ bool result = Time::FromString(kDateTime, &integral_build_time); + DCHECK(result); + return integral_build_time; + } +--- a/base/build_time_unittest.cc ++++ b/base/build_time_unittest.cc +@@ -3,13 +3,19 @@ + // found in the LICENSE file. + + #include "base/build_time.h" ++#if !defined(DONT_EMBED_BUILD_METADATA) + #include "base/generated_build_date.h" ++#endif + #include "base/time/time.h" + + #include "testing/gtest/include/gtest/gtest.h" + + TEST(BuildTime, DateLooksValid) { ++#if !defined(DONT_EMBED_BUILD_METADATA) + char build_date[] = BUILD_DATE; ++#else ++ char build_date[] = "Sep 02 2008 05:00:00"; ++#endif + + EXPECT_EQ(20u, strlen(build_date)); + EXPECT_EQ(' ', build_date[3]); +@@ -30,8 +36,10 @@ TEST(BuildTime, InThePast) { + EXPECT_LT(base::GetBuildTime(), base::Time::NowFromSystemTime()); + } + ++#if !defined(DONT_EMBED_BUILD_METADATA) + TEST(BuildTime, NotTooFar) { + // BuildTime must be less than 45 days old. + base::Time cutoff(base::Time::Now() - base::TimeDelta::FromDays(45)); + EXPECT_GT(base::GetBuildTime(), cutoff); + } ++#endif diff --git a/libchrome_tools/patch/buildflag_header.patch b/libchrome_tools/patch/buildflag_header.patch new file mode 100644 index 0000000..69cc417 --- /dev/null +++ b/libchrome_tools/patch/buildflag_header.patch @@ -0,0 +1,32 @@ +# These files are generated by buildflag_header rule in Chromium. +# Instead, in libchrome, these are checked in. + +--- /dev/null ++++ b/base/allocator/features.h +@@ -0,0 +1,15 @@ ++// Generated by build/write_buildflag_header.py ++// From "allocator_features" ++ ++#ifndef BASE_ALLOCATOR_FEATURES_H_ ++#define BASE_ALLOCATOR_FEATURES_H_ ++ ++#include "build/buildflag.h" ++ ++#if defined(__APPLE__) || defined(ANDROID) ++#define BUILDFLAG_INTERNAL_USE_EXPERIMENTAL_ALLOCATOR_SHIM() (0) ++#else ++#define BUILDFLAG_INTERNAL_USE_EXPERIMENTAL_ALLOCATOR_SHIM() (1) ++#endif ++ ++#endif // BASE_ALLOCATOR_FEATURES_H_ +--- /dev/null ++++ b/base/debug/debugging_flags.h +@@ -0,0 +1,8 @@ ++// Generated by build/write_buildflag_header.py ++// From "base_debugging_flags" ++#ifndef BASE_DEBUG_DEBUGGING_FLAGS_H_ ++#define BASE_DEBUG_DEBUGGING_FLAGS_H_ ++#include "build/buildflag.h" ++#define BUILDFLAG_INTERNAL_ENABLE_PROFILING() (0) ++#define BUILDFLAG_INTERNAL_ENABLE_MEMORY_TASK_PROFILER() (0) ++#endif // BASE_DEBUG_DEBUGGING_FLAGS_H_ diff --git a/libchrome_tools/patch/compiler_specific.patch b/libchrome_tools/patch/compiler_specific.patch new file mode 100644 index 0000000..87d7354 --- /dev/null +++ b/libchrome_tools/patch/compiler_specific.patch @@ -0,0 +1,16 @@ +# In Android, prefer Android's libbase definitions for LIKELY/UNLIKELY macros. + +--- a/base/compiler_specific.h ++++ b/base/compiler_specific.h +@@ -7,6 +7,11 @@ + + #include "build/build_config.h" + ++#if defined(ANDROID) ++// Prefer Android's libbase definitions to our own. ++#include ++#endif // defined(ANDROID) ++ + #if defined(COMPILER_MSVC) + + // For _Printf_format_string_. diff --git a/libchrome_tools/patch/dmg_fp.patch b/libchrome_tools/patch/dmg_fp.patch new file mode 100644 index 0000000..5042771 --- /dev/null +++ b/libchrome_tools/patch/dmg_fp.patch @@ -0,0 +1,156 @@ +# Libchrome does not support/require dmg_fp library. Instead, use standard +# library. + +--- a/base/strings/string_number_conversions.cc ++++ b/base/strings/string_number_conversions.cc +@@ -15,7 +15,6 @@ + #include "base/logging.h" + #include "base/numerics/safe_math.h" + #include "base/scoped_clear_errno.h" +-#include "base/third_party/dmg_fp/dmg_fp.h" + + namespace base { + +@@ -369,10 +368,18 @@ string16 SizeTToString16(size_t value) { + } + + std::string DoubleToString(double value) { +- // According to g_fmt.cc, it is sufficient to declare a buffer of size 32. +- char buffer[32]; +- dmg_fp::g_fmt(buffer, value); +- return std::string(buffer); ++ auto ret = std::to_string(value); ++ // If this returned an integer, don't do anything. ++ if (ret.find('.') == std::string::npos) { ++ return ret; ++ } ++ // Otherwise, it has an annoying tendency to leave trailing zeros. ++ size_t len = ret.size(); ++ while (len >= 2 && ret[len - 1] == '0' && ret[len - 2] != '.') { ++ --len; ++ } ++ ret.erase(len); ++ return ret; + } + + bool StringToInt(const StringPiece& input, int* output) { +@@ -416,14 +423,10 @@ bool StringToSizeT(const StringPiece16& + } + + bool StringToDouble(const std::string& input, double* output) { +- // Thread-safe? It is on at least Mac, Linux, and Windows. +- ScopedClearErrno clear_errno; +- +- char* endptr = NULL; +- *output = dmg_fp::strtod(input.c_str(), &endptr); ++ char* endptr = nullptr; ++ *output = strtod(input.c_str(), &endptr); + + // Cases to return false: +- // - If errno is ERANGE, there was an overflow or underflow. + // - If the input string is empty, there was nothing to parse. + // - If endptr does not point to the end of the string, there are either + // characters remaining in the string after a parsed number, or the string +@@ -431,10 +434,11 @@ bool StringToDouble(const std::string& i + // expected end given the string's stated length to correctly catch cases + // where the string contains embedded NUL characters. + // - If the first character is a space, there was leading whitespace +- return errno == 0 && +- !input.empty() && ++ return !input.empty() && + input.c_str() + input.length() == endptr && +- !isspace(input[0]); ++ !isspace(input[0]) && ++ *output != std::numeric_limits::infinity() && ++ *output != -std::numeric_limits::infinity(); + } + + // Note: if you need to add String16ToDouble, first ask yourself if it's +--- a/base/strings/string_number_conversions.h ++++ b/base/strings/string_number_conversions.h +@@ -54,6 +54,7 @@ BASE_EXPORT string16 Uint64ToString16(ui + BASE_EXPORT std::string SizeTToString(size_t value); + BASE_EXPORT string16 SizeTToString16(size_t value); + ++// Deprecated: prefer std::to_string(double) instead. + // DoubleToString converts the double to a string format that ignores the + // locale. If you want to use locale specific formatting, use ICU. + BASE_EXPORT std::string DoubleToString(double value); +@@ -91,6 +92,7 @@ BASE_EXPORT bool StringToUint64(const St + BASE_EXPORT bool StringToSizeT(const StringPiece& input, size_t* output); + BASE_EXPORT bool StringToSizeT(const StringPiece16& input, size_t* output); + ++// Deprecated: prefer std::stod() instead. + // For floating-point conversions, only conversions of input strings in decimal + // form are defined to work. Behavior with strings representing floating-point + // numbers in hexadecimal, and strings representing non-finite values (such as +--- a/base/strings/string_number_conversions_unittest.cc ++++ b/base/strings/string_number_conversions_unittest.cc +@@ -752,20 +752,8 @@ TEST(StringNumberConversionsTest, String + {"9e999", HUGE_VAL, false}, + {"9e1999", HUGE_VAL, false}, + {"9e19999", HUGE_VAL, false}, +- {"9e99999999999999999999", HUGE_VAL, false}, +- {"-9e307", -9e307, true}, +- {"-1.7976e308", -1.7976e308, true}, +- {"-1.7977e308", -HUGE_VAL, false}, +- {"-1.797693134862315807e+308", -HUGE_VAL, true}, +- {"-1.797693134862315808e+308", -HUGE_VAL, false}, +- {"-9e308", -HUGE_VAL, false}, +- {"-9e309", -HUGE_VAL, false}, +- {"-9e999", -HUGE_VAL, false}, +- {"-9e1999", -HUGE_VAL, false}, +- {"-9e19999", -HUGE_VAL, false}, +- {"-9e99999999999999999999", -HUGE_VAL, false}, +- +- // Test more exponents. ++ {"9e99999999999999999999", std::numeric_limits::infinity(), false}, ++ {"-9e99999999999999999999", -std::numeric_limits::infinity(), false}, + {"1e-2", 0.01, true}, + {"42 ", 42.0, false}, + {" 1e-2", 0.01, false}, +@@ -795,7 +783,8 @@ TEST(StringNumberConversionsTest, String + for (size_t i = 0; i < arraysize(cases); ++i) { + double output; + errno = 1; +- EXPECT_EQ(cases[i].success, StringToDouble(cases[i].input, &output)); ++ EXPECT_EQ(cases[i].success, StringToDouble(cases[i].input, &output)) ++ << "for input=" << cases[i].input << "got output=" << output; + if (cases[i].success) + EXPECT_EQ(1, errno) << i; // confirm that errno is unchanged. + EXPECT_DOUBLE_EQ(cases[i].output, output); +@@ -816,13 +805,13 @@ TEST(StringNumberConversionsTest, Double + double input; + const char* expected; + } cases[] = { +- {0.0, "0"}, ++ {0.0, "0.0"}, + {1.25, "1.25"}, +- {1.33518e+012, "1.33518e+12"}, +- {1.33489e+012, "1.33489e+12"}, +- {1.33505e+012, "1.33505e+12"}, +- {1.33545e+009, "1335450000"}, +- {1.33503e+009, "1335030000"}, ++ {1.33518e+012, "1335180000000.0"}, ++ {1.33489e+012, "1334890000000.0"}, ++ {1.33505e+012, "1335050000000.0"}, ++ {1.33545e+009, "1335450000.0"}, ++ {1.33503e+009, "1335030000.0"}, + }; + + for (size_t i = 0; i < arraysize(cases); ++i) { +@@ -833,12 +822,12 @@ TEST(StringNumberConversionsTest, Double + const char input_bytes[8] = {0, 0, 0, 0, '\xee', '\x6d', '\x73', '\x42'}; + double input = 0; + memcpy(&input, input_bytes, arraysize(input_bytes)); +- EXPECT_EQ("1335179083776", DoubleToString(input)); ++ EXPECT_EQ("1335179083776.0", DoubleToString(input)); + const char input_bytes2[8] = + {0, 0, 0, '\xa0', '\xda', '\x6c', '\x73', '\x42'}; + input = 0; + memcpy(&input, input_bytes2, arraysize(input_bytes2)); +- EXPECT_EQ("1334890332160", DoubleToString(input)); ++ EXPECT_EQ("1334890332160.0", DoubleToString(input)); + } + + TEST(StringNumberConversionsTest, HexEncode) { diff --git a/libchrome_tools/patch/file_posix.patch b/libchrome_tools/patch/file_posix.patch new file mode 100644 index 0000000..c7428e3 --- /dev/null +++ b/libchrome_tools/patch/file_posix.patch @@ -0,0 +1,15 @@ +# On Android, lseek64 should be used, whlie lseek should be in other platfrom. + +--- a/base/files/file_posix.cc ++++ b/base/files/file_posix.cc +@@ -185,7 +185,9 @@ int64_t File::Seek(Whence whence, int64_ + + SCOPED_FILE_TRACE_WITH_SIZE("Seek", offset); + +-#if defined(OS_ANDROID) ++// Additionally check __BIONIC__ since older versions of Android don't define ++// _FILE_OFFSET_BITS. ++#if _FILE_OFFSET_BITS != 64 || defined(__BIONIC__) + static_assert(sizeof(int64_t) == sizeof(off64_t), "off64_t must be 64 bits"); + return lseek64(file_.get(), static_cast(offset), + static_cast(whence)); diff --git a/libchrome_tools/patch/gmock.patch b/libchrome_tools/patch/gmock.patch new file mode 100644 index 0000000..a38afdc --- /dev/null +++ b/libchrome_tools/patch/gmock.patch @@ -0,0 +1,6 @@ +# Use system installed gmock library. + +--- /dev/null ++++ b/testing/gmock/include/gmock/gmock.h +@@ -0,0 +1 @@ ++#include diff --git a/libchrome_tools/patch/gtest.patch b/libchrome_tools/patch/gtest.patch new file mode 100644 index 0000000..6da17c9 --- /dev/null +++ b/libchrome_tools/patch/gtest.patch @@ -0,0 +1,10 @@ +# Use system installed gtest library. + +--- /dev/null ++++ b/testing/gtest/include/gtest/gtest.h +@@ -0,0 +1 @@ ++#include +--- /dev/null ++++ b/testing/gtest/include/gtest/gtest_prod.h +@@ -0,0 +1 @@ ++#include diff --git a/libchrome_tools/patch/hash.patch b/libchrome_tools/patch/hash.patch new file mode 100644 index 0000000..71e13d7 --- /dev/null +++ b/libchrome_tools/patch/hash.patch @@ -0,0 +1,28 @@ +# libchrome does not support SuperFastHash. Instead use std::hash. + +--- a/base/hash.cc ++++ b/base/hash.cc +@@ -4,19 +4,13 @@ + + #include "base/hash.h" + +-// Definition in base/third_party/superfasthash/superfasthash.c. (Third-party +-// code did not come with its own header file, so declaring the function here.) +-// Note: This algorithm is also in Blink under Source/wtf/StringHasher.h. +-extern "C" uint32_t SuperFastHash(const char* data, int len); ++#include + + namespace base { + +-uint32_t SuperFastHash(const char* data, size_t length) { +- if (length > static_cast(std::numeric_limits::max())) { +- NOTREACHED(); +- return 0; +- } +- return ::SuperFastHash(data, static_cast(length)); ++uint32_t SuperFastHash(const char* data, size_t len) { ++ std::hash hash_fn; ++ return hash_fn(std::string(data, len)); + } + + } // namespace base diff --git a/libchrome_tools/patch/lazy_instance.patch b/libchrome_tools/patch/lazy_instance.patch new file mode 100644 index 0000000..d144137 --- /dev/null +++ b/libchrome_tools/patch/lazy_instance.patch @@ -0,0 +1,18 @@ +# LAZY_INSTANCE_INITIALIZER will be embedded into the users of libchrome, +# and could cause compile warning. + +--- a/base/lazy_instance.h ++++ b/base/lazy_instance.h +@@ -48,7 +48,11 @@ + // initialization, as base's LINKER_INITIALIZED requires a constructor and on + // some compilers (notably gcc 4.4) this still ends up needing runtime + // initialization. +-#define LAZY_INSTANCE_INITIALIZER {0} ++#ifdef __clang__ ++ #define LAZY_INSTANCE_INITIALIZER {} ++#else ++ #define LAZY_INSTANCE_INITIALIZER {0, 0} ++#endif + + namespace base { + diff --git a/libchrome_tools/patch/libevent.patch b/libchrome_tools/patch/libevent.patch new file mode 100644 index 0000000..723d621 --- /dev/null +++ b/libchrome_tools/patch/libevent.patch @@ -0,0 +1,13 @@ +--- /dev/null ++++ b/base/third_party/libevent/event.h +@@ -0,0 +1,10 @@ ++// The Chromium build contains its own checkout of libevent. This stub is used ++// when building the Chrome OS or Android libchrome package to instead use the ++// system headers. ++#if defined(__ANDROID__) || defined(__ANDROID_HOST__) ++#include ++#include ++#include ++#else ++#include ++#endif diff --git a/libchrome_tools/patch/logging.patch b/libchrome_tools/patch/logging.patch new file mode 100644 index 0000000..8deece9 --- /dev/null +++ b/libchrome_tools/patch/logging.patch @@ -0,0 +1,80 @@ +--- a/base/logging.cc ++++ b/base/logging.cc +@@ -71,7 +71,11 @@ typedef pthread_mutex_t* MutexHandle; + #include "base/posix/safe_strerror.h" + #endif + +-#if defined(OS_ANDROID) ++#if !defined(OS_ANDROID) ++#include "base/files/file_path.h" ++#endif ++ ++#if defined(OS_ANDROID) || defined(__ANDROID__) + #include + #endif + +@@ -358,21 +362,23 @@ bool BaseInitLoggingImpl(const LoggingSe + // Can log only to the system debug log. + CHECK_EQ(settings.logging_dest & ~LOG_TO_SYSTEM_DEBUG_LOG, 0); + #endif +- base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); +- // Don't bother initializing |g_vlog_info| unless we use one of the +- // vlog switches. +- if (command_line->HasSwitch(switches::kV) || +- command_line->HasSwitch(switches::kVModule)) { +- // NOTE: If |g_vlog_info| has already been initialized, it might be in use +- // by another thread. Don't delete the old VLogInfo, just create a second +- // one. We keep track of both to avoid memory leak warnings. +- CHECK(!g_vlog_info_prev); +- g_vlog_info_prev = g_vlog_info; +- +- g_vlog_info = +- new VlogInfo(command_line->GetSwitchValueASCII(switches::kV), +- command_line->GetSwitchValueASCII(switches::kVModule), +- &g_min_log_level); ++ if (base::CommandLine::InitializedForCurrentProcess()) { ++ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); ++ // Don't bother initializing |g_vlog_info| unless we use one of the ++ // vlog switches. ++ if (command_line->HasSwitch(switches::kV) || ++ command_line->HasSwitch(switches::kVModule)) { ++ // NOTE: If |g_vlog_info| has already been initialized, it might be in use ++ // by another thread. Don't delete the old VLogInfo, just create a second ++ // one. We keep track of both to avoid memory leak warnings. ++ CHECK(!g_vlog_info_prev); ++ g_vlog_info_prev = g_vlog_info; ++ ++ g_vlog_info = ++ new VlogInfo(command_line->GetSwitchValueASCII(switches::kV), ++ command_line->GetSwitchValueASCII(switches::kVModule), ++ &g_min_log_level); ++ } + } + + g_logging_destination = settings.logging_dest; +@@ -668,7 +674,7 @@ LogMessage::~LogMessage() { + + asl_send(asl_client.get(), asl_message.get()); + } +-#elif defined(OS_ANDROID) ++#elif defined(OS_ANDROID) || defined(__ANDROID__) + android_LogPriority priority = + (severity_ < 0) ? ANDROID_LOG_VERBOSE : ANDROID_LOG_UNKNOWN; + switch (severity_) { +@@ -685,7 +691,16 @@ LogMessage::~LogMessage() { + priority = ANDROID_LOG_FATAL; + break; + } ++#if defined(OS_ANDROID) + __android_log_write(priority, "chromium", str_newline.c_str()); ++#else ++ __android_log_write( ++ priority, ++ base::CommandLine::InitializedForCurrentProcess() ? ++ base::CommandLine::ForCurrentProcess()-> ++ GetProgram().BaseName().value().c_str() : nullptr, ++ str_newline.c_str()); ++#endif // defined(OS_ANDROID) + #endif + ignore_result(fwrite(str_newline.data(), str_newline.size(), 1, stderr)); + fflush(stderr); diff --git a/libchrome_tools/patch/macros.patch b/libchrome_tools/patch/macros.patch new file mode 100644 index 0000000..f57ec88 --- /dev/null +++ b/libchrome_tools/patch/macros.patch @@ -0,0 +1,69 @@ +--- a/base/macros.h ++++ b/base/macros.h +@@ -12,19 +12,32 @@ + + #include // For size_t. + ++#if defined(ANDROID) ++// Prefer Android's libbase definitions to our own. ++#include ++#endif // defined(ANDROID) ++ ++// We define following macros conditionally as they may be defined by another libraries. ++ + // Put this in the declarations for a class to be uncopyable. ++#if !defined(DISALLOW_COPY) + #define DISALLOW_COPY(TypeName) \ + TypeName(const TypeName&) = delete ++#endif + + // Put this in the declarations for a class to be unassignable. ++#if !defined(DISALLOW_ASSIGN) + #define DISALLOW_ASSIGN(TypeName) \ + void operator=(const TypeName&) = delete ++#endif + + // A macro to disallow the copy constructor and operator= functions. + // This should be used in the private: declarations for a class. ++#if !defined(DISALLOW_COPY_AND_ASSIGN) + #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete ++#endif + + // A macro to disallow all the implicit constructors, namely the + // default constructor, copy constructor and operator= functions. +@@ -32,9 +45,11 @@ + // This should be used in the private: declarations for a class + // that wants to prevent anyone from instantiating it. This is + // especially useful for classes containing only static methods. ++#if !defined(DISALLOW_IMPLICIT_CONSTRUCTORS) + #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) ++#endif + + // The arraysize(arr) macro returns the # of elements in an array arr. The + // expression is a compile-time constant, and therefore can be used in defining +@@ -45,8 +60,10 @@ + // This template function declaration is used in defining arraysize. + // Note that the function doesn't need an implementation, as we only + // use its type. ++#if !defined(arraysize) + template char (&ArraySizeHelper(T (&array)[N]))[N]; + #define arraysize(array) (sizeof(ArraySizeHelper(array))) ++#endif + + // Used to explicitly mark the return value of a function as unused. If you are + // really sure you don't want to do anything with the return value of a function +@@ -79,8 +96,10 @@ enum LinkerInitialized { LINKER_INITIALI + // Use these to declare and define a static local variable (static T;) so that + // it is leaked so that its destructors are not called at exit. If you need + // thread-safe initialization, use base/lazy_instance.h instead. ++#if !defined(CR_DEFINE_STATIC_LOCAL) + #define CR_DEFINE_STATIC_LOCAL(type, name, arguments) \ + static type& name = *new type arguments ++#endif + + } // base + diff --git a/libchrome_tools/patch/message_loop.patch b/libchrome_tools/patch/message_loop.patch new file mode 100644 index 0000000..0ab3630 --- /dev/null +++ b/libchrome_tools/patch/message_loop.patch @@ -0,0 +1,13 @@ +--- a/base/message_loop/message_loop.h ++++ b/base/message_loop/message_loop.h +@@ -565,7 +565,9 @@ class BASE_EXPORT MessageLoopForIO : pub + // Returns the MessageLoopForIO of the current thread. + static MessageLoopForIO* current() { + MessageLoop* loop = MessageLoop::current(); +- DCHECK(loop); ++ DCHECK(loop) << "Can't call MessageLoopForIO::current() when no message " ++ "loop was created for this thread. Use " ++ " MessageLoop::current() or MessageLoopForIO::IsCurrent()."; + DCHECK_EQ(MessageLoop::TYPE_IO, loop->type()); + return static_cast(loop); + } diff --git a/libchrome_tools/patch/modp_b64.patch b/libchrome_tools/patch/modp_b64.patch new file mode 100644 index 0000000..89d838c --- /dev/null +++ b/libchrome_tools/patch/modp_b64.patch @@ -0,0 +1,19 @@ +--- /dev/null ++++ b/third_party/modp_b64/modp_b64.h +@@ -0,0 +1,16 @@ ++// Copyright (C) 2018 The Android Open Source Project ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++// Redirect to system header. ++#include diff --git a/libchrome_tools/patch/path_service.patch b/libchrome_tools/patch/path_service.patch new file mode 100644 index 0000000..f831d98 --- /dev/null +++ b/libchrome_tools/patch/path_service.patch @@ -0,0 +1,92 @@ +# Currently, PathService is not available on libchrome. + +--- a/base/files/file_util_posix.cc ++++ b/base/files/file_util_posix.cc +@@ -29,7 +29,6 @@ + #include "base/logging.h" + #include "base/macros.h" + #include "base/memory/singleton.h" +-#include "base/path_service.h" + #include "base/posix/eintr_wrapper.h" + #include "base/stl_util.h" + #include "base/strings/string_split.h" +@@ -50,6 +49,7 @@ + #if defined(OS_ANDROID) + #include "base/android/content_uri_utils.h" + #include "base/os_compat_android.h" ++#include "base/path_service.h" + #endif + + #if !defined(OS_IOS) +@@ -533,6 +533,8 @@ bool GetTempDir(FilePath* path) { + } else { + #if defined(OS_ANDROID) + return PathService::Get(base::DIR_CACHE, path); ++#elif defined(__ANDROID__) ++ *path = FilePath("/data/local/tmp"); + #else + *path = FilePath("/tmp"); + #endif +--- a/base/json/json_reader_unittest.cc ++++ b/base/json/json_reader_unittest.cc +@@ -8,11 +8,14 @@ + + #include + ++#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) + #include "base/base_paths.h" ++#include "base/path_service.h" ++#endif ++ + #include "base/files/file_util.h" + #include "base/logging.h" + #include "base/macros.h" +-#include "base/path_service.h" + #include "base/strings/string_piece.h" + #include "base/strings/utf_string_conversions.h" + #include "base/values.h" +@@ -567,6 +570,7 @@ TEST(JSONReaderTest, Reading) { + } + } + ++#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) + TEST(JSONReaderTest, ReadFromFile) { + FilePath path; + ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &path)); +@@ -581,6 +585,7 @@ TEST(JSONReaderTest, ReadFromFile) { + ASSERT_TRUE(root) << reader.GetErrorMessage(); + EXPECT_TRUE(root->IsType(Value::Type::DICTIONARY)); + } ++#endif // !__ANDROID__ && !__ANDROID_HOST__ + + // Tests that the root of a JSON object can be deleted safely while its + // children outlive it. +--- a/base/json/json_value_serializer_unittest.cc ++++ b/base/json/json_value_serializer_unittest.cc +@@ -11,7 +11,9 @@ + #include "base/json/json_reader.h" + #include "base/json/json_string_value_serializer.h" + #include "base/json/json_writer.h" ++#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) + #include "base/path_service.h" ++#endif + #include "base/strings/string_piece.h" + #include "base/strings/string_util.h" + #include "base/strings/utf_string_conversions.h" +@@ -395,6 +397,8 @@ TEST(JSONValueSerializerTest, JSONReader + ASSERT_FALSE(JSONReader::Read("/ * * / [1]")); + } + ++#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) ++ + class JSONFileValueSerializerTest : public testing::Test { + protected: + void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } +@@ -481,6 +485,7 @@ TEST_F(JSONFileValueSerializerTest, NoWh + std::unique_ptr root = deserializer.Deserialize(nullptr, nullptr); + ASSERT_TRUE(root); + } ++#endif // !__ANDROID__ && !__ANDROID_HOST__ + + } // namespace + diff --git a/libchrome_tools/patch/protobuf.patch b/libchrome_tools/patch/protobuf.patch new file mode 100644 index 0000000..343da9c --- /dev/null +++ b/libchrome_tools/patch/protobuf.patch @@ -0,0 +1,4 @@ +--- /dev/null ++++ b/third_party/protobuf/src/google/protobuf/message_lite.h +@@ -0,0 +1 @@ ++#include diff --git a/libchrome_tools/patch/shared_memory_posix.patch b/libchrome_tools/patch/shared_memory_posix.patch new file mode 100644 index 0000000..abae9f4 --- /dev/null +++ b/libchrome_tools/patch/shared_memory_posix.patch @@ -0,0 +1,56 @@ +--- a/base/memory/shared_memory_posix.cc ++++ b/base/memory/shared_memory_posix.cc +@@ -27,6 +27,8 @@ + + #if defined(OS_ANDROID) + #include "base/os_compat_android.h" ++#endif ++#if defined(OS_ANDROID) || defined(__ANDROID__) + #include "third_party/ashmem/ashmem.h" + #endif + +@@ -96,7 +98,7 @@ bool SharedMemory::CreateAndMapAnonymous + return CreateAnonymous(size) && Map(size); + } + +-#if !defined(OS_ANDROID) ++#if !defined(OS_ANDROID) && !defined(__ANDROID__) + // static + bool SharedMemory::GetSizeFromSharedMemoryHandle( + const SharedMemoryHandle& handle, +@@ -255,7 +257,7 @@ bool SharedMemory::Open(const std::strin + return PrepareMapFile(std::move(fp), std::move(readonly_fd), &mapped_file_, + &readonly_mapped_file_); + } +-#endif // !defined(OS_ANDROID) ++#endif // !defined(OS_ANDROID) && !defined(__ANDROID__) + + bool SharedMemory::MapAt(off_t offset, size_t bytes) { + if (mapped_file_ == -1) +@@ -267,7 +269,7 @@ bool SharedMemory::MapAt(off_t offset, s + if (memory_) + return false; + +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(__ANDROID__) + // On Android, Map can be called with a size and offset of zero to use the + // ashmem-determined size. + if (bytes == 0) { +@@ -332,7 +334,7 @@ void SharedMemory::Close() { + } + } + +-#if !defined(OS_ANDROID) ++#if !defined(OS_ANDROID) && !defined(__ANDROID__) + // For the given shmem named |mem_name|, return a filename to mmap() + // (and possibly create). Modifies |filename|. Return false on + // error, or true of we are happy. +@@ -355,7 +357,7 @@ bool SharedMemory::FilePathForMemoryName + *path = temp_dir.AppendASCII(name_base + ".shmem." + mem_name); + return true; + } +-#endif // !defined(OS_ANDROID) ++#endif // !defined(OS_ANDROID) && !defined(__ANDROID__) + + bool SharedMemory::ShareToProcessCommon(ProcessHandle process, + SharedMemoryHandle* new_handle, diff --git a/libchrome_tools/patch/ssl.patch b/libchrome_tools/patch/ssl.patch new file mode 100644 index 0000000..f4a2f8f --- /dev/null +++ b/libchrome_tools/patch/ssl.patch @@ -0,0 +1,126 @@ +# Chrome asumes boringssl, while system installed ssl library may not. + +--- a/crypto/openssl_util.cc ++++ b/crypto/openssl_util.cc +@@ -4,6 +4,13 @@ + + #include "crypto/openssl_util.h" + ++#if defined(OPENSSL_IS_BORINGSSL) ++#include ++#else ++#include ++#endif ++#include ++#include + #include + #include + +@@ -11,8 +18,6 @@ + + #include "base/logging.h" + #include "base/strings/string_piece.h" +-#include "third_party/boringssl/src/include/openssl/crypto.h" +-#include "third_party/boringssl/src/include/openssl/err.h" + + namespace crypto { + +@@ -35,8 +40,12 @@ int OpenSSLErrorCallback(const char* str + } // namespace + + void EnsureOpenSSLInit() { ++#if defined(OPENSSL_IS_BORINGSSL) + // CRYPTO_library_init may be safely called concurrently. + CRYPTO_library_init(); ++#else ++ SSL_library_init(); ++#endif + } + + void ClearOpenSSLERRStack(const tracked_objects::Location& location) { +--- a/crypto/rsa_private_key.h ++++ b/crypto/rsa_private_key.h +@@ -7,6 +7,7 @@ + + #include + #include ++#include + + #include + #include +@@ -14,7 +15,6 @@ + #include "base/macros.h" + #include "build/build_config.h" + #include "crypto/crypto_export.h" +-#include "third_party/boringssl/src/include/openssl/base.h" + + namespace crypto { + +--- a/crypto/secure_hash.cc ++++ b/crypto/secure_hash.cc +@@ -4,14 +4,18 @@ + + #include "crypto/secure_hash.h" + ++#if defined(OPENSSL_IS_BORINGSSL) ++#include ++#else ++#include ++#endif ++#include + #include + + #include "base/logging.h" + #include "base/memory/ptr_util.h" + #include "base/pickle.h" + #include "crypto/openssl_util.h" +-#include "third_party/boringssl/src/include/openssl/mem.h" +-#include "third_party/boringssl/src/include/openssl/sha.h" + + namespace crypto { + +--- a/crypto/signature_verifier.h ++++ b/crypto/signature_verifier.h +@@ -54,9 +54,9 @@ class CRYPTO_EXPORT SignatureVerifier { + // subjectPublicKey BIT STRING } + bool VerifyInit(SignatureAlgorithm signature_algorithm, + const uint8_t* signature, +- size_t signature_len, ++ int signature_len, + const uint8_t* public_key_info, +- size_t public_key_info_len); ++ int public_key_info_len); + + // Initiates a RSA-PSS signature verification operation. This should be + // followed by one or more VerifyUpdate calls and a VerifyFinal call. +@@ -76,14 +76,14 @@ class CRYPTO_EXPORT SignatureVerifier { + // subjectPublicKey BIT STRING } + bool VerifyInitRSAPSS(HashAlgorithm hash_alg, + HashAlgorithm mask_hash_alg, +- size_t salt_len, ++ int salt_len, + const uint8_t* signature, +- size_t signature_len, ++ int signature_len, + const uint8_t* public_key_info, +- size_t public_key_info_len); ++ int public_key_info_len); + + // Feeds a piece of the data to the signature verifier. +- void VerifyUpdate(const uint8_t* data_part, size_t data_part_len); ++ void VerifyUpdate(const uint8_t* data_part, int data_part_len); + + // Concludes a signature verification operation. Returns true if the + // signature is valid. Returns false if the signature is invalid or an +@@ -94,9 +94,9 @@ class CRYPTO_EXPORT SignatureVerifier { + bool CommonInit(int pkey_type, + const EVP_MD* digest, + const uint8_t* signature, +- size_t signature_len, ++ int signature_len, + const uint8_t* public_key_info, +- size_t public_key_info_len, ++ int public_key_info_len, + EVP_PKEY_CTX** pkey_ctx); + + void Reset(); diff --git a/libchrome_tools/patch/stack_trace_posix.patch b/libchrome_tools/patch/stack_trace_posix.patch new file mode 100644 index 0000000..3e2c188 --- /dev/null +++ b/libchrome_tools/patch/stack_trace_posix.patch @@ -0,0 +1,45 @@ +# stack_trace_posix.cc depends on glibc, but Android uses bionic. +# Exclude glibc specific part. + +--- a/base/debug/stack_trace_posix.cc ++++ b/base/debug/stack_trace_posix.cc +@@ -59,7 +59,7 @@ namespace { + + volatile sig_atomic_t in_signal_handler = 0; + +-#if !defined(USE_SYMBOLIZE) ++#if !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) + // The prefix used for mangled symbols, per the Itanium C++ ABI: + // http://www.codesourcery.com/cxx-abi/abi.html#mangling + const char kMangledSymbolPrefix[] = "_Z"; +@@ -68,9 +68,9 @@ const char kMangledSymbolPrefix[] = "_Z" + // (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join + const char kSymbolCharacters[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; +-#endif // !defined(USE_SYMBOLIZE) ++#endif // !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) + +-#if !defined(USE_SYMBOLIZE) ++#if !defined(USE_SYMBOLIZE) && !defined(__UCLIBC__) + // Demangles C++ symbols in the given text. Example: + // + // "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" +@@ -79,8 +79,7 @@ const char kSymbolCharacters[] = + void DemangleSymbols(std::string* text) { + // Note: code in this function is NOT async-signal safe (std::string uses + // malloc internally). +- +-#if !defined(__UCLIBC__) ++#if defined(__GLIBCXX__) && !defined(__UCLIBC__) + + std::string::size_type search_from = 0; + while (search_from < text->size()) { +@@ -116,7 +115,7 @@ void DemangleSymbols(std::string* text) + search_from = mangled_start + 2; + } + } +-#endif // !defined(__UCLIBC__) ++#endif // defined(__GLIBCXX__) && !defined(__UCLIBC__) + } + #endif // !defined(USE_SYMBOLIZE) + diff --git a/libchrome_tools/patch/statfs_f_type.patch b/libchrome_tools/patch/statfs_f_type.patch new file mode 100644 index 0000000..989380b --- /dev/null +++ b/libchrome_tools/patch/statfs_f_type.patch @@ -0,0 +1,40 @@ +# In some platforms, |statfs_buf.f_type| is declared as signed, but some of the +# values will overflow it, causing narrowing warnings. Cast to the largest +# possible unsigned integer type to avoid it. + +--- a/base/files/file_util_linux.cc ++++ b/base/files/file_util_linux.cc +@@ -6,6 +6,7 @@ + + #include + #include ++#include + #include + + #include "base/files/file_path.h" +@@ -23,7 +24,10 @@ bool GetFileSystemType(const FilePath& p + + // Not all possible |statfs_buf.f_type| values are in linux/magic.h. + // Missing values are copied from the statfs man page. +- switch (statfs_buf.f_type) { ++ // In some platforms, |statfs_buf.f_type| is declared as signed, but some of ++ // the values will overflow it, causing narrowing warnings. Cast to the ++ // largest possible unsigned integer type to avoid it. ++ switch (static_cast(statfs_buf.f_type)) { + case 0: + *type = FILE_SYSTEM_0; + break; +--- a/base/sys_info_posix.cc ++++ b/base/sys_info_posix.cc +@@ -85,7 +85,10 @@ bool IsStatsZeroIfUnlimited(const base:: + if (HANDLE_EINTR(statfs(path.value().c_str(), &stats)) != 0) + return false; + +- switch (stats.f_type) { ++ // In some platforms, |statfs_buf.f_type| is declared as signed, but some of ++ // the values will overflow it, causing narrowing warnings. Cast to the ++ // largest possible unsigned integer type to avoid it. ++ switch (static_cast(stats.f_type)) { + case TMPFS_MAGIC: + case HUGETLBFS_MAGIC: + case RAMFS_MAGIC: diff --git a/libchrome_tools/patch/subprocess.patch b/libchrome_tools/patch/subprocess.patch new file mode 100644 index 0000000..ad4457d --- /dev/null +++ b/libchrome_tools/patch/subprocess.patch @@ -0,0 +1,74 @@ +--- a/base/process/process_metrics_unittest.cc ++++ b/base/process/process_metrics_unittest.cc +@@ -549,6 +549,9 @@ MULTIPROCESS_TEST_MAIN(ChildMain) { + + } // namespace + ++// Arc++ note: don't compile as SpawnMultiProcessTestChild brings in a lot of ++// extra dependency. ++#if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__) + TEST(ProcessMetricsTest, GetOpenFdCount) { + ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); +@@ -562,9 +565,23 @@ TEST(ProcessMetricsTest, GetOpenFdCount) + + std::unique_ptr metrics( + ProcessMetrics::CreateProcessMetrics(spawn_child.process.Handle())); +- EXPECT_EQ(0, metrics->GetOpenFdCount()); ++ // Try a couple times to observe the child with 0 fds open. ++ // Sometimes we've seen that the child can have 1 remaining ++ // fd shortly after receiving the signal. Potentially this ++ // is actually the signal file still open in the child. ++ int open_fds = -1; ++ for (int tries = 0; tries < 5; ++tries) { ++ open_fds = metrics->GetOpenFdCount(); ++ if (!open_fds) { ++ break; ++ } ++ PlatformThread::Sleep(TimeDelta::FromMilliseconds(1)); ++ } ++ EXPECT_EQ(0, open_fds); + ASSERT_TRUE(spawn_child.process.Terminate(0, true)); + } ++#endif // !defined(__ANDROID__) ++ + #endif // defined(OS_LINUX) + + } // namespace debug +--- a/base/test/multiprocess_test.cc ++++ b/base/test/multiprocess_test.cc +@@ -12,7 +12,7 @@ + + namespace base { + +-#if !defined(OS_ANDROID) ++#if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__) + SpawnChildResult SpawnMultiProcessTestChild( + const std::string& procname, + const CommandLine& base_command_line, +@@ -41,7 +41,7 @@ bool TerminateMultiProcessTestChild(cons + return process.Terminate(exit_code, wait); + } + +-#endif // !defined(OS_ANDROID) ++#endif // !OS_ANDROID && !__ANDROID__ && !__ANDROID_HOST__ + + CommandLine GetMultiProcessTestChildBaseCommandLine() { + CommandLine cmd_line = *CommandLine::ForCurrentProcess(); +@@ -54,6 +54,8 @@ CommandLine GetMultiProcessTestChildBase + MultiProcessTest::MultiProcessTest() { + } + ++// Don't compile on Arc++. ++#if 0 + SpawnChildResult MultiProcessTest::SpawnChild(const std::string& procname) { + LaunchOptions options; + #if defined(OS_WIN) +@@ -67,6 +69,7 @@ SpawnChildResult MultiProcessTest::Spawn + const LaunchOptions& options) { + return SpawnMultiProcessTestChild(procname, MakeCmdLine(procname), options); + } ++#endif + + CommandLine MultiProcessTest::MakeCmdLine(const std::string& procname) { + CommandLine command_line = GetMultiProcessTestChildBaseCommandLine(); diff --git a/libchrome_tools/patch/symbolize.patch b/libchrome_tools/patch/symbolize.patch new file mode 100644 index 0000000..a7eeda5 --- /dev/null +++ b/libchrome_tools/patch/symbolize.patch @@ -0,0 +1,18 @@ +--- /dev/null ++++ b/base/third_party/symbolize/symbolize.h +@@ -0,0 +1,15 @@ ++// Copyright (C) 2018 The Android Open Source Project ++// ++// Licensed under the Apache License, Version 2.0 (the "License"); ++// you may not use this file except in compliance with the License. ++// You may obtain a copy of the License at ++// ++// http://www.apache.org/licenses/LICENSE-2.0 ++// ++// Unless required by applicable law or agreed to in writing, software ++// distributed under the License is distributed on an "AS IS" BASIS, ++// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++// See the License for the specific language governing permissions and ++// limitations under the License. ++ ++#error "symbolize support was removed from libchrome" diff --git a/libchrome_tools/patch/task_scheduler.patch b/libchrome_tools/patch/task_scheduler.patch new file mode 100644 index 0000000..c32520b --- /dev/null +++ b/libchrome_tools/patch/task_scheduler.patch @@ -0,0 +1,152 @@ +# libchrome does not support TaskScheduler. + +--- a/base/threading/sequenced_worker_pool.cc ++++ b/base/threading/sequenced_worker_pool.cc +@@ -27,8 +27,12 @@ + #include "base/strings/stringprintf.h" + #include "base/synchronization/condition_variable.h" + #include "base/synchronization/lock.h" ++// Don't enable the redirect to TaskScheduler on Arc++ to avoid pulling a bunch ++// of dependencies. Some code also #ifdef'ed below. ++#if 0 + #include "base/task_scheduler/post_task.h" + #include "base/task_scheduler/task_scheduler.h" ++#endif + #include "base/threading/platform_thread.h" + #include "base/threading/sequenced_task_runner_handle.h" + #include "base/threading/simple_thread.h" +@@ -755,10 +759,13 @@ bool SequencedWorkerPool::Inner::PostTas + if (optional_token_name) + sequenced.sequence_token_id = LockedGetNamedTokenID(*optional_token_name); + ++ // See on top of the file why we don't compile this on Arc++. ++#if 0 + if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) { + if (!PostTaskToTaskScheduler(std::move(sequenced), delay)) + return false; + } else { ++#endif + SequencedWorkerPool::WorkerShutdown shutdown_behavior = + sequenced.shutdown_behavior; + pending_tasks_.insert(std::move(sequenced)); +@@ -767,7 +774,9 @@ bool SequencedWorkerPool::Inner::PostTas + blocking_shutdown_pending_task_count_++; + + create_thread_id = PrepareToStartAdditionalThreadIfHelpful(); ++#if 0 + } ++#endif + } + + // Use != REDIRECTED_TO_TASK_SCHEDULER instead of == USE_WORKER_POOL to ensure +@@ -802,6 +811,10 @@ bool SequencedWorkerPool::Inner::PostTas + bool SequencedWorkerPool::Inner::PostTaskToTaskScheduler( + SequencedTask sequenced, + const TimeDelta& delay) { ++#if 1 ++ NOTREACHED(); ++ return false; ++#else + DCHECK_EQ(AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER, g_all_pools_state); + + lock_.AssertAcquired(); +@@ -832,12 +845,17 @@ bool SequencedWorkerPool::Inner::PostTas + return GetTaskSchedulerTaskRunner(sequenced.sequence_token_id, traits) + ->PostDelayedTask(sequenced.posted_from, std::move(sequenced.task), + delay); ++#endif + } + + scoped_refptr + SequencedWorkerPool::Inner::GetTaskSchedulerTaskRunner( + int sequence_token_id, + const TaskTraits& traits) { ++#if 1 ++ NOTREACHED(); ++ return scoped_refptr(); ++#else + DCHECK_EQ(AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER, g_all_pools_state); + + lock_.AssertAcquired(); +@@ -871,16 +889,19 @@ SequencedWorkerPool::Inner::GetTaskSched + } + + return task_runner; ++#endif + } + + bool SequencedWorkerPool::Inner::RunsTasksOnCurrentThread() const { + AutoLock lock(lock_); + if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) { ++#if 0 + if (!runs_tasks_on_verifier_) { + runs_tasks_on_verifier_ = CreateTaskRunnerWithTraits( + TaskTraits().MayBlock().WithBaseSyncPrimitives().WithPriority( + task_priority_)); + } ++#endif + return runs_tasks_on_verifier_->RunsTasksOnCurrentThread(); + } else { + return ContainsKey(threads_, PlatformThread::CurrentId()); +@@ -1467,6 +1488,9 @@ void SequencedWorkerPool::EnableForProce + // static + void SequencedWorkerPool::EnableWithRedirectionToTaskSchedulerForProcess( + TaskPriority max_task_priority) { ++#if 1 ++ NOTREACHED(); ++#else + // TODO(fdoray): Uncomment this line. It is initially commented to avoid a + // revert of the CL that adds debug::DumpWithoutCrashing() in case of + // waterfall failures. +@@ -1474,6 +1498,7 @@ void SequencedWorkerPool::EnableWithRedi + DCHECK(TaskScheduler::GetInstance()); + g_all_pools_state = AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER; + g_max_task_priority = max_task_priority; ++#endif + } + + // static +@@ -1623,8 +1648,12 @@ void SequencedWorkerPool::FlushForTestin + DCHECK(!RunsTasksOnCurrentThread()); + base::ThreadRestrictions::ScopedAllowWait allow_wait; + if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) { ++#if 1 ++ NOTREACHED(); ++#else + // TODO(gab): Remove this if http://crbug.com/622400 fails. + TaskScheduler::GetInstance()->FlushForTesting(); ++#endif + } else { + inner_->CleanupForTesting(); + } +--- a/base/trace_event/trace_log.cc ++++ b/base/trace_event/trace_log.cc +@@ -27,7 +27,10 @@ + #include "base/strings/string_tokenizer.h" + #include "base/strings/stringprintf.h" + #include "base/sys_info.h" ++// post_task.h pulls in a lot of code not needed on Arc++. ++#if 0 + #include "base/task_scheduler/post_task.h" ++#endif + #include "base/threading/platform_thread.h" + #include "base/threading/thread_id_name_manager.h" + #include "base/threading/thread_task_runner_handle.h" +@@ -968,6 +971,7 @@ void TraceLog::FinishFlush(int generatio + } + + if (use_worker_thread_) { ++#if 0 + base::PostTaskWithTraits( + FROM_HERE, base::TaskTraits() + .MayBlock() +@@ -978,6 +982,9 @@ void TraceLog::FinishFlush(int generatio + Passed(&previous_logged_events), flush_output_callback, + argument_filter_predicate)); + return; ++#else ++ NOTREACHED(); ++#endif + } + + ConvertTraceEventsToTraceFormat(std::move(previous_logged_events), diff --git a/libchrome_tools/patch/time.patch b/libchrome_tools/patch/time.patch new file mode 100644 index 0000000..496e68f --- /dev/null +++ b/libchrome_tools/patch/time.patch @@ -0,0 +1,19 @@ +# Cherry-pick from r488841. + +--- a/base/time/time.h ++++ b/base/time/time.h +@@ -341,6 +341,14 @@ class TimeBase { + // provided operators. + int64_t ToInternalValue() const { return us_; } + ++ // The amount of time since the origin (or "zero") point. This is a syntactic ++ // convenience to aid in code readability, mainly for debugging/testing use ++ // cases. ++ // ++ // Warning: While the Time subclass has a fixed origin point, the origin for ++ // the other subclasses can vary each time the application is restarted. ++ TimeDelta since_origin() const { return TimeDelta::FromMicroseconds(us_); } ++ + TimeClass& operator=(TimeClass other) { + us_ = other.us_; + return *(static_cast(this)); diff --git a/libchrome_tools/patch/valgrind.patch b/libchrome_tools/patch/valgrind.patch new file mode 100644 index 0000000..7a7722c --- /dev/null +++ b/libchrome_tools/patch/valgrind.patch @@ -0,0 +1,33 @@ +# On Android, use system valgrind, instead of ones checked in to the Chromium +# repository. + +--- a/base/third_party/valgrind/memcheck.h ++++ b/base/third_party/valgrind/memcheck.h +@@ -1,4 +1,6 @@ +- ++#ifdef ANDROID ++ #include "memcheck/memcheck.h" ++#else + /* + ---------------------------------------------------------------- + +@@ -277,3 +279,4 @@ typedef + + #endif + ++#endif +--- a/base/third_party/valgrind/valgrind.h ++++ b/base/third_party/valgrind/valgrind.h +@@ -1,3 +1,6 @@ ++#ifdef ANDROID ++ #include "include/valgrind.h" ++#else + /* -*- c -*- + ---------------------------------------------------------------- + +@@ -4790,3 +4793,5 @@ VALGRIND_PRINTF_BACKTRACE(const char *fo + #undef PLAT_ppc64_aix5 + + #endif /* __VALGRIND_H */ ++ ++#endif diff --git a/libchrome_tools/patch/virtual_destructor.patch b/libchrome_tools/patch/virtual_destructor.patch new file mode 100644 index 0000000..6411133 --- /dev/null +++ b/libchrome_tools/patch/virtual_destructor.patch @@ -0,0 +1,78 @@ +--- a/base/metrics/histogram.cc ++++ b/base/metrics/histogram.cc +@@ -94,6 +94,7 @@ class Histogram::Factory { + uint32_t bucket_count, + int32_t flags) + : Factory(name, HISTOGRAM, minimum, maximum, bucket_count, flags) {} ++ virtual ~Factory() = default; + + // Create histogram based on construction parameters. Caller takes + // ownership of the returned object. +@@ -741,6 +742,7 @@ class LinearHistogram::Factory : public + bucket_count, flags) { + descriptions_ = descriptions; + } ++ ~Factory() override = default; + + protected: + BucketRanges* CreateRanges() override { +@@ -932,6 +934,7 @@ class BooleanHistogram::Factory : public + public: + Factory(const std::string& name, int32_t flags) + : Histogram::Factory(name, BOOLEAN_HISTOGRAM, 1, 2, 3, flags) {} ++ ~Factory() override = default; + + protected: + BucketRanges* CreateRanges() override { +@@ -1020,6 +1023,7 @@ class CustomHistogram::Factory : public + : Histogram::Factory(name, CUSTOM_HISTOGRAM, 0, 0, 0, flags) { + custom_ranges_ = custom_ranges; + } ++ ~Factory() override = default; + + protected: + BucketRanges* CreateRanges() override { +--- a/base/metrics/statistics_recorder.h ++++ b/base/metrics/statistics_recorder.h +@@ -67,6 +67,7 @@ class BASE_EXPORT StatisticsRecorder { + // histograms from providers when necessary. + class HistogramProvider { + public: ++ virtual ~HistogramProvider() {} + // Merges all histogram information into the global versions. + virtual void MergeHistogramDeltas() = 0; + }; +--- a/base/bind_unittest.cc ++++ b/base/bind_unittest.cc +@@ -69,6 +69,7 @@ static const int kChildValue = 2; + + class Parent { + public: ++ virtual ~Parent() {} + void AddRef() const {} + void Release() const {} + virtual void VirtualSet() { value = kParentValue; } +@@ -78,18 +79,23 @@ class Parent { + + class Child : public Parent { + public: ++ ~Child() override {} + void VirtualSet() override { value = kChildValue; } + void NonVirtualSet() { value = kChildValue; } + }; + + class NoRefParent { + public: ++ virtual ~NoRefParent() {} + virtual void VirtualSet() { value = kParentValue; } + void NonVirtualSet() { value = kParentValue; } + int value; + }; + + class NoRefChild : public NoRefParent { ++ public: ++ ~NoRefChild() override {} ++ private: + void VirtualSet() override { value = kChildValue; } + void NonVirtualSet() { value = kChildValue; } + }; diff --git a/libchrome_tools/update_libchrome.py b/libchrome_tools/update_libchrome.py new file mode 100644 index 0000000..89d97c2 --- /dev/null +++ b/libchrome_tools/update_libchrome.py @@ -0,0 +1,158 @@ +#!/usr/bin/python3 + +# Copyright (C) 2018 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Updates libchrome. + +This script uprevs the libchrome library with newer Chromium code. +How to use: + +Prepare your local Chromium repository with the target revision. +$ cd external/libchrome +$ python3 libchrome_tools/update_libchrome.py \ + --chromium_root=${PATH_TO_YOUR_LOCAL_CHROMIUM_REPO} + +This script does following things; +- Clean existing libchrome code, except some manually created files and tools. +- Copy necessary files from original Chromium repository. +- Apply patches to the copied files, if necessary. +""" + + +import argparse +import fnmatch +import glob +import os +import re +import shutil +import subprocess + + +_TOOLS_DIR = os.path.dirname(os.path.realpath(__file__)) +_LIBCHROME_ROOT = os.path.dirname(_TOOLS_DIR) + + +# Files which are in the repository, but should not be imported from Chrome +# repository. +_IMPORT_BLACKLIST = [ + # Libchrome specific files. + 'Android.bp', + 'MODULE_LICENSE_BSD', + 'NOTICE', + 'OWNERS', + 'SConstruct', + 'testrunner.cc', + + # libchrome_tools is out of the update target. + 'libchrome_tools/*', + + # Those files should be generated. Please see also buildflag_header.patch. + 'base/allocator/features.h', + 'base/debug/debugging_flags.h', + + # Blacklist several third party libraries, instead system libraries should + # be used. + 'base/third_party/libevent/*', + 'base/third_party/symbolize/*', + 'testing/gmock/*', + 'testing/gtest/*', + 'third_party/*', +] + +def _find_target_files(): + """Returns target files to be upreved.""" + output = subprocess.check_output( + ['git', 'ls-tree', '-r', '--name-only', '--full-name', 'HEAD'], + cwd=_LIBCHROME_ROOT).decode('utf-8') + exclude_pattern = re.compile('|'.join( + '(?:%s)' % fnmatch.translate(pattern) for pattern in _IMPORT_BLACKLIST)) + return [filepath for filepath in output.splitlines() + if not exclude_pattern.match(filepath)] + + +def _clean_existing_dir(output_root): + """Removes existing libchrome files. + + Args: + output_root: Path to the output directory. + """ + os.makedirs(output_root, mode=0o755, exist_ok=True) + for path in os.listdir(output_root): + target_path = os.path.join(output_root, path) + if not os.path.isdir(target_path) or path in ('.git', 'libchrome_tools'): + continue + shutil.rmtree(target_path) + + +def _import_files(chromium_root, output_root): + """Copies files from Chromium repository into libchrome. + + Args: + chromium_root: Path to the Chromium's repository. + output_root: Path to the output directory. + """ + for filepath in _find_target_files(): + target_path = os.path.join(output_root, filepath) + os.makedirs(os.path.dirname(target_path), mode=0o755, exist_ok=True) + shutil.copy2(os.path.join(chromium_root, filepath), target_path) + + +def _apply_patch_files(patch_root, output_root): + """Applies patches. + + libchrome needs some modification from Chromium repository, e.g. supporting + toolchain which is not used by Chrome, or using system library rather than + the library checked in the Chromium repository. + See each *.patch file in libchrome_tools/patch/ directory for details. + + Args: + patch_root: Path to the directory containing patch files. + output_root: Path to the output directory. + """ + for patch_file in glob.iglob(os.path.join(patch_root, '*.patch')): + with open(patch_file, 'r') as f: + subprocess.check_call(['patch', '-p1'], stdin=f, cwd=output_root) + + +def _parse_args(): + """Parses commandline arguments.""" + parser = argparse.ArgumentParser() + + # TODO(hidehiko): Support to specify the Chromium's revision number. + parser.add_argument( + '--chromium_root', + help='Root directory to the local chromium repository.') + parser.add_argument( + '--output_root', + default=_LIBCHROME_ROOT, + help='Output directory, which is libchrome root directory.') + parser.add_argument( + '--patch_dir', + default=os.path.join(_TOOLS_DIR, 'patch'), + help='Directory containing patch files to be applied.') + + return parser.parse_args() + + +def main(): + args = _parse_args() + _clean_existing_dir(args.output_root) + _import_files(args.chromium_root, args.output_root) + _apply_patch_files(args.patch_dir, args.output_root) + # TODO(hidehiko): Create a git commit with filling templated message. + + +if __name__ == '__main__': + main() -- GitLab From d7e7f68f7b706658ab8c251f5dbcf352c1755330 Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Thu, 15 Mar 2018 00:16:10 +0900 Subject: [PATCH 03/22] Introduce stack_trace_android.cc. This CL moves stack_trace_android.cc from libmojo. The file is identical with what is in chrome repository at r462023. Along with the change, this CL removes UCLIBC macro definition. It looks workaround to deal with stack trace. Bug: 73270448, 73606903 Test: Ran on Treehugger. Change-Id: I6b12b767c64c81e07b7a2a29f5073d9aebc47afa --- Android.bp | 6 +- base/debug/proc_maps_linux.cc | 169 ++++++++++++++++++ base/debug/stack_trace_android.cc | 134 ++++++++++++++ base/debug/stack_trace_posix.cc | 11 +- build/build_config.h | 3 - libchrome_tools/patch/build_config.patch | 7 +- libchrome_tools/patch/stack_trace_posix.patch | 45 ----- 7 files changed, 315 insertions(+), 60 deletions(-) create mode 100644 base/debug/proc_maps_linux.cc create mode 100644 base/debug/stack_trace_android.cc delete mode 100644 libchrome_tools/patch/stack_trace_posix.patch diff --git a/Android.bp b/Android.bp index ac00435..c2a3ec0 100644 --- a/Android.bp +++ b/Android.bp @@ -92,9 +92,9 @@ libchromeCommonSrc = [ "base/debug/debugger.cc", "base/debug/debugger_posix.cc", "base/debug/dump_without_crashing.cc", + "base/debug/proc_maps_linux.cc", "base/debug/profiler.cc", "base/debug/stack_trace.cc", - "base/debug/stack_trace_posix.cc", "base/debug/task_annotator.cc", "base/environment.cc", "base/feature_list.cc", @@ -296,10 +296,12 @@ libchromeLinuxSrc = [ libchromeLinuxGlibcSrc = [ "base/allocator/allocator_shim.cc", - "base/allocator/allocator_shim_default_dispatch_to_glibc.cc" + "base/allocator/allocator_shim_default_dispatch_to_glibc.cc", + "base/debug/stack_trace_posix.cc", ] libchromeAndroidSrc = [ + "base/debug/stack_trace_android.cc", "base/memory/shared_memory_android.cc", "base/sys_info_chromeos.cc", ] diff --git a/base/debug/proc_maps_linux.cc b/base/debug/proc_maps_linux.cc new file mode 100644 index 0000000..0bb44b4 --- /dev/null +++ b/base/debug/proc_maps_linux.cc @@ -0,0 +1,169 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/proc_maps_linux.h" + +#include +#include + +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/strings/string_split.h" +#include "build/build_config.h" + +#if defined(OS_LINUX) || defined(OS_ANDROID) +#include +#endif + +#if defined(OS_ANDROID) && !defined(__LP64__) +// In 32-bit mode, Bionic's inttypes.h defines PRI/SCNxPTR as an +// unsigned long int, which is incompatible with Bionic's stdint.h +// defining uintptr_t as an unsigned int: +// https://code.google.com/p/android/issues/detail?id=57218 +#undef SCNxPTR +#define SCNxPTR "x" +#endif + +namespace base { +namespace debug { + +// Scans |proc_maps| starting from |pos| returning true if the gate VMA was +// found, otherwise returns false. +static bool ContainsGateVMA(std::string* proc_maps, size_t pos) { +#if defined(ARCH_CPU_ARM_FAMILY) + // The gate VMA on ARM kernels is the interrupt vectors page. + return proc_maps->find(" [vectors]\n", pos) != std::string::npos; +#elif defined(ARCH_CPU_X86_64) + // The gate VMA on x86 64-bit kernels is the virtual system call page. + return proc_maps->find(" [vsyscall]\n", pos) != std::string::npos; +#else + // Otherwise assume there is no gate VMA in which case we shouldn't + // get duplicate entires. + return false; +#endif +} + +bool ReadProcMaps(std::string* proc_maps) { + // seq_file only writes out a page-sized amount on each call. Refer to header + // file for details. + const long kReadSize = sysconf(_SC_PAGESIZE); + + base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY))); + if (!fd.is_valid()) { + DPLOG(ERROR) << "Couldn't open /proc/self/maps"; + return false; + } + proc_maps->clear(); + + while (true) { + // To avoid a copy, resize |proc_maps| so read() can write directly into it. + // Compute |buffer| afterwards since resize() may reallocate. + size_t pos = proc_maps->size(); + proc_maps->resize(pos + kReadSize); + void* buffer = &(*proc_maps)[pos]; + + ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, kReadSize)); + if (bytes_read < 0) { + DPLOG(ERROR) << "Couldn't read /proc/self/maps"; + proc_maps->clear(); + return false; + } + + // ... and don't forget to trim off excess bytes. + proc_maps->resize(pos + bytes_read); + + if (bytes_read == 0) + break; + + // The gate VMA is handled as a special case after seq_file has finished + // iterating through all entries in the virtual memory table. + // + // Unfortunately, if additional entries are added at this point in time + // seq_file gets confused and the next call to read() will return duplicate + // entries including the gate VMA again. + // + // Avoid this by searching for the gate VMA and breaking early. + if (ContainsGateVMA(proc_maps, pos)) + break; + } + + return true; +} + +bool ParseProcMaps(const std::string& input, + std::vector* regions_out) { + CHECK(regions_out); + std::vector regions; + + // This isn't async safe nor terribly efficient, but it doesn't need to be at + // this point in time. + std::vector lines = SplitString( + input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + + for (size_t i = 0; i < lines.size(); ++i) { + // Due to splitting on '\n' the last line should be empty. + if (i == lines.size() - 1) { + if (!lines[i].empty()) { + DLOG(WARNING) << "Last line not empty"; + return false; + } + break; + } + + MappedMemoryRegion region; + const char* line = lines[i].c_str(); + char permissions[5] = {'\0'}; // Ensure NUL-terminated string. + uint8_t dev_major = 0; + uint8_t dev_minor = 0; + long inode = 0; + int path_index = 0; + + // Sample format from man 5 proc: + // + // address perms offset dev inode pathname + // 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm + // + // The final %n term captures the offset in the input string, which is used + // to determine the path name. It *does not* increment the return value. + // Refer to man 3 sscanf for details. + if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n", + ®ion.start, ®ion.end, permissions, ®ion.offset, + &dev_major, &dev_minor, &inode, &path_index) < 7) { + DPLOG(WARNING) << "sscanf failed for line: " << line; + return false; + } + + region.permissions = 0; + + if (permissions[0] == 'r') + region.permissions |= MappedMemoryRegion::READ; + else if (permissions[0] != '-') + return false; + + if (permissions[1] == 'w') + region.permissions |= MappedMemoryRegion::WRITE; + else if (permissions[1] != '-') + return false; + + if (permissions[2] == 'x') + region.permissions |= MappedMemoryRegion::EXECUTE; + else if (permissions[2] != '-') + return false; + + if (permissions[3] == 'p') + region.permissions |= MappedMemoryRegion::PRIVATE; + else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory. + return false; + + // Pushing then assigning saves us a string copy. + regions.push_back(region); + regions.back().path.assign(line + path_index); + } + + regions_out->swap(regions); + return true; +} + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace_android.cc b/base/debug/stack_trace_android.cc new file mode 100644 index 0000000..329204c --- /dev/null +++ b/base/debug/stack_trace_android.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/debug/stack_trace.h" + +#include +#include +#include + +#include +#include + +#include "base/debug/proc_maps_linux.h" +#include "base/strings/stringprintf.h" +#include "base/threading/thread_restrictions.h" + +#ifdef __LP64__ +#define FMT_ADDR "0x%016lx" +#else +#define FMT_ADDR "0x%08x" +#endif + +namespace { + +struct StackCrawlState { + StackCrawlState(uintptr_t* frames, size_t max_depth) + : frames(frames), + frame_count(0), + max_depth(max_depth), + have_skipped_self(false) {} + + uintptr_t* frames; + size_t frame_count; + size_t max_depth; + bool have_skipped_self; +}; + +_Unwind_Reason_Code TraceStackFrame(_Unwind_Context* context, void* arg) { + StackCrawlState* state = static_cast(arg); + uintptr_t ip = _Unwind_GetIP(context); + + // The first stack frame is this function itself. Skip it. + if (ip != 0 && !state->have_skipped_self) { + state->have_skipped_self = true; + return _URC_NO_REASON; + } + + state->frames[state->frame_count++] = ip; + if (state->frame_count >= state->max_depth) + return _URC_END_OF_STACK; + return _URC_NO_REASON; +} + +} // namespace + +namespace base { +namespace debug { + +bool EnableInProcessStackDumping() { + // When running in an application, our code typically expects SIGPIPE + // to be ignored. Therefore, when testing that same code, it should run + // with SIGPIPE ignored as well. + // TODO(phajdan.jr): De-duplicate this SIGPIPE code. + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = SIG_IGN; + sigemptyset(&action.sa_mask); + return (sigaction(SIGPIPE, &action, NULL) == 0); +} + +StackTrace::StackTrace(size_t count) { + count = std::min(arraysize(trace_), count); + + StackCrawlState state(reinterpret_cast(trace_), count); + _Unwind_Backtrace(&TraceStackFrame, &state); + count_ = state.frame_count; +} + +void StackTrace::Print() const { + std::string backtrace = ToString(); + __android_log_write(ANDROID_LOG_ERROR, "chromium", backtrace.c_str()); +} + +// NOTE: Native libraries in APKs are stripped before installing. Print out the +// relocatable address and library names so host computers can use tools to +// symbolize and demangle (e.g., addr2line, c++filt). +void StackTrace::OutputToStream(std::ostream* os) const { + std::string proc_maps; + std::vector regions; + // Allow IO to read /proc/self/maps. Reading this file doesn't hit the disk + // since it lives in procfs, and this is currently used to print a stack trace + // on fatal log messages in debug builds only. If the restriction is enabled + // then it will recursively trigger fatal failures when this enters on the + // UI thread. + base::ThreadRestrictions::ScopedAllowIO allow_io; + if (!ReadProcMaps(&proc_maps)) { + __android_log_write( + ANDROID_LOG_ERROR, "chromium", "Failed to read /proc/self/maps"); + } else if (!ParseProcMaps(proc_maps, ®ions)) { + __android_log_write( + ANDROID_LOG_ERROR, "chromium", "Failed to parse /proc/self/maps"); + } + + for (size_t i = 0; i < count_; ++i) { + // Subtract one as return address of function may be in the next + // function when a function is annotated as noreturn. + uintptr_t address = reinterpret_cast(trace_[i]) - 1; + + std::vector::iterator iter = regions.begin(); + while (iter != regions.end()) { + if (address >= iter->start && address < iter->end && + !iter->path.empty()) { + break; + } + ++iter; + } + + *os << base::StringPrintf("#%02zd " FMT_ADDR " ", i, address); + + if (iter != regions.end()) { + uintptr_t rel_pc = address - iter->start + iter->offset; + const char* path = iter->path.c_str(); + *os << base::StringPrintf("%s+" FMT_ADDR, path, rel_pc); + } else { + *os << ""; + } + + *os << "\n"; + } +} + +} // namespace debug +} // namespace base diff --git a/base/debug/stack_trace_posix.cc b/base/debug/stack_trace_posix.cc index ab4c34b..4a55f64 100644 --- a/base/debug/stack_trace_posix.cc +++ b/base/debug/stack_trace_posix.cc @@ -59,7 +59,7 @@ namespace { volatile sig_atomic_t in_signal_handler = 0; -#if !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) +#if !defined(USE_SYMBOLIZE) // The prefix used for mangled symbols, per the Itanium C++ ABI: // http://www.codesourcery.com/cxx-abi/abi.html#mangling const char kMangledSymbolPrefix[] = "_Z"; @@ -68,9 +68,9 @@ const char kMangledSymbolPrefix[] = "_Z"; // (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join const char kSymbolCharacters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; -#endif // !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) +#endif // !defined(USE_SYMBOLIZE) -#if !defined(USE_SYMBOLIZE) && !defined(__UCLIBC__) +#if !defined(USE_SYMBOLIZE) // Demangles C++ symbols in the given text. Example: // // "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" @@ -79,7 +79,8 @@ const char kSymbolCharacters[] = void DemangleSymbols(std::string* text) { // Note: code in this function is NOT async-signal safe (std::string uses // malloc internally). -#if defined(__GLIBCXX__) && !defined(__UCLIBC__) + +#if !defined(__UCLIBC__) std::string::size_type search_from = 0; while (search_from < text->size()) { @@ -115,7 +116,7 @@ void DemangleSymbols(std::string* text) { search_from = mangled_start + 2; } } -#endif // defined(__GLIBCXX__) && !defined(__UCLIBC__) +#endif // !defined(__UCLIBC__) } #endif // !defined(USE_SYMBOLIZE) diff --git a/build/build_config.h b/build/build_config.h index 219b0e2..5ee96b1 100644 --- a/build/build_config.h +++ b/build/build_config.h @@ -40,9 +40,6 @@ #if defined(__ANDROID__) // Android targets #define __linux__ 1 -#if defined(__BIONIC__) -#define __UCLIBC__ 1 -#endif // defined(__BIONIC__) #elif !defined(__ANDROID_HOST__) // Chrome OS diff --git a/libchrome_tools/patch/build_config.patch b/libchrome_tools/patch/build_config.patch index d22e935..daf5fb2 100644 --- a/libchrome_tools/patch/build_config.patch +++ b/libchrome_tools/patch/build_config.patch @@ -1,6 +1,6 @@ --- a/build/build_config.h +++ b/build/build_config.h -@@ -16,6 +16,43 @@ +@@ -16,6 +16,40 @@ #ifndef BUILD_BUILD_CONFIG_H_ #define BUILD_BUILD_CONFIG_H_ @@ -28,9 +28,6 @@ +#if defined(__ANDROID__) // Android targets + +#define __linux__ 1 -+#if defined(__BIONIC__) -+#define __UCLIBC__ 1 -+#endif // defined(__BIONIC__) + +#elif !defined(__ANDROID_HOST__) // Chrome OS + @@ -44,7 +41,7 @@ // A set of macros to use for platform detection. #if defined(__native_client__) // __native_client__ must be first, so that other OS_ defines are not set. -@@ -28,8 +65,7 @@ +@@ -28,8 +62,7 @@ #else #define OS_NACL_SFI #endif diff --git a/libchrome_tools/patch/stack_trace_posix.patch b/libchrome_tools/patch/stack_trace_posix.patch deleted file mode 100644 index 3e2c188..0000000 --- a/libchrome_tools/patch/stack_trace_posix.patch +++ /dev/null @@ -1,45 +0,0 @@ -# stack_trace_posix.cc depends on glibc, but Android uses bionic. -# Exclude glibc specific part. - ---- a/base/debug/stack_trace_posix.cc -+++ b/base/debug/stack_trace_posix.cc -@@ -59,7 +59,7 @@ namespace { - - volatile sig_atomic_t in_signal_handler = 0; - --#if !defined(USE_SYMBOLIZE) -+#if !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) - // The prefix used for mangled symbols, per the Itanium C++ ABI: - // http://www.codesourcery.com/cxx-abi/abi.html#mangling - const char kMangledSymbolPrefix[] = "_Z"; -@@ -68,9 +68,9 @@ const char kMangledSymbolPrefix[] = "_Z" - // (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join - const char kSymbolCharacters[] = - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; --#endif // !defined(USE_SYMBOLIZE) -+#endif // !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__) - --#if !defined(USE_SYMBOLIZE) -+#if !defined(USE_SYMBOLIZE) && !defined(__UCLIBC__) - // Demangles C++ symbols in the given text. Example: - // - // "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]" -@@ -79,8 +79,7 @@ const char kSymbolCharacters[] = - void DemangleSymbols(std::string* text) { - // Note: code in this function is NOT async-signal safe (std::string uses - // malloc internally). -- --#if !defined(__UCLIBC__) -+#if defined(__GLIBCXX__) && !defined(__UCLIBC__) - - std::string::size_type search_from = 0; - while (search_from < text->size()) { -@@ -116,7 +115,7 @@ void DemangleSymbols(std::string* text) - search_from = mangled_start + 2; - } - } --#endif // !defined(__UCLIBC__) -+#endif // defined(__GLIBCXX__) && !defined(__UCLIBC__) - } - #endif // !defined(USE_SYMBOLIZE) - -- GitLab From 34c1558fedad7b65b8595d9e452e3190efa3ed12 Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Thu, 12 Apr 2018 17:02:44 +0900 Subject: [PATCH 04/22] Migrate libmojo repository into libchrome, part 1. This CL moves following files. - base/* except base/android/* - devices/* - ui/* except ui/gfx/**/mojom/* - ui/gfx/range/range_mac(_unittest)?.mm are just deleted. Bug: 73606903, 73270448 Test: Built locally. libchrome_test locally. Run update_libchrome.py and made sure no diff is made. Change-Id: I7f47337aa190f901c9c1d41758f312a2b95adc1b --- Android.bp | 22 ++ base/base_paths.cc | 49 +++ base/base_paths.h | 54 +++ base/base_paths_posix.cc | 123 ++++++ base/base_paths_posix.h | 27 ++ base/files/file_util_posix.cc | 2 +- base/i18n/base_i18n_export.h | 29 ++ base/i18n/rtl.h | 155 ++++++++ base/json/json_reader_unittest.cc | 9 +- base/json/json_value_serializer_unittest.cc | 11 +- base/path_service.cc | 329 ++++++++++++++++ base/path_service.h | 97 +++++ base/unguessable_token.cc | 41 ++ base/unguessable_token.h | 103 ++++++ device/bluetooth/bluetooth_advertisement.cc | 37 ++ device/bluetooth/bluetooth_advertisement.h | 152 ++++++++ device/bluetooth/bluetooth_common.h | 49 +++ device/bluetooth/bluetooth_export.h | 28 ++ device/bluetooth/bluetooth_uuid.cc | 98 +++++ device/bluetooth/bluetooth_uuid.h | 106 ++++++ ...bluetooth_service_attribute_value_bluez.cc | 54 +++ .../bluetooth_service_attribute_value_bluez.h | 59 +++ libchrome_tools/patch/path_service.patch | 124 +++---- ui/gfx/geometry/insets.cc | 22 ++ ui/gfx/geometry/insets.h | 130 +++++++ ui/gfx/geometry/insets_f.cc | 16 + ui/gfx/geometry/insets_f.h | 100 +++++ ui/gfx/geometry/point.cc | 105 ++++++ ui/gfx/geometry/point.h | 148 ++++++++ ui/gfx/geometry/point_conversions.cc | 30 ++ ui/gfx/geometry/point_conversions.h | 24 ++ ui/gfx/geometry/point_f.cc | 32 ++ ui/gfx/geometry/point_f.h | 126 +++++++ ui/gfx/geometry/rect.cc | 346 +++++++++++++++++ ui/gfx/geometry/rect.h | 350 ++++++++++++++++++ ui/gfx/geometry/rect_f.cc | 259 +++++++++++++ ui/gfx/geometry/rect_f.h | 242 ++++++++++++ ui/gfx/geometry/safe_integer_conversions.h | 62 ++++ ui/gfx/geometry/size.cc | 115 ++++++ ui/gfx/geometry/size.h | 103 ++++++ ui/gfx/geometry/size_conversions.cc | 30 ++ ui/gfx/geometry/size_conversions.h | 24 ++ ui/gfx/geometry/size_f.cc | 39 ++ ui/gfx/geometry/size_f.h | 97 +++++ ui/gfx/geometry/vector2d.cc | 40 ++ ui/gfx/geometry/vector2d.h | 100 +++++ ui/gfx/geometry/vector2d_f.cc | 60 +++ ui/gfx/geometry/vector2d_f.h | 118 ++++++ ui/gfx/gfx_export.h | 29 ++ ui/gfx/range/BUILD.gn | 33 ++ ui/gfx/range/gfx_range_export.h | 29 ++ ui/gfx/range/range.cc | 34 ++ ui/gfx/range/range.h | 139 +++++++ ui/gfx/range/range_f.cc | 58 +++ ui/gfx/range/range_f.h | 101 +++++ ui/gfx/range/range_unittest.cc | 266 +++++++++++++ 56 files changed, 5082 insertions(+), 83 deletions(-) create mode 100644 base/base_paths.cc create mode 100644 base/base_paths.h create mode 100644 base/base_paths_posix.cc create mode 100644 base/base_paths_posix.h create mode 100644 base/i18n/base_i18n_export.h create mode 100644 base/i18n/rtl.h create mode 100644 base/path_service.cc create mode 100644 base/path_service.h create mode 100644 base/unguessable_token.cc create mode 100644 base/unguessable_token.h create mode 100644 device/bluetooth/bluetooth_advertisement.cc create mode 100644 device/bluetooth/bluetooth_advertisement.h create mode 100644 device/bluetooth/bluetooth_common.h create mode 100644 device/bluetooth/bluetooth_export.h create mode 100644 device/bluetooth/bluetooth_uuid.cc create mode 100644 device/bluetooth/bluetooth_uuid.h create mode 100644 device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc create mode 100644 device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h create mode 100644 ui/gfx/geometry/insets.cc create mode 100644 ui/gfx/geometry/insets.h create mode 100644 ui/gfx/geometry/insets_f.cc create mode 100644 ui/gfx/geometry/insets_f.h create mode 100644 ui/gfx/geometry/point.cc create mode 100644 ui/gfx/geometry/point.h create mode 100644 ui/gfx/geometry/point_conversions.cc create mode 100644 ui/gfx/geometry/point_conversions.h create mode 100644 ui/gfx/geometry/point_f.cc create mode 100644 ui/gfx/geometry/point_f.h create mode 100644 ui/gfx/geometry/rect.cc create mode 100644 ui/gfx/geometry/rect.h create mode 100644 ui/gfx/geometry/rect_f.cc create mode 100644 ui/gfx/geometry/rect_f.h create mode 100644 ui/gfx/geometry/safe_integer_conversions.h create mode 100644 ui/gfx/geometry/size.cc create mode 100644 ui/gfx/geometry/size.h create mode 100644 ui/gfx/geometry/size_conversions.cc create mode 100644 ui/gfx/geometry/size_conversions.h create mode 100644 ui/gfx/geometry/size_f.cc create mode 100644 ui/gfx/geometry/size_f.h create mode 100644 ui/gfx/geometry/vector2d.cc create mode 100644 ui/gfx/geometry/vector2d.h create mode 100644 ui/gfx/geometry/vector2d_f.cc create mode 100644 ui/gfx/geometry/vector2d_f.h create mode 100644 ui/gfx/gfx_export.h create mode 100644 ui/gfx/range/BUILD.gn create mode 100644 ui/gfx/range/gfx_range_export.h create mode 100644 ui/gfx/range/range.cc create mode 100644 ui/gfx/range/range.h create mode 100644 ui/gfx/range/range_f.cc create mode 100644 ui/gfx/range/range_f.h create mode 100644 ui/gfx/range/range_unittest.cc diff --git a/Android.bp b/Android.bp index c2a3ec0..d4842e1 100644 --- a/Android.bp +++ b/Android.bp @@ -80,6 +80,8 @@ libchromeCommonSrc = [ "base/at_exit.cc", "base/base64.cc", "base/base64url.cc", + "base/base_paths.cc", + "base/base_paths_posix.cc", "base/base_switches.cc", "base/bind_helpers.cc", "base/build_time.cc", @@ -154,6 +156,7 @@ libchromeCommonSrc = [ "base/metrics/sample_vector.cc", "base/metrics/sparse_histogram.cc", "base/metrics/statistics_recorder.cc", + "base/path_service.cc", "base/pending_task.cc", "base/pickle.cc", "base/posix/global_descriptors.cc", @@ -270,9 +273,27 @@ libchromeCommonSrc = [ "base/trace_event/trace_log_constants.cc", "base/tracked_objects.cc", "base/tracking_info.cc", + "base/unguessable_token.cc", "base/values.cc", "base/version.cc", "base/vlog.cc", + "device/bluetooth/bluetooth_advertisement.cc", + "device/bluetooth/bluetooth_uuid.cc", + "device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc", + "ui/gfx/geometry/insets.cc", + "ui/gfx/geometry/insets_f.cc", + "ui/gfx/geometry/point.cc", + "ui/gfx/geometry/point_conversions.cc", + "ui/gfx/geometry/point_f.cc", + "ui/gfx/geometry/rect.cc", + "ui/gfx/geometry/rect_f.cc", + "ui/gfx/geometry/size.cc", + "ui/gfx/geometry/size_conversions.cc", + "ui/gfx/geometry/size_f.cc", + "ui/gfx/geometry/vector2d.cc", + "ui/gfx/geometry/vector2d_f.cc", + "ui/gfx/range/range.cc", + "ui/gfx/range/range_f.cc", ] libchromeLinuxSrc = [ @@ -553,6 +574,7 @@ cc_test { "base/vlog_unittest.cc", "testing/multiprocess_func_list.cc", "testrunner.cc", + "ui/gfx/range/range_unittest.cc", ], cflags: ["-DUNIT_TEST"], diff --git a/base/base_paths.cc b/base/base_paths.cc new file mode 100644 index 0000000..31bc554 --- /dev/null +++ b/base/base_paths.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2006-2008 The Chromium Authors. 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/base_paths.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/path_service.h" + +namespace base { + +bool PathProvider(int key, FilePath* result) { + // NOTE: DIR_CURRENT is a special case in PathService::Get + + switch (key) { + case DIR_EXE: + PathService::Get(FILE_EXE, result); + *result = result->DirName(); + return true; + case DIR_MODULE: + PathService::Get(FILE_MODULE, result); + *result = result->DirName(); + return true; + case DIR_TEMP: + if (!GetTempDir(result)) + return false; + return true; + case base::DIR_HOME: + *result = GetHomeDir(); + return true; + case DIR_TEST_DATA: { + FilePath test_data_path; + if (!PathService::Get(DIR_SOURCE_ROOT, &test_data_path)) + return false; + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("base")); + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("test")); + test_data_path = test_data_path.Append(FILE_PATH_LITERAL("data")); + if (!PathExists(test_data_path)) // We don't want to create this. + return false; + *result = test_data_path; + return true; + } + default: + return false; + } +} + +} // namespace base diff --git a/base/base_paths.h b/base/base_paths.h new file mode 100644 index 0000000..ef6aa82 --- /dev/null +++ b/base/base_paths.h @@ -0,0 +1,54 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_BASE_PATHS_H_ +#define BASE_BASE_PATHS_H_ + +// This file declares path keys for the base module. These can be used with +// the PathService to access various special directories and files. + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include "base/base_paths_win.h" +#elif defined(OS_MACOSX) +#include "base/base_paths_mac.h" +#elif defined(OS_ANDROID) +#include "base/base_paths_android.h" +#endif + +#if defined(OS_POSIX) +#include "base/base_paths_posix.h" +#endif + +namespace base { + +enum BasePathKey { + PATH_START = 0, + + DIR_CURRENT, // Current directory. + DIR_EXE, // Directory containing FILE_EXE. + DIR_MODULE, // Directory containing FILE_MODULE. + DIR_TEMP, // Temporary directory. + DIR_HOME, // User's root home directory. On Windows this will look + // like "C:\Users\" which isn't necessarily a great + // place to put files. + FILE_EXE, // Path and filename of the current executable. + FILE_MODULE, // Path and filename of the module containing the code for + // the PathService (which could differ from FILE_EXE if the + // PathService were compiled into a shared object, for + // example). + DIR_SOURCE_ROOT, // Returns the root of the source tree. This key is useful + // for tests that need to locate various resources. It + // should not be used outside of test code. + DIR_USER_DESKTOP, // The current user's Desktop. + + DIR_TEST_DATA, // Used only for testing. + + PATH_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_H_ diff --git a/base/base_paths_posix.cc b/base/base_paths_posix.cc new file mode 100644 index 0000000..37d646c --- /dev/null +++ b/base/base_paths_posix.cc @@ -0,0 +1,123 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines base::PathProviderPosix, default path provider on POSIX OSes that +// don't have their own base_paths_OS.cc implementation (i.e. all but Mac and +// Android). + +#include "base/base_paths.h" + +#include +#include + +#include +#include +#include + +#include "base/environment.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +// Unused, and this file is not ported to libchrome. +// #include "base/nix/xdg_util.h" +#include "base/path_service.h" +#include "base/process/process_metrics.h" +#include "build/build_config.h" + +#if defined(OS_FREEBSD) +#include +#include +#elif defined(OS_SOLARIS) +#include +#endif + +namespace base { + +bool PathProviderPosix(int key, FilePath* result) { + FilePath path; + switch (key) { + case FILE_EXE: + case FILE_MODULE: { // TODO(evanm): is this correct? +#if defined(OS_LINUX) + FilePath bin_dir; + if (!ReadSymbolicLink(FilePath(kProcSelfExe), &bin_dir)) { + NOTREACHED() << "Unable to resolve " << kProcSelfExe << "."; + return false; + } + *result = bin_dir; + return true; +#elif defined(OS_FREEBSD) + int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; + char bin_dir[PATH_MAX + 1]; + size_t length = sizeof(bin_dir); + // Upon return, |length| is the number of bytes written to |bin_dir| + // including the string terminator. + int error = sysctl(name, 4, bin_dir, &length, NULL, 0); + if (error < 0 || length <= 1) { + NOTREACHED() << "Unable to resolve path."; + return false; + } + *result = FilePath(FilePath::StringType(bin_dir, length - 1)); + return true; +#elif defined(OS_SOLARIS) + char bin_dir[PATH_MAX + 1]; + if (realpath(getexecname(), bin_dir) == NULL) { + NOTREACHED() << "Unable to resolve " << getexecname() << "."; + return false; + } + *result = FilePath(bin_dir); + return true; +#elif defined(OS_OPENBSD) + // There is currently no way to get the executable path on OpenBSD + char* cpath; + if ((cpath = getenv("CHROME_EXE_PATH")) != NULL) + *result = FilePath(cpath); + else + *result = FilePath("/usr/local/chrome/chrome"); + return true; +#endif + } +// Following paths are not supported in libchrome/libmojo. +#if 0 + case DIR_SOURCE_ROOT: { + // Allow passing this in the environment, for more flexibility in build + // tree configurations (sub-project builds, gyp --output_dir, etc.) + std::unique_ptr env(Environment::Create()); + std::string cr_source_root; + if (env->GetVar("CR_SOURCE_ROOT", &cr_source_root)) { + path = FilePath(cr_source_root); + if (PathExists(path)) { + *result = path; + return true; + } + DLOG(WARNING) << "CR_SOURCE_ROOT is set, but it appears to not " + << "point to a directory."; + } + // On POSIX, unit tests execute two levels deep from the source root. + // For example: out/{Debug|Release}/net_unittest + if (PathService::Get(DIR_EXE, &path)) { + *result = path.DirName().DirName(); + return true; + } + + DLOG(ERROR) << "Couldn't find your source root. " + << "Try running from your chromium/src directory."; + return false; + } + case DIR_USER_DESKTOP: + *result = nix::GetXDGUserDirectory("DESKTOP", "Desktop"); + return true; + case DIR_CACHE: { + std::unique_ptr env(Environment::Create()); + FilePath cache_dir( + nix::GetXDGDirectory(env.get(), "XDG_CACHE_HOME", ".cache")); + *result = cache_dir; + return true; + } +#endif + } + return false; +} + +} // namespace base diff --git a/base/base_paths_posix.h b/base/base_paths_posix.h new file mode 100644 index 0000000..ef002ae --- /dev/null +++ b/base/base_paths_posix.h @@ -0,0 +1,27 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_BASE_PATHS_POSIX_H_ +#define BASE_BASE_PATHS_POSIX_H_ + +// This file declares windows-specific path keys for the base module. +// These can be used with the PathService to access various special +// directories and files. + +namespace base { + +enum { + PATH_POSIX_START = 400, + + DIR_CACHE, // Directory where to put cache data. Note this is + // *not* where the browser cache lives, but the + // browser cache can be a subdirectory. + // This is $XDG_CACHE_HOME on Linux and + // ~/Library/Caches on Mac. + PATH_POSIX_END +}; + +} // namespace base + +#endif // BASE_BASE_PATHS_POSIX_H_ diff --git a/base/files/file_util_posix.cc b/base/files/file_util_posix.cc index 3501e24..91f1203 100644 --- a/base/files/file_util_posix.cc +++ b/base/files/file_util_posix.cc @@ -29,6 +29,7 @@ #include "base/logging.h" #include "base/macros.h" #include "base/memory/singleton.h" +#include "base/path_service.h" #include "base/posix/eintr_wrapper.h" #include "base/stl_util.h" #include "base/strings/string_split.h" @@ -49,7 +50,6 @@ #if defined(OS_ANDROID) #include "base/android/content_uri_utils.h" #include "base/os_compat_android.h" -#include "base/path_service.h" #endif #if !defined(OS_IOS) diff --git a/base/i18n/base_i18n_export.h b/base/i18n/base_i18n_export.h new file mode 100644 index 0000000..e8a2add --- /dev/null +++ b/base/i18n/base_i18n_export.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_I18N_BASE_I18N_EXPORT_H_ +#define BASE_I18N_BASE_I18N_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(BASE_I18N_IMPLEMENTATION) +#define BASE_I18N_EXPORT __declspec(dllexport) +#else +#define BASE_I18N_EXPORT __declspec(dllimport) +#endif // defined(BASE_I18N_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BASE_I18N_IMPLEMENTATION) +#define BASE_I18N_EXPORT __attribute__((visibility("default"))) +#else +#define BASE_I18N_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define BASE_I18N_EXPORT +#endif + +#endif // BASE_I18N_BASE_I18N_EXPORT_H_ diff --git a/base/i18n/rtl.h b/base/i18n/rtl.h new file mode 100644 index 0000000..df15cd0 --- /dev/null +++ b/base/i18n/rtl.h @@ -0,0 +1,155 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_I18N_RTL_H_ +#define BASE_I18N_RTL_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/i18n/base_i18n_export.h" +#include "base/strings/string16.h" +#include "build/build_config.h" + +namespace base { + +class FilePath; + +namespace i18n { + +const char16 kRightToLeftMark = 0x200F; +const char16 kLeftToRightMark = 0x200E; +const char16 kLeftToRightEmbeddingMark = 0x202A; +const char16 kRightToLeftEmbeddingMark = 0x202B; +const char16 kPopDirectionalFormatting = 0x202C; +const char16 kLeftToRightOverride = 0x202D; +const char16 kRightToLeftOverride = 0x202E; + +// Locale.java mirrored this enum TextDirection. Please keep in sync. +enum TextDirection { + UNKNOWN_DIRECTION = 0, + RIGHT_TO_LEFT = 1, + LEFT_TO_RIGHT = 2, + TEXT_DIRECTION_MAX = LEFT_TO_RIGHT, +}; + +// Get the locale that the currently running process has been configured to use. +// The return value is of the form language[-country] (e.g., en-US) where the +// language is the 2 or 3 letter code from ISO-639. +BASE_I18N_EXPORT std::string GetConfiguredLocale(); + +// Canonicalize a string (eg. a POSIX locale string) to a Chrome locale name. +BASE_I18N_EXPORT std::string GetCanonicalLocale(const std::string& locale); + +// Sets the default locale of ICU. +// Once the application locale of Chrome in GetApplicationLocale is determined, +// the default locale of ICU need to be changed to match the application locale +// so that ICU functions work correctly in a locale-dependent manner. +// This is handy in that we don't have to call GetApplicationLocale() +// everytime we call locale-dependent ICU APIs as long as we make sure +// that this is called before any locale-dependent API is called. +BASE_I18N_EXPORT void SetICUDefaultLocale(const std::string& locale_string); + +// Returns true if the application text direction is right-to-left. +BASE_I18N_EXPORT bool IsRTL(); + +// Returns whether the text direction for the default ICU locale is RTL. This +// assumes that SetICUDefaultLocale has been called to set the default locale to +// the UI locale of Chrome. +// NOTE: Generally, you should call IsRTL() instead of this. +BASE_I18N_EXPORT bool ICUIsRTL(); + +// Returns the text direction for |locale_name|. +// As a startup optimization, this method checks the locale against a list of +// Chrome-supported RTL locales. +BASE_I18N_EXPORT TextDirection +GetTextDirectionForLocaleInStartUp(const char* locale_name); + +// Returns the text direction for |locale_name|. +BASE_I18N_EXPORT TextDirection GetTextDirectionForLocale( + const char* locale_name); + +// Given the string in |text|, returns the directionality of the first or last +// character with strong directionality in the string. If no character in the +// text has strong directionality, LEFT_TO_RIGHT is returned. The Bidi +// character types L, LRE, LRO, R, AL, RLE, and RLO are considered as strong +// directionality characters. Please refer to http://unicode.org/reports/tr9/ +// for more information. +BASE_I18N_EXPORT TextDirection GetFirstStrongCharacterDirection( + const string16& text); +BASE_I18N_EXPORT TextDirection GetLastStrongCharacterDirection( + const string16& text); + +// Given the string in |text|, returns LEFT_TO_RIGHT or RIGHT_TO_LEFT if all the +// strong directionality characters in the string are of the same +// directionality. It returns UNKNOWN_DIRECTION if the string contains a mix of +// LTR and RTL strong directionality characters. Defaults to LEFT_TO_RIGHT if +// the string does not contain directionality characters. Please refer to +// http://unicode.org/reports/tr9/ for more information. +BASE_I18N_EXPORT TextDirection GetStringDirection(const string16& text); + +// Given the string in |text|, this function modifies the string in place with +// the appropriate Unicode formatting marks that mark the string direction +// (either left-to-right or right-to-left). The function checks both the current +// locale and the contents of the string in order to determine the direction of +// the returned string. The function returns true if the string in |text| was +// properly adjusted. +// +// Certain LTR strings are not rendered correctly when the context is RTL. For +// example, the string "Foo!" will appear as "!Foo" if it is rendered as is in +// an RTL context. Calling this function will make sure the returned localized +// string is always treated as a right-to-left string. This is done by +// inserting certain Unicode formatting marks into the returned string. +// +// ** Notes about the Windows version of this function: +// TODO(idana) bug 6806: this function adjusts the string in question only +// if the current locale is right-to-left. The function does not take care of +// the opposite case (an RTL string displayed in an LTR context) since +// adjusting the string involves inserting Unicode formatting characters that +// Windows does not handle well unless right-to-left language support is +// installed. Since the English version of Windows doesn't have right-to-left +// language support installed by default, inserting the direction Unicode mark +// results in Windows displaying squares. +BASE_I18N_EXPORT bool AdjustStringForLocaleDirection(string16* text); + +// Undoes the actions of the above function (AdjustStringForLocaleDirection). +BASE_I18N_EXPORT bool UnadjustStringForLocaleDirection(string16* text); + +// Returns true if the string contains at least one character with strong right +// to left directionality; that is, a character with either R or AL Unicode +// BiDi character type. +BASE_I18N_EXPORT bool StringContainsStrongRTLChars(const string16& text); + +// Wraps a string with an LRE-PDF pair which essentialy marks the string as a +// Left-To-Right string. Doing this is useful in order to make sure LTR +// strings are rendered properly in an RTL context. +BASE_I18N_EXPORT void WrapStringWithLTRFormatting(string16* text); + +// Wraps a string with an RLE-PDF pair which essentialy marks the string as a +// Right-To-Left string. Doing this is useful in order to make sure RTL +// strings are rendered properly in an LTR context. +BASE_I18N_EXPORT void WrapStringWithRTLFormatting(string16* text); + +// Wraps file path to get it to display correctly in RTL UI. All filepaths +// should be passed through this function before display in UI for RTL locales. +BASE_I18N_EXPORT void WrapPathWithLTRFormatting(const FilePath& path, + string16* rtl_safe_path); + +// Return the string in |text| wrapped with LRE (Left-To-Right Embedding) and +// PDF (Pop Directional Formatting) marks, if needed for UI display purposes. +BASE_I18N_EXPORT string16 GetDisplayStringInLTRDirectionality( + const string16& text) WARN_UNUSED_RESULT; + +// Strip the beginning (U+202A..U+202B, U+202D..U+202E) and/or ending (U+202C) +// explicit bidi control characters from |text|, if there are any. Otherwise, +// return the text itself. Explicit bidi control characters display and have +// semantic effect. They can be deleted so they might not always appear in a +// pair. +BASE_I18N_EXPORT string16 StripWrappingBidiControlCharacters( + const string16& text) WARN_UNUSED_RESULT; + +} // namespace i18n +} // namespace base + +#endif // BASE_I18N_RTL_H_ diff --git a/base/json/json_reader_unittest.cc b/base/json/json_reader_unittest.cc index 1344de6..f645b42 100644 --- a/base/json/json_reader_unittest.cc +++ b/base/json/json_reader_unittest.cc @@ -8,14 +8,11 @@ #include -#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) #include "base/base_paths.h" -#include "base/path_service.h" -#endif - #include "base/files/file_util.h" #include "base/logging.h" #include "base/macros.h" +#include "base/path_service.h" #include "base/strings/string_piece.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" @@ -570,8 +567,7 @@ TEST(JSONReaderTest, Reading) { } } -#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) -TEST(JSONReaderTest, ReadFromFile) { +TEST(JSONReaderTest, DISABLED_ReadFromFile) { FilePath path; ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &path)); path = path.AppendASCII("json"); @@ -585,7 +581,6 @@ TEST(JSONReaderTest, ReadFromFile) { ASSERT_TRUE(root) << reader.GetErrorMessage(); EXPECT_TRUE(root->IsType(Value::Type::DICTIONARY)); } -#endif // !__ANDROID__ && !__ANDROID_HOST__ // Tests that the root of a JSON object can be deleted safely while its // children outlive it. diff --git a/base/json/json_value_serializer_unittest.cc b/base/json/json_value_serializer_unittest.cc index 1d58c61..e5cb126 100644 --- a/base/json/json_value_serializer_unittest.cc +++ b/base/json/json_value_serializer_unittest.cc @@ -11,9 +11,7 @@ #include "base/json/json_reader.h" #include "base/json/json_string_value_serializer.h" #include "base/json/json_writer.h" -#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) #include "base/path_service.h" -#endif #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" @@ -397,8 +395,6 @@ TEST(JSONValueSerializerTest, JSONReaderComments) { ASSERT_FALSE(JSONReader::Read("/ * * / [1]")); } -#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) - class JSONFileValueSerializerTest : public testing::Test { protected: void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } @@ -406,7 +402,7 @@ class JSONFileValueSerializerTest : public testing::Test { ScopedTempDir temp_dir_; }; -TEST_F(JSONFileValueSerializerTest, Roundtrip) { +TEST_F(JSONFileValueSerializerTest, DISABLED_Roundtrip) { FilePath original_file_path; ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); original_file_path = original_file_path.AppendASCII("serializer_test.json"); @@ -449,7 +445,7 @@ TEST_F(JSONFileValueSerializerTest, Roundtrip) { EXPECT_TRUE(DeleteFile(written_file_path, false)); } -TEST_F(JSONFileValueSerializerTest, RoundtripNested) { +TEST_F(JSONFileValueSerializerTest, DISABLED_RoundtripNested) { FilePath original_file_path; ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); original_file_path = @@ -475,7 +471,7 @@ TEST_F(JSONFileValueSerializerTest, RoundtripNested) { EXPECT_TRUE(DeleteFile(written_file_path, false)); } -TEST_F(JSONFileValueSerializerTest, NoWhitespace) { +TEST_F(JSONFileValueSerializerTest, DISABLED_NoWhitespace) { FilePath source_file_path; ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &source_file_path)); source_file_path = @@ -485,7 +481,6 @@ TEST_F(JSONFileValueSerializerTest, NoWhitespace) { std::unique_ptr root = deserializer.Deserialize(nullptr, nullptr); ASSERT_TRUE(root); } -#endif // !__ANDROID__ && !__ANDROID_HOST__ } // namespace diff --git a/base/path_service.cc b/base/path_service.cc new file mode 100644 index 0000000..1b9d394 --- /dev/null +++ b/base/path_service.cc @@ -0,0 +1,329 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/path_service.h" + +#if defined(OS_WIN) +#include +#include +#include +#endif + +#include "base/containers/hash_tables.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/synchronization/lock.h" +#include "build/build_config.h" + +namespace base { + +bool PathProvider(int key, FilePath* result); + +#if defined(OS_WIN) +bool PathProviderWin(int key, FilePath* result); +#elif defined(OS_MACOSX) +bool PathProviderMac(int key, FilePath* result); +#elif defined(OS_ANDROID) +bool PathProviderAndroid(int key, FilePath* result); +#elif defined(OS_POSIX) +// PathProviderPosix is the default path provider on POSIX OSes other than +// Mac and Android. +bool PathProviderPosix(int key, FilePath* result); +#endif + +namespace { + +typedef hash_map PathMap; + +// We keep a linked list of providers. In a debug build we ensure that no two +// providers claim overlapping keys. +struct Provider { + PathService::ProviderFunc func; + struct Provider* next; +#ifndef NDEBUG + int key_start; + int key_end; +#endif + bool is_static; +}; + +Provider base_provider = { + PathProvider, + NULL, +#ifndef NDEBUG + PATH_START, + PATH_END, +#endif + true +}; + +#if defined(OS_WIN) +Provider base_provider_win = { + PathProviderWin, + &base_provider, +#ifndef NDEBUG + PATH_WIN_START, + PATH_WIN_END, +#endif + true +}; +#endif + +#if defined(OS_MACOSX) +Provider base_provider_mac = { + PathProviderMac, + &base_provider, +#ifndef NDEBUG + PATH_MAC_START, + PATH_MAC_END, +#endif + true +}; +#endif + +#if defined(OS_ANDROID) +Provider base_provider_android = { + PathProviderAndroid, + &base_provider, +#ifndef NDEBUG + PATH_ANDROID_START, + PATH_ANDROID_END, +#endif + true +}; +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_ANDROID) +Provider base_provider_posix = { + PathProviderPosix, + &base_provider, +#ifndef NDEBUG + PATH_POSIX_START, + PATH_POSIX_END, +#endif + true +}; +#endif + + +struct PathData { + Lock lock; + PathMap cache; // Cache mappings from path key to path value. + PathMap overrides; // Track path overrides. + Provider* providers; // Linked list of path service providers. + bool cache_disabled; // Don't use cache if true; + + PathData() : cache_disabled(false) { +#if defined(OS_WIN) + providers = &base_provider_win; +#elif defined(OS_MACOSX) + providers = &base_provider_mac; +#elif defined(OS_ANDROID) + providers = &base_provider_android; +#elif defined(OS_POSIX) + providers = &base_provider_posix; +#endif + } +}; + +static PathData* GetPathData() { + static auto* path_data = new PathData(); + return path_data; +} + +// Tries to find |key| in the cache. |path_data| should be locked by the caller! +bool LockedGetFromCache(int key, const PathData* path_data, FilePath* result) { + if (path_data->cache_disabled) + return false; + // check for a cached version + PathMap::const_iterator it = path_data->cache.find(key); + if (it != path_data->cache.end()) { + *result = it->second; + return true; + } + return false; +} + +// Tries to find |key| in the overrides map. |path_data| should be locked by the +// caller! +bool LockedGetFromOverrides(int key, PathData* path_data, FilePath* result) { + // check for an overridden version. + PathMap::const_iterator it = path_data->overrides.find(key); + if (it != path_data->overrides.end()) { + if (!path_data->cache_disabled) + path_data->cache[key] = it->second; + *result = it->second; + return true; + } + return false; +} + +} // namespace + +// TODO(brettw): this function does not handle long paths (filename > MAX_PATH) +// characters). This isn't supported very well by Windows right now, so it is +// moot, but we should keep this in mind for the future. +// static +bool PathService::Get(int key, FilePath* result) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK(result); + DCHECK_GE(key, DIR_CURRENT); + + // special case the current directory because it can never be cached + if (key == DIR_CURRENT) + return GetCurrentDirectory(result); + + Provider* provider = NULL; + { + AutoLock scoped_lock(path_data->lock); + if (LockedGetFromCache(key, path_data, result)) + return true; + + if (LockedGetFromOverrides(key, path_data, result)) + return true; + + // Get the beginning of the list while it is still locked. + provider = path_data->providers; + } + + FilePath path; + + // Iterating does not need the lock because only the list head might be + // modified on another thread. + while (provider) { + if (provider->func(key, &path)) + break; + DCHECK(path.empty()) << "provider should not have modified path"; + provider = provider->next; + } + + if (path.empty()) + return false; + + if (path.ReferencesParent()) { + // Make sure path service never returns a path with ".." in it. + path = MakeAbsoluteFilePath(path); + if (path.empty()) + return false; + } + *result = path; + + AutoLock scoped_lock(path_data->lock); + if (!path_data->cache_disabled) + path_data->cache[key] = path; + + return true; +} + +// static +bool PathService::Override(int key, const FilePath& path) { + // Just call the full function with true for the value of |create|, and + // assume that |path| may not be absolute yet. + return OverrideAndCreateIfNeeded(key, path, false, true); +} + +// static +bool PathService::OverrideAndCreateIfNeeded(int key, + const FilePath& path, + bool is_absolute, + bool create) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK_GT(key, DIR_CURRENT) << "invalid path key"; + + FilePath file_path = path; + + // For some locations this will fail if called from inside the sandbox there- + // fore we protect this call with a flag. + if (create) { + // Make sure the directory exists. We need to do this before we translate + // this to the absolute path because on POSIX, MakeAbsoluteFilePath fails + // if called on a non-existent path. + if (!PathExists(file_path) && !CreateDirectory(file_path)) + return false; + } + + // We need to have an absolute path. + if (!is_absolute) { + file_path = MakeAbsoluteFilePath(file_path); + if (file_path.empty()) + return false; + } + DCHECK(file_path.IsAbsolute()); + + AutoLock scoped_lock(path_data->lock); + + // Clear the cache now. Some of its entries could have depended + // on the value we are overriding, and are now out of sync with reality. + path_data->cache.clear(); + + path_data->overrides[key] = file_path; + + return true; +} + +// static +bool PathService::RemoveOverride(int key) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + + AutoLock scoped_lock(path_data->lock); + + if (path_data->overrides.find(key) == path_data->overrides.end()) + return false; + + // Clear the cache now. Some of its entries could have depended on the value + // we are going to remove, and are now out of sync. + path_data->cache.clear(); + + path_data->overrides.erase(key); + + return true; +} + +// static +void PathService::RegisterProvider(ProviderFunc func, int key_start, + int key_end) { + PathData* path_data = GetPathData(); + DCHECK(path_data); + DCHECK_GT(key_end, key_start); + + Provider* p; + + p = new Provider; + p->is_static = false; + p->func = func; +#ifndef NDEBUG + p->key_start = key_start; + p->key_end = key_end; +#endif + + AutoLock scoped_lock(path_data->lock); + +#ifndef NDEBUG + Provider *iter = path_data->providers; + while (iter) { + DCHECK(key_start >= iter->key_end || key_end <= iter->key_start) << + "path provider collision"; + iter = iter->next; + } +#endif + + p->next = path_data->providers; + path_data->providers = p; +} + +// static +void PathService::DisableCache() { + PathData* path_data = GetPathData(); + DCHECK(path_data); + + AutoLock scoped_lock(path_data->lock); + path_data->cache.clear(); + path_data->cache_disabled = true; +} + +} // namespace base diff --git a/base/path_service.h b/base/path_service.h new file mode 100644 index 0000000..c7f1abe --- /dev/null +++ b/base/path_service.h @@ -0,0 +1,97 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PATH_SERVICE_H_ +#define BASE_PATH_SERVICE_H_ + +#include + +#include "base/base_export.h" +#include "base/base_paths.h" +#include "base/gtest_prod_util.h" +#include "build/build_config.h" + +namespace base { + +class FilePath; +class ScopedPathOverride; + +// The path service is a global table mapping keys to file system paths. It is +// OK to use this service from multiple threads. +// +class BASE_EXPORT PathService { + public: + // Retrieves a path to a special directory or file and places it into the + // string pointed to by 'path'. If you ask for a directory it is guaranteed + // to NOT have a path separator at the end. For example, "c:\windows\temp" + // Directories are also guaranteed to exist when this function succeeds. + // + // Returns true if the directory or file was successfully retrieved. On + // failure, 'path' will not be changed. + static bool Get(int key, FilePath* path); + + // Overrides the path to a special directory or file. This cannot be used to + // change the value of DIR_CURRENT, but that should be obvious. Also, if the + // path specifies a directory that does not exist, the directory will be + // created by this method. This method returns true if successful. + // + // If the given path is relative, then it will be resolved against + // DIR_CURRENT. + // + // WARNING: Consumers of PathService::Get may expect paths to be constant + // over the lifetime of the app, so this method should be used with caution. + // + // Unit tests generally should use ScopedPathOverride instead. Overrides from + // one test should not carry over to another. + static bool Override(int key, const FilePath& path); + + // This function does the same as PathService::Override but it takes extra + // parameters: + // - |is_absolute| indicates that |path| has already been expanded into an + // absolute path, otherwise MakeAbsoluteFilePath() will be used. This is + // useful to override paths that may not exist yet, since MakeAbsoluteFilePath + // fails for those. Note that MakeAbsoluteFilePath also expands symbolic + // links, even if path.IsAbsolute() is already true. + // - |create| guides whether the directory to be overriden must + // be created in case it doesn't exist already. + static bool OverrideAndCreateIfNeeded(int key, + const FilePath& path, + bool is_absolute, + bool create); + + // To extend the set of supported keys, you can register a path provider, + // which is just a function mirroring PathService::Get. The ProviderFunc + // returns false if it cannot provide a non-empty path for the given key. + // Otherwise, true is returned. + // + // WARNING: This function could be called on any thread from which the + // PathService is used, so a the ProviderFunc MUST BE THREADSAFE. + // + typedef bool (*ProviderFunc)(int, FilePath*); + + // Call to register a path provider. You must specify the range "[key_start, + // key_end)" of supported path keys. + static void RegisterProvider(ProviderFunc provider, + int key_start, + int key_end); + + // Disable internal cache. + static void DisableCache(); + + private: + friend class ScopedPathOverride; + FRIEND_TEST_ALL_PREFIXES(PathServiceTest, RemoveOverride); + + // Removes an override for a special directory or file. Returns true if there + // was an override to remove or false if none was present. + // NOTE: This function is intended to be used by tests only! + static bool RemoveOverride(int key); +}; + +} // namespace base + +// TODO(brettw) Convert all callers to using the base namespace and remove this. +using base::PathService; + +#endif // BASE_PATH_SERVICE_H_ diff --git a/base/unguessable_token.cc b/base/unguessable_token.cc new file mode 100644 index 0000000..cd9830e --- /dev/null +++ b/base/unguessable_token.cc @@ -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. + +#include "base/unguessable_token.h" + +#include "base/format_macros.h" +#include "base/rand_util.h" +#include "base/strings/stringprintf.h" + +namespace base { + +UnguessableToken::UnguessableToken(uint64_t high, uint64_t low) + : high_(high), low_(low) {} + +std::string UnguessableToken::ToString() const { + return base::StringPrintf("(%08" PRIX64 "%08" PRIX64 ")", high_, low_); +} + +// static +UnguessableToken UnguessableToken::Create() { + UnguessableToken token; + // Use base::RandBytes instead of crypto::RandBytes, because crypto calls the + // base version directly, and to prevent the dependency from base/ to crypto/. + base::RandBytes(&token, sizeof(token)); + return token; +} + +// static +UnguessableToken UnguessableToken::Deserialize(uint64_t high, uint64_t low) { + // Receiving a zeroed out UnguessableToken from another process means that it + // was never initialized via Create(). Treat this case as a security issue. + DCHECK(!(high == 0 && low == 0)); + return UnguessableToken(high, low); +} + +std::ostream& operator<<(std::ostream& out, const UnguessableToken& token) { + return out << token.ToString(); +} + +} // namespace base diff --git a/base/unguessable_token.h b/base/unguessable_token.h new file mode 100644 index 0000000..9f38783 --- /dev/null +++ b/base/unguessable_token.h @@ -0,0 +1,103 @@ +// 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 BASE_UNGUESSABLE_TOKEN_H_ +#define BASE_UNGUESSABLE_TOKEN_H_ + +#include +#include +#include +#include + +#include "base/base_export.h" +#include "base/hash.h" +#include "base/logging.h" + +namespace base { + +struct UnguessableTokenHash; + +// A UnguessableToken is an 128-bit token generated from a cryptographically +// strong random source. +// +// UnguessableToken should be used when a sensitive ID needs to be unguessable, +// and is shared across processes. It can be used as part of a larger aggregate +// type, or as an ID in and of itself. +// +// Use Create() for creating new UnguessableTokens. +// +// NOTE: It is illegal to send empty UnguessableTokens across processes, and +// sending/receiving empty tokens should be treated as a security issue. +// If there is a valid scenario for sending "no token" across processes, +// base::Optional should be used instead of an empty token. +class BASE_EXPORT UnguessableToken { + public: + // Create a unique UnguessableToken. + static UnguessableToken Create(); + + // Return a UnguessableToken built from the high/low bytes provided. + // It should only be used in deserialization scenarios. + // + // NOTE: If the deserialized token is empty, it means that it was never + // initialized via Create(). This is a security issue, and should be handled. + static UnguessableToken Deserialize(uint64_t high, uint64_t low); + + // Creates an empty UnguessableToken. + // Assign to it with Create() before using it. + constexpr UnguessableToken() = default; + + // NOTE: Serializing an empty UnguessableToken is an illegal operation. + uint64_t GetHighForSerialization() const { + DCHECK(!is_empty()); + return high_; + }; + + // NOTE: Serializing an empty UnguessableToken is an illegal operation. + uint64_t GetLowForSerialization() const { + DCHECK(!is_empty()); + return low_; + } + + bool is_empty() const { return high_ == 0 && low_ == 0; } + + std::string ToString() const; + + explicit operator bool() const { return !is_empty(); } + + bool operator<(const UnguessableToken& other) const { + return std::tie(high_, low_) < std::tie(other.high_, other.low_); + } + + bool operator==(const UnguessableToken& other) const { + return high_ == other.high_ && low_ == other.low_; + } + + bool operator!=(const UnguessableToken& other) const { + return !(*this == other); + } + + private: + friend struct UnguessableTokenHash; + UnguessableToken(uint64_t high, uint64_t low); + + // Note: Two uint64_t are used instead of uint8_t[16], in order to have a + // simpler ToString() and is_empty(). + uint64_t high_ = 0; + uint64_t low_ = 0; +}; + +BASE_EXPORT std::ostream& operator<<(std::ostream& out, + const UnguessableToken& token); + +// For use in std::unordered_map. +struct UnguessableTokenHash { + size_t operator()(const base::UnguessableToken& token) const { + DCHECK(token); + return base::HashInts64(token.high_, token.low_); + } +}; + +} // namespace base + +#endif // BASE_UNGUESSABLE_TOKEN_H_ diff --git a/device/bluetooth/bluetooth_advertisement.cc b/device/bluetooth/bluetooth_advertisement.cc new file mode 100644 index 0000000..05b0e52 --- /dev/null +++ b/device/bluetooth/bluetooth_advertisement.cc @@ -0,0 +1,37 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/bluetooth/bluetooth_advertisement.h" + +namespace device { + +BluetoothAdvertisement::Data::Data(AdvertisementType type) + : type_(type), include_tx_power_(false) { +} + +BluetoothAdvertisement::Data::~Data() { +} + +BluetoothAdvertisement::Data::Data() + : type_(ADVERTISEMENT_TYPE_BROADCAST), include_tx_power_(false) { +} + +void BluetoothAdvertisement::AddObserver( + BluetoothAdvertisement::Observer* observer) { + CHECK(observer); + observers_.AddObserver(observer); +} + +void BluetoothAdvertisement::RemoveObserver( + BluetoothAdvertisement::Observer* observer) { + CHECK(observer); + observers_.RemoveObserver(observer); +} + +BluetoothAdvertisement::BluetoothAdvertisement() { +} +BluetoothAdvertisement::~BluetoothAdvertisement() { +} + +} // namespace device diff --git a/device/bluetooth/bluetooth_advertisement.h b/device/bluetooth/bluetooth_advertisement.h new file mode 100644 index 0000000..412baa7 --- /dev/null +++ b/device/bluetooth/bluetooth_advertisement.h @@ -0,0 +1,152 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_ + +#include + +#include +#include +#include +#include +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/observer_list.h" +#include "device/bluetooth/bluetooth_export.h" + +namespace device { + +// BluetoothAdvertisement represents an advertisement which advertises over the +// LE channel during its lifetime. +class DEVICE_BLUETOOTH_EXPORT BluetoothAdvertisement + : public base::RefCounted { + public: + // Possible types of error raised while registering or unregistering + // advertisements. + enum ErrorCode { + ERROR_UNSUPPORTED_PLATFORM, // Bluetooth advertisement not supported on + // current platform. + ERROR_ADVERTISEMENT_ALREADY_EXISTS, // An advertisement is already + // registered. + ERROR_ADVERTISEMENT_DOES_NOT_EXIST, // Unregistering an advertisement which + // is not registered. + ERROR_ADVERTISEMENT_INVALID_LENGTH, // Advertisement is not of a valid + // length. +#if defined(OS_CHROMEOS) || defined(OS_LINUX) + ERROR_INVALID_ADVERTISEMENT_INTERVAL, // Advertisement interval specified + // is out of valid range. +#endif + INVALID_ADVERTISEMENT_ERROR_CODE + }; + + // Type of advertisement. + enum AdvertisementType { + // This advertises with the type set to ADV_NONCONN_IND, which indicates + // to receivers that our device is not connectable. + ADVERTISEMENT_TYPE_BROADCAST, + // This advertises with the type set to ADV_IND or ADV_SCAN_IND, which + // indicates to receivers that our device is connectable. + ADVERTISEMENT_TYPE_PERIPHERAL + }; + + using UUIDList = std::vector; + using ManufacturerData = std::map>; + using ServiceData = std::map>; + + // Structure that holds the data for an advertisement. + class DEVICE_BLUETOOTH_EXPORT Data { + public: + explicit Data(AdvertisementType type); + ~Data(); + + AdvertisementType type() { return type_; } + std::unique_ptr service_uuids() { + return std::move(service_uuids_); + } + std::unique_ptr manufacturer_data() { + return std::move(manufacturer_data_); + } + std::unique_ptr solicit_uuids() { + return std::move(solicit_uuids_); + } + std::unique_ptr service_data() { + return std::move(service_data_); + } + + void set_service_uuids(std::unique_ptr service_uuids) { + service_uuids_ = std::move(service_uuids); + } + void set_manufacturer_data( + std::unique_ptr manufacturer_data) { + manufacturer_data_ = std::move(manufacturer_data); + } + void set_solicit_uuids(std::unique_ptr solicit_uuids) { + solicit_uuids_ = std::move(solicit_uuids); + } + void set_service_data(std::unique_ptr service_data) { + service_data_ = std::move(service_data); + } + + void set_include_tx_power(bool include_tx_power) { + include_tx_power_ = include_tx_power; + } + + private: + Data(); + + AdvertisementType type_; + std::unique_ptr service_uuids_; + std::unique_ptr manufacturer_data_; + std::unique_ptr solicit_uuids_; + std::unique_ptr service_data_; + bool include_tx_power_; + + DISALLOW_COPY_AND_ASSIGN(Data); + }; + + // Interface for observing changes to this advertisement. + class Observer { + public: + virtual ~Observer() {} + + // Called when this advertisement is released and is no longer advertising. + virtual void AdvertisementReleased( + BluetoothAdvertisement* advertisement) = 0; + }; + + // Adds and removes observers for events for this advertisement. + void AddObserver(BluetoothAdvertisement::Observer* observer); + void RemoveObserver(BluetoothAdvertisement::Observer* observer); + + // Unregisters this advertisement. Called on destruction of this object + // automatically but can be called directly to explicitly unregister this + // object. + using SuccessCallback = base::Closure; + using ErrorCallback = base::Callback; + virtual void Unregister(const SuccessCallback& success_callback, + const ErrorCallback& error_callback) = 0; + + protected: + friend class base::RefCounted; + + BluetoothAdvertisement(); + + // The destructor will unregister this advertisement. + virtual ~BluetoothAdvertisement(); + + // List of observers interested in event notifications from us. Objects in + // |observers_| are expected to outlive a BluetoothAdvertisement object. + base::ObserverList observers_; + + private: + DISALLOW_COPY_AND_ASSIGN(BluetoothAdvertisement); +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_ADVERTISEMENT_H_ diff --git a/device/bluetooth/bluetooth_common.h b/device/bluetooth/bluetooth_common.h new file mode 100644 index 0000000..6045980 --- /dev/null +++ b/device/bluetooth/bluetooth_common.h @@ -0,0 +1,49 @@ +// 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 DEVICE_BLUETOOTH_BLUETOOTH_TYPES_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_TYPES_H_ + +#include "device/bluetooth/bluetooth_export.h" + +// This file is for enums and small types common to several +// parts of bluetooth. + +namespace device { + +// Devices and adapters can support a number of transports, +// and bluetooth hosts can scan for devices based on the +// transports they support. +enum BluetoothTransport : uint8_t { + BLUETOOTH_TRANSPORT_INVALID = 0x00, + // Valid transports are given as a bitset. + BLUETOOTH_TRANSPORT_CLASSIC = 0x01, + BLUETOOTH_TRANSPORT_LE = 0x02, + BLUETOOTH_TRANSPORT_DUAL = + (BLUETOOTH_TRANSPORT_CLASSIC | BLUETOOTH_TRANSPORT_LE) +}; + +// Possible values that may be returned by BluetoothDevice::GetDeviceType(), +// representing different types of bluetooth device that we support or are aware +// of decoded from the bluetooth class information. +enum class BluetoothDeviceType { + UNKNOWN, + COMPUTER, + PHONE, + MODEM, + AUDIO, + CAR_AUDIO, + VIDEO, + PERIPHERAL, + JOYSTICK, + GAMEPAD, + KEYBOARD, + MOUSE, + TABLET, + KEYBOARD_MOUSE_COMBO +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_TYPES_H_ diff --git a/device/bluetooth/bluetooth_export.h b/device/bluetooth/bluetooth_export.h new file mode 100644 index 0000000..90cc58c --- /dev/null +++ b/device/bluetooth/bluetooth_export.h @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_BLUETOOTH_DEVICE_BLUETOOTH_EXPORT_H_ +#define DEVICE_BLUETOOTH_DEVICE_BLUETOOTH_EXPORT_H_ + +#if defined(COMPONENT_BUILD) && defined(WIN32) + +#if defined(DEVICE_BLUETOOTH_IMPLEMENTATION) +#define DEVICE_BLUETOOTH_EXPORT __declspec(dllexport) +#else +#define DEVICE_BLUETOOTH_EXPORT __declspec(dllimport) +#endif + +#elif defined(COMPONENT_BUILD) && !defined(WIN32) + +#if defined(DEVICE_BLUETOOTH_IMPLEMENTATION) +#define DEVICE_BLUETOOTH_EXPORT __attribute__((visibility("default"))) +#else +#define DEVICE_BLUETOOTH_EXPORT +#endif + +#else +#define DEVICE_BLUETOOTH_EXPORT +#endif + +#endif // DEVICE_BLUETOOTH_DEVICE_BLUETOOTH_EXPORT_H_ diff --git a/device/bluetooth/bluetooth_uuid.cc b/device/bluetooth/bluetooth_uuid.cc new file mode 100644 index 0000000..b35094d --- /dev/null +++ b/device/bluetooth/bluetooth_uuid.cc @@ -0,0 +1,98 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/bluetooth/bluetooth_uuid.h" + +#include + +#include "base/logging.h" +#include "base/strings/string_util.h" + +namespace device { + +namespace { + +const char kCommonUuidPostfix[] = "-0000-1000-8000-00805f9b34fb"; +const char kCommonUuidPrefix[] = "0000"; + +// Returns the canonical, 128-bit canonical, and the format of the UUID +// in |canonical|, |canonical_128|, and |format| based on |uuid|. +void GetCanonicalUuid(std::string uuid, + std::string* canonical, + std::string* canonical_128, + BluetoothUUID::Format* format) { + // Initialize the values for the failure case. + canonical->clear(); + canonical_128->clear(); + *format = BluetoothUUID::kFormatInvalid; + + if (uuid.empty()) + return; + + if (uuid.size() < 11 && + base::StartsWith(uuid, "0x", base::CompareCase::SENSITIVE)) { + uuid = uuid.substr(2); + } + + if (!(uuid.size() == 4 || uuid.size() == 8 || uuid.size() == 36)) + return; + + for (size_t i = 0; i < uuid.size(); ++i) { + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (uuid[i] != '-') + return; + } else { + if (!base::IsHexDigit(uuid[i])) + return; + uuid[i] = base::ToLowerASCII(uuid[i]); + } + } + + canonical->assign(uuid); + if (uuid.size() == 4) { + canonical_128->assign(kCommonUuidPrefix + uuid + kCommonUuidPostfix); + *format = BluetoothUUID::kFormat16Bit; + } else if (uuid.size() == 8) { + canonical_128->assign(uuid + kCommonUuidPostfix); + *format = BluetoothUUID::kFormat32Bit; + } else { + canonical_128->assign(uuid); + *format = BluetoothUUID::kFormat128Bit; + } +} + +} // namespace + + +BluetoothUUID::BluetoothUUID(const std::string& uuid) { + GetCanonicalUuid(uuid, &value_, &canonical_value_, &format_); +} + +BluetoothUUID::BluetoothUUID() : format_(kFormatInvalid) { +} + +BluetoothUUID::~BluetoothUUID() { +} + +bool BluetoothUUID::IsValid() const { + return format_ != kFormatInvalid; +} + +bool BluetoothUUID::operator<(const BluetoothUUID& uuid) const { + return canonical_value_ < uuid.canonical_value_; +} + +bool BluetoothUUID::operator==(const BluetoothUUID& uuid) const { + return canonical_value_ == uuid.canonical_value_; +} + +bool BluetoothUUID::operator!=(const BluetoothUUID& uuid) const { + return canonical_value_ != uuid.canonical_value_; +} + +void PrintTo(const BluetoothUUID& uuid, std::ostream* out) { + *out << uuid.canonical_value(); +} + +} // namespace device diff --git a/device/bluetooth/bluetooth_uuid.h b/device/bluetooth/bluetooth_uuid.h new file mode 100644 index 0000000..8487f6a --- /dev/null +++ b/device/bluetooth/bluetooth_uuid.h @@ -0,0 +1,106 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ +#define DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ + +#include + +#include "device/bluetooth/bluetooth_export.h" + +namespace device { + +// Opaque wrapper around a Bluetooth UUID. Instances of UUID represent the +// 128-bit universally unique identifiers (UUIDs) of profiles and attributes +// used in Bluetooth based communication, such as a peripheral's services, +// characteristics, and characteristic descriptors. An instance are +// constructed using a string representing 16, 32, or 128 bit UUID formats. +class DEVICE_BLUETOOTH_EXPORT BluetoothUUID { + public: + // Possible representation formats used during construction. + enum Format { + kFormatInvalid, + kFormat16Bit, + kFormat32Bit, + kFormat128Bit + }; + + // Single argument constructor. |uuid| can be a 16, 32, or 128 bit UUID + // represented as a 4, 8, or 36 character string with the following + // formats: + // xxxx + // 0xxxxx + // xxxxxxxx + // 0xxxxxxxxx + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // + // 16 and 32 bit UUIDs will be internally converted to a 128 bit UUID using + // the base UUID defined in the Bluetooth specification, hence custom UUIDs + // should be provided in the 128-bit format. If |uuid| is in an unsupported + // format, the result might be invalid. Use IsValid to check for validity + // after construction. + explicit BluetoothUUID(const std::string& uuid); + + // Default constructor does nothing. Since BluetoothUUID is copyable, this + // constructor is useful for initializing member variables and assigning a + // value to them later. The default constructor will initialize an invalid + // UUID by definition and the string accessors will return an empty string. + BluetoothUUID(); + virtual ~BluetoothUUID(); + + // Returns true, if the UUID is in a valid canonical format. + bool IsValid() const; + + // Returns the representation format of the UUID. This reflects the format + // that was provided during construction. + Format format() const { return format_; } + + // Returns the value of the UUID as a string. The representation format is + // based on what was passed in during construction. For the supported sizes, + // this representation can have the following formats: + // - 16 bit: xxxx + // - 32 bit: xxxxxxxx + // - 128 bit: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // where x is a lowercase hex digit. + const std::string& value() const { return value_; } + + // Returns the underlying 128-bit value as a string in the following format: + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // where x is a lowercase hex digit. + const std::string& canonical_value() const { return canonical_value_; } + + // Permit sufficient comparison to allow a UUID to be used as a key in a + // std::map. + bool operator<(const BluetoothUUID& uuid) const; + + // Equality operators. + bool operator==(const BluetoothUUID& uuid) const; + bool operator!=(const BluetoothUUID& uuid) const; + + private: + // String representation of the UUID that was used during construction. For + // the supported sizes, this representation can have the following formats: + // - 16 bit: xxxx + // - 32 bit: xxxxxxxx + // - 128 bit: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + Format format_; + std::string value_; + + // The 128-bit string representation of the UUID. + std::string canonical_value_; +}; + +// This is required by gtest to print a readable output on test failures. +void DEVICE_BLUETOOTH_EXPORT +PrintTo(const BluetoothUUID& uuid, std::ostream* out); + +struct BluetoothUUIDHash { + size_t operator()(const device::BluetoothUUID& uuid) const { + return std::hash()(uuid.canonical_value()); + } +}; + +} // namespace device + +#endif // DEVICE_BLUETOOTH_BLUETOOTH_UUID_H_ diff --git a/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc new file mode 100644 index 0000000..ee6cbf6 --- /dev/null +++ b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.cc @@ -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. + +#include "device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h" + +#include + +#include "base/logging.h" +#include "base/memory/ptr_util.h" + +namespace bluez { + +BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ() + : type_(NULLTYPE), size_(0), value_(base::Value::CreateNullValue()) {} + +BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ( + Type type, + size_t size, + std::unique_ptr value) + : type_(type), size_(size), value_(std::move(value)) { + CHECK_NE(type, SEQUENCE); +} + +BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ( + std::unique_ptr sequence) + : type_(SEQUENCE), + size_(sequence->size()), + sequence_(std::move(sequence)) {} + +BluetoothServiceAttributeValueBlueZ::BluetoothServiceAttributeValueBlueZ( + const BluetoothServiceAttributeValueBlueZ& attribute) { + *this = attribute; +} + +BluetoothServiceAttributeValueBlueZ& BluetoothServiceAttributeValueBlueZ:: +operator=(const BluetoothServiceAttributeValueBlueZ& attribute) { + if (this != &attribute) { + type_ = attribute.type_; + size_ = attribute.size_; + if (attribute.type_ == SEQUENCE) { + value_ = nullptr; + sequence_ = base::MakeUnique(*attribute.sequence_); + } else { + value_ = attribute.value_->CreateDeepCopy(); + sequence_ = nullptr; + } + } + return *this; +} + +BluetoothServiceAttributeValueBlueZ::~BluetoothServiceAttributeValueBlueZ() {} + +} // namespace bluez diff --git a/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h new file mode 100644 index 0000000..fdd291a --- /dev/null +++ b/device/bluetooth/bluez/bluetooth_service_attribute_value_bluez.h @@ -0,0 +1,59 @@ +// 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 DEVICE_BLUETOOTH_BLUEZ_BLUETOOTH_SERVICE_ATTRIBUTE_VALUE_BLUEZ_H_ +#define DEVICE_BLUETOOTH_BLUEZ_BLUETOOTH_SERVICE_ATTRIBUTE_VALUE_BLUEZ_H_ + +#include +#include +#include + +#include "base/values.h" +#include "device/bluetooth/bluetooth_export.h" + +namespace bluez { + +// This class contains a Bluetooth service attribute. A service attribute is +// defined by the following fields, +// type: This is the type of the attribute. Along with being any of the +// fixed types, an attribute can also be of type sequence, which means +// that it contains an array of other attributes. +// size: This is the size of the attribute. This can be variable for each type. +// For example, a UUID can have the sizes, 2, 4 or 16 bytes. +// value: This is the raw value of the attribute. For example, for a UUID, it +// will be the string representation of the UUID. For a sequence, it +// will be an array of other attributes. +class DEVICE_BLUETOOTH_EXPORT BluetoothServiceAttributeValueBlueZ { + public: + enum Type { NULLTYPE = 0, UINT, INT, UUID, STRING, BOOL, SEQUENCE, URL }; + + using Sequence = std::vector; + + BluetoothServiceAttributeValueBlueZ(); + BluetoothServiceAttributeValueBlueZ(Type type, + size_t size, + std::unique_ptr value); + explicit BluetoothServiceAttributeValueBlueZ( + std::unique_ptr sequence); + BluetoothServiceAttributeValueBlueZ( + const BluetoothServiceAttributeValueBlueZ& attribute); + BluetoothServiceAttributeValueBlueZ& operator=( + const BluetoothServiceAttributeValueBlueZ& attribute); + ~BluetoothServiceAttributeValueBlueZ(); + + Type type() const { return type_; } + size_t size() const { return size_; } + const Sequence& sequence() const { return *sequence_.get(); } + const base::Value& value() const { return *value_.get(); } + + private: + Type type_; + size_t size_; + std::unique_ptr value_; + std::unique_ptr sequence_; +}; + +} // namespace bluez + +#endif // DEVICE_BLUETOOTH_BLUEZ_BLUETOOTH_SERVICE_ATTRIBUTE_VALUE_BLUEZ_H_ diff --git a/libchrome_tools/patch/path_service.patch b/libchrome_tools/patch/path_service.patch index f831d98..75b30dc 100644 --- a/libchrome_tools/patch/path_service.patch +++ b/libchrome_tools/patch/path_service.patch @@ -1,23 +1,36 @@ -# Currently, PathService is not available on libchrome. +# Several paths are not supported in PathService by libchrome. ---- a/base/files/file_util_posix.cc -+++ b/base/files/file_util_posix.cc -@@ -29,7 +29,6 @@ +--- a/base/base_paths_posix.cc ++++ b/base/base_paths_posix.cc +@@ -19,7 +19,8 @@ + #include "base/files/file_path.h" + #include "base/files/file_util.h" #include "base/logging.h" - #include "base/macros.h" - #include "base/memory/singleton.h" --#include "base/path_service.h" - #include "base/posix/eintr_wrapper.h" - #include "base/stl_util.h" - #include "base/strings/string_split.h" -@@ -50,6 +49,7 @@ - #if defined(OS_ANDROID) - #include "base/android/content_uri_utils.h" - #include "base/os_compat_android.h" -+#include "base/path_service.h" +-#include "base/nix/xdg_util.h" ++// Unused, and this file is not ported to libchrome. ++// #include "base/nix/xdg_util.h" + #include "base/path_service.h" + #include "base/process/process_metrics.h" + #include "build/build_config.h" +@@ -77,6 +78,8 @@ bool PathProviderPosix(int key, FilePath + return true; #endif - - #if !defined(OS_IOS) + } ++// Following paths are not supported in libchrome/libmojo. ++#if 0 + case DIR_SOURCE_ROOT: { + // Allow passing this in the environment, for more flexibility in build + // tree configurations (sub-project builds, gyp --output_dir, etc.) +@@ -112,6 +115,7 @@ bool PathProviderPosix(int key, FilePath + *result = cache_dir; + return true; + } ++#endif + } + return false; + } +--- a/base/files/file_util_posix.cc ++++ b/base/files/file_util_posix.cc @@ -533,6 +533,8 @@ bool GetTempDir(FilePath* path) { } else { #if defined(OS_ANDROID) @@ -29,64 +42,41 @@ #endif --- a/base/json/json_reader_unittest.cc +++ b/base/json/json_reader_unittest.cc -@@ -8,11 +8,14 @@ - - #include - -+#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) - #include "base/base_paths.h" -+#include "base/path_service.h" -+#endif -+ - #include "base/files/file_util.h" - #include "base/logging.h" - #include "base/macros.h" --#include "base/path_service.h" - #include "base/strings/string_piece.h" - #include "base/strings/utf_string_conversions.h" - #include "base/values.h" -@@ -567,6 +570,7 @@ TEST(JSONReaderTest, Reading) { +@@ -567,7 +567,7 @@ TEST(JSONReaderTest, Reading) { } } -+#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) - TEST(JSONReaderTest, ReadFromFile) { +-TEST(JSONReaderTest, ReadFromFile) { ++TEST(JSONReaderTest, DISABLED_ReadFromFile) { FilePath path; ASSERT_TRUE(PathService::Get(base::DIR_TEST_DATA, &path)); -@@ -581,6 +585,7 @@ TEST(JSONReaderTest, ReadFromFile) { - ASSERT_TRUE(root) << reader.GetErrorMessage(); - EXPECT_TRUE(root->IsType(Value::Type::DICTIONARY)); - } -+#endif // !__ANDROID__ && !__ANDROID_HOST__ - - // Tests that the root of a JSON object can be deleted safely while its - // children outlive it. + path = path.AppendASCII("json"); --- a/base/json/json_value_serializer_unittest.cc +++ b/base/json/json_value_serializer_unittest.cc -@@ -11,7 +11,9 @@ - #include "base/json/json_reader.h" - #include "base/json/json_string_value_serializer.h" - #include "base/json/json_writer.h" -+#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) - #include "base/path_service.h" -+#endif - #include "base/strings/string_piece.h" - #include "base/strings/string_util.h" - #include "base/strings/utf_string_conversions.h" -@@ -395,6 +397,8 @@ TEST(JSONValueSerializerTest, JSONReader - ASSERT_FALSE(JSONReader::Read("/ * * / [1]")); - } +@@ -402,7 +402,7 @@ class JSONFileValueSerializerTest : publ + ScopedTempDir temp_dir_; + }; -+#if !defined(__ANDROID__) && !defined(__ANDROID_HOST__) -+ - class JSONFileValueSerializerTest : public testing::Test { - protected: - void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } -@@ -481,6 +485,7 @@ TEST_F(JSONFileValueSerializerTest, NoWh - std::unique_ptr root = deserializer.Deserialize(nullptr, nullptr); - ASSERT_TRUE(root); +-TEST_F(JSONFileValueSerializerTest, Roundtrip) { ++TEST_F(JSONFileValueSerializerTest, DISABLED_Roundtrip) { + FilePath original_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); + original_file_path = original_file_path.AppendASCII("serializer_test.json"); +@@ -445,7 +445,7 @@ TEST_F(JSONFileValueSerializerTest, Roun + EXPECT_TRUE(DeleteFile(written_file_path, false)); } -+#endif // !__ANDROID__ && !__ANDROID_HOST__ - } // namespace +-TEST_F(JSONFileValueSerializerTest, RoundtripNested) { ++TEST_F(JSONFileValueSerializerTest, DISABLED_RoundtripNested) { + FilePath original_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &original_file_path)); + original_file_path = +@@ -471,7 +471,7 @@ TEST_F(JSONFileValueSerializerTest, Roun + EXPECT_TRUE(DeleteFile(written_file_path, false)); + } +-TEST_F(JSONFileValueSerializerTest, NoWhitespace) { ++TEST_F(JSONFileValueSerializerTest, DISABLED_NoWhitespace) { + FilePath source_file_path; + ASSERT_TRUE(PathService::Get(DIR_TEST_DATA, &source_file_path)); + source_file_path = diff --git a/ui/gfx/geometry/insets.cc b/ui/gfx/geometry/insets.cc new file mode 100644 index 0000000..9acc6e0 --- /dev/null +++ b/ui/gfx/geometry/insets.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/insets.h" + +#include "base/strings/stringprintf.h" +#include "ui/gfx/geometry/vector2d.h" + +namespace gfx { + +std::string Insets::ToString() const { + // Print members in the same order of the constructor parameters. + return base::StringPrintf("%d,%d,%d,%d", top(), left(), bottom(), right()); +} + +Insets Insets::Offset(const gfx::Vector2d& vector) const { + return gfx::Insets(top() + vector.y(), left() + vector.x(), + bottom() - vector.y(), right() - vector.x()); +} + +} // namespace gfx diff --git a/ui/gfx/geometry/insets.h b/ui/gfx/geometry/insets.h new file mode 100644 index 0000000..a88bae3 --- /dev/null +++ b/ui/gfx/geometry/insets.h @@ -0,0 +1,130 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_INSETS_H_ +#define UI_GFX_GEOMETRY_INSETS_H_ + +#include + +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class Vector2d; + +// Represents the widths of the four borders or margins of an unspecified +// rectangle. An Insets stores the thickness of the top, left, bottom and right +// edges, without storing the actual size and position of the rectangle itself. +// +// This can be used to represent a space within a rectangle, by "shrinking" the +// rectangle by the inset amount on all four sides. Alternatively, it can +// represent a border that has a different thickness on each side. +class GFX_EXPORT Insets { + public: + constexpr Insets() : top_(0), left_(0), bottom_(0), right_(0) {} + constexpr explicit Insets(int all) + : top_(all), left_(all), bottom_(all), right_(all) {} + constexpr Insets(int vertical, int horizontal) + : top_(vertical), + left_(horizontal), + bottom_(vertical), + right_(horizontal) {} + constexpr Insets(int top, int left, int bottom, int right) + : top_(top), left_(left), bottom_(bottom), right_(right) {} + + constexpr int top() const { return top_; } + constexpr int left() const { return left_; } + constexpr int bottom() const { return bottom_; } + constexpr int right() const { return right_; } + + // Returns the total width taken up by the insets, which is the sum of the + // left and right insets. + constexpr int width() const { return left_ + right_; } + + // Returns the total height taken up by the insets, which is the sum of the + // top and bottom insets. + constexpr int height() const { return top_ + bottom_; } + + // Returns true if the insets are empty. + bool IsEmpty() const { return width() == 0 && height() == 0; } + + void Set(int top, int left, int bottom, int right) { + top_ = top; + left_ = left; + bottom_ = bottom; + right_ = right; + } + + bool operator==(const Insets& insets) const { + return top_ == insets.top_ && left_ == insets.left_ && + bottom_ == insets.bottom_ && right_ == insets.right_; + } + + bool operator!=(const Insets& insets) const { + return !(*this == insets); + } + + void operator+=(const Insets& insets) { + top_ += insets.top_; + left_ += insets.left_; + bottom_ += insets.bottom_; + right_ += insets.right_; + } + + void operator-=(const Insets& insets) { + top_ -= insets.top_; + left_ -= insets.left_; + bottom_ -= insets.bottom_; + right_ -= insets.right_; + } + + Insets operator-() const { + return Insets(-top_, -left_, -bottom_, -right_); + } + + Insets Scale(float scale) const { + return Scale(scale, scale); + } + + Insets Scale(float x_scale, float y_scale) const { + return Insets(static_cast(top() * y_scale), + static_cast(left() * x_scale), + static_cast(bottom() * y_scale), + static_cast(right() * x_scale)); + } + + // Adjusts the vertical and horizontal dimensions by the values described in + // |vector|. Offsetting insets before applying to a rectangle would be + // equivalent to offseting the rectangle then applying the insets. + Insets Offset(const gfx::Vector2d& vector) const; + + operator InsetsF() const { + return InsetsF(static_cast(top()), static_cast(left()), + static_cast(bottom()), static_cast(right())); + } + + // Returns a string representation of the insets. + std::string ToString() const; + + private: + int top_; + int left_; + int bottom_; + int right_; +}; + +inline Insets operator+(Insets lhs, const Insets& rhs) { + lhs += rhs; + return lhs; +} + +inline Insets operator-(Insets lhs, const Insets& rhs) { + lhs -= rhs; + return lhs; +} + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_INSETS_H_ diff --git a/ui/gfx/geometry/insets_f.cc b/ui/gfx/geometry/insets_f.cc new file mode 100644 index 0000000..c1bc27e --- /dev/null +++ b/ui/gfx/geometry/insets_f.cc @@ -0,0 +1,16 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/insets_f.h" + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string InsetsF::ToString() const { + // Print members in the same order of the constructor parameters. + return base::StringPrintf("%f,%f,%f,%f", top(), left(), bottom(), right()); +} + +} // namespace gfx diff --git a/ui/gfx/geometry/insets_f.h b/ui/gfx/geometry/insets_f.h new file mode 100644 index 0000000..30c2ff2 --- /dev/null +++ b/ui/gfx/geometry/insets_f.h @@ -0,0 +1,100 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_INSETS_F_H_ +#define UI_GFX_GEOMETRY_INSETS_F_H_ + +#include + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// A floating point version of gfx::Insets. +class GFX_EXPORT InsetsF { + public: + constexpr InsetsF() : top_(0.f), left_(0.f), bottom_(0.f), right_(0.f) {} + constexpr explicit InsetsF(float all) + : top_(all), left_(all), bottom_(all), right_(all) {} + constexpr InsetsF(float vertical, float horizontal) + : top_(vertical), + left_(horizontal), + bottom_(vertical), + right_(horizontal) {} + constexpr InsetsF(float top, float left, float bottom, float right) + : top_(top), left_(left), bottom_(bottom), right_(right) {} + + constexpr float top() const { return top_; } + constexpr float left() const { return left_; } + constexpr float bottom() const { return bottom_; } + constexpr float right() const { return right_; } + + // Returns the total width taken up by the insets, which is the sum of the + // left and right insets. + constexpr float width() const { return left_ + right_; } + + // Returns the total height taken up by the insets, which is the sum of the + // top and bottom insets. + constexpr float height() const { return top_ + bottom_; } + + // Returns true if the insets are empty. + bool IsEmpty() const { return width() == 0.f && height() == 0.f; } + + void Set(float top, float left, float bottom, float right) { + top_ = top; + left_ = left; + bottom_ = bottom; + right_ = right; + } + + bool operator==(const InsetsF& insets) const { + return top_ == insets.top_ && left_ == insets.left_ && + bottom_ == insets.bottom_ && right_ == insets.right_; + } + + bool operator!=(const InsetsF& insets) const { + return !(*this == insets); + } + + void operator+=(const InsetsF& insets) { + top_ += insets.top_; + left_ += insets.left_; + bottom_ += insets.bottom_; + right_ += insets.right_; + } + + void operator-=(const InsetsF& insets) { + top_ -= insets.top_; + left_ -= insets.left_; + bottom_ -= insets.bottom_; + right_ -= insets.right_; + } + + InsetsF operator-() const { + return InsetsF(-top_, -left_, -bottom_, -right_); + } + + // Returns a string representation of the insets. + std::string ToString() const; + + private: + float top_; + float left_; + float bottom_; + float right_; +}; + +inline InsetsF operator+(InsetsF lhs, const InsetsF& rhs) { + lhs += rhs; + return lhs; +} + +inline InsetsF operator-(InsetsF lhs, const InsetsF& rhs) { + lhs -= rhs; + return lhs; +} + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_INSETS_F_H_ diff --git a/ui/gfx/geometry/point.cc b/ui/gfx/geometry/point.cc new file mode 100644 index 0000000..285b208 --- /dev/null +++ b/ui/gfx/geometry/point.cc @@ -0,0 +1,105 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/point.h" + +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/point_conversions.h" +#include "ui/gfx/geometry/point_f.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_IOS) +#include +#elif defined(OS_MACOSX) +#include +#endif + +namespace gfx { + +#if defined(OS_WIN) +Point::Point(DWORD point) { + POINTS points = MAKEPOINTS(point); + x_ = points.x; + y_ = points.y; +} + +Point::Point(const POINT& point) : x_(point.x), y_(point.y) { +} + +Point& Point::operator=(const POINT& point) { + x_ = point.x; + y_ = point.y; + return *this; +} +#elif defined(OS_MACOSX) +Point::Point(const CGPoint& point) : x_(point.x), y_(point.y) { +} +#endif + +#if defined(OS_WIN) +POINT Point::ToPOINT() const { + POINT p; + p.x = x(); + p.y = y(); + return p; +} +#elif defined(OS_MACOSX) +CGPoint Point::ToCGPoint() const { + return CGPointMake(x(), y()); +} +#endif + +void Point::SetToMin(const Point& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; +} + +void Point::SetToMax(const Point& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; +} + +std::string Point::ToString() const { + return base::StringPrintf("%d,%d", x(), y()); +} + +Point ScaleToCeiledPoint(const Point& point, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return point; + return ToCeiledPoint(ScalePoint(gfx::PointF(point), x_scale, y_scale)); +} + +Point ScaleToCeiledPoint(const Point& point, float scale) { + if (scale == 1.f) + return point; + return ToCeiledPoint(ScalePoint(gfx::PointF(point), scale, scale)); +} + +Point ScaleToFlooredPoint(const Point& point, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return point; + return ToFlooredPoint(ScalePoint(gfx::PointF(point), x_scale, y_scale)); +} + +Point ScaleToFlooredPoint(const Point& point, float scale) { + if (scale == 1.f) + return point; + return ToFlooredPoint(ScalePoint(gfx::PointF(point), scale, scale)); +} + +Point ScaleToRoundedPoint(const Point& point, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return point; + return ToRoundedPoint(ScalePoint(gfx::PointF(point), x_scale, y_scale)); +} + +Point ScaleToRoundedPoint(const Point& point, float scale) { + if (scale == 1.f) + return point; + return ToRoundedPoint(ScalePoint(gfx::PointF(point), scale, scale)); +} + +} // namespace gfx diff --git a/ui/gfx/geometry/point.h b/ui/gfx/geometry/point.h new file mode 100644 index 0000000..bb248d5 --- /dev/null +++ b/ui/gfx/geometry/point.h @@ -0,0 +1,148 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_POINT_H_ +#define UI_GFX_GEOMETRY_POINT_H_ + +#include +#include +#include + +#include "base/numerics/saturated_arithmetic.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/gfx_export.h" + +#if defined(OS_WIN) +typedef unsigned long DWORD; +typedef struct tagPOINT POINT; +#elif defined(OS_MACOSX) +typedef struct CGPoint CGPoint; +#endif + +namespace gfx { + +// A point has an x and y coordinate. +class GFX_EXPORT Point { + public: + constexpr Point() : x_(0), y_(0) {} + constexpr Point(int x, int y) : x_(x), y_(y) {} +#if defined(OS_WIN) + // |point| is a DWORD value that contains a coordinate. The x-coordinate is + // the low-order short and the y-coordinate is the high-order short. This + // value is commonly acquired from GetMessagePos/GetCursorPos. + explicit Point(DWORD point); + explicit Point(const POINT& point); + Point& operator=(const POINT& point); +#elif defined(OS_MACOSX) + explicit Point(const CGPoint& point); +#endif + +#if defined(OS_WIN) + POINT ToPOINT() const; +#elif defined(OS_MACOSX) + CGPoint ToCGPoint() const; +#endif + + constexpr int x() const { return x_; } + constexpr int y() const { return y_; } + void set_x(int x) { x_ = x; } + void set_y(int y) { y_ = y; } + + void SetPoint(int x, int y) { + x_ = x; + y_ = y; + } + + void Offset(int delta_x, int delta_y) { + x_ = base::SaturatedAddition(x_, delta_x); + y_ = base::SaturatedAddition(y_, delta_y); + } + + void operator+=(const Vector2d& vector) { + x_ = base::SaturatedAddition(x_, vector.x()); + y_ = base::SaturatedAddition(y_, vector.y()); + } + + void operator-=(const Vector2d& vector) { + x_ = base::SaturatedSubtraction(x_, vector.x()); + y_ = base::SaturatedSubtraction(y_, vector.y()); + } + + void SetToMin(const Point& other); + void SetToMax(const Point& other); + + bool IsOrigin() const { return x_ == 0 && y_ == 0; } + + Vector2d OffsetFromOrigin() const { return Vector2d(x_, y_); } + + // A point is less than another point if its y-value is closer + // to the origin. If the y-values are the same, then point with + // the x-value closer to the origin is considered less than the + // other. + // This comparison is required to use Point in sets, or sorted + // vectors. + bool operator<(const Point& rhs) const { + return std::tie(y_, x_) < std::tie(rhs.y_, rhs.x_); + } + + // Returns a string representation of point. + std::string ToString() const; + + private: + int x_; + int y_; +}; + +inline bool operator==(const Point& lhs, const Point& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y(); +} + +inline bool operator!=(const Point& lhs, const Point& rhs) { + return !(lhs == rhs); +} + +inline Point operator+(const Point& lhs, const Vector2d& rhs) { + Point result(lhs); + result += rhs; + return result; +} + +inline Point operator-(const Point& lhs, const Vector2d& rhs) { + Point result(lhs); + result -= rhs; + return result; +} + +inline Vector2d operator-(const Point& lhs, const Point& rhs) { + return Vector2d(base::SaturatedSubtraction(lhs.x(), rhs.x()), + base::SaturatedSubtraction(lhs.y(), rhs.y())); +} + +inline Point PointAtOffsetFromOrigin(const Vector2d& offset_from_origin) { + return Point(offset_from_origin.x(), offset_from_origin.y()); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Point& point, ::std::ostream* os); + +// Helper methods to scale a gfx::Point to a new gfx::Point. +GFX_EXPORT Point ScaleToCeiledPoint(const Point& point, + float x_scale, + float y_scale); +GFX_EXPORT Point ScaleToCeiledPoint(const Point& point, float x_scale); +GFX_EXPORT Point ScaleToFlooredPoint(const Point& point, + float x_scale, + float y_scale); +GFX_EXPORT Point ScaleToFlooredPoint(const Point& point, float x_scale); +GFX_EXPORT Point ScaleToRoundedPoint(const Point& point, + float x_scale, + float y_scale); +GFX_EXPORT Point ScaleToRoundedPoint(const Point& point, float x_scale); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_POINT_H_ diff --git a/ui/gfx/geometry/point_conversions.cc b/ui/gfx/geometry/point_conversions.cc new file mode 100644 index 0000000..0613e7a --- /dev/null +++ b/ui/gfx/geometry/point_conversions.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/point_conversions.h" + +#include "ui/gfx/geometry/safe_integer_conversions.h" + +namespace gfx { + +Point ToFlooredPoint(const PointF& point) { + int x = ToFlooredInt(point.x()); + int y = ToFlooredInt(point.y()); + return Point(x, y); +} + +Point ToCeiledPoint(const PointF& point) { + int x = ToCeiledInt(point.x()); + int y = ToCeiledInt(point.y()); + return Point(x, y); +} + +Point ToRoundedPoint(const PointF& point) { + int x = ToRoundedInt(point.x()); + int y = ToRoundedInt(point.y()); + return Point(x, y); +} + +} // namespace gfx + diff --git a/ui/gfx/geometry/point_conversions.h b/ui/gfx/geometry/point_conversions.h new file mode 100644 index 0000000..bfab9e4 --- /dev/null +++ b/ui/gfx/geometry/point_conversions.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_POINT_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_POINT_CONVERSIONS_H_ + +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/point_f.h" + +namespace gfx { + +// Returns a Point with each component from the input PointF floored. +GFX_EXPORT Point ToFlooredPoint(const PointF& point); + +// Returns a Point with each component from the input PointF ceiled. +GFX_EXPORT Point ToCeiledPoint(const PointF& point); + +// Returns a Point with each component from the input PointF rounded. +GFX_EXPORT Point ToRoundedPoint(const PointF& point); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_POINT_CONVERSIONS_H_ diff --git a/ui/gfx/geometry/point_f.cc b/ui/gfx/geometry/point_f.cc new file mode 100644 index 0000000..0d15394 --- /dev/null +++ b/ui/gfx/geometry/point_f.cc @@ -0,0 +1,32 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/point_f.h" + +#include "base/strings/stringprintf.h" + +namespace gfx { + +void PointF::SetToMin(const PointF& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; +} + +void PointF::SetToMax(const PointF& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; +} + +std::string PointF::ToString() const { + return base::StringPrintf("%f,%f", x(), y()); +} + +PointF ScalePoint(const PointF& p, float x_scale, float y_scale) { + PointF scaled_p(p); + scaled_p.Scale(x_scale, y_scale); + return scaled_p; +} + + +} // namespace gfx diff --git a/ui/gfx/geometry/point_f.h b/ui/gfx/geometry/point_f.h new file mode 100644 index 0000000..5d92b11 --- /dev/null +++ b/ui/gfx/geometry/point_f.h @@ -0,0 +1,126 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_POINT_F_H_ +#define UI_GFX_GEOMETRY_POINT_F_H_ + +#include +#include +#include + +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/vector2d_f.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +// A floating version of gfx::Point. +class GFX_EXPORT PointF { + public: + constexpr PointF() : x_(0.f), y_(0.f) {} + constexpr PointF(float x, float y) : x_(x), y_(y) {} + + constexpr explicit PointF(const Point& p) + : PointF(static_cast(p.x()), static_cast(p.y())) {} + + constexpr float x() const { return x_; } + constexpr float y() const { return y_; } + void set_x(float x) { x_ = x; } + void set_y(float y) { y_ = y; } + + void SetPoint(float x, float y) { + x_ = x; + y_ = y; + } + + void Offset(float delta_x, float delta_y) { + x_ += delta_x; + y_ += delta_y; + } + + void operator+=(const Vector2dF& vector) { + x_ += vector.x(); + y_ += vector.y(); + } + + void operator-=(const Vector2dF& vector) { + x_ -= vector.x(); + y_ -= vector.y(); + } + + void SetToMin(const PointF& other); + void SetToMax(const PointF& other); + + bool IsOrigin() const { return x_ == 0 && y_ == 0; } + + Vector2dF OffsetFromOrigin() const { return Vector2dF(x_, y_); } + + // A point is less than another point if its y-value is closer + // to the origin. If the y-values are the same, then point with + // the x-value closer to the origin is considered less than the + // other. + // This comparison is required to use PointF in sets, or sorted + // vectors. + bool operator<(const PointF& rhs) const { + return std::tie(y_, x_) < std::tie(rhs.y_, rhs.x_); + } + + void Scale(float scale) { + Scale(scale, scale); + } + + void Scale(float x_scale, float y_scale) { + SetPoint(x() * x_scale, y() * y_scale); + } + + // Returns a string representation of point. + std::string ToString() const; + + private: + float x_; + float y_; +}; + +inline bool operator==(const PointF& lhs, const PointF& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y(); +} + +inline bool operator!=(const PointF& lhs, const PointF& rhs) { + return !(lhs == rhs); +} + +inline PointF operator+(const PointF& lhs, const Vector2dF& rhs) { + PointF result(lhs); + result += rhs; + return result; +} + +inline PointF operator-(const PointF& lhs, const Vector2dF& rhs) { + PointF result(lhs); + result -= rhs; + return result; +} + +inline Vector2dF operator-(const PointF& lhs, const PointF& rhs) { + return Vector2dF(lhs.x() - rhs.x(), lhs.y() - rhs.y()); +} + +inline PointF PointAtOffsetFromOrigin(const Vector2dF& offset_from_origin) { + return PointF(offset_from_origin.x(), offset_from_origin.y()); +} + +GFX_EXPORT PointF ScalePoint(const PointF& p, float x_scale, float y_scale); + +inline PointF ScalePoint(const PointF& p, float scale) { + return ScalePoint(p, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const PointF& point, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_POINT_F_H_ diff --git a/ui/gfx/geometry/rect.cc b/ui/gfx/geometry/rect.cc new file mode 100644 index 0000000..b5ceda5 --- /dev/null +++ b/ui/gfx/geometry/rect.cc @@ -0,0 +1,346 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rect.h" + +#include + +#if defined(OS_WIN) +#include +#elif defined(OS_IOS) +#include +#elif defined(OS_MACOSX) +#include +#endif + +#include "base/logging.h" +#include "base/numerics/saturated_arithmetic.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/insets.h" + +namespace gfx { + +#if defined(OS_WIN) +Rect::Rect(const RECT& r) + : origin_(r.left, r.top), + size_(std::abs(r.right - r.left), std::abs(r.bottom - r.top)) { +} +#elif defined(OS_MACOSX) +Rect::Rect(const CGRect& r) + : origin_(r.origin.x, r.origin.y), size_(r.size.width, r.size.height) { +} +#endif + +#if defined(OS_WIN) +RECT Rect::ToRECT() const { + RECT r; + r.left = x(); + r.right = right(); + r.top = y(); + r.bottom = bottom(); + return r; +} +#elif defined(OS_MACOSX) +CGRect Rect::ToCGRect() const { + return CGRectMake(x(), y(), width(), height()); +} +#endif + +void AdjustAlongAxis(int dst_origin, int dst_size, int* origin, int* size) { + *size = std::min(dst_size, *size); + if (*origin < dst_origin) + *origin = dst_origin; + else + *origin = std::min(dst_origin + dst_size, *origin + *size) - *size; +} + +} // namespace + +namespace gfx { + +// This is the per-axis heuristic for picking the most useful origin and +// width/height to represent the input range. +static void SaturatedClampRange(int min, int max, int* origin, int* span) { + if (max < min) { + *span = 0; + *origin = min; + return; + } + + int effective_span = base::SaturatedSubtraction(max, min); + int span_loss = base::SaturatedSubtraction(max, min + effective_span); + + // If the desired width is within the limits of ints, we can just + // use the simple computations to represent the range precisely. + if (span_loss == 0) { + *span = effective_span; + *origin = min; + return; + } + + // Now we have to approximate. If one of min or max is close enough + // to zero we choose to represent that one precisely. The other side is + // probably practically "infinite", so we move it. + if (base::SaturatedAbsolute(max) < std::numeric_limits::max() / 2) { + // Maintain origin + span == max. + *span = effective_span; + *origin = max - effective_span; + } else if (base::SaturatedAbsolute(min) < + std::numeric_limits::max() / 2) { + // Maintain origin == min. + *span = effective_span; + *origin = min; + } else { + // Both are big, so keep the center. + *span = effective_span; + *origin = min + span_loss / 2; + } +} + +void Rect::SetByBounds(int left, int top, int right, int bottom) { + int x, y; + int width, height; + SaturatedClampRange(left, right, &x, &width); + SaturatedClampRange(top, bottom, &y, &height); + origin_.SetPoint(x, y); + size_.SetSize(width, height); +} + +void Rect::Inset(const Insets& insets) { + Inset(insets.left(), insets.top(), insets.right(), insets.bottom()); +} + +void Rect::Inset(int left, int top, int right, int bottom) { + origin_ += Vector2d(left, top); + // left+right might overflow/underflow, but width() - (left+right) might + // overflow as well. + set_width(base::SaturatedSubtraction(width(), + base::SaturatedAddition(left, right))); + set_height(base::SaturatedSubtraction(height(), + base::SaturatedAddition(top, bottom))); +} + +void Rect::Offset(int horizontal, int vertical) { + origin_ += Vector2d(horizontal, vertical); + // Ensure that width and height remain valid. + set_width(width()); + set_height(height()); +} + +void Rect::operator+=(const Vector2d& offset) { + origin_ += offset; + // Ensure that width and height remain valid. + set_width(width()); + set_height(height()); +} + +void Rect::operator-=(const Vector2d& offset) { + origin_ -= offset; +} + +Insets Rect::InsetsFrom(const Rect& inner) const { + return Insets(inner.y() - y(), + inner.x() - x(), + bottom() - inner.bottom(), + right() - inner.right()); +} + +bool Rect::operator<(const Rect& other) const { + if (origin_ == other.origin_) { + if (width() == other.width()) { + return height() < other.height(); + } else { + return width() < other.width(); + } + } else { + return origin_ < other.origin_; + } +} + +bool Rect::Contains(int point_x, int point_y) const { + return (point_x >= x()) && (point_x < right()) && (point_y >= y()) && + (point_y < bottom()); +} + +bool Rect::Contains(const Rect& rect) const { + return (rect.x() >= x() && rect.right() <= right() && rect.y() >= y() && + rect.bottom() <= bottom()); +} + +bool Rect::Intersects(const Rect& rect) const { + return !(IsEmpty() || rect.IsEmpty() || rect.x() >= right() || + rect.right() <= x() || rect.y() >= bottom() || rect.bottom() <= y()); +} + +void Rect::Intersect(const Rect& rect) { + if (IsEmpty() || rect.IsEmpty()) { + SetRect(0, 0, 0, 0); // Throws away empty position. + return; + } + + int left = std::max(x(), rect.x()); + int top = std::max(y(), rect.y()); + int new_right = std::min(right(), rect.right()); + int new_bottom = std::min(bottom(), rect.bottom()); + + if (left >= new_right || top >= new_bottom) { + SetRect(0, 0, 0, 0); // Throws away empty position. + return; + } + + SetByBounds(left, top, new_right, new_bottom); +} + +void Rect::Union(const Rect& rect) { + if (IsEmpty()) { + *this = rect; + return; + } + if (rect.IsEmpty()) + return; + + SetByBounds(std::min(x(), rect.x()), std::min(y(), rect.y()), + std::max(right(), rect.right()), + std::max(bottom(), rect.bottom())); +} + +void Rect::Subtract(const Rect& rect) { + if (!Intersects(rect)) + return; + if (rect.Contains(*this)) { + SetRect(0, 0, 0, 0); + return; + } + + int rx = x(); + int ry = y(); + int rr = right(); + int rb = bottom(); + + if (rect.y() <= y() && rect.bottom() >= bottom()) { + // complete intersection in the y-direction + if (rect.x() <= x()) { + rx = rect.right(); + } else if (rect.right() >= right()) { + rr = rect.x(); + } + } else if (rect.x() <= x() && rect.right() >= right()) { + // complete intersection in the x-direction + if (rect.y() <= y()) { + ry = rect.bottom(); + } else if (rect.bottom() >= bottom()) { + rb = rect.y(); + } + } + SetByBounds(rx, ry, rr, rb); +} + +void Rect::AdjustToFit(const Rect& rect) { + int new_x = x(); + int new_y = y(); + int new_width = width(); + int new_height = height(); + AdjustAlongAxis(rect.x(), rect.width(), &new_x, &new_width); + AdjustAlongAxis(rect.y(), rect.height(), &new_y, &new_height); + SetRect(new_x, new_y, new_width, new_height); +} + +Point Rect::CenterPoint() const { + return Point(x() + width() / 2, y() + height() / 2); +} + +void Rect::ClampToCenteredSize(const Size& size) { + int new_width = std::min(width(), size.width()); + int new_height = std::min(height(), size.height()); + int new_x = x() + (width() - new_width) / 2; + int new_y = y() + (height() - new_height) / 2; + SetRect(new_x, new_y, new_width, new_height); +} + +void Rect::SplitVertically(Rect* left_half, Rect* right_half) const { + DCHECK(left_half); + DCHECK(right_half); + + left_half->SetRect(x(), y(), width() / 2, height()); + right_half->SetRect( + left_half->right(), y(), width() - left_half->width(), height()); +} + +bool Rect::SharesEdgeWith(const Rect& rect) const { + return (y() == rect.y() && height() == rect.height() && + (x() == rect.right() || right() == rect.x())) || + (x() == rect.x() && width() == rect.width() && + (y() == rect.bottom() || bottom() == rect.y())); +} + +int Rect::ManhattanDistanceToPoint(const Point& point) const { + int x_distance = + std::max(0, std::max(x() - point.x(), point.x() - right())); + int y_distance = + std::max(0, std::max(y() - point.y(), point.y() - bottom())); + + return x_distance + y_distance; +} + +int Rect::ManhattanInternalDistance(const Rect& rect) const { + Rect c(*this); + c.Union(rect); + + int x = std::max(0, c.width() - width() - rect.width() + 1); + int y = std::max(0, c.height() - height() - rect.height() + 1); + return x + y; +} + +std::string Rect::ToString() const { + return base::StringPrintf("%s %s", + origin().ToString().c_str(), + size().ToString().c_str()); +} + +bool Rect::ApproximatelyEqual(const Rect& rect, int tolerance) const { + return std::abs(x() - rect.x()) <= tolerance && + std::abs(y() - rect.y()) <= tolerance && + std::abs(right() - rect.right()) <= tolerance && + std::abs(bottom() - rect.bottom()) <= tolerance; +} + +Rect operator+(const Rect& lhs, const Vector2d& rhs) { + Rect result(lhs); + result += rhs; + return result; +} + +Rect operator-(const Rect& lhs, const Vector2d& rhs) { + Rect result(lhs); + result -= rhs; + return result; +} + +Rect IntersectRects(const Rect& a, const Rect& b) { + Rect result = a; + result.Intersect(b); + return result; +} + +Rect UnionRects(const Rect& a, const Rect& b) { + Rect result = a; + result.Union(b); + return result; +} + +Rect SubtractRects(const Rect& a, const Rect& b) { + Rect result = a; + result.Subtract(b); + return result; +} + +Rect BoundingRect(const Point& p1, const Point& p2) { + Rect result; + result.SetByBounds(std::min(p1.x(), p2.x()), std::min(p1.y(), p2.y()), + std::max(p1.x(), p2.x()), std::max(p1.y(), p2.y())); + return result; +} + +} // namespace gfx diff --git a/ui/gfx/geometry/rect.h b/ui/gfx/geometry/rect.h new file mode 100644 index 0000000..1858d44 --- /dev/null +++ b/ui/gfx/geometry/rect.h @@ -0,0 +1,350 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines a simple integer rectangle class. The containment semantics +// are array-like; that is, the coordinate (x, y) is considered to be +// contained by the rectangle, but the coordinate (x + width, y) is not. +// The class will happily let you create malformed rectangles (that is, +// rectangles with negative width and/or height), but there will be assertions +// in the operations (such as Contains()) to complain in this case. + +#ifndef UI_GFX_GEOMETRY_RECT_H_ +#define UI_GFX_GEOMETRY_RECT_H_ + +#include +#include +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/safe_integer_conversions.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/vector2d.h" + +#if defined(OS_WIN) +typedef struct tagRECT RECT; +#elif defined(OS_MACOSX) +typedef struct CGRect CGRect; +#endif + +namespace gfx { + +class Insets; + +class GFX_EXPORT Rect { + public: + constexpr Rect() = default; + constexpr Rect(int width, int height) : size_(width, height) {} + constexpr Rect(int x, int y, int width, int height) + : origin_(x, y), + size_(GetClampedValue(x, width), GetClampedValue(y, height)) {} + constexpr explicit Rect(const Size& size) : size_(size) {} + constexpr Rect(const Point& origin, const Size& size) + : origin_(origin), + size_(GetClampedValue(origin.x(), size.width()), + GetClampedValue(origin.y(), size.height())) {} + +#if defined(OS_WIN) + explicit Rect(const RECT& r); +#elif defined(OS_MACOSX) + explicit Rect(const CGRect& r); +#endif + +#if defined(OS_WIN) + // Construct an equivalent Win32 RECT object. + RECT ToRECT() const; +#elif defined(OS_MACOSX) + // Construct an equivalent CoreGraphics object. + CGRect ToCGRect() const; +#endif + + constexpr int x() const { return origin_.x(); } + void set_x(int x) { + origin_.set_x(x); + size_.set_width(GetClampedValue(x, width())); + } + + constexpr int y() const { return origin_.y(); } + void set_y(int y) { + origin_.set_y(y); + size_.set_height(GetClampedValue(y, height())); + } + + constexpr int width() const { return size_.width(); } + void set_width(int width) { size_.set_width(GetClampedValue(x(), width)); } + + constexpr int height() const { return size_.height(); } + void set_height(int height) { + size_.set_height(GetClampedValue(y(), height)); + } + + constexpr const Point& origin() const { return origin_; } + void set_origin(const Point& origin) { + origin_ = origin; + // Ensure that width and height remain valid. + set_width(width()); + set_height(height()); + } + + constexpr const Size& size() const { return size_; } + void set_size(const Size& size) { + set_width(size.width()); + set_height(size.height()); + } + + constexpr int right() const { return x() + width(); } + constexpr int bottom() const { return y() + height(); } + + constexpr Point top_right() const { return Point(right(), y()); } + constexpr Point bottom_left() const { return Point(x(), bottom()); } + constexpr Point bottom_right() const { return Point(right(), bottom()); } + + Vector2d OffsetFromOrigin() const { return Vector2d(x(), y()); } + + void SetRect(int x, int y, int width, int height) { + origin_.SetPoint(x, y); + // Ensure that width and height remain valid. + set_width(width); + set_height(height); + } + + // Use in place of SetRect() when you know the edges of the rectangle instead + // of the dimensions, rather than trying to determine the width/height + // yourself. This safely handles cases where the width/height would overflow. + void SetByBounds(int left, int top, int right, int bottom); + + // Shrink the rectangle by a horizontal and vertical distance on all sides. + void Inset(int horizontal, int vertical) { + Inset(horizontal, vertical, horizontal, vertical); + } + + // Shrink the rectangle by the given insets. + void Inset(const Insets& insets); + + // Shrink the rectangle by the specified amount on each side. + void Inset(int left, int top, int right, int bottom); + + // Move the rectangle by a horizontal and vertical distance. + void Offset(int horizontal, int vertical); + void Offset(const Vector2d& distance) { Offset(distance.x(), distance.y()); } + void operator+=(const Vector2d& offset); + void operator-=(const Vector2d& offset); + + Insets InsetsFrom(const Rect& inner) const; + + // Returns true if the area of the rectangle is zero. + bool IsEmpty() const { return size_.IsEmpty(); } + + // A rect is less than another rect if its origin is less than + // the other rect's origin. If the origins are equal, then the + // shortest rect is less than the other. If the origin and the + // height are equal, then the narrowest rect is less than. + // This comparison is required to use Rects in sets, or sorted + // vectors. + bool operator<(const Rect& other) const; + + // Returns true if the point identified by point_x and point_y falls inside + // this rectangle. The point (x, y) is inside the rectangle, but the + // point (x + width, y + height) is not. + bool Contains(int point_x, int point_y) const; + + // Returns true if the specified point is contained by this rectangle. + bool Contains(const Point& point) const { + return Contains(point.x(), point.y()); + } + + // Returns true if this rectangle contains the specified rectangle. + bool Contains(const Rect& rect) const; + + // Returns true if this rectangle intersects the specified rectangle. + // An empty rectangle doesn't intersect any rectangle. + bool Intersects(const Rect& rect) const; + + // Computes the intersection of this rectangle with the given rectangle. + void Intersect(const Rect& rect); + + // Computes the union of this rectangle with the given rectangle. The union + // is the smallest rectangle containing both rectangles. + void Union(const Rect& rect); + + // Computes the rectangle resulting from subtracting |rect| from |*this|, + // i.e. the bounding rect of |Region(*this) - Region(rect)|. + void Subtract(const Rect& rect); + + // Fits as much of the receiving rectangle into the supplied rectangle as + // possible, becoming the result. For example, if the receiver had + // a x-location of 2 and a width of 4, and the supplied rectangle had + // an x-location of 0 with a width of 5, the returned rectangle would have + // an x-location of 1 with a width of 4. + void AdjustToFit(const Rect& rect); + + // Returns the center of this rectangle. + Point CenterPoint() const; + + // Becomes a rectangle that has the same center point but with a size capped + // at given |size|. + void ClampToCenteredSize(const Size& size); + + // Splits |this| in two halves, |left_half| and |right_half|. + void SplitVertically(Rect* left_half, Rect* right_half) const; + + // Returns true if this rectangle shares an entire edge (i.e., same width or + // same height) with the given rectangle, and the rectangles do not overlap. + bool SharesEdgeWith(const Rect& rect) const; + + // Returns the manhattan distance from the rect to the point. If the point is + // inside the rect, returns 0. + int ManhattanDistanceToPoint(const Point& point) const; + + // Returns the manhattan distance between the contents of this rect and the + // contents of the given rect. That is, if the intersection of the two rects + // is non-empty then the function returns 0. If the rects share a side, it + // returns the smallest non-zero value appropriate for int. + int ManhattanInternalDistance(const Rect& rect) const; + + std::string ToString() const; + + bool ApproximatelyEqual(const Rect& rect, int tolerance) const; + + private: + gfx::Point origin_; + gfx::Size size_; + + // Returns true iff a+b would overflow max int. + static constexpr bool AddWouldOverflow(int a, int b) { + // In this function, GCC tries to make optimizations that would only work if + // max - a wouldn't overflow but it isn't smart enough to notice that a > 0. + // So cast everything to unsigned to avoid this. As it is guaranteed that + // max - a and b are both already positive, the cast is a noop. + // + // This is intended to be: a > 0 && max - a < b + return a > 0 && b > 0 && + static_cast(std::numeric_limits::max() - a) < + static_cast(b); + } + + // Clamp the size to avoid integer overflow in bottom() and right(). + // This returns the width given an origin and a width. + // TODO(enne): this should probably use base::SaturatedAddition, but that + // function is not a constexpr. + static constexpr int GetClampedValue(int origin, int size) { + return AddWouldOverflow(origin, size) + ? std::numeric_limits::max() - origin + : size; + } +}; + +inline bool operator==(const Rect& lhs, const Rect& rhs) { + return lhs.origin() == rhs.origin() && lhs.size() == rhs.size(); +} + +inline bool operator!=(const Rect& lhs, const Rect& rhs) { + return !(lhs == rhs); +} + +GFX_EXPORT Rect operator+(const Rect& lhs, const Vector2d& rhs); +GFX_EXPORT Rect operator-(const Rect& lhs, const Vector2d& rhs); + +inline Rect operator+(const Vector2d& lhs, const Rect& rhs) { + return rhs + lhs; +} + +GFX_EXPORT Rect IntersectRects(const Rect& a, const Rect& b); +GFX_EXPORT Rect UnionRects(const Rect& a, const Rect& b); +GFX_EXPORT Rect SubtractRects(const Rect& a, const Rect& b); + +// Constructs a rectangle with |p1| and |p2| as opposite corners. +// +// This could also be thought of as "the smallest rect that contains both +// points", except that we consider points on the right/bottom edges of the +// rect to be outside the rect. So technically one or both points will not be +// contained within the rect, because they will appear on one of these edges. +GFX_EXPORT Rect BoundingRect(const Point& p1, const Point& p2); + +// Scales the rect and returns the enclosing rect. Use this only the inputs are +// known to not overflow. Use ScaleToEnclosingRectSafe if the inputs are +// unknown and need to use saturated math. +inline Rect ScaleToEnclosingRect(const Rect& rect, + float x_scale, + float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return rect; + // These next functions cast instead of using e.g. ToFlooredInt() because we + // haven't checked to ensure that the clamping behavior of the helper + // functions doesn't degrade performance, and callers shouldn't be passing + // values that cause overflow anyway. + DCHECK(base::IsValueInRangeForNumericType( + std::floor(rect.x() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::floor(rect.y() * y_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::ceil(rect.right() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::ceil(rect.bottom() * y_scale))); + int x = static_cast(std::floor(rect.x() * x_scale)); + int y = static_cast(std::floor(rect.y() * y_scale)); + int r = rect.width() == 0 ? + x : static_cast(std::ceil(rect.right() * x_scale)); + int b = rect.height() == 0 ? + y : static_cast(std::ceil(rect.bottom() * y_scale)); + return Rect(x, y, r - x, b - y); +} + +inline Rect ScaleToEnclosingRect(const Rect& rect, float scale) { + return ScaleToEnclosingRect(rect, scale, scale); +} + +// ScaleToEnclosingRect but clamping instead of asserting if the resulting rect +// would overflow. +inline Rect ScaleToEnclosingRectSafe(const Rect& rect, + float x_scale, + float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return rect; + int x = base::saturated_cast(std::floor(rect.x() * x_scale)); + int y = base::saturated_cast(std::floor(rect.y() * y_scale)); + int w = base::saturated_cast(std::ceil(rect.width() * x_scale)); + int h = base::saturated_cast(std::ceil(rect.height() * y_scale)); + return Rect(x, y, w, h); +} + +inline Rect ScaleToEnclosingRectSafe(const Rect& rect, float scale) { + return ScaleToEnclosingRectSafe(rect, scale, scale); +} + +inline Rect ScaleToEnclosedRect(const Rect& rect, + float x_scale, + float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return rect; + DCHECK(base::IsValueInRangeForNumericType( + std::ceil(rect.x() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::ceil(rect.y() * y_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::floor(rect.right() * x_scale))); + DCHECK(base::IsValueInRangeForNumericType( + std::floor(rect.bottom() * y_scale))); + int x = static_cast(std::ceil(rect.x() * x_scale)); + int y = static_cast(std::ceil(rect.y() * y_scale)); + int r = rect.width() == 0 ? + x : static_cast(std::floor(rect.right() * x_scale)); + int b = rect.height() == 0 ? + y : static_cast(std::floor(rect.bottom() * y_scale)); + return Rect(x, y, r - x, b - y); +} + +inline Rect ScaleToEnclosedRect(const Rect& rect, float scale) { + return ScaleToEnclosedRect(rect, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Rect& rect, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_RECT_H_ diff --git a/ui/gfx/geometry/rect_f.cc b/ui/gfx/geometry/rect_f.cc new file mode 100644 index 0000000..a08e384 --- /dev/null +++ b/ui/gfx/geometry/rect_f.cc @@ -0,0 +1,259 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/rect_f.h" + +#include + +#if defined(OS_IOS) +#include +#elif defined(OS_MACOSX) +#include +#endif + +#include "base/logging.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/insets_f.h" +#include "ui/gfx/geometry/safe_integer_conversions.h" + +namespace gfx { + +static void AdjustAlongAxis(float dst_origin, + float dst_size, + float* origin, + float* size) { + *size = std::min(dst_size, *size); + if (*origin < dst_origin) + *origin = dst_origin; + else + *origin = std::min(dst_origin + dst_size, *origin + *size) - *size; +} + +#if defined(OS_MACOSX) +RectF::RectF(const CGRect& r) + : origin_(r.origin.x, r.origin.y), size_(r.size.width, r.size.height) { +} + +CGRect RectF::ToCGRect() const { + return CGRectMake(x(), y(), width(), height()); +} +#endif + +void RectF::Inset(const InsetsF& insets) { + Inset(insets.left(), insets.top(), insets.right(), insets.bottom()); +} + +void RectF::Inset(float left, float top, float right, float bottom) { + origin_ += Vector2dF(left, top); + set_width(std::max(width() - left - right, static_cast(0))); + set_height(std::max(height() - top - bottom, static_cast(0))); +} + +void RectF::Offset(float horizontal, float vertical) { + origin_ += Vector2dF(horizontal, vertical); +} + +void RectF::operator+=(const Vector2dF& offset) { + origin_ += offset; +} + +void RectF::operator-=(const Vector2dF& offset) { + origin_ -= offset; +} + +InsetsF RectF::InsetsFrom(const RectF& inner) const { + return InsetsF(inner.y() - y(), + inner.x() - x(), + bottom() - inner.bottom(), + right() - inner.right()); +} + +bool RectF::operator<(const RectF& other) const { + if (origin_ == other.origin_) { + if (width() == other.width()) { + return height() < other.height(); + } else { + return width() < other.width(); + } + } else { + return origin_ < other.origin_; + } +} + +bool RectF::Contains(float point_x, float point_y) const { + return (point_x >= x()) && (point_x < right()) && (point_y >= y()) && + (point_y < bottom()); +} + +bool RectF::Contains(const RectF& rect) const { + return (rect.x() >= x() && rect.right() <= right() && rect.y() >= y() && + rect.bottom() <= bottom()); +} + +bool RectF::Intersects(const RectF& rect) const { + return !(IsEmpty() || rect.IsEmpty() || rect.x() >= right() || + rect.right() <= x() || rect.y() >= bottom() || rect.bottom() <= y()); +} + +void RectF::Intersect(const RectF& rect) { + if (IsEmpty() || rect.IsEmpty()) { + SetRect(0, 0, 0, 0); + return; + } + + float rx = std::max(x(), rect.x()); + float ry = std::max(y(), rect.y()); + float rr = std::min(right(), rect.right()); + float rb = std::min(bottom(), rect.bottom()); + + if (rx >= rr || ry >= rb) + rx = ry = rr = rb = 0; // non-intersecting + + SetRect(rx, ry, rr - rx, rb - ry); +} + +void RectF::Union(const RectF& rect) { + if (IsEmpty()) { + *this = rect; + return; + } + if (rect.IsEmpty()) + return; + + float rx = std::min(x(), rect.x()); + float ry = std::min(y(), rect.y()); + float rr = std::max(right(), rect.right()); + float rb = std::max(bottom(), rect.bottom()); + + SetRect(rx, ry, rr - rx, rb - ry); +} + +void RectF::Subtract(const RectF& rect) { + if (!Intersects(rect)) + return; + if (rect.Contains(*static_cast(this))) { + SetRect(0, 0, 0, 0); + return; + } + + float rx = x(); + float ry = y(); + float rr = right(); + float rb = bottom(); + + if (rect.y() <= y() && rect.bottom() >= bottom()) { + // complete intersection in the y-direction + if (rect.x() <= x()) { + rx = rect.right(); + } else if (rect.right() >= right()) { + rr = rect.x(); + } + } else if (rect.x() <= x() && rect.right() >= right()) { + // complete intersection in the x-direction + if (rect.y() <= y()) { + ry = rect.bottom(); + } else if (rect.bottom() >= bottom()) { + rb = rect.y(); + } + } + SetRect(rx, ry, rr - rx, rb - ry); +} + +void RectF::AdjustToFit(const RectF& rect) { + float new_x = x(); + float new_y = y(); + float new_width = width(); + float new_height = height(); + AdjustAlongAxis(rect.x(), rect.width(), &new_x, &new_width); + AdjustAlongAxis(rect.y(), rect.height(), &new_y, &new_height); + SetRect(new_x, new_y, new_width, new_height); +} + +PointF RectF::CenterPoint() const { + return PointF(x() + width() / 2, y() + height() / 2); +} + +void RectF::ClampToCenteredSize(const SizeF& size) { + float new_width = std::min(width(), size.width()); + float new_height = std::min(height(), size.height()); + float new_x = x() + (width() - new_width) / 2; + float new_y = y() + (height() - new_height) / 2; + SetRect(new_x, new_y, new_width, new_height); +} + +void RectF::SplitVertically(RectF* left_half, RectF* right_half) const { + DCHECK(left_half); + DCHECK(right_half); + + left_half->SetRect(x(), y(), width() / 2, height()); + right_half->SetRect( + left_half->right(), y(), width() - left_half->width(), height()); +} + +bool RectF::SharesEdgeWith(const RectF& rect) const { + return (y() == rect.y() && height() == rect.height() && + (x() == rect.right() || right() == rect.x())) || + (x() == rect.x() && width() == rect.width() && + (y() == rect.bottom() || bottom() == rect.y())); +} + +float RectF::ManhattanDistanceToPoint(const PointF& point) const { + float x_distance = + std::max(0, std::max(x() - point.x(), point.x() - right())); + float y_distance = + std::max(0, std::max(y() - point.y(), point.y() - bottom())); + + return x_distance + y_distance; +} + +float RectF::ManhattanInternalDistance(const RectF& rect) const { + RectF c(*this); + c.Union(rect); + + static const float kEpsilon = std::numeric_limits::epsilon(); + float x = std::max(0.f, c.width() - width() - rect.width() + kEpsilon); + float y = std::max(0.f, c.height() - height() - rect.height() + kEpsilon); + return x + y; +} + +bool RectF::IsExpressibleAsRect() const { + return IsExpressibleAsInt(x()) && IsExpressibleAsInt(y()) && + IsExpressibleAsInt(width()) && IsExpressibleAsInt(height()) && + IsExpressibleAsInt(right()) && IsExpressibleAsInt(bottom()); +} + +std::string RectF::ToString() const { + return base::StringPrintf("%s %s", + origin().ToString().c_str(), + size().ToString().c_str()); +} + +RectF IntersectRects(const RectF& a, const RectF& b) { + RectF result = a; + result.Intersect(b); + return result; +} + +RectF UnionRects(const RectF& a, const RectF& b) { + RectF result = a; + result.Union(b); + return result; +} + +RectF SubtractRects(const RectF& a, const RectF& b) { + RectF result = a; + result.Subtract(b); + return result; +} + +RectF BoundingRect(const PointF& p1, const PointF& p2) { + float rx = std::min(p1.x(), p2.x()); + float ry = std::min(p1.y(), p2.y()); + float rr = std::max(p1.x(), p2.x()); + float rb = std::max(p1.y(), p2.y()); + return RectF(rx, ry, rr - rx, rb - ry); +} + +} // namespace gfx diff --git a/ui/gfx/geometry/rect_f.h b/ui/gfx/geometry/rect_f.h new file mode 100644 index 0000000..9d99052 --- /dev/null +++ b/ui/gfx/geometry/rect_f.h @@ -0,0 +1,242 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_RECT_F_H_ +#define UI_GFX_GEOMETRY_RECT_F_H_ + +#include +#include + +#include "build/build_config.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size_f.h" +#include "ui/gfx/geometry/vector2d_f.h" + +#if defined(OS_MACOSX) +typedef struct CGRect CGRect; +#endif + +namespace gfx { + +class InsetsF; + +// A floating version of gfx::Rect. +class GFX_EXPORT RectF { + public: + constexpr RectF() = default; + constexpr RectF(float width, float height) : size_(width, height) {} + constexpr RectF(float x, float y, float width, float height) + : origin_(x, y), size_(width, height) {} + constexpr explicit RectF(const SizeF& size) : size_(size) {} + constexpr RectF(const PointF& origin, const SizeF& size) + : origin_(origin), size_(size) {} + + constexpr explicit RectF(const Rect& r) + : RectF(static_cast(r.x()), + static_cast(r.y()), + static_cast(r.width()), + static_cast(r.height())) {} + +#if defined(OS_MACOSX) + explicit RectF(const CGRect& r); + // Construct an equivalent CoreGraphics object. + CGRect ToCGRect() const; +#endif + + constexpr float x() const { return origin_.x(); } + void set_x(float x) { origin_.set_x(x); } + + constexpr float y() const { return origin_.y(); } + void set_y(float y) { origin_.set_y(y); } + + constexpr float width() const { return size_.width(); } + void set_width(float width) { size_.set_width(width); } + + constexpr float height() const { return size_.height(); } + void set_height(float height) { size_.set_height(height); } + + constexpr const PointF& origin() const { return origin_; } + void set_origin(const PointF& origin) { origin_ = origin; } + + constexpr const SizeF& size() const { return size_; } + void set_size(const SizeF& size) { size_ = size; } + + constexpr float right() const { return x() + width(); } + constexpr float bottom() const { return y() + height(); } + + constexpr PointF top_right() const { return PointF(right(), y()); } + constexpr PointF bottom_left() const { return PointF(x(), bottom()); } + constexpr PointF bottom_right() const { return PointF(right(), bottom()); } + + Vector2dF OffsetFromOrigin() const { return Vector2dF(x(), y()); } + + void SetRect(float x, float y, float width, float height) { + origin_.SetPoint(x, y); + size_.SetSize(width, height); + } + + // Shrink the rectangle by a horizontal and vertical distance on all sides. + void Inset(float horizontal, float vertical) { + Inset(horizontal, vertical, horizontal, vertical); + } + + // Shrink the rectangle by the given insets. + void Inset(const InsetsF& insets); + + // Shrink the rectangle by the specified amount on each side. + void Inset(float left, float top, float right, float bottom); + + // Move the rectangle by a horizontal and vertical distance. + void Offset(float horizontal, float vertical); + void Offset(const Vector2dF& distance) { Offset(distance.x(), distance.y()); } + void operator+=(const Vector2dF& offset); + void operator-=(const Vector2dF& offset); + + InsetsF InsetsFrom(const RectF& inner) const; + + // Returns true if the area of the rectangle is zero. + bool IsEmpty() const { return size_.IsEmpty(); } + + // A rect is less than another rect if its origin is less than + // the other rect's origin. If the origins are equal, then the + // shortest rect is less than the other. If the origin and the + // height are equal, then the narrowest rect is less than. + // This comparison is required to use Rects in sets, or sorted + // vectors. + bool operator<(const RectF& other) const; + + // Returns true if the point identified by point_x and point_y falls inside + // this rectangle. The point (x, y) is inside the rectangle, but the + // point (x + width, y + height) is not. + bool Contains(float point_x, float point_y) const; + + // Returns true if the specified point is contained by this rectangle. + bool Contains(const PointF& point) const { + return Contains(point.x(), point.y()); + } + + // Returns true if this rectangle contains the specified rectangle. + bool Contains(const RectF& rect) const; + + // Returns true if this rectangle intersects the specified rectangle. + // An empty rectangle doesn't intersect any rectangle. + bool Intersects(const RectF& rect) const; + + // Computes the intersection of this rectangle with the given rectangle. + void Intersect(const RectF& rect); + + // Computes the union of this rectangle with the given rectangle. The union + // is the smallest rectangle containing both rectangles. + void Union(const RectF& rect); + + // Computes the rectangle resulting from subtracting |rect| from |*this|, + // i.e. the bounding rect of |Region(*this) - Region(rect)|. + void Subtract(const RectF& rect); + + // Fits as much of the receiving rectangle into the supplied rectangle as + // possible, becoming the result. For example, if the receiver had + // a x-location of 2 and a width of 4, and the supplied rectangle had + // an x-location of 0 with a width of 5, the returned rectangle would have + // an x-location of 1 with a width of 4. + void AdjustToFit(const RectF& rect); + + // Returns the center of this rectangle. + PointF CenterPoint() const; + + // Becomes a rectangle that has the same center point but with a size capped + // at given |size|. + void ClampToCenteredSize(const SizeF& size); + + // Splits |this| in two halves, |left_half| and |right_half|. + void SplitVertically(RectF* left_half, RectF* right_half) const; + + // Returns true if this rectangle shares an entire edge (i.e., same width or + // same height) with the given rectangle, and the rectangles do not overlap. + bool SharesEdgeWith(const RectF& rect) const; + + // Returns the manhattan distance from the rect to the point. If the point is + // inside the rect, returns 0. + float ManhattanDistanceToPoint(const PointF& point) const; + + // Returns the manhattan distance between the contents of this rect and the + // contents of the given rect. That is, if the intersection of the two rects + // is non-empty then the function returns 0. If the rects share a side, it + // returns the smallest non-zero value appropriate for float. + float ManhattanInternalDistance(const RectF& rect) const; + + // Scales the rectangle by |scale|. + void Scale(float scale) { + Scale(scale, scale); + } + + void Scale(float x_scale, float y_scale) { + set_origin(ScalePoint(origin(), x_scale, y_scale)); + set_size(ScaleSize(size(), x_scale, y_scale)); + } + + // This method reports if the RectF can be safely converted to an integer + // Rect. When it is false, some dimension of the RectF is outside the bounds + // of what an integer can represent, and converting it to a Rect will require + // clamping. + bool IsExpressibleAsRect() const; + + std::string ToString() const; + + private: + PointF origin_; + SizeF size_; +}; + +inline bool operator==(const RectF& lhs, const RectF& rhs) { + return lhs.origin() == rhs.origin() && lhs.size() == rhs.size(); +} + +inline bool operator!=(const RectF& lhs, const RectF& rhs) { + return !(lhs == rhs); +} + +inline RectF operator+(const RectF& lhs, const Vector2dF& rhs) { + return RectF(lhs.x() + rhs.x(), lhs.y() + rhs.y(), + lhs.width(), lhs.height()); +} + +inline RectF operator-(const RectF& lhs, const Vector2dF& rhs) { + return RectF(lhs.x() - rhs.x(), lhs.y() - rhs.y(), + lhs.width(), lhs.height()); +} + +inline RectF operator+(const Vector2dF& lhs, const RectF& rhs) { + return rhs + lhs; +} + +GFX_EXPORT RectF IntersectRects(const RectF& a, const RectF& b); +GFX_EXPORT RectF UnionRects(const RectF& a, const RectF& b); +GFX_EXPORT RectF SubtractRects(const RectF& a, const RectF& b); + +inline RectF ScaleRect(const RectF& r, float x_scale, float y_scale) { + return RectF(r.x() * x_scale, r.y() * y_scale, + r.width() * x_scale, r.height() * y_scale); +} + +inline RectF ScaleRect(const RectF& r, float scale) { + return ScaleRect(r, scale, scale); +} + +// Constructs a rectangle with |p1| and |p2| as opposite corners. +// +// This could also be thought of as "the smallest rect that contains both +// points", except that we consider points on the right/bottom edges of the +// rect to be outside the rect. So technically one or both points will not be +// contained within the rect, because they will appear on one of these edges. +GFX_EXPORT RectF BoundingRect(const PointF& p1, const PointF& p2); + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const RectF& rect, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_RECT_F_H_ diff --git a/ui/gfx/geometry/safe_integer_conversions.h b/ui/gfx/geometry/safe_integer_conversions.h new file mode 100644 index 0000000..5efe134 --- /dev/null +++ b/ui/gfx/geometry/safe_integer_conversions.h @@ -0,0 +1,62 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SAFE_INTEGER_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_SAFE_INTEGER_CONVERSIONS_H_ + +#include +#include + +#include "base/numerics/safe_conversions.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +inline int ToFlooredInt(float value) { + return base::saturated_cast(std::floor(value)); +} + +inline int ToCeiledInt(float value) { + return base::saturated_cast(std::ceil(value)); +} + +inline int ToFlooredInt(double value) { + return base::saturated_cast(std::floor(value)); +} + +inline int ToCeiledInt(double value) { + return base::saturated_cast(std::ceil(value)); +} + +inline int ToRoundedInt(float value) { + float rounded; + if (value >= 0.0f) + rounded = std::floor(value + 0.5f); + else + rounded = std::ceil(value - 0.5f); + return base::saturated_cast(rounded); +} + +inline int ToRoundedInt(double value) { + double rounded; + if (value >= 0.0) + rounded = std::floor(value + 0.5); + else + rounded = std::ceil(value - 0.5); + return base::saturated_cast(rounded); +} + +inline bool IsExpressibleAsInt(float value) { + if (value != value) + return false; // no int NaN. + if (value > std::numeric_limits::max()) + return false; + if (value < std::numeric_limits::min()) + return false; + return true; +} + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_SAFE_INTEGER_CONVERSIONS_H_ diff --git a/ui/gfx/geometry/size.cc b/ui/gfx/geometry/size.cc new file mode 100644 index 0000000..6948672 --- /dev/null +++ b/ui/gfx/geometry/size.cc @@ -0,0 +1,115 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/size.h" + +#if defined(OS_WIN) +#include +#elif defined(OS_IOS) +#include +#elif defined(OS_MACOSX) +#include +#endif + +#include "base/numerics/safe_math.h" +#include "base/numerics/saturated_arithmetic.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/safe_integer_conversions.h" +#include "ui/gfx/geometry/size_conversions.h" + +namespace gfx { + +#if defined(OS_MACOSX) +Size::Size(const CGSize& s) + : width_(s.width < 0 ? 0 : s.width), + height_(s.height < 0 ? 0 : s.height) { +} + +Size& Size::operator=(const CGSize& s) { + set_width(s.width); + set_height(s.height); + return *this; +} +#endif + +#if defined(OS_WIN) +SIZE Size::ToSIZE() const { + SIZE s; + s.cx = width(); + s.cy = height(); + return s; +} +#elif defined(OS_MACOSX) +CGSize Size::ToCGSize() const { + return CGSizeMake(width(), height()); +} +#endif + +int Size::GetArea() const { + return GetCheckedArea().ValueOrDie(); +} + +base::CheckedNumeric Size::GetCheckedArea() const { + base::CheckedNumeric checked_area = width(); + checked_area *= height(); + return checked_area; +} + +void Size::Enlarge(int grow_width, int grow_height) { + SetSize(base::SaturatedAddition(width(), grow_width), + base::SaturatedAddition(height(), grow_height)); +} + +void Size::SetToMin(const Size& other) { + width_ = width() <= other.width() ? width() : other.width(); + height_ = height() <= other.height() ? height() : other.height(); +} + +void Size::SetToMax(const Size& other) { + width_ = width() >= other.width() ? width() : other.width(); + height_ = height() >= other.height() ? height() : other.height(); +} + +std::string Size::ToString() const { + return base::StringPrintf("%dx%d", width(), height()); +} + +Size ScaleToCeiledSize(const Size& size, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return size; + return ToCeiledSize(ScaleSize(gfx::SizeF(size), x_scale, y_scale)); +} + +Size ScaleToCeiledSize(const Size& size, float scale) { + if (scale == 1.f) + return size; + return ToCeiledSize(ScaleSize(gfx::SizeF(size), scale, scale)); +} + +Size ScaleToFlooredSize(const Size& size, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return size; + return ToFlooredSize(ScaleSize(gfx::SizeF(size), x_scale, y_scale)); +} + +Size ScaleToFlooredSize(const Size& size, float scale) { + if (scale == 1.f) + return size; + return ToFlooredSize(ScaleSize(gfx::SizeF(size), scale, scale)); +} + +Size ScaleToRoundedSize(const Size& size, float x_scale, float y_scale) { + if (x_scale == 1.f && y_scale == 1.f) + return size; + return ToRoundedSize(ScaleSize(gfx::SizeF(size), x_scale, y_scale)); +} + +Size ScaleToRoundedSize(const Size& size, float scale) { + if (scale == 1.f) + return size; + return ToRoundedSize(ScaleSize(gfx::SizeF(size), scale, scale)); +} + +} // namespace gfx diff --git a/ui/gfx/geometry/size.h b/ui/gfx/geometry/size.h new file mode 100644 index 0000000..8ce4178 --- /dev/null +++ b/ui/gfx/geometry/size.h @@ -0,0 +1,103 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SIZE_H_ +#define UI_GFX_GEOMETRY_SIZE_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/numerics/safe_math.h" +#include "build/build_config.h" +#include "ui/gfx/gfx_export.h" + +#if defined(OS_WIN) +typedef struct tagSIZE SIZE; +#elif defined(OS_MACOSX) +typedef struct CGSize CGSize; +#endif + +namespace gfx { + +// A size has width and height values. +class GFX_EXPORT Size { + public: + constexpr Size() : width_(0), height_(0) {} + constexpr Size(int width, int height) + : width_(width < 0 ? 0 : width), height_(height < 0 ? 0 : height) {} +#if defined(OS_MACOSX) + explicit Size(const CGSize& s); +#endif + +#if defined(OS_MACOSX) + Size& operator=(const CGSize& s); +#endif + +#if defined(OS_WIN) + SIZE ToSIZE() const; +#elif defined(OS_MACOSX) + CGSize ToCGSize() const; +#endif + + constexpr int width() const { return width_; } + constexpr int height() const { return height_; } + + void set_width(int width) { width_ = width < 0 ? 0 : width; } + void set_height(int height) { height_ = height < 0 ? 0 : height; } + + // This call will CHECK if the area of this size would overflow int. + int GetArea() const; + // Returns a checked numeric representation of the area. + base::CheckedNumeric GetCheckedArea() const; + + void SetSize(int width, int height) { + set_width(width); + set_height(height); + } + + void Enlarge(int grow_width, int grow_height); + + void SetToMin(const Size& other); + void SetToMax(const Size& other); + + bool IsEmpty() const { return !width() || !height(); } + + std::string ToString() const; + + private: + int width_; + int height_; +}; + +inline bool operator==(const Size& lhs, const Size& rhs) { + return lhs.width() == rhs.width() && lhs.height() == rhs.height(); +} + +inline bool operator!=(const Size& lhs, const Size& rhs) { + return !(lhs == rhs); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Size& size, ::std::ostream* os); + +// Helper methods to scale a gfx::Size to a new gfx::Size. +GFX_EXPORT Size ScaleToCeiledSize(const Size& size, + float x_scale, + float y_scale); +GFX_EXPORT Size ScaleToCeiledSize(const Size& size, float scale); +GFX_EXPORT Size ScaleToFlooredSize(const Size& size, + float x_scale, + float y_scale); +GFX_EXPORT Size ScaleToFlooredSize(const Size& size, float scale); +GFX_EXPORT Size ScaleToRoundedSize(const Size& size, + float x_scale, + float y_scale); +GFX_EXPORT Size ScaleToRoundedSize(const Size& size, float scale); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_SIZE_H_ diff --git a/ui/gfx/geometry/size_conversions.cc b/ui/gfx/geometry/size_conversions.cc new file mode 100644 index 0000000..c924e86 --- /dev/null +++ b/ui/gfx/geometry/size_conversions.cc @@ -0,0 +1,30 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/size_conversions.h" + +#include "ui/gfx/geometry/safe_integer_conversions.h" + +namespace gfx { + +Size ToFlooredSize(const SizeF& size) { + int w = ToFlooredInt(size.width()); + int h = ToFlooredInt(size.height()); + return Size(w, h); +} + +Size ToCeiledSize(const SizeF& size) { + int w = ToCeiledInt(size.width()); + int h = ToCeiledInt(size.height()); + return Size(w, h); +} + +Size ToRoundedSize(const SizeF& size) { + int w = ToRoundedInt(size.width()); + int h = ToRoundedInt(size.height()); + return Size(w, h); +} + +} // namespace gfx + diff --git a/ui/gfx/geometry/size_conversions.h b/ui/gfx/geometry/size_conversions.h new file mode 100644 index 0000000..96fb79f --- /dev/null +++ b/ui/gfx/geometry/size_conversions.h @@ -0,0 +1,24 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SIZE_CONVERSIONS_H_ +#define UI_GFX_GEOMETRY_SIZE_CONVERSIONS_H_ + +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/geometry/size_f.h" + +namespace gfx { + +// Returns a Size with each component from the input SizeF floored. +GFX_EXPORT Size ToFlooredSize(const SizeF& size); + +// Returns a Size with each component from the input SizeF ceiled. +GFX_EXPORT Size ToCeiledSize(const SizeF& size); + +// Returns a Size with each component from the input SizeF rounded. +GFX_EXPORT Size ToRoundedSize(const SizeF& size); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_SIZE_CONVERSIONS_H_ diff --git a/ui/gfx/geometry/size_f.cc b/ui/gfx/geometry/size_f.cc new file mode 100644 index 0000000..6d08e18 --- /dev/null +++ b/ui/gfx/geometry/size_f.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/size_f.h" + +#include "base/strings/stringprintf.h" + +namespace gfx { + +float SizeF::GetArea() const { + return width() * height(); +} + +void SizeF::Enlarge(float grow_width, float grow_height) { + SetSize(width() + grow_width, height() + grow_height); +} + +void SizeF::SetToMin(const SizeF& other) { + width_ = width() <= other.width() ? width() : other.width(); + height_ = height() <= other.height() ? height() : other.height(); +} + +void SizeF::SetToMax(const SizeF& other) { + width_ = width() >= other.width() ? width() : other.width(); + height_ = height() >= other.height() ? height() : other.height(); +} + +std::string SizeF::ToString() const { + return base::StringPrintf("%fx%f", width(), height()); +} + +SizeF ScaleSize(const SizeF& s, float x_scale, float y_scale) { + SizeF scaled_s(s); + scaled_s.Scale(x_scale, y_scale); + return scaled_s; +} + +} // namespace gfx diff --git a/ui/gfx/geometry/size_f.h b/ui/gfx/geometry/size_f.h new file mode 100644 index 0000000..0757ef6 --- /dev/null +++ b/ui/gfx/geometry/size_f.h @@ -0,0 +1,97 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_GEOMETRY_SIZE_F_H_ +#define UI_GFX_GEOMETRY_SIZE_F_H_ + +#include +#include + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "ui/gfx/geometry/size.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +FORWARD_DECLARE_TEST(SizeTest, TrivialDimensionTests); +FORWARD_DECLARE_TEST(SizeTest, ClampsToZero); +FORWARD_DECLARE_TEST(SizeTest, ConsistentClamping); + +// A floating version of gfx::Size. +class GFX_EXPORT SizeF { + public: + constexpr SizeF() : width_(0.f), height_(0.f) {} + constexpr SizeF(float width, float height) + : width_(clamp(width)), height_(clamp(height)) {} + + constexpr explicit SizeF(const Size& size) + : SizeF(static_cast(size.width()), + static_cast(size.height())) {} + + constexpr float width() const { return width_; } + constexpr float height() const { return height_; } + + void set_width(float width) { width_ = clamp(width); } + void set_height(float height) { height_ = clamp(height); } + + float GetArea() const; + + void SetSize(float width, float height) { + set_width(width); + set_height(height); + } + + void Enlarge(float grow_width, float grow_height); + + void SetToMin(const SizeF& other); + void SetToMax(const SizeF& other); + + bool IsEmpty() const { return !width() || !height(); } + + void Scale(float scale) { + Scale(scale, scale); + } + + void Scale(float x_scale, float y_scale) { + SetSize(width() * x_scale, height() * y_scale); + } + + std::string ToString() const; + + private: + FRIEND_TEST_ALL_PREFIXES(SizeTest, TrivialDimensionTests); + FRIEND_TEST_ALL_PREFIXES(SizeTest, ClampsToZero); + FRIEND_TEST_ALL_PREFIXES(SizeTest, ConsistentClamping); + + static constexpr float kTrivial = 8.f * std::numeric_limits::epsilon(); + + static constexpr float clamp(float f) { return f > kTrivial ? f : 0.f; } + + float width_; + float height_; +}; + +inline bool operator==(const SizeF& lhs, const SizeF& rhs) { + return lhs.width() == rhs.width() && lhs.height() == rhs.height(); +} + +inline bool operator!=(const SizeF& lhs, const SizeF& rhs) { + return !(lhs == rhs); +} + +GFX_EXPORT SizeF ScaleSize(const SizeF& p, float x_scale, float y_scale); + +inline SizeF ScaleSize(const SizeF& p, float scale) { + return ScaleSize(p, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const SizeF& size, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_SIZE_F_H_ diff --git a/ui/gfx/geometry/vector2d.cc b/ui/gfx/geometry/vector2d.cc new file mode 100644 index 0000000..2b4875c --- /dev/null +++ b/ui/gfx/geometry/vector2d.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/vector2d.h" + +#include + +#include "base/numerics/saturated_arithmetic.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +bool Vector2d::IsZero() const { + return x_ == 0 && y_ == 0; +} + +void Vector2d::Add(const Vector2d& other) { + x_ = base::SaturatedAddition(other.x_, x_); + y_ = base::SaturatedAddition(other.y_, y_); +} + +void Vector2d::Subtract(const Vector2d& other) { + x_ = base::SaturatedSubtraction(x_, other.x_); + y_ = base::SaturatedSubtraction(y_, other.y_); +} + +int64_t Vector2d::LengthSquared() const { + return static_cast(x_) * x_ + static_cast(y_) * y_; +} + +float Vector2d::Length() const { + return static_cast(std::sqrt(static_cast(LengthSquared()))); +} + +std::string Vector2d::ToString() const { + return base::StringPrintf("[%d %d]", x_, y_); +} + +} // namespace gfx diff --git a/ui/gfx/geometry/vector2d.h b/ui/gfx/geometry/vector2d.h new file mode 100644 index 0000000..4b45667 --- /dev/null +++ b/ui/gfx/geometry/vector2d.h @@ -0,0 +1,100 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines a simple integer vector class. This class is used to indicate a +// distance in two dimensions between two points. Subtracting two points should +// produce a vector, and adding a vector to a point produces the point at the +// vector's distance from the original point. + +#ifndef UI_GFX_GEOMETRY_VECTOR2D_H_ +#define UI_GFX_GEOMETRY_VECTOR2D_H_ + +#include + +#include +#include + +#include "ui/gfx/geometry/vector2d_f.h" +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class GFX_EXPORT Vector2d { + public: + constexpr Vector2d() : x_(0), y_(0) {} + constexpr Vector2d(int x, int y) : x_(x), y_(y) {} + + constexpr int x() const { return x_; } + void set_x(int x) { x_ = x; } + + constexpr int y() const { return y_; } + void set_y(int y) { y_ = y; } + + // True if both components of the vector are 0. + bool IsZero() const; + + // Add the components of the |other| vector to the current vector. + void Add(const Vector2d& other); + // Subtract the components of the |other| vector from the current vector. + void Subtract(const Vector2d& other); + + void operator+=(const Vector2d& other) { Add(other); } + void operator-=(const Vector2d& other) { Subtract(other); } + + void SetToMin(const Vector2d& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; + } + + void SetToMax(const Vector2d& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; + } + + // Gives the square of the diagonal length of the vector. Since this is + // cheaper to compute than Length(), it is useful when you want to compare + // relative lengths of different vectors without needing the actual lengths. + int64_t LengthSquared() const; + // Gives the diagonal length of the vector. + float Length() const; + + std::string ToString() const; + + operator Vector2dF() const { + return Vector2dF(static_cast(x()), static_cast(y())); + } + + private: + int x_; + int y_; +}; + +inline bool operator==(const Vector2d& lhs, const Vector2d& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y(); +} + +inline Vector2d operator-(const Vector2d& v) { + return Vector2d(-v.x(), -v.y()); +} + +inline Vector2d operator+(const Vector2d& lhs, const Vector2d& rhs) { + Vector2d result = lhs; + result.Add(rhs); + return result; +} + +inline Vector2d operator-(const Vector2d& lhs, const Vector2d& rhs) { + Vector2d result = lhs; + result.Add(-rhs); + return result; +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Vector2d& vector, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_VECTOR2D_H_ diff --git a/ui/gfx/geometry/vector2d_f.cc b/ui/gfx/geometry/vector2d_f.cc new file mode 100644 index 0000000..ccb15ae --- /dev/null +++ b/ui/gfx/geometry/vector2d_f.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/geometry/vector2d_f.h" + +#include + +#include "base/strings/stringprintf.h" + +namespace gfx { + +std::string Vector2dF::ToString() const { + return base::StringPrintf("[%f %f]", x_, y_); +} + +bool Vector2dF::IsZero() const { + return x_ == 0 && y_ == 0; +} + +void Vector2dF::Add(const Vector2dF& other) { + x_ += other.x_; + y_ += other.y_; +} + +void Vector2dF::Subtract(const Vector2dF& other) { + x_ -= other.x_; + y_ -= other.y_; +} + +double Vector2dF::LengthSquared() const { + return static_cast(x_) * x_ + static_cast(y_) * y_; +} + +float Vector2dF::Length() const { + return static_cast(std::sqrt(LengthSquared())); +} + +void Vector2dF::Scale(float x_scale, float y_scale) { + x_ *= x_scale; + y_ *= y_scale; +} + +double CrossProduct(const Vector2dF& lhs, const Vector2dF& rhs) { + return static_cast(lhs.x()) * rhs.y() - + static_cast(lhs.y()) * rhs.x(); +} + +double DotProduct(const Vector2dF& lhs, const Vector2dF& rhs) { + return static_cast(lhs.x()) * rhs.x() + + static_cast(lhs.y()) * rhs.y(); +} + +Vector2dF ScaleVector2d(const Vector2dF& v, float x_scale, float y_scale) { + Vector2dF scaled_v(v); + scaled_v.Scale(x_scale, y_scale); + return scaled_v; +} + +} // namespace gfx diff --git a/ui/gfx/geometry/vector2d_f.h b/ui/gfx/geometry/vector2d_f.h new file mode 100644 index 0000000..92f7f87 --- /dev/null +++ b/ui/gfx/geometry/vector2d_f.h @@ -0,0 +1,118 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines a simple float vector class. This class is used to indicate a +// distance in two dimensions between two points. Subtracting two points should +// produce a vector, and adding a vector to a point produces the point at the +// vector's distance from the original point. + +#ifndef UI_GFX_GEOMETRY_VECTOR2D_F_H_ +#define UI_GFX_GEOMETRY_VECTOR2D_F_H_ + +#include +#include + +#include "ui/gfx/gfx_export.h" + +namespace gfx { + +class GFX_EXPORT Vector2dF { + public: + constexpr Vector2dF() : x_(0), y_(0) {} + constexpr Vector2dF(float x, float y) : x_(x), y_(y) {} + + constexpr float x() const { return x_; } + void set_x(float x) { x_ = x; } + + constexpr float y() const { return y_; } + void set_y(float y) { y_ = y; } + + // True if both components of the vector are 0. + bool IsZero() const; + + // Add the components of the |other| vector to the current vector. + void Add(const Vector2dF& other); + // Subtract the components of the |other| vector from the current vector. + void Subtract(const Vector2dF& other); + + void operator+=(const Vector2dF& other) { Add(other); } + void operator-=(const Vector2dF& other) { Subtract(other); } + + void SetToMin(const Vector2dF& other) { + x_ = x_ <= other.x_ ? x_ : other.x_; + y_ = y_ <= other.y_ ? y_ : other.y_; + } + + void SetToMax(const Vector2dF& other) { + x_ = x_ >= other.x_ ? x_ : other.x_; + y_ = y_ >= other.y_ ? y_ : other.y_; + } + + // Gives the square of the diagonal length of the vector. + double LengthSquared() const; + // Gives the diagonal length of the vector. + float Length() const; + + // Scale the x and y components of the vector by |scale|. + void Scale(float scale) { Scale(scale, scale); } + // Scale the x and y components of the vector by |x_scale| and |y_scale| + // respectively. + void Scale(float x_scale, float y_scale); + + std::string ToString() const; + + private: + float x_; + float y_; +}; + +inline bool operator==(const Vector2dF& lhs, const Vector2dF& rhs) { + return lhs.x() == rhs.x() && lhs.y() == rhs.y(); +} + +inline bool operator!=(const Vector2dF& lhs, const Vector2dF& rhs) { + return !(lhs == rhs); +} + +inline Vector2dF operator-(const Vector2dF& v) { + return Vector2dF(-v.x(), -v.y()); +} + +inline Vector2dF operator+(const Vector2dF& lhs, const Vector2dF& rhs) { + Vector2dF result = lhs; + result.Add(rhs); + return result; +} + +inline Vector2dF operator-(const Vector2dF& lhs, const Vector2dF& rhs) { + Vector2dF result = lhs; + result.Add(-rhs); + return result; +} + +// Return the cross product of two vectors. +GFX_EXPORT double CrossProduct(const Vector2dF& lhs, const Vector2dF& rhs); + +// Return the dot product of two vectors. +GFX_EXPORT double DotProduct(const Vector2dF& lhs, const Vector2dF& rhs); + +// Return a vector that is |v| scaled by the given scale factors along each +// axis. +GFX_EXPORT Vector2dF ScaleVector2d(const Vector2dF& v, + float x_scale, + float y_scale); + +// Return a vector that is |v| scaled by the given scale factor. +inline Vector2dF ScaleVector2d(const Vector2dF& v, float scale) { + return ScaleVector2d(v, scale, scale); +} + +// This is declared here for use in gtest-based unit tests but is defined in +// the //ui/gfx:test_support target. Depend on that to use this in your unit +// test. This should not be used in production code - call ToString() instead. +void PrintTo(const Vector2dF& vector, ::std::ostream* os); + +} // namespace gfx + +#endif // UI_GFX_GEOMETRY_VECTOR2D_F_H_ diff --git a/ui/gfx/gfx_export.h b/ui/gfx/gfx_export.h new file mode 100644 index 0000000..20c8bb1 --- /dev/null +++ b/ui/gfx/gfx_export.h @@ -0,0 +1,29 @@ +// Copyright (c) 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 UI_GFX_GFX_EXPORT_H_ +#define UI_GFX_GFX_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_IMPLEMENTATION) +#define GFX_EXPORT __declspec(dllexport) +#else +#define GFX_EXPORT __declspec(dllimport) +#endif // defined(GFX_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_IMPLEMENTATION) +#define GFX_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_EXPORT +#endif + +#endif // UI_GFX_GFX_EXPORT_H_ diff --git a/ui/gfx/range/BUILD.gn b/ui/gfx/range/BUILD.gn new file mode 100644 index 0000000..0a8d8b2 --- /dev/null +++ b/ui/gfx/range/BUILD.gn @@ -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. + +component("range") { + sources = [ + "gfx_range_export.h", + "range.cc", + "range.h", + "range_f.cc", + "range_f.h", + "range_mac.mm", + "range_win.cc", + ] + + if (is_ios) { + set_sources_assignment_filter([]) + sources += [ "range_mac.mm" ] + set_sources_assignment_filter(sources_assignment_filter) + } + + configs += [ + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + "//build/config/compiler:no_size_t_to_int_warning", + ] + + defines = [ "GFX_RANGE_IMPLEMENTATION" ] + + deps = [ + "//base", + "//ui/gfx:gfx_export", + ] +} diff --git a/ui/gfx/range/gfx_range_export.h b/ui/gfx/range/gfx_range_export.h new file mode 100644 index 0000000..5634c49 --- /dev/null +++ b/ui/gfx/range/gfx_range_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 GFX_RANGE_EXPORT_H_ +#define GFX_RANGE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(GFX_RANGE_IMPLEMENTATION) +#define GFX_RANGE_EXPORT __declspec(dllexport) +#else +#define GFX_RANGE_EXPORT __declspec(dllimport) +#endif // defined(GFX_RANGE_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(GFX_RANGE_IMPLEMENTATION) +#define GFX_RANGE_EXPORT __attribute__((visibility("default"))) +#else +#define GFX_RANGE_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define GFX_RANGE_EXPORT +#endif + +#endif // GFX_RANGE_EXPORT_H_ diff --git a/ui/gfx/range/range.cc b/ui/gfx/range/range.cc new file mode 100644 index 0000000..fa837d0 --- /dev/null +++ b/ui/gfx/range/range.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gfx/range/range.h" + +#include + +#include + +#include "base/logging.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +Range Range::Intersect(const Range& range) const { + uint32_t min = std::max(GetMin(), range.GetMin()); + uint32_t max = std::min(GetMax(), range.GetMax()); + + if (min >= max) // No intersection. + return InvalidRange(); + + return Range(min, max); +} + +std::string Range::ToString() const { + return base::StringPrintf("{%" PRIu32 ",%" PRIu32 "}", start(), end()); +} + +std::ostream& operator<<(std::ostream& os, const Range& range) { + return os << range.ToString(); +} + +} // namespace gfx diff --git a/ui/gfx/range/range.h b/ui/gfx/range/range.h new file mode 100644 index 0000000..e785eb6 --- /dev/null +++ b/ui/gfx/range/range.h @@ -0,0 +1,139 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GFX_RANGE_RANGE_H_ +#define UI_GFX_RANGE_RANGE_H_ + +#include +#include + +#include +#include +#include + +#include "build/build_config.h" +#include "ui/gfx/range/gfx_range_export.h" + +#if defined(OS_MACOSX) +#if __OBJC__ +#import +#else +typedef struct _NSRange NSRange; +#endif +#endif // defined(OS_MACOSX) + +#if defined(OS_WIN) +#include +#include +#endif + +namespace gfx { + +// A Range contains two integer values that represent a numeric range, like the +// range of characters in a text selection. A range is made of a start and end +// position; when they are the same, the Range is akin to a caret. Note that +// |start_| can be greater than |end_| to respect the directionality of the +// range. +class GFX_RANGE_EXPORT Range { + public: + // Creates an empty range {0,0}. + constexpr Range() : Range(0) {} + + // Initializes the range with a start and end. + constexpr Range(uint32_t start, uint32_t end) : start_(start), end_(end) {} + + // Initializes the range with the same start and end positions. + constexpr explicit Range(uint32_t position) : Range(position, position) {} + + // Platform constructors. +#if defined(OS_MACOSX) + explicit Range(const NSRange& range); +#elif defined(OS_WIN) + // The |total_length| paramater should be used if the CHARRANGE is set to + // {0,-1} to indicate the whole range. + Range(const CHARRANGE& range, LONG total_length = -1); +#endif + + // Returns a range that is invalid, which is {UINT32_MAX,UINT32_MAX}. + static constexpr Range InvalidRange() { + return Range(std::numeric_limits::max()); + } + + // Checks if the range is valid through comparison to InvalidRange(). + constexpr bool IsValid() const { return *this != InvalidRange(); } + + // Getters and setters. + constexpr uint32_t start() const { return start_; } + void set_start(uint32_t start) { start_ = start; } + + constexpr uint32_t end() const { return end_; } + void set_end(uint32_t end) { end_ = end; } + + // Returns the absolute value of the length. + constexpr uint32_t length() const { return GetMax() - GetMin(); } + + constexpr bool is_reversed() const { return start() > end(); } + constexpr bool is_empty() const { return start() == end(); } + + // Returns the minimum and maximum values. + constexpr uint32_t GetMin() const { + return start() < end() ? start() : end(); + } + constexpr uint32_t GetMax() const { + return start() > end() ? start() : end(); + } + + constexpr bool operator==(const Range& other) const { + return start() == other.start() && end() == other.end(); + } + constexpr bool operator!=(const Range& other) const { + return !(*this == other); + } + constexpr bool EqualsIgnoringDirection(const Range& other) const { + return GetMin() == other.GetMin() && GetMax() == other.GetMax(); + } + + // Returns true if this range intersects the specified |range|. + constexpr bool Intersects(const Range& range) const { + return IsValid() && range.IsValid() && + !(range.GetMax() < GetMin() || range.GetMin() >= GetMax()); + } + + // Returns true if this range contains the specified |range|. + constexpr bool Contains(const Range& range) const { + return IsValid() && range.IsValid() && GetMin() <= range.GetMin() && + range.GetMax() <= GetMax(); + } + + // Computes the intersection of this range with the given |range|. + // If they don't intersect, it returns an InvalidRange(). + // The returned range is always empty or forward (never reversed). + Range Intersect(const Range& range) const; + +#if defined(OS_MACOSX) + Range& operator=(const NSRange& range); + + // NSRange does not store the directionality of a range, so if this + // is_reversed(), the range will get flipped when converted to an NSRange. + NSRange ToNSRange() const; +#elif defined(OS_WIN) + CHARRANGE ToCHARRANGE() const; +#endif + // GTK+ has no concept of a range. + + std::string ToString() const; + + private: + // Note: we use uint32_t instead of size_t because this struct is sent over + // IPC which could span 32 & 64 bit processes. This is fine since text spans + // shouldn't exceed UINT32_MAX even on 64 bit builds. + uint32_t start_; + uint32_t end_; +}; + +GFX_RANGE_EXPORT std::ostream& operator<<(std::ostream& os, const Range& range); + +} // namespace gfx + +#endif // UI_GFX_RANGE_RANGE_H_ diff --git a/ui/gfx/range/range_f.cc b/ui/gfx/range/range_f.cc new file mode 100644 index 0000000..b3bfc69 --- /dev/null +++ b/ui/gfx/range/range_f.cc @@ -0,0 +1,58 @@ +// 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 "ui/gfx/range/range_f.h" + +#include + +#include +#include + +#include "base/format_macros.h" +#include "base/strings/stringprintf.h" + +namespace gfx { + +RangeF RangeF::Intersect(const RangeF& range) const { + float min = std::max(GetMin(), range.GetMin()); + float max = std::min(GetMax(), range.GetMax()); + + if (min >= max) // No intersection. + return InvalidRange(); + + return RangeF(min, max); +} + +RangeF RangeF::Intersect(const Range& range) const { + RangeF range_f(range.start(), range.end()); + return Intersect(range_f); +} + +Range RangeF::Floor() const { + uint32_t start = start_ > 0 ? static_cast(std::floor(start_)) : 0; + uint32_t end = end_ > 0 ? static_cast(std::floor(end_)) : 0; + return Range(start, end); +} + +Range RangeF::Ceil() const { + uint32_t start = start_ > 0 ? static_cast(std::ceil(start_)) : 0; + uint32_t end = end_ > 0 ? static_cast(std::ceil(end_)) : 0; + return Range(start, end); +} + +Range RangeF::Round() const { + uint32_t start = start_ > 0 ? static_cast(std::round(start_)) : 0; + uint32_t end = end_ > 0 ? static_cast(std::round(end_)) : 0; + return Range(start, end); +} + +std::string RangeF::ToString() const { + return base::StringPrintf("{%f,%f}", start(), end()); +} + +std::ostream& operator<<(std::ostream& os, const RangeF& range) { + return os << range.ToString(); +} + +} // namespace gfx diff --git a/ui/gfx/range/range_f.h b/ui/gfx/range/range_f.h new file mode 100644 index 0000000..1d58ad4 --- /dev/null +++ b/ui/gfx/range/range_f.h @@ -0,0 +1,101 @@ +// 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 UI_GFX_RANGE_RANGE_F_H_ +#define UI_GFX_RANGE_RANGE_F_H_ + +#include +#include +#include + +#include "ui/gfx/range/gfx_range_export.h" +#include "ui/gfx/range/range.h" + +namespace gfx { + +// A float version of Range. RangeF is made of a start and end position; when +// they are the same, the range is empty. Note that |start_| can be greater +// than |end_| to respect the directionality of the range. +class GFX_RANGE_EXPORT RangeF { + public: + // Creates an empty range {0,0}. + constexpr RangeF() : RangeF(0.f) {} + + // Initializes the range with a start and end. + constexpr RangeF(float start, float end) : start_(start), end_(end) {} + + // Initializes the range with the same start and end positions. + constexpr explicit RangeF(float position) : RangeF(position, position) {} + + // Returns a range that is invalid, which is {float_max,float_max}. + static constexpr RangeF InvalidRange() { + return RangeF(std::numeric_limits::max()); + } + + // Checks if the range is valid through comparison to InvalidRange(). + constexpr bool IsValid() const { return *this != InvalidRange(); } + + // Getters and setters. + constexpr float start() const { return start_; } + void set_start(float start) { start_ = start; } + + constexpr float end() const { return end_; } + void set_end(float end) { end_ = end; } + + // Returns the absolute value of the length. + constexpr float length() const { return GetMax() - GetMin(); } + + constexpr bool is_reversed() const { return start() > end(); } + constexpr bool is_empty() const { return start() == end(); } + + // Returns the minimum and maximum values. + constexpr float GetMin() const { return start() < end() ? start() : end(); } + constexpr float GetMax() const { return start() > end() ? start() : end(); } + + constexpr bool operator==(const RangeF& other) const { + return start() == other.start() && end() == other.end(); + } + constexpr bool operator!=(const RangeF& other) const { + return !(*this == other); + } + constexpr bool EqualsIgnoringDirection(const RangeF& other) const { + return GetMin() == other.GetMin() && GetMax() == other.GetMax(); + } + + // Returns true if this range intersects the specified |range|. + constexpr bool Intersects(const RangeF& range) const { + return IsValid() && range.IsValid() && + !(range.GetMax() < GetMin() || range.GetMin() >= GetMax()); + } + + // Returns true if this range contains the specified |range|. + constexpr bool Contains(const RangeF& range) const { + return IsValid() && range.IsValid() && GetMin() <= range.GetMin() && + range.GetMax() <= GetMax(); + } + + // Computes the intersection of this range with the given |range|. + // If they don't intersect, it returns an InvalidRange(). + // The returned range is always empty or forward (never reversed). + RangeF Intersect(const RangeF& range) const; + RangeF Intersect(const Range& range) const; + + // Floor/Ceil/Round the start and end values of the given RangeF. + Range Floor() const; + Range Ceil() const; + Range Round() const; + + std::string ToString() const; + + private: + float start_; + float end_; +}; + +GFX_RANGE_EXPORT std::ostream& operator<<(std::ostream& os, + const RangeF& range); + +} // namespace gfx + +#endif // UI_GFX_RANGE_RANGE_F_H_ diff --git a/ui/gfx/range/range_unittest.cc b/ui/gfx/range/range_unittest.cc new file mode 100644 index 0000000..4ae7a67 --- /dev/null +++ b/ui/gfx/range/range_unittest.cc @@ -0,0 +1,266 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/range/range.h" +#include "ui/gfx/range/range_f.h" + +namespace { + +template +class RangeTest : public testing::Test { +}; + +typedef testing::Types RangeTypes; +TYPED_TEST_CASE(RangeTest, RangeTypes); + +template +void TestContainsAndIntersects(const T& r1, + const T& r2, + const T& r3) { + EXPECT_TRUE(r1.Intersects(r1)); + EXPECT_TRUE(r1.Contains(r1)); + EXPECT_EQ(T(10, 12), r1.Intersect(r1)); + + EXPECT_FALSE(r1.Intersects(r2)); + EXPECT_FALSE(r1.Contains(r2)); + EXPECT_TRUE(r1.Intersect(r2).is_empty()); + EXPECT_FALSE(r2.Intersects(r1)); + EXPECT_FALSE(r2.Contains(r1)); + EXPECT_TRUE(r2.Intersect(r1).is_empty()); + + EXPECT_TRUE(r1.Intersects(r3)); + EXPECT_TRUE(r3.Intersects(r1)); + EXPECT_TRUE(r3.Contains(r1)); + EXPECT_FALSE(r1.Contains(r3)); + EXPECT_EQ(T(10, 12), r1.Intersect(r3)); + EXPECT_EQ(T(10, 12), r3.Intersect(r1)); + + EXPECT_TRUE(r2.Intersects(r3)); + EXPECT_TRUE(r3.Intersects(r2)); + EXPECT_FALSE(r3.Contains(r2)); + EXPECT_FALSE(r2.Contains(r3)); + EXPECT_EQ(T(5, 8), r2.Intersect(r3)); + EXPECT_EQ(T(5, 8), r3.Intersect(r2)); +} + +} // namespace + +TYPED_TEST(RangeTest, EmptyInit) { + TypeParam r; + EXPECT_EQ(0U, r.start()); + EXPECT_EQ(0U, r.end()); + EXPECT_EQ(0U, r.length()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_TRUE(r.is_empty()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(0U, r.GetMin()); + EXPECT_EQ(0U, r.GetMax()); +} + +TYPED_TEST(RangeTest, StartEndInit) { + TypeParam r(10, 15); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(15U, r.end()); + EXPECT_EQ(5U, r.length()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_FALSE(r.is_empty()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(10U, r.GetMin()); + EXPECT_EQ(15U, r.GetMax()); +} + +TYPED_TEST(RangeTest, StartEndReversedInit) { + TypeParam r(10, 5); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(5U, r.end()); + EXPECT_EQ(5U, r.length()); + EXPECT_TRUE(r.is_reversed()); + EXPECT_FALSE(r.is_empty()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(5U, r.GetMin()); + EXPECT_EQ(10U, r.GetMax()); +} + +TYPED_TEST(RangeTest, PositionInit) { + TypeParam r(12); + EXPECT_EQ(12U, r.start()); + EXPECT_EQ(12U, r.end()); + EXPECT_EQ(0U, r.length()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_TRUE(r.is_empty()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(12U, r.GetMin()); + EXPECT_EQ(12U, r.GetMax()); +} + +TYPED_TEST(RangeTest, InvalidRange) { + TypeParam r(TypeParam::InvalidRange()); + EXPECT_EQ(0U, r.length()); + EXPECT_EQ(r.start(), r.end()); + EXPECT_EQ(r.GetMax(), r.GetMin()); + EXPECT_FALSE(r.is_reversed()); + EXPECT_TRUE(r.is_empty()); + EXPECT_FALSE(r.IsValid()); + EXPECT_EQ(r, TypeParam::InvalidRange()); + EXPECT_TRUE(r.EqualsIgnoringDirection(TypeParam::InvalidRange())); +} + +TYPED_TEST(RangeTest, Equality) { + TypeParam r1(10, 4); + TypeParam r2(10, 4); + TypeParam r3(10, 2); + EXPECT_EQ(r1, r2); + EXPECT_NE(r1, r3); + EXPECT_NE(r2, r3); + + TypeParam r4(11, 4); + EXPECT_NE(r1, r4); + EXPECT_NE(r2, r4); + EXPECT_NE(r3, r4); + + TypeParam r5(12, 5); + EXPECT_NE(r1, r5); + EXPECT_NE(r2, r5); + EXPECT_NE(r3, r5); +} + +TYPED_TEST(RangeTest, EqualsIgnoringDirection) { + TypeParam r1(10, 5); + TypeParam r2(5, 10); + EXPECT_TRUE(r1.EqualsIgnoringDirection(r2)); +} + +TYPED_TEST(RangeTest, SetStart) { + TypeParam r(10, 20); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(10U, r.length()); + + r.set_start(42); + EXPECT_EQ(42U, r.start()); + EXPECT_EQ(20U, r.end()); + EXPECT_EQ(22U, r.length()); + EXPECT_TRUE(r.is_reversed()); +} + +TYPED_TEST(RangeTest, SetEnd) { + TypeParam r(10, 13); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(3U, r.length()); + + r.set_end(20); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(20U, r.end()); + EXPECT_EQ(10U, r.length()); +} + +TYPED_TEST(RangeTest, SetStartAndEnd) { + TypeParam r; + r.set_end(5); + r.set_start(1); + EXPECT_EQ(1U, r.start()); + EXPECT_EQ(5U, r.end()); + EXPECT_EQ(4U, r.length()); + EXPECT_EQ(1U, r.GetMin()); + EXPECT_EQ(5U, r.GetMax()); +} + +TYPED_TEST(RangeTest, ReversedRange) { + TypeParam r(10, 5); + EXPECT_EQ(10U, r.start()); + EXPECT_EQ(5U, r.end()); + EXPECT_EQ(5U, r.length()); + EXPECT_TRUE(r.is_reversed()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(5U, r.GetMin()); + EXPECT_EQ(10U, r.GetMax()); +} + +TYPED_TEST(RangeTest, SetReversedRange) { + TypeParam r(10, 20); + r.set_start(25); + EXPECT_EQ(25U, r.start()); + EXPECT_EQ(20U, r.end()); + EXPECT_EQ(5U, r.length()); + EXPECT_TRUE(r.is_reversed()); + EXPECT_TRUE(r.IsValid()); + + r.set_end(21); + EXPECT_EQ(25U, r.start()); + EXPECT_EQ(21U, r.end()); + EXPECT_EQ(4U, r.length()); + EXPECT_TRUE(r.IsValid()); + EXPECT_EQ(21U, r.GetMin()); + EXPECT_EQ(25U, r.GetMax()); +} + +TYPED_TEST(RangeTest, ContainAndIntersect) { + { + SCOPED_TRACE("contain and intersect"); + TypeParam r1(10, 12); + TypeParam r2(1, 8); + TypeParam r3(5, 12); + TestContainsAndIntersects(r1, r2, r3); + } + { + SCOPED_TRACE("contain and intersect: reversed"); + TypeParam r1(12, 10); + TypeParam r2(8, 1); + TypeParam r3(12, 5); + TestContainsAndIntersects(r1, r2, r3); + } + // Invalid rect tests + TypeParam r1(10, 12); + TypeParam r2(8, 1); + TypeParam invalid = r1.Intersect(r2); + EXPECT_FALSE(invalid.IsValid()); + EXPECT_FALSE(invalid.Contains(invalid)); + EXPECT_FALSE(invalid.Contains(r1)); + EXPECT_FALSE(invalid.Intersects(invalid)); + EXPECT_FALSE(invalid.Intersects(r1)); + EXPECT_FALSE(r1.Contains(invalid)); + EXPECT_FALSE(r1.Intersects(invalid)); +} + +TEST(RangeTest, RangeFConverterTest) { + gfx::RangeF range_f(1.2f, 3.9f); + gfx::Range range = range_f.Floor(); + EXPECT_EQ(1U, range.start()); + EXPECT_EQ(3U, range.end()); + + range = range_f.Ceil(); + EXPECT_EQ(2U, range.start()); + EXPECT_EQ(4U, range.end()); + + range = range_f.Round(); + EXPECT_EQ(1U, range.start()); + EXPECT_EQ(4U, range.end()); + + // Test for negative values. + range_f.set_start(-1.2f); + range_f.set_end(-3.8f); + range = range_f.Floor(); + EXPECT_EQ(0U, range.start()); + EXPECT_EQ(0U, range.end()); + + range = range_f.Ceil(); + EXPECT_EQ(0U, range.start()); + EXPECT_EQ(0U, range.end()); + + range = range_f.Round(); + EXPECT_EQ(0U, range.start()); + EXPECT_EQ(0U, range.end()); +} + +TEST(RangeTest, ToString) { + gfx::Range range(4, 7); + EXPECT_EQ("{4,7}", range.ToString()); + + range = gfx::Range::InvalidRange(); + std::ostringstream expected; + expected << "{" << range.start() << "," << range.end() << "}"; + EXPECT_EQ(expected.str(), range.ToString()); +} -- GitLab From 28fbe310b77de4522ee7c21508ec1a99778a0887 Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Mon, 16 Apr 2018 14:40:49 +0900 Subject: [PATCH 05/22] Update comment. To address remaining review comments in https://android-review.googlesource.com/c/platform/external/libchrome/+/638248 Bug: None Test: Ran update_libchrom.py. Change-Id: If7738fb4419f597b21dff287b23f6888f91d4625 --- base/process/process_metrics_unittest.cc | 2 +- base/test/multiprocess_test.cc | 2 +- libchrome_tools/patch/subprocess.patch | 4 ++-- libchrome_tools/update_libchrome.py | 3 +-- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/base/process/process_metrics_unittest.cc b/base/process/process_metrics_unittest.cc index 288cde9..d9c8aaa 100644 --- a/base/process/process_metrics_unittest.cc +++ b/base/process/process_metrics_unittest.cc @@ -549,7 +549,7 @@ MULTIPROCESS_TEST_MAIN(ChildMain) { } // namespace -// Arc++ note: don't compile as SpawnMultiProcessTestChild brings in a lot of +// ARC note: don't compile as SpawnMultiProcessTestChild brings in a lot of // extra dependency. #if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__) TEST(ProcessMetricsTest, GetOpenFdCount) { diff --git a/base/test/multiprocess_test.cc b/base/test/multiprocess_test.cc index c8fd3ed..3e09e69 100644 --- a/base/test/multiprocess_test.cc +++ b/base/test/multiprocess_test.cc @@ -54,7 +54,7 @@ CommandLine GetMultiProcessTestChildBaseCommandLine() { MultiProcessTest::MultiProcessTest() { } -// Don't compile on Arc++. +// Don't compile on ARC. #if 0 SpawnChildResult MultiProcessTest::SpawnChild(const std::string& procname) { LaunchOptions options; diff --git a/libchrome_tools/patch/subprocess.patch b/libchrome_tools/patch/subprocess.patch index ad4457d..ff1d02d 100644 --- a/libchrome_tools/patch/subprocess.patch +++ b/libchrome_tools/patch/subprocess.patch @@ -4,7 +4,7 @@ } // namespace -+// Arc++ note: don't compile as SpawnMultiProcessTestChild brings in a lot of ++// ARC note: don't compile as SpawnMultiProcessTestChild brings in a lot of +// extra dependency. +#if !defined(OS_ANDROID) && !defined(__ANDROID__) && !defined(__ANDROID_HOST__) TEST(ProcessMetricsTest, GetOpenFdCount) { @@ -59,7 +59,7 @@ MultiProcessTest::MultiProcessTest() { } -+// Don't compile on Arc++. ++// Don't compile on ARC. +#if 0 SpawnChildResult MultiProcessTest::SpawnChild(const std::string& procname) { LaunchOptions options; diff --git a/libchrome_tools/update_libchrome.py b/libchrome_tools/update_libchrome.py index 89d97c2..b92615b 100644 --- a/libchrome_tools/update_libchrome.py +++ b/libchrome_tools/update_libchrome.py @@ -62,8 +62,7 @@ _IMPORT_BLACKLIST = [ 'base/allocator/features.h', 'base/debug/debugging_flags.h', - # Blacklist several third party libraries, instead system libraries should - # be used. + # Blacklist several third party libraries; system libraries should be used. 'base/third_party/libevent/*', 'base/third_party/symbolize/*', 'testing/gmock/*', -- GitLab From 66a4050c0acc77f5bdd20bcfc44c52ba9b91ca61 Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Mon, 16 Apr 2018 14:27:04 +0900 Subject: [PATCH 06/22] Add lhchavez@ to OWNERS for Mojo. Along with merging libmojo into libchrome, add lhchavez@ who is the OWNER of libmojo into libchrome's OWNER. Bug: 73606903 Test: n/a Change-Id: Ic741bd4bb3ab1ddd5108f1a2183be5cf7bd679b5 --- OWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OWNERS b/OWNERS index c474ccf..65f5a6b 100644 --- a/OWNERS +++ b/OWNERS @@ -2,3 +2,6 @@ avakulenko@google.com derat@google.com deymo@google.com jorgelo@google.com + +# For Mojo changes. +lhchavez@google.com -- GitLab From 3dc91090b75cd8f8d503b4cbcbe31999620fb86a Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Tue, 17 Apr 2018 02:39:24 +0900 Subject: [PATCH 07/22] Split header files generation rule. When merging libmojo into this, current approach, generate wrapper header files for all .h files under libchrome, will hit the commandline length limit. Stepping back, we should generate header files for each library in theory, so this CL does so, as preparation to merge libmojo. Bug: 73606903 Test: built locally. Treehugger. Change-Id: Icc3e2c9625e78c54ff69f418e790456d2b297436 --- Android.bp | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Android.bp b/Android.bp index d4842e1..be9d763 100644 --- a/Android.bp +++ b/Android.bp @@ -23,7 +23,15 @@ gensrcs { cmd: "$(location libchrome_tools/include_generator.py) $(in) $(out)", tool_files: ["libchrome_tools/include_generator.py"], export_include_dirs: ["."], - srcs: ["**/*.h"], + srcs: [ + "base/**/*.h", + "build/**/*.h", + "components/**/*.h", + "device/**/*.h", + "testing/**/*.h", + "third_party/**/*.h", + "ui/**/*.h", + ], output_extension: "h", } @@ -365,6 +373,18 @@ cc_library { // libchrome-crypto shared library for device // ======================================================== + +// Similar to libchrome, generate wrapped header files. See comments for +// libchrome-include for the details. +gensrcs { + name: "libchrome-crypto-include", + cmd: "$(location libchrome_tools/include_generator.py) $(in) $(out)", + tool_files: ["libchrome_tools/include_generator.py"], + export_include_dirs: ["."], + srcs: ["crypto/**/*.h"], + output_extension: "h", +} + cc_library_shared { name: "libchrome-crypto", defaults: ["libchrome-defaults"], @@ -376,6 +396,9 @@ cc_library_shared { "crypto/sha2.cc", ], + generated_headers: ["libchrome-crypto-include"], + export_generated_headers: ["libchrome-crypto-include"], + shared_libs: [ "libchrome", "libcrypto", -- GitLab From 4a54b98aa445f521c6945e4f4a1e0ea788fa7da8 Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Fri, 20 Apr 2018 11:36:57 +0900 Subject: [PATCH 08/22] Add hidehiko to OWNERS as Mojo maintainer. Similar to libmojo/OWNERS, add myself to libchrome because libmojo is being merged into libchrome. Bug: None Test: None Change-Id: I5c6f411e812530571e1bcbd91bf14b6db0ba60b8 --- OWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/OWNERS b/OWNERS index 65f5a6b..8d673fa 100644 --- a/OWNERS +++ b/OWNERS @@ -5,3 +5,4 @@ jorgelo@google.com # For Mojo changes. lhchavez@google.com +hidehiko@google.com -- GitLab From b268b43ac6fdbc4f3a2ed1429b99ace424906090 Mon Sep 17 00:00:00 2001 From: Hidehiko Abe Date: Tue, 24 Apr 2018 01:37:19 +0900 Subject: [PATCH 09/22] Migrate libmojo repository into libchrome, part 2. This CL moves following files. - .gitignore - Android.bp is merged into libchrome's Android.bp. - base/android/* - build/* except build_config.h which is exactly same with libchrome's. - ipc/* - mojo/* except mojo/public/tools/bindings/generators/__init__.py which is unused and not in chrome repository. - soong/* into libchrome_tools/ - third_party/{catapult,jinja2,markupsafe,ply}/* - ui/gfx/{geometry,range}/mojo/* Then, update several paths/build rules to be adapted. Bug: 73606903 Test: Built locally. Ran on DUT. Change-Id: I2a532a42aa68dcb215dbd71d8673192311509726 --- .gitignore | 1 + Android.bp | 432 ++- base/android/build_info.cc | 80 + base/android/build_info.h | 145 + base/android/context_utils.cc | 53 + base/android/context_utils.h | 26 + .../java/src/org/chromium/base/BuildInfo.java | 171 + .../src/org/chromium/base/ContextUtils.java | 115 + .../java/src/org/chromium/base/Log.java | 387 ++ .../src/org/chromium/base/PackageUtils.java | 37 + .../org/chromium/base/VisibleForTesting.java | 12 + .../base/annotations/AccessedByNative.java | 20 + .../base/annotations/CalledByNative.java | 23 + .../annotations/CalledByNativeUnchecked.java | 27 + .../base/annotations/JNIAdditionalImport.java | 35 + .../base/annotations/JNINamespace.java | 20 + .../chromium/base/annotations/MainDex.java | 21 + .../chromium/base/annotations/NativeCall.java | 24 + .../annotations/NativeClassQualifiedName.java | 25 + .../base/annotations/RemovableInRelease.java | 18 + .../base/annotations/SuppressFBWarnings.java | 20 + .../base/annotations/UsedByReflection.java | 24 + base/android/java_runtime.cc | 21 + base/android/java_runtime.h | 25 + base/android/jni_android.cc | 320 ++ base/android/jni_android.h | 190 + base/android/jni_android_unittest.cc | 61 + .../android/jni_generator/android_jar.classes | 98 + .../example/jni_generator/SampleForTests.java | 318 ++ base/android/jni_generator/jni_generator.py | 1418 +++++++ .../jni_generator/jni_generator_helper.h | 62 + .../jni_generator/jni_generator_tests.py | 1097 ++++++ .../android/jni_generator/sample_for_tests.cc | 142 + base/android/jni_generator/sample_for_tests.h | 126 + .../jni_generator/testCalledByNatives.golden | 497 +++ .../testConstantsFromJavaP.golden | 2195 +++++++++++ .../jni_generator/testFromJavaP.golden | 260 ++ .../testFromJavaPGenerics.golden | 56 + .../testInnerClassNatives.golden | 71 + ...tInnerClassNativesBothInnerAndOuter.golden | 98 + .../testInnerClassNativesMultiple.golden | 105 + .../jni_generator/testInputStream.javap | 228 ++ .../jni_generator/testMotionEvent.javap | 2295 ++++++++++++ .../jni_generator/testMotionEvent.javap7 | 2370 ++++++++++++ .../testMultipleJNIAdditionalImport.golden | 95 + base/android/jni_generator/testNatives.golden | 340 ++ .../jni_generator/testNativesLong.golden | 66 + .../testSingleJNIAdditionalImport.golden | 89 + base/android/jni_int_wrapper.h | 56 + base/android/jni_string.cc | 121 + base/android/jni_string.h | 49 + base/android/jni_string_unittest.cc | 48 + base/android/scoped_java_ref.cc | 92 + base/android/scoped_java_ref.h | 301 ++ base/android/scoped_java_ref_unittest.cc | 133 + build/android/gyp/util/__init__.py | 4 + build/android/gyp/util/build_utils.py | 589 +++ build/android/gyp/util/md5_check.py | 410 +++ build/android/pylib/__init__.py | 31 + build/android/pylib/constants/__init__.py | 225 ++ build/android/pylib/constants/host_paths.py | 38 + build/gn_helpers.py | 351 ++ .../common/common_custom_types__type_mappings | 193 + ipc/ipc.mojom | 40 + ipc/ipc_channel_handle.h | 41 + ipc/ipc_export.h | 34 + ipc/ipc_listener.h | 60 + ipc/ipc_message.cc | 205 ++ ipc/ipc_message.h | 305 ++ ipc/ipc_message_attachment.cc | 15 + ipc/ipc_message_attachment.h | 36 + ipc/ipc_message_attachment_set.cc | 136 + ipc/ipc_message_attachment_set.h | 96 + ipc/ipc_message_start.h | 126 + ipc/ipc_message_utils.cc | 1238 +++++++ ipc/ipc_message_utils.h | 1145 ++++++ ipc/ipc_mojo_handle_attachment.cc | 29 + ipc/ipc_mojo_handle_attachment.h | 42 + ipc/ipc_mojo_message_helper.cc | 51 + ipc/ipc_mojo_message_helper.h | 29 + ipc/ipc_mojo_param_traits.cc | 50 + ipc/ipc_mojo_param_traits.h | 34 + ipc/ipc_param_traits.h | 23 + ipc/ipc_platform_file_attachment_posix.cc | 38 + ipc/ipc_platform_file_attachment_posix.h | 43 + ipc/ipc_sync_message.h | 107 + libchrome_tools/jni_generator_helper.sh | 42 + libchrome_tools/mojom_source_generator.sh | 115 + libchrome_tools/patch/mojo.patch | 566 +++ libchrome_tools/update_libchrome.py | 13 +- libmojo.pc.in | 13 + mojo/BUILD.gn | 41 + mojo/DEPS | 7 + mojo/PRESUBMIT.py | 44 + mojo/README.md | 142 + mojo/android/BUILD.gn | 155 + mojo/android/DEPS | 3 + mojo/android/javatests/AndroidManifest.xml | 29 + mojo/android/javatests/DEPS | 4 + mojo/android/javatests/apk/.empty | 0 mojo/android/javatests/init_library.cc | 50 + mojo/android/javatests/mojo_test_case.cc | 70 + mojo/android/javatests/mojo_test_case.h | 20 + .../src/org/chromium/mojo/HandleMock.java | 226 ++ .../src/org/chromium/mojo/MojoTestCase.java | 66 + .../src/org/chromium/mojo/TestUtils.java | 32 + .../mojo/bindings/BindingsHelperTest.java | 55 + .../chromium/mojo/bindings/BindingsTest.java | 241 ++ .../mojo/bindings/BindingsTestUtils.java | 108 + .../mojo/bindings/BindingsVersioningTest.java | 211 ++ .../chromium/mojo/bindings/CallbacksTest.java | 59 + .../chromium/mojo/bindings/ConnectorTest.java | 108 + .../mojo/bindings/ExecutorFactoryTest.java | 104 + .../bindings/InterfaceControlMessageTest.java | 129 + .../mojo/bindings/InterfacesTest.java | 284 ++ .../mojo/bindings/MessageHeaderTest.java | 69 + .../bindings/ReadAndDispatchMessageTest.java | 109 + .../chromium/mojo/bindings/RouterTest.java | 231 ++ .../mojo/bindings/SerializationTest.java | 175 + .../mojo/bindings/ValidationTest.java | 237 ++ .../mojo/bindings/ValidationTestUtil.java | 68 + .../mojo/bindings/ValidationTestUtilTest.java | 151 + .../IntegrationTestInterfaceTestHelper.java | 31 + .../mojo/system/impl/CoreImplTest.java | 545 +++ .../mojo/system/impl/WatcherImplTest.java | 255 ++ .../android/javatests/validation_test_util.cc | 54 + mojo/android/javatests/validation_test_util.h | 20 + mojo/android/system/base_run_loop.cc | 82 + mojo/android/system/base_run_loop.h | 20 + mojo/android/system/core_impl.cc | 310 ++ mojo/android/system/core_impl.h | 20 + .../mojo/system/impl/BaseRunLoop.java | 74 + .../chromium/mojo/system/impl/CoreImpl.java | 522 +++ .../impl/DataPipeConsumerHandleImpl.java | 72 + .../impl/DataPipeProducerHandleImpl.java | 64 + .../chromium/mojo/system/impl/HandleBase.java | 140 + .../system/impl/MessagePipeHandleImpl.java | 58 + .../system/impl/SharedBufferHandleImpl.java | 62 + .../mojo/system/impl/UntypedHandleImpl.java | 72 + .../mojo/system/impl/WatcherImpl.java | 63 + mojo/android/system/watcher_impl.cc | 109 + mojo/android/system/watcher_impl.h | 20 + mojo/common/BUILD.gn | 93 + mojo/common/DEPS | 6 + .../common_custom_types_struct_traits.cc | 108 + .../common_custom_types_struct_traits.h | 85 + mojo/common/common_custom_types_unittest.cc | 433 +++ mojo/common/data_pipe_drainer.cc | 50 + mojo/common/data_pipe_drainer.h | 49 + mojo/common/data_pipe_utils.cc | 96 + mojo/common/data_pipe_utils.h | 32 + mojo/common/file.mojom | 10 + mojo/common/file.typemap | 13 + mojo/common/file_path.mojom | 8 + mojo/common/file_path.typemap | 12 + mojo/common/mojo_common_export.h | 32 + mojo/common/string16.mojom | 12 + mojo/common/string16.typemap | 12 + mojo/common/struct_traits_unittest.cc | 57 + mojo/common/test_common_custom_types.mojom | 61 + mojo/common/text_direction.mojom | 12 + mojo/common/text_direction.typemap | 12 + mojo/common/time.mojom | 21 + mojo/common/time.typemap | 20 + mojo/common/time_struct_traits.h | 55 + mojo/common/traits_test_service.mojom | 14 + mojo/common/typemaps.gni | 14 + mojo/common/unguessable_token.mojom | 11 + mojo/common/unguessable_token.typemap | 12 + mojo/common/values.mojom | 32 + mojo/common/values.typemap | 25 + mojo/common/values_struct_traits.cc | 109 + mojo/common/values_struct_traits.h | 257 ++ mojo/common/version.mojom | 10 + mojo/common/version.typemap | 12 + mojo/edk/DEPS | 14 + mojo/edk/embedder/BUILD.gn | 147 + mojo/edk/embedder/README.md | 346 ++ mojo/edk/embedder/configuration.h | 65 + mojo/edk/embedder/connection_params.cc | 28 + mojo/edk/embedder/connection_params.h | 34 + mojo/edk/embedder/embedder.cc | 158 + mojo/edk/embedder/embedder.h | 173 + mojo/edk/embedder/embedder_internal.h | 44 + mojo/edk/embedder/embedder_unittest.cc | 603 +++ mojo/edk/embedder/entrypoints.cc | 283 ++ mojo/edk/embedder/entrypoints.h | 22 + .../embedder/named_platform_channel_pair.h | 73 + .../named_platform_channel_pair_win.cc | 89 + mojo/edk/embedder/named_platform_handle.h | 51 + .../embedder/named_platform_handle_utils.h | 56 + .../named_platform_handle_utils_posix.cc | 141 + .../named_platform_handle_utils_win.cc | 95 + .../embedder/pending_process_connection.cc | 50 + .../edk/embedder/pending_process_connection.h | 124 + mojo/edk/embedder/platform_channel_pair.cc | 34 + mojo/edk/embedder/platform_channel_pair.h | 106 + .../embedder/platform_channel_pair_posix.cc | 172 + .../platform_channel_pair_posix_unittest.cc | 261 ++ .../edk/embedder/platform_channel_pair_win.cc | 123 + .../embedder/platform_channel_utils_posix.cc | 282 ++ .../embedder/platform_channel_utils_posix.h | 87 + mojo/edk/embedder/platform_handle.cc | 74 + mojo/edk/embedder/platform_handle.h | 90 + mojo/edk/embedder/platform_handle_utils.h | 33 + .../embedder/platform_handle_utils_posix.cc | 24 + .../edk/embedder/platform_handle_utils_win.cc | 28 + mojo/edk/embedder/platform_handle_vector.h | 35 + mojo/edk/embedder/platform_shared_buffer.cc | 325 ++ mojo/edk/embedder/platform_shared_buffer.h | 178 + .../platform_shared_buffer_unittest.cc | 227 ++ mojo/edk/embedder/scoped_ipc_support.cc | 39 + mojo/edk/embedder/scoped_ipc_support.h | 118 + mojo/edk/embedder/scoped_platform_handle.h | 63 + mojo/edk/embedder/test_embedder.cc | 46 + mojo/edk/embedder/test_embedder.h | 28 + mojo/edk/js/BUILD.gn | 35 + mojo/edk/js/core.cc | 454 +++ mojo/edk/js/core.h | 25 + mojo/edk/js/drain_data.cc | 129 + mojo/edk/js/drain_data.h | 65 + mojo/edk/js/handle.cc | 85 + mojo/edk/js/handle.h | 107 + mojo/edk/js/handle_close_observer.h | 24 + mojo/edk/js/handle_unittest.cc | 92 + mojo/edk/js/js_export.h | 32 + mojo/edk/js/mojo_runner_delegate.cc | 80 + mojo/edk/js/mojo_runner_delegate.h | 36 + mojo/edk/js/support.cc | 77 + mojo/edk/js/support.h | 25 + mojo/edk/js/tests/BUILD.gn | 68 + mojo/edk/js/tests/js_to_cpp.mojom | 54 + mojo/edk/js/tests/js_to_cpp_tests.cc | 455 +++ mojo/edk/js/tests/js_to_cpp_tests.js | 223 ++ mojo/edk/js/tests/run_js_unittests.cc | 61 + mojo/edk/js/threading.cc | 49 + mojo/edk/js/threading.h | 28 + mojo/edk/js/waiting_callback.cc | 95 + mojo/edk/js/waiting_callback.h | 67 + mojo/edk/system/BUILD.gn | 205 ++ mojo/edk/system/atomic_flag.h | 57 + mojo/edk/system/broker.h | 52 + mojo/edk/system/broker_host.cc | 153 + mojo/edk/system/broker_host.h | 64 + mojo/edk/system/broker_messages.h | 80 + mojo/edk/system/broker_posix.cc | 125 + mojo/edk/system/broker_win.cc | 155 + mojo/edk/system/channel.cc | 683 ++++ mojo/edk/system/channel.h | 303 ++ mojo/edk/system/channel_posix.cc | 572 +++ mojo/edk/system/channel_unittest.cc | 177 + mojo/edk/system/channel_win.cc | 360 ++ mojo/edk/system/configuration.cc | 25 + mojo/edk/system/configuration.h | 29 + mojo/edk/system/core.cc | 1019 +++++ mojo/edk/system/core.h | 297 ++ mojo/edk/system/core_test_base.cc | 272 ++ mojo/edk/system/core_test_base.h | 94 + mojo/edk/system/core_unittest.cc | 971 +++++ .../system/data_pipe_consumer_dispatcher.cc | 562 +++ .../system/data_pipe_consumer_dispatcher.h | 123 + mojo/edk/system/data_pipe_control_message.cc | 35 + mojo/edk/system/data_pipe_control_message.h | 43 + .../system/data_pipe_producer_dispatcher.cc | 507 +++ .../system/data_pipe_producer_dispatcher.h | 123 + mojo/edk/system/data_pipe_unittest.cc | 2034 ++++++++++ mojo/edk/system/dispatcher.cc | 198 + mojo/edk/system/dispatcher.h | 245 ++ mojo/edk/system/handle_signals_state.h | 13 + mojo/edk/system/handle_table.cc | 135 + mojo/edk/system/handle_table.h | 75 + mojo/edk/system/mach_port_relay.cc | 248 ++ mojo/edk/system/mach_port_relay.h | 94 + mojo/edk/system/mapping_table.cc | 48 + mojo/edk/system/mapping_table.h | 57 + mojo/edk/system/message_for_transit.cc | 136 + mojo/edk/system/message_for_transit.h | 115 + mojo/edk/system/message_pipe_dispatcher.cc | 554 +++ mojo/edk/system/message_pipe_dispatcher.h | 115 + mojo/edk/system/message_pipe_perftest.cc | 167 + mojo/edk/system/message_pipe_unittest.cc | 699 ++++ .../multiprocess_message_pipe_unittest.cc | 1366 +++++++ mojo/edk/system/node_channel.cc | 905 +++++ mojo/edk/system/node_channel.h | 219 ++ mojo/edk/system/node_controller.cc | 1470 ++++++++ mojo/edk/system/node_controller.h | 378 ++ mojo/edk/system/options_validation.h | 97 + .../edk/system/options_validation_unittest.cc | 134 + mojo/edk/system/platform_handle_dispatcher.cc | 104 + mojo/edk/system/platform_handle_dispatcher.h | 61 + .../platform_handle_dispatcher_unittest.cc | 123 + mojo/edk/system/platform_wrapper_unittest.cc | 212 ++ mojo/edk/system/ports/BUILD.gn | 46 + mojo/edk/system/ports/event.cc | 46 + mojo/edk/system/ports/event.h | 111 + mojo/edk/system/ports/message.cc | 100 + mojo/edk/system/ports/message.h | 93 + mojo/edk/system/ports/message_filter.h | 29 + mojo/edk/system/ports/message_queue.cc | 87 + mojo/edk/system/ports/message_queue.h | 73 + mojo/edk/system/ports/name.cc | 26 + mojo/edk/system/ports/name.h | 74 + mojo/edk/system/ports/node.cc | 1385 +++++++ mojo/edk/system/ports/node.h | 228 ++ mojo/edk/system/ports/node_delegate.h | 48 + mojo/edk/system/ports/port.cc | 24 + mojo/edk/system/ports/port.h | 60 + mojo/edk/system/ports/port_ref.cc | 36 + mojo/edk/system/ports/port_ref.h | 41 + mojo/edk/system/ports/ports_unittest.cc | 1478 ++++++++ mojo/edk/system/ports/user_data.h | 25 + mojo/edk/system/ports_message.cc | 62 + mojo/edk/system/ports_message.h | 69 + mojo/edk/system/request_context.cc | 110 + mojo/edk/system/request_context.h | 107 + mojo/edk/system/shared_buffer_dispatcher.cc | 339 ++ mojo/edk/system/shared_buffer_dispatcher.h | 127 + .../shared_buffer_dispatcher_unittest.cc | 312 ++ mojo/edk/system/shared_buffer_unittest.cc | 318 ++ mojo/edk/system/signals_unittest.cc | 76 + mojo/edk/system/system_impl_export.h | 29 + mojo/edk/system/test_utils.cc | 76 + mojo/edk/system/test_utils.h | 59 + mojo/edk/system/watch.cc | 83 + mojo/edk/system/watch.h | 124 + mojo/edk/system/watcher_dispatcher.cc | 232 ++ mojo/edk/system/watcher_dispatcher.h | 101 + mojo/edk/system/watcher_set.cc | 82 + mojo/edk/system/watcher_set.h | 71 + mojo/edk/system/watcher_unittest.cc | 1637 ++++++++ mojo/edk/test/BUILD.gn | 131 + mojo/edk/test/mojo_test_base.cc | 327 ++ mojo/edk/test/mojo_test_base.h | 249 ++ mojo/edk/test/multiprocess_test_helper.cc | 263 ++ mojo/edk/test/multiprocess_test_helper.h | 110 + .../test/multiprocess_test_helper_unittest.cc | 165 + mojo/edk/test/run_all_perftests.cc | 26 + mojo/edk/test/run_all_unittests.cc | 49 + mojo/edk/test/test_support_impl.cc | 84 + mojo/edk/test/test_support_impl.h | 38 + mojo/edk/test/test_utils.h | 55 + mojo/edk/test/test_utils_posix.cc | 94 + mojo/edk/test/test_utils_win.cc | 115 + mojo/public/BUILD.gn | 28 + mojo/public/DEPS | 11 + mojo/public/LICENSE | 27 + mojo/public/c/system/BUILD.gn | 37 + mojo/public/c/system/README.md | 869 +++++ mojo/public/c/system/buffer.h | 188 + mojo/public/c/system/core.h | 22 + mojo/public/c/system/data_pipe.h | 344 ++ mojo/public/c/system/functions.h | 78 + mojo/public/c/system/macros.h | 49 + mojo/public/c/system/message_pipe.h | 341 ++ mojo/public/c/system/platform_handle.h | 191 + mojo/public/c/system/set_thunks_for_app.cc | 20 + mojo/public/c/system/system_export.h | 33 + mojo/public/c/system/tests/BUILD.gn | 38 + mojo/public/c/system/tests/core_perftest.cc | 329 ++ mojo/public/c/system/tests/core_unittest.cc | 322 ++ .../c/system/tests/core_unittest_pure_c.c | 77 + mojo/public/c/system/tests/macros_unittest.cc | 68 + mojo/public/c/system/thunks.cc | 273 ++ mojo/public/c/system/thunks.h | 148 + mojo/public/c/system/types.h | 223 ++ mojo/public/c/system/watcher.h | 184 + mojo/public/c/test_support/BUILD.gn | 15 + mojo/public/c/test_support/test_support.h | 52 + mojo/public/cpp/bindings/BUILD.gn | 194 + mojo/public/cpp/bindings/DEPS | 3 + mojo/public/cpp/bindings/README.md | 1231 +++++++ mojo/public/cpp/bindings/array_data_view.h | 244 ++ mojo/public/cpp/bindings/array_traits.h | 71 + .../public/cpp/bindings/array_traits_carray.h | 76 + mojo/public/cpp/bindings/array_traits_stl.h | 127 + .../cpp/bindings/array_traits_wtf_vector.h | 47 + mojo/public/cpp/bindings/associated_binding.h | 171 + .../cpp/bindings/associated_binding_set.h | 29 + mojo/public/cpp/bindings/associated_group.h | 51 + .../bindings/associated_group_controller.h | 93 + .../cpp/bindings/associated_interface_ptr.h | 283 ++ .../bindings/associated_interface_ptr_info.h | 77 + .../bindings/associated_interface_request.h | 90 + mojo/public/cpp/bindings/binding.h | 276 ++ mojo/public/cpp/bindings/binding_set.h | 267 ++ mojo/public/cpp/bindings/bindings_export.h | 34 + mojo/public/cpp/bindings/clone_traits.h | 86 + .../cpp/bindings/connection_error_callback.h | 21 + mojo/public/cpp/bindings/connector.h | 241 ++ mojo/public/cpp/bindings/disconnect_reason.h | 25 + mojo/public/cpp/bindings/enum_traits.h | 27 + mojo/public/cpp/bindings/filter_chain.h | 61 + .../public/cpp/bindings/interface_data_view.h | 25 + .../cpp/bindings/interface_endpoint_client.h | 193 + .../bindings/interface_endpoint_controller.h | 37 + mojo/public/cpp/bindings/interface_id.h | 35 + mojo/public/cpp/bindings/interface_ptr.h | 242 ++ mojo/public/cpp/bindings/interface_ptr_info.h | 63 + mojo/public/cpp/bindings/interface_ptr_set.h | 107 + mojo/public/cpp/bindings/interface_request.h | 178 + .../public/cpp/bindings/lib/array_internal.cc | 59 + mojo/public/cpp/bindings/lib/array_internal.h | 368 ++ .../cpp/bindings/lib/array_serialization.h | 555 +++ .../cpp/bindings/lib/associated_binding.cc | 62 + .../cpp/bindings/lib/associated_group.cc | 34 + .../lib/associated_group_controller.cc | 24 + .../bindings/lib/associated_interface_ptr.cc | 18 + .../lib/associated_interface_ptr_state.h | 157 + mojo/public/cpp/bindings/lib/binding_state.cc | 90 + mojo/public/cpp/bindings/lib/binding_state.h | 128 + .../cpp/bindings/lib/bindings_internal.h | 336 ++ mojo/public/cpp/bindings/lib/buffer.h | 70 + mojo/public/cpp/bindings/lib/connector.cc | 493 +++ .../bindings/lib/control_message_handler.cc | 150 + .../bindings/lib/control_message_handler.h | 48 + .../cpp/bindings/lib/control_message_proxy.cc | 188 + .../cpp/bindings/lib/control_message_proxy.h | 49 + mojo/public/cpp/bindings/lib/equals_traits.h | 94 + mojo/public/cpp/bindings/lib/filter_chain.cc | 47 + mojo/public/cpp/bindings/lib/fixed_buffer.cc | 30 + mojo/public/cpp/bindings/lib/fixed_buffer.h | 39 + .../lib/handle_interface_serialization.h | 181 + mojo/public/cpp/bindings/lib/hash_util.h | 84 + .../bindings/lib/interface_endpoint_client.cc | 412 +++ .../cpp/bindings/lib/interface_ptr_state.h | 226 ++ .../cpp/bindings/lib/map_data_internal.h | 85 + .../cpp/bindings/lib/map_serialization.h | 182 + mojo/public/cpp/bindings/lib/may_auto_lock.h | 62 + mojo/public/cpp/bindings/lib/message.cc | 332 ++ .../public/cpp/bindings/lib/message_buffer.cc | 52 + mojo/public/cpp/bindings/lib/message_buffer.h | 43 + .../cpp/bindings/lib/message_builder.cc | 69 + .../public/cpp/bindings/lib/message_builder.h | 45 + .../bindings/lib/message_header_validator.cc | 133 + .../cpp/bindings/lib/message_internal.h | 82 + .../cpp/bindings/lib/multiplex_router.cc | 960 +++++ .../cpp/bindings/lib/multiplex_router.h | 275 ++ .../cpp/bindings/lib/native_enum_data.h | 26 + .../bindings/lib/native_enum_serialization.h | 82 + mojo/public/cpp/bindings/lib/native_struct.cc | 34 + .../cpp/bindings/lib/native_struct_data.cc | 22 + .../cpp/bindings/lib/native_struct_data.h | 38 + .../lib/native_struct_serialization.cc | 61 + .../lib/native_struct_serialization.h | 134 + .../lib/pipe_control_message_handler.cc | 90 + .../lib/pipe_control_message_proxy.cc | 68 + .../lib/scoped_interface_endpoint_handle.cc | 382 ++ mojo/public/cpp/bindings/lib/serialization.h | 107 + .../cpp/bindings/lib/serialization_context.cc | 57 + .../cpp/bindings/lib/serialization_context.h | 77 + .../cpp/bindings/lib/serialization_forward.h | 123 + .../cpp/bindings/lib/serialization_util.h | 213 ++ .../cpp/bindings/lib/string_serialization.h | 70 + .../bindings/lib/string_traits_string16.cc | 42 + .../cpp/bindings/lib/string_traits_wtf.cc | 84 + .../bindings/lib/sync_call_restrictions.cc | 93 + .../cpp/bindings/lib/sync_event_watcher.cc | 67 + .../cpp/bindings/lib/sync_handle_registry.cc | 135 + .../cpp/bindings/lib/sync_handle_watcher.cc | 76 + mojo/public/cpp/bindings/lib/template_util.h | 120 + mojo/public/cpp/bindings/lib/union_accessor.h | 33 + .../public/cpp/bindings/lib/validate_params.h | 88 + .../cpp/bindings/lib/validation_context.cc | 50 + .../cpp/bindings/lib/validation_context.h | 169 + .../cpp/bindings/lib/validation_errors.cc | 150 + .../cpp/bindings/lib/validation_errors.h | 167 + .../cpp/bindings/lib/validation_util.cc | 210 ++ .../public/cpp/bindings/lib/validation_util.h | 206 ++ .../cpp/bindings/lib/wtf_clone_equals_util.h | 78 + mojo/public/cpp/bindings/lib/wtf_hash_util.h | 132 + .../cpp/bindings/lib/wtf_serialization.h | 12 + mojo/public/cpp/bindings/map.h | 42 + mojo/public/cpp/bindings/map_data_view.h | 63 + mojo/public/cpp/bindings/map_traits.h | 56 + mojo/public/cpp/bindings/map_traits_stl.h | 109 + .../cpp/bindings/map_traits_wtf_hash_map.h | 64 + mojo/public/cpp/bindings/message.h | 302 ++ .../cpp/bindings/message_header_validator.h | 32 + mojo/public/cpp/bindings/native_enum.h | 28 + mojo/public/cpp/bindings/native_struct.h | 51 + .../cpp/bindings/native_struct_data_view.h | 36 + .../bindings/pipe_control_message_handler.h | 55 + .../pipe_control_message_handler_delegate.h | 29 + .../cpp/bindings/pipe_control_message_proxy.h | 45 + .../cpp/bindings/raw_ptr_impl_ref_traits.h | 22 + .../scoped_interface_endpoint_handle.h | 123 + mojo/public/cpp/bindings/string_data_view.h | 34 + mojo/public/cpp/bindings/string_traits.h | 54 + mojo/public/cpp/bindings/string_traits_stl.h | 38 + .../cpp/bindings/string_traits_string16.h | 37 + .../cpp/bindings/string_traits_string_piece.h | 43 + mojo/public/cpp/bindings/string_traits_wtf.h | 31 + .../cpp/bindings/strong_associated_binding.h | 125 + mojo/public/cpp/bindings/strong_binding.h | 125 + mojo/public/cpp/bindings/strong_binding_set.h | 26 + mojo/public/cpp/bindings/struct_ptr.h | 283 ++ mojo/public/cpp/bindings/struct_traits.h | 165 + .../cpp/bindings/sync_call_restrictions.h | 108 + mojo/public/cpp/bindings/sync_event_watcher.h | 68 + .../cpp/bindings/sync_handle_registry.h | 71 + .../public/cpp/bindings/sync_handle_watcher.h | 75 + mojo/public/cpp/bindings/tests/BUILD.gn | 146 + .../tests/associated_interface_unittest.cc | 1189 ++++++ .../tests/bind_task_runner_unittest.cc | 395 ++ .../tests/binding_callback_unittest.cc | 338 ++ .../bindings/tests/binding_set_unittest.cc | 416 +++ .../cpp/bindings/tests/binding_unittest.cc | 611 +++ .../cpp/bindings/tests/bindings_perftest.cc | 286 ++ .../cpp/bindings/tests/blink_typemaps.gni | 8 + .../cpp/bindings/tests/buffer_unittest.cc | 93 + .../cpp/bindings/tests/chromium_typemaps.gni | 9 + .../cpp/bindings/tests/connector_unittest.cc | 599 +++ .../cpp/bindings/tests/constant_unittest.cc | 60 + .../cpp/bindings/tests/container_test_util.cc | 52 + .../cpp/bindings/tests/container_test_util.h | 55 + .../cpp/bindings/tests/data_view_unittest.cc | 303 ++ .../public/cpp/bindings/tests/e2e_perftest.cc | 204 + .../cpp/bindings/tests/equals_unittest.cc | 122 + .../bindings/tests/handle_passing_unittest.cc | 356 ++ .../cpp/bindings/tests/hash_unittest.cc | 35 + .../bindings/tests/interface_ptr_unittest.cc | 937 +++++ .../public/cpp/bindings/tests/map_unittest.cc | 46 + .../cpp/bindings/tests/message_queue.cc | 39 + .../public/cpp/bindings/tests/message_queue.h | 44 + .../bindings/tests/mojo_test_blink_export.h | 29 + .../cpp/bindings/tests/mojo_test_export.h | 29 + .../tests/multiplex_router_unittest.cc | 314 ++ .../cpp/bindings/tests/pickle_unittest.cc | 403 ++ .../cpp/bindings/tests/pickled_types_blink.cc | 67 + .../cpp/bindings/tests/pickled_types_blink.h | 88 + .../bindings/tests/pickled_types_chromium.cc | 69 + .../bindings/tests/pickled_types_chromium.h | 81 + mojo/public/cpp/bindings/tests/rect_blink.h | 83 + .../cpp/bindings/tests/rect_blink.typemap | 18 + .../cpp/bindings/tests/rect_blink_traits.h | 35 + .../public/cpp/bindings/tests/rect_chromium.h | 87 + .../cpp/bindings/tests/rect_chromium.typemap | 18 + .../cpp/bindings/tests/rect_chromium_traits.h | 34 + .../tests/report_bad_message_unittest.cc | 194 + .../tests/request_response_unittest.cc | 157 + .../cpp/bindings/tests/router_test_util.cc | 111 + .../cpp/bindings/tests/router_test_util.h | 92 + .../bindings/tests/sample_service_unittest.cc | 362 ++ .../tests/serialization_warning_unittest.cc | 251 ++ mojo/public/cpp/bindings/tests/shared_rect.h | 43 + .../cpp/bindings/tests/shared_rect_traits.h | 33 + .../bindings/tests/struct_traits_unittest.cc | 553 +++ .../cpp/bindings/tests/struct_unittest.cc | 526 +++ .../bindings/tests/struct_with_traits.typemap | 26 + .../bindings/tests/struct_with_traits_impl.cc | 36 + .../bindings/tests/struct_with_traits_impl.h | 168 + .../tests/struct_with_traits_impl_traits.cc | 137 + .../tests/struct_with_traits_impl_traits.h | 196 + .../bindings/tests/sync_method_unittest.cc | 831 +++++ .../tests/test_native_types_blink.typemap | 17 + .../tests/test_native_types_chromium.typemap | 17 + .../tests/type_conversion_unittest.cc | 161 + .../cpp/bindings/tests/union_unittest.cc | 1246 +++++++ .../tests/validation_context_unittest.cc | 297 ++ .../tests/validation_test_input_parser.cc | 412 +++ .../tests/validation_test_input_parser.h | 121 + .../cpp/bindings/tests/validation_unittest.cc | 498 +++ .../cpp/bindings/tests/variant_test_util.h | 32 + .../cpp/bindings/tests/versioning_apptest.cc | 123 + .../bindings/tests/versioning_test_service.cc | 127 + .../cpp/bindings/tests/wtf_hash_unittest.cc | 60 + .../cpp/bindings/tests/wtf_map_unittest.cc | 41 + .../cpp/bindings/tests/wtf_types_unittest.cc | 245 ++ .../cpp/bindings/thread_safe_interface_ptr.h | 394 ++ mojo/public/cpp/bindings/type_converter.h | 116 + mojo/public/cpp/bindings/union_traits.h | 39 + .../cpp/bindings/unique_ptr_impl_ref_traits.h | 22 + mojo/public/cpp/system/BUILD.gn | 57 + mojo/public/cpp/system/README.md | 396 ++ mojo/public/cpp/system/buffer.cc | 46 + mojo/public/cpp/system/buffer.h | 82 + mojo/public/cpp/system/core.h | 14 + mojo/public/cpp/system/data_pipe.h | 163 + mojo/public/cpp/system/functions.h | 26 + mojo/public/cpp/system/handle.h | 220 ++ mojo/public/cpp/system/handle_signals_state.h | 83 + mojo/public/cpp/system/message.cc | 13 + mojo/public/cpp/system/message.h | 83 + mojo/public/cpp/system/message_pipe.h | 158 + mojo/public/cpp/system/platform_handle.cc | 178 + mojo/public/cpp/system/platform_handle.h | 92 + mojo/public/cpp/system/simple_watcher.cc | 279 ++ mojo/public/cpp/system/simple_watcher.h | 215 ++ mojo/public/cpp/system/system_export.h | 34 + mojo/public/cpp/system/tests/BUILD.gn | 23 + mojo/public/cpp/system/tests/core_unittest.cc | 510 +++ .../tests/handle_signals_state_unittest.cc | 42 + .../system/tests/simple_watcher_unittest.cc | 277 ++ .../cpp/system/tests/wait_set_unittest.cc | 376 ++ mojo/public/cpp/system/tests/wait_unittest.cc | 321 ++ mojo/public/cpp/system/wait.cc | 200 + mojo/public/cpp/system/wait.h | 75 + mojo/public/cpp/system/wait_set.cc | 371 ++ mojo/public/cpp/system/wait_set.h | 124 + mojo/public/cpp/system/watcher.cc | 20 + mojo/public/cpp/system/watcher.h | 37 + mojo/public/cpp/test_support/BUILD.gn | 19 + .../cpp/test_support/lib/test_support.cc | 26 + .../public/cpp/test_support/lib/test_utils.cc | 100 + mojo/public/cpp/test_support/test_support.h | 35 + mojo/public/cpp/test_support/test_utils.h | 40 + mojo/public/interfaces/BUILD.gn | 9 + mojo/public/interfaces/bindings/BUILD.gn | 29 + .../bindings/interface_control_messages.mojom | 67 + .../interface_control_messages.mojom | 67 + .../new_bindings/pipe_control_messages.mojom | 46 + .../bindings/pipe_control_messages.mojom | 46 + .../public/interfaces/bindings/tests/BUILD.gn | 204 + .../associated_conformance_mthd0_good.data | 26 + ...associated_conformance_mthd0_good.expected | 1 + ...ce_mthd0_illegal_invalid_interface_id.data | 24 + ...thd0_illegal_invalid_interface_id.expected | 1 + ...nce_mthd0_illegal_master_interface_id.data | 24 + ...mthd0_illegal_master_interface_id.expected | 1 + ...mthd0_interface_id_index_out_of_range.data | 27 + ...0_interface_id_index_out_of_range.expected | 1 + ...expected_invalid_associated_interface.data | 25 + ...cted_invalid_associated_interface.expected | 1 + .../associated_conformance_mthd1_good.data | 26 + ...associated_conformance_mthd1_good.expected | 1 + ...unexpected_invalid_associated_request.data | 27 + ...pected_invalid_associated_request.expected | 1 + .../associated_conformance_mthd2_good.data | 18 + ...associated_conformance_mthd2_good.expected | 1 + ...e_mthd3_collided_interface_id_indices.data | 36 + ...hd3_collided_interface_id_indices.expected | 1 + .../associated_conformance_mthd3_good.data | 35 + ...associated_conformance_mthd3_good.expected | 1 + ...invalid_associated_interface_in_array.data | 36 + ...lid_associated_interface_in_array.expected | 1 + ..._mthd3_wrong_interface_id_index_order.data | 38 + ...d3_wrong_interface_id_index_order.expected | 1 + .../boundscheck_msghdr_no_such_method.data | 7 + ...boundscheck_msghdr_no_such_method.expected | 1 + .../data/validation/conformance_empty.data | 0 .../validation/conformance_empty.expected | 1 + .../conformance_msghdr_incomplete_struct.data | 2 + ...formance_msghdr_incomplete_struct.expected | 1 + ...mance_msghdr_incomplete_struct_header.data | 1 + ...e_msghdr_incomplete_struct_header.expected | 1 + ...conformance_msghdr_invalid_flag_combo.data | 8 + ...ormance_msghdr_invalid_flag_combo.expected | 1 + ...conformance_msghdr_missing_request_id.data | 8 + ...ormance_msghdr_missing_request_id.expected | 1 + .../conformance_msghdr_no_such_method.data | 7 + ...conformance_msghdr_no_such_method.expected | 1 + .../conformance_msghdr_num_bytes_huge.data | 6 + ...conformance_msghdr_num_bytes_huge.expected | 1 + ...r_num_bytes_less_than_min_requirement.data | 4 + ...m_bytes_less_than_min_requirement.expected | 1 + ...hdr_num_bytes_less_than_struct_header.data | 6 + ...num_bytes_less_than_struct_header.expected | 1 + ...e_msghdr_num_bytes_version_mismatch_1.data | 9 + ...ghdr_num_bytes_version_mismatch_1.expected | 1 + ...e_msghdr_num_bytes_version_mismatch_2.data | 10 + ...ghdr_num_bytes_version_mismatch_2.expected | 1 + ...e_msghdr_num_bytes_version_mismatch_3.data | 7 + ...ghdr_num_bytes_version_mismatch_3.expected | 1 + .../validation/conformance_mthd0_good.data | 13 + .../conformance_mthd0_good.expected | 1 + .../conformance_mthd0_incomplete_struct.data | 11 + ...nformance_mthd0_incomplete_struct.expected | 1 + ...rmance_mthd0_incomplete_struct_header.data | 9 + ...ce_mthd0_incomplete_struct_header.expected | 1 + ...nformance_mthd0_invalid_request_flags.data | 8 + ...mance_mthd0_invalid_request_flags.expected | 1 + ...formance_mthd0_invalid_request_flags2.data | 9 + ...ance_mthd0_invalid_request_flags2.expected | 1 + ...nformance_mthd0_struct_num_bytes_huge.data | 12 + ...mance_mthd0_struct_num_bytes_huge.expected | 1 + ...t_num_bytes_less_than_min_requirement.data | 11 + ...m_bytes_less_than_min_requirement.expected | 1 + ...uct_num_bytes_less_than_struct_header.data | 12 + ...num_bytes_less_than_struct_header.expected | 1 + .../validation/conformance_mthd10_good.data | 48 + .../conformance_mthd10_good.expected | 1 + ...nformance_mthd10_good_non_unique_keys.data | 48 + ...mance_mthd10_good_non_unique_keys.expected | 1 + .../conformance_mthd10_null_keys.data | 25 + .../conformance_mthd10_null_keys.expected | 1 + .../conformance_mthd10_null_values.data | 40 + .../conformance_mthd10_null_values.expected | 1 + .../conformance_mthd10_one_null_key.data | 40 + .../conformance_mthd10_one_null_key.expected | 1 + ...conformance_mthd10_unequal_array_size.data | 48 + ...ormance_mthd10_unequal_array_size.expected | 1 + .../conformance_mthd11_good_version0.data | 19 + .../conformance_mthd11_good_version0.expected | 1 + .../conformance_mthd11_good_version1.data | 20 + .../conformance_mthd11_good_version1.expected | 1 + .../conformance_mthd11_good_version2.data | 20 + .../conformance_mthd11_good_version2.expected | 1 + .../conformance_mthd11_good_version3.data | 28 + .../conformance_mthd11_good_version3.expected | 1 + ...thd11_good_version_newer_than_known_1.data | 30 + ...1_good_version_newer_than_known_1.expected | 1 + ...thd11_good_version_newer_than_known_2.data | 30 + ...1_good_version_newer_than_known_2.expected | 1 + ...e_mthd11_num_bytes_version_mismatch_1.data | 21 + ...hd11_num_bytes_version_mismatch_1.expected | 1 + ...e_mthd11_num_bytes_version_mismatch_2.data | 19 + ...hd11_num_bytes_version_mismatch_2.expected | 1 + ...formance_mthd12_invalid_request_flags.data | 9 + ...ance_mthd12_invalid_request_flags.expected | 1 + .../validation/conformance_mthd13_good_1.data | 17 + .../conformance_mthd13_good_1.expected | 1 + .../validation/conformance_mthd13_good_2.data | 19 + .../conformance_mthd13_good_2.expected | 1 + ...ormance_mthd14_good_known_enum_values.data | 13 + ...nce_mthd14_good_known_enum_values.expected | 1 + ...d14_good_uknown_extensible_enum_value.data | 13 + ...good_uknown_extensible_enum_value.expected | 1 + ...hd14_uknown_non_extensible_enum_value.data | 14 + ..._uknown_non_extensible_enum_value.expected | 1 + ...nformance_mthd15_good_empy_enum_array.data | 22 + ...mance_mthd15_good_empy_enum_array.expected | 1 + ...e_mthd15_good_known_enum_array_values.data | 27 + ...hd15_good_known_enum_array_values.expected | 1 + ...od_uknown_extensible_enum_array_value.data | 20 + ...known_extensible_enum_array_value.expected | 1 + ...known_non_extensible_enum_array_value.data | 21 + ...n_non_extensible_enum_array_value.expected | 1 + ...16_uknown_non_extensible_enum_map_key.data | 34 + ...known_non_extensible_enum_map_key.expected | 1 + ..._uknown_non_extensible_enum_map_value.data | 34 + ...own_non_extensible_enum_map_value.expected | 1 + .../validation/conformance_mthd17_good.data | 23 + .../conformance_mthd17_good.expected | 1 + ...nterface_handle_out_of_range_in_array.data | 24 + ...face_handle_out_of_range_in_array.expected | 1 + ...unexpected_invalid_interface_in_array.data | 23 + ...pected_invalid_interface_in_array.expected | 1 + .../validation/conformance_mthd18_good.data | 14 + .../conformance_mthd18_good.expected | 1 + ...ormance_mthd19_exceed_recursion_limit.data | 612 +++ ...nce_mthd19_exceed_recursion_limit.expected | 1 + .../validation/conformance_mthd1_good.data | 18 + .../conformance_mthd1_good.expected | 1 + .../conformance_mthd1_misaligned_struct.data | 20 + ...nformance_mthd1_misaligned_struct.expected | 1 + ...ormance_mthd1_struct_pointer_overflow.data | 13 + ...nce_mthd1_struct_pointer_overflow.expected | 1 + ...formance_mthd1_unexpected_null_struct.data | 12 + ...ance_mthd1_unexpected_null_struct.expected | 1 + .../validation/conformance_mthd20_good.data | 57 + .../conformance_mthd20_good.expected | 1 + ...pty_extensible_enum_accepts_any_value.data | 13 + ...extensible_enum_accepts_any_value.expected | 1 + ..._nonextensible_enum_accepts_no_values.data | 14 + ...extensible_enum_accepts_no_values.expected | 1 + .../validation/conformance_mthd2_good.data | 34 + .../conformance_mthd2_good.expected | 1 + ...thd2_multiple_pointers_to_same_struct.data | 27 + ..._multiple_pointers_to_same_struct.expected | 1 + .../conformance_mthd2_overlapped_objects.data | 32 + ...formance_mthd2_overlapped_objects.expected | 1 + .../conformance_mthd2_wrong_layout_order.data | 34 + ...formance_mthd2_wrong_layout_order.expected | 1 + ...onformance_mthd3_array_num_bytes_huge.data | 18 + ...rmance_mthd3_array_num_bytes_huge.expected | 1 + ...rray_num_bytes_less_than_array_header.data | 18 + ..._num_bytes_less_than_array_header.expected | 1 + ...ay_num_bytes_less_than_necessary_size.data | 20 + ...um_bytes_less_than_necessary_size.expected | 1 + ...formance_mthd3_array_pointer_overflow.data | 13 + ...ance_mthd3_array_pointer_overflow.expected | 1 + .../validation/conformance_mthd3_good.data | 19 + .../conformance_mthd3_good.expected | 1 + .../conformance_mthd3_incomplete_array.data | 16 + ...onformance_mthd3_incomplete_array.expected | 1 + ...ormance_mthd3_incomplete_array_header.data | 15 + ...nce_mthd3_incomplete_array_header.expected | 1 + .../conformance_mthd3_misaligned_array.data | 21 + ...onformance_mthd3_misaligned_array.expected | 1 + ...nformance_mthd3_unexpected_null_array.data | 12 + ...mance_mthd3_unexpected_null_array.expected | 1 + .../validation/conformance_mthd4_good.data | 34 + .../conformance_mthd4_good.expected | 1 + ...mthd4_multiple_pointers_to_same_array.data | 26 + ...4_multiple_pointers_to_same_array.expected | 1 + .../conformance_mthd4_overlapped_objects.data | 32 + ...formance_mthd4_overlapped_objects.expected | 1 + .../conformance_mthd4_wrong_layout_order.data | 35 + ...formance_mthd4_wrong_layout_order.expected | 1 + .../validation/conformance_mthd5_good.data | 37 + .../conformance_mthd5_good.expected | 1 + ...conformance_mthd5_handle_out_of_range.data | 38 + ...ormance_mthd5_handle_out_of_range.expected | 1 + ...d5_multiple_handles_with_same_value_1.data | 37 + ...ultiple_handles_with_same_value_1.expected | 1 + ...d5_multiple_handles_with_same_value_2.data | 37 + ...ultiple_handles_with_same_value_2.expected | 1 + ...mance_mthd5_unexpected_invalid_handle.data | 36 + ...e_mthd5_unexpected_invalid_handle.expected | 1 + .../conformance_mthd5_wrong_handle_order.data | 38 + ...formance_mthd5_wrong_handle_order.expected | 1 + .../validation/conformance_mthd6_good.data | 24 + .../conformance_mthd6_good.expected | 1 + ...ay_num_bytes_less_than_necessary_size.data | 24 + ...um_bytes_less_than_necessary_size.expected | 1 + .../validation/conformance_mthd7_good.data | 40 + .../conformance_mthd7_good.expected | 1 + ...nce_mthd7_unexpected_null_fixed_array.data | 26 + ...mthd7_unexpected_null_fixed_array.expected | 1 + ...rmance_mthd7_unmatched_array_elements.data | 34 + ...ce_mthd7_unmatched_array_elements.expected | 1 + ...mthd7_unmatched_array_elements_nested.data | 40 + ...7_unmatched_array_elements_nested.expected | 1 + ...rmance_mthd8_array_num_bytes_overflow.data | 21 + ...ce_mthd8_array_num_bytes_overflow.expected | 1 + .../validation/conformance_mthd8_good.data | 32 + .../conformance_mthd8_good.expected | 1 + ...nformance_mthd8_unexpected_null_array.data | 12 + ...mance_mthd8_unexpected_null_array.expected | 1 + ...formance_mthd8_unexpected_null_string.data | 33 + ...ance_mthd8_unexpected_null_string.expected | 1 + .../validation/conformance_mthd9_good.data | 36 + .../conformance_mthd9_good.expected | 1 + .../conformance_mthd9_good_null_array.data | 14 + ...conformance_mthd9_good_null_array.expected | 1 + ...nformance_mthd9_unexpected_null_array.data | 27 + ...mance_mthd9_unexpected_null_array.expected | 1 + .../integration_intf_resp_mthd0_good.data | 19 + .../integration_intf_resp_mthd0_good.expected | 1 + ...tf_resp_mthd0_unexpected_array_header.data | 19 + ...esp_mthd0_unexpected_array_header.expected | 1 + .../integration_intf_rqst_mthd0_good.data | 20 + .../integration_intf_rqst_mthd0_good.expected | 1 + ...f_rqst_mthd0_unexpected_struct_header.data | 17 + ...st_mthd0_unexpected_struct_header.expected | 1 + .../integration_msghdr_invalid_flags.data | 8 + .../integration_msghdr_invalid_flags.expected | 1 + ...esp_boundscheck_msghdr_no_such_method.data | 8 + ...boundscheck_msghdr_no_such_method.expected | 1 + ...rmance_msghdr_invalid_response_flags1.data | 8 + ...ce_msghdr_invalid_response_flags1.expected | 1 + ...rmance_msghdr_invalid_response_flags2.data | 8 + ...ce_msghdr_invalid_response_flags2.expected | 1 + ...esp_conformance_msghdr_no_such_method.data | 8 + ...conformance_msghdr_no_such_method.expected | 1 + .../interfaces/bindings/tests/echo.mojom | 12 + .../bindings/tests/echo_import.mojom | 10 + .../bindings/tests/math_calculator.mojom | 12 + .../interfaces/bindings/tests/no_module.mojom | 9 + .../bindings/tests/ping_service.mojom | 14 + .../interfaces/bindings/tests/rect.mojom | 31 + .../bindings/tests/regression_tests.mojom | 76 + .../bindings/tests/sample_factory.mojom | 41 + .../bindings/tests/sample_import.mojom | 38 + .../bindings/tests/sample_import2.mojom | 28 + .../bindings/tests/sample_interfaces.mojom | 32 + .../bindings/tests/sample_service.mojom | 112 + .../interfaces/bindings/tests/scoping.mojom | 17 + .../tests/serialization_test_structs.mojom | 36 + .../bindings/tests/struct_with_traits.mojom | 83 + .../tests/test_associated_interfaces.mojom | 54 + .../bindings/tests/test_bad_messages.mojom | 13 + .../bindings/tests/test_constants.mojom | 57 + .../bindings/tests/test_data_view.mojom | 41 + .../bindings/tests/test_export.mojom | 20 + .../bindings/tests/test_export2.mojom | 10 + .../bindings/tests/test_import.mojom | 11 + .../bindings/tests/test_native_types.mojom | 38 + .../bindings/tests/test_structs.mojom | 414 +++ .../bindings/tests/test_sync_methods.mojom | 44 + .../bindings/tests/test_unions.mojom | 105 + .../bindings/tests/test_wtf_types.mojom | 50 + ...alidation_test_associated_interfaces.mojom | 18 + .../tests/validation_test_interfaces.mojom | 135 + .../tests/versioning_test_client.mojom | 34 + .../tests/versioning_test_service.mojom | 38 + mojo/public/java/BUILD.gn | 64 + mojo/public/java/bindings/README.md | 12 + .../AssociatedInterfaceNotSupported.java | 11 + ...ssociatedInterfaceRequestNotSupported.java | 11 + .../mojo/bindings/AutoCloseableRouter.java | 116 + .../mojo/bindings/BindingsHelper.java | 199 + .../org/chromium/mojo/bindings/Callbacks.java | 130 + .../mojo/bindings/ConnectionErrorHandler.java | 15 + .../org/chromium/mojo/bindings/Connector.java | 214 ++ .../chromium/mojo/bindings/DataHeader.java | 70 + .../org/chromium/mojo/bindings/Decoder.java | 776 ++++ .../DelegatingConnectionErrorHandler.java | 49 + .../bindings/DeserializationException.java | 26 + .../org/chromium/mojo/bindings/Encoder.java | 587 +++ .../mojo/bindings/ExecutorFactory.java | 169 + .../chromium/mojo/bindings/HandleOwner.java | 29 + .../org/chromium/mojo/bindings/Interface.java | 425 +++ .../InterfaceControlMessagesHelper.java | 105 + .../mojo/bindings/InterfaceRequest.java | 58 + .../org/chromium/mojo/bindings/Message.java | 69 + .../chromium/mojo/bindings/MessageHeader.java | 248 ++ .../mojo/bindings/MessageReceiver.java | 25 + .../MessageReceiverWithResponder.java | 21 + .../org/chromium/mojo/bindings/Router.java | 31 + .../chromium/mojo/bindings/RouterImpl.java | 274 ++ .../mojo/bindings/SerializationException.java | 26 + .../mojo/bindings/ServiceMessage.java | 73 + .../bindings/SideEffectFreeCloseable.java | 21 + .../org/chromium/mojo/bindings/Struct.java | 88 + .../src/org/chromium/mojo/bindings/Union.java | 30 + mojo/public/java/system/README.md | 25 + .../src/org/chromium/mojo/system/Core.java | 183 + .../org/chromium/mojo/system/DataPipe.java | 334 ++ .../src/org/chromium/mojo/system/Flags.java | 83 + .../src/org/chromium/mojo/system/Handle.java | 61 + .../chromium/mojo/system/InvalidHandle.java | 219 ++ .../mojo/system/MessagePipeHandle.java | 224 ++ .../chromium/mojo/system/MojoException.java | 44 + .../org/chromium/mojo/system/MojoResult.java | 82 + .../src/org/chromium/mojo/system/Pair.java | 67 + .../org/chromium/mojo/system/ResultAnd.java | 34 + .../src/org/chromium/mojo/system/RunLoop.java | 41 + .../mojo/system/SharedBufferHandle.java | 160 + .../chromium/mojo/system/UntypedHandle.java | 45 + .../src/org/chromium/mojo/system/Watcher.java | 38 + mojo/public/js/BUILD.gn | 93 + mojo/public/js/README.md | 7 + mojo/public/js/bindings.js | 322 ++ mojo/public/js/buffer.js | 156 + mojo/public/js/codec.js | 926 +++++ mojo/public/js/connector.js | 113 + mojo/public/js/constants.cc | 33 + mojo/public/js/constants.h | 30 + mojo/public/js/core.js | 304 ++ mojo/public/js/interface_types.js | 70 + mojo/public/js/lib/control_message_handler.js | 111 + mojo/public/js/lib/control_message_proxy.js | 104 + .../js/lib/interface_endpoint_client.js | 232 ++ .../js/lib/interface_endpoint_handle.js | 158 + .../js/lib/pipe_control_message_handler.js | 61 + .../js/lib/pipe_control_message_proxy.js | 56 + mojo/public/js/new_bindings/base.js | 111 + mojo/public/js/new_bindings/bindings.js | 275 ++ mojo/public/js/new_bindings/buffer.js | 155 + mojo/public/js/new_bindings/codec.js | 917 +++++ mojo/public/js/new_bindings/connector.js | 104 + .../public/js/new_bindings/interface_types.js | 46 + .../lib/control_message_handler.js | 106 + .../new_bindings/lib/control_message_proxy.js | 97 + mojo/public/js/new_bindings/router.js | 190 + mojo/public/js/new_bindings/unicode.js | 51 + mojo/public/js/new_bindings/validator.js | 511 +++ mojo/public/js/router.js | 269 ++ mojo/public/js/support.js | 53 + mojo/public/js/tests/core_unittest.js | 223 ++ .../js/tests/validation_test_input_parser.js | 299 ++ mojo/public/js/tests/validation_unittest.js | 334 ++ mojo/public/js/threading.js | 21 + mojo/public/js/unicode.js | 51 + mojo/public/js/validator.js | 560 +++ mojo/public/tests/test_support_private.cc | 76 + mojo/public/tests/test_support_private.h | 37 + mojo/public/tools/bindings/BUILD.gn | 77 + mojo/public/tools/bindings/README.md | 749 ++++ .../bindings/blink_bindings_configuration.gni | 33 + .../chromium_bindings_configuration.gni | 83 + .../bindings/format_typemap_generator_args.py | 34 + .../tools/bindings/generate_type_mappings.py | 148 + .../generators/cpp_templates/enum_macros.tmpl | 131 + .../enum_serialization_declaration.tmpl | 29 + .../cpp_templates/interface_declaration.tmpl | 65 + .../cpp_templates/interface_definition.tmpl | 448 +++ .../cpp_templates/interface_macros.tmpl | 49 + .../interface_proxy_declaration.tmpl | 16 + ...terface_request_validator_declaration.tmpl | 4 + ...erface_response_validator_declaration.tmpl | 4 + .../interface_stub_declaration.tmpl | 41 + .../module-shared-internal.h.tmpl | 96 + .../cpp_templates/module-shared.cc.tmpl | 64 + .../cpp_templates/module-shared.h.tmpl | 212 ++ .../generators/cpp_templates/module.cc.tmpl | 111 + .../generators/cpp_templates/module.h.tmpl | 236 ++ .../struct_data_view_declaration.tmpl | 118 + .../struct_data_view_definition.tmpl | 30 + .../cpp_templates/struct_declaration.tmpl | 46 + .../cpp_templates/struct_definition.tmpl | 70 + .../cpp_templates/struct_macros.tmpl | 161 + .../struct_serialization_declaration.tmpl | 57 + .../struct_traits_declaration.tmpl | 32 + .../struct_traits_definition.tmpl | 14 + .../union_data_view_declaration.tmpl | 92 + .../union_data_view_definition.tmpl | 12 + .../cpp_templates/union_declaration.tmpl | 56 + .../cpp_templates/union_definition.tmpl | 47 + .../union_serialization_declaration.tmpl | 141 + .../union_traits_declaration.tmpl | 24 + .../union_traits_definition.tmpl | 47 + .../cpp_templates/validation_macros.tmpl | 82 + .../wrapper_class_declaration.tmpl | 94 + .../wrapper_class_definition.tmpl | 39 + .../wrapper_class_template_definition.tmpl | 20 + .../wrapper_union_class_declaration.tmpl | 80 + .../wrapper_union_class_definition.tmpl | 85 + ...apper_union_class_template_definition.tmpl | 41 + .../java_templates/constant_definition.tmpl | 3 + .../java_templates/constants.java.tmpl | 12 + .../java_templates/data_types_definition.tmpl | 418 +++ .../generators/java_templates/enum.java.tmpl | 4 + .../java_templates/enum_definition.tmpl | 42 + .../java_templates/header.java.tmpl | 14 + .../java_templates/interface.java.tmpl | 4 + .../java_templates/interface_definition.tmpl | 297 ++ .../interface_internal.java.tmpl | 4 + .../java_templates/struct.java.tmpl | 4 + .../generators/java_templates/union.java.tmpl | 4 + .../js_templates/enum_definition.tmpl | 33 + .../js_templates/interface_definition.tmpl | 198 + .../generators/js_templates/module.amd.tmpl | 70 + .../js_templates/module_definition.tmpl | 49 + .../js_templates/struct_definition.tmpl | 126 + .../js_templates/union_definition.tmpl | 154 + .../js_templates/validation_macros.tmpl | 60 + .../generators/mojom_cpp_generator.py | 818 ++++ .../generators/mojom_java_generator.py | 550 +++ .../bindings/generators/mojom_js_generator.py | 425 +++ mojo/public/tools/bindings/mojom.gni | 661 ++++ .../bindings/mojom_bindings_generator.py | 336 ++ .../mojom_bindings_generator_unittest.py | 23 + .../tools/bindings/pylib/mojom/__init__.py | 0 .../tools/bindings/pylib/mojom/error.py | 27 + .../tools/bindings/pylib/mojom/fileutil.py | 18 + .../bindings/pylib/mojom/generate/__init__.py | 0 .../pylib/mojom/generate/constant_resolver.py | 91 + .../pylib/mojom/generate/generator.py | 153 + .../mojom/generate/generator_unittest.py | 24 + .../bindings/pylib/mojom/generate/module.py | 891 +++++ .../pylib/mojom/generate/module_tests.py | 34 + .../bindings/pylib/mojom/generate/pack.py | 250 ++ .../pylib/mojom/generate/pack_tests.py | 193 + .../pylib/mojom/generate/run_tests.py | 35 + .../pylib/mojom/generate/template_expander.py | 67 + .../pylib/mojom/generate/test_support.py | 193 + .../pylib/mojom/generate/translate.py | 639 ++++ .../bindings/pylib/mojom/parse/__init__.py | 0 .../tools/bindings/pylib/mojom/parse/ast.py | 410 +++ .../tools/bindings/pylib/mojom/parse/lexer.py | 254 ++ .../bindings/pylib/mojom/parse/parser.py | 461 +++ .../bindings/pylib/mojom_tests/__init__.py | 0 .../pylib/mojom_tests/fileutil_unittest.py | 55 + .../pylib/mojom_tests/generate/__init__.py | 0 .../mojom_tests/generate/data_unittest.py | 156 + .../generate/generator_unittest.py | 37 + .../mojom_tests/generate/module_unittest.py | 48 + .../mojom_tests/generate/pack_unittest.py | 136 + .../pylib/mojom_tests/parse/__init__.py | 0 .../pylib/mojom_tests/parse/ast_unittest.py | 135 + .../pylib/mojom_tests/parse/lexer_unittest.py | 192 + .../mojom_tests/parse/parser_unittest.py | 1497 ++++++++ .../pylib/mojom_tests/parse/run_parser.py | 36 + .../pylib/mojom_tests/parse/run_translate.py | 34 + .../mojom_tests/parse/translate_unittest.py | 80 + .../pylib/mojom_tests/support/__init__.py | 0 .../pylib/mojom_tests/support/find_files.py | 32 + .../support/run_bindings_generator.py | 47 + .../public/tools/chrome_ipc/generate_mojom.py | 453 +++ mojo/public/tools/gn/zip.py | 86 + third_party/catapult/LICENSE | 27 + third_party/catapult/devil/PRESUBMIT.py | 81 + third_party/catapult/devil/README.md | 37 + .../catapult/devil/bin/generate_md_docs | 45 + .../catapult/devil/bin/run_py_devicetests | 32 + third_party/catapult/devil/bin/run_py_tests | 22 + third_party/catapult/devil/devil/__init__.py | 7 + .../catapult/devil/devil/android/__init__.py | 3 + .../devil/devil/android/apk_helper.py | 164 + .../devil/devil/android/apk_helper_test.py | 169 + .../catapult/devil/devil/android/app_ui.py | 243 ++ .../devil/devil/android/app_ui_test.py | 191 + .../devil/devil/android/battery_utils.py | 699 ++++ .../devil/devil/android/battery_utils_test.py | 694 ++++ .../devil/devil/android/constants/__init__.py | 3 + .../devil/devil/android/constants/chrome.py | 57 + .../devil/android/constants/file_system.py | 5 + .../devil/devil/android/decorators.py | 176 + .../devil/devil/android/decorators_test.py | 332 ++ .../devil/devil/android/device_blacklist.py | 80 + .../devil/android/device_blacklist_test.py | 38 + .../devil/devil/android/device_errors.py | 180 + .../devil/devil/android/device_errors_test.py | 72 + .../devil/devil/android/device_list.py | 52 + .../devil/devil/android/device_signal.py | 41 + .../devil/devil/android/device_temp_file.py | 63 + .../devil/devil/android/device_test_case.py | 54 + .../devil/devil/android/device_utils.py | 2640 +++++++++++++ .../devil/android/device_utils_devicetest.py | 229 ++ .../devil/devil/android/device_utils_test.py | 2900 +++++++++++++++ .../devil/devil/android/fastboot_utils.py | 256 ++ .../devil/android/fastboot_utils_test.py | 375 ++ .../devil/devil/android/flag_changer.py | 300 ++ .../devil/android/flag_changer_devicetest.py | 88 + .../devil/devil/android/flag_changer_test.py | 135 + .../catapult/devil/devil/android/forwarder.py | 464 +++ .../devil/devil/android/install_commands.py | 57 + .../devil/devil/android/logcat_monitor.py | 255 ++ .../devil/android/logcat_monitor_test.py | 230 ++ .../catapult/devil/devil/android/md5sum.py | 120 + .../devil/devil/android/md5sum_test.py | 237 ++ .../devil/devil/android/perf/__init__.py | 3 + .../devil/devil/android/perf/cache_control.py | 15 + .../devil/devil/android/perf/perf_control.py | 210 ++ .../android/perf/perf_control_devicetest.py | 38 + .../android/perf/surface_stats_collector.py | 186 + .../devil/android/perf/thermal_throttle.py | 135 + .../catapult/devil/devil/android/ports.py | 178 + .../devil/devil/android/sdk/__init__.py | 6 + .../catapult/devil/devil/android/sdk/aapt.py | 43 + .../sdk/adb_compatibility_devicetest.py | 230 ++ .../devil/devil/android/sdk/adb_wrapper.py | 917 +++++ .../android/sdk/adb_wrapper_devicetest.py | 118 + .../devil/android/sdk/adb_wrapper_test.py | 59 + .../devil/devil/android/sdk/build_tools.py | 51 + .../devil/devil/android/sdk/dexdump.py | 31 + .../devil/devil/android/sdk/fastboot.py | 98 + .../devil/android/sdk/gce_adb_wrapper.py | 154 + .../devil/devil/android/sdk/intent.py | 129 + .../devil/devil/android/sdk/keyevent.py | 63 + .../devil/devil/android/sdk/shared_prefs.py | 420 +++ .../devil/android/sdk/shared_prefs_test.py | 171 + .../devil/devil/android/sdk/split_select.py | 63 + .../push_directory_contents.txt | 1 + .../devil/android/sdk/test/data/push_file.txt | 1 + .../devil/devil/android/sdk/version_codes.py | 20 + .../catapult/devil/devil/android/settings.py | 273 ++ .../devil/devil/android/tools/__init__.py | 3 + .../devil/android/tools/adb_run_shell_cmd.py | 61 + .../devil/devil/android/tools/cpufreq.py | 87 + .../devil/android/tools/device_monitor.py | 231 ++ .../android/tools/device_monitor_test.py | 168 + .../devil/android/tools/device_recovery.py | 208 ++ .../devil/android/tools/device_status.py | 313 ++ .../devil/devil/android/tools/flash_device.py | 70 + .../devil/devil/android/tools/keyboard.py | 129 + .../devil/android/tools/provision_devices.py | 637 ++++ .../devil/devil/android/tools/screenshot.py | 59 + .../devil/android/tools/script_common.py | 29 + .../devil/android/tools/script_common_test.py | 58 + .../devil/android/tools/video_recorder.py | 175 + .../devil/android/tools/wait_for_devices.py | 50 + .../devil/android/valgrind_tools/__init__.py | 21 + .../devil/android/valgrind_tools/base_tool.py | 53 + .../catapult/devil/devil/base_error.py | 24 + .../devil/devil/constants/__init__.py | 3 + .../devil/devil/constants/exit_codes.py | 9 + .../devil/devil/devil_dependencies.json | 127 + third_party/catapult/devil/devil/devil_env.py | 194 + .../catapult/devil/devil/devil_env_test.py | 63 + .../catapult/devil/devil/utils/__init__.py | 23 + .../devil/utils/battor_device_mapping.py | 309 ++ .../catapult/devil/devil/utils/cmd_helper.py | 394 ++ .../devil/devil/utils/cmd_helper_test.py | 262 ++ .../catapult/devil/devil/utils/file_utils.py | 31 + .../devil/devil/utils/find_usb_devices.py | 532 +++ .../devil/utils/find_usb_devices_test.py | 379 ++ .../catapult/devil/devil/utils/geometry.py | 75 + .../devil/devil/utils/geometry_test.py | 61 + .../catapult/devil/devil/utils/host_utils.py | 16 + .../devil/devil/utils/lazy/__init__.py | 5 + .../devil/devil/utils/lazy/weak_constant.py | 29 + .../catapult/devil/devil/utils/lsusb.py | 174 + .../catapult/devil/devil/utils/lsusb_test.py | 250 ++ .../catapult/devil/devil/utils/markdown.py | 320 ++ .../devil/devil/utils/markdown_test.py | 121 + .../catapult/devil/devil/utils/mock_calls.py | 180 + .../devil/devil/utils/mock_calls_test.py | 173 + .../devil/devil/utils/parallelizer.py | 238 ++ .../devil/devil/utils/parallelizer_test.py | 162 + .../devil/devil/utils/reraiser_thread.py | 228 ++ .../devil/utils/reraiser_thread_unittest.py | 117 + .../catapult/devil/devil/utils/reset_usb.py | 111 + .../devil/devil/utils/run_tests_helper.py | 44 + .../devil/devil/utils/signal_handler.py | 48 + .../utils/test/data/test_serial_map.json | 1 + .../devil/devil/utils/timeout_retry.py | 175 + .../devil/utils/timeout_retry_unittest.py | 79 + .../devil/devil/utils/update_mapping.py | 47 + .../catapult/devil/devil/utils/usb_hubs.py | 165 + .../devil/devil/utils/watchdog_timer.py | 47 + .../catapult/devil/devil/utils/zip_utils.py | 33 + .../catapult/devil/docs/adb_wrapper.md | 388 ++ .../catapult/devil/docs/device_blacklist.md | 59 + .../catapult/devil/docs/device_utils.md | 1041 ++++++ third_party/catapult/devil/docs/markdown.md | 139 + .../devil/docs/persistent_device_list.md | 41 + third_party/catapult/devil/pylintrc | 68 + third_party/jinja2/AUTHORS | 33 + third_party/jinja2/Jinja2-2.8.tar.gz.md5 | 1 + third_party/jinja2/Jinja2-2.8.tar.gz.sha512 | 1 + third_party/jinja2/LICENSE | 31 + third_party/jinja2/README.chromium | 25 + third_party/jinja2/__init__.py | 70 + third_party/jinja2/_compat.py | 111 + third_party/jinja2/_stringdefs.py | 132 + third_party/jinja2/bccache.py | 362 ++ third_party/jinja2/compiler.py | 1686 +++++++++ third_party/jinja2/constants.py | 32 + third_party/jinja2/debug.py | 350 ++ third_party/jinja2/defaults.py | 43 + third_party/jinja2/environment.py | 1213 ++++++ third_party/jinja2/exceptions.py | 146 + third_party/jinja2/ext.py | 636 ++++ third_party/jinja2/filters.py | 996 +++++ third_party/jinja2/get_jinja2.sh | 122 + third_party/jinja2/lexer.py | 734 ++++ third_party/jinja2/loaders.py | 481 +++ third_party/jinja2/meta.py | 103 + third_party/jinja2/nodes.py | 919 +++++ third_party/jinja2/optimizer.py | 68 + third_party/jinja2/parser.py | 899 +++++ third_party/jinja2/runtime.py | 667 ++++ third_party/jinja2/sandbox.py | 367 ++ third_party/jinja2/tests.py | 173 + third_party/jinja2/utils.py | 531 +++ third_party/jinja2/visitor.py | 87 + third_party/markupsafe/AUTHORS | 13 + third_party/markupsafe/LICENSE | 33 + .../markupsafe/MarkupSafe-0.18.tar.gz.md5 | 1 + .../markupsafe/MarkupSafe-0.18.tar.gz.sha512 | 1 + third_party/markupsafe/README.chromium | 24 + third_party/markupsafe/__init__.py | 234 ++ third_party/markupsafe/_compat.py | 24 + third_party/markupsafe/_constants.py | 267 ++ third_party/markupsafe/_native.py | 46 + third_party/markupsafe/_speedups.c | 239 ++ third_party/markupsafe/get_markupsafe.sh | 121 + third_party/ply/LICENSE | 30 + third_party/ply/README | 271 ++ third_party/ply/README.chromium | 21 + third_party/ply/__init__.py | 36 + third_party/ply/lex.py | 1058 ++++++ third_party/ply/license.patch | 41 + third_party/ply/yacc.py | 3276 +++++++++++++++++ ui/gfx/geometry/mojo/BUILD.gn | 49 + ui/gfx/geometry/mojo/DEPS | 4 + ui/gfx/geometry/mojo/geometry.mojom | 63 + ui/gfx/geometry/mojo/geometry.typemap | 32 + ui/gfx/geometry/mojo/geometry_struct_traits.h | 146 + .../mojo/geometry_struct_traits_unittest.cc | 206 ++ .../mojo/geometry_traits_test_service.mojom | 41 + ui/gfx/range/mojo/BUILD.gn | 49 + ui/gfx/range/mojo/DEPS | 4 + ui/gfx/range/mojo/range.mojom | 15 + ui/gfx/range/mojo/range.typemap | 17 + ui/gfx/range/mojo/range_struct_traits.h | 38 + .../mojo/range_struct_traits_unittest.cc | 65 + .../mojo/range_traits_test_service.mojom | 17 + 1251 files changed, 179928 insertions(+), 7 deletions(-) create mode 100644 .gitignore create mode 100644 base/android/build_info.cc create mode 100644 base/android/build_info.h create mode 100644 base/android/context_utils.cc create mode 100644 base/android/context_utils.h create mode 100644 base/android/java/src/org/chromium/base/BuildInfo.java create mode 100644 base/android/java/src/org/chromium/base/ContextUtils.java create mode 100644 base/android/java/src/org/chromium/base/Log.java create mode 100644 base/android/java/src/org/chromium/base/PackageUtils.java create mode 100644 base/android/java/src/org/chromium/base/VisibleForTesting.java create mode 100644 base/android/java/src/org/chromium/base/annotations/AccessedByNative.java create mode 100644 base/android/java/src/org/chromium/base/annotations/CalledByNative.java create mode 100644 base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java create mode 100644 base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java create mode 100644 base/android/java/src/org/chromium/base/annotations/JNINamespace.java create mode 100644 base/android/java/src/org/chromium/base/annotations/MainDex.java create mode 100644 base/android/java/src/org/chromium/base/annotations/NativeCall.java create mode 100644 base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java create mode 100644 base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java create mode 100644 base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java create mode 100644 base/android/java/src/org/chromium/base/annotations/UsedByReflection.java create mode 100644 base/android/java_runtime.cc create mode 100644 base/android/java_runtime.h create mode 100644 base/android/jni_android.cc create mode 100644 base/android/jni_android.h create mode 100644 base/android/jni_android_unittest.cc create mode 100644 base/android/jni_generator/android_jar.classes create mode 100644 base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java create mode 100755 base/android/jni_generator/jni_generator.py create mode 100644 base/android/jni_generator/jni_generator_helper.h create mode 100755 base/android/jni_generator/jni_generator_tests.py create mode 100644 base/android/jni_generator/sample_for_tests.cc create mode 100644 base/android/jni_generator/sample_for_tests.h create mode 100644 base/android/jni_generator/testCalledByNatives.golden create mode 100644 base/android/jni_generator/testConstantsFromJavaP.golden create mode 100644 base/android/jni_generator/testFromJavaP.golden create mode 100644 base/android/jni_generator/testFromJavaPGenerics.golden create mode 100644 base/android/jni_generator/testInnerClassNatives.golden create mode 100644 base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden create mode 100644 base/android/jni_generator/testInnerClassNativesMultiple.golden create mode 100644 base/android/jni_generator/testInputStream.javap create mode 100644 base/android/jni_generator/testMotionEvent.javap create mode 100644 base/android/jni_generator/testMotionEvent.javap7 create mode 100644 base/android/jni_generator/testMultipleJNIAdditionalImport.golden create mode 100644 base/android/jni_generator/testNatives.golden create mode 100644 base/android/jni_generator/testNativesLong.golden create mode 100644 base/android/jni_generator/testSingleJNIAdditionalImport.golden create mode 100644 base/android/jni_int_wrapper.h create mode 100644 base/android/jni_string.cc create mode 100644 base/android/jni_string.h create mode 100644 base/android/jni_string_unittest.cc create mode 100644 base/android/scoped_java_ref.cc create mode 100644 base/android/scoped_java_ref.h create mode 100644 base/android/scoped_java_ref_unittest.cc create mode 100644 build/android/gyp/util/__init__.py create mode 100644 build/android/gyp/util/build_utils.py create mode 100644 build/android/gyp/util/md5_check.py create mode 100644 build/android/pylib/__init__.py create mode 100644 build/android/pylib/constants/__init__.py create mode 100644 build/android/pylib/constants/host_paths.py create mode 100644 build/gn_helpers.py create mode 100644 gen/mojo/common/common_custom_types__type_mappings create mode 100644 ipc/ipc.mojom create mode 100644 ipc/ipc_channel_handle.h create mode 100644 ipc/ipc_export.h create mode 100644 ipc/ipc_listener.h create mode 100644 ipc/ipc_message.cc create mode 100644 ipc/ipc_message.h create mode 100644 ipc/ipc_message_attachment.cc create mode 100644 ipc/ipc_message_attachment.h create mode 100644 ipc/ipc_message_attachment_set.cc create mode 100644 ipc/ipc_message_attachment_set.h create mode 100644 ipc/ipc_message_start.h create mode 100644 ipc/ipc_message_utils.cc create mode 100644 ipc/ipc_message_utils.h create mode 100644 ipc/ipc_mojo_handle_attachment.cc create mode 100644 ipc/ipc_mojo_handle_attachment.h create mode 100644 ipc/ipc_mojo_message_helper.cc create mode 100644 ipc/ipc_mojo_message_helper.h create mode 100644 ipc/ipc_mojo_param_traits.cc create mode 100644 ipc/ipc_mojo_param_traits.h create mode 100644 ipc/ipc_param_traits.h create mode 100644 ipc/ipc_platform_file_attachment_posix.cc create mode 100644 ipc/ipc_platform_file_attachment_posix.h create mode 100644 ipc/ipc_sync_message.h create mode 100755 libchrome_tools/jni_generator_helper.sh create mode 100755 libchrome_tools/mojom_source_generator.sh create mode 100644 libchrome_tools/patch/mojo.patch create mode 100644 libmojo.pc.in create mode 100644 mojo/BUILD.gn create mode 100644 mojo/DEPS create mode 100644 mojo/PRESUBMIT.py create mode 100644 mojo/README.md create mode 100644 mojo/android/BUILD.gn create mode 100644 mojo/android/DEPS create mode 100644 mojo/android/javatests/AndroidManifest.xml create mode 100644 mojo/android/javatests/DEPS create mode 100644 mojo/android/javatests/apk/.empty create mode 100644 mojo/android/javatests/init_library.cc create mode 100644 mojo/android/javatests/mojo_test_case.cc create mode 100644 mojo/android/javatests/mojo_test_case.h create mode 100644 mojo/android/javatests/src/org/chromium/mojo/HandleMock.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/TestUtils.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java create mode 100644 mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java create mode 100644 mojo/android/javatests/validation_test_util.cc create mode 100644 mojo/android/javatests/validation_test_util.h create mode 100644 mojo/android/system/base_run_loop.cc create mode 100644 mojo/android/system/base_run_loop.h create mode 100644 mojo/android/system/core_impl.cc create mode 100644 mojo/android/system/core_impl.h create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java create mode 100644 mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java create mode 100644 mojo/android/system/watcher_impl.cc create mode 100644 mojo/android/system/watcher_impl.h create mode 100644 mojo/common/BUILD.gn create mode 100644 mojo/common/DEPS create mode 100644 mojo/common/common_custom_types_struct_traits.cc create mode 100644 mojo/common/common_custom_types_struct_traits.h create mode 100644 mojo/common/common_custom_types_unittest.cc create mode 100644 mojo/common/data_pipe_drainer.cc create mode 100644 mojo/common/data_pipe_drainer.h create mode 100644 mojo/common/data_pipe_utils.cc create mode 100644 mojo/common/data_pipe_utils.h create mode 100644 mojo/common/file.mojom create mode 100644 mojo/common/file.typemap create mode 100644 mojo/common/file_path.mojom create mode 100644 mojo/common/file_path.typemap create mode 100644 mojo/common/mojo_common_export.h create mode 100644 mojo/common/string16.mojom create mode 100644 mojo/common/string16.typemap create mode 100644 mojo/common/struct_traits_unittest.cc create mode 100644 mojo/common/test_common_custom_types.mojom create mode 100644 mojo/common/text_direction.mojom create mode 100644 mojo/common/text_direction.typemap create mode 100644 mojo/common/time.mojom create mode 100644 mojo/common/time.typemap create mode 100644 mojo/common/time_struct_traits.h create mode 100644 mojo/common/traits_test_service.mojom create mode 100644 mojo/common/typemaps.gni create mode 100644 mojo/common/unguessable_token.mojom create mode 100644 mojo/common/unguessable_token.typemap create mode 100644 mojo/common/values.mojom create mode 100644 mojo/common/values.typemap create mode 100644 mojo/common/values_struct_traits.cc create mode 100644 mojo/common/values_struct_traits.h create mode 100644 mojo/common/version.mojom create mode 100644 mojo/common/version.typemap create mode 100644 mojo/edk/DEPS create mode 100644 mojo/edk/embedder/BUILD.gn create mode 100644 mojo/edk/embedder/README.md create mode 100644 mojo/edk/embedder/configuration.h create mode 100644 mojo/edk/embedder/connection_params.cc create mode 100644 mojo/edk/embedder/connection_params.h create mode 100644 mojo/edk/embedder/embedder.cc create mode 100644 mojo/edk/embedder/embedder.h create mode 100644 mojo/edk/embedder/embedder_internal.h create mode 100644 mojo/edk/embedder/embedder_unittest.cc create mode 100644 mojo/edk/embedder/entrypoints.cc create mode 100644 mojo/edk/embedder/entrypoints.h create mode 100644 mojo/edk/embedder/named_platform_channel_pair.h create mode 100644 mojo/edk/embedder/named_platform_channel_pair_win.cc create mode 100644 mojo/edk/embedder/named_platform_handle.h create mode 100644 mojo/edk/embedder/named_platform_handle_utils.h create mode 100644 mojo/edk/embedder/named_platform_handle_utils_posix.cc create mode 100644 mojo/edk/embedder/named_platform_handle_utils_win.cc create mode 100644 mojo/edk/embedder/pending_process_connection.cc create mode 100644 mojo/edk/embedder/pending_process_connection.h create mode 100644 mojo/edk/embedder/platform_channel_pair.cc create mode 100644 mojo/edk/embedder/platform_channel_pair.h create mode 100644 mojo/edk/embedder/platform_channel_pair_posix.cc create mode 100644 mojo/edk/embedder/platform_channel_pair_posix_unittest.cc create mode 100644 mojo/edk/embedder/platform_channel_pair_win.cc create mode 100644 mojo/edk/embedder/platform_channel_utils_posix.cc create mode 100644 mojo/edk/embedder/platform_channel_utils_posix.h create mode 100644 mojo/edk/embedder/platform_handle.cc create mode 100644 mojo/edk/embedder/platform_handle.h create mode 100644 mojo/edk/embedder/platform_handle_utils.h create mode 100644 mojo/edk/embedder/platform_handle_utils_posix.cc create mode 100644 mojo/edk/embedder/platform_handle_utils_win.cc create mode 100644 mojo/edk/embedder/platform_handle_vector.h create mode 100644 mojo/edk/embedder/platform_shared_buffer.cc create mode 100644 mojo/edk/embedder/platform_shared_buffer.h create mode 100644 mojo/edk/embedder/platform_shared_buffer_unittest.cc create mode 100644 mojo/edk/embedder/scoped_ipc_support.cc create mode 100644 mojo/edk/embedder/scoped_ipc_support.h create mode 100644 mojo/edk/embedder/scoped_platform_handle.h create mode 100644 mojo/edk/embedder/test_embedder.cc create mode 100644 mojo/edk/embedder/test_embedder.h create mode 100644 mojo/edk/js/BUILD.gn create mode 100644 mojo/edk/js/core.cc create mode 100644 mojo/edk/js/core.h create mode 100644 mojo/edk/js/drain_data.cc create mode 100644 mojo/edk/js/drain_data.h create mode 100644 mojo/edk/js/handle.cc create mode 100644 mojo/edk/js/handle.h create mode 100644 mojo/edk/js/handle_close_observer.h create mode 100644 mojo/edk/js/handle_unittest.cc create mode 100644 mojo/edk/js/js_export.h create mode 100644 mojo/edk/js/mojo_runner_delegate.cc create mode 100644 mojo/edk/js/mojo_runner_delegate.h create mode 100644 mojo/edk/js/support.cc create mode 100644 mojo/edk/js/support.h create mode 100644 mojo/edk/js/tests/BUILD.gn create mode 100644 mojo/edk/js/tests/js_to_cpp.mojom create mode 100644 mojo/edk/js/tests/js_to_cpp_tests.cc create mode 100644 mojo/edk/js/tests/js_to_cpp_tests.js create mode 100644 mojo/edk/js/tests/run_js_unittests.cc create mode 100644 mojo/edk/js/threading.cc create mode 100644 mojo/edk/js/threading.h create mode 100644 mojo/edk/js/waiting_callback.cc create mode 100644 mojo/edk/js/waiting_callback.h create mode 100644 mojo/edk/system/BUILD.gn create mode 100644 mojo/edk/system/atomic_flag.h create mode 100644 mojo/edk/system/broker.h create mode 100644 mojo/edk/system/broker_host.cc create mode 100644 mojo/edk/system/broker_host.h create mode 100644 mojo/edk/system/broker_messages.h create mode 100644 mojo/edk/system/broker_posix.cc create mode 100644 mojo/edk/system/broker_win.cc create mode 100644 mojo/edk/system/channel.cc create mode 100644 mojo/edk/system/channel.h create mode 100644 mojo/edk/system/channel_posix.cc create mode 100644 mojo/edk/system/channel_unittest.cc create mode 100644 mojo/edk/system/channel_win.cc create mode 100644 mojo/edk/system/configuration.cc create mode 100644 mojo/edk/system/configuration.h create mode 100644 mojo/edk/system/core.cc create mode 100644 mojo/edk/system/core.h create mode 100644 mojo/edk/system/core_test_base.cc create mode 100644 mojo/edk/system/core_test_base.h create mode 100644 mojo/edk/system/core_unittest.cc create mode 100644 mojo/edk/system/data_pipe_consumer_dispatcher.cc create mode 100644 mojo/edk/system/data_pipe_consumer_dispatcher.h create mode 100644 mojo/edk/system/data_pipe_control_message.cc create mode 100644 mojo/edk/system/data_pipe_control_message.h create mode 100644 mojo/edk/system/data_pipe_producer_dispatcher.cc create mode 100644 mojo/edk/system/data_pipe_producer_dispatcher.h create mode 100644 mojo/edk/system/data_pipe_unittest.cc create mode 100644 mojo/edk/system/dispatcher.cc create mode 100644 mojo/edk/system/dispatcher.h create mode 100644 mojo/edk/system/handle_signals_state.h create mode 100644 mojo/edk/system/handle_table.cc create mode 100644 mojo/edk/system/handle_table.h create mode 100644 mojo/edk/system/mach_port_relay.cc create mode 100644 mojo/edk/system/mach_port_relay.h create mode 100644 mojo/edk/system/mapping_table.cc create mode 100644 mojo/edk/system/mapping_table.h create mode 100644 mojo/edk/system/message_for_transit.cc create mode 100644 mojo/edk/system/message_for_transit.h create mode 100644 mojo/edk/system/message_pipe_dispatcher.cc create mode 100644 mojo/edk/system/message_pipe_dispatcher.h create mode 100644 mojo/edk/system/message_pipe_perftest.cc create mode 100644 mojo/edk/system/message_pipe_unittest.cc create mode 100644 mojo/edk/system/multiprocess_message_pipe_unittest.cc create mode 100644 mojo/edk/system/node_channel.cc create mode 100644 mojo/edk/system/node_channel.h create mode 100644 mojo/edk/system/node_controller.cc create mode 100644 mojo/edk/system/node_controller.h create mode 100644 mojo/edk/system/options_validation.h create mode 100644 mojo/edk/system/options_validation_unittest.cc create mode 100644 mojo/edk/system/platform_handle_dispatcher.cc create mode 100644 mojo/edk/system/platform_handle_dispatcher.h create mode 100644 mojo/edk/system/platform_handle_dispatcher_unittest.cc create mode 100644 mojo/edk/system/platform_wrapper_unittest.cc create mode 100644 mojo/edk/system/ports/BUILD.gn create mode 100644 mojo/edk/system/ports/event.cc create mode 100644 mojo/edk/system/ports/event.h create mode 100644 mojo/edk/system/ports/message.cc create mode 100644 mojo/edk/system/ports/message.h create mode 100644 mojo/edk/system/ports/message_filter.h create mode 100644 mojo/edk/system/ports/message_queue.cc create mode 100644 mojo/edk/system/ports/message_queue.h create mode 100644 mojo/edk/system/ports/name.cc create mode 100644 mojo/edk/system/ports/name.h create mode 100644 mojo/edk/system/ports/node.cc create mode 100644 mojo/edk/system/ports/node.h create mode 100644 mojo/edk/system/ports/node_delegate.h create mode 100644 mojo/edk/system/ports/port.cc create mode 100644 mojo/edk/system/ports/port.h create mode 100644 mojo/edk/system/ports/port_ref.cc create mode 100644 mojo/edk/system/ports/port_ref.h create mode 100644 mojo/edk/system/ports/ports_unittest.cc create mode 100644 mojo/edk/system/ports/user_data.h create mode 100644 mojo/edk/system/ports_message.cc create mode 100644 mojo/edk/system/ports_message.h create mode 100644 mojo/edk/system/request_context.cc create mode 100644 mojo/edk/system/request_context.h create mode 100644 mojo/edk/system/shared_buffer_dispatcher.cc create mode 100644 mojo/edk/system/shared_buffer_dispatcher.h create mode 100644 mojo/edk/system/shared_buffer_dispatcher_unittest.cc create mode 100644 mojo/edk/system/shared_buffer_unittest.cc create mode 100644 mojo/edk/system/signals_unittest.cc create mode 100644 mojo/edk/system/system_impl_export.h create mode 100644 mojo/edk/system/test_utils.cc create mode 100644 mojo/edk/system/test_utils.h create mode 100644 mojo/edk/system/watch.cc create mode 100644 mojo/edk/system/watch.h create mode 100644 mojo/edk/system/watcher_dispatcher.cc create mode 100644 mojo/edk/system/watcher_dispatcher.h create mode 100644 mojo/edk/system/watcher_set.cc create mode 100644 mojo/edk/system/watcher_set.h create mode 100644 mojo/edk/system/watcher_unittest.cc create mode 100644 mojo/edk/test/BUILD.gn create mode 100644 mojo/edk/test/mojo_test_base.cc create mode 100644 mojo/edk/test/mojo_test_base.h create mode 100644 mojo/edk/test/multiprocess_test_helper.cc create mode 100644 mojo/edk/test/multiprocess_test_helper.h create mode 100644 mojo/edk/test/multiprocess_test_helper_unittest.cc create mode 100644 mojo/edk/test/run_all_perftests.cc create mode 100644 mojo/edk/test/run_all_unittests.cc create mode 100644 mojo/edk/test/test_support_impl.cc create mode 100644 mojo/edk/test/test_support_impl.h create mode 100644 mojo/edk/test/test_utils.h create mode 100644 mojo/edk/test/test_utils_posix.cc create mode 100644 mojo/edk/test/test_utils_win.cc create mode 100644 mojo/public/BUILD.gn create mode 100644 mojo/public/DEPS create mode 100644 mojo/public/LICENSE create mode 100644 mojo/public/c/system/BUILD.gn create mode 100644 mojo/public/c/system/README.md create mode 100644 mojo/public/c/system/buffer.h create mode 100644 mojo/public/c/system/core.h create mode 100644 mojo/public/c/system/data_pipe.h create mode 100644 mojo/public/c/system/functions.h create mode 100644 mojo/public/c/system/macros.h create mode 100644 mojo/public/c/system/message_pipe.h create mode 100644 mojo/public/c/system/platform_handle.h create mode 100644 mojo/public/c/system/set_thunks_for_app.cc create mode 100644 mojo/public/c/system/system_export.h create mode 100644 mojo/public/c/system/tests/BUILD.gn create mode 100644 mojo/public/c/system/tests/core_perftest.cc create mode 100644 mojo/public/c/system/tests/core_unittest.cc create mode 100644 mojo/public/c/system/tests/core_unittest_pure_c.c create mode 100644 mojo/public/c/system/tests/macros_unittest.cc create mode 100644 mojo/public/c/system/thunks.cc create mode 100644 mojo/public/c/system/thunks.h create mode 100644 mojo/public/c/system/types.h create mode 100644 mojo/public/c/system/watcher.h create mode 100644 mojo/public/c/test_support/BUILD.gn create mode 100644 mojo/public/c/test_support/test_support.h create mode 100644 mojo/public/cpp/bindings/BUILD.gn create mode 100644 mojo/public/cpp/bindings/DEPS create mode 100644 mojo/public/cpp/bindings/README.md create mode 100644 mojo/public/cpp/bindings/array_data_view.h create mode 100644 mojo/public/cpp/bindings/array_traits.h create mode 100644 mojo/public/cpp/bindings/array_traits_carray.h create mode 100644 mojo/public/cpp/bindings/array_traits_stl.h create mode 100644 mojo/public/cpp/bindings/array_traits_wtf_vector.h create mode 100644 mojo/public/cpp/bindings/associated_binding.h create mode 100644 mojo/public/cpp/bindings/associated_binding_set.h create mode 100644 mojo/public/cpp/bindings/associated_group.h create mode 100644 mojo/public/cpp/bindings/associated_group_controller.h create mode 100644 mojo/public/cpp/bindings/associated_interface_ptr.h create mode 100644 mojo/public/cpp/bindings/associated_interface_ptr_info.h create mode 100644 mojo/public/cpp/bindings/associated_interface_request.h create mode 100644 mojo/public/cpp/bindings/binding.h create mode 100644 mojo/public/cpp/bindings/binding_set.h create mode 100644 mojo/public/cpp/bindings/bindings_export.h create mode 100644 mojo/public/cpp/bindings/clone_traits.h create mode 100644 mojo/public/cpp/bindings/connection_error_callback.h create mode 100644 mojo/public/cpp/bindings/connector.h create mode 100644 mojo/public/cpp/bindings/disconnect_reason.h create mode 100644 mojo/public/cpp/bindings/enum_traits.h create mode 100644 mojo/public/cpp/bindings/filter_chain.h create mode 100644 mojo/public/cpp/bindings/interface_data_view.h create mode 100644 mojo/public/cpp/bindings/interface_endpoint_client.h create mode 100644 mojo/public/cpp/bindings/interface_endpoint_controller.h create mode 100644 mojo/public/cpp/bindings/interface_id.h create mode 100644 mojo/public/cpp/bindings/interface_ptr.h create mode 100644 mojo/public/cpp/bindings/interface_ptr_info.h create mode 100644 mojo/public/cpp/bindings/interface_ptr_set.h create mode 100644 mojo/public/cpp/bindings/interface_request.h create mode 100644 mojo/public/cpp/bindings/lib/array_internal.cc create mode 100644 mojo/public/cpp/bindings/lib/array_internal.h create mode 100644 mojo/public/cpp/bindings/lib/array_serialization.h create mode 100644 mojo/public/cpp/bindings/lib/associated_binding.cc create mode 100644 mojo/public/cpp/bindings/lib/associated_group.cc create mode 100644 mojo/public/cpp/bindings/lib/associated_group_controller.cc create mode 100644 mojo/public/cpp/bindings/lib/associated_interface_ptr.cc create mode 100644 mojo/public/cpp/bindings/lib/associated_interface_ptr_state.h create mode 100644 mojo/public/cpp/bindings/lib/binding_state.cc create mode 100644 mojo/public/cpp/bindings/lib/binding_state.h create mode 100644 mojo/public/cpp/bindings/lib/bindings_internal.h create mode 100644 mojo/public/cpp/bindings/lib/buffer.h create mode 100644 mojo/public/cpp/bindings/lib/connector.cc create mode 100644 mojo/public/cpp/bindings/lib/control_message_handler.cc create mode 100644 mojo/public/cpp/bindings/lib/control_message_handler.h create mode 100644 mojo/public/cpp/bindings/lib/control_message_proxy.cc create mode 100644 mojo/public/cpp/bindings/lib/control_message_proxy.h create mode 100644 mojo/public/cpp/bindings/lib/equals_traits.h create mode 100644 mojo/public/cpp/bindings/lib/filter_chain.cc create mode 100644 mojo/public/cpp/bindings/lib/fixed_buffer.cc create mode 100644 mojo/public/cpp/bindings/lib/fixed_buffer.h create mode 100644 mojo/public/cpp/bindings/lib/handle_interface_serialization.h create mode 100644 mojo/public/cpp/bindings/lib/hash_util.h create mode 100644 mojo/public/cpp/bindings/lib/interface_endpoint_client.cc create mode 100644 mojo/public/cpp/bindings/lib/interface_ptr_state.h create mode 100644 mojo/public/cpp/bindings/lib/map_data_internal.h create mode 100644 mojo/public/cpp/bindings/lib/map_serialization.h create mode 100644 mojo/public/cpp/bindings/lib/may_auto_lock.h create mode 100644 mojo/public/cpp/bindings/lib/message.cc create mode 100644 mojo/public/cpp/bindings/lib/message_buffer.cc create mode 100644 mojo/public/cpp/bindings/lib/message_buffer.h create mode 100644 mojo/public/cpp/bindings/lib/message_builder.cc create mode 100644 mojo/public/cpp/bindings/lib/message_builder.h create mode 100644 mojo/public/cpp/bindings/lib/message_header_validator.cc create mode 100644 mojo/public/cpp/bindings/lib/message_internal.h create mode 100644 mojo/public/cpp/bindings/lib/multiplex_router.cc create mode 100644 mojo/public/cpp/bindings/lib/multiplex_router.h create mode 100644 mojo/public/cpp/bindings/lib/native_enum_data.h create mode 100644 mojo/public/cpp/bindings/lib/native_enum_serialization.h create mode 100644 mojo/public/cpp/bindings/lib/native_struct.cc create mode 100644 mojo/public/cpp/bindings/lib/native_struct_data.cc create mode 100644 mojo/public/cpp/bindings/lib/native_struct_data.h create mode 100644 mojo/public/cpp/bindings/lib/native_struct_serialization.cc create mode 100644 mojo/public/cpp/bindings/lib/native_struct_serialization.h create mode 100644 mojo/public/cpp/bindings/lib/pipe_control_message_handler.cc create mode 100644 mojo/public/cpp/bindings/lib/pipe_control_message_proxy.cc create mode 100644 mojo/public/cpp/bindings/lib/scoped_interface_endpoint_handle.cc create mode 100644 mojo/public/cpp/bindings/lib/serialization.h create mode 100644 mojo/public/cpp/bindings/lib/serialization_context.cc create mode 100644 mojo/public/cpp/bindings/lib/serialization_context.h create mode 100644 mojo/public/cpp/bindings/lib/serialization_forward.h create mode 100644 mojo/public/cpp/bindings/lib/serialization_util.h create mode 100644 mojo/public/cpp/bindings/lib/string_serialization.h create mode 100644 mojo/public/cpp/bindings/lib/string_traits_string16.cc create mode 100644 mojo/public/cpp/bindings/lib/string_traits_wtf.cc create mode 100644 mojo/public/cpp/bindings/lib/sync_call_restrictions.cc create mode 100644 mojo/public/cpp/bindings/lib/sync_event_watcher.cc create mode 100644 mojo/public/cpp/bindings/lib/sync_handle_registry.cc create mode 100644 mojo/public/cpp/bindings/lib/sync_handle_watcher.cc create mode 100644 mojo/public/cpp/bindings/lib/template_util.h create mode 100644 mojo/public/cpp/bindings/lib/union_accessor.h create mode 100644 mojo/public/cpp/bindings/lib/validate_params.h create mode 100644 mojo/public/cpp/bindings/lib/validation_context.cc create mode 100644 mojo/public/cpp/bindings/lib/validation_context.h create mode 100644 mojo/public/cpp/bindings/lib/validation_errors.cc create mode 100644 mojo/public/cpp/bindings/lib/validation_errors.h create mode 100644 mojo/public/cpp/bindings/lib/validation_util.cc create mode 100644 mojo/public/cpp/bindings/lib/validation_util.h create mode 100644 mojo/public/cpp/bindings/lib/wtf_clone_equals_util.h create mode 100644 mojo/public/cpp/bindings/lib/wtf_hash_util.h create mode 100644 mojo/public/cpp/bindings/lib/wtf_serialization.h create mode 100644 mojo/public/cpp/bindings/map.h create mode 100644 mojo/public/cpp/bindings/map_data_view.h create mode 100644 mojo/public/cpp/bindings/map_traits.h create mode 100644 mojo/public/cpp/bindings/map_traits_stl.h create mode 100644 mojo/public/cpp/bindings/map_traits_wtf_hash_map.h create mode 100644 mojo/public/cpp/bindings/message.h create mode 100644 mojo/public/cpp/bindings/message_header_validator.h create mode 100644 mojo/public/cpp/bindings/native_enum.h create mode 100644 mojo/public/cpp/bindings/native_struct.h create mode 100644 mojo/public/cpp/bindings/native_struct_data_view.h create mode 100644 mojo/public/cpp/bindings/pipe_control_message_handler.h create mode 100644 mojo/public/cpp/bindings/pipe_control_message_handler_delegate.h create mode 100644 mojo/public/cpp/bindings/pipe_control_message_proxy.h create mode 100644 mojo/public/cpp/bindings/raw_ptr_impl_ref_traits.h create mode 100644 mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h create mode 100644 mojo/public/cpp/bindings/string_data_view.h create mode 100644 mojo/public/cpp/bindings/string_traits.h create mode 100644 mojo/public/cpp/bindings/string_traits_stl.h create mode 100644 mojo/public/cpp/bindings/string_traits_string16.h create mode 100644 mojo/public/cpp/bindings/string_traits_string_piece.h create mode 100644 mojo/public/cpp/bindings/string_traits_wtf.h create mode 100644 mojo/public/cpp/bindings/strong_associated_binding.h create mode 100644 mojo/public/cpp/bindings/strong_binding.h create mode 100644 mojo/public/cpp/bindings/strong_binding_set.h create mode 100644 mojo/public/cpp/bindings/struct_ptr.h create mode 100644 mojo/public/cpp/bindings/struct_traits.h create mode 100644 mojo/public/cpp/bindings/sync_call_restrictions.h create mode 100644 mojo/public/cpp/bindings/sync_event_watcher.h create mode 100644 mojo/public/cpp/bindings/sync_handle_registry.h create mode 100644 mojo/public/cpp/bindings/sync_handle_watcher.h create mode 100644 mojo/public/cpp/bindings/tests/BUILD.gn create mode 100644 mojo/public/cpp/bindings/tests/associated_interface_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/bind_task_runner_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/binding_callback_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/binding_set_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/binding_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/bindings_perftest.cc create mode 100644 mojo/public/cpp/bindings/tests/blink_typemaps.gni create mode 100644 mojo/public/cpp/bindings/tests/buffer_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/chromium_typemaps.gni create mode 100644 mojo/public/cpp/bindings/tests/connector_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/constant_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/container_test_util.cc create mode 100644 mojo/public/cpp/bindings/tests/container_test_util.h create mode 100644 mojo/public/cpp/bindings/tests/data_view_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/e2e_perftest.cc create mode 100644 mojo/public/cpp/bindings/tests/equals_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/handle_passing_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/hash_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/interface_ptr_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/map_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/message_queue.cc create mode 100644 mojo/public/cpp/bindings/tests/message_queue.h create mode 100644 mojo/public/cpp/bindings/tests/mojo_test_blink_export.h create mode 100644 mojo/public/cpp/bindings/tests/mojo_test_export.h create mode 100644 mojo/public/cpp/bindings/tests/multiplex_router_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/pickle_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/pickled_types_blink.cc create mode 100644 mojo/public/cpp/bindings/tests/pickled_types_blink.h create mode 100644 mojo/public/cpp/bindings/tests/pickled_types_chromium.cc create mode 100644 mojo/public/cpp/bindings/tests/pickled_types_chromium.h create mode 100644 mojo/public/cpp/bindings/tests/rect_blink.h create mode 100644 mojo/public/cpp/bindings/tests/rect_blink.typemap create mode 100644 mojo/public/cpp/bindings/tests/rect_blink_traits.h create mode 100644 mojo/public/cpp/bindings/tests/rect_chromium.h create mode 100644 mojo/public/cpp/bindings/tests/rect_chromium.typemap create mode 100644 mojo/public/cpp/bindings/tests/rect_chromium_traits.h create mode 100644 mojo/public/cpp/bindings/tests/report_bad_message_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/request_response_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/router_test_util.cc create mode 100644 mojo/public/cpp/bindings/tests/router_test_util.h create mode 100644 mojo/public/cpp/bindings/tests/sample_service_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/serialization_warning_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/shared_rect.h create mode 100644 mojo/public/cpp/bindings/tests/shared_rect_traits.h create mode 100644 mojo/public/cpp/bindings/tests/struct_traits_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/struct_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits.typemap create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits_impl.cc create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits_impl.h create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.cc create mode 100644 mojo/public/cpp/bindings/tests/struct_with_traits_impl_traits.h create mode 100644 mojo/public/cpp/bindings/tests/sync_method_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/test_native_types_blink.typemap create mode 100644 mojo/public/cpp/bindings/tests/test_native_types_chromium.typemap create mode 100644 mojo/public/cpp/bindings/tests/type_conversion_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/union_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/validation_context_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/validation_test_input_parser.cc create mode 100644 mojo/public/cpp/bindings/tests/validation_test_input_parser.h create mode 100644 mojo/public/cpp/bindings/tests/validation_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/variant_test_util.h create mode 100644 mojo/public/cpp/bindings/tests/versioning_apptest.cc create mode 100644 mojo/public/cpp/bindings/tests/versioning_test_service.cc create mode 100644 mojo/public/cpp/bindings/tests/wtf_hash_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/wtf_map_unittest.cc create mode 100644 mojo/public/cpp/bindings/tests/wtf_types_unittest.cc create mode 100644 mojo/public/cpp/bindings/thread_safe_interface_ptr.h create mode 100644 mojo/public/cpp/bindings/type_converter.h create mode 100644 mojo/public/cpp/bindings/union_traits.h create mode 100644 mojo/public/cpp/bindings/unique_ptr_impl_ref_traits.h create mode 100644 mojo/public/cpp/system/BUILD.gn create mode 100644 mojo/public/cpp/system/README.md create mode 100644 mojo/public/cpp/system/buffer.cc create mode 100644 mojo/public/cpp/system/buffer.h create mode 100644 mojo/public/cpp/system/core.h create mode 100644 mojo/public/cpp/system/data_pipe.h create mode 100644 mojo/public/cpp/system/functions.h create mode 100644 mojo/public/cpp/system/handle.h create mode 100644 mojo/public/cpp/system/handle_signals_state.h create mode 100644 mojo/public/cpp/system/message.cc create mode 100644 mojo/public/cpp/system/message.h create mode 100644 mojo/public/cpp/system/message_pipe.h create mode 100644 mojo/public/cpp/system/platform_handle.cc create mode 100644 mojo/public/cpp/system/platform_handle.h create mode 100644 mojo/public/cpp/system/simple_watcher.cc create mode 100644 mojo/public/cpp/system/simple_watcher.h create mode 100644 mojo/public/cpp/system/system_export.h create mode 100644 mojo/public/cpp/system/tests/BUILD.gn create mode 100644 mojo/public/cpp/system/tests/core_unittest.cc create mode 100644 mojo/public/cpp/system/tests/handle_signals_state_unittest.cc create mode 100644 mojo/public/cpp/system/tests/simple_watcher_unittest.cc create mode 100644 mojo/public/cpp/system/tests/wait_set_unittest.cc create mode 100644 mojo/public/cpp/system/tests/wait_unittest.cc create mode 100644 mojo/public/cpp/system/wait.cc create mode 100644 mojo/public/cpp/system/wait.h create mode 100644 mojo/public/cpp/system/wait_set.cc create mode 100644 mojo/public/cpp/system/wait_set.h create mode 100644 mojo/public/cpp/system/watcher.cc create mode 100644 mojo/public/cpp/system/watcher.h create mode 100644 mojo/public/cpp/test_support/BUILD.gn create mode 100644 mojo/public/cpp/test_support/lib/test_support.cc create mode 100644 mojo/public/cpp/test_support/lib/test_utils.cc create mode 100644 mojo/public/cpp/test_support/test_support.h create mode 100644 mojo/public/cpp/test_support/test_utils.h create mode 100644 mojo/public/interfaces/BUILD.gn create mode 100644 mojo/public/interfaces/bindings/BUILD.gn create mode 100644 mojo/public/interfaces/bindings/interface_control_messages.mojom create mode 100644 mojo/public/interfaces/bindings/new_bindings/interface_control_messages.mojom create mode 100644 mojo/public/interfaces/bindings/new_bindings/pipe_control_messages.mojom create mode 100644 mojo/public/interfaces/bindings/pipe_control_messages.mojom create mode 100644 mojo/public/interfaces/bindings/tests/BUILD.gn create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_invalid_interface_id.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_illegal_master_interface_id.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_interface_id_index_out_of_range.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd0_unexpected_invalid_associated_interface.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd1_unexpected_invalid_associated_request.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd2_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_collided_interface_id_indices.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_unexpected_invalid_associated_interface_in_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/associated_conformance_mthd3_wrong_interface_id_index_order.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/boundscheck_msghdr_no_such_method.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_empty.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_incomplete_struct_header.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_invalid_flag_combo.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_missing_request_id.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_no_such_method.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_huge.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_min_requirement.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_less_than_struct_header.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_1.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_2.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_msghdr_num_bytes_version_mismatch_3.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_incomplete_struct_header.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_invalid_request_flags2.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_huge.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_min_requirement.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd0_struct_num_bytes_less_than_struct_header.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_good_non_unique_keys.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_keys.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_null_values.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_one_null_key.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd10_unequal_array_size.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version0.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version1.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version2.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version3.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_1.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_good_version_newer_than_known_2.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_1.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd11_num_bytes_version_mismatch_2.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd12_invalid_request_flags.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_1.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd13_good_2.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_known_enum_values.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_good_uknown_extensible_enum_value.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd14_uknown_non_extensible_enum_value.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_empy_enum_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_known_enum_array_values.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_good_uknown_extensible_enum_array_value.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd15_uknown_non_extensible_enum_array_value.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_key.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd16_uknown_non_extensible_enum_map_value.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_interface_handle_out_of_range_in_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd17_unexpected_invalid_interface_in_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd18_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd19_exceed_recursion_limit.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_misaligned_struct.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_struct_pointer_overflow.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd1_unexpected_null_struct.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd20_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd21_empty_extensible_enum_accepts_any_value.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd22_empty_nonextensible_enum_accepts_no_values.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_multiple_pointers_to_same_struct.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_overlapped_objects.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd2_wrong_layout_order.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_huge.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_array_header.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_num_bytes_less_than_necessary_size.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_array_pointer_overflow.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_incomplete_array_header.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_misaligned_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd3_unexpected_null_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_multiple_pointers_to_same_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_overlapped_objects.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd4_wrong_layout_order.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_handle_out_of_range.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_1.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_multiple_handles_with_same_value_2.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_unexpected_invalid_handle.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd5_wrong_handle_order.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd6_nested_array_num_bytes_less_than_necessary_size.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unexpected_null_fixed_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd7_unmatched_array_elements_nested.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_array_num_bytes_overflow.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd8_unexpected_null_string.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_good_null_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/conformance_mthd9_unexpected_null_array.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_intf_resp_mthd0_unexpected_array_header.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_good.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_intf_rqst_mthd0_unexpected_struct_header.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/integration_msghdr_invalid_flags.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/resp_boundscheck_msghdr_no_such_method.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags1.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_invalid_response_flags2.expected create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.data create mode 100644 mojo/public/interfaces/bindings/tests/data/validation/resp_conformance_msghdr_no_such_method.expected create mode 100644 mojo/public/interfaces/bindings/tests/echo.mojom create mode 100644 mojo/public/interfaces/bindings/tests/echo_import.mojom create mode 100644 mojo/public/interfaces/bindings/tests/math_calculator.mojom create mode 100644 mojo/public/interfaces/bindings/tests/no_module.mojom create mode 100644 mojo/public/interfaces/bindings/tests/ping_service.mojom create mode 100644 mojo/public/interfaces/bindings/tests/rect.mojom create mode 100644 mojo/public/interfaces/bindings/tests/regression_tests.mojom create mode 100644 mojo/public/interfaces/bindings/tests/sample_factory.mojom create mode 100644 mojo/public/interfaces/bindings/tests/sample_import.mojom create mode 100644 mojo/public/interfaces/bindings/tests/sample_import2.mojom create mode 100644 mojo/public/interfaces/bindings/tests/sample_interfaces.mojom create mode 100644 mojo/public/interfaces/bindings/tests/sample_service.mojom create mode 100644 mojo/public/interfaces/bindings/tests/scoping.mojom create mode 100644 mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom create mode 100644 mojo/public/interfaces/bindings/tests/struct_with_traits.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_associated_interfaces.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_bad_messages.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_constants.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_data_view.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_export.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_export2.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_import.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_native_types.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_structs.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_sync_methods.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_unions.mojom create mode 100644 mojo/public/interfaces/bindings/tests/test_wtf_types.mojom create mode 100644 mojo/public/interfaces/bindings/tests/validation_test_associated_interfaces.mojom create mode 100644 mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom create mode 100644 mojo/public/interfaces/bindings/tests/versioning_test_client.mojom create mode 100644 mojo/public/interfaces/bindings/tests/versioning_test_service.mojom create mode 100644 mojo/public/java/BUILD.gn create mode 100644 mojo/public/java/bindings/README.md create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceNotSupported.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/AssociatedInterfaceRequestNotSupported.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/AutoCloseableRouter.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/BindingsHelper.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Callbacks.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/ConnectionErrorHandler.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Connector.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/DataHeader.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Decoder.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/DelegatingConnectionErrorHandler.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/DeserializationException.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Encoder.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/ExecutorFactory.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/HandleOwner.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceControlMessagesHelper.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/InterfaceRequest.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Message.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageHeader.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiver.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/MessageReceiverWithResponder.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Router.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/SerializationException.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/ServiceMessage.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/SideEffectFreeCloseable.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Struct.java create mode 100644 mojo/public/java/bindings/src/org/chromium/mojo/bindings/Union.java create mode 100644 mojo/public/java/system/README.md create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/Core.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/DataPipe.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/Flags.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/Handle.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/InvalidHandle.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/MessagePipeHandle.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/MojoException.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/MojoResult.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/Pair.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/ResultAnd.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/RunLoop.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/SharedBufferHandle.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/UntypedHandle.java create mode 100644 mojo/public/java/system/src/org/chromium/mojo/system/Watcher.java create mode 100644 mojo/public/js/BUILD.gn create mode 100644 mojo/public/js/README.md create mode 100644 mojo/public/js/bindings.js create mode 100644 mojo/public/js/buffer.js create mode 100644 mojo/public/js/codec.js create mode 100644 mojo/public/js/connector.js create mode 100644 mojo/public/js/constants.cc create mode 100644 mojo/public/js/constants.h create mode 100644 mojo/public/js/core.js create mode 100644 mojo/public/js/interface_types.js create mode 100644 mojo/public/js/lib/control_message_handler.js create mode 100644 mojo/public/js/lib/control_message_proxy.js create mode 100644 mojo/public/js/lib/interface_endpoint_client.js create mode 100644 mojo/public/js/lib/interface_endpoint_handle.js create mode 100644 mojo/public/js/lib/pipe_control_message_handler.js create mode 100644 mojo/public/js/lib/pipe_control_message_proxy.js create mode 100644 mojo/public/js/new_bindings/base.js create mode 100644 mojo/public/js/new_bindings/bindings.js create mode 100644 mojo/public/js/new_bindings/buffer.js create mode 100644 mojo/public/js/new_bindings/codec.js create mode 100644 mojo/public/js/new_bindings/connector.js create mode 100644 mojo/public/js/new_bindings/interface_types.js create mode 100644 mojo/public/js/new_bindings/lib/control_message_handler.js create mode 100644 mojo/public/js/new_bindings/lib/control_message_proxy.js create mode 100644 mojo/public/js/new_bindings/router.js create mode 100644 mojo/public/js/new_bindings/unicode.js create mode 100644 mojo/public/js/new_bindings/validator.js create mode 100644 mojo/public/js/router.js create mode 100644 mojo/public/js/support.js create mode 100644 mojo/public/js/tests/core_unittest.js create mode 100644 mojo/public/js/tests/validation_test_input_parser.js create mode 100644 mojo/public/js/tests/validation_unittest.js create mode 100644 mojo/public/js/threading.js create mode 100644 mojo/public/js/unicode.js create mode 100644 mojo/public/js/validator.js create mode 100644 mojo/public/tests/test_support_private.cc create mode 100644 mojo/public/tests/test_support_private.h create mode 100644 mojo/public/tools/bindings/BUILD.gn create mode 100644 mojo/public/tools/bindings/README.md create mode 100644 mojo/public/tools/bindings/blink_bindings_configuration.gni create mode 100644 mojo/public/tools/bindings/chromium_bindings_configuration.gni create mode 100755 mojo/public/tools/bindings/format_typemap_generator_args.py create mode 100755 mojo/public/tools/bindings/generate_type_mappings.py create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/enum_macros.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/enum_serialization_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/interface_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/interface_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/interface_macros.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/interface_proxy_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/interface_request_validator_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/interface_response_validator_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/interface_stub_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/module-shared-internal.h.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/module-shared.cc.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/module-shared.h.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/module.cc.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/module.h.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_data_view_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_macros.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_serialization_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_traits_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/struct_traits_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/union_data_view_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/union_data_view_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/union_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/union_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/union_serialization_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/union_traits_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/union_traits_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/validation_macros.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/wrapper_class_template_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_declaration.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/cpp_templates/wrapper_union_class_template_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/constant_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/constants.java.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/data_types_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/enum.java.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/enum_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/header.java.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/interface.java.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/interface_internal.java.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/struct.java.tmpl create mode 100644 mojo/public/tools/bindings/generators/java_templates/union.java.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/enum_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/interface_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/module.amd.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/module_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/struct_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/union_definition.tmpl create mode 100644 mojo/public/tools/bindings/generators/js_templates/validation_macros.tmpl create mode 100644 mojo/public/tools/bindings/generators/mojom_cpp_generator.py create mode 100644 mojo/public/tools/bindings/generators/mojom_java_generator.py create mode 100644 mojo/public/tools/bindings/generators/mojom_js_generator.py create mode 100644 mojo/public/tools/bindings/mojom.gni create mode 100755 mojo/public/tools/bindings/mojom_bindings_generator.py create mode 100644 mojo/public/tools/bindings/mojom_bindings_generator_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/__init__.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/error.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/fileutil.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/__init__.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/constant_resolver.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/generator.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/generator_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/module.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/module_tests.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/pack.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/pack_tests.py create mode 100755 mojo/public/tools/bindings/pylib/mojom/generate/run_tests.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/template_expander.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/test_support.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/generate/translate.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/parse/__init__.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/parse/ast.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/parse/lexer.py create mode 100644 mojo/public/tools/bindings/pylib/mojom/parse/parser.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/__init__.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/fileutil_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/generate/__init__.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/generate/data_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/generate/generator_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/generate/module_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/generate/pack_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/parse/__init__.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/parse/ast_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/parse/lexer_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/parse/parser_unittest.py create mode 100755 mojo/public/tools/bindings/pylib/mojom_tests/parse/run_parser.py create mode 100755 mojo/public/tools/bindings/pylib/mojom_tests/parse/run_translate.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/parse/translate_unittest.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/support/__init__.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/support/find_files.py create mode 100644 mojo/public/tools/bindings/pylib/mojom_tests/support/run_bindings_generator.py create mode 100755 mojo/public/tools/chrome_ipc/generate_mojom.py create mode 100755 mojo/public/tools/gn/zip.py create mode 100644 third_party/catapult/LICENSE create mode 100644 third_party/catapult/devil/PRESUBMIT.py create mode 100644 third_party/catapult/devil/README.md create mode 100755 third_party/catapult/devil/bin/generate_md_docs create mode 100755 third_party/catapult/devil/bin/run_py_devicetests create mode 100755 third_party/catapult/devil/bin/run_py_tests create mode 100644 third_party/catapult/devil/devil/__init__.py create mode 100644 third_party/catapult/devil/devil/android/__init__.py create mode 100644 third_party/catapult/devil/devil/android/apk_helper.py create mode 100755 third_party/catapult/devil/devil/android/apk_helper_test.py create mode 100644 third_party/catapult/devil/devil/android/app_ui.py create mode 100644 third_party/catapult/devil/devil/android/app_ui_test.py create mode 100644 third_party/catapult/devil/devil/android/battery_utils.py create mode 100755 third_party/catapult/devil/devil/android/battery_utils_test.py create mode 100644 third_party/catapult/devil/devil/android/constants/__init__.py create mode 100644 third_party/catapult/devil/devil/android/constants/chrome.py create mode 100644 third_party/catapult/devil/devil/android/constants/file_system.py create mode 100644 third_party/catapult/devil/devil/android/decorators.py create mode 100644 third_party/catapult/devil/devil/android/decorators_test.py create mode 100644 third_party/catapult/devil/devil/android/device_blacklist.py create mode 100644 third_party/catapult/devil/devil/android/device_blacklist_test.py create mode 100644 third_party/catapult/devil/devil/android/device_errors.py create mode 100755 third_party/catapult/devil/devil/android/device_errors_test.py create mode 100644 third_party/catapult/devil/devil/android/device_list.py create mode 100644 third_party/catapult/devil/devil/android/device_signal.py create mode 100644 third_party/catapult/devil/devil/android/device_temp_file.py create mode 100644 third_party/catapult/devil/devil/android/device_test_case.py create mode 100644 third_party/catapult/devil/devil/android/device_utils.py create mode 100755 third_party/catapult/devil/devil/android/device_utils_devicetest.py create mode 100755 third_party/catapult/devil/devil/android/device_utils_test.py create mode 100644 third_party/catapult/devil/devil/android/fastboot_utils.py create mode 100755 third_party/catapult/devil/devil/android/fastboot_utils_test.py create mode 100644 third_party/catapult/devil/devil/android/flag_changer.py create mode 100644 third_party/catapult/devil/devil/android/flag_changer_devicetest.py create mode 100755 third_party/catapult/devil/devil/android/flag_changer_test.py create mode 100644 third_party/catapult/devil/devil/android/forwarder.py create mode 100644 third_party/catapult/devil/devil/android/install_commands.py create mode 100644 third_party/catapult/devil/devil/android/logcat_monitor.py create mode 100755 third_party/catapult/devil/devil/android/logcat_monitor_test.py create mode 100644 third_party/catapult/devil/devil/android/md5sum.py create mode 100755 third_party/catapult/devil/devil/android/md5sum_test.py create mode 100644 third_party/catapult/devil/devil/android/perf/__init__.py create mode 100644 third_party/catapult/devil/devil/android/perf/cache_control.py create mode 100644 third_party/catapult/devil/devil/android/perf/perf_control.py create mode 100644 third_party/catapult/devil/devil/android/perf/perf_control_devicetest.py create mode 100644 third_party/catapult/devil/devil/android/perf/surface_stats_collector.py create mode 100644 third_party/catapult/devil/devil/android/perf/thermal_throttle.py create mode 100644 third_party/catapult/devil/devil/android/ports.py create mode 100644 third_party/catapult/devil/devil/android/sdk/__init__.py create mode 100644 third_party/catapult/devil/devil/android/sdk/aapt.py create mode 100644 third_party/catapult/devil/devil/android/sdk/adb_compatibility_devicetest.py create mode 100644 third_party/catapult/devil/devil/android/sdk/adb_wrapper.py create mode 100755 third_party/catapult/devil/devil/android/sdk/adb_wrapper_devicetest.py create mode 100755 third_party/catapult/devil/devil/android/sdk/adb_wrapper_test.py create mode 100644 third_party/catapult/devil/devil/android/sdk/build_tools.py create mode 100644 third_party/catapult/devil/devil/android/sdk/dexdump.py create mode 100644 third_party/catapult/devil/devil/android/sdk/fastboot.py create mode 100644 third_party/catapult/devil/devil/android/sdk/gce_adb_wrapper.py create mode 100644 third_party/catapult/devil/devil/android/sdk/intent.py create mode 100644 third_party/catapult/devil/devil/android/sdk/keyevent.py create mode 100644 third_party/catapult/devil/devil/android/sdk/shared_prefs.py create mode 100755 third_party/catapult/devil/devil/android/sdk/shared_prefs_test.py create mode 100644 third_party/catapult/devil/devil/android/sdk/split_select.py create mode 100644 third_party/catapult/devil/devil/android/sdk/test/data/push_directory/push_directory_contents.txt create mode 100644 third_party/catapult/devil/devil/android/sdk/test/data/push_file.txt create mode 100644 third_party/catapult/devil/devil/android/sdk/version_codes.py create mode 100644 third_party/catapult/devil/devil/android/settings.py create mode 100644 third_party/catapult/devil/devil/android/tools/__init__.py create mode 100755 third_party/catapult/devil/devil/android/tools/adb_run_shell_cmd.py create mode 100755 third_party/catapult/devil/devil/android/tools/cpufreq.py create mode 100755 third_party/catapult/devil/devil/android/tools/device_monitor.py create mode 100755 third_party/catapult/devil/devil/android/tools/device_monitor_test.py create mode 100755 third_party/catapult/devil/devil/android/tools/device_recovery.py create mode 100755 third_party/catapult/devil/devil/android/tools/device_status.py create mode 100755 third_party/catapult/devil/devil/android/tools/flash_device.py create mode 100755 third_party/catapult/devil/devil/android/tools/keyboard.py create mode 100755 third_party/catapult/devil/devil/android/tools/provision_devices.py create mode 100755 third_party/catapult/devil/devil/android/tools/screenshot.py create mode 100644 third_party/catapult/devil/devil/android/tools/script_common.py create mode 100755 third_party/catapult/devil/devil/android/tools/script_common_test.py create mode 100755 third_party/catapult/devil/devil/android/tools/video_recorder.py create mode 100755 third_party/catapult/devil/devil/android/tools/wait_for_devices.py create mode 100644 third_party/catapult/devil/devil/android/valgrind_tools/__init__.py create mode 100644 third_party/catapult/devil/devil/android/valgrind_tools/base_tool.py create mode 100644 third_party/catapult/devil/devil/base_error.py create mode 100644 third_party/catapult/devil/devil/constants/__init__.py create mode 100644 third_party/catapult/devil/devil/constants/exit_codes.py create mode 100644 third_party/catapult/devil/devil/devil_dependencies.json create mode 100644 third_party/catapult/devil/devil/devil_env.py create mode 100755 third_party/catapult/devil/devil/devil_env_test.py create mode 100644 third_party/catapult/devil/devil/utils/__init__.py create mode 100755 third_party/catapult/devil/devil/utils/battor_device_mapping.py create mode 100644 third_party/catapult/devil/devil/utils/cmd_helper.py create mode 100755 third_party/catapult/devil/devil/utils/cmd_helper_test.py create mode 100644 third_party/catapult/devil/devil/utils/file_utils.py create mode 100755 third_party/catapult/devil/devil/utils/find_usb_devices.py create mode 100755 third_party/catapult/devil/devil/utils/find_usb_devices_test.py create mode 100644 third_party/catapult/devil/devil/utils/geometry.py create mode 100644 third_party/catapult/devil/devil/utils/geometry_test.py create mode 100644 third_party/catapult/devil/devil/utils/host_utils.py create mode 100644 third_party/catapult/devil/devil/utils/lazy/__init__.py create mode 100644 third_party/catapult/devil/devil/utils/lazy/weak_constant.py create mode 100644 third_party/catapult/devil/devil/utils/lsusb.py create mode 100755 third_party/catapult/devil/devil/utils/lsusb_test.py create mode 100755 third_party/catapult/devil/devil/utils/markdown.py create mode 100755 third_party/catapult/devil/devil/utils/markdown_test.py create mode 100644 third_party/catapult/devil/devil/utils/mock_calls.py create mode 100755 third_party/catapult/devil/devil/utils/mock_calls_test.py create mode 100644 third_party/catapult/devil/devil/utils/parallelizer.py create mode 100644 third_party/catapult/devil/devil/utils/parallelizer_test.py create mode 100644 third_party/catapult/devil/devil/utils/reraiser_thread.py create mode 100644 third_party/catapult/devil/devil/utils/reraiser_thread_unittest.py create mode 100755 third_party/catapult/devil/devil/utils/reset_usb.py create mode 100644 third_party/catapult/devil/devil/utils/run_tests_helper.py create mode 100644 third_party/catapult/devil/devil/utils/signal_handler.py create mode 100644 third_party/catapult/devil/devil/utils/test/data/test_serial_map.json create mode 100644 third_party/catapult/devil/devil/utils/timeout_retry.py create mode 100755 third_party/catapult/devil/devil/utils/timeout_retry_unittest.py create mode 100755 third_party/catapult/devil/devil/utils/update_mapping.py create mode 100644 third_party/catapult/devil/devil/utils/usb_hubs.py create mode 100644 third_party/catapult/devil/devil/utils/watchdog_timer.py create mode 100644 third_party/catapult/devil/devil/utils/zip_utils.py create mode 100644 third_party/catapult/devil/docs/adb_wrapper.md create mode 100644 third_party/catapult/devil/docs/device_blacklist.md create mode 100644 third_party/catapult/devil/docs/device_utils.md create mode 100644 third_party/catapult/devil/docs/markdown.md create mode 100644 third_party/catapult/devil/docs/persistent_device_list.md create mode 100644 third_party/catapult/devil/pylintrc create mode 100644 third_party/jinja2/AUTHORS create mode 100644 third_party/jinja2/Jinja2-2.8.tar.gz.md5 create mode 100644 third_party/jinja2/Jinja2-2.8.tar.gz.sha512 create mode 100644 third_party/jinja2/LICENSE create mode 100644 third_party/jinja2/README.chromium create mode 100644 third_party/jinja2/__init__.py create mode 100644 third_party/jinja2/_compat.py create mode 100644 third_party/jinja2/_stringdefs.py create mode 100644 third_party/jinja2/bccache.py create mode 100644 third_party/jinja2/compiler.py create mode 100644 third_party/jinja2/constants.py create mode 100644 third_party/jinja2/debug.py create mode 100644 third_party/jinja2/defaults.py create mode 100644 third_party/jinja2/environment.py create mode 100644 third_party/jinja2/exceptions.py create mode 100644 third_party/jinja2/ext.py create mode 100644 third_party/jinja2/filters.py create mode 100755 third_party/jinja2/get_jinja2.sh create mode 100644 third_party/jinja2/lexer.py create mode 100644 third_party/jinja2/loaders.py create mode 100644 third_party/jinja2/meta.py create mode 100644 third_party/jinja2/nodes.py create mode 100644 third_party/jinja2/optimizer.py create mode 100644 third_party/jinja2/parser.py create mode 100644 third_party/jinja2/runtime.py create mode 100644 third_party/jinja2/sandbox.py create mode 100644 third_party/jinja2/tests.py create mode 100644 third_party/jinja2/utils.py create mode 100644 third_party/jinja2/visitor.py create mode 100644 third_party/markupsafe/AUTHORS create mode 100644 third_party/markupsafe/LICENSE create mode 100644 third_party/markupsafe/MarkupSafe-0.18.tar.gz.md5 create mode 100644 third_party/markupsafe/MarkupSafe-0.18.tar.gz.sha512 create mode 100644 third_party/markupsafe/README.chromium create mode 100644 third_party/markupsafe/__init__.py create mode 100644 third_party/markupsafe/_compat.py create mode 100644 third_party/markupsafe/_constants.py create mode 100644 third_party/markupsafe/_native.py create mode 100644 third_party/markupsafe/_speedups.c create mode 100755 third_party/markupsafe/get_markupsafe.sh create mode 100644 third_party/ply/LICENSE create mode 100644 third_party/ply/README create mode 100644 third_party/ply/README.chromium create mode 100644 third_party/ply/__init__.py create mode 100644 third_party/ply/lex.py create mode 100644 third_party/ply/license.patch create mode 100644 third_party/ply/yacc.py create mode 100644 ui/gfx/geometry/mojo/BUILD.gn create mode 100644 ui/gfx/geometry/mojo/DEPS create mode 100644 ui/gfx/geometry/mojo/geometry.mojom create mode 100644 ui/gfx/geometry/mojo/geometry.typemap create mode 100644 ui/gfx/geometry/mojo/geometry_struct_traits.h create mode 100644 ui/gfx/geometry/mojo/geometry_struct_traits_unittest.cc create mode 100644 ui/gfx/geometry/mojo/geometry_traits_test_service.mojom create mode 100644 ui/gfx/range/mojo/BUILD.gn create mode 100644 ui/gfx/range/mojo/DEPS create mode 100644 ui/gfx/range/mojo/range.mojom create mode 100644 ui/gfx/range/mojo/range.typemap create mode 100644 ui/gfx/range/mojo/range_struct_traits.h create mode 100644 ui/gfx/range/mojo/range_struct_traits_unittest.cc create mode 100644 ui/gfx/range/mojo/range_traits_test_service.mojom diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/Android.bp b/Android.bp index be9d763..320f402 100644 --- a/Android.bp +++ b/Android.bp @@ -18,11 +18,10 @@ // Using Chrome header files directly could cause -Wunused-parameter errors, // and this is workaround. Please find the document in include_generator.py // for details. -gensrcs { - name: "libchrome-include", - cmd: "$(location libchrome_tools/include_generator.py) $(in) $(out)", - tool_files: ["libchrome_tools/include_generator.py"], - export_include_dirs: ["."], +// Note: gensrcs does not support exclude_srcs, so filegroup rule is +// introduced. +filegroup { + name: "libchrome-include-sources", srcs: [ "base/**/*.h", "build/**/*.h", @@ -32,6 +31,17 @@ gensrcs { "third_party/**/*.h", "ui/**/*.h", ], + exclude_srcs: [ + "base/android/**/*", + ], +} + +gensrcs { + name: "libchrome-include", + cmd: "$(location libchrome_tools/include_generator.py) $(in) $(out)", + tool_files: ["libchrome_tools/include_generator.py"], + export_include_dirs: ["."], + srcs: [":libchrome-include-sources"], output_extension: "h", } @@ -622,3 +632,415 @@ cc_test { }, }, } + +filegroup { + name: "libmojo_mojom_files", + srcs: [ + "ipc/ipc.mojom", + "mojo/common/file.mojom", + "mojo/common/file_path.mojom", + "mojo/common/string16.mojom", + "mojo/common/text_direction.mojom", + "mojo/common/time.mojom", + "mojo/common/unguessable_token.mojom", + "mojo/common/values.mojom", + "mojo/common/version.mojom", + "mojo/public/interfaces/bindings/interface_control_messages.mojom", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom", + "ui/gfx/geometry/mojo/geometry.mojom", + "ui/gfx/range/mojo/range.mojom", + ], +} + +filegroup { + name: "libmojo_mojo_sources", + srcs: [ + "mojo/**/*.cc", + ], + exclude_srcs: [ + // Unused in Chrome. Looks like mistakenly checked in. + // TODO(hidehiko): Remove this after the file is removed in Chrome + // repository. http://crrev.com/c/644531 + "mojo/public/cpp/system/message.cc", + + // No WTF support. + "mojo/public/cpp/bindings/lib/string_traits_wtf.cc", + + // Exclude windows/mac/ios files. + "**/*_win.cc", + "mojo/edk/system/mach_port_relay.cc", + + // Exclude js binding related files. + "mojo/edk/js/**/*", + "mojo/public/js/**/*", + + // Exclude tests. + // Note that mojo/edk/embedder/test_embedder.cc needs to be included + // for Mojo support. cf) b/62071944. + "**/*_unittest.cc", + "**/*_unittests.cc", + "**/*_perftest.cc", + "mojo/android/javatests/**/*", + "mojo/edk/system/core_test_base.cc", + "mojo/edk/system/test_utils.cc", + "mojo/edk/test/**/*", + "mojo/public/c/system/tests/**/*", + "mojo/public/cpp/bindings/tests/**/*", + "mojo/public/cpp/system/tests/**/*", + "mojo/public/cpp/test_support/**/*", + "mojo/public/tests/**/*", + ], +} + +// Python in Chrome repository requires still Python 2. +python_defaults { + name: "libmojo_scripts", + version: { + py2: { + enabled: true, + }, + py3: { + enabled: false, + }, + }, +} + +python_binary_host { + name: "jni_generator", + main: "base/android/jni_generator/jni_generator.py", + srcs: [ + "base/android/jni_generator/jni_generator.py", + "build/**/*.py", + "third_party/catapult/devil/devil/**/*.py", + ], + defaults: ["libmojo_scripts"], +} + +python_binary_host { + name: "mojom_bindings_generator", + main: "mojo/public/tools/bindings/mojom_bindings_generator.py", + srcs: [ + "mojo/public/tools/bindings/**/*.py", + "build/**/*.py", + "third_party/catapult/devil/**/*.py", + "third_party/jinja2/**/*.py", + "third_party/markupsafe/**/*.py", + "third_party/ply/**/*.py", + ], + data: [ + "mojo/public/tools/bindings/generators/cpp_templates/*.tmpl", + "mojo/public/tools/bindings/generators/java_templates/*.tmpl", + "mojo/public/tools/bindings/generators/js_templates/*.tmpl", + ], + defaults: ["libmojo_scripts"], +} + +cc_prebuilt_binary { + name: "mojom_source_generator_sh", + srcs: ["libchrome_tools/mojom_source_generator.sh"], + host_supported: true, +} + +genrule { + name: "libmojo_mojom_headers", + cmd: "$(location mojom_source_generator_sh)" + + " --mojom_bindings_generator=$(location mojom_bindings_generator)" + + " --package=external/libchrome" + + " --output_dir=$(genDir)" + + " --bytecode_path=$(genDir)" + + " --typemap=$(location gen/mojo/common/common_custom_types__type_mappings)" + + " --generators=c++" + + " --use_new_wrapper_types" + + " $(in)", + + tools: [ + "mojom_bindings_generator", + "mojom_source_generator_sh", + ], + + tool_files: [ + // This file was copied from out/Release in a Chrome checkout. + // TODO(lhchavez): Generate this file instead of hardcoding it. + "gen/mojo/common/common_custom_types__type_mappings", + ], + + srcs: [":libmojo_mojom_files"], + + out: [ + "ipc/ipc.mojom.h", + "ipc/ipc.mojom-shared.h", + "ipc/ipc.mojom-shared-internal.h", + "mojo/common/file.mojom.h", + "mojo/common/file.mojom-shared.h", + "mojo/common/file.mojom-shared-internal.h", + "mojo/common/file_path.mojom.h", + "mojo/common/file_path.mojom-shared.h", + "mojo/common/file_path.mojom-shared-internal.h", + "mojo/common/string16.mojom.h", + "mojo/common/string16.mojom-shared.h", + "mojo/common/string16.mojom-shared-internal.h", + "mojo/common/text_direction.mojom.h", + "mojo/common/text_direction.mojom-shared.h", + "mojo/common/text_direction.mojom-shared-internal.h", + "mojo/common/time.mojom.h", + "mojo/common/time.mojom-shared.h", + "mojo/common/time.mojom-shared-internal.h", + "mojo/common/unguessable_token.mojom.h", + "mojo/common/unguessable_token.mojom-shared.h", + "mojo/common/unguessable_token.mojom-shared-internal.h", + "mojo/common/values.mojom.h", + "mojo/common/values.mojom-shared.h", + "mojo/common/values.mojom-shared-internal.h", + "mojo/common/version.mojom.h", + "mojo/common/version.mojom-shared.h", + "mojo/common/version.mojom-shared-internal.h", + "mojo/public/interfaces/bindings/interface_control_messages.mojom.h", + "mojo/public/interfaces/bindings/interface_control_messages.mojom-shared.h", + "mojo/public/interfaces/bindings/interface_control_messages.mojom-shared-internal.h", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom.h", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom-shared.h", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom-shared-internal.h", + "ui/gfx/geometry/mojo/geometry.mojom.h", + "ui/gfx/geometry/mojo/geometry.mojom-shared.h", + "ui/gfx/geometry/mojo/geometry.mojom-shared-internal.h", + "ui/gfx/range/mojo/range.mojom.h", + "ui/gfx/range/mojo/range.mojom-shared.h", + "ui/gfx/range/mojo/range.mojom-shared-internal.h", + ], +} + +genrule { + name: "libmojo_mojom_srcs", + cmd: "$(location mojom_source_generator_sh)" + + " --mojom_bindings_generator=$(location mojom_bindings_generator)" + + " --package=external/libchrome" + + " --output_dir=$(genDir)" + + " --bytecode_path=$(genDir)" + + " --typemap=$(location gen/mojo/common/common_custom_types__type_mappings)" + + " --generators=c++" + + " --use_new_wrapper_types" + + " $(in)", + + tools: [ + "mojom_bindings_generator", + "mojom_source_generator_sh", + ], + + tool_files: [ + // This file was copied from out/Release in a Chrome checkout. + // TODO(lhchavez): Generate this file instead of hardcoding it. + "gen/mojo/common/common_custom_types__type_mappings", + "libchrome_tools/mojom_source_generator.sh", + ], + + srcs: [":libmojo_mojom_files"], + + out: [ + "ipc/ipc.mojom.cc", + "ipc/ipc.mojom-shared.cc", + "mojo/common/file.mojom.cc", + "mojo/common/file.mojom-shared.cc", + "mojo/common/string16.mojom.cc", + "mojo/common/string16.mojom-shared.cc", + "mojo/common/text_direction.mojom.cc", + "mojo/common/text_direction.mojom-shared.cc", + "mojo/common/time.mojom.cc", + "mojo/common/time.mojom-shared.cc", + "mojo/common/unguessable_token.mojom.cc", + "mojo/common/unguessable_token.mojom-shared.cc", + "mojo/common/version.mojom.cc", + "mojo/common/version.mojom-shared.cc", + "mojo/public/interfaces/bindings/interface_control_messages.mojom.cc", + "mojo/public/interfaces/bindings/interface_control_messages.mojom-shared.cc", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom.cc", + "mojo/public/interfaces/bindings/pipe_control_messages.mojom-shared.cc", + "ui/gfx/geometry/mojo/geometry.mojom.cc", + "ui/gfx/geometry/mojo/geometry.mojom-shared.cc", + "ui/gfx/range/mojo/range.mojom.cc", + "ui/gfx/range/mojo/range.mojom-shared.cc", + ], +} + +// TODO(hidehiko): Remove JNI for ContextUtils, after cleaning up the +// depended code. +genrule { + name: "libmojo_jni_headers", + cmd: "$(location libchrome_tools/jni_generator_helper.sh)" + + " --jni_generator=$(location jni_generator)" + + " --output_dir=$(genDir)/jni" + + " --includes=base/android/jni_generator/jni_generator_helper.h" + + " --ptr_type=long" + + " --native_exports_optional" + + " $(in)", + + tools: [ + "jni_generator", + ], + + tool_files: [ + "libchrome_tools/jni_generator_helper.sh", + ], + + srcs: [ + "base/android/java/src/org/chromium/base/BuildInfo.java", + "base/android/java/src/org/chromium/base/ContextUtils.java", + "mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java", + "mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java", + "mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java", + ], + + out: [ + "jni/BuildInfo_jni.h", + "jni/ContextUtils_jni.h", + "jni/BaseRunLoop_jni.h", + "jni/CoreImpl_jni.h", + "jni/WatcherImpl_jni.h", + ], +} + +cc_library_shared { + name: "libmojo", + + generated_headers: [ + "libmojo_jni_headers", + "libmojo_mojom_headers", + ], + + generated_sources: [ + "libmojo_mojom_srcs", + ], + + export_generated_headers: [ + "libmojo_jni_headers", + "libmojo_mojom_headers", + ], + + srcs: [ + "base/android/build_info.cc", + "base/android/context_utils.cc", + "base/android/jni_android.cc", + "base/android/jni_string.cc", + "base/android/scoped_java_ref.cc", + "ipc/ipc_message.cc", + "ipc/ipc_message_attachment.cc", + "ipc/ipc_message_attachment_set.cc", + "ipc/ipc_message_utils.cc", + "ipc/ipc_mojo_handle_attachment.cc", + "ipc/ipc_mojo_message_helper.cc", + "ipc/ipc_mojo_param_traits.cc", + "ipc/ipc_platform_file_attachment_posix.cc", + ":libmojo_mojo_sources", + ], + + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + "-Wno-missing-field-initializers", + "-DMOJO_EDK_LEGACY_PROTOCOL", + ], + + // We also pass NO_ASHMEM to make base::SharedMemory avoid using it and prefer + // the POSIX versions. + cppflags: [ + "-Wno-sign-promo", + "-Wno-non-virtual-dtor", + "-Wno-ignored-qualifiers", + "-Wno-extra", + "-DNO_ASHMEM", + ], + + shared_libs: [ + "libevent", + "liblog", + "libchrome", + "libchrome-crypto", + ], + + export_include_dirs: ["."], +} + +genrule { + name: "libmojo_mojom_java_srcs", + cmd: "$(location mojom_source_generator_sh)" + + " --mojom_bindings_generator=$(location mojom_bindings_generator)" + + " --package=external/libchrome" + + " --output_dir=$(genDir)" + + " --bytecode_path=$(genDir)" + + " --typemap=$(location gen/mojo/common/common_custom_types__type_mappings)" + + " --generators=java" + + " --use_new_wrapper_types" + + " $(in)", + + tools: [ + "mojom_bindings_generator", + "mojom_source_generator_sh", + ], + + tool_files: [ + // This file was copied from out/Release in a Chrome checkout. + // TODO(lhchavez): Generate this file instead of hardcoding it. + "gen/mojo/common/common_custom_types__type_mappings", + ], + + srcs: [":libmojo_mojom_files"], + + out: [ + "src/org/chromium/gfx/mojom/InsetsF.java", + "src/org/chromium/gfx/mojom/Insets.java", + "src/org/chromium/gfx/mojom/PointF.java", + "src/org/chromium/gfx/mojom/Point.java", + "src/org/chromium/gfx/mojom/RangeF.java", + "src/org/chromium/gfx/mojom/Range.java", + "src/org/chromium/gfx/mojom/RectF.java", + "src/org/chromium/gfx/mojom/Rect.java", + "src/org/chromium/gfx/mojom/SizeF.java", + "src/org/chromium/gfx/mojom/Size.java", + "src/org/chromium/gfx/mojom/Vector2dF.java", + "src/org/chromium/gfx/mojom/Vector2d.java", + "src/org/chromium/IPC/mojom/ChannelBootstrap_Internal.java", + "src/org/chromium/IPC/mojom/ChannelBootstrap.java", + "src/org/chromium/IPC/mojom/Channel_Internal.java", + "src/org/chromium/IPC/mojom/Channel.java", + "src/org/chromium/IPC/mojom/GenericInterface_Internal.java", + "src/org/chromium/IPC/mojom/GenericInterface.java", + "src/org/chromium/IPC/mojom/IpcConstants.java", + "src/org/chromium/IPC/mojom/SerializedHandle.java", + "src/org/chromium/mojo/bindings/interfacecontrol/FlushForTesting.java", + "src/org/chromium/mojo/bindings/interfacecontrol/InterfaceControlMessagesConstants.java", + "src/org/chromium/mojo/bindings/interfacecontrol/QueryVersion.java", + "src/org/chromium/mojo/bindings/interfacecontrol/QueryVersionResult.java", + "src/org/chromium/mojo/bindings/interfacecontrol/RequireVersion.java", + "src/org/chromium/mojo/bindings/interfacecontrol/RunInput.java", + "src/org/chromium/mojo/bindings/interfacecontrol/RunMessageParams.java", + "src/org/chromium/mojo/bindings/interfacecontrol/RunOrClosePipeInput.java", + "src/org/chromium/mojo/bindings/interfacecontrol/RunOrClosePipeMessageParams.java", + "src/org/chromium/mojo/bindings/interfacecontrol/RunOutput.java", + "src/org/chromium/mojo/bindings/interfacecontrol/RunResponseMessageParams.java", + "src/org/chromium/mojo/bindings/pipecontrol/DisconnectReason.java", + "src/org/chromium/mojo/bindings/pipecontrol/PeerAssociatedEndpointClosedEvent.java", + "src/org/chromium/mojo/bindings/pipecontrol/PipeControlMessagesConstants.java", + "src/org/chromium/mojo/bindings/pipecontrol/RunOrClosePipeInput.java", + "src/org/chromium/mojo/bindings/pipecontrol/RunOrClosePipeMessageParams.java", + "src/org/chromium/mojo/common/mojom/File.java", + "src/org/chromium/mojo/common/mojom/String16.java", + "src/org/chromium/mojo/common/mojom/TextDirection.java", + "src/org/chromium/mojo/common/mojom/TimeDelta.java", + "src/org/chromium/mojo/common/mojom/Time.java", + "src/org/chromium/mojo/common/mojom/TimeTicks.java", + "src/org/chromium/mojo/common/mojom/UnguessableToken.java", + "src/org/chromium/mojo/common/mojom/Version.java", + ], +} + +java_library { + name: "android.mojo", + + srcs: [ + ":libmojo_mojom_java_srcs", + "base/android/java/src/**/*.java", + "mojo/android/system/src/**/*.java", + "mojo/public/java/system/src/**/*.java", + "mojo/public/java/bindings/src/**/*.java", + ], +} diff --git a/base/android/build_info.cc b/base/android/build_info.cc new file mode 100644 index 0000000..869c703 --- /dev/null +++ b/base/android/build_info.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/build_info.h" + +#include + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "jni/BuildInfo_jni.h" + +namespace { + +// We are leaking these strings. +const char* StrDupJString(const base::android::JavaRef& java_string) { + std::string str = ConvertJavaStringToUTF8(java_string); + return strdup(str.c_str()); +} + +} // namespace + +namespace base { +namespace android { + +struct BuildInfoSingletonTraits { + static BuildInfo* New() { + return new BuildInfo(AttachCurrentThread()); + } + + static void Delete(BuildInfo* x) { + // We're leaking this type, see kRegisterAtExit. + NOTREACHED(); + } + + static const bool kRegisterAtExit = false; +#if DCHECK_IS_ON() + static const bool kAllowedToAccessOnNonjoinableThread = true; +#endif +}; + +BuildInfo::BuildInfo(JNIEnv* env) + : device_(StrDupJString(Java_BuildInfo_getDevice(env))), + manufacturer_(StrDupJString(Java_BuildInfo_getDeviceManufacturer(env))), + model_(StrDupJString(Java_BuildInfo_getDeviceModel(env))), + brand_(StrDupJString(Java_BuildInfo_getBrand(env))), + android_build_id_(StrDupJString(Java_BuildInfo_getAndroidBuildId(env))), + android_build_fp_( + StrDupJString(Java_BuildInfo_getAndroidBuildFingerprint(env))), + gms_version_code_(StrDupJString(Java_BuildInfo_getGMSVersionCode(env))), + package_version_code_( + StrDupJString(Java_BuildInfo_getPackageVersionCode(env))), + package_version_name_( + StrDupJString(Java_BuildInfo_getPackageVersionName(env))), + package_label_(StrDupJString(Java_BuildInfo_getPackageLabel(env))), + package_name_(StrDupJString(Java_BuildInfo_getPackageName(env))), + build_type_(StrDupJString(Java_BuildInfo_getBuildType(env))), + sdk_int_(Java_BuildInfo_getSdkInt(env)), + java_exception_info_(NULL) {} + +// static +BuildInfo* BuildInfo::GetInstance() { + return Singleton::get(); +} + +void BuildInfo::SetJavaExceptionInfo(const std::string& info) { + DCHECK(!java_exception_info_) << "info should be set only once."; + java_exception_info_ = strndup(info.c_str(), 4096); +} + +void BuildInfo::ClearJavaExceptionInfo() { + delete java_exception_info_; + java_exception_info_ = nullptr; +} + +} // namespace android +} // namespace base diff --git a/base/android/build_info.h b/base/android/build_info.h new file mode 100644 index 0000000..cce74f4 --- /dev/null +++ b/base/android/build_info.h @@ -0,0 +1,145 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_BUILD_INFO_H_ +#define BASE_ANDROID_BUILD_INFO_H_ + +#include + +#include + +#include "base/base_export.h" +#include "base/macros.h" +#include "base/memory/singleton.h" + +namespace base { +namespace android { + +// This enumeration maps to the values returned by BuildInfo::sdk_int(), +// indicating the Android release associated with a given SDK version. +enum SdkVersion { + SDK_VERSION_JELLY_BEAN = 16, + SDK_VERSION_JELLY_BEAN_MR1 = 17, + SDK_VERSION_JELLY_BEAN_MR2 = 18, + SDK_VERSION_KITKAT = 19, + SDK_VERSION_KITKAT_WEAR = 20, + SDK_VERSION_LOLLIPOP = 21, + SDK_VERSION_LOLLIPOP_MR1 = 22, + SDK_VERSION_MARSHMALLOW = 23, + SDK_VERSION_NOUGAT = 24 +}; + +// BuildInfo is a singleton class that stores android build and device +// information. It will be called from Android specific code and gets used +// primarily in crash reporting. + +// It is also used to store the last java exception seen during JNI. +// TODO(nileshagrawal): Find a better place to store this info. +class BASE_EXPORT BuildInfo { + public: + + ~BuildInfo() {} + + // Static factory method for getting the singleton BuildInfo instance. + // Note that ownership is not conferred on the caller and the BuildInfo in + // question isn't actually freed until shutdown. This is ok because there + // should only be one instance of BuildInfo ever created. + static BuildInfo* GetInstance(); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* device() const { + return device_; + } + + const char* manufacturer() const { + return manufacturer_; + } + + const char* model() const { + return model_; + } + + const char* brand() const { + return brand_; + } + + const char* android_build_id() const { + return android_build_id_; + } + + const char* android_build_fp() const { + return android_build_fp_; + } + + const char* gms_version_code() const { + return gms_version_code_; + } + + const char* package_version_code() const { + return package_version_code_; + } + + const char* package_version_name() const { + return package_version_name_; + } + + const char* package_label() const { + return package_label_; + } + + const char* package_name() const { + return package_name_; + } + + const char* build_type() const { + return build_type_; + } + + int sdk_int() const { + return sdk_int_; + } + + const char* java_exception_info() const { + return java_exception_info_; + } + + void SetJavaExceptionInfo(const std::string& info); + + void ClearJavaExceptionInfo(); + + private: + friend struct BuildInfoSingletonTraits; + + explicit BuildInfo(JNIEnv* env); + + // Const char* is used instead of std::strings because these values must be + // available even if the process is in a crash state. Sadly + // std::string.c_str() doesn't guarantee that memory won't be allocated when + // it is called. + const char* const device_; + const char* const manufacturer_; + const char* const model_; + const char* const brand_; + const char* const android_build_id_; + const char* const android_build_fp_; + const char* const gms_version_code_; + const char* const package_version_code_; + const char* const package_version_name_; + const char* const package_label_; + const char* const package_name_; + const char* const build_type_; + const int sdk_int_; + // This is set via set_java_exception_info, not at constructor time. + const char* java_exception_info_; + + DISALLOW_COPY_AND_ASSIGN(BuildInfo); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_BUILD_INFO_H_ diff --git a/base/android/context_utils.cc b/base/android/context_utils.cc new file mode 100644 index 0000000..e2c4ed0 --- /dev/null +++ b/base/android/context_utils.cc @@ -0,0 +1,53 @@ +// 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/android/context_utils.h" + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/lazy_instance.h" +#include "jni/ContextUtils_jni.h" + +using base::android::JavaRef; + +namespace base { +namespace android { + +namespace { + +// Leak the global app context, as it is used from a non-joinable worker thread +// that may still be running at shutdown. There is no harm in doing this. +base::LazyInstance>::Leaky + g_application_context = LAZY_INSTANCE_INITIALIZER; + +void SetNativeApplicationContext(JNIEnv* env, const JavaRef& context) { + if (env->IsSameObject(g_application_context.Get().obj(), context.obj())) { + // It's safe to set the context more than once if it's the same context. + return; + } + DCHECK(g_application_context.Get().is_null()); + g_application_context.Get().Reset(context); +} + +} // namespace + +const JavaRef& GetApplicationContext() { + DCHECK(!g_application_context.Get().is_null()); + return g_application_context.Get(); +} + +static void InitNativeSideApplicationContext( + JNIEnv* env, + const JavaParamRef& clazz, + const JavaParamRef& context) { + SetNativeApplicationContext(env, context); +} + +bool RegisterContextUtils(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace base diff --git a/base/android/context_utils.h b/base/android/context_utils.h new file mode 100644 index 0000000..c5289f1 --- /dev/null +++ b/base/android/context_utils.h @@ -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. + +#ifndef BASE_ANDROID_CONTEXT_UTILS_H_ +#define BASE_ANDROID_CONTEXT_UTILS_H_ + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" + +namespace base { +namespace android { + +// Gets a global ref to the application context set with +// InitApplicationContext(). Ownership is retained by the function - the caller +// must NOT release it. +BASE_EXPORT const JavaRef& GetApplicationContext(); + +bool RegisterContextUtils(JNIEnv* env); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_CONTEXT_UTILS_H_ diff --git a/base/android/java/src/org/chromium/base/BuildInfo.java b/base/android/java/src/org/chromium/base/BuildInfo.java new file mode 100644 index 0000000..de4ad08 --- /dev/null +++ b/base/android/java/src/org/chromium/base/BuildInfo.java @@ -0,0 +1,171 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.os.StrictMode; + +import org.chromium.base.annotations.CalledByNative; + +/** + * BuildInfo is a utility class providing easy access to {@link PackageInfo} information. This is + * primarily of use for accessing package information from native code. + */ +public class BuildInfo { + private static final String TAG = "BuildInfo"; + private static final int MAX_FINGERPRINT_LENGTH = 128; + + /** + * BuildInfo is a static utility class and therefore shouldn't be instantiated. + */ + private BuildInfo() {} + + @CalledByNative + public static String getDevice() { + return Build.DEVICE; + } + + @CalledByNative + public static String getBrand() { + return Build.BRAND; + } + + @CalledByNative + public static String getAndroidBuildId() { + return Build.ID; + } + + /** + * @return The build fingerprint for the current Android install. The value is truncated to a + * 128 characters as this is used for crash and UMA reporting, which should avoid huge + * strings. + */ + @CalledByNative + public static String getAndroidBuildFingerprint() { + return Build.FINGERPRINT.substring( + 0, Math.min(Build.FINGERPRINT.length(), MAX_FINGERPRINT_LENGTH)); + } + + @CalledByNative + public static String getDeviceManufacturer() { + return Build.MANUFACTURER; + } + + @CalledByNative + public static String getDeviceModel() { + return Build.MODEL; + } + + @CalledByNative + public static String getGMSVersionCode() { + String msg = "gms versionCode not available."; + try { + PackageManager packageManager = + ContextUtils.getApplicationContext().getPackageManager(); + PackageInfo packageInfo = packageManager.getPackageInfo("com.google.android.gms", 0); + msg = Integer.toString(packageInfo.versionCode); + } catch (NameNotFoundException e) { + Log.d(TAG, "GMS package is not found.", e); + } + return msg; + } + + @CalledByNative + public static String getPackageVersionCode() { + String msg = "versionCode not available."; + try { + PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); + PackageInfo pi = pm.getPackageInfo(getPackageName(), 0); + msg = ""; + if (pi.versionCode > 0) { + msg = Integer.toString(pi.versionCode); + } + } catch (NameNotFoundException e) { + Log.d(TAG, msg); + } + return msg; + } + + @CalledByNative + public static String getPackageVersionName() { + String msg = "versionName not available"; + try { + PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); + PackageInfo pi = pm.getPackageInfo(getPackageName(), 0); + msg = ""; + if (pi.versionName != null) { + msg = pi.versionName; + } + } catch (NameNotFoundException e) { + Log.d(TAG, msg); + } + return msg; + } + + @CalledByNative + public static String getPackageLabel() { + // Third-party code does disk read on the getApplicationInfo call. http://crbug.com/614343 + StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads(); + try { + PackageManager packageManager = + ContextUtils.getApplicationContext().getPackageManager(); + ApplicationInfo appInfo = packageManager.getApplicationInfo( + getPackageName(), PackageManager.GET_META_DATA); + CharSequence label = packageManager.getApplicationLabel(appInfo); + return label != null ? label.toString() : ""; + } catch (NameNotFoundException e) { + return ""; + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + @CalledByNative + public static String getPackageName() { + if (ContextUtils.getApplicationContext() == null) { + return ""; + } + return ContextUtils.getApplicationContext().getPackageName(); + } + + @CalledByNative + public static String getBuildType() { + return Build.TYPE; + } + + /** + * Check if this is a debuggable build of Android. Use this to enable developer-only features. + */ + public static boolean isDebugAndroid() { + return "eng".equals(Build.TYPE) || "userdebug".equals(Build.TYPE); + } + + @CalledByNative + public static int getSdkInt() { + return Build.VERSION.SDK_INT; + } + + /** + * @return Whether the current device is running Android O release or newer. + */ + public static boolean isAtLeastO() { + return !"REL".equals(Build.VERSION.CODENAME) + && ("O".equals(Build.VERSION.CODENAME) || Build.VERSION.CODENAME.startsWith("OMR")); + } + + /** + * @return Whether the current app targets the SDK for at least O + */ + public static boolean targetsAtLeastO(Context appContext) { + return isAtLeastO() + && appContext.getApplicationInfo().targetSdkVersion + == Build.VERSION_CODES.CUR_DEVELOPMENT; + } +} diff --git a/base/android/java/src/org/chromium/base/ContextUtils.java b/base/android/java/src/org/chromium/base/ContextUtils.java new file mode 100644 index 0000000..448eff9 --- /dev/null +++ b/base/android/java/src/org/chromium/base/ContextUtils.java @@ -0,0 +1,115 @@ +// 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.base; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; + +/** + * This class provides Android application context related utility methods. + */ +@JNINamespace("base::android") +@MainDex +public class ContextUtils { + private static final String TAG = "ContextUtils"; + private static Context sApplicationContext; + + /** + * Initialization-on-demand holder. This exists for thread-safe lazy initialization. + */ + private static class Holder { + // Not final for tests. + private static SharedPreferences sSharedPreferences = fetchAppSharedPreferences(); + } + + /** + * Get the Android application context. + * + * Under normal circumstances there is only one application context in a process, so it's safe + * to treat this as a global. In WebView it's possible for more than one app using WebView to be + * running in a single process, but this mechanism is rarely used and this is not the only + * problem in that scenario, so we don't currently forbid using it as a global. + * + * Do not downcast the context returned by this method to Application (or any subclass). It may + * not be an Application object; it may be wrapped in a ContextWrapper. The only assumption you + * may make is that it is a Context whose lifetime is the same as the lifetime of the process. + */ + public static Context getApplicationContext() { + return sApplicationContext; + } + + /** + * Initializes the java application context. + * + * This should be called exactly once early on during startup, before native is loaded and + * before any other clients make use of the application context through this class. + * + * @param appContext The application context. + */ + public static void initApplicationContext(Context appContext) { + // Conceding that occasionally in tests, native is loaded before the browser process is + // started, in which case the browser process re-sets the application context. + if (sApplicationContext != null && sApplicationContext != appContext) { + throw new RuntimeException("Attempting to set multiple global application contexts."); + } + initJavaSideApplicationContext(appContext); + } + + /** + * Initialize the native Android application context to be the same as the java counter-part. + */ + public static void initApplicationContextForNative() { + if (sApplicationContext == null) { + throw new RuntimeException("Cannot have native global application context be null."); + } + nativeInitNativeSideApplicationContext(sApplicationContext); + } + + /** + * Only called by the static holder class and tests. + * + * @return The application-wide shared preferences. + */ + private static SharedPreferences fetchAppSharedPreferences() { + return PreferenceManager.getDefaultSharedPreferences(sApplicationContext); + } + + /** + * This is used to ensure that we always use the application context to fetch the default shared + * preferences. This avoids needless I/O for android N and above. It also makes it clear that + * the app-wide shared preference is desired, rather than the potentially context-specific one. + * + * @return application-wide shared preferences. + */ + public static SharedPreferences getAppSharedPreferences() { + return Holder.sSharedPreferences; + } + + /** + * Occasionally tests cannot ensure the application context doesn't change between tests (junit) + * and sometimes specific tests has its own special needs, initApplicationContext should be used + * as much as possible, but this method can be used to override it. + * + * @param appContext The new application context. + */ + @VisibleForTesting + public static void initApplicationContextForTests(Context appContext) { + initJavaSideApplicationContext(appContext); + Holder.sSharedPreferences = fetchAppSharedPreferences(); + } + + private static void initJavaSideApplicationContext(Context appContext) { + if (appContext == null) { + throw new RuntimeException("Global application context cannot be set to null."); + } + sApplicationContext = appContext; + } + + private static native void nativeInitNativeSideApplicationContext(Context appContext); +} diff --git a/base/android/java/src/org/chromium/base/Log.java b/base/android/java/src/org/chromium/base/Log.java new file mode 100644 index 0000000..399f16d --- /dev/null +++ b/base/android/java/src/org/chromium/base/Log.java @@ -0,0 +1,387 @@ +// 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.base; + +import org.chromium.base.annotations.RemovableInRelease; + +import java.util.Locale; + +/** + * Utility class for Logging. + * + *

+ * Defines logging access points for each feature. They format and forward the logs to + * {@link android.util.Log}, allowing to standardize the output, to make it easy to identify + * the origin of logs, and enable or disable logging in different parts of the code. + *

+ *

+ * Usage documentation: {@code //docs/android_logging.md}. + *

+ */ +public class Log { + /** Convenience property, same as {@link android.util.Log#ASSERT}. */ + public static final int ASSERT = android.util.Log.ASSERT; + + /** Convenience property, same as {@link android.util.Log#DEBUG}. */ + public static final int DEBUG = android.util.Log.DEBUG; + + /** Convenience property, same as {@link android.util.Log#ERROR}. */ + public static final int ERROR = android.util.Log.ERROR; + + /** Convenience property, same as {@link android.util.Log#INFO}. */ + public static final int INFO = android.util.Log.INFO; + + /** Convenience property, same as {@link android.util.Log#VERBOSE}. */ + public static final int VERBOSE = android.util.Log.VERBOSE; + + /** Convenience property, same as {@link android.util.Log#WARN}. */ + public static final int WARN = android.util.Log.WARN; + + private static final String sTagPrefix = "cr_"; + private static final String sDeprecatedTagPrefix = "cr."; + + private Log() { + // Static only access + } + + /** Returns a formatted log message, using the supplied format and arguments.*/ + private static String formatLog(String messageTemplate, Object... params) { + if (params != null && params.length != 0) { + messageTemplate = String.format(Locale.US, messageTemplate, params); + } + + return messageTemplate; + } + + /** + * Returns a normalized tag that will be in the form: "cr_foo". This function is called by the + * various Log overrides. If using {@link #isLoggable(String, int)}, you might want to call it + * to get the tag that will actually be used. + * @see #sTagPrefix + */ + public static String normalizeTag(String tag) { + if (tag.startsWith(sTagPrefix)) return tag; + + // TODO(dgn) simplify this once 'cr.' is out of the repo (http://crbug.com/533072) + int unprefixedTagStart = 0; + if (tag.startsWith(sDeprecatedTagPrefix)) { + unprefixedTagStart = sDeprecatedTagPrefix.length(); + } + + return sTagPrefix + tag.substring(unprefixedTagStart, tag.length()); + } + + /** + * Returns a formatted log message, using the supplied format and arguments. + * The message will be prepended with the filename and line number of the call. + */ + private static String formatLogWithStack(String messageTemplate, Object... params) { + return "[" + getCallOrigin() + "] " + formatLog(messageTemplate, params); + } + + /** + * Convenience function, forwards to {@link android.util.Log#isLoggable(String, int)}. + * + * Note: Has no effect on whether logs are sent or not. Use a method with + * {@link RemovableInRelease} to log something in Debug builds only. + */ + public static boolean isLoggable(String tag, int level) { + return android.util.Log.isLoggable(tag, level); + } + + /** + * Sends a {@link android.util.Log#VERBOSE} log message. + * + * For optimization purposes, only the fixed parameters versions are visible. If you need more + * than 7 parameters, consider building your log message using a function annotated with + * {@link RemovableInRelease}. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + private static void verbose(String tag, String messageTemplate, Object... args) { + String message = formatLogWithStack(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.v(normalizeTag(tag), message, tr); + } else { + android.util.Log.v(normalizeTag(tag), message); + } + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 0 args version. */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String message) { + verbose(tag, message); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 1 arg version. */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1) { + verbose(tag, messageTemplate, arg1); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 2 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2) { + verbose(tag, messageTemplate, arg1, arg2); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 3 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v( + String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) { + verbose(tag, messageTemplate, arg1, arg2, arg3); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 4 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 5 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 6 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6); + } + + /** Sends a {@link android.util.Log#VERBOSE} log message. 7 args version */ + @RemovableInRelease + @VisibleForTesting + public static void v(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6, Object arg7) { + verbose(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + /** + * Sends a {@link android.util.Log#DEBUG} log message. + * + * For optimization purposes, only the fixed parameters versions are visible. If you need more + * than 7 parameters, consider building your log message using a function annotated with + * {@link RemovableInRelease}. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + private static void debug(String tag, String messageTemplate, Object... args) { + String message = formatLogWithStack(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.d(normalizeTag(tag), message, tr); + } else { + android.util.Log.d(normalizeTag(tag), message); + } + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 0 args version. */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String message) { + debug(tag, message); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 1 arg version. */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1) { + debug(tag, messageTemplate, arg1); + } + /** Sends a {@link android.util.Log#DEBUG} log message. 2 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2) { + debug(tag, messageTemplate, arg1, arg2); + } + /** Sends a {@link android.util.Log#DEBUG} log message. 3 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d( + String tag, String messageTemplate, Object arg1, Object arg2, Object arg3) { + debug(tag, messageTemplate, arg1, arg2, arg3); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 4 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 5 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 6 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6); + } + + /** Sends a {@link android.util.Log#DEBUG} log message. 7 args version */ + @RemovableInRelease + @VisibleForTesting + public static void d(String tag, String messageTemplate, Object arg1, Object arg2, Object arg3, + Object arg4, Object arg5, Object arg6, Object arg7) { + debug(tag, messageTemplate, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + /** + * Sends an {@link android.util.Log#INFO} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void i(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.i(normalizeTag(tag), message, tr); + } else { + android.util.Log.i(normalizeTag(tag), message); + } + } + + /** + * Sends a {@link android.util.Log#WARN} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void w(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.w(normalizeTag(tag), message, tr); + } else { + android.util.Log.w(normalizeTag(tag), message); + } + } + + /** + * Sends an {@link android.util.Log#ERROR} log message. + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void e(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.e(normalizeTag(tag), message, tr); + } else { + android.util.Log.e(normalizeTag(tag), message); + } + } + + /** + * What a Terrible Failure: Used for conditions that should never happen, and logged at + * the {@link android.util.Log#ASSERT} level. Depending on the configuration, it might + * terminate the process. + * + * @see android.util.Log#wtf(String, String, Throwable) + * + * @param tag Used to identify the source of a log message. Might be modified in the output + * (see {@link #normalizeTag(String)}) + * @param messageTemplate The message you would like logged. It is to be specified as a format + * string. + * @param args Arguments referenced by the format specifiers in the format string. If the last + * one is a {@link Throwable}, its trace will be printed. + */ + @VisibleForTesting + public static void wtf(String tag, String messageTemplate, Object... args) { + String message = formatLog(messageTemplate, args); + Throwable tr = getThrowableToLog(args); + if (tr != null) { + android.util.Log.wtf(normalizeTag(tag), message, tr); + } else { + android.util.Log.wtf(normalizeTag(tag), message); + } + } + + /** Handy function to get a loggable stack trace from a Throwable. */ + public static String getStackTraceString(Throwable tr) { + return android.util.Log.getStackTraceString(tr); + } + + private static Throwable getThrowableToLog(Object[] args) { + if (args == null || args.length == 0) return null; + + Object lastArg = args[args.length - 1]; + + if (!(lastArg instanceof Throwable)) return null; + return (Throwable) lastArg; + } + + /** Returns a string form of the origin of the log call, to be used as secondary tag.*/ + private static String getCallOrigin() { + StackTraceElement[] st = Thread.currentThread().getStackTrace(); + + // The call stack should look like: + // n [a variable number of calls depending on the vm used] + // +0 getCallOrigin() + // +1 privateLogFunction: verbose or debug + // +2 formatLogWithStack() + // +3 logFunction: v or d + // +4 caller + + int callerStackIndex; + String logClassName = Log.class.getName(); + for (callerStackIndex = 0; callerStackIndex < st.length; callerStackIndex++) { + if (st[callerStackIndex].getClassName().equals(logClassName)) { + callerStackIndex += 4; + break; + } + } + + return st[callerStackIndex].getFileName() + ":" + st[callerStackIndex].getLineNumber(); + } +} diff --git a/base/android/java/src/org/chromium/base/PackageUtils.java b/base/android/java/src/org/chromium/base/PackageUtils.java new file mode 100644 index 0000000..ab554cd --- /dev/null +++ b/base/android/java/src/org/chromium/base/PackageUtils.java @@ -0,0 +1,37 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +/** + * This class provides package checking related methods. + */ +public class PackageUtils { + /** + * Retrieves the version of the given package installed on the device. + * + * @param context Any context. + * @param packageName Name of the package to find. + * @return The package's version code if found, -1 otherwise. + */ + public static int getPackageVersion(Context context, String packageName) { + int versionCode = -1; + PackageManager pm = context.getPackageManager(); + try { + PackageInfo packageInfo = pm.getPackageInfo(packageName, 0); + if (packageInfo != null) versionCode = packageInfo.versionCode; + } catch (PackageManager.NameNotFoundException e) { + // Do nothing, versionCode stays -1 + } + return versionCode; + } + + private PackageUtils() { + // Hide constructor + } +} diff --git a/base/android/java/src/org/chromium/base/VisibleForTesting.java b/base/android/java/src/org/chromium/base/VisibleForTesting.java new file mode 100644 index 0000000..24cbfad --- /dev/null +++ b/base/android/java/src/org/chromium/base/VisibleForTesting.java @@ -0,0 +1,12 @@ +// Copyright 2014 The Chromium 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.base; + +/** + * Annotation used to mark code that has wider visibility or present for testing code. + */ +public @interface VisibleForTesting { + +} diff --git a/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java b/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java new file mode 100644 index 0000000..6df7c11 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/AccessedByNative.java @@ -0,0 +1,20 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @AccessedByNative is used to ensure proguard will keep this field, since it's + * only accessed by native. + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.CLASS) +public @interface AccessedByNative { + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/CalledByNative.java b/base/android/java/src/org/chromium/base/annotations/CalledByNative.java new file mode 100644 index 0000000..94ef3fa --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/CalledByNative.java @@ -0,0 +1,23 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNative is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNative { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java b/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java new file mode 100644 index 0000000..c0abcbe --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/CalledByNativeUnchecked.java @@ -0,0 +1,27 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @CalledByNativeUnchecked is used to generate JNI bindings that do not check for exceptions. + * It only makes sense to use this annotation on methods that declare a throws... spec. + * However, note that the exception received native side maybe an 'unchecked' (RuntimeExpception) + * such as NullPointerException, so the native code should differentiate these cases. + * Usage of this should be very rare; where possible handle exceptions in the Java side and use a + * return value to indicate success / failure. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface CalledByNativeUnchecked { + /* + * If present, tells which inner class the method belongs to. + */ + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java b/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java new file mode 100644 index 0000000..f1bf85e --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/JNIAdditionalImport.java @@ -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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JNIAdditionalImport is used by the JNI generator to qualify inner types used on JNI methods. Must + * be used when an inner class is used from a class within the same package. Example: + * + *
+ * @JNIAdditionImport(Foo.class)
+ * public class Bar {
+ *     @CalledByNative static void doSomethingWithInner(Foo.Inner inner) {
+ *     ...
+ *     }
+ * }
+ * 
+ * 

+ * Notes: + * 1) Foo must be in the same package as Bar + * 2) For classes in different packages, they should be imported as: + * import other.package.Foo; + * and this annotation should not be used. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface JNIAdditionalImport { + Class[] value(); +} diff --git a/base/android/java/src/org/chromium/base/annotations/JNINamespace.java b/base/android/java/src/org/chromium/base/annotations/JNINamespace.java new file mode 100644 index 0000000..4cd5531 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/JNINamespace.java @@ -0,0 +1,20 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @JNINamespace is used by the JNI generator to create the necessary JNI + * bindings and expose this method to native code using the specified namespace. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface JNINamespace { + public String value(); +} diff --git a/base/android/java/src/org/chromium/base/annotations/MainDex.java b/base/android/java/src/org/chromium/base/annotations/MainDex.java new file mode 100644 index 0000000..0b35ade --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/MainDex.java @@ -0,0 +1,21 @@ +// 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.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation that signals that a class should be kept in the main dex file. + * + * This generally means it's used by renderer processes, which can't load secondary dexes + * on K and below. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface MainDex { +} diff --git a/base/android/java/src/org/chromium/base/annotations/NativeCall.java b/base/android/java/src/org/chromium/base/annotations/NativeCall.java new file mode 100644 index 0000000..b69cd17 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/NativeCall.java @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium 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.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @NativeCall is used by the JNI generator to create the necessary JNI bindings + * so a native function can be bound to a Java inner class. The native class for + * which the JNI method will be generated is specified by the first parameter. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.CLASS) +public @interface NativeCall { + /* + * Value determines which native class the method should map to. + */ + public String value() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java b/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java new file mode 100644 index 0000000..afbc368 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/NativeClassQualifiedName.java @@ -0,0 +1,25 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @NativeClassQualifiedName is used by the JNI generator to create the necessary JNI + * bindings to call into the specified native class name. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface NativeClassQualifiedName { + /* + * Tells which native class the method is going to be bound to. + * The first parameter of the annotated method must be an int nativePtr pointing to + * an instance of this class. + */ + public String value(); +} diff --git a/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java b/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java new file mode 100644 index 0000000..2191334 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/RemovableInRelease.java @@ -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. + +package org.chromium.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * The annotated function can be removed in release builds. + * + * Calls to this function will be removed if its return value is not used. If all calls are removed, + * the function definition itself will be candidate for removal. + * It works by indicating to Proguard that the function has no side effects. + */ +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface RemovableInRelease {} diff --git a/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java b/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java new file mode 100644 index 0000000..89068ac --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/SuppressFBWarnings.java @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium 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.base.annotations; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @SuppressFBWarnings is used to suppress FindBugs warnings. + * + * The long name of FindBugs warnings can be found at + * http://findbugs.sourceforge.net/bugDescriptions.html + */ +@Retention(RetentionPolicy.CLASS) +public @interface SuppressFBWarnings { + String[] value() default {}; + String justification() default ""; +} diff --git a/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java b/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java new file mode 100644 index 0000000..a2af704 --- /dev/null +++ b/base/android/java/src/org/chromium/base/annotations/UsedByReflection.java @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium 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.base.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Annotation used for marking methods and fields that are called by reflection. + * Useful for keeping components that would otherwise be removed by Proguard. + * Use the value parameter to mention a file that calls this method. + * + * Note that adding this annotation to a method is not enough to guarantee that + * it is kept - either its class must be referenced elsewhere in the program, or + * the class must be annotated with this as well. + */ +@Target({ + ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, + ElementType.CONSTRUCTOR }) +public @interface UsedByReflection { + String value(); +} diff --git a/base/android/java_runtime.cc b/base/android/java_runtime.cc new file mode 100644 index 0000000..5fae49a --- /dev/null +++ b/base/android/java_runtime.cc @@ -0,0 +1,21 @@ +// 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/android/java_runtime.h" + +#include "jni/Runtime_jni.h" + +namespace base { +namespace android { + +void JavaRuntime::GetMemoryUsage(long* total_memory, long* free_memory) { + JNIEnv* env = base::android::AttachCurrentThread(); + base::android::ScopedJavaLocalRef runtime = + JNI_Runtime::Java_Runtime_getRuntime(env); + *total_memory = JNI_Runtime::Java_Runtime_totalMemory(env, runtime); + *free_memory = JNI_Runtime::Java_Runtime_freeMemory(env, runtime); +} + +} // namespace android +} // namespace base diff --git a/base/android/java_runtime.h b/base/android/java_runtime.h new file mode 100644 index 0000000..2034fb9 --- /dev/null +++ b/base/android/java_runtime.h @@ -0,0 +1,25 @@ +// 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 BASE_ANDROID_JAVA_RUNTIME_H_ +#define BASE_ANDROID_JAVA_RUNTIME_H_ + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" + +namespace base { +namespace android { + +// Wrapper class for using the java.lang.Runtime object from jni. +class BASE_EXPORT JavaRuntime { + public: + // Fills the total memory used and memory allocated for objects by the java + // heap in the current process. Returns true on success. + static void GetMemoryUsage(long* total_memory, long* free_memory); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JAVA_RUNTIME_H_ diff --git a/base/android/jni_android.cc b/base/android/jni_android.cc new file mode 100644 index 0000000..2b5869f --- /dev/null +++ b/base/android/jni_android.cc @@ -0,0 +1,320 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/jni_android.h" + +#include + +#include + +#include "base/android/build_info.h" +#include "base/android/jni_string.h" +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/jni_utils.h" +#include "base/debug/debugging_flags.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" + +namespace { +using base::android::GetClass; +using base::android::MethodID; +using base::android::ScopedJavaLocalRef; + +base::android::JniRegistrationType g_jni_registration_type = + base::android::ALL_JNI_REGISTRATION; + +JavaVM* g_jvm = NULL; +base::LazyInstance>::Leaky + g_class_loader = LAZY_INSTANCE_INITIALIZER; +jmethodID g_class_loader_load_class_method_id = 0; + +#if BUILDFLAG(ENABLE_PROFILING) && HAVE_TRACE_STACK_FRAME_POINTERS +base::LazyInstance>::Leaky + g_stack_frame_pointer = LAZY_INSTANCE_INITIALIZER; +#endif + +} // namespace + +namespace base { +namespace android { + +JniRegistrationType GetJniRegistrationType() { + return g_jni_registration_type; +} + +void SetJniRegistrationType(JniRegistrationType jni_registration_type) { + g_jni_registration_type = jni_registration_type; +} + +JNIEnv* AttachCurrentThread() { + DCHECK(g_jvm); + JNIEnv* env = NULL; + jint ret = g_jvm->AttachCurrentThread(&env, NULL); + DCHECK_EQ(JNI_OK, ret); + return env; +} + +JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name) { + DCHECK(g_jvm); + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_2; + args.name = thread_name.c_str(); + args.group = NULL; + JNIEnv* env = NULL; + jint ret = g_jvm->AttachCurrentThread(&env, &args); + DCHECK_EQ(JNI_OK, ret); + return env; +} + +void DetachFromVM() { + // Ignore the return value, if the thread is not attached, DetachCurrentThread + // will fail. But it is ok as the native thread may never be attached. + if (g_jvm) + g_jvm->DetachCurrentThread(); +} + +void InitVM(JavaVM* vm) { + DCHECK(!g_jvm || g_jvm == vm); + g_jvm = vm; +} + +bool IsVMInitialized() { + return g_jvm != NULL; +} + +void InitReplacementClassLoader(JNIEnv* env, + const JavaRef& class_loader) { + DCHECK(g_class_loader.Get().is_null()); + DCHECK(!class_loader.is_null()); + + ScopedJavaLocalRef class_loader_clazz = + GetClass(env, "java/lang/ClassLoader"); + CHECK(!ClearException(env)); + g_class_loader_load_class_method_id = + env->GetMethodID(class_loader_clazz.obj(), + "loadClass", + "(Ljava/lang/String;)Ljava/lang/Class;"); + CHECK(!ClearException(env)); + + DCHECK(env->IsInstanceOf(class_loader.obj(), class_loader_clazz.obj())); + g_class_loader.Get().Reset(class_loader); +} + +ScopedJavaLocalRef GetClass(JNIEnv* env, const char* class_name) { + jclass clazz; + if (!g_class_loader.Get().is_null()) { + // ClassLoader.loadClass expects a classname with components separated by + // dots instead of the slashes that JNIEnv::FindClass expects. The JNI + // generator generates names with slashes, so we have to replace them here. + // TODO(torne): move to an approach where we always use ClassLoader except + // for the special case of base::android::GetClassLoader(), and change the + // JNI generator to generate dot-separated names. http://crbug.com/461773 + size_t bufsize = strlen(class_name) + 1; + char dotted_name[bufsize]; + memmove(dotted_name, class_name, bufsize); + for (size_t i = 0; i < bufsize; ++i) { + if (dotted_name[i] == '/') { + dotted_name[i] = '.'; + } + } + + clazz = static_cast( + env->CallObjectMethod(g_class_loader.Get().obj(), + g_class_loader_load_class_method_id, + ConvertUTF8ToJavaString(env, dotted_name).obj())); + } else { + clazz = env->FindClass(class_name); + } + if (ClearException(env) || !clazz) { + LOG(FATAL) << "Failed to find class " << class_name; + } + return ScopedJavaLocalRef(env, clazz); +} + +jclass LazyGetClass( + JNIEnv* env, + const char* class_name, + base::subtle::AtomicWord* atomic_class_id) { + static_assert(sizeof(subtle::AtomicWord) >= sizeof(jclass), + "AtomicWord can't be smaller than jclass"); + subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_class_id); + if (value) + return reinterpret_cast(value); + ScopedJavaGlobalRef clazz; + clazz.Reset(GetClass(env, class_name)); + subtle::AtomicWord null_aw = reinterpret_cast(NULL); + subtle::AtomicWord cas_result = base::subtle::Release_CompareAndSwap( + atomic_class_id, + null_aw, + reinterpret_cast(clazz.obj())); + if (cas_result == null_aw) { + // We intentionally leak the global ref since we now storing it as a raw + // pointer in |atomic_class_id|. + return clazz.Release(); + } else { + return reinterpret_cast(cas_result); + } +} + +template +jmethodID MethodID::Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature) { + jmethodID id = type == TYPE_STATIC ? + env->GetStaticMethodID(clazz, method_name, jni_signature) : + env->GetMethodID(clazz, method_name, jni_signature); + if (base::android::ClearException(env) || !id) { + LOG(FATAL) << "Failed to find " << + (type == TYPE_STATIC ? "static " : "") << + "method " << method_name << " " << jni_signature; + } + return id; +} + +// If |atomic_method_id| set, it'll return immediately. Otherwise, it'll call +// into ::Get() above. If there's a race, it's ok since the values are the same +// (and the duplicated effort will happen only once). +template +jmethodID MethodID::LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + base::subtle::AtomicWord* atomic_method_id) { + static_assert(sizeof(subtle::AtomicWord) >= sizeof(jmethodID), + "AtomicWord can't be smaller than jMethodID"); + subtle::AtomicWord value = base::subtle::Acquire_Load(atomic_method_id); + if (value) + return reinterpret_cast(value); + jmethodID id = MethodID::Get(env, clazz, method_name, jni_signature); + base::subtle::Release_Store( + atomic_method_id, reinterpret_cast(id)); + return id; +} + +// Various template instantiations. +template jmethodID MethodID::Get( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::Get( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature); + +template jmethodID MethodID::LazyGet( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +template jmethodID MethodID::LazyGet( + JNIEnv* env, jclass clazz, const char* method_name, + const char* jni_signature, base::subtle::AtomicWord* atomic_method_id); + +bool HasException(JNIEnv* env) { + return env->ExceptionCheck() != JNI_FALSE; +} + +bool ClearException(JNIEnv* env) { + if (!HasException(env)) + return false; + env->ExceptionDescribe(); + env->ExceptionClear(); + return true; +} + +void CheckException(JNIEnv* env) { + if (!HasException(env)) + return; + + // Exception has been found, might as well tell breakpad about it. + jthrowable java_throwable = env->ExceptionOccurred(); + if (java_throwable) { + // Clear the pending exception, since a local reference is now held. + env->ExceptionDescribe(); + env->ExceptionClear(); + + // Set the exception_string in BuildInfo so that breakpad can read it. + // RVO should avoid any extra copies of the exception string. + base::android::BuildInfo::GetInstance()->SetJavaExceptionInfo( + GetJavaExceptionInfo(env, java_throwable)); + } + + // Now, feel good about it and die. + // TODO(lhchavez): Remove this hack. See b/28814913 for details. + // We're using BuildInfo's java_exception_info() instead of storing the + // exception info a few lines above to avoid extra copies. It will be + // truncated to 1024 bytes anyways. + const char* exception_string = + base::android::BuildInfo::GetInstance()->java_exception_info(); + if (exception_string) + LOG(FATAL) << exception_string; + else + LOG(FATAL) << "Unhandled exception"; +} + +std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { + ScopedJavaLocalRef throwable_clazz = + GetClass(env, "java/lang/Throwable"); + jmethodID throwable_printstacktrace = + MethodID::Get( + env, throwable_clazz.obj(), "printStackTrace", + "(Ljava/io/PrintStream;)V"); + + // Create an instance of ByteArrayOutputStream. + ScopedJavaLocalRef bytearray_output_stream_clazz = + GetClass(env, "java/io/ByteArrayOutputStream"); + jmethodID bytearray_output_stream_constructor = + MethodID::Get( + env, bytearray_output_stream_clazz.obj(), "", "()V"); + jmethodID bytearray_output_stream_tostring = + MethodID::Get( + env, bytearray_output_stream_clazz.obj(), "toString", + "()Ljava/lang/String;"); + ScopedJavaLocalRef bytearray_output_stream(env, + env->NewObject(bytearray_output_stream_clazz.obj(), + bytearray_output_stream_constructor)); + + // Create an instance of PrintStream. + ScopedJavaLocalRef printstream_clazz = + GetClass(env, "java/io/PrintStream"); + jmethodID printstream_constructor = + MethodID::Get( + env, printstream_clazz.obj(), "", + "(Ljava/io/OutputStream;)V"); + ScopedJavaLocalRef printstream(env, + env->NewObject(printstream_clazz.obj(), printstream_constructor, + bytearray_output_stream.obj())); + + // Call Throwable.printStackTrace(PrintStream) + env->CallVoidMethod(java_throwable, throwable_printstacktrace, + printstream.obj()); + + // Call ByteArrayOutputStream.toString() + ScopedJavaLocalRef exception_string( + env, static_cast( + env->CallObjectMethod(bytearray_output_stream.obj(), + bytearray_output_stream_tostring))); + + return ConvertJavaStringToUTF8(exception_string); +} + +#if BUILDFLAG(ENABLE_PROFILING) && HAVE_TRACE_STACK_FRAME_POINTERS + +JNIStackFrameSaver::JNIStackFrameSaver(void* current_fp) { + previous_fp_ = g_stack_frame_pointer.Pointer()->Get(); + g_stack_frame_pointer.Pointer()->Set(current_fp); +} + +JNIStackFrameSaver::~JNIStackFrameSaver() { + g_stack_frame_pointer.Pointer()->Set(previous_fp_); +} + +void* JNIStackFrameSaver::SavedFrame() { + return g_stack_frame_pointer.Pointer()->Get(); +} + +#endif // ENABLE_PROFILING && HAVE_TRACE_STACK_FRAME_POINTERS + +} // namespace android +} // namespace base diff --git a/base/android/jni_android.h b/base/android/jni_android.h new file mode 100644 index 0000000..de53c10 --- /dev/null +++ b/base/android/jni_android.h @@ -0,0 +1,190 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_JNI_ANDROID_H_ +#define BASE_ANDROID_JNI_ANDROID_H_ + +#include +#include + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/atomicops.h" +#include "base/base_export.h" +#include "base/compiler_specific.h" +#include "base/debug/stack_trace.h" +#include "base/macros.h" + +#if HAVE_TRACE_STACK_FRAME_POINTERS + +// When profiling is enabled (enable_profiling=true) this macro is added to +// all generated JNI stubs so that it becomes the last thing that runs before +// control goes into Java. +// +// This macro saves stack frame pointer of the current function. Saved value +// used later by JNI_LINK_SAVED_FRAME_POINTER. +#define JNI_SAVE_FRAME_POINTER \ + base::android::JNIStackFrameSaver jni_frame_saver(__builtin_frame_address(0)) + +// When profiling is enabled (enable_profiling=true) this macro is added to +// all generated JNI callbacks so that it becomes the first thing that runs +// after control returns from Java. +// +// This macro links stack frame of the current function to the stack frame +// saved by JNI_SAVE_FRAME_POINTER, allowing frame-based unwinding +// (used by the heap profiler) to produce complete traces. +#define JNI_LINK_SAVED_FRAME_POINTER \ + base::debug::ScopedStackFrameLinker jni_frame_linker( \ + __builtin_frame_address(0), \ + base::android::JNIStackFrameSaver::SavedFrame()) + +#else + +// Frame-based stack unwinding is not supported, do nothing. +#define JNI_SAVE_FRAME_POINTER +#define JNI_LINK_SAVED_FRAME_POINTER + +#endif // HAVE_TRACE_STACK_FRAME_POINTERS + +namespace base { +namespace android { + +// Used to mark symbols to be exported in a shared library's symbol table. +#define JNI_EXPORT __attribute__ ((visibility("default"))) + +// The level of JNI registration required for the current process. +enum JniRegistrationType { + // Register all native methods. + ALL_JNI_REGISTRATION, + // Register some native methods, as controlled by the jni_generator. + SELECTIVE_JNI_REGISTRATION, + // Do not register any native methods. + NO_JNI_REGISTRATION, +}; + +BASE_EXPORT JniRegistrationType GetJniRegistrationType(); + +// Set the JniRegistrationType for this process (defaults to +// ALL_JNI_REGISTRATION). This should be called in the JNI_OnLoad function +// which is called when the native library is first loaded. +BASE_EXPORT void SetJniRegistrationType( + JniRegistrationType jni_registration_type); + +// Contains the registration method information for initializing JNI bindings. +struct RegistrationMethod { + const char* name; + bool (*func)(JNIEnv* env); +}; + +// Attaches the current thread to the VM (if necessary) and return the JNIEnv*. +BASE_EXPORT JNIEnv* AttachCurrentThread(); + +// Same to AttachCurrentThread except that thread name will be set to +// |thread_name| if it is the first call. Otherwise, thread_name won't be +// changed. AttachCurrentThread() doesn't regard underlying platform thread +// name, but just resets it to "Thread-???". This function should be called +// right after new thread is created if it is important to keep thread name. +BASE_EXPORT JNIEnv* AttachCurrentThreadWithName(const std::string& thread_name); + +// Detaches the current thread from VM if it is attached. +BASE_EXPORT void DetachFromVM(); + +// Initializes the global JVM. +BASE_EXPORT void InitVM(JavaVM* vm); + +// Returns true if the global JVM has been initialized. +BASE_EXPORT bool IsVMInitialized(); + +// Initializes the global ClassLoader used by the GetClass and LazyGetClass +// methods. This is needed because JNI will use the base ClassLoader when there +// is no Java code on the stack. The base ClassLoader doesn't know about any of +// the application classes and will fail to lookup anything other than system +// classes. +BASE_EXPORT void InitReplacementClassLoader( + JNIEnv* env, + const JavaRef& class_loader); + +// Finds the class named |class_name| and returns it. +// Use this method instead of invoking directly the JNI FindClass method (to +// prevent leaking local references). +// This method triggers a fatal assertion if the class could not be found. +// Use HasClass if you need to check whether the class exists. +BASE_EXPORT ScopedJavaLocalRef GetClass(JNIEnv* env, + const char* class_name); + +// The method will initialize |atomic_class_id| to contain a global ref to the +// class. And will return that ref on subsequent calls. It's the caller's +// responsibility to release the ref when it is no longer needed. +// The caller is responsible to zero-initialize |atomic_method_id|. +// It's fine to simultaneously call this on multiple threads referencing the +// same |atomic_method_id|. +BASE_EXPORT jclass LazyGetClass( + JNIEnv* env, + const char* class_name, + base::subtle::AtomicWord* atomic_class_id); + +// This class is a wrapper for JNIEnv Get(Static)MethodID. +class BASE_EXPORT MethodID { + public: + enum Type { + TYPE_STATIC, + TYPE_INSTANCE, + }; + + // Returns the method ID for the method with the specified name and signature. + // This method triggers a fatal assertion if the method could not be found. + template + static jmethodID Get(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature); + + // The caller is responsible to zero-initialize |atomic_method_id|. + // It's fine to simultaneously call this on multiple threads referencing the + // same |atomic_method_id|. + template + static jmethodID LazyGet(JNIEnv* env, + jclass clazz, + const char* method_name, + const char* jni_signature, + base::subtle::AtomicWord* atomic_method_id); +}; + +// Returns true if an exception is pending in the provided JNIEnv*. +BASE_EXPORT bool HasException(JNIEnv* env); + +// If an exception is pending in the provided JNIEnv*, this function clears it +// and returns true. +BASE_EXPORT bool ClearException(JNIEnv* env); + +// This function will call CHECK() macro if there's any pending exception. +BASE_EXPORT void CheckException(JNIEnv* env); + +// This returns a string representation of the java stack trace. +BASE_EXPORT std::string GetJavaExceptionInfo(JNIEnv* env, + jthrowable java_throwable); + +#if HAVE_TRACE_STACK_FRAME_POINTERS + +// Saves caller's PC and stack frame in a thread-local variable. +// Implemented only when profiling is enabled (enable_profiling=true). +class BASE_EXPORT JNIStackFrameSaver { + public: + JNIStackFrameSaver(void* current_fp); + ~JNIStackFrameSaver(); + static void* SavedFrame(); + + private: + void* previous_fp_; + + DISALLOW_COPY_AND_ASSIGN(JNIStackFrameSaver); +}; + +#endif // HAVE_TRACE_STACK_FRAME_POINTERS + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_ANDROID_H_ diff --git a/base/android/jni_android_unittest.cc b/base/android/jni_android_unittest.cc new file mode 100644 index 0000000..dabd480 --- /dev/null +++ b/base/android/jni_android_unittest.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/jni_android.h" + +#include "base/at_exit.h" +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { + +base::subtle::AtomicWord g_atomic_id = 0; +int LazyMethodIDCall(JNIEnv* env, jclass clazz, int p) { + jmethodID id = base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, clazz, + "abs", + "(I)I", + &g_atomic_id); + + return env->CallStaticIntMethod(clazz, id, p); +} + +int MethodIDCall(JNIEnv* env, jclass clazz, jmethodID id, int p) { + return env->CallStaticIntMethod(clazz, id, p); +} + +} // namespace + +TEST(JNIAndroidMicrobenchmark, MethodId) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef clazz(GetClass(env, "java/lang/Math")); + base::Time start_lazy = base::Time::Now(); + int o = 0; + for (int i = 0; i < 1024; ++i) + o += LazyMethodIDCall(env, clazz.obj(), i); + base::Time end_lazy = base::Time::Now(); + + jmethodID id = reinterpret_cast(g_atomic_id); + base::Time start = base::Time::Now(); + for (int i = 0; i < 1024; ++i) + o += MethodIDCall(env, clazz.obj(), id, i); + base::Time end = base::Time::Now(); + + // On a Galaxy Nexus, results were in the range of: + // JNI LazyMethodIDCall (us) 1984 + // JNI MethodIDCall (us) 1861 + LOG(ERROR) << "JNI LazyMethodIDCall (us) " << + base::TimeDelta(end_lazy - start_lazy).InMicroseconds(); + LOG(ERROR) << "JNI MethodIDCall (us) " << + base::TimeDelta(end - start).InMicroseconds(); + LOG(ERROR) << "JNI " << o; +} + + +} // namespace android +} // namespace base diff --git a/base/android/jni_generator/android_jar.classes b/base/android/jni_generator/android_jar.classes new file mode 100644 index 0000000..7d412ce --- /dev/null +++ b/base/android/jni_generator/android_jar.classes @@ -0,0 +1,98 @@ +java/lang/AbstractMethodError.class +java/lang/AbstractStringBuilder.class +java/lang/Appendable.class +java/lang/ArithmeticException.class +java/lang/ArrayIndexOutOfBoundsException.class +java/lang/ArrayStoreException.class +java/lang/AssertionError.class +java/lang/AutoCloseable.class +java/lang/Boolean.class +java/lang/Byte.class +java/lang/Character.class +java/lang/Character$Subset.class +java/lang/Character$UnicodeBlock.class +java/lang/CharSequence.class +java/lang/ClassCastException.class +java/lang/ClassCircularityError.class +java/lang/Class.class +java/lang/ClassFormatError.class +java/lang/ClassLoader.class +java/lang/ClassNotFoundException.class +java/lang/Cloneable.class +java/lang/CloneNotSupportedException.class +java/lang/Comparable.class +java/lang/Compiler.class +java/lang/Deprecated.class +java/lang/Double.class +java/lang/Enum.class +java/lang/EnumConstantNotPresentException.class +java/lang/Error.class +java/lang/Exception.class +java/lang/ExceptionInInitializerError.class +java/lang/Float.class +java/lang/IllegalAccessError.class +java/lang/IllegalAccessException.class +java/lang/IllegalArgumentException.class +java/lang/IllegalMonitorStateException.class +java/lang/IllegalStateException.class +java/lang/IncompatibleClassChangeError.class +java/lang/IndexOutOfBoundsException.class +java/lang/InheritableThreadLocal.class +java/lang/InstantiationError.class +java/lang/InstantiationException.class +java/lang/Integer.class +java/lang/InternalError.class +java/lang/InterruptedException.class +java/lang/Iterable.class +java/lang/LinkageError.class +java/lang/Long.class +java/lang/Math.class +java/lang/NegativeArraySizeException.class +java/lang/NoClassDefFoundError.class +java/lang/NoSuchFieldError.class +java/lang/NoSuchFieldException.class +java/lang/NoSuchMethodError.class +java/lang/NoSuchMethodException.class +java/lang/NullPointerException.class +java/lang/Number.class +java/lang/NumberFormatException.class +java/lang/Object.class +java/lang/OutOfMemoryError.class +java/lang/Override.class +java/lang/Package.class +java/lang/ProcessBuilder.class +java/lang/Process.class +java/lang/Readable.class +java/lang/ReflectiveOperationException.class +java/lang/Runnable.class +java/lang/Runtime.class +java/lang/RuntimeException.class +java/lang/RuntimePermission.class +java/lang/SafeVarargs.class +java/lang/SecurityException.class +java/lang/SecurityManager.class +java/lang/Short.class +java/lang/StackOverflowError.class +java/lang/StackTraceElement.class +java/lang/StrictMath.class +java/lang/StringBuffer.class +java/lang/StringBuilder.class +java/lang/String.class +java/lang/StringIndexOutOfBoundsException.class +java/lang/SuppressWarnings.class +java/lang/System.class +java/lang/Thread.class +java/lang/ThreadDeath.class +java/lang/ThreadGroup.class +java/lang/ThreadLocal.class +java/lang/Thread$State.class +java/lang/Thread$UncaughtExceptionHandler.class +java/lang/Throwable.class +java/lang/TypeNotPresentException.class +java/lang/UnknownError.class +java/lang/UnsatisfiedLinkError.class +java/lang/UnsupportedClassVersionError.class +java/lang/UnsupportedOperationException.class +java/lang/VerifyError.class +java/lang/VirtualMachineError.class +java/lang/Void.class diff --git a/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java b/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java new file mode 100644 index 0000000..42d8e56 --- /dev/null +++ b/base/android/jni_generator/java/src/org/chromium/example/jni_generator/SampleForTests.java @@ -0,0 +1,318 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.example.jni_generator; + +import android.graphics.Rect; + +import org.chromium.base.annotations.AccessedByNative; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.CalledByNativeUnchecked; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeCall; +import org.chromium.base.annotations.NativeClassQualifiedName; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +// This class serves as a reference test for the bindings generator, and as example documentation +// for how to use the jni generator. +// The C++ counter-part is sample_for_tests.cc. +// jni_generator/BUILD.gn has a jni_generator_tests target that will: +// * Generate a header file for the JNI bindings based on this file. +// * Compile sample_for_tests.cc using the generated header file. +// * link a native executable to prove the generated header + cc file are self-contained. +// All comments are informational only, and are ignored by the jni generator. +// +// Binding C/C++ with Java is not trivial, specially when ownership and object lifetime +// semantics needs to be managed across boundaries. +// Following a few guidelines will make the code simpler and less buggy: +// +// - Never write any JNI "by hand". Rely on the bindings generator to have a thin +// layer of type-safety. +// +// - Treat the types from the other side as "opaque" as possible. Do not inspect any +// object directly, but rather, rely on well-defined getters / setters. +// +// - Minimize the surface API between the two sides, and rather than calling multiple +// functions across boundaries, call only one (and then, internally in the other side, +// call as many little functions as required). +// +// - If a Java object "owns" a native object, stash the pointer in a "long mNativeClassName". +// Note that it needs to have a "destruction path", i.e., it must eventually call a method +// to delete the native object (for example, the java object has a "close()" method that +// in turn deletes the native object). Avoid relying on finalizers: those run in a different +// thread and makes the native lifetime management more difficult. +// +// - For native object "owning" java objects: +// - If there's a strong 1:1 to relationship between native and java, the best way is to +// stash the java object into a base::android::ScopedJavaGlobalRef. This will ensure the +// java object can be GC'd once the native object is destroyed but note that this global strong +// ref implies a new GC root, so be sure it will not leak and it must never rely on being +// triggered (transitively) from a java side GC. +// - In all other cases, the native side should keep a JavaObjectWeakGlobalRef, and check whether +// that reference is still valid before de-referencing it. Note that you will need another +// java-side object to be holding a strong reference to this java object while it is in use, to +// avoid unpredictable GC of the object before native side has finished with it. +// +// - The best way to pass "compound" datatypes across in either direction is to create an inner +// class with PODs and a factory function. If possible, make it immutable (i.e., mark all the +// fields as "final"). See examples with "InnerStructB" below. +// +// - It's simpler to create thin wrappers with a well defined JNI interface than to +// expose a lot of internal details. This is specially significant for system classes where it's +// simpler to wrap factory methods and a few getters / setters than expose the entire class. +// +// - Use static factory functions annotated with @CalledByNative rather than calling the +// constructors directly. +// +// - Iterate over containers where they are originally owned, then create inner structs or +// directly call methods on the other side. It's much simpler than trying to amalgamate +// java and stl containers. +// +// An important note about qualified class name resolution: +// The generator doesn't compile the class and have little context about the +// classes being passed through the JNI layers. It adds a few simple rules: +// +// - all classes are either explicitly imported, or they are assumed to be in +// the same package. +// +// - Inner class needs to be done through an import and usage of the +// outer class, so that the generator knows how to qualify it: +// import foo.bar.Zoo; +// void call(Zoo.Inner); +// +// - implicitly imported classes aren't supported, so in order to pass +// things like Runnable, please import java.lang.Runnable; +// +// This JNINamespace annotation indicates that all native methods should be +// generated inside this namespace, including the native class that this +// object binds to. +@JNINamespace("base::android") +class SampleForTests { + // Classes can store their C++ pointer counter part as an int that is normally initialized by + // calling out a nativeInit() function. Replace "CPPClass" with your particular class name! + long mNativeCPPObject; + + // You can define methods and attributes on the java class just like any other. + // Methods without the @CalledByNative annotation won't be exposed to JNI. + public SampleForTests() { + } + + public void startExample() { + // Calls C++ Init(...) method and holds a pointer to the C++ class. + mNativeCPPObject = nativeInit("myParam"); + } + + public void doStuff() { + // This will call CPPClass::Method() using nativePtr as a pointer to the object. This must + // be done to: + // * avoid leaks. + // * using finalizers are not allowed to destroy the cpp class. + nativeMethod(mNativeCPPObject); + } + + public void finishExample() { + // We're done, so let's destroy nativePtr object. + nativeDestroy(mNativeCPPObject); + } + + // --------------------------------------------------------------------------------------------- + // The following methods demonstrate exporting Java methods for invocation from C++ code. + // Java functions are mapping into C global functions by prefixing the method name with + // "Java__" + // This is triggered by the @CalledByNative annotation; the methods may be named as you wish. + + // Exported to C++ as: + // Java_SampleForTests_javaMethod(JNIEnv* env, jobject caller, jint foo, jint bar) + // Typically the C++ code would have obtained the jobject via the Init() call described above. + @CalledByNative + public int javaMethod(int foo, int bar) { + return 0; + } + + // Exported to C++ as Java_SampleForTests_staticJavaMethod(JNIEnv* env) + // Note no jobject argument, as it is static. + @CalledByNative + public static boolean staticJavaMethod() { + return true; + } + + // No prefix, so this method is package private. It will still be exported. + @CalledByNative + void packagePrivateJavaMethod() { + } + + // Note the "Unchecked" suffix. By default, @CalledByNative will always generate bindings that + // call CheckException(). With "@CalledByNativeUnchecked", the client C++ code is responsible to + // call ClearException() and act as appropriate. + // See more details at the "@CalledByNativeUnchecked" annotation. + @CalledByNativeUnchecked + void methodThatThrowsException() throws Exception {} + + // The generator is not confused by inline comments: + // @CalledByNative void thisShouldNotAppearInTheOutput(); + // @CalledByNativeUnchecked public static void neitherShouldThis(int foo); + + /** + * The generator is not confused by block comments: + * @CalledByNative void thisShouldNotAppearInTheOutputEither(); + * @CalledByNativeUnchecked public static void andDefinitelyNotThis(int foo); + */ + + // String constants that look like comments don't confuse the generator: + private String mArrgh = "*/*"; + + private @interface SomeAnnotation {} + + // The generator is not confused by @Annotated parameters. + @CalledByNative + void javaMethodWithAnnotatedParam(@SomeAnnotation int foo) { + } + + // --------------------------------------------------------------------------------------------- + // Java fields which are accessed from C++ code only must be annotated with @AccessedByNative to + // prevent them being eliminated when unreferenced code is stripped. + @AccessedByNative + private int mJavaField; + + // --------------------------------------------------------------------------------------------- + // The following methods demonstrate declaring methods to call into C++ from Java. + // The generator detects the "native" and "static" keywords, the type and name of the first + // parameter, and the "native" prefix to the function name to determine the C++ function + // signatures. Besides these constraints the methods can be freely named. + + // This declares a C++ function which the application code must implement: + // static jint Init(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + // The implementation must return the pointer to the C++ object cast to jint. + // The caller of this method should store it, and supply it as a the nativeCPPClass param to + // subsequent native method calls (see the methods below that take an "int native..." as first + // param). + private native long nativeInit(String param); + + // This defines a function binding to the associated C++ class member function. The name is + // derived from |nativeDestroy| and |nativeCPPClass| to arrive at CPPClass::Destroy() (i.e. + // native prefixes stripped). + // + // The |nativeCPPClass| is automatically cast to type CPPClass*, in order to obtain the object + // on + // which to invoke the member function. Replace "CPPClass" with your particular class name! + private native void nativeDestroy(long nativeCPPClass); + + // This declares a C++ function which the application code must implement: + // static jdouble GetDoubleFunction(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + private native double nativeGetDoubleFunction(); + + // Similar to nativeGetDoubleFunction(), but here the C++ side will receive a jclass rather than + // jobject param, as the function is declared static. + private static native float nativeGetFloatFunction(); + + // This function takes a non-POD datatype. We have a list mapping them to their full classpath + // in jni_generator.py JavaParamToJni. If you require a new datatype, make sure you add to that + // function. + private native void nativeSetNonPODDatatype(Rect rect); + + // This declares a C++ function which the application code must implement: + // static ScopedJavaLocalRef GetNonPODDatatype(JNIEnv* env, jobject caller); + // The jobject parameter refers back to this java side object instance. + // Note that it returns a ScopedJavaLocalRef so that you don' have to worry about + // deleting the JNI local reference. This is similar with Strings and arrays. + private native Object nativeGetNonPODDatatype(); + + // Similar to nativeDestroy above, this will cast nativeCPPClass into pointer of CPPClass type + // and call its Method member function. Replace "CPPClass" with your particular class name! + private native int nativeMethod(long nativeCPPClass); + + // Similar to nativeMethod above, but here the C++ fully qualified class name is taken from the + // annotation rather than parameter name, which can thus be chosen freely. + @NativeClassQualifiedName("CPPClass::InnerClass") + private native double nativeMethodOtherP0(long nativePtr); + + // This "struct" will be created by the native side using |createInnerStructA|, + // and used by the java-side somehow. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructA { + private final long mLong; + private final int mInt; + private final String mString; + + private InnerStructA(long l, int i, String s) { + mLong = l; + mInt = i; + mString = s; + } + + @CalledByNative("InnerStructA") + private static InnerStructA create(long l, int i, String s) { + return new InnerStructA(l, i, s); + } + } + + private List mListInnerStructA = new ArrayList(); + + @CalledByNative + private void addStructA(InnerStructA a) { + // Called by the native side to append another element. + mListInnerStructA.add(a); + } + + @CalledByNative + private void iterateAndDoSomething() { + Iterator it = mListInnerStructA.iterator(); + while (it.hasNext()) { + InnerStructA element = it.next(); + // Now, do something with element. + } + // Done, clear the list. + mListInnerStructA.clear(); + } + + // This "struct" will be created by the java side passed to native, which + // will use its getters. + // Note that |@CalledByNative| has to contain the inner class name. + static class InnerStructB { + private final long mKey; + private final String mValue; + + private InnerStructB(long k, String v) { + mKey = k; + mValue = v; + } + + @CalledByNative("InnerStructB") + private long getKey() { + return mKey; + } + + @CalledByNative("InnerStructB") + private String getValue() { + return mValue; + } + } + + List mListInnerStructB = new ArrayList(); + + void iterateAndDoSomethingWithMap() { + Iterator it = mListInnerStructB.iterator(); + while (it.hasNext()) { + InnerStructB element = it.next(); + // Now, do something with element. + nativeAddStructB(mNativeCPPObject, element); + } + nativeIterateAndDoSomethingWithStructB(mNativeCPPObject); + } + + native void nativeAddStructB(long nativeCPPClass, InnerStructB b); + native void nativeIterateAndDoSomethingWithStructB(long nativeCPPClass); + native String nativeReturnAString(long nativeCPPClass); + + // This inner class shows how to annotate native methods on inner classes. + static class InnerClass { + @NativeCall("InnerClass") + private static native int nativeGetInnerIntFunction(); + } +} diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py new file mode 100755 index 0000000..99d8b42 --- /dev/null +++ b/base/android/jni_generator/jni_generator.py @@ -0,0 +1,1418 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Extracts native methods from a Java file and generates the JNI bindings. +If you change this, please run and update the tests.""" + +import collections +import errno +import optparse +import os +import re +import string +from string import Template +import subprocess +import sys +import textwrap +import zipfile + +CHROMIUM_SRC = os.path.join( + os.path.dirname(__file__), os.pardir, os.pardir, os.pardir) +BUILD_ANDROID_GYP = os.path.join( + CHROMIUM_SRC, 'build', 'android', 'gyp') + +sys.path.append(BUILD_ANDROID_GYP) + +from util import build_utils + + +class ParseError(Exception): + """Exception thrown when we can't parse the input file.""" + + def __init__(self, description, *context_lines): + Exception.__init__(self) + self.description = description + self.context_lines = context_lines + + def __str__(self): + context = '\n'.join(self.context_lines) + return '***\nERROR: %s\n\n%s\n***' % (self.description, context) + + +class Param(object): + """Describes a param for a method, either java or native.""" + + def __init__(self, **kwargs): + self.datatype = kwargs['datatype'] + self.name = kwargs['name'] + + +class NativeMethod(object): + """Describes a C/C++ method that is called by Java code""" + + def __init__(self, **kwargs): + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + if self.params: + assert type(self.params) is list + assert type(self.params[0]) is Param + if (self.params and + self.params[0].datatype == kwargs.get('ptr_type', 'int') and + self.params[0].name.startswith('native')): + self.type = 'method' + self.p0_type = self.params[0].name[len('native'):] + if kwargs.get('native_class_name'): + self.p0_type = kwargs['native_class_name'] + else: + self.type = 'function' + self.method_id_var_name = kwargs.get('method_id_var_name', None) + + +class CalledByNative(object): + """Describes a java method exported to c/c++""" + + def __init__(self, **kwargs): + self.system_class = kwargs['system_class'] + self.unchecked = kwargs['unchecked'] + self.static = kwargs['static'] + self.java_class_name = kwargs['java_class_name'] + self.return_type = kwargs['return_type'] + self.name = kwargs['name'] + self.params = kwargs['params'] + self.method_id_var_name = kwargs.get('method_id_var_name', None) + self.signature = kwargs.get('signature') + self.is_constructor = kwargs.get('is_constructor', False) + self.env_call = GetEnvCall(self.is_constructor, self.static, + self.return_type) + self.static_cast = GetStaticCastForReturnType(self.return_type) + + +class ConstantField(object): + def __init__(self, **kwargs): + self.name = kwargs['name'] + self.value = kwargs['value'] + + +def JavaDataTypeToC(java_type): + """Returns a C datatype for the given java type.""" + java_pod_type_map = { + 'int': 'jint', + 'byte': 'jbyte', + 'char': 'jchar', + 'short': 'jshort', + 'boolean': 'jboolean', + 'long': 'jlong', + 'double': 'jdouble', + 'float': 'jfloat', + } + java_type_map = { + 'void': 'void', + 'String': 'jstring', + 'Throwable': 'jthrowable', + 'java/lang/String': 'jstring', + 'java/lang/Class': 'jclass', + 'java/lang/Throwable': 'jthrowable', + } + + if java_type in java_pod_type_map: + return java_pod_type_map[java_type] + elif java_type in java_type_map: + return java_type_map[java_type] + elif java_type.endswith('[]'): + if java_type[:-2] in java_pod_type_map: + return java_pod_type_map[java_type[:-2]] + 'Array' + return 'jobjectArray' + elif java_type.startswith('Class'): + # Checking just the start of the name, rather than a direct comparison, + # in order to handle generics. + return 'jclass' + else: + return 'jobject' + + +def WrapCTypeForDeclaration(c_type): + """Wrap the C datatype in a JavaRef if required.""" + if re.match(RE_SCOPED_JNI_TYPES, c_type): + return 'const base::android::JavaParamRef<' + c_type + '>&' + else: + return c_type + + +def JavaDataTypeToCForDeclaration(java_type): + """Returns a JavaRef-wrapped C datatype for the given java type.""" + return WrapCTypeForDeclaration(JavaDataTypeToC(java_type)) + + +def JavaDataTypeToCForCalledByNativeParam(java_type): + """Returns a C datatype to be when calling from native.""" + if java_type == 'int': + return 'JniIntWrapper' + else: + c_type = JavaDataTypeToC(java_type) + if re.match(RE_SCOPED_JNI_TYPES, c_type): + return 'const base::android::JavaRefOrBare<' + c_type + '>&' + else: + return c_type + + +def JavaReturnValueToC(java_type): + """Returns a valid C return value for the given java type.""" + java_pod_type_map = { + 'int': '0', + 'byte': '0', + 'char': '0', + 'short': '0', + 'boolean': 'false', + 'long': '0', + 'double': '0', + 'float': '0', + 'void': '' + } + return java_pod_type_map.get(java_type, 'NULL') + + +class JniParams(object): + _imports = [] + _fully_qualified_class = '' + _package = '' + _inner_classes = [] + _implicit_imports = [] + + @staticmethod + def SetFullyQualifiedClass(fully_qualified_class): + JniParams._fully_qualified_class = 'L' + fully_qualified_class + JniParams._package = '/'.join(fully_qualified_class.split('/')[:-1]) + + @staticmethod + def AddAdditionalImport(class_name): + assert class_name.endswith('.class') + raw_class_name = class_name[:-len('.class')] + if '.' in raw_class_name: + raise SyntaxError('%s cannot be used in @JNIAdditionalImport. ' + 'Only import unqualified outer classes.' % class_name) + new_import = 'L%s/%s' % (JniParams._package, raw_class_name) + if new_import in JniParams._imports: + raise SyntaxError('Do not use JNIAdditionalImport on an already ' + 'imported class: %s' % (new_import.replace('/', '.'))) + JniParams._imports += [new_import] + + @staticmethod + def ExtractImportsAndInnerClasses(contents): + if not JniParams._package: + raise RuntimeError('SetFullyQualifiedClass must be called before ' + 'ExtractImportsAndInnerClasses') + contents = contents.replace('\n', '') + re_import = re.compile(r'import.*?(?P\S*?);') + for match in re.finditer(re_import, contents): + JniParams._imports += ['L' + match.group('class').replace('.', '/')] + + re_inner = re.compile(r'(class|interface)\s+?(?P\w+?)\W') + for match in re.finditer(re_inner, contents): + inner = match.group('name') + if not JniParams._fully_qualified_class.endswith(inner): + JniParams._inner_classes += [JniParams._fully_qualified_class + '$' + + inner] + + re_additional_imports = re.compile( + r'@JNIAdditionalImport\(\s*{?(?P.*?)}?\s*\)') + for match in re.finditer(re_additional_imports, contents): + for class_name in match.group('class_names').split(','): + JniParams.AddAdditionalImport(class_name.strip()) + + @staticmethod + def ParseJavaPSignature(signature_line): + prefix = 'Signature: ' + index = signature_line.find(prefix) + if index == -1: + prefix = 'descriptor: ' + index = signature_line.index(prefix) + return '"%s"' % signature_line[index + len(prefix):] + + @staticmethod + def JavaToJni(param): + """Converts a java param into a JNI signature type.""" + pod_param_map = { + 'int': 'I', + 'boolean': 'Z', + 'char': 'C', + 'short': 'S', + 'long': 'J', + 'double': 'D', + 'float': 'F', + 'byte': 'B', + 'void': 'V', + } + object_param_list = [ + 'Ljava/lang/Boolean', + 'Ljava/lang/Integer', + 'Ljava/lang/Long', + 'Ljava/lang/Object', + 'Ljava/lang/String', + 'Ljava/lang/Class', + 'Ljava/lang/CharSequence', + 'Ljava/lang/Runnable', + 'Ljava/lang/Throwable', + ] + + prefix = '' + # Array? + while param[-2:] == '[]': + prefix += '[' + param = param[:-2] + # Generic? + if '<' in param: + param = param[:param.index('<')] + if param in pod_param_map: + return prefix + pod_param_map[param] + if '/' in param: + # Coming from javap, use the fully qualified param directly. + return prefix + 'L' + param + ';' + + for qualified_name in (object_param_list + + [JniParams._fully_qualified_class] + + JniParams._inner_classes): + if (qualified_name.endswith('/' + param) or + qualified_name.endswith('$' + param.replace('.', '$')) or + qualified_name == 'L' + param): + return prefix + qualified_name + ';' + + # Is it from an import? (e.g. referecing Class from import pkg.Class; + # note that referencing an inner class Inner from import pkg.Class.Inner + # is not supported). + for qualified_name in JniParams._imports: + if qualified_name.endswith('/' + param): + # Ensure it's not an inner class. + components = qualified_name.split('/') + if len(components) > 2 and components[-2][0].isupper(): + raise SyntaxError('Inner class (%s) can not be imported ' + 'and used by JNI (%s). Please import the outer ' + 'class and use Outer.Inner instead.' % + (qualified_name, param)) + return prefix + qualified_name + ';' + + # Is it an inner class from an outer class import? (e.g. referencing + # Class.Inner from import pkg.Class). + if '.' in param: + components = param.split('.') + outer = '/'.join(components[:-1]) + inner = components[-1] + for qualified_name in JniParams._imports: + if qualified_name.endswith('/' + outer): + return (prefix + qualified_name + '$' + inner + ';') + raise SyntaxError('Inner class (%s) can not be ' + 'used directly by JNI. Please import the outer ' + 'class, probably:\n' + 'import %s.%s;' % + (param, JniParams._package.replace('/', '.'), + outer.replace('/', '.'))) + + JniParams._CheckImplicitImports(param) + + # Type not found, falling back to same package as this class. + return (prefix + 'L' + JniParams._package + '/' + param + ';') + + @staticmethod + def _CheckImplicitImports(param): + # Ensure implicit imports, such as java.lang.*, are not being treated + # as being in the same package. + if not JniParams._implicit_imports: + # This file was generated from android.jar and lists + # all classes that are implicitly imported. + with file(os.path.join(os.path.dirname(sys.argv[0]), + 'android_jar.classes'), 'r') as f: + JniParams._implicit_imports = f.readlines() + for implicit_import in JniParams._implicit_imports: + implicit_import = implicit_import.strip().replace('.class', '') + implicit_import = implicit_import.replace('/', '.') + if implicit_import.endswith('.' + param): + raise SyntaxError('Ambiguous class (%s) can not be used directly ' + 'by JNI.\nPlease import it, probably:\n\n' + 'import %s;' % + (param, implicit_import)) + + + @staticmethod + def Signature(params, returns, wrap): + """Returns the JNI signature for the given datatypes.""" + items = ['('] + items += [JniParams.JavaToJni(param.datatype) for param in params] + items += [')'] + items += [JniParams.JavaToJni(returns)] + if wrap: + return '\n' + '\n'.join(['"' + item + '"' for item in items]) + else: + return '"' + ''.join(items) + '"' + + @staticmethod + def Parse(params): + """Parses the params into a list of Param objects.""" + if not params: + return [] + ret = [] + for p in [p.strip() for p in params.split(',')]: + items = p.split(' ') + + # Remove @Annotations from parameters. + while items[0].startswith('@'): + del items[0] + + if 'final' in items: + items.remove('final') + + param = Param( + datatype=items[0], + name=(items[1] if len(items) > 1 else 'p%s' % len(ret)), + ) + ret += [param] + return ret + + +def ExtractJNINamespace(contents): + re_jni_namespace = re.compile('.*?@JNINamespace\("(.*?)"\)') + m = re.findall(re_jni_namespace, contents) + if not m: + return '' + return m[0] + + +def ExtractFullyQualifiedJavaClassName(java_file_name, contents): + re_package = re.compile('.*?package (.*?);') + matches = re.findall(re_package, contents) + if not matches: + raise SyntaxError('Unable to find "package" line in %s' % java_file_name) + return (matches[0].replace('.', '/') + '/' + + os.path.splitext(os.path.basename(java_file_name))[0]) + + +def ExtractNatives(contents, ptr_type): + """Returns a list of dict containing information about a native method.""" + contents = contents.replace('\n', '') + natives = [] + re_native = re.compile(r'(@NativeClassQualifiedName' + '\(\"(?P.*?)\"\)\s+)?' + '(@NativeCall(\(\"(?P.*?)\"\))\s+)?' + '(?P\w+\s\w+|\w+|\s+)\s*native ' + '(?P\S*) ' + '(?Pnative\w+)\((?P.*?)\);') + for match in re.finditer(re_native, contents): + native = NativeMethod( + static='static' in match.group('qualifiers'), + java_class_name=match.group('java_class_name'), + native_class_name=match.group('native_class_name'), + return_type=match.group('return_type'), + name=match.group('name').replace('native', ''), + params=JniParams.Parse(match.group('params')), + ptr_type=ptr_type) + natives += [native] + return natives + + +def IsMainDexJavaClass(contents): + """Returns "true" if the class is annotated with "@MainDex", "false" if not. + + JNI registration doesn't always need to be completed for non-browser processes + since most Java code is only used by the browser process. Classes that are + needed by non-browser processes must explicitly be annotated with @MainDex + to force JNI registration. + """ + re_maindex = re.compile(r'@MainDex[\s\S]*class\s+\w+\s*{') + found = re.search(re_maindex, contents) + return 'true' if found else 'false' + + +def GetStaticCastForReturnType(return_type): + type_map = { 'String' : 'jstring', + 'java/lang/String' : 'jstring', + 'Throwable': 'jthrowable', + 'java/lang/Throwable': 'jthrowable', + 'boolean[]': 'jbooleanArray', + 'byte[]': 'jbyteArray', + 'char[]': 'jcharArray', + 'short[]': 'jshortArray', + 'int[]': 'jintArray', + 'long[]': 'jlongArray', + 'float[]': 'jfloatArray', + 'double[]': 'jdoubleArray' } + ret = type_map.get(return_type, None) + if ret: + return ret + if return_type.endswith('[]'): + return 'jobjectArray' + return None + + +def GetEnvCall(is_constructor, is_static, return_type): + """Maps the types availabe via env->Call__Method.""" + if is_constructor: + return 'NewObject' + env_call_map = {'boolean': 'Boolean', + 'byte': 'Byte', + 'char': 'Char', + 'short': 'Short', + 'int': 'Int', + 'long': 'Long', + 'float': 'Float', + 'void': 'Void', + 'double': 'Double', + 'Object': 'Object', + } + call = env_call_map.get(return_type, 'Object') + if is_static: + call = 'Static' + call + return 'Call' + call + 'Method' + + +def GetMangledParam(datatype): + """Returns a mangled identifier for the datatype.""" + if len(datatype) <= 2: + return datatype.replace('[', 'A') + ret = '' + for i in range(1, len(datatype)): + c = datatype[i] + if c == '[': + ret += 'A' + elif c.isupper() or datatype[i - 1] in ['/', 'L']: + ret += c.upper() + return ret + + +def GetMangledMethodName(name, params, return_type): + """Returns a mangled method name for the given signature. + + The returned name can be used as a C identifier and will be unique for all + valid overloads of the same method. + + Args: + name: string. + params: list of Param. + return_type: string. + + Returns: + A mangled name. + """ + mangled_items = [] + for datatype in [return_type] + [x.datatype for x in params]: + mangled_items += [GetMangledParam(JniParams.JavaToJni(datatype))] + mangled_name = name + '_'.join(mangled_items) + assert re.match(r'[0-9a-zA-Z_]+', mangled_name) + return mangled_name + + +def MangleCalledByNatives(called_by_natives): + """Mangles all the overloads from the call_by_natives list.""" + method_counts = collections.defaultdict( + lambda: collections.defaultdict(lambda: 0)) + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + name = called_by_native.name + method_counts[java_class_name][name] += 1 + for called_by_native in called_by_natives: + java_class_name = called_by_native.java_class_name + method_name = called_by_native.name + method_id_var_name = method_name + if method_counts[java_class_name][method_name] > 1: + method_id_var_name = GetMangledMethodName(method_name, + called_by_native.params, + called_by_native.return_type) + called_by_native.method_id_var_name = method_id_var_name + return called_by_natives + + +# Regex to match the JNI types that should be wrapped in a JavaRef. +RE_SCOPED_JNI_TYPES = re.compile('jobject|jclass|jstring|jthrowable|.*Array') + + +# Regex to match a string like "@CalledByNative public void foo(int bar)". +RE_CALLED_BY_NATIVE = re.compile( + '@CalledByNative(?P(Unchecked)*?)(?:\("(?P.*)"\))?' + '\s+(?P[\w ]*?)' + '(:?\s*@\w+)?' # Ignore annotations in return types. + '\s*(?P\S+?)' + '\s+(?P\w+)' + '\s*\((?P[^\)]*)\)') + + +# Removes empty lines that are indented (i.e. start with 2x spaces). +def RemoveIndentedEmptyLines(string): + return re.sub('^(?: {2})+$\n', '', string, flags=re.MULTILINE) + + +def ExtractCalledByNatives(contents): + """Parses all methods annotated with @CalledByNative. + + Args: + contents: the contents of the java file. + + Returns: + A list of dict with information about the annotated methods. + TODO(bulach): return a CalledByNative object. + + Raises: + ParseError: if unable to parse. + """ + called_by_natives = [] + for match in re.finditer(RE_CALLED_BY_NATIVE, contents): + called_by_natives += [CalledByNative( + system_class=False, + unchecked='Unchecked' in match.group('Unchecked'), + static='static' in match.group('prefix'), + java_class_name=match.group('annotation') or '', + return_type=match.group('return_type'), + name=match.group('name'), + params=JniParams.Parse(match.group('params')))] + # Check for any @CalledByNative occurrences that weren't matched. + unmatched_lines = re.sub(RE_CALLED_BY_NATIVE, '', contents).split('\n') + for line1, line2 in zip(unmatched_lines, unmatched_lines[1:]): + if '@CalledByNative' in line1: + raise ParseError('could not parse @CalledByNative method signature', + line1, line2) + return MangleCalledByNatives(called_by_natives) + + +class JNIFromJavaP(object): + """Uses 'javap' to parse a .class file and generate the JNI header file.""" + + def __init__(self, contents, options): + self.contents = contents + self.namespace = options.namespace + for line in contents: + class_name = re.match( + '.*?(public).*?(class|interface) (?P\S+?)( |\Z)', + line) + if class_name: + self.fully_qualified_class = class_name.group('class_name') + break + self.fully_qualified_class = self.fully_qualified_class.replace('.', '/') + # Java 7's javap includes type parameters in output, like HashSet. Strip + # away the <...> and use the raw class name that Java 6 would've given us. + self.fully_qualified_class = self.fully_qualified_class.split('<', 1)[0] + JniParams.SetFullyQualifiedClass(self.fully_qualified_class) + self.java_class_name = self.fully_qualified_class.split('/')[-1] + if not self.namespace: + self.namespace = 'JNI_' + self.java_class_name + re_method = re.compile('(?P.*?)(?P\S+?) (?P\w+?)' + '\((?P.*?)\)') + self.called_by_natives = [] + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_method, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static='static' in match.group('prefix'), + java_class_name='', + return_type=match.group('return_type').replace('.', '/'), + name=match.group('name'), + params=JniParams.Parse(match.group('params').replace('.', '/')), + signature=JniParams.ParseJavaPSignature(contents[lineno + 1]))] + re_constructor = re.compile('(.*?)public ' + + self.fully_qualified_class.replace('/', '.') + + '\((?P.*?)\)') + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_constructor, content) + if not match: + continue + self.called_by_natives += [CalledByNative( + system_class=True, + unchecked=False, + static=False, + java_class_name='', + return_type=self.fully_qualified_class, + name='Constructor', + params=JniParams.Parse(match.group('params').replace('.', '/')), + signature=JniParams.ParseJavaPSignature(contents[lineno + 1]), + is_constructor=True)] + self.called_by_natives = MangleCalledByNatives(self.called_by_natives) + + self.constant_fields = [] + re_constant_field = re.compile('.*?public static final int (?P.*?);') + re_constant_field_value = re.compile( + '.*?Constant(Value| value): int (?P(-*[0-9]+)?)') + for lineno, content in enumerate(contents[2:], 2): + match = re.match(re_constant_field, content) + if not match: + continue + value = re.match(re_constant_field_value, contents[lineno + 2]) + if not value: + value = re.match(re_constant_field_value, contents[lineno + 3]) + if value: + self.constant_fields.append( + ConstantField(name=match.group('name'), + value=value.group('value'))) + + self.inl_header_file_generator = InlHeaderFileGenerator( + self.namespace, self.fully_qualified_class, [], self.called_by_natives, + self.constant_fields, options) + + def GetContent(self): + return self.inl_header_file_generator.GetContent() + + @staticmethod + def CreateFromClass(class_file, options): + class_name = os.path.splitext(os.path.basename(class_file))[0] + p = subprocess.Popen(args=[options.javap, '-c', '-verbose', + '-s', class_name], + cwd=os.path.dirname(class_file), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, _ = p.communicate() + jni_from_javap = JNIFromJavaP(stdout.split('\n'), options) + return jni_from_javap + + +class JNIFromJavaSource(object): + """Uses the given java source file to generate the JNI header file.""" + + # Match single line comments, multiline comments, character literals, and + # double-quoted strings. + _comment_remover_regex = re.compile( + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', + re.DOTALL | re.MULTILINE) + + def __init__(self, contents, fully_qualified_class, options): + contents = self._RemoveComments(contents) + JniParams.SetFullyQualifiedClass(fully_qualified_class) + JniParams.ExtractImportsAndInnerClasses(contents) + jni_namespace = ExtractJNINamespace(contents) or options.namespace + natives = ExtractNatives(contents, options.ptr_type) + called_by_natives = ExtractCalledByNatives(contents) + maindex = IsMainDexJavaClass(contents) + if len(natives) == 0 and len(called_by_natives) == 0: + raise SyntaxError('Unable to find any JNI methods for %s.' % + fully_qualified_class) + inl_header_file_generator = InlHeaderFileGenerator( + jni_namespace, fully_qualified_class, natives, called_by_natives, [], + options, maindex) + self.content = inl_header_file_generator.GetContent() + + @classmethod + def _RemoveComments(cls, contents): + # We need to support both inline and block comments, and we need to handle + # strings that contain '//' or '/*'. + # TODO(bulach): This is a bit hacky. It would be cleaner to use a real Java + # parser. Maybe we could ditch JNIFromJavaSource and just always use + # JNIFromJavaP; or maybe we could rewrite this script in Java and use APT. + # http://code.google.com/p/chromium/issues/detail?id=138941 + def replacer(match): + # Replace matches that are comments with nothing; return literals/strings + # unchanged. + s = match.group(0) + if s.startswith('/'): + return '' + else: + return s + return cls._comment_remover_regex.sub(replacer, contents) + + def GetContent(self): + return self.content + + @staticmethod + def CreateFromFile(java_file_name, options): + contents = file(java_file_name).read() + fully_qualified_class = ExtractFullyQualifiedJavaClassName(java_file_name, + contents) + return JNIFromJavaSource(contents, fully_qualified_class, options) + + +class InlHeaderFileGenerator(object): + """Generates an inline header file for JNI integration.""" + + def __init__(self, namespace, fully_qualified_class, natives, + called_by_natives, constant_fields, options, maindex='false'): + self.namespace = namespace + self.fully_qualified_class = fully_qualified_class + self.class_name = self.fully_qualified_class.split('/')[-1] + self.natives = natives + self.called_by_natives = called_by_natives + self.header_guard = fully_qualified_class.replace('/', '_') + '_JNI' + self.constant_fields = constant_fields + self.maindex = maindex + self.options = options + + + def GetContent(self): + """Returns the content of the JNI binding file.""" + template = Template("""\ +// Copyright 2014 The Chromium 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 +// ${SCRIPT_NAME} +// For +// ${FULLY_QUALIFIED_CLASS} + +#ifndef ${HEADER_GUARD} +#define ${HEADER_GUARD} + +#include + +${INCLUDES} + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +$CLASS_PATH_DEFINITIONS + +} // namespace + +$OPEN_NAMESPACE + +$CONSTANT_FIELDS + +// Step 2: method stubs. +$METHOD_STUBS + +// Step 3: RegisterNatives. +$JNI_NATIVE_METHODS +$REGISTER_NATIVES +$CLOSE_NAMESPACE + +#endif // ${HEADER_GUARD} +""") + values = { + 'SCRIPT_NAME': self.options.script_name, + 'FULLY_QUALIFIED_CLASS': self.fully_qualified_class, + 'CLASS_PATH_DEFINITIONS': self.GetClassPathDefinitionsString(), + 'CONSTANT_FIELDS': self.GetConstantFieldsString(), + 'METHOD_STUBS': self.GetMethodStubsString(), + 'OPEN_NAMESPACE': self.GetOpenNamespaceString(), + 'JNI_NATIVE_METHODS': self.GetJNINativeMethodsString(), + 'REGISTER_NATIVES': self.GetRegisterNativesString(), + 'CLOSE_NAMESPACE': self.GetCloseNamespaceString(), + 'HEADER_GUARD': self.header_guard, + 'INCLUDES': self.GetIncludesString(), + } + assert ((values['JNI_NATIVE_METHODS'] == '') == + (values['REGISTER_NATIVES'] == '')) + return WrapOutput(template.substitute(values)) + + def GetClassPathDefinitionsString(self): + ret = [] + ret += [self.GetClassPathDefinitions()] + return '\n'.join(ret) + + def GetConstantFieldsString(self): + if not self.constant_fields: + return '' + ret = ['enum Java_%s_constant_fields {' % self.class_name] + for c in self.constant_fields: + ret += [' %s = %s,' % (c.name, c.value)] + ret += ['};'] + return '\n'.join(ret) + + def GetMethodStubsString(self): + """Returns the code corresponding to method stubs.""" + ret = [] + for native in self.natives: + ret += [self.GetNativeStub(native)] + ret += self.GetLazyCalledByNativeMethodStubs() + return '\n'.join(ret) + + def GetLazyCalledByNativeMethodStubs(self): + return [self.GetLazyCalledByNativeMethodStub(called_by_native) + for called_by_native in self.called_by_natives] + + def GetIncludesString(self): + if not self.options.includes: + return '' + includes = self.options.includes.split(',') + return '\n'.join('#include "%s"' % x for x in includes) + + def GetKMethodsString(self, clazz): + ret = [] + for native in self.natives: + if (native.java_class_name == clazz or + (not native.java_class_name and clazz == self.class_name)): + ret += [self.GetKMethodArrayEntry(native)] + return '\n'.join(ret) + + def SubstituteNativeMethods(self, template): + """Substitutes JAVA_CLASS and KMETHODS in the provided template.""" + ret = [] + all_classes = self.GetUniqueClasses(self.natives) + all_classes[self.class_name] = self.fully_qualified_class + for clazz in all_classes: + kmethods = self.GetKMethodsString(clazz) + if kmethods: + values = {'JAVA_CLASS': clazz, + 'KMETHODS': kmethods} + ret += [template.substitute(values)] + if not ret: return '' + return '\n' + '\n'.join(ret) + + def GetJNINativeMethodsString(self): + """Returns the implementation of the array of native methods.""" + if not self.options.native_exports_optional: + return '' + template = Template("""\ +static const JNINativeMethod kMethods${JAVA_CLASS}[] = { +${KMETHODS} +}; +""") + return self.SubstituteNativeMethods(template) + + def GetRegisterNativesString(self): + """Returns the code for RegisterNatives.""" + natives = self.GetRegisterNativesImplString() + if not natives: + return '' + + template = Template("""\ +${REGISTER_NATIVES_SIGNATURE} { +${EARLY_EXIT} +${NATIVES} + return true; +} +""") + signature = 'static bool RegisterNativesImpl(JNIEnv* env)' + early_exit = '' + if self.options.native_exports_optional: + early_exit = """\ + if (jni_generator::ShouldSkipJniRegistration(%s)) + return true; +""" % self.maindex + + values = {'REGISTER_NATIVES_SIGNATURE': signature, + 'EARLY_EXIT': early_exit, + 'NATIVES': natives, + } + + return template.substitute(values) + + def GetRegisterNativesImplString(self): + """Returns the shared implementation for RegisterNatives.""" + if not self.options.native_exports_optional: + return '' + + template = Template("""\ + const int kMethods${JAVA_CLASS}Size = arraysize(kMethods${JAVA_CLASS}); + + if (env->RegisterNatives(${JAVA_CLASS}_clazz(env), + kMethods${JAVA_CLASS}, + kMethods${JAVA_CLASS}Size) < 0) { + jni_generator::HandleRegistrationError( + env, ${JAVA_CLASS}_clazz(env), __FILE__); + return false; + } +""") + return self.SubstituteNativeMethods(template) + + def GetOpenNamespaceString(self): + if self.namespace: + all_namespaces = ['namespace %s {' % ns + for ns in self.namespace.split('::')] + return '\n'.join(all_namespaces) + return '' + + def GetCloseNamespaceString(self): + if self.namespace: + all_namespaces = ['} // namespace %s' % ns + for ns in self.namespace.split('::')] + all_namespaces.reverse() + return '\n'.join(all_namespaces) + '\n' + return '' + + def GetJNIFirstParamType(self, native): + if native.type == 'method': + return 'jobject' + elif native.type == 'function': + if native.static: + return 'jclass' + else: + return 'jobject' + + def GetJNIFirstParam(self, native, for_declaration): + c_type = self.GetJNIFirstParamType(native) + if for_declaration: + c_type = WrapCTypeForDeclaration(c_type) + return [c_type + ' jcaller'] + + def GetParamsInDeclaration(self, native): + """Returns the params for the forward declaration. + + Args: + native: the native dictionary describing the method. + + Returns: + A string containing the params. + """ + return ',\n '.join(self.GetJNIFirstParam(native, True) + + [JavaDataTypeToCForDeclaration(param.datatype) + ' ' + + param.name + for param in native.params]) + + def GetParamsInStub(self, native): + """Returns the params for the stub declaration. + + Args: + native: the native dictionary describing the method. + + Returns: + A string containing the params. + """ + return ',\n '.join(self.GetJNIFirstParam(native, False) + + [JavaDataTypeToC(param.datatype) + ' ' + + param.name + for param in native.params]) + + def GetCalledByNativeParamsInDeclaration(self, called_by_native): + return ',\n '.join([ + JavaDataTypeToCForCalledByNativeParam(param.datatype) + ' ' + + param.name + for param in called_by_native.params]) + + def GetStubName(self, native): + """Return the name of the stub function for this native method. + + Args: + native: the native dictionary describing the method. + + Returns: + A string with the stub function name (used by the JVM). + """ + template = Template("Java_${JAVA_NAME}_native${NAME}") + + java_name = self.fully_qualified_class.replace('_', '_1').replace('/', '_') + if native.java_class_name: + java_name += '_00024' + native.java_class_name + + values = {'NAME': native.name, + 'JAVA_NAME': java_name} + return template.substitute(values) + + def GetJavaParamRefForCall(self, c_type, name): + return Template( + 'base::android::JavaParamRef<${TYPE}>(env, ${NAME})').substitute({ + 'TYPE': c_type, + 'NAME': name, + }) + + def GetJNIFirstParamForCall(self, native): + c_type = self.GetJNIFirstParamType(native) + return [self.GetJavaParamRefForCall(c_type, 'jcaller')] + + def GetNativeStub(self, native): + is_method = native.type == 'method' + + if is_method: + params = native.params[1:] + else: + params = native.params + params_in_call = ['env'] + self.GetJNIFirstParamForCall(native) + for p in params: + c_type = JavaDataTypeToC(p.datatype) + if re.match(RE_SCOPED_JNI_TYPES, c_type): + params_in_call.append(self.GetJavaParamRefForCall(c_type, p.name)) + else: + params_in_call.append(p.name) + params_in_call = ', '.join(params_in_call) + + return_type = return_declaration = JavaDataTypeToC(native.return_type) + post_call = '' + if re.match(RE_SCOPED_JNI_TYPES, return_type): + post_call = '.Release()' + return_declaration = ('base::android::ScopedJavaLocalRef<' + return_type + + '>') + profiling_entered_native = '' + if self.options.enable_profiling: + profiling_entered_native = 'JNI_LINK_SAVED_FRAME_POINTER;' + values = { + 'RETURN': return_type, + 'RETURN_DECLARATION': return_declaration, + 'NAME': native.name, + 'PARAMS': self.GetParamsInDeclaration(native), + 'PARAMS_IN_STUB': self.GetParamsInStub(native), + 'PARAMS_IN_CALL': params_in_call, + 'POST_CALL': post_call, + 'STUB_NAME': self.GetStubName(native), + 'PROFILING_ENTERED_NATIVE': profiling_entered_native, + } + + if is_method: + optional_error_return = JavaReturnValueToC(native.return_type) + if optional_error_return: + optional_error_return = ', ' + optional_error_return + values.update({ + 'OPTIONAL_ERROR_RETURN': optional_error_return, + 'PARAM0_NAME': native.params[0].name, + 'P0_TYPE': native.p0_type, + }) + template = Template("""\ +JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) { + ${PROFILING_ENTERED_NATIVE} + ${P0_TYPE}* native = reinterpret_cast<${P0_TYPE}*>(${PARAM0_NAME}); + CHECK_NATIVE_PTR(env, jcaller, native, "${NAME}"${OPTIONAL_ERROR_RETURN}); + return native->${NAME}(${PARAMS_IN_CALL})${POST_CALL}; +} +""") + else: + template = Template(""" +static ${RETURN_DECLARATION} ${NAME}(JNIEnv* env, ${PARAMS}); + +JNI_GENERATOR_EXPORT ${RETURN} ${STUB_NAME}(JNIEnv* env, ${PARAMS_IN_STUB}) { + ${PROFILING_ENTERED_NATIVE} + return ${NAME}(${PARAMS_IN_CALL})${POST_CALL}; +} +""") + + return RemoveIndentedEmptyLines(template.substitute(values)) + + def GetArgument(self, param): + if param.datatype == 'int': + return 'as_jint(' + param.name + ')' + elif re.match(RE_SCOPED_JNI_TYPES, JavaDataTypeToC(param.datatype)): + return param.name + '.obj()' + else: + return param.name + + def GetArgumentsInCall(self, params): + """Return a string of arguments to call from native into Java""" + return [self.GetArgument(p) for p in params] + + def GetCalledByNativeValues(self, called_by_native): + """Fills in necessary values for the CalledByNative methods.""" + java_class = called_by_native.java_class_name or self.class_name + if called_by_native.static or called_by_native.is_constructor: + first_param_in_declaration = '' + first_param_in_call = ('%s_clazz(env)' % java_class) + else: + first_param_in_declaration = ( + ', const base::android::JavaRefOrBare& obj') + first_param_in_call = 'obj.obj()' + params_in_declaration = self.GetCalledByNativeParamsInDeclaration( + called_by_native) + if params_in_declaration: + params_in_declaration = ', ' + params_in_declaration + params_in_call = ', '.join(self.GetArgumentsInCall(called_by_native.params)) + if params_in_call: + params_in_call = ', ' + params_in_call + pre_call = '' + post_call = '' + if called_by_native.static_cast: + pre_call = 'static_cast<%s>(' % called_by_native.static_cast + post_call = ')' + check_exception = '' + if not called_by_native.unchecked: + check_exception = 'jni_generator::CheckException(env);' + return_type = JavaDataTypeToC(called_by_native.return_type) + optional_error_return = JavaReturnValueToC(called_by_native.return_type) + if optional_error_return: + optional_error_return = ', ' + optional_error_return + return_declaration = '' + return_clause = '' + if return_type != 'void': + pre_call = ' ' + pre_call + return_declaration = return_type + ' ret =' + if re.match(RE_SCOPED_JNI_TYPES, return_type): + return_type = 'base::android::ScopedJavaLocalRef<' + return_type + '>' + return_clause = 'return ' + return_type + '(env, ret);' + else: + return_clause = 'return ret;' + profiling_leaving_native = '' + if self.options.enable_profiling: + profiling_leaving_native = 'JNI_SAVE_FRAME_POINTER;' + return { + 'JAVA_CLASS': java_class, + 'RETURN_TYPE': return_type, + 'OPTIONAL_ERROR_RETURN': optional_error_return, + 'RETURN_DECLARATION': return_declaration, + 'RETURN_CLAUSE': return_clause, + 'FIRST_PARAM_IN_DECLARATION': first_param_in_declaration, + 'PARAMS_IN_DECLARATION': params_in_declaration, + 'PRE_CALL': pre_call, + 'POST_CALL': post_call, + 'ENV_CALL': called_by_native.env_call, + 'FIRST_PARAM_IN_CALL': first_param_in_call, + 'PARAMS_IN_CALL': params_in_call, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'CHECK_EXCEPTION': check_exception, + 'GET_METHOD_ID_IMPL': self.GetMethodIDImpl(called_by_native), + 'PROFILING_LEAVING_NATIVE': profiling_leaving_native, + } + + + def GetLazyCalledByNativeMethodStub(self, called_by_native): + """Returns a string.""" + function_signature_template = Template("""\ +static ${RETURN_TYPE} Java_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}(\ +JNIEnv* env${FIRST_PARAM_IN_DECLARATION}${PARAMS_IN_DECLARATION})""") + function_header_template = Template("""\ +${FUNCTION_SIGNATURE} {""") + function_header_with_unused_template = Template("""\ +${FUNCTION_SIGNATURE} __attribute__ ((unused)); +${FUNCTION_SIGNATURE} {""") + template = Template(""" +static base::subtle::AtomicWord g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME} = 0; +${FUNCTION_HEADER} + CHECK_CLAZZ(env, ${FIRST_PARAM_IN_CALL}, + ${JAVA_CLASS}_clazz(env)${OPTIONAL_ERROR_RETURN}); + jmethodID method_id = + ${GET_METHOD_ID_IMPL} + ${PROFILING_LEAVING_NATIVE} + ${RETURN_DECLARATION} + ${PRE_CALL}env->${ENV_CALL}(${FIRST_PARAM_IN_CALL}, + method_id${PARAMS_IN_CALL})${POST_CALL}; + ${CHECK_EXCEPTION} + ${RETURN_CLAUSE} +}""") + values = self.GetCalledByNativeValues(called_by_native) + values['FUNCTION_SIGNATURE'] = ( + function_signature_template.substitute(values)) + if called_by_native.system_class: + values['FUNCTION_HEADER'] = ( + function_header_with_unused_template.substitute(values)) + else: + values['FUNCTION_HEADER'] = function_header_template.substitute(values) + return RemoveIndentedEmptyLines(template.substitute(values)) + + def GetKMethodArrayEntry(self, native): + template = Template(' { "native${NAME}", ${JNI_SIGNATURE}, ' + + 'reinterpret_cast(${STUB_NAME}) },') + values = {'NAME': native.name, + 'JNI_SIGNATURE': JniParams.Signature(native.params, + native.return_type, + True), + 'STUB_NAME': self.GetStubName(native)} + return template.substitute(values) + + def GetUniqueClasses(self, origin): + ret = {self.class_name: self.fully_qualified_class} + for entry in origin: + class_name = self.class_name + jni_class_path = self.fully_qualified_class + if entry.java_class_name: + class_name = entry.java_class_name + jni_class_path = self.fully_qualified_class + '$' + class_name + ret[class_name] = jni_class_path + return ret + + def GetClassPathDefinitions(self): + """Returns the ClassPath constants.""" + ret = [] + template = Template("""\ +const char k${JAVA_CLASS}ClassPath[] = "${JNI_CLASS_PATH}";""") + all_classes = self.GetUniqueClasses(self.called_by_natives) + if self.options.native_exports_optional: + all_classes.update(self.GetUniqueClasses(self.natives)) + + for clazz in all_classes: + values = { + 'JAVA_CLASS': clazz, + 'JNI_CLASS_PATH': all_classes[clazz], + } + ret += [template.substitute(values)] + ret += '' + + template = Template("""\ +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_${JAVA_CLASS}_clazz __attribute__((unused)) = 0; +#define ${JAVA_CLASS}_clazz(env) \ +base::android::LazyGetClass(env, k${JAVA_CLASS}ClassPath, \ +&g_${JAVA_CLASS}_clazz)""") + + for clazz in all_classes: + values = { + 'JAVA_CLASS': clazz, + } + ret += [template.substitute(values)] + + return '\n'.join(ret) + + def GetMethodIDImpl(self, called_by_native): + """Returns the implementation of GetMethodID.""" + template = Template("""\ + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_${STATIC}>( + env, ${JAVA_CLASS}_clazz(env), + "${JNI_NAME}", + ${JNI_SIGNATURE}, + &g_${JAVA_CLASS}_${METHOD_ID_VAR_NAME}); +""") + jni_name = called_by_native.name + jni_return_type = called_by_native.return_type + if called_by_native.is_constructor: + jni_name = '' + jni_return_type = 'void' + if called_by_native.signature: + signature = called_by_native.signature + else: + signature = JniParams.Signature(called_by_native.params, + jni_return_type, + True) + values = { + 'JAVA_CLASS': called_by_native.java_class_name or self.class_name, + 'JNI_NAME': jni_name, + 'METHOD_ID_VAR_NAME': called_by_native.method_id_var_name, + 'STATIC': 'STATIC' if called_by_native.static else 'INSTANCE', + 'JNI_SIGNATURE': signature, + } + return template.substitute(values) + + +def WrapOutput(output): + ret = [] + for line in output.splitlines(): + # Do not wrap lines under 80 characters or preprocessor directives. + if len(line) < 80 or line.lstrip()[:1] == '#': + stripped = line.rstrip() + if len(ret) == 0 or len(ret[-1]) or len(stripped): + ret.append(stripped) + else: + first_line_indent = ' ' * (len(line) - len(line.lstrip())) + subsequent_indent = first_line_indent + ' ' * 4 + if line.startswith('//'): + subsequent_indent = '//' + subsequent_indent + wrapper = textwrap.TextWrapper(width=80, + subsequent_indent=subsequent_indent, + break_long_words=False) + ret += [wrapped.rstrip() for wrapped in wrapper.wrap(line)] + ret += [''] + return '\n'.join(ret) + + +def ExtractJarInputFile(jar_file, input_file, out_dir): + """Extracts input file from jar and returns the filename. + + The input file is extracted to the same directory that the generated jni + headers will be placed in. This is passed as an argument to script. + + Args: + jar_file: the jar file containing the input files to extract. + input_files: the list of files to extract from the jar file. + out_dir: the name of the directories to extract to. + + Returns: + the name of extracted input file. + """ + jar_file = zipfile.ZipFile(jar_file) + + out_dir = os.path.join(out_dir, os.path.dirname(input_file)) + try: + os.makedirs(out_dir) + except OSError as e: + if e.errno != errno.EEXIST: + raise + extracted_file_name = os.path.join(out_dir, os.path.basename(input_file)) + with open(extracted_file_name, 'w') as outfile: + outfile.write(jar_file.read(input_file)) + + return extracted_file_name + + +def GenerateJNIHeader(input_file, output_file, options): + try: + if os.path.splitext(input_file)[1] == '.class': + jni_from_javap = JNIFromJavaP.CreateFromClass(input_file, options) + content = jni_from_javap.GetContent() + else: + jni_from_java_source = JNIFromJavaSource.CreateFromFile( + input_file, options) + content = jni_from_java_source.GetContent() + except ParseError, e: + print e + sys.exit(1) + if output_file: + if not os.path.exists(os.path.dirname(os.path.abspath(output_file))): + os.makedirs(os.path.dirname(os.path.abspath(output_file))) + if options.optimize_generation and os.path.exists(output_file): + with file(output_file, 'r') as f: + existing_content = f.read() + if existing_content == content: + return + with file(output_file, 'w') as f: + f.write(content) + else: + print content + + +def GetScriptName(): + script_components = os.path.abspath(sys.argv[0]).split(os.path.sep) + base_index = 0 + for idx, value in enumerate(script_components): + if value == 'base' or value == 'third_party': + base_index = idx + break + return os.sep.join(script_components[base_index:]) + + +def main(argv): + usage = """usage: %prog [OPTIONS] +This script will parse the given java source code extracting the native +declarations and print the header file to stdout (or a file). +See SampleForTests.java for more details. + """ + option_parser = optparse.OptionParser(usage=usage) + build_utils.AddDepfileOption(option_parser) + + option_parser.add_option('-j', '--jar_file', dest='jar_file', + help='Extract the list of input files from' + ' a specified jar file.' + ' Uses javap to extract the methods from a' + ' pre-compiled class. --input should point' + ' to pre-compiled Java .class files.') + option_parser.add_option('-n', dest='namespace', + help='Uses as a namespace in the generated header ' + 'instead of the javap class name, or when there is ' + 'no JNINamespace annotation in the java source.') + option_parser.add_option('--input_file', + help='Single input file name. The output file name ' + 'will be derived from it. Must be used with ' + '--output_dir.') + option_parser.add_option('--output_dir', + help='The output directory. Must be used with ' + '--input') + option_parser.add_option('--optimize_generation', type="int", + default=0, help='Whether we should optimize JNI ' + 'generation by not regenerating files if they have ' + 'not changed.') + option_parser.add_option('--script_name', default=GetScriptName(), + help='The name of this script in the generated ' + 'header.') + option_parser.add_option('--includes', + help='The comma-separated list of header files to ' + 'include in the generated header.') + option_parser.add_option('--ptr_type', default='int', + type='choice', choices=['int', 'long'], + help='The type used to represent native pointers in ' + 'Java code. For 32-bit, use int; ' + 'for 64-bit, use long.') + option_parser.add_option('--cpp', default='cpp', + help='The path to cpp command.') + option_parser.add_option('--javap', default='javap', + help='The path to javap command.') + option_parser.add_option('--native_exports_optional', action='store_true', + help='Support both explicit and native method' + 'registration.') + option_parser.add_option('--enable_profiling', action='store_true', + help='Add additional profiling instrumentation.') + options, args = option_parser.parse_args(argv) + if options.jar_file: + input_file = ExtractJarInputFile(options.jar_file, options.input_file, + options.output_dir) + elif options.input_file: + input_file = options.input_file + else: + option_parser.print_help() + print '\nError: Must specify --jar_file or --input_file.' + return 1 + output_file = None + if options.output_dir: + root_name = os.path.splitext(os.path.basename(input_file))[0] + output_file = os.path.join(options.output_dir, root_name) + '_jni.h' + GenerateJNIHeader(input_file, output_file, options) + + if options.depfile: + build_utils.WriteDepfile(options.depfile, output_file) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/base/android/jni_generator/jni_generator_helper.h b/base/android/jni_generator/jni_generator_helper.h new file mode 100644 index 0000000..3062806 --- /dev/null +++ b/base/android/jni_generator/jni_generator_helper.h @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_ +#define BASE_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_ + +#include + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/logging.h" +#include "build/build_config.h" + +// Project-specific macros used by the header files generated by +// jni_generator.py. Different projects can then specify their own +// implementation for this file. +#define CHECK_NATIVE_PTR(env, jcaller, native_ptr, method_name, ...) \ + DCHECK(native_ptr) << method_name; + +#define CHECK_CLAZZ(env, jcaller, clazz, ...) DCHECK(clazz); + +#if defined(ARCH_CPU_X86) +// Dalvik JIT generated code doesn't guarantee 16-byte stack alignment on +// x86 - use force_align_arg_pointer to realign the stack at the JNI +// boundary. crbug.com/655248 +#define JNI_GENERATOR_EXPORT \ + extern "C" __attribute__((visibility("default"), force_align_arg_pointer)) +#else +#define JNI_GENERATOR_EXPORT extern "C" __attribute__((visibility("default"))) +#endif + +namespace jni_generator { + +inline void HandleRegistrationError(JNIEnv* env, + jclass clazz, + const char* filename) { + LOG(ERROR) << "RegisterNatives failed in " << filename; +} + +inline void CheckException(JNIEnv* env) { + base::android::CheckException(env); +} + +inline bool ShouldSkipJniRegistration(bool is_maindex_class) { + switch (base::android::GetJniRegistrationType()) { + case base::android::ALL_JNI_REGISTRATION: + return false; + case base::android::NO_JNI_REGISTRATION: + // TODO(estevenson): Change this to a DCHECK. + return true; + case base::android::SELECTIVE_JNI_REGISTRATION: + return !is_maindex_class; + default: + NOTREACHED(); + return false; + } +} + +} // namespace jni_generator + +#endif // BASE_ANDROID_JNI_GENERATOR_JNI_GENERATOR_HELPER_H_ diff --git a/base/android/jni_generator/jni_generator_tests.py b/base/android/jni_generator/jni_generator_tests.py new file mode 100755 index 0000000..c0c8238 --- /dev/null +++ b/base/android/jni_generator/jni_generator_tests.py @@ -0,0 +1,1097 @@ +#!/usr/bin/env python +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Tests for jni_generator.py. + +This test suite contains various tests for the JNI generator. +It exercises the low-level parser all the way up to the +code generator and ensures the output matches a golden +file. +""" + +import difflib +import inspect +import optparse +import os +import sys +import unittest +import jni_generator +from jni_generator import CalledByNative, JniParams, NativeMethod, Param + + +SCRIPT_NAME = 'base/android/jni_generator/jni_generator.py' +INCLUDES = ( + 'base/android/jni_generator/jni_generator_helper.h' +) + +# Set this environment variable in order to regenerate the golden text +# files. +REBASELINE_ENV = 'REBASELINE' + +class TestOptions(object): + """The mock options object which is passed to the jni_generator.py script.""" + + def __init__(self): + self.namespace = None + self.script_name = SCRIPT_NAME + self.includes = INCLUDES + self.ptr_type = 'long' + self.cpp = 'cpp' + self.javap = 'javap' + self.native_exports_optional = True + self.enable_profiling = False + +class TestGenerator(unittest.TestCase): + def assertObjEquals(self, first, second): + dict_first = first.__dict__ + dict_second = second.__dict__ + self.assertEquals(dict_first.keys(), dict_second.keys()) + for key, value in dict_first.iteritems(): + if (type(value) is list and len(value) and + isinstance(type(value[0]), object)): + self.assertListEquals(value, second.__getattribute__(key)) + else: + actual = second.__getattribute__(key) + self.assertEquals(value, actual, + 'Key ' + key + ': ' + str(value) + '!=' + str(actual)) + + def assertListEquals(self, first, second): + self.assertEquals(len(first), len(second)) + for i in xrange(len(first)): + if isinstance(first[i], object): + self.assertObjEquals(first[i], second[i]) + else: + self.assertEquals(first[i], second[i]) + + def assertTextEquals(self, golden_text, generated_text): + if not self.compareText(golden_text, generated_text): + self.fail('Golden text mismatch.') + + def compareText(self, golden_text, generated_text): + def FilterText(text): + return [ + l.strip() for l in text.split('\n') + if not l.startswith('// Copyright') + ] + stripped_golden = FilterText(golden_text) + stripped_generated = FilterText(generated_text) + if stripped_golden == stripped_generated: + return True + print self.id() + for line in difflib.context_diff(stripped_golden, stripped_generated): + print line + print '\n\nGenerated' + print '=' * 80 + print generated_text + print '=' * 80 + print 'Run with:' + print 'REBASELINE=1', sys.argv[0] + print 'to regenerate the data files.' + + def _ReadGoldenFile(self, golden_file): + if not os.path.exists(golden_file): + return None + with file(golden_file, 'r') as f: + return f.read() + + def assertGoldenTextEquals(self, generated_text): + script_dir = os.path.dirname(sys.argv[0]) + # This is the caller test method. + caller = inspect.stack()[1][3] + self.assertTrue(caller.startswith('test'), + 'assertGoldenTextEquals can only be called from a ' + 'test* method, not %s' % caller) + golden_file = os.path.join(script_dir, caller + '.golden') + golden_text = self._ReadGoldenFile(golden_file) + if os.environ.get(REBASELINE_ENV): + if golden_text != generated_text: + with file(golden_file, 'w') as f: + f.write(generated_text) + return + self.assertTextEquals(golden_text, generated_text) + + def testInspectCaller(self): + def willRaise(): + # This function can only be called from a test* method. + self.assertGoldenTextEquals('') + self.assertRaises(AssertionError, willRaise) + + def testNatives(self): + test_data = """" + interface OnFrameAvailableListener {} + private native int nativeInit(); + private native void nativeDestroy(int nativeChromeBrowserProvider); + private native long nativeAddBookmark( + int nativeChromeBrowserProvider, + String url, String title, boolean isFolder, long parentId); + private static native String nativeGetDomainAndRegistry(String url); + private static native void nativeCreateHistoricalTabFromState( + byte[] state, int tab_index); + private native byte[] nativeGetStateAsByteArray(View view); + private static native String[] nativeGetAutofillProfileGUIDs(); + private native void nativeSetRecognitionResults( + int sessionId, String[] results); + private native long nativeAddBookmarkFromAPI( + int nativeChromeBrowserProvider, + String url, Long created, Boolean isBookmark, + Long date, byte[] favicon, String title, Integer visits); + native int nativeFindAll(String find); + private static native OnFrameAvailableListener nativeGetInnerClass(); + private native Bitmap nativeQueryBitmap( + int nativeChromeBrowserProvider, + String[] projection, String selection, + String[] selectionArgs, String sortOrder); + private native void nativeGotOrientation( + int nativeDataFetcherImplAndroid, + double alpha, double beta, double gamma); + private static native Throwable nativeMessWithJavaException(Throwable e); + """ + jni_generator.JniParams.SetFullyQualifiedClass( + 'org/chromium/example/jni_generator/SampleForTests') + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, name='Destroy', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='long', static=False, name='AddBookmark', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='String', + name='title'), + Param(datatype='boolean', + name='isFolder'), + Param(datatype='long', + name='parentId')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='String', static=True, + name='GetDomainAndRegistry', + params=[Param(datatype='String', + name='url')], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=True, + name='CreateHistoricalTabFromState', + params=[Param(datatype='byte[]', + name='state'), + Param(datatype='int', + name='tab_index')], + java_class_name=None, + type='function'), + NativeMethod(return_type='byte[]', static=False, + name='GetStateAsByteArray', + params=[Param(datatype='View', name='view')], + java_class_name=None, + type='function'), + NativeMethod(return_type='String[]', static=True, + name='GetAutofillProfileGUIDs', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='void', static=False, + name='SetRecognitionResults', + params=[Param(datatype='int', name='sessionId'), + Param(datatype='String[]', name='results')], + java_class_name=None, + type='function'), + NativeMethod(return_type='long', static=False, + name='AddBookmarkFromAPI', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String', + name='url'), + Param(datatype='Long', + name='created'), + Param(datatype='Boolean', + name='isBookmark'), + Param(datatype='Long', + name='date'), + Param(datatype='byte[]', + name='favicon'), + Param(datatype='String', + name='title'), + Param(datatype='Integer', + name='visits')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='int', static=False, + name='FindAll', + params=[Param(datatype='String', + name='find')], + java_class_name=None, + type='function'), + NativeMethod(return_type='OnFrameAvailableListener', static=True, + name='GetInnerClass', + params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='Bitmap', + static=False, + name='QueryBitmap', + params=[Param(datatype='int', + name='nativeChromeBrowserProvider'), + Param(datatype='String[]', + name='projection'), + Param(datatype='String', + name='selection'), + Param(datatype='String[]', + name='selectionArgs'), + Param(datatype='String', + name='sortOrder'), + ], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider'), + NativeMethod(return_type='void', static=False, + name='GotOrientation', + params=[Param(datatype='int', + name='nativeDataFetcherImplAndroid'), + Param(datatype='double', + name='alpha'), + Param(datatype='double', + name='beta'), + Param(datatype='double', + name='gamma'), + ], + java_class_name=None, + type='method', + p0_type='content::DataFetcherImplAndroid'), + NativeMethod(return_type='Throwable', static=True, + name='MessWithJavaException', + params=[Param(datatype='Throwable', name='e')], + java_class_name=None, + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testInnerClassNatives(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testInnerClassNativesMultiple(self): + test_data = """ + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyInnerClass', + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testInnerClassNativesBothInnerAndOuter(self): + test_data = """ + class MyOuterClass { + private native int nativeInit(); + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + } + """ + natives = jni_generator.ExtractNatives(test_data, 'int') + golden_natives = [ + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name=None, + type='function'), + NativeMethod(return_type='int', static=False, + name='Init', params=[], + java_class_name='MyOtherInnerClass', + type='function') + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testCalledByNatives(self): + test_data = """" + import android.graphics.Bitmap; + import android.view.View; + import java.io.InputStream; + import java.util.List; + + class InnerClass {} + + @CalledByNative + InnerClass showConfirmInfoBar(int nativeInfoBar, + String buttonOk, String buttonCancel, String title, Bitmap icon) { + InfoBar infobar = new ConfirmInfoBar(nativeInfoBar, mContext, + buttonOk, buttonCancel, + title, icon); + return infobar; + } + @CalledByNative + InnerClass showAutoLoginInfoBar(int nativeInfoBar, + String realm, String account, String args) { + AutoLoginInfoBar infobar = new AutoLoginInfoBar(nativeInfoBar, mContext, + realm, account, args); + if (infobar.displayedAccountCount() == 0) + infobar = null; + return infobar; + } + @CalledByNative("InfoBar") + void dismiss(); + @SuppressWarnings("unused") + @CalledByNative + private static boolean shouldShowAutoLogin(View view, + String realm, String account, String args) { + AccountManagerContainer accountManagerContainer = + new AccountManagerContainer((Activity)contentView.getContext(), + realm, account, args); + String[] logins = accountManagerContainer.getAccountLogins(null); + return logins.length != 0; + } + @CalledByNative + static InputStream openUrl(String url) { + return null; + } + @CalledByNative + private void activateHardwareAcceleration(final boolean activated, + final int iPid, final int iType, + final int iPrimaryID, final int iSecondaryID) { + if (!activated) { + return + } + } + @CalledByNative + public static @Status int updateStatus(@Status int status) { + return getAndUpdateStatus(status); + } + @CalledByNativeUnchecked + private void uncheckedCall(int iParam); + + @CalledByNative + public byte[] returnByteArray(); + + @CalledByNative + public boolean[] returnBooleanArray(); + + @CalledByNative + public char[] returnCharArray(); + + @CalledByNative + public short[] returnShortArray(); + + @CalledByNative + public int[] returnIntArray(); + + @CalledByNative + public long[] returnLongArray(); + + @CalledByNative + public double[] returnDoubleArray(); + + @CalledByNative + public Object[] returnObjectArray(); + + @CalledByNative + public byte[][] returnArrayOfByteArray(); + + @CalledByNative + public Bitmap.CompressFormat getCompressFormat(); + + @CalledByNative + public List getCompressFormatList(); + """ + jni_generator.JniParams.SetFullyQualifiedClass('org/chromium/Foo') + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + called_by_natives = jni_generator.ExtractCalledByNatives(test_data) + golden_called_by_natives = [ + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showConfirmInfoBar', + method_id_var_name='showConfirmInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='buttonOk'), + Param(datatype='String', name='buttonCancel'), + Param(datatype='String', name='title'), + Param(datatype='Bitmap', name='icon')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='InnerClass', + system_class=False, + static=False, + name='showAutoLoginInfoBar', + method_id_var_name='showAutoLoginInfoBar', + java_class_name='', + params=[Param(datatype='int', name='nativeInfoBar'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='dismiss', + method_id_var_name='dismiss', + java_class_name='InfoBar', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean', + system_class=False, + static=True, + name='shouldShowAutoLogin', + method_id_var_name='shouldShowAutoLogin', + java_class_name='', + params=[Param(datatype='View', name='view'), + Param(datatype='String', name='realm'), + Param(datatype='String', name='account'), + Param(datatype='String', name='args')], + env_call=('Boolean', ''), + unchecked=False, + ), + CalledByNative( + return_type='InputStream', + system_class=False, + static=True, + name='openUrl', + method_id_var_name='openUrl', + java_class_name='', + params=[Param(datatype='String', name='url')], + env_call=('Object', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='activateHardwareAcceleration', + method_id_var_name='activateHardwareAcceleration', + java_class_name='', + params=[Param(datatype='boolean', name='activated'), + Param(datatype='int', name='iPid'), + Param(datatype='int', name='iType'), + Param(datatype='int', name='iPrimaryID'), + Param(datatype='int', name='iSecondaryID'), + ], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='int', + system_class=False, + static=True, + name='updateStatus', + method_id_var_name='updateStatus', + java_class_name='', + params=[Param(datatype='int', name='status')], + env_call=('Integer', ''), + unchecked=False, + ), + CalledByNative( + return_type='void', + system_class=False, + static=False, + name='uncheckedCall', + method_id_var_name='uncheckedCall', + java_class_name='', + params=[Param(datatype='int', name='iParam')], + env_call=('Void', ''), + unchecked=True, + ), + CalledByNative( + return_type='byte[]', + system_class=False, + static=False, + name='returnByteArray', + method_id_var_name='returnByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='boolean[]', + system_class=False, + static=False, + name='returnBooleanArray', + method_id_var_name='returnBooleanArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='char[]', + system_class=False, + static=False, + name='returnCharArray', + method_id_var_name='returnCharArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='short[]', + system_class=False, + static=False, + name='returnShortArray', + method_id_var_name='returnShortArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='int[]', + system_class=False, + static=False, + name='returnIntArray', + method_id_var_name='returnIntArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='long[]', + system_class=False, + static=False, + name='returnLongArray', + method_id_var_name='returnLongArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='double[]', + system_class=False, + static=False, + name='returnDoubleArray', + method_id_var_name='returnDoubleArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Object[]', + system_class=False, + static=False, + name='returnObjectArray', + method_id_var_name='returnObjectArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='byte[][]', + system_class=False, + static=False, + name='returnArrayOfByteArray', + method_id_var_name='returnArrayOfByteArray', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='Bitmap.CompressFormat', + system_class=False, + static=False, + name='getCompressFormat', + method_id_var_name='getCompressFormat', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + CalledByNative( + return_type='List', + system_class=False, + static=False, + name='getCompressFormatList', + method_id_var_name='getCompressFormatList', + java_class_name='', + params=[], + env_call=('Void', ''), + unchecked=False, + ), + ] + self.assertListEquals(golden_called_by_natives, called_by_natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + [], called_by_natives, [], + TestOptions()) + self.assertGoldenTextEquals(h.GetContent()) + + def testCalledByNativeParseError(self): + try: + jni_generator.ExtractCalledByNatives(""" +@CalledByNative +public static int foo(); // This one is fine + +@CalledByNative +scooby doo +""") + self.fail('Expected a ParseError') + except jni_generator.ParseError, e: + self.assertEquals(('@CalledByNative', 'scooby doo'), e.context_lines) + + def testFullyQualifiedClassName(self): + contents = """ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser; + +import org.chromium.base.BuildInfo; +""" + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'org/chromium/content/browser/Foo.java', contents)) + self.assertEquals('org/chromium/content/browser/Foo', + jni_generator.ExtractFullyQualifiedJavaClassName( + 'frameworks/Foo.java', contents)) + self.assertRaises(SyntaxError, + jni_generator.ExtractFullyQualifiedJavaClassName, + 'com/foo/Bar', 'no PACKAGE line') + + def testMethodNameMangling(self): + self.assertEquals('closeV', + jni_generator.GetMangledMethodName('close', [], 'void')) + self.assertEquals('readI_AB_I_I', + jni_generator.GetMangledMethodName('read', + [Param(name='p1', + datatype='byte[]'), + Param(name='p2', + datatype='int'), + Param(name='p3', + datatype='int'),], + 'int')) + self.assertEquals('openJIIS_JLS', + jni_generator.GetMangledMethodName('open', + [Param(name='p1', + datatype='java/lang/String'),], + 'java/io/InputStream')) + + def testFromJavaPGenerics(self): + contents = """ +public abstract class java.util.HashSet extends java.util.AbstractSet + implements java.util.Set, java.lang.Cloneable, java.io.Serializable { + public void dummy(); + Signature: ()V +} +""" + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(1, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testSnippnetJavap6_7_8(self): + content_javap6 = """ +public class java.util.HashSet { +public boolean add(java.lang.Object); + Signature: (Ljava/lang/Object;)Z +} +""" + + content_javap7 = """ +public class java.util.HashSet { +public boolean add(E); + Signature: (Ljava/lang/Object;)Z +} +""" + + content_javap8 = """ +public class java.util.HashSet { + public boolean add(E); + descriptor: (Ljava/lang/Object;)Z +} +""" + + jni_from_javap6 = jni_generator.JNIFromJavaP(content_javap6.split('\n'), + TestOptions()) + jni_from_javap7 = jni_generator.JNIFromJavaP(content_javap7.split('\n'), + TestOptions()) + jni_from_javap8 = jni_generator.JNIFromJavaP(content_javap8.split('\n'), + TestOptions()) + self.assertTrue(jni_from_javap6.GetContent()) + self.assertTrue(jni_from_javap7.GetContent()) + self.assertTrue(jni_from_javap8.GetContent()) + # Ensure the javap7 is correctly parsed and uses the Signature field rather + # than the "E" parameter. + self.assertTextEquals(jni_from_javap6.GetContent(), + jni_from_javap7.GetContent()) + # Ensure the javap8 is correctly parsed and uses the descriptor field. + self.assertTextEquals(jni_from_javap7.GetContent(), + jni_from_javap8.GetContent()) + + def testFromJavaP(self): + contents = self._ReadGoldenFile(os.path.join(os.path.dirname(sys.argv[0]), + 'testInputStream.javap')) + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(10, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testConstantsFromJavaP(self): + for f in ['testMotionEvent.javap', 'testMotionEvent.javap7']: + contents = self._ReadGoldenFile(os.path.join(os.path.dirname(sys.argv[0]), + f)) + jni_from_javap = jni_generator.JNIFromJavaP(contents.split('\n'), + TestOptions()) + self.assertEquals(86, len(jni_from_javap.called_by_natives)) + self.assertGoldenTextEquals(jni_from_javap.GetContent()) + + def testREForNatives(self): + # We should not match "native SyncSetupFlow" inside the comment. + test_data = """ + /** + * Invoked when the setup process is complete so we can disconnect from the + * native-side SyncSetupFlowHandler. + */ + public void destroy() { + Log.v(TAG, "Destroying native SyncSetupFlow"); + if (mNativeSyncSetupFlow != 0) { + nativeSyncSetupEnded(mNativeSyncSetupFlow); + mNativeSyncSetupFlow = 0; + } + } + private native void nativeSyncSetupEnded( + int nativeAndroidSyncSetupFlowHandler); + """ + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'foo/bar', TestOptions()) + + def testRaisesOnNonJNIMethod(self): + test_data = """ + class MyInnerClass { + private int Foo(int p0) { + } + } + """ + self.assertRaises(SyntaxError, + jni_generator.JNIFromJavaSource, + test_data, 'foo/bar', TestOptions()) + + def testJniSelfDocumentingExample(self): + script_dir = os.path.dirname(sys.argv[0]) + content = file(os.path.join(script_dir, + 'java/src/org/chromium/example/jni_generator/SampleForTests.java') + ).read() + golden_file = os.path.join(script_dir, 'SampleForTests_jni.golden') + golden_content = file(golden_file).read() + jni_from_java = jni_generator.JNIFromJavaSource( + content, 'org/chromium/example/jni_generator/SampleForTests', + TestOptions()) + generated_text = jni_from_java.GetContent() + if not self.compareText(golden_content, generated_text): + if os.environ.get(REBASELINE_ENV): + with file(golden_file, 'w') as f: + f.write(generated_text) + return + self.fail('testJniSelfDocumentingExample') + + def testNoWrappingPreprocessorLines(self): + test_data = """ + package com.google.lookhowextremelylongiam.snarf.icankeepthisupallday; + + class ReallyLongClassNamesAreAllTheRage { + private static native int nativeTest(); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, ('com/google/lookhowextremelylongiam/snarf/' + 'icankeepthisupallday/ReallyLongClassNamesAreAllTheRage'), + TestOptions()) + jni_lines = jni_from_java.GetContent().split('\n') + line = filter(lambda line: line.lstrip().startswith('#ifndef'), + jni_lines)[0] + self.assertTrue(len(line) > 80, + ('Expected #ifndef line to be > 80 chars: ', line)) + + def testImports(self): + import_header = """ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.app; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.graphics.SurfaceTexture; +import android.os.Bundle; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import android.view.Surface; + +import java.util.ArrayList; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.content.app.ContentMain; +import org.chromium.content.browser.SandboxedProcessConnection; +import org.chromium.content.common.ISandboxedProcessCallback; +import org.chromium.content.common.ISandboxedProcessService; +import org.chromium.content.common.WillNotRaise.AnException; +import org.chromium.content.common.WillRaise.AnException; + +import static org.chromium.Bar.Zoo; + +class Foo { + public static class BookmarkNode implements Parcelable { + } + public interface PasswordListObserver { + } +} + """ + jni_generator.JniParams.SetFullyQualifiedClass( + 'org/chromium/content/app/Foo') + jni_generator.JniParams.ExtractImportsAndInnerClasses(import_header) + self.assertTrue('Lorg/chromium/content/common/ISandboxedProcessService' in + jni_generator.JniParams._imports) + self.assertTrue('Lorg/chromium/Bar/Zoo' in + jni_generator.JniParams._imports) + self.assertTrue('Lorg/chromium/content/app/Foo$BookmarkNode' in + jni_generator.JniParams._inner_classes) + self.assertTrue('Lorg/chromium/content/app/Foo$PasswordListObserver' in + jni_generator.JniParams._inner_classes) + self.assertEquals('Lorg/chromium/content/app/ContentMain$Inner;', + jni_generator.JniParams.JavaToJni('ContentMain.Inner')) + self.assertRaises(SyntaxError, + jni_generator.JniParams.JavaToJni, + 'AnException') + + def testJniParamsJavaToJni(self): + self.assertTextEquals('I', JniParams.JavaToJni('int')) + self.assertTextEquals('[B', JniParams.JavaToJni('byte[]')) + self.assertTextEquals( + '[Ljava/nio/ByteBuffer;', JniParams.JavaToJni('java/nio/ByteBuffer[]')) + + def testNativesLong(self): + test_options = TestOptions() + test_options.ptr_type = 'long' + test_data = """" + private native void nativeDestroy(long nativeChromeBrowserProvider); + """ + jni_generator.JniParams.ExtractImportsAndInnerClasses(test_data) + natives = jni_generator.ExtractNatives(test_data, test_options.ptr_type) + golden_natives = [ + NativeMethod(return_type='void', static=False, name='Destroy', + params=[Param(datatype='long', + name='nativeChromeBrowserProvider')], + java_class_name=None, + type='method', + p0_type='ChromeBrowserProvider', + ptr_type=test_options.ptr_type), + ] + self.assertListEquals(golden_natives, natives) + h = jni_generator.InlHeaderFileGenerator('', 'org/chromium/TestJni', + natives, [], [], test_options) + self.assertGoldenTextEquals(h.GetContent()) + + def testMainDexFile(self): + test_data = """ + package org.chromium.example.jni_generator; + + @MainDex + class Test { + private static native int nativeStaticMethod(long nativeTest, int arg1); + } + """ + options = TestOptions() + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/foo/Bar', options) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testNonMainDexFile(self): + test_data = """ + package org.chromium.example.jni_generator; + + class Test { + private static native int nativeStaticMethod(long nativeTest, int arg1); + } + """ + options = TestOptions() + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/foo/Bar', options) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testNativeExportsOnlyOption(self): + test_data = """ + package org.chromium.example.jni_generator; + + /** The pointer to the native Test. */ + long nativeTest; + + class Test { + private static native int nativeStaticMethod(long nativeTest, int arg1); + private native int nativeMethod(long nativeTest, int arg1); + @CalledByNative + private void testMethodWithParam(int iParam); + @CalledByNative + private String testMethodWithParamAndReturn(int iParam); + @CalledByNative + private static int testStaticMethodWithParam(int iParam); + @CalledByNative + private static double testMethodWithNoParam(); + @CalledByNative + private static String testStaticMethodWithNoParam(); + + class MyInnerClass { + @NativeCall("MyInnerClass") + private native int nativeInit(); + } + class MyOtherInnerClass { + @NativeCall("MyOtherInnerClass") + private native int nativeInit(); + } + } + """ + options = TestOptions() + options.native_exports_optional = False + jni_from_java = jni_generator.JNIFromJavaSource( + test_data, 'org/chromium/example/jni_generator/SampleForTests', options) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testOuterInnerRaises(self): + test_data = """ + package org.chromium.media; + + @CalledByNative + static int getCaptureFormatWidth(VideoCapture.CaptureFormat format) { + return format.getWidth(); + } + """ + def willRaise(): + jni_generator.JNIFromJavaSource( + test_data, + 'org/chromium/media/VideoCaptureFactory', + TestOptions()) + self.assertRaises(SyntaxError, willRaise) + + def testSingleJNIAdditionalImport(self): + test_data = """ + package org.chromium.foo; + + @JNIAdditionalImport(Bar.class) + class Foo { + + @CalledByNative + private static void calledByNative(Bar.Callback callback) { + } + + private static native void nativeDoSomething(Bar.Callback callback); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource(test_data, + 'org/chromium/foo/Foo', + TestOptions()) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + def testMultipleJNIAdditionalImport(self): + test_data = """ + package org.chromium.foo; + + @JNIAdditionalImport({Bar1.class, Bar2.class}) + class Foo { + + @CalledByNative + private static void calledByNative(Bar1.Callback callback1, + Bar2.Callback callback2) { + } + + private static native void nativeDoSomething(Bar1.Callback callback1, + Bar2.Callback callback2); + } + """ + jni_from_java = jni_generator.JNIFromJavaSource(test_data, + 'org/chromium/foo/Foo', + TestOptions()) + self.assertGoldenTextEquals(jni_from_java.GetContent()) + + +def TouchStamp(stamp_path): + dir_name = os.path.dirname(stamp_path) + if not os.path.isdir(dir_name): + os.makedirs(dir_name) + + with open(stamp_path, 'a'): + os.utime(stamp_path, None) + + +def main(argv): + parser = optparse.OptionParser() + parser.add_option('--stamp', help='Path to touch on success.') + options, _ = parser.parse_args(argv[1:]) + + test_result = unittest.main(argv=argv[0:1], exit=False) + + if test_result.result.wasSuccessful() and options.stamp: + TouchStamp(options.stamp) + + return not test_result.result.wasSuccessful() + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/base/android/jni_generator/sample_for_tests.cc b/base/android/jni_generator/sample_for_tests.cc new file mode 100644 index 0000000..42b2143 --- /dev/null +++ b/base/android/jni_generator/sample_for_tests.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +#include "base/android/jni_generator/sample_for_tests.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +// Generated file for JNI bindings from C++ to Java @CalledByNative methods. +// Only to be included in one .cc file. +// Name is based on the java file name: *.java -> jni/*_jni.h +#include "jni/SampleForTests_jni.h" // Generated by JNI. + +using base::android::AttachCurrentThread; +using base::android::ConvertJavaStringToUTF8; +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaLocalRef; + +namespace base { +namespace android { + +jdouble CPPClass::InnerClass::MethodOtherP0( + JNIEnv* env, + const JavaParamRef& caller) { + return 0.0; +} + +CPPClass::CPPClass() { +} + +CPPClass::~CPPClass() { +} + +// static +bool CPPClass::RegisterJNI(JNIEnv* env) { + return RegisterNativesImpl(env); // Generated in SampleForTests_jni.h +} + +void CPPClass::Destroy(JNIEnv* env, const JavaParamRef& caller) { + delete this; +} + +jint CPPClass::Method(JNIEnv* env, const JavaParamRef& caller) { + return 0; +} + +void CPPClass::AddStructB(JNIEnv* env, + const JavaParamRef& caller, + const JavaParamRef& structb) { + long key = Java_InnerStructB_getKey(env, structb); + std::string value = + ConvertJavaStringToUTF8(env, Java_InnerStructB_getValue(env, structb)); + map_[key] = value; +} + +void CPPClass::IterateAndDoSomethingWithStructB( + JNIEnv* env, + const JavaParamRef& caller) { + // Iterate over the elements and do something with them. + for (std::map::const_iterator it = map_.begin(); + it != map_.end(); ++it) { + long key = it->first; + std::string value = it->second; + std::cout << key << value; + } + map_.clear(); +} + +ScopedJavaLocalRef CPPClass::ReturnAString( + JNIEnv* env, + const JavaParamRef& caller) { + return ConvertUTF8ToJavaString(env, "test"); +} + +// Static free functions declared and called directly from java. +static jlong Init(JNIEnv* env, + const JavaParamRef& caller, + const JavaParamRef& param) { + return 0; +} + +static jdouble GetDoubleFunction(JNIEnv*, const JavaParamRef&) { + return 0; +} + +static jfloat GetFloatFunction(JNIEnv*, const JavaParamRef&) { + return 0; +} + +static void SetNonPODDatatype(JNIEnv*, + const JavaParamRef&, + const JavaParamRef&) {} + +static ScopedJavaLocalRef GetNonPODDatatype( + JNIEnv*, + const JavaParamRef&) { + return ScopedJavaLocalRef(); +} + +static jint GetInnerIntFunction(JNIEnv*, const JavaParamRef&) { + return 0; +} + +} // namespace android +} // namespace base + +int main() { + // On a regular application, you'd call AttachCurrentThread(). This sample is + // not yet linking with all the libraries. + JNIEnv* env = /* AttachCurrentThread() */ NULL; + + // This is how you call a java static method from C++. + bool foo = base::android::Java_SampleForTests_staticJavaMethod(env); + + // This is how you call a java method from C++. Note that you must have + // obtained the jobject somehow. + ScopedJavaLocalRef my_java_object; + int bar = base::android::Java_SampleForTests_javaMethod( + env, my_java_object, 1, 2); + + std::cout << foo << bar; + + for (int i = 0; i < 10; ++i) { + // Creates a "struct" that will then be used by the java side. + ScopedJavaLocalRef struct_a = + base::android::Java_InnerStructA_create( + env, 0, 1, ConvertUTF8ToJavaString(env, "test")); + base::android::Java_SampleForTests_addStructA(env, my_java_object, + struct_a); + } + base::android::Java_SampleForTests_iterateAndDoSomething(env, my_java_object); + base::android::Java_SampleForTests_packagePrivateJavaMethod(env, + my_java_object); + base::android::Java_SampleForTests_methodThatThrowsException(env, + my_java_object); + base::android::Java_SampleForTests_javaMethodWithAnnotatedParam( + env, my_java_object, 42); + return 0; +} diff --git a/base/android/jni_generator/sample_for_tests.h b/base/android/jni_generator/sample_for_tests.h new file mode 100644 index 0000000..a9cf7b0 --- /dev/null +++ b/base/android/jni_generator/sample_for_tests.h @@ -0,0 +1,126 @@ +// Copyright (c) 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 BASE_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_ +#define BASE_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_ + +#include +#include +#include + +#include "base/android/jni_android.h" + +namespace base { +namespace android { + +// This file is used to: +// - document the best practices and guidelines on JNI usage. +// - ensure sample_for_tests_jni.h compiles and the functions declared in it +// as expected. +// +// Methods are called directly from Java (except RegisterJNI). More +// documentation in SampleForTests.java +// +// For C++ to access Java methods: +// - GN Build must be configured to generate bindings: +// # Add import at top of file: +// if (is_android) { +// import("//build/config/android/rules.gni") # For generate_jni(). +// } +// # ... +// # An example target that will rely on JNI: +// component("foo") { +// # ... normal sources, defines, deps. +// # For each jni generated .java -> .h header file in jni_headers +// # target there will be a single .cc file here that includes it. +// # +// # Add a dep for JNI: +// if (is_android) { +// deps += [ ":foo_jni" ] +// } +// } +// # ... +// # Create target for JNI: +// if (is_android) { +// generate_jni("jni_headers") { +// sources = [ +// "java/src/org/chromium/example/jni_generator/SampleForTests.java", +// ] +// jni_package = "foo" +// } +// android_library("java") { +// java_files = [ +// "java/src/org/chromium/example/jni_generator/SampleForTests.java", +// "java/src/org/chromium/example/jni_generator/NonJniFile.java", +// ] +// } +// } +// +// For C++ methods to be exposed to Java: +// - The generated RegisterNativesImpl method must be called, this is typically +// done by having a static RegisterJNI method in the C++ class. +// - The RegisterJNI method is added to a module's collection of register +// methods, such as: example_jni_registrar.h/cc files which call +// base::android::RegisterNativeMethods. +// An example_jni_registstrar.cc: +// +// namespace { +// const base::android::RegistrationMethod kRegisteredMethods[] = { +// // Initial string is for debugging only. +// { "ExampleName", base::ExampleNameAndroid::RegisterJNI }, +// { "ExampleName2", base::ExampleName2Android::RegisterJNI }, +// }; +// } // namespace +// +// bool RegisterModuleNameJni(JNIEnv* env) { +// return RegisterNativeMethods(env, kRegisteredMethods, +// arraysize(kRegisteredMethods)); +// } +// +// - Each module's RegisterModuleNameJni must be called by a larger module, +// or application during startup. +// +class CPPClass { + public: + CPPClass(); + ~CPPClass(); + + // Register C++ methods exposed to Java using JNI. + static bool RegisterJNI(JNIEnv* env); + + // Java @CalledByNative methods implicitly available to C++ via the _jni.h + // file included in the .cc file. + + class InnerClass { + public: + jdouble MethodOtherP0(JNIEnv* env, + const base::android::JavaParamRef& caller); + }; + + void Destroy(JNIEnv* env, const base::android::JavaParamRef& caller); + + jint Method(JNIEnv* env, const base::android::JavaParamRef& caller); + + void AddStructB(JNIEnv* env, + const base::android::JavaParamRef& caller, + const base::android::JavaParamRef& structb); + + void IterateAndDoSomethingWithStructB( + JNIEnv* env, + const base::android::JavaParamRef& caller); + + base::android::ScopedJavaLocalRef ReturnAString( + JNIEnv* env, + const base::android::JavaParamRef& caller); + + private: + std::map map_; + + DISALLOW_COPY_AND_ASSIGN(CPPClass); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_GENERATOR_SAMPLE_FOR_TESTS_H_ diff --git a/base/android/jni_generator/testCalledByNatives.golden b/base/android/jni_generator/testCalledByNatives.golden new file mode 100644 index 0000000..ac86b2e --- /dev/null +++ b/base/android/jni_generator/testCalledByNatives.golden @@ -0,0 +1,497 @@ +// Copyright 2014 The Chromium 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 +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kInfoBarClassPath[] = "org/chromium/TestJni$InfoBar"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_InfoBar_clazz __attribute__((unused)) = 0; +#define InfoBar_clazz(env) base::android::LazyGetClass(env, kInfoBarClassPath, &g_InfoBar_clazz) + +} // namespace + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_TestJni_showConfirmInfoBar = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_showConfirmInfoBar(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper nativeInfoBar, + const base::android::JavaRefOrBare& buttonOk, + const base::android::JavaRefOrBare& buttonCancel, + const base::android::JavaRefOrBare& title, + const base::android::JavaRefOrBare& icon) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "showConfirmInfoBar", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Landroid/graphics/Bitmap;" +")" +"Lorg/chromium/Foo$InnerClass;", + &g_TestJni_showConfirmInfoBar); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id, as_jint(nativeInfoBar), buttonOk.obj(), buttonCancel.obj(), + title.obj(), icon.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_showAutoLoginInfoBar = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_showAutoLoginInfoBar(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper nativeInfoBar, + const base::android::JavaRefOrBare& realm, + const base::android::JavaRefOrBare& account, + const base::android::JavaRefOrBare& args) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "showAutoLoginInfoBar", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Lorg/chromium/Foo$InnerClass;", + &g_TestJni_showAutoLoginInfoBar); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id, as_jint(nativeInfoBar), realm.obj(), account.obj(), + args.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_InfoBar_dismiss = 0; +static void Java_InfoBar_dismiss(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InfoBar_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InfoBar_clazz(env), + "dismiss", +"(" +")" +"V", + &g_InfoBar_dismiss); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_TestJni_shouldShowAutoLogin = 0; +static jboolean Java_TestJni_shouldShowAutoLogin(JNIEnv* env, const + base::android::JavaRefOrBare& view, + const base::android::JavaRefOrBare& realm, + const base::android::JavaRefOrBare& account, + const base::android::JavaRefOrBare& args) { + CHECK_CLAZZ(env, TestJni_clazz(env), + TestJni_clazz(env), false); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, TestJni_clazz(env), + "shouldShowAutoLogin", +"(" +"Landroid/view/View;" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Z", + &g_TestJni_shouldShowAutoLogin); + + jboolean ret = + env->CallStaticBooleanMethod(TestJni_clazz(env), + method_id, view.obj(), realm.obj(), account.obj(), args.obj()); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_TestJni_openUrl = 0; +static base::android::ScopedJavaLocalRef Java_TestJni_openUrl(JNIEnv* + env, const base::android::JavaRefOrBare& url) { + CHECK_CLAZZ(env, TestJni_clazz(env), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, TestJni_clazz(env), + "openUrl", +"(" +"Ljava/lang/String;" +")" +"Ljava/io/InputStream;", + &g_TestJni_openUrl); + + jobject ret = + env->CallStaticObjectMethod(TestJni_clazz(env), + method_id, url.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_activateHardwareAcceleration = 0; +static void Java_TestJni_activateHardwareAcceleration(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jboolean activated, + JniIntWrapper iPid, + JniIntWrapper iType, + JniIntWrapper iPrimaryID, + JniIntWrapper iSecondaryID) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "activateHardwareAcceleration", +"(" +"Z" +"I" +"I" +"I" +"I" +")" +"V", + &g_TestJni_activateHardwareAcceleration); + + env->CallVoidMethod(obj.obj(), + method_id, activated, as_jint(iPid), as_jint(iType), + as_jint(iPrimaryID), as_jint(iSecondaryID)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_TestJni_updateStatus = 0; +static jint Java_TestJni_updateStatus(JNIEnv* env, JniIntWrapper status) { + CHECK_CLAZZ(env, TestJni_clazz(env), + TestJni_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, TestJni_clazz(env), + "updateStatus", +"(" +"I" +")" +"I", + &g_TestJni_updateStatus); + + jint ret = + env->CallStaticIntMethod(TestJni_clazz(env), + method_id, as_jint(status)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_TestJni_uncheckedCall = 0; +static void Java_TestJni_uncheckedCall(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper iParam) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "uncheckedCall", +"(" +"I" +")" +"V", + &g_TestJni_uncheckedCall); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(iParam)); +} + +static base::subtle::AtomicWord g_TestJni_returnByteArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnByteArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnByteArray", +"(" +")" +"[B", + &g_TestJni_returnByteArray); + + jbyteArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnBooleanArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnBooleanArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnBooleanArray", +"(" +")" +"[Z", + &g_TestJni_returnBooleanArray); + + jbooleanArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnCharArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnCharArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnCharArray", +"(" +")" +"[C", + &g_TestJni_returnCharArray); + + jcharArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnShortArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnShortArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnShortArray", +"(" +")" +"[S", + &g_TestJni_returnShortArray); + + jshortArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnIntArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnIntArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnIntArray", +"(" +")" +"[I", + &g_TestJni_returnIntArray); + + jintArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnLongArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnLongArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnLongArray", +"(" +")" +"[J", + &g_TestJni_returnLongArray); + + jlongArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnDoubleArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnDoubleArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnDoubleArray", +"(" +")" +"[D", + &g_TestJni_returnDoubleArray); + + jdoubleArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnObjectArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnObjectArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnObjectArray", +"(" +")" +"[Ljava/lang/Object;", + &g_TestJni_returnObjectArray); + + jobjectArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_returnArrayOfByteArray = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_returnArrayOfByteArray(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "returnArrayOfByteArray", +"(" +")" +"[[B", + &g_TestJni_returnArrayOfByteArray); + + jobjectArray ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_getCompressFormat = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_getCompressFormat(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "getCompressFormat", +"(" +")" +"Landroid/graphics/Bitmap$CompressFormat;", + &g_TestJni_getCompressFormat); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_TestJni_getCompressFormatList = 0; +static base::android::ScopedJavaLocalRef + Java_TestJni_getCompressFormatList(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + TestJni_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, TestJni_clazz(env), + "getCompressFormatList", +"(" +")" +"Ljava/util/List;", + &g_TestJni_getCompressFormatList); + + jobject ret = + env->CallObjectMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +// Step 3: RegisterNatives. + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testConstantsFromJavaP.golden b/base/android/jni_generator/testConstantsFromJavaP.golden new file mode 100644 index 0000000..b16956f --- /dev/null +++ b/base/android/jni_generator/testConstantsFromJavaP.golden @@ -0,0 +1,2195 @@ +// Copyright 2014 The Chromium 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 +// base/android/jni_generator/jni_generator.py +// For +// android/view/MotionEvent + +#ifndef android_view_MotionEvent_JNI +#define android_view_MotionEvent_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kMotionEventClassPath[] = "android/view/MotionEvent"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MotionEvent_clazz __attribute__((unused)) = 0; +#define MotionEvent_clazz(env) base::android::LazyGetClass(env, kMotionEventClassPath, &g_MotionEvent_clazz) + +} // namespace + +namespace JNI_MotionEvent { + +enum Java_MotionEvent_constant_fields { + INVALID_POINTER_ID = -1, + ACTION_MASK = 255, + ACTION_DOWN = 0, + ACTION_UP = 1, + ACTION_MOVE = 2, + ACTION_CANCEL = 3, + ACTION_OUTSIDE = 4, + ACTION_POINTER_DOWN = 5, + ACTION_POINTER_UP = 6, + ACTION_HOVER_MOVE = 7, + ACTION_SCROLL = 8, + ACTION_HOVER_ENTER = 9, + ACTION_HOVER_EXIT = 10, + ACTION_POINTER_INDEX_MASK = 65280, + ACTION_POINTER_INDEX_SHIFT = 8, + ACTION_POINTER_1_DOWN = 5, + ACTION_POINTER_2_DOWN = 261, + ACTION_POINTER_3_DOWN = 517, + ACTION_POINTER_1_UP = 6, + ACTION_POINTER_2_UP = 262, + ACTION_POINTER_3_UP = 518, + ACTION_POINTER_ID_MASK = 65280, + ACTION_POINTER_ID_SHIFT = 8, + FLAG_WINDOW_IS_OBSCURED = 1, + EDGE_TOP = 1, + EDGE_BOTTOM = 2, + EDGE_LEFT = 4, + EDGE_RIGHT = 8, + AXIS_X = 0, + AXIS_Y = 1, + AXIS_PRESSURE = 2, + AXIS_SIZE = 3, + AXIS_TOUCH_MAJOR = 4, + AXIS_TOUCH_MINOR = 5, + AXIS_TOOL_MAJOR = 6, + AXIS_TOOL_MINOR = 7, + AXIS_ORIENTATION = 8, + AXIS_VSCROLL = 9, + AXIS_HSCROLL = 10, + AXIS_Z = 11, + AXIS_RX = 12, + AXIS_RY = 13, + AXIS_RZ = 14, + AXIS_HAT_X = 15, + AXIS_HAT_Y = 16, + AXIS_LTRIGGER = 17, + AXIS_RTRIGGER = 18, + AXIS_THROTTLE = 19, + AXIS_RUDDER = 20, + AXIS_WHEEL = 21, + AXIS_GAS = 22, + AXIS_BRAKE = 23, + AXIS_DISTANCE = 24, + AXIS_TILT = 25, + AXIS_GENERIC_1 = 32, + AXIS_GENERIC_2 = 33, + AXIS_GENERIC_3 = 34, + AXIS_GENERIC_4 = 35, + AXIS_GENERIC_5 = 36, + AXIS_GENERIC_6 = 37, + AXIS_GENERIC_7 = 38, + AXIS_GENERIC_8 = 39, + AXIS_GENERIC_9 = 40, + AXIS_GENERIC_10 = 41, + AXIS_GENERIC_11 = 42, + AXIS_GENERIC_12 = 43, + AXIS_GENERIC_13 = 44, + AXIS_GENERIC_14 = 45, + AXIS_GENERIC_15 = 46, + AXIS_GENERIC_16 = 47, + BUTTON_PRIMARY = 1, + BUTTON_SECONDARY = 2, + BUTTON_TERTIARY = 4, + BUTTON_BACK = 8, + BUTTON_FORWARD = 16, + TOOL_TYPE_UNKNOWN = 0, + TOOL_TYPE_FINGER = 1, + TOOL_TYPE_STYLUS = 2, + TOOL_TYPE_MOUSE = 3, + TOOL_TYPE_ERASER = 4, +}; + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_MotionEvent_finalize = 0; +static void Java_MotionEvent_finalize(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_MotionEvent_finalize(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "finalize", + "()V", + &g_MotionEvent_finalize); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord + g_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* + env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRefOrBare& p4, + const base::android::JavaRefOrBare& p5, + JniIntWrapper p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12, + JniIntWrapper p13) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I(JNIEnv* + env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRefOrBare& p4, + const base::android::JavaRefOrBare& p5, + JniIntWrapper p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12, + JniIntWrapper p13) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", +"(JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_I_LAVMEPP_LAVMEPC_I_I_F_F_I_I_I_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4.obj(), p5.obj(), + as_jint(p6), as_jint(p7), p8, p9, as_jint(p10), as_jint(p11), + as_jint(p12), as_jint(p13)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord + g_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, + jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRefOrBare& p4, + const base::android::JavaRefOrBare& p5, + JniIntWrapper p6, + jfloat p7, + jfloat p8, + JniIntWrapper p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I(JNIEnv* env, + jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + const base::android::JavaRefOrBare& p4, + const base::android::JavaRefOrBare& p5, + JniIntWrapper p6, + jfloat p7, + jfloat p8, + JniIntWrapper p9, + JniIntWrapper p10, + JniIntWrapper p11, + JniIntWrapper p12) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", +"(JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_I_AI_LAVMEPC_I_F_F_I_I_I_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4.obj(), p5.obj(), + as_jint(p6), p7, p8, as_jint(p9), as_jint(p10), as_jint(p11), + as_jint(p12)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I + = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + jfloat p5, + jfloat p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + jfloat p5, + jfloat p6, + JniIntWrapper p7, + jfloat p8, + jfloat p9, + JniIntWrapper p10, + JniIntWrapper p11) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", + "(JJIFFFFIFFII)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_F_F_F_F_I_F_F_I_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), p3, p4, p5, p6, as_jint(p7), p8, p9, + as_jint(p10), as_jint(p11)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord + g_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + jfloat p4, + jfloat p5, + jfloat p6, + jfloat p7, + JniIntWrapper p8, + jfloat p9, + jfloat p10, + JniIntWrapper p11, + JniIntWrapper p12) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + JniIntWrapper p3, + jfloat p4, + jfloat p5, + jfloat p6, + jfloat p7, + JniIntWrapper p8, + jfloat p9, + jfloat p10, + JniIntWrapper p11, + JniIntWrapper p12) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", + "(JJIIFFFFIFFII)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_I_F_F_F_F_I_F_F_I_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), as_jint(p3), p4, p5, p6, p7, + as_jint(p8), p9, p10, as_jint(p11), as_jint(p12)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_obtainAVME_J_J_I_F_F_I = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_J_J_I_F_F_I(JNIEnv* env, jlong p0, + jlong p1, + JniIntWrapper p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", + "(JJIFFI)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_J_J_I_F_F_I); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0, p1, as_jint(p2), p3, p4, as_jint(p5)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_obtainAVME_AVME = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainAVME_AVME(JNIEnv* env, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtain", + "(Landroid/view/MotionEvent;)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainAVME_AVME); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_obtainNoHistory = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainNoHistory(JNIEnv* env, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_obtainNoHistory(JNIEnv* env, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "obtainNoHistory", + "(Landroid/view/MotionEvent;)Landroid/view/MotionEvent;", + &g_MotionEvent_obtainNoHistory); + + jobject ret = + env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_recycle = 0; +static void Java_MotionEvent_recycle(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_MotionEvent_recycle(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "recycle", + "()V", + &g_MotionEvent_recycle); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getDeviceId = 0; +static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getDeviceId(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getDeviceId", + "()I", + &g_MotionEvent_getDeviceId); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getSource = 0; +static jint Java_MotionEvent_getSource(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getSource(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getSource", + "()I", + &g_MotionEvent_getSource); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_setSource = 0; +static void Java_MotionEvent_setSource(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static void Java_MotionEvent_setSource(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "setSource", + "(I)V", + &g_MotionEvent_setSource); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getAction = 0; +static jint Java_MotionEvent_getAction(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getAction(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getAction", + "()I", + &g_MotionEvent_getAction); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getActionMasked = 0; +static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getActionMasked(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getActionMasked", + "()I", + &g_MotionEvent_getActionMasked); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getActionIndex = 0; +static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getActionIndex(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getActionIndex", + "()I", + &g_MotionEvent_getActionIndex); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getFlags = 0; +static jint Java_MotionEvent_getFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getFlags", + "()I", + &g_MotionEvent_getFlags); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getDownTime = 0; +static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jlong Java_MotionEvent_getDownTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getDownTime", + "()J", + &g_MotionEvent_getDownTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getEventTime = 0; +static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jlong Java_MotionEvent_getEventTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getEventTime", + "()J", + &g_MotionEvent_getEventTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getXF = 0; +static jfloat Java_MotionEvent_getXF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getX", + "()F", + &g_MotionEvent_getXF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getYF = 0; +static jfloat Java_MotionEvent_getYF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getY", + "()F", + &g_MotionEvent_getYF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPressureF = 0; +static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getPressureF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPressure", + "()F", + &g_MotionEvent_getPressureF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getSizeF = 0; +static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getSizeF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getSize", + "()F", + &g_MotionEvent_getSizeF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getTouchMajorF = 0; +static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMajorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getTouchMajor", + "()F", + &g_MotionEvent_getTouchMajorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getTouchMinorF = 0; +static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getTouchMinorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getTouchMinor", + "()F", + &g_MotionEvent_getTouchMinorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolMajorF = 0; +static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMajorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolMajor", + "()F", + &g_MotionEvent_getToolMajorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolMinorF = 0; +static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getToolMinorF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolMinor", + "()F", + &g_MotionEvent_getToolMinorF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getOrientationF = 0; +static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getOrientationF(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getOrientation", + "()F", + &g_MotionEvent_getOrientationF); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getAxisValueF_I = 0; +static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getAxisValueF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getAxisValue", + "(I)F", + &g_MotionEvent_getAxisValueF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPointerCount = 0; +static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getPointerCount(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPointerCount", + "()I", + &g_MotionEvent_getPointerCount); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPointerId = 0; +static jint Java_MotionEvent_getPointerId(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jint Java_MotionEvent_getPointerId(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPointerId", + "(I)I", + &g_MotionEvent_getPointerId); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolType = 0; +static jint Java_MotionEvent_getToolType(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jint Java_MotionEvent_getToolType(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolType", + "(I)I", + &g_MotionEvent_getToolType); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_findPointerIndex = 0; +static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jint Java_MotionEvent_findPointerIndex(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "findPointerIndex", + "(I)I", + &g_MotionEvent_findPointerIndex); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getXF_I = 0; +static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getXF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getX", + "(I)F", + &g_MotionEvent_getXF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getYF_I = 0; +static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getYF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getY", + "(I)F", + &g_MotionEvent_getYF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPressureF_I = 0; +static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getPressureF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPressure", + "(I)F", + &g_MotionEvent_getPressureF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getSizeF_I = 0; +static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getSizeF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getSize", + "(I)F", + &g_MotionEvent_getSizeF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getTouchMajorF_I = 0; +static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getTouchMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getTouchMajor", + "(I)F", + &g_MotionEvent_getTouchMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getTouchMinorF_I = 0; +static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getTouchMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getTouchMinor", + "(I)F", + &g_MotionEvent_getTouchMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolMajorF_I = 0; +static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getToolMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolMajor", + "(I)F", + &g_MotionEvent_getToolMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getToolMinorF_I = 0; +static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getToolMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getToolMinor", + "(I)F", + &g_MotionEvent_getToolMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getOrientationF_I = 0; +static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getOrientationF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getOrientation", + "(I)F", + &g_MotionEvent_getOrientationF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getAxisValueF_I_I = 0; +static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getAxisValue", + "(II)F", + &g_MotionEvent_getAxisValueF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getPointerCoords = 0; +static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + const base::android::JavaRefOrBare& p1) __attribute__ ((unused)); +static void Java_MotionEvent_getPointerCoords(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + const base::android::JavaRefOrBare& p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPointerCoords", + "(ILandroid/view/MotionEvent$PointerCoords;)V", + &g_MotionEvent_getPointerCoords); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), p1.obj()); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getPointerProperties = 0; +static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + const base::android::JavaRefOrBare& p1) __attribute__ ((unused)); +static void Java_MotionEvent_getPointerProperties(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + const base::android::JavaRefOrBare& p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getPointerProperties", + "(ILandroid/view/MotionEvent$PointerProperties;)V", + &g_MotionEvent_getPointerProperties); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), p1.obj()); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getMetaState = 0; +static jint Java_MotionEvent_getMetaState(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getMetaState(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getMetaState", + "()I", + &g_MotionEvent_getMetaState); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getButtonState = 0; +static jint Java_MotionEvent_getButtonState(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getButtonState(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getButtonState", + "()I", + &g_MotionEvent_getButtonState); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getRawX = 0; +static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getRawX(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getRawX", + "()F", + &g_MotionEvent_getRawX); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getRawY = 0; +static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getRawY(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getRawY", + "()F", + &g_MotionEvent_getRawY); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getXPrecision = 0; +static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getXPrecision(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getXPrecision", + "()F", + &g_MotionEvent_getXPrecision); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getYPrecision = 0; +static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getYPrecision(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getYPrecision", + "()F", + &g_MotionEvent_getYPrecision); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistorySize = 0; +static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getHistorySize(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistorySize", + "()I", + &g_MotionEvent_getHistorySize); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalEventTime = 0; +static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jlong Java_MotionEvent_getHistoricalEventTime(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalEventTime", + "(I)J", + &g_MotionEvent_getHistoricalEventTime); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalXF_I = 0; +static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalXF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalX", + "(I)F", + &g_MotionEvent_getHistoricalXF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalYF_I = 0; +static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalYF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalY", + "(I)F", + &g_MotionEvent_getHistoricalYF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalPressureF_I = 0; +static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalPressureF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalPressure", + "(I)F", + &g_MotionEvent_getHistoricalPressureF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalSizeF_I = 0; +static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalSizeF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalSize", + "(I)F", + &g_MotionEvent_getHistoricalSizeF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMajorF_I = 0; +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalTouchMajor", + "(I)F", + &g_MotionEvent_getHistoricalTouchMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMinorF_I = 0; +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalTouchMinor", + "(I)F", + &g_MotionEvent_getHistoricalTouchMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMajorF_I = 0; +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalToolMajor", + "(I)F", + &g_MotionEvent_getHistoricalToolMajorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMinorF_I = 0; +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalToolMinor", + "(I)F", + &g_MotionEvent_getHistoricalToolMinorF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalOrientationF_I = 0; +static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static jfloat Java_MotionEvent_getHistoricalOrientationF_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalOrientation", + "(I)F", + &g_MotionEvent_getHistoricalOrientationF_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalAxisValueF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalAxisValue", + "(II)F", + &g_MotionEvent_getHistoricalAxisValueF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalXF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalXF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalX", + "(II)F", + &g_MotionEvent_getHistoricalXF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalYF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalYF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalY", + "(II)F", + &g_MotionEvent_getHistoricalYF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalPressureF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalPressureF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalPressure", + "(II)F", + &g_MotionEvent_getHistoricalPressureF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalSizeF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalSizeF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalSize", + "(II)F", + &g_MotionEvent_getHistoricalSizeF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMajorF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMajorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalTouchMajor", + "(II)F", + &g_MotionEvent_getHistoricalTouchMajorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalTouchMinorF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalTouchMinorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalTouchMinor", + "(II)F", + &g_MotionEvent_getHistoricalTouchMinorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMajorF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMajorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalToolMajor", + "(II)F", + &g_MotionEvent_getHistoricalToolMajorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalToolMinorF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalToolMinorF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalToolMinor", + "(II)F", + &g_MotionEvent_getHistoricalToolMinorF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalOrientationF_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalOrientationF_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalOrientation", + "(II)F", + &g_MotionEvent_getHistoricalOrientationF_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalAxisValueF_I_I_I = 0; +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1, + JniIntWrapper p2) __attribute__ ((unused)); +static jfloat Java_MotionEvent_getHistoricalAxisValueF_I_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalAxisValue", + "(III)F", + &g_MotionEvent_getHistoricalAxisValueF_I_I_I); + + jfloat ret = + env->CallFloatMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1), as_jint(p2)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_getHistoricalPointerCoords = 0; +static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1, + const base::android::JavaRefOrBare& p2) __attribute__ ((unused)); +static void Java_MotionEvent_getHistoricalPointerCoords(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0, + JniIntWrapper p1, + const base::android::JavaRefOrBare& p2) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getHistoricalPointerCoords", + "(IILandroid/view/MotionEvent$PointerCoords;)V", + &g_MotionEvent_getHistoricalPointerCoords); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0), as_jint(p1), p2.obj()); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_getEdgeFlags = 0; +static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_MotionEvent_getEdgeFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "getEdgeFlags", + "()I", + &g_MotionEvent_getEdgeFlags); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_setEdgeFlags = 0; +static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static void Java_MotionEvent_setEdgeFlags(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "setEdgeFlags", + "(I)V", + &g_MotionEvent_setEdgeFlags); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_setAction = 0; +static void Java_MotionEvent_setAction(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static void Java_MotionEvent_setAction(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "setAction", + "(I)V", + &g_MotionEvent_setAction); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_offsetLocation = 0; +static void Java_MotionEvent_offsetLocation(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jfloat p0, + jfloat p1) __attribute__ ((unused)); +static void Java_MotionEvent_offsetLocation(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jfloat p0, + jfloat p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "offsetLocation", + "(FF)V", + &g_MotionEvent_offsetLocation); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_setLocation = 0; +static void Java_MotionEvent_setLocation(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jfloat p0, + jfloat p1) __attribute__ ((unused)); +static void Java_MotionEvent_setLocation(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jfloat p0, + jfloat p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "setLocation", + "(FF)V", + &g_MotionEvent_setLocation); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_transform = 0; +static void Java_MotionEvent_transform(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static void Java_MotionEvent_transform(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "transform", + "(Landroid/graphics/Matrix;)V", + &g_MotionEvent_transform); + + env->CallVoidMethod(obj.obj(), + method_id, p0.obj()); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_addBatchV_J_F_F_F_F_I = 0; +static void Java_MotionEvent_addBatchV_J_F_F_F_F_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0, + jfloat p1, + jfloat p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) __attribute__ ((unused)); +static void Java_MotionEvent_addBatchV_J_F_F_F_F_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0, + jfloat p1, + jfloat p2, + jfloat p3, + jfloat p4, + JniIntWrapper p5) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "addBatch", + "(JFFFFI)V", + &g_MotionEvent_addBatchV_J_F_F_F_F_I); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1, p2, p3, p4, as_jint(p5)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_addBatchV_J_LAVMEPC_I = 0; +static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0, + const base::android::JavaRefOrBare& p1, + JniIntWrapper p2) __attribute__ ((unused)); +static void Java_MotionEvent_addBatchV_J_LAVMEPC_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0, + const base::android::JavaRefOrBare& p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "addBatch", + "(J[Landroid/view/MotionEvent$PointerCoords;I)V", + &g_MotionEvent_addBatchV_J_LAVMEPC_I); + + env->CallVoidMethod(obj.obj(), + method_id, p0, p1.obj(), as_jint(p2)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_MotionEvent_toString = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_toString(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_toString(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "toString", + "()Ljava/lang/String;", + &g_MotionEvent_toString); + + jstring ret = + static_cast(env->CallObjectMethod(obj.obj(), + method_id)); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_actionToString = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_actionToString(JNIEnv* env, JniIntWrapper p0) __attribute__ + ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_actionToString(JNIEnv* env, JniIntWrapper p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "actionToString", + "(I)Ljava/lang/String;", + &g_MotionEvent_actionToString); + + jstring ret = + static_cast(env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, as_jint(p0))); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_axisToString = 0; +static base::android::ScopedJavaLocalRef + Java_MotionEvent_axisToString(JNIEnv* env, JniIntWrapper p0) __attribute__ + ((unused)); +static base::android::ScopedJavaLocalRef + Java_MotionEvent_axisToString(JNIEnv* env, JniIntWrapper p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "axisToString", + "(I)Ljava/lang/String;", + &g_MotionEvent_axisToString); + + jstring ret = + static_cast(env->CallStaticObjectMethod(MotionEvent_clazz(env), + method_id, as_jint(p0))); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +static base::subtle::AtomicWord g_MotionEvent_axisFromString = 0; +static jint Java_MotionEvent_axisFromString(JNIEnv* env, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static jint Java_MotionEvent_axisFromString(JNIEnv* env, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, MotionEvent_clazz(env), + MotionEvent_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, MotionEvent_clazz(env), + "axisFromString", + "(Ljava/lang/String;)I", + &g_MotionEvent_axisFromString); + + jint ret = + env->CallStaticIntMethod(MotionEvent_clazz(env), + method_id, p0.obj()); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_MotionEvent_writeToParcel = 0; +static void Java_MotionEvent_writeToParcel(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0, + JniIntWrapper p1) __attribute__ ((unused)); +static void Java_MotionEvent_writeToParcel(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0, + JniIntWrapper p1) { + CHECK_CLAZZ(env, obj.obj(), + MotionEvent_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, MotionEvent_clazz(env), + "writeToParcel", + "(Landroid/os/Parcel;I)V", + &g_MotionEvent_writeToParcel); + + env->CallVoidMethod(obj.obj(), + method_id, p0.obj(), as_jint(p1)); + jni_generator::CheckException(env); +} + +// Step 3: RegisterNatives. + +} // namespace JNI_MotionEvent + +#endif // android_view_MotionEvent_JNI diff --git a/base/android/jni_generator/testFromJavaP.golden b/base/android/jni_generator/testFromJavaP.golden new file mode 100644 index 0000000..18a9430 --- /dev/null +++ b/base/android/jni_generator/testFromJavaP.golden @@ -0,0 +1,260 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// java/io/InputStream + +#ifndef java_io_InputStream_JNI +#define java_io_InputStream_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kInputStreamClassPath[] = "java/io/InputStream"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_InputStream_clazz __attribute__((unused)) = 0; +#define InputStream_clazz(env) base::android::LazyGetClass(env, kInputStreamClassPath, &g_InputStream_clazz) + +} // namespace + +namespace JNI_InputStream { + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_InputStream_available = 0; +static jint Java_InputStream_available(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_InputStream_available(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "available", + "()I", + &g_InputStream_available); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_close = 0; +static void Java_InputStream_close(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_InputStream_close(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "close", + "()V", + &g_InputStream_close); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_InputStream_mark = 0; +static void Java_InputStream_mark(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) __attribute__ + ((unused)); +static void Java_InputStream_mark(JNIEnv* env, const + base::android::JavaRefOrBare& obj, JniIntWrapper p0) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "mark", + "(I)V", + &g_InputStream_mark); + + env->CallVoidMethod(obj.obj(), + method_id, as_jint(p0)); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_InputStream_markSupported = 0; +static jboolean Java_InputStream_markSupported(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jboolean Java_InputStream_markSupported(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), false); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "markSupported", + "()Z", + &g_InputStream_markSupported); + + jboolean ret = + env->CallBooleanMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI = 0; +static jint Java_InputStream_readI(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static jint Java_InputStream_readI(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "read", + "()I", + &g_InputStream_readI); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI_AB = 0; +static jint Java_InputStream_readI_AB(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "read", + "([B)I", + &g_InputStream_readI_AB); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, p0.obj()); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_readI_AB_I_I = 0; +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0, + JniIntWrapper p1, + JniIntWrapper p2) __attribute__ ((unused)); +static jint Java_InputStream_readI_AB_I_I(JNIEnv* env, const + base::android::JavaRefOrBare& obj, const + base::android::JavaRefOrBare& p0, + JniIntWrapper p1, + JniIntWrapper p2) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "read", + "([BII)I", + &g_InputStream_readI_AB_I_I); + + jint ret = + env->CallIntMethod(obj.obj(), + method_id, p0.obj(), as_jint(p1), as_jint(p2)); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_reset = 0; +static void Java_InputStream_reset(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_InputStream_reset(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "reset", + "()V", + &g_InputStream_reset); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +static base::subtle::AtomicWord g_InputStream_skip = 0; +static jlong Java_InputStream_skip(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0) __attribute__ + ((unused)); +static jlong Java_InputStream_skip(JNIEnv* env, const + base::android::JavaRefOrBare& obj, jlong p0) { + CHECK_CLAZZ(env, obj.obj(), + InputStream_clazz(env), 0); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "skip", + "(J)J", + &g_InputStream_skip); + + jlong ret = + env->CallLongMethod(obj.obj(), + method_id, p0); + jni_generator::CheckException(env); + return ret; +} + +static base::subtle::AtomicWord g_InputStream_Constructor = 0; +static base::android::ScopedJavaLocalRef + Java_InputStream_Constructor(JNIEnv* env) __attribute__ ((unused)); +static base::android::ScopedJavaLocalRef + Java_InputStream_Constructor(JNIEnv* env) { + CHECK_CLAZZ(env, InputStream_clazz(env), + InputStream_clazz(env), NULL); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, InputStream_clazz(env), + "", + "()V", + &g_InputStream_Constructor); + + jobject ret = + env->NewObject(InputStream_clazz(env), + method_id); + jni_generator::CheckException(env); + return base::android::ScopedJavaLocalRef(env, ret); +} + +// Step 3: RegisterNatives. + +} // namespace JNI_InputStream + +#endif // java_io_InputStream_JNI diff --git a/base/android/jni_generator/testFromJavaPGenerics.golden b/base/android/jni_generator/testFromJavaPGenerics.golden new file mode 100644 index 0000000..c076c39 --- /dev/null +++ b/base/android/jni_generator/testFromJavaPGenerics.golden @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// java/util/HashSet + +#ifndef java_util_HashSet_JNI +#define java_util_HashSet_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kHashSetClassPath[] = "java/util/HashSet"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_HashSet_clazz __attribute__((unused)) = 0; +#define HashSet_clazz(env) base::android::LazyGetClass(env, kHashSetClassPath, &g_HashSet_clazz) + +} // namespace + +namespace JNI_HashSet { + +// Step 2: method stubs. + +static base::subtle::AtomicWord g_HashSet_dummy = 0; +static void Java_HashSet_dummy(JNIEnv* env, const + base::android::JavaRefOrBare& obj) __attribute__ ((unused)); +static void Java_HashSet_dummy(JNIEnv* env, const + base::android::JavaRefOrBare& obj) { + CHECK_CLAZZ(env, obj.obj(), + HashSet_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_INSTANCE>( + env, HashSet_clazz(env), + "dummy", + "()V", + &g_HashSet_dummy); + + env->CallVoidMethod(obj.obj(), + method_id); + jni_generator::CheckException(env); +} + +// Step 3: RegisterNatives. + +} // namespace JNI_HashSet + +#endif // java_util_HashSet_JNI diff --git a/base/android/jni_generator/testInnerClassNatives.golden b/base/android/jni_generator/testInnerClassNatives.golden new file mode 100644 index 0000000..20b8830 --- /dev/null +++ b/base/android/jni_generator/testInnerClassNatives.golden @@ -0,0 +1,71 @@ +// Copyright 2014 The Chromium 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 +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MyInnerClass_clazz __attribute__((unused)) = 0; +#define MyInnerClass_clazz(env) base::android::LazyGetClass(env, kMyInnerClassClassPath, &g_MyInnerClass_clazz) + +} // namespace + +// Step 2: method stubs. + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(JNIEnv* env, jobject + jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", + reinterpret_cast(Java_org_chromium_TestJni_00024MyInnerClass_nativeInit) + }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); + + if (env->RegisterNatives(MyInnerClass_clazz(env), + kMethodsMyInnerClass, + kMethodsMyInnerClassSize) < 0) { + jni_generator::HandleRegistrationError( + env, MyInnerClass_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden new file mode 100644 index 0000000..67352e7 --- /dev/null +++ b/base/android/jni_generator/testInnerClassNativesBothInnerAndOuter.golden @@ -0,0 +1,98 @@ +// Copyright 2014 The Chromium 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 +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kMyOtherInnerClassClassPath[] = + "org/chromium/TestJni$MyOtherInnerClass"; +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MyOtherInnerClass_clazz __attribute__((unused)) = 0; +#define MyOtherInnerClass_clazz(env) base::android::LazyGetClass(env, kMyOtherInnerClassClassPath, &g_MyOtherInnerClass_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) + +} // namespace + +// Step 2: method stubs. + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(JNIEnv* env, + jobject jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(JNIEnv* env, + jobject jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", + reinterpret_cast(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) + }, +}; + +static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast(Java_org_chromium_TestJni_nativeInit) }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsMyOtherInnerClassSize = + arraysize(kMethodsMyOtherInnerClass); + + if (env->RegisterNatives(MyOtherInnerClass_clazz(env), + kMethodsMyOtherInnerClass, + kMethodsMyOtherInnerClassSize) < 0) { + jni_generator::HandleRegistrationError( + env, MyOtherInnerClass_clazz(env), __FILE__); + return false; + } + + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(TestJni_clazz(env), + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + jni_generator::HandleRegistrationError( + env, TestJni_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testInnerClassNativesMultiple.golden b/base/android/jni_generator/testInnerClassNativesMultiple.golden new file mode 100644 index 0000000..7807efa --- /dev/null +++ b/base/android/jni_generator/testInnerClassNativesMultiple.golden @@ -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. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kMyOtherInnerClassClassPath[] = + "org/chromium/TestJni$MyOtherInnerClass"; +const char kTestJniClassPath[] = "org/chromium/TestJni"; +const char kMyInnerClassClassPath[] = "org/chromium/TestJni$MyInnerClass"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MyOtherInnerClass_clazz __attribute__((unused)) = 0; +#define MyOtherInnerClass_clazz(env) base::android::LazyGetClass(env, kMyOtherInnerClassClassPath, &g_MyOtherInnerClass_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_MyInnerClass_clazz __attribute__((unused)) = 0; +#define MyInnerClass_clazz(env) base::android::LazyGetClass(env, kMyInnerClassClassPath, &g_MyInnerClass_clazz) + +} // namespace + +// Step 2: method stubs. + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_TestJni_00024MyInnerClass_nativeInit(JNIEnv* env, jobject + jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint + Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit(JNIEnv* env, + jobject jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsMyOtherInnerClass[] = { + { "nativeInit", +"(" +")" +"I", + reinterpret_cast(Java_org_chromium_TestJni_00024MyOtherInnerClass_nativeInit) + }, +}; + +static const JNINativeMethod kMethodsMyInnerClass[] = { + { "nativeInit", +"(" +")" +"I", + reinterpret_cast(Java_org_chromium_TestJni_00024MyInnerClass_nativeInit) + }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsMyOtherInnerClassSize = + arraysize(kMethodsMyOtherInnerClass); + + if (env->RegisterNatives(MyOtherInnerClass_clazz(env), + kMethodsMyOtherInnerClass, + kMethodsMyOtherInnerClassSize) < 0) { + jni_generator::HandleRegistrationError( + env, MyOtherInnerClass_clazz(env), __FILE__); + return false; + } + + const int kMethodsMyInnerClassSize = arraysize(kMethodsMyInnerClass); + + if (env->RegisterNatives(MyInnerClass_clazz(env), + kMethodsMyInnerClass, + kMethodsMyInnerClassSize) < 0) { + jni_generator::HandleRegistrationError( + env, MyInnerClass_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testInputStream.javap b/base/android/jni_generator/testInputStream.javap new file mode 100644 index 0000000..50ab617 --- /dev/null +++ b/base/android/jni_generator/testInputStream.javap @@ -0,0 +1,228 @@ +Compiled from "InputStream.java" +public abstract class java.io.InputStream extends java.lang.Object implements java.io.Closeable + SourceFile: "InputStream.java" + minor version: 0 + major version: 49 + Constant pool: +const #1 = Method #6.#39; // java/lang/Object."":()V +const #2 = class #40; // java/lang/RuntimeException +const #3 = String #41; // Stub! +const #4 = Method #2.#42; // java/lang/RuntimeException."":(Ljava/lang/String;)V +const #5 = class #43; // java/io/InputStream +const #6 = class #44; // java/lang/Object +const #7 = class #45; // java/io/Closeable +const #8 = Asciz ; +const #9 = Asciz ()V; +const #10 = Asciz Code; +const #11 = Asciz LineNumberTable; +const #12 = Asciz LocalVariableTable; +const #13 = Asciz this; +const #14 = Asciz Ljava/io/InputStream;; +const #15 = Asciz available; +const #16 = Asciz ()I; +const #17 = Asciz Exceptions; +const #18 = class #46; // java/io/IOException +const #19 = Asciz close; +const #20 = Asciz mark; +const #21 = Asciz (I)V; +const #22 = Asciz readlimit; +const #23 = Asciz I; +const #24 = Asciz markSupported; +const #25 = Asciz ()Z; +const #26 = Asciz read; +const #27 = Asciz ([B)I; +const #28 = Asciz buffer; +const #29 = Asciz [B; +const #30 = Asciz ([BII)I; +const #31 = Asciz byteOffset; +const #32 = Asciz byteCount; +const #33 = Asciz reset; +const #34 = Asciz skip; +const #35 = Asciz (J)J; +const #36 = Asciz J; +const #37 = Asciz SourceFile; +const #38 = Asciz InputStream.java; +const #39 = NameAndType #8:#9;// "":()V +const #40 = Asciz java/lang/RuntimeException; +const #41 = Asciz Stub!; +const #42 = NameAndType #8:#47;// "":(Ljava/lang/String;)V +const #43 = Asciz java/io/InputStream; +const #44 = Asciz java/lang/Object; +const #45 = Asciz java/io/Closeable; +const #46 = Asciz java/io/IOException; +const #47 = Asciz (Ljava/lang/String;)V; + +{ +public java.io.InputStream(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: aload_0 + 1: invokespecial #1; //Method java/lang/Object."":()V + 4: new #2; //class java/lang/RuntimeException + 7: dup + 8: ldc #3; //String Stub! + 10: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 5: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Ljava/io/InputStream; + + +public int available() throws java.io.IOException; + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 6: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public void close() throws java.io.IOException; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 7: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public void mark(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 8: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 readlimit I + + +public boolean markSupported(); + Signature: ()Z + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 9: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + +public abstract int read() throws java.io.IOException; + Signature: ()I + Exceptions: + throws java.io.IOException +public int read(byte[]) throws java.io.IOException; + Signature: ([B)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 11: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 buffer [B + + Exceptions: + throws java.io.IOException +public int read(byte[], int, int) throws java.io.IOException; + Signature: ([BII)I + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 12: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 buffer [B + 0 10 2 byteOffset I + 0 10 3 byteCount I + + Exceptions: + throws java.io.IOException +public synchronized void reset() throws java.io.IOException; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 13: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + + Exceptions: + throws java.io.IOException +public long skip(long) throws java.io.IOException; + Signature: (J)J + Code: + Stack=3, Locals=3, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 14: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Ljava/io/InputStream; + 0 10 1 byteCount J + + Exceptions: + throws java.io.IOException +} + diff --git a/base/android/jni_generator/testMotionEvent.javap b/base/android/jni_generator/testMotionEvent.javap new file mode 100644 index 0000000..0746943 --- /dev/null +++ b/base/android/jni_generator/testMotionEvent.javap @@ -0,0 +1,2295 @@ +Compiled from "MotionEvent.java" +public final class android.view.MotionEvent extends android.view.InputEvent implements android.os.Parcelable + SourceFile: "MotionEvent.java" + InnerClass: + public final #10= #9 of #6; //PointerProperties=class android/view/MotionEvent$PointerProperties of class android/view/MotionEvent + public final #13= #12 of #6; //PointerCoords=class android/view/MotionEvent$PointerCoords of class android/view/MotionEvent + public abstract #150= #149 of #8; //Creator=class android/os/Parcelable$Creator of class android/os/Parcelable + minor version: 0 + major version: 49 + Constant pool: +const #1 = Method #7.#293; // android/view/InputEvent."":()V +const #2 = class #294; // java/lang/RuntimeException +const #3 = String #295; // Stub! +const #4 = Method #2.#296; // java/lang/RuntimeException."":(Ljava/lang/String;)V +const #5 = Field #6.#297; // android/view/MotionEvent.CREATOR:Landroid/os/Parcelable$Creator; +const #6 = class #298; // android/view/MotionEvent +const #7 = class #299; // android/view/InputEvent +const #8 = class #300; // android/os/Parcelable +const #9 = class #301; // android/view/MotionEvent$PointerProperties +const #10 = Asciz PointerProperties; +const #11 = Asciz InnerClasses; +const #12 = class #302; // android/view/MotionEvent$PointerCoords +const #13 = Asciz PointerCoords; +const #14 = Asciz INVALID_POINTER_ID; +const #15 = Asciz I; +const #16 = Asciz ConstantValue; +const #17 = int -1; +const #18 = Asciz ACTION_MASK; +const #19 = int 255; +const #20 = Asciz ACTION_DOWN; +const #21 = int 0; +const #22 = Asciz ACTION_UP; +const #23 = int 1; +const #24 = Asciz ACTION_MOVE; +const #25 = int 2; +const #26 = Asciz ACTION_CANCEL; +const #27 = int 3; +const #28 = Asciz ACTION_OUTSIDE; +const #29 = int 4; +const #30 = Asciz ACTION_POINTER_DOWN; +const #31 = int 5; +const #32 = Asciz ACTION_POINTER_UP; +const #33 = int 6; +const #34 = Asciz ACTION_HOVER_MOVE; +const #35 = int 7; +const #36 = Asciz ACTION_SCROLL; +const #37 = int 8; +const #38 = Asciz ACTION_HOVER_ENTER; +const #39 = int 9; +const #40 = Asciz ACTION_HOVER_EXIT; +const #41 = int 10; +const #42 = Asciz ACTION_POINTER_INDEX_MASK; +const #43 = int 65280; +const #44 = Asciz ACTION_POINTER_INDEX_SHIFT; +const #45 = Asciz ACTION_POINTER_1_DOWN; +const #46 = Asciz Deprecated; +const #47 = Asciz RuntimeVisibleAnnotations; +const #48 = Asciz Ljava/lang/Deprecated;; +const #49 = Asciz ACTION_POINTER_2_DOWN; +const #50 = int 261; +const #51 = Asciz ACTION_POINTER_3_DOWN; +const #52 = int 517; +const #53 = Asciz ACTION_POINTER_1_UP; +const #54 = Asciz ACTION_POINTER_2_UP; +const #55 = int 262; +const #56 = Asciz ACTION_POINTER_3_UP; +const #57 = int 518; +const #58 = Asciz ACTION_POINTER_ID_MASK; +const #59 = Asciz ACTION_POINTER_ID_SHIFT; +const #60 = Asciz FLAG_WINDOW_IS_OBSCURED; +const #61 = Asciz EDGE_TOP; +const #62 = Asciz EDGE_BOTTOM; +const #63 = Asciz EDGE_LEFT; +const #64 = Asciz EDGE_RIGHT; +const #65 = Asciz AXIS_X; +const #66 = Asciz AXIS_Y; +const #67 = Asciz AXIS_PRESSURE; +const #68 = Asciz AXIS_SIZE; +const #69 = Asciz AXIS_TOUCH_MAJOR; +const #70 = Asciz AXIS_TOUCH_MINOR; +const #71 = Asciz AXIS_TOOL_MAJOR; +const #72 = Asciz AXIS_TOOL_MINOR; +const #73 = Asciz AXIS_ORIENTATION; +const #74 = Asciz AXIS_VSCROLL; +const #75 = Asciz AXIS_HSCROLL; +const #76 = Asciz AXIS_Z; +const #77 = int 11; +const #78 = Asciz AXIS_RX; +const #79 = int 12; +const #80 = Asciz AXIS_RY; +const #81 = int 13; +const #82 = Asciz AXIS_RZ; +const #83 = int 14; +const #84 = Asciz AXIS_HAT_X; +const #85 = int 15; +const #86 = Asciz AXIS_HAT_Y; +const #87 = int 16; +const #88 = Asciz AXIS_LTRIGGER; +const #89 = int 17; +const #90 = Asciz AXIS_RTRIGGER; +const #91 = int 18; +const #92 = Asciz AXIS_THROTTLE; +const #93 = int 19; +const #94 = Asciz AXIS_RUDDER; +const #95 = int 20; +const #96 = Asciz AXIS_WHEEL; +const #97 = int 21; +const #98 = Asciz AXIS_GAS; +const #99 = int 22; +const #100 = Asciz AXIS_BRAKE; +const #101 = int 23; +const #102 = Asciz AXIS_DISTANCE; +const #103 = int 24; +const #104 = Asciz AXIS_TILT; +const #105 = int 25; +const #106 = Asciz AXIS_GENERIC_1; +const #107 = int 32; +const #108 = Asciz AXIS_GENERIC_2; +const #109 = int 33; +const #110 = Asciz AXIS_GENERIC_3; +const #111 = int 34; +const #112 = Asciz AXIS_GENERIC_4; +const #113 = int 35; +const #114 = Asciz AXIS_GENERIC_5; +const #115 = int 36; +const #116 = Asciz AXIS_GENERIC_6; +const #117 = int 37; +const #118 = Asciz AXIS_GENERIC_7; +const #119 = int 38; +const #120 = Asciz AXIS_GENERIC_8; +const #121 = int 39; +const #122 = Asciz AXIS_GENERIC_9; +const #123 = int 40; +const #124 = Asciz AXIS_GENERIC_10; +const #125 = int 41; +const #126 = Asciz AXIS_GENERIC_11; +const #127 = int 42; +const #128 = Asciz AXIS_GENERIC_12; +const #129 = int 43; +const #130 = Asciz AXIS_GENERIC_13; +const #131 = int 44; +const #132 = Asciz AXIS_GENERIC_14; +const #133 = int 45; +const #134 = Asciz AXIS_GENERIC_15; +const #135 = int 46; +const #136 = Asciz AXIS_GENERIC_16; +const #137 = int 47; +const #138 = Asciz BUTTON_PRIMARY; +const #139 = Asciz BUTTON_SECONDARY; +const #140 = Asciz BUTTON_TERTIARY; +const #141 = Asciz BUTTON_BACK; +const #142 = Asciz BUTTON_FORWARD; +const #143 = Asciz TOOL_TYPE_UNKNOWN; +const #144 = Asciz TOOL_TYPE_FINGER; +const #145 = Asciz TOOL_TYPE_STYLUS; +const #146 = Asciz TOOL_TYPE_MOUSE; +const #147 = Asciz TOOL_TYPE_ERASER; +const #148 = Asciz CREATOR; +const #149 = class #303; // android/os/Parcelable$Creator +const #150 = Asciz Creator; +const #151 = Asciz Landroid/os/Parcelable$Creator;; +const #152 = Asciz Signature; +const #153 = Asciz Landroid/os/Parcelable$Creator;; +const #154 = Asciz ; +const #155 = Asciz ()V; +const #156 = Asciz Code; +const #157 = Asciz LineNumberTable; +const #158 = Asciz LocalVariableTable; +const #159 = Asciz this; +const #160 = Asciz Landroid/view/MotionEvent;; +const #161 = Asciz finalize; +const #162 = Asciz Exceptions; +const #163 = class #304; // java/lang/Throwable +const #164 = Asciz obtain; +const #165 = Asciz (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent;; +const #166 = Asciz downTime; +const #167 = Asciz J; +const #168 = Asciz eventTime; +const #169 = Asciz action; +const #170 = Asciz pointerCount; +const #171 = Asciz pointerProperties; +const #172 = Asciz [Landroid/view/MotionEvent$PointerProperties;; +const #173 = Asciz pointerCoords; +const #174 = Asciz [Landroid/view/MotionEvent$PointerCoords;; +const #175 = Asciz metaState; +const #176 = Asciz buttonState; +const #177 = Asciz xPrecision; +const #178 = Asciz F; +const #179 = Asciz yPrecision; +const #180 = Asciz deviceId; +const #181 = Asciz edgeFlags; +const #182 = Asciz source; +const #183 = Asciz flags; +const #184 = Asciz (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent;; +const #185 = Asciz pointerIds; +const #186 = Asciz [I; +const #187 = Asciz (JJIFFFFIFFII)Landroid/view/MotionEvent;; +const #188 = Asciz x; +const #189 = Asciz y; +const #190 = Asciz pressure; +const #191 = Asciz size; +const #192 = Asciz (JJIIFFFFIFFII)Landroid/view/MotionEvent;; +const #193 = Asciz (JJIFFI)Landroid/view/MotionEvent;; +const #194 = Asciz (Landroid/view/MotionEvent;)Landroid/view/MotionEvent;; +const #195 = Asciz other; +const #196 = Asciz obtainNoHistory; +const #197 = Asciz recycle; +const #198 = Asciz getDeviceId; +const #199 = Asciz ()I; +const #200 = Asciz getSource; +const #201 = Asciz setSource; +const #202 = Asciz (I)V; +const #203 = Asciz getAction; +const #204 = Asciz getActionMasked; +const #205 = Asciz getActionIndex; +const #206 = Asciz getFlags; +const #207 = Asciz getDownTime; +const #208 = Asciz ()J; +const #209 = Asciz getEventTime; +const #210 = Asciz getX; +const #211 = Asciz ()F; +const #212 = Asciz getY; +const #213 = Asciz getPressure; +const #214 = Asciz getSize; +const #215 = Asciz getTouchMajor; +const #216 = Asciz getTouchMinor; +const #217 = Asciz getToolMajor; +const #218 = Asciz getToolMinor; +const #219 = Asciz getOrientation; +const #220 = Asciz getAxisValue; +const #221 = Asciz (I)F; +const #222 = Asciz axis; +const #223 = Asciz getPointerCount; +const #224 = Asciz getPointerId; +const #225 = Asciz (I)I; +const #226 = Asciz pointerIndex; +const #227 = Asciz getToolType; +const #228 = Asciz findPointerIndex; +const #229 = Asciz pointerId; +const #230 = Asciz (II)F; +const #231 = Asciz getPointerCoords; +const #232 = Asciz (ILandroid/view/MotionEvent$PointerCoords;)V; +const #233 = Asciz outPointerCoords; +const #234 = Asciz Landroid/view/MotionEvent$PointerCoords;; +const #235 = Asciz getPointerProperties; +const #236 = Asciz (ILandroid/view/MotionEvent$PointerProperties;)V; +const #237 = Asciz outPointerProperties; +const #238 = Asciz Landroid/view/MotionEvent$PointerProperties;; +const #239 = Asciz getMetaState; +const #240 = Asciz getButtonState; +const #241 = Asciz getRawX; +const #242 = Asciz getRawY; +const #243 = Asciz getXPrecision; +const #244 = Asciz getYPrecision; +const #245 = Asciz getHistorySize; +const #246 = Asciz getHistoricalEventTime; +const #247 = Asciz (I)J; +const #248 = Asciz pos; +const #249 = Asciz getHistoricalX; +const #250 = Asciz getHistoricalY; +const #251 = Asciz getHistoricalPressure; +const #252 = Asciz getHistoricalSize; +const #253 = Asciz getHistoricalTouchMajor; +const #254 = Asciz getHistoricalTouchMinor; +const #255 = Asciz getHistoricalToolMajor; +const #256 = Asciz getHistoricalToolMinor; +const #257 = Asciz getHistoricalOrientation; +const #258 = Asciz getHistoricalAxisValue; +const #259 = Asciz (III)F; +const #260 = Asciz getHistoricalPointerCoords; +const #261 = Asciz (IILandroid/view/MotionEvent$PointerCoords;)V; +const #262 = Asciz getEdgeFlags; +const #263 = Asciz setEdgeFlags; +const #264 = Asciz setAction; +const #265 = Asciz offsetLocation; +const #266 = Asciz (FF)V; +const #267 = Asciz deltaX; +const #268 = Asciz deltaY; +const #269 = Asciz setLocation; +const #270 = Asciz transform; +const #271 = Asciz (Landroid/graphics/Matrix;)V; +const #272 = Asciz matrix; +const #273 = Asciz Landroid/graphics/Matrix;; +const #274 = Asciz addBatch; +const #275 = Asciz (JFFFFI)V; +const #276 = Asciz (J[Landroid/view/MotionEvent$PointerCoords;I)V; +const #277 = Asciz toString; +const #278 = Asciz ()Ljava/lang/String;; +const #279 = Asciz actionToString; +const #280 = Asciz (I)Ljava/lang/String;; +const #281 = Asciz axisToString; +const #282 = Asciz axisFromString; +const #283 = Asciz (Ljava/lang/String;)I; +const #284 = Asciz symbolicName; +const #285 = Asciz Ljava/lang/String;; +const #286 = Asciz writeToParcel; +const #287 = Asciz (Landroid/os/Parcel;I)V; +const #288 = Asciz out; +const #289 = Asciz Landroid/os/Parcel;; +const #290 = Asciz ; +const #291 = Asciz SourceFile; +const #292 = Asciz MotionEvent.java; +const #293 = NameAndType #154:#155;// "":()V +const #294 = Asciz java/lang/RuntimeException; +const #295 = Asciz Stub!; +const #296 = NameAndType #154:#305;// "":(Ljava/lang/String;)V +const #297 = NameAndType #148:#151;// CREATOR:Landroid/os/Parcelable$Creator; +const #298 = Asciz android/view/MotionEvent; +const #299 = Asciz android/view/InputEvent; +const #300 = Asciz android/os/Parcelable; +const #301 = Asciz android/view/MotionEvent$PointerProperties; +const #302 = Asciz android/view/MotionEvent$PointerCoords; +const #303 = Asciz android/os/Parcelable$Creator; +const #304 = Asciz java/lang/Throwable; +const #305 = Asciz (Ljava/lang/String;)V; + +{ +public static final int INVALID_POINTER_ID; + Signature: I + Constant value: int -1 + +public static final int ACTION_MASK; + Signature: I + Constant value: int 255 + +public static final int ACTION_DOWN; + Signature: I + Constant value: int 0 + +public static final int ACTION_UP; + Signature: I + Constant value: int 1 + +public static final int ACTION_MOVE; + Signature: I + Constant value: int 2 + +public static final int ACTION_CANCEL; + Signature: I + Constant value: int 3 + +public static final int ACTION_OUTSIDE; + Signature: I + Constant value: int 4 + +public static final int ACTION_POINTER_DOWN; + Signature: I + Constant value: int 5 + +public static final int ACTION_POINTER_UP; + Signature: I + Constant value: int 6 + +public static final int ACTION_HOVER_MOVE; + Signature: I + Constant value: int 7 + +public static final int ACTION_SCROLL; + Signature: I + Constant value: int 8 + +public static final int ACTION_HOVER_ENTER; + Signature: I + Constant value: int 9 + +public static final int ACTION_HOVER_EXIT; + Signature: I + Constant value: int 10 + +public static final int ACTION_POINTER_INDEX_MASK; + Signature: I + Constant value: int 65280 + +public static final int ACTION_POINTER_INDEX_SHIFT; + Signature: I + Constant value: int 8 + +public static final int ACTION_POINTER_1_DOWN; + Signature: I + Constant value: int 5Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_2_DOWN; + Signature: I + Constant value: int 261Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_3_DOWN; + Signature: I + Constant value: int 517Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_1_UP; + Signature: I + Constant value: int 6Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_2_UP; + Signature: I + Constant value: int 262Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_3_UP; + Signature: I + Constant value: int 518Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_ID_MASK; + Signature: I + Constant value: int 65280Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int ACTION_POINTER_ID_SHIFT; + Signature: I + Constant value: int 8Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + + +public static final int FLAG_WINDOW_IS_OBSCURED; + Signature: I + Constant value: int 1 + +public static final int EDGE_TOP; + Signature: I + Constant value: int 1 + +public static final int EDGE_BOTTOM; + Signature: I + Constant value: int 2 + +public static final int EDGE_LEFT; + Signature: I + Constant value: int 4 + +public static final int EDGE_RIGHT; + Signature: I + Constant value: int 8 + +public static final int AXIS_X; + Signature: I + Constant value: int 0 + +public static final int AXIS_Y; + Signature: I + Constant value: int 1 + +public static final int AXIS_PRESSURE; + Signature: I + Constant value: int 2 + +public static final int AXIS_SIZE; + Signature: I + Constant value: int 3 + +public static final int AXIS_TOUCH_MAJOR; + Signature: I + Constant value: int 4 + +public static final int AXIS_TOUCH_MINOR; + Signature: I + Constant value: int 5 + +public static final int AXIS_TOOL_MAJOR; + Signature: I + Constant value: int 6 + +public static final int AXIS_TOOL_MINOR; + Signature: I + Constant value: int 7 + +public static final int AXIS_ORIENTATION; + Signature: I + Constant value: int 8 + +public static final int AXIS_VSCROLL; + Signature: I + Constant value: int 9 + +public static final int AXIS_HSCROLL; + Signature: I + Constant value: int 10 + +public static final int AXIS_Z; + Signature: I + Constant value: int 11 + +public static final int AXIS_RX; + Signature: I + Constant value: int 12 + +public static final int AXIS_RY; + Signature: I + Constant value: int 13 + +public static final int AXIS_RZ; + Signature: I + Constant value: int 14 + +public static final int AXIS_HAT_X; + Signature: I + Constant value: int 15 + +public static final int AXIS_HAT_Y; + Signature: I + Constant value: int 16 + +public static final int AXIS_LTRIGGER; + Signature: I + Constant value: int 17 + +public static final int AXIS_RTRIGGER; + Signature: I + Constant value: int 18 + +public static final int AXIS_THROTTLE; + Signature: I + Constant value: int 19 + +public static final int AXIS_RUDDER; + Signature: I + Constant value: int 20 + +public static final int AXIS_WHEEL; + Signature: I + Constant value: int 21 + +public static final int AXIS_GAS; + Signature: I + Constant value: int 22 + +public static final int AXIS_BRAKE; + Signature: I + Constant value: int 23 + +public static final int AXIS_DISTANCE; + Signature: I + Constant value: int 24 + +public static final int AXIS_TILT; + Signature: I + Constant value: int 25 + +public static final int AXIS_GENERIC_1; + Signature: I + Constant value: int 32 + +public static final int AXIS_GENERIC_2; + Signature: I + Constant value: int 33 + +public static final int AXIS_GENERIC_3; + Signature: I + Constant value: int 34 + +public static final int AXIS_GENERIC_4; + Signature: I + Constant value: int 35 + +public static final int AXIS_GENERIC_5; + Signature: I + Constant value: int 36 + +public static final int AXIS_GENERIC_6; + Signature: I + Constant value: int 37 + +public static final int AXIS_GENERIC_7; + Signature: I + Constant value: int 38 + +public static final int AXIS_GENERIC_8; + Signature: I + Constant value: int 39 + +public static final int AXIS_GENERIC_9; + Signature: I + Constant value: int 40 + +public static final int AXIS_GENERIC_10; + Signature: I + Constant value: int 41 + +public static final int AXIS_GENERIC_11; + Signature: I + Constant value: int 42 + +public static final int AXIS_GENERIC_12; + Signature: I + Constant value: int 43 + +public static final int AXIS_GENERIC_13; + Signature: I + Constant value: int 44 + +public static final int AXIS_GENERIC_14; + Signature: I + Constant value: int 45 + +public static final int AXIS_GENERIC_15; + Signature: I + Constant value: int 46 + +public static final int AXIS_GENERIC_16; + Signature: I + Constant value: int 47 + +public static final int BUTTON_PRIMARY; + Signature: I + Constant value: int 1 + +public static final int BUTTON_SECONDARY; + Signature: I + Constant value: int 2 + +public static final int BUTTON_TERTIARY; + Signature: I + Constant value: int 4 + +public static final int BUTTON_BACK; + Signature: I + Constant value: int 8 + +public static final int BUTTON_FORWARD; + Signature: I + Constant value: int 16 + +public static final int TOOL_TYPE_UNKNOWN; + Signature: I + Constant value: int 0 + +public static final int TOOL_TYPE_FINGER; + Signature: I + Constant value: int 1 + +public static final int TOOL_TYPE_STYLUS; + Signature: I + Constant value: int 2 + +public static final int TOOL_TYPE_MOUSE; + Signature: I + Constant value: int 3 + +public static final int TOOL_TYPE_ERASER; + Signature: I + Constant value: int 4 + +public static final android.os.Parcelable$Creator CREATOR; + Signature: Landroid/os/Parcelable$Creator; + Signature: length = 0x2 + 00 FFFFFF99 + + +android.view.MotionEvent(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: aload_0 + 1: invokespecial #1; //Method android/view/InputEvent."":()V + 4: new #2; //class java/lang/RuntimeException + 7: dup + 8: ldc #3; //String Stub! + 10: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 35: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Landroid/view/MotionEvent; + + +protected void finalize() throws java.lang.Throwable; + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 36: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + Exceptions: + throws java.lang.Throwable +public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent$PointerProperties[], android.view.MotionEvent$PointerCoords[], int, int, float, float, int, int, int, int); + Signature: (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=16, Args_size=14 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 37: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerProperties [Landroid/view/MotionEvent$PointerProperties; + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 buttonState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + 0 10 14 source I + 0 10 15 flags I + + +public static android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent$PointerCoords[], int, float, float, int, int, int, int); + Signature: (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=15, Args_size=13 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 39: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerIds [I + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 xPrecision F + 0 10 10 yPrecision F + 0 10 11 deviceId I + 0 10 12 edgeFlags I + 0 10 13 source I + 0 10 14 flags I + + Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + +public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIFFFFIFFII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=14, Args_size=12 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 40: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 pressure F + 0 10 8 size F + 0 10 9 metaState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + + +public static android.view.MotionEvent obtain(long, long, int, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIIFFFFIFFII)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=15, Args_size=13 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 42: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 x F + 0 10 7 y F + 0 10 8 pressure F + 0 10 9 size F + 0 10 10 metaState I + 0 10 11 xPrecision F + 0 10 12 yPrecision F + 0 10 13 deviceId I + 0 10 14 edgeFlags I + + Deprecated: true + RuntimeVisibleAnnotations: length = 0x6 + 00 01 00 30 00 00 + +public static android.view.MotionEvent obtain(long, long, int, float, float, int); + Signature: (JJIFFI)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=8, Args_size=6 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 43: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 metaState I + + +public static android.view.MotionEvent obtain(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 44: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + +public static android.view.MotionEvent obtainNoHistory(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 45: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + +public final void recycle(); + Signature: ()V + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 46: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getDeviceId(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 47: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getSource(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 48: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final void setSource(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 49: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 source I + + +public final int getAction(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 50: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getActionMasked(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 51: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getActionIndex(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 52: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getFlags(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 53: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getDownTime(); + Signature: ()J + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 54: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getEventTime(); + Signature: ()J + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 55: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getX(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 56: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getY(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 57: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getPressure(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 58: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getSize(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 59: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getTouchMajor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 60: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getTouchMinor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 61: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getToolMajor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 62: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getToolMinor(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 63: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getOrientation(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 64: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getAxisValue(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 65: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + + +public final int getPointerCount(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 66: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getPointerId(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 67: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final int getToolType(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 68: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final int findPointerIndex(int); + Signature: (I)I + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 69: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerId I + + +public final float getX(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 70: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getY(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 71: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getPressure(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 72: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getSize(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 73: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getTouchMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 74: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getTouchMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 75: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getToolMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 76: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getToolMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 77: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getOrientation(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 78: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + +public final float getAxisValue(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 79: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + + +public final void getPointerCoords(int, android.view.MotionEvent$PointerCoords); + Signature: (ILandroid/view/MotionEvent$PointerCoords;)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 80: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + +public final void getPointerProperties(int, android.view.MotionEvent$PointerProperties); + Signature: (ILandroid/view/MotionEvent$PointerProperties;)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 81: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerProperties Landroid/view/MotionEvent$PointerProperties; + + +public final int getMetaState(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 82: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getButtonState(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 83: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getRawX(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 84: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getRawY(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 85: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getXPrecision(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 86: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final float getYPrecision(); + Signature: ()F + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 87: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final int getHistorySize(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 88: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final long getHistoricalEventTime(int); + Signature: (I)J + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 89: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalX(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 90: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalY(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 91: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalPressure(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 92: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalSize(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 93: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalTouchMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 94: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalTouchMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 95: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalToolMajor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 96: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalToolMinor(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 97: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalOrientation(int); + Signature: (I)F + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 98: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + +public final float getHistoricalAxisValue(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 99: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pos I + + +public final float getHistoricalX(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 100: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalY(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 101: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalPressure(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 102: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalSize(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 103: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalTouchMajor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 104: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalTouchMinor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 105: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalToolMajor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 106: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalToolMinor(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 107: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalOrientation(int, int); + Signature: (II)F + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 108: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + +public final float getHistoricalAxisValue(int, int, int); + Signature: (III)F + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 109: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + 0 10 3 pos I + + +public final void getHistoricalPointerCoords(int, int, android.view.MotionEvent$PointerCoords); + Signature: (IILandroid/view/MotionEvent$PointerCoords;)V + Code: + Stack=3, Locals=4, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 110: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + 0 10 3 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + +public final int getEdgeFlags(); + Signature: ()I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 111: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public final void setEdgeFlags(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 112: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 flags I + + +public final void setAction(int); + Signature: (I)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 113: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 action I + + +public final void offsetLocation(float, float); + Signature: (FF)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 114: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 deltaX F + 0 10 2 deltaY F + + +public final void setLocation(float, float); + Signature: (FF)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 115: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 x F + 0 10 2 y F + + +public final void transform(android.graphics.Matrix); + Signature: (Landroid/graphics/Matrix;)V + Code: + Stack=3, Locals=2, Args_size=2 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 116: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 matrix Landroid/graphics/Matrix; + + +public final void addBatch(long, float, float, float, float, int); + Signature: (JFFFFI)V + Code: + Stack=3, Locals=8, Args_size=7 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 117: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 x F + 0 10 4 y F + 0 10 5 pressure F + 0 10 6 size F + 0 10 7 metaState I + + +public final void addBatch(long, android.view.MotionEvent$PointerCoords[], int); + Signature: (J[Landroid/view/MotionEvent$PointerCoords;I)V + Code: + Stack=3, Locals=5, Args_size=4 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 118: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 4 metaState I + + +public java.lang.String toString(); + Signature: ()Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 119: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + +public static java.lang.String actionToString(int); + Signature: (I)Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 120: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 action I + + +public static java.lang.String axisToString(int); + Signature: (I)Ljava/lang/String; + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 121: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 axis I + + +public static int axisFromString(java.lang.String); + Signature: (Ljava/lang/String;)I + Code: + Stack=3, Locals=1, Args_size=1 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 122: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 symbolicName Ljava/lang/String; + + +public void writeToParcel(android.os.Parcel, int); + Signature: (Landroid/os/Parcel;I)V + Code: + Stack=3, Locals=3, Args_size=3 + 0: new #2; //class java/lang/RuntimeException + 3: dup + 4: ldc #3; //String Stub! + 6: invokespecial #4; //Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 123: 0 + + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 out Landroid/os/Parcel; + 0 10 2 flags I + + +static {}; + Signature: ()V + Code: + Stack=1, Locals=0, Args_size=0 + 0: aconst_null + 1: putstatic #5; //Field CREATOR:Landroid/os/Parcelable$Creator; + 4: return + LineNumberTable: + line 213: 0 + + +} + diff --git a/base/android/jni_generator/testMotionEvent.javap7 b/base/android/jni_generator/testMotionEvent.javap7 new file mode 100644 index 0000000..f4f5444 --- /dev/null +++ b/base/android/jni_generator/testMotionEvent.javap7 @@ -0,0 +1,2370 @@ +Classfile out_android/Debug/gen/content/jni/android/view/MotionEvent.class + Last modified Feb 27, 2014; size 13369 bytes + MD5 checksum 3718d77a994cb8aceb7b35c5df3c4dd1 + Compiled from "MotionEvent.java" +public final class android.view.MotionEvent extends android.view.InputEvent implements android.os.Parcelable + SourceFile: "MotionEvent.java" + InnerClasses: + public static final #10= #9 of #6; //PointerProperties=class android/view/MotionEvent$PointerProperties of class android/view/MotionEvent + public static final #13= #12 of #6; //PointerCoords=class android/view/MotionEvent$PointerCoords of class android/view/MotionEvent + public static #150= #149 of #8; //Creator=class android/os/Parcelable$Creator of class android/os/Parcelable + minor version: 0 + major version: 49 + flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER +Constant pool: + #1 = Methodref #7.#293 // android/view/InputEvent."":()V + #2 = Class #294 // java/lang/RuntimeException + #3 = String #295 // Stub! + #4 = Methodref #2.#296 // java/lang/RuntimeException."":(Ljava/lang/String;)V + #5 = Fieldref #6.#297 // android/view/MotionEvent.CREATOR:Landroid/os/Parcelable$Creator; + #6 = Class #298 // android/view/MotionEvent + #7 = Class #299 // android/view/InputEvent + #8 = Class #300 // android/os/Parcelable + #9 = Class #301 // android/view/MotionEvent$PointerProperties + #10 = Utf8 PointerProperties + #11 = Utf8 InnerClasses + #12 = Class #302 // android/view/MotionEvent$PointerCoords + #13 = Utf8 PointerCoords + #14 = Utf8 INVALID_POINTER_ID + #15 = Utf8 I + #16 = Utf8 ConstantValue + #17 = Integer -1 + #18 = Utf8 ACTION_MASK + #19 = Integer 255 + #20 = Utf8 ACTION_DOWN + #21 = Integer 0 + #22 = Utf8 ACTION_UP + #23 = Integer 1 + #24 = Utf8 ACTION_MOVE + #25 = Integer 2 + #26 = Utf8 ACTION_CANCEL + #27 = Integer 3 + #28 = Utf8 ACTION_OUTSIDE + #29 = Integer 4 + #30 = Utf8 ACTION_POINTER_DOWN + #31 = Integer 5 + #32 = Utf8 ACTION_POINTER_UP + #33 = Integer 6 + #34 = Utf8 ACTION_HOVER_MOVE + #35 = Integer 7 + #36 = Utf8 ACTION_SCROLL + #37 = Integer 8 + #38 = Utf8 ACTION_HOVER_ENTER + #39 = Integer 9 + #40 = Utf8 ACTION_HOVER_EXIT + #41 = Integer 10 + #42 = Utf8 ACTION_POINTER_INDEX_MASK + #43 = Integer 65280 + #44 = Utf8 ACTION_POINTER_INDEX_SHIFT + #45 = Utf8 ACTION_POINTER_1_DOWN + #46 = Utf8 Deprecated + #47 = Utf8 RuntimeVisibleAnnotations + #48 = Utf8 Ljava/lang/Deprecated; + #49 = Utf8 ACTION_POINTER_2_DOWN + #50 = Integer 261 + #51 = Utf8 ACTION_POINTER_3_DOWN + #52 = Integer 517 + #53 = Utf8 ACTION_POINTER_1_UP + #54 = Utf8 ACTION_POINTER_2_UP + #55 = Integer 262 + #56 = Utf8 ACTION_POINTER_3_UP + #57 = Integer 518 + #58 = Utf8 ACTION_POINTER_ID_MASK + #59 = Utf8 ACTION_POINTER_ID_SHIFT + #60 = Utf8 FLAG_WINDOW_IS_OBSCURED + #61 = Utf8 EDGE_TOP + #62 = Utf8 EDGE_BOTTOM + #63 = Utf8 EDGE_LEFT + #64 = Utf8 EDGE_RIGHT + #65 = Utf8 AXIS_X + #66 = Utf8 AXIS_Y + #67 = Utf8 AXIS_PRESSURE + #68 = Utf8 AXIS_SIZE + #69 = Utf8 AXIS_TOUCH_MAJOR + #70 = Utf8 AXIS_TOUCH_MINOR + #71 = Utf8 AXIS_TOOL_MAJOR + #72 = Utf8 AXIS_TOOL_MINOR + #73 = Utf8 AXIS_ORIENTATION + #74 = Utf8 AXIS_VSCROLL + #75 = Utf8 AXIS_HSCROLL + #76 = Utf8 AXIS_Z + #77 = Integer 11 + #78 = Utf8 AXIS_RX + #79 = Integer 12 + #80 = Utf8 AXIS_RY + #81 = Integer 13 + #82 = Utf8 AXIS_RZ + #83 = Integer 14 + #84 = Utf8 AXIS_HAT_X + #85 = Integer 15 + #86 = Utf8 AXIS_HAT_Y + #87 = Integer 16 + #88 = Utf8 AXIS_LTRIGGER + #89 = Integer 17 + #90 = Utf8 AXIS_RTRIGGER + #91 = Integer 18 + #92 = Utf8 AXIS_THROTTLE + #93 = Integer 19 + #94 = Utf8 AXIS_RUDDER + #95 = Integer 20 + #96 = Utf8 AXIS_WHEEL + #97 = Integer 21 + #98 = Utf8 AXIS_GAS + #99 = Integer 22 + #100 = Utf8 AXIS_BRAKE + #101 = Integer 23 + #102 = Utf8 AXIS_DISTANCE + #103 = Integer 24 + #104 = Utf8 AXIS_TILT + #105 = Integer 25 + #106 = Utf8 AXIS_GENERIC_1 + #107 = Integer 32 + #108 = Utf8 AXIS_GENERIC_2 + #109 = Integer 33 + #110 = Utf8 AXIS_GENERIC_3 + #111 = Integer 34 + #112 = Utf8 AXIS_GENERIC_4 + #113 = Integer 35 + #114 = Utf8 AXIS_GENERIC_5 + #115 = Integer 36 + #116 = Utf8 AXIS_GENERIC_6 + #117 = Integer 37 + #118 = Utf8 AXIS_GENERIC_7 + #119 = Integer 38 + #120 = Utf8 AXIS_GENERIC_8 + #121 = Integer 39 + #122 = Utf8 AXIS_GENERIC_9 + #123 = Integer 40 + #124 = Utf8 AXIS_GENERIC_10 + #125 = Integer 41 + #126 = Utf8 AXIS_GENERIC_11 + #127 = Integer 42 + #128 = Utf8 AXIS_GENERIC_12 + #129 = Integer 43 + #130 = Utf8 AXIS_GENERIC_13 + #131 = Integer 44 + #132 = Utf8 AXIS_GENERIC_14 + #133 = Integer 45 + #134 = Utf8 AXIS_GENERIC_15 + #135 = Integer 46 + #136 = Utf8 AXIS_GENERIC_16 + #137 = Integer 47 + #138 = Utf8 BUTTON_PRIMARY + #139 = Utf8 BUTTON_SECONDARY + #140 = Utf8 BUTTON_TERTIARY + #141 = Utf8 BUTTON_BACK + #142 = Utf8 BUTTON_FORWARD + #143 = Utf8 TOOL_TYPE_UNKNOWN + #144 = Utf8 TOOL_TYPE_FINGER + #145 = Utf8 TOOL_TYPE_STYLUS + #146 = Utf8 TOOL_TYPE_MOUSE + #147 = Utf8 TOOL_TYPE_ERASER + #148 = Utf8 CREATOR + #149 = Class #303 // android/os/Parcelable$Creator + #150 = Utf8 Creator + #151 = Utf8 Landroid/os/Parcelable$Creator; + #152 = Utf8 Signature + #153 = Utf8 Landroid/os/Parcelable$Creator; + #154 = Utf8 + #155 = Utf8 ()V + #156 = Utf8 Code + #157 = Utf8 LineNumberTable + #158 = Utf8 LocalVariableTable + #159 = Utf8 this + #160 = Utf8 Landroid/view/MotionEvent; + #161 = Utf8 finalize + #162 = Utf8 Exceptions + #163 = Class #304 // java/lang/Throwable + #164 = Utf8 obtain + #165 = Utf8 (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + #166 = Utf8 downTime + #167 = Utf8 J + #168 = Utf8 eventTime + #169 = Utf8 action + #170 = Utf8 pointerCount + #171 = Utf8 pointerProperties + #172 = Utf8 [Landroid/view/MotionEvent$PointerProperties; + #173 = Utf8 pointerCoords + #174 = Utf8 [Landroid/view/MotionEvent$PointerCoords; + #175 = Utf8 metaState + #176 = Utf8 buttonState + #177 = Utf8 xPrecision + #178 = Utf8 F + #179 = Utf8 yPrecision + #180 = Utf8 deviceId + #181 = Utf8 edgeFlags + #182 = Utf8 source + #183 = Utf8 flags + #184 = Utf8 (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + #185 = Utf8 pointerIds + #186 = Utf8 [I + #187 = Utf8 (JJIFFFFIFFII)Landroid/view/MotionEvent; + #188 = Utf8 x + #189 = Utf8 y + #190 = Utf8 pressure + #191 = Utf8 size + #192 = Utf8 (JJIIFFFFIFFII)Landroid/view/MotionEvent; + #193 = Utf8 (JJIFFI)Landroid/view/MotionEvent; + #194 = Utf8 (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + #195 = Utf8 other + #196 = Utf8 obtainNoHistory + #197 = Utf8 recycle + #198 = Utf8 getDeviceId + #199 = Utf8 ()I + #200 = Utf8 getSource + #201 = Utf8 setSource + #202 = Utf8 (I)V + #203 = Utf8 getAction + #204 = Utf8 getActionMasked + #205 = Utf8 getActionIndex + #206 = Utf8 getFlags + #207 = Utf8 getDownTime + #208 = Utf8 ()J + #209 = Utf8 getEventTime + #210 = Utf8 getX + #211 = Utf8 ()F + #212 = Utf8 getY + #213 = Utf8 getPressure + #214 = Utf8 getSize + #215 = Utf8 getTouchMajor + #216 = Utf8 getTouchMinor + #217 = Utf8 getToolMajor + #218 = Utf8 getToolMinor + #219 = Utf8 getOrientation + #220 = Utf8 getAxisValue + #221 = Utf8 (I)F + #222 = Utf8 axis + #223 = Utf8 getPointerCount + #224 = Utf8 getPointerId + #225 = Utf8 (I)I + #226 = Utf8 pointerIndex + #227 = Utf8 getToolType + #228 = Utf8 findPointerIndex + #229 = Utf8 pointerId + #230 = Utf8 (II)F + #231 = Utf8 getPointerCoords + #232 = Utf8 (ILandroid/view/MotionEvent$PointerCoords;)V + #233 = Utf8 outPointerCoords + #234 = Utf8 Landroid/view/MotionEvent$PointerCoords; + #235 = Utf8 getPointerProperties + #236 = Utf8 (ILandroid/view/MotionEvent$PointerProperties;)V + #237 = Utf8 outPointerProperties + #238 = Utf8 Landroid/view/MotionEvent$PointerProperties; + #239 = Utf8 getMetaState + #240 = Utf8 getButtonState + #241 = Utf8 getRawX + #242 = Utf8 getRawY + #243 = Utf8 getXPrecision + #244 = Utf8 getYPrecision + #245 = Utf8 getHistorySize + #246 = Utf8 getHistoricalEventTime + #247 = Utf8 (I)J + #248 = Utf8 pos + #249 = Utf8 getHistoricalX + #250 = Utf8 getHistoricalY + #251 = Utf8 getHistoricalPressure + #252 = Utf8 getHistoricalSize + #253 = Utf8 getHistoricalTouchMajor + #254 = Utf8 getHistoricalTouchMinor + #255 = Utf8 getHistoricalToolMajor + #256 = Utf8 getHistoricalToolMinor + #257 = Utf8 getHistoricalOrientation + #258 = Utf8 getHistoricalAxisValue + #259 = Utf8 (III)F + #260 = Utf8 getHistoricalPointerCoords + #261 = Utf8 (IILandroid/view/MotionEvent$PointerCoords;)V + #262 = Utf8 getEdgeFlags + #263 = Utf8 setEdgeFlags + #264 = Utf8 setAction + #265 = Utf8 offsetLocation + #266 = Utf8 (FF)V + #267 = Utf8 deltaX + #268 = Utf8 deltaY + #269 = Utf8 setLocation + #270 = Utf8 transform + #271 = Utf8 (Landroid/graphics/Matrix;)V + #272 = Utf8 matrix + #273 = Utf8 Landroid/graphics/Matrix; + #274 = Utf8 addBatch + #275 = Utf8 (JFFFFI)V + #276 = Utf8 (J[Landroid/view/MotionEvent$PointerCoords;I)V + #277 = Utf8 toString + #278 = Utf8 ()Ljava/lang/String; + #279 = Utf8 actionToString + #280 = Utf8 (I)Ljava/lang/String; + #281 = Utf8 axisToString + #282 = Utf8 axisFromString + #283 = Utf8 (Ljava/lang/String;)I + #284 = Utf8 symbolicName + #285 = Utf8 Ljava/lang/String; + #286 = Utf8 writeToParcel + #287 = Utf8 (Landroid/os/Parcel;I)V + #288 = Utf8 out + #289 = Utf8 Landroid/os/Parcel; + #290 = Utf8 + #291 = Utf8 SourceFile + #292 = Utf8 MotionEvent.java + #293 = NameAndType #154:#155 // "":()V + #294 = Utf8 java/lang/RuntimeException + #295 = Utf8 Stub! + #296 = NameAndType #154:#305 // "":(Ljava/lang/String;)V + #297 = NameAndType #148:#151 // CREATOR:Landroid/os/Parcelable$Creator; + #298 = Utf8 android/view/MotionEvent + #299 = Utf8 android/view/InputEvent + #300 = Utf8 android/os/Parcelable + #301 = Utf8 android/view/MotionEvent$PointerProperties + #302 = Utf8 android/view/MotionEvent$PointerCoords + #303 = Utf8 android/os/Parcelable$Creator + #304 = Utf8 java/lang/Throwable + #305 = Utf8 (Ljava/lang/String;)V +{ + public static final int INVALID_POINTER_ID; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int -1 + + + public static final int ACTION_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 255 + + + public static final int ACTION_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int ACTION_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int ACTION_MOVE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int ACTION_CANCEL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int ACTION_OUTSIDE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int ACTION_POINTER_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + + + public static final int ACTION_POINTER_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + + + public static final int ACTION_HOVER_MOVE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 7 + + + public static final int ACTION_SCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int ACTION_HOVER_ENTER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 9 + + + public static final int ACTION_HOVER_EXIT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 10 + + + public static final int ACTION_POINTER_INDEX_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 65280 + + + public static final int ACTION_POINTER_INDEX_SHIFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int ACTION_POINTER_1_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_2_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 261 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_3_DOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 517 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_1_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_2_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 262 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_3_UP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 518 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_ID_MASK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 65280 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int ACTION_POINTER_ID_SHIFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + + public static final int FLAG_WINDOW_IS_OBSCURED; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int EDGE_TOP; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int EDGE_BOTTOM; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int EDGE_LEFT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int EDGE_RIGHT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int AXIS_X; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int AXIS_Y; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int AXIS_PRESSURE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int AXIS_SIZE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int AXIS_TOUCH_MAJOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int AXIS_TOUCH_MINOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 5 + + + public static final int AXIS_TOOL_MAJOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 6 + + + public static final int AXIS_TOOL_MINOR; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 7 + + + public static final int AXIS_ORIENTATION; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int AXIS_VSCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 9 + + + public static final int AXIS_HSCROLL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 10 + + + public static final int AXIS_Z; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 11 + + + public static final int AXIS_RX; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 12 + + + public static final int AXIS_RY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 13 + + + public static final int AXIS_RZ; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 14 + + + public static final int AXIS_HAT_X; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 15 + + + public static final int AXIS_HAT_Y; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 16 + + + public static final int AXIS_LTRIGGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 17 + + + public static final int AXIS_RTRIGGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 18 + + + public static final int AXIS_THROTTLE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 19 + + + public static final int AXIS_RUDDER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 20 + + + public static final int AXIS_WHEEL; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 21 + + + public static final int AXIS_GAS; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 22 + + + public static final int AXIS_BRAKE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 23 + + + public static final int AXIS_DISTANCE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 24 + + + public static final int AXIS_TILT; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 25 + + + public static final int AXIS_GENERIC_1; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 32 + + + public static final int AXIS_GENERIC_2; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 33 + + + public static final int AXIS_GENERIC_3; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 34 + + + public static final int AXIS_GENERIC_4; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 35 + + + public static final int AXIS_GENERIC_5; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 36 + + + public static final int AXIS_GENERIC_6; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 37 + + + public static final int AXIS_GENERIC_7; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 38 + + + public static final int AXIS_GENERIC_8; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 39 + + + public static final int AXIS_GENERIC_9; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 40 + + + public static final int AXIS_GENERIC_10; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 41 + + + public static final int AXIS_GENERIC_11; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 42 + + + public static final int AXIS_GENERIC_12; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 43 + + + public static final int AXIS_GENERIC_13; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 44 + + + public static final int AXIS_GENERIC_14; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 45 + + + public static final int AXIS_GENERIC_15; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 46 + + + public static final int AXIS_GENERIC_16; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 47 + + + public static final int BUTTON_PRIMARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int BUTTON_SECONDARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int BUTTON_TERTIARY; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final int BUTTON_BACK; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 8 + + + public static final int BUTTON_FORWARD; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 16 + + + public static final int TOOL_TYPE_UNKNOWN; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 0 + + + public static final int TOOL_TYPE_FINGER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 1 + + + public static final int TOOL_TYPE_STYLUS; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 2 + + + public static final int TOOL_TYPE_MOUSE; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 3 + + + public static final int TOOL_TYPE_ERASER; + Signature: I + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + ConstantValue: int 4 + + + public static final android.os.Parcelable$Creator CREATOR; + Signature: Landroid/os/Parcelable$Creator; + flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL + Signature: #153 // Landroid/os/Parcelable$Creator; + + + android.view.MotionEvent(); + Signature: ()V + flags: + Code: + stack=3, locals=1, args_size=1 + 0: aload_0 + 1: invokespecial #1 // Method android/view/InputEvent."":()V + 4: new #2 // class java/lang/RuntimeException + 7: dup + 8: ldc #3 // String Stub! + 10: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 13: athrow + LineNumberTable: + line 35: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 14 0 this Landroid/view/MotionEvent; + + protected void finalize() throws java.lang.Throwable; + Signature: ()V + flags: ACC_PROTECTED + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 36: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + Exceptions: + throws java.lang.Throwable + + public static android.view.MotionEvent obtain(long, long, int, int, android.view.MotionEvent$PointerProperties[], android.view.MotionEvent$PointerCoords[], int, int, float, float, int, int, int, int); + Signature: (JJII[Landroid/view/MotionEvent$PointerProperties;[Landroid/view/MotionEvent$PointerCoords;IIFFIIII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=16, args_size=14 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 37: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerProperties [Landroid/view/MotionEvent$PointerProperties; + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 buttonState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + 0 10 14 source I + 0 10 15 flags I + + public static android.view.MotionEvent obtain(long, long, int, int, int[], android.view.MotionEvent$PointerCoords[], int, float, float, int, int, int, int); + Signature: (JJII[I[Landroid/view/MotionEvent$PointerCoords;IFFIIII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=15, args_size=13 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 39: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 pointerIds [I + 0 10 7 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 8 metaState I + 0 10 9 xPrecision F + 0 10 10 yPrecision F + 0 10 11 deviceId I + 0 10 12 edgeFlags I + 0 10 13 source I + 0 10 14 flags I + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + public static android.view.MotionEvent obtain(long, long, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIFFFFIFFII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=14, args_size=12 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 40: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 pressure F + 0 10 8 size F + 0 10 9 metaState I + 0 10 10 xPrecision F + 0 10 11 yPrecision F + 0 10 12 deviceId I + 0 10 13 edgeFlags I + + public static android.view.MotionEvent obtain(long, long, int, int, float, float, float, float, int, float, float, int, int); + Signature: (JJIIFFFFIFFII)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=15, args_size=13 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 42: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 pointerCount I + 0 10 6 x F + 0 10 7 y F + 0 10 8 pressure F + 0 10 9 size F + 0 10 10 metaState I + 0 10 11 xPrecision F + 0 10 12 yPrecision F + 0 10 13 deviceId I + 0 10 14 edgeFlags I + Deprecated: true + RuntimeVisibleAnnotations: + 0: #48() + + public static android.view.MotionEvent obtain(long, long, int, float, float, int); + Signature: (JJIFFI)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=8, args_size=6 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 43: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 downTime J + 0 10 2 eventTime J + 0 10 4 action I + 0 10 5 x F + 0 10 6 y F + 0 10 7 metaState I + + public static android.view.MotionEvent obtain(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 44: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + public static android.view.MotionEvent obtainNoHistory(android.view.MotionEvent); + Signature: (Landroid/view/MotionEvent;)Landroid/view/MotionEvent; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 45: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 other Landroid/view/MotionEvent; + + public final void recycle(); + Signature: ()V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 46: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getDeviceId(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 47: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getSource(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 48: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final void setSource(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 49: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 source I + + public final int getAction(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 50: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getActionMasked(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 51: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getActionIndex(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 52: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getFlags(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 53: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getDownTime(); + Signature: ()J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 54: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getEventTime(); + Signature: ()J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 55: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getX(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 56: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getY(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 57: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getPressure(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 58: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getSize(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 59: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getTouchMajor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 60: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getTouchMinor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 61: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getToolMajor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 62: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getToolMinor(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 63: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getOrientation(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 64: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getAxisValue(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 65: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + + public final int getPointerCount(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 66: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getPointerId(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 67: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final int getToolType(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 68: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final int findPointerIndex(int); + Signature: (I)I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 69: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerId I + + public final float getX(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 70: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getY(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 71: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getPressure(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 72: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getSize(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 73: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getTouchMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 74: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getTouchMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 75: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getToolMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 76: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getToolMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 77: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getOrientation(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 78: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + + public final float getAxisValue(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 79: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + + public final void getPointerCoords(int, android.view.MotionEvent$PointerCoords); + Signature: (ILandroid/view/MotionEvent$PointerCoords;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 80: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + public final void getPointerProperties(int, android.view.MotionEvent$PointerProperties); + Signature: (ILandroid/view/MotionEvent$PointerProperties;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 81: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 outPointerProperties Landroid/view/MotionEvent$PointerProperties; + + public final int getMetaState(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 82: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getButtonState(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 83: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getRawX(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 84: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getRawY(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 85: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getXPrecision(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 86: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final float getYPrecision(); + Signature: ()F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 87: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final int getHistorySize(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 88: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final long getHistoricalEventTime(int); + Signature: (I)J + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 89: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalX(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 90: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalY(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 91: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalPressure(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 92: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalSize(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 93: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalTouchMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 94: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalTouchMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 95: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalToolMajor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 96: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalToolMinor(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 97: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalOrientation(int); + Signature: (I)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 98: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pos I + + public final float getHistoricalAxisValue(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 99: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pos I + + public final float getHistoricalX(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 100: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalY(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 101: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalPressure(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 102: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalSize(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 103: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalTouchMajor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 104: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalTouchMinor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 105: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalToolMajor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 106: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalToolMinor(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 107: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalOrientation(int, int); + Signature: (II)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 108: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + + public final float getHistoricalAxisValue(int, int, int); + Signature: (III)F + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=4, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 109: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 axis I + 0 10 2 pointerIndex I + 0 10 3 pos I + + public final void getHistoricalPointerCoords(int, int, android.view.MotionEvent$PointerCoords); + Signature: (IILandroid/view/MotionEvent$PointerCoords;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=4, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 110: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 pointerIndex I + 0 10 2 pos I + 0 10 3 outPointerCoords Landroid/view/MotionEvent$PointerCoords; + + public final int getEdgeFlags(); + Signature: ()I + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 111: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public final void setEdgeFlags(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 112: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 flags I + + public final void setAction(int); + Signature: (I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 113: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 action I + + public final void offsetLocation(float, float); + Signature: (FF)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 114: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 deltaX F + 0 10 2 deltaY F + + public final void setLocation(float, float); + Signature: (FF)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 115: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 x F + 0 10 2 y F + + public final void transform(android.graphics.Matrix); + Signature: (Landroid/graphics/Matrix;)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=2, args_size=2 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 116: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 matrix Landroid/graphics/Matrix; + + public final void addBatch(long, float, float, float, float, int); + Signature: (JFFFFI)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=8, args_size=7 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 117: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 x F + 0 10 4 y F + 0 10 5 pressure F + 0 10 6 size F + 0 10 7 metaState I + + public final void addBatch(long, android.view.MotionEvent$PointerCoords[], int); + Signature: (J[Landroid/view/MotionEvent$PointerCoords;I)V + flags: ACC_PUBLIC, ACC_FINAL + Code: + stack=3, locals=5, args_size=4 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 118: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 eventTime J + 0 10 3 pointerCoords [Landroid/view/MotionEvent$PointerCoords; + 0 10 4 metaState I + + public java.lang.String toString(); + Signature: ()Ljava/lang/String; + flags: ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 119: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + + public static java.lang.String actionToString(int); + Signature: (I)Ljava/lang/String; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 120: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 action I + + public static java.lang.String axisToString(int); + Signature: (I)Ljava/lang/String; + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 121: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 axis I + + public static int axisFromString(java.lang.String); + Signature: (Ljava/lang/String;)I + flags: ACC_PUBLIC, ACC_STATIC + Code: + stack=3, locals=1, args_size=1 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 122: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 symbolicName Ljava/lang/String; + + public void writeToParcel(android.os.Parcel, int); + Signature: (Landroid/os/Parcel;I)V + flags: ACC_PUBLIC + Code: + stack=3, locals=3, args_size=3 + 0: new #2 // class java/lang/RuntimeException + 3: dup + 4: ldc #3 // String Stub! + 6: invokespecial #4 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V + 9: athrow + LineNumberTable: + line 123: 0 + LocalVariableTable: + Start Length Slot Name Signature + 0 10 0 this Landroid/view/MotionEvent; + 0 10 1 out Landroid/os/Parcel; + 0 10 2 flags I + + static {}; + Signature: ()V + flags: ACC_STATIC + Code: + stack=1, locals=0, args_size=0 + 0: aconst_null + 1: putstatic #5 // Field CREATOR:Landroid/os/Parcelable$Creator; + 4: return + LineNumberTable: + line 213: 0 +} diff --git a/base/android/jni_generator/testMultipleJNIAdditionalImport.golden b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden new file mode 100644 index 0000000..0eecb5a --- /dev/null +++ b/base/android/jni_generator/testMultipleJNIAdditionalImport.golden @@ -0,0 +1,95 @@ +// Copyright 2014 The Chromium 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 +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/foo/Foo + +#ifndef org_chromium_foo_Foo_JNI +#define org_chromium_foo_Foo_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kFooClassPath[] = "org/chromium/foo/Foo"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_Foo_clazz __attribute__((unused)) = 0; +#define Foo_clazz(env) base::android::LazyGetClass(env, kFooClassPath, &g_Foo_clazz) + +} // namespace + +// Step 2: method stubs. + +static void DoSomething(JNIEnv* env, const base::android::JavaParamRef& + jcaller, + const base::android::JavaParamRef& callback1, + const base::android::JavaParamRef& callback2); + +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(JNIEnv* + env, jclass jcaller, + jobject callback1, + jobject callback2) { + return DoSomething(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, callback1), + base::android::JavaParamRef(env, callback2)); +} + +static base::subtle::AtomicWord g_Foo_calledByNative = 0; +static void Java_Foo_calledByNative(JNIEnv* env, const + base::android::JavaRefOrBare& callback1, + const base::android::JavaRefOrBare& callback2) { + CHECK_CLAZZ(env, Foo_clazz(env), + Foo_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, Foo_clazz(env), + "calledByNative", +"(" +"Lorg/chromium/foo/Bar1$Callback;" +"Lorg/chromium/foo/Bar2$Callback;" +")" +"V", + &g_Foo_calledByNative); + + env->CallStaticVoidMethod(Foo_clazz(env), + method_id, callback1.obj(), callback2.obj()); + jni_generator::CheckException(env); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsFoo[] = { + { "nativeDoSomething", +"(" +"Lorg/chromium/foo/Bar1$Callback;" +"Lorg/chromium/foo/Bar2$Callback;" +")" +"V", reinterpret_cast(Java_org_chromium_foo_Foo_nativeDoSomething) }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsFooSize = arraysize(kMethodsFoo); + + if (env->RegisterNatives(Foo_clazz(env), + kMethodsFoo, + kMethodsFooSize) < 0) { + jni_generator::HandleRegistrationError( + env, Foo_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_foo_Foo_JNI diff --git a/base/android/jni_generator/testNatives.golden b/base/android/jni_generator/testNatives.golden new file mode 100644 index 0000000..3362c92 --- /dev/null +++ b/base/android/jni_generator/testNatives.golden @@ -0,0 +1,340 @@ +// Copyright 2014 The Chromium 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 +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) + +} // namespace + +// Step 2: method stubs. + +static jint Init(JNIEnv* env, const base::android::JavaParamRef& + jcaller); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeInit(JNIEnv* env, + jobject jcaller) { + return Init(env, base::android::JavaParamRef(env, jcaller)); +} + +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(JNIEnv* env, + jobject jcaller, + jint nativeChromeBrowserProvider) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); + return native->Destroy(env, base::android::JavaParamRef(env, + jcaller)); +} + +JNI_GENERATOR_EXPORT jlong Java_org_chromium_TestJni_nativeAddBookmark(JNIEnv* + env, jobject jcaller, + jint nativeChromeBrowserProvider, + jstring url, + jstring title, + jboolean isFolder, + jlong parentId) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmark", 0); + return native->AddBookmark(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, url), + base::android::JavaParamRef(env, title), isFolder, parentId); +} + +static base::android::ScopedJavaLocalRef GetDomainAndRegistry(JNIEnv* + env, const base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& url); + +JNI_GENERATOR_EXPORT jstring + Java_org_chromium_TestJni_nativeGetDomainAndRegistry(JNIEnv* env, jclass + jcaller, + jstring url) { + return GetDomainAndRegistry(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, url)).Release(); +} + +static void CreateHistoricalTabFromState(JNIEnv* env, const + base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& state, + jint tab_index); + +JNI_GENERATOR_EXPORT void + Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState(JNIEnv* env, + jclass jcaller, + jbyteArray state, + jint tab_index) { + return CreateHistoricalTabFromState(env, + base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, state), tab_index); +} + +static base::android::ScopedJavaLocalRef GetStateAsByteArray(JNIEnv* + env, const base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& view); + +JNI_GENERATOR_EXPORT jbyteArray + Java_org_chromium_TestJni_nativeGetStateAsByteArray(JNIEnv* env, jobject + jcaller, + jobject view) { + return GetStateAsByteArray(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, view)).Release(); +} + +static base::android::ScopedJavaLocalRef + GetAutofillProfileGUIDs(JNIEnv* env, const + base::android::JavaParamRef& jcaller); + +JNI_GENERATOR_EXPORT jobjectArray + Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs(JNIEnv* env, jclass + jcaller) { + return GetAutofillProfileGUIDs(env, base::android::JavaParamRef(env, + jcaller)).Release(); +} + +static void SetRecognitionResults(JNIEnv* env, const + base::android::JavaParamRef& jcaller, + jint sessionId, + const base::android::JavaParamRef& results); + +JNI_GENERATOR_EXPORT void + Java_org_chromium_TestJni_nativeSetRecognitionResults(JNIEnv* env, jobject + jcaller, + jint sessionId, + jobjectArray results) { + return SetRecognitionResults(env, base::android::JavaParamRef(env, + jcaller), sessionId, base::android::JavaParamRef(env, + results)); +} + +JNI_GENERATOR_EXPORT jlong + Java_org_chromium_TestJni_nativeAddBookmarkFromAPI(JNIEnv* env, jobject + jcaller, + jint nativeChromeBrowserProvider, + jstring url, + jobject created, + jobject isBookmark, + jobject date, + jbyteArray favicon, + jstring title, + jobject visits) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "AddBookmarkFromAPI", 0); + return native->AddBookmarkFromAPI(env, + base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, url), + base::android::JavaParamRef(env, created), + base::android::JavaParamRef(env, isBookmark), + base::android::JavaParamRef(env, date), + base::android::JavaParamRef(env, favicon), + base::android::JavaParamRef(env, title), + base::android::JavaParamRef(env, visits)); +} + +static jint FindAll(JNIEnv* env, const base::android::JavaParamRef& + jcaller, + const base::android::JavaParamRef& find); + +JNI_GENERATOR_EXPORT jint Java_org_chromium_TestJni_nativeFindAll(JNIEnv* env, + jobject jcaller, + jstring find) { + return FindAll(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, find)); +} + +static base::android::ScopedJavaLocalRef GetInnerClass(JNIEnv* env, + const base::android::JavaParamRef& jcaller); + +JNI_GENERATOR_EXPORT jobject + Java_org_chromium_TestJni_nativeGetInnerClass(JNIEnv* env, jclass jcaller) { + return GetInnerClass(env, base::android::JavaParamRef(env, + jcaller)).Release(); +} + +JNI_GENERATOR_EXPORT jobject Java_org_chromium_TestJni_nativeQueryBitmap(JNIEnv* + env, jobject jcaller, + jint nativeChromeBrowserProvider, + jobjectArray projection, + jstring selection, + jobjectArray selectionArgs, + jstring sortOrder) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "QueryBitmap", NULL); + return native->QueryBitmap(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, projection), + base::android::JavaParamRef(env, selection), + base::android::JavaParamRef(env, selectionArgs), + base::android::JavaParamRef(env, sortOrder)).Release(); +} + +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeGotOrientation(JNIEnv* + env, jobject jcaller, + jint nativeDataFetcherImplAndroid, + jdouble alpha, + jdouble beta, + jdouble gamma) { + DataFetcherImplAndroid* native = + reinterpret_cast(nativeDataFetcherImplAndroid); + CHECK_NATIVE_PTR(env, jcaller, native, "GotOrientation"); + return native->GotOrientation(env, base::android::JavaParamRef(env, + jcaller), alpha, beta, gamma); +} + +static base::android::ScopedJavaLocalRef + MessWithJavaException(JNIEnv* env, const + base::android::JavaParamRef& jcaller, + const base::android::JavaParamRef& e); + +JNI_GENERATOR_EXPORT jthrowable + Java_org_chromium_TestJni_nativeMessWithJavaException(JNIEnv* env, jclass + jcaller, + jthrowable e) { + return MessWithJavaException(env, base::android::JavaParamRef(env, + jcaller), base::android::JavaParamRef(env, e)).Release(); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsTestJni[] = { + { "nativeInit", +"(" +")" +"I", reinterpret_cast(Java_org_chromium_TestJni_nativeInit) }, + { "nativeDestroy", +"(" +"I" +")" +"V", reinterpret_cast(Java_org_chromium_TestJni_nativeDestroy) }, + { "nativeAddBookmark", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/String;" +"Z" +"J" +")" +"J", reinterpret_cast(Java_org_chromium_TestJni_nativeAddBookmark) }, + { "nativeGetDomainAndRegistry", +"(" +"Ljava/lang/String;" +")" +"Ljava/lang/String;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetDomainAndRegistry) + }, + { "nativeCreateHistoricalTabFromState", +"(" +"[B" +"I" +")" +"V", + reinterpret_cast(Java_org_chromium_TestJni_nativeCreateHistoricalTabFromState) + }, + { "nativeGetStateAsByteArray", +"(" +"Landroid/view/View;" +")" +"[B", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetStateAsByteArray) + }, + { "nativeGetAutofillProfileGUIDs", +"(" +")" +"[Ljava/lang/String;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetAutofillProfileGUIDs) + }, + { "nativeSetRecognitionResults", +"(" +"I" +"[Ljava/lang/String;" +")" +"V", + reinterpret_cast(Java_org_chromium_TestJni_nativeSetRecognitionResults) + }, + { "nativeAddBookmarkFromAPI", +"(" +"I" +"Ljava/lang/String;" +"Ljava/lang/Long;" +"Ljava/lang/Boolean;" +"Ljava/lang/Long;" +"[B" +"Ljava/lang/String;" +"Ljava/lang/Integer;" +")" +"J", reinterpret_cast(Java_org_chromium_TestJni_nativeAddBookmarkFromAPI) + }, + { "nativeFindAll", +"(" +"Ljava/lang/String;" +")" +"I", reinterpret_cast(Java_org_chromium_TestJni_nativeFindAll) }, + { "nativeGetInnerClass", +"(" +")" +"Lorg/chromium/example/jni_generator/SampleForTests$OnFrameAvailableListener;", + reinterpret_cast(Java_org_chromium_TestJni_nativeGetInnerClass) }, + { "nativeQueryBitmap", +"(" +"I" +"[Ljava/lang/String;" +"Ljava/lang/String;" +"[Ljava/lang/String;" +"Ljava/lang/String;" +")" +"Landroid/graphics/Bitmap;", + reinterpret_cast(Java_org_chromium_TestJni_nativeQueryBitmap) }, + { "nativeGotOrientation", +"(" +"I" +"D" +"D" +"D" +")" +"V", reinterpret_cast(Java_org_chromium_TestJni_nativeGotOrientation) }, + { "nativeMessWithJavaException", +"(" +"Ljava/lang/Throwable;" +")" +"Ljava/lang/Throwable;", + reinterpret_cast(Java_org_chromium_TestJni_nativeMessWithJavaException) + }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(TestJni_clazz(env), + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + jni_generator::HandleRegistrationError( + env, TestJni_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testNativesLong.golden b/base/android/jni_generator/testNativesLong.golden new file mode 100644 index 0000000..ec029ce --- /dev/null +++ b/base/android/jni_generator/testNativesLong.golden @@ -0,0 +1,66 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is autogenerated by +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/TestJni + +#ifndef org_chromium_TestJni_JNI +#define org_chromium_TestJni_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kTestJniClassPath[] = "org/chromium/TestJni"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_TestJni_clazz __attribute__((unused)) = 0; +#define TestJni_clazz(env) base::android::LazyGetClass(env, kTestJniClassPath, &g_TestJni_clazz) + +} // namespace + +// Step 2: method stubs. +JNI_GENERATOR_EXPORT void Java_org_chromium_TestJni_nativeDestroy(JNIEnv* env, + jobject jcaller, + jlong nativeChromeBrowserProvider) { + ChromeBrowserProvider* native = + reinterpret_cast(nativeChromeBrowserProvider); + CHECK_NATIVE_PTR(env, jcaller, native, "Destroy"); + return native->Destroy(env, base::android::JavaParamRef(env, + jcaller)); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsTestJni[] = { + { "nativeDestroy", +"(" +"J" +")" +"V", reinterpret_cast(Java_org_chromium_TestJni_nativeDestroy) }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsTestJniSize = arraysize(kMethodsTestJni); + + if (env->RegisterNatives(TestJni_clazz(env), + kMethodsTestJni, + kMethodsTestJniSize) < 0) { + jni_generator::HandleRegistrationError( + env, TestJni_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_TestJni_JNI diff --git a/base/android/jni_generator/testSingleJNIAdditionalImport.golden b/base/android/jni_generator/testSingleJNIAdditionalImport.golden new file mode 100644 index 0000000..ef618da --- /dev/null +++ b/base/android/jni_generator/testSingleJNIAdditionalImport.golden @@ -0,0 +1,89 @@ +// Copyright 2014 The Chromium 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 +// base/android/jni_generator/jni_generator.py +// For +// org/chromium/foo/Foo + +#ifndef org_chromium_foo_Foo_JNI +#define org_chromium_foo_Foo_JNI + +#include + +#include "base/android/jni_generator/jni_generator_helper.h" + +#include "base/android/jni_int_wrapper.h" + +// Step 1: forward declarations. +namespace { +const char kFooClassPath[] = "org/chromium/foo/Foo"; +// Leaking this jclass as we cannot use LazyInstance from some threads. +base::subtle::AtomicWord g_Foo_clazz __attribute__((unused)) = 0; +#define Foo_clazz(env) base::android::LazyGetClass(env, kFooClassPath, &g_Foo_clazz) + +} // namespace + +// Step 2: method stubs. + +static void DoSomething(JNIEnv* env, const base::android::JavaParamRef& + jcaller, + const base::android::JavaParamRef& callback); + +JNI_GENERATOR_EXPORT void Java_org_chromium_foo_Foo_nativeDoSomething(JNIEnv* + env, jclass jcaller, + jobject callback) { + return DoSomething(env, base::android::JavaParamRef(env, jcaller), + base::android::JavaParamRef(env, callback)); +} + +static base::subtle::AtomicWord g_Foo_calledByNative = 0; +static void Java_Foo_calledByNative(JNIEnv* env, const + base::android::JavaRefOrBare& callback) { + CHECK_CLAZZ(env, Foo_clazz(env), + Foo_clazz(env)); + jmethodID method_id = + base::android::MethodID::LazyGet< + base::android::MethodID::TYPE_STATIC>( + env, Foo_clazz(env), + "calledByNative", +"(" +"Lorg/chromium/foo/Bar$Callback;" +")" +"V", + &g_Foo_calledByNative); + + env->CallStaticVoidMethod(Foo_clazz(env), + method_id, callback.obj()); + jni_generator::CheckException(env); +} + +// Step 3: RegisterNatives. + +static const JNINativeMethod kMethodsFoo[] = { + { "nativeDoSomething", +"(" +"Lorg/chromium/foo/Bar$Callback;" +")" +"V", reinterpret_cast(Java_org_chromium_foo_Foo_nativeDoSomething) }, +}; + +static bool RegisterNativesImpl(JNIEnv* env) { + if (jni_generator::ShouldSkipJniRegistration(false)) + return true; + + const int kMethodsFooSize = arraysize(kMethodsFoo); + + if (env->RegisterNatives(Foo_clazz(env), + kMethodsFoo, + kMethodsFooSize) < 0) { + jni_generator::HandleRegistrationError( + env, Foo_clazz(env), __FILE__); + return false; + } + + return true; +} + +#endif // org_chromium_foo_Foo_JNI diff --git a/base/android/jni_int_wrapper.h b/base/android/jni_int_wrapper.h new file mode 100644 index 0000000..fa0f3d5 --- /dev/null +++ b/base/android/jni_int_wrapper.h @@ -0,0 +1,56 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_JNI_INT_WRAPPER_H_ +#define BASE_ANDROID_JNI_INT_WRAPPER_H_ + +// Wrapper used to receive int when calling Java from native. +// The wrapper disallows automatic conversion of long to int. +// This is to avoid a common anti-pattern where a Java int is used +// to receive a native pointer. Please use a Java long to receive +// native pointers, so that the code works on both 32-bit and 64-bit +// platforms. Note the wrapper allows other lossy conversions into +// jint that could be consider anti-patterns, such as from size_t. + +// Checking is only done in debugging builds. + +#ifdef NDEBUG + +typedef jint JniIntWrapper; + +// This inline is sufficiently trivial that it does not change the +// final code generated by g++. +inline jint as_jint(JniIntWrapper wrapper) { + return wrapper; +} + +#else + +class JniIntWrapper { + public: + JniIntWrapper() : i_(0) {} + JniIntWrapper(int i) : i_(i) {} + JniIntWrapper(const JniIntWrapper& ji) : i_(ji.i_) {} + template JniIntWrapper(const T& t) : i_(t) {} + jint as_jint() const { return i_; } + private: + // If you get an "is private" error at the line below it is because you used + // an implicit conversion to convert a long to an int when calling Java. + // We disallow this, as a common anti-pattern allows converting a native + // pointer (intptr_t) to a Java int. Please use a Java long to represent + // a native pointer. If you want a lossy conversion, please use an + // explicit conversion in your C++ code. Note an error is only seen when + // compiling on a 64-bit platform, as intptr_t is indistinguishable from + // int on 32-bit platforms. + JniIntWrapper(long); + jint i_; +}; + +inline jint as_jint(const JniIntWrapper& wrapper) { + return wrapper.as_jint(); +} + +#endif // NDEBUG + +#endif // BASE_ANDROID_JNI_INT_WRAPPER_H_ diff --git a/base/android/jni_string.cc b/base/android/jni_string.cc new file mode 100644 index 0000000..f28f649 --- /dev/null +++ b/base/android/jni_string.cc @@ -0,0 +1,121 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace { + +// Internal version that does not use a scoped local pointer. +jstring ConvertUTF16ToJavaStringImpl(JNIEnv* env, + const base::StringPiece16& str) { + jstring result = env->NewString(str.data(), str.length()); + base::android::CheckException(env); + return result; +} + +} // namespace + +namespace base { +namespace android { + +void ConvertJavaStringToUTF8(JNIEnv* env, jstring str, std::string* result) { + DCHECK(str); + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF8 called with null string."; + result->clear(); + return; + } + const jsize length = env->GetStringLength(str); + if (!length) { + result->clear(); + CheckException(env); + return; + } + // JNI's GetStringUTFChars() returns strings in Java "modified" UTF8, so + // instead get the String in UTF16 and convert using chromium's conversion + // function that yields plain (non Java-modified) UTF8. + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + UTF16ToUTF8(chars, length, result); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str) { + std::string result; + ConvertJavaStringToUTF8(env, str, &result); + return result; +} + +std::string ConvertJavaStringToUTF8(const JavaRef& str) { + return ConvertJavaStringToUTF8(AttachCurrentThread(), str.obj()); +} + +std::string ConvertJavaStringToUTF8(JNIEnv* env, const JavaRef& str) { + return ConvertJavaStringToUTF8(env, str.obj()); +} + +ScopedJavaLocalRef ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str) { + // JNI's NewStringUTF expects "modified" UTF8 so instead create the string + // via our own UTF16 conversion utility. + // Further, Dalvik requires the string passed into NewStringUTF() to come from + // a trusted source. We can't guarantee that all UTF8 will be sanitized before + // it gets here, so constructing via UTF16 side-steps this issue. + // (Dalvik stores strings internally as UTF16 anyway, so there shouldn't be + // a significant performance hit by doing it this way). + return ScopedJavaLocalRef(env, ConvertUTF16ToJavaStringImpl( + env, UTF8ToUTF16(str))); +} + +void ConvertJavaStringToUTF16(JNIEnv* env, jstring str, string16* result) { + DCHECK(str); + if (!str) { + LOG(WARNING) << "ConvertJavaStringToUTF16 called with null string."; + result->clear(); + return; + } + const jsize length = env->GetStringLength(str); + if (!length) { + result->clear(); + CheckException(env); + return; + } + const jchar* chars = env->GetStringChars(str, NULL); + DCHECK(chars); + // GetStringChars isn't required to NULL-terminate the strings + // it returns, so the length must be explicitly checked. + result->assign(chars, length); + env->ReleaseStringChars(str, chars); + CheckException(env); +} + +string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str) { + string16 result; + ConvertJavaStringToUTF16(env, str, &result); + return result; +} + +string16 ConvertJavaStringToUTF16(const JavaRef& str) { + return ConvertJavaStringToUTF16(AttachCurrentThread(), str.obj()); +} + +string16 ConvertJavaStringToUTF16(JNIEnv* env, const JavaRef& str) { + return ConvertJavaStringToUTF16(env, str.obj()); +} + +ScopedJavaLocalRef ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str) { + return ScopedJavaLocalRef(env, + ConvertUTF16ToJavaStringImpl(env, str)); +} + +} // namespace android +} // namespace base diff --git a/base/android/jni_string.h b/base/android/jni_string.h new file mode 100644 index 0000000..09e85f3 --- /dev/null +++ b/base/android/jni_string.h @@ -0,0 +1,49 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_JNI_STRING_H_ +#define BASE_ANDROID_JNI_STRING_H_ + +#include +#include + +#include "base/android/scoped_java_ref.h" +#include "base/base_export.h" +#include "base/strings/string_piece.h" + +namespace base { +namespace android { + +// Convert a Java string to UTF8. Returns a std string. +BASE_EXPORT void ConvertJavaStringToUTF8(JNIEnv* env, + jstring str, + std::string* result); +BASE_EXPORT std::string ConvertJavaStringToUTF8(JNIEnv* env, jstring str); +BASE_EXPORT std::string ConvertJavaStringToUTF8(const JavaRef& str); +BASE_EXPORT std::string ConvertJavaStringToUTF8(JNIEnv* env, + const JavaRef& str); + +// Convert a std string to Java string. +BASE_EXPORT ScopedJavaLocalRef ConvertUTF8ToJavaString( + JNIEnv* env, + const base::StringPiece& str); + +// Convert a Java string to UTF16. Returns a string16. +BASE_EXPORT void ConvertJavaStringToUTF16(JNIEnv* env, + jstring str, + string16* result); +BASE_EXPORT string16 ConvertJavaStringToUTF16(JNIEnv* env, jstring str); +BASE_EXPORT string16 ConvertJavaStringToUTF16(const JavaRef& str); +BASE_EXPORT string16 ConvertJavaStringToUTF16(JNIEnv* env, + const JavaRef& str); + +// Convert a string16 to a Java string. +BASE_EXPORT ScopedJavaLocalRef ConvertUTF16ToJavaString( + JNIEnv* env, + const base::StringPiece16& str); + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_JNI_STRING_H_ diff --git a/base/android/jni_string_unittest.cc b/base/android/jni_string_unittest.cc new file mode 100644 index 0000000..3da8b87 --- /dev/null +++ b/base/android/jni_string_unittest.cc @@ -0,0 +1,48 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/jni_string.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +TEST(JniString, BasicConversionsUTF8) { + const std::string kSimpleString = "SimpleTest8"; + JNIEnv* env = AttachCurrentThread(); + std::string result = + ConvertJavaStringToUTF8(ConvertUTF8ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +TEST(JniString, BasicConversionsUTF16) { + const string16 kSimpleString = UTF8ToUTF16("SimpleTest16"); + JNIEnv* env = AttachCurrentThread(); + string16 result = + ConvertJavaStringToUTF16(ConvertUTF16ToJavaString(env, kSimpleString)); + EXPECT_EQ(kSimpleString, result); +} + +TEST(JniString, EmptyConversionUTF8) { + const std::string kEmptyString = ""; + JNIEnv* env = AttachCurrentThread(); + std::string result = + ConvertJavaStringToUTF8(ConvertUTF8ToJavaString(env, kEmptyString)); + EXPECT_EQ(kEmptyString, result); +} + +TEST(JniString, EmptyConversionUTF16) { + const string16 kEmptyString = UTF8ToUTF16(""); + JNIEnv* env = AttachCurrentThread(); + string16 result = + ConvertJavaStringToUTF16(ConvertUTF16ToJavaString(env, kEmptyString)); + EXPECT_EQ(kEmptyString, result); +} + +} // namespace android +} // namespace base diff --git a/base/android/scoped_java_ref.cc b/base/android/scoped_java_ref.cc new file mode 100644 index 0000000..7d31a75 --- /dev/null +++ b/base/android/scoped_java_ref.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/logging.h" + +namespace base { +namespace android { +namespace { + +const int kDefaultLocalFrameCapacity = 16; + +} // namespace + +ScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env) : env_(env) { + int failed = env_->PushLocalFrame(kDefaultLocalFrameCapacity); + DCHECK(!failed); +} + +ScopedJavaLocalFrame::ScopedJavaLocalFrame(JNIEnv* env, int capacity) + : env_(env) { + int failed = env_->PushLocalFrame(capacity); + DCHECK(!failed); +} + +ScopedJavaLocalFrame::~ScopedJavaLocalFrame() { + env_->PopLocalFrame(nullptr); +} + +#if DCHECK_IS_ON() +// This constructor is inlined when DCHECKs are disabled; don't add anything +// else here. +JavaRef::JavaRef(JNIEnv* env, jobject obj) : obj_(obj) { + if (obj) { + DCHECK(env && env->GetObjectRefType(obj) == JNILocalRefType); + } +} +#endif + +JNIEnv* JavaRef::SetNewLocalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewLocalRef(obj); + if (obj_) + env->DeleteLocalRef(obj_); + obj_ = obj; + return env; +} + +void JavaRef::SetNewGlobalRef(JNIEnv* env, jobject obj) { + if (!env) { + env = AttachCurrentThread(); + } else { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + } + if (obj) + obj = env->NewGlobalRef(obj); + if (obj_) + env->DeleteGlobalRef(obj_); + obj_ = obj; +} + +void JavaRef::ResetLocalRef(JNIEnv* env) { + if (obj_) { + DCHECK_EQ(env, AttachCurrentThread()); // Is |env| on correct thread. + env->DeleteLocalRef(obj_); + obj_ = nullptr; + } +} + +void JavaRef::ResetGlobalRef() { + if (obj_) { + AttachCurrentThread()->DeleteGlobalRef(obj_); + obj_ = nullptr; + } +} + +jobject JavaRef::ReleaseInternal() { + jobject obj = obj_; + obj_ = nullptr; + return obj; +} + +} // namespace android +} // namespace base diff --git a/base/android/scoped_java_ref.h b/base/android/scoped_java_ref.h new file mode 100644 index 0000000..6d728e9 --- /dev/null +++ b/base/android/scoped_java_ref.h @@ -0,0 +1,301 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ANDROID_SCOPED_JAVA_REF_H_ +#define BASE_ANDROID_SCOPED_JAVA_REF_H_ + +#include +#include + +#include +#include + +#include "base/base_export.h" +#include "base/logging.h" +#include "base/macros.h" + +namespace base { +namespace android { + +// Creates a new local reference frame, in which at least a given number of +// local references can be created. Note that local references already created +// in previous local frames are still valid in the current local frame. +class BASE_EXPORT ScopedJavaLocalFrame { + public: + explicit ScopedJavaLocalFrame(JNIEnv* env); + ScopedJavaLocalFrame(JNIEnv* env, int capacity); + ~ScopedJavaLocalFrame(); + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; + + DISALLOW_COPY_AND_ASSIGN(ScopedJavaLocalFrame); +}; + +// Forward declare the generic java reference template class. +template class JavaRef; + +// Template specialization of JavaRef, which acts as the base class for all +// other JavaRef<> template types. This allows you to e.g. pass +// ScopedJavaLocalRef into a function taking const JavaRef& +template<> +class BASE_EXPORT JavaRef { + public: + // Initializes a null reference. Don't add anything else here; it's inlined. + JavaRef() : obj_(nullptr) {} + + // Allow nullptr to be converted to JavaRef. This avoids having to declare an + // empty JavaRef just to pass null to a function, and makes C++ "nullptr" and + // Java "null" equivalent. + JavaRef(std::nullptr_t) : JavaRef() {} + + // Public to allow destruction of null JavaRef objects. + // Don't add anything else here; it's inlined. + ~JavaRef() {} + + jobject obj() const { return obj_; } + + bool is_null() const { return obj_ == nullptr; } + + protected: + // Takes ownership of the |obj| reference passed; requires it to be a local + // reference type. +#if DCHECK_IS_ON() + // Implementation contains a DCHECK; implement out-of-line when DCHECK_IS_ON. + JavaRef(JNIEnv* env, jobject obj); +#else + // Don't add anything else here; it's inlined. + JavaRef(JNIEnv* env, jobject obj) : obj_(obj) {} +#endif + + void swap(JavaRef& other) { std::swap(obj_, other.obj_); } + + // The following are implementation detail convenience methods, for + // use by the sub-classes. + JNIEnv* SetNewLocalRef(JNIEnv* env, jobject obj); + void SetNewGlobalRef(JNIEnv* env, jobject obj); + void ResetLocalRef(JNIEnv* env); + void ResetGlobalRef(); + jobject ReleaseInternal(); + + private: + jobject obj_; + + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Generic base class for ScopedJavaLocalRef and ScopedJavaGlobalRef. Useful +// for allowing functions to accept a reference without having to mandate +// whether it is a local or global type. +template +class JavaRef : public JavaRef { + public: + JavaRef() {} + JavaRef(std::nullptr_t) : JavaRef(nullptr) {} + ~JavaRef() {} + + T obj() const { return static_cast(JavaRef::obj()); } + + protected: + JavaRef(JNIEnv* env, T obj) : JavaRef(env, obj) {} + + private: + DISALLOW_COPY_AND_ASSIGN(JavaRef); +}; + +// Holds a local reference to a JNI method parameter. +// Method parameters should not be deleted, and so this class exists purely to +// wrap them as a JavaRef in the JNI binding generator. Do not create +// instances manually. +template +class JavaParamRef : public JavaRef { + public: + // Assumes that |obj| is a parameter passed to a JNI method from Java. + // Does not assume ownership as parameters should not be deleted. + JavaParamRef(JNIEnv* env, T obj) : JavaRef(env, obj) {} + + // Allow nullptr to be converted to JavaParamRef. Some unit tests call JNI + // methods directly from C++ and pass null for objects which are not actually + // used by the implementation (e.g. the caller object); allow this to keep + // working. + JavaParamRef(std::nullptr_t) : JavaRef(nullptr) {} + + ~JavaParamRef() {} + + // TODO(torne): remove this cast once we're using JavaRef consistently. + // http://crbug.com/506850 + operator T() const { return JavaRef::obj(); } + + private: + DISALLOW_COPY_AND_ASSIGN(JavaParamRef); +}; + +// Holds a local reference to a Java object. The local reference is scoped +// to the lifetime of this object. +// Instances of this class may hold onto any JNIEnv passed into it until +// destroyed. Therefore, since a JNIEnv is only suitable for use on a single +// thread, objects of this class must be created, used, and destroyed, on a +// single thread. +// Therefore, this class should only be used as a stack-based object and from a +// single thread. If you wish to have the reference outlive the current +// callstack (e.g. as a class member) or you wish to pass it across threads, +// use a ScopedJavaGlobalRef instead. +template +class ScopedJavaLocalRef : public JavaRef { + public: + ScopedJavaLocalRef() : env_(nullptr) {} + ScopedJavaLocalRef(std::nullptr_t) : env_(nullptr) {} + + // Non-explicit copy constructor, to allow ScopedJavaLocalRef to be returned + // by value as this is the normal usage pattern. + ScopedJavaLocalRef(const ScopedJavaLocalRef& other) + : env_(other.env_) { + this->SetNewLocalRef(env_, other.obj()); + } + + ScopedJavaLocalRef(ScopedJavaLocalRef&& other) : env_(other.env_) { + this->swap(other); + } + + explicit ScopedJavaLocalRef(const JavaRef& other) : env_(nullptr) { + this->Reset(other); + } + + // Assumes that |obj| is a local reference to a Java object and takes + // ownership of this local reference. + // TODO(torne): this shouldn't be used outside of JNI helper functions but + // there are currently some cases where there aren't helpers for things. + ScopedJavaLocalRef(JNIEnv* env, T obj) : JavaRef(env, obj), env_(env) {} + + ~ScopedJavaLocalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaLocalRef& other) { + this->Reset(other); + } + + void operator=(ScopedJavaLocalRef&& other) { + env_ = other.env_; + this->swap(other); + } + + void Reset() { + this->ResetLocalRef(env_); + } + + void Reset(const ScopedJavaLocalRef& other) { + // We can copy over env_ here as |other| instance must be from the same + // thread as |this| local ref. (See class comment for multi-threading + // limitations, and alternatives). + this->Reset(other.env_, other.obj()); + } + + void Reset(const JavaRef& other) { + // If |env_| was not yet set (is still null) it will be attached to the + // current thread in SetNewLocalRef(). + this->Reset(env_, other.obj()); + } + + // Creates a new local reference to the Java object, unlike the constructor + // with the same parameters that takes ownership of the existing reference. + // TODO(torne): these should match as this is confusing. + void Reset(JNIEnv* env, T obj) { env_ = this->SetNewLocalRef(env, obj); } + + // Releases the local reference to the caller. The caller *must* delete the + // local reference when it is done with it. Note that calling a Java method + // is *not* a transfer of ownership and Release() should not be used. + T Release() { + return static_cast(this->ReleaseInternal()); + } + + private: + // This class is only good for use on the thread it was created on so + // it's safe to cache the non-threadsafe JNIEnv* inside this object. + JNIEnv* env_; + + // Prevent ScopedJavaLocalRef(JNIEnv*, T obj) from being used to take + // ownership of a JavaParamRef's underlying object - parameters are not + // allowed to be deleted and so should not be owned by ScopedJavaLocalRef. + // TODO(torne): this can be removed once JavaParamRef no longer has an + // implicit conversion back to T. + ScopedJavaLocalRef(JNIEnv* env, const JavaParamRef& other); +}; + +// Holds a global reference to a Java object. The global reference is scoped +// to the lifetime of this object. This class does not hold onto any JNIEnv* +// passed to it, hence it is safe to use across threads (within the constraints +// imposed by the underlying Java object that it references). +template +class ScopedJavaGlobalRef : public JavaRef { + public: + ScopedJavaGlobalRef() {} + ScopedJavaGlobalRef(std::nullptr_t) {} + + ScopedJavaGlobalRef(const ScopedJavaGlobalRef& other) { + this->Reset(other); + } + + ScopedJavaGlobalRef(ScopedJavaGlobalRef&& other) { this->swap(other); } + + ScopedJavaGlobalRef(JNIEnv* env, T obj) { this->Reset(env, obj); } + + explicit ScopedJavaGlobalRef(const JavaRef& other) { this->Reset(other); } + + ~ScopedJavaGlobalRef() { + this->Reset(); + } + + // Overloaded assignment operator defined for consistency with the implicit + // copy constructor. + void operator=(const ScopedJavaGlobalRef& other) { + this->Reset(other); + } + + void operator=(ScopedJavaGlobalRef&& other) { this->swap(other); } + + void Reset() { + this->ResetGlobalRef(); + } + + void Reset(const JavaRef& other) { this->Reset(nullptr, other.obj()); } + + void Reset(JNIEnv* env, const JavaParamRef& other) { + this->Reset(env, other.obj()); + } + + void Reset(JNIEnv* env, T obj) { this->SetNewGlobalRef(env, obj); } + + // Releases the global reference to the caller. The caller *must* delete the + // global reference when it is done with it. Note that calling a Java method + // is *not* a transfer of ownership and Release() should not be used. + T Release() { + return static_cast(this->ReleaseInternal()); + } +}; + +// Temporary type for parameters to Java functions, to allow incremental +// migration from bare jobject to JavaRef. Don't use outside JNI generator. +template +class JavaRefOrBare { + public: + JavaRefOrBare(std::nullptr_t) : obj_(nullptr) {} + JavaRefOrBare(const JavaRef& ref) : obj_(ref.obj()) {} + JavaRefOrBare(T obj) : obj_(obj) {} + T obj() const { return obj_; } + + private: + T obj_; + + DISALLOW_COPY_AND_ASSIGN(JavaRefOrBare); +}; + +} // namespace android +} // namespace base + +#endif // BASE_ANDROID_SCOPED_JAVA_REF_H_ diff --git a/base/android/scoped_java_ref_unittest.cc b/base/android/scoped_java_ref_unittest.cc new file mode 100644 index 0000000..99d035b --- /dev/null +++ b/base/android/scoped_java_ref_unittest.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/android/scoped_java_ref.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +namespace android { + +namespace { +int g_local_refs = 0; +int g_global_refs = 0; + +const JNINativeInterface* g_previous_functions; + +jobject NewGlobalRef(JNIEnv* env, jobject obj) { + ++g_global_refs; + return g_previous_functions->NewGlobalRef(env, obj); +} + +void DeleteGlobalRef(JNIEnv* env, jobject obj) { + --g_global_refs; + return g_previous_functions->DeleteGlobalRef(env, obj); +} + +jobject NewLocalRef(JNIEnv* env, jobject obj) { + ++g_local_refs; + return g_previous_functions->NewLocalRef(env, obj); +} + +void DeleteLocalRef(JNIEnv* env, jobject obj) { + --g_local_refs; + return g_previous_functions->DeleteLocalRef(env, obj); +} +} // namespace + +class ScopedJavaRefTest : public testing::Test { + protected: + void SetUp() override { + g_local_refs = 0; + g_global_refs = 0; + JNIEnv* env = AttachCurrentThread(); + g_previous_functions = env->functions; + hooked_functions = *g_previous_functions; + env->functions = &hooked_functions; + // We inject our own functions in JNINativeInterface so we can keep track + // of the reference counting ourselves. + hooked_functions.NewGlobalRef = &NewGlobalRef; + hooked_functions.DeleteGlobalRef = &DeleteGlobalRef; + hooked_functions.NewLocalRef = &NewLocalRef; + hooked_functions.DeleteLocalRef = &DeleteLocalRef; + } + + void TearDown() override { + JNIEnv* env = AttachCurrentThread(); + env->functions = g_previous_functions; + } + // From JellyBean release, the instance of this struct provided in JNIEnv is + // read-only, so we deep copy it to allow individual functions to be hooked. + JNINativeInterface hooked_functions; +}; + +// The main purpose of this is testing the various conversions compile. +TEST_F(ScopedJavaRefTest, Conversions) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef str = ConvertUTF8ToJavaString(env, "string"); + ScopedJavaGlobalRef global(str); + { + ScopedJavaGlobalRef global_obj(str); + ScopedJavaLocalRef local_obj(global); + const JavaRef& obj_ref1(str); + const JavaRef& obj_ref2(global); + EXPECT_TRUE(env->IsSameObject(obj_ref1.obj(), obj_ref2.obj())); + EXPECT_TRUE(env->IsSameObject(global_obj.obj(), obj_ref2.obj())); + } + global.Reset(str); + const JavaRef& str_ref = str; + EXPECT_EQ("string", ConvertJavaStringToUTF8(str_ref)); + str.Reset(); +} + +TEST_F(ScopedJavaRefTest, RefCounts) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef str; + // The ConvertJavaStringToUTF8 below creates a new string that would normally + // return a local ref. We simulate that by starting the g_local_refs count at + // 1. + g_local_refs = 1; + str.Reset(ConvertUTF8ToJavaString(env, "string")); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(0, g_global_refs); + { + ScopedJavaGlobalRef global_str(str); + ScopedJavaGlobalRef global_obj(global_str); + EXPECT_EQ(1, g_local_refs); + EXPECT_EQ(2, g_global_refs); + + ScopedJavaLocalRef str2(env, str.Release()); + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef str3(str2); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef str4((ScopedJavaLocalRef(str2))); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + { + ScopedJavaLocalRef str5; + str5 = ScopedJavaLocalRef(str2); + EXPECT_EQ(2, g_local_refs); + } + EXPECT_EQ(1, g_local_refs); + str2.Reset(); + EXPECT_EQ(0, g_local_refs); + global_str.Reset(); + EXPECT_EQ(1, g_global_refs); + ScopedJavaGlobalRef global_obj2(global_obj); + EXPECT_EQ(2, g_global_refs); + } + + EXPECT_EQ(0, g_local_refs); + EXPECT_EQ(0, g_global_refs); +} + +} // namespace android +} // namespace base diff --git a/build/android/gyp/util/__init__.py b/build/android/gyp/util/__init__.py new file mode 100644 index 0000000..727e987 --- /dev/null +++ b/build/android/gyp/util/__init__.py @@ -0,0 +1,4 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + diff --git a/build/android/gyp/util/build_utils.py b/build/android/gyp/util/build_utils.py new file mode 100644 index 0000000..abd2dfc --- /dev/null +++ b/build/android/gyp/util/build_utils.py @@ -0,0 +1,589 @@ +# 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 ast +import contextlib +import fnmatch +import json +import os +import pipes +import re +import shlex +import shutil +import stat +import subprocess +import sys +import tempfile +import zipfile + +# Some clients do not add //build/android/gyp to PYTHONPATH. +import md5_check # pylint: disable=relative-import + +# pylib conflicts with mojo/public/tools/bindings/pylib. Prioritize +# build/android/pylib. +# PYTHONPATH wouldn't help in this case, because soong put source files under +# temp directory for each build, so the abspath is unknown until the +# execution. +# sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) +from pylib.constants import host_paths + +sys.path.append(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir)) +import gn_helpers + +COLORAMA_ROOT = os.path.join(host_paths.DIR_SOURCE_ROOT, + 'third_party', 'colorama', 'src') +# aapt should ignore OWNERS files in addition the default ignore pattern. +AAPT_IGNORE_PATTERN = ('!OWNERS:!.svn:!.git:!.ds_store:!*.scc:.*:

_*:' + + '!CVS:!thumbs.db:!picasa.ini:!*~:!*.d.stamp') +HERMETIC_TIMESTAMP = (2001, 1, 1, 0, 0, 0) +_HERMETIC_FILE_ATTR = (0644 << 16L) + + +@contextlib.contextmanager +def TempDir(): + dirname = tempfile.mkdtemp() + try: + yield dirname + finally: + shutil.rmtree(dirname) + + +def MakeDirectory(dir_path): + try: + os.makedirs(dir_path) + except OSError: + pass + + +def DeleteDirectory(dir_path): + if os.path.exists(dir_path): + shutil.rmtree(dir_path) + + +def Touch(path, fail_if_missing=False): + if fail_if_missing and not os.path.exists(path): + raise Exception(path + ' doesn\'t exist.') + + MakeDirectory(os.path.dirname(path)) + with open(path, 'a'): + os.utime(path, None) + + +def FindInDirectory(directory, filename_filter): + files = [] + for root, _dirnames, filenames in os.walk(directory): + matched_files = fnmatch.filter(filenames, filename_filter) + files.extend((os.path.join(root, f) for f in matched_files)) + return files + + +def FindInDirectories(directories, filename_filter): + all_files = [] + for directory in directories: + all_files.extend(FindInDirectory(directory, filename_filter)) + return all_files + + +def ParseGnList(gn_string): + """Converts a command-line parameter into a list. + + If the input starts with a '[' it is assumed to be a GN-formatted list and + it will be parsed accordingly. When empty an empty list will be returned. + Otherwise, the parameter will be treated as a single raw string (not + GN-formatted in that it's not assumed to have literal quotes that must be + removed) and a list will be returned containing that string. + + The common use for this behavior is in the Android build where things can + take lists of @FileArg references that are expanded via ExpandFileArgs. + """ + if gn_string.startswith('['): + parser = gn_helpers.GNValueParser(gn_string) + return parser.ParseList() + if len(gn_string): + return [ gn_string ] + return [] + + +def CheckOptions(options, parser, required=None): + if not required: + return + for option_name in required: + if getattr(options, option_name) is None: + parser.error('--%s is required' % option_name.replace('_', '-')) + + +def WriteJson(obj, path, only_if_changed=False): + old_dump = None + if os.path.exists(path): + with open(path, 'r') as oldfile: + old_dump = oldfile.read() + + new_dump = json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': ')) + + if not only_if_changed or old_dump != new_dump: + with open(path, 'w') as outfile: + outfile.write(new_dump) + + +def ReadJson(path): + with open(path, 'r') as jsonfile: + return json.load(jsonfile) + + +class CalledProcessError(Exception): + """This exception is raised when the process run by CheckOutput + exits with a non-zero exit code.""" + + def __init__(self, cwd, args, output): + super(CalledProcessError, self).__init__() + self.cwd = cwd + self.args = args + self.output = output + + def __str__(self): + # A user should be able to simply copy and paste the command that failed + # into their shell. + copyable_command = '( cd {}; {} )'.format(os.path.abspath(self.cwd), + ' '.join(map(pipes.quote, self.args))) + return 'Command failed: {}\n{}'.format(copyable_command, self.output) + + +# This can be used in most cases like subprocess.check_output(). The output, +# particularly when the command fails, better highlights the command's failure. +# If the command fails, raises a build_utils.CalledProcessError. +def CheckOutput(args, cwd=None, env=None, + print_stdout=False, print_stderr=True, + stdout_filter=None, + stderr_filter=None, + fail_func=lambda returncode, stderr: returncode != 0): + if not cwd: + cwd = os.getcwd() + + child = subprocess.Popen(args, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env) + stdout, stderr = child.communicate() + + if stdout_filter is not None: + stdout = stdout_filter(stdout) + + if stderr_filter is not None: + stderr = stderr_filter(stderr) + + if fail_func(child.returncode, stderr): + raise CalledProcessError(cwd, args, stdout + stderr) + + if print_stdout: + sys.stdout.write(stdout) + if print_stderr: + sys.stderr.write(stderr) + + return stdout + + +def GetModifiedTime(path): + # For a symlink, the modified time should be the greater of the link's + # modified time and the modified time of the target. + return max(os.lstat(path).st_mtime, os.stat(path).st_mtime) + + +def IsTimeStale(output, inputs): + if not os.path.exists(output): + return True + + output_time = GetModifiedTime(output) + for i in inputs: + if GetModifiedTime(i) > output_time: + return True + return False + + +def IsDeviceReady(): + device_state = CheckOutput(['adb', 'get-state']) + return device_state.strip() == 'device' + + +def CheckZipPath(name): + if os.path.normpath(name) != name: + raise Exception('Non-canonical zip path: %s' % name) + if os.path.isabs(name): + raise Exception('Absolute zip path: %s' % name) + + +def IsSymlink(zip_file, name): + zi = zip_file.getinfo(name) + + # The two high-order bytes of ZipInfo.external_attr represent + # UNIX permissions and file type bits. + return stat.S_ISLNK(zi.external_attr >> 16L) + + +def ExtractAll(zip_path, path=None, no_clobber=True, pattern=None, + predicate=None): + if path is None: + path = os.getcwd() + elif not os.path.exists(path): + MakeDirectory(path) + + if not zipfile.is_zipfile(zip_path): + raise Exception('Invalid zip file: %s' % zip_path) + + extracted = [] + with zipfile.ZipFile(zip_path) as z: + for name in z.namelist(): + if name.endswith('/'): + continue + if pattern is not None: + if not fnmatch.fnmatch(name, pattern): + continue + if predicate and not predicate(name): + continue + CheckZipPath(name) + if no_clobber: + output_path = os.path.join(path, name) + if os.path.exists(output_path): + raise Exception( + 'Path already exists from zip: %s %s %s' + % (zip_path, name, output_path)) + if IsSymlink(z, name): + dest = os.path.join(path, name) + MakeDirectory(os.path.dirname(dest)) + os.symlink(z.read(name), dest) + extracted.append(dest) + else: + z.extract(name, path) + extracted.append(os.path.join(path, name)) + + return extracted + + +def AddToZipHermetic(zip_file, zip_path, src_path=None, data=None, + compress=None): + """Adds a file to the given ZipFile with a hard-coded modified time. + + Args: + zip_file: ZipFile instance to add the file to. + zip_path: Destination path within the zip file. + src_path: Path of the source file. Mutually exclusive with |data|. + data: File data as a string. + compress: Whether to enable compression. Default is take from ZipFile + constructor. + """ + assert (src_path is None) != (data is None), ( + '|src_path| and |data| are mutually exclusive.') + CheckZipPath(zip_path) + zipinfo = zipfile.ZipInfo(filename=zip_path, date_time=HERMETIC_TIMESTAMP) + zipinfo.external_attr = _HERMETIC_FILE_ATTR + + if src_path and os.path.islink(src_path): + zipinfo.filename = zip_path + zipinfo.external_attr |= stat.S_IFLNK << 16L # mark as a symlink + zip_file.writestr(zipinfo, os.readlink(src_path)) + return + + if src_path: + with file(src_path) as f: + data = f.read() + + # zipfile will deflate even when it makes the file bigger. To avoid + # growing files, disable compression at an arbitrary cut off point. + if len(data) < 16: + compress = False + + # None converts to ZIP_STORED, when passed explicitly rather than the + # default passed to the ZipFile constructor. + compress_type = zip_file.compression + if compress is not None: + compress_type = zipfile.ZIP_DEFLATED if compress else zipfile.ZIP_STORED + zip_file.writestr(zipinfo, data, compress_type) + + +def DoZip(inputs, output, base_dir=None): + """Creates a zip file from a list of files. + + Args: + inputs: A list of paths to zip, or a list of (zip_path, fs_path) tuples. + output: Destination .zip file. + base_dir: Prefix to strip from inputs. + """ + input_tuples = [] + for tup in inputs: + if isinstance(tup, basestring): + tup = (os.path.relpath(tup, base_dir), tup) + input_tuples.append(tup) + + # Sort by zip path to ensure stable zip ordering. + input_tuples.sort(key=lambda tup: tup[0]) + with zipfile.ZipFile(output, 'w') as outfile: + for zip_path, fs_path in input_tuples: + AddToZipHermetic(outfile, zip_path, src_path=fs_path) + + +def ZipDir(output, base_dir): + """Creates a zip file from a directory.""" + inputs = [] + for root, _, files in os.walk(base_dir): + for f in files: + inputs.append(os.path.join(root, f)) + DoZip(inputs, output, base_dir) + + +def MatchesGlob(path, filters): + """Returns whether the given path matches any of the given glob patterns.""" + return filters and any(fnmatch.fnmatch(path, f) for f in filters) + + +def MergeZips(output, inputs, exclude_patterns=None, path_transform=None): + path_transform = path_transform or (lambda p, z: p) + added_names = set() + + output_is_already_open = not isinstance(output, basestring) + if output_is_already_open: + assert isinstance(output, zipfile.ZipFile) + out_zip = output + else: + out_zip = zipfile.ZipFile(output, 'w') + + try: + for in_file in inputs: + with zipfile.ZipFile(in_file, 'r') as in_zip: + in_zip._expected_crc = None + for info in in_zip.infolist(): + # Ignore directories. + if info.filename[-1] == '/': + continue + dst_name = path_transform(info.filename, in_file) + already_added = dst_name in added_names + if not already_added and not MatchesGlob(dst_name, exclude_patterns): + AddToZipHermetic(out_zip, dst_name, data=in_zip.read(info), + compress=info.compress_type != zipfile.ZIP_STORED) + added_names.add(dst_name) + finally: + if not output_is_already_open: + out_zip.close() + + +def PrintWarning(message): + print 'WARNING: ' + message + + +def PrintBigWarning(message): + print '***** ' * 8 + PrintWarning(message) + print '***** ' * 8 + + +def GetSortedTransitiveDependencies(top, deps_func): + """Gets the list of all transitive dependencies in sorted order. + + There should be no cycles in the dependency graph. + + Args: + top: a list of the top level nodes + deps_func: A function that takes a node and returns its direct dependencies. + Returns: + A list of all transitive dependencies of nodes in top, in order (a node will + appear in the list at a higher index than all of its dependencies). + """ + def Node(dep): + return (dep, deps_func(dep)) + + # First: find all deps + unchecked_deps = list(top) + all_deps = set(top) + while unchecked_deps: + dep = unchecked_deps.pop() + new_deps = deps_func(dep).difference(all_deps) + unchecked_deps.extend(new_deps) + all_deps = all_deps.union(new_deps) + + # Then: simple, slow topological sort. + sorted_deps = [] + unsorted_deps = dict(map(Node, all_deps)) + while unsorted_deps: + for library, dependencies in unsorted_deps.items(): + if not dependencies.intersection(unsorted_deps.keys()): + sorted_deps.append(library) + del unsorted_deps[library] + + return sorted_deps + + +def GetPythonDependencies(): + """Gets the paths of imported non-system python modules. + + A path is assumed to be a "system" import if it is outside of chromium's + src/. The paths will be relative to the current directory. + """ + module_paths = GetModulePaths() + + abs_module_paths = map(os.path.abspath, module_paths) + + assert os.path.isabs(host_paths.DIR_SOURCE_ROOT) + non_system_module_paths = [ + p for p in abs_module_paths if p.startswith(host_paths.DIR_SOURCE_ROOT)] + def ConvertPycToPy(s): + if s.endswith('.pyc'): + return s[:-1] + return s + + non_system_module_paths = map(ConvertPycToPy, non_system_module_paths) + non_system_module_paths = map(os.path.relpath, non_system_module_paths) + return sorted(set(non_system_module_paths)) + + +def GetModulePaths(): + """Returns the paths to all of the modules in sys.modules.""" + ForceLazyModulesToLoad() + return (m.__file__ for m in sys.modules.itervalues() + if m is not None and hasattr(m, '__file__')) + + +def ForceLazyModulesToLoad(): + """Forces any lazily imported modules to fully load themselves. + + Inspecting the modules' __file__ attribute causes lazily imported modules + (e.g. from email) to get fully imported and update sys.modules. Iterate + over the values until sys.modules stabilizes so that no modules are missed. + """ + while True: + num_modules_before = len(sys.modules.keys()) + for m in sys.modules.values(): + if m is not None and hasattr(m, '__file__'): + _ = m.__file__ + num_modules_after = len(sys.modules.keys()) + if num_modules_before == num_modules_after: + break + + +def AddDepfileOption(parser): + # TODO(agrieve): Get rid of this once we've moved to argparse. + if hasattr(parser, 'add_option'): + func = parser.add_option + else: + func = parser.add_argument + func('--depfile', + help='Path to depfile (refer to `gn help depfile`)') + + +def WriteDepfile(depfile_path, first_gn_output, inputs=None, add_pydeps=True): + assert depfile_path != first_gn_output # http://crbug.com/646165 + inputs = inputs or [] + if add_pydeps: + inputs = GetPythonDependencies() + inputs + MakeDirectory(os.path.dirname(depfile_path)) + # Ninja does not support multiple outputs in depfiles. + with open(depfile_path, 'w') as depfile: + depfile.write(first_gn_output.replace(' ', '\\ ')) + depfile.write(': ') + depfile.write(' '.join(i.replace(' ', '\\ ') for i in inputs)) + depfile.write('\n') + + +def ExpandFileArgs(args): + """Replaces file-arg placeholders in args. + + These placeholders have the form: + @FileArg(filename:key1:key2:...:keyn) + + The value of such a placeholder is calculated by reading 'filename' as json. + And then extracting the value at [key1][key2]...[keyn]. + + Note: This intentionally does not return the list of files that appear in such + placeholders. An action that uses file-args *must* know the paths of those + files prior to the parsing of the arguments (typically by explicitly listing + them in the action's inputs in build files). + """ + new_args = list(args) + file_jsons = dict() + r = re.compile('@FileArg\((.*?)\)') + for i, arg in enumerate(args): + match = r.search(arg) + if not match: + continue + + if match.end() != len(arg): + raise Exception('Unexpected characters after FileArg: ' + arg) + + lookup_path = match.group(1).split(':') + file_path = lookup_path[0] + if not file_path in file_jsons: + file_jsons[file_path] = ReadJson(file_path) + + expansion = file_jsons[file_path] + for k in lookup_path[1:]: + expansion = expansion[k] + + # This should match ParseGNList. The output is either a GN-formatted list + # or a literal (with no quotes). + if isinstance(expansion, list): + new_args[i] = arg[:match.start()] + gn_helpers.ToGNString(expansion) + else: + new_args[i] = arg[:match.start()] + str(expansion) + + return new_args + + +def ReadSourcesList(sources_list_file_name): + """Reads a GN-written file containing list of file names and returns a list. + + Note that this function should not be used to parse response files. + """ + with open(sources_list_file_name) as f: + return [file_name.strip() for file_name in f] + + +def CallAndWriteDepfileIfStale(function, options, record_path=None, + input_paths=None, input_strings=None, + output_paths=None, force=False, + pass_changes=False, + depfile_deps=None): + """Wraps md5_check.CallAndRecordIfStale() and also writes dep & stamp files. + + Depfiles and stamp files are automatically added to output_paths when present + in the |options| argument. They are then created after |function| is called. + + By default, only python dependencies are added to the depfile. If there are + other input paths that are not captured by GN deps, then they should be listed + in depfile_deps. It's important to write paths to the depfile that are already + captured by GN deps since GN args can cause GN deps to change, and such + changes are not immediately reflected in depfiles (http://crbug.com/589311). + """ + if not output_paths: + raise Exception('At least one output_path must be specified.') + input_paths = list(input_paths or []) + input_strings = list(input_strings or []) + output_paths = list(output_paths or []) + + python_deps = None + if hasattr(options, 'depfile') and options.depfile: + python_deps = GetPythonDependencies() + input_paths += python_deps + output_paths += [options.depfile] + + stamp_file = hasattr(options, 'stamp') and options.stamp + if stamp_file: + output_paths += [stamp_file] + + def on_stale_md5(changes): + args = (changes,) if pass_changes else () + function(*args) + if python_deps is not None: + all_depfile_deps = list(python_deps) + if depfile_deps: + all_depfile_deps.extend(depfile_deps) + WriteDepfile(options.depfile, output_paths[0], all_depfile_deps, + add_pydeps=False) + if stamp_file: + Touch(stamp_file) + + md5_check.CallAndRecordIfStale( + on_stale_md5, + record_path=record_path, + input_paths=input_paths, + input_strings=input_strings, + output_paths=output_paths, + force=force, + pass_changes=True) diff --git a/build/android/gyp/util/md5_check.py b/build/android/gyp/util/md5_check.py new file mode 100644 index 0000000..7659124 --- /dev/null +++ b/build/android/gyp/util/md5_check.py @@ -0,0 +1,410 @@ +# 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 difflib +import hashlib +import itertools +import json +import os +import sys +import zipfile + + +# When set and a difference is detected, a diff of what changed is printed. +PRINT_EXPLANATIONS = int(os.environ.get('PRINT_BUILD_EXPLANATIONS', 0)) + +# An escape hatch that causes all targets to be rebuilt. +_FORCE_REBUILD = int(os.environ.get('FORCE_REBUILD', 0)) + + +def CallAndRecordIfStale( + function, record_path=None, input_paths=None, input_strings=None, + output_paths=None, force=False, pass_changes=False): + """Calls function if outputs are stale. + + Outputs are considered stale if: + - any output_paths are missing, or + - the contents of any file within input_paths has changed, or + - the contents of input_strings has changed. + + To debug which files are out-of-date, set the environment variable: + PRINT_MD5_DIFFS=1 + + Args: + function: The function to call. + record_path: Path to record metadata. + Defaults to output_paths[0] + '.md5.stamp' + input_paths: List of paths to calcualte an md5 sum on. + input_strings: List of strings to record verbatim. + output_paths: List of output paths. + force: Whether to treat outputs as missing regardless of whether they + actually are. + pass_changes: Whether to pass a Changes instance to |function|. + """ + assert record_path or output_paths + input_paths = input_paths or [] + input_strings = input_strings or [] + output_paths = output_paths or [] + record_path = record_path or output_paths[0] + '.md5.stamp' + + assert record_path.endswith('.stamp'), ( + 'record paths must end in \'.stamp\' so that they are easy to find ' + 'and delete') + + new_metadata = _Metadata() + new_metadata.AddStrings(input_strings) + + for path in input_paths: + if _IsZipFile(path): + entries = _ExtractZipEntries(path) + new_metadata.AddZipFile(path, entries) + else: + new_metadata.AddFile(path, _Md5ForPath(path)) + + old_metadata = None + force = force or _FORCE_REBUILD + missing_outputs = [x for x in output_paths if force or not os.path.exists(x)] + # When outputs are missing, don't bother gathering change information. + if not missing_outputs and os.path.exists(record_path): + with open(record_path, 'r') as jsonfile: + try: + old_metadata = _Metadata.FromFile(jsonfile) + except: # pylint: disable=bare-except + pass # Not yet using new file format. + + changes = Changes(old_metadata, new_metadata, force, missing_outputs) + if not changes.HasChanges(): + return + + if PRINT_EXPLANATIONS: + print '=' * 80 + print 'Target is stale: %s' % record_path + print changes.DescribeDifference() + print '=' * 80 + + args = (changes,) if pass_changes else () + function(*args) + + with open(record_path, 'w') as f: + new_metadata.ToFile(f) + + +class Changes(object): + """Provides and API for querying what changed between runs.""" + + def __init__(self, old_metadata, new_metadata, force, missing_outputs): + self.old_metadata = old_metadata + self.new_metadata = new_metadata + self.force = force + self.missing_outputs = missing_outputs + + def _GetOldTag(self, path, subpath=None): + return self.old_metadata and self.old_metadata.GetTag(path, subpath) + + def HasChanges(self): + """Returns whether any changes exist.""" + return (self.force or + not self.old_metadata or + self.old_metadata.StringsMd5() != self.new_metadata.StringsMd5() or + self.old_metadata.FilesMd5() != self.new_metadata.FilesMd5()) + + def AddedOrModifiedOnly(self): + """Returns whether the only changes were from added or modified (sub)files. + + No missing outputs, no removed paths/subpaths. + """ + if (self.force or + not self.old_metadata or + self.old_metadata.StringsMd5() != self.new_metadata.StringsMd5()): + return False + if any(self.IterRemovedPaths()): + return False + for path in self.IterModifiedPaths(): + if any(self.IterRemovedSubpaths(path)): + return False + return True + + def IterAllPaths(self): + """Generator for paths.""" + return self.new_metadata.IterPaths(); + + def IterAllSubpaths(self, path): + """Generator for subpaths.""" + return self.new_metadata.IterSubpaths(path); + + def IterAddedPaths(self): + """Generator for paths that were added.""" + for path in self.new_metadata.IterPaths(): + if self._GetOldTag(path) is None: + yield path + + def IterAddedSubpaths(self, path): + """Generator for paths that were added within the given zip file.""" + for subpath in self.new_metadata.IterSubpaths(path): + if self._GetOldTag(path, subpath) is None: + yield subpath + + def IterRemovedPaths(self): + """Generator for paths that were removed.""" + if self.old_metadata: + for path in self.old_metadata.IterPaths(): + if self.new_metadata.GetTag(path) is None: + yield path + + def IterRemovedSubpaths(self, path): + """Generator for paths that were removed within the given zip file.""" + if self.old_metadata: + for subpath in self.old_metadata.IterSubpaths(path): + if self.new_metadata.GetTag(path, subpath) is None: + yield subpath + + def IterModifiedPaths(self): + """Generator for paths whose contents have changed.""" + for path in self.new_metadata.IterPaths(): + old_tag = self._GetOldTag(path) + new_tag = self.new_metadata.GetTag(path) + if old_tag is not None and old_tag != new_tag: + yield path + + def IterModifiedSubpaths(self, path): + """Generator for paths within a zip file whose contents have changed.""" + for subpath in self.new_metadata.IterSubpaths(path): + old_tag = self._GetOldTag(path, subpath) + new_tag = self.new_metadata.GetTag(path, subpath) + if old_tag is not None and old_tag != new_tag: + yield subpath + + def IterChangedPaths(self): + """Generator for all changed paths (added/removed/modified).""" + return itertools.chain(self.IterRemovedPaths(), + self.IterModifiedPaths(), + self.IterAddedPaths()) + + def IterChangedSubpaths(self, path): + """Generator for paths within a zip that were added/removed/modified.""" + return itertools.chain(self.IterRemovedSubpaths(path), + self.IterModifiedSubpaths(path), + self.IterAddedSubpaths(path)) + + def DescribeDifference(self): + """Returns a human-readable description of what changed.""" + if self.force: + return 'force=True' + elif self.missing_outputs: + return 'Outputs do not exist:\n ' + '\n '.join(self.missing_outputs) + elif self.old_metadata is None: + return 'Previous stamp file not found.' + + if self.old_metadata.StringsMd5() != self.new_metadata.StringsMd5(): + ndiff = difflib.ndiff(self.old_metadata.GetStrings(), + self.new_metadata.GetStrings()) + changed = [s for s in ndiff if not s.startswith(' ')] + return 'Input strings changed:\n ' + '\n '.join(changed) + + if self.old_metadata.FilesMd5() == self.new_metadata.FilesMd5(): + return "There's no difference." + + lines = [] + lines.extend('Added: ' + p for p in self.IterAddedPaths()) + lines.extend('Removed: ' + p for p in self.IterRemovedPaths()) + for path in self.IterModifiedPaths(): + lines.append('Modified: ' + path) + lines.extend(' -> Subpath added: ' + p + for p in self.IterAddedSubpaths(path)) + lines.extend(' -> Subpath removed: ' + p + for p in self.IterRemovedSubpaths(path)) + lines.extend(' -> Subpath modified: ' + p + for p in self.IterModifiedSubpaths(path)) + if lines: + return 'Input files changed:\n ' + '\n '.join(lines) + return 'I have no idea what changed (there is a bug).' + + +class _Metadata(object): + """Data model for tracking change metadata.""" + # Schema: + # { + # "files-md5": "VALUE", + # "strings-md5": "VALUE", + # "input-files": [ + # { + # "path": "path.jar", + # "tag": "{MD5 of entries}", + # "entries": [ + # { "path": "org/chromium/base/Foo.class", "tag": "{CRC32}" }, ... + # ] + # }, { + # "path": "path.txt", + # "tag": "{MD5}", + # } + # ], + # "input-strings": ["a", "b", ...], + # } + def __init__(self): + self._files_md5 = None + self._strings_md5 = None + self._files = [] + self._strings = [] + # Map of (path, subpath) -> entry. Created upon first call to _GetEntry(). + self._file_map = None + + @classmethod + def FromFile(cls, fileobj): + """Returns a _Metadata initialized from a file object.""" + ret = cls() + obj = json.load(fileobj) + ret._files_md5 = obj['files-md5'] + ret._strings_md5 = obj['strings-md5'] + ret._files = obj['input-files'] + ret._strings = obj['input-strings'] + return ret + + def ToFile(self, fileobj): + """Serializes metadata to the given file object.""" + obj = { + "files-md5": self.FilesMd5(), + "strings-md5": self.StringsMd5(), + "input-files": self._files, + "input-strings": self._strings, + } + json.dump(obj, fileobj, indent=2) + + def _AssertNotQueried(self): + assert self._files_md5 is None + assert self._strings_md5 is None + assert self._file_map is None + + def AddStrings(self, values): + self._AssertNotQueried() + self._strings.extend(str(v) for v in values) + + def AddFile(self, path, tag): + """Adds metadata for a non-zip file. + + Args: + path: Path to the file. + tag: A short string representative of the file contents. + """ + self._AssertNotQueried() + self._files.append({ + 'path': path, + 'tag': tag, + }) + + def AddZipFile(self, path, entries): + """Adds metadata for a zip file. + + Args: + path: Path to the file. + entries: List of (subpath, tag) tuples for entries within the zip. + """ + self._AssertNotQueried() + tag = _ComputeInlineMd5(itertools.chain((e[0] for e in entries), + (e[1] for e in entries))) + self._files.append({ + 'path': path, + 'tag': tag, + 'entries': [{"path": e[0], "tag": e[1]} for e in entries], + }) + + def GetStrings(self): + """Returns the list of input strings.""" + return self._strings + + def FilesMd5(self): + """Lazily computes and returns the aggregate md5 of input files.""" + if self._files_md5 is None: + # Omit paths from md5 since temporary files have random names. + self._files_md5 = _ComputeInlineMd5( + self.GetTag(p) for p in sorted(self.IterPaths())) + return self._files_md5 + + def StringsMd5(self): + """Lazily computes and returns the aggregate md5 of input strings.""" + if self._strings_md5 is None: + self._strings_md5 = _ComputeInlineMd5(self._strings) + return self._strings_md5 + + def _GetEntry(self, path, subpath=None): + """Returns the JSON entry for the given path / subpath.""" + if self._file_map is None: + self._file_map = {} + for entry in self._files: + self._file_map[(entry['path'], None)] = entry + for subentry in entry.get('entries', ()): + self._file_map[(entry['path'], subentry['path'])] = subentry + return self._file_map.get((path, subpath)) + + def GetTag(self, path, subpath=None): + """Returns the tag for the given path / subpath.""" + ret = self._GetEntry(path, subpath) + return ret and ret['tag'] + + def IterPaths(self): + """Returns a generator for all top-level paths.""" + return (e['path'] for e in self._files) + + def IterSubpaths(self, path): + """Returns a generator for all subpaths in the given zip. + + If the given path is not a zip file or doesn't exist, returns an empty + iterable. + """ + outer_entry = self._GetEntry(path) + if not outer_entry: + return () + subentries = outer_entry.get('entries', []) + return (entry['path'] for entry in subentries) + + +def _UpdateMd5ForFile(md5, path, block_size=2**16): + with open(path, 'rb') as infile: + while True: + data = infile.read(block_size) + if not data: + break + md5.update(data) + + +def _UpdateMd5ForDirectory(md5, dir_path): + for root, _, files in os.walk(dir_path): + for f in files: + _UpdateMd5ForFile(md5, os.path.join(root, f)) + + +def _Md5ForPath(path): + md5 = hashlib.md5() + if os.path.isdir(path): + _UpdateMd5ForDirectory(md5, path) + else: + _UpdateMd5ForFile(md5, path) + return md5.hexdigest() + + +def _ComputeInlineMd5(iterable): + """Computes the md5 of the concatenated parameters.""" + md5 = hashlib.md5() + for item in iterable: + md5.update(str(item)) + return md5.hexdigest() + + +def _IsZipFile(path): + """Returns whether to treat the given file as a zip file.""" + # ijar doesn't set the CRC32 field. + if path.endswith('.interface.jar'): + return False + return path[-4:] in ('.zip', '.apk', '.jar') or path.endswith('.srcjar') + + +def _ExtractZipEntries(path): + """Returns a list of (path, CRC32) of all files within |path|.""" + entries = [] + with zipfile.ZipFile(path) as zip_file: + for zip_info in zip_file.infolist(): + # Skip directories and empty files. + if zip_info.CRC: + entries.append( + (zip_info.filename, zip_info.CRC + zip_info.compress_type)) + return entries diff --git a/build/android/pylib/__init__.py b/build/android/pylib/__init__.py new file mode 100644 index 0000000..b93eb4f --- /dev/null +++ b/build/android/pylib/__init__.py @@ -0,0 +1,31 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import os +import sys + + +_CATAPULT_PATH = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..', '..', '..', 'third_party', 'catapult')) + +_DEVIL_PATH = os.path.join(_CATAPULT_PATH, 'devil') + +_PYTRACE_PATH = os.path.join(_CATAPULT_PATH, 'common', 'py_trace_event') + +_PY_UTILS_PATH = os.path.join(_CATAPULT_PATH, 'common', 'py_utils') + +_TRACE2HTML_PATH = os.path.join(_CATAPULT_PATH, 'tracing') + + +if _DEVIL_PATH not in sys.path: + sys.path.append(_DEVIL_PATH) + +if _PYTRACE_PATH not in sys.path: + sys.path.append(_PYTRACE_PATH) + +if _PY_UTILS_PATH not in sys.path: + sys.path.append(_PY_UTILS_PATH) + +if _TRACE2HTML_PATH not in sys.path: + sys.path.append(_TRACE2HTML_PATH) diff --git a/build/android/pylib/constants/__init__.py b/build/android/pylib/constants/__init__.py new file mode 100644 index 0000000..916ee27 --- /dev/null +++ b/build/android/pylib/constants/__init__.py @@ -0,0 +1,225 @@ +# Copyright (c) 2012 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Defines a set of constants shared by test runners and other scripts.""" + +# TODO(jbudorick): Split these constants into coherent modules. + +# pylint: disable=W0212 + +import collections +import glob +import logging +import os +import subprocess + +import devil.android.sdk.keyevent +from devil.android.constants import chrome +from devil.android.sdk import version_codes +from devil.constants import exit_codes + + +keyevent = devil.android.sdk.keyevent + + +DIR_SOURCE_ROOT = os.environ.get('CHECKOUT_SOURCE_ROOT', + os.path.abspath(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir, os.pardir))) + +PACKAGE_INFO = dict(chrome.PACKAGE_INFO) +PACKAGE_INFO.update({ + 'legacy_browser': chrome.PackageInfo( + 'com.google.android.browser', + 'com.android.browser.BrowserActivity', + None, + None), + 'chromecast_shell': chrome.PackageInfo( + 'com.google.android.apps.mediashell', + 'com.google.android.apps.mediashell.MediaShellActivity', + 'castshell-command-line', + None), + 'android_webview_shell': chrome.PackageInfo( + 'org.chromium.android_webview.shell', + 'org.chromium.android_webview.shell.AwShellActivity', + 'android-webview-command-line', + None), + 'gtest': chrome.PackageInfo( + 'org.chromium.native_test', + 'org.chromium.native_test.NativeUnitTestActivity', + 'chrome-native-tests-command-line', + None), + 'components_browsertests': chrome.PackageInfo( + 'org.chromium.components_browsertests_apk', + ('org.chromium.components_browsertests_apk' + + '.ComponentsBrowserTestsActivity'), + 'chrome-native-tests-command-line', + None), + 'content_browsertests': chrome.PackageInfo( + 'org.chromium.content_browsertests_apk', + 'org.chromium.content_browsertests_apk.ContentBrowserTestsActivity', + 'chrome-native-tests-command-line', + None), + 'chromedriver_webview_shell': chrome.PackageInfo( + 'org.chromium.chromedriver_webview_shell', + 'org.chromium.chromedriver_webview_shell.Main', + None, + None), +}) + + +# Ports arrangement for various test servers used in Chrome for Android. +# Lighttpd server will attempt to use 9000 as default port, if unavailable it +# will find a free port from 8001 - 8999. +LIGHTTPD_DEFAULT_PORT = 9000 +LIGHTTPD_RANDOM_PORT_FIRST = 8001 +LIGHTTPD_RANDOM_PORT_LAST = 8999 +TEST_SYNC_SERVER_PORT = 9031 +TEST_SEARCH_BY_IMAGE_SERVER_PORT = 9041 +TEST_POLICY_SERVER_PORT = 9051 + + +TEST_EXECUTABLE_DIR = '/data/local/tmp' +# Directories for common java libraries for SDK build. +# These constants are defined in build/android/ant/common.xml +SDK_BUILD_JAVALIB_DIR = 'lib.java' +SDK_BUILD_TEST_JAVALIB_DIR = 'test.lib.java' +SDK_BUILD_APKS_DIR = 'apks' + +ADB_KEYS_FILE = '/data/misc/adb/adb_keys' + +PERF_OUTPUT_DIR = os.path.join(DIR_SOURCE_ROOT, 'out', 'step_results') +# The directory on the device where perf test output gets saved to. +DEVICE_PERF_OUTPUT_DIR = ( + '/data/data/' + PACKAGE_INFO['chrome'].package + '/files') + +SCREENSHOTS_DIR = os.path.join(DIR_SOURCE_ROOT, 'out_screenshots') + +ANDROID_SDK_VERSION = version_codes.MARSHMALLOW +ANDROID_SDK_BUILD_TOOLS_VERSION = '24.0.2' +ANDROID_SDK_ROOT = os.path.join(DIR_SOURCE_ROOT, + 'third_party', 'android_tools', 'sdk') +ANDROID_SDK_TOOLS = os.path.join(ANDROID_SDK_ROOT, + 'build-tools', ANDROID_SDK_BUILD_TOOLS_VERSION) +ANDROID_NDK_ROOT = os.path.join(DIR_SOURCE_ROOT, + 'third_party', 'android_tools', 'ndk') + +PROGUARD_SCRIPT_PATH = os.path.join( + ANDROID_SDK_ROOT, 'tools', 'proguard', 'bin', 'proguard.sh') + +PROGUARD_ROOT = os.path.join(DIR_SOURCE_ROOT, 'third_party', 'proguard') + +BAD_DEVICES_JSON = os.path.join(DIR_SOURCE_ROOT, + os.environ.get('CHROMIUM_OUT_DIR', 'out'), + 'bad_devices.json') + +UPSTREAM_FLAKINESS_SERVER = 'test-results.appspot.com' + +# TODO(jbudorick): Remove once unused. +DEVICE_LOCAL_PROPERTIES_PATH = '/data/local.prop' + +# TODO(jbudorick): Rework this into testing/buildbot/ +PYTHON_UNIT_TEST_SUITES = { + 'pylib_py_unittests': { + 'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android'), + 'test_modules': [ + 'devil.android.device_utils_test', + 'devil.android.md5sum_test', + 'devil.utils.cmd_helper_test', + 'pylib.results.json_results_test', + 'pylib.utils.proguard_test', + ] + }, + 'gyp_py_unittests': { + 'path': os.path.join(DIR_SOURCE_ROOT, 'build', 'android', 'gyp'), + 'test_modules': [ + 'java_cpp_enum_tests', + 'java_google_api_keys_tests', + ] + }, +} + +LOCAL_MACHINE_TESTS = ['junit', 'python'] +VALID_ENVIRONMENTS = ['local'] +VALID_TEST_TYPES = ['gtest', 'instrumentation', 'junit', 'linker', 'monkey', + 'perf', 'python'] +VALID_DEVICE_TYPES = ['Android', 'iOS'] + + +def GetBuildType(): + try: + return os.environ['BUILDTYPE'] + except KeyError: + raise EnvironmentError( + 'The BUILDTYPE environment variable has not been set') + + +def SetBuildType(build_type): + os.environ['BUILDTYPE'] = build_type + + +def SetBuildDirectory(build_directory): + os.environ['CHROMIUM_OUT_DIR'] = build_directory + + +def SetOutputDirectory(output_directory): + os.environ['CHROMIUM_OUTPUT_DIR'] = output_directory + + +def GetOutDirectory(build_type=None): + """Returns the out directory where the output binaries are built. + + Args: + build_type: Build type, generally 'Debug' or 'Release'. Defaults to the + globally set build type environment variable BUILDTYPE. + """ + if 'CHROMIUM_OUTPUT_DIR' in os.environ: + return os.path.abspath(os.path.join( + DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUTPUT_DIR'))) + + return os.path.abspath(os.path.join( + DIR_SOURCE_ROOT, os.environ.get('CHROMIUM_OUT_DIR', 'out'), + GetBuildType() if build_type is None else build_type)) + + +def CheckOutputDirectory(): + """Checks that CHROMIUM_OUT_DIR or CHROMIUM_OUTPUT_DIR is set. + + If neither are set, but the current working directory is a build directory, + then CHROMIUM_OUTPUT_DIR is set to the current working directory. + + Raises: + Exception: If no output directory is detected. + """ + output_dir = os.environ.get('CHROMIUM_OUTPUT_DIR') + out_dir = os.environ.get('CHROMIUM_OUT_DIR') + if not output_dir and not out_dir: + # If CWD is an output directory, then assume it's the desired one. + if os.path.exists('build.ninja'): + output_dir = os.getcwd() + SetOutputDirectory(output_dir) + elif os.environ.get('CHROME_HEADLESS'): + # When running on bots, see if the output directory is obvious. + dirs = glob.glob(os.path.join(DIR_SOURCE_ROOT, 'out', '*', 'build.ninja')) + if len(dirs) == 1: + SetOutputDirectory(dirs[0]) + else: + raise Exception('Neither CHROMIUM_OUTPUT_DIR nor CHROMIUM_OUT_DIR ' + 'has been set. CHROME_HEADLESS detected, but multiple ' + 'out dirs exist: %r' % dirs) + else: + raise Exception('Neither CHROMIUM_OUTPUT_DIR nor CHROMIUM_OUT_DIR ' + 'has been set') + + +# TODO(jbudorick): Convert existing callers to AdbWrapper.GetAdbPath() and +# remove this. +def GetAdbPath(): + from devil.android.sdk import adb_wrapper + return adb_wrapper.AdbWrapper.GetAdbPath() + + +# Exit codes +ERROR_EXIT_CODE = exit_codes.ERROR +INFRA_EXIT_CODE = exit_codes.INFRA +WARNING_EXIT_CODE = exit_codes.WARNING diff --git a/build/android/pylib/constants/host_paths.py b/build/android/pylib/constants/host_paths.py new file mode 100644 index 0000000..98aa53d --- /dev/null +++ b/build/android/pylib/constants/host_paths.py @@ -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. + +import contextlib +import os +import sys + +DIR_SOURCE_ROOT = os.environ.get( + 'CHECKOUT_SOURCE_ROOT', + os.path.abspath(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, os.pardir, os.pardir))) + +BUILD_COMMON_PATH = os.path.join( + DIR_SOURCE_ROOT, 'build', 'util', 'lib', 'common') + +# third-party libraries +ANDROID_PLATFORM_DEVELOPMENT_SCRIPTS_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'android_platform', 'development', + 'scripts') +DEVIL_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'catapult', 'devil') +PYMOCK_PATH = os.path.join( + DIR_SOURCE_ROOT, 'third_party', 'pymock') + +@contextlib.contextmanager +def SysPath(path, position=None): + if position is None: + sys.path.append(path) + else: + sys.path.insert(position, path) + try: + yield + finally: + if sys.path[-1] == path: + sys.path.pop() + else: + sys.path.remove(path) diff --git a/build/gn_helpers.py b/build/gn_helpers.py new file mode 100644 index 0000000..33cc578 --- /dev/null +++ b/build/gn_helpers.py @@ -0,0 +1,351 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Helper functions useful when writing scripts that integrate with GN. + +The main functions are ToGNString and FromGNString which convert between +serialized GN veriables and Python variables. + +To use in a random python file in the build: + + import os + import sys + + sys.path.append(os.path.join(os.path.dirname(__file__), + os.pardir, os.pardir, "build")) + import gn_helpers + +Where the sequence of parameters to join is the relative path from your source +file to the build directory.""" + +class GNException(Exception): + pass + + +def ToGNString(value, allow_dicts = True): + """Returns a stringified GN equivalent of the Python value. + + allow_dicts indicates if this function will allow converting dictionaries + to GN scopes. This is only possible at the top level, you can't nest a + GN scope in a list, so this should be set to False for recursive calls.""" + if isinstance(value, basestring): + if value.find('\n') >= 0: + raise GNException("Trying to print a string with a newline in it.") + return '"' + \ + value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \ + '"' + + if isinstance(value, unicode): + return ToGNString(value.encode('utf-8')) + + if isinstance(value, bool): + if value: + return "true" + return "false" + + if isinstance(value, list): + return '[ %s ]' % ', '.join(ToGNString(v) for v in value) + + if isinstance(value, dict): + if not allow_dicts: + raise GNException("Attempting to recursively print a dictionary.") + result = "" + for key in sorted(value): + if not isinstance(key, basestring): + raise GNException("Dictionary key is not a string.") + result += "%s = %s\n" % (key, ToGNString(value[key], False)) + return result + + if isinstance(value, int): + return str(value) + + raise GNException("Unsupported type when printing to GN.") + + +def FromGNString(input_string): + """Converts the input string from a GN serialized value to Python values. + + For details on supported types see GNValueParser.Parse() below. + + If your GN script did: + something = [ "file1", "file2" ] + args = [ "--values=$something" ] + The command line would look something like: + --values="[ \"file1\", \"file2\" ]" + Which when interpreted as a command line gives the value: + [ "file1", "file2" ] + + You can parse this into a Python list using GN rules with: + input_values = FromGNValues(options.values) + Although the Python 'ast' module will parse many forms of such input, it + will not handle GN escaping properly, nor GN booleans. You should use this + function instead. + + + A NOTE ON STRING HANDLING: + + If you just pass a string on the command line to your Python script, or use + string interpolation on a string variable, the strings will not be quoted: + str = "asdf" + args = [ str, "--value=$str" ] + Will yield the command line: + asdf --value=asdf + The unquoted asdf string will not be valid input to this function, which + accepts only quoted strings like GN scripts. In such cases, you can just use + the Python string literal directly. + + The main use cases for this is for other types, in particular lists. When + using string interpolation on a list (as in the top example) the embedded + strings will be quoted and escaped according to GN rules so the list can be + re-parsed to get the same result.""" + parser = GNValueParser(input_string) + return parser.Parse() + + +def FromGNArgs(input_string): + """Converts a string with a bunch of gn arg assignments into a Python dict. + + Given a whitespace-separated list of + + = (integer | string | boolean | ) + + gn assignments, this returns a Python dict, i.e.: + + FromGNArgs("foo=true\nbar=1\n") -> { 'foo': True, 'bar': 1 }. + + Only simple types and lists supported; variables, structs, calls + and other, more complicated things are not. + + This routine is meant to handle only the simple sorts of values that + arise in parsing --args. + """ + parser = GNValueParser(input_string) + return parser.ParseArgs() + + +def UnescapeGNString(value): + """Given a string with GN escaping, returns the unescaped string. + + Be careful not to feed with input from a Python parsing function like + 'ast' because it will do Python unescaping, which will be incorrect when + fed into the GN unescaper.""" + result = '' + i = 0 + while i < len(value): + if value[i] == '\\': + if i < len(value) - 1: + next_char = value[i + 1] + if next_char in ('$', '"', '\\'): + # These are the escaped characters GN supports. + result += next_char + i += 1 + else: + # Any other backslash is a literal. + result += '\\' + else: + result += value[i] + i += 1 + return result + + +def _IsDigitOrMinus(char): + return char in "-0123456789" + + +class GNValueParser(object): + """Duplicates GN parsing of values and converts to Python types. + + Normally you would use the wrapper function FromGNValue() below. + + If you expect input as a specific type, you can also call one of the Parse* + functions directly. All functions throw GNException on invalid input. """ + def __init__(self, string): + self.input = string + self.cur = 0 + + def IsDone(self): + return self.cur == len(self.input) + + def ConsumeWhitespace(self): + while not self.IsDone() and self.input[self.cur] in ' \t\n': + self.cur += 1 + + def Parse(self): + """Converts a string representing a printed GN value to the Python type. + + See additional usage notes on FromGNString above. + + - GN booleans ('true', 'false') will be converted to Python booleans. + + - GN numbers ('123') will be converted to Python numbers. + + - GN strings (double-quoted as in '"asdf"') will be converted to Python + strings with GN escaping rules. GN string interpolation (embedded + variables preceeded by $) are not supported and will be returned as + literals. + + - GN lists ('[1, "asdf", 3]') will be converted to Python lists. + + - GN scopes ('{ ... }') are not supported.""" + result = self._ParseAllowTrailing() + self.ConsumeWhitespace() + if not self.IsDone(): + raise GNException("Trailing input after parsing:\n " + + self.input[self.cur:]) + return result + + def ParseArgs(self): + """Converts a whitespace-separated list of ident=literals to a dict. + + See additional usage notes on FromGNArgs, above. + """ + d = {} + + self.ConsumeWhitespace() + while not self.IsDone(): + ident = self._ParseIdent() + self.ConsumeWhitespace() + if self.input[self.cur] != '=': + raise GNException("Unexpected token: " + self.input[self.cur:]) + self.cur += 1 + self.ConsumeWhitespace() + val = self._ParseAllowTrailing() + self.ConsumeWhitespace() + d[ident] = val + + return d + + def _ParseAllowTrailing(self): + """Internal version of Parse that doesn't check for trailing stuff.""" + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException("Expected input to parse.") + + next_char = self.input[self.cur] + if next_char == '[': + return self.ParseList() + elif _IsDigitOrMinus(next_char): + return self.ParseNumber() + elif next_char == '"': + return self.ParseString() + elif self._ConstantFollows('true'): + return True + elif self._ConstantFollows('false'): + return False + else: + raise GNException("Unexpected token: " + self.input[self.cur:]) + + def _ParseIdent(self): + ident = '' + + next_char = self.input[self.cur] + if not next_char.isalpha() and not next_char=='_': + raise GNException("Expected an identifier: " + self.input[self.cur:]) + + ident += next_char + self.cur += 1 + + next_char = self.input[self.cur] + while next_char.isalpha() or next_char.isdigit() or next_char=='_': + ident += next_char + self.cur += 1 + next_char = self.input[self.cur] + + return ident + + def ParseNumber(self): + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException('Expected number but got nothing.') + + begin = self.cur + + # The first character can include a negative sign. + if not self.IsDone() and _IsDigitOrMinus(self.input[self.cur]): + self.cur += 1 + while not self.IsDone() and self.input[self.cur].isdigit(): + self.cur += 1 + + number_string = self.input[begin:self.cur] + if not len(number_string) or number_string == '-': + raise GNException("Not a valid number.") + return int(number_string) + + def ParseString(self): + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException('Expected string but got nothing.') + + if self.input[self.cur] != '"': + raise GNException('Expected string beginning in a " but got:\n ' + + self.input[self.cur:]) + self.cur += 1 # Skip over quote. + + begin = self.cur + while not self.IsDone() and self.input[self.cur] != '"': + if self.input[self.cur] == '\\': + self.cur += 1 # Skip over the backslash. + if self.IsDone(): + raise GNException("String ends in a backslash in:\n " + + self.input) + self.cur += 1 + + if self.IsDone(): + raise GNException('Unterminated string:\n ' + self.input[begin:]) + + end = self.cur + self.cur += 1 # Consume trailing ". + + return UnescapeGNString(self.input[begin:end]) + + def ParseList(self): + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException('Expected list but got nothing.') + + # Skip over opening '['. + if self.input[self.cur] != '[': + raise GNException("Expected [ for list but got:\n " + + self.input[self.cur:]) + self.cur += 1 + self.ConsumeWhitespace() + if self.IsDone(): + raise GNException("Unterminated list:\n " + self.input) + + list_result = [] + previous_had_trailing_comma = True + while not self.IsDone(): + if self.input[self.cur] == ']': + self.cur += 1 # Skip over ']'. + return list_result + + if not previous_had_trailing_comma: + raise GNException("List items not separated by comma.") + + list_result += [ self._ParseAllowTrailing() ] + self.ConsumeWhitespace() + if self.IsDone(): + break + + # Consume comma if there is one. + previous_had_trailing_comma = self.input[self.cur] == ',' + if previous_had_trailing_comma: + # Consume comma. + self.cur += 1 + self.ConsumeWhitespace() + + raise GNException("Unterminated list:\n " + self.input) + + def _ConstantFollows(self, constant): + """Returns true if the given constant follows immediately at the current + location in the input. If it does, the text is consumed and the function + returns true. Otherwise, returns false and the current position is + unchanged.""" + end = self.cur + len(constant) + if end > len(self.input): + return False # Not enough room. + if self.input[self.cur:end] == constant: + self.cur = end + return True + return False diff --git a/gen/mojo/common/common_custom_types__type_mappings b/gen/mojo/common/common_custom_types__type_mappings new file mode 100644 index 0000000..2e5cd5d --- /dev/null +++ b/gen/mojo/common/common_custom_types__type_mappings @@ -0,0 +1,193 @@ +{ + "c++": { + "mojo.common.mojom.Value": { + "hashable": false, + "typename": "std::unique_ptr", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/values_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": true, + "nullable_is_same_type": true, + "non_copyable_non_movable": false, + "public_headers": [ + "base/values.h" + ] + }, + "mojo.common.mojom.UnguessableToken": { + "hashable": false, + "typename": "base::UnguessableToken", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/unguessable_token.h" + ] + }, + "mojo.common.mojom.TextDirection": { + "hashable": false, + "typename": "base::i18n::TextDirection", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/i18n/rtl.h" + ] + }, + "mojo.common.mojom.ListValue": { + "hashable": false, + "typename": "std::unique_ptr", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/values_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": true, + "nullable_is_same_type": true, + "non_copyable_non_movable": false, + "public_headers": [ + "base/values.h" + ] + }, + "mojo.common.mojom.String16": { + "hashable": false, + "typename": "base::string16", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/strings/string16.h" + ] + }, + "mojo.common.mojom.Time": { + "hashable": false, + "typename": "base::Time", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": true, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/time/time.h" + ] + }, + "mojo.common.mojom.TimeDelta": { + "hashable": false, + "typename": "base::TimeDelta", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": true, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/time/time.h" + ] + }, + "mojo.common.mojom.TimeTicks": { + "hashable": false, + "typename": "base::TimeTicks", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": true, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/time/time.h" + ] + }, + "mojo.common.mojom.LegacyListValue": { + "hashable": false, + "typename": "base::ListValue", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/values_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": true, + "public_headers": [ + "base/values.h" + ] + }, + "mojo.common.mojom.File": { + "hashable": false, + "typename": "base::File", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": true, + "nullable_is_same_type": true, + "non_copyable_non_movable": false, + "public_headers": [ + "base/files/file.h" + ] + }, + "mojo.common.mojom.FilePath": { + "hashable": false, + "typename": "base::FilePath", + "traits_headers": [ + "ipc/ipc_message_utils.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/files/file_path.h" + ] + }, + "mojo.common.mojom.DictionaryValue": { + "hashable": false, + "typename": "std::unique_ptr", + "traits_headers": [ + "ipc/ipc_message_utils.h", + "mojo/common/values_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": true, + "nullable_is_same_type": true, + "non_copyable_non_movable": false, + "public_headers": [ + "base/values.h" + ] + }, + "mojo.common.mojom.Version": { + "hashable": false, + "typename": "base::Version", + "traits_headers": [ + "mojo/common/common_custom_types_struct_traits.h" + ], + "copyable_pass_by_value": false, + "move_only": false, + "nullable_is_same_type": false, + "non_copyable_non_movable": false, + "public_headers": [ + "base/version.h" + ] + } + } +} diff --git a/ipc/ipc.mojom b/ipc/ipc.mojom new file mode 100644 index 0000000..0a4fcfa --- /dev/null +++ b/ipc/ipc.mojom @@ -0,0 +1,40 @@ +// 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 IPC.mojom; + +// NOTE: This MUST match the value of MSG_ROUTING_NONE in src/ipc/ipc_message.h. +const int32 kRoutingIdNone = -2; + +struct SerializedHandle { + handle the_handle; + + enum Type { + MOJO_HANDLE, + PLATFORM_FILE, + WIN_HANDLE, + MACH_PORT, + }; + + Type type; +}; + +// A placeholder interface type since we don't yet support generic associated +// message pipe handles. +interface GenericInterface {}; + +interface Channel { + // Informs the remote end of this client's PID. Must be called exactly once, + // before any calls to Receive() below. + SetPeerPid(int32 pid); + + // Transmits a classical Chrome IPC message. + Receive(array data, array? handles); + + // Requests a Channel-associated interface. + GetAssociatedInterface(string name, associated GenericInterface& request); +}; + +// A strictly nominal interface used to identify Channel bootstrap requests. +interface ChannelBootstrap {}; diff --git a/ipc/ipc_channel_handle.h b/ipc/ipc_channel_handle.h new file mode 100644 index 0000000..ef31b84 --- /dev/null +++ b/ipc/ipc_channel_handle.h @@ -0,0 +1,41 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_CHANNEL_HANDLE_H_ +#define IPC_IPC_CHANNEL_HANDLE_H_ + +#include + +#include "build/build_config.h" +#include "mojo/public/cpp/system/message_pipe.h" + +#if defined(OS_NACL_SFI) +#include "base/file_descriptor_posix.h" +#endif // defined (OS_NACL_SFI) + +namespace IPC { + +// Note that serialization for this object is defined in the ParamTraits +// template specialization in ipc_message_utils.h. +#if defined(OS_NACL_SFI) +struct ChannelHandle { + ChannelHandle() {} + explicit ChannelHandle(const base::FileDescriptor& s) : socket(s) {} + + base::FileDescriptor socket; +}; +#else +struct ChannelHandle { + ChannelHandle() {} + ChannelHandle(mojo::MessagePipeHandle h) : mojo_handle(h) {} + + bool is_mojo_channel_handle() const { return mojo_handle.is_valid(); } + + mojo::MessagePipeHandle mojo_handle; +}; +#endif // defined(OS_NACL_SFI) + +} // namespace IPC + +#endif // IPC_IPC_CHANNEL_HANDLE_H_ diff --git a/ipc/ipc_export.h b/ipc/ipc_export.h new file mode 100644 index 0000000..e1cbe88 --- /dev/null +++ b/ipc/ipc_export.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_EXPORT_H_ +#define IPC_IPC_EXPORT_H_ + +// Defines IPC_EXPORT so that functionality implemented by the IPC module can be +// exported to consumers. + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(IPC_IMPLEMENTATION) +#define IPC_EXPORT __declspec(dllexport) +#else +#define IPC_EXPORT __declspec(dllimport) +#endif // defined(IPC_IMPLEMENTATION) + +#else // defined(WIN32) + +#if defined(IPC_IMPLEMENTATION) +#define IPC_EXPORT __attribute__((visibility("default"))) +#else +#define IPC_EXPORT +#endif + +#endif + +#else // defined(COMPONENT_BUILD) +#define IPC_EXPORT +#endif + +#endif // IPC_IPC_EXPORT_H_ diff --git a/ipc/ipc_listener.h b/ipc/ipc_listener.h new file mode 100644 index 0000000..d7ad75c --- /dev/null +++ b/ipc/ipc_listener.h @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_LISTENER_H_ +#define IPC_IPC_LISTENER_H_ + +#include + +#include + +#include "build/build_config.h" +#include "ipc/ipc_export.h" +#include "mojo/public/cpp/bindings/scoped_interface_endpoint_handle.h" + +namespace IPC { + +class Message; + +// Implemented by consumers of a Channel to receive messages. +class IPC_EXPORT Listener { + public: + // Called when a message is received. Returns true iff the message was + // handled. + virtual bool OnMessageReceived(const Message& message) = 0; + + // Called when the channel is connected and we have received the internal + // Hello message from the peer. + virtual void OnChannelConnected(int32_t peer_pid) {} + + // Called when an error is detected that causes the channel to close. + // This method is not called when a channel is closed normally. + virtual void OnChannelError() {} + + // Called when a message's deserialization failed. + virtual void OnBadMessageReceived(const Message& message) {} + + // Called when an associated interface request is received on a Channel and + // the Channel has no registered handler for it. + virtual void OnAssociatedInterfaceRequest( + const std::string& interface_name, + mojo::ScopedInterfaceEndpointHandle handle) {} + +#if defined(OS_POSIX) + // Called on the server side when a channel that listens for connections + // denies an attempt to connect. + virtual void OnChannelDenied() {} + + // Called on the server side when a channel that listens for connections + // has an error that causes the listening channel to close. + virtual void OnChannelListenError() {} +#endif // OS_POSIX + + protected: + virtual ~Listener() {} +}; + +} // namespace IPC + +#endif // IPC_IPC_LISTENER_H_ diff --git a/ipc/ipc_message.cc b/ipc/ipc_message.cc new file mode 100644 index 0000000..f5e9ac7 --- /dev/null +++ b/ipc/ipc_message.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ipc/ipc_message.h" + +#include +#include +#include + +#include "base/atomic_sequence_num.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "ipc/ipc_message_attachment.h" +#include "ipc/ipc_message_attachment_set.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#include "ipc/ipc_platform_file_attachment_posix.h" +#endif + +namespace { + +base::StaticAtomicSequenceNumber g_ref_num; + +// Create a reference number for identifying IPC messages in traces. The return +// values has the reference number stored in the upper 24 bits, leaving the low +// 8 bits set to 0 for use as flags. +inline uint32_t GetRefNumUpper24() { + base::trace_event::TraceLog* trace_log = + base::trace_event::TraceLog::GetInstance(); + uint32_t pid = trace_log ? trace_log->process_id() : 0; + uint32_t count = g_ref_num.GetNext(); + // The 24 bit hash is composed of 14 bits of the count and 10 bits of the + // Process ID. With the current trace event buffer cap, the 14-bit count did + // not appear to wrap during a trace. Note that it is not a big deal if + // collisions occur, as this is only used for debugging and trace analysis. + return ((pid << 14) | (count & 0x3fff)) << 8; +} + +} // namespace + +namespace IPC { + +//------------------------------------------------------------------------------ + +Message::~Message() { +} + +Message::Message() : base::Pickle(sizeof(Header)) { + header()->routing = header()->type = 0; + header()->flags = GetRefNumUpper24(); +#if defined(OS_POSIX) + header()->num_fds = 0; + header()->pad = 0; +#endif + Init(); +} + +Message::Message(int32_t routing_id, uint32_t type, PriorityValue priority) + : base::Pickle(sizeof(Header)) { + header()->routing = routing_id; + header()->type = type; + DCHECK((priority & 0xffffff00) == 0); + header()->flags = priority | GetRefNumUpper24(); +#if defined(OS_POSIX) + header()->num_fds = 0; + header()->pad = 0; +#endif + Init(); +} + +Message::Message(const char* data, int data_len) + : base::Pickle(data, data_len) { + Init(); +} + +Message::Message(const Message& other) : base::Pickle(other) { + Init(); + attachment_set_ = other.attachment_set_; +} + +void Message::Init() { + dispatch_error_ = false; +#ifdef IPC_MESSAGE_LOG_ENABLED + received_time_ = 0; + dont_log_ = false; + log_data_ = NULL; +#endif +} + +Message& Message::operator=(const Message& other) { + *static_cast(this) = other; + attachment_set_ = other.attachment_set_; + return *this; +} + +void Message::SetHeaderValues(int32_t routing, uint32_t type, uint32_t flags) { + // This should only be called when the message is already empty. + DCHECK(payload_size() == 0); + + header()->routing = routing; + header()->type = type; + header()->flags = flags; +} + +void Message::EnsureMessageAttachmentSet() { + if (attachment_set_.get() == NULL) + attachment_set_ = new MessageAttachmentSet; +} + +#ifdef IPC_MESSAGE_LOG_ENABLED +void Message::set_sent_time(int64_t time) { + DCHECK((header()->flags & HAS_SENT_TIME_BIT) == 0); + header()->flags |= HAS_SENT_TIME_BIT; + WriteInt64(time); +} + +int64_t Message::sent_time() const { + if ((header()->flags & HAS_SENT_TIME_BIT) == 0) + return 0; + + const char* data = end_of_payload(); + data -= sizeof(int64_t); + return *(reinterpret_cast(data)); +} + +void Message::set_received_time(int64_t time) const { + received_time_ = time; +} +#endif + +Message::NextMessageInfo::NextMessageInfo() + : message_size(0), message_found(false), pickle_end(nullptr), + message_end(nullptr) {} +Message::NextMessageInfo::~NextMessageInfo() {} + +// static +void Message::FindNext(const char* range_start, + const char* range_end, + NextMessageInfo* info) { + DCHECK(info); + info->message_found = false; + info->message_size = 0; + + size_t pickle_size = 0; + if (!base::Pickle::PeekNext(sizeof(Header), + range_start, range_end, &pickle_size)) + return; + + bool have_entire_pickle = + static_cast(range_end - range_start) >= pickle_size; + + info->message_size = pickle_size; + + if (!have_entire_pickle) + return; + + const char* pickle_end = range_start + pickle_size; + + info->message_end = pickle_end; + + info->pickle_end = pickle_end; + info->message_found = true; +} + +bool Message::WriteAttachment( + scoped_refptr attachment) { + size_t index; + bool success = attachment_set()->AddAttachment( + make_scoped_refptr(static_cast(attachment.get())), + &index); + DCHECK(success); + + // NOTE: If you add more data to the pickle, make sure to update + // PickleSizer::AddAttachment. + + // Write the index of the descriptor so that we don't have to + // keep the current descriptor as extra decoding state when deserialising. + WriteInt(static_cast(index)); + + return success; +} + +bool Message::ReadAttachment( + base::PickleIterator* iter, + scoped_refptr* attachment) const { + int index; + if (!iter->ReadInt(&index)) + return false; + + MessageAttachmentSet* attachment_set = attachment_set_.get(); + if (!attachment_set) + return false; + + *attachment = attachment_set->GetAttachmentAt(index); + + return nullptr != attachment->get(); +} + +bool Message::HasAttachments() const { + return attachment_set_.get() && !attachment_set_->empty(); +} + +} // namespace IPC diff --git a/ipc/ipc_message.h b/ipc/ipc_message.h new file mode 100644 index 0000000..43e9ae3 --- /dev/null +++ b/ipc/ipc_message.h @@ -0,0 +1,305 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_MESSAGE_H_ +#define IPC_IPC_MESSAGE_H_ + +#include +#include + +#include + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/pickle.h" +#include "base/trace_event/trace_event.h" +#include "build/build_config.h" +#include "ipc/ipc_export.h" + +#if !defined(NDEBUG) +#define IPC_MESSAGE_LOG_ENABLED +#endif + +namespace IPC { + +namespace internal { +class ChannelReader; +} // namespace internal + +//------------------------------------------------------------------------------ + +struct LogData; +class MessageAttachmentSet; + +class IPC_EXPORT Message : public base::Pickle { + public: + enum PriorityValue { + PRIORITY_LOW = 1, + PRIORITY_NORMAL, + PRIORITY_HIGH + }; + + // Bit values used in the flags field. + // Upper 24 bits of flags store a reference number, so this enum is limited to + // 8 bits. + enum { + PRIORITY_MASK = 0x03, // Low 2 bits of store the priority value. + SYNC_BIT = 0x04, + REPLY_BIT = 0x08, + REPLY_ERROR_BIT = 0x10, + UNBLOCK_BIT = 0x20, + PUMPING_MSGS_BIT = 0x40, + HAS_SENT_TIME_BIT = 0x80, + }; + + ~Message() override; + + Message(); + + // Initialize a message with a user-defined type, priority value, and + // destination WebView ID. + Message(int32_t routing_id, uint32_t type, PriorityValue priority); + + // Initializes a message from a const block of data. The data is not copied; + // instead the data is merely referenced by this message. Only const methods + // should be used on the message when initialized this way. + Message(const char* data, int data_len); + + Message(const Message& other); + Message& operator=(const Message& other); + + PriorityValue priority() const { + return static_cast(header()->flags & PRIORITY_MASK); + } + + // True if this is a synchronous message. + void set_sync() { + header()->flags |= SYNC_BIT; + } + bool is_sync() const { + return (header()->flags & SYNC_BIT) != 0; + } + + // Set this on a reply to a synchronous message. + void set_reply() { + header()->flags |= REPLY_BIT; + } + + bool is_reply() const { + return (header()->flags & REPLY_BIT) != 0; + } + + // Set this on a reply to a synchronous message to indicate that no receiver + // was found. + void set_reply_error() { + header()->flags |= REPLY_ERROR_BIT; + } + + bool is_reply_error() const { + return (header()->flags & REPLY_ERROR_BIT) != 0; + } + + // Normally when a receiver gets a message and they're blocked on a + // synchronous message Send, they buffer a message. Setting this flag causes + // the receiver to be unblocked and the message to be dispatched immediately. + void set_unblock(bool unblock) { + if (unblock) { + header()->flags |= UNBLOCK_BIT; + } else { + header()->flags &= ~UNBLOCK_BIT; + } + } + + bool should_unblock() const { + return (header()->flags & UNBLOCK_BIT) != 0; + } + + // Tells the receiver that the caller is pumping messages while waiting + // for the result. + bool is_caller_pumping_messages() const { + return (header()->flags & PUMPING_MSGS_BIT) != 0; + } + + void set_dispatch_error() const { + dispatch_error_ = true; + } + + bool dispatch_error() const { + return dispatch_error_; + } + + uint32_t type() const { + return header()->type; + } + + int32_t routing_id() const { + return header()->routing; + } + + void set_routing_id(int32_t new_id) { + header()->routing = new_id; + } + + uint32_t flags() const { + return header()->flags; + } + + // Sets all the given header values. The message should be empty at this + // call. + void SetHeaderValues(int32_t routing, uint32_t type, uint32_t flags); + + template + static bool Dispatch(const Message* msg, T* obj, S* sender, P* parameter, + void (T::*func)()) { + (obj->*func)(); + return true; + } + + template + static bool Dispatch(const Message* msg, T* obj, S* sender, P* parameter, + void (T::*func)(P*)) { + (obj->*func)(parameter); + return true; + } + + // Used for async messages with no parameters. + static void Log(std::string* name, const Message* msg, std::string* l) { + } + + // The static method FindNext() returns several pieces of information, which + // are aggregated into an instance of this struct. + struct IPC_EXPORT NextMessageInfo { + NextMessageInfo(); + ~NextMessageInfo(); + + // Total message size. Always valid if |message_found| is true. + // If |message_found| is false but we could determine message size + // from the header, this field is non-zero. Otherwise it's zero. + size_t message_size; + // Whether an entire message was found in the given memory range. + bool message_found; + // Only filled in if |message_found| is true. + // The start address is passed into FindNext() by the caller, so isn't + // repeated in this struct. The end address of the pickle should be used to + // construct a base::Pickle. + const char* pickle_end; + // Only filled in if |message_found| is true. + // The end address of the message should be used to determine the start + // address of the next message. + const char* message_end; + }; + + // |info| is an output parameter and must not be nullptr. + static void FindNext(const char* range_start, + const char* range_end, + NextMessageInfo* info); + + // WriteAttachment appends |attachment| to the end of the set. It returns + // false iff the set is full. + bool WriteAttachment( + scoped_refptr attachment) override; + // ReadAttachment parses an attachment given the parsing state |iter| and + // writes it to |*attachment|. It returns true on success. + bool ReadAttachment( + base::PickleIterator* iter, + scoped_refptr* attachment) const override; + // Returns true if there are any attachment in this message. + bool HasAttachments() const override; + +#ifdef IPC_MESSAGE_LOG_ENABLED + // Adds the outgoing time from Time::Now() at the end of the message and sets + // a bit to indicate that it's been added. + void set_sent_time(int64_t time); + int64_t sent_time() const; + + void set_received_time(int64_t time) const; + int64_t received_time() const { return received_time_; } + void set_output_params(const std::string& op) const { output_params_ = op; } + const std::string& output_params() const { return output_params_; } + // The following four functions are needed so we can log sync messages with + // delayed replies. We stick the log data from the sent message into the + // reply message, so that when it's sent and we have the output parameters + // we can log it. As such, we set a flag on the sent message to not log it. + void set_sync_log_data(LogData* data) const { log_data_ = data; } + LogData* sync_log_data() const { return log_data_; } + void set_dont_log() const { dont_log_ = true; } + bool dont_log() const { return dont_log_; } +#endif + + protected: + friend class Channel; + friend class ChannelMojo; + friend class ChannelNacl; + friend class ChannelPosix; + friend class ChannelWin; + friend class internal::ChannelReader; + friend class MessageReplyDeserializer; + friend class SyncMessage; + +#pragma pack(push, 4) + struct Header : base::Pickle::Header { + int32_t routing; // ID of the view that this message is destined for + uint32_t type; // specifies the user-defined message type + uint32_t flags; // specifies control flags for the message +#if defined(OS_POSIX) + uint16_t num_fds; // the number of descriptors included with this message + uint16_t pad; // explicitly initialize this to appease valgrind +#endif + }; +#pragma pack(pop) + + Header* header() { + return headerT
(); + } + const Header* header() const { + return headerT
(); + } + + void Init(); + + // Used internally to support IPC::Listener::OnBadMessageReceived. + mutable bool dispatch_error_; + + // The set of file descriptors associated with this message. + scoped_refptr attachment_set_; + + // Ensure that a MessageAttachmentSet is allocated + void EnsureMessageAttachmentSet(); + + MessageAttachmentSet* attachment_set() { + EnsureMessageAttachmentSet(); + return attachment_set_.get(); + } + const MessageAttachmentSet* attachment_set() const { + return attachment_set_.get(); + } + +#ifdef IPC_MESSAGE_LOG_ENABLED + // Used for logging. + mutable int64_t received_time_; + mutable std::string output_params_; + mutable LogData* log_data_; + mutable bool dont_log_; +#endif + + FRIEND_TEST_ALL_PREFIXES(IPCMessageTest, FindNext); + FRIEND_TEST_ALL_PREFIXES(IPCMessageTest, FindNextOverflow); +}; + +//------------------------------------------------------------------------------ + +} // namespace IPC + +enum SpecialRoutingIDs { + // indicates that we don't have a routing ID yet. + MSG_ROUTING_NONE = -2, + + // indicates a general message not sent to a particular tab. + MSG_ROUTING_CONTROL = INT32_MAX, +}; + +#define IPC_REPLY_ID 0xFFFFFFF0 // Special message id for replies +#define IPC_LOGGING_ID 0xFFFFFFF1 // Special message id for logging + +#endif // IPC_IPC_MESSAGE_H_ diff --git a/ipc/ipc_message_attachment.cc b/ipc/ipc_message_attachment.cc new file mode 100644 index 0000000..83440ae --- /dev/null +++ b/ipc/ipc_message_attachment.cc @@ -0,0 +1,15 @@ +// Copyright (c) 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 "ipc/ipc_message_attachment.h" + +namespace IPC { + +MessageAttachment::MessageAttachment() { +} + +MessageAttachment::~MessageAttachment() { +} + +} // namespace IPC diff --git a/ipc/ipc_message_attachment.h b/ipc/ipc_message_attachment.h new file mode 100644 index 0000000..9ff1de8 --- /dev/null +++ b/ipc/ipc_message_attachment.h @@ -0,0 +1,36 @@ +// Copyright (c) 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 IPC_IPC_MESSAGE_ATTACHMENT_H_ +#define IPC_IPC_MESSAGE_ATTACHMENT_H_ + +#include "base/files/file.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/pickle.h" +#include "build/build_config.h" +#include "ipc/ipc.mojom.h" +#include "ipc/ipc_export.h" + +namespace IPC { + +// Auxiliary data sent with |Message|. This can be a platform file descriptor +// or a mojo |MessagePipe|. |GetType()| returns the type of the subclass. +class IPC_EXPORT MessageAttachment : public base::Pickle::Attachment { + public: + using Type = mojom::SerializedHandle::Type; + + virtual Type GetType() const = 0; + + protected: + friend class base::RefCountedThreadSafe; + MessageAttachment(); + ~MessageAttachment() override; + + DISALLOW_COPY_AND_ASSIGN(MessageAttachment); +}; + +} // namespace IPC + +#endif // IPC_IPC_MESSAGE_ATTACHMENT_H_ diff --git a/ipc/ipc_message_attachment_set.cc b/ipc/ipc_message_attachment_set.cc new file mode 100644 index 0000000..b9a990d --- /dev/null +++ b/ipc/ipc_message_attachment_set.cc @@ -0,0 +1,136 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ipc/ipc_message_attachment_set.h" + +#include + +#include + +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "ipc/ipc_message_attachment.h" + +namespace IPC { + +namespace { + +unsigned count_attachments_of_type( + const std::vector>& attachments, + MessageAttachment::Type type) { + unsigned count = 0; + for (const scoped_refptr& attachment : attachments) { + if (attachment->GetType() == type) + ++count; + } + return count; +} + +} // namespace + +MessageAttachmentSet::MessageAttachmentSet() + : consumed_descriptor_highwater_(0) { +} + +MessageAttachmentSet::~MessageAttachmentSet() { + if (consumed_descriptor_highwater_ == size()) + return; + + // We close all the owning descriptors. If this message should have + // been transmitted, then closing those with close flags set mirrors + // the expected behaviour. + // + // If this message was received with more descriptors than expected + // (which could a DOS against the browser by a rogue renderer) then all + // the descriptors have their close flag set and we free all the extra + // kernel resources. + LOG(WARNING) << "MessageAttachmentSet destroyed with unconsumed attachments: " + << consumed_descriptor_highwater_ << "/" << size(); +} + +unsigned MessageAttachmentSet::num_descriptors() const { + return count_attachments_of_type(attachments_, + MessageAttachment::Type::PLATFORM_FILE); +} + +unsigned MessageAttachmentSet::size() const { + return static_cast(attachments_.size()); +} + +bool MessageAttachmentSet::AddAttachment( + scoped_refptr attachment, + size_t* index) { +#if defined(OS_POSIX) + if (attachment->GetType() == MessageAttachment::Type::PLATFORM_FILE && + num_descriptors() == kMaxDescriptorsPerMessage) { + DLOG(WARNING) << "Cannot add file descriptor. MessageAttachmentSet full."; + return false; + } +#endif + + switch (attachment->GetType()) { + case MessageAttachment::Type::PLATFORM_FILE: + case MessageAttachment::Type::MOJO_HANDLE: + case MessageAttachment::Type::WIN_HANDLE: + case MessageAttachment::Type::MACH_PORT: + attachments_.push_back(attachment); + *index = attachments_.size() - 1; + return true; + } + return false; +} + +bool MessageAttachmentSet::AddAttachment( + scoped_refptr attachment) { + size_t index; + return AddAttachment(attachment, &index); +} + +scoped_refptr MessageAttachmentSet::GetAttachmentAt( + unsigned index) { + if (index >= size()) { + DLOG(WARNING) << "Accessing out of bound index:" << index << "/" << size(); + return scoped_refptr(); + } + + // We should always walk the descriptors in order, so it's reasonable to + // enforce this. Consider the case where a compromised renderer sends us + // the following message: + // + // ExampleMsg: + // num_fds:2 msg:FD(index = 1) control:SCM_RIGHTS {n, m} + // + // Here the renderer sent us a message which should have a descriptor, but + // actually sent two in an attempt to fill our fd table and kill us. By + // setting the index of the descriptor in the message to 1 (it should be + // 0), we would record a highwater of 1 and then consider all the + // descriptors to have been used. + // + // So we can either track of the use of each descriptor in a bitset, or we + // can enforce that we walk the indexes strictly in order. + // + // There's one more wrinkle: When logging messages, we may reparse them. So + // we have an exception: When the consumed_descriptor_highwater_ is at the + // end of the array and index 0 is requested, we reset the highwater value. + // TODO(morrita): This is absurd. This "wringle" disallow to introduce clearer + // ownership model. Only client is NaclIPCAdapter. See crbug.com/415294 + if (index == 0 && consumed_descriptor_highwater_ == size()) { + consumed_descriptor_highwater_ = 0; + } + + if (index != consumed_descriptor_highwater_) + return scoped_refptr(); + + consumed_descriptor_highwater_ = index + 1; + + return attachments_[index]; +} + +void MessageAttachmentSet::CommitAllDescriptors() { + attachments_.clear(); + consumed_descriptor_highwater_ = 0; +} + +} // namespace IPC diff --git a/ipc/ipc_message_attachment_set.h b/ipc/ipc_message_attachment_set.h new file mode 100644 index 0000000..de37211 --- /dev/null +++ b/ipc/ipc_message_attachment_set.h @@ -0,0 +1,96 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_MESSAGE_ATTACHMENT_SET_H_ +#define IPC_IPC_MESSAGE_ATTACHMENT_SET_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "build/build_config.h" +#include "ipc/ipc_export.h" + +namespace IPC { + +class MessageAttachment; + +// ----------------------------------------------------------------------------- +// A MessageAttachmentSet is an ordered set of MessageAttachment objects +// associated with an IPC message. All attachments are wrapped in a mojo handle +// if necessary and sent over the mojo message pipe. +// +// For ChannelNacl under SFI NaCl, only Type::PLATFORM_FILE is supported. In +// that case, the FD is sent over socket. +// ----------------------------------------------------------------------------- +class IPC_EXPORT MessageAttachmentSet + : public base::RefCountedThreadSafe { + public: + MessageAttachmentSet(); + + // Return the number of attachments + unsigned size() const; + + // Return true if no unconsumed descriptors remain + bool empty() const { return attachments_.empty(); } + + // Returns whether the attachment was successfully added. + // |index| is an output variable. On success, it contains the index of the + // newly added attachment. + bool AddAttachment(scoped_refptr attachment, + size_t* index); + + // Similar to the above method, but without output variables. + bool AddAttachment(scoped_refptr attachment); + + // Take the nth from the beginning of the vector, Code using this /must/ + // access the attachments in order, and must do it at most once. + // + // This interface is designed for the deserialising code as it doesn't + // support close flags. + // returns: an attachment, or nullptr on error + scoped_refptr GetAttachmentAt(unsigned index); + + // Marks all the descriptors as consumed and closes those which are + // auto-close. + void CommitAllDescriptors(); + +#if defined(OS_POSIX) + // This is the maximum number of descriptors per message. We need to know this + // because the control message kernel interface has to be given a buffer which + // is large enough to store all the descriptor numbers. Otherwise the kernel + // tells us that it truncated the control data and the extra descriptors are + // lost. + // + // In debugging mode, it's a fatal error to try and add more than this number + // of descriptors to a MessageAttachmentSet. + static const size_t kMaxDescriptorsPerMessage = 7; +#endif // OS_POSIX + + // --------------------------------------------------------------------------- + + private: + friend class base::RefCountedThreadSafe; + + ~MessageAttachmentSet(); + + // Return the number of file descriptors + unsigned num_descriptors() const; + + std::vector> attachments_; + + // This contains the index of the next descriptor which should be consumed. + // It's used in a couple of ways. Firstly, at destruction we can check that + // all the descriptors have been read (with GetNthDescriptor). Secondly, we + // can check that they are read in order. + unsigned consumed_descriptor_highwater_; + + DISALLOW_COPY_AND_ASSIGN(MessageAttachmentSet); +}; + +} // namespace IPC + +#endif // IPC_IPC_MESSAGE_ATTACHMENT_SET_H_ diff --git a/ipc/ipc_message_start.h b/ipc/ipc_message_start.h new file mode 100644 index 0000000..1368635 --- /dev/null +++ b/ipc/ipc_message_start.h @@ -0,0 +1,126 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_MESSAGE_START_H_ +#define IPC_IPC_MESSAGE_START_H_ + +// Used by IPC_BEGIN_MESSAGES so that each message class starts from a unique +// base. Messages have unique IDs across channels in order for the IPC logging +// code to figure out the message class from its ID. +enum IPCMessageStart { + AutomationMsgStart = 0, + FrameMsgStart, + PageMsgStart, + ViewMsgStart, + InputMsgStart, + ProfileImportMsgStart, + TestMsgStart, + DevToolsMsgStart, + WorkerMsgStart, + NaClMsgStart, + UtilityMsgStart, + GpuChannelMsgStart, + GpuMsgStart, + MediaMsgStart, + ServiceMsgStart, + PpapiMsgStart, + FirefoxImporterUnittestMsgStart, + FileUtilitiesMsgStart, + DatabaseMsgStart, + DOMStorageMsgStart, + SpeechRecognitionMsgStart, + SafeBrowsingMsgStart, + P2PMsgStart, + ResourceMsgStart, + FileSystemMsgStart, + ChildProcessMsgStart, + ClipboardMsgStart, + BlobMsgStart, + AppCacheMsgStart, + AudioMsgStart, + MidiMsgStart, + ChromeMsgStart, + DragMsgStart, + PrintMsgStart, + SpellCheckMsgStart, + ExtensionMsgStart, + VideoCaptureMsgStart, + QuotaMsgStart, + TextInputClientMsgStart, + ChromeUtilityMsgStart, + MediaStreamMsgStart, + ChromeBenchmarkingMsgStart, + JavaBridgeMsgStart, + GamepadMsgStart, + ShellMsgStart, + AccessibilityMsgStart, + PrefetchMsgStart, + PrerenderMsgStart, + ChromotingMsgStart, + BrowserPluginMsgStart, + AndroidWebViewMsgStart, + MetroViewerMsgStart, + CCMsgStart, + MediaPlayerMsgStart, + TracingMsgStart, + PeerConnectionTrackerMsgStart, + VisitedLinkMsgStart, + AppShimMsgStart, + WebRtcLoggingMsgStart, + TtsMsgStart, + WebSocketMsgStart, + NaClHostMsgStart, + WebRTCIdentityMsgStart, + PowerMonitorMsgStart, + EncryptedMediaMsgStart, + CacheStorageMsgStart, + ServiceWorkerMsgStart, + MessagePortMsgStart, + EmbeddedWorkerMsgStart, + EmbeddedWorkerContextMsgStart, + CastMsgStart, + CdmMsgStart, + MediaStreamTrackMetricsHostMsgStart, + ChromeExtensionMsgStart, + PushMessagingMsgStart, + GinJavaBridgeMsgStart, + ChromeUtilityPrintingMsgStart, + AecDumpMsgStart, + OzoneGpuMsgStart, + ChromeUtilityExtensionsMsgStart, + PlatformNotificationMsgStart, + PDFMsgStart, + ManifestManagerMsgStart, + LayoutTestMsgStart, + NetworkHintsMsgStart, + BluetoothMsgStart, + CastMediaMsgStart, + AwMessagePortMsgStart, + SyncCompositorMsgStart, + ExtensionsGuestViewMsgStart, + GuestViewMsgStart, + // Note: CastCryptoMsgStart and CastChannelMsgStart reserved for Chromecast + // internal code. Contact gunsch@ before changing/removing. + CastCryptoMsgStart, + CastChannelMsgStart, + DataReductionProxyStart, + ChromeAppBannerMsgStart, + AttachmentBrokerMsgStart, + RenderProcessMsgStart, + PageLoadMetricsMsgStart, + MemoryMsgStart, + IPCTestMsgStart, + ArcInstanceMsgStart, + ArcInstanceHostMsgStart, + DistillerMsgStart, + ArcCameraMsgStart, + DWriteFontProxyMsgStart, + MediaPlayerDelegateMsgStart, + SurfaceViewManagerMsgStart, + ExtensionWorkerMsgStart, + SubresourceFilterMsgStart, + LastIPCMsgStart // Must come last. +}; + +#endif // IPC_IPC_MESSAGE_START_H_ diff --git a/ipc/ipc_message_utils.cc b/ipc/ipc_message_utils.cc new file mode 100644 index 0000000..bf8daa5 --- /dev/null +++ b/ipc/ipc_message_utils.cc @@ -0,0 +1,1238 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ipc/ipc_message_utils.h" + +#include +#include + +#include "base/files/file_path.h" +#include "base/json/json_writer.h" +#include "base/strings/nullable_string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/unguessable_token.h" +#include "base/values.h" +#include "build/build_config.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_message_attachment.h" +#include "ipc/ipc_message_attachment_set.h" +#include "ipc/ipc_mojo_param_traits.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#include "ipc/ipc_platform_file_attachment_posix.h" +#endif + +#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) +#include "base/memory/shared_memory_handle.h" +#endif // (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "ipc/mach_port_mac.h" +#endif + +#if defined(OS_WIN) +#include +#include "ipc/handle_win.h" +#endif + +namespace IPC { + +namespace { + +const int kMaxRecursionDepth = 200; + +template +void LogBytes(const std::vector& data, std::string* out) { +#if defined(OS_WIN) + // Windows has a GUI for logging, which can handle arbitrary binary data. + for (size_t i = 0; i < data.size(); ++i) + out->push_back(data[i]); +#else + // On POSIX, we log to stdout, which we assume can display ASCII. + static const size_t kMaxBytesToLog = 100; + for (size_t i = 0; i < std::min(data.size(), kMaxBytesToLog); ++i) { + if (isprint(data[i])) + out->push_back(data[i]); + else + out->append( + base::StringPrintf("[%02X]", static_cast(data[i]))); + } + if (data.size() > kMaxBytesToLog) { + out->append(base::StringPrintf( + " and %u more bytes", + static_cast(data.size() - kMaxBytesToLog))); + } +#endif +} + +bool ReadValue(const base::Pickle* m, + base::PickleIterator* iter, + base::Value** value, + int recursion); + +void GetValueSize(base::PickleSizer* sizer, + const base::Value* value, + int recursion) { + if (recursion > kMaxRecursionDepth) { + LOG(ERROR) << "Max recursion depth hit in GetValueSize."; + return; + } + + sizer->AddInt(); + switch (value->GetType()) { + case base::Value::Type::NONE: + break; + case base::Value::Type::BOOLEAN: + sizer->AddBool(); + break; + case base::Value::Type::INTEGER: + sizer->AddInt(); + break; + case base::Value::Type::DOUBLE: + sizer->AddDouble(); + break; + case base::Value::Type::STRING: { + const base::Value* result; + value->GetAsString(&result); + if (value->GetAsString(&result)) { + DCHECK(result); + GetParamSize(sizer, result->GetString()); + } else { + std::string str; + bool as_string_result = value->GetAsString(&str); + DCHECK(as_string_result); + GetParamSize(sizer, str); + } + break; + } + case base::Value::Type::BINARY: { + sizer->AddData(static_cast(value->GetSize())); + break; + } + case base::Value::Type::DICTIONARY: { + sizer->AddInt(); + const base::DictionaryValue* dict = + static_cast(value); + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); + it.Advance()) { + GetParamSize(sizer, it.key()); + GetValueSize(sizer, &it.value(), recursion + 1); + } + break; + } + case base::Value::Type::LIST: { + sizer->AddInt(); + const base::ListValue* list = static_cast(value); + for (const auto& entry : *list) { + GetValueSize(sizer, entry.get(), recursion + 1); + } + break; + } + default: + NOTREACHED() << "Invalid base::Value type."; + } +} + +void WriteValue(base::Pickle* m, const base::Value* value, int recursion) { + bool result; + if (recursion > kMaxRecursionDepth) { + LOG(ERROR) << "Max recursion depth hit in WriteValue."; + return; + } + + m->WriteInt(static_cast(value->GetType())); + + switch (value->GetType()) { + case base::Value::Type::NONE: + break; + case base::Value::Type::BOOLEAN: { + bool val; + result = value->GetAsBoolean(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::Type::INTEGER: { + int val; + result = value->GetAsInteger(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::Type::DOUBLE: { + double val; + result = value->GetAsDouble(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::Type::STRING: { + std::string val; + result = value->GetAsString(&val); + DCHECK(result); + WriteParam(m, val); + break; + } + case base::Value::Type::BINARY: { + m->WriteData(value->GetBuffer(), static_cast(value->GetSize())); + break; + } + case base::Value::Type::DICTIONARY: { + const base::DictionaryValue* dict = + static_cast(value); + + WriteParam(m, static_cast(dict->size())); + + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); + it.Advance()) { + WriteParam(m, it.key()); + WriteValue(m, &it.value(), recursion + 1); + } + break; + } + case base::Value::Type::LIST: { + const base::ListValue* list = static_cast(value); + WriteParam(m, static_cast(list->GetSize())); + for (const auto& entry : *list) { + WriteValue(m, entry.get(), recursion + 1); + } + break; + } + } +} + +// Helper for ReadValue that reads a DictionaryValue into a pre-allocated +// object. +bool ReadDictionaryValue(const base::Pickle* m, + base::PickleIterator* iter, + base::DictionaryValue* value, + int recursion) { + int size; + if (!ReadParam(m, iter, &size)) + return false; + + for (int i = 0; i < size; ++i) { + std::string key; + base::Value* subval; + if (!ReadParam(m, iter, &key) || + !ReadValue(m, iter, &subval, recursion + 1)) + return false; + value->SetWithoutPathExpansion(key, subval); + } + + return true; +} + +// Helper for ReadValue that reads a ReadListValue into a pre-allocated +// object. +bool ReadListValue(const base::Pickle* m, + base::PickleIterator* iter, + base::ListValue* value, + int recursion) { + int size; + if (!ReadParam(m, iter, &size)) + return false; + + for (int i = 0; i < size; ++i) { + base::Value* subval; + if (!ReadValue(m, iter, &subval, recursion + 1)) + return false; + value->Set(i, subval); + } + + return true; +} + +bool ReadValue(const base::Pickle* m, + base::PickleIterator* iter, + base::Value** value, + int recursion) { + if (recursion > kMaxRecursionDepth) { + LOG(ERROR) << "Max recursion depth hit in ReadValue."; + return false; + } + + int type; + if (!ReadParam(m, iter, &type)) + return false; + + switch (static_cast(type)) { + case base::Value::Type::NONE: + *value = base::Value::CreateNullValue().release(); + break; + case base::Value::Type::BOOLEAN: { + bool val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::Value(val); + break; + } + case base::Value::Type::INTEGER: { + int val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::Value(val); + break; + } + case base::Value::Type::DOUBLE: { + double val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::Value(val); + break; + } + case base::Value::Type::STRING: { + std::string val; + if (!ReadParam(m, iter, &val)) + return false; + *value = new base::Value(val); + break; + } + case base::Value::Type::BINARY: { + const char* data; + int length; + if (!iter->ReadData(&data, &length)) + return false; + std::unique_ptr val = + base::BinaryValue::CreateWithCopiedBuffer(data, length); + *value = val.release(); + break; + } + case base::Value::Type::DICTIONARY: { + std::unique_ptr val(new base::DictionaryValue()); + if (!ReadDictionaryValue(m, iter, val.get(), recursion)) + return false; + *value = val.release(); + break; + } + case base::Value::Type::LIST: { + std::unique_ptr val(new base::ListValue()); + if (!ReadListValue(m, iter, val.get(), recursion)) + return false; + *value = val.release(); + break; + } + default: + return false; + } + + return true; +} + +} // namespace + +// ----------------------------------------------------------------------------- + +LogData::LogData() + : routing_id(0), + type(0), + sent(0), + receive(0), + dispatch(0) { +} + +LogData::LogData(const LogData& other) = default; + +LogData::~LogData() { +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(p ? "true" : "false"); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(sizeof(param_type)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char* data; + if (!iter->ReadBytes(&data, sizeof(param_type))) + return false; + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::IntToString(p)); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(sizeof(param_type)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char* data; + if (!iter->ReadBytes(&data, sizeof(param_type))) + return false; + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(sizeof(param_type)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteBytes(&p, sizeof(param_type)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char* data; + if (!iter->ReadBytes(&data, sizeof(param_type))) + return false; + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::IntToString(p)); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::UintToString(p)); +} + +#if defined(OS_WIN) || defined(OS_LINUX) || \ + (defined(OS_ANDROID) && defined(ARCH_CPU_64_BITS)) +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::Int64ToString(static_cast(p))); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::Uint64ToString(static_cast(p))); +} +#endif + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::Int64ToString(static_cast(p))); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::Uint64ToString(p)); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("%e", p)); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(sizeof(param_type)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteBytes(reinterpret_cast(&p), sizeof(param_type)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + if (!iter->ReadBytes(&data, sizeof(*r))) { + NOTREACHED(); + return false; + } + memcpy(r, data, sizeof(param_type)); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("%e", p)); +} + + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(p); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::UTF16ToUTF8(p)); +} + +void ParamTraits>::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddData(static_cast(p.size())); +} + +void ParamTraits>::Write(base::Pickle* m, + const param_type& p) { + if (p.empty()) { + m->WriteData(NULL, 0); + } else { + m->WriteData(&p.front(), static_cast(p.size())); + } +} + +bool ParamTraits>::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (!iter->ReadData(&data, &data_size) || data_size < 0) + return false; + r->resize(data_size); + if (data_size) + memcpy(&r->front(), data, data_size); + return true; +} + +void ParamTraits >::Log(const param_type& p, std::string* l) { + LogBytes(p, l); +} + +void ParamTraits>::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddData(static_cast(p.size())); +} + +void ParamTraits>::Write(base::Pickle* m, + const param_type& p) { + if (p.empty()) { + m->WriteData(NULL, 0); + } else { + m->WriteData(reinterpret_cast(&p.front()), + static_cast(p.size())); + } +} + +bool ParamTraits>::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (!iter->ReadData(&data, &data_size) || data_size < 0) + return false; + r->resize(data_size); + if (data_size) + memcpy(&r->front(), data, data_size); + return true; +} + +void ParamTraits >::Log(const param_type& p, + std::string* l) { + LogBytes(p, l); +} + +void ParamTraits>::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + for (size_t i = 0; i < p.size(); ++i) + GetParamSize(sizer, static_cast(p[i])); +} + +void ParamTraits>::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, static_cast(p.size())); + // Cast to bool below is required because libc++'s + // vector::const_reference is different from bool, and we want to avoid + // writing an extra specialization of ParamTraits for it. + for (size_t i = 0; i < p.size(); i++) + WriteParam(m, static_cast(p[i])); +} + +bool ParamTraits>::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + // ReadLength() checks for < 0 itself. + if (!iter->ReadLength(&size)) + return false; + r->resize(size); + for (int i = 0; i < size; i++) { + bool value; + if (!ReadParam(m, iter, &value)) + return false; + (*r)[i] = value; + } + return true; +} + +void ParamTraits >::Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p.size(); ++i) { + if (i != 0) + l->push_back(' '); + LogParam(static_cast(p[i]), l); + } +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetValueSize(sizer, &p, 0); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteValue(m, &p, 0); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int type; + if (!ReadParam(m, iter, &type) || + type != static_cast(base::Value::Type::DICTIONARY)) + return false; + + return ReadDictionaryValue(m, iter, r, 0); +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + std::string json; + base::JSONWriter::Write(p, &json); + l->append(json); +} + +#if defined(OS_POSIX) +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.fd >= 0); + if (p.fd >= 0) + sizer->AddAttachment(); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + const bool valid = p.fd >= 0; + WriteParam(m, valid); + + if (!valid) + return; + + if (p.auto_close) { + if (!m->WriteAttachment( + new internal::PlatformFileAttachment(base::ScopedFD(p.fd)))) + NOTREACHED(); + } else { + if (!m->WriteAttachment(new internal::PlatformFileAttachment(p.fd))) + NOTREACHED(); + } +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + *r = base::FileDescriptor(); + + bool valid; + if (!ReadParam(m, iter, &valid)) + return false; + + // TODO(morrita): Seems like this should return false. + if (!valid) + return true; + + scoped_refptr attachment; + if (!m->ReadAttachment(iter, &attachment)) + return false; + + if (static_cast(attachment.get())->GetType() != + MessageAttachment::Type::PLATFORM_FILE) { + return false; + } + + *r = base::FileDescriptor( + static_cast(attachment.get()) + ->TakePlatformFile(), + true); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + if (p.auto_close) { + l->append(base::StringPrintf("FD(%d auto-close)", p.fd)); + } else { + l->append(base::StringPrintf("FD(%d)", p.fd)); + } +} +#endif // defined(OS_POSIX) + +#if defined(OS_MACOSX) && !defined(OS_IOS) +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.GetMemoryObject()); + uint32_t dummy = 0; + GetParamSize(sizer, dummy); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + MachPortMac mach_port_mac(p.GetMemoryObject()); + ParamTraits::Write(m, mach_port_mac); + size_t size = 0; + bool result = p.GetSize(&size); + DCHECK(result); + ParamTraits::Write(m, static_cast(size)); + + // If the caller intended to pass ownership to the IPC stack, release a + // reference. + if (p.OwnershipPassesToIPC()) + p.Close(); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + MachPortMac mach_port_mac; + if (!ParamTraits::Read(m, iter, &mach_port_mac)) + return false; + + uint32_t size; + if (!ParamTraits::Read(m, iter, &size)) + return false; + + *r = base::SharedMemoryHandle(mach_port_mac.get_mach_port(), + static_cast(size), + base::GetCurrentProcId()); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("Mach port: "); + LogParam(p.GetMemoryObject(), l); +} + +#elif defined(OS_WIN) +void ParamTraits::GetSize(base::PickleSizer* s, + const param_type& p) { + GetParamSize(s, p.NeedsBrokering()); + if (p.NeedsBrokering()) { + GetParamSize(s, p.GetHandle()); + } else { + GetParamSize(s, HandleToLong(p.GetHandle())); + } +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + m->WriteBool(p.NeedsBrokering()); + + if (p.NeedsBrokering()) { + HandleWin handle_win(p.GetHandle(), HandleWin::DUPLICATE); + ParamTraits::Write(m, handle_win); + + // If the caller intended to pass ownership to the IPC stack, release a + // reference. + if (p.OwnershipPassesToIPC() && p.BelongsToCurrentProcess()) + p.Close(); + } else { + m->WriteInt(HandleToLong(p.GetHandle())); + } +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + bool needs_brokering; + if (!iter->ReadBool(&needs_brokering)) + return false; + + if (needs_brokering) { + HandleWin handle_win; + if (!ParamTraits::Read(m, iter, &handle_win)) + return false; + *r = base::SharedMemoryHandle(handle_win.get_handle(), + base::GetCurrentProcId()); + return true; + } + + int handle_int; + if (!iter->ReadInt(&handle_int)) + return false; + HANDLE handle = LongToHandle(handle_int); + *r = base::SharedMemoryHandle(handle, base::GetCurrentProcId()); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + LogParam(p.GetHandle(), l); + l->append(" needs brokering: "); + LogParam(p.NeedsBrokering(), l); +} +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + p.GetSizeForPickle(sizer); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + p.WriteToPickle(m); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return r->ReadFromPickle(iter); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + ParamTraits::Log(p.value(), l); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetValueSize(sizer, &p, 0); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + WriteValue(m, &p, 0); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int type; + if (!ReadParam(m, iter, &type) || + type != static_cast(base::Value::Type::LIST)) + return false; + + return ReadListValue(m, iter, r, 0); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + std::string json; + base::JSONWriter::Write(p, &json); + l->append(json); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.string()); + GetParamSize(sizer, p.is_null()); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, p.string()); + WriteParam(m, p.is_null()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + base::string16 string; + if (!ReadParam(m, iter, &string)) + return false; + bool is_null; + if (!ReadParam(m, iter, &is_null)) + return false; + *r = base::NullableString16(string, is_null); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("("); + LogParam(p.string(), l); + l->append(", "); + LogParam(p.is_null(), l); + l->append(")"); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.size); + GetParamSize(sizer, p.is_directory); + GetParamSize(sizer, p.last_modified.ToDoubleT()); + GetParamSize(sizer, p.last_accessed.ToDoubleT()); + GetParamSize(sizer, p.creation_time.ToDoubleT()); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, p.size); + WriteParam(m, p.is_directory); + WriteParam(m, p.last_modified.ToDoubleT()); + WriteParam(m, p.last_accessed.ToDoubleT()); + WriteParam(m, p.creation_time.ToDoubleT()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* p) { + double last_modified, last_accessed, creation_time; + if (!ReadParam(m, iter, &p->size) || + !ReadParam(m, iter, &p->is_directory) || + !ReadParam(m, iter, &last_modified) || + !ReadParam(m, iter, &last_accessed) || + !ReadParam(m, iter, &creation_time)) + return false; + p->last_modified = base::Time::FromDoubleT(last_modified); + p->last_accessed = base::Time::FromDoubleT(last_accessed); + p->creation_time = base::Time::FromDoubleT(creation_time); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("("); + LogParam(p.size, l); + l->append(","); + LogParam(p.is_directory, l); + l->append(","); + LogParam(p.last_modified.ToDoubleT(), l); + l->append(","); + LogParam(p.last_accessed.ToDoubleT(), l); + l->append(","); + LogParam(p.creation_time.ToDoubleT(), l); + l->append(")"); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt64(); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + ParamTraits::Write(m, p.ToInternalValue()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int64_t value; + if (!ParamTraits::Read(m, iter, &value)) + return false; + *r = base::Time::FromInternalValue(value); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + ParamTraits::Log(p.ToInternalValue(), l); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt64(); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + ParamTraits::Write(m, p.ToInternalValue()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int64_t value; + bool ret = ParamTraits::Read(m, iter, &value); + if (ret) + *r = base::TimeDelta::FromInternalValue(value); + + return ret; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + ParamTraits::Log(p.ToInternalValue(), l); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt64(); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + ParamTraits::Write(m, p.ToInternalValue()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int64_t value; + bool ret = ParamTraits::Read(m, iter, &value); + if (ret) + *r = base::TimeTicks::FromInternalValue(value); + + return ret; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + ParamTraits::Log(p.ToInternalValue(), l); +} + +// If base::UnguessableToken is no longer 128 bits, the IPC serialization logic +// below should be updated. +static_assert(sizeof(base::UnguessableToken) == 2 * sizeof(uint64_t), + "base::UnguessableToken should be of size 2 * sizeof(uint64_t)."); + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddBytes(2 * sizeof(uint64_t)); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + DCHECK(!p.is_empty()); + + ParamTraits::Write(m, p.GetHighForSerialization()); + ParamTraits::Write(m, p.GetLowForSerialization()); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + uint64_t high, low; + if (!ParamTraits::Read(m, iter, &high) || + !ParamTraits::Read(m, iter, &low)) + return false; + + // Receiving a zeroed UnguessableToken is a security issue. + if (high == 0 && low == 0) + return false; + + *r = base::UnguessableToken::Deserialize(high, low); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append(p.ToString()); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { +#if defined(OS_NACL_SFI) + GetParamSize(sizer, p.socket); +#else + GetParamSize(sizer, p.mojo_handle); +#endif +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { +#if defined(OS_NACL_SFI) + WriteParam(m, p.socket); +#else + WriteParam(m, p.mojo_handle); +#endif +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { +#if defined(OS_NACL_SFI) + return ReadParam(m, iter, &r->socket); +#else + return ReadParam(m, iter, &r->mojo_handle); +#endif +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("ChannelHandle("); +#if defined(OS_NACL_SFI) + ParamTraits::Log(p.socket, l); +#else + LogParam(p.mojo_handle, l); +#endif + l->append(")"); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.channel); + GetParamSize(sizer, p.routing_id); + GetParamSize(sizer, p.type); + GetParamSize(sizer, p.flags); + GetParamSize(sizer, p.sent); + GetParamSize(sizer, p.receive); + GetParamSize(sizer, p.dispatch); + GetParamSize(sizer, p.message_name); + GetParamSize(sizer, p.params); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + WriteParam(m, p.channel); + WriteParam(m, p.routing_id); + WriteParam(m, p.type); + WriteParam(m, p.flags); + WriteParam(m, p.sent); + WriteParam(m, p.receive); + WriteParam(m, p.dispatch); + WriteParam(m, p.message_name); + WriteParam(m, p.params); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return + ReadParam(m, iter, &r->channel) && + ReadParam(m, iter, &r->routing_id) && + ReadParam(m, iter, &r->type) && + ReadParam(m, iter, &r->flags) && + ReadParam(m, iter, &r->sent) && + ReadParam(m, iter, &r->receive) && + ReadParam(m, iter, &r->dispatch) && + ReadParam(m, iter, &r->message_name) && + ReadParam(m, iter, &r->params); +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + // Doesn't make sense to implement this! +} + +void ParamTraits::Write(base::Pickle* m, const Message& p) { +#if defined(OS_POSIX) + // We don't serialize the file descriptors in the nested message, so there + // better not be any. + DCHECK(!p.HasAttachments()); +#endif + + // Don't just write out the message. This is used to send messages between + // NaCl (Posix environment) and the browser (could be on Windows). The message + // header formats differ between these systems (so does handle sharing, but + // we already asserted we don't have any handles). So just write out the + // parts of the header we use. + // + // Be careful also to use only explicitly-sized types. The NaCl environment + // could be 64-bit and the host browser could be 32-bits. The nested message + // may or may not be safe to send between 32-bit and 64-bit systems, but we + // leave that up to the code sending the message to ensure. + m->WriteUInt32(static_cast(p.routing_id())); + m->WriteUInt32(p.type()); + m->WriteUInt32(p.flags()); + m->WriteData(p.payload(), static_cast(p.payload_size())); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + Message* r) { + uint32_t routing_id, type, flags; + if (!iter->ReadUInt32(&routing_id) || + !iter->ReadUInt32(&type) || + !iter->ReadUInt32(&flags)) + return false; + + int payload_size; + const char* payload; + if (!iter->ReadData(&payload, &payload_size)) + return false; + + r->SetHeaderValues(static_cast(routing_id), type, flags); + return r->WriteBytes(payload, payload_size); +} + +void ParamTraits::Log(const Message& p, std::string* l) { + l->append(""); +} + +#if defined(OS_WIN) +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddInt(); +} + +// Note that HWNDs/HANDLE/HCURSOR/HACCEL etc are always 32 bits, even on 64 +// bit systems. That's why we use the Windows macros to convert to 32 bits. +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteInt(HandleToLong(p)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int32_t temp; + if (!iter->ReadInt(&temp)) + return false; + *r = LongToHandle(temp); + return true; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("0x%p", p)); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + sizer->AddData(sizeof(LOGFONT)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteData(reinterpret_cast(&p), sizeof(LOGFONT)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + if (iter->ReadData(&data, &data_size) && data_size == sizeof(LOGFONT)) { + const LOGFONT *font = reinterpret_cast(const_cast(data)); + if (_tcsnlen(font->lfFaceName, LF_FACESIZE) < LF_FACESIZE) { + memcpy(r, data, sizeof(LOGFONT)); + return true; + } + } + + NOTREACHED(); + return false; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(base::StringPrintf("")); +} + +void ParamTraits::GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddData(sizeof(MSG)); +} + +void ParamTraits::Write(base::Pickle* m, const param_type& p) { + m->WriteData(reinterpret_cast(&p), sizeof(MSG)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + const char *data; + int data_size = 0; + bool result = iter->ReadData(&data, &data_size); + if (result && data_size == sizeof(MSG)) { + memcpy(r, data, sizeof(MSG)); + } else { + result = false; + NOTREACHED(); + } + + return result; +} + +void ParamTraits::Log(const param_type& p, std::string* l) { + l->append(""); +} + +#endif // OS_WIN + +} // namespace IPC diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h new file mode 100644 index 0000000..2d51c98 --- /dev/null +++ b/ipc/ipc_message_utils.h @@ -0,0 +1,1145 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_MESSAGE_UTILS_H_ +#define IPC_IPC_MESSAGE_UTILS_H_ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "base/containers/small_map.h" +#include "base/containers/stack_container.h" +#include "base/files/file.h" +#include "base/format_macros.h" +#include "base/optional.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "build/build_config.h" +#include "ipc/ipc_message_start.h" +#include "ipc/ipc_param_traits.h" +#include "ipc/ipc_sync_message.h" + +namespace base { +class DictionaryValue; +class FilePath; +class ListValue; +class NullableString16; +class Time; +class TimeDelta; +class TimeTicks; +class UnguessableToken; +struct FileDescriptor; + +#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) +class SharedMemoryHandle; +#endif // (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) +} + +namespace IPC { + +struct ChannelHandle; + +// ----------------------------------------------------------------------------- +// How we send IPC message logs across channels. +struct IPC_EXPORT LogData { + LogData(); + LogData(const LogData& other); + ~LogData(); + + std::string channel; + int32_t routing_id; + uint32_t type; // "User-defined" message type, from ipc_message.h. + std::string flags; + int64_t sent; // Time that the message was sent (i.e. at Send()). + int64_t receive; // Time before it was dispatched (i.e. before calling + // OnMessageReceived). + int64_t dispatch; // Time after it was dispatched (i.e. after calling + // OnMessageReceived). + std::string message_name; + std::string params; +}; + +//----------------------------------------------------------------------------- + +// A dummy struct to place first just to allow leading commas for all +// members in the macro-generated constructor initializer lists. +struct NoParams { +}; + +// Specializations are checked by 'IPC checker' part of find-bad-constructs +// Clang plugin (see WriteParam() below for the details). +template +struct CheckedTuple { + typedef std::tuple Tuple; +}; + +template +static inline void GetParamSize(base::PickleSizer* sizer, const P& p) { + typedef typename SimilarTypeTraits

::Type Type; + ParamTraits::GetSize(sizer, static_cast(p)); +} + +// This function is checked by 'IPC checker' part of find-bad-constructs +// Clang plugin to make it's not called on the following types: +// 1. long / unsigned long (but not typedefs to) +// 2. intmax_t, uintmax_t, intptr_t, uintptr_t, wint_t, +// size_t, rsize_t, ssize_t, ptrdiff_t, dev_t, off_t, clock_t, +// time_t, suseconds_t (including typedefs to) +// 3. Any template referencing types above (e.g. std::vector) +template +static inline void WriteParam(base::Pickle* m, const P& p) { + typedef typename SimilarTypeTraits

::Type Type; + ParamTraits::Write(m, static_cast(p)); +} + +template +static inline bool WARN_UNUSED_RESULT ReadParam(const base::Pickle* m, + base::PickleIterator* iter, + P* p) { + typedef typename SimilarTypeTraits

::Type Type; + return ParamTraits::Read(m, iter, reinterpret_cast(p)); +} + +template +static inline void LogParam(const P& p, std::string* l) { + typedef typename SimilarTypeTraits

::Type Type; + ParamTraits::Log(static_cast(p), l); +} + +// Primitive ParamTraits ------------------------------------------------------- + +template <> +struct ParamTraits { + typedef bool param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddBool(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteBool(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadBool(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef signed char param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef unsigned char param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef unsigned short param_type; + 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); +}; + +template <> +struct ParamTraits { + typedef int param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddInt(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteInt(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadInt(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef unsigned int param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddInt(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteInt(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadInt(reinterpret_cast(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +// long isn't safe to send over IPC because it's 4 bytes on 32 bit builds but +// 8 bytes on 64 bit builds. So if a 32 bit and 64 bit process have a channel +// that would cause problem. +// We need to keep this on for a few configs: +// 1) Windows because DWORD is typedef'd to it, which is fine because we have +// very few IPCs that cross this boundary. +// 2) We also need to keep it for Linux for two reasons: int64_t is typedef'd +// to long, and gfx::PluginWindow is long and is used in one GPU IPC. +// 3) Android 64 bit also has int64_t typedef'd to long. +// Since we want to support Android 32<>64 bit IPC, as long as we don't have +// these traits for 32 bit ARM then that'll catch any errors. +#if defined(OS_WIN) || defined(OS_LINUX) || \ + (defined(OS_ANDROID) && defined(ARCH_CPU_64_BITS)) +template <> +struct ParamTraits { + typedef long param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddLong(); + } + static void Write(base::Pickle* m, const param_type& p) { + m->WriteLong(p); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadLong(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef unsigned long param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddLong(); + } + static void Write(base::Pickle* m, const param_type& p) { + m->WriteLong(p); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadLong(reinterpret_cast(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; +#endif + +template <> +struct ParamTraits { + typedef long long param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddInt64(); + } + static void Write(base::Pickle* m, const param_type& p) { + m->WriteInt64(static_cast(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadInt64(reinterpret_cast(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef unsigned long long param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddInt64(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteInt64(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadInt64(reinterpret_cast(r)); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +// Note that the IPC layer doesn't sanitize NaNs and +/- INF values. Clients +// should be sure to check the sanity of these values after receiving them over +// IPC. +template <> +struct IPC_EXPORT ParamTraits { + typedef float param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddFloat(); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteFloat(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadFloat(r); + } + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef double param_type; + 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); +}; + +template +struct ParamTraits { + using param_type = P[Size]; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + for (const P& element : p) + GetParamSize(sizer, element); + } + static void Write(base::Pickle* m, const param_type& p) { + for (const P& element : p) + WriteParam(m, element); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + for (P& element : *r) { + if (!ReadParam(m, iter, &element)) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append("["); + for (const P& element : p) { + if (&element != &p[0]) + l->append(" "); + LogParam(element, l); + } + l->append("]"); + } +}; + +// STL ParamTraits ------------------------------------------------------------- + +template <> +struct ParamTraits { + typedef std::string param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddString(p); + } + static void Write(base::Pickle* m, const param_type& p) { m->WriteString(p); } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadString(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits { + typedef base::string16 param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + sizer->AddString16(p); + } + static void Write(base::Pickle* m, const param_type& p) { + m->WriteString16(p); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return iter->ReadString16(r); + } + IPC_EXPORT static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits > { + typedef std::vector param_type; + 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*, + base::PickleIterator* iter, + param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct IPC_EXPORT ParamTraits > { + typedef std::vector param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits > { + typedef std::vector param_type; + 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); +}; + +template +struct ParamTraits> { + typedef std::vector

param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + for (size_t i = 0; i < p.size(); i++) + GetParamSize(sizer, p[i]); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p.size())); + for (size_t i = 0; i < p.size(); i++) + WriteParam(m, p[i]); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + // ReadLength() checks for < 0 itself. + if (!iter->ReadLength(&size)) + return false; + // Resizing beforehand is not safe, see BUG 1006367 for details. + if (INT_MAX / sizeof(P) <= static_cast(size)) + return false; + r->resize(size); + for (int i = 0; i < size; i++) { + if (!ReadParam(m, iter, &(*r)[i])) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p.size(); ++i) { + if (i != 0) + l->append(" "); + LogParam((p[i]), l); + } + } +}; + +template +struct ParamTraits > { + typedef std::set

param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) + GetParamSize(sizer, *iter); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) + WriteParam(m, *iter); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + if (!iter->ReadLength(&size)) + return false; + for (int i = 0; i < size; ++i) { + P item; + if (!ReadParam(m, iter, &item)) + return false; + r->insert(item); + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append(""); + } +}; + +template +struct ParamTraits > { + typedef std::map param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + GetParamSize(sizer, iter->first); + GetParamSize(sizer, iter->second); + } + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + WriteParam(m, iter->first); + WriteParam(m, iter->second); + } + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + if (!ReadParam(m, iter, &size) || size < 0) + return false; + for (int i = 0; i < size; ++i) { + K k; + if (!ReadParam(m, iter, &k)) + return false; + V& value = (*r)[k]; + if (!ReadParam(m, iter, &value)) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append(""); + } +}; + +template +struct ParamTraits > { + typedef std::pair param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, p.first); + GetParamSize(sizer, p.second); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, p.first); + WriteParam(m, p.second); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return ReadParam(m, iter, &r->first) && ReadParam(m, iter, &r->second); + } + static void Log(const param_type& p, std::string* l) { + l->append("("); + LogParam(p.first, l); + l->append(", "); + LogParam(p.second, l); + l->append(")"); + } +}; + +// Base ParamTraits ------------------------------------------------------------ + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::DictionaryValue param_type; + 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); +}; + +#if defined(OS_POSIX) +// FileDescriptors may be serialised over IPC channels on POSIX. On the +// receiving side, the FileDescriptor is a valid duplicate of the file +// descriptor which was transmitted: *it is not just a copy of the integer like +// HANDLEs on Windows*. The only exception is if the file descriptor is < 0. In +// this case, the receiving end will see a value of -1. *Zero is a valid file +// descriptor*. +// +// The received file descriptor will have the |auto_close| flag set to true. The +// code which handles the message is responsible for taking ownership of it. +// File descriptors are OS resources and must be closed when no longer needed. +// +// When sending a file descriptor, the file descriptor must be valid at the time +// of transmission. Since transmission is not synchronous, one should consider +// dup()ing any file descriptors to be transmitted and setting the |auto_close| +// flag, which causes the file descriptor to be closed after writing. +template<> +struct IPC_EXPORT ParamTraits { + typedef base::FileDescriptor param_type; + 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); +}; +#endif // defined(OS_POSIX) + +#if (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) +template <> +struct IPC_EXPORT ParamTraits { + typedef base::SharedMemoryHandle param_type; + 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); +}; +#endif // (defined(OS_MACOSX) && !defined(OS_IOS)) || defined(OS_WIN) + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::FilePath param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::ListValue param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::NullableString16 param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::File::Info param_type; + 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); +}; + +template <> +struct SimilarTypeTraits { + typedef int Type; +}; + +#if defined(OS_WIN) +template <> +struct SimilarTypeTraits { + typedef HANDLE Type; +}; +#endif // defined(OS_WIN) + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::Time param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::TimeDelta param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::TimeTicks param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef base::UnguessableToken param_type; + 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); +}; + +template <> +struct ParamTraits> { + typedef std::tuple<> param_type; + 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) { + return true; + } + static void Log(const param_type& p, std::string* l) { + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return ReadParam(m, iter, &std::get<0>(*r)); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + GetParamSize(sizer, std::get<1>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + WriteParam(m, std::get<1>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return (ReadParam(m, iter, &std::get<0>(*r)) && + ReadParam(m, iter, &std::get<1>(*r))); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + l->append(", "); + LogParam(std::get<1>(p), l); + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + GetParamSize(sizer, std::get<1>(p)); + GetParamSize(sizer, std::get<2>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + WriteParam(m, std::get<1>(p)); + WriteParam(m, std::get<2>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return (ReadParam(m, iter, &std::get<0>(*r)) && + ReadParam(m, iter, &std::get<1>(*r)) && + ReadParam(m, iter, &std::get<2>(*r))); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + l->append(", "); + LogParam(std::get<1>(p), l); + l->append(", "); + LogParam(std::get<2>(p), l); + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + GetParamSize(sizer, std::get<1>(p)); + GetParamSize(sizer, std::get<2>(p)); + GetParamSize(sizer, std::get<3>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + WriteParam(m, std::get<1>(p)); + WriteParam(m, std::get<2>(p)); + WriteParam(m, std::get<3>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return (ReadParam(m, iter, &std::get<0>(*r)) && + ReadParam(m, iter, &std::get<1>(*r)) && + ReadParam(m, iter, &std::get<2>(*r)) && + ReadParam(m, iter, &std::get<3>(*r))); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + l->append(", "); + LogParam(std::get<1>(p), l); + l->append(", "); + LogParam(std::get<2>(p), l); + l->append(", "); + LogParam(std::get<3>(p), l); + } +}; + +template +struct ParamTraits> { + typedef std::tuple param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, std::get<0>(p)); + GetParamSize(sizer, std::get<1>(p)); + GetParamSize(sizer, std::get<2>(p)); + GetParamSize(sizer, std::get<3>(p)); + GetParamSize(sizer, std::get<4>(p)); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, std::get<0>(p)); + WriteParam(m, std::get<1>(p)); + WriteParam(m, std::get<2>(p)); + WriteParam(m, std::get<3>(p)); + WriteParam(m, std::get<4>(p)); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + return (ReadParam(m, iter, &std::get<0>(*r)) && + ReadParam(m, iter, &std::get<1>(*r)) && + ReadParam(m, iter, &std::get<2>(*r)) && + ReadParam(m, iter, &std::get<3>(*r)) && + ReadParam(m, iter, &std::get<4>(*r))); + } + static void Log(const param_type& p, std::string* l) { + LogParam(std::get<0>(p), l); + l->append(", "); + LogParam(std::get<1>(p), l); + l->append(", "); + LogParam(std::get<2>(p), l); + l->append(", "); + LogParam(std::get<3>(p), l); + l->append(", "); + LogParam(std::get<4>(p), l); + } +}; + +template +struct ParamTraits > { + typedef base::StackVector param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p->size())); + for (size_t i = 0; i < p->size(); i++) + GetParamSize(sizer, p[i]); + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p->size())); + for (size_t i = 0; i < p->size(); i++) + WriteParam(m, p[i]); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + // ReadLength() checks for < 0 itself. + if (!iter->ReadLength(&size)) + return false; + // Sanity check for the vector size. + if (INT_MAX / sizeof(P) <= static_cast(size)) + return false; + P value; + for (int i = 0; i < size; i++) { + if (!ReadParam(m, iter, &value)) + return false; + (*r)->push_back(value); + } + return true; + } + static void Log(const param_type& p, std::string* l) { + for (size_t i = 0; i < p->size(); ++i) { + if (i != 0) + l->append(" "); + LogParam((p[i]), l); + } + } +}; + +template +struct ParamTraits > { + typedef base::SmallMap param_type; + typedef typename param_type::key_type K; + typedef typename param_type::data_type V; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + GetParamSize(sizer, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + GetParamSize(sizer, iter->first); + GetParamSize(sizer, iter->second); + } + } + static void Write(base::Pickle* m, const param_type& p) { + WriteParam(m, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + WriteParam(m, iter->first); + WriteParam(m, iter->second); + } + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + int size; + if (!iter->ReadLength(&size)) + return false; + for (int i = 0; i < size; ++i) { + K key; + if (!ReadParam(m, iter, &key)) + return false; + V& value = (*r)[key]; + if (!ReadParam(m, iter, &value)) + return false; + } + return true; + } + static void Log(const param_type& p, std::string* l) { + l->append(""); + } +}; + +template +struct ParamTraits> { + typedef std::unique_ptr

param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + bool valid = !!p; + GetParamSize(sizer, valid); + if (valid) + GetParamSize(sizer, *p); + } + static void Write(base::Pickle* m, const param_type& p) { + bool valid = !!p; + WriteParam(m, valid); + if (valid) + WriteParam(m, *p); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + bool valid = false; + if (!ReadParam(m, iter, &valid)) + return false; + + if (!valid) { + r->reset(); + return true; + } + + param_type temp(new P()); + if (!ReadParam(m, iter, temp.get())) + return false; + + r->swap(temp); + return true; + } + static void Log(const param_type& p, std::string* l) { + if (p) + LogParam(*p, l); + else + l->append("NULL"); + } +}; + +template +struct ParamTraits> { + typedef base::Optional

param_type; + static void GetSize(base::PickleSizer* sizer, const param_type& p) { + const bool is_set = static_cast(p); + GetParamSize(sizer, is_set); + if (is_set) + GetParamSize(sizer, p.value()); + } + static void Write(base::Pickle* m, const param_type& p) { + const bool is_set = static_cast(p); + WriteParam(m, is_set); + if (is_set) + WriteParam(m, p.value()); + } + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + bool is_set = false; + if (!iter->ReadBool(&is_set)) + return false; + if (is_set) { + P value; + if (!ReadParam(m, iter, &value)) + return false; + *r = std::move(value); + } + return true; + } + static void Log(const param_type& p, std::string* l) { + if (p) + LogParam(p.value(), l); + else + l->append("(unset)"); + } +}; + +// IPC types ParamTraits ------------------------------------------------------- + +// A ChannelHandle is basically a platform-inspecific wrapper around the +// fact that IPC endpoints are handled specially on POSIX. See above comments +// on FileDescriptor for more background. +template<> +struct IPC_EXPORT ParamTraits { + typedef ChannelHandle param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef LogData param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + static void Write(base::Pickle* m, const Message& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + Message* r); + static void Log(const Message& p, std::string* l); +}; + +// Windows ParamTraits --------------------------------------------------------- + +#if defined(OS_WIN) +template <> +struct IPC_EXPORT ParamTraits { + typedef HANDLE param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef LOGFONT param_type; + 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); +}; + +template <> +struct IPC_EXPORT ParamTraits { + typedef MSG param_type; + 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); +}; +#endif // defined(OS_WIN) + +//----------------------------------------------------------------------------- +// Generic message subclasses + +// defined in ipc_logging.cc +IPC_EXPORT void GenerateLogData(const Message& message, + LogData* data, + bool get_params); + +#if defined(IPC_MESSAGE_LOG_ENABLED) +inline void AddOutputParamsToLog(const Message* msg, std::string* l) { + const std::string& output_params = msg->output_params(); + if (!l->empty() && !output_params.empty()) + l->append(", "); + + l->append(output_params); +} + +template +inline void LogReplyParamsToMessage(const ReplyParamType& reply_params, + const Message* msg) { + if (msg->received_time() != 0) { + std::string output_params; + LogParam(reply_params, &output_params); + msg->set_output_params(output_params); + } +} + +inline void ConnectMessageAndReply(const Message* msg, Message* reply) { + if (msg->sent_time()) { + // Don't log the sync message after dispatch, as we don't have the + // output parameters at that point. Instead, save its data and log it + // with the outgoing reply message when it's sent. + LogData* data = new LogData; + GenerateLogData(*msg, data, true); + msg->set_dont_log(); + reply->set_sync_log_data(data); + } +} +#else +inline void AddOutputParamsToLog(const Message* msg, std::string* l) {} + +template +inline void LogReplyParamsToMessage(const ReplyParamType& reply_params, + const Message* msg) {} + +inline void ConnectMessageAndReply(const Message* msg, Message* reply) {} +#endif + +} // namespace IPC + +#endif // IPC_IPC_MESSAGE_UTILS_H_ diff --git a/ipc/ipc_mojo_handle_attachment.cc b/ipc/ipc_mojo_handle_attachment.cc new file mode 100644 index 0000000..e3421c3 --- /dev/null +++ b/ipc/ipc_mojo_handle_attachment.cc @@ -0,0 +1,29 @@ +// Copyright (c) 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 "ipc/ipc_mojo_handle_attachment.h" + +#include + +#include "build/build_config.h" + +namespace IPC { +namespace internal { + +MojoHandleAttachment::MojoHandleAttachment(mojo::ScopedHandle handle) + : handle_(std::move(handle)) {} + +MojoHandleAttachment::~MojoHandleAttachment() { +} + +MessageAttachment::Type MojoHandleAttachment::GetType() const { + return Type::MOJO_HANDLE; +} + +mojo::ScopedHandle MojoHandleAttachment::TakeHandle() { + return std::move(handle_); +} + +} // namespace internal +} // namespace IPC diff --git a/ipc/ipc_mojo_handle_attachment.h b/ipc/ipc_mojo_handle_attachment.h new file mode 100644 index 0000000..d615276 --- /dev/null +++ b/ipc/ipc_mojo_handle_attachment.h @@ -0,0 +1,42 @@ +// Copyright (c) 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 IPC_IPC_MOJO_HANDLE_ATTACHMENT_H_ +#define IPC_IPC_MOJO_HANDLE_ATTACHMENT_H_ + +#include "base/files/file.h" +#include "base/macros.h" +#include "build/build_config.h" +#include "ipc/ipc_export.h" +#include "ipc/ipc_message_attachment.h" +#include "mojo/public/cpp/system/handle.h" + +namespace IPC { + +namespace internal { + +// A MessageAttachment that holds a MojoHandle. +// This can hold any type of transferrable Mojo handle (i.e. message pipe, data +// pipe, etc), but the receiver is expected to know what type of handle to +// expect. +class IPC_EXPORT MojoHandleAttachment : public MessageAttachment { + public: + explicit MojoHandleAttachment(mojo::ScopedHandle handle); + + Type GetType() const override; + + // Returns the owning handle transferring the ownership. + mojo::ScopedHandle TakeHandle(); + + private: + ~MojoHandleAttachment() override; + mojo::ScopedHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(MojoHandleAttachment); +}; + +} // namespace internal +} // namespace IPC + +#endif // IPC_IPC_MOJO_HANDLE_ATTACHMENT_H_ diff --git a/ipc/ipc_mojo_message_helper.cc b/ipc/ipc_mojo_message_helper.cc new file mode 100644 index 0000000..a87a2d6 --- /dev/null +++ b/ipc/ipc_mojo_message_helper.cc @@ -0,0 +1,51 @@ +// Copyright (c) 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 "ipc/ipc_mojo_message_helper.h" + +#include + +#include "ipc/ipc_mojo_handle_attachment.h" + +namespace IPC { + +// static +bool MojoMessageHelper::WriteMessagePipeTo( + base::Pickle* message, + mojo::ScopedMessagePipeHandle handle) { + message->WriteAttachment(new internal::MojoHandleAttachment( + mojo::ScopedHandle::From(std::move(handle)))); + return true; +} + +// static +bool MojoMessageHelper::ReadMessagePipeFrom( + const base::Pickle* message, + base::PickleIterator* iter, + mojo::ScopedMessagePipeHandle* handle) { + scoped_refptr attachment; + if (!message->ReadAttachment(iter, &attachment)) { + LOG(ERROR) << "Failed to read attachment for message pipe."; + return false; + } + + MessageAttachment::Type type = + static_cast(attachment.get())->GetType(); + if (type != MessageAttachment::Type::MOJO_HANDLE) { + LOG(ERROR) << "Unxpected attachment type:" << type; + return false; + } + + handle->reset(mojo::MessagePipeHandle( + static_cast(attachment.get()) + ->TakeHandle() + .release() + .value())); + return true; +} + +MojoMessageHelper::MojoMessageHelper() { +} + +} // namespace IPC diff --git a/ipc/ipc_mojo_message_helper.h b/ipc/ipc_mojo_message_helper.h new file mode 100644 index 0000000..4a71b5c --- /dev/null +++ b/ipc/ipc_mojo_message_helper.h @@ -0,0 +1,29 @@ +// Copyright (c) 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 IPC_IPC_MOJO_MESSAGE_HELPER_H_ +#define IPC_IPC_MOJO_MESSAGE_HELPER_H_ + +#include "ipc/ipc_export.h" +#include "ipc/ipc_message.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace IPC { + +// Reads and writes |mojo::MessagePipe| from/to |Message|. +class IPC_EXPORT MojoMessageHelper { + public: + static bool WriteMessagePipeTo(base::Pickle* message, + mojo::ScopedMessagePipeHandle handle); + static bool ReadMessagePipeFrom(const base::Pickle* message, + base::PickleIterator* iter, + mojo::ScopedMessagePipeHandle* handle); + + private: + MojoMessageHelper(); +}; + +} // namespace IPC + +#endif // IPC_IPC_MOJO_MESSAGE_HELPER_H_ diff --git a/ipc/ipc_mojo_param_traits.cc b/ipc/ipc_mojo_param_traits.cc new file mode 100644 index 0000000..189af35 --- /dev/null +++ b/ipc/ipc_mojo_param_traits.cc @@ -0,0 +1,50 @@ +// 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 "ipc/ipc_mojo_param_traits.h" + +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_mojo_message_helper.h" + +namespace IPC { + +void ParamTraits::GetSize(base::PickleSizer* sizer, + const param_type& p) { + GetParamSize(sizer, p.is_valid()); + if (p.is_valid()) + sizer->AddAttachment(); +} + +void ParamTraits::Write(base::Pickle* m, + const param_type& p) { + WriteParam(m, p.is_valid()); + if (p.is_valid()) + MojoMessageHelper::WriteMessagePipeTo(m, mojo::ScopedMessagePipeHandle(p)); +} + +bool ParamTraits::Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* r) { + bool is_valid; + if (!ReadParam(m, iter, &is_valid)) + return false; + if (!is_valid) + return true; + + mojo::ScopedMessagePipeHandle handle; + if (!MojoMessageHelper::ReadMessagePipeFrom(m, iter, &handle)) + return false; + DCHECK(handle.is_valid()); + *r = handle.release(); + return true; +} + +void ParamTraits::Log(const param_type& p, + std::string* l) { + l->append("mojo::MessagePipeHandle("); + LogParam(p.value(), l); + l->append(")"); +} + +} // namespace IPC diff --git a/ipc/ipc_mojo_param_traits.h b/ipc/ipc_mojo_param_traits.h new file mode 100644 index 0000000..39be43e --- /dev/null +++ b/ipc/ipc_mojo_param_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 IPC_IPC_MOJO_PARAM_TRAITS_H_ +#define IPC_IPC_MOJO_PARAM_TRAITS_H_ + +#include + +#include "ipc/ipc_export.h" +#include "ipc/ipc_param_traits.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace base { +class Pickle; +class PickleIterator; +class PickleSizer; +} + +namespace IPC { + +template <> +struct IPC_EXPORT ParamTraits { + typedef mojo::MessagePipeHandle param_type; + 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 + +#endif // IPC_IPC_MOJO_PARAM_TRAITS_H_ diff --git a/ipc/ipc_param_traits.h b/ipc/ipc_param_traits.h new file mode 100644 index 0000000..45e975c --- /dev/null +++ b/ipc/ipc_param_traits.h @@ -0,0 +1,23 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_PARAM_TRAITS_H_ +#define IPC_IPC_PARAM_TRAITS_H_ + +// Our IPC system uses the following partially specialized header to define how +// a data type is read, written and logged in the IPC system. + +namespace IPC { + +template struct ParamTraits { +}; + +template +struct SimilarTypeTraits { + typedef P Type; +}; + +} // namespace IPC + +#endif // IPC_IPC_PARAM_TRAITS_H_ diff --git a/ipc/ipc_platform_file_attachment_posix.cc b/ipc/ipc_platform_file_attachment_posix.cc new file mode 100644 index 0000000..7111cfa --- /dev/null +++ b/ipc/ipc_platform_file_attachment_posix.cc @@ -0,0 +1,38 @@ +// Copyright (c) 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 "ipc/ipc_platform_file_attachment_posix.h" + +#include + +namespace IPC { +namespace internal { + +PlatformFileAttachment::PlatformFileAttachment(base::PlatformFile file) + : file_(file) { +} + +PlatformFileAttachment::PlatformFileAttachment(base::ScopedFD file) + : file_(file.get()), owning_(std::move(file)) {} + +PlatformFileAttachment::~PlatformFileAttachment() { +} + +MessageAttachment::Type PlatformFileAttachment::GetType() const { + return Type::PLATFORM_FILE; +} + +base::PlatformFile PlatformFileAttachment::TakePlatformFile() { + ignore_result(owning_.release()); + return file_; +} + +base::PlatformFile GetPlatformFile( + scoped_refptr attachment) { + DCHECK_EQ(attachment->GetType(), MessageAttachment::Type::PLATFORM_FILE); + return static_cast(attachment.get())->file(); +} + +} // namespace internal +} // namespace IPC diff --git a/ipc/ipc_platform_file_attachment_posix.h b/ipc/ipc_platform_file_attachment_posix.h new file mode 100644 index 0000000..9b07900 --- /dev/null +++ b/ipc/ipc_platform_file_attachment_posix.h @@ -0,0 +1,43 @@ +// Copyright (c) 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 IPC_IPC_PLATFORM_FILE_ATTACHMENT_H_ +#define IPC_IPC_PLATFORM_FILE_ATTACHMENT_H_ + +#include "ipc/ipc_export.h" +#include "ipc/ipc_message_attachment.h" + +namespace IPC { +namespace internal { + +// A platform file that is sent over |Channel| as a part of |Message|. +// PlatformFileAttachment optionally owns the file and |owning_| is set in that +// case. Also, |file_| is not cleared even after the ownership is taken. +// Some old clients require this strange behavior. +class IPC_EXPORT PlatformFileAttachment : public MessageAttachment { + public: + // Non-owning constructor + explicit PlatformFileAttachment(base::PlatformFile file); + // Owning constructor + explicit PlatformFileAttachment(base::ScopedFD file); + + Type GetType() const override; + base::PlatformFile TakePlatformFile(); + + base::PlatformFile file() const { return file_; } + bool Owns() const { return owning_.is_valid(); } + + private: + ~PlatformFileAttachment() override; + + base::PlatformFile file_; + base::ScopedFD owning_; +}; + +base::PlatformFile GetPlatformFile(scoped_refptr attachment); + +} // namespace internal +} // namespace IPC + +#endif // IPC_IPC_PLATFORM_FILE_ATTACHMENT_H_ diff --git a/ipc/ipc_sync_message.h b/ipc/ipc_sync_message.h new file mode 100644 index 0000000..7f05551 --- /dev/null +++ b/ipc/ipc_sync_message.h @@ -0,0 +1,107 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef IPC_IPC_SYNC_MESSAGE_H_ +#define IPC_IPC_SYNC_MESSAGE_H_ + +#include + +#if defined(OS_WIN) +#include +#endif + +#include +#include + +#include "build/build_config.h" +#include "ipc/ipc_message.h" + +namespace base { +class WaitableEvent; +} + +namespace IPC { + +class MessageReplyDeserializer; + +class IPC_EXPORT SyncMessage : public Message { + public: + SyncMessage(int32_t routing_id, + uint32_t type, + PriorityValue priority, + MessageReplyDeserializer* deserializer); + ~SyncMessage() override; + + // Call this to get a deserializer for the output parameters. + // Note that this can only be called once, and the caller is responsible + // for deleting the deserializer when they're done. + MessageReplyDeserializer* GetReplyDeserializer(); + + // If this message can cause the receiver to block while waiting for user + // input (i.e. by calling MessageBox), then the caller needs to pump window + // messages and dispatch asynchronous messages while waiting for the reply. + // This call enables message pumping behavior while waiting for a reply to + // this message. + void EnableMessagePumping() { + header()->flags |= PUMPING_MSGS_BIT; + } + + // Indicates whether window messages should be pumped while waiting for a + // reply to this message. + bool ShouldPumpMessages() const { + return (header()->flags & PUMPING_MSGS_BIT) != 0; + } + + // Returns true if the message is a reply to the given request id. + static bool IsMessageReplyTo(const Message& msg, int request_id); + + // Given a reply message, returns an iterator to the beginning of the data + // (i.e. skips over the synchronous specific data). + static base::PickleIterator GetDataIterator(const Message* msg); + + // Given a synchronous message (or its reply), returns its id. + static int GetMessageId(const Message& msg); + + // Generates a reply message to the given message. + static Message* GenerateReply(const Message* msg); + + private: + struct SyncHeader { + // unique ID (unique per sender) + int message_id; + }; + + static bool ReadSyncHeader(const Message& msg, SyncHeader* header); + static bool WriteSyncHeader(Message* msg, const SyncHeader& header); + + std::unique_ptr deserializer_; +}; + +// Used to deserialize parameters from a reply to a synchronous message +class IPC_EXPORT MessageReplyDeserializer { + public: + virtual ~MessageReplyDeserializer() {} + bool SerializeOutputParameters(const Message& msg); + private: + // Derived classes need to implement this, using the given iterator (which + // is skipped past the header for synchronous messages). + virtual bool SerializeOutputParameters(const Message& msg, + base::PickleIterator iter) = 0; +}; + +// When sending a synchronous message, this structure contains an object +// that knows how to deserialize the response. +struct PendingSyncMsg { + PendingSyncMsg(int id, MessageReplyDeserializer* d, base::WaitableEvent* e) + : id(id), deserializer(d), done_event(e), send_result(false) {} + + int id; + MessageReplyDeserializer* deserializer; + base::WaitableEvent* done_event; + bool send_result; +}; + +} // namespace IPC + +#endif // IPC_IPC_SYNC_MESSAGE_H_ diff --git a/libchrome_tools/jni_generator_helper.sh b/libchrome_tools/jni_generator_helper.sh new file mode 100755 index 0000000..d610801 --- /dev/null +++ b/libchrome_tools/jni_generator_helper.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Generates jni. + +set -e + +args=() +files=() + +jni_generator='' + +for arg in "$@"; do + case "${arg}" in + --jni_generator=*) + jni_generator=${arg#'--jni_generator='} + ;; + --*) + args=("${args[@]}" "${arg}") + ;; + *) + files=("${files[@]}" "${arg}") + ;; + esac +done + +for file in "${files[@]}"; do + "${jni_generator}" "${args[@]}" --input_file="${file}" +done diff --git a/libchrome_tools/mojom_source_generator.sh b/libchrome_tools/mojom_source_generator.sh new file mode 100755 index 0000000..6830241 --- /dev/null +++ b/libchrome_tools/mojom_source_generator.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Generates mojo sources given a list of .mojom files and args. +# Usage: $0 --mojom_bindings_generator= --package= +# --output_dir= +# [] + +set -e + +args=() +files=() + +mojom_bindings_generator="" +package="" +output_dir="" +generators="" +private_mojo_root="$(pwd)/external/libchrome" + +# Given a path to directory or file, return the absolute path. +get_abs_path() { + if [[ -d $1 ]] ; then + cd "$1" + filename="" + else + filepath=$1 + dir="${filepath%/*}" + cd "${dir}" + filename="${filepath#${dir}/}" + fi + absdir=`pwd` + cd - > /dev/null + echo "${absdir}/${filename}" +} + +for arg in "$@"; do + case "${arg}" in + --mojom_bindings_generator=*) + mojom_bindings_generator="${arg#'--mojom_bindings_generator='}" + mojom_bindings_generator="$(get_abs_path ${mojom_bindings_generator})" + ;; + --package=*) + package="${arg#'--package='}" + ;; + --output_dir=*) + output_dir="${arg#'--output_dir='}" + output_dir="$(get_abs_path ${output_dir})" + ;; + --typemap=*) + typemap="${arg#'--typemap='}" + typemap="$(get_abs_path ${typemap})" + args=("${args[@]}" "--typemap=${typemap}") + ;; + --bytecode_path=*) + bytecode_path="${arg#'--bytecode_path='}" + bytecode_path="$(get_abs_path ${bytecode_path})" + ;; + --generators=*) + generators="${arg#'--generators='}" + ;; + --*) + args=("${args[@]}" "${arg}") + ;; + *) + files=("${files[@]}" "$(get_abs_path ${arg})") + ;; + esac +done + +cd "${package}" +"${mojom_bindings_generator}" --use_bundled_pylibs precompile \ + -o "${output_dir}" + +for file in "${files[@]}"; do + # Java source generations depends on zipfile that assumes the output directory + # already exists. So, we need to create the directory beforehand. + rel_path="${file#`pwd`/}" + rel_dir="${rel_path%/*}" + + mkdir -p "${output_dir}/${rel_dir}" + + # The calls to mojom_bindings_generator below uses -I option to include the + # libmojo root directory as part of searchable directory for imports. With + # this, we can have a mojo file located in some arbitrary directories that + # imports a mojo file under external/libchrome. + "${mojom_bindings_generator}" --use_bundled_pylibs generate \ + -o "${output_dir}" "${args[@]}" \ + --bytecode_path="${bytecode_path}" \ + -I "${private_mojo_root}:${private_mojo_root}" \ + --generators=${generators} "${file}" + if [[ "${generators}" =~ .*c\+\+.* ]] ; then + "${mojom_bindings_generator}" --use_bundled_pylibs generate \ + -o "${output_dir}" \ + --generate_non_variant_code "${args[@]}" \ + -I "${private_mojo_root}:${private_mojo_root}" \ + --bytecode_path="${bytecode_path}" --generators=${generators} \ + "${file}" + fi + if [[ "${generators}" =~ .*java.* ]] ; then + unzip -qo -d "${output_dir}"/src "${output_dir}/${rel_path}".srcjar + fi +done diff --git a/libchrome_tools/patch/mojo.patch b/libchrome_tools/patch/mojo.patch new file mode 100644 index 0000000..723fe37 --- /dev/null +++ b/libchrome_tools/patch/mojo.patch @@ -0,0 +1,566 @@ +# Local patches for libmojo. + +--- a/mojo/android/system/base_run_loop.cc ++++ b/mojo/android/system/base_run_loop.cc +@@ -6,9 +6,10 @@ + + #include + +-#include "base/android/base_jni_registrar.h" ++// Removed unused headers. TODO(hidehiko): Upstream. ++// #include "base/android/base_jni_registrar.h" + #include "base/android/jni_android.h" +-#include "base/android/jni_registrar.h" ++// #include "base/android/jni_registrar.h" + #include "base/bind.h" + #include "base/logging.h" + #include "base/message_loop/message_loop.h" +@@ -79,4 +80,3 @@ bool RegisterBaseRunLoop(JNIEnv* env) { + + } // namespace android + } // namespace mojo +- +--- a/mojo/android/system/core_impl.cc ++++ b/mojo/android/system/core_impl.cc +@@ -7,10 +7,11 @@ + #include + #include + +-#include "base/android/base_jni_registrar.h" ++// Removed unused headers. TODO(hidehiko): Upstream. ++// #include "base/android/base_jni_registrar.h" + #include "base/android/jni_android.h" +-#include "base/android/jni_registrar.h" +-#include "base/android/library_loader/library_loader_hooks.h" ++// #include "base/android/jni_registrar.h" ++// #include "base/android/library_loader/library_loader_hooks.h" + #include "base/android/scoped_java_ref.h" + #include "jni/CoreImpl_jni.h" + #include "mojo/public/c/system/core.h" +--- a/mojo/android/system/watcher_impl.cc ++++ b/mojo/android/system/watcher_impl.cc +@@ -7,10 +7,11 @@ + #include + #include + +-#include "base/android/base_jni_registrar.h" ++// Removed unused headers. TODO(hidehiko): Upstream. ++// #include "base/android/base_jni_registrar.h" + #include "base/android/jni_android.h" +-#include "base/android/jni_registrar.h" +-#include "base/android/library_loader/library_loader_hooks.h" ++// #include "base/android/jni_registrar.h" ++// #include "base/android/library_loader/library_loader_hooks.h" + #include "base/android/scoped_java_ref.h" + #include "base/bind.h" + #include "jni/WatcherImpl_jni.h" +--- a/mojo/common/common_custom_types_struct_traits.h ++++ b/mojo/common/common_custom_types_struct_traits.h +@@ -63,19 +63,6 @@ struct StructTraits +-struct StructTraits { +- static int64_t microseconds(const base::TimeDelta& delta) { +- return delta.InMicroseconds(); +- } +- +- static bool Read(common::mojom::TimeDeltaDataView data, +- base::TimeDelta* delta) { +- *delta = base::TimeDelta::FromMicroseconds(data.microseconds()); +- return true; +- } +-}; +- +-template <> + struct StructTraits { + static bool IsNull(const base::File& file) { return !file.IsValid(); } + +--- a/mojo/common/time.mojom ++++ b/mojo/common/time.mojom +@@ -4,12 +4,18 @@ + + module mojo.common.mojom; + +-[Native] +-struct Time; ++struct Time { ++ // The internal value is expressed in terms of microseconds since a fixed but ++ // intentionally unspecified epoch. ++ int64 internal_value; ++}; + + struct TimeDelta { + int64 microseconds; + }; + +-[Native] +-struct TimeTicks; ++struct TimeTicks { ++ // The internal value is expressed in terms of microseconds since a fixed but ++ // intentionally unspecified epoch. ++ int64 internal_value; ++}; +--- a/mojo/edk/embedder/platform_channel_pair_posix.cc ++++ b/mojo/edk/embedder/platform_channel_pair_posix.cc +@@ -35,7 +35,7 @@ namespace edk { + + namespace { + +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(__ANDROID__) + enum { + // Leave room for any other descriptors defined in content for example. + // TODO(jcivelli): consider changing base::GlobalDescriptors to generate a +@@ -102,7 +102,7 @@ ScopedPlatformHandle + PlatformChannelPair::PassClientHandleFromParentProcessFromString( + const std::string& value) { + int client_fd = -1; +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(__ANDROID__) + base::GlobalDescriptors::Key key = -1; + if (value.empty() || !base::StringToUint(value, &key)) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; +@@ -142,7 +142,7 @@ void PlatformChannelPair::PrepareToPassC + std::string + PlatformChannelPair::PrepareToPassClientHandleToChildProcessAsString( + HandlePassingInformation* handle_passing_info) const { +-#if defined(OS_ANDROID) ++#if defined(OS_ANDROID) || defined(__ANDROID__) + int fd = client_handle_.get().handle; + handle_passing_info->push_back( + std::pair(fd, kAndroidClientHandleDescriptor)); +--- a/mojo/edk/system/ports/node.cc ++++ b/mojo/edk/system/ports/node.cc +@@ -803,7 +803,7 @@ scoped_refptr Node::GetPort_Locked + if (iter == ports_.end()) + return nullptr; + +-#if defined(OS_ANDROID) && defined(ARCH_CPU_ARM64) ++#if (defined(OS_ANDROID) || defined(__ANDROID__)) && defined(ARCH_CPU_ARM64) + // Workaround for https://crbug.com/665869. + base::subtle::MemoryBarrier(); + #endif +--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java ++++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/Interface.java +@@ -206,7 +206,13 @@ public interface Interface extends Conne + == RunOutput.Tag.QueryVersionResult) { + mVersion = response.output.getQueryVersionResult().version; + } +- callback.call(mVersion); ++ 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); ++ } + } + }); + } +--- a/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java ++++ b/mojo/public/java/bindings/src/org/chromium/mojo/bindings/RouterImpl.java +@@ -171,20 +171,23 @@ public class RouterImpl implements Route + assert messageWithHeader.getHeader().hasFlag(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG); + + // Compute a request id for being able to route the response. +- long requestId = mNextRequestId++; +- // Reserve 0 in case we want it to convey special meaning in the future. +- if (requestId == 0) { +- requestId = mNextRequestId++; +- } +- if (mResponders.containsKey(requestId)) { +- throw new IllegalStateException("Unable to find a new request identifier."); +- } +- messageWithHeader.setRequestId(requestId); +- if (!mConnector.accept(messageWithHeader)) { +- return false; ++ // 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); + } +- // Only keep the responder is the message has been accepted. +- mResponders.put(requestId, responder); + return true; + } + +@@ -227,11 +230,15 @@ public class RouterImpl implements Route + return false; + } else if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) { + long requestId = header.getRequestId(); +- MessageReceiver responder = mResponders.get(requestId); +- if (responder == null) { +- return false; ++ 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); + } +- mResponders.remove(requestId); + return responder.accept(message); + } else { + if (mIncomingMessageReceiver != null) { +--- a/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl ++++ b/mojo/public/tools/bindings/generators/java_templates/interface_definition.tmpl +@@ -113,7 +113,12 @@ try { + {% else %} + {{request_struct|name}}.deserialize(messageWithHeader.getPayload()); + {% endif %} +- 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 %}); ++ 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 %} +@@ -241,7 +246,12 @@ class {{interface|name}}_Internal { + {% if method.response_parameters|length %} + {{response_struct|name}} response = {{response_struct|name}}.deserialize(messageWithHeader.getPayload()); + {% endif %} +- mCallback.call({{run_callback('response', method.response_parameters)}}); ++ 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; +--- a/base/android/jni_android.cc ++++ b/base/android/jni_android.cc +@@ -10,7 +10,8 @@ + + #include "base/android/build_info.h" + #include "base/android/jni_string.h" +-#include "base/android/jni_utils.h" ++// Removed unused headers. TODO(hidehiko): Upstream. ++// #include "base/android/jni_utils.h" + #include "base/debug/debugging_flags.h" + #include "base/lazy_instance.h" + #include "base/logging.h" +@@ -240,7 +241,16 @@ void CheckException(JNIEnv* env) { + } + + // Now, feel good about it and die. +- LOG(FATAL) << "Please include Java exception stack in crash report"; ++ // TODO(lhchavez): Remove this hack. See b/28814913 for details. ++ // We're using BuildInfo's java_exception_info() instead of storing the ++ // exception info a few lines above to avoid extra copies. It will be ++ // truncated to 1024 bytes anyways. ++ const char* exception_string = ++ base::android::BuildInfo::GetInstance()->java_exception_info(); ++ if (exception_string) ++ LOG(FATAL) << exception_string; ++ else ++ LOG(FATAL) << "Unhandled exception"; + } + + std::string GetJavaExceptionInfo(JNIEnv* env, jthrowable java_throwable) { +--- a/build/android/gyp/util/build_utils.py ++++ b/build/android/gyp/util/build_utils.py +@@ -20,7 +20,13 @@ import zipfile + # Some clients do not add //build/android/gyp to PYTHONPATH. + import md5_check # pylint: disable=relative-import + +-sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) ++# pylib conflicts with mojo/public/tools/bindings/pylib. Prioritize ++# build/android/pylib. ++# PYTHONPATH wouldn't help in this case, because soong put source files under ++# temp directory for each build, so the abspath is unknown until the ++# execution. ++# sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) ++sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)) + from pylib.constants import host_paths + + sys.path.append(os.path.join(os.path.dirname(__file__), +@@ -581,4 +587,3 @@ def CallAndWriteDepfileIfStale(function, + output_paths=output_paths, + force=force, + pass_changes=True) +- +--- a/build/android/pylib/constants/__init__.py ++++ b/build/android/pylib/constants/__init__.py +@@ -96,7 +96,7 @@ DEVICE_PERF_OUTPUT_DIR = ( + SCREENSHOTS_DIR = os.path.join(DIR_SOURCE_ROOT, 'out_screenshots') + + ANDROID_SDK_VERSION = version_codes.MARSHMALLOW +-ANDROID_SDK_BUILD_TOOLS_VERSION = '25.0.2' ++ANDROID_SDK_BUILD_TOOLS_VERSION = '24.0.2' + ANDROID_SDK_ROOT = os.path.join(DIR_SOURCE_ROOT, + 'third_party', 'android_tools', 'sdk') + ANDROID_SDK_TOOLS = os.path.join(ANDROID_SDK_ROOT, +--- /dev/null ++++ b/gen/mojo/common/common_custom_types__type_mappings +@@ -0,0 +1,193 @@ ++{ ++ "c++": { ++ "mojo.common.mojom.Value": { ++ "hashable": false, ++ "typename": "std::unique_ptr", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/values_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": true, ++ "nullable_is_same_type": true, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/values.h" ++ ] ++ }, ++ "mojo.common.mojom.UnguessableToken": { ++ "hashable": false, ++ "typename": "base::UnguessableToken", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/unguessable_token.h" ++ ] ++ }, ++ "mojo.common.mojom.TextDirection": { ++ "hashable": false, ++ "typename": "base::i18n::TextDirection", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/i18n/rtl.h" ++ ] ++ }, ++ "mojo.common.mojom.ListValue": { ++ "hashable": false, ++ "typename": "std::unique_ptr", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/values_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": true, ++ "nullable_is_same_type": true, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/values.h" ++ ] ++ }, ++ "mojo.common.mojom.String16": { ++ "hashable": false, ++ "typename": "base::string16", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/strings/string16.h" ++ ] ++ }, ++ "mojo.common.mojom.Time": { ++ "hashable": false, ++ "typename": "base::Time", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": true, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/time/time.h" ++ ] ++ }, ++ "mojo.common.mojom.TimeDelta": { ++ "hashable": false, ++ "typename": "base::TimeDelta", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": true, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/time/time.h" ++ ] ++ }, ++ "mojo.common.mojom.TimeTicks": { ++ "hashable": false, ++ "typename": "base::TimeTicks", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": true, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/time/time.h" ++ ] ++ }, ++ "mojo.common.mojom.LegacyListValue": { ++ "hashable": false, ++ "typename": "base::ListValue", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/values_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": true, ++ "public_headers": [ ++ "base/values.h" ++ ] ++ }, ++ "mojo.common.mojom.File": { ++ "hashable": false, ++ "typename": "base::File", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": true, ++ "nullable_is_same_type": true, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/files/file.h" ++ ] ++ }, ++ "mojo.common.mojom.FilePath": { ++ "hashable": false, ++ "typename": "base::FilePath", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/files/file_path.h" ++ ] ++ }, ++ "mojo.common.mojom.DictionaryValue": { ++ "hashable": false, ++ "typename": "std::unique_ptr", ++ "traits_headers": [ ++ "ipc/ipc_message_utils.h", ++ "mojo/common/values_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": true, ++ "nullable_is_same_type": true, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/values.h" ++ ] ++ }, ++ "mojo.common.mojom.Version": { ++ "hashable": false, ++ "typename": "base::Version", ++ "traits_headers": [ ++ "mojo/common/common_custom_types_struct_traits.h" ++ ], ++ "copyable_pass_by_value": false, ++ "move_only": false, ++ "nullable_is_same_type": false, ++ "non_copyable_non_movable": false, ++ "public_headers": [ ++ "base/version.h" ++ ] ++ } ++ } ++} +--- /dev/null ++++ b/mojo/common/time_struct_traits.h +@@ -0,0 +1,55 @@ ++// 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_COMMON_TIME_STRUCT_TRAITS_H_ ++#define MOJO_COMMON_TIME_STRUCT_TRAITS_H_ ++ ++#include "base/time/time.h" ++#include "mojo/common/time.mojom-shared.h" ++ ++namespace mojo { ++ ++template <> ++struct StructTraits { ++ static int64_t internal_value(const base::Time& time) { ++ return time.since_origin().InMicroseconds(); ++ } ++ ++ static bool Read(common::mojom::TimeDataView data, base::Time* time) { ++ *time = ++ base::Time() + base::TimeDelta::FromMicroseconds(data.internal_value()); ++ return true; ++ } ++}; ++ ++template <> ++struct StructTraits { ++ static int64_t microseconds(const base::TimeDelta& delta) { ++ return delta.InMicroseconds(); ++ } ++ ++ static bool Read(common::mojom::TimeDeltaDataView data, ++ base::TimeDelta* delta) { ++ *delta = base::TimeDelta::FromMicroseconds(data.microseconds()); ++ return true; ++ } ++}; ++ ++template <> ++struct StructTraits { ++ static int64_t internal_value(const base::TimeTicks& time) { ++ return time.since_origin().InMicroseconds(); ++ } ++ ++ static bool Read(common::mojom::TimeTicksDataView data, ++ base::TimeTicks* time) { ++ *time = base::TimeTicks() + ++ base::TimeDelta::FromMicroseconds(data.internal_value()); ++ return true; ++ } ++}; ++ ++} // namespace mojo ++ ++#endif // MOJO_COMMON_TIME_STRUCT_TRAITS_H_ diff --git a/libchrome_tools/update_libchrome.py b/libchrome_tools/update_libchrome.py index b92615b..2c51f15 100644 --- a/libchrome_tools/update_libchrome.py +++ b/libchrome_tools/update_libchrome.py @@ -48,11 +48,13 @@ _LIBCHROME_ROOT = os.path.dirname(_TOOLS_DIR) # repository. _IMPORT_BLACKLIST = [ # Libchrome specific files. + '.gitignore', 'Android.bp', 'MODULE_LICENSE_BSD', 'NOTICE', 'OWNERS', 'SConstruct', + 'libmojo.pc.in', 'testrunner.cc', # libchrome_tools is out of the update target. @@ -61,13 +63,20 @@ _IMPORT_BLACKLIST = [ # Those files should be generated. Please see also buildflag_header.patch. 'base/allocator/features.h', 'base/debug/debugging_flags.h', + 'gen/*', + + # Local patch to support time.mojom. + # TODO(hidehiko): Remove this after roll to ToT Chrome. + 'mojo/common/time_struct_traits.h', # Blacklist several third party libraries; system libraries should be used. 'base/third_party/libevent/*', 'base/third_party/symbolize/*', 'testing/gmock/*', 'testing/gtest/*', - 'third_party/*', + 'third_party/ashmem/*', + 'third_party/modp_b64/*', + 'third_party/protobuf/*', ] def _find_target_files(): @@ -90,7 +99,7 @@ def _clean_existing_dir(output_root): os.makedirs(output_root, mode=0o755, exist_ok=True) for path in os.listdir(output_root): target_path = os.path.join(output_root, path) - if not os.path.isdir(target_path) or path in ('.git', 'libchrome_tools'): + if (not os.path.isdir(target_path) or path in ('.git', 'libchrome_tools')): continue shutil.rmtree(target_path) diff --git a/libmojo.pc.in b/libmojo.pc.in new file mode 100644 index 0000000..a750bdd --- /dev/null +++ b/libmojo.pc.in @@ -0,0 +1,13 @@ +bslot=@BSLOT@ +prefix=/usr +exec_prefix=${prefix} +libdir=${exec_prefix}/@LIB@ +includedir=${prefix}/include + +Name: libmojo +Description: Chrome Mojo IPC library +Requires.private: +Version: ${bslot} +Libs: -lmojo-${bslot}.pic +Libs.private: +Cflags: -I${includedir}/libmojo-${bslot} -Wno-cast-qual -Wno-cast-align diff --git a/mojo/BUILD.gn b/mojo/BUILD.gn new file mode 100644 index 0000000..070e2d1 --- /dev/null +++ b/mojo/BUILD.gn @@ -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. + +import("//build/config/ui.gni") + +group("mojo") { + # Meta-target, don't link into production code. + testonly = true + deps = [ + ":tests", + "//mojo/common", + ] + + if (!(is_linux && current_cpu == "x86")) { + deps += [ "//mojo/public" ] + } + + if (is_android) { + deps += [ "//mojo/android" ] + } + + deps += [ "//services/service_manager:all" ] +} + +group("tests") { + testonly = true + deps = [ + "//ipc:ipc_tests", + "//mojo/common:mojo_common_unittests", + "//mojo/edk/js/tests", + "//mojo/edk/system:mojo_message_pipe_perftests", + "//mojo/edk/system:mojo_system_unittests", + "//mojo/edk/test:mojo_public_bindings_perftests", + "//mojo/edk/test:mojo_public_bindings_unittests", + "//mojo/edk/test:mojo_public_system_perftests", + "//mojo/edk/test:mojo_public_system_unittests", + "//services/service_manager/public/cpp/tests:mojo_public_application_unittests", + "//services/service_manager/tests", + ] +} diff --git a/mojo/DEPS b/mojo/DEPS new file mode 100644 index 0000000..49d7fd3 --- /dev/null +++ b/mojo/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+base", + "+build", + "+testing", + + "+services/service_manager", +] diff --git a/mojo/PRESUBMIT.py b/mojo/PRESUBMIT.py new file mode 100644 index 0000000..2766055 --- /dev/null +++ b/mojo/PRESUBMIT.py @@ -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. + +"""Presubmit script for mojo + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details about the presubmit API built into depot_tools. +""" + +import os.path + +def CheckChangeOnUpload(input_api, output_api): + # Additional python module paths (we're in src/mojo/); not everyone needs + # them, but it's easiest to add them to everyone's path. + # For ply and jinja2: + third_party_path = os.path.join( + input_api.PresubmitLocalPath(), "..", "third_party") + # For the bindings generator: + mojo_public_bindings_pylib_path = os.path.join( + input_api.PresubmitLocalPath(), "public", "tools", "bindings", "pylib") + # For the python bindings: + mojo_python_bindings_path = os.path.join( + input_api.PresubmitLocalPath(), "public", "python") + # TODO(vtl): Don't lint these files until the (many) problems are fixed + # (possibly by deleting/rewriting some files). + temporary_black_list = input_api.DEFAULT_BLACK_LIST + \ + (r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]pylib[\\\/]mojom[\\\/]" + r"generate[\\\/].+\.py$", + r".*\bpublic[\\\/]tools[\\\/]bindings[\\\/]generators[\\\/].+\.py$", + r".*\bspy[\\\/]ui[\\\/].+\.py$", + r".*\btools[\\\/]pylib[\\\/]transitive_hash\.py$", + r".*\btools[\\\/]test_runner\.py$") + + results = [] + pylint_extra_paths = [ + third_party_path, + mojo_public_bindings_pylib_path, + mojo_python_bindings_path, + ] + results += input_api.canned_checks.RunPylint( + input_api, output_api, extra_paths_list=pylint_extra_paths, + black_list=temporary_black_list) + return results diff --git a/mojo/README.md b/mojo/README.md new file mode 100644 index 0000000..e1e7583 --- /dev/null +++ b/mojo/README.md @@ -0,0 +1,142 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Getting Started With Mojo + +To get started using Mojo in applications which already support it (such as +Chrome), the fastest path forward will be to look at the bindings documentation +for your language of choice ([**C++**](#C_Bindings), +[**JavaScript**](#JavaScript-Bindings), or [**Java**](#Java-Bindings)) as well +as the documentation for the +[**Mojom IDL and bindings generator**](/mojo/public/tools/bindings). + +If you're looking for information on creating and/or connecting to services, see +the top-level [Services documentation](/services). + +For specific details regarding the conversion of old things to new things, check +out [Converting Legacy Chrome IPC To Mojo](/ipc). + +## System Overview + +Mojo is a layered collection of runtime libraries providing a platform-agnostic +abstraction of common IPC primitives, a message IDL format, and a bindings +library with code generation for multiple target languages to facilitate +convenient message passing across arbitrary inter- and intra-process boundaries. + +The documentation here is segmented according to the different isolated layers +and libraries comprising the system. The basic hierarchy of features is as +follows: + +![Mojo Library Layering: EDK on bottom, different language bindings on top, public system support APIs in the middle](https://docs.google.com/drawings/d/1aNbLfF-fejgzxCxH_b8xAaCVvftW8BGTH_EHD7nvU1w/pub?w=570&h=327) + +## Embedder Development Kit (EDK) +Every process to be interconnected via Mojo IPC is called a **Mojo embedder** +and needs to embed the +[**Embedder Development Kit (EDK)**](/mojo/edk/embedder) library. The EDK +exposes the means for an embedder to physically connect one process to another +using any supported native IPC primitive (*e.g.,* a UNIX domain socket or +Windows named pipe) on the host platform. + +Details regarding where and how an application process actually embeds and +configures the EDK are generaly hidden from the rest of the application code, +and applications instead use the public System and Bindings APIs to get things +done within processes that embed Mojo. + +## C System API +Once the EDK is initialized within a process, the public +[**C System API**](/mojo/public/c/system) is usable on any thread for the +remainder of the process's lifetime. This is a lightweight API with a relatively +small (and eventually stable) ABI. Typically this API is not used directly, but +it is the foundation upon which all remaining upper layers are built. It exposes +the fundamental capabilities to create and interact with various types of Mojo +handles including **message pipes**, **data pipes**, and **shared buffers**. + +## High-Level System APIs + +There is a relatively small, higher-level system API for each supported +language, built upon the low-level C API. Like the C API, direct usage of these +system APIs is rare compared to the bindings APIs, but it is sometimes desirable +or necessary. + +### C++ +The [**C++ System API**](/mojo/public/cpp/system) provides a layer of +C++ helper classes and functions to make safe System API usage easier: +strongly-typed handle scopers, synchronous waiting operations, system handle +wrapping and unwrapping helpers, common handle operations, and utilities for +more easily watching handle state changes. + +### JavaScript +The [**JavaScript APIs**](/mojo/public/js) are WIP. :) + +### Java +The [**Java System API**](/mojo/public/java/system) provides helper classes for +working with Mojo primitives, covering all basic functionality of the low-level +C API. + +## High-Level Bindings APIs +Typically developers do not use raw message pipe I/O directly, but instead +define some set of interfaces which are used to generate code that message pipe +usage feel like a more idiomatic method-calling interface in the target +language of choice. This is the bindings layer. + +### Mojom IDL and Bindings Generator +Interfaces are defined using the [**Mojom IDL**](/mojo/public/tools/bindings), +which can be fed to the [**bindings generator**](/mojo/public/tools/bindings) to +generate code in various supported languages. Generated code manages +serialization and deserialization of messages between interface clients and +implementations, simplifying the code -- and ultimately hiding the message pipe +-- on either side of an interface connection. + +### C++ Bindings +By far the most commonly used API defined by Mojo, the +[**C++ Bindings API**](/mojo/public/cpp/bindings) exposes a robust set of +features for interacting with message pipes via generated C++ bindings code, +including support for sets of related bindings endpoints, associated interfaces, +nested sync IPC, versioning, bad-message reporting, arbitrary message filter +injection, and convenient test facilities. + +### JavaScript Bindings +The [**JavaScript APIs**](/mojo/public/js) are WIP. :) + +### Java Bindings +The [**Java Bindings API**](/mojo/public/java/bindings) provides helper classes +for working with Java code emitted by the bindings generator. + +## FAQ + +### Why not protobuf? Why a new thing? +There are number of potentially decent answers to this question, but the +deal-breaker is that a useful IPC mechanism must support transfer of native +object handles (*e.g.* file descriptors) across process boundaries. Other +non-new IPC things that do support this capability (*e.g.* D-Bus) have their own +substantial deficiencies. + +### Are message pipes expensive? +No. As an implementation detail, creating a message pipe is essentially +generating two random numbers and stuffing them into a hash table, along with a +few tiny heap allocations. + +### So really, can I create like, thousands of them? +Yes! Nobody will mind. Create millions if you like. (OK but maybe don't.) + +### Can I use in-process message pipes? +Yes, and message pipe usage is identical regardless of whether the pipe actually +crosses a process boundary -- in fact this detail is intentionally obscured. + +Message pipes which don't cross a process boundary are efficient: sent messages +are never copied, and a write on one end will synchronously modify the message +queue on the other end. When working with generated C++ bindings, for example, +the net result is that an `InterfacePtr` on one thread sending a message to a +`Binding` on another thread (or even the same thread) is effectively a +`PostTask` to the `Binding`'s `TaskRunner` with the added -- but often small -- +costs of serialization, deserialization, validation, and some internal routing +logic. + +### What about ____? + +Please post questions to +[`chromium-mojo@chromium.org`](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-mojo)! +The list is quite responsive. + diff --git a/mojo/android/BUILD.gn b/mojo/android/BUILD.gn new file mode 100644 index 0000000..1a8cdbd --- /dev/null +++ b/mojo/android/BUILD.gn @@ -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. + +import("//build/config/android/rules.gni") + +group("android") { + testonly = true + deps = [ + ":mojo_javatests", + ":mojo_test_apk", + ":system_java", + ] +} + +generate_jni("jni_headers") { + sources = [ + "javatests/src/org/chromium/mojo/MojoTestCase.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java", + ] + public_deps = [ + ":system_java_jni_headers", + ] + + jni_package = "mojo" +} + +generate_jni("system_java_jni_headers") { + sources = [ + "system/src/org/chromium/mojo/system/impl/BaseRunLoop.java", + "system/src/org/chromium/mojo/system/impl/CoreImpl.java", + "system/src/org/chromium/mojo/system/impl/WatcherImpl.java", + ] + + jni_package = "mojo" +} + +source_set("libsystem_java") { + sources = [ + "system/base_run_loop.cc", + "system/base_run_loop.h", + "system/core_impl.cc", + "system/core_impl.h", + "system/watcher_impl.cc", + "system/watcher_impl.h", + ] + + deps = [ + ":system_java_jni_headers", + "//base", + "//mojo/public/c/system", + "//mojo/public/cpp/system", + ] +} + +android_library("system_java") { + java_files = [ + "system/src/org/chromium/mojo/system/impl/BaseRunLoop.java", + "system/src/org/chromium/mojo/system/impl/CoreImpl.java", + "system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/HandleBase.java", + "system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java", + "system/src/org/chromium/mojo/system/impl/WatcherImpl.java", + ] + + deps = [ + "//base:base_java", + "//mojo/public/java:system_java", + ] +} + +android_library("mojo_javatests") { + testonly = true + java_files = [ + "javatests/src/org/chromium/mojo/HandleMock.java", + "javatests/src/org/chromium/mojo/MojoTestCase.java", + "javatests/src/org/chromium/mojo/TestUtils.java", + "javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java", + "javatests/src/org/chromium/mojo/bindings/BindingsTest.java", + "javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java", + "javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java", + "javatests/src/org/chromium/mojo/bindings/CallbacksTest.java", + "javatests/src/org/chromium/mojo/bindings/ConnectorTest.java", + "javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java", + "javatests/src/org/chromium/mojo/bindings/InterfacesTest.java", + "javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java", + "javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java", + "javatests/src/org/chromium/mojo/bindings/RouterTest.java", + "javatests/src/org/chromium/mojo/bindings/SerializationTest.java", + "javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTest.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java", + "javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java", + "javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java", + "javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java", + ] + + deps = [ + ":system_java", + "//base:base_java", + "//base:base_java_test_support", + "//mojo/public/interfaces/bindings/tests:test_interfaces_java", + "//mojo/public/interfaces/bindings/tests:test_mojom_import2_java", + "//mojo/public/interfaces/bindings/tests:test_mojom_import_java", + "//mojo/public/java:bindings_java", + "//mojo/public/java:system_java", + "//third_party/android_support_test_runner:runner_java", + ] + + data = [ + "//mojo/public/interfaces/bindings/tests/data/validation/", + ] +} + +shared_library("mojo_java_unittests") { + testonly = true + + sources = [ + "javatests/init_library.cc", + "javatests/mojo_test_case.cc", + "javatests/mojo_test_case.h", + "javatests/validation_test_util.cc", + "javatests/validation_test_util.h", + ] + + deps = [ + ":jni_headers", + ":libsystem_java", + ":system_java_jni_headers", + "//base", + "//base/test:test_support", + "//build/config/sanitizers:deps", + "//mojo/edk/system", + "//mojo/public/cpp/bindings/tests:mojo_public_bindings_test_utils", + "//mojo/public/cpp/test_support:test_utils", + ] + defines = [ "UNIT_TEST" ] +} + +instrumentation_test_apk("mojo_test_apk") { + deps = [ + ":mojo_javatests", + ":system_java", + "//base:base_java", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/java:bindings_java", + "//third_party/android_support_test_runner:runner_java", + ] + shared_libraries = [ ":mojo_java_unittests" ] + apk_name = "MojoTest" + android_manifest = "javatests/AndroidManifest.xml" +} diff --git a/mojo/android/DEPS b/mojo/android/DEPS new file mode 100644 index 0000000..c80012b --- /dev/null +++ b/mojo/android/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+jni", +] diff --git a/mojo/android/javatests/AndroidManifest.xml b/mojo/android/javatests/AndroidManifest.xml new file mode 100644 index 0000000..32a3927 --- /dev/null +++ b/mojo/android/javatests/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + diff --git a/mojo/android/javatests/DEPS b/mojo/android/javatests/DEPS new file mode 100644 index 0000000..78cf465 --- /dev/null +++ b/mojo/android/javatests/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + # out should be allowed by default, but bots are failing on this. + "+out", +] diff --git a/mojo/android/javatests/apk/.empty b/mojo/android/javatests/apk/.empty new file mode 100644 index 0000000..e69de29 diff --git a/mojo/android/javatests/init_library.cc b/mojo/android/javatests/init_library.cc new file mode 100644 index 0000000..9e1a593 --- /dev/null +++ b/mojo/android/javatests/init_library.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 "base/android/base_jni_onload.h" +#include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "base/android/library_loader/library_loader_hooks.h" +#include "base/bind.h" +#include "mojo/android/javatests/mojo_test_case.h" +#include "mojo/android/javatests/validation_test_util.h" +#include "mojo/android/system/core_impl.h" +#include "mojo/android/system/watcher_impl.h" +#include "mojo/edk/embedder/embedder.h" + +namespace { + +base::android::RegistrationMethod kMojoRegisteredMethods[] = { + {"CoreImpl", mojo::android::RegisterCoreImpl}, + {"MojoTestCase", mojo::android::RegisterMojoTestCase}, + {"ValidationTestUtil", mojo::android::RegisterValidationTestUtil}, + {"WatcherImpl", mojo::android::RegisterWatcherImpl}, +}; + +bool RegisterJNI(JNIEnv* env) { + return base::android::RegisterJni(env) && + RegisterNativeMethods(env, kMojoRegisteredMethods, + arraysize(kMojoRegisteredMethods)); +} + +bool NativeInit() { + if (!base::android::OnJNIOnLoadInit()) + return false; + + mojo::edk::Init(); + return true; +} + +} // namespace + +JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + base::android::InitVM(vm); + JNIEnv* env = base::android::AttachCurrentThread(); + if (!base::android::OnJNIOnLoadRegisterJNI(env) || !RegisterJNI(env) || + !NativeInit()) { + return -1; + } + return JNI_VERSION_1_4; +} diff --git a/mojo/android/javatests/mojo_test_case.cc b/mojo/android/javatests/mojo_test_case.cc new file mode 100644 index 0000000..fc59009 --- /dev/null +++ b/mojo/android/javatests/mojo_test_case.cc @@ -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. + +#include "mojo/android/javatests/mojo_test_case.h" + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "base/at_exit.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "base/test/test_support_android.h" +#include "base/threading/thread_task_runner_handle.h" +#include "jni/MojoTestCase_jni.h" + +using base::android::JavaParamRef; + +namespace { + +struct TestEnvironment { + TestEnvironment() {} + + base::ShadowingAtExitManager at_exit; + base::MessageLoop message_loop; +}; + +} // namespace + +namespace mojo { +namespace android { + +static void Init(JNIEnv* env, const JavaParamRef& jcaller) { + base::InitAndroidTestMessageLoop(); +} + +static jlong SetupTestEnvironment(JNIEnv* env, + const JavaParamRef& jcaller) { + return reinterpret_cast(new TestEnvironment()); +} + +static void TearDownTestEnvironment(JNIEnv* env, + const JavaParamRef& jcaller, + jlong test_environment) { + delete reinterpret_cast(test_environment); +} + +static void RunLoop(JNIEnv* env, + const JavaParamRef& jcaller, + jlong timeout_ms) { + base::RunLoop run_loop; + if (timeout_ms) { + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, base::MessageLoop::QuitWhenIdleClosure(), + base::TimeDelta::FromMilliseconds(timeout_ms)); + run_loop.Run(); + } else { + run_loop.RunUntilIdle(); + } +} + +bool RegisterMojoTestCase(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/javatests/mojo_test_case.h b/mojo/android/javatests/mojo_test_case.h new file mode 100644 index 0000000..2ce3428 --- /dev/null +++ b/mojo/android/javatests/mojo_test_case.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. 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_ANDROID_JAVATESTS_CORE_TEST_H_ +#define MOJO_ANDROID_JAVATESTS_CORE_TEST_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterMojoTestCase(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_SYSTEM_ANDROID_JAVATESTS_CORE_TEST_H_ diff --git a/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java new file mode 100644 index 0000000..1f8de94 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/HandleMock.java @@ -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. + +package org.chromium.mojo; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * A mock handle, that does nothing. + */ +public class HandleMock implements UntypedHandle, MessagePipeHandle, + ProducerHandle, ConsumerHandle, SharedBufferHandle { + + /** + * @see Handle#close() + */ + @Override + public void close() { + // Do nothing. + } + + /** + * @see Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + return null; + } + + /** + * @see Handle#isValid() + */ + @Override + public boolean isValid() { + return true; + } + + /** + * @see Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return this; + } + + /** + * @see org.chromium.mojo.system.Handle#getCore() + */ + @Override + public Core getCore() { + return CoreImpl.getInstance(); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#pass() + */ + @Override + public HandleMock pass() { + return this; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + return 0; + } + + /** + * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + @Override + public int discardData(int numBytes, DataPipe.ReadFlags flags) { + // Do nothing. + return 0; + } + + /** + * @see ConsumerHandle#readData(java.nio.ByteBuffer, DataPipe.ReadFlags) + */ + @Override + public ResultAnd readData(ByteBuffer elements, DataPipe.ReadFlags flags) { + // Do nothing. + return new ResultAnd(MojoResult.OK, 0); + } + + /** + * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, + DataPipe.ReadFlags flags) { + // Do nothing. + return null; + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + // Do nothing. + } + + /** + * @see ProducerHandle#writeData(java.nio.ByteBuffer, DataPipe.WriteFlags) + */ + @Override + public ResultAnd writeData(ByteBuffer elements, DataPipe.WriteFlags flags) { + // Do nothing. + return new ResultAnd(MojoResult.OK, 0); + } + + /** + * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, + DataPipe.WriteFlags flags) { + // Do nothing. + return null; + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + // Do nothing. + } + + /** + * @see MessagePipeHandle#writeMessage(java.nio.ByteBuffer, java.util.List, + * MessagePipeHandle.WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List handles, + WriteFlags flags) { + // Do nothing. + } + + /** + * @see MessagePipeHandle#readMessage(java.nio.ByteBuffer, int, MessagePipeHandle.ReadFlags) + */ + @Override + public ResultAnd readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) { + // Do nothing. + return new ResultAnd(MojoResult.OK, new ReadMessageResult()); + } + + /** + * @see UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return this; + } + + /** + * @see UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return this; + } + + /** + * @see UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return this; + } + + /** + * @see SharedBufferHandle#duplicate(SharedBufferHandle.DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + // Do nothing. + return null; + } + + /** + * @see SharedBufferHandle#map(long, long, SharedBufferHandle.MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + // Do nothing. + return null; + } + + /** + * @see SharedBufferHandle#unmap(java.nio.ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + // Do nothing. + } + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java b/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java new file mode 100644 index 0000000..f4d7ab7 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/MojoTestCase.java @@ -0,0 +1,66 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo; + +import android.test.InstrumentationTestCase; + +import org.chromium.base.ContextUtils; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.LibraryProcessType; + +/** + * Base class to test mojo. Setup the environment. + */ +@JNINamespace("mojo::android") +public class MojoTestCase extends InstrumentationTestCase { + + private long mTestEnvironmentPointer; + + /** + * @see junit.framework.TestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + ContextUtils.initApplicationContext( + getInstrumentation().getTargetContext().getApplicationContext()); + LibraryLoader.get(LibraryProcessType.PROCESS_BROWSER).ensureInitialized(); + nativeInit(); + mTestEnvironmentPointer = nativeSetupTestEnvironment(); + } + + /** + * @see android.test.InstrumentationTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + nativeTearDownTestEnvironment(mTestEnvironmentPointer); + super.tearDown(); + } + + /** + * Runs the run loop for the given time. + */ + protected void runLoop(long timeoutMS) { + nativeRunLoop(timeoutMS); + } + + /** + * Runs the run loop until no handle or task are immediately available. + */ + protected void runLoopUntilIdle() { + nativeRunLoop(0); + } + + private native void nativeInit(); + + private native long nativeSetupTestEnvironment(); + + private native void nativeTearDownTestEnvironment(long testEnvironment); + + private native void nativeRunLoop(long timeoutMS); + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java b/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java new file mode 100644 index 0000000..d10d0d7 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/TestUtils.java @@ -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. + +package org.chromium.mojo; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Random; + +/** + * Utilities methods for tests. + */ +public final class TestUtils { + + private static final Random RANDOM = new Random(); + + /** + * Returns a new direct ByteBuffer of the given size with random (but reproducible) data. + */ + public static ByteBuffer newRandomBuffer(int size) { + byte bytes[] = new byte[size]; + RANDOM.setSeed(size); + RANDOM.nextBytes(bytes); + ByteBuffer data = ByteBuffer.allocateDirect(size); + data.order(ByteOrder.LITTLE_ENDIAN); + data.put(bytes); + data.flip(); + return data; + } + +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java new file mode 100644 index 0000000..38bd348 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsHelperTest.java @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import java.nio.charset.Charset; + +/** + * Testing {@link BindingsHelper}. + */ +public class BindingsHelperTest extends TestCase { + + /** + * Testing {@link BindingsHelper#utf8StringSizeInBytes(String)}. + */ + @SmallTest + public void testUTF8StringLength() { + String[] stringsToTest = { + "", + "a", + "hello world", + "éléphant", + "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕", + "你午饭想吃什么", + "你午饭想吃什么\0éléphant", + }; + for (String s : stringsToTest) { + assertEquals(s.getBytes(Charset.forName("utf8")).length, + BindingsHelper.utf8StringSizeInBytes(s)); + } + assertEquals(1, BindingsHelper.utf8StringSizeInBytes("\0")); + String s = new StringBuilder().appendCodePoint(0x0).appendCodePoint(0x80) + .appendCodePoint(0x800).appendCodePoint(0x10000).toString(); + assertEquals(10, BindingsHelper.utf8StringSizeInBytes(s)); + assertEquals(10, s.getBytes(Charset.forName("utf8")).length); + } + + /** + * Testing {@link BindingsHelper#align(int)}. + */ + @SmallTest + public void testAlign() { + for (int i = 0; i < 3 * BindingsHelper.ALIGNMENT; ++i) { + int j = BindingsHelper.align(i); + assertTrue(j >= i); + assertTrue(j % BindingsHelper.ALIGNMENT == 0); + assertTrue(j - i < BindingsHelper.ALIGNMENT); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java new file mode 100644 index 0000000..d280c77 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTest.java @@ -0,0 +1,241 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.bindings.test.mojom.imported.Color; +import org.chromium.mojo.bindings.test.mojom.imported.Point; +import org.chromium.mojo.bindings.test.mojom.imported.Shape; +import org.chromium.mojo.bindings.test.mojom.imported.Thing; +import org.chromium.mojo.bindings.test.mojom.sample.Bar; +import org.chromium.mojo.bindings.test.mojom.sample.Bar.Type; +import org.chromium.mojo.bindings.test.mojom.sample.DefaultsTest; +import org.chromium.mojo.bindings.test.mojom.sample.Enum; +import org.chromium.mojo.bindings.test.mojom.sample.Foo; +import org.chromium.mojo.bindings.test.mojom.sample.InterfaceConstants; +import org.chromium.mojo.bindings.test.mojom.sample.SampleServiceConstants; +import org.chromium.mojo.bindings.test.mojom.test_structs.EmptyStruct; +import org.chromium.mojo.bindings.test.mojom.test_structs.Rect; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.MessagePipeHandle; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +/** + * Testing generated classes and associated features. + */ +public class BindingsTest extends TestCase { + + /** + * Create a new typical Bar instance. + */ + private static Bar newBar() { + Bar bar = new Bar(); + bar.alpha = (byte) 0x01; + bar.beta = (byte) 0x02; + bar.gamma = (byte) 0x03; + bar.type = Type.BOTH; + return bar; + } + + /** + * Create a new typical Foo instance. + */ + private static Foo createFoo() { + Foo foo = new Foo(); + foo.name = "HELLO WORLD"; + foo.arrayOfArrayOfBools = new boolean[][] { + { true, false, true }, { }, { }, { false }, { true } }; + foo.bar = newBar(); + foo.a = true; + foo.c = true; + foo.data = new byte[] { 0x01, 0x02, 0x03 }; + foo.extraBars = new Bar[] { newBar(), newBar() }; + String[][][] strings = new String[3][2][1]; + for (int i0 = 0; i0 < strings.length; ++i0) { + for (int i1 = 0; i1 < strings[i0].length; ++i1) { + for (int i2 = 0; i2 < strings[i0][i1].length; ++i2) { + strings[i0][i1][i2] = "Hello(" + i0 + ", " + i1 + ", " + i2 + ")"; + } + } + } + foo.multiArrayOfStrings = strings; + ConsumerHandle[] inputStreams = new ConsumerHandle[5]; + for (int i = 0; i < inputStreams.length; ++i) { + inputStreams[i] = new HandleMock(); + } + foo.inputStreams = inputStreams; + ProducerHandle[] outputStreams = new ProducerHandle[3]; + for (int i = 0; i < outputStreams.length; ++i) { + outputStreams[i] = new HandleMock(); + } + foo.outputStreams = outputStreams; + foo.source = new HandleMock(); + return foo; + } + + private static Rect createRect(int x, int y, int width, int height) { + Rect rect = new Rect(); + rect.x = x; + rect.y = y; + rect.width = width; + rect.height = height; + return rect; + } + + private static void checkConstantField( + Field field, Class expectedClass, T value) throws IllegalAccessException { + assertEquals(expectedClass, field.getType()); + assertEquals(Modifier.FINAL, field.getModifiers() & Modifier.FINAL); + assertEquals(Modifier.STATIC, field.getModifiers() & Modifier.STATIC); + assertEquals(value, field.get(null)); + } + + private static void checkField(Field field, Class expectedClass, + Object object, T value) throws IllegalArgumentException, IllegalAccessException { + assertEquals(expectedClass, field.getType()); + assertEquals(0, field.getModifiers() & Modifier.FINAL); + assertEquals(0, field.getModifiers() & Modifier.STATIC); + assertEquals(value, field.get(object)); + } + + /** + * Testing constants are correctly generated. + */ + @SmallTest + public void testConstants() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + checkConstantField(SampleServiceConstants.class.getField("TWELVE"), byte.class, (byte) 12); + checkConstantField(InterfaceConstants.class.getField("LONG"), long.class, 4405L); + } + + /** + * Testing enums are correctly generated. + */ + @SmallTest + public void testEnums() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + checkConstantField(Color.class.getField("RED"), int.class, 0); + checkConstantField(Color.class.getField("BLACK"), int.class, 1); + + checkConstantField(Enum.class.getField("VALUE"), int.class, 0); + + checkConstantField(Shape.class.getField("RECTANGLE"), int.class, 1); + checkConstantField(Shape.class.getField("CIRCLE"), int.class, 2); + checkConstantField(Shape.class.getField("TRIANGLE"), int.class, 3); + } + + /** + * Testing default values on structs. + * + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + @SmallTest + public void testStructDefaults() throws NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + // Check default values. + DefaultsTest test = new DefaultsTest(); + + checkField(DefaultsTest.class.getField("a0"), byte.class, test, (byte) -12); + checkField(DefaultsTest.class.getField("a1"), byte.class, test, (byte) 12); + checkField(DefaultsTest.class.getField("a2"), short.class, test, (short) 1234); + checkField(DefaultsTest.class.getField("a3"), short.class, test, (short) 34567); + checkField(DefaultsTest.class.getField("a4"), int.class, test, 123456); + checkField(DefaultsTest.class.getField("a5"), int.class, test, (int) 3456789012L); + checkField(DefaultsTest.class.getField("a6"), long.class, test, -111111111111L); + // -8446744073709551617 == 9999999999999999999 - 2 ^ 64. + checkField(DefaultsTest.class.getField("a7"), long.class, test, -8446744073709551617L); + checkField(DefaultsTest.class.getField("a8"), int.class, test, 0x12345); + checkField(DefaultsTest.class.getField("a9"), int.class, test, -0x12345); + checkField(DefaultsTest.class.getField("a10"), int.class, test, 1234); + checkField(DefaultsTest.class.getField("a11"), boolean.class, test, true); + checkField(DefaultsTest.class.getField("a12"), boolean.class, test, false); + checkField(DefaultsTest.class.getField("a13"), float.class, test, (float) 123.25); + checkField(DefaultsTest.class.getField("a14"), double.class, test, 1234567890.123); + checkField(DefaultsTest.class.getField("a15"), double.class, test, 1E10); + checkField(DefaultsTest.class.getField("a16"), double.class, test, -1.2E+20); + checkField(DefaultsTest.class.getField("a17"), double.class, test, +1.23E-20); + checkField(DefaultsTest.class.getField("a18"), byte[].class, test, null); + checkField(DefaultsTest.class.getField("a19"), String.class, test, null); + checkField(DefaultsTest.class.getField("a20"), int.class, test, Bar.Type.BOTH); + checkField(DefaultsTest.class.getField("a21"), Point.class, test, null); + + assertNotNull(test.a22); + checkField(DefaultsTest.class.getField("a22"), Thing.class, test, test.a22); + checkField(DefaultsTest.class.getField("a23"), long.class, test, -1L); + checkField(DefaultsTest.class.getField("a24"), long.class, test, 0x123456789L); + checkField(DefaultsTest.class.getField("a25"), long.class, test, -0x123456789L); + } + + /** + * Testing generation of the Foo class. + * + * @throws IllegalAccessException + */ + @SmallTest + public void testFooGeneration() throws NoSuchFieldException, SecurityException, + IllegalAccessException { + // Checking Foo constants. + checkConstantField(Foo.class.getField("FOOBY"), String.class, "Fooby"); + + // Checking Foo default values. + Foo foo = new Foo(); + checkField(Foo.class.getField("name"), String.class, foo, Foo.FOOBY); + + assertNotNull(foo.source); + assertFalse(foo.source.isValid()); + checkField(Foo.class.getField("source"), MessagePipeHandle.class, foo, foo.source); + } + + /** + * Testing serialization of the Foo class. + */ + @SmallTest + public void testFooSerialization() { + // Checking serialization and deserialization of a Foo object. + Foo typicalFoo = createFoo(); + Message serializedFoo = typicalFoo.serialize(null); + Foo deserializedFoo = Foo.deserialize(serializedFoo); + assertEquals(typicalFoo, deserializedFoo); + } + + /** + * Testing serialization of the EmptyStruct class. + */ + @SmallTest + public void testEmptyStructSerialization() { + // Checking serialization and deserialization of a EmptyStruct object. + Message serializedStruct = new EmptyStruct().serialize(null); + EmptyStruct emptyStruct = EmptyStruct.deserialize(serializedStruct); + assertNotNull(emptyStruct); + } + + // In testing maps we want to make sure that the key used when inserting an + // item the key used when looking it up again are different objects. Java + // has default implementations of equals and hashCode that use reference + // equality and hashing, respectively, and that's not what we want for our + // mojom values. + @SmallTest + public void testHashMapStructKey() { + Map map = new HashMap<>(); + map.put(createRect(1, 2, 3, 4), 123); + + Rect key = createRect(1, 2, 3, 4); + assertNotNull(map.get(key)); + assertEquals(123, map.get(key).intValue()); + + map.remove(key); + assertTrue(map.isEmpty()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java new file mode 100644 index 0000000..5554f80 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsTestUtils.java @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import org.chromium.mojo.TestUtils; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.Closeable; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for bindings tests. + */ +public class BindingsTestUtils { + + /** + * {@link MessageReceiver} that records any message it receives. + */ + public static class RecordingMessageReceiver extends SideEffectFreeCloseable + implements MessageReceiver { + + public final List messages = new ArrayList(); + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + messages.add(message); + return true; + } + } + + /** + * {@link MessageReceiverWithResponder} that records any message it receives. + */ + public static class RecordingMessageReceiverWithResponder extends RecordingMessageReceiver + implements MessageReceiverWithResponder { + + public final List> messagesWithReceivers = + new ArrayList>(); + + /** + * @see MessageReceiverWithResponder#acceptWithResponder(Message, MessageReceiver) + */ + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + messagesWithReceivers.add(Pair.create(message, responder)); + return true; + } + } + + /** + * {@link ConnectionErrorHandler} that records any error it received. + */ + public static class CapturingErrorHandler implements ConnectionErrorHandler { + + private MojoException mLastMojoException = null; + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mLastMojoException = e; + } + + /** + * Returns the last recorded exception. + */ + public MojoException getLastMojoException() { + return mLastMojoException; + } + + } + + /** + * Creates a new valid {@link Message}. The message will have a valid header. + */ + public static Message newRandomMessage(int size) { + assert size > 16; + ByteBuffer message = TestUtils.newRandomBuffer(size); + int[] headerAsInts = {16, 2, 0, 0}; + for (int i = 0; i < 4; ++i) { + message.putInt(4 * i, headerAsInts[i]); + } + message.position(0); + return new Message(message, new ArrayList()); + } + + public static P newProxyOverPipe( + Interface.Manager manager, I impl, List toClose) { + Pair handles = + CoreImpl.getInstance().createMessagePipe(null); + P proxy = manager.attachProxy(handles.first, 0); + toClose.add(proxy); + manager.bind(impl, handles.second); + return proxy; + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java new file mode 100644 index 0000000..eea92ab --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/BindingsVersioningTest.java @@ -0,0 +1,211 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStruct; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV0; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV1; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV3; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV5; +import org.chromium.mojo.bindings.test.mojom.test_structs.MultiVersionStructV7; +import org.chromium.mojo.bindings.test.mojom.test_structs.Rect; +import org.chromium.mojo.system.impl.CoreImpl; + +/** + * Testing generated classes with the [MinVersion] annotation. Struct in this test are from: + * mojo/public/interfaces/bindings/tests/rect.mojom and + * mojo/public/interfaces/bindings/tests/test_structs.mojom + */ +public class BindingsVersioningTest extends MojoTestCase { + private static Rect newRect(int factor) { + Rect rect = new Rect(); + rect.x = factor; + rect.y = 2 * factor; + rect.width = 10 * factor; + rect.height = 20 * factor; + return rect; + } + + private static MultiVersionStruct newStruct() { + MultiVersionStruct struct = new MultiVersionStruct(); + struct.fInt32 = 123; + struct.fRect = newRect(5); + struct.fString = "hello"; + struct.fArray = new byte[] {10, 9, 8}; + struct.fBool = true; + struct.fInt16 = 256; + return struct; + } + + /** + * Testing serializing old struct version to newer one. + */ + @SmallTest + public void testOldToNew() { + { + MultiVersionStructV0 v0 = new MultiVersionStructV0(); + v0.fInt32 = 123; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v0.serialize(null)); + assertEquals(expected, output); + assertEquals(0, v0.getVersion()); + assertEquals(0, output.getVersion()); + } + + { + MultiVersionStructV1 v1 = new MultiVersionStructV1(); + v1.fInt32 = 123; + v1.fRect = newRect(5); + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + + MultiVersionStruct output = MultiVersionStruct.deserialize(v1.serialize(null)); + assertEquals(expected, output); + assertEquals(1, v1.getVersion()); + assertEquals(1, output.getVersion()); + } + + { + MultiVersionStructV3 v3 = new MultiVersionStructV3(); + v3.fInt32 = 123; + v3.fRect = newRect(5); + v3.fString = "hello"; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v3.serialize(null)); + assertEquals(expected, output); + assertEquals(3, v3.getVersion()); + assertEquals(3, output.getVersion()); + } + + { + MultiVersionStructV5 v5 = new MultiVersionStructV5(); + v5.fInt32 = 123; + v5.fRect = newRect(5); + v5.fString = "hello"; + v5.fArray = new byte[] {10, 9, 8}; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v5.serialize(null)); + assertEquals(expected, output); + assertEquals(5, v5.getVersion()); + assertEquals(5, output.getVersion()); + } + + { + int expectedHandle = 42; + MultiVersionStructV7 v7 = new MultiVersionStructV7(); + v7.fInt32 = 123; + v7.fRect = newRect(5); + v7.fString = "hello"; + v7.fArray = new byte[] {10, 9, 8}; + v7.fMessagePipe = CoreImpl.getInstance() + .acquireNativeHandle(expectedHandle) + .toMessagePipeHandle(); + v7.fBool = true; + MultiVersionStruct expected = new MultiVersionStruct(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + expected.fBool = true; + + MultiVersionStruct output = MultiVersionStruct.deserialize(v7.serialize(null)); + + // Handles must be tested separately. + assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle()); + output.fMessagePipe = expected.fMessagePipe; + + assertEquals(expected, output); + assertEquals(7, v7.getVersion()); + assertEquals(7, output.getVersion()); + } + } + + /** + * Testing serializing new struct version to older one. + */ + @SmallTest + public void testNewToOld() { + MultiVersionStruct struct = newStruct(); + { + MultiVersionStructV0 expected = new MultiVersionStructV0(); + expected.fInt32 = 123; + + MultiVersionStructV0 output = MultiVersionStructV0.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV1 expected = new MultiVersionStructV1(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + + MultiVersionStructV1 output = MultiVersionStructV1.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV3 expected = new MultiVersionStructV3(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + + MultiVersionStructV3 output = MultiVersionStructV3.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + MultiVersionStructV5 expected = new MultiVersionStructV5(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + + MultiVersionStructV5 output = MultiVersionStructV5.deserialize(struct.serialize(null)); + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + + { + int expectedHandle = 42; + MultiVersionStructV7 expected = new MultiVersionStructV7(); + expected.fInt32 = 123; + expected.fRect = newRect(5); + expected.fString = "hello"; + expected.fArray = new byte[] {10, 9, 8}; + expected.fBool = true; + + MultiVersionStruct input = struct; + input.fMessagePipe = CoreImpl.getInstance() + .acquireNativeHandle(expectedHandle) + .toMessagePipeHandle(); + + MultiVersionStructV7 output = MultiVersionStructV7.deserialize(input.serialize(null)); + + assertEquals(expectedHandle, output.fMessagePipe.releaseNativeHandle()); + output.fMessagePipe = expected.fMessagePipe; + + assertEquals(expected, output); + assertEquals(9, output.getVersion()); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java new file mode 100644 index 0000000..497be65 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/CallbacksTest.java @@ -0,0 +1,59 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.Callbacks.Callback7; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Testing generated callbacks + */ +public class CallbacksTest extends TestCase { + + /** + * Testing {@link Callback1}. + */ + @SmallTest + public void testCallback1() { + final List parameters = new ArrayList(); + new Callback1() { + @Override + public void call(Integer i1) { + parameters.add(i1); + } + }.call(1); + assertEquals(Arrays.asList(1), parameters); + } + + /** + * Testing {@link Callback7}. + */ + @SmallTest + public void testCallback7() { + final List parameters = new ArrayList(); + new Callback7() { + @Override + public void call(Integer i1, Integer i2, Integer i3, Integer i4, Integer i5, Integer i6, + Integer i7) { + parameters.add(i1); + parameters.add(i2); + parameters.add(i3); + parameters.add(i4); + parameters.add(i5); + parameters.add(i6); + parameters.add(i7); + } + }.call(1, 2, 3, 4, 5, 6, 7); + assertEquals(Arrays.asList(1, 2, 3, 4, 5, 6, 7), parameters); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java new file mode 100644 index 0000000..15f9f1f --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ConnectorTest.java @@ -0,0 +1,108 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Testing the {@link Connector} class. + */ +public class ConnectorTest extends MojoTestCase { + + private static final int DATA_LENGTH = 1024; + + private MessagePipeHandle mHandle; + private Connector mConnector; + private Message mTestMessage; + private RecordingMessageReceiver mReceiver; + private CapturingErrorHandler mErrorHandler; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe( + new MessagePipeHandle.CreateOptions()); + mHandle = handles.first; + mConnector = new Connector(handles.second); + mReceiver = new RecordingMessageReceiver(); + mConnector.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mConnector.setErrorHandler(mErrorHandler); + mConnector.start(); + mTestMessage = BindingsTestUtils.newRandomMessage(DATA_LENGTH); + assertNull(mErrorHandler.getLastMojoException()); + assertEquals(0, mReceiver.messages.size()); + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + mConnector.close(); + mHandle.close(); + super.tearDown(); + } + + /** + * Test sending a message through a {@link Connector}. + */ + @SmallTest + public void testSendingMessage() { + mConnector.accept(mTestMessage); + assertNull(mErrorHandler.getLastMojoException()); + ByteBuffer received = ByteBuffer.allocateDirect(DATA_LENGTH); + ResultAnd result = + mHandle.readMessage(received, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(DATA_LENGTH, result.getValue().getMessageSize()); + assertEquals(mTestMessage.getData(), received); + } + + /** + * Test receiving a message through a {@link Connector} + */ + @SmallTest + public void testReceivingMessage() { + mHandle.writeMessage(mTestMessage.getData(), new ArrayList(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertNull(mErrorHandler.getLastMojoException()); + assertEquals(1, mReceiver.messages.size()); + Message received = mReceiver.messages.get(0); + assertEquals(0, received.getHandles().size()); + assertEquals(mTestMessage.getData(), received.getData()); + } + + /** + * Test receiving an error through a {@link Connector}. + */ + @SmallTest + public void testErrors() { + mHandle.close(); + runLoopUntilIdle(); + assertNotNull(mErrorHandler.getLastMojoException()); + assertEquals(MojoResult.FAILED_PRECONDITION, + mErrorHandler.getLastMojoException().getMojoResult()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java new file mode 100644 index 0000000..cabe230 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ExecutorFactoryTest.java @@ -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. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Testing the executor factory. + */ +public class ExecutorFactoryTest extends MojoTestCase { + + private static final long RUN_LOOP_TIMEOUT_MS = 50; + private static final int CONCURRENCY_LEVEL = 5; + private static final ExecutorService WORKERS = Executors.newFixedThreadPool(CONCURRENCY_LEVEL); + + private Executor mExecutor; + private List mThreadContainer; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + mExecutor = ExecutorFactory.getExecutorForCurrentThread(CoreImpl.getInstance()); + mThreadContainer = new ArrayList(); + } + + /** + * Testing the {@link Executor} when called from the executor thread. + */ + @SmallTest + public void testExecutorOnCurrentThread() { + Runnable action = new Runnable() { + @Override + public void run() { + mThreadContainer.add(Thread.currentThread()); + } + }; + mExecutor.execute(action); + mExecutor.execute(action); + assertEquals(0, mThreadContainer.size()); + runLoop(RUN_LOOP_TIMEOUT_MS); + assertEquals(2, mThreadContainer.size()); + for (Thread thread : mThreadContainer) { + assertEquals(Thread.currentThread(), thread); + } + } + + /** + * Testing the {@link Executor} when called from another thread. + */ + @SmallTest + public void testExecutorOnOtherThread() { + final CyclicBarrier barrier = new CyclicBarrier(CONCURRENCY_LEVEL + 1); + for (int i = 0; i < CONCURRENCY_LEVEL; ++i) { + WORKERS.execute(new Runnable() { + @Override + public void run() { + mExecutor.execute(new Runnable() { + + @Override + public void run() { + mThreadContainer.add(Thread.currentThread()); + } + }); + try { + barrier.await(); + } catch (InterruptedException e) { + fail("Unexpected exception: " + e.getMessage()); + } catch (BrokenBarrierException e) { + fail("Unexpected exception: " + e.getMessage()); + } + } + }); + } + try { + barrier.await(); + } catch (InterruptedException e) { + fail("Unexpected exception: " + e.getMessage()); + } catch (BrokenBarrierException e) { + fail("Unexpected exception: " + e.getMessage()); + } + assertEquals(0, mThreadContainer.size()); + runLoop(RUN_LOOP_TIMEOUT_MS); + assertEquals(CONCURRENCY_LEVEL, mThreadContainer.size()); + for (Thread thread : mThreadContainer) { + assertEquals(Thread.currentThread(), thread); + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java new file mode 100644 index 0000000..8cdd4ab --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfaceControlMessageTest.java @@ -0,0 +1,129 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.Callbacks.Callback1; +import org.chromium.mojo.bindings.test.mojom.sample.Enum; +import org.chromium.mojo.bindings.test.mojom.sample.IntegerAccessor; +import org.chromium.mojo.system.MojoException; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for interface control messages. + */ +public class InterfaceControlMessageTest extends MojoTestCase { + private final List mCloseablesToClose = new ArrayList(); + + /** + * See mojo/public/interfaces/bindings/tests/sample_interfaces.mojom. + */ + class IntegerAccessorImpl extends SideEffectFreeCloseable implements IntegerAccessor { + private long mValue = 0; + private int mEnum = 0; + private boolean mEncounteredError = false; + + /** + * @see ConnectionErrorHandler#onConnectionError(MojoException) + */ + @Override + public void onConnectionError(MojoException e) { + mEncounteredError = true; + } + + /** + * @see IntegerAccessor#getInteger(IntegerAccessor.GetIntegerResponse) + */ + @Override + public void getInteger(GetIntegerResponse response) { + response.call(mValue, mEnum); + } + + /** + * @see IntegerAccessor#setInteger(long, int) + */ + @Override + public void setInteger(long value, int enumValue) { + mValue = value; + mEnum = enumValue; + } + + public long getValue() { + return mValue; + } + + public boolean encounteredError() { + return mEncounteredError; + } + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + // Close the elements in the reverse order they were added. This is needed because it is an + // error to close the handle of a proxy without closing the proxy first. + Collections.reverse(mCloseablesToClose); + for (Closeable c : mCloseablesToClose) { + c.close(); + } + super.tearDown(); + } + + @SmallTest + public void testQueryVersion() { + IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe( + IntegerAccessor.MANAGER, new IntegerAccessorImpl(), mCloseablesToClose); + assertEquals(0, p.getProxyHandler().getVersion()); + p.getProxyHandler().queryVersion(new Callback1() { + @Override + public void call(Integer version) { + assertEquals(3, version.intValue()); + } + }); + runLoopUntilIdle(); + assertEquals(3, p.getProxyHandler().getVersion()); + } + + @SmallTest + public void testRequireVersion() { + IntegerAccessorImpl impl = new IntegerAccessorImpl(); + IntegerAccessor.Proxy p = BindingsTestUtils.newProxyOverPipe( + IntegerAccessor.MANAGER, impl, mCloseablesToClose); + + assertEquals(0, p.getProxyHandler().getVersion()); + + p.getProxyHandler().requireVersion(1); + assertEquals(1, p.getProxyHandler().getVersion()); + p.setInteger(123, Enum.VALUE); + runLoopUntilIdle(); + assertFalse(impl.encounteredError()); + assertEquals(123, impl.getValue()); + + p.getProxyHandler().requireVersion(3); + assertEquals(3, p.getProxyHandler().getVersion()); + p.setInteger(456, Enum.VALUE); + runLoopUntilIdle(); + assertFalse(impl.encounteredError()); + assertEquals(456, impl.getValue()); + + // Require a version that is not supported by the implementation side. + p.getProxyHandler().requireVersion(4); + // getVersion() is updated synchronously. + assertEquals(4, p.getProxyHandler().getVersion()); + p.setInteger(789, Enum.VALUE); + runLoopUntilIdle(); + assertTrue(impl.encounteredError()); + // The call to setInteger() after requireVersion() is ignored. + assertEquals(456, impl.getValue()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java new file mode 100644 index 0000000..d8bd3e8 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/InterfacesTest.java @@ -0,0 +1,284 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.test.mojom.imported.ImportedInterface; +import org.chromium.mojo.bindings.test.mojom.sample.Factory; +import org.chromium.mojo.bindings.test.mojom.sample.NamedObject; +import org.chromium.mojo.bindings.test.mojom.sample.NamedObject.GetNameResponse; +import org.chromium.mojo.bindings.test.mojom.sample.Request; +import org.chromium.mojo.bindings.test.mojom.sample.Response; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Tests for interfaces / proxies / stubs generated for sample_factory.mojom. + */ +public class InterfacesTest extends MojoTestCase { + + private static final String OBJECT_NAME = "hello world"; + + private final List mCloseablesToClose = new ArrayList(); + + /** + * Basic implementation of {@link NamedObject}. + */ + public static class MockNamedObjectImpl extends CapturingErrorHandler implements NamedObject { + + private String mName = ""; + + /** + * @see org.chromium.mojo.bindings.Interface#close() + */ + @Override + public void close() { + } + + @Override + public void setName(String name) { + mName = name; + } + + @Override + public void getName(GetNameResponse callback) { + callback.call(mName); + } + + public String getNameSynchronously() { + return mName; + } + } + + /** + * Implementation of {@link GetNameResponse} keeping track of usage. + */ + public static class RecordingGetNameResponse implements GetNameResponse { + private String mName; + private boolean mCalled; + + public RecordingGetNameResponse() { + reset(); + } + + @Override + public void call(String name) { + mName = name; + mCalled = true; + } + + public String getName() { + return mName; + } + + public boolean wasCalled() { + return mCalled; + } + + public void reset() { + mName = null; + mCalled = false; + } + } + + /** + * Basic implementation of {@link Factory}. + */ + public class MockFactoryImpl extends CapturingErrorHandler implements Factory { + + private boolean mClosed = false; + + public boolean isClosed() { + return mClosed; + } + + /** + * @see org.chromium.mojo.bindings.Interface#close() + */ + @Override + public void close() { + mClosed = true; + } + + @Override + public void doStuff(Request request, MessagePipeHandle pipe, DoStuffResponse callback) { + if (pipe != null) { + pipe.close(); + } + Response response = new Response(); + response.x = 42; + callback.call(response, "Hello"); + } + + @Override + public void doStuff2(ConsumerHandle pipe, DoStuff2Response callback) { + callback.call("World"); + } + + @Override + public void createNamedObject(InterfaceRequest obj) { + NamedObject.MANAGER.bind(new MockNamedObjectImpl(), obj); + } + + @Override + public void requestImportedInterface(InterfaceRequest obj, + RequestImportedInterfaceResponse callback) { + throw new UnsupportedOperationException("Not implemented."); + } + + @Override + public void takeImportedInterface(ImportedInterface obj, + TakeImportedInterfaceResponse callback) { + throw new UnsupportedOperationException("Not implemented."); + } + } + + /** + * Implementation of DoStuffResponse that keeps track of if the response is called. + */ + public static class DoStuffResponseImpl implements Factory.DoStuffResponse { + private boolean mResponseCalled = false; + + public boolean wasResponseCalled() { + return mResponseCalled; + } + + @Override + public void call(Response response, String string) { + mResponseCalled = true; + } + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + // Close the elements in the reverse order they were added. This is needed because it is an + // error to close the handle of a proxy without closing the proxy first. + Collections.reverse(mCloseablesToClose); + for (Closeable c : mCloseablesToClose) { + c.close(); + } + super.tearDown(); + } + + /** + * Check that the given proxy receives the calls. If |impl| is not null, also check that the + * calls are forwared to |impl|. + */ + private void checkProxy(NamedObject.Proxy proxy, MockNamedObjectImpl impl) { + RecordingGetNameResponse callback = new RecordingGetNameResponse(); + CapturingErrorHandler errorHandler = new CapturingErrorHandler(); + proxy.getProxyHandler().setErrorHandler(errorHandler); + + if (impl != null) { + assertNull(impl.getLastMojoException()); + assertEquals("", impl.getNameSynchronously()); + } + + proxy.getName(callback); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + assertTrue(callback.wasCalled()); + assertEquals("", callback.getName()); + + callback.reset(); + proxy.setName(OBJECT_NAME); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + if (impl != null) { + assertNull(impl.getLastMojoException()); + assertEquals(OBJECT_NAME, impl.getNameSynchronously()); + } + + proxy.getName(callback); + runLoopUntilIdle(); + + assertNull(errorHandler.getLastMojoException()); + assertTrue(callback.wasCalled()); + assertEquals(OBJECT_NAME, callback.getName()); + } + + @SmallTest + public void testName() { + assertEquals("sample::NamedObject", NamedObject.MANAGER.getName()); + } + + @SmallTest + public void testProxyAndStub() { + MockNamedObjectImpl impl = new MockNamedObjectImpl(); + NamedObject.Proxy proxy = + NamedObject.MANAGER.buildProxy(null, NamedObject.MANAGER.buildStub(null, impl)); + + checkProxy(proxy, impl); + } + + @SmallTest + public void testProxyAndStubOverPipe() { + MockNamedObjectImpl impl = new MockNamedObjectImpl(); + NamedObject.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(NamedObject.MANAGER, impl, mCloseablesToClose); + + checkProxy(proxy, impl); + } + + @SmallTest + public void testFactoryOverPipe() { + Factory.Proxy proxy = BindingsTestUtils.newProxyOverPipe( + Factory.MANAGER, new MockFactoryImpl(), mCloseablesToClose); + Pair> request = + NamedObject.MANAGER.getInterfaceRequest(CoreImpl.getInstance()); + mCloseablesToClose.add(request.first); + proxy.createNamedObject(request.second); + + checkProxy(request.first, null); + } + + @SmallTest + public void testInterfaceClosing() { + MockFactoryImpl impl = new MockFactoryImpl(); + Factory.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose); + + assertFalse(impl.isClosed()); + + proxy.close(); + runLoopUntilIdle(); + + assertTrue(impl.isClosed()); + } + + @SmallTest + public void testResponse() { + MockFactoryImpl impl = new MockFactoryImpl(); + Factory.Proxy proxy = + BindingsTestUtils.newProxyOverPipe(Factory.MANAGER, impl, mCloseablesToClose); + Request request = new Request(); + request.x = 42; + Pair handles = + CoreImpl.getInstance().createMessagePipe(null); + DoStuffResponseImpl response = new DoStuffResponseImpl(); + proxy.doStuff(request, handles.first, response); + + assertFalse(response.wasResponseCalled()); + + runLoopUntilIdle(); + + assertTrue(response.wasResponseCalled()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.java new file mode 100644 index 0000000..b2e6ac8 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/MessageHeaderTest.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 android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.bindings.test.mojom.imported.Point; + +/** + * Testing internal classes of interfaces. + */ +public class MessageHeaderTest extends TestCase { + + /** + * Testing that headers are identical after being serialized/deserialized. + */ + @SmallTest + public void testSimpleMessageHeader() { + final int xValue = 1; + final int yValue = 2; + final int type = 6; + Point p = new Point(); + p.x = xValue; + p.y = yValue; + ServiceMessage message = p.serializeWithHeader(null, new MessageHeader(type)); + + MessageHeader header = message.getHeader(); + assertTrue(header.validateHeader(type, 0)); + assertEquals(type, header.getType()); + assertEquals(0, header.getFlags()); + + Point p2 = Point.deserialize(message.getPayload()); + assertNotNull(p2); + assertEquals(p.x, p2.x); + assertEquals(p.y, p2.y); + } + + /** + * Testing that headers are identical after being serialized/deserialized. + */ + @SmallTest + public void testMessageWithRequestIdHeader() { + final int xValue = 1; + final int yValue = 2; + final int type = 6; + final long requestId = 0x1deadbeafL; + Point p = new Point(); + p.x = xValue; + p.y = yValue; + ServiceMessage message = p.serializeWithHeader(null, + new MessageHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0)); + message.setRequestId(requestId); + + MessageHeader header = message.getHeader(); + assertTrue(header.validateHeader(type, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG)); + assertEquals(type, header.getType()); + assertEquals(MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, header.getFlags()); + assertEquals(requestId, header.getRequestId()); + + Point p2 = Point.deserialize(message.getPayload()); + assertNotNull(p2); + assertEquals(p.x, p2.x); + assertEquals(p.y, p2.y); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java new file mode 100644 index 0000000..51dbd22 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ReadAndDispatchMessageTest.java @@ -0,0 +1,109 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiver; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Testing {@link Connector#readAndDispatchMessage}. + */ +public class ReadAndDispatchMessageTest extends MojoTestCase { + + private static final int DATA_SIZE = 1024; + + private ByteBuffer mData; + private Pair mHandles; + private List mHandlesToSend = new ArrayList(); + private List mHandlesToClose = new ArrayList(); + private RecordingMessageReceiver mMessageReceiver; + + /** + * @see org.chromium.mojo.MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + Core core = CoreImpl.getInstance(); + mData = BindingsTestUtils.newRandomMessage(DATA_SIZE).getData(); + mMessageReceiver = new RecordingMessageReceiver(); + mHandles = core.createMessagePipe(new MessagePipeHandle.CreateOptions()); + Pair datapipe = core.createDataPipe(null); + mHandlesToSend.addAll(Arrays.asList(datapipe.first, datapipe.second)); + mHandlesToClose.addAll(Arrays.asList(mHandles.first, mHandles.second)); + mHandlesToClose.addAll(mHandlesToSend); + } + + /** + * @see org.chromium.mojo.MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + for (Handle handle : mHandlesToClose) { + handle.close(); + } + super.tearDown(); + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + */ + @SmallTest + public void testReadAndDispatchMessage() { + mHandles.first.writeMessage(mData, mHandlesToSend, MessagePipeHandle.WriteFlags.NONE); + assertEquals(MojoResult.OK, Connector.readAndDispatchMessage(mHandles.second, + mMessageReceiver).getMojoResult()); + assertEquals(1, mMessageReceiver.messages.size()); + Message message = mMessageReceiver.messages.get(0); + mHandlesToClose.addAll(message.getHandles()); + assertEquals(mData, message.getData()); + assertEquals(2, message.getHandles().size()); + for (Handle handle : message.getHandles()) { + assertTrue(handle.isValid()); + } + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + * with no message available. + */ + @SmallTest + public void testReadAndDispatchMessageOnEmptyHandle() { + assertEquals(MojoResult.SHOULD_WAIT, Connector.readAndDispatchMessage(mHandles.second, + mMessageReceiver).getMojoResult()); + assertEquals(0, mMessageReceiver.messages.size()); + } + + /** + * Testing {@link Connector#readAndDispatchMessage(MessagePipeHandle, MessageReceiver)} + * on closed handle. + */ + @SmallTest + public void testReadAndDispatchMessageOnClosedHandle() { + mHandles.first.close(); + try { + Connector.readAndDispatchMessage(mHandles.second, mMessageReceiver); + fail("MojoException should have been thrown"); + } catch (MojoException expected) { + assertEquals(MojoResult.FAILED_PRECONDITION, expected.getMojoResult()); + } + assertEquals(0, mMessageReceiver.messages.size()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java new file mode 100644 index 0000000..6aa1726 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/RouterTest.java @@ -0,0 +1,231 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.base.annotations.SuppressFBWarnings; +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.BindingsTestUtils.CapturingErrorHandler; +import org.chromium.mojo.bindings.BindingsTestUtils.RecordingMessageReceiverWithResponder; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignals; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +/** + * Testing {@link Router} + */ +public class RouterTest extends MojoTestCase { + + private MessagePipeHandle mHandle; + private Router mRouter; + private RecordingMessageReceiverWithResponder mReceiver; + private CapturingErrorHandler mErrorHandler; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + mHandle = handles.first; + mRouter = new RouterImpl(handles.second); + mReceiver = new RecordingMessageReceiverWithResponder(); + mRouter.setIncomingMessageReceiver(mReceiver); + mErrorHandler = new CapturingErrorHandler(); + mRouter.setErrorHandler(mErrorHandler); + mRouter.start(); + } + + /** + * Testing sending a message via the router that expected a response. + */ + @SmallTest + public void testSendingToRouterWithResponse() { + final int requestMessageType = 0xdead; + final int responseMessageType = 0xbeaf; + + // Sending a message expecting a response. + MessageHeader header = new MessageHeader(requestMessageType, + MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, 0); + Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + header.encode(encoder); + mRouter.acceptWithResponder(encoder.getMessage(), mReceiver); + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(header.getSize()); + ResultAnd result = + mHandle.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + + assertEquals(MojoResult.OK, result.getMojoResult()); + MessageHeader receivedHeader = new Message( + receiveBuffer, new ArrayList()).asServiceMessage().getHeader(); + + assertEquals(header.getType(), receivedHeader.getType()); + assertEquals(header.getFlags(), receivedHeader.getFlags()); + assertTrue(receivedHeader.getRequestId() != 0); + + // Sending the response. + MessageHeader responseHeader = new MessageHeader(responseMessageType, + MessageHeader.MESSAGE_IS_RESPONSE_FLAG, receivedHeader.getRequestId()); + encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + responseHeader.encode(encoder); + Message responseMessage = encoder.getMessage(); + mHandle.writeMessage(responseMessage.getData(), new ArrayList(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + + assertEquals(1, mReceiver.messages.size()); + ServiceMessage receivedResponseMessage = mReceiver.messages.get(0).asServiceMessage(); + assertEquals(MessageHeader.MESSAGE_IS_RESPONSE_FLAG, + receivedResponseMessage.getHeader().getFlags()); + assertEquals(responseMessage.getData(), receivedResponseMessage.getData()); + } + + /** + * Sends a message to the Router. + * + * @param messageIndex Used when sending multiple messages to indicate the index of this + * message. + * @param requestMessageType The message type to use in the header of the sent message. + * @param requestId The requestId to use in the header of the sent message. + */ + private void sendMessageToRouter(int messageIndex, int requestMessageType, int requestId) { + MessageHeader header = new MessageHeader( + requestMessageType, MessageHeader.MESSAGE_EXPECTS_RESPONSE_FLAG, requestId); + Encoder encoder = new Encoder(CoreImpl.getInstance(), header.getSize()); + header.encode(encoder); + Message headerMessage = encoder.getMessage(); + mHandle.writeMessage(headerMessage.getData(), new ArrayList(), + MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + + assertEquals(messageIndex + 1, mReceiver.messagesWithReceivers.size()); + Pair receivedMessage = + mReceiver.messagesWithReceivers.get(messageIndex); + assertEquals(headerMessage.getData(), receivedMessage.first.getData()); + } + + /** + * Sends a response message from the Router. + * + * @param messageIndex Used when sending responses to multiple messages to indicate the index + * of the message that this message is a response to. + * @param responseMessageType The message type to use in the header of the response message. + */ + private void sendResponseFromRouter(int messageIndex, int responseMessageType) { + Pair receivedMessage = + mReceiver.messagesWithReceivers.get(messageIndex); + + long requestId = receivedMessage.first.asServiceMessage().getHeader().getRequestId(); + + MessageHeader responseHeader = new MessageHeader( + responseMessageType, MessageHeader.MESSAGE_IS_RESPONSE_FLAG, requestId); + Encoder encoder = new Encoder(CoreImpl.getInstance(), responseHeader.getSize()); + responseHeader.encode(encoder); + Message message = encoder.getMessage(); + receivedMessage.second.accept(message); + + ByteBuffer receivedResponseMessage = ByteBuffer.allocateDirect(responseHeader.getSize()); + ResultAnd result = + mHandle.readMessage(receivedResponseMessage, 0, MessagePipeHandle.ReadFlags.NONE); + + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(message.getData(), receivedResponseMessage); + } + + /** + * Clears {@code mReceiver.messagesWithReceivers} allowing all message receivers to be + * finalized. + *

+ * Since there is no way to force the Garbage Collector to actually call finalize and we want to + * test the effects of the finalize() method, we explicitly call finalize() on all of the + * message receivers. We do this in a custom thread to better approximate what the JVM does. + */ + private void clearAllMessageReceivers() { + Thread myFinalizerThread = new Thread() { + @Override + @SuppressFBWarnings("FI_EXPLICIT_INVOCATION") + public void run() { + for (Pair receivedMessage : + mReceiver.messagesWithReceivers) { + RouterImpl.ResponderThunk thunk = + (RouterImpl.ResponderThunk) receivedMessage.second; + try { + thunk.finalize(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + } + }; + myFinalizerThread.start(); + try { + myFinalizerThread.join(); + } catch (InterruptedException e) { + // ignore. + } + mReceiver.messagesWithReceivers.clear(); + } + + /** + * Testing receiving a message via the router that expected a response. + */ + @SmallTest + public void testReceivingViaRouterWithResponse() { + final int requestMessageType = 0xdead; + final int responseMessageType = 0xbeef; + final int requestId = 0xdeadbeaf; + + // Send a message expecting a response. + sendMessageToRouter(0, requestMessageType, requestId); + + // Sending the response. + sendResponseFromRouter(0, responseMessageType); + } + + /** + * Tests that if a callback is dropped (i.e. becomes unreachable and is finalized + * without being used), then the message pipe will be closed. + */ + @SmallTest + public void testDroppingReceiverWithoutUsingIt() { + // Send 10 messages to the router without sending a response. + for (int i = 0; i < 10; i++) { + sendMessageToRouter(i, i, i); + } + + // Now send the 10 responses. This should work fine. + for (int i = 0; i < 10; i++) { + sendResponseFromRouter(i, i); + } + + // Clear all MessageRecievers so that the ResponderThunks will + // be finalized. + clearAllMessageReceivers(); + + // Send another message to the router without sending a response. + sendMessageToRouter(0, 0, 0); + + // Clear the MessageReciever so that the ResponderThunk will + // be finalized. Since the RespondeThunk was never used, this + // should close the pipe. + clearAllMessageReceivers(); + // The close() occurs asynchronously on this thread. + runLoopUntilIdle(); + + // Confirm that the pipe was closed on the Router side. + HandleSignals closedFlag = HandleSignals.none().setPeerClosed(true); + assertEquals(closedFlag, mHandle.querySignalsState().getSatisfiedSignals()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java new file mode 100644 index 0000000..2c17e3a --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/SerializationTest.java @@ -0,0 +1,175 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import junit.framework.TestCase; + +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct1; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct2; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct3; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct4; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct5; +import org.chromium.mojo.bindings.test.mojom.mojo.Struct6; +import org.chromium.mojo.bindings.test.mojom.mojo.StructOfNullables; + +import java.nio.ByteBuffer; + +/** + * Tests for the serialization logic of the generated structs, using structs defined in + * mojo/public/interfaces/bindings/tests/serialization_test_structs.mojom . + */ +public class SerializationTest extends TestCase { + + private static void assertThrowsSerializationException(Struct struct) { + try { + struct.serialize(null); + fail("Serialization of invalid struct should have thrown an exception."); + } catch (SerializationException ex) { + // Expected. + } + } + + /** + * Verifies that serializing a struct with an invalid handle of a non-nullable type throws an + * exception. + */ + @SmallTest + public void testHandle() { + Struct2 struct = new Struct2(); + assertFalse(struct.hdl.isValid()); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.hdl = new HandleMock(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a null struct pointer throws an exception. + */ + @SmallTest + public void testStructPointer() { + Struct3 struct = new Struct3(); + assertNull(struct.struct1); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.struct1 = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with an array of structs throws an exception when the + * struct is invalid. + */ + @SmallTest + public void testStructArray() { + Struct4 struct = new Struct4(); + assertNull(struct.data); + assertThrowsSerializationException(struct); + + // Create the (1-element) array but have the element null. + struct.data = new Struct1[1]; + assertThrowsSerializationException(struct); + + // Create the array element, struct should serialize now. + struct.data[0] = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a fixed-size array of incorrect length throws an + * exception. + */ + @SmallTest + public void testFixedSizeArray() { + Struct5 struct = new Struct5(); + assertNull(struct.pair); + assertThrowsSerializationException(struct); + + // Create the (1-element) array, 2-element array is required. + struct.pair = new Struct1[1]; + struct.pair[0] = new Struct1(); + assertThrowsSerializationException(struct); + + // Create the array of a correct size, struct should serialize now. + struct.pair = new Struct1[2]; + struct.pair[0] = new Struct1(); + struct.pair[1] = new Struct1(); + struct.serialize(null); + } + + /** + * Verifies that serializing a struct with a null string throws an exception. + */ + @SmallTest + public void testString() { + Struct6 struct = new Struct6(); + assertNull(struct.str); + assertThrowsSerializationException(struct); + + // Make the struct valid and verify that it serializes without an exception. + struct.str = ""; + struct.serialize(null); + } + + /** + * Verifies that a struct with an invalid nullable handle, null nullable struct pointer and null + * nullable string serializes without an exception. + */ + @SmallTest + public void testNullableFields() { + StructOfNullables struct = new StructOfNullables(); + assertFalse(struct.hdl.isValid()); + assertNull(struct.struct1); + assertNull(struct.str); + struct.serialize(null); + } + + /** + * Verifies that a struct can be serialized to and deserialized from a ByteBuffer. + */ + @SmallTest + public void testByteBufferSerialization() { + Struct1 input = new Struct1(); + input.i = 0x7F; + + ByteBuffer buf = input.serialize(); + + byte[] expected_raw_bytes = {16, 0, 0, 0, 0, 0, 0, 0, 0x7F, 0, 0, 0, 0, 0, 0, 0}; + ByteBuffer expected_buf = ByteBuffer.wrap(expected_raw_bytes); + assertEquals(expected_buf, buf); + + Struct1 output = Struct1.deserialize(buf); + assertEquals(0x7F, output.i); + } + + /** + * Verifies that a struct with handles cannot be serialized to a ByteBuffer. + */ + @SmallTest + public void testByteBufferSerializationWithHandles() { + StructOfNullables struct = new StructOfNullables(); + assertFalse(struct.hdl.isValid()); + assertNull(struct.struct1); + assertNull(struct.str); + + // It is okay to serialize invalid handles. + struct.serialize(); + + struct.hdl = new HandleMock(); + + try { + struct.serialize(); + fail("Serializing a struct with handles to a ByteBuffer should have thrown an " + + "exception."); + } catch (UnsupportedOperationException ex) { + // Expected. + } + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java new file mode 100644 index 0000000..8424618 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTest.java @@ -0,0 +1,237 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.base.test.util.UrlUtils; +import org.chromium.mojo.HandleMock; +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.bindings.test.mojom.mojo.ConformanceTestInterface; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterfaceTestHelper; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.impl.CoreImpl; + +import java.io.File; +import java.io.FileFilter; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * Testing validation upon deserialization using the interfaces defined in the + * mojo/public/interfaces/bindings/tests/validation_test_interfaces.mojom file. + *

+ * One needs to pass '--test_data=bindings:{path to mojo/public/interfaces/bindings/tests/data}' to + * the test_runner script for this test to find the validation data it needs. + */ +public class ValidationTest extends MojoTestCase { + + /** + * The path where validation test data is. + */ + private static final File VALIDATION_TEST_DATA_PATH = + new File(UrlUtils.getIsolatedTestFilePath( + "mojo/public/interfaces/bindings/tests/data/validation")); + + /** + * The data needed for a validation test. + */ + private static class TestData { + public File dataFile; + public ValidationTestUtil.Data inputData; + public String expectedResult; + } + + private static class DataFileFilter implements FileFilter { + private final String mPrefix; + + public DataFileFilter(String prefix) { + this.mPrefix = prefix; + } + + @Override + public boolean accept(File pathname) { + // TODO(yzshen, qsr): skip some interface versioning tests. + if (pathname.getName().startsWith("conformance_mthd13_good_2")) { + return false; + } + return pathname.isFile() && pathname.getName().startsWith(mPrefix) + && pathname.getName().endsWith(".data"); + } + } + + private static String getStringContent(File f) throws FileNotFoundException { + try (Scanner scanner = new Scanner(f)) { + scanner.useDelimiter("\\Z"); + StringBuilder result = new StringBuilder(); + while (scanner.hasNext()) { + result.append(scanner.next()); + } + return result.toString().trim(); + } + } + + private static List getTestData(String prefix) + throws FileNotFoundException { + List results = new ArrayList(); + + // Fail if the test data is not present. + if (!VALIDATION_TEST_DATA_PATH.isDirectory()) { + fail("No test data directory found. " + + "Expected directory at: " + VALIDATION_TEST_DATA_PATH); + } + + File[] files = VALIDATION_TEST_DATA_PATH.listFiles(new DataFileFilter(prefix)); + if (files != null) { + for (File dataFile : files) { + File resultFile = new File(dataFile.getParent(), + dataFile.getName().replaceFirst("\\.data$", ".expected")); + TestData testData = new TestData(); + testData.dataFile = dataFile; + testData.inputData = ValidationTestUtil.parseData(getStringContent(dataFile)); + testData.expectedResult = getStringContent(resultFile); + results.add(testData); + } + } + return results; + } + + /** + * Runs all the test with the given prefix on the given {@link MessageReceiver}. + */ + private static void runTest(String prefix, MessageReceiver messageReceiver) + throws FileNotFoundException { + List testData = getTestData(prefix); + for (TestData test : testData) { + assertNull("Unable to read: " + test.dataFile.getName() + + ": " + test.inputData.getErrorMessage(), + test.inputData.getErrorMessage()); + List handles = new ArrayList(); + for (int i = 0; i < test.inputData.getHandlesCount(); ++i) { + handles.add(new HandleMock()); + } + Message message = new Message(test.inputData.getData(), handles); + boolean passed = messageReceiver.accept(message); + if (passed && !test.expectedResult.equals("PASS")) { + fail("Input: " + test.dataFile.getName() + + ": The message should have been refused. Expected error: " + + test.expectedResult); + } + if (!passed && test.expectedResult.equals("PASS")) { + fail("Input: " + test.dataFile.getName() + + ": The message should have been accepted."); + } + } + } + + private static class RoutingMessageReceiver implements MessageReceiver { + private final MessageReceiverWithResponder mRequest; + private final MessageReceiver mResponse; + + private RoutingMessageReceiver(MessageReceiverWithResponder request, + MessageReceiver response) { + this.mRequest = request; + this.mResponse = response; + } + + /** + * @see MessageReceiver#accept(Message) + */ + @Override + public boolean accept(Message message) { + try { + MessageHeader header = message.asServiceMessage().getHeader(); + if (header.hasFlag(MessageHeader.MESSAGE_IS_RESPONSE_FLAG)) { + return mResponse.accept(message); + } else { + return mRequest.acceptWithResponder(message, new SinkMessageReceiver()); + } + } catch (DeserializationException e) { + return false; + } + } + + /** + * @see MessageReceiver#close() + */ + @Override + public void close() { + } + + } + + /** + * A trivial message receiver that refuses all messages it receives. + */ + private static class SinkMessageReceiver implements MessageReceiverWithResponder { + + @Override + public boolean accept(Message message) { + return true; + } + + @Override + public void close() { + } + + @Override + public boolean acceptWithResponder(Message message, MessageReceiver responder) { + return true; + } + } + + /** + * Testing the conformance suite. + */ + @SmallTest + public void testConformance() throws FileNotFoundException { + runTest("conformance_", + ConformanceTestInterface.MANAGER.buildStub(CoreImpl.getInstance(), + ConformanceTestInterface.MANAGER.buildProxy( + CoreImpl.getInstance(), new SinkMessageReceiver()))); + } + + /** + * Testing the integration suite for message headers. + */ + @SmallTest + public void testIntegrationMessageHeader() throws FileNotFoundException { + runTest("integration_msghdr_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } + + /** + * Testing the integration suite for request messages. + */ + @SmallTest + public void testIntegrationRequestMessage() throws FileNotFoundException { + runTest("integration_intf_rqst_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } + + /** + * Testing the integration suite for response messages. + */ + @SmallTest + public void testIntegrationResponseMessage() throws FileNotFoundException { + runTest("integration_intf_resp_", + new RoutingMessageReceiver(IntegrationTestInterface.MANAGER.buildStub(null, + IntegrationTestInterface.MANAGER.buildProxy(null, + new SinkMessageReceiver())), + IntegrationTestInterfaceTestHelper + .newIntegrationTestInterfaceMethodCallback())); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java new file mode 100644 index 0000000..91b993c --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtil.java @@ -0,0 +1,68 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Utility class for testing message validation. The file format used to describe a message is + * described in The format is described in + * mojo/public/cpp/bindings/tests/validation_test_input_parser.h + */ +@JNINamespace("mojo::android") +public class ValidationTestUtil { + + /** + * Content of a '.data' file. + */ + public static class Data { + private final ByteBuffer mData; + private final int mHandlesCount; + private final String mErrorMessage; + + public ByteBuffer getData() { + return mData; + } + + public int getHandlesCount() { + return mHandlesCount; + } + + public String getErrorMessage() { + return mErrorMessage; + } + + private Data(ByteBuffer data, int handlesCount, String errorMessage) { + this.mData = data; + this.mHandlesCount = handlesCount; + this.mErrorMessage = errorMessage; + } + } + + /** + * Parse a '.data' file. + */ + public static Data parseData(String dataAsString) { + return nativeParseData(dataAsString); + } + + private static native Data nativeParseData(String dataAsString); + + @CalledByNative + private static Data buildData(ByteBuffer data, int handlesCount, String errorMessage) { + ByteBuffer copiedData = null; + if (data != null) { + copiedData = ByteBuffer.allocateDirect(data.limit()); + copiedData.order(ByteOrder.LITTLE_ENDIAN); + copiedData.put(data); + copiedData.flip(); + } + return new Data(copiedData, handlesCount, errorMessage); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java new file mode 100644 index 0000000..623abc3 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/ValidationTestUtilTest.java @@ -0,0 +1,151 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.bindings; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Testing {@link ValidationTestUtil}. + */ +public class ValidationTestUtilTest extends MojoTestCase { + + /** + * Check that the input parser is correct on a given input. + */ + public static void checkInputParser( + String input, boolean isInputValid, ByteBuffer expectedData, int expectedHandlesCount) { + ValidationTestUtil.Data data = ValidationTestUtil.parseData(input); + if (isInputValid) { + assertNull(data.getErrorMessage()); + assertEquals(expectedData, data.getData()); + assertEquals(expectedHandlesCount, data.getHandlesCount()); + } else { + assertNotNull(data.getErrorMessage()); + assertNull(data.getData()); + } + } + + /** + * Testing {@link ValidationTestUtil#parseData(String)}. + */ + @SmallTest + public void testCorrectMessageParsing() { + { + // Test empty input. + String input = ""; + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.LITTLE_ENDIAN); + + checkInputParser(input, true, expected, 0); + } + { + // Test input that only consists of comments and whitespaces. + String input = " \t // hello world \n\r \t// the answer is 42 "; + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.nativeOrder()); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[u1]0x10// hello world !! \n\r \t [u2]65535 \n" + + "[u4]65536 [u8]0xFFFFFFFFFFFFFFFF 0 0Xff"; + ByteBuffer expected = ByteBuffer.allocateDirect(17); + expected.order(ByteOrder.nativeOrder()); + expected.put((byte) 0x10); + expected.putShort((short) 65535); + expected.putInt(65536); + expected.putLong(-1); + expected.put((byte) 0); + expected.put((byte) 0xff); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40"; + ByteBuffer expected = ByteBuffer.allocateDirect(15); + expected.order(ByteOrder.nativeOrder()); + expected.putLong(-0x800); + expected.put((byte) -128); + expected.putShort((short) 0); + expected.putInt(-40); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[b]00001011 [b]10000000 // hello world\r [b]00000000"; + ByteBuffer expected = ByteBuffer.allocateDirect(3); + expected.order(ByteOrder.nativeOrder()); + expected.put((byte) 11); + expected.put((byte) 128); + expected.put((byte) 0); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[f]+.3e9 [d]-10.03"; + ByteBuffer expected = ByteBuffer.allocateDirect(12); + expected.order(ByteOrder.nativeOrder()); + expected.putFloat(+.3e9f); + expected.putDouble(-10.03); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar"; + ByteBuffer expected = ByteBuffer.allocateDirect(14); + expected.order(ByteOrder.nativeOrder()); + expected.putInt(14); + expected.put((byte) 0); + expected.putLong(9); + expected.put((byte) 0); + expected.flip(); + + checkInputParser(input, true, expected, 0); + } + { + String input = "// This message has handles! \n[handles]50 [u8]2"; + ByteBuffer expected = ByteBuffer.allocateDirect(8); + expected.order(ByteOrder.nativeOrder()); + expected.putLong(2); + expected.flip(); + + checkInputParser(input, true, expected, 50); + } + + // Test some failure cases. + { + String error_inputs[] = { + "/ hello world", + "[u1]x", + "[u2]-1000", + "[u1]0x100", + "[s2]-0x8001", + "[b]1", + "[b]1111111k", + "[dist4]unmatched", + "[anchr]hello [dist8]hello", + "[dist4]a [dist4]a [anchr]a", + "[dist4]a [anchr]a [dist4]a [anchr]a", + "0 [handles]50" + }; + + for (String input : error_inputs) { + ByteBuffer expected = ByteBuffer.allocateDirect(0); + expected.order(ByteOrder.nativeOrder()); + checkInputParser(input, false, expected, 0); + } + } + + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java b/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.java new file mode 100644 index 0000000..8fb79d7 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/bindings/test/mojom/mojo/IntegrationTestInterfaceTestHelper.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.test.mojom.mojo; + +import org.chromium.mojo.bindings.MessageReceiver; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface.Method0Response; +import org.chromium.mojo.bindings.test.mojom.mojo.IntegrationTestInterface_Internal.IntegrationTestInterfaceMethod0ResponseParamsForwardToCallback; + +/** + * Helper class to access {@link IntegrationTestInterface_Internal} package protected method for + * tests. + */ +public class IntegrationTestInterfaceTestHelper { + + private static final class SinkMethod0Response implements Method0Response { + @Override + public void call(byte[] arg1) { + } + } + + /** + * Creates a new {@link MessageReceiver} to use for the callback of + * |IntegrationTestInterface#method0(Method0Response)|. + */ + public static MessageReceiver newIntegrationTestInterfaceMethodCallback() { + return new IntegrationTestInterfaceMethod0ResponseParamsForwardToCallback( + new SinkMethod0Response()); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java new file mode 100644 index 0000000..5120198 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/CoreImplTest.java @@ -0,0 +1,545 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignals; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.SharedBufferHandle; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +/** + * Testing the core API. + */ +public class CoreImplTest extends MojoTestCase { + private static final long RUN_LOOP_TIMEOUT_MS = 5; + + private static final ScheduledExecutorService WORKER = + Executors.newSingleThreadScheduledExecutor(); + + private static final HandleSignals ALL_SIGNALS = + HandleSignals.none().setPeerClosed(true).setReadable(true).setWritable(true); + + private List mHandlesToClose = new ArrayList(); + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + MojoException toThrow = null; + for (Handle handle : mHandlesToClose) { + try { + handle.close(); + } catch (MojoException e) { + if (toThrow == null) { + toThrow = e; + } + } + } + if (toThrow != null) { + throw toThrow; + } + super.tearDown(); + } + + private void addHandleToClose(Handle handle) { + mHandlesToClose.add(handle); + } + + private void addHandlePairToClose(Pair handles) { + mHandlesToClose.add(handles.first); + mHandlesToClose.add(handles.second); + } + + private static void checkSendingMessage(MessagePipeHandle in, MessagePipeHandle out) { + Random random = new Random(); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + in.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE); + + // Try to read into a small buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length / 2); + ResultAnd result = + out.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + + // Read into a correct buffer. + receiveBuffer = ByteBuffer.allocateDirect(bytes.length); + result = out.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + assertEquals(0, receiveBuffer.position()); + assertEquals(result.getValue().getMessageSize(), receiveBuffer.limit()); + byte[] receivedBytes = new byte[result.getValue().getMessageSize()]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + } + + private static void checkSendingData(DataPipe.ProducerHandle in, DataPipe.ConsumerHandle out) { + Random random = new Random(); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + ResultAnd result = in.writeData(buffer, DataPipe.WriteFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().intValue()); + + // Query number of bytes available. + ResultAnd readResult = out.readData(null, DataPipe.ReadFlags.none().query(true)); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + + // Peek data into a buffer. + ByteBuffer peekBuffer = ByteBuffer.allocateDirect(bytes.length); + readResult = out.readData(peekBuffer, DataPipe.ReadFlags.none().peek(true)); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + assertEquals(bytes.length, peekBuffer.limit()); + byte[] peekBytes = new byte[bytes.length]; + peekBuffer.get(peekBytes); + assertTrue(Arrays.equals(bytes, peekBytes)); + + // Read into a buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length); + readResult = out.readData(receiveBuffer, DataPipe.ReadFlags.NONE); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length, readResult.getValue().intValue()); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + } + + private static void checkSharing(SharedBufferHandle in, SharedBufferHandle out) { + Random random = new Random(); + + ByteBuffer buffer1 = in.map(0, 8, SharedBufferHandle.MapFlags.NONE); + assertEquals(8, buffer1.capacity()); + ByteBuffer buffer2 = out.map(0, 8, SharedBufferHandle.MapFlags.NONE); + assertEquals(8, buffer2.capacity()); + + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + buffer1.put(bytes); + + byte[] receivedBytes = new byte[bytes.length]; + buffer2.get(receivedBytes); + + assertTrue(Arrays.equals(bytes, receivedBytes)); + + in.unmap(buffer1); + out.unmap(buffer2); + } + + /** + * Testing that Core can be retrieved from a handle. + */ + @SmallTest + public void testGetCore() { + Core core = CoreImpl.getInstance(); + + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + assertEquals(core, handles.first.getCore()); + assertEquals(core, handles.second.getCore()); + + handles = core.createDataPipe(null); + addHandlePairToClose(handles); + assertEquals(core, handles.first.getCore()); + assertEquals(core, handles.second.getCore()); + + SharedBufferHandle handle = core.createSharedBuffer(null, 100); + SharedBufferHandle handle2 = handle.duplicate(null); + addHandleToClose(handle); + addHandleToClose(handle2); + assertEquals(core, handle.getCore()); + assertEquals(core, handle2.getCore()); + } + + private static void createAndCloseMessagePipe(MessagePipeHandle.CreateOptions options) { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(options); + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link MessagePipeHandle} creation. + */ + @SmallTest + public void testMessagePipeCreation() { + // Test creation with null options. + createAndCloseMessagePipe(null); + // Test creation with default options. + createAndCloseMessagePipe(new MessagePipeHandle.CreateOptions()); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeEmpty() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + // Testing read on an empty pipe. + ResultAnd readResult = + handles.first.readMessage(null, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.SHOULD_WAIT, readResult.getMojoResult()); + + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeSend() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + checkSendingMessage(handles.first, handles.second); + checkSendingMessage(handles.second, handles.first); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeReceiveOnSmallBuffer() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + handles.first.writeMessage(buffer, null, MessagePipeHandle.WriteFlags.NONE); + + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(1); + ResultAnd result = + handles.second.readMessage(receiveBuffer, 0, MessagePipeHandle.ReadFlags.NONE); + assertEquals(MojoResult.RESOURCE_EXHAUSTED, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().getMessageSize()); + assertEquals(0, result.getValue().getHandlesCount()); + } + + /** + * Testing {@link MessagePipeHandle}. + */ + @SmallTest + public void testMessagePipeSendHandles() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + Pair handlesToShare = core.createMessagePipe(null); + addHandlePairToClose(handles); + addHandlePairToClose(handlesToShare); + + handles.first.writeMessage(null, Collections.singletonList(handlesToShare.second), + MessagePipeHandle.WriteFlags.NONE); + assertFalse(handlesToShare.second.isValid()); + ResultAnd readMessageResult = + handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE); + assertEquals(1, readMessageResult.getValue().getHandlesCount()); + MessagePipeHandle newHandle = + readMessageResult.getValue().getHandles().get(0).toMessagePipeHandle(); + addHandleToClose(newHandle); + assertTrue(newHandle.isValid()); + checkSendingMessage(handlesToShare.first, newHandle); + checkSendingMessage(newHandle, handlesToShare.first); + } + + private static void createAndCloseDataPipe(DataPipe.CreateOptions options) { + Core core = CoreImpl.getInstance(); + Pair handles = + core.createDataPipe(options); + handles.first.close(); + handles.second.close(); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeCreation() { + // Create datapipe with null options. + createAndCloseDataPipe(null); + DataPipe.CreateOptions options = new DataPipe.CreateOptions(); + // Create datapipe with element size set. + options.setElementNumBytes(24); + createAndCloseDataPipe(options); + // Create datapipe with capacity set. + options.setCapacityNumBytes(1024 * options.getElementNumBytes()); + createAndCloseDataPipe(options); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeSend() { + Core core = CoreImpl.getInstance(); + + Pair handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + checkSendingData(handles.first, handles.second); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeTwoPhaseSend() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = handles.first.beginWriteData(bytes.length, DataPipe.WriteFlags.NONE); + assertTrue(buffer.capacity() >= bytes.length); + buffer.put(bytes); + handles.first.endWriteData(bytes.length); + + // Read into a buffer. + ByteBuffer receiveBuffer = + handles.second.beginReadData(bytes.length, DataPipe.ReadFlags.NONE); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals(bytes, receivedBytes)); + handles.second.endReadData(bytes.length); + } + + /** + * Testing {@link DataPipe}. + */ + @SmallTest + public void testDataPipeDiscard() { + Random random = new Random(); + Core core = CoreImpl.getInstance(); + Pair handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + // Writing a random 8 bytes message. + byte[] bytes = new byte[8]; + random.nextBytes(bytes); + ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length); + buffer.put(bytes); + ResultAnd result = handles.first.writeData(buffer, DataPipe.WriteFlags.NONE); + assertEquals(MojoResult.OK, result.getMojoResult()); + assertEquals(bytes.length, result.getValue().intValue()); + + // Discard bytes. + final int nbBytesToDiscard = 4; + assertEquals(nbBytesToDiscard, + handles.second.discardData(nbBytesToDiscard, DataPipe.ReadFlags.NONE)); + + // Read into a buffer. + ByteBuffer receiveBuffer = ByteBuffer.allocateDirect(bytes.length - nbBytesToDiscard); + ResultAnd readResult = + handles.second.readData(receiveBuffer, DataPipe.ReadFlags.NONE); + assertEquals(MojoResult.OK, readResult.getMojoResult()); + assertEquals(bytes.length - nbBytesToDiscard, readResult.getValue().intValue()); + assertEquals(0, receiveBuffer.position()); + assertEquals(bytes.length - nbBytesToDiscard, receiveBuffer.limit()); + byte[] receivedBytes = new byte[bytes.length - nbBytesToDiscard]; + receiveBuffer.get(receivedBytes); + assertTrue(Arrays.equals( + Arrays.copyOfRange(bytes, nbBytesToDiscard, bytes.length), receivedBytes)); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferCreation() { + Core core = CoreImpl.getInstance(); + // Test creation with empty options. + core.createSharedBuffer(null, 8).close(); + // Test creation with default options. + core.createSharedBuffer(new SharedBufferHandle.CreateOptions(), 8).close(); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferDuplication() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + + // Test duplication with empty options. + handle.duplicate(null).close(); + // Test creation with default options. + handle.duplicate(new SharedBufferHandle.DuplicateOptions()).close(); + } + + /** + * Testing {@link SharedBufferHandle}. + */ + @SmallTest + public void testSharedBufferSending() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + SharedBufferHandle newHandle = handle.duplicate(null); + addHandleToClose(newHandle); + + checkSharing(handle, newHandle); + checkSharing(newHandle, handle); + } + + /** + * Testing that invalid handle can be used with this implementation. + */ + @SmallTest + public void testInvalidHandle() { + Core core = CoreImpl.getInstance(); + Handle handle = InvalidHandle.INSTANCE; + + // Checking sending an invalid handle. + // Until the behavior is changed on the C++ side, handle gracefully 2 different use case: + // - Receive a INVALID_ARGUMENT exception + // - Receive an invalid handle on the other side. + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + try { + handles.first.writeMessage(null, Collections.singletonList(handle), + MessagePipeHandle.WriteFlags.NONE); + ResultAnd readMessageResult = + handles.second.readMessage(null, 1, MessagePipeHandle.ReadFlags.NONE); + assertEquals(1, readMessageResult.getValue().getHandlesCount()); + assertFalse(readMessageResult.getValue().getHandles().get(0).isValid()); + } catch (MojoException e) { + assertEquals(MojoResult.INVALID_ARGUMENT, e.getMojoResult()); + } + } + + /** + * Testing the pass method on message pipes. + */ + @SmallTest + public void testMessagePipeHandlePass() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + assertTrue(handles.first.isValid()); + MessagePipeHandle handleClone = handles.first.pass(); + + addHandleToClose(handleClone); + + assertFalse(handles.first.isValid()); + assertTrue(handleClone.isValid()); + checkSendingMessage(handleClone, handles.second); + checkSendingMessage(handles.second, handleClone); + } + + /** + * Testing the pass method on data pipes. + */ + @SmallTest + public void testDataPipeHandlePass() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createDataPipe(null); + addHandlePairToClose(handles); + + DataPipe.ProducerHandle producerClone = handles.first.pass(); + DataPipe.ConsumerHandle consumerClone = handles.second.pass(); + + addHandleToClose(producerClone); + addHandleToClose(consumerClone); + + assertFalse(handles.first.isValid()); + assertFalse(handles.second.isValid()); + assertTrue(producerClone.isValid()); + assertTrue(consumerClone.isValid()); + checkSendingData(producerClone, consumerClone); + } + + /** + * Testing the pass method on shared buffers. + */ + @SmallTest + public void testSharedBufferPass() { + Core core = CoreImpl.getInstance(); + SharedBufferHandle handle = core.createSharedBuffer(null, 8); + addHandleToClose(handle); + SharedBufferHandle newHandle = handle.duplicate(null); + addHandleToClose(newHandle); + + SharedBufferHandle handleClone = handle.pass(); + SharedBufferHandle newHandleClone = newHandle.pass(); + + addHandleToClose(handleClone); + addHandleToClose(newHandleClone); + + assertFalse(handle.isValid()); + assertTrue(handleClone.isValid()); + checkSharing(handleClone, newHandleClone); + checkSharing(newHandleClone, handleClone); + } + + /** + * esting handle conversion to native and back. + */ + @SmallTest + public void testHandleConversion() { + Core core = CoreImpl.getInstance(); + Pair handles = core.createMessagePipe(null); + addHandlePairToClose(handles); + + MessagePipeHandle converted = + core.acquireNativeHandle(handles.first.releaseNativeHandle()).toMessagePipeHandle(); + addHandleToClose(converted); + + assertFalse(handles.first.isValid()); + + checkSendingMessage(converted, handles.second); + checkSendingMessage(handles.second, converted); + } +} diff --git a/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java new file mode 100644 index 0000000..e14adb1 --- /dev/null +++ b/mojo/android/javatests/src/org/chromium/mojo/system/impl/WatcherImplTest.java @@ -0,0 +1,255 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import android.support.test.filters.SmallTest; + +import org.chromium.mojo.MojoTestCase; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.InvalidHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.Watcher; +import org.chromium.mojo.system.Watcher.Callback; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/** + * Testing the Watcher. + */ +public class WatcherImplTest extends MojoTestCase { + private List mHandlesToClose = new ArrayList(); + private Watcher mWatcher; + private Core mCore; + + /** + * @see MojoTestCase#setUp() + */ + @Override + protected void setUp() throws Exception { + super.setUp(); + mWatcher = new WatcherImpl(); + mCore = CoreImpl.getInstance(); + } + + /** + * @see MojoTestCase#tearDown() + */ + @Override + protected void tearDown() throws Exception { + mWatcher.destroy(); + MojoException toThrow = null; + for (Handle handle : mHandlesToClose) { + try { + handle.close(); + } catch (MojoException e) { + if (toThrow == null) { + toThrow = e; + } + } + } + if (toThrow != null) { + throw toThrow; + } + super.tearDown(); + } + + private void addHandlePairToClose(Pair handles) { + mHandlesToClose.add(handles.first); + mHandlesToClose.add(handles.second); + } + + private static class WatcherResult implements Callback { + private int mResult = Integer.MIN_VALUE; + private MessagePipeHandle mReadPipe; + + /** + * @param readPipe A MessagePipeHandle to read from when onResult triggers success. + */ + public WatcherResult(MessagePipeHandle readPipe) { + mReadPipe = readPipe; + } + public WatcherResult() { + this(null); + } + + /** + * @see Callback#onResult(int) + */ + @Override + public void onResult(int result) { + this.mResult = result; + + if (result == MojoResult.OK && mReadPipe != null) { + mReadPipe.readMessage( + null, 0, MessagePipeHandle.ReadFlags.none().setMayDiscard(true)); + } + } + + /** + * @return the result + */ + public int getResult() { + return mResult; + } + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testCorrectResult() { + // Checking a correct result. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + final WatcherResult watcherResult = new WatcherResult(handles.first); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.writeMessage( + ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertEquals(MojoResult.OK, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testClosingPeerHandle() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.close(); + runLoopUntilIdle(); + assertEquals(MojoResult.FAILED_PRECONDITION, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testClosingWatchedHandle() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.first.close(); + runLoopUntilIdle(); + assertEquals(MojoResult.CANCELLED, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testInvalidHandle() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.first.close(); + assertEquals(MojoResult.INVALID_ARGUMENT, + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult)); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testDefaultInvalidHandle() { + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + assertEquals(MojoResult.INVALID_ARGUMENT, + mWatcher.start(InvalidHandle.INSTANCE, Core.HandleSignals.READABLE, watcherResult)); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testCancel() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.cancel(); + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + handles.second.writeMessage( + ByteBuffer.allocateDirect(1), null, MessagePipeHandle.WriteFlags.NONE); + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } + + /** + * Testing {@link Watcher} implementation. + */ + @SmallTest + public void testImmediateCancelOnInvalidHandle() { + // Closing the peer handle. + Pair handles = mCore.createMessagePipe(null); + addHandlePairToClose(handles); + + final WatcherResult watcherResult = new WatcherResult(); + handles.first.close(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + + mWatcher.start(handles.first, Core.HandleSignals.READABLE, watcherResult); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + mWatcher.cancel(); + + runLoopUntilIdle(); + assertEquals(Integer.MIN_VALUE, watcherResult.getResult()); + } +} diff --git a/mojo/android/javatests/validation_test_util.cc b/mojo/android/javatests/validation_test_util.cc new file mode 100644 index 0000000..75f79b3 --- /dev/null +++ b/mojo/android/javatests/validation_test_util.cc @@ -0,0 +1,54 @@ +// Copyright 2014 The Chromium Authors. 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/android/javatests/validation_test_util.h" + +#include +#include + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/android/scoped_java_ref.h" +#include "base/test/test_support_android.h" +#include "jni/ValidationTestUtil_jni.h" +#include "mojo/public/cpp/bindings/tests/validation_test_input_parser.h" + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +namespace mojo { +namespace android { + +bool RegisterValidationTestUtil(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +ScopedJavaLocalRef ParseData( + JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& data_as_string) { + std::string input = + base::android::ConvertJavaStringToUTF8(env, data_as_string); + std::vector data; + size_t num_handles; + std::string error_message; + if (!test::ParseValidationTestInput( + input, &data, &num_handles, &error_message)) { + ScopedJavaLocalRef j_error_message = + base::android::ConvertUTF8ToJavaString(env, error_message); + return Java_ValidationTestUtil_buildData(env, nullptr, 0, j_error_message); + } + void* data_ptr = &data[0]; + if (!data_ptr) { + DCHECK(!data.size()); + data_ptr = &data; + } + jobject byte_buffer = + env->NewDirectByteBuffer(data_ptr, data.size()); + return Java_ValidationTestUtil_buildData(env, byte_buffer, num_handles, + nullptr); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/javatests/validation_test_util.h b/mojo/android/javatests/validation_test_util.h new file mode 100644 index 0000000..f58dc07 --- /dev/null +++ b/mojo/android/javatests/validation_test_util.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. 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_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ +#define MOJO_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterValidationTestUtil(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_SYSTEM_ANDROID_JAVATESTS_VALIDATION_TEST_UTIL_H_ diff --git a/mojo/android/system/base_run_loop.cc b/mojo/android/system/base_run_loop.cc new file mode 100644 index 0000000..7993ba8 --- /dev/null +++ b/mojo/android/system/base_run_loop.cc @@ -0,0 +1,82 @@ +// 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/android/system/base_run_loop.h" + +#include + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +#include "base/bind.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/single_thread_task_runner.h" +#include "jni/BaseRunLoop_jni.h" + +using base::android::JavaParamRef; + +namespace mojo { +namespace android { + +static jlong CreateBaseRunLoop(JNIEnv* env, + const JavaParamRef& jcaller) { + base::MessageLoop* message_loop = new base::MessageLoop; + return reinterpret_cast(message_loop); +} + +static void Run(JNIEnv* env, + const JavaParamRef& jcaller) { + base::RunLoop().Run(); +} + +static void RunUntilIdle(JNIEnv* env, + const JavaParamRef& jcaller) { + base::RunLoop().RunUntilIdle(); +} + +static void Quit(JNIEnv* env, + const JavaParamRef& jcaller, + jlong runLoopID) { + reinterpret_cast(runLoopID)->QuitWhenIdle(); +} + +static void RunJavaRunnable( + const base::android::ScopedJavaGlobalRef& runnable_ref) { + Java_BaseRunLoop_runRunnable(base::android::AttachCurrentThread(), + runnable_ref); +} + +static void PostDelayedTask(JNIEnv* env, + const JavaParamRef& jcaller, + jlong runLoopID, + const JavaParamRef& runnable, + jlong delay) { + base::android::ScopedJavaGlobalRef runnable_ref; + // ScopedJavaGlobalRef do not hold onto the env reference, so it is safe to + // use it across threads. |RunJavaRunnable| will acquire a new JNIEnv before + // running the Runnable. + runnable_ref.Reset(env, runnable); + reinterpret_cast(runLoopID) + ->task_runner() + ->PostDelayedTask(FROM_HERE, base::Bind(&RunJavaRunnable, runnable_ref), + base::TimeDelta::FromMicroseconds(delay)); +} + +static void DeleteMessageLoop(JNIEnv* env, + const JavaParamRef& jcaller, + jlong runLoopID) { + base::MessageLoop* message_loop = + reinterpret_cast(runLoopID); + delete message_loop; +} + +bool RegisterBaseRunLoop(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/base_run_loop.h b/mojo/android/system/base_run_loop.h new file mode 100644 index 0000000..f225c65 --- /dev/null +++ b/mojo/android/system/base_run_loop.h @@ -0,0 +1,20 @@ +// 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_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ +#define MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterBaseRunLoop(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_BASE_RUN_LOOP_H_ diff --git a/mojo/android/system/core_impl.cc b/mojo/android/system/core_impl.cc new file mode 100644 index 0000000..7d5a402 --- /dev/null +++ b/mojo/android/system/core_impl.cc @@ -0,0 +1,310 @@ +// Copyright 2014 The Chromium Authors. 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/android/system/core_impl.h" + +#include +#include + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +// #include "base/android/library_loader/library_loader_hooks.h" +#include "base/android/scoped_java_ref.h" +#include "jni/CoreImpl_jni.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { +namespace android { + +using base::android::JavaParamRef; +using base::android::ScopedJavaLocalRef; + +static jlong GetTimeTicksNow(JNIEnv* env, + const JavaParamRef& jcaller) { + return MojoGetTimeTicksNow(); +} + +static ScopedJavaLocalRef CreateMessagePipe( + JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& options_buffer) { + const MojoCreateMessagePipeOptions* options = NULL; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateMessagePipeOptions)); + options = static_cast(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle1; + MojoHandle handle2; + MojoResult result = MojoCreateMessagePipe(options, &handle1, &handle2); + return Java_CoreImpl_newNativeCreationResult(env, result, handle1, handle2); +} + +static ScopedJavaLocalRef CreateDataPipe( + JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& options_buffer) { + const MojoCreateDataPipeOptions* options = NULL; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateDataPipeOptions)); + options = static_cast(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle1; + MojoHandle handle2; + MojoResult result = MojoCreateDataPipe(options, &handle1, &handle2); + return Java_CoreImpl_newNativeCreationResult(env, result, handle1, handle2); +} + +static ScopedJavaLocalRef CreateSharedBuffer( + JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& options_buffer, + jlong num_bytes) { + const MojoCreateSharedBufferOptions* options = 0; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + DCHECK_EQ(reinterpret_cast(buffer_start) % 8, 0u); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoCreateSharedBufferOptions)); + options = static_cast(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle; + MojoResult result = MojoCreateSharedBuffer(options, num_bytes, &handle); + return Java_CoreImpl_newResultAndInteger(env, result, handle); +} + +static jint Close(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle) { + return MojoClose(mojo_handle); +} + +static jint QueryHandleSignalsState(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& buffer) { + MojoHandleSignalsState* signals_state = + static_cast(env->GetDirectBufferAddress(buffer)); + DCHECK(signals_state); + DCHECK_EQ(sizeof(MojoHandleSignalsState), + static_cast(env->GetDirectBufferCapacity(buffer))); + return MojoQueryHandleSignalsState(mojo_handle, signals_state); +} + +static jint WriteMessage(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& bytes, + jint num_bytes, + const JavaParamRef& handles_buffer, + jint flags) { + const void* buffer_start = 0; + uint32_t buffer_size = 0; + if (bytes) { + buffer_start = env->GetDirectBufferAddress(bytes); + DCHECK(buffer_start); + DCHECK(env->GetDirectBufferCapacity(bytes) >= num_bytes); + buffer_size = num_bytes; + } + const MojoHandle* handles = 0; + uint32_t num_handles = 0; + if (handles_buffer) { + handles = + static_cast(env->GetDirectBufferAddress(handles_buffer)); + num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4; + } + // Java code will handle invalidating handles if the write succeeded. + return MojoWriteMessage( + mojo_handle, buffer_start, buffer_size, handles, num_handles, flags); +} + +static ScopedJavaLocalRef ReadMessage( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& bytes, + const JavaParamRef& handles_buffer, + jint flags) { + void* buffer_start = 0; + uint32_t buffer_size = 0; + if (bytes) { + buffer_start = env->GetDirectBufferAddress(bytes); + DCHECK(buffer_start); + buffer_size = env->GetDirectBufferCapacity(bytes); + } + MojoHandle* handles = 0; + uint32_t num_handles = 0; + if (handles_buffer) { + handles = + static_cast(env->GetDirectBufferAddress(handles_buffer)); + num_handles = env->GetDirectBufferCapacity(handles_buffer) / 4; + } + MojoResult result = MojoReadMessage( + mojo_handle, buffer_start, &buffer_size, handles, &num_handles, flags); + // Jave code will handle taking ownership of any received handle. + return Java_CoreImpl_newReadMessageResult(env, result, buffer_size, + num_handles); +} + +static ScopedJavaLocalRef ReadData( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& elements, + jint elements_capacity, + jint flags) { + void* buffer_start = 0; + uint32_t buffer_size = elements_capacity; + if (elements) { + buffer_start = env->GetDirectBufferAddress(elements); + DCHECK(buffer_start); + DCHECK(elements_capacity <= env->GetDirectBufferCapacity(elements)); + } + MojoResult result = + MojoReadData(mojo_handle, buffer_start, &buffer_size, flags); + return Java_CoreImpl_newResultAndInteger( + env, result, (result == MOJO_RESULT_OK) ? buffer_size : 0); +} + +static ScopedJavaLocalRef BeginReadData( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint num_bytes, + jint flags) { + void const* buffer = 0; + uint32_t buffer_size = num_bytes; + MojoResult result = + MojoBeginReadData(mojo_handle, &buffer, &buffer_size, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = + env->NewDirectByteBuffer(const_cast(buffer), buffer_size); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static jint EndReadData(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint num_bytes_read) { + return MojoEndReadData(mojo_handle, num_bytes_read); +} + +static ScopedJavaLocalRef WriteData( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& elements, + jint limit, + jint flags) { + void* buffer_start = env->GetDirectBufferAddress(elements); + DCHECK(buffer_start); + DCHECK(limit <= env->GetDirectBufferCapacity(elements)); + uint32_t buffer_size = limit; + MojoResult result = + MojoWriteData(mojo_handle, buffer_start, &buffer_size, flags); + return Java_CoreImpl_newResultAndInteger( + env, result, (result == MOJO_RESULT_OK) ? buffer_size : 0); +} + +static ScopedJavaLocalRef BeginWriteData( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint num_bytes, + jint flags) { + void* buffer = 0; + uint32_t buffer_size = num_bytes; + MojoResult result = + MojoBeginWriteData(mojo_handle, &buffer, &buffer_size, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = env->NewDirectByteBuffer(buffer, buffer_size); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static jint EndWriteData(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint num_bytes_written) { + return MojoEndWriteData(mojo_handle, num_bytes_written); +} + +static ScopedJavaLocalRef Duplicate( + JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + const JavaParamRef& options_buffer) { + const MojoDuplicateBufferHandleOptions* options = 0; + if (options_buffer) { + const void* buffer_start = env->GetDirectBufferAddress(options_buffer); + DCHECK(buffer_start); + const size_t buffer_size = env->GetDirectBufferCapacity(options_buffer); + DCHECK_EQ(buffer_size, sizeof(MojoDuplicateBufferHandleOptions)); + options = + static_cast(buffer_start); + DCHECK_EQ(options->struct_size, buffer_size); + } + MojoHandle handle; + MojoResult result = MojoDuplicateBufferHandle(mojo_handle, options, &handle); + return Java_CoreImpl_newResultAndInteger(env, result, handle); +} + +static ScopedJavaLocalRef Map(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jlong offset, + jlong num_bytes, + jint flags) { + void* buffer = 0; + MojoResult result = + MojoMapBuffer(mojo_handle, offset, num_bytes, &buffer, flags); + jobject byte_buffer = 0; + if (result == MOJO_RESULT_OK) { + byte_buffer = env->NewDirectByteBuffer(buffer, num_bytes); + } + return Java_CoreImpl_newResultAndBuffer(env, result, byte_buffer); +} + +static int Unmap(JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& buffer) { + void* buffer_start = env->GetDirectBufferAddress(buffer); + DCHECK(buffer_start); + return MojoUnmapBuffer(buffer_start); +} + +static jint GetNativeBufferOffset(JNIEnv* env, + const JavaParamRef& jcaller, + const JavaParamRef& buffer, + jint alignment) { + jint offset = + reinterpret_cast(env->GetDirectBufferAddress(buffer)) % + alignment; + if (offset == 0) + return 0; + return alignment - offset; +} + +bool RegisterCoreImpl(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/core_impl.h b/mojo/android/system/core_impl.h new file mode 100644 index 0000000..c624999 --- /dev/null +++ b/mojo/android/system/core_impl.h @@ -0,0 +1,20 @@ +// Copyright 2014 The Chromium Authors. 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_ANDROID_SYSTEM_CORE_IMPL_H_ +#define MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterCoreImpl(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_CORE_IMPL_H_ diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java b/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java new file mode 100644 index 0000000..3db6670 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/BaseRunLoop.java @@ -0,0 +1,74 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.mojo.system.RunLoop; + +/** + * Implementation of {@link RunLoop} suitable for the base:: message loop implementation. + */ +@JNINamespace("mojo::android") +class BaseRunLoop implements RunLoop { + /** + * Pointer to the C run loop. + */ + private long mRunLoopID; + private final CoreImpl mCore; + + BaseRunLoop(CoreImpl core) { + this.mCore = core; + this.mRunLoopID = nativeCreateBaseRunLoop(); + } + + @Override + public void run() { + assert mRunLoopID != 0 : "The run loop cannot run once closed"; + nativeRun(); + } + + @Override + public void runUntilIdle() { + assert mRunLoopID != 0 : "The run loop cannot run once closed"; + nativeRunUntilIdle(); + } + + @Override + public void quit() { + assert mRunLoopID != 0 : "The run loop cannot be quitted run once closed"; + nativeQuit(mRunLoopID); + } + + @Override + public void postDelayedTask(Runnable runnable, long delay) { + assert mRunLoopID != 0 : "The run loop cannot run tasks once closed"; + nativePostDelayedTask(mRunLoopID, runnable, delay); + } + + @Override + public void close() { + if (mRunLoopID == 0) { + return; + } + // We don't want to de-register a different run loop! + assert mCore.getCurrentRunLoop() == this : "Only the current run loop can be closed"; + mCore.clearCurrentRunLoop(); + nativeDeleteMessageLoop(mRunLoopID); + mRunLoopID = 0; + } + + @CalledByNative + private static void runRunnable(Runnable runnable) { + runnable.run(); + } + + private native long nativeCreateBaseRunLoop(); + private native void nativeRun(); + private native void nativeRunUntilIdle(); + private native void nativeQuit(long runLoopID); + private native void nativePostDelayedTask(long runLoopID, Runnable runnable, long delay); + private native void nativeDeleteMessageLoop(long runLoopID); +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java new file mode 100644 index 0000000..173f801 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/CoreImpl.java @@ -0,0 +1,522 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.MainDex; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.DataPipe; +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.MojoException; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Pair; +import org.chromium.mojo.system.ResultAnd; +import org.chromium.mojo.system.RunLoop; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.SharedBufferHandle.DuplicateOptions; +import org.chromium.mojo.system.SharedBufferHandle.MapFlags; +import org.chromium.mojo.system.UntypedHandle; +import org.chromium.mojo.system.Watcher; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * Implementation of {@link Core}. + */ +@JNINamespace("mojo::android") +@MainDex +public class CoreImpl implements Core { + /** + * Discard flag for the |MojoReadData| operation. + */ + private static final int MOJO_READ_DATA_FLAG_DISCARD = 1 << 1; + + /** + * the size of a handle, in bytes. + */ + private static final int HANDLE_SIZE = 4; + + /** + * the size of a flag, in bytes. + */ + private static final int FLAG_SIZE = 4; + + /** + * The mojo handle for an invalid handle. + */ + static final int INVALID_HANDLE = 0; + + private static class LazyHolder { private static final Core INSTANCE = new CoreImpl(); } + + /** + * The run loop for the current thread. + */ + private final ThreadLocal mCurrentRunLoop = new ThreadLocal(); + + /** + * The offset needed to get an aligned buffer. + */ + private final int mByteBufferOffset; + + /** + * @return the instance. + */ + public static Core getInstance() { + return LazyHolder.INSTANCE; + } + + private CoreImpl() { + // Fix for the ART runtime, before: + // https://android.googlesource.com/platform/libcore/+/fb6c80875a8a8d0a9628562f89c250b6a962e824%5E!/ + // This assumes consistent allocation. + mByteBufferOffset = nativeGetNativeBufferOffset(ByteBuffer.allocateDirect(8), 8); + } + + /** + * @see Core#getTimeTicksNow() + */ + @Override + public long getTimeTicksNow() { + return nativeGetTimeTicksNow(); + } + + /** + * @see Core#createMessagePipe(MessagePipeHandle.CreateOptions) + */ + @Override + public Pair createMessagePipe( + MessagePipeHandle.CreateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd result = nativeCreateMessagePipe(optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return Pair.create( + new MessagePipeHandleImpl(this, result.getValue().first), + new MessagePipeHandleImpl(this, result.getValue().second)); + } + + /** + * @see Core#createDataPipe(DataPipe.CreateOptions) + */ + @Override + public Pair createDataPipe(DataPipe.CreateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(16); + optionsBuffer.putInt(0, 16); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + optionsBuffer.putInt(8, options.getElementNumBytes()); + optionsBuffer.putInt(12, options.getCapacityNumBytes()); + } + ResultAnd result = nativeCreateDataPipe(optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return Pair.create( + new DataPipeProducerHandleImpl(this, result.getValue().first), + new DataPipeConsumerHandleImpl(this, result.getValue().second)); + } + + /** + * @see Core#createSharedBuffer(SharedBufferHandle.CreateOptions, long) + */ + @Override + public SharedBufferHandle createSharedBuffer( + SharedBufferHandle.CreateOptions options, long numBytes) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd result = nativeCreateSharedBuffer(optionsBuffer, numBytes); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return new SharedBufferHandleImpl(this, result.getValue()); + } + + /** + * @see org.chromium.mojo.system.Core#acquireNativeHandle(int) + */ + @Override + public UntypedHandle acquireNativeHandle(int handle) { + return new UntypedHandleImpl(this, handle); + } + + /** + * @see Core#getWatcher() + */ + @Override + public Watcher getWatcher() { + return new WatcherImpl(); + } + + /** + * @see Core#createDefaultRunLoop() + */ + @Override + public RunLoop createDefaultRunLoop() { + if (mCurrentRunLoop.get() != null) { + throw new MojoException(MojoResult.FAILED_PRECONDITION); + } + BaseRunLoop runLoop = new BaseRunLoop(this); + mCurrentRunLoop.set(runLoop); + return runLoop; + } + + /** + * @see Core#getCurrentRunLoop() + */ + @Override + public RunLoop getCurrentRunLoop() { + return mCurrentRunLoop.get(); + } + + /** + * Remove the current run loop. + */ + void clearCurrentRunLoop() { + mCurrentRunLoop.remove(); + } + + int closeWithResult(int mojoHandle) { + return nativeClose(mojoHandle); + } + + void close(int mojoHandle) { + int mojoResult = nativeClose(mojoHandle); + if (mojoResult != MojoResult.OK) { + throw new MojoException(mojoResult); + } + } + + HandleSignalsState queryHandleSignalsState(int mojoHandle) { + ByteBuffer buffer = allocateDirectBuffer(8); + int result = nativeQueryHandleSignalsState(mojoHandle, buffer); + if (result != MojoResult.OK) throw new MojoException(result); + return new HandleSignalsState( + new HandleSignals(buffer.getInt(0)), new HandleSignals(buffer.getInt(4))); + } + + /** + * @see MessagePipeHandle#writeMessage(ByteBuffer, List, MessagePipeHandle.WriteFlags) + */ + void writeMessage(MessagePipeHandleImpl pipeHandle, ByteBuffer bytes, + List handles, MessagePipeHandle.WriteFlags flags) { + ByteBuffer handlesBuffer = null; + if (handles != null && !handles.isEmpty()) { + handlesBuffer = allocateDirectBuffer(handles.size() * HANDLE_SIZE); + for (Handle handle : handles) { + handlesBuffer.putInt(getMojoHandle(handle)); + } + handlesBuffer.position(0); + } + int mojoResult = nativeWriteMessage(pipeHandle.getMojoHandle(), bytes, + bytes == null ? 0 : bytes.limit(), handlesBuffer, flags.getFlags()); + if (mojoResult != MojoResult.OK) { + throw new MojoException(mojoResult); + } + // Success means the handles have been invalidated. + if (handles != null) { + for (Handle handle : handles) { + if (handle.isValid()) { + ((HandleBase) handle).invalidateHandle(); + } + } + } + } + + /** + * @see MessagePipeHandle#readMessage(ByteBuffer, int, MessagePipeHandle.ReadFlags) + */ + ResultAnd readMessage(MessagePipeHandleImpl handle, + ByteBuffer bytes, int maxNumberOfHandles, MessagePipeHandle.ReadFlags flags) { + ByteBuffer handlesBuffer = null; + if (maxNumberOfHandles > 0) { + handlesBuffer = allocateDirectBuffer(maxNumberOfHandles * HANDLE_SIZE); + } + ResultAnd result = + nativeReadMessage(handle.getMojoHandle(), bytes, handlesBuffer, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK + && result.getMojoResult() != MojoResult.RESOURCE_EXHAUSTED + && result.getMojoResult() != MojoResult.SHOULD_WAIT) { + throw new MojoException(result.getMojoResult()); + } + + if (result.getMojoResult() == MojoResult.OK) { + MessagePipeHandle.ReadMessageResult readResult = result.getValue(); + if (bytes != null) { + bytes.position(0); + bytes.limit(readResult.getMessageSize()); + } + + List handles = + new ArrayList(readResult.getHandlesCount()); + for (int i = 0; i < readResult.getHandlesCount(); ++i) { + int mojoHandle = handlesBuffer.getInt(HANDLE_SIZE * i); + handles.add(new UntypedHandleImpl(this, mojoHandle)); + } + readResult.setHandles(handles); + } + return result; + } + + /** + * @see ConsumerHandle#discardData(int, DataPipe.ReadFlags) + */ + int discardData(DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) { + ResultAnd result = nativeReadData(handle.getMojoHandle(), null, numBytes, + flags.getFlags() | MOJO_READ_DATA_FLAG_DISCARD); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see ConsumerHandle#readData(ByteBuffer, DataPipe.ReadFlags) + */ + ResultAnd readData( + DataPipeConsumerHandleImpl handle, ByteBuffer elements, DataPipe.ReadFlags flags) { + ResultAnd result = nativeReadData(handle.getMojoHandle(), elements, + elements == null ? 0 : elements.capacity(), flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK + && result.getMojoResult() != MojoResult.SHOULD_WAIT) { + throw new MojoException(result.getMojoResult()); + } + if (result.getMojoResult() == MojoResult.OK) { + if (elements != null) { + elements.limit(result.getValue()); + } + } + return result; + } + + /** + * @see ConsumerHandle#beginReadData(int, DataPipe.ReadFlags) + */ + ByteBuffer beginReadData( + DataPipeConsumerHandleImpl handle, int numBytes, DataPipe.ReadFlags flags) { + ResultAnd result = + nativeBeginReadData(handle.getMojoHandle(), numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue().asReadOnlyBuffer(); + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + void endReadData(DataPipeConsumerHandleImpl handle, int numBytesRead) { + int result = nativeEndReadData(handle.getMojoHandle(), numBytesRead); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @see ProducerHandle#writeData(ByteBuffer, DataPipe.WriteFlags) + */ + ResultAnd writeData( + DataPipeProducerHandleImpl handle, ByteBuffer elements, DataPipe.WriteFlags flags) { + return nativeWriteData( + handle.getMojoHandle(), elements, elements.limit(), flags.getFlags()); + } + + /** + * @see ProducerHandle#beginWriteData(int, DataPipe.WriteFlags) + */ + ByteBuffer beginWriteData( + DataPipeProducerHandleImpl handle, int numBytes, DataPipe.WriteFlags flags) { + ResultAnd result = + nativeBeginWriteData(handle.getMojoHandle(), numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + void endWriteData(DataPipeProducerHandleImpl handle, int numBytesWritten) { + int result = nativeEndWriteData(handle.getMojoHandle(), numBytesWritten); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @see SharedBufferHandle#duplicate(DuplicateOptions) + */ + SharedBufferHandle duplicate(SharedBufferHandleImpl handle, DuplicateOptions options) { + ByteBuffer optionsBuffer = null; + if (options != null) { + optionsBuffer = allocateDirectBuffer(8); + optionsBuffer.putInt(0, 8); + optionsBuffer.putInt(4, options.getFlags().getFlags()); + } + ResultAnd result = nativeDuplicate(handle.getMojoHandle(), optionsBuffer); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return new SharedBufferHandleImpl(this, result.getValue()); + } + + /** + * @see SharedBufferHandle#map(long, long, MapFlags) + */ + ByteBuffer map(SharedBufferHandleImpl handle, long offset, long numBytes, MapFlags flags) { + ResultAnd result = + nativeMap(handle.getMojoHandle(), offset, numBytes, flags.getFlags()); + if (result.getMojoResult() != MojoResult.OK) { + throw new MojoException(result.getMojoResult()); + } + return result.getValue(); + } + + /** + * @see SharedBufferHandle#unmap(ByteBuffer) + */ + void unmap(ByteBuffer buffer) { + int result = nativeUnmap(buffer); + if (result != MojoResult.OK) { + throw new MojoException(result); + } + } + + /** + * @return the mojo handle associated to the given handle, considering invalid handles. + */ + private int getMojoHandle(Handle handle) { + if (handle.isValid()) { + return ((HandleBase) handle).getMojoHandle(); + } + return 0; + } + + private static boolean isUnrecoverableError(int code) { + switch (code) { + case MojoResult.OK: + case MojoResult.DEADLINE_EXCEEDED: + case MojoResult.CANCELLED: + case MojoResult.FAILED_PRECONDITION: + return false; + default: + return true; + } + } + + private static int filterMojoResultForWait(int code) { + if (isUnrecoverableError(code)) { + throw new MojoException(code); + } + return code; + } + + private ByteBuffer allocateDirectBuffer(int capacity) { + ByteBuffer buffer = ByteBuffer.allocateDirect(capacity + mByteBufferOffset); + if (mByteBufferOffset != 0) { + buffer.position(mByteBufferOffset); + buffer = buffer.slice(); + } + return buffer.order(ByteOrder.nativeOrder()); + } + + @CalledByNative + private static ResultAnd newResultAndBuffer(int mojoResult, ByteBuffer buffer) { + return new ResultAnd<>(mojoResult, buffer); + } + + /** + * Trivial alias for Pair. This is needed because our jni generator is unable + * to handle class that contains space. + */ + private static final class IntegerPair extends Pair { + public IntegerPair(Integer first, Integer second) { + super(first, second); + } + } + + @CalledByNative + private static ResultAnd newReadMessageResult( + int mojoResult, int messageSize, int handlesCount) { + MessagePipeHandle.ReadMessageResult result = new MessagePipeHandle.ReadMessageResult(); + result.setMessageSize(messageSize); + result.setHandlesCount(handlesCount); + return new ResultAnd<>(mojoResult, result); + } + + @CalledByNative + private static ResultAnd newResultAndInteger(int mojoResult, int numBytesRead) { + return new ResultAnd<>(mojoResult, numBytesRead); + } + + @CalledByNative + private static ResultAnd newNativeCreationResult( + int mojoResult, int mojoHandle1, int mojoHandle2) { + return new ResultAnd<>(mojoResult, new IntegerPair(mojoHandle1, mojoHandle2)); + } + + private native long nativeGetTimeTicksNow(); + + private native ResultAnd nativeCreateMessagePipe(ByteBuffer optionsBuffer); + + private native ResultAnd nativeCreateDataPipe(ByteBuffer optionsBuffer); + + private native ResultAnd nativeCreateSharedBuffer( + ByteBuffer optionsBuffer, long numBytes); + + private native int nativeClose(int mojoHandle); + + private native int nativeQueryHandleSignalsState(int mojoHandle, ByteBuffer signalsStateBuffer); + + private native int nativeWriteMessage( + int mojoHandle, ByteBuffer bytes, int numBytes, ByteBuffer handlesBuffer, int flags); + + private native ResultAnd nativeReadMessage( + int mojoHandle, ByteBuffer bytes, ByteBuffer handlesBuffer, int flags); + + private native ResultAnd nativeReadData( + int mojoHandle, ByteBuffer elements, int elementsSize, int flags); + + private native ResultAnd nativeBeginReadData( + int mojoHandle, int numBytes, int flags); + + private native int nativeEndReadData(int mojoHandle, int numBytesRead); + + private native ResultAnd nativeWriteData( + int mojoHandle, ByteBuffer elements, int limit, int flags); + + private native ResultAnd nativeBeginWriteData( + int mojoHandle, int numBytes, int flags); + + private native int nativeEndWriteData(int mojoHandle, int numBytesWritten); + + private native ResultAnd nativeDuplicate(int mojoHandle, ByteBuffer optionsBuffer); + + private native ResultAnd nativeMap( + int mojoHandle, long offset, long numBytes, int flags); + + private native int nativeUnmap(ByteBuffer buffer); + + private native int nativeGetNativeBufferOffset(ByteBuffer buffer, int alignment); +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java new file mode 100644 index 0000000..83097d7 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeConsumerHandleImpl.java @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ReadFlags; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link ConsumerHandle}. + */ +class DataPipeConsumerHandleImpl extends HandleBase implements ConsumerHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + DataPipeConsumerHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + DataPipeConsumerHandleImpl(HandleBase other) { + super(other); + } + + /** + * @see org.chromium.mojo.system.Handle#pass() + */ + @Override + public ConsumerHandle pass() { + return new DataPipeConsumerHandleImpl(this); + } + + /** + * @see ConsumerHandle#discardData(int, ReadFlags) + */ + @Override + public int discardData(int numBytes, ReadFlags flags) { + return mCore.discardData(this, numBytes, flags); + } + + /** + * @see ConsumerHandle#readData(ByteBuffer, ReadFlags) + */ + @Override + public ResultAnd readData(ByteBuffer elements, ReadFlags flags) { + return mCore.readData(this, elements, flags); + } + + /** + * @see ConsumerHandle#beginReadData(int, ReadFlags) + */ + @Override + public ByteBuffer beginReadData(int numBytes, ReadFlags flags) { + return mCore.beginReadData(this, numBytes, flags); + } + + /** + * @see ConsumerHandle#endReadData(int) + */ + @Override + public void endReadData(int numBytesRead) { + mCore.endReadData(this, numBytesRead); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java new file mode 100644 index 0000000..901f26c --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/DataPipeProducerHandleImpl.java @@ -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. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.DataPipe.WriteFlags; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link ProducerHandle}. + */ +class DataPipeProducerHandleImpl extends HandleBase implements ProducerHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + DataPipeProducerHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + DataPipeProducerHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.DataPipe.ProducerHandle#pass() + */ + @Override + public ProducerHandle pass() { + return new DataPipeProducerHandleImpl(this); + } + + /** + * @see ProducerHandle#writeData(ByteBuffer, WriteFlags) + */ + @Override + public ResultAnd writeData(ByteBuffer elements, WriteFlags flags) { + return mCore.writeData(this, elements, flags); + } + + /** + * @see ProducerHandle#beginWriteData(int, WriteFlags) + */ + @Override + public ByteBuffer beginWriteData(int numBytes, WriteFlags flags) { + return mCore.beginWriteData(this, numBytes, flags); + } + + /** + * @see ProducerHandle#endWriteData(int) + */ + @Override + public void endWriteData(int numBytesWritten) { + mCore.endWriteData(this, numBytesWritten); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java new file mode 100644 index 0000000..4d149a4 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/HandleBase.java @@ -0,0 +1,140 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import android.util.Log; + +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Core.HandleSignalsState; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.UntypedHandle; + +/** + * Implementation of {@link Handle}. + */ +abstract class HandleBase implements Handle { + + private static final String TAG = "HandleImpl"; + + /** + * The pointer to the scoped handle owned by this object. + */ + private int mMojoHandle; + + /** + * The core implementation. Will be used to delegate all behavior. + */ + protected CoreImpl mCore; + + /** + * Base constructor. Takes ownership of the passed handle. + */ + HandleBase(CoreImpl core, int mojoHandle) { + mCore = core; + mMojoHandle = mojoHandle; + } + + /** + * Constructor for transforming {@link HandleBase} into a specific one. It is used to transform + * an {@link UntypedHandle} into a typed one, or any handle into an {@link UntypedHandle}. + */ + protected HandleBase(HandleBase other) { + mCore = other.mCore; + HandleBase otherAsHandleImpl = other; + int mojoHandle = otherAsHandleImpl.mMojoHandle; + otherAsHandleImpl.mMojoHandle = CoreImpl.INVALID_HANDLE; + mMojoHandle = mojoHandle; + } + + /** + * @see org.chromium.mojo.system.Handle#close() + */ + @Override + public void close() { + if (mMojoHandle != CoreImpl.INVALID_HANDLE) { + // After a close, the handle is invalid whether the close succeed or not. + int handle = mMojoHandle; + mMojoHandle = CoreImpl.INVALID_HANDLE; + mCore.close(handle); + } + } + + /** + * @see org.chromium.mojo.system.Handle#querySignalsState() + */ + @Override + public HandleSignalsState querySignalsState() { + return mCore.queryHandleSignalsState(mMojoHandle); + } + + /** + * @see org.chromium.mojo.system.Handle#isValid() + */ + @Override + public boolean isValid() { + return mMojoHandle != CoreImpl.INVALID_HANDLE; + } + + /** + * @see org.chromium.mojo.system.Handle#toUntypedHandle() + */ + @Override + public UntypedHandle toUntypedHandle() { + return new UntypedHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.Handle#getCore() + */ + @Override + public Core getCore() { + return mCore; + } + + /** + * @see Handle#releaseNativeHandle() + */ + @Override + public int releaseNativeHandle() { + int result = mMojoHandle; + mMojoHandle = CoreImpl.INVALID_HANDLE; + return result; + } + + /** + * Getter for the native scoped handle. + * + * @return the native scoped handle. + */ + int getMojoHandle() { + return mMojoHandle; + } + + /** + * invalidate the handle. The caller must ensures that the handle does not leak. + */ + void invalidateHandle() { + mMojoHandle = CoreImpl.INVALID_HANDLE; + } + + /** + * Close the handle if it is valid. Necessary because we cannot let handle leak, and we cannot + * ensure that every handle will be manually closed. + * + * @see java.lang.Object#finalize() + */ + @Override + protected final void finalize() throws Throwable { + if (isValid()) { + // This should not happen, as the user of this class should close the handle. Adding a + // warning. + Log.w(TAG, "Handle was not closed."); + // Ignore result at this point. + mCore.closeWithResult(mMojoHandle); + } + super.finalize(); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.java new file mode 100644 index 0000000..b3df0ae --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/MessagePipeHandleImpl.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.system.impl; + +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.ResultAnd; + +import java.nio.ByteBuffer; +import java.util.List; + +/** + * Implementation of {@link MessagePipeHandle}. + */ +class MessagePipeHandleImpl extends HandleBase implements MessagePipeHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + MessagePipeHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + MessagePipeHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.MessagePipeHandle#pass() + */ + @Override + public MessagePipeHandle pass() { + return new MessagePipeHandleImpl(this); + } + + /** + * @see MessagePipeHandle#writeMessage(ByteBuffer, List, WriteFlags) + */ + @Override + public void writeMessage(ByteBuffer bytes, List handles, WriteFlags flags) { + mCore.writeMessage(this, bytes, handles, flags); + } + + /** + * @see MessagePipeHandle#readMessage(ByteBuffer, int, ReadFlags) + */ + @Override + public ResultAnd readMessage( + ByteBuffer bytes, int maxNumberOfHandles, ReadFlags flags) { + return mCore.readMessage(this, bytes, maxNumberOfHandles, flags); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java new file mode 100644 index 0000000..76ef739 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/SharedBufferHandleImpl.java @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.SharedBufferHandle; + +import java.nio.ByteBuffer; + +/** + * Implementation of {@link SharedBufferHandle}. + */ +class SharedBufferHandleImpl extends HandleBase implements SharedBufferHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + SharedBufferHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + SharedBufferHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.SharedBufferHandle#pass() + */ + @Override + public SharedBufferHandle pass() { + return new SharedBufferHandleImpl(this); + } + + /** + * @see SharedBufferHandle#duplicate(DuplicateOptions) + */ + @Override + public SharedBufferHandle duplicate(DuplicateOptions options) { + return mCore.duplicate(this, options); + } + + /** + * @see SharedBufferHandle#map(long, long, MapFlags) + */ + @Override + public ByteBuffer map(long offset, long numBytes, MapFlags flags) { + return mCore.map(this, offset, numBytes, flags); + } + + /** + * @see SharedBufferHandle#unmap(ByteBuffer) + */ + @Override + public void unmap(ByteBuffer buffer) { + mCore.unmap(buffer); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java new file mode 100644 index 0000000..4774ab8 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/UntypedHandleImpl.java @@ -0,0 +1,72 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import org.chromium.mojo.system.DataPipe.ConsumerHandle; +import org.chromium.mojo.system.DataPipe.ProducerHandle; +import org.chromium.mojo.system.MessagePipeHandle; +import org.chromium.mojo.system.SharedBufferHandle; +import org.chromium.mojo.system.UntypedHandle; + +/** + * Implementation of {@link UntypedHandle}. + */ +class UntypedHandleImpl extends HandleBase implements UntypedHandle { + + /** + * @see HandleBase#HandleBase(CoreImpl, int) + */ + UntypedHandleImpl(CoreImpl core, int mojoHandle) { + super(core, mojoHandle); + } + + /** + * @see HandleBase#HandleBase(HandleBase) + */ + UntypedHandleImpl(HandleBase handle) { + super(handle); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#pass() + */ + @Override + public UntypedHandle pass() { + return new UntypedHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toMessagePipeHandle() + */ + @Override + public MessagePipeHandle toMessagePipeHandle() { + return new MessagePipeHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toDataPipeConsumerHandle() + */ + @Override + public ConsumerHandle toDataPipeConsumerHandle() { + return new DataPipeConsumerHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toDataPipeProducerHandle() + */ + @Override + public ProducerHandle toDataPipeProducerHandle() { + return new DataPipeProducerHandleImpl(this); + } + + /** + * @see org.chromium.mojo.system.UntypedHandle#toSharedBufferHandle() + */ + @Override + public SharedBufferHandle toSharedBufferHandle() { + return new SharedBufferHandleImpl(this); + } + +} diff --git a/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java b/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java new file mode 100644 index 0000000..094ad90 --- /dev/null +++ b/mojo/android/system/src/org/chromium/mojo/system/impl/WatcherImpl.java @@ -0,0 +1,63 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.mojo.system.impl; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.mojo.system.Core; +import org.chromium.mojo.system.Handle; +import org.chromium.mojo.system.MojoResult; +import org.chromium.mojo.system.Watcher; + +@JNINamespace("mojo::android") +class WatcherImpl implements Watcher { + private long mImplPtr = nativeCreateWatcher(); + private Callback mCallback; + + @Override + public int start(Handle handle, Core.HandleSignals signals, Callback callback) { + if (mImplPtr == 0) { + return MojoResult.INVALID_ARGUMENT; + } + if (!(handle instanceof HandleBase)) { + return MojoResult.INVALID_ARGUMENT; + } + int result = + nativeStart(mImplPtr, ((HandleBase) handle).getMojoHandle(), signals.getFlags()); + if (result == MojoResult.OK) mCallback = callback; + return result; + } + + @Override + public void cancel() { + if (mImplPtr == 0) { + return; + } + mCallback = null; + nativeCancel(mImplPtr); + } + + @Override + public void destroy() { + if (mImplPtr == 0) { + return; + } + nativeDelete(mImplPtr); + mImplPtr = 0; + } + + @CalledByNative + private void onHandleReady(int result) { + mCallback.onResult(result); + } + + private native long nativeCreateWatcher(); + + private native int nativeStart(long implPtr, int mojoHandle, int flags); + + private native void nativeCancel(long implPtr); + + private native void nativeDelete(long implPtr); +} diff --git a/mojo/android/system/watcher_impl.cc b/mojo/android/system/watcher_impl.cc new file mode 100644 index 0000000..3344447 --- /dev/null +++ b/mojo/android/system/watcher_impl.cc @@ -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. + +#include "mojo/android/system/watcher_impl.h" + +#include +#include + +// Removed unused headers. TODO(hidehiko): Upstream. +// #include "base/android/base_jni_registrar.h" +#include "base/android/jni_android.h" +// #include "base/android/jni_registrar.h" +// #include "base/android/library_loader/library_loader_hooks.h" +#include "base/android/scoped_java_ref.h" +#include "base/bind.h" +#include "jni/WatcherImpl_jni.h" +#include "mojo/public/cpp/system/handle.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace mojo { +namespace android { + +using base::android::JavaParamRef; + +namespace { + +class WatcherImpl { + public: + WatcherImpl() : watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) {} + + ~WatcherImpl() = default; + + jint Start(JNIEnv* env, + const JavaParamRef& jcaller, + jint mojo_handle, + jint signals) { + java_watcher_.Reset(env, jcaller); + + auto ready_callback = + base::Bind(&WatcherImpl::OnHandleReady, base::Unretained(this)); + + MojoResult result = + watcher_.Watch(mojo::Handle(static_cast(mojo_handle)), + static_cast(signals), ready_callback); + if (result != MOJO_RESULT_OK) + java_watcher_.Reset(); + + return result; + } + + void Cancel() { + java_watcher_.Reset(); + watcher_.Cancel(); + } + + private: + void OnHandleReady(MojoResult result) { + DCHECK(!java_watcher_.is_null()); + + base::android::ScopedJavaGlobalRef java_watcher_preserver; + if (result == MOJO_RESULT_CANCELLED) + java_watcher_preserver = std::move(java_watcher_); + + Java_WatcherImpl_onHandleReady( + base::android::AttachCurrentThread(), + java_watcher_.is_null() ? java_watcher_preserver : java_watcher_, + result); + } + + SimpleWatcher watcher_; + base::android::ScopedJavaGlobalRef java_watcher_; + + DISALLOW_COPY_AND_ASSIGN(WatcherImpl); +}; + +} // namespace + +static jlong CreateWatcher(JNIEnv* env, const JavaParamRef& jcaller) { + return reinterpret_cast(new WatcherImpl); +} + +static jint Start(JNIEnv* env, + const JavaParamRef& jcaller, + jlong watcher_ptr, + jint mojo_handle, + jint signals) { + auto* watcher = reinterpret_cast(watcher_ptr); + return watcher->Start(env, jcaller, mojo_handle, signals); +} + +static void Cancel(JNIEnv* env, + const JavaParamRef& jcaller, + jlong watcher_ptr) { + reinterpret_cast(watcher_ptr)->Cancel(); +} + +static void Delete(JNIEnv* env, + const JavaParamRef& jcaller, + jlong watcher_ptr) { + delete reinterpret_cast(watcher_ptr); +} + +bool RegisterWatcherImpl(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace android +} // namespace mojo diff --git a/mojo/android/system/watcher_impl.h b/mojo/android/system/watcher_impl.h new file mode 100644 index 0000000..784f007 --- /dev/null +++ b/mojo/android/system/watcher_impl.h @@ -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. + +#ifndef MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ +#define MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ + +#include + +#include "base/android/jni_android.h" + +namespace mojo { +namespace android { + +JNI_EXPORT bool RegisterWatcherImpl(JNIEnv* env); + +} // namespace android +} // namespace mojo + +#endif // MOJO_ANDROID_SYSTEM_WATCHER_IMPL_H_ diff --git a/mojo/common/BUILD.gn b/mojo/common/BUILD.gn new file mode 100644 index 0000000..9e74e58 --- /dev/null +++ b/mojo/common/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. + +import("//mojo/public/tools/bindings/mojom.gni") +import("//testing/test.gni") + +group("common") { + public_deps = [ + ":common_base", + ":common_custom_types", + ] +} + +mojom("common_custom_types") { + sources = [ + "file.mojom", + "file_path.mojom", + "string16.mojom", + "text_direction.mojom", + "time.mojom", + "unguessable_token.mojom", + "values.mojom", + "version.mojom", + ] +} + +component("common_base") { + output_name = "mojo_common_lib" + + sources = [ + "data_pipe_drainer.cc", + "data_pipe_drainer.h", + "data_pipe_utils.cc", + "data_pipe_utils.h", + ] + + defines = [ "MOJO_COMMON_IMPLEMENTATION" ] + + public_deps = [ + "//base", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + ] +} + +mojom("test_common_custom_types") { + sources = [ + "test_common_custom_types.mojom", + "traits_test_service.mojom", + ] + public_deps = [ + ":common_custom_types", + ] +} + +test("mojo_common_unittests") { + deps = [ + ":common", + ":common_custom_types", + ":struct_traits", + ":test_common_custom_types", + "//base", + "//base:message_loop_tests", + "//base/test:test_support", + "//mojo/edk/test:run_all_unittests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/test_support:test_utils", + "//testing/gtest", + "//url", + ] + + sources = [ + "common_custom_types_unittest.cc", + "struct_traits_unittest.cc", + ] +} + +source_set("struct_traits") { + sources = [ + "common_custom_types_struct_traits.cc", + "common_custom_types_struct_traits.h", + ] + deps = [ + ":common_custom_types_shared_cpp_sources", + "//base:base", + "//mojo/public/cpp/system", + ] + public_deps = [ + "//base:i18n", + ] +} diff --git a/mojo/common/DEPS b/mojo/common/DEPS new file mode 100644 index 0000000..e8ac428 --- /dev/null +++ b/mojo/common/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + # common must not depend on embedder. + "-mojo", + "+mojo/common", + "+mojo/public", +] diff --git a/mojo/common/common_custom_types_struct_traits.cc b/mojo/common/common_custom_types_struct_traits.cc new file mode 100644 index 0000000..6289504 --- /dev/null +++ b/mojo/common/common_custom_types_struct_traits.cc @@ -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. + +#include "mojo/common/common_custom_types_struct_traits.h" + +#include "mojo/public/cpp/system/platform_handle.h" + +namespace mojo { + +// static +bool StructTraits::Read( + common::mojom::String16DataView data, + base::string16* out) { + ArrayDataView view; + data.GetDataDataView(&view); + out->assign(reinterpret_cast(view.data()), view.size()); + return true; +} + +// static +const std::vector& +StructTraits::components( + const base::Version& version) { + return version.components(); +} + +// static +bool StructTraits::Read( + common::mojom::VersionDataView data, + base::Version* out) { + std::vector components; + if (!data.ReadComponents(&components)) + return false; + + *out = base::Version(base::Version(std::move(components))); + return out->IsValid(); +} + +// static +bool StructTraits< + common::mojom::UnguessableTokenDataView, + base::UnguessableToken>::Read(common::mojom::UnguessableTokenDataView data, + base::UnguessableToken* out) { + uint64_t high = data.high(); + uint64_t low = data.low(); + + // Receiving a zeroed UnguessableToken is a security issue. + if (high == 0 && low == 0) + return false; + + *out = base::UnguessableToken::Deserialize(high, low); + return true; +} + +mojo::ScopedHandle StructTraits::fd( + base::File& file) { + DCHECK(file.IsValid()); + return mojo::WrapPlatformFile(file.TakePlatformFile()); +} + +bool StructTraits::Read( + common::mojom::FileDataView data, + base::File* file) { + base::PlatformFile platform_handle = base::kInvalidPlatformFile; + if (mojo::UnwrapPlatformFile(data.TakeFd(), &platform_handle) != + MOJO_RESULT_OK) { + return false; + } + *file = base::File(platform_handle); + return true; +} + +// static +common::mojom::TextDirection +EnumTraits::ToMojom( + base::i18n::TextDirection text_direction) { + switch (text_direction) { + case base::i18n::UNKNOWN_DIRECTION: + return common::mojom::TextDirection::UNKNOWN_DIRECTION; + case base::i18n::RIGHT_TO_LEFT: + return common::mojom::TextDirection::RIGHT_TO_LEFT; + case base::i18n::LEFT_TO_RIGHT: + return common::mojom::TextDirection::LEFT_TO_RIGHT; + } + NOTREACHED(); + return common::mojom::TextDirection::UNKNOWN_DIRECTION; +} + +// static +bool EnumTraits:: + FromMojom(common::mojom::TextDirection input, + base::i18n::TextDirection* out) { + switch (input) { + case common::mojom::TextDirection::UNKNOWN_DIRECTION: + *out = base::i18n::UNKNOWN_DIRECTION; + return true; + case common::mojom::TextDirection::RIGHT_TO_LEFT: + *out = base::i18n::RIGHT_TO_LEFT; + return true; + case common::mojom::TextDirection::LEFT_TO_RIGHT: + *out = base::i18n::LEFT_TO_RIGHT; + return true; + } + return false; +} + +} // namespace mojo diff --git a/mojo/common/common_custom_types_struct_traits.h b/mojo/common/common_custom_types_struct_traits.h new file mode 100644 index 0000000..85815ff --- /dev/null +++ b/mojo/common/common_custom_types_struct_traits.h @@ -0,0 +1,85 @@ +// 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_COMMON_COMMON_CUSTOM_TYPES_STRUCT_TRAITS_H_ +#define MOJO_COMMON_COMMON_CUSTOM_TYPES_STRUCT_TRAITS_H_ + +#include "base/files/file.h" +#include "base/i18n/rtl.h" +#include "base/strings/utf_string_conversions.h" +#include "base/unguessable_token.h" +#include "base/version.h" +#include "mojo/common/file.mojom-shared.h" +#include "mojo/common/mojo_common_export.h" +#include "mojo/common/string16.mojom-shared.h" +#include "mojo/common/text_direction.mojom-shared.h" +#include "mojo/common/time.mojom-shared.h" +#include "mojo/common/unguessable_token.mojom-shared.h" +#include "mojo/common/version.mojom-shared.h" + +namespace mojo { + +template <> +struct StructTraits { + static ConstCArray data(const base::string16& str) { + return ConstCArray(str.size(), + reinterpret_cast(str.data())); + } + + static bool Read(common::mojom::String16DataView data, base::string16* out); +}; + +template <> +struct StructTraits { + static bool IsNull(const base::Version& version) { + return !version.IsValid(); + } + static void SetToNull(base::Version* out) { + *out = base::Version(std::string()); + } + static const std::vector& components(const base::Version& version); + static bool Read(common::mojom::VersionDataView data, base::Version* out); +}; + +// If base::UnguessableToken is no longer 128 bits, the logic below and the +// mojom::UnguessableToken type should be updated. +static_assert(sizeof(base::UnguessableToken) == 2 * sizeof(uint64_t), + "base::UnguessableToken should be of size 2 * sizeof(uint64_t)."); + +template <> +struct StructTraits { + static uint64_t high(const base::UnguessableToken& token) { + return token.GetHighForSerialization(); + } + + static uint64_t low(const base::UnguessableToken& token) { + return token.GetLowForSerialization(); + } + + static bool Read(common::mojom::UnguessableTokenDataView data, + base::UnguessableToken* out); +}; + +template <> +struct StructTraits { + static bool IsNull(const base::File& file) { return !file.IsValid(); } + + static void SetToNull(base::File* file) { *file = base::File(); } + + static mojo::ScopedHandle fd(base::File& file); + static bool Read(common::mojom::FileDataView data, base::File* file); +}; + +template <> +struct EnumTraits { + static common::mojom::TextDirection ToMojom( + base::i18n::TextDirection text_direction); + static bool FromMojom(common::mojom::TextDirection input, + base::i18n::TextDirection* out); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_COMMON_CUSTOM_TYPES_STRUCT_TRAITS_H_ diff --git a/mojo/common/common_custom_types_unittest.cc b/mojo/common/common_custom_types_unittest.cc new file mode 100644 index 0000000..e3571d9 --- /dev/null +++ b/mojo/common/common_custom_types_unittest.cc @@ -0,0 +1,433 @@ +// 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/files/file_path.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ptr_util.h" +#include "base/message_loop/message_loop.h" +#include "base/numerics/safe_math.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "mojo/common/test_common_custom_types.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace common { +namespace test { +namespace { + +template +struct BounceTestTraits { + static void ExpectEquality(const T& a, const T& b) { + EXPECT_EQ(a, b); + } +}; + +template +struct PassTraits { + using Type = const T&; +}; + +template <> +struct PassTraits { + using Type = base::Time; +}; + +template <> +struct PassTraits { + using Type = base::TimeDelta; +}; + +template <> +struct PassTraits { + using Type = base::TimeTicks; +}; + +template +void DoExpectResponse(T* expected_value, + const base::Closure& closure, + typename PassTraits::Type value) { + BounceTestTraits::ExpectEquality(*expected_value, value); + closure.Run(); +} + +template +base::Callback::Type)> ExpectResponse( + T* expected_value, + const base::Closure& closure) { + return base::Bind(&DoExpectResponse, expected_value, closure); +} + +class TestFilePathImpl : public TestFilePath { + public: + explicit TestFilePathImpl(TestFilePathRequest request) + : binding_(this, std::move(request)) {} + + // TestFilePath implementation: + void BounceFilePath(const base::FilePath& in, + const BounceFilePathCallback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class TestUnguessableTokenImpl : public TestUnguessableToken { + public: + explicit TestUnguessableTokenImpl(TestUnguessableTokenRequest request) + : binding_(this, std::move(request)) {} + + // TestUnguessableToken implementation: + void BounceNonce(const base::UnguessableToken& in, + const BounceNonceCallback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class TestTimeImpl : public TestTime { + public: + explicit TestTimeImpl(TestTimeRequest request) + : binding_(this, std::move(request)) {} + + // TestTime implementation: + void BounceTime(base::Time in, const BounceTimeCallback& callback) override { + callback.Run(in); + } + + void BounceTimeDelta(base::TimeDelta in, + const BounceTimeDeltaCallback& callback) override { + callback.Run(in); + } + + void BounceTimeTicks(base::TimeTicks in, + const BounceTimeTicksCallback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class TestValueImpl : public TestValue { + public: + explicit TestValueImpl(TestValueRequest request) + : binding_(this, std::move(request)) {} + + // TestValue implementation: + void BounceDictionaryValue( + std::unique_ptr in, + const BounceDictionaryValueCallback& callback) override { + callback.Run(std::move(in)); + } + + void BounceListValue(std::unique_ptr in, + const BounceListValueCallback& callback) override { + callback.Run(std::move(in)); + } + + void BounceValue(std::unique_ptr in, + const BounceValueCallback& callback) override { + callback.Run(std::move(in)); + } + + private: + mojo::Binding binding_; +}; + +class TestString16Impl : public TestString16 { + public: + explicit TestString16Impl(TestString16Request request) + : binding_(this, std::move(request)) {} + + // TestString16 implementation: + void BounceString16(const base::string16& in, + const BounceString16Callback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class TestFileImpl : public TestFile { + public: + explicit TestFileImpl(TestFileRequest request) + : binding_(this, std::move(request)) {} + + // TestFile implementation: + void BounceFile(base::File in, const BounceFileCallback& callback) override { + callback.Run(std::move(in)); + } + + private: + mojo::Binding binding_; +}; + +class TestTextDirectionImpl : public TestTextDirection { + public: + explicit TestTextDirectionImpl(TestTextDirectionRequest request) + : binding_(this, std::move(request)) {} + + // TestTextDirection: + void BounceTextDirection( + base::i18n::TextDirection in, + const BounceTextDirectionCallback& callback) override { + callback.Run(in); + } + + private: + mojo::Binding binding_; +}; + +class CommonCustomTypesTest : public testing::Test { + protected: + CommonCustomTypesTest() {} + ~CommonCustomTypesTest() override {} + + private: + base::MessageLoop message_loop_; + + DISALLOW_COPY_AND_ASSIGN(CommonCustomTypesTest); +}; + +} // namespace + +TEST_F(CommonCustomTypesTest, FilePath) { + base::RunLoop run_loop; + + TestFilePathPtr ptr; + TestFilePathImpl impl(MakeRequest(&ptr)); + + base::FilePath dir(FILE_PATH_LITERAL("hello")); + base::FilePath file = dir.Append(FILE_PATH_LITERAL("world")); + + ptr->BounceFilePath(file, ExpectResponse(&file, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, UnguessableToken) { + base::RunLoop run_loop; + + TestUnguessableTokenPtr ptr; + TestUnguessableTokenImpl impl(MakeRequest(&ptr)); + + base::UnguessableToken token = base::UnguessableToken::Create(); + + ptr->BounceNonce(token, ExpectResponse(&token, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, Time) { + base::RunLoop run_loop; + + TestTimePtr ptr; + TestTimeImpl impl(MakeRequest(&ptr)); + + base::Time t = base::Time::Now(); + + ptr->BounceTime(t, ExpectResponse(&t, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, TimeDelta) { + base::RunLoop run_loop; + + TestTimePtr ptr; + TestTimeImpl impl(MakeRequest(&ptr)); + + base::TimeDelta t = base::TimeDelta::FromDays(123); + + ptr->BounceTimeDelta(t, ExpectResponse(&t, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, TimeTicks) { + base::RunLoop run_loop; + + TestTimePtr ptr; + TestTimeImpl impl(MakeRequest(&ptr)); + + base::TimeTicks t = base::TimeTicks::Now(); + + ptr->BounceTimeTicks(t, ExpectResponse(&t, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, Value) { + TestValuePtr ptr; + TestValueImpl impl(MakeRequest(&ptr)); + + std::unique_ptr output; + + ASSERT_TRUE(ptr->BounceValue(nullptr, &output)); + EXPECT_FALSE(output); + + std::unique_ptr input = base::Value::CreateNullValue(); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::MakeUnique(123); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::MakeUnique(1.23); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::MakeUnique(false); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::MakeUnique("test string"); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + input = base::BinaryValue::CreateWithCopiedBuffer("mojo", 4); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + auto dict = base::MakeUnique(); + dict->SetBoolean("bool", false); + dict->SetInteger("int", 2); + dict->SetString("string", "some string"); + dict->SetBoolean("nested.bool", true); + dict->SetInteger("nested.int", 9); + dict->Set("some_binary", + base::BinaryValue::CreateWithCopiedBuffer("mojo", 4)); + dict->Set("null_value", base::Value::CreateNullValue()); + dict->SetIntegerWithoutPathExpansion("non_nested.int", 10); + { + std::unique_ptr dict_list(new base::ListValue()); + dict_list->AppendString("string"); + dict_list->AppendBoolean(true); + dict->Set("list", std::move(dict_list)); + } + + std::unique_ptr dict_output; + ASSERT_TRUE(ptr->BounceDictionaryValue(dict->CreateDeepCopy(), &dict_output)); + EXPECT_TRUE(base::Value::Equals(dict.get(), dict_output.get())); + + input = std::move(dict); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + EXPECT_TRUE(base::Value::Equals(input.get(), output.get())); + + auto list = base::MakeUnique(); + list->AppendString("string"); + list->AppendDouble(42.1); + list->AppendBoolean(true); + list->Append(base::BinaryValue::CreateWithCopiedBuffer("mojo", 4)); + list->Append(base::Value::CreateNullValue()); + { + std::unique_ptr list_dict( + new base::DictionaryValue()); + list_dict->SetString("string", "str"); + list->Append(std::move(list_dict)); + } + std::unique_ptr list_output; + ASSERT_TRUE(ptr->BounceListValue(list->CreateDeepCopy(), &list_output)); + EXPECT_TRUE(base::Value::Equals(list.get(), list_output.get())); + + input = std::move(list); + ASSERT_TRUE(ptr->BounceValue(input->CreateDeepCopy(), &output)); + ASSERT_TRUE(base::Value::Equals(input.get(), output.get())); +} + +TEST_F(CommonCustomTypesTest, String16) { + base::RunLoop run_loop; + + TestString16Ptr ptr; + TestString16Impl impl(MakeRequest(&ptr)); + + base::string16 str16 = base::ASCIIToUTF16("hello world"); + + ptr->BounceString16(str16, ExpectResponse(&str16, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, EmptyString16) { + base::RunLoop run_loop; + + TestString16Ptr ptr; + TestString16Impl impl(MakeRequest(&ptr)); + + base::string16 str16; + + ptr->BounceString16(str16, ExpectResponse(&str16, run_loop.QuitClosure())); + + run_loop.Run(); +} + +TEST_F(CommonCustomTypesTest, File) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + TestFilePtr ptr; + TestFileImpl impl(MakeRequest(&ptr)); + + base::File file( + temp_dir.GetPath().AppendASCII("test_file.txt"), + base::File::FLAG_CREATE | base::File::FLAG_WRITE | base::File::FLAG_READ); + const base::StringPiece test_content = + "A test string to be stored in a test file"; + file.WriteAtCurrentPos( + test_content.data(), + base::CheckedNumeric(test_content.size()).ValueOrDie()); + + base::File file_out; + ASSERT_TRUE(ptr->BounceFile(std::move(file), &file_out)); + std::vector content(test_content.size()); + ASSERT_TRUE(file_out.IsValid()); + ASSERT_EQ(static_cast(test_content.size()), + file_out.Read( + 0, content.data(), + base::CheckedNumeric(test_content.size()).ValueOrDie())); + EXPECT_EQ(test_content, + base::StringPiece(content.data(), test_content.size())); +} + +TEST_F(CommonCustomTypesTest, InvalidFile) { + TestFilePtr ptr; + TestFileImpl impl(MakeRequest(&ptr)); + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + // Test that |file_out| is set to an invalid file. + base::File file_out( + temp_dir.GetPath().AppendASCII("test_file.txt"), + base::File::FLAG_CREATE | base::File::FLAG_WRITE | base::File::FLAG_READ); + + ASSERT_TRUE(ptr->BounceFile(base::File(), &file_out)); + EXPECT_FALSE(file_out.IsValid()); +} + +TEST_F(CommonCustomTypesTest, TextDirection) { + base::i18n::TextDirection kTestDirections[] = {base::i18n::LEFT_TO_RIGHT, + base::i18n::RIGHT_TO_LEFT, + base::i18n::UNKNOWN_DIRECTION}; + + TestTextDirectionPtr ptr; + TestTextDirectionImpl impl(MakeRequest(&ptr)); + + for (size_t i = 0; i < arraysize(kTestDirections); i++) { + base::i18n::TextDirection direction_out; + ASSERT_TRUE(ptr->BounceTextDirection(kTestDirections[i], &direction_out)); + EXPECT_EQ(kTestDirections[i], direction_out); + } +} + +} // namespace test +} // namespace common +} // namespace mojo diff --git a/mojo/common/data_pipe_drainer.cc b/mojo/common/data_pipe_drainer.cc new file mode 100644 index 0000000..e705c8d --- /dev/null +++ b/mojo/common/data_pipe_drainer.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/common/data_pipe_drainer.h" + +#include + +#include + +#include "base/bind.h" + +namespace mojo { +namespace common { + +DataPipeDrainer::DataPipeDrainer(Client* client, + mojo::ScopedDataPipeConsumerHandle source) + : client_(client), + source_(std::move(source)), + handle_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC), + weak_factory_(this) { + DCHECK(client_); + handle_watcher_.Watch( + source_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&DataPipeDrainer::WaitComplete, weak_factory_.GetWeakPtr())); +} + +DataPipeDrainer::~DataPipeDrainer() {} + +void DataPipeDrainer::ReadData() { + const void* buffer = nullptr; + uint32_t num_bytes = 0; + MojoResult rv = BeginReadDataRaw(source_.get(), &buffer, &num_bytes, + MOJO_READ_DATA_FLAG_NONE); + if (rv == MOJO_RESULT_OK) { + client_->OnDataAvailable(buffer, num_bytes); + EndReadDataRaw(source_.get(), num_bytes); + } else if (rv == MOJO_RESULT_FAILED_PRECONDITION) { + client_->OnDataComplete(); + } else if (rv != MOJO_RESULT_SHOULD_WAIT) { + DCHECK(false) << "Unhandled MojoResult: " << rv; + } +} + +void DataPipeDrainer::WaitComplete(MojoResult result) { + ReadData(); +} + +} // namespace common +} // namespace mojo diff --git a/mojo/common/data_pipe_drainer.h b/mojo/common/data_pipe_drainer.h new file mode 100644 index 0000000..5cff820 --- /dev/null +++ b/mojo/common/data_pipe_drainer.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_COMMON_DATA_PIPE_DRAINER_H_ +#define MOJO_COMMON_DATA_PIPE_DRAINER_H_ + +#include + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace mojo { +namespace common { + +class MOJO_COMMON_EXPORT DataPipeDrainer { + public: + class Client { + public: + virtual void OnDataAvailable(const void* data, size_t num_bytes) = 0; + virtual void OnDataComplete() = 0; + + protected: + virtual ~Client() {} + }; + + DataPipeDrainer(Client*, mojo::ScopedDataPipeConsumerHandle source); + ~DataPipeDrainer(); + + private: + void ReadData(); + void WaitComplete(MojoResult result); + + Client* client_; + mojo::ScopedDataPipeConsumerHandle source_; + mojo::SimpleWatcher handle_watcher_; + + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(DataPipeDrainer); +}; + +} // namespace common +} // namespace mojo + +#endif // MOJO_COMMON_DATA_PIPE_DRAINER_H_ diff --git a/mojo/common/data_pipe_utils.cc b/mojo/common/data_pipe_utils.cc new file mode 100644 index 0000000..9b069b8 --- /dev/null +++ b/mojo/common/data_pipe_utils.cc @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium Authors. 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/common/data_pipe_utils.h" + +#include + +#include "base/bind.h" +#include "mojo/public/cpp/system/wait.h" + +namespace mojo { +namespace common { +namespace { + +bool BlockingCopyHelper(ScopedDataPipeConsumerHandle source, + const base::Callback& write_bytes) { + for (;;) { + const void* buffer; + uint32_t num_bytes; + MojoResult result = BeginReadDataRaw( + source.get(), &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + size_t bytes_written = write_bytes.Run(buffer, num_bytes); + result = EndReadDataRaw(source.get(), num_bytes); + if (bytes_written < num_bytes || result != MOJO_RESULT_OK) + return false; + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + result = Wait(source.get(), MOJO_HANDLE_SIGNAL_READABLE); + if (result != MOJO_RESULT_OK) { + // If the producer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } else if (result == MOJO_RESULT_FAILED_PRECONDITION) { + // If the producer handle was closed, then treat as EOF. + return true; + } else { + // Some other error occurred. + break; + } + } + + return false; +} + +size_t CopyToStringHelper( + std::string* result, const void* buffer, uint32_t num_bytes) { + result->append(static_cast(buffer), num_bytes); + return num_bytes; +} + +} // namespace + +// TODO(hansmuller): Add a max_size parameter. +bool BlockingCopyToString(ScopedDataPipeConsumerHandle source, + std::string* result) { + CHECK(result); + result->clear(); + return BlockingCopyHelper(std::move(source), + base::Bind(&CopyToStringHelper, result)); +} + +bool MOJO_COMMON_EXPORT BlockingCopyFromString( + const std::string& source, + const ScopedDataPipeProducerHandle& destination) { + auto it = source.begin(); + for (;;) { + void* buffer = nullptr; + uint32_t buffer_num_bytes = 0; + MojoResult result = + BeginWriteDataRaw(destination.get(), &buffer, &buffer_num_bytes, + MOJO_WRITE_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + char* char_buffer = static_cast(buffer); + uint32_t byte_index = 0; + while (it != source.end() && byte_index < buffer_num_bytes) { + char_buffer[byte_index++] = *it++; + } + EndWriteDataRaw(destination.get(), byte_index); + if (it == source.end()) + return true; + } else if (result == MOJO_RESULT_SHOULD_WAIT) { + result = Wait(destination.get(), MOJO_HANDLE_SIGNAL_WRITABLE); + if (result != MOJO_RESULT_OK) { + // If the consumer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } else { + // If the consumer handle was closed, then treat as EOF. + return result == MOJO_RESULT_FAILED_PRECONDITION; + } + } +} + +} // namespace common +} // namespace mojo diff --git a/mojo/common/data_pipe_utils.h b/mojo/common/data_pipe_utils.h new file mode 100644 index 0000000..a3f7c09 --- /dev/null +++ b/mojo/common/data_pipe_utils.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_COMMON_DATA_PIPE_UTILS_H_ +#define MOJO_COMMON_DATA_PIPE_UTILS_H_ + +#include + +#include + +#include "mojo/common/mojo_common_export.h" +#include "mojo/public/cpp/system/data_pipe.h" + +namespace mojo { +namespace common { + +// Copies the data from |source| into |contents| and returns true on success and +// false on error. In case of I/O error, |contents| holds the data that could +// be read from source before the error occurred. +bool MOJO_COMMON_EXPORT BlockingCopyToString( + ScopedDataPipeConsumerHandle source, + std::string* contents); + +bool MOJO_COMMON_EXPORT BlockingCopyFromString( + const std::string& source, + const ScopedDataPipeProducerHandle& destination); + +} // namespace common +} // namespace mojo + +#endif // MOJO_COMMON_DATA_PIPE_UTILS_H_ diff --git a/mojo/common/file.mojom b/mojo/common/file.mojom new file mode 100644 index 0000000..fe22473 --- /dev/null +++ b/mojo/common/file.mojom @@ -0,0 +1,10 @@ +// 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.common.mojom; + +// Corresponds to |base::File| in base/files/file.h +struct File { + handle fd; +}; diff --git a/mojo/common/file.typemap b/mojo/common/file.typemap new file mode 100644 index 0000000..26d4941 --- /dev/null +++ b/mojo/common/file.typemap @@ -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. + +mojom = "//mojo/common/file.mojom" +public_headers = [ "//base/files/file.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//mojo/common:struct_traits", +] + +type_mappings = + [ "mojo.common.mojom.File=base::File[move_only,nullable_is_same_type]" ] diff --git a/mojo/common/file_path.mojom b/mojo/common/file_path.mojom new file mode 100644 index 0000000..10ebe05 --- /dev/null +++ b/mojo/common/file_path.mojom @@ -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. + +module mojo.common.mojom; + +[Native] +struct FilePath; diff --git a/mojo/common/file_path.typemap b/mojo/common/file_path.typemap new file mode 100644 index 0000000..66d8c54 --- /dev/null +++ b/mojo/common/file_path.typemap @@ -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. + +mojom = "//mojo/common/file_path.mojom" +public_headers = [ "//base/files/file_path.h" ] +traits_headers = [ "//ipc/ipc_message_utils.h" ] +public_deps = [ + "//ipc", +] + +type_mappings = [ "mojo.common.mojom.FilePath=base::FilePath" ] diff --git a/mojo/common/mojo_common_export.h b/mojo/common/mojo_common_export.h new file mode 100644 index 0000000..48d21d0 --- /dev/null +++ b/mojo/common/mojo_common_export.h @@ -0,0 +1,32 @@ +// 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_COMMON_MOJO_COMMON_EXPORT_H_ +#define MOJO_COMMON_MOJO_COMMON_EXPORT_H_ + +#if defined(COMPONENT_BUILD) + +#if defined(WIN32) + +#if defined(MOJO_COMMON_IMPLEMENTATION) +#define MOJO_COMMON_EXPORT __declspec(dllexport) +#else +#define MOJO_COMMON_EXPORT __declspec(dllimport) +#endif + +#else // !defined(WIN32) + +#if defined(MOJO_COMMON_IMPLEMENTATION) +#define MOJO_COMMON_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_COMMON_EXPORT +#endif + +#endif // defined(WIN32) + +#else // !defined(COMPONENT_BUILD) +#define MOJO_COMMON_EXPORT +#endif + +#endif // MOJO_COMMON_MOJO_COMMON_EXPORT_H_ diff --git a/mojo/common/string16.mojom b/mojo/common/string16.mojom new file mode 100644 index 0000000..173c867 --- /dev/null +++ b/mojo/common/string16.mojom @@ -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. + +module mojo.common.mojom; + +// Corresponds to |base::string16| in base/strings/string16.h +// Corresponds to |WTF::String| in +// third_party/WebKit/Source/wtf/text/WTFString.h. +struct String16 { + array data; +}; diff --git a/mojo/common/string16.typemap b/mojo/common/string16.typemap new file mode 100644 index 0000000..223de29 --- /dev/null +++ b/mojo/common/string16.typemap @@ -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. + +mojom = "//mojo/common/string16.mojom" +public_headers = [ "//base/strings/string16.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//mojo/common:struct_traits", +] + +type_mappings = [ "mojo.common.mojom.String16=base::string16" ] diff --git a/mojo/common/struct_traits_unittest.cc b/mojo/common/struct_traits_unittest.cc new file mode 100644 index 0000000..5ac4bc9 --- /dev/null +++ b/mojo/common/struct_traits_unittest.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 "base/message_loop/message_loop.h" +#include "mojo/common/traits_test_service.mojom.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace common { +namespace { + +class StructTraitsTest : public testing::Test, public mojom::TraitsTestService { + public: + StructTraitsTest() {} + + protected: + mojom::TraitsTestServicePtr GetTraitsTestProxy() { + return traits_test_bindings_.CreateInterfacePtrAndBind(this); + } + + private: + // TraitsTestService: + void EchoVersion(const base::Optional& m, + const EchoVersionCallback& callback) override { + callback.Run(m); + } + + base::MessageLoop loop_; + mojo::BindingSet traits_test_bindings_; + + DISALLOW_COPY_AND_ASSIGN(StructTraitsTest); +}; + +TEST_F(StructTraitsTest, Version) { + const std::string& version_str = "1.2.3.4"; + base::Version input(version_str); + mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy(); + base::Optional output; + proxy->EchoVersion(input, &output); + EXPECT_TRUE(output.has_value()); + EXPECT_EQ(version_str, output->GetString()); +} + +TEST_F(StructTraitsTest, InvalidVersion) { + const std::string invalid_version_str; + base::Version input(invalid_version_str); + mojom::TraitsTestServicePtr proxy = GetTraitsTestProxy(); + base::Optional output; + proxy->EchoVersion(input, &output); + EXPECT_FALSE(output.has_value()); +} + +} // namespace +} // namespace common +} // namespace mojo diff --git a/mojo/common/test_common_custom_types.mojom b/mojo/common/test_common_custom_types.mojom new file mode 100644 index 0000000..0f13680 --- /dev/null +++ b/mojo/common/test_common_custom_types.mojom @@ -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. + +module mojo.common.test; + +import "mojo/common/file.mojom"; +import "mojo/common/file_path.mojom"; +import "mojo/common/string16.mojom"; +import "mojo/common/text_direction.mojom"; +import "mojo/common/time.mojom"; +import "mojo/common/unguessable_token.mojom"; +import "mojo/common/values.mojom"; + +interface TestFilePath { + BounceFilePath(mojo.common.mojom.FilePath in) + => (mojo.common.mojom.FilePath out); +}; + +interface TestUnguessableToken { + BounceNonce(mojo.common.mojom.UnguessableToken in) + => (mojo.common.mojom.UnguessableToken out); +}; + +interface TestTime { + BounceTime(mojo.common.mojom.Time time) => (mojo.common.mojom.Time time); + BounceTimeDelta(mojo.common.mojom.TimeDelta time_delta) + => (mojo.common.mojom.TimeDelta time_delta); + BounceTimeTicks(mojo.common.mojom.TimeTicks time_ticks) + => (mojo.common.mojom.TimeTicks time_ticks); +}; + +interface TestValue { + [Sync] + BounceDictionaryValue(mojo.common.mojom.DictionaryValue in) + => (mojo.common.mojom.DictionaryValue out); + [Sync] + BounceListValue(mojo.common.mojom.ListValue in) + => (mojo.common.mojom.ListValue out); + [Sync] + BounceValue(mojo.common.mojom.Value? in) + => (mojo.common.mojom.Value? out); +}; + +interface TestString16 { + [Sync] + BounceString16(mojo.common.mojom.String16 in) + => (mojo.common.mojom.String16 out); +}; + +interface TestFile { + [Sync] + BounceFile(mojo.common.mojom.File? in) + => (mojo.common.mojom.File? out); +}; + +interface TestTextDirection { + [Sync] + BounceTextDirection(mojo.common.mojom.TextDirection in) + => (mojo.common.mojom.TextDirection out); +}; diff --git a/mojo/common/text_direction.mojom b/mojo/common/text_direction.mojom new file mode 100644 index 0000000..7d65124 --- /dev/null +++ b/mojo/common/text_direction.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 mojo.common.mojom; + +// Corresponds to |base::i18n::TextDirection| in base/i18n/rtl.h +enum TextDirection { + UNKNOWN_DIRECTION, + RIGHT_TO_LEFT, + LEFT_TO_RIGHT +}; diff --git a/mojo/common/text_direction.typemap b/mojo/common/text_direction.typemap new file mode 100644 index 0000000..1f5be8e --- /dev/null +++ b/mojo/common/text_direction.typemap @@ -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. + +mojom = "//mojo/common/text_direction.mojom" +public_headers = [ "//base/i18n/rtl.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//base:i18n", + "//mojo/common:struct_traits", +] +type_mappings = [ "mojo.common.mojom.TextDirection=base::i18n::TextDirection" ] diff --git a/mojo/common/time.mojom b/mojo/common/time.mojom new file mode 100644 index 0000000..b403bca --- /dev/null +++ b/mojo/common/time.mojom @@ -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. + +module mojo.common.mojom; + +struct Time { + // The internal value is expressed in terms of microseconds since a fixed but + // intentionally unspecified epoch. + int64 internal_value; +}; + +struct TimeDelta { + int64 microseconds; +}; + +struct TimeTicks { + // The internal value is expressed in terms of microseconds since a fixed but + // intentionally unspecified epoch. + int64 internal_value; +}; diff --git a/mojo/common/time.typemap b/mojo/common/time.typemap new file mode 100644 index 0000000..99e9e3a --- /dev/null +++ b/mojo/common/time.typemap @@ -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. + +mojom = "//mojo/common/time.mojom" +public_headers = [ "//base/time/time.h" ] +traits_headers = [ + "//ipc/ipc_message_utils.h", + "//mojo/common/common_custom_types_struct_traits.h", +] +public_deps = [ + "//ipc", + "//mojo/common:struct_traits", +] + +type_mappings = [ + "mojo.common.mojom.Time=base::Time[copyable_pass_by_value]", + "mojo.common.mojom.TimeDelta=base::TimeDelta[copyable_pass_by_value]", + "mojo.common.mojom.TimeTicks=base::TimeTicks[copyable_pass_by_value]", +] diff --git a/mojo/common/time_struct_traits.h b/mojo/common/time_struct_traits.h new file mode 100644 index 0000000..b480edb --- /dev/null +++ b/mojo/common/time_struct_traits.h @@ -0,0 +1,55 @@ +// 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_COMMON_TIME_STRUCT_TRAITS_H_ +#define MOJO_COMMON_TIME_STRUCT_TRAITS_H_ + +#include "base/time/time.h" +#include "mojo/common/time.mojom-shared.h" + +namespace mojo { + +template <> +struct StructTraits { + static int64_t internal_value(const base::Time& time) { + return time.since_origin().InMicroseconds(); + } + + static bool Read(common::mojom::TimeDataView data, base::Time* time) { + *time = + base::Time() + base::TimeDelta::FromMicroseconds(data.internal_value()); + return true; + } +}; + +template <> +struct StructTraits { + static int64_t microseconds(const base::TimeDelta& delta) { + return delta.InMicroseconds(); + } + + static bool Read(common::mojom::TimeDeltaDataView data, + base::TimeDelta* delta) { + *delta = base::TimeDelta::FromMicroseconds(data.microseconds()); + return true; + } +}; + +template <> +struct StructTraits { + static int64_t internal_value(const base::TimeTicks& time) { + return time.since_origin().InMicroseconds(); + } + + static bool Read(common::mojom::TimeTicksDataView data, + base::TimeTicks* time) { + *time = base::TimeTicks() + + base::TimeDelta::FromMicroseconds(data.internal_value()); + return true; + } +}; + +} // namespace mojo + +#endif // MOJO_COMMON_TIME_STRUCT_TRAITS_H_ diff --git a/mojo/common/traits_test_service.mojom b/mojo/common/traits_test_service.mojom new file mode 100644 index 0000000..7659eea --- /dev/null +++ b/mojo/common/traits_test_service.mojom @@ -0,0 +1,14 @@ +// 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.common.mojom; + +import "mojo/common/version.mojom"; + +// All functions on this interface echo their arguments to test StructTraits +// serialization and deserialization. +interface TraitsTestService { + [Sync] + EchoVersion(Version? v) => (Version? pass); +}; diff --git a/mojo/common/typemaps.gni b/mojo/common/typemaps.gni new file mode 100644 index 0000000..ae36031 --- /dev/null +++ b/mojo/common/typemaps.gni @@ -0,0 +1,14 @@ +# 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/common/file.typemap", + "//mojo/common/file_path.typemap", + "//mojo/common/string16.typemap", + "//mojo/common/text_direction.typemap", + "//mojo/common/time.typemap", + "//mojo/common/unguessable_token.typemap", + "//mojo/common/values.typemap", + "//mojo/common/version.typemap", +] diff --git a/mojo/common/unguessable_token.mojom b/mojo/common/unguessable_token.mojom new file mode 100644 index 0000000..3279717 --- /dev/null +++ b/mojo/common/unguessable_token.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.common.mojom; + +// Corresponds to |base::UnguessableToken| in base/unguessable_token.h +struct UnguessableToken { + uint64 high; + uint64 low; +}; diff --git a/mojo/common/unguessable_token.typemap b/mojo/common/unguessable_token.typemap new file mode 100644 index 0000000..ec7b194 --- /dev/null +++ b/mojo/common/unguessable_token.typemap @@ -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. + +mojom = "//mojo/common/unguessable_token.mojom" +public_headers = [ "//base/unguessable_token.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//mojo/common:struct_traits", +] + +type_mappings = [ "mojo.common.mojom.UnguessableToken=base::UnguessableToken" ] diff --git a/mojo/common/values.mojom b/mojo/common/values.mojom new file mode 100644 index 0000000..722198c --- /dev/null +++ b/mojo/common/values.mojom @@ -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. + +module mojo.common.mojom; + +union Value { + NullValue? null_value; + bool bool_value; + int32 int_value; + double double_value; + string string_value; + array binary_value; + DictionaryValue dictionary_value; + ListValue list_value; +}; + +struct ListValue { + array values; +}; + +struct DictionaryValue { + map values; +}; + +// An empty struct representing a null base::Value. +struct NullValue { +}; + +// To avoid versioning problems for arc. TODO(sammc): Remove ASAP. +[Native] +struct LegacyListValue; diff --git a/mojo/common/values.typemap b/mojo/common/values.typemap new file mode 100644 index 0000000..f1f3fc2 --- /dev/null +++ b/mojo/common/values.typemap @@ -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. + +mojom = "//mojo/common/values.mojom" +public_headers = [ "//base/values.h" ] +traits_headers = [ + "//ipc/ipc_message_utils.h", + "//mojo/common/values_struct_traits.h", +] +public_deps = [ + "//base", + "//ipc", +] +sources = [ + "values_struct_traits.cc", + "values_struct_traits.h", +] + +type_mappings = [ + "mojo.common.mojom.DictionaryValue=std::unique_ptr[move_only,nullable_is_same_type]", + "mojo.common.mojom.LegacyListValue=base::ListValue[non_copyable_non_movable]", + "mojo.common.mojom.ListValue=std::unique_ptr[move_only,nullable_is_same_type]", + "mojo.common.mojom.Value=std::unique_ptr[move_only,nullable_is_same_type]", +] diff --git a/mojo/common/values_struct_traits.cc b/mojo/common/values_struct_traits.cc new file mode 100644 index 0000000..6af7a39 --- /dev/null +++ b/mojo/common/values_struct_traits.cc @@ -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. + +#include "base/memory/ptr_util.h" +#include "mojo/common/values_struct_traits.h" + +namespace mojo { + +bool StructTraits>:: + Read(common::mojom::ListValueDataView data, + std::unique_ptr* value_out) { + mojo::ArrayDataView view; + data.GetValuesDataView(&view); + + auto list_value = base::MakeUnique(); + for (size_t i = 0; i < view.size(); ++i) { + std::unique_ptr value; + if (!view.Read(i, &value)) + return false; + + list_value->Append(std::move(value)); + } + *value_out = std::move(list_value); + return true; +} + +bool StructTraits>:: + Read(common::mojom::DictionaryValueDataView data, + std::unique_ptr* value_out) { + mojo::MapDataView view; + data.GetValuesDataView(&view); + auto dictionary_value = base::MakeUnique(); + for (size_t i = 0; i < view.size(); ++i) { + base::StringPiece key; + std::unique_ptr value; + if (!view.keys().Read(i, &key) || !view.values().Read(i, &value)) + return false; + + dictionary_value->SetWithoutPathExpansion(key, std::move(value)); + } + *value_out = std::move(dictionary_value); + return true; +} + +std::unique_ptr +CloneTraits, false>::Clone( + const std::unique_ptr& input) { + auto result = base::MakeUnique(); + result->MergeDictionary(input.get()); + return result; +} + +bool UnionTraits>:: + Read(common::mojom::ValueDataView data, + std::unique_ptr* value_out) { + switch (data.tag()) { + case common::mojom::ValueDataView::Tag::NULL_VALUE: { + *value_out = base::Value::CreateNullValue(); + return true; + } + case common::mojom::ValueDataView::Tag::BOOL_VALUE: { + *value_out = base::MakeUnique(data.bool_value()); + return true; + } + case common::mojom::ValueDataView::Tag::INT_VALUE: { + *value_out = base::MakeUnique(data.int_value()); + return true; + } + case common::mojom::ValueDataView::Tag::DOUBLE_VALUE: { + *value_out = base::MakeUnique(data.double_value()); + return true; + } + case common::mojom::ValueDataView::Tag::STRING_VALUE: { + base::StringPiece string_value; + if (!data.ReadStringValue(&string_value)) + return false; + *value_out = base::MakeUnique(string_value); + return true; + } + case common::mojom::ValueDataView::Tag::BINARY_VALUE: { + mojo::ArrayDataView binary_data; + data.GetBinaryValueDataView(&binary_data); + *value_out = base::BinaryValue::CreateWithCopiedBuffer( + reinterpret_cast(binary_data.data()), + binary_data.size()); + return true; + } + case common::mojom::ValueDataView::Tag::DICTIONARY_VALUE: { + std::unique_ptr dictionary_value; + if (!data.ReadDictionaryValue(&dictionary_value)) + return false; + *value_out = std::move(dictionary_value); + return true; + } + case common::mojom::ValueDataView::Tag::LIST_VALUE: { + std::unique_ptr list_value; + if (!data.ReadListValue(&list_value)) + return false; + *value_out = std::move(list_value); + return true; + } + } + return false; +} + +} // namespace mojo diff --git a/mojo/common/values_struct_traits.h b/mojo/common/values_struct_traits.h new file mode 100644 index 0000000..befcf3a --- /dev/null +++ b/mojo/common/values_struct_traits.h @@ -0,0 +1,257 @@ +// 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_COMMON_VALUES_STRUCT_TRAITS_H_ +#define MOJO_COMMON_VALUES_STRUCT_TRAITS_H_ + +#include "base/values.h" +#include "mojo/common/values.mojom.h" +#include "mojo/public/cpp/bindings/array_traits.h" +#include "mojo/public/cpp/bindings/clone_traits.h" +#include "mojo/public/cpp/bindings/map_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/union_traits.h" + +namespace mojo { + +template <> +struct ArrayTraits { + using Element = std::unique_ptr; + using ConstIterator = base::ListValue::const_iterator; + + static size_t GetSize(const base::ListValue& input) { + return input.GetSize(); + } + + static ConstIterator GetBegin(const base::ListValue& input) { + return input.begin(); + } + + static void AdvanceIterator(ConstIterator& iterator) { ++iterator; } + + static const Element& GetValue(ConstIterator& iterator) { return *iterator; } +}; + +template <> +struct StructTraits { + static const base::ListValue& values(const base::ListValue& value) { + return value; + } +}; + +template <> +struct StructTraits> { + static bool IsNull(const std::unique_ptr& value) { + return !value; + } + + static void SetToNull(std::unique_ptr* value) { + value->reset(); + } + + static const base::ListValue& values( + const std::unique_ptr& value) { + return *value; + } + + static bool Read(common::mojom::ListValueDataView data, + std::unique_ptr* value); +}; + +template <> +struct MapTraits { + using Key = std::string; + using Value = base::Value; + using Iterator = base::DictionaryValue::Iterator; + + static size_t GetSize(const base::DictionaryValue& input) { + return input.size(); + } + + static Iterator GetBegin(const base::DictionaryValue& input) { + return Iterator(input); + } + + static void AdvanceIterator(Iterator& iterator) { iterator.Advance(); } + + static const Key& GetKey(Iterator& iterator) { return iterator.key(); } + + static const Value& GetValue(Iterator& iterator) { return iterator.value(); } +}; + +template <> +struct StructTraits { + static const base::DictionaryValue& values( + const base::DictionaryValue& value) { + return value; + } +}; + +template <> +struct StructTraits> { + static bool IsNull(const std::unique_ptr& value) { + return !value; + } + + static void SetToNull(std::unique_ptr* value) { + value->reset(); + } + + static const base::DictionaryValue& values( + const std::unique_ptr& value) { + return *value; + } + static bool Read(common::mojom::DictionaryValueDataView data, + std::unique_ptr* value); +}; + +template <> +struct CloneTraits, false> { + static std::unique_ptr Clone( + const std::unique_ptr& input); +}; + +template <> +struct UnionTraits { + static common::mojom::ValueDataView::Tag GetTag(const base::Value& data) { + switch (data.GetType()) { + case base::Value::Type::NONE: + return common::mojom::ValueDataView::Tag::NULL_VALUE; + case base::Value::Type::BOOLEAN: + return common::mojom::ValueDataView::Tag::BOOL_VALUE; + case base::Value::Type::INTEGER: + return common::mojom::ValueDataView::Tag::INT_VALUE; + case base::Value::Type::DOUBLE: + return common::mojom::ValueDataView::Tag::DOUBLE_VALUE; + case base::Value::Type::STRING: + return common::mojom::ValueDataView::Tag::STRING_VALUE; + case base::Value::Type::BINARY: + return common::mojom::ValueDataView::Tag::BINARY_VALUE; + case base::Value::Type::DICTIONARY: + return common::mojom::ValueDataView::Tag::DICTIONARY_VALUE; + case base::Value::Type::LIST: + return common::mojom::ValueDataView::Tag::LIST_VALUE; + } + NOTREACHED(); + return common::mojom::ValueDataView::Tag::NULL_VALUE; + } + + static common::mojom::NullValuePtr null_value(const base::Value& value) { + return common::mojom::NullValuePtr(); + } + + static bool bool_value(const base::Value& value) { + bool bool_value{}; + if (!value.GetAsBoolean(&bool_value)) + NOTREACHED(); + return bool_value; + } + + static int32_t int_value(const base::Value& value) { + int int_value{}; + if (!value.GetAsInteger(&int_value)) + NOTREACHED(); + return int_value; + } + + static double double_value(const base::Value& value) { + double double_value{}; + if (!value.GetAsDouble(&double_value)) + NOTREACHED(); + return double_value; + } + + static base::StringPiece string_value(const base::Value& value) { + base::StringPiece string_piece; + if (!value.GetAsString(&string_piece)) + NOTREACHED(); + return string_piece; + } + + static mojo::ConstCArray binary_value(const base::Value& value) { + const base::BinaryValue* binary_value = nullptr; + if (!value.GetAsBinary(&binary_value)) + NOTREACHED(); + return mojo::ConstCArray( + binary_value->GetSize(), + reinterpret_cast(binary_value->GetBuffer())); + } + + static const base::ListValue& list_value(const base::Value& value) { + const base::ListValue* list_value = nullptr; + if (!value.GetAsList(&list_value)) + NOTREACHED(); + return *list_value; + } + static const base::DictionaryValue& dictionary_value( + const base::Value& value) { + const base::DictionaryValue* dictionary_value = nullptr; + if (!value.GetAsDictionary(&dictionary_value)) + NOTREACHED(); + return *dictionary_value; + } +}; + +template <> +struct UnionTraits> { + static bool IsNull(const std::unique_ptr& value) { + return !value; + } + + static void SetToNull(std::unique_ptr* value) { value->reset(); } + + static common::mojom::ValueDataView::Tag GetTag( + const std::unique_ptr& value) { + return UnionTraits::GetTag( + *value); + } + + static common::mojom::NullValuePtr null_value( + const std::unique_ptr& value) { + return UnionTraits::null_value( + *value); + } + static bool bool_value(const std::unique_ptr& value) { + return UnionTraits::bool_value( + *value); + } + static int32_t int_value(const std::unique_ptr& value) { + return UnionTraits::int_value( + *value); + } + static double double_value(const std::unique_ptr& value) { + return UnionTraits::double_value( + *value); + } + static base::StringPiece string_value( + const std::unique_ptr& value) { + return UnionTraits::string_value( + *value); + } + static mojo::ConstCArray binary_value( + const std::unique_ptr& value) { + return UnionTraits::binary_value( + *value); + } + static const base::ListValue& list_value( + const std::unique_ptr& value) { + return UnionTraits::list_value( + *value); + } + static const base::DictionaryValue& dictionary_value( + const std::unique_ptr& value) { + return UnionTraits::dictionary_value(*value); + } + + static bool Read(common::mojom::ValueDataView data, + std::unique_ptr* value); +}; + +} // namespace mojo + +#endif // MOJO_COMMON_VALUES_STRUCT_TRAITS_H_ diff --git a/mojo/common/version.mojom b/mojo/common/version.mojom new file mode 100644 index 0000000..6ddf6e6 --- /dev/null +++ b/mojo/common/version.mojom @@ -0,0 +1,10 @@ +// 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.common.mojom; + +// Corresponds to |base::Version| in base/version.h +struct Version { + array components; +}; diff --git a/mojo/common/version.typemap b/mojo/common/version.typemap new file mode 100644 index 0000000..fa7fed9 --- /dev/null +++ b/mojo/common/version.typemap @@ -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. + +mojom = "//mojo/common/version.mojom" +public_headers = [ "//base/version.h" ] +traits_headers = [ "//mojo/common/common_custom_types_struct_traits.h" ] +public_deps = [ + "//mojo/common:struct_traits", +] + +type_mappings = [ "mojo.common.mojom.Version=base::Version" ] diff --git a/mojo/edk/DEPS b/mojo/edk/DEPS new file mode 100644 index 0000000..77abb21 --- /dev/null +++ b/mojo/edk/DEPS @@ -0,0 +1,14 @@ +include_rules = [ + # This code is checked into the chromium repo so it's fine to depend on this. + "+base", + "+crypto", + "+build", + "+gin", + "+native_client/src/public", + "+testing", + "+third_party/ashmem", + "+v8", + + # internal includes. + "+mojo", +] diff --git a/mojo/edk/embedder/BUILD.gn b/mojo/edk/embedder/BUILD.gn new file mode 100644 index 0000000..8105bed --- /dev/null +++ b/mojo/edk/embedder/BUILD.gn @@ -0,0 +1,147 @@ +# Copyright 2014 The Chromium 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/nacl/config.gni") + +source_set("headers") { + sources = [ + "configuration.h", + "connection_params.h", + "embedder.h", + "embedder_internal.h", + "named_platform_channel_pair.h", + "named_platform_handle.h", + "named_platform_handle_utils.h", + "pending_process_connection.h", + "platform_channel_pair.h", + "platform_handle.h", + "platform_handle_utils.h", + "scoped_platform_handle.h", + ] + + public_deps = [ + "//base", + "//mojo/public/cpp/system", + ] +} + +source_set("embedder") { + # This isn't really a standalone target; it must be linked into the + # mojo_system_impl component. + visibility = [ + "//mojo/edk/system", + "//components/nacl:nacl", + ] + + sources = [ + "configuration.h", + "connection_params.cc", + "connection_params.h", + "embedder.cc", + "embedder.h", + "embedder_internal.h", + "entrypoints.cc", + "entrypoints.h", + "pending_process_connection.cc", + "scoped_ipc_support.cc", + "scoped_ipc_support.h", + + # Test-only code: + # TODO(vtl): It's a little unfortunate that these end up in the same + # component as non-test-only code. In the static build, this code should + # hopefully be dead-stripped. + "test_embedder.cc", + "test_embedder.h", + ] + + defines = [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ] + + public_deps = [ + ":headers", + ":platform", + "//base", + "//mojo/public/cpp/system", + ] + + if (!is_nacl) { + deps = [ + "//crypto", + ] + } +} + +source_set("platform") { + # This isn't really a standalone target; it must be linked into the + # mojo_system_impl component. + visibility = [ + ":embedder", + "//mojo/edk/system", + ] + + sources = [ + "named_platform_channel_pair.h", + "named_platform_channel_pair_win.cc", + "named_platform_handle.h", + "named_platform_handle_utils.h", + "named_platform_handle_utils_win.cc", + "platform_channel_pair.cc", + "platform_channel_pair.h", + "platform_channel_pair_posix.cc", + "platform_channel_pair_win.cc", + "platform_channel_utils_posix.cc", + "platform_channel_utils_posix.h", + "platform_handle.cc", + "platform_handle.h", + "platform_handle_utils.h", + "platform_handle_utils_posix.cc", + "platform_handle_utils_win.cc", + "platform_handle_vector.h", + "platform_shared_buffer.cc", + "platform_shared_buffer.h", + "scoped_platform_handle.h", + ] + if (!is_nacl) { + sources += [ "named_platform_handle_utils_posix.cc" ] + } + + defines = [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ] + + public_deps = [ + "//mojo/public/cpp/system", + ] + + deps = [ + "//base", + ] + + if (is_android) { + deps += [ "//third_party/ashmem" ] + } + + if (is_nacl && !is_nacl_nonsfi) { + sources -= [ "platform_channel_utils_posix.cc" ] + } +} + +source_set("embedder_unittests") { + testonly = true + + # TODO: Figure out why this visibility check fails on Android. + # visibility = [ "//mojo/edk/system:mojo_system_unittests" ] + + sources = [ + "embedder_unittest.cc", + "platform_channel_pair_posix_unittest.cc", + "platform_shared_buffer_unittest.cc", + ] + + deps = [ + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/edk/system:test_utils", + "//mojo/edk/test:test_support", + "//testing/gtest", + ] +} diff --git a/mojo/edk/embedder/README.md b/mojo/edk/embedder/README.md new file mode 100644 index 0000000..fc53bec --- /dev/null +++ b/mojo/edk/embedder/README.md @@ -0,0 +1,346 @@ +# ![Mojo Graphic](https://goo.gl/6CdlbH) Mojo Embedder Development Kit (EDK) +This document is a subset of the [Mojo documentation](/mojo). + +[TOC] + +## Overview + +The Mojo EDK is a (binary-unstable) API which enables a process to use Mojo both +internally and for IPC to other Mojo-embedding processes. + +Using any of the API surface in `//mojo/edk/embedder` requires (somewhat +confusingly) a direct dependency on the GN `//mojo/edk/system` target. Despite +this fact, you should never reference any of the headers in `mojo/edk/system` +directly, as everything there is considered to be an internal detail of the EDK. + +**NOTE:** Unless you are introducing a new binary entry point into the system +(*e.g.,* a new executable with a new `main()` definition), you probably don't +need to know anything about the EDK API. Most processes defined in the Chrome +repo today already fully initialize the EDK so that Mojo's other public APIs +"just work" out of the box. + +## Basic Initialization + +In order to use Mojo in a given process, it's necessary to call +`mojo::edk::Init` exactly once: + +``` +#include "mojo/edk/embedder/embedder.h" + +int main(int argc, char** argv) { + mojo::edk::Init(); + + // Now you can create message pipes, write messages, etc + + return 0; +} +``` + +As it happens though, Mojo is less useful without some kind of IPC support as +well, and that's a second initialization step. + +## IPC Initialization + +You also need to provide the system with a background TaskRunner on which it can +watch for inbound I/O from any of the various other processes you will later +connect to it. + +Here we'll just create a new background thread for IPC and let Mojo use that. +Note that in Chromium, we use the existing "IO thread" in the browser process +and content child processes. + +``` +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" + +int main(int argc, char** argv) { + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + // As long as this object is alive, all EDK API surface relevant to IPC + // connections is usable and message pipes which span a process boundary will + // continue to function. + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + return 0; +} +``` + +This process is now fully prepared to use Mojo IPC! + +Note that all existing process types in Chromium already perform this setup +very early during startup. + +## Connecting Two Processes + +Now suppose you're running a process which has initialized Mojo IPC, and you +want to launch another process which you know will also initialize Mojo IPC. +You want to be able to connect Mojo interfaces between these two processes. +Rejoice, because this section was written just for you. + +NOTE: For legacy reasons, some API terminology may refer to concepts of "parent" +and "child" as a relationship between processes being connected by Mojo. This +relationship is today completely orthogonal to any notion of process hierarchy +in the OS, and so use of these APIs is not constrained by an adherence to any +such hierarchy. + +Mojo requires you to bring your own OS pipe to the party, and it will do the +rest. It also provides a convenient mechanism for creating such pipes, known as +a `PlatformChannelPair`. + +You provide one end of this pipe to the EDK in the local process via +`PendingProcessConnection` - which can also be used to create cross-process +message pipes (see the next section) - and you're responsible for getting the +other end into the remote process. + +``` +#include "base/process/process_handle.h" +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" + +// You write this. It launches a new process, passing the pipe handle +// encapsulated by |channel| by any means possible (e.g. on Windows or POSIX +// you may inhert the file descriptor/HANDLE at launch and pass a commandline +// argument to indicate its numeric value). Returns the handle of the new +// process. +base::ProcessHandle LaunchCoolChildProcess( + mojo::edk::ScopedPlatformHandle channel); + +int main(int argc, char** argv) { + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + // This is essentially always an OS pipe (domain socket pair, Windows named + // pipe, etc.) + mojo::edk::PlatformChannelPair channel; + + // This is a scoper which encapsulates the intent to connect to another + // process. It exists because process connection is inherently asynchronous, + // things may go wrong, and the lifetime of any associated resources is bound + // by the lifetime of this object regardless of success or failure. + mojo::edk::PendingProcessConnection child; + + base::ProcessHandle child_handle = + LaunchCoolChildProcess(channel.PassClientHandle()); + + // At this point it's safe for |child| to go out of scope and nothing will + // break. + child.Connect(child_handle, channel.PassServerHandle()); + + return 0; +} +``` + +The launched process code uses `SetParentPipeHandle` to get connected, and might +look something like: + +``` +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" + +// You write this. It acquires the ScopedPlatformHandle that was passed by +// whomever launched this process (i.e. LaunchCoolChildProcess above). +mojo::edk::ScopedPlatformHandle GetChannelHandle(); + +int main(int argc, char** argv) { + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + mojo::edk::SetParentPipeHandle(GetChannelHandle()); + + return 0; +} +``` + +Now you have IPC initialized between two processes. For some practical examples +of how this is done, you can dig into the various multiprocess tests in the +`mojo_system_unittests` test suite. + +## Bootstrapping Cross-Process Message Pipes + +Having internal Mojo IPC support initialized is pretty useless if you don't have +any message pipes spanning the process boundary. Fortunately, this is made +trivial by the EDK: `PendingProcessConnection` has a +`CreateMessagePipe` method which synthesizes a new solitary message pipe +endpoint for your immediate use, while also generating a magic token string that +can be exchanged for the other end of the pipe via +`mojo::edk::CreateChildMessagePipe`. + +The token exchange can be done by the same process (which is sometimes useful), +or by the process that is eventually connected via `Connect()` on that +`PendingProcessConnection`. This means that you can effectively pass message +pipes on the commandline by passing a token string. + +We can modify our existing sample code as follows: + +``` +#include "base/command_line.h" +#include "base/process/process_handle.h" +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "local/foo.mojom.h" // You provide this + +base::ProcessHandle LaunchCoolChildProcess( + const base::CommandLine& command_line, + mojo::edk::ScopedPlatformHandle channel); + +int main(int argc, char** argv) { + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + mojo::edk::PlatformChannelPair channel; + + mojo::edk::PendingProcessConnection child; + + base::CommandLine command_line; // Assume this is appropriately initialized + + // Create a new message pipe with one end being retrievable in the new + // process. Note that it doesn't matter whether we call CreateMessagePipe() + // before or after Connect(), and we can create as many different pipes as + // we like. + std::string pipe_token; + mojo::ScopedMessagePipeHandle my_pipe = child.CreateMessagePipe(&pipe_token); + command_line.AppendSwitchASCII("primordial-pipe", pipe_token); + + base::ProcessHandle child_handle = + LaunchCoolChildProcess(command_line, channel.PassClientHandle()); + + child.Connect(child_handle, channel.PassServerHandle()); + + // We can start using our end of the pipe immediately. Here we assume the + // other end will eventually be bound to a local::mojom::Foo implementation, + // so we can start making calls on that interface. + // + // Note that this could even be done before the child process is launched and + // it would still work as expected. + local::mojom::FooPtr foo; + foo.Bind(local::mojom::FooPtrInfo(std::move(my_pipe), 0)); + foo->DoSomeStuff(42); + + return 0; +} +``` + +and for the launched process: + + +``` +#include "base/command_line.h" +#include "base/run_loop/run_loop.h" +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "local/foo.mojom.h" // You provide this + +mojo::edk::ScopedPlatformHandle GetChannelHandle(); + +class FooImpl : local::mojom::Foo { + public: + explicit FooImpl(local::mojom::FooRequest request) + : binding_(this, std::move(request)) {} + ~FooImpl() override {} + + void DoSomeStuff(int32_t n) override { + // ... + } + + private: + mojo::Binding binding_; + + DISALLOW_COPY_AND_ASSIGN(FooImpl); +}; + +int main(int argc, char** argv) { + base::CommandLine::Init(argc, argv); + + mojo::edk::Init(); + + base::Thread ipc_thread("ipc!"); + ipc_thread.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO)); + + mojo::edk::ScopedIPCSupport ipc_support( + ipc_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + + mojo::edk::SetParentPipeHandle(GetChannelHandle()); + + mojo::ScopedMessagePipeHandle my_pipe = mojo::edk::CreateChildMessagePipe( + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + "primordial-pipe")); + + local::mojom::FooRequest foo_request; + foo_request.Bind(std::move(my_pipe)); + FooImpl impl(std::move(foo_request)); + + // Run forever! + base::RunLoop().Run(); + + return 0; +} +``` + +Note that the above samples assume an interface definition in +`//local/test.mojom` which would look something like: + +``` +module local.mojom; + +interface Foo { + DoSomeStuff(int32 n); +}; +``` + +Once you've bootstrapped your process connection with a real mojom interface, +you can avoid any further mucking around with EDK APIs or raw message pipe +handles, as everything beyond this point - including the passing of other +interface pipes - can be handled eloquently using +[public bindings APIs](/mojo#High-Level-Bindings-APIs). + +## Setting System Properties + +The public Mojo C System API exposes a +[**`MojoGetProperty`**](/mojo/public/c/system#MojoGetProperty) function for +querying global, embedder-defined property values. These can be set by calling: + +``` +mojo::edk::SetProperty(MojoPropertyType type, const void* value) +``` + diff --git a/mojo/edk/embedder/configuration.h b/mojo/edk/embedder/configuration.h new file mode 100644 index 0000000..1990fb1 --- /dev/null +++ b/mojo/edk/embedder/configuration.h @@ -0,0 +1,65 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_CONFIGURATION_H_ +#define MOJO_EDK_EMBEDDER_CONFIGURATION_H_ + +#include +#include + +namespace mojo { +namespace edk { + +// A set of constants that the Mojo system internally uses. These values should +// be consistent across all processes on the same system. +// +// In general, there should be no need to change these values from their +// defaults. However, if you do change them, you must do so before +// initialization. +struct Configuration { + // Maximum number of open (Mojo) handles. The default is 1,000,000. + // + // TODO(vtl): This doesn't count "live" handles, some of which may live in + // messages. + size_t max_handle_table_size; + + // Maximum number of active memory mappings. The default is 1,000,000. + size_t max_mapping_table_sze; + + // Maximum data size of messages sent over message pipes, in bytes. The + // default is 4MB. + size_t max_message_num_bytes; + + // Maximum number of handles that can be attached to messages sent over + // message pipes. The default is 10,000. + size_t max_message_num_handles; + + // Maximum capacity of a data pipe, in bytes. The default is 256MB. This value + // must fit into a |uint32_t|. WARNING: If you bump it closer to 2^32, you + // must audit all the code to check that we don't overflow (2^31 would + // definitely be risky; up to 2^30 is probably okay). + size_t max_data_pipe_capacity_bytes; + + // Default data pipe capacity, if not specified explicitly in the creation + // options. The default is 1MB. + size_t default_data_pipe_capacity_bytes; + + // Alignment for the "start" of the data buffer used by data pipes. (The + // alignment of elements will depend on this and the element size.) The + // default is 16 bytes. + size_t data_pipe_buffer_alignment_bytes; + + // Maximum size of a single shared memory segment, in bytes. The default is + // 1GB. + // + // TODO(vtl): Set this hard limit appropriately (e.g., higher on 64-bit). + // (This will also entail some auditing to make sure I'm not messing up my + // checks anywhere.) + size_t max_shared_memory_num_bytes; +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_CONFIGURATION_H_ diff --git a/mojo/edk/embedder/connection_params.cc b/mojo/edk/embedder/connection_params.cc new file mode 100644 index 0000000..9b7ec54 --- /dev/null +++ b/mojo/edk/embedder/connection_params.cc @@ -0,0 +1,28 @@ +// 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/edk/embedder/connection_params.h" + +#include + +namespace mojo { +namespace edk { + +ConnectionParams::ConnectionParams(ScopedPlatformHandle channel) + : channel_(std::move(channel)) {} + +ConnectionParams::ConnectionParams(ConnectionParams&& param) + : channel_(std::move(param.channel_)) {} + +ConnectionParams& ConnectionParams::operator=(ConnectionParams&& param) { + channel_ = std::move(param.channel_); + return *this; +} + +ScopedPlatformHandle ConnectionParams::TakeChannelHandle() { + return std::move(channel_); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/connection_params.h b/mojo/edk/embedder/connection_params.h new file mode 100644 index 0000000..25ffdde --- /dev/null +++ b/mojo/edk/embedder/connection_params.h @@ -0,0 +1,34 @@ +// 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_EDK_EMBEDDER_CONNECTION_PARAMS_H_ +#define MOJO_EDK_EMBEDDER_CONNECTION_PARAMS_H_ + +#include "base/macros.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +class MOJO_SYSTEM_IMPL_EXPORT ConnectionParams { + public: + explicit ConnectionParams(ScopedPlatformHandle channel); + + ConnectionParams(ConnectionParams&& param); + ConnectionParams& operator=(ConnectionParams&& param); + + ScopedPlatformHandle TakeChannelHandle(); + + private: + ScopedPlatformHandle channel_; + + DISALLOW_COPY_AND_ASSIGN(ConnectionParams); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_CONNECTION_PARAMS_H_ diff --git a/mojo/edk/embedder/embedder.cc b/mojo/edk/embedder/embedder.cc new file mode 100644 index 0000000..0fdda5c --- /dev/null +++ b/mojo/edk/embedder/embedder.cc @@ -0,0 +1,158 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/embedder.h" + +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/entrypoints.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/node_controller.h" + +#if !defined(OS_NACL) +#include "crypto/random.h" +#endif + +namespace mojo { +namespace edk { + +class Core; +class PlatformSupport; + +namespace internal { + +Core* g_core; + +Core* GetCore() { return g_core; } + +} // namespace internal + +void SetMaxMessageSize(size_t bytes) { +} + +void SetParentPipeHandle(ScopedPlatformHandle pipe) { + CHECK(internal::g_core); + internal::g_core->InitChild(ConnectionParams(std::move(pipe))); +} + +void SetParentPipeHandleFromCommandLine() { + ScopedPlatformHandle platform_channel = + PlatformChannelPair::PassClientHandleFromParentProcess( + *base::CommandLine::ForCurrentProcess()); + CHECK(platform_channel.is_valid()); + SetParentPipeHandle(std::move(platform_channel)); +} + +ScopedMessagePipeHandle ConnectToPeerProcess(ScopedPlatformHandle pipe) { + return ConnectToPeerProcess(std::move(pipe), GenerateRandomToken()); +} + +ScopedMessagePipeHandle ConnectToPeerProcess(ScopedPlatformHandle pipe, + const std::string& peer_token) { + DCHECK(pipe.is_valid()); + DCHECK(!peer_token.empty()); + return internal::g_core->ConnectToPeerProcess(std::move(pipe), peer_token); +} + +void ClosePeerConnection(const std::string& peer_token) { + return internal::g_core->ClosePeerConnection(peer_token); +} + +void Init() { + MojoSystemThunks thunks = MakeSystemThunks(); + size_t expected_size = MojoEmbedderSetSystemThunks(&thunks); + DCHECK_EQ(expected_size, sizeof(thunks)); + + internal::g_core = new Core(); +} + +void SetDefaultProcessErrorCallback(const ProcessErrorCallback& callback) { + internal::g_core->SetDefaultProcessErrorCallback(callback); +} + +MojoResult CreatePlatformHandleWrapper( + ScopedPlatformHandle platform_handle, + MojoHandle* platform_handle_wrapper_handle) { + return internal::g_core->CreatePlatformHandleWrapper( + std::move(platform_handle), platform_handle_wrapper_handle); +} + +MojoResult PassWrappedPlatformHandle(MojoHandle platform_handle_wrapper_handle, + ScopedPlatformHandle* platform_handle) { + return internal::g_core->PassWrappedPlatformHandle( + platform_handle_wrapper_handle, platform_handle); +} + +MojoResult CreateSharedBufferWrapper( + base::SharedMemoryHandle shared_memory_handle, + size_t num_bytes, + bool read_only, + MojoHandle* mojo_wrapper_handle) { + return internal::g_core->CreateSharedBufferWrapper( + shared_memory_handle, num_bytes, read_only, mojo_wrapper_handle); +} + +MojoResult PassSharedMemoryHandle( + MojoHandle mojo_handle, + base::SharedMemoryHandle* shared_memory_handle, + size_t* num_bytes, + bool* read_only) { + return internal::g_core->PassSharedMemoryHandle( + mojo_handle, shared_memory_handle, num_bytes, read_only); +} + +void InitIPCSupport(scoped_refptr io_thread_task_runner) { + CHECK(internal::g_core); + internal::g_core->SetIOTaskRunner(io_thread_task_runner); +} + +scoped_refptr GetIOTaskRunner() { + return internal::g_core->GetNodeController()->io_task_runner(); +} + +void ShutdownIPCSupport(const base::Closure& callback) { + CHECK(internal::g_core); + internal::g_core->RequestShutdown(callback); +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +void SetMachPortProvider(base::PortProvider* port_provider) { + DCHECK(port_provider); + internal::g_core->SetMachPortProvider(port_provider); +} +#endif + +ScopedMessagePipeHandle CreateChildMessagePipe(const std::string& token) { + return internal::g_core->CreateChildMessagePipe(token); +} + +std::string GenerateRandomToken() { + char random_bytes[16]; +#if defined(OS_NACL) + // Not secure. For NaCl only! + base::RandBytes(random_bytes, 16); +#else + crypto::RandBytes(random_bytes, 16); +#endif + return base::HexEncode(random_bytes, 16); +} + +MojoResult SetProperty(MojoPropertyType type, const void* value) { + CHECK(internal::g_core); + return internal::g_core->SetProperty(type, value); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/embedder.h b/mojo/edk/embedder/embedder.h new file mode 100644 index 0000000..97258e5 --- /dev/null +++ b/mojo/edk/embedder/embedder.h @@ -0,0 +1,173 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_EMBEDDER_H_ +#define MOJO_EDK_EMBEDDER_EMBEDDER_H_ + +#include + +#include +#include + +#include "base/callback.h" +#include "base/command_line.h" +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory_handle.h" +#include "base/process/process_handle.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace base { +class PortProvider; +} + +namespace mojo { +namespace edk { + +// Basic configuration/initialization ------------------------------------------ + +// |Init()| sets up the basic Mojo system environment, making the |Mojo...()| +// functions available and functional. This is never shut down (except in tests +// -- see test_embedder.h). + +// Allows changing the default max message size. Must be called before Init. +MOJO_SYSTEM_IMPL_EXPORT void SetMaxMessageSize(size_t bytes); + +// Should be called as early as possible in a child process with a handle to the +// other end of a pipe provided in the parent to +// PendingProcessConnection::Connect. +MOJO_SYSTEM_IMPL_EXPORT void SetParentPipeHandle(ScopedPlatformHandle pipe); + +// Same as above but extracts the pipe handle from the command line. See +// PlatformChannelPair for details. +MOJO_SYSTEM_IMPL_EXPORT void SetParentPipeHandleFromCommandLine(); + +// Called to connect to a peer process. This should be called only if there +// is no common ancestor for the processes involved within this mojo system. +// Both processes must call this function, each passing one end of a platform +// channel. This returns one end of a message pipe to each process. +MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle +ConnectToPeerProcess(ScopedPlatformHandle pipe); + +// Called to connect to a peer process. This should be called only if there +// is no common ancestor for the processes involved within this mojo system. +// Both processes must call this function, each passing one end of a platform +// channel. This returns one end of a message pipe to each process. |peer_token| +// may be passed to ClosePeerConnection() to close the connection. +MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle +ConnectToPeerProcess(ScopedPlatformHandle pipe, const std::string& peer_token); + +// Closes a connection to a peer process created by ConnectToPeerProcess() +// where the same |peer_token| was used. +MOJO_SYSTEM_IMPL_EXPORT void ClosePeerConnection(const std::string& peer_token); + +// Must be called first, or just after setting configuration parameters, to +// initialize the (global, singleton) system. +MOJO_SYSTEM_IMPL_EXPORT void Init(); + +// Sets a default callback to invoke when an internal error is reported but +// cannot be associated with a specific child process. +MOJO_SYSTEM_IMPL_EXPORT void SetDefaultProcessErrorCallback( + const ProcessErrorCallback& callback); + +// Basic functions ------------------------------------------------------------- + +// The functions in this section are available once |Init()| has been called. + +// Creates a |MojoHandle| that wraps the given |PlatformHandle| (taking +// ownership of it). This |MojoHandle| can then, e.g., be passed through message +// pipes. Note: This takes ownership (and thus closes) |platform_handle| even on +// failure, which is different from what you'd expect from a Mojo API, but it +// makes for a more convenient embedder API. +MOJO_SYSTEM_IMPL_EXPORT MojoResult +CreatePlatformHandleWrapper(ScopedPlatformHandle platform_handle, + MojoHandle* platform_handle_wrapper_handle); + +// Retrieves the |PlatformHandle| that was wrapped into a |MojoHandle| (using +// |CreatePlatformHandleWrapper()| above). Note that the |MojoHandle| is closed +// on success. +MOJO_SYSTEM_IMPL_EXPORT MojoResult +PassWrappedPlatformHandle(MojoHandle platform_handle_wrapper_handle, + ScopedPlatformHandle* platform_handle); + +// Creates a |MojoHandle| that wraps the given |SharedMemoryHandle| (taking +// ownership of it). |num_bytes| is the size of the shared memory object, and +// |read_only| is whether the handle is a read-only handle to shared memory. +// This |MojoHandle| is a Mojo shared buffer and can be manipulated using the +// shared buffer functions and transferred over a message pipe. +MOJO_SYSTEM_IMPL_EXPORT MojoResult +CreateSharedBufferWrapper(base::SharedMemoryHandle shared_memory_handle, + size_t num_bytes, + bool read_only, + MojoHandle* mojo_wrapper_handle); + +// Retrieves the underlying |SharedMemoryHandle| from a shared buffer +// |MojoHandle| and closes the handle. If successful, |num_bytes| will contain +// the size of the shared memory buffer and |read_only| will contain whether the +// buffer handle is read-only. Both |num_bytes| and |read_only| may be null. +// Note: The value of |shared_memory_handle| may be +// base::SharedMemory::NULLHandle(), even if this function returns success. +// Callers should perform appropriate checks. +MOJO_SYSTEM_IMPL_EXPORT MojoResult +PassSharedMemoryHandle(MojoHandle mojo_handle, + base::SharedMemoryHandle* shared_memory_handle, + size_t* num_bytes, + bool* read_only); + +// Initialialization/shutdown for interprocess communication (IPC) ------------- + +// |InitIPCSupport()| sets up the subsystem for interprocess communication, +// making the IPC functions (in the following section) available and functional. +// (This may only be done after |Init()|.) +// +// This subsystem may be shut down using |ShutdownIPCSupport()|. None of the IPC +// functions may be called after this is called. +// +// |io_thread_task_runner| should live at least until |ShutdownIPCSupport()|'s +// callback has been run. +MOJO_SYSTEM_IMPL_EXPORT void InitIPCSupport( + scoped_refptr io_thread_task_runner); + +// Retrieves the TaskRunner used for IPC I/O, as set by InitIPCSupport. +MOJO_SYSTEM_IMPL_EXPORT scoped_refptr GetIOTaskRunner(); + +// Shuts down the subsystem initialized by |InitIPCSupport()|. It be called from +// any thread and will attempt to complete shutdown on the I/O thread with which +// the system was initialized. Upon completion, |callback| is invoked on an +// arbitrary thread. +MOJO_SYSTEM_IMPL_EXPORT void ShutdownIPCSupport(const base::Closure& callback); + +#if defined(OS_MACOSX) && !defined(OS_IOS) +// Set the |base::PortProvider| for this process. Can be called on any thread, +// but must be set in the root process before any Mach ports can be transferred. +MOJO_SYSTEM_IMPL_EXPORT void SetMachPortProvider( + base::PortProvider* port_provider); +#endif + +// Creates a message pipe from a token in a child process. This token must have +// been acquired by a corresponding call to +// PendingProcessConnection::CreateMessagePipe. +MOJO_SYSTEM_IMPL_EXPORT ScopedMessagePipeHandle +CreateChildMessagePipe(const std::string& token); + +// Generates a random ASCII token string for use with various APIs that expect +// a globally unique token string. +MOJO_SYSTEM_IMPL_EXPORT std::string GenerateRandomToken(); + +// Sets system properties that can be read by the MojoGetProperty() API. See the +// documentation for MojoPropertyType for supported property types and their +// corresponding value type. +// +// Default property values: +// |MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED| - true +MOJO_SYSTEM_IMPL_EXPORT MojoResult SetProperty(MojoPropertyType type, + const void* value); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_EMBEDDER_H_ diff --git a/mojo/edk/embedder/embedder_internal.h b/mojo/edk/embedder/embedder_internal.h new file mode 100644 index 0000000..7deeca1 --- /dev/null +++ b/mojo/edk/embedder/embedder_internal.h @@ -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. + +// This header contains internal details for the *implementation* of the +// embedder API. It should not be included by any public header (nor by users of +// the embedder API). + +#ifndef MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_ +#define MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_ + +#include + +#include "mojo/edk/system/system_impl_export.h" + +namespace base { +class TaskRunner; +} + +namespace mojo { + +namespace edk { + +class Broker; +class Core; +class ProcessDelegate; + +namespace internal { + +// Instance of |Broker| to use. +extern Broker* g_broker; + +// Instance of |Core| used by the system functions (|Mojo...()|). +extern MOJO_SYSTEM_IMPL_EXPORT Core* g_core; +extern base::TaskRunner* g_delegate_thread_task_runner; +extern ProcessDelegate* g_process_delegate; + +} // namespace internal + +} // namepace edk + +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_EMBEDDER_INTERNAL_H_ diff --git a/mojo/edk/embedder/embedder_unittest.cc b/mojo/edk/embedder/embedder_unittest.cc new file mode 100644 index 0000000..388b45c --- /dev/null +++ b/mojo/edk/embedder/embedder_unittest.cc @@ -0,0 +1,603 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/embedder.h" + +#include +#include +#include + +#include + +#include "base/base_paths.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "base/memory/shared_memory.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/process/process_handle.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "base/test/test_timeouts.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/named_platform_handle_utils.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/test_embedder.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/core.h" +#include "mojo/public/cpp/system/handle.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 edk { +namespace { + +// The multiprocess tests that use these don't compile on iOS. +#if !defined(OS_IOS) +const char kHelloWorld[] = "hello world"; +const char kByeWorld[] = "bye world"; +#endif + +using EmbedderTest = test::MojoTestBase; + +TEST_F(EmbedderTest, ChannelBasic) { + MojoHandle server_mp, client_mp; + CreateMessagePipe(&server_mp, &client_mp); + + const std::string kHello = "hello"; + + // We can write to a message pipe handle immediately. + WriteMessage(server_mp, kHello); + EXPECT_EQ(kHello, ReadMessage(client_mp)); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); +} + +// Verifies that a MP with pending messages to be written can be sent and the +// pending messages aren't dropped. +TEST_F(EmbedderTest, SendMessagePipeWithWriteQueue) { + MojoHandle server_mp, client_mp; + CreateMessagePipe(&server_mp, &client_mp); + + MojoHandle server_mp2, client_mp2; + CreateMessagePipe(&server_mp2, &client_mp2); + + static const size_t kNumMessages = 1001; + for (size_t i = 1; i <= kNumMessages; i++) + WriteMessage(client_mp2, std::string(i, 'A' + (i % 26))); + + // Now send client2. + WriteMessageWithHandles(server_mp, "hey", &client_mp2, 1); + client_mp2 = MOJO_HANDLE_INVALID; + + // Read client2 just so we can close it later. + EXPECT_EQ("hey", ReadMessageWithHandles(client_mp, &client_mp2, 1)); + EXPECT_NE(MOJO_HANDLE_INVALID, client_mp2); + + // Now verify that all the messages that were written were sent correctly. + for (size_t i = 1; i <= kNumMessages; i++) + ASSERT_EQ(std::string(i, 'A' + (i % 26)), ReadMessage(server_mp2)); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp2)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp2)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); +} + +TEST_F(EmbedderTest, ChannelsHandlePassing) { + MojoHandle server_mp, client_mp; + CreateMessagePipe(&server_mp, &client_mp); + EXPECT_NE(server_mp, MOJO_HANDLE_INVALID); + EXPECT_NE(client_mp, MOJO_HANDLE_INVALID); + + MojoHandle h0, h1; + CreateMessagePipe(&h0, &h1); + + // Write a message to |h0| (attaching nothing). + const std::string kHello = "hello"; + WriteMessage(h0, kHello); + + // Write one message to |server_mp|, attaching |h1|. + const std::string kWorld = "world!!!"; + WriteMessageWithHandles(server_mp, kWorld, &h1, 1); + h1 = MOJO_HANDLE_INVALID; + + // Write another message to |h0|. + const std::string kFoo = "foo"; + WriteMessage(h0, kFoo); + + // Wait for |client_mp| to become readable and read a message from it. + EXPECT_EQ(kWorld, ReadMessageWithHandles(client_mp, &h1, 1)); + EXPECT_NE(h1, MOJO_HANDLE_INVALID); + + // Wait for |h1| to become readable and read a message from it. + EXPECT_EQ(kHello, ReadMessage(h1)); + + // Wait for |h1| to become readable (again) and read its second message. + EXPECT_EQ(kFoo, ReadMessage(h1)); + + // Write a message to |h1|. + const std::string kBarBaz = "barbaz"; + WriteMessage(h1, kBarBaz); + + // Wait for |h0| to become readable and read a message from it. + EXPECT_EQ(kBarBaz, ReadMessage(h0)); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(server_mp)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(client_mp)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(h0)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(h1)); +} + +TEST_F(EmbedderTest, PipeSetup) { + // Ensures that a pending process connection's message pipe can be claimed by + // the host process itself. + PendingProcessConnection process; + std::string pipe_token; + ScopedMessagePipeHandle parent_mp = process.CreateMessagePipe(&pipe_token); + ScopedMessagePipeHandle child_mp = CreateChildMessagePipe(pipe_token); + + const std::string kHello = "hello"; + WriteMessage(parent_mp.get().value(), kHello); + + EXPECT_EQ(kHello, ReadMessage(child_mp.get().value())); +} + +TEST_F(EmbedderTest, PipeSetup_LaunchDeath) { + PlatformChannelPair pair; + + PendingProcessConnection process; + std::string pipe_token; + ScopedMessagePipeHandle parent_mp = process.CreateMessagePipe(&pipe_token); + process.Connect(base::GetCurrentProcessHandle(), + ConnectionParams(pair.PassServerHandle())); + + // Close the remote end, simulating child death before the child connects to + // the reserved port. + ignore_result(pair.PassClientHandle()); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(parent_mp.get().value(), + MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +TEST_F(EmbedderTest, PipeSetup_LaunchFailure) { + PlatformChannelPair pair; + + auto process = base::MakeUnique(); + std::string pipe_token; + ScopedMessagePipeHandle parent_mp = process->CreateMessagePipe(&pipe_token); + + // Ensure that if a PendingProcessConnection goes away before Connect() is + // called, any message pipes associated with it detect peer closure. + process.reset(); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(parent_mp.get().value(), + MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +// The sequence of messages sent is: +// server_mp client_mp mp0 mp1 mp2 mp3 +// 1. "hello" +// 2. "world!" +// 3. "FOO" +// 4. "Bar"+mp1 +// 5. (close) +// 6. (close) +// 7. "baz" +// 8. (closed) +// 9. "quux"+mp2 +// 10. (close) +// 11. (wait/cl.) +// 12. (wait/cl.) + +#if !defined(OS_IOS) + +TEST_F(EmbedderTest, MultiprocessChannels) { + RUN_CHILD_ON_PIPE(MultiprocessChannelsClient, server_mp) + // 1. Write a message to |server_mp| (attaching nothing). + WriteMessage(server_mp, "hello"); + + // 2. Read a message from |server_mp|. + EXPECT_EQ("world!", ReadMessage(server_mp)); + + // 3. Create a new message pipe (endpoints |mp0| and |mp1|). + MojoHandle mp0, mp1; + CreateMessagePipe(&mp0, &mp1); + + // 4. Write something to |mp0|. + WriteMessage(mp0, "FOO"); + + // 5. Write a message to |server_mp|, attaching |mp1|. + WriteMessageWithHandles(server_mp, "Bar", &mp1, 1); + mp1 = MOJO_HANDLE_INVALID; + + // 6. Read a message from |mp0|, which should have |mp2| attached. + MojoHandle mp2 = MOJO_HANDLE_INVALID; + EXPECT_EQ("quux", ReadMessageWithHandles(mp0, &mp2, 1)); + + // 7. Read a message from |mp2|. + EXPECT_EQ("baz", ReadMessage(mp2)); + + // 8. Close |mp0|. + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp0)); + + // 9. Tell the client to quit. + WriteMessage(server_mp, "quit"); + + // 10. Wait on |mp2| (which should eventually fail) and then close it. + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(mp2, MOJO_HANDLE_SIGNAL_READABLE, &state)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp2)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessChannelsClient, EmbedderTest, + client_mp) { + // 1. Read the first message from |client_mp|. + EXPECT_EQ("hello", ReadMessage(client_mp)); + + // 2. Write a message to |client_mp| (attaching nothing). + WriteMessage(client_mp, "world!"); + + // 4. Read a message from |client_mp|, which should have |mp1| attached. + MojoHandle mp1; + EXPECT_EQ("Bar", ReadMessageWithHandles(client_mp, &mp1, 1)); + + // 5. Create a new message pipe (endpoints |mp2| and |mp3|). + MojoHandle mp2, mp3; + CreateMessagePipe(&mp2, &mp3); + + // 6. Write a message to |mp3|. + WriteMessage(mp3, "baz"); + + // 7. Close |mp3|. + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp3)); + + // 8. Write a message to |mp1|, attaching |mp2|. + WriteMessageWithHandles(mp1, "quux", &mp2, 1); + mp2 = MOJO_HANDLE_INVALID; + + // 9. Read a message from |mp1|. + EXPECT_EQ("FOO", ReadMessage(mp1)); + + EXPECT_EQ("quit", ReadMessage(client_mp)); + + // 10. Wait on |mp1| (which should eventually fail) and then close it. + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &state)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(mp1)); +} + +TEST_F(EmbedderTest, MultiprocessBaseSharedMemory) { + RUN_CHILD_ON_PIPE(MultiprocessSharedMemoryClient, server_mp) + // 1. Create a base::SharedMemory object and create a mojo shared buffer + // from it. + base::SharedMemoryCreateOptions options; + options.size = 123; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + base::SharedMemoryHandle shm_handle = base::SharedMemory::DuplicateHandle( + shared_memory.handle()); + MojoHandle sb1; + ASSERT_EQ(MOJO_RESULT_OK, + CreateSharedBufferWrapper(shm_handle, 123, false, &sb1)); + + // 2. Map |sb1| and write something into it. + char* buffer = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(sb1, 0, 123, reinterpret_cast(&buffer), 0)); + ASSERT_TRUE(buffer); + memcpy(buffer, kHelloWorld, sizeof(kHelloWorld)); + + // 3. Duplicate |sb1| into |sb2| and pass to |server_mp|. + MojoHandle sb2 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(sb1, 0, &sb2)); + EXPECT_NE(MOJO_HANDLE_INVALID, sb2); + WriteMessageWithHandles(server_mp, "hello", &sb2, 1); + + // 4. Read a message from |server_mp|. + EXPECT_EQ("bye", ReadMessage(server_mp)); + + // 5. Expect that the contents of the shared buffer have changed. + EXPECT_EQ(kByeWorld, std::string(buffer)); + + // 6. Map the original base::SharedMemory and expect it contains the + // expected value. + ASSERT_TRUE(shared_memory.Map(123)); + EXPECT_EQ(kByeWorld, + std::string(static_cast(shared_memory.memory()))); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(sb1)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessSharedMemoryClient, EmbedderTest, + client_mp) { + // 1. Read the first message from |client_mp|, which should have |sb1| which + // should be a shared buffer handle. + MojoHandle sb1; + EXPECT_EQ("hello", ReadMessageWithHandles(client_mp, &sb1, 1)); + + // 2. Map |sb1|. + char* buffer = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(sb1, 0, 123, reinterpret_cast(&buffer), 0)); + ASSERT_TRUE(buffer); + + // 3. Ensure |buffer| contains the values we expect. + EXPECT_EQ(kHelloWorld, std::string(buffer)); + + // 4. Write into |buffer| and send a message back. + memcpy(buffer, kByeWorld, sizeof(kByeWorld)); + WriteMessage(client_mp, "bye"); + + // 5. Extract the shared memory handle and ensure we can map it and read the + // contents. + base::SharedMemoryHandle shm_handle; + ASSERT_EQ(MOJO_RESULT_OK, + PassSharedMemoryHandle(sb1, &shm_handle, nullptr, nullptr)); + base::SharedMemory shared_memory(shm_handle, false); + ASSERT_TRUE(shared_memory.Map(123)); + EXPECT_NE(buffer, shared_memory.memory()); + EXPECT_EQ(kByeWorld, std::string(static_cast(shared_memory.memory()))); + + // 6. Close |sb1|. Should fail because |PassSharedMemoryHandle()| should have + // closed the handle. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoClose(sb1)); +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +TEST_F(EmbedderTest, MultiprocessMachSharedMemory) { + RUN_CHILD_ON_PIPE(MultiprocessSharedMemoryClient, server_mp) + // 1. Create a Mach base::SharedMemory object and create a mojo shared + // buffer from it. + base::SharedMemoryCreateOptions options; + options.size = 123; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + base::SharedMemoryHandle shm_handle = base::SharedMemory::DuplicateHandle( + shared_memory.handle()); + MojoHandle sb1; + ASSERT_EQ(MOJO_RESULT_OK, + CreateSharedBufferWrapper(shm_handle, 123, false, &sb1)); + + // 2. Map |sb1| and write something into it. + char* buffer = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(sb1, 0, 123, reinterpret_cast(&buffer), 0)); + ASSERT_TRUE(buffer); + memcpy(buffer, kHelloWorld, sizeof(kHelloWorld)); + + // 3. Duplicate |sb1| into |sb2| and pass to |server_mp|. + MojoHandle sb2 = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, MojoDuplicateBufferHandle(sb1, 0, &sb2)); + EXPECT_NE(MOJO_HANDLE_INVALID, sb2); + WriteMessageWithHandles(server_mp, "hello", &sb2, 1); + + // 4. Read a message from |server_mp|. + EXPECT_EQ("bye", ReadMessage(server_mp)); + + // 5. Expect that the contents of the shared buffer have changed. + EXPECT_EQ(kByeWorld, std::string(buffer)); + + // 6. Map the original base::SharedMemory and expect it contains the + // expected value. + ASSERT_TRUE(shared_memory.Map(123)); + EXPECT_EQ(kByeWorld, + std::string(static_cast(shared_memory.memory()))); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(sb1)); + END_CHILD() +} + +enum class HandleType { + POSIX, + MACH, + MACH_NULL, +}; + +const HandleType kTestHandleTypes[] = { + HandleType::MACH, + HandleType::MACH_NULL, + HandleType::POSIX, + HandleType::POSIX, + HandleType::MACH, +}; + +// Test that we can mix file descriptors and mach port handles. +TEST_F(EmbedderTest, MultiprocessMixMachAndFds) { + const size_t kShmSize = 1234; + RUN_CHILD_ON_PIPE(MultiprocessMixMachAndFdsClient, server_mp) + // 1. Create fds or Mach objects and mojo handles from them. + MojoHandle platform_handles[arraysize(kTestHandleTypes)]; + for (size_t i = 0; i < arraysize(kTestHandleTypes); i++) { + const auto type = kTestHandleTypes[i]; + ScopedPlatformHandle scoped_handle; + if (type == HandleType::POSIX) { + // The easiest source of fds is opening /dev/null. + base::File file(base::FilePath("/dev/null"), + base::File::FLAG_OPEN | base::File::FLAG_WRITE); + ASSERT_TRUE(file.IsValid()); + scoped_handle.reset(PlatformHandle(file.TakePlatformFile())); + EXPECT_EQ(PlatformHandle::Type::POSIX, scoped_handle.get().type); + } else if (type == HandleType::MACH_NULL) { + scoped_handle.reset(PlatformHandle( + static_cast(MACH_PORT_NULL))); + EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type); + } else { + base::SharedMemoryCreateOptions options; + options.size = kShmSize; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + base::SharedMemoryHandle shm_handle = + base::SharedMemory::DuplicateHandle(shared_memory.handle()); + scoped_handle.reset(PlatformHandle(shm_handle.GetMemoryObject())); + EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type); + } + ASSERT_EQ(MOJO_RESULT_OK, CreatePlatformHandleWrapper( + std::move(scoped_handle), platform_handles + i)); + } + + // 2. Send all the handles to the child. + WriteMessageWithHandles(server_mp, "hello", platform_handles, + arraysize(kTestHandleTypes)); + + // 3. Read a message from |server_mp|. + EXPECT_EQ("bye", ReadMessage(server_mp)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessMixMachAndFdsClient, EmbedderTest, + client_mp) { + const int kNumHandles = arraysize(kTestHandleTypes); + MojoHandle platform_handles[kNumHandles]; + + // 1. Read from |client_mp|, which should have a message containing + // |kNumHandles| handles. + EXPECT_EQ("hello", + ReadMessageWithHandles(client_mp, platform_handles, kNumHandles)); + + // 2. Extract each handle, and verify the type. + for (int i = 0; i < kNumHandles; i++) { + const auto type = kTestHandleTypes[i]; + ScopedPlatformHandle scoped_handle; + ASSERT_EQ(MOJO_RESULT_OK, + PassWrappedPlatformHandle(platform_handles[i], &scoped_handle)); + if (type == HandleType::POSIX) { + EXPECT_NE(0, scoped_handle.get().handle); + EXPECT_EQ(PlatformHandle::Type::POSIX, scoped_handle.get().type); + } else if (type == HandleType::MACH_NULL) { + EXPECT_EQ(static_cast(MACH_PORT_NULL), + scoped_handle.get().port); + EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type); + } else { + EXPECT_NE(static_cast(MACH_PORT_NULL), + scoped_handle.get().port); + EXPECT_EQ(PlatformHandle::Type::MACH, scoped_handle.get().type); + } + } + + // 3. Say bye! + WriteMessage(client_mp, "bye"); +} + +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + +// TODO(vtl): Test immediate write & close. +// TODO(vtl): Test broken-connection cases. + +#endif // !defined(OS_IOS) + +NamedPlatformHandle GenerateChannelName() { +#if defined(OS_POSIX) + base::FilePath temp_dir; + CHECK(base::PathService::Get(base::DIR_TEMP, &temp_dir)); + return NamedPlatformHandle( + temp_dir.AppendASCII(GenerateRandomToken()).value()); +#else + return NamedPlatformHandle(GenerateRandomToken()); +#endif +} + +void CreateClientHandleOnIoThread(const NamedPlatformHandle& named_handle, + ScopedPlatformHandle* output) { + *output = CreateClientHandle(named_handle); +} + +TEST_F(EmbedderTest, ClosePendingPeerConnection) { + NamedPlatformHandle named_handle = GenerateChannelName(); + std::string peer_token = GenerateRandomToken(); + ScopedMessagePipeHandle server_pipe = + ConnectToPeerProcess(CreateServerHandle(named_handle), peer_token); + ClosePeerConnection(peer_token); + EXPECT_EQ(MOJO_RESULT_OK, + Wait(server_pipe.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + base::MessageLoop message_loop; + base::RunLoop run_loop; + ScopedPlatformHandle client_handle; + // Closing the channel involves posting a task to the IO thread to do the + // work. By the time the local message pipe has been observerd as closed, + // that task will have been posted. Therefore, a task to create the client + // connection should be handled after the channel is closed. + GetIOTaskRunner()->PostTaskAndReply( + FROM_HERE, + base::Bind(&CreateClientHandleOnIoThread, named_handle, &client_handle), + run_loop.QuitClosure()); + run_loop.Run(); + EXPECT_FALSE(client_handle.is_valid()); +} + +#if !defined(OS_IOS) + +TEST_F(EmbedderTest, ClosePipeToConnectedPeer) { + set_launch_type(LaunchType::PEER); + auto& controller = StartClient("ClosePipeToConnectedPeerClient"); + MojoHandle server_mp = controller.pipe(); + // 1. Write a message to |server_mp| (attaching nothing). + WriteMessage(server_mp, "hello"); + + // 2. Read a message from |server_mp|. + EXPECT_EQ("world!", ReadMessage(server_mp)); + + controller.ClosePeerConnection(); + + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(0, controller.WaitForShutdown()); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ClosePipeToConnectedPeerClient, EmbedderTest, + client_mp) { + // 1. Read the first message from |client_mp|. + EXPECT_EQ("hello", ReadMessage(client_mp)); + + // 2. Write a message to |client_mp| (attaching nothing). + WriteMessage(client_mp, "world!"); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +TEST_F(EmbedderTest, ClosePipeToConnectingPeer) { + set_launch_type(LaunchType::PEER); + auto& controller = StartClient("ClosePipeToConnectingPeerClient"); + controller.ClosePeerConnection(); + + MojoHandle server_mp = controller.pipe(); + + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(0, controller.WaitForShutdown()); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ClosePipeToConnectingPeerClient, EmbedderTest, + client_mp) { + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +#endif // !defined(OS_IOS) + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/entrypoints.cc b/mojo/edk/embedder/entrypoints.cc new file mode 100644 index 0000000..9081368 --- /dev/null +++ b/mojo/edk/embedder/entrypoints.cc @@ -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. + +#include "mojo/edk/embedder/entrypoints.h" + +#include + +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/platform_handle.h" + +using mojo::edk::internal::g_core; + +// Definitions of the system functions. +extern "C" { + +MojoTimeTicks MojoGetTimeTicksNowImpl() { + return g_core->GetTimeTicksNow(); +} + +MojoResult MojoCloseImpl(MojoHandle handle) { + return g_core->Close(handle); +} + +MojoResult MojoQueryHandleSignalsStateImpl( + MojoHandle handle, + MojoHandleSignalsState* signals_state) { + return g_core->QueryHandleSignalsState(handle, signals_state); +} + +MojoResult MojoCreateWatcherImpl(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { + return g_core->CreateWatcher(callback, watcher_handle); +} + +MojoResult MojoArmWatcherImpl(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + return g_core->ArmWatcher(watcher_handle, num_ready_contexts, ready_contexts, + ready_results, ready_signals_states); +} + +MojoResult MojoWatchImpl(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context) { + return g_core->Watch(watcher_handle, handle, signals, context); +} + +MojoResult MojoCancelWatchImpl(MojoHandle watcher_handle, uintptr_t context) { + return g_core->CancelWatch(watcher_handle, context); +} + +MojoResult MojoAllocMessageImpl(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message) { + return g_core->AllocMessage(num_bytes, handles, num_handles, flags, message); +} + +MojoResult MojoFreeMessageImpl(MojoMessageHandle message) { + return g_core->FreeMessage(message); +} + +MojoResult MojoGetMessageBufferImpl(MojoMessageHandle message, void** buffer) { + return g_core->GetMessageBuffer(message, buffer); +} + +MojoResult MojoCreateMessagePipeImpl( + const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + return g_core->CreateMessagePipe(options, message_pipe_handle0, + message_pipe_handle1); +} + +MojoResult MojoWriteMessageImpl(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + return g_core->WriteMessage(message_pipe_handle, bytes, num_bytes, handles, + num_handles, flags); +} + +MojoResult MojoWriteMessageNewImpl(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags) { + return g_core->WriteMessageNew(message_pipe_handle, message, flags); +} + +MojoResult MojoReadMessageImpl(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return g_core->ReadMessage( + message_pipe_handle, bytes, num_bytes, handles, num_handles, flags); +} + +MojoResult MojoReadMessageNewImpl(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + return g_core->ReadMessageNew( + message_pipe_handle, message, num_bytes, handles, num_handles, flags); +} + +MojoResult MojoFuseMessagePipesImpl(MojoHandle handle0, MojoHandle handle1) { + return g_core->FuseMessagePipes(handle0, handle1); +} + +MojoResult MojoCreateDataPipeImpl(const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + return g_core->CreateDataPipe(options, data_pipe_producer_handle, + data_pipe_consumer_handle); +} + +MojoResult MojoWriteDataImpl(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_elements, + MojoWriteDataFlags flags) { + return g_core->WriteData(data_pipe_producer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginWriteDataImpl(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_elements, + MojoWriteDataFlags flags) { + return g_core->BeginWriteData(data_pipe_producer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndWriteDataImpl(MojoHandle data_pipe_producer_handle, + uint32_t num_elements_written) { + return g_core->EndWriteData(data_pipe_producer_handle, num_elements_written); +} + +MojoResult MojoReadDataImpl(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_elements, + MojoReadDataFlags flags) { + return g_core->ReadData(data_pipe_consumer_handle, elements, num_elements, + flags); +} + +MojoResult MojoBeginReadDataImpl(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_elements, + MojoReadDataFlags flags) { + return g_core->BeginReadData(data_pipe_consumer_handle, buffer, + buffer_num_elements, flags); +} + +MojoResult MojoEndReadDataImpl(MojoHandle data_pipe_consumer_handle, + uint32_t num_elements_read) { + return g_core->EndReadData(data_pipe_consumer_handle, num_elements_read); +} + +MojoResult MojoCreateSharedBufferImpl( + const struct MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + return g_core->CreateSharedBuffer(options, num_bytes, shared_buffer_handle); +} + +MojoResult MojoDuplicateBufferHandleImpl( + MojoHandle buffer_handle, + const struct MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + return g_core->DuplicateBufferHandle(buffer_handle, options, + new_buffer_handle); +} + +MojoResult MojoMapBufferImpl(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + return g_core->MapBuffer(buffer_handle, offset, num_bytes, buffer, flags); +} + +MojoResult MojoUnmapBufferImpl(void* buffer) { + return g_core->UnmapBuffer(buffer); +} + +MojoResult MojoWrapPlatformHandleImpl(const MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle) { + return g_core->WrapPlatformHandle(platform_handle, mojo_handle); +} + +MojoResult MojoUnwrapPlatformHandleImpl(MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle) { + return g_core->UnwrapPlatformHandle(mojo_handle, platform_handle); +} + +MojoResult MojoWrapPlatformSharedBufferHandleImpl( + const MojoPlatformHandle* platform_handle, + size_t num_bytes, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle) { + return g_core->WrapPlatformSharedBufferHandle(platform_handle, num_bytes, + flags, mojo_handle); +} + +MojoResult MojoUnwrapPlatformSharedBufferHandleImpl( + MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle, + size_t* num_bytes, + MojoPlatformSharedBufferHandleFlags* flags) { + return g_core->UnwrapPlatformSharedBufferHandle(mojo_handle, platform_handle, + num_bytes, flags); +} + +MojoResult MojoNotifyBadMessageImpl(MojoMessageHandle message, + const char* error, + size_t error_num_bytes) { + return g_core->NotifyBadMessage(message, error, error_num_bytes); +} + +MojoResult MojoGetPropertyImpl(MojoPropertyType type, void* value) { + return g_core->GetProperty(type, value); +} + +} // extern "C" + +namespace mojo { +namespace edk { + +MojoSystemThunks MakeSystemThunks() { + MojoSystemThunks system_thunks = {sizeof(MojoSystemThunks), + MojoGetTimeTicksNowImpl, + MojoCloseImpl, + MojoQueryHandleSignalsStateImpl, + MojoCreateMessagePipeImpl, + MojoWriteMessageImpl, + MojoReadMessageImpl, + MojoCreateDataPipeImpl, + MojoWriteDataImpl, + MojoBeginWriteDataImpl, + MojoEndWriteDataImpl, + MojoReadDataImpl, + MojoBeginReadDataImpl, + MojoEndReadDataImpl, + MojoCreateSharedBufferImpl, + MojoDuplicateBufferHandleImpl, + MojoMapBufferImpl, + MojoUnmapBufferImpl, + MojoCreateWatcherImpl, + MojoWatchImpl, + MojoCancelWatchImpl, + MojoArmWatcherImpl, + MojoFuseMessagePipesImpl, + MojoWriteMessageNewImpl, + MojoReadMessageNewImpl, + MojoAllocMessageImpl, + MojoFreeMessageImpl, + MojoGetMessageBufferImpl, + MojoWrapPlatformHandleImpl, + MojoUnwrapPlatformHandleImpl, + MojoWrapPlatformSharedBufferHandleImpl, + MojoUnwrapPlatformSharedBufferHandleImpl, + MojoNotifyBadMessageImpl, + MojoGetPropertyImpl}; + return system_thunks; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/entrypoints.h b/mojo/edk/embedder/entrypoints.h new file mode 100644 index 0000000..8e448c1 --- /dev/null +++ b/mojo/edk/embedder/entrypoints.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_EDK_EMBEDDER_ENTRYPOINTS_H_ +#define MOJO_EDK_EMBEDDER_ENTRYPOINTS_H_ + +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/thunks.h" + +namespace mojo { +namespace edk { + +// Creates a MojoSystemThunks struct populated with the EDK's implementation of +// each function. This may be used by embedders to populate thunks for +// application loading. +MOJO_SYSTEM_IMPL_EXPORT MojoSystemThunks MakeSystemThunks(); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_ENTRYPOINTS_H_ diff --git a/mojo/edk/embedder/named_platform_channel_pair.h b/mojo/edk/embedder/named_platform_channel_pair.h new file mode 100644 index 0000000..5a83ae3 --- /dev/null +++ b/mojo/edk/embedder/named_platform_channel_pair.h @@ -0,0 +1,73 @@ +// 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_EDK_EMBEDDER_NAMED_PLATFORM_CHANNEL_PAIR_H_ +#define MOJO_EDK_EMBEDDER_NAMED_PLATFORM_CHANNEL_PAIR_H_ + +#include + +#include "base/macros.h" +#include "base/strings/string16.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace base { +class CommandLine; +} + +namespace mojo { +namespace edk { + +// This is used to create a named bidirectional pipe to connect new child +// processes. The resulting server handle should be passed to the EDK, and the +// child end passed as a pipe name on the command line to the child process. The +// child process can then retrieve the pipe name from the command line and +// resolve it into a client handle. +class MOJO_SYSTEM_IMPL_EXPORT NamedPlatformChannelPair { + public: + struct Options { +#if defined(OS_WIN) + // If non-empty, a security descriptor to use when creating the pipe. If + // empty, a default security descriptor will be used. See + // kDefaultSecurityDescriptor in named_platform_handle_utils_win.cc. + base::string16 security_descriptor; +#endif + }; + + NamedPlatformChannelPair(const Options& options = {}); + ~NamedPlatformChannelPair(); + + // Note: It is NOT acceptable to use this handle as a generic pipe channel. It + // MUST be passed to PendingProcessConnection::Connect() only. + ScopedPlatformHandle PassServerHandle(); + + // To be called in the child process, after the parent process called + // |PrepareToPassClientHandleToChildProcess()| and launched the child (using + // the provided data), to create a client handle connected to the server + // handle (in the parent process). + static ScopedPlatformHandle PassClientHandleFromParentProcess( + const base::CommandLine& command_line); + + // Prepares to pass the client channel to a new child process, to be launched + // using |LaunchProcess()| (from base/launch.h). Modifies |*command_line| and + // |*handle_passing_info| as needed. + // Note: For Windows, this method only works on Vista and later. + void PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line) const; + + const NamedPlatformHandle& handle() const { return pipe_handle_; } + + private: + NamedPlatformHandle pipe_handle_; + ScopedPlatformHandle server_handle_; + + DISALLOW_COPY_AND_ASSIGN(NamedPlatformChannelPair); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_NAMED_PLATFORM_CHANNEL_PAIR_H_ diff --git a/mojo/edk/embedder/named_platform_channel_pair_win.cc b/mojo/edk/embedder/named_platform_channel_pair_win.cc new file mode 100644 index 0000000..96589ff --- /dev/null +++ b/mojo/edk/embedder/named_platform_channel_pair_win.cc @@ -0,0 +1,89 @@ +// 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/edk/embedder/named_platform_channel_pair.h" + +#include + +#include +#include +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/windows_version.h" +#include "mojo/edk/embedder/named_platform_handle_utils.h" +#include "mojo/edk/embedder/platform_handle.h" + +namespace mojo { +namespace edk { + +namespace { + +const char kMojoNamedPlatformChannelPipeSwitch[] = + "mojo-named-platform-channel-pipe"; + +std::wstring GeneratePipeName() { + return base::StringPrintf(L"%u.%u.%I64u", GetCurrentProcessId(), + GetCurrentThreadId(), base::RandUint64()); +} + +} // namespace + +NamedPlatformChannelPair::NamedPlatformChannelPair( + const NamedPlatformChannelPair::Options& options) + : pipe_handle_(GeneratePipeName()) { + CreateServerHandleOptions server_handle_options; + server_handle_options.security_descriptor = options.security_descriptor; + server_handle_options.enforce_uniqueness = true; + server_handle_ = CreateServerHandle(pipe_handle_, server_handle_options); + PCHECK(server_handle_.is_valid()); +} + +NamedPlatformChannelPair::~NamedPlatformChannelPair() {} + +ScopedPlatformHandle NamedPlatformChannelPair::PassServerHandle() { + return std::move(server_handle_); +} + +// static +ScopedPlatformHandle +NamedPlatformChannelPair::PassClientHandleFromParentProcess( + const base::CommandLine& command_line) { + // In order to support passing the pipe name on the command line, the pipe + // handle is lazily created from the pipe name when requested. + NamedPlatformHandle handle( + command_line.GetSwitchValueNative(kMojoNamedPlatformChannelPipeSwitch)); + + if (!handle.is_valid()) + return ScopedPlatformHandle(); + + return CreateClientHandle(handle); +} + +void NamedPlatformChannelPair::PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line) const { + DCHECK(command_line); + + // Log a warning if the command line already has the switch, but "clobber" it + // anyway, since it's reasonably likely that all the switches were just copied + // from the parent. + LOG_IF(WARNING, + command_line->HasSwitch(kMojoNamedPlatformChannelPipeSwitch)) + << "Child command line already has switch --" + << kMojoNamedPlatformChannelPipeSwitch << "=" + << command_line->GetSwitchValueNative( + kMojoNamedPlatformChannelPipeSwitch); + // (Any existing switch won't actually be removed from the command line, but + // the last one appended takes precedence.) + command_line->AppendSwitchNative(kMojoNamedPlatformChannelPipeSwitch, + pipe_handle_.name); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/named_platform_handle.h b/mojo/edk/embedder/named_platform_handle.h new file mode 100644 index 0000000..15ca656 --- /dev/null +++ b/mojo/edk/embedder/named_platform_handle.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_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_H_ +#define MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_H_ + +#include + +#include "base/strings/string16.h" +#include "base/strings/string_piece.h" +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +#if defined(OS_POSIX) +struct MOJO_SYSTEM_IMPL_EXPORT NamedPlatformHandle { + NamedPlatformHandle() {} + explicit NamedPlatformHandle(const base::StringPiece& name) + : name(name.as_string()) {} + + bool is_valid() const { return !name.empty(); } + + std::string name; +}; +#elif defined(OS_WIN) +struct MOJO_SYSTEM_IMPL_EXPORT NamedPlatformHandle { + NamedPlatformHandle() {} + explicit NamedPlatformHandle(const base::StringPiece& name) + : name(base::UTF8ToUTF16(name)) {} + + explicit NamedPlatformHandle(const base::StringPiece16& name) + : name(name.as_string()) {} + + bool is_valid() const { return !name.empty(); } + + base::string16 pipe_name() const { return L"\\\\.\\pipe\\mojo." + name; } + + base::string16 name; +}; +#else +#error "Platform not yet supported." +#endif + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_H_ diff --git a/mojo/edk/embedder/named_platform_handle_utils.h b/mojo/edk/embedder/named_platform_handle_utils.h new file mode 100644 index 0000000..b767ea0 --- /dev/null +++ b/mojo/edk/embedder/named_platform_handle_utils.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_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_UTILS_H_ +#define MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_UTILS_H_ + +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +#if defined(OS_WIN) +#include "base/strings/string16.h" +#endif + +namespace mojo { +namespace edk { + +struct NamedPlatformHandle; + +#if defined(OS_POSIX) + +// The maximum length of the name of a unix domain socket. The standard size on +// linux is 108, mac is 104. To maintain consistency across platforms we +// standardize on the smaller value. +const size_t kMaxSocketNameLength = 104; + +#endif + +struct CreateServerHandleOptions { +#if defined(OS_WIN) + // If true, creating a server handle will fail if another pipe with the same + // name exists. + bool enforce_uniqueness = true; + + // If non-empty, a security descriptor to use when creating the pipe. If + // empty, a default security descriptor will be used. See + // kDefaultSecurityDescriptor in named_platform_handle_utils_win.cc. + base::string16 security_descriptor; +#endif +}; + +// Creates a client platform handle from |handle|. This may block until |handle| +// is ready to receive connections. +MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle +CreateClientHandle(const NamedPlatformHandle& handle); + +// Creates a server platform handle from |handle|. +MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle +CreateServerHandle(const NamedPlatformHandle& handle, + const CreateServerHandleOptions& options = {}); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_NAMED_PLATFORM_HANDLE_UTILS_H_ diff --git a/mojo/edk/embedder/named_platform_handle_utils_posix.cc b/mojo/edk/embedder/named_platform_handle_utils_posix.cc new file mode 100644 index 0000000..056f4d6 --- /dev/null +++ b/mojo/edk/embedder/named_platform_handle_utils_posix.cc @@ -0,0 +1,141 @@ +// 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/edk/embedder/named_platform_handle_utils.h" + +#include +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "mojo/edk/embedder/named_platform_handle.h" + +namespace mojo { +namespace edk { +namespace { + +// This function fills in |unix_addr| with the appropriate data for the socket, +// and sets |unix_addr_len| to the length of the data therein. +// Returns true on success, or false on failure (typically because |handle.name| +// violated the naming rules). +bool MakeUnixAddr(const NamedPlatformHandle& handle, + struct sockaddr_un* unix_addr, + size_t* unix_addr_len) { + DCHECK(unix_addr); + DCHECK(unix_addr_len); + DCHECK(handle.is_valid()); + + // We reject handle.name.length() == kMaxSocketNameLength to make room for the + // NUL terminator at the end of the string. + if (handle.name.length() >= kMaxSocketNameLength) { + LOG(ERROR) << "Socket name too long: " << handle.name; + return false; + } + + // Create unix_addr structure. + memset(unix_addr, 0, sizeof(struct sockaddr_un)); + unix_addr->sun_family = AF_UNIX; + strncpy(unix_addr->sun_path, handle.name.c_str(), kMaxSocketNameLength); + *unix_addr_len = + offsetof(struct sockaddr_un, sun_path) + handle.name.length(); + return true; +} + +// This function creates a unix domain socket, and set it as non-blocking. +// If successful, this returns a ScopedPlatformHandle containing the socket. +// Otherwise, this returns an invalid ScopedPlatformHandle. +ScopedPlatformHandle CreateUnixDomainSocket(bool needs_connection) { + // Create the unix domain socket. + PlatformHandle socket_handle(socket(AF_UNIX, SOCK_STREAM, 0)); + socket_handle.needs_connection = needs_connection; + ScopedPlatformHandle handle(socket_handle); + if (!handle.is_valid()) { + PLOG(ERROR) << "Failed to create AF_UNIX socket."; + return ScopedPlatformHandle(); + } + + // Now set it as non-blocking. + if (!base::SetNonBlocking(handle.get().handle)) { + PLOG(ERROR) << "base::SetNonBlocking() failed " << handle.get().handle; + return ScopedPlatformHandle(); + } + return handle; +} + +} // namespace + +ScopedPlatformHandle CreateClientHandle( + const NamedPlatformHandle& named_handle) { + if (!named_handle.is_valid()) + return ScopedPlatformHandle(); + + struct sockaddr_un unix_addr; + size_t unix_addr_len; + if (!MakeUnixAddr(named_handle, &unix_addr, &unix_addr_len)) + return ScopedPlatformHandle(); + + ScopedPlatformHandle handle = CreateUnixDomainSocket(false); + if (!handle.is_valid()) + return ScopedPlatformHandle(); + + if (HANDLE_EINTR(connect(handle.get().handle, + reinterpret_cast(&unix_addr), + unix_addr_len)) < 0) { + PLOG(ERROR) << "connect " << named_handle.name; + return ScopedPlatformHandle(); + } + + return handle; +} + +ScopedPlatformHandle CreateServerHandle( + const NamedPlatformHandle& named_handle, + const CreateServerHandleOptions& options) { + if (!named_handle.is_valid()) + return ScopedPlatformHandle(); + + // Make sure the path we need exists. + base::FilePath socket_dir = base::FilePath(named_handle.name).DirName(); + if (!base::CreateDirectory(socket_dir)) { + LOG(ERROR) << "Couldn't create directory: " << socket_dir.value(); + return ScopedPlatformHandle(); + } + + // Delete any old FS instances. + if (unlink(named_handle.name.c_str()) < 0 && errno != ENOENT) { + PLOG(ERROR) << "unlink " << named_handle.name; + return ScopedPlatformHandle(); + } + + struct sockaddr_un unix_addr; + size_t unix_addr_len; + if (!MakeUnixAddr(named_handle, &unix_addr, &unix_addr_len)) + return ScopedPlatformHandle(); + + ScopedPlatformHandle handle = CreateUnixDomainSocket(true); + if (!handle.is_valid()) + return ScopedPlatformHandle(); + + // Bind the socket. + if (bind(handle.get().handle, reinterpret_cast(&unix_addr), + unix_addr_len) < 0) { + PLOG(ERROR) << "bind " << named_handle.name; + return ScopedPlatformHandle(); + } + + // Start listening on the socket. + if (listen(handle.get().handle, SOMAXCONN) < 0) { + PLOG(ERROR) << "listen " << named_handle.name; + unlink(named_handle.name.c_str()); + return ScopedPlatformHandle(); + } + return handle; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/named_platform_handle_utils_win.cc b/mojo/edk/embedder/named_platform_handle_utils_win.cc new file mode 100644 index 0000000..a145847 --- /dev/null +++ b/mojo/edk/embedder/named_platform_handle_utils_win.cc @@ -0,0 +1,95 @@ +// 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/edk/embedder/named_platform_handle_utils.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "base/win/windows_version.h" +#include "mojo/edk/embedder/named_platform_handle.h" + +namespace mojo { +namespace edk { +namespace { + +// A DACL to grant: +// GA = Generic All +// access to: +// SY = LOCAL_SYSTEM +// BA = BUILTIN_ADMINISTRATORS +// OW = OWNER_RIGHTS +constexpr base::char16 kDefaultSecurityDescriptor[] = + L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;OW)"; + +} // namespace + +ScopedPlatformHandle CreateClientHandle( + const NamedPlatformHandle& named_handle) { + if (!named_handle.is_valid()) + return ScopedPlatformHandle(); + + base::string16 pipe_name = named_handle.pipe_name(); + + // Note: This may block. + if (!WaitNamedPipeW(pipe_name.c_str(), NMPWAIT_USE_DEFAULT_WAIT)) + return ScopedPlatformHandle(); + + const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE; + // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate + // the client. + const DWORD kFlags = + SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | FILE_FLAG_OVERLAPPED; + ScopedPlatformHandle handle( + PlatformHandle(CreateFileW(pipe_name.c_str(), kDesiredAccess, + 0, // No sharing. + nullptr, OPEN_EXISTING, kFlags, + nullptr))); // No template file. + // The server may have stopped accepting a connection between the + // WaitNamedPipe() and CreateFile(). If this occurs, an invalid handle is + // returned. + DPLOG_IF(ERROR, !handle.is_valid()) + << "Named pipe " << named_handle.pipe_name() + << " could not be opened after WaitNamedPipe succeeded"; + return handle; +} + +ScopedPlatformHandle CreateServerHandle( + const NamedPlatformHandle& named_handle, + const CreateServerHandleOptions& options) { + if (!named_handle.is_valid()) + return ScopedPlatformHandle(); + + PSECURITY_DESCRIPTOR security_desc = nullptr; + ULONG security_desc_len = 0; + PCHECK(ConvertStringSecurityDescriptorToSecurityDescriptor( + options.security_descriptor.empty() ? kDefaultSecurityDescriptor + : options.security_descriptor.c_str(), + SDDL_REVISION_1, &security_desc, &security_desc_len)); + std::unique_ptr p(security_desc, ::LocalFree); + SECURITY_ATTRIBUTES security_attributes = {sizeof(SECURITY_ATTRIBUTES), + security_desc, FALSE}; + + const DWORD kOpenMode = options.enforce_uniqueness + ? PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | + FILE_FLAG_FIRST_PIPE_INSTANCE + : PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED; + const DWORD kPipeMode = + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_REJECT_REMOTE_CLIENTS; + PlatformHandle handle( + CreateNamedPipeW(named_handle.pipe_name().c_str(), kOpenMode, kPipeMode, + options.enforce_uniqueness ? 1 : 255, // Max instances. + 4096, // Out buffer size. + 4096, // In buffer size. + 5000, // Timeout in milliseconds. + &security_attributes)); + handle.needs_connection = true; + return ScopedPlatformHandle(handle); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/pending_process_connection.cc b/mojo/edk/embedder/pending_process_connection.cc new file mode 100644 index 0000000..d6be76e --- /dev/null +++ b/mojo/edk/embedder/pending_process_connection.cc @@ -0,0 +1,50 @@ +// 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/edk/embedder/pending_process_connection.h" + +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core.h" + +namespace mojo { +namespace edk { + +PendingProcessConnection::PendingProcessConnection() + : process_token_(GenerateRandomToken()) { + DCHECK(internal::g_core); +} + +PendingProcessConnection::~PendingProcessConnection() { + if (has_message_pipes_ && !connected_) { + DCHECK(internal::g_core); + internal::g_core->ChildLaunchFailed(process_token_); + } +} + +ScopedMessagePipeHandle PendingProcessConnection::CreateMessagePipe( + std::string* token) { + has_message_pipes_ = true; + DCHECK(internal::g_core); + *token = GenerateRandomToken(); + return internal::g_core->CreateParentMessagePipe(*token, process_token_); +} + +void PendingProcessConnection::Connect( + base::ProcessHandle process, + ConnectionParams connection_params, + const ProcessErrorCallback& error_callback) { + // It's now safe to avoid cleanup in the destructor, as the lifetime of any + // associated resources is effectively bound to the |channel| passed to + // AddChild() below. + DCHECK(!connected_); + connected_ = true; + + DCHECK(internal::g_core); + internal::g_core->AddChild(process, std::move(connection_params), + process_token_, error_callback); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/pending_process_connection.h b/mojo/edk/embedder/pending_process_connection.h new file mode 100644 index 0000000..ca18227 --- /dev/null +++ b/mojo/edk/embedder/pending_process_connection.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_EDK_EMBEDDER_PENDING_PROCESS_CONNECTION_H_ +#define MOJO_EDK_EMBEDDER_PENDING_PROCESS_CONNECTION_H_ + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/process/process_handle.h" +#include "mojo/edk/embedder/connection_params.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace mojo { +namespace edk { + +using ProcessErrorCallback = base::Callback; + +// Represents a potential connection to an external process. Use this object +// to make other processes reachable from this one via Mojo IPC. Typical usage +// might look something like: +// +// PendingProcessConnection connection; +// +// std::string pipe_token; +// ScopedMessagePipeHandle pipe = connection.CreateMessagePipe(&pipe_token); +// +// // New pipes to the process are fully functional and can be used right +// // away, even if the process doesn't exist yet. +// GoDoSomethingInteresting(std::move(pipe)); +// +// ScopedPlatformChannelPair channel; +// +// // Give the pipe token to the child process via command-line. +// child_command_line.AppendSwitchASCII("yer-pipe", pipe_token); +// +// // Magic child process launcher which gives one end of the pipe to the +// // new process. +// LaunchProcess(child_command_line, channel.PassClientHandle()); +// +// // Some time later... +// connection.Connect(new_process, channel.PassServerHandle()); +// +// If at any point during the above process, |connection| is destroyed before +// Connect() can be called, |pipe| will imminently behave as if its peer has +// been closed. +// +// Otherwise, if the remote process in this example eventually calls: +// +// mojo::edk::SetParentPipeHandle(std::move(client_channel_handle)); +// +// std::string token = command_line.GetSwitchValueASCII("yer-pipe"); +// ScopedMessagePipeHandle pipe = mojo::edk::CreateChildMessagePipe(token); +// +// it will be connected to this process, and its |pipe| will be connected to +// this process's |pipe|. +// +// If the remote process exits or otherwise closes its client channel handle +// before calling CreateChildMessagePipe for a given message pipe token, +// this process's end of the corresponding message pipe will imminently behave +// as if its peer has been closed. +// +class MOJO_SYSTEM_IMPL_EXPORT PendingProcessConnection { + public: + PendingProcessConnection(); + ~PendingProcessConnection(); + + // Creates a message pipe associated with a new globally unique string value + // which will be placed in |*token|. + // + // The other end of the new pipe is obtainable in the remote process (or in + // this process, to facilitate "single-process mode" in some applications) by + // passing the new |*token| value to mojo::edk::CreateChildMessagePipe. It's + // the caller's responsibility to communicate the value of |*token| to the + // remote process by any means available, e.g. a command-line argument on + // process launch, or some other out-of-band communication channel for an + // existing process. + // + // NOTES: This may be called any number of times to create multiple message + // pipes to the same remote process. This call ALWAYS succeeds, returning + // a valid message pipe handle and populating |*token| with a new unique + // string value. + ScopedMessagePipeHandle CreateMessagePipe(std::string* token); + + // Connects to the process. This must be called at most once, with the process + // handle in |process|. + // + // |connection_param| contains the platform handle of an OS pipe which can be + // used to communicate with the connected process. The other end of that pipe + // must ultimately be passed to mojo::edk::SetParentPipeHandle in the remote + // process, and getting that end of the pipe into the other process is the + // embedder's responsibility. + // + // If this method is not called by the time the PendingProcessConnection is + // destroyed, it's assumed that the process is unavailable (e.g. process + // launch failed or the process has otherwise been terminated early), and + // any associated resources, such as remote endpoints of messages pipes + // created by CreateMessagePipe above) will be cleaned up at that time. + void Connect( + base::ProcessHandle process, + ConnectionParams connection_params, + const ProcessErrorCallback& error_callback = ProcessErrorCallback()); + + private: + // A GUID representing a potential new process to be connected to this one. + const std::string process_token_; + + // Indicates whether this object has been used to create new message pipes. + bool has_message_pipes_ = false; + + // Indicates whether Connect() has been called yet. + bool connected_ = false; + + DISALLOW_COPY_AND_ASSIGN(PendingProcessConnection); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PENDING_PROCESS_CONNECTION_H_ diff --git a/mojo/edk/embedder/platform_channel_pair.cc b/mojo/edk/embedder/platform_channel_pair.cc new file mode 100644 index 0000000..ee1905a --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair.cc @@ -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. + +#include "mojo/edk/embedder/platform_channel_pair.h" + +#include + +#include "base/logging.h" + +namespace mojo { +namespace edk { + +const char PlatformChannelPair::kMojoPlatformChannelHandleSwitch[] = + "mojo-platform-channel-handle"; + +PlatformChannelPair::~PlatformChannelPair() { +} + +ScopedPlatformHandle PlatformChannelPair::PassServerHandle() { + return std::move(server_handle_); +} + +ScopedPlatformHandle PlatformChannelPair::PassClientHandle() { + return std::move(client_handle_); +} + +void PlatformChannelPair::ChildProcessLaunched() { + DCHECK(client_handle_.is_valid()); + client_handle_.reset(); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_pair.h b/mojo/edk/embedder/platform_channel_pair.h new file mode 100644 index 0000000..9c93f76 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair.h @@ -0,0 +1,106 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ + +#include + +#include "base/macros.h" +#include "base/process/launch.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace base { +class CommandLine; +} + +namespace mojo { +namespace edk { + +// It would be nice to refactor base/process/launch.h to have a more platform- +// independent way of representing handles that are passed to child processes. +#if defined(OS_WIN) +using HandlePassingInformation = base::HandlesToInheritVector; +#elif defined(OS_POSIX) +using HandlePassingInformation = base::FileHandleMappingVector; +#else +#error "Unsupported." +#endif + +// This is used to create a pair of |PlatformHandle|s that are connected by a +// suitable (platform-specific) bidirectional "pipe" (e.g., socket on POSIX, +// named pipe on Windows). The resulting handles can then be used in the same +// process (e.g., in tests) or between processes. (The "server" handle is the +// one that will be used in the process that created the pair, whereas the +// "client" handle is the one that will be used in a different process.) +// +// This class provides facilities for passing the client handle to a child +// process. The parent should call |PrepareToPassClientHandlelToChildProcess()| +// to get the data needed to do this, spawn the child using that data, and then +// call |ChildProcessLaunched()|. Note that on Windows this facility (will) only +// work on Vista and later (TODO(vtl)). +// +// Note: |PlatformChannelPair()|, |PassClientHandleFromParentProcess()| and +// |PrepareToPassClientHandleToChildProcess()| have platform-specific +// implementations. +// +// Note: On POSIX platforms, to write to the "pipe", use +// |PlatformChannel{Write,Writev}()| (from platform_channel_utils_posix.h) +// instead of |write()|, |writev()|, etc. Otherwise, you have to worry about +// platform differences in suppressing |SIGPIPE|. +class MOJO_SYSTEM_IMPL_EXPORT PlatformChannelPair { + public: + static const char kMojoPlatformChannelHandleSwitch[]; + + // If |client_is_blocking| is true, then the client handle only supports + // blocking reads and writes. The default is nonblocking. + PlatformChannelPair(bool client_is_blocking = false); + ~PlatformChannelPair(); + + ScopedPlatformHandle PassServerHandle(); + + // For in-process use (e.g., in tests or to pass over another channel). + ScopedPlatformHandle PassClientHandle(); + + // To be called in the child process, after the parent process called + // |PrepareToPassClientHandleToChildProcess()| and launched the child (using + // the provided data), to create a client handle connected to the server + // handle (in the parent process). + // TODO(jcivelli): remove the command_line param. http://crbug.com/670106 + static ScopedPlatformHandle PassClientHandleFromParentProcess( + const base::CommandLine& command_line); + + // Like above, but gets the handle from the passed in string. + static ScopedPlatformHandle PassClientHandleFromParentProcessFromString( + const std::string& value); + + // Prepares to pass the client channel to a new child process, to be launched + // using |LaunchProcess()| (from base/launch.h). Modifies |*command_line| and + // |*handle_passing_info| as needed. + // Note: For Windows, this method only works on Vista and later. + void PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + HandlePassingInformation* handle_passing_info) const; + + // Like above, but returns a string instead of changing the command line. + std::string PrepareToPassClientHandleToChildProcessAsString( + HandlePassingInformation* handle_passing_info) const; + + // To be called once the child process has been successfully launched, to do + // any cleanup necessary. + void ChildProcessLaunched(); + + private: + ScopedPlatformHandle server_handle_; + ScopedPlatformHandle client_handle_; + + DISALLOW_COPY_AND_ASSIGN(PlatformChannelPair); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_PAIR_H_ diff --git a/mojo/edk/embedder/platform_channel_pair_posix.cc b/mojo/edk/embedder/platform_channel_pair_posix.cc new file mode 100644 index 0000000..fe9f8f5 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair_posix.cc @@ -0,0 +1,172 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_channel_pair.h" + +#include +#include +#include +#include +#include + +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/posix/global_descriptors.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/platform_handle.h" + +#if !defined(OS_NACL_SFI) +#include +#else +#include "native_client/src/public/imc_syscalls.h" +#endif + +#if !defined(SO_PEEK_OFF) +#define SO_PEEK_OFF 42 +#endif + +namespace mojo { +namespace edk { + +namespace { + +#if defined(OS_ANDROID) || defined(__ANDROID__) +enum { + // Leave room for any other descriptors defined in content for example. + // TODO(jcivelli): consider changing base::GlobalDescriptors to generate a + // key when setting the file descriptor (http://crbug.com/676442). + kAndroidClientHandleDescriptor = + base::GlobalDescriptors::kBaseDescriptor + 10000, +}; +#else +bool IsTargetDescriptorUsed( + const base::FileHandleMappingVector& file_handle_mapping, + int target_fd) { + for (size_t i = 0; i < file_handle_mapping.size(); i++) { + if (file_handle_mapping[i].second == target_fd) + return true; + } + return false; +} +#endif + +} // namespace + +PlatformChannelPair::PlatformChannelPair(bool client_is_blocking) { + // Create the Unix domain socket. + int fds[2]; + // TODO(vtl): Maybe fail gracefully if |socketpair()| fails. + +#if defined(OS_NACL_SFI) + PCHECK(imc_socketpair(fds) == 0); +#else + PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); + + // Set the ends to nonblocking. + PCHECK(fcntl(fds[0], F_SETFL, O_NONBLOCK) == 0); + if (!client_is_blocking) + PCHECK(fcntl(fds[1], F_SETFL, O_NONBLOCK) == 0); + +#if defined(OS_MACOSX) + // This turns off |SIGPIPE| when writing to a closed socket (causing it to + // fail with |EPIPE| instead). On Linux, we have to use |send...()| with + // |MSG_NOSIGNAL| -- which is not supported on Mac -- instead. + int no_sigpipe = 1; + PCHECK(setsockopt(fds[0], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, + sizeof(no_sigpipe)) == 0); + PCHECK(setsockopt(fds[1], SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, + sizeof(no_sigpipe)) == 0); +#endif // defined(OS_MACOSX) +#endif // defined(OS_NACL_SFI) + + server_handle_.reset(PlatformHandle(fds[0])); + DCHECK(server_handle_.is_valid()); + client_handle_.reset(PlatformHandle(fds[1])); + DCHECK(client_handle_.is_valid()); +} + +// static +ScopedPlatformHandle PlatformChannelPair::PassClientHandleFromParentProcess( + const base::CommandLine& command_line) { + std::string client_fd_string = + command_line.GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + return PassClientHandleFromParentProcessFromString(client_fd_string); +} + +ScopedPlatformHandle +PlatformChannelPair::PassClientHandleFromParentProcessFromString( + const std::string& value) { + int client_fd = -1; +#if defined(OS_ANDROID) || defined(__ANDROID__) + base::GlobalDescriptors::Key key = -1; + if (value.empty() || !base::StringToUint(value, &key)) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; + return ScopedPlatformHandle(); + } + client_fd = base::GlobalDescriptors::GetInstance()->Get(key); +#else + if (value.empty() || + !base::StringToInt(value, &client_fd) || + client_fd < base::GlobalDescriptors::kBaseDescriptor) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; + return ScopedPlatformHandle(); + } +#endif + return ScopedPlatformHandle(PlatformHandle(client_fd)); +} + +void PlatformChannelPair::PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + base::FileHandleMappingVector* handle_passing_info) const { + DCHECK(command_line); + + // Log a warning if the command line already has the switch, but "clobber" it + // anyway, since it's reasonably likely that all the switches were just copied + // from the parent. + LOG_IF(WARNING, command_line->HasSwitch(kMojoPlatformChannelHandleSwitch)) + << "Child command line already has switch --" + << kMojoPlatformChannelHandleSwitch << "=" + << command_line->GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + // (Any existing switch won't actually be removed from the command line, but + // the last one appended takes precedence.) + command_line->AppendSwitchASCII( + kMojoPlatformChannelHandleSwitch, + PrepareToPassClientHandleToChildProcessAsString(handle_passing_info)); +} + +std::string +PlatformChannelPair::PrepareToPassClientHandleToChildProcessAsString( + HandlePassingInformation* handle_passing_info) const { +#if defined(OS_ANDROID) || defined(__ANDROID__) + int fd = client_handle_.get().handle; + handle_passing_info->push_back( + std::pair(fd, kAndroidClientHandleDescriptor)); + return base::UintToString(kAndroidClientHandleDescriptor); +#else + DCHECK(handle_passing_info); + // This is an arbitrary sanity check. (Note that this guarantees that the loop + // below will terminate sanely.) + CHECK_LT(handle_passing_info->size(), 1000u); + + DCHECK(client_handle_.is_valid()); + + // Find a suitable FD to map our client handle to in the child process. + // This has quadratic time complexity in the size of |*handle_passing_info|, + // but |*handle_passing_info| should be very small (usually/often empty). + int target_fd = base::GlobalDescriptors::kBaseDescriptor; + while (IsTargetDescriptorUsed(*handle_passing_info, target_fd)) + target_fd++; + + handle_passing_info->push_back( + std::pair(client_handle_.get().handle, target_fd)); + return base::IntToString(target_fd); +#endif +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc b/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc new file mode 100644 index 0000000..a3fd275 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair_posix_unittest.cc @@ -0,0 +1,261 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_channel_pair.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/edk/embedder/platform_channel_utils_posix.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +void WaitReadable(PlatformHandle h) { + struct pollfd pfds = {}; + pfds.fd = h.handle; + pfds.events = POLLIN; + CHECK_EQ(poll(&pfds, 1, -1), 1); +} + +class PlatformChannelPairPosixTest : public testing::Test { + public: + PlatformChannelPairPosixTest() {} + ~PlatformChannelPairPosixTest() override {} + + void SetUp() override { + // Make sure |SIGPIPE| isn't being ignored. + struct sigaction action = {}; + action.sa_handler = SIG_DFL; + ASSERT_EQ(0, sigaction(SIGPIPE, &action, &old_action_)); + } + + void TearDown() override { + // Restore the |SIGPIPE| handler. + ASSERT_EQ(0, sigaction(SIGPIPE, &old_action_, nullptr)); + } + + private: + struct sigaction old_action_; + + DISALLOW_COPY_AND_ASSIGN(PlatformChannelPairPosixTest); +}; + +TEST_F(PlatformChannelPairPosixTest, NoSigPipe) { + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); + + // Write to the client. + static const char kHello[] = "hello"; + EXPECT_EQ(static_cast(sizeof(kHello)), + write(client_handle.get().handle, kHello, sizeof(kHello))); + + // Close the client. + client_handle.reset(); + + // Read from the server; this should be okay. + char buffer[100] = {}; + EXPECT_EQ(static_cast(sizeof(kHello)), + read(server_handle.get().handle, buffer, sizeof(buffer))); + EXPECT_STREQ(kHello, buffer); + + // Try reading again. + ssize_t result = read(server_handle.get().handle, buffer, sizeof(buffer)); + // We should probably get zero (for "end of file"), but -1 would also be okay. + EXPECT_TRUE(result == 0 || result == -1); + if (result == -1) + PLOG(WARNING) << "read (expected 0 for EOF)"; + + // Test our replacement for |write()|/|send()|. + result = PlatformChannelWrite(server_handle.get(), kHello, sizeof(kHello)); + EXPECT_EQ(-1, result); + if (errno != EPIPE) + PLOG(WARNING) << "write (expected EPIPE)"; + + // Test our replacement for |writev()|/|sendv()|. + struct iovec iov[2] = {{const_cast(kHello), sizeof(kHello)}, + {const_cast(kHello), sizeof(kHello)}}; + result = PlatformChannelWritev(server_handle.get(), iov, 2); + EXPECT_EQ(-1, result); + if (errno != EPIPE) + PLOG(WARNING) << "write (expected EPIPE)"; +} + +TEST_F(PlatformChannelPairPosixTest, SendReceiveData) { + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); + + for (size_t i = 0; i < 10; i++) { + std::string send_string(1 << i, 'A' + i); + + EXPECT_EQ(static_cast(send_string.size()), + PlatformChannelWrite(server_handle.get(), send_string.data(), + send_string.size())); + + WaitReadable(client_handle.get()); + + char buf[10000] = {}; + std::deque received_handles; + ssize_t result = PlatformChannelRecvmsg(client_handle.get(), buf, + sizeof(buf), &received_handles); + EXPECT_EQ(static_cast(send_string.size()), result); + EXPECT_EQ(send_string, std::string(buf, static_cast(result))); + EXPECT_TRUE(received_handles.empty()); + } +} + +TEST_F(PlatformChannelPairPosixTest, SendReceiveFDs) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + static const char kHello[] = "hello"; + + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); + +// Reduce the number of FDs opened on OS X to avoid test flake. +#if defined(OS_MACOSX) + const size_t kNumHandlesToSend = kPlatformChannelMaxNumHandles / 2; +#else + const size_t kNumHandlesToSend = kPlatformChannelMaxNumHandles; +#endif + + for (size_t i = 1; i < kNumHandlesToSend; i++) { + // Make |i| files, with the j-th file consisting of j copies of the digit + // |c|. + const char c = '0' + (i % 10); + ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector); + for (size_t j = 1; j <= i; j++) { + base::FilePath unused; + base::ScopedFILE fp( + base::CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + ASSERT_TRUE(fp); + ASSERT_EQ(j, fwrite(std::string(j, c).data(), 1, j, fp.get())); + platform_handles->push_back( + test::PlatformHandleFromFILE(std::move(fp)).release()); + ASSERT_TRUE(platform_handles->back().is_valid()); + } + + // Send the FDs (+ "hello"). + struct iovec iov = {const_cast(kHello), sizeof(kHello)}; + // We assume that the |sendmsg()| actually sends all the data. + EXPECT_EQ(static_cast(sizeof(kHello)), + PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1, + &platform_handles->at(0), + platform_handles->size())); + + WaitReadable(client_handle.get()); + + char buf[10000] = {}; + std::deque received_handles; + // We assume that the |recvmsg()| actually reads all the data. + EXPECT_EQ(static_cast(sizeof(kHello)), + PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), + &received_handles)); + EXPECT_STREQ(kHello, buf); + EXPECT_EQ(i, received_handles.size()); + + for (size_t j = 0; !received_handles.empty(); j++) { + base::ScopedFILE fp(test::FILEFromPlatformHandle( + ScopedPlatformHandle(received_handles.front()), "rb")); + received_handles.pop_front(); + ASSERT_TRUE(fp); + rewind(fp.get()); + char read_buf[kNumHandlesToSend]; + size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get()); + EXPECT_EQ(j + 1, bytes_read); + EXPECT_EQ(std::string(j + 1, c), std::string(read_buf, bytes_read)); + } + } +} + +TEST_F(PlatformChannelPairPosixTest, AppendReceivedFDs) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + static const char kHello[] = "hello"; + + PlatformChannelPair channel_pair; + ScopedPlatformHandle server_handle = channel_pair.PassServerHandle(); + ScopedPlatformHandle client_handle = channel_pair.PassClientHandle(); + + const std::string file_contents("hello world"); + + { + base::FilePath unused; + base::ScopedFILE fp( + base::CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + ASSERT_TRUE(fp); + ASSERT_EQ(file_contents.size(), + fwrite(file_contents.data(), 1, file_contents.size(), fp.get())); + ScopedPlatformHandleVectorPtr platform_handles(new PlatformHandleVector); + platform_handles->push_back( + test::PlatformHandleFromFILE(std::move(fp)).release()); + ASSERT_TRUE(platform_handles->back().is_valid()); + + // Send the FD (+ "hello"). + struct iovec iov = {const_cast(kHello), sizeof(kHello)}; + // We assume that the |sendmsg()| actually sends all the data. + EXPECT_EQ(static_cast(sizeof(kHello)), + PlatformChannelSendmsgWithHandles(server_handle.get(), &iov, 1, + &platform_handles->at(0), + platform_handles->size())); + } + + WaitReadable(client_handle.get()); + + // Start with an invalid handle in the deque. + std::deque received_handles; + received_handles.push_back(PlatformHandle()); + + char buf[100] = {}; + // We assume that the |recvmsg()| actually reads all the data. + EXPECT_EQ(static_cast(sizeof(kHello)), + PlatformChannelRecvmsg(client_handle.get(), buf, sizeof(buf), + &received_handles)); + EXPECT_STREQ(kHello, buf); + ASSERT_EQ(2u, received_handles.size()); + EXPECT_FALSE(received_handles[0].is_valid()); + EXPECT_TRUE(received_handles[1].is_valid()); + + { + base::ScopedFILE fp(test::FILEFromPlatformHandle( + ScopedPlatformHandle(received_handles[1]), "rb")); + received_handles[1] = PlatformHandle(); + ASSERT_TRUE(fp); + rewind(fp.get()); + char read_buf[100]; + size_t bytes_read = fread(read_buf, 1, sizeof(read_buf), fp.get()); + EXPECT_EQ(file_contents.size(), bytes_read); + EXPECT_EQ(file_contents, std::string(read_buf, bytes_read)); + } +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_pair_win.cc b/mojo/edk/embedder/platform_channel_pair_win.cc new file mode 100644 index 0000000..f523ade --- /dev/null +++ b/mojo/edk/embedder/platform_channel_pair_win.cc @@ -0,0 +1,123 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_channel_pair.h" + +#include + +#include + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/win/windows_version.h" +#include "mojo/edk/embedder/platform_handle.h" + +namespace mojo { +namespace edk { + +namespace { + +std::wstring GeneratePipeName() { + return base::StringPrintf(L"\\\\.\\pipe\\mojo.%u.%u.%I64u", + GetCurrentProcessId(), GetCurrentThreadId(), + base::RandUint64()); +} + +} // namespace + +PlatformChannelPair::PlatformChannelPair(bool client_is_blocking) { + std::wstring pipe_name = GeneratePipeName(); + + DWORD kOpenMode = + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE; + const DWORD kPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE; + server_handle_.reset(PlatformHandle( + CreateNamedPipeW(pipe_name.c_str(), kOpenMode, kPipeMode, + 1, // Max instances. + 4096, // Out buffer size. + 4096, // In buffer size. + 5000, // Timeout in milliseconds. + nullptr))); // Default security descriptor. + PCHECK(server_handle_.is_valid()); + + const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE; + // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate + // the client. + DWORD kFlags = SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS; + if (!client_is_blocking) + kFlags |= FILE_FLAG_OVERLAPPED; + // Allow the handle to be inherited by child processes. + SECURITY_ATTRIBUTES security_attributes = { + sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE}; + client_handle_.reset( + PlatformHandle(CreateFileW(pipe_name.c_str(), kDesiredAccess, + 0, // No sharing. + &security_attributes, OPEN_EXISTING, kFlags, + nullptr))); // No template file. + PCHECK(client_handle_.is_valid()); + + // Since a client has connected, ConnectNamedPipe() should return zero and + // GetLastError() should return ERROR_PIPE_CONNECTED. + CHECK(!ConnectNamedPipe(server_handle_.get().handle, nullptr)); + PCHECK(GetLastError() == ERROR_PIPE_CONNECTED); +} + +// static +ScopedPlatformHandle PlatformChannelPair::PassClientHandleFromParentProcess( + const base::CommandLine& command_line) { + std::string client_handle_string = + command_line.GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + return PassClientHandleFromParentProcessFromString(client_handle_string); +} + +ScopedPlatformHandle +PlatformChannelPair::PassClientHandleFromParentProcessFromString( + const std::string& value) { + int client_handle_value = 0; + if (value.empty() || + !base::StringToInt(value, &client_handle_value)) { + LOG(ERROR) << "Missing or invalid --" << kMojoPlatformChannelHandleSwitch; + return ScopedPlatformHandle(); + } + + return ScopedPlatformHandle( + PlatformHandle(LongToHandle(client_handle_value))); +} + +void PlatformChannelPair::PrepareToPassClientHandleToChildProcess( + base::CommandLine* command_line, + base::HandlesToInheritVector* handle_passing_info) const { + DCHECK(command_line); + + // Log a warning if the command line already has the switch, but "clobber" it + // anyway, since it's reasonably likely that all the switches were just copied + // from the parent. + LOG_IF(WARNING, command_line->HasSwitch(kMojoPlatformChannelHandleSwitch)) + << "Child command line already has switch --" + << kMojoPlatformChannelHandleSwitch << "=" + << command_line->GetSwitchValueASCII(kMojoPlatformChannelHandleSwitch); + // (Any existing switch won't actually be removed from the command line, but + // the last one appended takes precedence.) + command_line->AppendSwitchASCII( + kMojoPlatformChannelHandleSwitch, + PrepareToPassClientHandleToChildProcessAsString(handle_passing_info)); +} + +std::string +PlatformChannelPair::PrepareToPassClientHandleToChildProcessAsString( + HandlePassingInformation* handle_passing_info) const { + DCHECK(handle_passing_info); + DCHECK(client_handle_.is_valid()); + + if (base::win::GetVersion() >= base::win::VERSION_VISTA) + handle_passing_info->push_back(client_handle_.get().handle); + + return base::IntToString(HandleToLong(client_handle_.get().handle)); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_utils_posix.cc b/mojo/edk/embedder/platform_channel_utils_posix.cc new file mode 100644 index 0000000..689b6ee --- /dev/null +++ b/mojo/edk/embedder/platform_channel_utils_posix.cc @@ -0,0 +1,282 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_channel_utils_posix.h" + +#include +#include +#include + +#include + +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/posix/eintr_wrapper.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +#if !defined(OS_NACL) +#include +#endif + +#if !defined(SO_PEEK_OFF) +#define SO_PEEK_OFF 42 +#endif + +namespace mojo { +namespace edk { +namespace { + +#if !defined(OS_NACL) +bool IsRecoverableError() { + return errno == ECONNABORTED || errno == EMFILE || errno == ENFILE || + errno == ENOMEM || errno == ENOBUFS; +} + +bool GetPeerEuid(PlatformHandle handle, uid_t* peer_euid) { + DCHECK(peer_euid); +#if defined(OS_MACOSX) || defined(OS_OPENBSD) || defined(OS_FREEBSD) + uid_t socket_euid; + gid_t socket_gid; + if (getpeereid(handle.handle, &socket_euid, &socket_gid) < 0) { + PLOG(ERROR) << "getpeereid " << handle.handle; + return false; + } + *peer_euid = socket_euid; + return true; +#else + struct ucred cred; + socklen_t cred_len = sizeof(cred); + if (getsockopt(handle.handle, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < + 0) { + PLOG(ERROR) << "getsockopt " << handle.handle; + return false; + } + if (static_cast(cred_len) < sizeof(cred)) { + NOTREACHED() << "Truncated ucred from SO_PEERCRED?"; + return false; + } + *peer_euid = cred.uid; + return true; +#endif +} + +bool IsPeerAuthorized(PlatformHandle peer_handle) { + uid_t peer_euid; + if (!GetPeerEuid(peer_handle, &peer_euid)) + return false; + if (peer_euid != geteuid()) { + DLOG(ERROR) << "Client euid is not authorised"; + return false; + } + return true; +} +#endif // !defined(OS_NACL) + +} // namespace + +// On Linux, |SIGPIPE| is suppressed by passing |MSG_NOSIGNAL| to +// |send()|/|sendmsg()|. (There is no way of suppressing |SIGPIPE| on +// |write()|/|writev().) On Mac, |SIGPIPE| is suppressed by setting the +// |SO_NOSIGPIPE| option on the socket. +// +// Performance notes: +// - On Linux, we have to use |send()|/|sendmsg()| rather than +// |write()|/|writev()| in order to suppress |SIGPIPE|. This is okay, since +// |send()| is (slightly) faster than |write()| (!), while |sendmsg()| is +// quite comparable to |writev()|. +// - On Mac, we may use |write()|/|writev()|. Here, |write()| is considerably +// faster than |send()|, whereas |sendmsg()| is quite comparable to +// |writev()|. +// - On both platforms, an appropriate |sendmsg()|/|writev()| is considerably +// faster than two |send()|s/|write()|s. +// - Relative numbers (minimum real times from 10 runs) for one |write()| of +// 1032 bytes, one |send()| of 1032 bytes, one |writev()| of 32+1000 bytes, +// one |sendmsg()| of 32+1000 bytes, two |write()|s of 32 and 1000 bytes, two +// |send()|s of 32 and 1000 bytes: +// - Linux: 0.81 s, 0.77 s, 0.87 s, 0.89 s, 1.31 s, 1.22 s +// - Mac: 2.21 s, 2.91 s, 2.98 s, 3.08 s, 3.59 s, 4.74 s + +// Flags to use with calling |send()| or |sendmsg()| (see above). +#if defined(OS_MACOSX) +const int kSendFlags = 0; +#else +const int kSendFlags = MSG_NOSIGNAL; +#endif + +ssize_t PlatformChannelWrite(PlatformHandle h, + const void* bytes, + size_t num_bytes) { + DCHECK(h.is_valid()); + DCHECK(bytes); + DCHECK_GT(num_bytes, 0u); + +#if defined(OS_MACOSX) || defined(OS_NACL_NONSFI) + // send() is not available under NaCl-nonsfi. + return HANDLE_EINTR(write(h.handle, bytes, num_bytes)); +#else + return send(h.handle, bytes, num_bytes, kSendFlags); +#endif +} + +ssize_t PlatformChannelWritev(PlatformHandle h, + struct iovec* iov, + size_t num_iov) { + DCHECK(h.is_valid()); + DCHECK(iov); + DCHECK_GT(num_iov, 0u); + +#if defined(OS_MACOSX) + return HANDLE_EINTR(writev(h.handle, iov, static_cast(num_iov))); +#else + struct msghdr msg = {}; + msg.msg_iov = iov; + msg.msg_iovlen = num_iov; + return HANDLE_EINTR(sendmsg(h.handle, &msg, kSendFlags)); +#endif +} + +ssize_t PlatformChannelSendmsgWithHandles(PlatformHandle h, + struct iovec* iov, + size_t num_iov, + PlatformHandle* platform_handles, + size_t num_platform_handles) { + DCHECK(iov); + DCHECK_GT(num_iov, 0u); + DCHECK(platform_handles); + DCHECK_GT(num_platform_handles, 0u); + DCHECK_LE(num_platform_handles, kPlatformChannelMaxNumHandles); + + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = iov; + msg.msg_iovlen = num_iov; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_LEN(num_platform_handles * sizeof(int)); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(num_platform_handles * sizeof(int)); + for (size_t i = 0; i < num_platform_handles; i++) { + DCHECK(platform_handles[i].is_valid()); + reinterpret_cast(CMSG_DATA(cmsg))[i] = platform_handles[i].handle; + } + + return HANDLE_EINTR(sendmsg(h.handle, &msg, kSendFlags)); +} + +bool PlatformChannelSendHandles(PlatformHandle h, + PlatformHandle* handles, + size_t num_handles) { + DCHECK(handles); + DCHECK_GT(num_handles, 0u); + DCHECK_LE(num_handles, kPlatformChannelMaxNumHandles); + + // Note: |sendmsg()| fails on Mac if we don't write at least one character. + struct iovec iov = {const_cast(""), 1}; + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = CMSG_LEN(num_handles * sizeof(int)); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(num_handles * sizeof(int)); + for (size_t i = 0; i < num_handles; i++) { + DCHECK(handles[i].is_valid()); + reinterpret_cast(CMSG_DATA(cmsg))[i] = handles[i].handle; + } + + ssize_t result = HANDLE_EINTR(sendmsg(h.handle, &msg, kSendFlags)); + if (result < 1) { + DCHECK_EQ(result, -1); + return false; + } + + for (size_t i = 0; i < num_handles; i++) + handles[i].CloseIfNecessary(); + return true; +} + +ssize_t PlatformChannelRecvmsg(PlatformHandle h, + void* buf, + size_t num_bytes, + std::deque* platform_handles, + bool block) { + DCHECK(buf); + DCHECK_GT(num_bytes, 0u); + DCHECK(platform_handles); + + struct iovec iov = {buf, num_bytes}; + char cmsg_buf[CMSG_SPACE(kPlatformChannelMaxNumHandles * sizeof(int))]; + struct msghdr msg = {}; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cmsg_buf; + msg.msg_controllen = sizeof(cmsg_buf); + + ssize_t result = + HANDLE_EINTR(recvmsg(h.handle, &msg, block ? 0 : MSG_DONTWAIT)); + if (result < 0) + return result; + + // Success; no control messages. + if (msg.msg_controllen == 0) + return result; + + DCHECK(!(msg.msg_flags & MSG_CTRUNC)); + + for (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + size_t payload_length = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK_EQ(payload_length % sizeof(int), 0u); + size_t num_fds = payload_length / sizeof(int); + const int* fds = reinterpret_cast(CMSG_DATA(cmsg)); + for (size_t i = 0; i < num_fds; i++) { + platform_handles->push_back(PlatformHandle(fds[i])); + DCHECK(platform_handles->back().is_valid()); + } + } + } + + return result; +} + +bool ServerAcceptConnection(PlatformHandle server_handle, + ScopedPlatformHandle* connection_handle, + bool check_peer_user) { + DCHECK(server_handle.is_valid()); + connection_handle->reset(); +#if defined(OS_NACL) + NOTREACHED(); + return false; +#else + ScopedPlatformHandle accept_handle( + PlatformHandle(HANDLE_EINTR(accept(server_handle.handle, NULL, 0)))); + if (!accept_handle.is_valid()) + return IsRecoverableError(); + + // Verify that the IPC channel peer is running as the same user. + if (check_peer_user && !IsPeerAuthorized(accept_handle.get())) { + return true; + } + + if (!base::SetNonBlocking(accept_handle.get().handle)) { + PLOG(ERROR) << "base::SetNonBlocking() failed " + << accept_handle.get().handle; + // It's safe to keep listening on |server_handle| even if the attempt to set + // O_NONBLOCK failed on the client fd. + return true; + } + + *connection_handle = std::move(accept_handle); + return true; +#endif // defined(OS_NACL) +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_channel_utils_posix.h b/mojo/edk/embedder/platform_channel_utils_posix.h new file mode 100644 index 0000000..23cfa92 --- /dev/null +++ b/mojo/edk/embedder/platform_channel_utils_posix.h @@ -0,0 +1,87 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ + +#include +#include // For |ssize_t|. + +#include +#include + +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +struct iovec; // Declared in . + +namespace mojo { +namespace edk { +class ScopedPlatformHandle; + +// The maximum number of handles that can be sent "at once" using +// |PlatformChannelSendmsgWithHandles()|. This must be less than the Linux +// kernel's SCM_MAX_FD which is 253. +const size_t kPlatformChannelMaxNumHandles = 128; + +// Use these to write to a socket created using |PlatformChannelPair| (or +// equivalent). These are like |write()| and |writev()|, but handle |EINTR| and +// never raise |SIGPIPE|. (Note: On Mac, the suppression of |SIGPIPE| is set up +// by |PlatformChannelPair|.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t +PlatformChannelWrite(PlatformHandle h, const void* bytes, size_t num_bytes); +MOJO_SYSTEM_IMPL_EXPORT ssize_t +PlatformChannelWritev(PlatformHandle h, struct iovec* iov, size_t num_iov); + +// Writes data, and the given set of |PlatformHandle|s (i.e., file descriptors) +// over the Unix domain socket given by |h| (e.g., created using +// |PlatformChannelPair()|). All the handles must be valid, and there must be at +// least one and at most |kPlatformChannelMaxNumHandles| handles. The return +// value is as for |sendmsg()|, namely -1 on failure and otherwise the number of +// bytes of data sent on success (note that this may not be all the data +// specified by |iov|). (The handles are not closed, regardless of success or +// failure.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t +PlatformChannelSendmsgWithHandles(PlatformHandle h, + struct iovec* iov, + size_t num_iov, + PlatformHandle* platform_handles, + size_t num_platform_handles); + +// TODO(vtl): Remove this once I've switched things over to +// |PlatformChannelSendmsgWithHandles()|. +// Sends |PlatformHandle|s (i.e., file descriptors) over the Unix domain socket +// (e.g., created using PlatformChannelPair|). (These will be sent in a single +// message having one null byte of data and one control message header with all +// the file descriptors.) All of the handles must be valid, and there must be at +// most |kPlatformChannelMaxNumHandles| (and at least one handle). Returns true +// on success, in which case it closes all the handles. +MOJO_SYSTEM_IMPL_EXPORT bool PlatformChannelSendHandles(PlatformHandle h, + PlatformHandle* handles, + size_t num_handles); + +// Wrapper around |recvmsg()|, which will extract any attached file descriptors +// (in the control message) to |PlatformHandle|s (and append them to +// |platform_handles|). (This also handles |EINTR|.) +MOJO_SYSTEM_IMPL_EXPORT ssize_t +PlatformChannelRecvmsg(PlatformHandle h, + void* buf, + size_t num_bytes, + std::deque* platform_handles, + bool block = false); + +// Returns false if |server_handle| encounters an unrecoverable error. +// Returns true if it's valid to keep listening on |server_handle|. In this +// case, it's possible that a connection wasn't successfully established; then, +// |connection_handle| will be invalid. If |check_peer_user| is True, the +// connection will be rejected if the peer is running as a different user. +MOJO_SYSTEM_IMPL_EXPORT bool ServerAcceptConnection( + PlatformHandle server_handle, + ScopedPlatformHandle* connection_handle, + bool check_peer_user = true); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_CHANNEL_UTILS_POSIX_H_ diff --git a/mojo/edk/embedder/platform_handle.cc b/mojo/edk/embedder/platform_handle.cc new file mode 100644 index 0000000..b6b2cd2 --- /dev/null +++ b/mojo/edk/embedder/platform_handle.cc @@ -0,0 +1,74 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_handle.h" + +#include "build/build_config.h" +#if defined(OS_POSIX) +#include +#elif defined(OS_WIN) +#include +#else +#error "Platform not yet supported." +#endif + +#include "base/logging.h" + +namespace mojo { +namespace edk { + +void PlatformHandle::CloseIfNecessary() { + if (!is_valid()) + return; + +#if defined(OS_POSIX) + if (type == Type::POSIX) { + bool success = (close(handle) == 0); + DPCHECK(success); + handle = -1; + } +#if defined(OS_MACOSX) && !defined(OS_IOS) + else if (type == Type::MACH) { + kern_return_t rv = mach_port_deallocate(mach_task_self(), port); + DPCHECK(rv == KERN_SUCCESS); + port = MACH_PORT_NULL; + } +#endif // defined(OS_MACOSX) && !defined(OS_IOS) +#elif defined(OS_WIN) + if (owning_process != base::GetCurrentProcessHandle()) { + // This handle may have been duplicated to a new target process but not yet + // sent there. In this case CloseHandle should NOT be called. From MSDN + // documentation for DuplicateHandle[1]: + // + // Normally the target process closes a duplicated handle when that + // process is finished using the handle. To close a duplicated handle + // from the source process, call DuplicateHandle with the following + // parameters: + // + // * Set hSourceProcessHandle to the target process from the + // call that created the handle. + // * Set hSourceHandle to the duplicated handle to close. + // * Set lpTargetHandle to NULL. + // * Set dwOptions to DUPLICATE_CLOSE_SOURCE. + // + // [1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms724251 + // + // NOTE: It's possible for this operation to fail if the owning process + // was terminated or is in the process of being terminated. Either way, + // there is nothing we can reasonably do about failure, so we ignore it. + DuplicateHandle(owning_process, handle, NULL, &handle, 0, FALSE, + DUPLICATE_CLOSE_SOURCE); + return; + } + + bool success = !!CloseHandle(handle); + DPCHECK(success); + handle = INVALID_HANDLE_VALUE; +#else +#error "Platform not yet supported." +#endif +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_handle.h b/mojo/edk/embedder/platform_handle.h new file mode 100644 index 0000000..4866e75 --- /dev/null +++ b/mojo/edk/embedder/platform_handle.h @@ -0,0 +1,90 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_ + +#include "build/build_config.h" +#include "mojo/edk/system/system_impl_export.h" + +#if defined(OS_WIN) +#include + +#include "base/process/process_handle.h" +#elif defined(OS_MACOSX) && !defined(OS_IOS) +#include +#endif + +namespace mojo { +namespace edk { + +#if defined(OS_POSIX) +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle { + PlatformHandle() {} + explicit PlatformHandle(int handle) : handle(handle) {} +#if defined(OS_MACOSX) && !defined(OS_IOS) + explicit PlatformHandle(mach_port_t port) + : type(Type::MACH), port(port) {} +#endif + + void CloseIfNecessary(); + + bool is_valid() const { +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (type == Type::MACH || type == Type::MACH_NAME) + return port != MACH_PORT_NULL; +#endif + return handle != -1; + } + + enum class Type { + POSIX, +#if defined(OS_MACOSX) && !defined(OS_IOS) + MACH, + // MACH_NAME isn't a real Mach port. But rather the "name" of one that can + // be resolved to a real port later. This distinction is needed so that the + // "port" doesn't try to be closed if CloseIfNecessary() is called. Having + // this also allows us to do checks in other places. + MACH_NAME, +#endif + }; + Type type = Type::POSIX; + + int handle = -1; + + // A POSIX handle may be a listen handle that can accept a connection. + bool needs_connection = false; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + mach_port_t port = MACH_PORT_NULL; +#endif +}; +#elif defined(OS_WIN) +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandle { + PlatformHandle() : PlatformHandle(INVALID_HANDLE_VALUE) {} + explicit PlatformHandle(HANDLE handle) + : handle(handle), owning_process(base::GetCurrentProcessHandle()) {} + + void CloseIfNecessary(); + + bool is_valid() const { return handle != INVALID_HANDLE_VALUE; } + + HANDLE handle; + + // A Windows HANDLE may be duplicated to another process but not yet sent to + // that process. This tracks the handle's owning process. + base::ProcessHandle owning_process; + + // A Windows HANDLE may be an unconnected named pipe. In this case, we need to + // wait for a connection before communicating on the pipe. + bool needs_connection = false; +}; +#else +#error "Platform not yet supported." +#endif + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_H_ diff --git a/mojo/edk/embedder/platform_handle_utils.h b/mojo/edk/embedder/platform_handle_utils.h new file mode 100644 index 0000000..fa683e4 --- /dev/null +++ b/mojo/edk/embedder/platform_handle_utils.h @@ -0,0 +1,33 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ + +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +// Closes all the |PlatformHandle|s in the given container. +template +MOJO_SYSTEM_IMPL_EXPORT inline void CloseAllPlatformHandles( + PlatformHandleContainer* platform_handles) { + for (typename PlatformHandleContainer::iterator it = + platform_handles->begin(); + it != platform_handles->end(); ++it) + it->CloseIfNecessary(); +} + +// Duplicates the given |PlatformHandle| (which must be valid). (Returns an +// invalid |ScopedPlatformHandle| on failure.) +MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle +DuplicatePlatformHandle(PlatformHandle platform_handle); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_UTILS_H_ diff --git a/mojo/edk/embedder/platform_handle_utils_posix.cc b/mojo/edk/embedder/platform_handle_utils_posix.cc new file mode 100644 index 0000000..5604f96 --- /dev/null +++ b/mojo/edk/embedder/platform_handle_utils_posix.cc @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_handle_utils.h" + +#include + +#include "base/logging.h" + +namespace mojo { +namespace edk { + +ScopedPlatformHandle DuplicatePlatformHandle(PlatformHandle platform_handle) { + DCHECK(platform_handle.is_valid()); + // Note that |dup()| returns -1 on error (which is exactly the value we use + // for invalid |PlatformHandle| FDs). + PlatformHandle duped(dup(platform_handle.handle)); + duped.needs_connection = platform_handle.needs_connection; + return ScopedPlatformHandle(duped); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_handle_utils_win.cc b/mojo/edk/embedder/platform_handle_utils_win.cc new file mode 100644 index 0000000..32ed49a --- /dev/null +++ b/mojo/edk/embedder/platform_handle_utils_win.cc @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_handle_utils.h" + +#include + +#include "base/logging.h" + +namespace mojo { +namespace edk { + +ScopedPlatformHandle DuplicatePlatformHandle(PlatformHandle platform_handle) { + DCHECK(platform_handle.is_valid()); + + HANDLE new_handle; + CHECK_NE(platform_handle.handle, INVALID_HANDLE_VALUE); + if (!DuplicateHandle(GetCurrentProcess(), platform_handle.handle, + GetCurrentProcess(), &new_handle, 0, TRUE, + DUPLICATE_SAME_ACCESS)) + return ScopedPlatformHandle(); + DCHECK_NE(new_handle, INVALID_HANDLE_VALUE); + return ScopedPlatformHandle(PlatformHandle(new_handle)); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_handle_vector.h b/mojo/edk/embedder/platform_handle_vector.h new file mode 100644 index 0000000..9892b23 --- /dev/null +++ b/mojo/edk/embedder/platform_handle_vector.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ + +#include +#include + +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/platform_handle_utils.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +using PlatformHandleVector = std::vector; + +// A deleter (for use with |scoped_ptr|) which closes all handles and then +// |delete|s the |PlatformHandleVector|. +struct MOJO_SYSTEM_IMPL_EXPORT PlatformHandleVectorDeleter { + void operator()(PlatformHandleVector* platform_handles) const { + CloseAllPlatformHandles(platform_handles); + delete platform_handles; + } +}; + +using ScopedPlatformHandleVectorPtr = + std::unique_ptr; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_HANDLE_VECTOR_H_ diff --git a/mojo/edk/embedder/platform_shared_buffer.cc b/mojo/edk/embedder/platform_shared_buffer.cc new file mode 100644 index 0000000..58af44d --- /dev/null +++ b/mojo/edk/embedder/platform_shared_buffer.cc @@ -0,0 +1,325 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_shared_buffer.h" + +#include + +#include + +#include "base/logging.h" +#include "base/memory/ptr_util.h" +#include "base/memory/shared_memory.h" +#include "base/process/process_handle.h" +#include "base/sys_info.h" +#include "mojo/edk/embedder/platform_handle_utils.h" + +#if defined(OS_NACL) +// For getpagesize() on NaCl. +#include +#endif + +namespace mojo { +namespace edk { + +namespace { + +// Takes ownership of |memory_handle|. +ScopedPlatformHandle SharedMemoryToPlatformHandle( + base::SharedMemoryHandle memory_handle) { +#if defined(OS_POSIX) && !(defined(OS_MACOSX) && !defined(OS_IOS)) + return ScopedPlatformHandle(PlatformHandle(memory_handle.fd)); +#elif defined(OS_WIN) + return ScopedPlatformHandle(PlatformHandle(memory_handle.GetHandle())); +#else + return ScopedPlatformHandle(PlatformHandle(memory_handle.GetMemoryObject())); +#endif +} + +} // namespace + +// static +PlatformSharedBuffer* PlatformSharedBuffer::Create(size_t num_bytes) { + DCHECK_GT(num_bytes, 0u); + + PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, false); + if (!rv->Init()) { + // We can't just delete it directly, due to the "in destructor" (debug) + // check. + scoped_refptr deleter(rv); + return nullptr; + } + + return rv; +} + +// static +PlatformSharedBuffer* PlatformSharedBuffer::CreateFromPlatformHandle( + size_t num_bytes, + bool read_only, + ScopedPlatformHandle platform_handle) { + DCHECK_GT(num_bytes, 0u); + + PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, read_only); + if (!rv->InitFromPlatformHandle(std::move(platform_handle))) { + // We can't just delete it directly, due to the "in destructor" (debug) + // check. + scoped_refptr deleter(rv); + return nullptr; + } + + return rv; +} + +// static +PlatformSharedBuffer* PlatformSharedBuffer::CreateFromPlatformHandlePair( + size_t num_bytes, + ScopedPlatformHandle rw_platform_handle, + ScopedPlatformHandle ro_platform_handle) { + DCHECK_GT(num_bytes, 0u); + DCHECK(rw_platform_handle.is_valid()); + DCHECK(ro_platform_handle.is_valid()); + + PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, false); + if (!rv->InitFromPlatformHandlePair(std::move(rw_platform_handle), + std::move(ro_platform_handle))) { + // We can't just delete it directly, due to the "in destructor" (debug) + // check. + scoped_refptr deleter(rv); + return nullptr; + } + + return rv; +} + +// static +PlatformSharedBuffer* PlatformSharedBuffer::CreateFromSharedMemoryHandle( + size_t num_bytes, + bool read_only, + base::SharedMemoryHandle handle) { + DCHECK_GT(num_bytes, 0u); + + PlatformSharedBuffer* rv = new PlatformSharedBuffer(num_bytes, read_only); + rv->InitFromSharedMemoryHandle(handle); + + return rv; +} + +size_t PlatformSharedBuffer::GetNumBytes() const { + return num_bytes_; +} + +bool PlatformSharedBuffer::IsReadOnly() const { + return read_only_; +} + +std::unique_ptr PlatformSharedBuffer::Map( + size_t offset, + size_t length) { + if (!IsValidMap(offset, length)) + return nullptr; + + return MapNoCheck(offset, length); +} + +bool PlatformSharedBuffer::IsValidMap(size_t offset, size_t length) { + if (offset > num_bytes_ || length == 0) + return false; + + // Note: This is an overflow-safe check of |offset + length > num_bytes_| + // (that |num_bytes >= offset| is verified above). + if (length > num_bytes_ - offset) + return false; + + return true; +} + +std::unique_ptr PlatformSharedBuffer::MapNoCheck( + size_t offset, + size_t length) { + DCHECK(IsValidMap(offset, length)); + DCHECK(shared_memory_); + base::SharedMemoryHandle handle; + { + base::AutoLock locker(lock_); + handle = base::SharedMemory::DuplicateHandle(shared_memory_->handle()); + } + if (handle == base::SharedMemory::NULLHandle()) + return nullptr; + + std::unique_ptr mapping( + new PlatformSharedBufferMapping(handle, read_only_, offset, length)); + if (mapping->Map()) + return base::WrapUnique(mapping.release()); + + return nullptr; +} + +ScopedPlatformHandle PlatformSharedBuffer::DuplicatePlatformHandle() { + DCHECK(shared_memory_); + base::SharedMemoryHandle handle; + { + base::AutoLock locker(lock_); + handle = base::SharedMemory::DuplicateHandle(shared_memory_->handle()); + } + if (handle == base::SharedMemory::NULLHandle()) + return ScopedPlatformHandle(); + + return SharedMemoryToPlatformHandle(handle); +} + +ScopedPlatformHandle PlatformSharedBuffer::PassPlatformHandle() { + DCHECK(HasOneRef()); + + // The only way to pass a handle from base::SharedMemory is to duplicate it + // and close the original. + ScopedPlatformHandle handle = DuplicatePlatformHandle(); + + base::AutoLock locker(lock_); + shared_memory_->Close(); + return handle; +} + +base::SharedMemoryHandle PlatformSharedBuffer::DuplicateSharedMemoryHandle() { + DCHECK(shared_memory_); + + base::AutoLock locker(lock_); + return base::SharedMemory::DuplicateHandle(shared_memory_->handle()); +} + +PlatformSharedBuffer* PlatformSharedBuffer::CreateReadOnlyDuplicate() { + DCHECK(shared_memory_); + + if (ro_shared_memory_) { + base::AutoLock locker(lock_); + base::SharedMemoryHandle handle; + handle = base::SharedMemory::DuplicateHandle(ro_shared_memory_->handle()); + if (handle == base::SharedMemory::NULLHandle()) + return nullptr; + return CreateFromSharedMemoryHandle(num_bytes_, true, handle); + } + + base::SharedMemoryHandle handle; + bool success; + { + base::AutoLock locker(lock_); + success = shared_memory_->ShareReadOnlyToProcess( + base::GetCurrentProcessHandle(), &handle); + } + if (!success || handle == base::SharedMemory::NULLHandle()) + return nullptr; + + return CreateFromSharedMemoryHandle(num_bytes_, true, handle); +} + +PlatformSharedBuffer::PlatformSharedBuffer(size_t num_bytes, bool read_only) + : num_bytes_(num_bytes), read_only_(read_only) {} + +PlatformSharedBuffer::~PlatformSharedBuffer() {} + +bool PlatformSharedBuffer::Init() { + DCHECK(!shared_memory_); + DCHECK(!read_only_); + + base::SharedMemoryCreateOptions options; + options.size = num_bytes_; + // By default, we can share as read-only. + options.share_read_only = true; + + shared_memory_.reset(new base::SharedMemory); + return shared_memory_->Create(options); +} + +bool PlatformSharedBuffer::InitFromPlatformHandle( + ScopedPlatformHandle platform_handle) { + DCHECK(!shared_memory_); + +#if defined(OS_WIN) + base::SharedMemoryHandle handle(platform_handle.release().handle, + base::GetCurrentProcId()); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + base::SharedMemoryHandle handle; + handle = base::SharedMemoryHandle(platform_handle.release().port, num_bytes_, + base::GetCurrentProcId()); +#else + base::SharedMemoryHandle handle(platform_handle.release().handle, false); +#endif + + shared_memory_.reset(new base::SharedMemory(handle, read_only_)); + return true; +} + +bool PlatformSharedBuffer::InitFromPlatformHandlePair( + ScopedPlatformHandle rw_platform_handle, + ScopedPlatformHandle ro_platform_handle) { +#if defined(OS_MACOSX) + NOTREACHED(); + return false; +#else // defined(OS_MACOSX) + +#if defined(OS_WIN) + base::SharedMemoryHandle handle(rw_platform_handle.release().handle, + base::GetCurrentProcId()); + base::SharedMemoryHandle ro_handle(ro_platform_handle.release().handle, + base::GetCurrentProcId()); +#else // defined(OS_WIN) + base::SharedMemoryHandle handle(rw_platform_handle.release().handle, false); + base::SharedMemoryHandle ro_handle(ro_platform_handle.release().handle, + false); +#endif // defined(OS_WIN) + + DCHECK(!shared_memory_); + shared_memory_.reset(new base::SharedMemory(handle, false)); + ro_shared_memory_.reset(new base::SharedMemory(ro_handle, true)); + return true; + +#endif // defined(OS_MACOSX) +} + +void PlatformSharedBuffer::InitFromSharedMemoryHandle( + base::SharedMemoryHandle handle) { + DCHECK(!shared_memory_); + + shared_memory_.reset(new base::SharedMemory(handle, read_only_)); +} + +PlatformSharedBufferMapping::~PlatformSharedBufferMapping() { + Unmap(); +} + +void* PlatformSharedBufferMapping::GetBase() const { + return base_; +} + +size_t PlatformSharedBufferMapping::GetLength() const { + return length_; +} + +bool PlatformSharedBufferMapping::Map() { + // Mojo shared buffers can be mapped at any offset. However, + // base::SharedMemory must be mapped at a page boundary. So calculate what the + // nearest whole page offset is, and build a mapping that's offset from that. +#if defined(OS_NACL) + // base::SysInfo isn't available under NaCl. + size_t page_size = getpagesize(); +#else + size_t page_size = base::SysInfo::VMAllocationGranularity(); +#endif + size_t offset_rounding = offset_ % page_size; + size_t real_offset = offset_ - offset_rounding; + size_t real_length = length_ + offset_rounding; + + if (!shared_memory_.MapAt(static_cast(real_offset), real_length)) + return false; + + base_ = static_cast(shared_memory_.memory()) + offset_rounding; + return true; +} + +void PlatformSharedBufferMapping::Unmap() { + shared_memory_.Unmap(); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/platform_shared_buffer.h b/mojo/edk/embedder/platform_shared_buffer.h new file mode 100644 index 0000000..45be723 --- /dev/null +++ b/mojo/edk/embedder/platform_shared_buffer.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_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_ +#define MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory.h" +#include "base/memory/shared_memory_handle.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +class PlatformSharedBufferMapping; + +// |PlatformSharedBuffer| is a thread-safe, ref-counted wrapper around +// OS-specific shared memory. It has the following features: +// - A |PlatformSharedBuffer| simply represents a piece of shared memory that +// *may* be mapped and *may* be shared to another process. +// - A single |PlatformSharedBuffer| may be mapped multiple times. The +// lifetime of the mapping (owned by |PlatformSharedBufferMapping|) is +// separate from the lifetime of the |PlatformSharedBuffer|. +// - Sizes/offsets (of the shared memory and mappings) are arbitrary, and not +// restricted by page size. However, more memory may actually be mapped than +// requested. +class MOJO_SYSTEM_IMPL_EXPORT PlatformSharedBuffer + : public base::RefCountedThreadSafe { + public: + // Creates a shared buffer of size |num_bytes| bytes (initially zero-filled). + // |num_bytes| must be nonzero. Returns null on failure. + static PlatformSharedBuffer* Create(size_t num_bytes); + + // Creates a shared buffer of size |num_bytes| from the existing platform + // handle |platform_handle|. Returns null on failure. + static PlatformSharedBuffer* CreateFromPlatformHandle( + size_t num_bytes, + bool read_only, + ScopedPlatformHandle platform_handle); + + // Creates a shared buffer of size |num_bytes| from the existing pair of + // read/write and read-only handles |rw_platform_handle| and + // |ro_platform_handle|. Returns null on failure. + static PlatformSharedBuffer* CreateFromPlatformHandlePair( + size_t num_bytes, + ScopedPlatformHandle rw_platform_handle, + ScopedPlatformHandle ro_platform_handle); + + // Creates a shared buffer of size |num_bytes| from the existing shared memory + // handle |handle|. + static PlatformSharedBuffer* CreateFromSharedMemoryHandle( + size_t num_bytes, + bool read_only, + base::SharedMemoryHandle handle); + + // Gets the size of shared buffer (in number of bytes). + size_t GetNumBytes() const; + + // Returns whether this shared buffer is read-only. + bool IsReadOnly() const; + + // Maps (some) of the shared buffer into memory; [|offset|, |offset + length|] + // must be contained in [0, |num_bytes|], and |length| must be at least 1. + // Returns null on failure. + std::unique_ptr Map(size_t offset, + size_t length); + + // Checks if |offset| and |length| are valid arguments. + bool IsValidMap(size_t offset, size_t length); + + // Like |Map()|, but doesn't check its arguments (which should have been + // preflighted using |IsValidMap()|). + std::unique_ptr MapNoCheck(size_t offset, + size_t length); + + // Duplicates the underlying platform handle and passes it to the caller. + ScopedPlatformHandle DuplicatePlatformHandle(); + + // Duplicates the underlying shared memory handle and passes it to the caller. + base::SharedMemoryHandle DuplicateSharedMemoryHandle(); + + // Passes the underlying platform handle to the caller. This should only be + // called if there's a unique reference to this object (owned by the caller). + // After calling this, this object should no longer be used, but should only + // be disposed of. + ScopedPlatformHandle PassPlatformHandle(); + + // Create and return a read-only duplicate of this shared buffer. If this + // shared buffer isn't capable of returning a read-only duplicate, then + // nullptr will be returned. + PlatformSharedBuffer* CreateReadOnlyDuplicate(); + + private: + friend class base::RefCountedThreadSafe; + + PlatformSharedBuffer(size_t num_bytes, bool read_only); + ~PlatformSharedBuffer(); + + // This is called by |Create()| before this object is given to anyone. + bool Init(); + + // This is like |Init()|, but for |CreateFromPlatformHandle()|. (Note: It + // should verify that |platform_handle| is an appropriate handle for the + // claimed |num_bytes_|.) + bool InitFromPlatformHandle(ScopedPlatformHandle platform_handle); + + bool InitFromPlatformHandlePair(ScopedPlatformHandle rw_platform_handle, + ScopedPlatformHandle ro_platform_handle); + + void InitFromSharedMemoryHandle(base::SharedMemoryHandle handle); + + const size_t num_bytes_; + const bool read_only_; + + base::Lock lock_; + std::unique_ptr shared_memory_; + + // A separate read-only shared memory for platforms that need it (i.e. Linux + // with sync broker). + std::unique_ptr ro_shared_memory_; + + DISALLOW_COPY_AND_ASSIGN(PlatformSharedBuffer); +}; + +// A mapping of a |PlatformSharedBuffer| (compararable to a "file view" in +// Windows); see above. Created by |PlatformSharedBuffer::Map()|. Automatically +// unmaps memory on destruction. +// +// Mappings are NOT thread-safe. +// +// Note: This is an entirely separate class (instead of +// |PlatformSharedBuffer::Mapping|) so that it can be forward-declared. +class MOJO_SYSTEM_IMPL_EXPORT PlatformSharedBufferMapping { + public: + ~PlatformSharedBufferMapping(); + + void* GetBase() const; + size_t GetLength() const; + + private: + friend class PlatformSharedBuffer; + + PlatformSharedBufferMapping(base::SharedMemoryHandle handle, + bool read_only, + size_t offset, + size_t length) + : offset_(offset), + length_(length), + base_(nullptr), + shared_memory_(handle, read_only) {} + + bool Map(); + void Unmap(); + + const size_t offset_; + const size_t length_; + void* base_; + + // Since mapping life cycles are separate from PlatformSharedBuffer and a + // buffer can be mapped multiple times, we have our own SharedMemory object + // created from a duplicate handle. + base::SharedMemory shared_memory_; + + DISALLOW_COPY_AND_ASSIGN(PlatformSharedBufferMapping); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_PLATFORM_SHARED_BUFFER_H_ diff --git a/mojo/edk/embedder/platform_shared_buffer_unittest.cc b/mojo/edk/embedder/platform_shared_buffer_unittest.cc new file mode 100644 index 0000000..f1593f0 --- /dev/null +++ b/mojo/edk/embedder/platform_shared_buffer_unittest.cc @@ -0,0 +1,227 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/platform_shared_buffer.h" + +#include + +#include +#include + +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory.h" +#include "base/sys_info.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace mojo { +namespace edk { +namespace { + +TEST(PlatformSharedBufferTest, Basic) { + const size_t kNumInts = 100; + const size_t kNumBytes = kNumInts * sizeof(int); + // A fudge so that we're not just writing zero bytes 75% of the time. + const int kFudge = 1234567890; + + // Make some memory. + scoped_refptr buffer( + PlatformSharedBuffer::Create(kNumBytes)); + ASSERT_TRUE(buffer); + + // Map it all, scribble some stuff, and then unmap it. + { + EXPECT_TRUE(buffer->IsValidMap(0, kNumBytes)); + std::unique_ptr mapping( + buffer->Map(0, kNumBytes)); + ASSERT_TRUE(mapping); + ASSERT_TRUE(mapping->GetBase()); + int* stuff = static_cast(mapping->GetBase()); + for (size_t i = 0; i < kNumInts; i++) + stuff[i] = static_cast(i) + kFudge; + } + + // Map it all again, check that our scribbling is still there, then do a + // partial mapping and scribble on that, check that everything is coherent, + // unmap the first mapping, scribble on some of the second mapping, and then + // unmap it. + { + ASSERT_TRUE(buffer->IsValidMap(0, kNumBytes)); + // Use |MapNoCheck()| this time. + std::unique_ptr mapping1( + buffer->MapNoCheck(0, kNumBytes)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->GetBase()); + int* stuff1 = static_cast(mapping1->GetBase()); + for (size_t i = 0; i < kNumInts; i++) + EXPECT_EQ(static_cast(i) + kFudge, stuff1[i]) << i; + + std::unique_ptr mapping2( + buffer->Map((kNumInts / 2) * sizeof(int), 2 * sizeof(int))); + ASSERT_TRUE(mapping2); + ASSERT_TRUE(mapping2->GetBase()); + int* stuff2 = static_cast(mapping2->GetBase()); + EXPECT_EQ(static_cast(kNumInts / 2) + kFudge, stuff2[0]); + EXPECT_EQ(static_cast(kNumInts / 2) + 1 + kFudge, stuff2[1]); + + stuff2[0] = 123; + stuff2[1] = 456; + EXPECT_EQ(123, stuff1[kNumInts / 2]); + EXPECT_EQ(456, stuff1[kNumInts / 2 + 1]); + + mapping1.reset(); + + EXPECT_EQ(123, stuff2[0]); + EXPECT_EQ(456, stuff2[1]); + stuff2[1] = 789; + } + + // Do another partial mapping and check that everything is the way we expect + // it to be. + { + EXPECT_TRUE(buffer->IsValidMap(sizeof(int), kNumBytes - sizeof(int))); + std::unique_ptr mapping( + buffer->Map(sizeof(int), kNumBytes - sizeof(int))); + ASSERT_TRUE(mapping); + ASSERT_TRUE(mapping->GetBase()); + int* stuff = static_cast(mapping->GetBase()); + + for (size_t j = 0; j < kNumInts - 1; j++) { + int i = static_cast(j) + 1; + if (i == kNumInts / 2) { + EXPECT_EQ(123, stuff[j]); + } else if (i == kNumInts / 2 + 1) { + EXPECT_EQ(789, stuff[j]); + } else { + EXPECT_EQ(i + kFudge, stuff[j]) << i; + } + } + } +} + +// TODO(vtl): Bigger buffers. + +TEST(PlatformSharedBufferTest, InvalidMappings) { + scoped_refptr buffer(PlatformSharedBuffer::Create(100)); + ASSERT_TRUE(buffer); + + // Zero length not allowed. + EXPECT_FALSE(buffer->Map(0, 0)); + EXPECT_FALSE(buffer->IsValidMap(0, 0)); + + // Okay: + EXPECT_TRUE(buffer->Map(0, 100)); + EXPECT_TRUE(buffer->IsValidMap(0, 100)); + // Offset + length too big. + EXPECT_FALSE(buffer->Map(0, 101)); + EXPECT_FALSE(buffer->IsValidMap(0, 101)); + EXPECT_FALSE(buffer->Map(1, 100)); + EXPECT_FALSE(buffer->IsValidMap(1, 100)); + + // Okay: + EXPECT_TRUE(buffer->Map(50, 50)); + EXPECT_TRUE(buffer->IsValidMap(50, 50)); + // Offset + length too big. + EXPECT_FALSE(buffer->Map(50, 51)); + EXPECT_FALSE(buffer->IsValidMap(50, 51)); + EXPECT_FALSE(buffer->Map(51, 50)); + EXPECT_FALSE(buffer->IsValidMap(51, 50)); +} + +TEST(PlatformSharedBufferTest, TooBig) { + // If |size_t| is 32-bit, it's quite possible/likely that |Create()| succeeds + // (since it only involves creating a 4 GB file). + size_t max_size = std::numeric_limits::max(); + if (base::SysInfo::AmountOfVirtualMemory() && + max_size > static_cast(base::SysInfo::AmountOfVirtualMemory())) + max_size = static_cast(base::SysInfo::AmountOfVirtualMemory()); + scoped_refptr buffer( + PlatformSharedBuffer::Create(max_size)); + // But, assuming |sizeof(size_t) == sizeof(void*)|, mapping all of it should + // always fail. + if (buffer) + EXPECT_FALSE(buffer->Map(0, max_size)); +} + +// Tests that separate mappings get distinct addresses. +// Note: It's not inconceivable that the OS could ref-count identical mappings +// and reuse the same address, in which case we'd have to be more careful about +// using the address as the key for unmapping. +TEST(PlatformSharedBufferTest, MappingsDistinct) { + scoped_refptr buffer(PlatformSharedBuffer::Create(100)); + std::unique_ptr mapping1(buffer->Map(0, 100)); + std::unique_ptr mapping2(buffer->Map(0, 100)); + EXPECT_NE(mapping1->GetBase(), mapping2->GetBase()); +} + +TEST(PlatformSharedBufferTest, BufferZeroInitialized) { + static const size_t kSizes[] = {10, 100, 1000, 10000, 100000}; + for (size_t i = 0; i < arraysize(kSizes); i++) { + scoped_refptr buffer( + PlatformSharedBuffer::Create(kSizes[i])); + std::unique_ptr mapping( + buffer->Map(0, kSizes[i])); + for (size_t j = 0; j < kSizes[i]; j++) { + // "Assert" instead of "expect" so we don't spam the output with thousands + // of failures if we fail. + ASSERT_EQ('\0', static_cast(mapping->GetBase())[j]) + << "size " << kSizes[i] << ", offset " << j; + } + } +} + +TEST(PlatformSharedBufferTest, MappingsOutliveBuffer) { + std::unique_ptr mapping1; + std::unique_ptr mapping2; + + { + scoped_refptr buffer( + PlatformSharedBuffer::Create(100)); + mapping1 = buffer->Map(0, 100); + mapping2 = buffer->Map(50, 50); + static_cast(mapping1->GetBase())[50] = 'x'; + } + + EXPECT_EQ('x', static_cast(mapping2->GetBase())[0]); + + static_cast(mapping2->GetBase())[1] = 'y'; + EXPECT_EQ('y', static_cast(mapping1->GetBase())[51]); +} + +TEST(PlatformSharedBufferTest, FromSharedMemoryHandle) { + const size_t kBufferSize = 1234; + base::SharedMemoryCreateOptions options; + options.size = kBufferSize; + base::SharedMemory shared_memory; + ASSERT_TRUE(shared_memory.Create(options)); + ASSERT_TRUE(shared_memory.Map(kBufferSize)); + + base::SharedMemoryHandle shm_handle = + base::SharedMemory::DuplicateHandle(shared_memory.handle()); + scoped_refptr simple_buffer( + PlatformSharedBuffer::CreateFromSharedMemoryHandle( + kBufferSize, false /* read_only */, shm_handle)); + ASSERT_TRUE(simple_buffer); + + std::unique_ptr mapping = + simple_buffer->Map(0, kBufferSize); + ASSERT_TRUE(mapping); + + const int kOffset = 123; + char* base_memory = static_cast(shared_memory.memory()); + char* mojo_memory = static_cast(mapping->GetBase()); + base_memory[kOffset] = 0; + EXPECT_EQ(0, mojo_memory[kOffset]); + base_memory[kOffset] = 'a'; + EXPECT_EQ('a', mojo_memory[kOffset]); + mojo_memory[kOffset] = 'z'; + EXPECT_EQ('z', base_memory[kOffset]); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/scoped_ipc_support.cc b/mojo/edk/embedder/scoped_ipc_support.cc new file mode 100644 index 0000000..f67210a --- /dev/null +++ b/mojo/edk/embedder/scoped_ipc_support.cc @@ -0,0 +1,39 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/scoped_ipc_support.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread_restrictions.h" +#include "mojo/edk/embedder/embedder.h" + +namespace mojo { +namespace edk { + +ScopedIPCSupport::ScopedIPCSupport( + scoped_refptr io_thread_task_runner, + ShutdownPolicy shutdown_policy) : shutdown_policy_(shutdown_policy) { + InitIPCSupport(io_thread_task_runner); +} + +ScopedIPCSupport::~ScopedIPCSupport() { + if (shutdown_policy_ == ShutdownPolicy::FAST) { + ShutdownIPCSupport(base::Bind(&base::DoNothing)); + return; + } + + base::WaitableEvent shutdown_event( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + ShutdownIPCSupport(base::Bind(&base::WaitableEvent::Signal, + base::Unretained(&shutdown_event))); + + base::ThreadRestrictions::ScopedAllowWait allow_io; + shutdown_event.Wait(); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/embedder/scoped_ipc_support.h b/mojo/edk/embedder/scoped_ipc_support.h new file mode 100644 index 0000000..22d8e50 --- /dev/null +++ b/mojo/edk/embedder/scoped_ipc_support.h @@ -0,0 +1,118 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_SCOPED_IPC_SUPPORT_H_ +#define MOJO_EDK_EMBEDDER_SCOPED_IPC_SUPPORT_H_ + +#include "base/memory/ref_counted.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace base { +class TaskRunner; +} + +namespace mojo { +namespace edk { + +// A simple class that calls |InitIPCSupport()| on construction and +// |ShutdownIPCSupport()| on destruction, blocking the destructor on clean IPC +// shutdown completion. +class MOJO_SYSTEM_IMPL_EXPORT ScopedIPCSupport { + public: + // ShutdownPolicy is a type for specifying the desired Mojo IPC support + // shutdown behavior used during ScopedIPCSupport destruction. + // + // What follows is a quick overview of why shutdown behavior is interesting + // and how you might decide which behavior is right for your use case. + // + // BACKGROUND + // ========== + // + // In order to facilitate efficient and reliable transfer of Mojo message pipe + // endpoints across process boundaries, the underlying model for a message + // pipe is actually a self-collapsing cycle of "ports." See + // //mojo/edk/system/ports for gritty implementation details. + // + // Ports are essentially globally unique identifiers used for system-wide + // message routing. Every message pipe consists of at least two such ports: + // the pipe's two concrete endpoints. + // + // When a message pipe endpoint is transferred over another message pipe, that + // endpoint's port (which subsequently exists only internally with no + // publicly-reachable handle) enters a transient proxying state for the + // remainder of its lifetime. Once sufficient information has been + // proagated throughout the system and this proxying port can be safely + // bypassed, it is garbage-collected. + // + // If a process is terminated while hosting any active proxy ports, this + // will necessarily break the message pipe(s) to which those ports belong. + // + // WHEN TO USE CLEAN SHUTDOWN + // ========================== + // + // Consider three processes, A, B, and C. Suppose A creates a message pipe, + // sending one end to B and the other to C. For some brief period of time, + // messages sent by B or C over this pipe may be proxied through A. + // + // If A is suddenly terminated, there may be no way for B's messages to reach + // C (and vice versa), since the message pipe state may not have been fully + // propagated to all concerned processes in the system. As such, both B and C + // may have no choice but to signal peer closure on their respective ends of + // the pipe, and thus the pipe may be broken despite a lack of intent by + // either B or C. + // + // This can also happen if A creates a pipe and passes one end to B, who then + // passes it along to C. B may temporarily proxy messages for this pipe + // between A and C, and B's sudden demise will in turn beget the pipe's + // own sudden demise. + // + // In situations where these sort of arrangements may occur, potentially + // proxying processes must ensure they are shut down cleanly in order to avoid + // flaky system behavior. + // + // WHEN TO USE FAST SHUTDOWN + // ========================= + // + // As a general rule of thumb, if your process never creates a message pipe + // where both ends are passed to other processes, or never forwards a pipe + // endpoint from one process to another, fast shutdown is safe. Satisfaction + // of these constraints can be difficult to prove though, so clean shutdown is + // a safe default choice. + // + // Content renderer processes are a good example of a case where fast shutdown + // is safe, because as a matter of security and stability, a renderer cannot + // be trusted to do any proxying on behalf of two other processes anyway. + // + // There are other practical scenarios where fast shutdown is safe even if + // the process may have live proxies. For example, content's browser process + // is treated as a sort of master process in the system, in the sense that if + // the browser is terminated, no other part of the system is expected to + // continue normal operation anyway. In this case the side-effects of fast + // shutdown are irrelevant, so fast shutdown is preferred. + enum class ShutdownPolicy { + // Clean shutdown. This causes the ScopedIPCSupport destructor to *block* + // the calling thread until clean shutdown is complete. See explanation + // above for details. + CLEAN, + + // Fast shutdown. In this case a cheap best-effort attempt is made to + // shut down the IPC system, but no effort is made to wait for its + // completion. See explanation above for details. + FAST, + }; + + ScopedIPCSupport(scoped_refptr io_thread_task_runner, + ShutdownPolicy shutdown_policy); + ~ScopedIPCSupport(); + + private: + const ShutdownPolicy shutdown_policy_; + + DISALLOW_COPY_AND_ASSIGN(ScopedIPCSupport); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_SCOPED_IPC_SUPPORT_H_ diff --git a/mojo/edk/embedder/scoped_platform_handle.h b/mojo/edk/embedder/scoped_platform_handle.h new file mode 100644 index 0000000..15b80ea --- /dev/null +++ b/mojo/edk/embedder/scoped_platform_handle.h @@ -0,0 +1,63 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ +#define MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/macros.h" + +namespace mojo { +namespace edk { + +class MOJO_SYSTEM_IMPL_EXPORT ScopedPlatformHandle { + public: + ScopedPlatformHandle() {} + explicit ScopedPlatformHandle(PlatformHandle handle) : handle_(handle) {} + ~ScopedPlatformHandle() { handle_.CloseIfNecessary(); } + + // Move-only constructor and operator=. + ScopedPlatformHandle(ScopedPlatformHandle&& other) + : handle_(other.release()) {} + + ScopedPlatformHandle& operator=(ScopedPlatformHandle&& other) { + if (this != &other) + handle_ = other.release(); + return *this; + } + + const PlatformHandle& get() const { return handle_; } + + void swap(ScopedPlatformHandle& other) { + PlatformHandle temp = handle_; + handle_ = other.handle_; + other.handle_ = temp; + } + + PlatformHandle release() WARN_UNUSED_RESULT { + PlatformHandle rv = handle_; + handle_ = PlatformHandle(); + return rv; + } + + void reset(PlatformHandle handle = PlatformHandle()) { + handle_.CloseIfNecessary(); + handle_ = handle; + } + + bool is_valid() const { return handle_.is_valid(); } + + private: + PlatformHandle handle_; + + DISALLOW_COPY_AND_ASSIGN(ScopedPlatformHandle); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_SCOPED_PLATFORM_HANDLE_H_ diff --git a/mojo/edk/embedder/test_embedder.cc b/mojo/edk/embedder/test_embedder.cc new file mode 100644 index 0000000..9658010 --- /dev/null +++ b/mojo/edk/embedder/test_embedder.cc @@ -0,0 +1,46 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/embedder/test_embedder.h" + +#include + +#include "base/logging.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/handle_table.h" + +namespace mojo { + +namespace edk { +namespace internal { + +bool ShutdownCheckNoLeaks(Core* core) { + std::vector leaked_handles; + core->GetActiveHandlesForTest(&leaked_handles); + if (leaked_handles.empty()) + return true; + for (auto handle : leaked_handles) + LOG(ERROR) << "Mojo embedder shutdown: Leaking handle " << handle; + return false; +} + +} // namespace internal + +namespace test { + +bool Shutdown() { + CHECK(internal::g_core); + bool rv = internal::ShutdownCheckNoLeaks(internal::g_core); + delete internal::g_core; + internal::g_core = nullptr; + + return rv; +} + +} // namespace test +} // namespace edk + +} // namespace mojo diff --git a/mojo/edk/embedder/test_embedder.h b/mojo/edk/embedder/test_embedder.h new file mode 100644 index 0000000..c64ba17 --- /dev/null +++ b/mojo/edk/embedder/test_embedder.h @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_ +#define MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_ + +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { +namespace test { + +// This shuts down the global, singleton instance. (Note: "Real" embedders are +// not expected to ever shut down this instance. This |Shutdown()| function will +// do more work to ensure that tests don't leak, etc.) Returns true if there +// were no problems, false if there were leaks -- i.e., handles still open -- or +// any other problems. +// +// Note: It is up to the caller to ensure that there are not outstanding +// callbacks from |CreateChannel()| before calling this. +MOJO_SYSTEM_IMPL_EXPORT bool Shutdown(); + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_EMBEDDER_TEST_EMBEDDER_H_ diff --git a/mojo/edk/js/BUILD.gn b/mojo/edk/js/BUILD.gn new file mode 100644 index 0000000..fc1e03c --- /dev/null +++ b/mojo/edk/js/BUILD.gn @@ -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. + +component("js") { + sources = [ + "core.cc", + "core.h", + "drain_data.cc", + "drain_data.h", + "handle.cc", + "handle.h", + "handle_close_observer.h", + "js_export.h", + "mojo_runner_delegate.cc", + "mojo_runner_delegate.h", + "support.cc", + "support.h", + "threading.cc", + "threading.h", + "waiting_callback.cc", + "waiting_callback.h", + ] + + public_deps = [ + "//base", + "//gin", + "//v8", + ] + + deps = [ + "//mojo/public/cpp/system", + ] + defines = [ "MOJO_JS_IMPLEMENTATION" ] +} diff --git a/mojo/edk/js/core.cc b/mojo/edk/js/core.cc new file mode 100644 index 0000000..baccc4c --- /dev/null +++ b/mojo/edk/js/core.cc @@ -0,0 +1,454 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/js/core.h" + +#include +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "gin/arguments.h" +#include "gin/array_buffer.h" +#include "gin/converter.h" +#include "gin/dictionary.h" +#include "gin/function_template.h" +#include "gin/handle.h" +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "gin/public/wrapper_info.h" +#include "gin/wrappable.h" +#include "mojo/edk/js/drain_data.h" +#include "mojo/edk/js/handle.h" +#include "mojo/public/cpp/system/wait.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +MojoResult CloseHandle(gin::Handle handle) { + if (!handle->get().is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + handle->Close(); + return MOJO_RESULT_OK; +} + +gin::Dictionary QueryHandleSignalsState(const gin::Arguments& args, + mojo::Handle handle) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + if (!handle.is_valid()) { + dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT); + } else { + HandleSignalsState state = handle.QuerySignalsState(); + dictionary.Set("result", MOJO_RESULT_OK); + dictionary.Set("satisfiedSignals", state.satisfied_signals); + dictionary.Set("satisfiableSignals", state.satisfiable_signals); + } + return dictionary; +} + +gin::Dictionary WaitHandle(const gin::Arguments& args, + mojo::Handle handle, + MojoHandleSignals signals) { + v8::Isolate* isolate = args.isolate(); + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate); + + MojoHandleSignalsState signals_state; + MojoResult result = Wait(handle, signals, &signals_state); + dictionary.Set("result", result); + + if (result != MOJO_RESULT_OK && result != MOJO_RESULT_FAILED_PRECONDITION) { + dictionary.Set("signalsState", v8::Null(isolate).As()); + } else { + gin::Dictionary signalsStateDict = gin::Dictionary::CreateEmpty(isolate); + signalsStateDict.Set("satisfiedSignals", signals_state.satisfied_signals); + signalsStateDict.Set("satisfiableSignals", + signals_state.satisfiable_signals); + dictionary.Set("signalsState", signalsStateDict); + } + + return dictionary; +} + +gin::Dictionary CreateMessagePipe(const gin::Arguments& args) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT); + + MojoHandle handle0 = MOJO_HANDLE_INVALID; + MojoHandle handle1 = MOJO_HANDLE_INVALID; + MojoResult result = MOJO_RESULT_OK; + + v8::Handle options_value = args.PeekNext(); + if (options_value.IsEmpty() || options_value->IsNull() || + options_value->IsUndefined()) { + result = MojoCreateMessagePipe(NULL, &handle0, &handle1); + } else if (options_value->IsObject()) { + gin::Dictionary options_dict(args.isolate(), options_value->ToObject()); + MojoCreateMessagePipeOptions options; + // For future struct_size, we can probably infer that from the presence of + // properties in options_dict. For now, it's always 8. + options.struct_size = 8; + // Ideally these would be optional. But the interface makes it hard to + // typecheck them then. + if (!options_dict.Get("flags", &options.flags)) { + return dictionary; + } + + result = MojoCreateMessagePipe(&options, &handle0, &handle1); + } else { + return dictionary; + } + + CHECK_EQ(MOJO_RESULT_OK, result); + + dictionary.Set("result", result); + dictionary.Set("handle0", mojo::Handle(handle0)); + dictionary.Set("handle1", mojo::Handle(handle1)); + return dictionary; +} + +MojoResult WriteMessage( + mojo::Handle handle, + const gin::ArrayBufferView& buffer, + const std::vector >& handles, + MojoWriteMessageFlags flags) { + std::vector raw_handles(handles.size()); + for (size_t i = 0; i < handles.size(); ++i) + raw_handles[i] = handles[i]->get().value(); + MojoResult rv = MojoWriteMessage(handle.value(), + buffer.bytes(), + static_cast(buffer.num_bytes()), + raw_handles.empty() ? NULL : &raw_handles[0], + static_cast(raw_handles.size()), + flags); + // MojoWriteMessage takes ownership of the handles, so release them here. + for (size_t i = 0; i < handles.size(); ++i) + ignore_result(handles[i]->release()); + + return rv; +} + +gin::Dictionary ReadMessage(const gin::Arguments& args, + mojo::Handle handle, + MojoReadMessageFlags flags) { + uint32_t num_bytes = 0; + uint32_t num_handles = 0; + MojoResult result = MojoReadMessage( + handle.value(), NULL, &num_bytes, NULL, &num_handles, flags); + if (result != MOJO_RESULT_RESOURCE_EXHAUSTED) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + return dictionary; + } + + v8::Handle array_buffer = + v8::ArrayBuffer::New(args.isolate(), num_bytes); + std::vector handles(num_handles); + + gin::ArrayBuffer buffer; + ConvertFromV8(args.isolate(), array_buffer, &buffer); + CHECK(buffer.num_bytes() == num_bytes); + + result = MojoReadMessage(handle.value(), + buffer.bytes(), + &num_bytes, + handles.empty() ? NULL : + reinterpret_cast(&handles[0]), + &num_handles, + flags); + + CHECK(buffer.num_bytes() == num_bytes); + CHECK(handles.size() == num_handles); + + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + dictionary.Set("buffer", array_buffer); + dictionary.Set("handles", handles); + return dictionary; +} + +gin::Dictionary CreateDataPipe(const gin::Arguments& args) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", MOJO_RESULT_INVALID_ARGUMENT); + + MojoHandle producer_handle = MOJO_HANDLE_INVALID; + MojoHandle consumer_handle = MOJO_HANDLE_INVALID; + MojoResult result = MOJO_RESULT_OK; + + v8::Handle options_value = args.PeekNext(); + if (options_value.IsEmpty() || options_value->IsNull() || + options_value->IsUndefined()) { + result = MojoCreateDataPipe(NULL, &producer_handle, &consumer_handle); + } else if (options_value->IsObject()) { + gin::Dictionary options_dict(args.isolate(), options_value->ToObject()); + MojoCreateDataPipeOptions options; + // For future struct_size, we can probably infer that from the presence of + // properties in options_dict. For now, it's always 16. + options.struct_size = 16; + // Ideally these would be optional. But the interface makes it hard to + // typecheck them then. + if (!options_dict.Get("flags", &options.flags) || + !options_dict.Get("elementNumBytes", &options.element_num_bytes) || + !options_dict.Get("capacityNumBytes", &options.capacity_num_bytes)) { + return dictionary; + } + + result = MojoCreateDataPipe(&options, &producer_handle, &consumer_handle); + } else { + return dictionary; + } + + CHECK_EQ(MOJO_RESULT_OK, result); + + dictionary.Set("result", result); + dictionary.Set("producerHandle", mojo::Handle(producer_handle)); + dictionary.Set("consumerHandle", mojo::Handle(consumer_handle)); + return dictionary; +} + +gin::Dictionary WriteData(const gin::Arguments& args, + mojo::Handle handle, + const gin::ArrayBufferView& buffer, + MojoWriteDataFlags flags) { + uint32_t num_bytes = static_cast(buffer.num_bytes()); + MojoResult result = + MojoWriteData(handle.value(), buffer.bytes(), &num_bytes, flags); + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + dictionary.Set("numBytes", num_bytes); + return dictionary; +} + +gin::Dictionary ReadData(const gin::Arguments& args, + mojo::Handle handle, + MojoReadDataFlags flags) { + uint32_t num_bytes = 0; + MojoResult result = MojoReadData( + handle.value(), NULL, &num_bytes, MOJO_READ_DATA_FLAG_QUERY); + if (result != MOJO_RESULT_OK) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + return dictionary; + } + + v8::Handle array_buffer = + v8::ArrayBuffer::New(args.isolate(), num_bytes); + gin::ArrayBuffer buffer; + ConvertFromV8(args.isolate(), array_buffer, &buffer); + CHECK_EQ(num_bytes, buffer.num_bytes()); + + result = MojoReadData(handle.value(), buffer.bytes(), &num_bytes, flags); + CHECK_EQ(num_bytes, buffer.num_bytes()); + + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + dictionary.Set("result", result); + dictionary.Set("buffer", array_buffer); + return dictionary; +} + +// Asynchronously read all of the data available for the specified data pipe +// consumer handle until the remote handle is closed or an error occurs. A +// Promise is returned whose settled value is an object like this: +// {result: core.RESULT_OK, buffer: dataArrayBuffer}. If the read failed, +// then the Promise is rejected, the result will be the actual error code, +// and the buffer will contain whatever was read before the error occurred. +// The drainData data pipe handle argument is closed automatically. + +v8::Handle DoDrainData(gin::Arguments* args, + gin::Handle handle) { + return (new DrainData(args->isolate(), handle->release()))->GetPromise(); +} + +bool IsHandle(gin::Arguments* args, v8::Handle val) { + gin::Handle ignore_handle; + return gin::Converter>::FromV8( + args->isolate(), val, &ignore_handle); +} + +gin::Dictionary CreateSharedBuffer(const gin::Arguments& args, + uint64_t num_bytes, + MojoCreateSharedBufferOptionsFlags flags) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + MojoHandle handle = MOJO_HANDLE_INVALID; + MojoCreateSharedBufferOptions options; + // The |flags| is mandatory parameter for CreateSharedBuffer, and it will + // be always initialized in MojoCreateSharedBufferOptions struct. For + // forward compatibility, set struct_size to be 8 bytes (struct_size + flags), + // so that validator will only check the field that is set. + options.struct_size = 8; + options.flags = flags; + MojoResult result = MojoCreateSharedBuffer(&options, num_bytes, &handle); + if (result != MOJO_RESULT_OK) { + dictionary.Set("result", result); + return dictionary; + } + + dictionary.Set("result", result); + dictionary.Set("handle", mojo::Handle(handle)); + + return dictionary; +} + +gin::Dictionary DuplicateBufferHandle( + const gin::Arguments& args, + mojo::Handle handle, + MojoDuplicateBufferHandleOptionsFlags flags) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + MojoHandle duped = MOJO_HANDLE_INVALID; + MojoDuplicateBufferHandleOptions options; + // The |flags| is mandatory parameter for DuplicateBufferHandle, and it will + // be always initialized in MojoDuplicateBufferHandleOptions struct. For + // forward compatibility, set struct_size to be 8 bytes (struct_size + flags), + // so that validator will only check the field that is set. + options.struct_size = 8; + options.flags = flags; + MojoResult result = + MojoDuplicateBufferHandle(handle.value(), &options, &duped); + if (result != MOJO_RESULT_OK) { + dictionary.Set("result", result); + return dictionary; + } + + dictionary.Set("result", result); + dictionary.Set("handle", mojo::Handle(duped)); + + return dictionary; +} + +gin::Dictionary MapBuffer(const gin::Arguments& args, + mojo::Handle handle, + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags) { + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(args.isolate()); + void* data = nullptr; + MojoResult result = + MojoMapBuffer(handle.value(), offset, num_bytes, &data, flags); + if (result != MOJO_RESULT_OK) { + dictionary.Set("result", result); + return dictionary; + } + + v8::Handle array_buffer = + v8::ArrayBuffer::New(args.isolate(), data, num_bytes); + + dictionary.Set("result", result); + dictionary.Set("buffer", array_buffer); + + return dictionary; +} + +MojoResult UnmapBuffer(const gin::Arguments& args, + const v8::Handle& buffer) { + // Buffer must be external, created by MapBuffer + if (!buffer->IsExternal()) + return MOJO_RESULT_INVALID_ARGUMENT; + + return MojoUnmapBuffer(buffer->GetContents().Data()); +} + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +} // namespace + +const char Core::kModuleName[] = "mojo/public/js/core"; + +v8::Local Core::GetModule(v8::Isolate* isolate) { + gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); + v8::Local templ = data->GetObjectTemplate( + &g_wrapper_info); + + if (templ.IsEmpty()) { + templ = + gin::ObjectTemplateBuilder(isolate) + // TODO(mpcomplete): Should these just be methods on the JS Handle + // object? + .SetMethod("close", CloseHandle) + .SetMethod("queryHandleSignalsState", QueryHandleSignalsState) + .SetMethod("wait", WaitHandle) + .SetMethod("createMessagePipe", CreateMessagePipe) + .SetMethod("writeMessage", WriteMessage) + .SetMethod("readMessage", ReadMessage) + .SetMethod("createDataPipe", CreateDataPipe) + .SetMethod("writeData", WriteData) + .SetMethod("readData", ReadData) + .SetMethod("drainData", DoDrainData) + .SetMethod("isHandle", IsHandle) + .SetMethod("createSharedBuffer", CreateSharedBuffer) + .SetMethod("duplicateBufferHandle", DuplicateBufferHandle) + .SetMethod("mapBuffer", MapBuffer) + .SetMethod("unmapBuffer", UnmapBuffer) + + .SetValue("RESULT_OK", MOJO_RESULT_OK) + .SetValue("RESULT_CANCELLED", MOJO_RESULT_CANCELLED) + .SetValue("RESULT_UNKNOWN", MOJO_RESULT_UNKNOWN) + .SetValue("RESULT_INVALID_ARGUMENT", MOJO_RESULT_INVALID_ARGUMENT) + .SetValue("RESULT_DEADLINE_EXCEEDED", MOJO_RESULT_DEADLINE_EXCEEDED) + .SetValue("RESULT_NOT_FOUND", MOJO_RESULT_NOT_FOUND) + .SetValue("RESULT_ALREADY_EXISTS", MOJO_RESULT_ALREADY_EXISTS) + .SetValue("RESULT_PERMISSION_DENIED", MOJO_RESULT_PERMISSION_DENIED) + .SetValue("RESULT_RESOURCE_EXHAUSTED", + MOJO_RESULT_RESOURCE_EXHAUSTED) + .SetValue("RESULT_FAILED_PRECONDITION", + MOJO_RESULT_FAILED_PRECONDITION) + .SetValue("RESULT_ABORTED", MOJO_RESULT_ABORTED) + .SetValue("RESULT_OUT_OF_RANGE", MOJO_RESULT_OUT_OF_RANGE) + .SetValue("RESULT_UNIMPLEMENTED", MOJO_RESULT_UNIMPLEMENTED) + .SetValue("RESULT_INTERNAL", MOJO_RESULT_INTERNAL) + .SetValue("RESULT_UNAVAILABLE", MOJO_RESULT_UNAVAILABLE) + .SetValue("RESULT_DATA_LOSS", MOJO_RESULT_DATA_LOSS) + .SetValue("RESULT_BUSY", MOJO_RESULT_BUSY) + .SetValue("RESULT_SHOULD_WAIT", MOJO_RESULT_SHOULD_WAIT) + + .SetValue("HANDLE_SIGNAL_NONE", MOJO_HANDLE_SIGNAL_NONE) + .SetValue("HANDLE_SIGNAL_READABLE", MOJO_HANDLE_SIGNAL_READABLE) + .SetValue("HANDLE_SIGNAL_WRITABLE", MOJO_HANDLE_SIGNAL_WRITABLE) + .SetValue("HANDLE_SIGNAL_PEER_CLOSED", + MOJO_HANDLE_SIGNAL_PEER_CLOSED) + + .SetValue("CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE", + MOJO_CREATE_MESSAGE_PIPE_OPTIONS_FLAG_NONE) + + .SetValue("WRITE_MESSAGE_FLAG_NONE", MOJO_WRITE_MESSAGE_FLAG_NONE) + + .SetValue("READ_MESSAGE_FLAG_NONE", MOJO_READ_MESSAGE_FLAG_NONE) + .SetValue("READ_MESSAGE_FLAG_MAY_DISCARD", + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD) + + .SetValue("CREATE_DATA_PIPE_OPTIONS_FLAG_NONE", + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE) + + .SetValue("WRITE_DATA_FLAG_NONE", MOJO_WRITE_DATA_FLAG_NONE) + .SetValue("WRITE_DATA_FLAG_ALL_OR_NONE", + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE) + + .SetValue("READ_DATA_FLAG_NONE", MOJO_READ_DATA_FLAG_NONE) + .SetValue("READ_DATA_FLAG_ALL_OR_NONE", + MOJO_READ_DATA_FLAG_ALL_OR_NONE) + .SetValue("READ_DATA_FLAG_DISCARD", MOJO_READ_DATA_FLAG_DISCARD) + .SetValue("READ_DATA_FLAG_QUERY", MOJO_READ_DATA_FLAG_QUERY) + .SetValue("READ_DATA_FLAG_PEEK", MOJO_READ_DATA_FLAG_PEEK) + .SetValue("CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE", + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE) + + .SetValue("DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE", + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE) + + .SetValue("DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY", + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY) + + .SetValue("MAP_BUFFER_FLAG_NONE", MOJO_MAP_BUFFER_FLAG_NONE) + .Build(); + + data->SetObjectTemplate(&g_wrapper_info, templ); + } + + return templ->NewInstance(); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/core.h b/mojo/edk/js/core.h new file mode 100644 index 0000000..97ef5df --- /dev/null +++ b/mojo/edk/js/core.h @@ -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. + +#ifndef MOJO_EDK_JS_CORE_H_ +#define MOJO_EDK_JS_CORE_H_ + +#include "mojo/edk/js/js_export.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace edk { +namespace js { + +class MOJO_JS_EXPORT Core { + public: + static const char kModuleName[]; + static v8::Local GetModule(v8::Isolate* isolate); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_CORE_H_ diff --git a/mojo/edk/js/drain_data.cc b/mojo/edk/js/drain_data.cc new file mode 100644 index 0000000..334ced3 --- /dev/null +++ b/mojo/edk/js/drain_data.cc @@ -0,0 +1,129 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/js/drain_data.h" + +#include +#include + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "gin/array_buffer.h" +#include "gin/converter.h" +#include "gin/dictionary.h" +#include "gin/per_context_data.h" +#include "gin/per_isolate_data.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace edk { +namespace js { + +DrainData::DrainData(v8::Isolate* isolate, mojo::Handle handle) + : isolate_(isolate), + handle_(DataPipeConsumerHandle(handle.value())), + handle_watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC) { + v8::Handle context(isolate_->GetCurrentContext()); + runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr(); + + WaitForData(); +} + +v8::Handle DrainData::GetPromise() { + CHECK(resolver_.IsEmpty()); + v8::Handle resolver( + v8::Promise::Resolver::New(isolate_)); + resolver_.Reset(isolate_, resolver); + return resolver->GetPromise(); +} + +DrainData::~DrainData() { + resolver_.Reset(); +} + +void DrainData::WaitForData() { + handle_watcher_.Watch( + handle_.get(), MOJO_HANDLE_SIGNAL_READABLE, + base::Bind(&DrainData::DataReady, base::Unretained(this))); +} + +void DrainData::DataReady(MojoResult result) { + if (result != MOJO_RESULT_OK) { + DeliverData(result); + return; + } + while (result == MOJO_RESULT_OK) { + result = ReadData(); + if (result == MOJO_RESULT_SHOULD_WAIT) + WaitForData(); + else if (result != MOJO_RESULT_OK) + DeliverData(result); + } +} + +MojoResult DrainData::ReadData() { + const void* buffer; + uint32_t num_bytes = 0; + MojoResult result = BeginReadDataRaw( + handle_.get(), &buffer, &num_bytes, MOJO_READ_DATA_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return result; + const char* p = static_cast(buffer); + data_buffers_.push_back(base::MakeUnique(p, p + num_bytes)); + return EndReadDataRaw(handle_.get(), num_bytes); +} + +void DrainData::DeliverData(MojoResult result) { + if (!runner_) { + delete this; + return; + } + + size_t total_bytes = 0; + for (unsigned i = 0; i < data_buffers_.size(); i++) + total_bytes += data_buffers_[i]->size(); + + // Create a total_bytes length ArrayBuffer return value. + gin::Runner::Scope scope(runner_.get()); + v8::Handle array_buffer = + v8::ArrayBuffer::New(isolate_, total_bytes); + gin::ArrayBuffer buffer; + ConvertFromV8(isolate_, array_buffer, &buffer); + CHECK_EQ(total_bytes, buffer.num_bytes()); + + // Copy the data_buffers into the ArrayBuffer. + char* array_buffer_ptr = static_cast(buffer.bytes()); + size_t offset = 0; + for (size_t i = 0; i < data_buffers_.size(); i++) { + size_t num_bytes = data_buffers_[i]->size(); + if (num_bytes == 0) + continue; + const char* data_buffer_ptr = &((*data_buffers_[i])[0]); + memcpy(array_buffer_ptr + offset, data_buffer_ptr, num_bytes); + offset += num_bytes; + } + + // The "settled" value of the promise always includes all of the data + // that was read before either an error occurred or the remote pipe handle + // was closed. The latter is indicated by MOJO_RESULT_FAILED_PRECONDITION. + + v8::Handle resolver( + v8::Local::New(isolate_, resolver_)); + + gin::Dictionary dictionary = gin::Dictionary::CreateEmpty(isolate_); + dictionary.Set("result", result); + dictionary.Set("buffer", array_buffer); + v8::Handle settled_value(ConvertToV8(isolate_, dictionary)); + + if (result == MOJO_RESULT_FAILED_PRECONDITION) + resolver->Resolve(settled_value); + else + resolver->Reject(settled_value); + + delete this; +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/drain_data.h b/mojo/edk/js/drain_data.h new file mode 100644 index 0000000..42da90f --- /dev/null +++ b/mojo/edk/js/drain_data.h @@ -0,0 +1,65 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_JS_DRAIN_DATA_H_ +#define MOJO_EDK_JS_DRAIN_DATA_H_ + +#include +#include + +#include "gin/runner.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace edk { +namespace js { + +// This class is the implementation of the Mojo JavaScript core module's +// drainData() method. It is not intended to be used directly. The caller +// allocates a DrainData on the heap and returns GetPromise() to JS. The +// implementation deletes itself after reading as much data as possible +// and rejecting or resolving the Promise. + +class DrainData { + public: + // Starts waiting for data on the specified data pipe consumer handle. + // See WaitForData(). The constructor does not block. + DrainData(v8::Isolate* isolate, mojo::Handle handle); + + // Returns a Promise that will be settled when no more data can be read. + // Should be called just once on a newly allocated DrainData object. + v8::Handle GetPromise(); + + private: + ~DrainData(); + + // Waits for data to be available. DataReady() will be notified. + void WaitForData(); + + // Use ReadData() to read whatever is availble now on handle_ and save + // it in data_buffers_. + void DataReady(MojoResult result); + MojoResult ReadData(); + + // When the remote data pipe handle is closed, or an error occurs, deliver + // all of the buffered data to the JS Promise and then delete this. + void DeliverData(MojoResult result); + + using DataBuffer = std::vector; + + v8::Isolate* isolate_; + ScopedDataPipeConsumerHandle handle_; + SimpleWatcher handle_watcher_; + base::WeakPtr runner_; + v8::UniquePersistent resolver_; + std::vector> data_buffers_; +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_DRAIN_DATA_H_ diff --git a/mojo/edk/js/handle.cc b/mojo/edk/js/handle.cc new file mode 100644 index 0000000..7da8e9f --- /dev/null +++ b/mojo/edk/js/handle.cc @@ -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. + +#include "mojo/edk/js/handle.h" + +#include "mojo/edk/js/handle_close_observer.h" + +namespace mojo { +namespace edk { +namespace js { + +gin::WrapperInfo HandleWrapper::kWrapperInfo = { gin::kEmbedderNativeGin }; + +HandleWrapper::HandleWrapper(MojoHandle handle) + : handle_(mojo::Handle(handle)) { +} + +HandleWrapper::~HandleWrapper() { + NotifyCloseObservers(); +} + +void HandleWrapper::Close() { + NotifyCloseObservers(); + handle_.reset(); +} + +void HandleWrapper::AddCloseObserver(HandleCloseObserver* observer) { + close_observers_.AddObserver(observer); +} + +void HandleWrapper::RemoveCloseObserver(HandleCloseObserver* observer) { + close_observers_.RemoveObserver(observer); +} + +void HandleWrapper::NotifyCloseObservers() { + if (!handle_.is_valid()) + return; + + for (auto& observer : close_observers_) + observer.OnWillCloseHandle(); +} + +} // namespace js +} // namespace edk +} // namespace mojo + +namespace gin { + +v8::Handle Converter::ToV8(v8::Isolate* isolate, + const mojo::Handle& val) { + if (!val.is_valid()) + return v8::Null(isolate); + return mojo::edk::js::HandleWrapper::Create(isolate, val.value()).ToV8(); +} + +bool Converter::FromV8(v8::Isolate* isolate, + v8::Handle val, + mojo::Handle* out) { + if (val->IsNull()) { + *out = mojo::Handle(); + return true; + } + + gin::Handle handle; + if (!Converter>::FromV8( + isolate, val, &handle)) + return false; + + *out = handle->get(); + return true; +} + +v8::Handle Converter::ToV8( + v8::Isolate* isolate, mojo::MessagePipeHandle val) { + return Converter::ToV8(isolate, val); +} + +bool Converter::FromV8(v8::Isolate* isolate, + v8::Handle val, + mojo::MessagePipeHandle* out) { + return Converter::FromV8(isolate, val, out); +} + +} // namespace gin diff --git a/mojo/edk/js/handle.h b/mojo/edk/js/handle.h new file mode 100644 index 0000000..60652ed --- /dev/null +++ b/mojo/edk/js/handle.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_EDK_JS_HANDLE_H_ +#define MOJO_EDK_JS_HANDLE_H_ + +#include + +#include "base/observer_list.h" +#include "gin/converter.h" +#include "gin/handle.h" +#include "gin/wrappable.h" +#include "mojo/edk/js/js_export.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace edk { +namespace js { + +class HandleCloseObserver; + +// Wrapper for mojo Handles exposed to JavaScript. This ensures the Handle +// is Closed when its JS object is garbage collected. +class MOJO_JS_EXPORT HandleWrapper : public gin::Wrappable { + public: + static gin::WrapperInfo kWrapperInfo; + + static gin::Handle Create(v8::Isolate* isolate, + MojoHandle handle) { + return gin::CreateHandle(isolate, new HandleWrapper(handle)); + } + + mojo::Handle get() const { return handle_.get(); } + mojo::Handle release() { return handle_.release(); } + void Close(); + + void AddCloseObserver(HandleCloseObserver* observer); + void RemoveCloseObserver(HandleCloseObserver* observer); + + protected: + HandleWrapper(MojoHandle handle); + ~HandleWrapper() override; + void NotifyCloseObservers(); + + mojo::ScopedHandle handle_; + base::ObserverList close_observers_; +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +namespace gin { + +// Note: It's important to use this converter rather than the one for +// MojoHandle, since that will do a simple int32_t conversion. It's unfortunate +// there's no way to prevent against accidental use. +// TODO(mpcomplete): define converters for all Handle subtypes. +template <> +struct MOJO_JS_EXPORT Converter { + static v8::Handle ToV8(v8::Isolate* isolate, + const mojo::Handle& val); + static bool FromV8(v8::Isolate* isolate, v8::Handle val, + mojo::Handle* out); +}; + +template <> +struct MOJO_JS_EXPORT Converter { + static v8::Handle ToV8(v8::Isolate* isolate, + mojo::MessagePipeHandle val); + static bool FromV8(v8::Isolate* isolate, + v8::Handle val, + mojo::MessagePipeHandle* out); +}; + +// We need to specialize the normal gin::Handle converter in order to handle +// converting |null| to a wrapper for an empty mojo::Handle. +template <> +struct MOJO_JS_EXPORT Converter> { + static v8::Handle ToV8( + v8::Isolate* isolate, + const gin::Handle& val) { + return val.ToV8(); + } + + static bool FromV8(v8::Isolate* isolate, + v8::Handle val, + gin::Handle* out) { + if (val->IsNull()) { + *out = mojo::edk::js::HandleWrapper::Create(isolate, MOJO_HANDLE_INVALID); + return true; + } + + mojo::edk::js::HandleWrapper* object = NULL; + if (!Converter::FromV8(isolate, val, + &object)) { + return false; + } + *out = gin::Handle(val, object); + return true; + } +}; + +} // namespace gin + +#endif // MOJO_EDK_JS_HANDLE_H_ diff --git a/mojo/edk/js/handle_close_observer.h b/mojo/edk/js/handle_close_observer.h new file mode 100644 index 0000000..c7b935e --- /dev/null +++ b/mojo/edk/js/handle_close_observer.h @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_ +#define MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_ + +namespace mojo { +namespace edk { +namespace js { + +class HandleCloseObserver { + public: + virtual void OnWillCloseHandle() = 0; + + protected: + virtual ~HandleCloseObserver() {} +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_HANDLE_CLOSE_OBSERVER_H_ diff --git a/mojo/edk/js/handle_unittest.cc b/mojo/edk/js/handle_unittest.cc new file mode 100644 index 0000000..dd2562f --- /dev/null +++ b/mojo/edk/js/handle_unittest.cc @@ -0,0 +1,92 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/macros.h" +#include "mojo/edk/js/handle.h" +#include "mojo/edk/js/handle_close_observer.h" +#include "mojo/public/cpp/system/core.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace js { + +class HandleWrapperTest : public testing::Test, + public HandleCloseObserver { + public: + HandleWrapperTest() : closes_observed_(0) {} + + void OnWillCloseHandle() override { closes_observed_++; } + + protected: + int closes_observed_; + + private: + DISALLOW_COPY_AND_ASSIGN(HandleWrapperTest); +}; + +class TestHandleWrapper : public HandleWrapper { + public: + explicit TestHandleWrapper(MojoHandle handle) : HandleWrapper(handle) {} + + private: + DISALLOW_COPY_AND_ASSIGN(TestHandleWrapper); +}; + +// Test that calling Close() on a HandleWrapper for an invalid handle does not +// notify observers. +TEST_F(HandleWrapperTest, CloseWithInvalidHandle) { + { + TestHandleWrapper wrapper(MOJO_HANDLE_INVALID); + wrapper.AddCloseObserver(this); + ASSERT_EQ(0, closes_observed_); + wrapper.Close(); + EXPECT_EQ(0, closes_observed_); + } + EXPECT_EQ(0, closes_observed_); +} + +// Test that destroying a HandleWrapper for an invalid handle does not notify +// observers. +TEST_F(HandleWrapperTest, DestroyWithInvalidHandle) { + { + TestHandleWrapper wrapper(MOJO_HANDLE_INVALID); + wrapper.AddCloseObserver(this); + ASSERT_EQ(0, closes_observed_); + } + EXPECT_EQ(0, closes_observed_); +} + +// Test that calling Close on a HandleWrapper for a valid handle notifies +// observers once. +TEST_F(HandleWrapperTest, CloseWithValidHandle) { + { + mojo::MessagePipe pipe; + TestHandleWrapper wrapper(pipe.handle0.release().value()); + wrapper.AddCloseObserver(this); + ASSERT_EQ(0, closes_observed_); + wrapper.Close(); + EXPECT_EQ(1, closes_observed_); + // Check that calling close again doesn't notify observers. + wrapper.Close(); + EXPECT_EQ(1, closes_observed_); + } + // Check that destroying a closed HandleWrapper doesn't notify observers. + EXPECT_EQ(1, closes_observed_); +} + +// Test that destroying a HandleWrapper for a valid handle notifies observers. +TEST_F(HandleWrapperTest, DestroyWithValidHandle) { + { + mojo::MessagePipe pipe; + TestHandleWrapper wrapper(pipe.handle0.release().value()); + wrapper.AddCloseObserver(this); + ASSERT_EQ(0, closes_observed_); + } + EXPECT_EQ(1, closes_observed_); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/js_export.h b/mojo/edk/js/js_export.h new file mode 100644 index 0000000..179113c --- /dev/null +++ b/mojo/edk/js/js_export.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_EDK_JS_JS_EXPORT_H_ +#define MOJO_EDK_JS_JS_EXPORT_H_ + +// Defines MOJO_JS_EXPORT so that functionality implemented by //mojo/edk/js can +// be exported to consumers. + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_JS_IMPLEMENTATION) +#define MOJO_JS_EXPORT __declspec(dllexport) +#else +#define MOJO_JS_EXPORT __declspec(dllimport) +#endif // defined(MOJO_JS_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_JS_IMPLEMENTATION) +#define MOJO_JS_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_JS_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_JS_EXPORT +#endif + +#endif // MOJO_EDK_JS_JS_EXPORT_H_ diff --git a/mojo/edk/js/mojo_runner_delegate.cc b/mojo/edk/js/mojo_runner_delegate.cc new file mode 100644 index 0000000..dda0b2c --- /dev/null +++ b/mojo/edk/js/mojo_runner_delegate.cc @@ -0,0 +1,80 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/js/mojo_runner_delegate.h" + +#include "base/bind.h" +#include "base/path_service.h" +#include "gin/converter.h" +#include "gin/modules/console.h" +#include "gin/modules/module_registry.h" +#include "gin/modules/timer.h" +#include "gin/try_catch.h" +#include "mojo/edk/js/core.h" +#include "mojo/edk/js/handle.h" +#include "mojo/edk/js/support.h" +#include "mojo/edk/js/threading.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +// TODO(abarth): Rather than loading these modules from the file system, we +// should load them from the network via Mojo IPC. +std::vector GetModuleSearchPaths() { + std::vector search_paths(2); + PathService::Get(base::DIR_SOURCE_ROOT, &search_paths[0]); + PathService::Get(base::DIR_EXE, &search_paths[1]); + search_paths[1] = search_paths[1].AppendASCII("gen"); + return search_paths; +} + +void StartCallback(base::WeakPtr runner, + MojoHandle pipe, + v8::Handle module) { + v8::Isolate* isolate = runner->GetContextHolder()->isolate(); + v8::Handle start; + CHECK(gin::ConvertFromV8(isolate, module, &start)); + + v8::Handle args[] = { + gin::ConvertToV8(isolate, Handle(pipe)) }; + runner->Call(start, runner->global(), 1, args); +} + +} // namespace + +MojoRunnerDelegate::MojoRunnerDelegate() + : ModuleRunnerDelegate(GetModuleSearchPaths()) { + AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule); + AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule); + AddBuiltinModule(Core::kModuleName, Core::GetModule); + AddBuiltinModule(Support::kModuleName, Support::GetModule); + AddBuiltinModule(Threading::kModuleName, Threading::GetModule); +} + +MojoRunnerDelegate::~MojoRunnerDelegate() { +} + +void MojoRunnerDelegate::Start(gin::Runner* runner, + MojoHandle pipe, + const std::string& module) { + gin::Runner::Scope scope(runner); + gin::ModuleRegistry* registry = + gin::ModuleRegistry::From(runner->GetContextHolder()->context()); + registry->LoadModule(runner->GetContextHolder()->isolate(), module, + base::Bind(StartCallback, runner->GetWeakPtr(), pipe)); + AttemptToLoadMoreModules(runner); +} + +void MojoRunnerDelegate::UnhandledException(gin::ShellRunner* runner, + gin::TryCatch& try_catch) { + gin::ModuleRunnerDelegate::UnhandledException(runner, try_catch); + LOG(ERROR) << try_catch.GetStackTrace(); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/mojo_runner_delegate.h b/mojo/edk/js/mojo_runner_delegate.h new file mode 100644 index 0000000..9ab325c --- /dev/null +++ b/mojo/edk/js/mojo_runner_delegate.h @@ -0,0 +1,36 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_ +#define MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_ + +#include "base/macros.h" +#include "gin/modules/module_runner_delegate.h" +#include "mojo/edk/js/js_export.h" +#include "mojo/public/c/system/core.h" + +namespace mojo { +namespace edk { +namespace js { + +class MOJO_JS_EXPORT MojoRunnerDelegate : public gin::ModuleRunnerDelegate { + public: + MojoRunnerDelegate(); + ~MojoRunnerDelegate() override; + + void Start(gin::Runner* runner, MojoHandle pipe, const std::string& module); + + private: + // From ModuleRunnerDelegate: + void UnhandledException(gin::ShellRunner* runner, + gin::TryCatch& try_catch) override; + + DISALLOW_COPY_AND_ASSIGN(MojoRunnerDelegate); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_MOJO_RUNNER_DELEGATE_H_ diff --git a/mojo/edk/js/support.cc b/mojo/edk/js/support.cc new file mode 100644 index 0000000..404cb9b --- /dev/null +++ b/mojo/edk/js/support.cc @@ -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. + +#include "mojo/edk/js/support.h" + +#include "base/bind.h" +#include "gin/arguments.h" +#include "gin/converter.h" +#include "gin/function_template.h" +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "gin/public/wrapper_info.h" +#include "gin/wrappable.h" +#include "mojo/edk/js/handle.h" +#include "mojo/edk/js/waiting_callback.h" +#include "mojo/public/cpp/system/core.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +WaitingCallback* AsyncWait(const gin::Arguments& args, + gin::Handle handle, + MojoHandleSignals signals, + v8::Handle callback) { + return WaitingCallback::Create( + args.isolate(), callback, handle, signals, true /* one_shot */).get(); +} + +void CancelWait(WaitingCallback* waiting_callback) { + waiting_callback->Cancel(); +} + +WaitingCallback* Watch(const gin::Arguments& args, + gin::Handle handle, + MojoHandleSignals signals, + v8::Handle callback) { + return WaitingCallback::Create( + args.isolate(), callback, handle, signals, false /* one_shot */).get(); +} + +void CancelWatch(WaitingCallback* waiting_callback) { + waiting_callback->Cancel(); +} + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +} // namespace + +const char Support::kModuleName[] = "mojo/public/js/support"; + +v8::Local Support::GetModule(v8::Isolate* isolate) { + gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); + v8::Local templ = data->GetObjectTemplate( + &g_wrapper_info); + + if (templ.IsEmpty()) { + templ = gin::ObjectTemplateBuilder(isolate) + // TODO(rockot): Remove asyncWait and cancelWait. + .SetMethod("asyncWait", AsyncWait) + .SetMethod("cancelWait", CancelWait) + .SetMethod("watch", Watch) + .SetMethod("cancelWatch", CancelWatch) + .Build(); + + data->SetObjectTemplate(&g_wrapper_info, templ); + } + + return templ->NewInstance(); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/support.h b/mojo/edk/js/support.h new file mode 100644 index 0000000..551f5ac --- /dev/null +++ b/mojo/edk/js/support.h @@ -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. + +#ifndef MOJO_EDK_JS_SUPPORT_H_ +#define MOJO_EDK_JS_SUPPORT_H_ + +#include "mojo/edk/js/js_export.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace edk { +namespace js { + +class MOJO_JS_EXPORT Support { + public: + static const char kModuleName[]; + static v8::Local GetModule(v8::Isolate* isolate); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_SUPPORT_H_ diff --git a/mojo/edk/js/tests/BUILD.gn b/mojo/edk/js/tests/BUILD.gn new file mode 100644 index 0000000..f56c4b9 --- /dev/null +++ b/mojo/edk/js/tests/BUILD.gn @@ -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. + +import("//mojo/public/tools/bindings/mojom.gni") +import("//testing/test.gni") + +# TODO(hansmuller): The organization of tests in this directory is weird: +# * Really, js_unittests tests public stuff, so that should live in public +# and be reworked as some sort of apptest. +# * Both js_unittests and js_integration_tests should auto-generate their +# tests somehow. The .cc files are just test runner stubs, including +# explicit lists of .js files. + +group("tests") { + testonly = true + deps = [ + ":mojo_js_integration_tests", + ":mojo_js_unittests", + ] +} + +test("mojo_js_integration_tests") { + deps = [ + ":js_to_cpp_bindings", + "//gin:gin_test", + "//mojo/common", + "//mojo/edk/js", + "//mojo/edk/test:run_all_unittests", + "//mojo/public/cpp/bindings", + "//mojo/public/cpp/system", + "//mojo/public/js:bindings", + ] + + sources = [ + "js_to_cpp_tests.cc", + ] + + data = [ + "js_to_cpp_tests.js", + ] + + configs += [ "//v8:external_startup_data" ] +} + +mojom("js_to_cpp_bindings") { + sources = [ + "js_to_cpp.mojom", + ] +} + +test("mojo_js_unittests") { + deps = [ + "//base", + "//gin:gin_test", + "//mojo/edk/js", + "//mojo/edk/test:run_all_unittests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/system", + "//mojo/public/interfaces/bindings/tests:test_interfaces", + "//mojo/public/js:tests", + ] + + sources = [ + "//mojo/edk/js/handle_unittest.cc", + "run_js_unittests.cc", + ] +} diff --git a/mojo/edk/js/tests/js_to_cpp.mojom b/mojo/edk/js/tests/js_to_cpp.mojom new file mode 100644 index 0000000..688b22b --- /dev/null +++ b/mojo/edk/js/tests/js_to_cpp.mojom @@ -0,0 +1,54 @@ +module js_to_cpp; + +// This struct encompasses all of the basic types, so that they +// may be sent from C++ to JS and back for validation. +struct EchoArgs { + int64 si64; + int32 si32; + int16 si16; + int8 si8; + uint64 ui64; + uint32 ui32; + uint16 ui16; + uint8 ui8; + float float_val; + float float_inf; + float float_nan; + double double_val; + double double_inf; + double double_nan; + string? name; + array? string_array; + handle? message_handle; + handle? data_handle; +}; + +struct EchoArgsList { + EchoArgsList? next; + EchoArgs? item; +}; + +// Note: For messages which control test flow, pick numbers that are unlikely +// to be hit as a result of our deliberate corruption of response messages. +interface CppSide { + // Sent for all tests to notify that the JS side is now ready. + StartTest@88888888(); + + // Indicates end for echo, bit-flip, and back-pointer tests. + TestFinished@99999999(); + + // Responses from specific tests. + PingResponse(); + EchoResponse(EchoArgsList list); + BitFlipResponse(EchoArgsList arg); + BackPointerResponse(EchoArgsList arg); +}; + +interface JsSide { + SetCppSide(CppSide cpp); + + Ping(); + Echo(int32 numIterations, EchoArgs arg); + BitFlip(EchoArgs arg); + BackPointer(EchoArgs arg); +}; diff --git a/mojo/edk/js/tests/js_to_cpp_tests.cc b/mojo/edk/js/tests/js_to_cpp_tests.cc new file mode 100644 index 0000000..b6b74e3 --- /dev/null +++ b/mojo/edk/js/tests/js_to_cpp_tests.cc @@ -0,0 +1,455 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include +#include + +#include +#include + +#include "base/at_exit.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_task_runner_handle.h" +#include "gin/array_buffer.h" +#include "gin/public/isolate_holder.h" +#include "gin/v8_initializer.h" +#include "mojo/common/data_pipe_utils.h" +#include "mojo/edk/js/mojo_runner_delegate.h" +#include "mojo/edk/js/tests/js_to_cpp.mojom.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/lib/validation_errors.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace js { + +// Global value updated by some checks to prevent compilers from optimizing +// reads out of existence. +uint32_t g_waste_accumulator = 0; + +namespace { + +// Negative numbers with different values in each byte, the last of +// which can survive promotion to double and back. +const int8_t kExpectedInt8Value = -65; +const int16_t kExpectedInt16Value = -16961; +const int32_t kExpectedInt32Value = -1145258561; +const int64_t kExpectedInt64Value = -77263311946305LL; + +// Positive numbers with different values in each byte, the last of +// which can survive promotion to double and back. +const uint8_t kExpectedUInt8Value = 65; +const uint16_t kExpectedUInt16Value = 16961; +const uint32_t kExpectedUInt32Value = 1145258561; +const uint64_t kExpectedUInt64Value = 77263311946305LL; + +// Double/float values, including special case constants. +const double kExpectedDoubleVal = 3.14159265358979323846; +const double kExpectedDoubleInf = std::numeric_limits::infinity(); +const double kExpectedDoubleNan = std::numeric_limits::quiet_NaN(); +const float kExpectedFloatVal = static_cast(kExpectedDoubleVal); +const float kExpectedFloatInf = std::numeric_limits::infinity(); +const float kExpectedFloatNan = std::numeric_limits::quiet_NaN(); + +// NaN has the property that it is not equal to itself. +#define EXPECT_NAN(x) EXPECT_NE(x, x) + +void CheckDataPipe(ScopedDataPipeConsumerHandle data_pipe_handle) { + std::string buffer; + bool result = common::BlockingCopyToString(std::move(data_pipe_handle), + &buffer); + EXPECT_TRUE(result); + EXPECT_EQ(64u, buffer.size()); + for (int i = 0; i < 64; ++i) { + EXPECT_EQ(i, buffer[i]); + } +} + +void CheckMessagePipe(MessagePipeHandle message_pipe_handle) { + unsigned char buffer[100]; + uint32_t buffer_size = static_cast(sizeof(buffer)); + MojoResult result = Wait(message_pipe_handle, MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_RESULT_OK, result); + result = ReadMessageRaw( + message_pipe_handle, buffer, &buffer_size, 0, 0, 0); + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(64u, buffer_size); + for (int i = 0; i < 64; ++i) { + EXPECT_EQ(255 - i, buffer[i]); + } +} + +js_to_cpp::EchoArgsPtr BuildSampleEchoArgs() { + js_to_cpp::EchoArgsPtr args(js_to_cpp::EchoArgs::New()); + args->si64 = kExpectedInt64Value; + args->si32 = kExpectedInt32Value; + args->si16 = kExpectedInt16Value; + args->si8 = kExpectedInt8Value; + args->ui64 = kExpectedUInt64Value; + args->ui32 = kExpectedUInt32Value; + args->ui16 = kExpectedUInt16Value; + args->ui8 = kExpectedUInt8Value; + args->float_val = kExpectedFloatVal; + args->float_inf = kExpectedFloatInf; + args->float_nan = kExpectedFloatNan; + args->double_val = kExpectedDoubleVal; + args->double_inf = kExpectedDoubleInf; + args->double_nan = kExpectedDoubleNan; + args->name.emplace("coming"); + args->string_array.emplace(3); + (*args->string_array)[0] = "one"; + (*args->string_array)[1] = "two"; + (*args->string_array)[2] = "three"; + return args; +} + +void CheckSampleEchoArgs(js_to_cpp::EchoArgsPtr arg) { + EXPECT_EQ(kExpectedInt64Value, arg->si64); + EXPECT_EQ(kExpectedInt32Value, arg->si32); + EXPECT_EQ(kExpectedInt16Value, arg->si16); + EXPECT_EQ(kExpectedInt8Value, arg->si8); + EXPECT_EQ(kExpectedUInt64Value, arg->ui64); + EXPECT_EQ(kExpectedUInt32Value, arg->ui32); + EXPECT_EQ(kExpectedUInt16Value, arg->ui16); + EXPECT_EQ(kExpectedUInt8Value, arg->ui8); + EXPECT_EQ(kExpectedFloatVal, arg->float_val); + EXPECT_EQ(kExpectedFloatInf, arg->float_inf); + EXPECT_NAN(arg->float_nan); + EXPECT_EQ(kExpectedDoubleVal, arg->double_val); + EXPECT_EQ(kExpectedDoubleInf, arg->double_inf); + EXPECT_NAN(arg->double_nan); + EXPECT_EQ(std::string("coming"), *arg->name); + EXPECT_EQ(std::string("one"), (*arg->string_array)[0]); + EXPECT_EQ(std::string("two"), (*arg->string_array)[1]); + EXPECT_EQ(std::string("three"), (*arg->string_array)[2]); + CheckDataPipe(std::move(arg->data_handle)); + CheckMessagePipe(arg->message_handle.get()); +} + +void CheckSampleEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) { + if (list.is_null()) + return; + CheckSampleEchoArgs(std::move(list->item)); + CheckSampleEchoArgsList(list->next); +} + +// More forgiving checks are needed in the face of potentially corrupt +// messages. The values don't matter so long as all accesses are within +// bounds. +void CheckCorruptedString(const std::string& arg) { + for (size_t i = 0; i < arg.size(); ++i) + g_waste_accumulator += arg[i]; +} + +void CheckCorruptedString(const base::Optional& arg) { + if (!arg) + return; + CheckCorruptedString(*arg); +} + +void CheckCorruptedStringArray( + const base::Optional>& string_array) { + if (!string_array) + return; + for (size_t i = 0; i < string_array->size(); ++i) + CheckCorruptedString((*string_array)[i]); +} + +void CheckCorruptedDataPipe(MojoHandle data_pipe_handle) { + unsigned char buffer[100]; + uint32_t buffer_size = static_cast(sizeof(buffer)); + MojoResult result = MojoReadData( + data_pipe_handle, buffer, &buffer_size, MOJO_READ_DATA_FLAG_NONE); + if (result != MOJO_RESULT_OK) + return; + for (uint32_t i = 0; i < buffer_size; ++i) + g_waste_accumulator += buffer[i]; +} + +void CheckCorruptedMessagePipe(MojoHandle message_pipe_handle) { + unsigned char buffer[100]; + uint32_t buffer_size = static_cast(sizeof(buffer)); + MojoResult result = MojoReadMessage( + message_pipe_handle, buffer, &buffer_size, 0, 0, 0); + if (result != MOJO_RESULT_OK) + return; + for (uint32_t i = 0; i < buffer_size; ++i) + g_waste_accumulator += buffer[i]; +} + +void CheckCorruptedEchoArgs(const js_to_cpp::EchoArgsPtr& arg) { + if (arg.is_null()) + return; + CheckCorruptedString(arg->name); + CheckCorruptedStringArray(arg->string_array); + if (arg->data_handle.is_valid()) + CheckCorruptedDataPipe(arg->data_handle.get().value()); + if (arg->message_handle.is_valid()) + CheckCorruptedMessagePipe(arg->message_handle.get().value()); +} + +void CheckCorruptedEchoArgsList(const js_to_cpp::EchoArgsListPtr& list) { + if (list.is_null()) + return; + CheckCorruptedEchoArgs(list->item); + CheckCorruptedEchoArgsList(list->next); +} + +// Base Provider implementation class. It's expected that tests subclass and +// override the appropriate Provider functions. When test is done quit the +// run_loop(). +class CppSideConnection : public js_to_cpp::CppSide { + public: + CppSideConnection() + : run_loop_(nullptr), + js_side_(nullptr), + mishandled_messages_(0), + binding_(this) {} + ~CppSideConnection() override {} + + void set_run_loop(base::RunLoop* run_loop) { run_loop_ = run_loop; } + base::RunLoop* run_loop() { return run_loop_; } + + void set_js_side(js_to_cpp::JsSide* js_side) { js_side_ = js_side; } + js_to_cpp::JsSide* js_side() { return js_side_; } + + void Bind(InterfaceRequest request) { + binding_.Bind(std::move(request)); + // Keep the pipe open even after validation errors. + binding_.EnableTestingMode(); + } + + // js_to_cpp::CppSide: + void StartTest() override { NOTREACHED(); } + + void TestFinished() override { NOTREACHED(); } + + void PingResponse() override { mishandled_messages_ += 1; } + + void EchoResponse(js_to_cpp::EchoArgsListPtr list) override { + mishandled_messages_ += 1; + } + + void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override { + mishandled_messages_ += 1; + } + + void BackPointerResponse(js_to_cpp::EchoArgsListPtr list) override { + mishandled_messages_ += 1; + } + + protected: + base::RunLoop* run_loop_; + js_to_cpp::JsSide* js_side_; + int mishandled_messages_; + mojo::Binding binding_; + + private: + DISALLOW_COPY_AND_ASSIGN(CppSideConnection); +}; + +// Trivial test to verify a message sent from JS is received. +class PingCppSideConnection : public CppSideConnection { + public: + PingCppSideConnection() : got_message_(false) {} + ~PingCppSideConnection() override {} + + // js_to_cpp::CppSide: + void StartTest() override { js_side_->Ping(); } + + void PingResponse() override { + got_message_ = true; + run_loop()->Quit(); + } + + bool DidSucceed() { + return got_message_ && !mishandled_messages_; + } + + private: + bool got_message_; + DISALLOW_COPY_AND_ASSIGN(PingCppSideConnection); +}; + +// Test that parameters are passed with correct values. +class EchoCppSideConnection : public CppSideConnection { + public: + EchoCppSideConnection() : + message_count_(0), + termination_seen_(false) { + } + ~EchoCppSideConnection() override {} + + // js_to_cpp::CppSide: + void StartTest() override { + js_side_->Echo(kExpectedMessageCount, BuildSampleEchoArgs()); + } + + void EchoResponse(js_to_cpp::EchoArgsListPtr list) override { + const js_to_cpp::EchoArgsPtr& special_arg = list->item; + message_count_ += 1; + EXPECT_EQ(-1, special_arg->si64); + EXPECT_EQ(-1, special_arg->si32); + EXPECT_EQ(-1, special_arg->si16); + EXPECT_EQ(-1, special_arg->si8); + EXPECT_EQ(std::string("going"), *special_arg->name); + CheckSampleEchoArgsList(list->next); + } + + void TestFinished() override { + termination_seen_ = true; + run_loop()->Quit(); + } + + bool DidSucceed() { + return termination_seen_ && + !mishandled_messages_ && + message_count_ == kExpectedMessageCount; + } + + private: + static const int kExpectedMessageCount = 10; + int message_count_; + bool termination_seen_; + DISALLOW_COPY_AND_ASSIGN(EchoCppSideConnection); +}; + +// Test that corrupted messages don't wreak havoc. +class BitFlipCppSideConnection : public CppSideConnection { + public: + BitFlipCppSideConnection() : termination_seen_(false) {} + ~BitFlipCppSideConnection() override {} + + // js_to_cpp::CppSide: + void StartTest() override { js_side_->BitFlip(BuildSampleEchoArgs()); } + + void BitFlipResponse(js_to_cpp::EchoArgsListPtr list) override { + CheckCorruptedEchoArgsList(list); + } + + void TestFinished() override { + termination_seen_ = true; + run_loop()->Quit(); + } + + bool DidSucceed() { + return termination_seen_; + } + + private: + bool termination_seen_; + DISALLOW_COPY_AND_ASSIGN(BitFlipCppSideConnection); +}; + +// Test that severely random messages don't wreak havoc. +class BackPointerCppSideConnection : public CppSideConnection { + public: + BackPointerCppSideConnection() : termination_seen_(false) {} + ~BackPointerCppSideConnection() override {} + + // js_to_cpp::CppSide: + void StartTest() override { js_side_->BackPointer(BuildSampleEchoArgs()); } + + void BackPointerResponse(js_to_cpp::EchoArgsListPtr list) override { + CheckCorruptedEchoArgsList(list); + } + + void TestFinished() override { + termination_seen_ = true; + run_loop()->Quit(); + } + + bool DidSucceed() { + return termination_seen_; + } + + private: + bool termination_seen_; + DISALLOW_COPY_AND_ASSIGN(BackPointerCppSideConnection); +}; + +} // namespace + +class JsToCppTest : public testing::Test { + public: + JsToCppTest() {} + + void RunTest(const std::string& test, CppSideConnection* cpp_side) { + cpp_side->set_run_loop(&run_loop_); + + js_to_cpp::JsSidePtr js_side; + auto js_side_proxy = MakeRequest(&js_side); + + cpp_side->set_js_side(js_side.get()); + js_to_cpp::CppSidePtr cpp_side_ptr; + cpp_side->Bind(MakeRequest(&cpp_side_ptr)); + + js_side->SetCppSide(std::move(cpp_side_ptr)); + +#ifdef V8_USE_EXTERNAL_STARTUP_DATA + gin::V8Initializer::LoadV8Snapshot(); + gin::V8Initializer::LoadV8Natives(); +#endif + + gin::IsolateHolder::Initialize(gin::IsolateHolder::kStrictMode, + gin::IsolateHolder::kStableV8Extras, + gin::ArrayBufferAllocator::SharedInstance()); + gin::IsolateHolder instance(base::ThreadTaskRunnerHandle::Get()); + MojoRunnerDelegate delegate; + gin::ShellRunner runner(&delegate, instance.isolate()); + delegate.Start(&runner, js_side_proxy.PassMessagePipe().release().value(), + test); + + run_loop_.Run(); + } + + private: + base::ShadowingAtExitManager at_exit_; + base::MessageLoop loop; + base::RunLoop run_loop_; + + DISALLOW_COPY_AND_ASSIGN(JsToCppTest); +}; + +TEST_F(JsToCppTest, Ping) { + PingCppSideConnection cpp_side_connection; + RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection); + EXPECT_TRUE(cpp_side_connection.DidSucceed()); +} + +TEST_F(JsToCppTest, Echo) { + EchoCppSideConnection cpp_side_connection; + RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection); + EXPECT_TRUE(cpp_side_connection.DidSucceed()); +} + +TEST_F(JsToCppTest, BitFlip) { + // These tests generate a lot of expected validation errors. Suppress logging. + mojo::internal::ScopedSuppressValidationErrorLoggingForTests log_suppression; + + BitFlipCppSideConnection cpp_side_connection; + RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection); + EXPECT_TRUE(cpp_side_connection.DidSucceed()); +} + +TEST_F(JsToCppTest, BackPointer) { + // These tests generate a lot of expected validation errors. Suppress logging. + mojo::internal::ScopedSuppressValidationErrorLoggingForTests log_suppression; + + BackPointerCppSideConnection cpp_side_connection; + RunTest("mojo/edk/js/tests/js_to_cpp_tests", &cpp_side_connection); + EXPECT_TRUE(cpp_side_connection.DidSucceed()); +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/tests/js_to_cpp_tests.js b/mojo/edk/js/tests/js_to_cpp_tests.js new file mode 100644 index 0000000..6b69fca --- /dev/null +++ b/mojo/edk/js/tests/js_to_cpp_tests.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('mojo/edk/js/tests/js_to_cpp_tests', [ + 'console', + 'mojo/edk/js/tests/js_to_cpp.mojom', + 'mojo/public/js/bindings', + 'mojo/public/js/connector', + 'mojo/public/js/core', +], function (console, jsToCpp, bindings, connector, core) { + var retainedJsSide; + var retainedJsSideStub; + var sampleData; + var sampleMessage; + var BAD_VALUE = 13; + var DATA_PIPE_PARAMS = { + flags: core.CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, + elementNumBytes: 1, + capacityNumBytes: 64 + }; + + function JsSideConnection() { + this.binding = new bindings.Binding(jsToCpp.JsSide, this); + } + + JsSideConnection.prototype.setCppSide = function(cppSide) { + this.cppSide_ = cppSide; + this.cppSide_.startTest(); + }; + + JsSideConnection.prototype.ping = function (arg) { + this.cppSide_.pingResponse(); + }; + + JsSideConnection.prototype.echo = function (numIterations, arg) { + var dataPipe1; + var dataPipe2; + var i; + var messagePipe1; + var messagePipe2; + var specialArg; + + // Ensure expected negative values are negative. + if (arg.si64 > 0) + arg.si64 = BAD_VALUE; + + if (arg.si32 > 0) + arg.si32 = BAD_VALUE; + + if (arg.si16 > 0) + arg.si16 = BAD_VALUE; + + if (arg.si8 > 0) + arg.si8 = BAD_VALUE; + + for (i = 0; i < numIterations; ++i) { + dataPipe1 = core.createDataPipe(DATA_PIPE_PARAMS); + dataPipe2 = core.createDataPipe(DATA_PIPE_PARAMS); + messagePipe1 = core.createMessagePipe(); + messagePipe2 = core.createMessagePipe(); + + arg.data_handle = dataPipe1.consumerHandle; + arg.message_handle = messagePipe1.handle1; + + specialArg = new jsToCpp.EchoArgs(); + specialArg.si64 = -1; + specialArg.si32 = -1; + specialArg.si16 = -1; + specialArg.si8 = -1; + specialArg.name = 'going'; + specialArg.data_handle = dataPipe2.consumerHandle; + specialArg.message_handle = messagePipe2.handle1; + + writeDataPipe(dataPipe1, sampleData); + writeDataPipe(dataPipe2, sampleData); + writeMessagePipe(messagePipe1, sampleMessage); + writeMessagePipe(messagePipe2, sampleMessage); + + this.cppSide_.echoResponse(createEchoArgsList(specialArg, arg)); + + core.close(dataPipe1.producerHandle); + core.close(dataPipe2.producerHandle); + core.close(messagePipe1.handle0); + core.close(messagePipe2.handle0); + } + this.cppSide_.testFinished(); + }; + + JsSideConnection.prototype.bitFlip = function (arg) { + var iteration = 0; + var dataPipe; + var messagePipe; + var proto = connector.Connector.prototype; + var stopSignalled = false; + + proto.realAccept = proto.accept; + proto.accept = function (message) { + var offset = iteration / 8; + var mask; + var value; + if (offset < message.buffer.arrayBuffer.byteLength) { + mask = 1 << (iteration % 8); + value = message.buffer.getUint8(offset) ^ mask; + message.buffer.setUint8(offset, value); + return this.realAccept(message); + } + stopSignalled = true; + return false; + }; + + while (!stopSignalled) { + dataPipe = core.createDataPipe(DATA_PIPE_PARAMS); + messagePipe = core.createMessagePipe(); + writeDataPipe(dataPipe, sampleData); + writeMessagePipe(messagePipe, sampleMessage); + arg.data_handle = dataPipe.consumerHandle; + arg.message_handle = messagePipe.handle1; + + this.cppSide_.bitFlipResponse(createEchoArgsList(arg)); + + core.close(dataPipe.producerHandle); + core.close(messagePipe.handle0); + iteration += 1; + } + + proto.accept = proto.realAccept; + proto.realAccept = null; + this.cppSide_.testFinished(); + }; + + JsSideConnection.prototype.backPointer = function (arg) { + var iteration = 0; + var dataPipe; + var messagePipe; + var proto = connector.Connector.prototype; + var stopSignalled = false; + + proto.realAccept = proto.accept; + proto.accept = function (message) { + var delta = 8 * (1 + iteration % 32); + var offset = 8 * ((iteration / 32) | 0); + if (offset < message.buffer.arrayBuffer.byteLength - 4) { + message.buffer.dataView.setUint32(offset, 0x100000000 - delta, true); + message.buffer.dataView.setUint32(offset + 4, 0xffffffff, true); + return this.realAccept(message); + } + stopSignalled = true; + return false; + }; + + while (!stopSignalled) { + dataPipe = core.createDataPipe(DATA_PIPE_PARAMS); + messagePipe = core.createMessagePipe(); + writeDataPipe(dataPipe, sampleData); + writeMessagePipe(messagePipe, sampleMessage); + arg.data_handle = dataPipe.consumerHandle; + arg.message_handle = messagePipe.handle1; + + this.cppSide_.backPointerResponse(createEchoArgsList(arg)); + + core.close(dataPipe.producerHandle); + core.close(messagePipe.handle0); + iteration += 1; + } + + proto.accept = proto.realAccept; + proto.realAccept = null; + this.cppSide_.testFinished(); + }; + + function writeDataPipe(pipe, data) { + var writeResult = core.writeData( + pipe.producerHandle, data, core.WRITE_DATA_FLAG_ALL_OR_NONE); + + if (writeResult.result != core.RESULT_OK) { + console.log('ERROR: Data pipe write result was ' + writeResult.result); + return false; + } + if (writeResult.numBytes != data.length) { + console.log('ERROR: Data pipe write length was ' + writeResult.numBytes); + return false; + } + return true; + } + + function writeMessagePipe(pipe, arrayBuffer) { + var result = core.writeMessage(pipe.handle0, arrayBuffer, [], 0); + if (result != core.RESULT_OK) { + console.log('ERROR: Message pipe write result was ' + result); + return false; + } + return true; + } + + function createEchoArgsListElement(item, next) { + var list = new jsToCpp.EchoArgsList(); + list.item = item; + list.next = next; + return list; + } + + function createEchoArgsList() { + var genuineArray = Array.prototype.slice.call(arguments); + return genuineArray.reduceRight(function (previous, current) { + return createEchoArgsListElement(current, previous); + }, null); + } + + return function(jsSideRequestHandle) { + var i; + sampleData = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes); + for (i = 0; i < sampleData.length; ++i) { + sampleData[i] = i; + } + sampleMessage = new Uint8Array(DATA_PIPE_PARAMS.capacityNumBytes); + for (i = 0; i < sampleMessage.length; ++i) { + sampleMessage[i] = 255 - i; + } + retainedJsSide = new JsSideConnection; + retainedJsSide.binding.bind(jsSideRequestHandle); + }; +}); diff --git a/mojo/edk/js/tests/run_js_unittests.cc b/mojo/edk/js/tests/run_js_unittests.cc new file mode 100644 index 0000000..13e796b --- /dev/null +++ b/mojo/edk/js/tests/run_js_unittests.cc @@ -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. + +#include "base/files/file_path.h" +#include "base/macros.h" +#include "base/path_service.h" +#include "gin/modules/console.h" +#include "gin/modules/module_registry.h" +#include "gin/modules/timer.h" +#include "gin/test/file_runner.h" +#include "gin/test/gtest.h" +#include "mojo/edk/js/core.h" +#include "mojo/edk/js/support.h" +#include "mojo/edk/js/threading.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace js { +namespace { + +class TestRunnerDelegate : public gin::FileRunnerDelegate { + public: + TestRunnerDelegate() { + AddBuiltinModule(gin::Console::kModuleName, gin::Console::GetModule); + AddBuiltinModule(gin::TimerModule::kName, gin::TimerModule::GetModule); + AddBuiltinModule(Core::kModuleName, Core::GetModule); + AddBuiltinModule(Threading::kModuleName, Threading::GetModule); + AddBuiltinModule(Support::kModuleName, Support::GetModule); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestRunnerDelegate); +}; + +void RunTest(std::string test, bool run_until_idle) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("mojo") + .AppendASCII("public") + .AppendASCII("js") + .AppendASCII("tests") + .AppendASCII(test); + TestRunnerDelegate delegate; + gin::RunTestFromFile(path, &delegate, run_until_idle); +} + +// TODO(abarth): Should we autogenerate these stubs from GYP? +TEST(JSTest, Core) { + RunTest("core_unittest.js", true); +} + +TEST(JSTest, Validation) { + RunTest("validation_unittest.js", true); +} + +} // namespace +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/threading.cc b/mojo/edk/js/threading.cc new file mode 100644 index 0000000..db9f12d --- /dev/null +++ b/mojo/edk/js/threading.cc @@ -0,0 +1,49 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/js/threading.h" + +#include "base/message_loop/message_loop.h" +#include "gin/object_template_builder.h" +#include "gin/per_isolate_data.h" +#include "mojo/edk/js/handle.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +void Quit() { + base::MessageLoop::current()->QuitNow(); +} + +gin::WrapperInfo g_wrapper_info = { gin::kEmbedderNativeGin }; + +} // namespace + +const char Threading::kModuleName[] = "mojo/public/js/threading"; + +v8::Local Threading::GetModule(v8::Isolate* isolate) { + gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); + v8::Local templ = data->GetObjectTemplate( + &g_wrapper_info); + + if (templ.IsEmpty()) { + templ = gin::ObjectTemplateBuilder(isolate) + .SetMethod("quit", Quit) + .Build(); + + data->SetObjectTemplate(&g_wrapper_info, templ); + } + + return templ->NewInstance(); +} + +Threading::Threading() { +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/threading.h b/mojo/edk/js/threading.h new file mode 100644 index 0000000..653d076 --- /dev/null +++ b/mojo/edk/js/threading.h @@ -0,0 +1,28 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_JS_THREADING_H_ +#define MOJO_EDK_JS_THREADING_H_ + +#include "gin/public/wrapper_info.h" +#include "mojo/edk/js/js_export.h" +#include "v8/include/v8.h" + +namespace mojo { +namespace edk { +namespace js { + +class MOJO_JS_EXPORT Threading { + public: + static const char kModuleName[]; + static v8::Local GetModule(v8::Isolate* isolate); + private: + Threading(); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_THREADING_H_ diff --git a/mojo/edk/js/waiting_callback.cc b/mojo/edk/js/waiting_callback.cc new file mode 100644 index 0000000..6ad4bd0 --- /dev/null +++ b/mojo/edk/js/waiting_callback.cc @@ -0,0 +1,95 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/js/waiting_callback.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "gin/per_context_data.h" + +namespace mojo { +namespace edk { +namespace js { + +namespace { + +v8::Handle GetHiddenPropertyName(v8::Isolate* isolate) { + return v8::Private::ForApi( + isolate, gin::StringToV8(isolate, "::mojo::js::WaitingCallback")); +} + +} // namespace + +gin::WrapperInfo WaitingCallback::kWrapperInfo = { gin::kEmbedderNativeGin }; + +// static +gin::Handle WaitingCallback::Create( + v8::Isolate* isolate, + v8::Handle callback, + gin::Handle handle_wrapper, + MojoHandleSignals signals, + bool one_shot) { + gin::Handle waiting_callback = gin::CreateHandle( + isolate, new WaitingCallback(isolate, callback, one_shot)); + MojoResult result = waiting_callback->watcher_.Watch( + handle_wrapper->get(), signals, + base::Bind(&WaitingCallback::OnHandleReady, + base::Unretained(waiting_callback.get()))); + + // The signals may already be unsatisfiable. + if (result == MOJO_RESULT_FAILED_PRECONDITION) + waiting_callback->OnHandleReady(MOJO_RESULT_FAILED_PRECONDITION); + + return waiting_callback; +} + +void WaitingCallback::Cancel() { + if (watcher_.IsWatching()) + watcher_.Cancel(); +} + +WaitingCallback::WaitingCallback(v8::Isolate* isolate, + v8::Handle callback, + bool one_shot) + : one_shot_(one_shot), + watcher_(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC), + weak_factory_(this) { + v8::Handle context = isolate->GetCurrentContext(); + runner_ = gin::PerContextData::From(context)->runner()->GetWeakPtr(); + GetWrapper(isolate) + ->SetPrivate(context, GetHiddenPropertyName(isolate), callback) + .FromJust(); +} + +WaitingCallback::~WaitingCallback() { + Cancel(); +} + +void WaitingCallback::OnHandleReady(MojoResult result) { + if (!runner_) + return; + + gin::Runner::Scope scope(runner_.get()); + v8::Isolate* isolate = runner_->GetContextHolder()->isolate(); + + v8::Handle hidden_value = + GetWrapper(isolate) + ->GetPrivate(runner_->GetContextHolder()->context(), + GetHiddenPropertyName(isolate)) + .ToLocalChecked(); + v8::Handle callback; + CHECK(gin::ConvertFromV8(isolate, hidden_value, &callback)); + + v8::Handle args[] = { gin::ConvertToV8(isolate, result) }; + runner_->Call(callback, runner_->global(), 1, args); + + if (one_shot_ || result == MOJO_RESULT_CANCELLED) { + runner_.reset(); + Cancel(); + } +} + +} // namespace js +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/js/waiting_callback.h b/mojo/edk/js/waiting_callback.h new file mode 100644 index 0000000..f97b389 --- /dev/null +++ b/mojo/edk/js/waiting_callback.h @@ -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. + +#ifndef MOJO_EDK_JS_WAITING_CALLBACK_H_ +#define MOJO_EDK_JS_WAITING_CALLBACK_H_ + +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "gin/handle.h" +#include "gin/runner.h" +#include "gin/wrappable.h" +#include "mojo/edk/js/handle.h" +#include "mojo/public/cpp/system/core.h" +#include "mojo/public/cpp/system/simple_watcher.h" + +namespace mojo { +namespace edk { +namespace js { + +class WaitingCallback : public gin::Wrappable { + public: + static gin::WrapperInfo kWrapperInfo; + + // Creates a new WaitingCallback. + // + // If |one_shot| is true, the callback will only ever be called at most once. + // If false, the callback may be called any number of times until the + // WaitingCallback is explicitly cancelled. + static gin::Handle Create( + v8::Isolate* isolate, + v8::Handle callback, + gin::Handle handle_wrapper, + MojoHandleSignals signals, + bool one_shot); + + // Cancels the callback. Does nothing if a callback is not pending. This is + // implicitly invoked from the destructor but can be explicitly invoked as + // necessary. + void Cancel(); + + private: + WaitingCallback(v8::Isolate* isolate, + v8::Handle callback, + bool one_shot); + ~WaitingCallback() override; + + // Callback from the Watcher. + void OnHandleReady(MojoResult result); + + // Indicates whether this is a one-shot callback or not. If so, it uses the + // deprecated HandleWatcher to wait for signals; otherwise it uses the new + // system Watcher API. + const bool one_shot_; + + base::WeakPtr runner_; + SimpleWatcher watcher_; + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(WaitingCallback); +}; + +} // namespace js +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_JS_WAITING_CALLBACK_H_ diff --git a/mojo/edk/system/BUILD.gn b/mojo/edk/system/BUILD.gn new file mode 100644 index 0000000..a68cd44 --- /dev/null +++ b/mojo/edk/system/BUILD.gn @@ -0,0 +1,205 @@ +# Copyright 2014 The Chromium 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/nacl/config.gni") +import("//testing/test.gni") +import("../../../mojo/public/tools/bindings/mojom.gni") + +if (is_android) { + import("//build/config/android/config.gni") + import("//build/config/android/rules.gni") +} + +component("system") { + output_name = "mojo_system_impl" + + sources = [ + "atomic_flag.h", + "broker.h", + "broker_host.cc", + "broker_host.h", + "broker_posix.cc", + "broker_win.cc", + "channel.cc", + "channel.h", + "channel_posix.cc", + "channel_win.cc", + "configuration.cc", + "configuration.h", + "core.cc", + "core.h", + "data_pipe_consumer_dispatcher.cc", + "data_pipe_consumer_dispatcher.h", + "data_pipe_control_message.cc", + "data_pipe_control_message.h", + "data_pipe_producer_dispatcher.cc", + "data_pipe_producer_dispatcher.h", + "dispatcher.cc", + "dispatcher.h", + "handle_signals_state.h", + "handle_table.cc", + "handle_table.h", + "mapping_table.cc", + "mapping_table.h", + "message_for_transit.cc", + "message_for_transit.h", + "message_pipe_dispatcher.cc", + "message_pipe_dispatcher.h", + "node_channel.cc", + "node_channel.h", + "node_controller.cc", + "node_controller.h", + "options_validation.h", + "platform_handle_dispatcher.cc", + "platform_handle_dispatcher.h", + "ports_message.cc", + "ports_message.h", + "request_context.cc", + "request_context.h", + "shared_buffer_dispatcher.cc", + "shared_buffer_dispatcher.h", + "watch.cc", + "watch.h", + "watcher_dispatcher.cc", + "watcher_dispatcher.h", + "watcher_set.cc", + "watcher_set.h", + ] + + defines = [ "MOJO_SYSTEM_IMPL_IMPLEMENTATION" ] + + public_deps = [ + "//mojo/edk/embedder", + "//mojo/edk/embedder:platform", + "//mojo/edk/system/ports", + "//mojo/public/c/system", + "//mojo/public/cpp/system", + ] + + deps = [ + "//base", + ] + + if (!is_nacl) { + deps += [ "//crypto" ] + } + + if (is_win) { + cflags = [ "/wd4324" ] # Structure was padded due to __declspec(align()), + # which is uninteresting. + } + + if (is_mac && !is_ios) { + sources += [ + "mach_port_relay.cc", + "mach_port_relay.h", + ] + } + + if (is_nacl && !is_nacl_nonsfi) { + sources -= [ + "broker_host.cc", + "broker_posix.cc", + "channel_posix.cc", + ] + } + + # Use target_os == "chromeos" instead of is_chromeos because we need to + # build NaCl targets (i.e. IRT) for ChromeOS the same as the rest of ChromeOS. + if (is_android || target_os == "chromeos") { + defines += [ "MOJO_EDK_LEGACY_PROTOCOL" ] + } + + allow_circular_includes_from = [ "//mojo/edk/embedder" ] +} + +group("tests") { + testonly = true + deps = [ + ":mojo_system_unittests", + ] + + if (!is_ios) { + deps += [ ":mojo_message_pipe_perftests" ] + } +} + +source_set("test_utils") { + testonly = true + + sources = [ + "test_utils.cc", + "test_utils.h", + ] + + public_deps = [ + "//mojo/public/c/system", + "//mojo/public/cpp/system", + ] + + deps = [ + "//base", + "//base/test:test_support", + "//mojo/edk/test:test_support", + "//testing/gtest:gtest", + ] +} + +test("mojo_system_unittests") { + sources = [ + "channel_unittest.cc", + "core_test_base.cc", + "core_test_base.h", + "core_unittest.cc", + "message_pipe_unittest.cc", + "options_validation_unittest.cc", + "platform_handle_dispatcher_unittest.cc", + "shared_buffer_dispatcher_unittest.cc", + "shared_buffer_unittest.cc", + "signals_unittest.cc", + "watcher_unittest.cc", + ] + + if (!is_ios) { + sources += [ + "data_pipe_unittest.cc", + "multiprocess_message_pipe_unittest.cc", + "platform_wrapper_unittest.cc", + ] + } + + deps = [ + ":test_utils", + "//base", + "//base/test:test_support", + "//mojo/edk/embedder:embedder_unittests", + "//mojo/edk/system", + "//mojo/edk/system/ports:tests", + "//mojo/edk/test:run_all_unittests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/system", + "//testing/gmock", + "//testing/gtest", + ] + + allow_circular_includes_from = [ "//mojo/edk/embedder:embedder_unittests" ] +} + +if (!is_ios) { + test("mojo_message_pipe_perftests") { + sources = [ + "message_pipe_perftest.cc", + ] + + deps = [ + ":test_utils", + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/edk/test:run_all_perftests", + "//mojo/edk/test:test_support", + "//testing/gtest", + ] + } +} diff --git a/mojo/edk/system/atomic_flag.h b/mojo/edk/system/atomic_flag.h new file mode 100644 index 0000000..6bdcfaa --- /dev/null +++ b/mojo/edk/system/atomic_flag.h @@ -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. + +#ifndef MOJO_EDK_SYSTEM_ATOMIC_FLAG_H_ +#define MOJO_EDK_SYSTEM_ATOMIC_FLAG_H_ + +#include "base/atomicops.h" +#include "base/macros.h" + +namespace mojo { +namespace edk { + +// AtomicFlag is a boolean flag that can be set and tested atomically. It is +// intended to be used to fast-path checks where the common case would normally +// release the governing mutex immediately after checking. +// +// Example usage: +// void DoFoo(Bar* bar) { +// AutoLock l(lock_); +// queue_.push_back(bar); +// flag_.Set(true); +// } +// +// void Baz() { +// if (!flag_) // Assume this is the common case. +// return; +// +// AutoLock l(lock_); +// ... drain queue_ ... +// flag_.Set(false); +// } +class AtomicFlag { + public: + AtomicFlag() : flag_(0) {} + ~AtomicFlag() {} + + void Set(bool value) { + base::subtle::Release_Store(&flag_, value ? 1 : 0); + } + + bool Get() const { + return base::subtle::Acquire_Load(&flag_) ? true : false; + } + + operator const bool() const { return Get(); } + + private: + base::subtle::Atomic32 flag_; + + DISALLOW_COPY_AND_ASSIGN(AtomicFlag); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_ATOMIC_FLAG_H_ diff --git a/mojo/edk/system/broker.h b/mojo/edk/system/broker.h new file mode 100644 index 0000000..1577972 --- /dev/null +++ b/mojo/edk/system/broker.h @@ -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. + +#ifndef MOJO_EDK_SYSTEM_BROKER_H_ +#define MOJO_EDK_SYSTEM_BROKER_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +namespace mojo { +namespace edk { + +class PlatformSharedBuffer; + +// The Broker is a channel to the parent process, which allows synchronous IPCs. +class Broker { + public: + // Note: This is blocking, and will wait for the first message over + // |platform_handle|. + explicit Broker(ScopedPlatformHandle platform_handle); + ~Broker(); + + // Returns the platform handle that should be used to establish a NodeChannel + // to the parent process. + ScopedPlatformHandle GetParentPlatformHandle(); + + // Request a shared buffer from the parent process. Blocks the current thread. + scoped_refptr GetSharedBuffer(size_t num_bytes); + + private: + // Handle to the parent process, used for synchronous IPCs. + ScopedPlatformHandle sync_channel_; + + // Handle to the parent process which is recieved in the first first message + // over |sync_channel_|. + ScopedPlatformHandle parent_channel_; + + // Lock to only allow one sync message at a time. This avoids having to deal + // with message ordering since we can only have one request at a time + // in-flight. + base::Lock lock_; + + DISALLOW_COPY_AND_ASSIGN(Broker); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_BROKER_H_ diff --git a/mojo/edk/system/broker_host.cc b/mojo/edk/system/broker_host.cc new file mode 100644 index 0000000..6096034 --- /dev/null +++ b/mojo/edk/system/broker_host.cc @@ -0,0 +1,153 @@ +// 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/edk/system/broker_host.h" + +#include + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_task_runner_handle.h" +#include "mojo/edk/embedder/named_platform_channel_pair.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/broker_messages.h" + +namespace mojo { +namespace edk { + +BrokerHost::BrokerHost(base::ProcessHandle client_process, + ScopedPlatformHandle platform_handle) +#if defined(OS_WIN) + : client_process_(client_process) +#endif +{ + CHECK(platform_handle.is_valid()); + + base::MessageLoop::current()->AddDestructionObserver(this); + + channel_ = Channel::Create(this, ConnectionParams(std::move(platform_handle)), + base::ThreadTaskRunnerHandle::Get()); + channel_->Start(); +} + +BrokerHost::~BrokerHost() { + // We're always destroyed on the creation thread, which is the IO thread. + base::MessageLoop::current()->RemoveDestructionObserver(this); + + if (channel_) + channel_->ShutDown(); +} + +bool BrokerHost::PrepareHandlesForClient(PlatformHandleVector* handles) { +#if defined(OS_WIN) + if (!Channel::Message::RewriteHandles( + base::GetCurrentProcessHandle(), client_process_, handles)) { + // NOTE: We only log an error here. We do not signal a logical error or + // prevent any message from being sent. The client should handle unexpected + // invalid handles appropriately. + DLOG(ERROR) << "Failed to rewrite one or more handles to broker client."; + return false; + } +#endif + return true; +} + +bool BrokerHost::SendChannel(ScopedPlatformHandle handle) { + CHECK(handle.is_valid()); + CHECK(channel_); + +#if defined(OS_WIN) + InitData* data; + Channel::MessagePtr message = + CreateBrokerMessage(BrokerMessageType::INIT, 1, 0, &data); + data->pipe_name_length = 0; +#else + Channel::MessagePtr message = + CreateBrokerMessage(BrokerMessageType::INIT, 1, nullptr); +#endif + ScopedPlatformHandleVectorPtr handles; + handles.reset(new PlatformHandleVector(1)); + handles->at(0) = handle.release(); + + // This may legitimately fail on Windows if the client process is in another + // session, e.g., is an elevated process. + if (!PrepareHandlesForClient(handles.get())) + return false; + + message->SetHandles(std::move(handles)); + channel_->Write(std::move(message)); + return true; +} + +#if defined(OS_WIN) + +void BrokerHost::SendNamedChannel(const base::StringPiece16& pipe_name) { + InitData* data; + base::char16* name_data; + Channel::MessagePtr message = CreateBrokerMessage( + BrokerMessageType::INIT, 0, sizeof(*name_data) * pipe_name.length(), + &data, reinterpret_cast(&name_data)); + data->pipe_name_length = static_cast(pipe_name.length()); + std::copy(pipe_name.begin(), pipe_name.end(), name_data); + channel_->Write(std::move(message)); +} + +#endif // defined(OS_WIN) + +void BrokerHost::OnBufferRequest(uint32_t num_bytes) { + scoped_refptr read_only_buffer; + scoped_refptr buffer = + PlatformSharedBuffer::Create(num_bytes); + if (buffer) + read_only_buffer = buffer->CreateReadOnlyDuplicate(); + if (!read_only_buffer) + buffer = nullptr; + + Channel::MessagePtr message = CreateBrokerMessage( + BrokerMessageType::BUFFER_RESPONSE, buffer ? 2 : 0, nullptr); + if (buffer) { + ScopedPlatformHandleVectorPtr handles; + handles.reset(new PlatformHandleVector(2)); + handles->at(0) = buffer->PassPlatformHandle().release(); + handles->at(1) = read_only_buffer->PassPlatformHandle().release(); + PrepareHandlesForClient(handles.get()); + message->SetHandles(std::move(handles)); + } + + channel_->Write(std::move(message)); +} + +void BrokerHost::OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) { + if (payload_size < sizeof(BrokerMessageHeader)) + return; + + const BrokerMessageHeader* header = + static_cast(payload); + switch (header->type) { + case BrokerMessageType::BUFFER_REQUEST: + if (payload_size == + sizeof(BrokerMessageHeader) + sizeof(BufferRequestData)) { + const BufferRequestData* request = + reinterpret_cast(header + 1); + OnBufferRequest(request->size); + } + break; + + default: + LOG(ERROR) << "Unexpected broker message type: " << header->type; + break; + } +} + +void BrokerHost::OnChannelError() { delete this; } + +void BrokerHost::WillDestroyCurrentMessageLoop() { delete this; } + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/broker_host.h b/mojo/edk/system/broker_host.h new file mode 100644 index 0000000..a7995d2 --- /dev/null +++ b/mojo/edk/system/broker_host.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_EDK_SYSTEM_BROKER_HOST_H_ +#define MOJO_EDK_SYSTEM_BROKER_HOST_H_ + +#include + +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process_handle.h" +#include "base/strings/string_piece.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +// The BrokerHost is a channel to the child process, which services synchronous +// IPCs. +class BrokerHost : public Channel::Delegate, + public base::MessageLoop::DestructionObserver { + public: + BrokerHost(base::ProcessHandle client_process, ScopedPlatformHandle handle); + + // Send |handle| to the child, to be used to establish a NodeChannel to us. + bool SendChannel(ScopedPlatformHandle handle); + +#if defined(OS_WIN) + // Sends a named channel to the child. Like above, but for named pipes. + void SendNamedChannel(const base::StringPiece16& pipe_name); +#endif + + private: + ~BrokerHost() override; + + bool PrepareHandlesForClient(PlatformHandleVector* handles); + + // Channel::Delegate: + void OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) override; + void OnChannelError() override; + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override; + + void OnBufferRequest(uint32_t num_bytes); + +#if defined(OS_WIN) + base::ProcessHandle client_process_; +#endif + + scoped_refptr channel_; + + DISALLOW_COPY_AND_ASSIGN(BrokerHost); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_BROKER_HOST_H_ diff --git a/mojo/edk/system/broker_messages.h b/mojo/edk/system/broker_messages.h new file mode 100644 index 0000000..0f0dd9d --- /dev/null +++ b/mojo/edk/system/broker_messages.h @@ -0,0 +1,80 @@ +// 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_EDK_SYSTEM_BROKER_MESSAGES_H_ +#define MOJO_EDK_SYSTEM_BROKER_MESSAGES_H_ + +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +#pragma pack(push, 1) + +enum BrokerMessageType : uint32_t { + INIT, + BUFFER_REQUEST, + BUFFER_RESPONSE, +}; + +struct BrokerMessageHeader { + BrokerMessageType type; + uint32_t padding; +}; + +static_assert(IsAlignedForChannelMessage(sizeof(BrokerMessageHeader)), + "Invalid header size."); + +struct BufferRequestData { + uint32_t size; +}; + +#if defined(OS_WIN) +struct InitData { + // NOTE: InitData in the payload is followed by string16 data with exactly + // |pipe_name_length| wide characters (i.e., |pipe_name_length|*2 bytes.) + // This applies to Windows only. + uint32_t pipe_name_length; +}; +#endif + +#pragma pack(pop) + +template +inline Channel::MessagePtr CreateBrokerMessage( + BrokerMessageType type, + size_t num_handles, + size_t extra_data_size, + T** out_message_data, + void** out_extra_data = nullptr) { + const size_t message_size = sizeof(BrokerMessageHeader) + + sizeof(**out_message_data) + extra_data_size; + Channel::MessagePtr message(new Channel::Message(message_size, num_handles)); + BrokerMessageHeader* header = + reinterpret_cast(message->mutable_payload()); + header->type = type; + header->padding = 0; + *out_message_data = reinterpret_cast(header + 1); + if (out_extra_data) + *out_extra_data = *out_message_data + 1; + return message; +} + +inline Channel::MessagePtr CreateBrokerMessage( + BrokerMessageType type, + size_t num_handles, + std::nullptr_t** dummy_out_data) { + Channel::MessagePtr message( + new Channel::Message(sizeof(BrokerMessageHeader), num_handles)); + BrokerMessageHeader* header = + reinterpret_cast(message->mutable_payload()); + header->type = type; + header->padding = 0; + return message; +} + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_BROKER_MESSAGES_H_ diff --git a/mojo/edk/system/broker_posix.cc b/mojo/edk/system/broker_posix.cc new file mode 100644 index 0000000..8742f70 --- /dev/null +++ b/mojo/edk/system/broker_posix.cc @@ -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. + +#include "mojo/edk/system/broker.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/platform_channel_utils_posix.h" +#include "mojo/edk/embedder/platform_handle_utils.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/broker_messages.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +namespace { + +bool WaitForBrokerMessage(PlatformHandle platform_handle, + BrokerMessageType expected_type, + size_t expected_num_handles, + std::deque* incoming_handles) { + Channel::MessagePtr message( + new Channel::Message(sizeof(BrokerMessageHeader), expected_num_handles)); + std::deque incoming_platform_handles; + ssize_t read_result = PlatformChannelRecvmsg( + platform_handle, const_cast(message->data()), + message->data_num_bytes(), &incoming_platform_handles, true /* block */); + bool error = false; + if (read_result < 0) { + PLOG(ERROR) << "Recvmsg error"; + error = true; + } else if (static_cast(read_result) != message->data_num_bytes()) { + LOG(ERROR) << "Invalid node channel message"; + error = true; + } else if (incoming_platform_handles.size() != expected_num_handles) { + LOG(ERROR) << "Received unexpected number of handles"; + error = true; + } + + if (!error) { + const BrokerMessageHeader* header = + reinterpret_cast(message->payload()); + if (header->type != expected_type) { + LOG(ERROR) << "Unexpected message"; + error = true; + } + } + + if (error) { + CloseAllPlatformHandles(&incoming_platform_handles); + } else { + if (incoming_handles) + incoming_handles->swap(incoming_platform_handles); + } + return !error; +} + +} // namespace + +Broker::Broker(ScopedPlatformHandle platform_handle) + : sync_channel_(std::move(platform_handle)) { + CHECK(sync_channel_.is_valid()); + + // Mark the channel as blocking. + int flags = fcntl(sync_channel_.get().handle, F_GETFL); + PCHECK(flags != -1); + flags = fcntl(sync_channel_.get().handle, F_SETFL, flags & ~O_NONBLOCK); + PCHECK(flags != -1); + + // Wait for the first message, which should contain a handle. + std::deque incoming_platform_handles; + if (WaitForBrokerMessage(sync_channel_.get(), BrokerMessageType::INIT, 1, + &incoming_platform_handles)) { + parent_channel_ = ScopedPlatformHandle(incoming_platform_handles.front()); + } +} + +Broker::~Broker() = default; + +ScopedPlatformHandle Broker::GetParentPlatformHandle() { + return std::move(parent_channel_); +} + +scoped_refptr Broker::GetSharedBuffer(size_t num_bytes) { + base::AutoLock lock(lock_); + + BufferRequestData* buffer_request; + Channel::MessagePtr out_message = CreateBrokerMessage( + BrokerMessageType::BUFFER_REQUEST, 0, 0, &buffer_request); + buffer_request->size = num_bytes; + ssize_t write_result = PlatformChannelWrite( + sync_channel_.get(), out_message->data(), out_message->data_num_bytes()); + if (write_result < 0) { + PLOG(ERROR) << "Error sending sync broker message"; + return nullptr; + } else if (static_cast(write_result) != + out_message->data_num_bytes()) { + LOG(ERROR) << "Error sending complete broker message"; + return nullptr; + } + + std::deque incoming_platform_handles; + if (WaitForBrokerMessage(sync_channel_.get(), + BrokerMessageType::BUFFER_RESPONSE, 2, + &incoming_platform_handles)) { + ScopedPlatformHandle rw_handle(incoming_platform_handles.front()); + incoming_platform_handles.pop_front(); + ScopedPlatformHandle ro_handle(incoming_platform_handles.front()); + return PlatformSharedBuffer::CreateFromPlatformHandlePair( + num_bytes, std::move(rw_handle), std::move(ro_handle)); + } + + return nullptr; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/broker_win.cc b/mojo/edk/system/broker_win.cc new file mode 100644 index 0000000..063282c --- /dev/null +++ b/mojo/edk/system/broker_win.cc @@ -0,0 +1,155 @@ +// 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 + +#include +#include + +#include "base/debug/alias.h" +#include "base/numerics/safe_conversions.h" +#include "base/strings/string_piece.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/named_platform_handle_utils.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/broker.h" +#include "mojo/edk/system/broker_messages.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +namespace { + +// 256 bytes should be enough for anyone! +const size_t kMaxBrokerMessageSize = 256; + +bool TakeHandlesFromBrokerMessage(Channel::Message* message, + size_t num_handles, + ScopedPlatformHandle* out_handles) { + if (message->num_handles() != num_handles) { + DLOG(ERROR) << "Received unexpected number of handles in broker message"; + return false; + } + + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + DCHECK(handles); + DCHECK_EQ(handles->size(), num_handles); + DCHECK(out_handles); + + for (size_t i = 0; i < num_handles; ++i) + out_handles[i] = ScopedPlatformHandle((*handles)[i]); + handles->clear(); + return true; +} + +Channel::MessagePtr WaitForBrokerMessage(PlatformHandle platform_handle, + BrokerMessageType expected_type) { + char buffer[kMaxBrokerMessageSize]; + DWORD bytes_read = 0; + BOOL result = ::ReadFile(platform_handle.handle, buffer, + kMaxBrokerMessageSize, &bytes_read, nullptr); + if (!result) { + // The pipe may be broken if the browser side has been closed, e.g. during + // browser shutdown. In that case the ReadFile call will fail and we + // shouldn't continue waiting. + PLOG(ERROR) << "Error reading broker pipe"; + return nullptr; + } + + Channel::MessagePtr message = + Channel::Message::Deserialize(buffer, static_cast(bytes_read)); + if (!message || message->payload_size() < sizeof(BrokerMessageHeader)) { + LOG(ERROR) << "Invalid broker message"; + + base::debug::Alias(&buffer[0]); + base::debug::Alias(&bytes_read); + base::debug::Alias(message.get()); + CHECK(false); + return nullptr; + } + + const BrokerMessageHeader* header = + reinterpret_cast(message->payload()); + if (header->type != expected_type) { + LOG(ERROR) << "Unexpected broker message type"; + + base::debug::Alias(&buffer[0]); + base::debug::Alias(&bytes_read); + base::debug::Alias(message.get()); + CHECK(false); + return nullptr; + } + + return message; +} + +} // namespace + +Broker::Broker(ScopedPlatformHandle handle) : sync_channel_(std::move(handle)) { + CHECK(sync_channel_.is_valid()); + Channel::MessagePtr message = + WaitForBrokerMessage(sync_channel_.get(), BrokerMessageType::INIT); + + // If we fail to read a message (broken pipe), just return early. The parent + // handle will be null and callers must handle this gracefully. + if (!message) + return; + + if (!TakeHandlesFromBrokerMessage(message.get(), 1, &parent_channel_)) { + // If the message has no handles, we expect it to carry pipe name instead. + const BrokerMessageHeader* header = + static_cast(message->payload()); + CHECK_GE(message->payload_size(), + sizeof(BrokerMessageHeader) + sizeof(InitData)); + const InitData* data = reinterpret_cast(header + 1); + CHECK_EQ(message->payload_size(), + sizeof(BrokerMessageHeader) + sizeof(InitData) + + data->pipe_name_length * sizeof(base::char16)); + const base::char16* name_data = + reinterpret_cast(data + 1); + CHECK(data->pipe_name_length); + parent_channel_ = CreateClientHandle(NamedPlatformHandle( + base::StringPiece16(name_data, data->pipe_name_length))); + } +} + +Broker::~Broker() {} + +ScopedPlatformHandle Broker::GetParentPlatformHandle() { + return std::move(parent_channel_); +} + +scoped_refptr Broker::GetSharedBuffer(size_t num_bytes) { + base::AutoLock lock(lock_); + BufferRequestData* buffer_request; + Channel::MessagePtr out_message = CreateBrokerMessage( + BrokerMessageType::BUFFER_REQUEST, 0, 0, &buffer_request); + buffer_request->size = base::checked_cast(num_bytes); + DWORD bytes_written = 0; + BOOL result = ::WriteFile(sync_channel_.get().handle, out_message->data(), + static_cast(out_message->data_num_bytes()), + &bytes_written, nullptr); + if (!result || + static_cast(bytes_written) != out_message->data_num_bytes()) { + LOG(ERROR) << "Error sending sync broker message"; + return nullptr; + } + + ScopedPlatformHandle handles[2]; + Channel::MessagePtr response = WaitForBrokerMessage( + sync_channel_.get(), BrokerMessageType::BUFFER_RESPONSE); + if (response && + TakeHandlesFromBrokerMessage(response.get(), 2, &handles[0])) { + return PlatformSharedBuffer::CreateFromPlatformHandlePair( + num_bytes, std::move(handles[0]), std::move(handles[1])); + } + + return nullptr; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/channel.cc b/mojo/edk/system/channel.cc new file mode 100644 index 0000000..8a44d36 --- /dev/null +++ b/mojo/edk/system/channel.cc @@ -0,0 +1,683 @@ +// 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/edk/system/channel.h" + +#include +#include + +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/aligned_memory.h" +#include "base/process/process_handle.h" +#include "mojo/edk/embedder/platform_handle.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_logging.h" +#elif defined(OS_WIN) +#include "base/win/win_util.h" +#endif + +namespace mojo { +namespace edk { + +namespace { + +static_assert( + IsAlignedForChannelMessage(sizeof(Channel::Message::LegacyHeader)), + "Invalid LegacyHeader size."); + +static_assert(IsAlignedForChannelMessage(sizeof(Channel::Message::Header)), + "Invalid Header size."); + +static_assert(sizeof(Channel::Message::LegacyHeader) == 8, + "LegacyHeader must be 8 bytes on ChromeOS and Android"); + +static_assert(offsetof(Channel::Message::LegacyHeader, num_bytes) == + offsetof(Channel::Message::Header, num_bytes), + "num_bytes should be at the same offset in both Header structs."); +static_assert(offsetof(Channel::Message::LegacyHeader, message_type) == + offsetof(Channel::Message::Header, message_type), + "message_type should be at the same offset in both Header " + "structs."); + +} // namespace + +const size_t kReadBufferSize = 4096; +const size_t kMaxUnusedReadBufferCapacity = 4096; +const size_t kMaxChannelMessageSize = 256 * 1024 * 1024; +const size_t kMaxAttachedHandles = 128; + +Channel::Message::Message(size_t payload_size, size_t max_handles) +#if defined(MOJO_EDK_LEGACY_PROTOCOL) + : Message(payload_size, max_handles, MessageType::NORMAL_LEGACY) { +} +#else + : Message(payload_size, max_handles, MessageType::NORMAL) { +} +#endif + +Channel::Message::Message(size_t payload_size, + size_t max_handles, + MessageType message_type) + : max_handles_(max_handles) { + DCHECK_LE(max_handles_, kMaxAttachedHandles); + + const bool is_legacy_message = (message_type == MessageType::NORMAL_LEGACY); + size_t extra_header_size = 0; +#if defined(OS_WIN) + // On Windows we serialize HANDLEs into the extra header space. + extra_header_size = max_handles_ * sizeof(HandleEntry); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, some of the platform handles may be mach ports, which are + // serialised into the message buffer. Since there could be a mix of fds and + // mach ports, we store the mach ports as an pair (of uint32_t), + // so that the original ordering of handles can be re-created. + if (max_handles) { + extra_header_size = + sizeof(MachPortsExtraHeader) + (max_handles * sizeof(MachPortsEntry)); + } +#endif + // Pad extra header data to be aliged to |kChannelMessageAlignment| bytes. + if (!IsAlignedForChannelMessage(extra_header_size)) { + extra_header_size += kChannelMessageAlignment - + (extra_header_size % kChannelMessageAlignment); + } + DCHECK(IsAlignedForChannelMessage(extra_header_size)); + const size_t header_size = + is_legacy_message ? sizeof(LegacyHeader) : sizeof(Header); + DCHECK(extra_header_size == 0 || !is_legacy_message); + + size_ = header_size + extra_header_size + payload_size; + data_ = static_cast(base::AlignedAlloc(size_, + kChannelMessageAlignment)); + // Only zero out the header and not the payload. Since the payload is going to + // be memcpy'd, zeroing the payload is unnecessary work and a significant + // performance issue when dealing with large messages. Any sanitizer errors + // complaining about an uninitialized read in the payload area should be + // treated as an error and fixed. + memset(data_, 0, header_size + extra_header_size); + + DCHECK_LE(size_, std::numeric_limits::max()); + legacy_header()->num_bytes = static_cast(size_); + + DCHECK_LE(header_size + extra_header_size, + std::numeric_limits::max()); + legacy_header()->message_type = message_type; + + if (is_legacy_message) { + legacy_header()->num_handles = static_cast(max_handles); + } else { + header()->num_header_bytes = + static_cast(header_size + extra_header_size); + } + + if (max_handles_ > 0) { +#if defined(OS_WIN) + handles_ = reinterpret_cast(mutable_extra_header()); + // Initialize all handles to invalid values. + for (size_t i = 0; i < max_handles_; ++i) + handles_[i].handle = base::win::HandleToUint32(INVALID_HANDLE_VALUE); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + mach_ports_header_ = + reinterpret_cast(mutable_extra_header()); + mach_ports_header_->num_ports = 0; + // Initialize all handles to invalid values. + for (size_t i = 0; i < max_handles_; ++i) { + mach_ports_header_->entries[i] = + {0, static_cast(MACH_PORT_NULL)}; + } +#endif + } +} + +Channel::Message::~Message() { + base::AlignedFree(data_); +} + +// static +Channel::MessagePtr Channel::Message::Deserialize(const void* data, + size_t data_num_bytes) { + if (data_num_bytes < sizeof(LegacyHeader)) + return nullptr; + + const LegacyHeader* legacy_header = + reinterpret_cast(data); + if (legacy_header->num_bytes != data_num_bytes) { + DLOG(ERROR) << "Decoding invalid message: " << legacy_header->num_bytes + << " != " << data_num_bytes; + return nullptr; + } + + const Header* header = nullptr; + if (legacy_header->message_type == MessageType::NORMAL) + header = reinterpret_cast(data); + + uint32_t extra_header_size = 0; + size_t payload_size = 0; + const char* payload = nullptr; + if (!header) { + payload_size = data_num_bytes - sizeof(LegacyHeader); + payload = static_cast(data) + sizeof(LegacyHeader); + } else { + if (header->num_bytes < header->num_header_bytes || + header->num_header_bytes < sizeof(Header)) { + DLOG(ERROR) << "Decoding invalid message: " << header->num_bytes << " < " + << header->num_header_bytes; + return nullptr; + } + extra_header_size = header->num_header_bytes - sizeof(Header); + payload_size = data_num_bytes - header->num_header_bytes; + payload = static_cast(data) + header->num_header_bytes; + } + +#if defined(OS_WIN) + uint32_t max_handles = extra_header_size / sizeof(HandleEntry); +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (extra_header_size > 0 && + extra_header_size < sizeof(MachPortsExtraHeader)) { + DLOG(ERROR) << "Decoding invalid message: " << extra_header_size << " < " + << sizeof(MachPortsExtraHeader); + return nullptr; + } + uint32_t max_handles = + extra_header_size == 0 + ? 0 + : (extra_header_size - sizeof(MachPortsExtraHeader)) / + sizeof(MachPortsEntry); +#else + const uint32_t max_handles = 0; +#endif // defined(OS_WIN) + + const uint16_t num_handles = + header ? header->num_handles : legacy_header->num_handles; + if (num_handles > max_handles || max_handles > kMaxAttachedHandles) { + DLOG(ERROR) << "Decoding invalid message: " << num_handles << " > " + << max_handles; + return nullptr; + } + + MessagePtr message( + new Message(payload_size, max_handles, legacy_header->message_type)); + DCHECK_EQ(message->data_num_bytes(), data_num_bytes); + + // Copy all payload bytes. + if (payload_size) + memcpy(message->mutable_payload(), payload, payload_size); + + if (header) { + DCHECK_EQ(message->extra_header_size(), extra_header_size); + DCHECK_EQ(message->header()->num_header_bytes, header->num_header_bytes); + + if (message->extra_header_size()) { + // Copy extra header bytes. + memcpy(message->mutable_extra_header(), + static_cast(data) + sizeof(Header), + message->extra_header_size()); + } + message->header()->num_handles = header->num_handles; + } else { + message->legacy_header()->num_handles = legacy_header->num_handles; + } + +#if defined(OS_WIN) + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector(num_handles)); + for (size_t i = 0; i < num_handles; i++) { + (*handles)[i].handle = + base::win::Uint32ToHandle(message->handles_[i].handle); + } + message->SetHandles(std::move(handles)); +#endif + + return message; +} + +const void* Channel::Message::extra_header() const { + DCHECK(!is_legacy_message()); + return data_ + sizeof(Header); +} + +void* Channel::Message::mutable_extra_header() { + DCHECK(!is_legacy_message()); + return data_ + sizeof(Header); +} + +size_t Channel::Message::extra_header_size() const { + return header()->num_header_bytes - sizeof(Header); +} + +void* Channel::Message::mutable_payload() { + if (is_legacy_message()) + return static_cast(legacy_header() + 1); + return data_ + header()->num_header_bytes; +} + +const void* Channel::Message::payload() const { + if (is_legacy_message()) + return static_cast(legacy_header() + 1); + return data_ + header()->num_header_bytes; +} + +size_t Channel::Message::payload_size() const { + if (is_legacy_message()) + return legacy_header()->num_bytes - sizeof(LegacyHeader); + return size_ - header()->num_header_bytes; +} + +size_t Channel::Message::num_handles() const { + return is_legacy_message() ? legacy_header()->num_handles + : header()->num_handles; +} + +bool Channel::Message::has_handles() const { + return (is_legacy_message() ? legacy_header()->num_handles + : header()->num_handles) > 0; +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +bool Channel::Message::has_mach_ports() const { + if (!has_handles()) + return false; + + for (const auto& handle : (*handle_vector_)) { + if (handle.type == PlatformHandle::Type::MACH || + handle.type == PlatformHandle::Type::MACH_NAME) { + return true; + } + } + return false; +} +#endif + +bool Channel::Message::is_legacy_message() const { + return legacy_header()->message_type == MessageType::NORMAL_LEGACY; +} + +Channel::Message::LegacyHeader* Channel::Message::legacy_header() const { + return reinterpret_cast(data_); +} + +Channel::Message::Header* Channel::Message::header() const { + DCHECK(!is_legacy_message()); + return reinterpret_cast(data_); +} + +void Channel::Message::SetHandles(ScopedPlatformHandleVectorPtr new_handles) { + if (is_legacy_message()) { + // Old semantics for ChromeOS and Android + if (legacy_header()->num_handles == 0) { + CHECK(!new_handles || new_handles->size() == 0); + return; + } + CHECK(new_handles && new_handles->size() == legacy_header()->num_handles); + std::swap(handle_vector_, new_handles); + return; + } + + if (max_handles_ == 0) { + CHECK(!new_handles || new_handles->size() == 0); + return; + } + + CHECK(new_handles && new_handles->size() <= max_handles_); + header()->num_handles = static_cast(new_handles->size()); + std::swap(handle_vector_, new_handles); +#if defined(OS_WIN) + memset(handles_, 0, extra_header_size()); + for (size_t i = 0; i < handle_vector_->size(); i++) + handles_[i].handle = base::win::HandleToUint32((*handle_vector_)[i].handle); +#endif // defined(OS_WIN) + +#if defined(OS_MACOSX) && !defined(OS_IOS) + size_t mach_port_index = 0; + if (mach_ports_header_) { + for (size_t i = 0; i < max_handles_; ++i) { + mach_ports_header_->entries[i] = + {0, static_cast(MACH_PORT_NULL)}; + } + for (size_t i = 0; i < handle_vector_->size(); i++) { + if ((*handle_vector_)[i].type == PlatformHandle::Type::MACH || + (*handle_vector_)[i].type == PlatformHandle::Type::MACH_NAME) { + mach_port_t port = (*handle_vector_)[i].port; + mach_ports_header_->entries[mach_port_index].index = i; + mach_ports_header_->entries[mach_port_index].mach_port = port; + mach_port_index++; + } + } + mach_ports_header_->num_ports = static_cast(mach_port_index); + } +#endif +} + +ScopedPlatformHandleVectorPtr Channel::Message::TakeHandles() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (mach_ports_header_) { + for (size_t i = 0; i < max_handles_; ++i) { + mach_ports_header_->entries[i] = + {0, static_cast(MACH_PORT_NULL)}; + } + mach_ports_header_->num_ports = 0; + } +#endif + if (is_legacy_message()) + legacy_header()->num_handles = 0; + else + header()->num_handles = 0; + return std::move(handle_vector_); +} + +ScopedPlatformHandleVectorPtr Channel::Message::TakeHandlesForTransport() { +#if defined(OS_WIN) + // Not necessary on Windows. + NOTREACHED(); + return nullptr; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (handle_vector_) { + for (auto it = handle_vector_->begin(); it != handle_vector_->end(); ) { + if (it->type == PlatformHandle::Type::MACH || + it->type == PlatformHandle::Type::MACH_NAME) { + // For Mach port names, we can can just leak them. They're not real + // ports anyways. For real ports, they're leaked because this is a child + // process and the remote process will take ownership. + it = handle_vector_->erase(it); + } else { + ++it; + } + } + } + return std::move(handle_vector_); +#else + return std::move(handle_vector_); +#endif +} + +#if defined(OS_WIN) +// static +bool Channel::Message::RewriteHandles(base::ProcessHandle from_process, + base::ProcessHandle to_process, + PlatformHandleVector* handles) { + bool success = true; + for (size_t i = 0; i < handles->size(); ++i) { + if (!(*handles)[i].is_valid()) { + DLOG(ERROR) << "Refusing to duplicate invalid handle."; + continue; + } + DCHECK_EQ((*handles)[i].owning_process, from_process); + BOOL result = DuplicateHandle( + from_process, (*handles)[i].handle, to_process, + &(*handles)[i].handle, 0, FALSE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); + if (result) { + (*handles)[i].owning_process = to_process; + } else { + success = false; + + // If handle duplication fails, the source handle will already be closed + // due to DUPLICATE_CLOSE_SOURCE. Replace the handle in the message with + // an invalid handle. + (*handles)[i].handle = INVALID_HANDLE_VALUE; + (*handles)[i].owning_process = base::GetCurrentProcessHandle(); + } + } + return success; +} +#endif + +// Helper class for managing a Channel's read buffer allocations. This maintains +// a single contiguous buffer with the layout: +// +// [discarded bytes][occupied bytes][unoccupied bytes] +// +// The Reserve() method ensures that a certain capacity of unoccupied bytes are +// available. It does not claim that capacity and only allocates new capacity +// when strictly necessary. +// +// Claim() marks unoccupied bytes as occupied. +// +// Discard() marks occupied bytes as discarded, signifying that their contents +// can be forgotten or overwritten. +// +// Realign() moves occupied bytes to the front of the buffer so that those +// occupied bytes are properly aligned. +// +// The most common Channel behavior in practice should result in very few +// allocations and copies, as memory is claimed and discarded shortly after +// being reserved, and future reservations will immediately reuse discarded +// memory. +class Channel::ReadBuffer { + public: + ReadBuffer() { + size_ = kReadBufferSize; + data_ = static_cast(base::AlignedAlloc(size_, + kChannelMessageAlignment)); + } + + ~ReadBuffer() { + DCHECK(data_); + base::AlignedFree(data_); + } + + const char* occupied_bytes() const { return data_ + num_discarded_bytes_; } + + size_t num_occupied_bytes() const { + return num_occupied_bytes_ - num_discarded_bytes_; + } + + // Ensures the ReadBuffer has enough contiguous space allocated to hold + // |num_bytes| more bytes; returns the address of the first available byte. + char* Reserve(size_t num_bytes) { + if (num_occupied_bytes_ + num_bytes > size_) { + size_ = std::max(size_ * 2, num_occupied_bytes_ + num_bytes); + void* new_data = base::AlignedAlloc(size_, kChannelMessageAlignment); + memcpy(new_data, data_, num_occupied_bytes_); + base::AlignedFree(data_); + data_ = static_cast(new_data); + } + + return data_ + num_occupied_bytes_; + } + + // Marks the first |num_bytes| unoccupied bytes as occupied. + void Claim(size_t num_bytes) { + DCHECK_LE(num_occupied_bytes_ + num_bytes, size_); + num_occupied_bytes_ += num_bytes; + } + + // Marks the first |num_bytes| occupied bytes as discarded. This may result in + // shrinkage of the internal buffer, and it is not safe to assume the result + // of a previous Reserve() call is still valid after this. + void Discard(size_t num_bytes) { + DCHECK_LE(num_discarded_bytes_ + num_bytes, num_occupied_bytes_); + num_discarded_bytes_ += num_bytes; + + if (num_discarded_bytes_ == num_occupied_bytes_) { + // We can just reuse the buffer from the beginning in this common case. + num_discarded_bytes_ = 0; + num_occupied_bytes_ = 0; + } + + if (num_discarded_bytes_ > kMaxUnusedReadBufferCapacity) { + // In the uncommon case that we have a lot of discarded data at the + // front of the buffer, simply move remaining data to a smaller buffer. + size_t num_preserved_bytes = num_occupied_bytes_ - num_discarded_bytes_; + size_ = std::max(num_preserved_bytes, kReadBufferSize); + char* new_data = static_cast( + base::AlignedAlloc(size_, kChannelMessageAlignment)); + memcpy(new_data, data_ + num_discarded_bytes_, num_preserved_bytes); + base::AlignedFree(data_); + data_ = new_data; + num_discarded_bytes_ = 0; + num_occupied_bytes_ = num_preserved_bytes; + } + + if (num_occupied_bytes_ == 0 && size_ > kMaxUnusedReadBufferCapacity) { + // Opportunistically shrink the read buffer back down to a small size if + // it's grown very large. We only do this if there are no remaining + // unconsumed bytes in the buffer to avoid copies in most the common + // cases. + size_ = kMaxUnusedReadBufferCapacity; + base::AlignedFree(data_); + data_ = static_cast( + base::AlignedAlloc(size_, kChannelMessageAlignment)); + } + } + + void Realign() { + size_t num_bytes = num_occupied_bytes(); + memmove(data_, occupied_bytes(), num_bytes); + num_discarded_bytes_ = 0; + num_occupied_bytes_ = num_bytes; + } + + private: + char* data_ = nullptr; + + // The total size of the allocated buffer. + size_t size_ = 0; + + // The number of discarded bytes at the beginning of the allocated buffer. + size_t num_discarded_bytes_ = 0; + + // The total number of occupied bytes, including discarded bytes. + size_t num_occupied_bytes_ = 0; + + DISALLOW_COPY_AND_ASSIGN(ReadBuffer); +}; + +Channel::Channel(Delegate* delegate) + : delegate_(delegate), read_buffer_(new ReadBuffer) { +} + +Channel::~Channel() { +} + +void Channel::ShutDown() { + delegate_ = nullptr; + ShutDownImpl(); +} + +char* Channel::GetReadBuffer(size_t *buffer_capacity) { + DCHECK(read_buffer_); + size_t required_capacity = *buffer_capacity; + if (!required_capacity) + required_capacity = kReadBufferSize; + + *buffer_capacity = required_capacity; + return read_buffer_->Reserve(required_capacity); +} + +bool Channel::OnReadComplete(size_t bytes_read, size_t *next_read_size_hint) { + bool did_dispatch_message = false; + read_buffer_->Claim(bytes_read); + while (read_buffer_->num_occupied_bytes() >= sizeof(Message::LegacyHeader)) { + // Ensure the occupied data is properly aligned. If it isn't, a SIGBUS could + // happen on architectures that don't allow misaligned words access (i.e. + // anything other than x86). Only re-align when necessary to avoid copies. + if (!IsAlignedForChannelMessage( + reinterpret_cast(read_buffer_->occupied_bytes()))) { + read_buffer_->Realign(); + } + + // We have at least enough data available for a LegacyHeader. + const Message::LegacyHeader* legacy_header = + reinterpret_cast( + read_buffer_->occupied_bytes()); + + if (legacy_header->num_bytes < sizeof(Message::LegacyHeader) || + legacy_header->num_bytes > kMaxChannelMessageSize) { + LOG(ERROR) << "Invalid message size: " << legacy_header->num_bytes; + return false; + } + + if (read_buffer_->num_occupied_bytes() < legacy_header->num_bytes) { + // Not enough data available to read the full message. Hint to the + // implementation that it should try reading the full size of the message. + *next_read_size_hint = + legacy_header->num_bytes - read_buffer_->num_occupied_bytes(); + return true; + } + + const Message::Header* header = nullptr; + if (legacy_header->message_type != Message::MessageType::NORMAL_LEGACY) { + header = reinterpret_cast(legacy_header); + } + + size_t extra_header_size = 0; + const void* extra_header = nullptr; + size_t payload_size = 0; + void* payload = nullptr; + if (header) { + if (header->num_header_bytes < sizeof(Message::Header) || + header->num_header_bytes > header->num_bytes) { + LOG(ERROR) << "Invalid message header size: " + << header->num_header_bytes; + return false; + } + extra_header_size = header->num_header_bytes - sizeof(Message::Header); + extra_header = extra_header_size ? header + 1 : nullptr; + payload_size = header->num_bytes - header->num_header_bytes; + payload = payload_size + ? reinterpret_cast( + const_cast(read_buffer_->occupied_bytes()) + + header->num_header_bytes) + : nullptr; + } else { + payload_size = legacy_header->num_bytes - sizeof(Message::LegacyHeader); + payload = payload_size + ? const_cast(&legacy_header[1]) + : nullptr; + } + + const uint16_t num_handles = + header ? header->num_handles : legacy_header->num_handles; + ScopedPlatformHandleVectorPtr handles; + if (num_handles > 0) { + if (!GetReadPlatformHandles(num_handles, extra_header, extra_header_size, + &handles)) { + return false; + } + + if (!handles) { + // Not enough handles available for this message. + break; + } + } + + // We've got a complete message! Dispatch it and try another. + if (legacy_header->message_type != Message::MessageType::NORMAL_LEGACY && + legacy_header->message_type != Message::MessageType::NORMAL) { + if (!OnControlMessage(legacy_header->message_type, payload, payload_size, + std::move(handles))) { + return false; + } + did_dispatch_message = true; + } else if (delegate_) { + delegate_->OnChannelMessage(payload, payload_size, std::move(handles)); + did_dispatch_message = true; + } + + read_buffer_->Discard(legacy_header->num_bytes); + } + + *next_read_size_hint = did_dispatch_message ? 0 : kReadBufferSize; + return true; +} + +void Channel::OnError() { + if (delegate_) + delegate_->OnChannelError(); +} + +bool Channel::OnControlMessage(Message::MessageType message_type, + const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) { + return false; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/channel.h b/mojo/edk/system/channel.h new file mode 100644 index 0000000..33a510c --- /dev/null +++ b/mojo/edk/system/channel.h @@ -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. + +#ifndef MOJO_EDK_SYSTEM_CHANNEL_H_ +#define MOJO_EDK_SYSTEM_CHANNEL_H_ + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/process/process_handle.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/connection_params.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +namespace mojo { +namespace edk { + +const size_t kChannelMessageAlignment = 8; + +constexpr bool IsAlignedForChannelMessage(size_t n) { + return n % kChannelMessageAlignment == 0; +} + +// Channel provides a thread-safe interface to read and write arbitrary +// delimited messages over an underlying I/O channel, optionally transferring +// one or more platform handles in the process. +class MOJO_SYSTEM_IMPL_EXPORT Channel + : public base::RefCountedThreadSafe { + public: + struct Message; + + using MessagePtr = std::unique_ptr; + + // A message to be written to a channel. + struct MOJO_SYSTEM_IMPL_EXPORT Message { + enum class MessageType : uint16_t { + // An old format normal message, that uses the LegacyHeader. + // Only used on Android and ChromeOS. + // TODO(jcivelli): remove legacy support when Arc++ has updated to Mojo + // with normal versioned messages. crbug.com/695645 + NORMAL_LEGACY = 0, +#if defined(OS_MACOSX) + // A control message containing handles to echo back. + HANDLES_SENT, + // A control message containing handles that can now be closed. + HANDLES_SENT_ACK, +#endif + // A normal message that uses Header and can contain extra header values. + NORMAL, + }; + +#pragma pack(push, 1) + // Old message wire format for ChromeOS and Android, used by NORMAL_LEGACY + // messages. + struct LegacyHeader { + // Message size in bytes, including the header. + uint32_t num_bytes; + + // Number of attached handles. + uint16_t num_handles; + + MessageType message_type; + }; + + // Header used by NORMAL messages. + // To preserve backward compatibility with LegacyHeader, the num_bytes and + // message_type field must be at the same offset as in LegacyHeader. + struct Header { + // Message size in bytes, including the header. + uint32_t num_bytes; + + // Total size of header, including extra header data (i.e. HANDLEs on + // windows). + uint16_t num_header_bytes; + + MessageType message_type; + + // Number of attached handles. May be less than the reserved handle + // storage size in this message on platforms that serialise handles as + // data (i.e. HANDLEs on Windows, Mach ports on OSX). + uint16_t num_handles; + + char padding[6]; + }; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + struct MachPortsEntry { + // Index of Mach port in the original vector of PlatformHandles. + uint16_t index; + + // Mach port name. + uint32_t mach_port; + static_assert(sizeof(mach_port_t) <= sizeof(uint32_t), + "mach_port_t must be no larger than uint32_t"); + }; + static_assert(sizeof(MachPortsEntry) == 6, + "sizeof(MachPortsEntry) must be 6 bytes"); + + // Structure of the extra header field when present on OSX. + struct MachPortsExtraHeader { + // Actual number of Mach ports encoded in the extra header. + uint16_t num_ports; + + // Array of encoded Mach ports. If |num_ports| > 0, |entries[0]| through + // to |entries[num_ports-1]| inclusive are valid. + MachPortsEntry entries[0]; + }; + static_assert(sizeof(MachPortsExtraHeader) == 2, + "sizeof(MachPortsExtraHeader) must be 2 bytes"); +#elif defined(OS_WIN) + struct HandleEntry { + // The windows HANDLE. HANDLEs are guaranteed to fit inside 32-bits. + // See: https://msdn.microsoft.com/en-us/library/aa384203(VS.85).aspx + uint32_t handle; + }; + static_assert(sizeof(HandleEntry) == 4, + "sizeof(HandleEntry) must be 4 bytes"); +#endif +#pragma pack(pop) + + // Allocates and owns a buffer for message data with enough capacity for + // |payload_size| bytes plus a header, plus |max_handles| platform handles. + Message(size_t payload_size, size_t max_handles); + Message(size_t payload_size, size_t max_handles, MessageType message_type); + ~Message(); + + // Constructs a Message from serialized message data. + static MessagePtr Deserialize(const void* data, size_t data_num_bytes); + + const void* data() const { return data_; } + size_t data_num_bytes() const { return size_; } + + const void* extra_header() const; + void* mutable_extra_header(); + size_t extra_header_size() const; + + void* mutable_payload(); + const void* payload() const; + size_t payload_size() const; + + size_t num_handles() const; + bool has_handles() const; +#if defined(OS_MACOSX) && !defined(OS_IOS) + bool has_mach_ports() const; +#endif + + bool is_legacy_message() const; + LegacyHeader* legacy_header() const; + Header* header() const; + + // Note: SetHandles() and TakeHandles() invalidate any previous value of + // handles(). + void SetHandles(ScopedPlatformHandleVectorPtr new_handles); + ScopedPlatformHandleVectorPtr TakeHandles(); + // Version of TakeHandles that returns a vector of platform handles suitable + // for transfer over an underlying OS mechanism. i.e. file descriptors over + // a unix domain socket. Any handle that cannot be transferred this way, + // such as Mach ports, will be removed. + ScopedPlatformHandleVectorPtr TakeHandlesForTransport(); + +#if defined(OS_WIN) + // Prepares the handles in this message for use in a different process. + // Upon calling this the handles should belong to |from_process|; after the + // call they'll belong to |to_process|. The source handles are always + // closed by this call. Returns false iff one or more handles failed + // duplication. + static bool RewriteHandles(base::ProcessHandle from_process, + base::ProcessHandle to_process, + PlatformHandleVector* handles); +#endif + + void SetVersionForTest(uint16_t version_number); + + private: + size_t size_ = 0; + size_t max_handles_ = 0; + char* data_ = nullptr; + + ScopedPlatformHandleVectorPtr handle_vector_; + +#if defined(OS_WIN) + // On Windows, handles are serialised into the extra header section. + HandleEntry* handles_ = nullptr; +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, handles are serialised into the extra header section. + MachPortsExtraHeader* mach_ports_header_ = nullptr; +#endif + + DISALLOW_COPY_AND_ASSIGN(Message); + }; + + // Delegate methods are called from the I/O task runner with which the Channel + // was created (see Channel::Create). + class Delegate { + public: + virtual ~Delegate() {} + + // Notify of a received message. |payload| is not owned and must not be + // retained; it will be null if |payload_size| is 0. |handles| are + // transferred to the callee. + virtual void OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) = 0; + + // Notify that an error has occured and the Channel will cease operation. + virtual void OnChannelError() = 0; + }; + + // Creates a new Channel around a |platform_handle|, taking ownership of the + // handle. All I/O on the handle will be performed on |io_task_runner|. + // Note that ShutDown() MUST be called on the Channel some time before + // |delegate| is destroyed. + static scoped_refptr Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner); + + // Request that the channel be shut down. This should always be called before + // releasing the last reference to a Channel to ensure that it's cleaned up + // on its I/O task runner's thread. + // + // Delegate methods will no longer be invoked after this call. + void ShutDown(); + + // Begin processing I/O events. Delegate methods must only be invoked after + // this call. + virtual void Start() = 0; + + // Stop processing I/O events. + virtual void ShutDownImpl() = 0; + + // Queues an outgoing message on the Channel. This message will either + // eventually be written or will fail to write and trigger + // Delegate::OnChannelError. + virtual void Write(MessagePtr message) = 0; + + // Causes the platform handle to leak when this channel is shut down instead + // of closing it. + virtual void LeakHandle() = 0; + + protected: + explicit Channel(Delegate* delegate); + virtual ~Channel(); + + // Called by the implementation when it wants somewhere to stick data. + // |*buffer_capacity| may be set by the caller to indicate the desired buffer + // size. If 0, a sane default size will be used instead. + // + // Returns the address of a buffer which can be written to, and indicates its + // actual capacity in |*buffer_capacity|. + char* GetReadBuffer(size_t* buffer_capacity); + + // Called by the implementation when new data is available in the read + // buffer. Returns false to indicate an error. Upon success, + // |*next_read_size_hint| will be set to a recommended size for the next + // read done by the implementation. + bool OnReadComplete(size_t bytes_read, size_t* next_read_size_hint); + + // Called by the implementation when something goes horribly wrong. It is NOT + // OK to call this synchronously from any public interface methods. + void OnError(); + + // Retrieves the set of platform handles read for a given message. + // |extra_header| and |extra_header_size| correspond to the extra header data. + // Depending on the Channel implementation, this body may encode platform + // handles, or handles may be stored and managed elsewhere by the + // implementation. + // + // Returns |false| on unrecoverable error (i.e. the Channel should be closed). + // Returns |true| otherwise. Note that it is possible on some platforms for an + // insufficient number of handles to be available when this call is made, but + // this is not necessarily an error condition. In such cases this returns + // |true| but |*handles| will also be reset to null. + virtual bool GetReadPlatformHandles( + size_t num_handles, + const void* extra_header, + size_t extra_header_size, + ScopedPlatformHandleVectorPtr* handles) = 0; + + // Handles a received control message. Returns |true| if the message is + // accepted, or |false| otherwise. + virtual bool OnControlMessage(Message::MessageType message_type, + const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles); + + private: + friend class base::RefCountedThreadSafe; + + class ReadBuffer; + + Delegate* delegate_; + const std::unique_ptr read_buffer_; + + DISALLOW_COPY_AND_ASSIGN(Channel); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_CHANNEL_H_ diff --git a/mojo/edk/system/channel_posix.cc b/mojo/edk/system/channel_posix.cc new file mode 100644 index 0000000..8b4ca7f --- /dev/null +++ b/mojo/edk/system/channel_posix.cc @@ -0,0 +1,572 @@ +// 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/edk/system/channel.h" + +#include +#include + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/platform_channel_utils_posix.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +#if !defined(OS_NACL) +#include +#endif + +namespace mojo { +namespace edk { + +namespace { + +const size_t kMaxBatchReadCapacity = 256 * 1024; + +// A view over a Channel::Message object. The write queue uses these since +// large messages may need to be sent in chunks. +class MessageView { + public: + // Owns |message|. |offset| indexes the first unsent byte in the message. + MessageView(Channel::MessagePtr message, size_t offset) + : message_(std::move(message)), + offset_(offset), + handles_(message_->TakeHandlesForTransport()) { + DCHECK_GT(message_->data_num_bytes(), offset_); + } + + MessageView(MessageView&& other) { *this = std::move(other); } + + MessageView& operator=(MessageView&& other) { + message_ = std::move(other.message_); + offset_ = other.offset_; + handles_ = std::move(other.handles_); + return *this; + } + + ~MessageView() {} + + const void* data() const { + return static_cast(message_->data()) + offset_; + } + + size_t data_num_bytes() const { return message_->data_num_bytes() - offset_; } + + size_t data_offset() const { return offset_; } + void advance_data_offset(size_t num_bytes) { + DCHECK_GT(message_->data_num_bytes(), offset_ + num_bytes); + offset_ += num_bytes; + } + + ScopedPlatformHandleVectorPtr TakeHandles() { return std::move(handles_); } + Channel::MessagePtr TakeMessage() { return std::move(message_); } + + void SetHandles(ScopedPlatformHandleVectorPtr handles) { + handles_ = std::move(handles); + } + + private: + Channel::MessagePtr message_; + size_t offset_; + ScopedPlatformHandleVectorPtr handles_; + + DISALLOW_COPY_AND_ASSIGN(MessageView); +}; + +class ChannelPosix : public Channel, + public base::MessageLoop::DestructionObserver, + public base::MessageLoopForIO::Watcher { + public: + ChannelPosix(Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner) + : Channel(delegate), + self_(this), + handle_(connection_params.TakeChannelHandle()), + io_task_runner_(io_task_runner) +#if defined(OS_MACOSX) + , + handles_to_close_(new PlatformHandleVector) +#endif + { + CHECK(handle_.is_valid()); + } + + void Start() override { + if (io_task_runner_->RunsTasksOnCurrentThread()) { + StartOnIOThread(); + } else { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelPosix::StartOnIOThread, this)); + } + } + + void ShutDownImpl() override { + // Always shut down asynchronously when called through the public interface. + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelPosix::ShutDownOnIOThread, this)); + } + + void Write(MessagePtr message) override { + bool write_error = false; + { + base::AutoLock lock(write_lock_); + if (reject_writes_) + return; + if (outgoing_messages_.empty()) { + if (!WriteNoLock(MessageView(std::move(message), 0))) + reject_writes_ = write_error = true; + } else { + outgoing_messages_.emplace_back(std::move(message), 0); + } + } + if (write_error) { + // Do not synchronously invoke OnError(). Write() may have been called by + // the delegate and we don't want to re-enter it. + io_task_runner_->PostTask(FROM_HERE, + base::Bind(&ChannelPosix::OnError, this)); + } + } + + void LeakHandle() override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + leak_handle_ = true; + } + + bool GetReadPlatformHandles( + size_t num_handles, + const void* extra_header, + size_t extra_header_size, + ScopedPlatformHandleVectorPtr* handles) override { + if (num_handles > std::numeric_limits::max()) + return false; +#if defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, we can have mach ports which are located in the extra header + // section. + using MachPortsEntry = Channel::Message::MachPortsEntry; + using MachPortsExtraHeader = Channel::Message::MachPortsExtraHeader; + CHECK(extra_header_size >= + sizeof(MachPortsExtraHeader) + num_handles * sizeof(MachPortsEntry)); + const MachPortsExtraHeader* mach_ports_header = + reinterpret_cast(extra_header); + size_t num_mach_ports = mach_ports_header->num_ports; + CHECK(num_mach_ports <= num_handles); + if (incoming_platform_handles_.size() + num_mach_ports < num_handles) { + handles->reset(); + return true; + } + + handles->reset(new PlatformHandleVector(num_handles)); + const MachPortsEntry* mach_ports = mach_ports_header->entries; + for (size_t i = 0, mach_port_index = 0; i < num_handles; ++i) { + if (mach_port_index < num_mach_ports && + mach_ports[mach_port_index].index == i) { + (*handles)->at(i) = PlatformHandle( + static_cast(mach_ports[mach_port_index].mach_port)); + CHECK((*handles)->at(i).type == PlatformHandle::Type::MACH); + // These are actually just Mach port names until they're resolved from + // the remote process. + (*handles)->at(i).type = PlatformHandle::Type::MACH_NAME; + mach_port_index++; + } else { + CHECK(!incoming_platform_handles_.empty()); + (*handles)->at(i) = incoming_platform_handles_.front(); + incoming_platform_handles_.pop_front(); + } + } +#else + if (incoming_platform_handles_.size() < num_handles) { + handles->reset(); + return true; + } + + handles->reset(new PlatformHandleVector(num_handles)); + for (size_t i = 0; i < num_handles; ++i) { + (*handles)->at(i) = incoming_platform_handles_.front(); + incoming_platform_handles_.pop_front(); + } +#endif + + return true; + } + + private: + ~ChannelPosix() override { + DCHECK(!read_watcher_); + DCHECK(!write_watcher_); + for (auto handle : incoming_platform_handles_) + handle.CloseIfNecessary(); + } + + void StartOnIOThread() { + DCHECK(!read_watcher_); + DCHECK(!write_watcher_); + read_watcher_.reset( + new base::MessageLoopForIO::FileDescriptorWatcher(FROM_HERE)); + base::MessageLoop::current()->AddDestructionObserver(this); + if (handle_.get().needs_connection) { + base::MessageLoopForIO::current()->WatchFileDescriptor( + handle_.get().handle, false /* persistent */, + base::MessageLoopForIO::WATCH_READ, read_watcher_.get(), this); + } else { + write_watcher_.reset( + new base::MessageLoopForIO::FileDescriptorWatcher(FROM_HERE)); + base::MessageLoopForIO::current()->WatchFileDescriptor( + handle_.get().handle, true /* persistent */, + base::MessageLoopForIO::WATCH_READ, read_watcher_.get(), this); + base::AutoLock lock(write_lock_); + FlushOutgoingMessagesNoLock(); + } + } + + void WaitForWriteOnIOThread() { + base::AutoLock lock(write_lock_); + WaitForWriteOnIOThreadNoLock(); + } + + void WaitForWriteOnIOThreadNoLock() { + if (pending_write_) + return; + if (!write_watcher_) + return; + if (io_task_runner_->RunsTasksOnCurrentThread()) { + pending_write_ = true; + base::MessageLoopForIO::current()->WatchFileDescriptor( + handle_.get().handle, false /* persistent */, + base::MessageLoopForIO::WATCH_WRITE, write_watcher_.get(), this); + } else { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelPosix::WaitForWriteOnIOThread, this)); + } + } + + void ShutDownOnIOThread() { + base::MessageLoop::current()->RemoveDestructionObserver(this); + + read_watcher_.reset(); + write_watcher_.reset(); + if (leak_handle_) + ignore_result(handle_.release()); + handle_.reset(); +#if defined(OS_MACOSX) + handles_to_close_.reset(); +#endif + + // May destroy the |this| if it was the last reference. + self_ = nullptr; + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + if (self_) + ShutDownOnIOThread(); + } + + // base::MessageLoopForIO::Watcher: + void OnFileCanReadWithoutBlocking(int fd) override { + CHECK_EQ(fd, handle_.get().handle); + if (handle_.get().needs_connection) { +#if !defined(OS_NACL) + read_watcher_.reset(); + base::MessageLoop::current()->RemoveDestructionObserver(this); + + ScopedPlatformHandle accept_fd; + ServerAcceptConnection(handle_.get(), &accept_fd); + if (!accept_fd.is_valid()) { + OnError(); + return; + } + handle_ = std::move(accept_fd); + StartOnIOThread(); +#else + NOTREACHED(); +#endif + return; + } + + bool read_error = false; + size_t next_read_size = 0; + size_t buffer_capacity = 0; + size_t total_bytes_read = 0; + size_t bytes_read = 0; + do { + buffer_capacity = next_read_size; + char* buffer = GetReadBuffer(&buffer_capacity); + DCHECK_GT(buffer_capacity, 0u); + + ssize_t read_result = PlatformChannelRecvmsg( + handle_.get(), + buffer, + buffer_capacity, + &incoming_platform_handles_); + + if (read_result > 0) { + bytes_read = static_cast(read_result); + total_bytes_read += bytes_read; + if (!OnReadComplete(bytes_read, &next_read_size)) { + read_error = true; + break; + } + } else if (read_result == 0 || + (errno != EAGAIN && errno != EWOULDBLOCK)) { + read_error = true; + break; + } + } while (bytes_read == buffer_capacity && + total_bytes_read < kMaxBatchReadCapacity && + next_read_size > 0); + if (read_error) { + // Stop receiving read notifications. + read_watcher_.reset(); + + OnError(); + } + } + + void OnFileCanWriteWithoutBlocking(int fd) override { + bool write_error = false; + { + base::AutoLock lock(write_lock_); + pending_write_ = false; + if (!FlushOutgoingMessagesNoLock()) + reject_writes_ = write_error = true; + } + if (write_error) + OnError(); + } + + // Attempts to write a message directly to the channel. If the full message + // cannot be written, it's queued and a wait is initiated to write the message + // ASAP on the I/O thread. + bool WriteNoLock(MessageView message_view) { + if (handle_.get().needs_connection) { + outgoing_messages_.emplace_front(std::move(message_view)); + return true; + } + size_t bytes_written = 0; + do { + message_view.advance_data_offset(bytes_written); + + ssize_t result; + ScopedPlatformHandleVectorPtr handles = message_view.TakeHandles(); + if (handles && handles->size()) { + iovec iov = { + const_cast(message_view.data()), + message_view.data_num_bytes() + }; + // TODO: Handle lots of handles. + result = PlatformChannelSendmsgWithHandles( + handle_.get(), &iov, 1, handles->data(), handles->size()); + if (result >= 0) { +#if defined(OS_MACOSX) + // There is a bug on OSX which makes it dangerous to close + // a file descriptor while it is in transit. So instead we + // store the file descriptor in a set and send a message to + // the recipient, which is queued AFTER the message that + // sent the FD. The recipient will reply to the message, + // letting us know that it is now safe to close the file + // descriptor. For more information, see: + // http://crbug.com/298276 + std::vector fds; + for (auto& handle : *handles) + fds.push_back(handle.handle); + { + base::AutoLock l(handles_to_close_lock_); + for (auto& handle : *handles) + handles_to_close_->push_back(handle); + } + MessagePtr fds_message( + new Channel::Message(sizeof(fds[0]) * fds.size(), 0, + Message::MessageType::HANDLES_SENT)); + memcpy(fds_message->mutable_payload(), fds.data(), + sizeof(fds[0]) * fds.size()); + outgoing_messages_.emplace_back(std::move(fds_message), 0); + handles->clear(); +#else + handles.reset(); +#endif // defined(OS_MACOSX) + } + } else { + result = PlatformChannelWrite(handle_.get(), message_view.data(), + message_view.data_num_bytes()); + } + + if (result < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK +#if defined(OS_MACOSX) + // On OS X if sendmsg() is trying to send fds between processes and + // there isn't enough room in the output buffer to send the fd + // structure over atomically then EMSGSIZE is returned. + // + // EMSGSIZE presents a problem since the system APIs can only call + // us when there's room in the socket buffer and not when there is + // "enough" room. + // + // The current behavior is to return to the event loop when EMSGSIZE + // is received and hopefull service another FD. This is however + // still technically a busy wait since the event loop will call us + // right back until the receiver has read enough data to allow + // passing the FD over atomically. + && errno != EMSGSIZE +#endif + ) { + return false; + } + message_view.SetHandles(std::move(handles)); + outgoing_messages_.emplace_front(std::move(message_view)); + WaitForWriteOnIOThreadNoLock(); + return true; + } + + bytes_written = static_cast(result); + } while (bytes_written < message_view.data_num_bytes()); + + return FlushOutgoingMessagesNoLock(); + } + + bool FlushOutgoingMessagesNoLock() { + std::deque messages; + std::swap(outgoing_messages_, messages); + + while (!messages.empty()) { + if (!WriteNoLock(std::move(messages.front()))) + return false; + + messages.pop_front(); + if (!outgoing_messages_.empty()) { + // The message was requeued by WriteNoLock(), so we have to wait for + // pipe to become writable again. Repopulate the message queue and exit. + // If sending the message triggered any control messages, they may be + // in |outgoing_messages_| in addition to or instead of the message + // being sent. + std::swap(messages, outgoing_messages_); + while (!messages.empty()) { + outgoing_messages_.push_front(std::move(messages.back())); + messages.pop_back(); + } + return true; + } + } + + return true; + } + +#if defined(OS_MACOSX) + bool OnControlMessage(Message::MessageType message_type, + const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) override { + switch (message_type) { + case Message::MessageType::HANDLES_SENT: { + if (payload_size == 0) + break; + MessagePtr message(new Channel::Message( + payload_size, 0, Message::MessageType::HANDLES_SENT_ACK)); + memcpy(message->mutable_payload(), payload, payload_size); + Write(std::move(message)); + return true; + } + + case Message::MessageType::HANDLES_SENT_ACK: { + size_t num_fds = payload_size / sizeof(int); + if (num_fds == 0 || payload_size % sizeof(int) != 0) + break; + + const int* fds = reinterpret_cast(payload); + if (!CloseHandles(fds, num_fds)) + break; + return true; + } + + default: + break; + } + + return false; + } + + // Closes handles referenced by |fds|. Returns false if |num_fds| is 0, or if + // |fds| does not match a sequence of handles in |handles_to_close_|. + bool CloseHandles(const int* fds, size_t num_fds) { + base::AutoLock l(handles_to_close_lock_); + if (!num_fds) + return false; + + auto start = + std::find_if(handles_to_close_->begin(), handles_to_close_->end(), + [&fds](const PlatformHandle& handle) { + return handle.handle == fds[0]; + }); + if (start == handles_to_close_->end()) + return false; + + auto it = start; + size_t i = 0; + // The FDs in the message should match a sequence of handles in + // |handles_to_close_|. + for (; i < num_fds && it != handles_to_close_->end(); i++, ++it) { + if (it->handle != fds[i]) + return false; + + it->CloseIfNecessary(); + } + if (i != num_fds) + return false; + + handles_to_close_->erase(start, it); + return true; + } +#endif // defined(OS_MACOSX) + + // Keeps the Channel alive at least until explicit shutdown on the IO thread. + scoped_refptr self_; + + ScopedPlatformHandle handle_; + scoped_refptr io_task_runner_; + + // These watchers must only be accessed on the IO thread. + std::unique_ptr read_watcher_; + std::unique_ptr write_watcher_; + + std::deque incoming_platform_handles_; + + // Protects |pending_write_| and |outgoing_messages_|. + base::Lock write_lock_; + bool pending_write_ = false; + bool reject_writes_ = false; + std::deque outgoing_messages_; + + bool leak_handle_ = false; + +#if defined(OS_MACOSX) + base::Lock handles_to_close_lock_; + ScopedPlatformHandleVectorPtr handles_to_close_; +#endif + + DISALLOW_COPY_AND_ASSIGN(ChannelPosix); +}; + +} // namespace + +// static +scoped_refptr Channel::Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner) { + return new ChannelPosix(delegate, std::move(connection_params), + io_task_runner); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/channel_unittest.cc b/mojo/edk/system/channel_unittest.cc new file mode 100644 index 0000000..ce2c804 --- /dev/null +++ b/mojo/edk/system/channel_unittest.cc @@ -0,0 +1,177 @@ +// 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/edk/system/channel.h" +#include "base/memory/ptr_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +class TestChannel : public Channel { + public: + TestChannel(Channel::Delegate* delegate) : Channel(delegate) {} + + char* GetReadBufferTest(size_t* buffer_capacity) { + return GetReadBuffer(buffer_capacity); + } + + bool OnReadCompleteTest(size_t bytes_read, size_t* next_read_size_hint) { + return OnReadComplete(bytes_read, next_read_size_hint); + } + + MOCK_METHOD4(GetReadPlatformHandles, + bool(size_t num_handles, + const void* extra_header, + size_t extra_header_size, + ScopedPlatformHandleVectorPtr* handles)); + MOCK_METHOD0(Start, void()); + MOCK_METHOD0(ShutDownImpl, void()); + MOCK_METHOD0(LeakHandle, void()); + + void Write(MessagePtr message) {} + + protected: + ~TestChannel() override {} +}; + +// Not using GMock as I don't think it supports movable types. +class MockChannelDelegate : public Channel::Delegate { + public: + MockChannelDelegate() {} + + size_t GetReceivedPayloadSize() const { return payload_size_; } + + const void* GetReceivedPayload() const { return payload_.get(); } + + protected: + void OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) override { + payload_.reset(new char[payload_size]); + memcpy(payload_.get(), payload, payload_size); + payload_size_ = payload_size; + } + + // Notify that an error has occured and the Channel will cease operation. + void OnChannelError() override {} + + private: + size_t payload_size_ = 0; + std::unique_ptr payload_; +}; + +Channel::MessagePtr CreateDefaultMessage(bool legacy_message) { + const size_t payload_size = 100; + Channel::MessagePtr message = base::MakeUnique( + payload_size, 0, + legacy_message ? Channel::Message::MessageType::NORMAL_LEGACY + : Channel::Message::MessageType::NORMAL); + char* payload = static_cast(message->mutable_payload()); + for (size_t i = 0; i < payload_size; i++) { + payload[i] = static_cast(i); + } + return message; +} + +void TestMemoryEqual(const void* data1, + size_t data1_size, + const void* data2, + size_t data2_size) { + ASSERT_EQ(data1_size, data2_size); + const unsigned char* data1_char = static_cast(data1); + const unsigned char* data2_char = static_cast(data2); + for (size_t i = 0; i < data1_size; i++) { + // ASSERT so we don't log tons of errors if the data is different. + ASSERT_EQ(data1_char[i], data2_char[i]); + } +} + +void TestMessagesAreEqual(Channel::Message* message1, + Channel::Message* message2, + bool legacy_messages) { + // If any of the message is null, this is probably not what you wanted to + // test. + ASSERT_NE(nullptr, message1); + ASSERT_NE(nullptr, message2); + + ASSERT_EQ(message1->payload_size(), message2->payload_size()); + EXPECT_EQ(message1->has_handles(), message2->has_handles()); + + TestMemoryEqual(message1->payload(), message1->payload_size(), + message2->payload(), message2->payload_size()); + + if (legacy_messages) + return; + + ASSERT_EQ(message1->extra_header_size(), message2->extra_header_size()); + TestMemoryEqual(message1->extra_header(), message1->extra_header_size(), + message2->extra_header(), message2->extra_header_size()); +} + +TEST(ChannelTest, LegacyMessageDeserialization) { + Channel::MessagePtr message = CreateDefaultMessage(true /* legacy_message */); + Channel::MessagePtr deserialized_message = + Channel::Message::Deserialize(message->data(), message->data_num_bytes()); + TestMessagesAreEqual(message.get(), deserialized_message.get(), + true /* legacy_message */); +} + +TEST(ChannelTest, NonLegacyMessageDeserialization) { + Channel::MessagePtr message = + CreateDefaultMessage(false /* legacy_message */); + Channel::MessagePtr deserialized_message = + Channel::Message::Deserialize(message->data(), message->data_num_bytes()); + TestMessagesAreEqual(message.get(), deserialized_message.get(), + false /* legacy_message */); +} + +TEST(ChannelTest, OnReadLegacyMessage) { + size_t buffer_size = 100 * 1024; + Channel::MessagePtr message = CreateDefaultMessage(true /* legacy_message */); + + MockChannelDelegate channel_delegate; + scoped_refptr channel = new TestChannel(&channel_delegate); + char* read_buffer = channel->GetReadBufferTest(&buffer_size); + ASSERT_LT(message->data_num_bytes(), + buffer_size); // Bad test. Increase buffer + // size. + memcpy(read_buffer, message->data(), message->data_num_bytes()); + + size_t next_read_size_hint = 0; + EXPECT_TRUE(channel->OnReadCompleteTest(message->data_num_bytes(), + &next_read_size_hint)); + + TestMemoryEqual(message->payload(), message->payload_size(), + channel_delegate.GetReceivedPayload(), + channel_delegate.GetReceivedPayloadSize()); +} + +TEST(ChannelTest, OnReadNonLegacyMessage) { + size_t buffer_size = 100 * 1024; + Channel::MessagePtr message = + CreateDefaultMessage(false /* legacy_message */); + + MockChannelDelegate channel_delegate; + scoped_refptr channel = new TestChannel(&channel_delegate); + char* read_buffer = channel->GetReadBufferTest(&buffer_size); + ASSERT_LT(message->data_num_bytes(), + buffer_size); // Bad test. Increase buffer + // size. + memcpy(read_buffer, message->data(), message->data_num_bytes()); + + size_t next_read_size_hint = 0; + EXPECT_TRUE(channel->OnReadCompleteTest(message->data_num_bytes(), + &next_read_size_hint)); + + TestMemoryEqual(message->payload(), message->payload_size(), + channel_delegate.GetReceivedPayload(), + channel_delegate.GetReceivedPayloadSize()); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/channel_win.cc b/mojo/edk/system/channel_win.cc new file mode 100644 index 0000000..c15df16 --- /dev/null +++ b/mojo/edk/system/channel_win.cc @@ -0,0 +1,360 @@ +// 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/edk/system/channel.h" + +#include +#include + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "base/win/win_util.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +namespace { + +// A view over a Channel::Message object. The write queue uses these since +// large messages may need to be sent in chunks. +class MessageView { + public: + // Owns |message|. |offset| indexes the first unsent byte in the message. + MessageView(Channel::MessagePtr message, size_t offset) + : message_(std::move(message)), + offset_(offset) { + DCHECK_GT(message_->data_num_bytes(), offset_); + } + + MessageView(MessageView&& other) { *this = std::move(other); } + + MessageView& operator=(MessageView&& other) { + message_ = std::move(other.message_); + offset_ = other.offset_; + return *this; + } + + ~MessageView() {} + + const void* data() const { + return static_cast(message_->data()) + offset_; + } + + size_t data_num_bytes() const { return message_->data_num_bytes() - offset_; } + + size_t data_offset() const { return offset_; } + void advance_data_offset(size_t num_bytes) { + DCHECK_GE(message_->data_num_bytes(), offset_ + num_bytes); + offset_ += num_bytes; + } + + Channel::MessagePtr TakeChannelMessage() { return std::move(message_); } + + private: + Channel::MessagePtr message_; + size_t offset_; + + DISALLOW_COPY_AND_ASSIGN(MessageView); +}; + +class ChannelWin : public Channel, + public base::MessageLoop::DestructionObserver, + public base::MessageLoopForIO::IOHandler { + public: + ChannelWin(Delegate* delegate, + ScopedPlatformHandle handle, + scoped_refptr io_task_runner) + : Channel(delegate), + self_(this), + handle_(std::move(handle)), + io_task_runner_(io_task_runner) { + CHECK(handle_.is_valid()); + + wait_for_connect_ = handle_.get().needs_connection; + } + + void Start() override { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelWin::StartOnIOThread, this)); + } + + void ShutDownImpl() override { + // Always shut down asynchronously when called through the public interface. + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&ChannelWin::ShutDownOnIOThread, this)); + } + + void Write(MessagePtr message) override { + bool write_error = false; + { + base::AutoLock lock(write_lock_); + if (reject_writes_) + return; + + bool write_now = !delay_writes_ && outgoing_messages_.empty(); + outgoing_messages_.emplace_back(std::move(message), 0); + + if (write_now && !WriteNoLock(outgoing_messages_.front())) + reject_writes_ = write_error = true; + } + if (write_error) { + // Do not synchronously invoke OnError(). Write() may have been called by + // the delegate and we don't want to re-enter it. + io_task_runner_->PostTask(FROM_HERE, + base::Bind(&ChannelWin::OnError, this)); + } + } + + void LeakHandle() override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + leak_handle_ = true; + } + + bool GetReadPlatformHandles( + size_t num_handles, + const void* extra_header, + size_t extra_header_size, + ScopedPlatformHandleVectorPtr* handles) override { + if (num_handles > std::numeric_limits::max()) + return false; + using HandleEntry = Channel::Message::HandleEntry; + size_t handles_size = sizeof(HandleEntry) * num_handles; + if (handles_size > extra_header_size) + return false; + DCHECK(extra_header); + handles->reset(new PlatformHandleVector(num_handles)); + const HandleEntry* extra_header_handles = + reinterpret_cast(extra_header); + for (size_t i = 0; i < num_handles; i++) { + (*handles)->at(i).handle = + base::win::Uint32ToHandle(extra_header_handles[i].handle); + } + return true; + } + + private: + // May run on any thread. + ~ChannelWin() override {} + + void StartOnIOThread() { + base::MessageLoop::current()->AddDestructionObserver(this); + base::MessageLoopForIO::current()->RegisterIOHandler( + handle_.get().handle, this); + + if (wait_for_connect_) { + BOOL ok = ConnectNamedPipe(handle_.get().handle, + &connect_context_.overlapped); + if (ok) { + PLOG(ERROR) << "Unexpected success while waiting for pipe connection"; + OnError(); + return; + } + + const DWORD err = GetLastError(); + switch (err) { + case ERROR_PIPE_CONNECTED: + wait_for_connect_ = false; + break; + case ERROR_IO_PENDING: + AddRef(); + return; + case ERROR_NO_DATA: + OnError(); + return; + } + } + + // Now that we have registered our IOHandler, we can start writing. + { + base::AutoLock lock(write_lock_); + if (delay_writes_) { + delay_writes_ = false; + WriteNextNoLock(); + } + } + + // Keep this alive in case we synchronously run shutdown. + scoped_refptr keep_alive(this); + ReadMore(0); + } + + void ShutDownOnIOThread() { + base::MessageLoop::current()->RemoveDestructionObserver(this); + + // BUG(crbug.com/583525): This function is expected to be called once, and + // |handle_| should be valid at this point. + CHECK(handle_.is_valid()); + CancelIo(handle_.get().handle); + if (leak_handle_) + ignore_result(handle_.release()); + handle_.reset(); + + // May destroy the |this| if it was the last reference. + self_ = nullptr; + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + if (self_) + ShutDownOnIOThread(); + } + + // base::MessageLoop::IOHandler: + void OnIOCompleted(base::MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, + DWORD error) override { + if (error != ERROR_SUCCESS) { + OnError(); + } else if (context == &connect_context_) { + DCHECK(wait_for_connect_); + wait_for_connect_ = false; + ReadMore(0); + + base::AutoLock lock(write_lock_); + if (delay_writes_) { + delay_writes_ = false; + WriteNextNoLock(); + } + } else if (context == &read_context_) { + OnReadDone(static_cast(bytes_transfered)); + } else { + CHECK(context == &write_context_); + OnWriteDone(static_cast(bytes_transfered)); + } + Release(); // Balancing reference taken after ReadFile / WriteFile. + } + + void OnReadDone(size_t bytes_read) { + if (bytes_read > 0) { + size_t next_read_size = 0; + if (OnReadComplete(bytes_read, &next_read_size)) { + ReadMore(next_read_size); + } else { + OnError(); + } + } else if (bytes_read == 0) { + OnError(); + } + } + + void OnWriteDone(size_t bytes_written) { + if (bytes_written == 0) + return; + + bool write_error = false; + { + base::AutoLock lock(write_lock_); + + DCHECK(!outgoing_messages_.empty()); + + MessageView& message_view = outgoing_messages_.front(); + message_view.advance_data_offset(bytes_written); + if (message_view.data_num_bytes() == 0) { + Channel::MessagePtr message = message_view.TakeChannelMessage(); + outgoing_messages_.pop_front(); + + // Clear any handles so they don't get closed on destruction. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + if (handles) + handles->clear(); + } + + if (!WriteNextNoLock()) + reject_writes_ = write_error = true; + } + if (write_error) + OnError(); + } + + void ReadMore(size_t next_read_size_hint) { + size_t buffer_capacity = next_read_size_hint; + char* buffer = GetReadBuffer(&buffer_capacity); + DCHECK_GT(buffer_capacity, 0u); + + BOOL ok = ReadFile(handle_.get().handle, + buffer, + static_cast(buffer_capacity), + NULL, + &read_context_.overlapped); + + if (ok || GetLastError() == ERROR_IO_PENDING) { + AddRef(); // Will be balanced in OnIOCompleted + } else { + OnError(); + } + } + + // Attempts to write a message directly to the channel. If the full message + // cannot be written, it's queued and a wait is initiated to write the message + // ASAP on the I/O thread. + bool WriteNoLock(const MessageView& message_view) { + BOOL ok = WriteFile(handle_.get().handle, + message_view.data(), + static_cast(message_view.data_num_bytes()), + NULL, + &write_context_.overlapped); + + if (ok || GetLastError() == ERROR_IO_PENDING) { + AddRef(); // Will be balanced in OnIOCompleted. + return true; + } + return false; + } + + bool WriteNextNoLock() { + if (outgoing_messages_.empty()) + return true; + return WriteNoLock(outgoing_messages_.front()); + } + + // Keeps the Channel alive at least until explicit shutdown on the IO thread. + scoped_refptr self_; + + ScopedPlatformHandle handle_; + scoped_refptr io_task_runner_; + + base::MessageLoopForIO::IOContext connect_context_; + base::MessageLoopForIO::IOContext read_context_; + base::MessageLoopForIO::IOContext write_context_; + + // Protects |reject_writes_| and |outgoing_messages_|. + base::Lock write_lock_; + + bool delay_writes_ = true; + + bool reject_writes_ = false; + std::deque outgoing_messages_; + + bool wait_for_connect_; + + bool leak_handle_ = false; + + DISALLOW_COPY_AND_ASSIGN(ChannelWin); +}; + +} // namespace + +// static +scoped_refptr Channel::Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner) { + return new ChannelWin(delegate, connection_params.TakeChannelHandle(), + io_task_runner); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/configuration.cc b/mojo/edk/system/configuration.cc new file mode 100644 index 0000000..f5eb2b8 --- /dev/null +++ b/mojo/edk/system/configuration.cc @@ -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. + +#include "mojo/edk/system/configuration.h" + +namespace mojo { +namespace edk { +namespace internal { + +// These default values should be synced with the documentation in +// mojo/edk/embedder/configuration.h. +Configuration g_configuration = { + 1000000, // max_handle_table_size + 1000000, // max_mapping_table_sze + 4 * 1024 * 1024, // max_message_num_bytes + 10000, // max_message_num_handles + 256 * 1024 * 1024, // max_data_pipe_capacity_bytes + 1024 * 1024, // default_data_pipe_capacity_bytes + 16, // data_pipe_buffer_alignment_bytes + 1024 * 1024 * 1024}; // max_shared_memory_num_bytes + +} // namespace internal +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/configuration.h b/mojo/edk/system/configuration.h new file mode 100644 index 0000000..038835f --- /dev/null +++ b/mojo/edk/system/configuration.h @@ -0,0 +1,29 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_CONFIGURATION_H_ +#define MOJO_EDK_SYSTEM_CONFIGURATION_H_ + +#include "mojo/edk/embedder/configuration.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +namespace internal { +MOJO_SYSTEM_IMPL_EXPORT extern Configuration g_configuration; +} // namespace internal + +MOJO_SYSTEM_IMPL_EXPORT inline const Configuration& GetConfiguration() { + return internal::g_configuration; +} + +MOJO_SYSTEM_IMPL_EXPORT inline Configuration* GetMutableConfiguration() { + return &internal::g_configuration; +} + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_CONFIGURATION_H_ diff --git a/mojo/edk/system/core.cc b/mojo/edk/system/core.cc new file mode 100644 index 0000000..360e8c3 --- /dev/null +++ b/mojo/edk/system/core.cc @@ -0,0 +1,1019 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/core.h" + +#include + +#include + +#include "base/bind.h" +#include "base/containers/stack_container.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/rand_util.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/time/time.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/channel.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/data_pipe_consumer_dispatcher.h" +#include "mojo/edk/system/data_pipe_producer_dispatcher.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/message_for_transit.h" +#include "mojo/edk/system/message_pipe_dispatcher.h" +#include "mojo/edk/system/platform_handle_dispatcher.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/ports/node.h" +#include "mojo/edk/system/request_context.h" +#include "mojo/edk/system/shared_buffer_dispatcher.h" +#include "mojo/edk/system/watcher_dispatcher.h" + +namespace mojo { +namespace edk { + +namespace { + +// This is an unnecessarily large limit that is relatively easy to enforce. +const uint32_t kMaxHandlesPerMessage = 1024 * 1024; + +// TODO(rockot): Maybe we could negotiate a debugging pipe ID for cross-process +// pipes too; for now we just use a constant. This only affects bootstrap pipes. +const uint64_t kUnknownPipeIdForDebug = 0x7f7f7f7f7f7f7f7fUL; + +MojoResult MojoPlatformHandleToScopedPlatformHandle( + const MojoPlatformHandle* platform_handle, + ScopedPlatformHandle* out_handle) { + if (platform_handle->struct_size != sizeof(MojoPlatformHandle)) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (platform_handle->type == MOJO_PLATFORM_HANDLE_TYPE_INVALID) { + out_handle->reset(); + return MOJO_RESULT_OK; + } + + PlatformHandle handle; + switch (platform_handle->type) { +#if defined(OS_POSIX) + case MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR: + handle.handle = static_cast(platform_handle->value); + break; +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) + case MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT: + handle.type = PlatformHandle::Type::MACH; + handle.port = static_cast(platform_handle->value); + break; +#endif + +#if defined(OS_WIN) + case MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE: + handle.handle = reinterpret_cast(platform_handle->value); + break; +#endif + + default: + return MOJO_RESULT_INVALID_ARGUMENT; + } + + out_handle->reset(handle); + return MOJO_RESULT_OK; +} + +MojoResult ScopedPlatformHandleToMojoPlatformHandle( + ScopedPlatformHandle handle, + MojoPlatformHandle* platform_handle) { + if (platform_handle->struct_size != sizeof(MojoPlatformHandle)) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!handle.is_valid()) { + platform_handle->type = MOJO_PLATFORM_HANDLE_TYPE_INVALID; + return MOJO_RESULT_OK; + } + +#if defined(OS_POSIX) + switch (handle.get().type) { + case PlatformHandle::Type::POSIX: + platform_handle->type = MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR; + platform_handle->value = static_cast(handle.release().handle); + break; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + case PlatformHandle::Type::MACH: + platform_handle->type = MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT; + platform_handle->value = static_cast(handle.release().port); + break; +#endif // defined(OS_MACOSX) && !defined(OS_IOS) + + default: + return MOJO_RESULT_INVALID_ARGUMENT; + } +#elif defined(OS_WIN) + platform_handle->type = MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE; + platform_handle->value = reinterpret_cast(handle.release().handle); +#endif // defined(OS_WIN) + + return MOJO_RESULT_OK; +} + +} // namespace + +Core::Core() {} + +Core::~Core() { + if (node_controller_ && node_controller_->io_task_runner()) { + // If this races with IO thread shutdown the callback will be dropped and + // the NodeController will be shutdown on this thread anyway, which is also + // just fine. + scoped_refptr io_task_runner = + node_controller_->io_task_runner(); + io_task_runner->PostTask(FROM_HERE, + base::Bind(&Core::PassNodeControllerToIOThread, + base::Passed(&node_controller_))); + } +} + +void Core::SetIOTaskRunner(scoped_refptr io_task_runner) { + GetNodeController()->SetIOTaskRunner(io_task_runner); +} + +NodeController* Core::GetNodeController() { + base::AutoLock lock(node_controller_lock_); + if (!node_controller_) + node_controller_.reset(new NodeController(this)); + return node_controller_.get(); +} + +scoped_refptr Core::GetDispatcher(MojoHandle handle) { + base::AutoLock lock(handles_lock_); + return handles_.GetDispatcher(handle); +} + +void Core::SetDefaultProcessErrorCallback( + const ProcessErrorCallback& callback) { + default_process_error_callback_ = callback; +} + +void Core::AddChild(base::ProcessHandle process_handle, + ConnectionParams connection_params, + const std::string& child_token, + const ProcessErrorCallback& process_error_callback) { + GetNodeController()->ConnectToChild(process_handle, + std::move(connection_params), child_token, + process_error_callback); +} + +void Core::ChildLaunchFailed(const std::string& child_token) { + RequestContext request_context; + GetNodeController()->CloseChildPorts(child_token); +} + +ScopedMessagePipeHandle Core::ConnectToPeerProcess( + ScopedPlatformHandle pipe_handle, + const std::string& peer_token) { + RequestContext request_context; + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + MojoHandle handle = AddDispatcher(new MessagePipeDispatcher( + GetNodeController(), port0, kUnknownPipeIdForDebug, 0)); + ConnectionParams connection_params(std::move(pipe_handle)); + GetNodeController()->ConnectToPeer(std::move(connection_params), port1, + peer_token); + return ScopedMessagePipeHandle(MessagePipeHandle(handle)); +} + +void Core::ClosePeerConnection(const std::string& peer_token) { + GetNodeController()->ClosePeerConnection(peer_token); +} + +void Core::InitChild(ConnectionParams connection_params) { + GetNodeController()->ConnectToParent(std::move(connection_params)); +} + +void Core::SetMachPortProvider(base::PortProvider* port_provider) { +#if defined(OS_MACOSX) && !defined(OS_IOS) + GetNodeController()->CreateMachPortRelay(port_provider); +#endif +} + +MojoHandle Core::AddDispatcher(scoped_refptr dispatcher) { + base::AutoLock lock(handles_lock_); + return handles_.AddDispatcher(dispatcher); +} + +bool Core::AddDispatchersFromTransit( + const std::vector& dispatchers, + MojoHandle* handles) { + bool failed = false; + { + base::AutoLock lock(handles_lock_); + if (!handles_.AddDispatchersFromTransit(dispatchers, handles)) + failed = true; + } + if (failed) { + for (auto d : dispatchers) + d.dispatcher->Close(); + return false; + } + return true; +} + +MojoResult Core::CreatePlatformHandleWrapper( + ScopedPlatformHandle platform_handle, + MojoHandle* wrapper_handle) { + MojoHandle h = AddDispatcher( + PlatformHandleDispatcher::Create(std::move(platform_handle))); + if (h == MOJO_HANDLE_INVALID) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + *wrapper_handle = h; + return MOJO_RESULT_OK; +} + +MojoResult Core::PassWrappedPlatformHandle( + MojoHandle wrapper_handle, + ScopedPlatformHandle* platform_handle) { + base::AutoLock lock(handles_lock_); + scoped_refptr d; + MojoResult result = handles_.GetAndRemoveDispatcher(wrapper_handle, &d); + if (result != MOJO_RESULT_OK) + return result; + if (d->GetType() == Dispatcher::Type::PLATFORM_HANDLE) { + PlatformHandleDispatcher* phd = + static_cast(d.get()); + *platform_handle = phd->PassPlatformHandle(); + } else { + result = MOJO_RESULT_INVALID_ARGUMENT; + } + d->Close(); + return result; +} + +MojoResult Core::CreateSharedBufferWrapper( + base::SharedMemoryHandle shared_memory_handle, + size_t num_bytes, + bool read_only, + MojoHandle* mojo_wrapper_handle) { + DCHECK(num_bytes); + scoped_refptr platform_buffer = + PlatformSharedBuffer::CreateFromSharedMemoryHandle(num_bytes, read_only, + shared_memory_handle); + if (!platform_buffer) + return MOJO_RESULT_UNKNOWN; + + scoped_refptr dispatcher; + MojoResult result = SharedBufferDispatcher::CreateFromPlatformSharedBuffer( + platform_buffer, &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + MojoHandle h = AddDispatcher(dispatcher); + if (h == MOJO_HANDLE_INVALID) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + *mojo_wrapper_handle = h; + return MOJO_RESULT_OK; +} + +MojoResult Core::PassSharedMemoryHandle( + MojoHandle mojo_handle, + base::SharedMemoryHandle* shared_memory_handle, + size_t* num_bytes, + bool* read_only) { + if (!shared_memory_handle) + return MOJO_RESULT_INVALID_ARGUMENT; + + scoped_refptr dispatcher; + MojoResult result = MOJO_RESULT_OK; + { + base::AutoLock lock(handles_lock_); + // Get the dispatcher and check it before removing it from the handle table + // to ensure that the dispatcher is of the correct type. This ensures we + // don't close and remove the wrong type of dispatcher. + dispatcher = handles_.GetDispatcher(mojo_handle); + if (!dispatcher || dispatcher->GetType() != Dispatcher::Type::SHARED_BUFFER) + return MOJO_RESULT_INVALID_ARGUMENT; + + result = handles_.GetAndRemoveDispatcher(mojo_handle, &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + } + + SharedBufferDispatcher* shm_dispatcher = + static_cast(dispatcher.get()); + scoped_refptr platform_shared_buffer = + shm_dispatcher->PassPlatformSharedBuffer(); + + if (!platform_shared_buffer) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (num_bytes) + *num_bytes = platform_shared_buffer->GetNumBytes(); + if (read_only) + *read_only = platform_shared_buffer->IsReadOnly(); + *shared_memory_handle = platform_shared_buffer->DuplicateSharedMemoryHandle(); + + shm_dispatcher->Close(); + return result; +} + +void Core::RequestShutdown(const base::Closure& callback) { + GetNodeController()->RequestShutdown(callback); +} + +ScopedMessagePipeHandle Core::CreateParentMessagePipe( + const std::string& token, const std::string& child_token) { + RequestContext request_context; + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + MojoHandle handle = AddDispatcher( + new MessagePipeDispatcher(GetNodeController(), port0, + kUnknownPipeIdForDebug, 0)); + GetNodeController()->ReservePort(token, port1, child_token); + return ScopedMessagePipeHandle(MessagePipeHandle(handle)); +} + +ScopedMessagePipeHandle Core::CreateChildMessagePipe(const std::string& token) { + RequestContext request_context; + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + MojoHandle handle = AddDispatcher( + new MessagePipeDispatcher(GetNodeController(), port0, + kUnknownPipeIdForDebug, 1)); + GetNodeController()->MergePortIntoParent(token, port1); + return ScopedMessagePipeHandle(MessagePipeHandle(handle)); +} + +MojoResult Core::SetProperty(MojoPropertyType type, const void* value) { + base::AutoLock locker(property_lock_); + switch (type) { + case MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED: + property_sync_call_allowed_ = *static_cast(value); + return MOJO_RESULT_OK; + default: + return MOJO_RESULT_INVALID_ARGUMENT; + } +} + +MojoTimeTicks Core::GetTimeTicksNow() { + return base::TimeTicks::Now().ToInternalValue(); +} + +MojoResult Core::Close(MojoHandle handle) { + RequestContext request_context; + scoped_refptr dispatcher; + { + base::AutoLock lock(handles_lock_); + MojoResult rv = handles_.GetAndRemoveDispatcher(handle, &dispatcher); + if (rv != MOJO_RESULT_OK) + return rv; + } + dispatcher->Close(); + return MOJO_RESULT_OK; +} + +MojoResult Core::QueryHandleSignalsState( + MojoHandle handle, + MojoHandleSignalsState* signals_state) { + RequestContext request_context; + scoped_refptr dispatcher = GetDispatcher(handle); + if (!dispatcher || !signals_state) + return MOJO_RESULT_INVALID_ARGUMENT; + *signals_state = dispatcher->GetHandleSignalsState(); + return MOJO_RESULT_OK; +} + +MojoResult Core::CreateWatcher(MojoWatcherCallback callback, + MojoHandle* watcher_handle) { + RequestContext request_context; + if (!watcher_handle) + return MOJO_RESULT_INVALID_ARGUMENT; + *watcher_handle = AddDispatcher(new WatcherDispatcher(callback)); + if (*watcher_handle == MOJO_HANDLE_INVALID) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + return MOJO_RESULT_OK; +} + +MojoResult Core::Watch(MojoHandle watcher_handle, + MojoHandle handle, + MojoHandleSignals signals, + uintptr_t context) { + RequestContext request_context; + scoped_refptr watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) + return MOJO_RESULT_INVALID_ARGUMENT; + scoped_refptr dispatcher = GetDispatcher(handle); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + return watcher->WatchDispatcher(dispatcher, signals, context); +} + +MojoResult Core::CancelWatch(MojoHandle watcher_handle, uintptr_t context) { + RequestContext request_context; + scoped_refptr watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) + return MOJO_RESULT_INVALID_ARGUMENT; + return watcher->CancelWatch(context); +} + +MojoResult Core::ArmWatcher(MojoHandle watcher_handle, + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + RequestContext request_context; + scoped_refptr watcher = GetDispatcher(watcher_handle); + if (!watcher || watcher->GetType() != Dispatcher::Type::WATCHER) + return MOJO_RESULT_INVALID_ARGUMENT; + return watcher->Arm(num_ready_contexts, ready_contexts, ready_results, + ready_signals_states); +} + +MojoResult Core::AllocMessage(uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoAllocMessageFlags flags, + MojoMessageHandle* message) { + if (!message) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (num_handles == 0) { // Fast path: no handles. + std::unique_ptr msg; + MojoResult rv = MessageForTransit::Create(&msg, num_bytes, nullptr, 0); + if (rv != MOJO_RESULT_OK) + return rv; + + *message = reinterpret_cast(msg.release()); + return MOJO_RESULT_OK; + } + + if (!handles) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (num_handles > kMaxHandlesPerMessage) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + std::vector dispatchers; + { + base::AutoLock lock(handles_lock_); + MojoResult rv = handles_.BeginTransit(handles, num_handles, &dispatchers); + if (rv != MOJO_RESULT_OK) { + handles_.CancelTransit(dispatchers); + return rv; + } + } + DCHECK_EQ(num_handles, dispatchers.size()); + + std::unique_ptr msg; + MojoResult rv = MessageForTransit::Create( + &msg, num_bytes, dispatchers.data(), num_handles); + + { + base::AutoLock lock(handles_lock_); + if (rv == MOJO_RESULT_OK) { + handles_.CompleteTransitAndClose(dispatchers); + *message = reinterpret_cast(msg.release()); + } else { + handles_.CancelTransit(dispatchers); + } + } + + return rv; +} + +MojoResult Core::FreeMessage(MojoMessageHandle message) { + if (!message) + return MOJO_RESULT_INVALID_ARGUMENT; + + delete reinterpret_cast(message); + + return MOJO_RESULT_OK; +} + +MojoResult Core::GetMessageBuffer(MojoMessageHandle message, void** buffer) { + if (!message) + return MOJO_RESULT_INVALID_ARGUMENT; + + *buffer = reinterpret_cast(message)->mutable_bytes(); + + return MOJO_RESULT_OK; +} + +MojoResult Core::GetProperty(MojoPropertyType type, void* value) { + base::AutoLock locker(property_lock_); + switch (type) { + case MOJO_PROPERTY_TYPE_SYNC_CALL_ALLOWED: + *static_cast(value) = property_sync_call_allowed_; + return MOJO_RESULT_OK; + default: + return MOJO_RESULT_INVALID_ARGUMENT; + } +} + +MojoResult Core::CreateMessagePipe( + const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1) { + RequestContext request_context; + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + + CHECK(message_pipe_handle0); + CHECK(message_pipe_handle1); + + uint64_t pipe_id = base::RandUint64(); + + *message_pipe_handle0 = AddDispatcher( + new MessagePipeDispatcher(GetNodeController(), port0, pipe_id, 0)); + if (*message_pipe_handle0 == MOJO_HANDLE_INVALID) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + *message_pipe_handle1 = AddDispatcher( + new MessagePipeDispatcher(GetNodeController(), port1, pipe_id, 1)); + if (*message_pipe_handle1 == MOJO_HANDLE_INVALID) { + scoped_refptr unused; + unused->Close(); + + base::AutoLock lock(handles_lock_); + handles_.GetAndRemoveDispatcher(*message_pipe_handle0, &unused); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +MojoResult Core::WriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags) { + if (num_bytes && !bytes) + return MOJO_RESULT_INVALID_ARGUMENT; + + MojoMessageHandle message; + MojoResult rv = AllocMessage(num_bytes, handles, num_handles, + MOJO_ALLOC_MESSAGE_FLAG_NONE, &message); + if (rv != MOJO_RESULT_OK) + return rv; + + if (num_bytes) { + void* buffer = nullptr; + rv = GetMessageBuffer(message, &buffer); + DCHECK_EQ(rv, MOJO_RESULT_OK); + memcpy(buffer, bytes, num_bytes); + } + + return WriteMessageNew(message_pipe_handle, message, flags); +} + +MojoResult Core::WriteMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags) { + RequestContext request_context; + std::unique_ptr message_for_transit( + reinterpret_cast(message)); + auto dispatcher = GetDispatcher(message_pipe_handle); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->WriteMessage(std::move(message_for_transit), flags); +} + +MojoResult Core::ReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + CHECK((!num_handles || !*num_handles || handles) && + (!num_bytes || !*num_bytes || bytes)); + RequestContext request_context; + auto dispatcher = GetDispatcher(message_pipe_handle); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + std::unique_ptr message; + MojoResult rv = + dispatcher->ReadMessage(&message, num_bytes, handles, num_handles, flags, + false /* ignore_num_bytes */); + if (rv != MOJO_RESULT_OK) + return rv; + + if (message && message->num_bytes()) + memcpy(bytes, message->bytes(), message->num_bytes()); + + return MOJO_RESULT_OK; +} + +MojoResult Core::ReadMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags) { + CHECK(message); + CHECK(!num_handles || !*num_handles || handles); + RequestContext request_context; + auto dispatcher = GetDispatcher(message_pipe_handle); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + std::unique_ptr msg; + MojoResult rv = + dispatcher->ReadMessage(&msg, num_bytes, handles, num_handles, flags, + true /* ignore_num_bytes */); + if (rv != MOJO_RESULT_OK) + return rv; + *message = reinterpret_cast(msg.release()); + return MOJO_RESULT_OK; +} + +MojoResult Core::FuseMessagePipes(MojoHandle handle0, MojoHandle handle1) { + RequestContext request_context; + scoped_refptr dispatcher0; + scoped_refptr dispatcher1; + + bool valid_handles = true; + { + base::AutoLock lock(handles_lock_); + MojoResult result0 = handles_.GetAndRemoveDispatcher(handle0, &dispatcher0); + MojoResult result1 = handles_.GetAndRemoveDispatcher(handle1, &dispatcher1); + if (result0 != MOJO_RESULT_OK || result1 != MOJO_RESULT_OK || + dispatcher0->GetType() != Dispatcher::Type::MESSAGE_PIPE || + dispatcher1->GetType() != Dispatcher::Type::MESSAGE_PIPE) + valid_handles = false; + } + + if (!valid_handles) { + if (dispatcher0) + dispatcher0->Close(); + if (dispatcher1) + dispatcher1->Close(); + return MOJO_RESULT_INVALID_ARGUMENT; + } + + MessagePipeDispatcher* mpd0 = + static_cast(dispatcher0.get()); + MessagePipeDispatcher* mpd1 = + static_cast(dispatcher1.get()); + + if (!mpd0->Fuse(mpd1)) + return MOJO_RESULT_FAILED_PRECONDITION; + + return MOJO_RESULT_OK; +} + +MojoResult Core::NotifyBadMessage(MojoMessageHandle message, + const char* error, + size_t error_num_bytes) { + if (!message) + return MOJO_RESULT_INVALID_ARGUMENT; + + const PortsMessage& ports_message = + reinterpret_cast(message)->ports_message(); + if (ports_message.source_node() == ports::kInvalidNodeName) { + DVLOG(1) << "Received invalid message from unknown node."; + if (!default_process_error_callback_.is_null()) + default_process_error_callback_.Run(std::string(error, error_num_bytes)); + return MOJO_RESULT_OK; + } + + GetNodeController()->NotifyBadMessageFrom( + ports_message.source_node(), std::string(error, error_num_bytes)); + return MOJO_RESULT_OK; +} + +MojoResult Core::CreateDataPipe( + const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle) { + RequestContext request_context; + if (options && options->struct_size != sizeof(MojoCreateDataPipeOptions)) + return MOJO_RESULT_INVALID_ARGUMENT; + + MojoCreateDataPipeOptions create_options; + create_options.struct_size = sizeof(MojoCreateDataPipeOptions); + create_options.flags = options ? options->flags : 0; + create_options.element_num_bytes = options ? options->element_num_bytes : 1; + // TODO(rockot): Use Configuration to get default data pipe capacity. + create_options.capacity_num_bytes = + options && options->capacity_num_bytes ? options->capacity_num_bytes + : 64 * 1024; + + // TODO(rockot): Broker through the parent when necessary. + scoped_refptr ring_buffer = + GetNodeController()->CreateSharedBuffer( + create_options.capacity_num_bytes); + if (!ring_buffer) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + ports::PortRef port0, port1; + GetNodeController()->node()->CreatePortPair(&port0, &port1); + + CHECK(data_pipe_producer_handle); + CHECK(data_pipe_consumer_handle); + + uint64_t pipe_id = base::RandUint64(); + + scoped_refptr producer = new DataPipeProducerDispatcher( + GetNodeController(), port0, ring_buffer, create_options, + true /* initialized */, pipe_id); + scoped_refptr consumer = new DataPipeConsumerDispatcher( + GetNodeController(), port1, ring_buffer, create_options, + true /* initialized */, pipe_id); + + *data_pipe_producer_handle = AddDispatcher(producer); + *data_pipe_consumer_handle = AddDispatcher(consumer); + if (*data_pipe_producer_handle == MOJO_HANDLE_INVALID || + *data_pipe_consumer_handle == MOJO_HANDLE_INVALID) { + if (*data_pipe_producer_handle != MOJO_HANDLE_INVALID) { + scoped_refptr unused; + base::AutoLock lock(handles_lock_); + handles_.GetAndRemoveDispatcher(*data_pipe_producer_handle, &unused); + } + producer->Close(); + consumer->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +MojoResult Core::WriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->WriteData(elements, num_bytes, flags); +} + +MojoResult Core::BeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->BeginWriteData(buffer, buffer_num_bytes, flags); +} + +MojoResult Core::EndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_producer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->EndWriteData(num_bytes_written); +} + +MojoResult Core::ReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->ReadData(elements, num_bytes, flags); +} + +MojoResult Core::BeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->BeginReadData(buffer, buffer_num_bytes, flags); +} + +MojoResult Core::EndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read) { + RequestContext request_context; + scoped_refptr dispatcher( + GetDispatcher(data_pipe_consumer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + return dispatcher->EndReadData(num_bytes_read); +} + +MojoResult Core::CreateSharedBuffer( + const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle) { + RequestContext request_context; + MojoCreateSharedBufferOptions validated_options = {}; + MojoResult result = SharedBufferDispatcher::ValidateCreateOptions( + options, &validated_options); + if (result != MOJO_RESULT_OK) + return result; + + scoped_refptr dispatcher; + result = SharedBufferDispatcher::Create( + validated_options, GetNodeController(), num_bytes, &dispatcher); + if (result != MOJO_RESULT_OK) { + DCHECK(!dispatcher); + return result; + } + + *shared_buffer_handle = AddDispatcher(dispatcher); + if (*shared_buffer_handle == MOJO_HANDLE_INVALID) { + LOG(ERROR) << "Handle table full"; + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +MojoResult Core::DuplicateBufferHandle( + MojoHandle buffer_handle, + const MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle) { + RequestContext request_context; + scoped_refptr dispatcher(GetDispatcher(buffer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + // Don't verify |options| here; that's the dispatcher's job. + scoped_refptr new_dispatcher; + MojoResult result = + dispatcher->DuplicateBufferHandle(options, &new_dispatcher); + if (result != MOJO_RESULT_OK) + return result; + + *new_buffer_handle = AddDispatcher(new_dispatcher); + if (*new_buffer_handle == MOJO_HANDLE_INVALID) { + LOG(ERROR) << "Handle table full"; + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +MojoResult Core::MapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags) { + RequestContext request_context; + scoped_refptr dispatcher(GetDispatcher(buffer_handle)); + if (!dispatcher) + return MOJO_RESULT_INVALID_ARGUMENT; + + std::unique_ptr mapping; + MojoResult result = dispatcher->MapBuffer(offset, num_bytes, flags, &mapping); + if (result != MOJO_RESULT_OK) + return result; + + DCHECK(mapping); + void* address = mapping->GetBase(); + { + base::AutoLock locker(mapping_table_lock_); + result = mapping_table_.AddMapping(std::move(mapping)); + } + if (result != MOJO_RESULT_OK) + return result; + + *buffer = address; + return MOJO_RESULT_OK; +} + +MojoResult Core::UnmapBuffer(void* buffer) { + RequestContext request_context; + base::AutoLock lock(mapping_table_lock_); + return mapping_table_.RemoveMapping(buffer); +} + +MojoResult Core::WrapPlatformHandle(const MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle) { + ScopedPlatformHandle handle; + MojoResult result = MojoPlatformHandleToScopedPlatformHandle(platform_handle, + &handle); + if (result != MOJO_RESULT_OK) + return result; + + return CreatePlatformHandleWrapper(std::move(handle), mojo_handle); +} + +MojoResult Core::UnwrapPlatformHandle(MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle) { + ScopedPlatformHandle handle; + MojoResult result = PassWrappedPlatformHandle(mojo_handle, &handle); + if (result != MOJO_RESULT_OK) + return result; + + return ScopedPlatformHandleToMojoPlatformHandle(std::move(handle), + platform_handle); +} + +MojoResult Core::WrapPlatformSharedBufferHandle( + const MojoPlatformHandle* platform_handle, + size_t size, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle) { + DCHECK(size); + ScopedPlatformHandle handle; + MojoResult result = MojoPlatformHandleToScopedPlatformHandle(platform_handle, + &handle); + if (result != MOJO_RESULT_OK) + return result; + + bool read_only = flags & MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY; + scoped_refptr platform_buffer = + PlatformSharedBuffer::CreateFromPlatformHandle(size, read_only, + std::move(handle)); + if (!platform_buffer) + return MOJO_RESULT_UNKNOWN; + + scoped_refptr dispatcher; + result = SharedBufferDispatcher::CreateFromPlatformSharedBuffer( + platform_buffer, &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + + MojoHandle h = AddDispatcher(dispatcher); + if (h == MOJO_HANDLE_INVALID) { + dispatcher->Close(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + *mojo_handle = h; + return MOJO_RESULT_OK; +} + +MojoResult Core::UnwrapPlatformSharedBufferHandle( + MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle, + size_t* size, + MojoPlatformSharedBufferHandleFlags* flags) { + scoped_refptr dispatcher; + MojoResult result = MOJO_RESULT_OK; + { + base::AutoLock lock(handles_lock_); + result = handles_.GetAndRemoveDispatcher(mojo_handle, &dispatcher); + if (result != MOJO_RESULT_OK) + return result; + } + + if (dispatcher->GetType() != Dispatcher::Type::SHARED_BUFFER) { + dispatcher->Close(); + return MOJO_RESULT_INVALID_ARGUMENT; + } + + SharedBufferDispatcher* shm_dispatcher = + static_cast(dispatcher.get()); + scoped_refptr platform_shared_buffer = + shm_dispatcher->PassPlatformSharedBuffer(); + CHECK(platform_shared_buffer); + + CHECK(size); + *size = platform_shared_buffer->GetNumBytes(); + + CHECK(flags); + *flags = MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE; + if (platform_shared_buffer->IsReadOnly()) + *flags |= MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_READ_ONLY; + + ScopedPlatformHandle handle = platform_shared_buffer->PassPlatformHandle(); + return ScopedPlatformHandleToMojoPlatformHandle(std::move(handle), + platform_handle); +} + +void Core::GetActiveHandlesForTest(std::vector* handles) { + base::AutoLock lock(handles_lock_); + handles_.GetActiveHandlesForTest(handles); +} + +// static +void Core::PassNodeControllerToIOThread( + std::unique_ptr node_controller) { + // It's OK to leak this reference. At this point we know the IO loop is still + // running, and we know the NodeController will observe its eventual + // destruction. This tells the NodeController to delete itself when that + // happens. + node_controller.release()->DestroyOnIOThreadShutdown(); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/core.h b/mojo/edk/system/core.h new file mode 100644 index 0000000..1f6d865 --- /dev/null +++ b/mojo/edk/system/core.h @@ -0,0 +1,297 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_CORE_H_ +#define MOJO_EDK_SYSTEM_CORE_H_ + +#include +#include +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/shared_memory_handle.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/handle_table.h" +#include "mojo/edk/system/mapping_table.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/platform_handle.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" +#include "mojo/public/cpp/system/message_pipe.h" + +namespace base { +class PortProvider; +} + +namespace mojo { +namespace edk { + +// |Core| is an object that implements the Mojo system calls. All public methods +// are thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT Core { + public: + Core(); + virtual ~Core(); + + // Called exactly once, shortly after construction, and before any other + // methods are called on this object. + void SetIOTaskRunner(scoped_refptr io_task_runner); + + // Retrieves the NodeController for the current process. + NodeController* GetNodeController(); + + scoped_refptr GetDispatcher(MojoHandle handle); + + void SetDefaultProcessErrorCallback(const ProcessErrorCallback& callback); + + // Called in the parent process any time a new child is launched. + void AddChild(base::ProcessHandle process_handle, + ConnectionParams connection_params, + const std::string& child_token, + const ProcessErrorCallback& process_error_callback); + + // Called in the parent process when a child process fails to launch. + void ChildLaunchFailed(const std::string& child_token); + + // Called to connect to a peer process. This should be called only if there + // is no common ancestor for the processes involved within this mojo system. + // Both processes must call this function, each passing one end of a platform + // channel. This returns one end of a message pipe to each process. + ScopedMessagePipeHandle ConnectToPeerProcess(ScopedPlatformHandle pipe_handle, + const std::string& peer_token); + void ClosePeerConnection(const std::string& peer_token); + + // Called in a child process exactly once during early initialization. + void InitChild(ConnectionParams connection_params); + + // Creates a message pipe endpoint associated with |token|, which a child + // holding the token can later locate and connect to. + ScopedMessagePipeHandle CreateParentMessagePipe( + const std::string& token, const std::string& child_token); + + // Creates a message pipe endpoint and connects it to a pipe the parent has + // associated with |token|. + ScopedMessagePipeHandle CreateChildMessagePipe(const std::string& token); + + // Sets the mach port provider for this process. + void SetMachPortProvider(base::PortProvider* port_provider); + + MojoHandle AddDispatcher(scoped_refptr dispatcher); + + // Adds new dispatchers for non-message-pipe handles received in a message. + // |dispatchers| and |handles| should be the same size. + bool AddDispatchersFromTransit( + const std::vector& dispatchers, + MojoHandle* handles); + + // See "mojo/edk/embedder/embedder.h" for more information on these functions. + MojoResult CreatePlatformHandleWrapper(ScopedPlatformHandle platform_handle, + MojoHandle* wrapper_handle); + + MojoResult PassWrappedPlatformHandle(MojoHandle wrapper_handle, + ScopedPlatformHandle* platform_handle); + + MojoResult CreateSharedBufferWrapper( + base::SharedMemoryHandle shared_memory_handle, + size_t num_bytes, + bool read_only, + MojoHandle* mojo_wrapper_handle); + + MojoResult PassSharedMemoryHandle( + MojoHandle mojo_handle, + base::SharedMemoryHandle* shared_memory_handle, + size_t* num_bytes, + bool* read_only); + + // Requests that the EDK tear itself down. |callback| will be called once + // the shutdown process is complete. Note that |callback| is always called + // asynchronously on the calling thread if said thread is running a message + // loop, and the calling thread must continue running a MessageLoop at least + // until the callback is called. If there is no running loop, the |callback| + // may be called from any thread. Beware! + void RequestShutdown(const base::Closure& callback); + + MojoResult SetProperty(MojoPropertyType type, const void* value); + + // --------------------------------------------------------------------------- + + // The following methods are essentially implementations of the Mojo Core + // functions of the Mojo API, with the C interface translated to C++ by + // "mojo/edk/embedder/entrypoints.cc". The best way to understand the contract + // of these methods is to look at the header files defining the corresponding + // API functions, referenced below. + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/functions.h": + MojoTimeTicks GetTimeTicksNow(); + MojoResult Close(MojoHandle handle); + MojoResult QueryHandleSignalsState(MojoHandle handle, + MojoHandleSignalsState* signals_state); + 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 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 GetProperty(MojoPropertyType type, void* value); + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/message_pipe.h": + MojoResult CreateMessagePipe( + const MojoCreateMessagePipeOptions* options, + MojoHandle* message_pipe_handle0, + MojoHandle* message_pipe_handle1); + MojoResult WriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes, + const MojoHandle* handles, + uint32_t num_handles, + MojoWriteMessageFlags flags); + MojoResult WriteMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle message, + MojoWriteMessageFlags flags); + MojoResult ReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult ReadMessageNew(MojoHandle message_pipe_handle, + MojoMessageHandle* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags); + MojoResult FuseMessagePipes(MojoHandle handle0, MojoHandle handle1); + MojoResult NotifyBadMessage(MojoMessageHandle message, + const char* error, + size_t error_num_bytes); + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/data_pipe.h": + MojoResult CreateDataPipe( + const MojoCreateDataPipeOptions* options, + MojoHandle* data_pipe_producer_handle, + MojoHandle* data_pipe_consumer_handle); + MojoResult WriteData(MojoHandle data_pipe_producer_handle, + const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags); + MojoResult BeginWriteData(MojoHandle data_pipe_producer_handle, + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags); + MojoResult EndWriteData(MojoHandle data_pipe_producer_handle, + uint32_t num_bytes_written); + MojoResult ReadData(MojoHandle data_pipe_consumer_handle, + void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags); + MojoResult BeginReadData(MojoHandle data_pipe_consumer_handle, + const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags); + MojoResult EndReadData(MojoHandle data_pipe_consumer_handle, + uint32_t num_bytes_read); + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/buffer.h": + MojoResult CreateSharedBuffer( + const MojoCreateSharedBufferOptions* options, + uint64_t num_bytes, + MojoHandle* shared_buffer_handle); + MojoResult DuplicateBufferHandle( + MojoHandle buffer_handle, + const MojoDuplicateBufferHandleOptions* options, + MojoHandle* new_buffer_handle); + MojoResult MapBuffer(MojoHandle buffer_handle, + uint64_t offset, + uint64_t num_bytes, + void** buffer, + MojoMapBufferFlags flags); + MojoResult UnmapBuffer(void* buffer); + + // These methods correspond to the API functions defined in + // "mojo/public/c/system/platform_handle.h". + MojoResult WrapPlatformHandle(const MojoPlatformHandle* platform_handle, + MojoHandle* mojo_handle); + MojoResult UnwrapPlatformHandle(MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle); + MojoResult WrapPlatformSharedBufferHandle( + const MojoPlatformHandle* platform_handle, + size_t size, + MojoPlatformSharedBufferHandleFlags flags, + MojoHandle* mojo_handle); + MojoResult UnwrapPlatformSharedBufferHandle( + MojoHandle mojo_handle, + MojoPlatformHandle* platform_handle, + size_t* size, + MojoPlatformSharedBufferHandleFlags* flags); + + void GetActiveHandlesForTest(std::vector* handles); + + private: + // Used to pass ownership of our NodeController over to the IO thread in the + // event that we're torn down before said thread. + static void PassNodeControllerToIOThread( + std::unique_ptr node_controller); + + // Guards node_controller_. + // + // TODO(rockot): Consider removing this. It's only needed because we + // initialize node_controller_ lazily and that may happen on any thread. + // Otherwise it's effectively const and shouldn't need to be guarded. + // + // We can get rid of lazy initialization if we defer Mojo initialization far + // enough that zygotes don't do it. The zygote can't create a NodeController. + base::Lock node_controller_lock_; + + // This is lazily initialized on first access. Always use GetNodeController() + // to access it. + std::unique_ptr node_controller_; + + // The default callback to invoke, if any, when a process error is reported + // but cannot be associated with a specific process. + ProcessErrorCallback default_process_error_callback_; + + base::Lock handles_lock_; + HandleTable handles_; + + base::Lock mapping_table_lock_; // Protects |mapping_table_|. + MappingTable mapping_table_; + + base::Lock property_lock_; + // Properties that can be read using the MojoGetProperty() API. + bool property_sync_call_allowed_ = true; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_CORE_H_ diff --git a/mojo/edk/system/core_test_base.cc b/mojo/edk/system/core_test_base.cc new file mode 100644 index 0000000..7751612 --- /dev/null +++ b/mojo/edk/system/core_test_base.cc @@ -0,0 +1,272 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/core_test_base.h" + +#include +#include + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/message_for_transit.h" + +namespace mojo { +namespace edk { +namespace test { + +namespace { + +// MockDispatcher -------------------------------------------------------------- + +class MockDispatcher : public Dispatcher { + public: + static scoped_refptr Create( + CoreTestBase::MockHandleInfo* info) { + return make_scoped_refptr(new MockDispatcher(info)); + } + + // Dispatcher: + Type GetType() const override { return Type::UNKNOWN; } + + MojoResult Close() override { + info_->IncrementCloseCallCount(); + return MOJO_RESULT_OK; + } + + MojoResult WriteMessage( + std::unique_ptr message, + MojoWriteMessageFlags /*flags*/) override { + info_->IncrementWriteMessageCallCount(); + + if (message->num_bytes() > GetConfiguration().max_message_num_bytes) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + if (message->num_handles()) + return MOJO_RESULT_UNIMPLEMENTED; + + return MOJO_RESULT_OK; + } + + MojoResult ReadMessage(std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handle, + uint32_t* num_handles, + MojoReadMessageFlags /*flags*/, + bool ignore_num_bytes) override { + info_->IncrementReadMessageCallCount(); + + if (num_handles) + *num_handles = 1; + + return MOJO_RESULT_OK; + } + + MojoResult WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) override { + info_->IncrementWriteDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) override { + info_->IncrementBeginWriteDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult EndWriteData(uint32_t num_bytes_written) override { + info_->IncrementEndWriteDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) override { + info_->IncrementReadDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) override { + info_->IncrementBeginReadDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + MojoResult EndReadData(uint32_t num_bytes_read) override { + info_->IncrementEndReadDataCallCount(); + return MOJO_RESULT_UNIMPLEMENTED; + } + + private: + explicit MockDispatcher(CoreTestBase::MockHandleInfo* info) : info_(info) { + CHECK(info_); + info_->IncrementCtorCallCount(); + } + + ~MockDispatcher() override { info_->IncrementDtorCallCount(); } + + CoreTestBase::MockHandleInfo* const info_; + + DISALLOW_COPY_AND_ASSIGN(MockDispatcher); +}; + +} // namespace + +// CoreTestBase ---------------------------------------------------------------- + +CoreTestBase::CoreTestBase() { +} + +CoreTestBase::~CoreTestBase() { +} + +MojoHandle CoreTestBase::CreateMockHandle(CoreTestBase::MockHandleInfo* info) { + scoped_refptr dispatcher = MockDispatcher::Create(info); + return core()->AddDispatcher(dispatcher); +} + +Core* CoreTestBase::core() { + return mojo::edk::internal::g_core; +} + +// CoreTestBase_MockHandleInfo ------------------------------------------------- + +CoreTestBase_MockHandleInfo::CoreTestBase_MockHandleInfo() + : ctor_call_count_(0), + dtor_call_count_(0), + close_call_count_(0), + write_message_call_count_(0), + read_message_call_count_(0), + write_data_call_count_(0), + begin_write_data_call_count_(0), + end_write_data_call_count_(0), + read_data_call_count_(0), + begin_read_data_call_count_(0), + end_read_data_call_count_(0) {} + +CoreTestBase_MockHandleInfo::~CoreTestBase_MockHandleInfo() { +} + +unsigned CoreTestBase_MockHandleInfo::GetCtorCallCount() const { + base::AutoLock locker(lock_); + return ctor_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetDtorCallCount() const { + base::AutoLock locker(lock_); + return dtor_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetCloseCallCount() const { + base::AutoLock locker(lock_); + return close_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetWriteMessageCallCount() const { + base::AutoLock locker(lock_); + return write_message_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetReadMessageCallCount() const { + base::AutoLock locker(lock_); + return read_message_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetWriteDataCallCount() const { + base::AutoLock locker(lock_); + return write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetBeginWriteDataCallCount() const { + base::AutoLock locker(lock_); + return begin_write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetEndWriteDataCallCount() const { + base::AutoLock locker(lock_); + return end_write_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetReadDataCallCount() const { + base::AutoLock locker(lock_); + return read_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetBeginReadDataCallCount() const { + base::AutoLock locker(lock_); + return begin_read_data_call_count_; +} + +unsigned CoreTestBase_MockHandleInfo::GetEndReadDataCallCount() const { + base::AutoLock locker(lock_); + return end_read_data_call_count_; +} + +void CoreTestBase_MockHandleInfo::IncrementCtorCallCount() { + base::AutoLock locker(lock_); + ctor_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementDtorCallCount() { + base::AutoLock locker(lock_); + dtor_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementCloseCallCount() { + base::AutoLock locker(lock_); + close_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementWriteMessageCallCount() { + base::AutoLock locker(lock_); + write_message_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementReadMessageCallCount() { + base::AutoLock locker(lock_); + read_message_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementWriteDataCallCount() { + base::AutoLock locker(lock_); + write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementBeginWriteDataCallCount() { + base::AutoLock locker(lock_); + begin_write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementEndWriteDataCallCount() { + base::AutoLock locker(lock_); + end_write_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementReadDataCallCount() { + base::AutoLock locker(lock_); + read_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementBeginReadDataCallCount() { + base::AutoLock locker(lock_); + begin_read_data_call_count_++; +} + +void CoreTestBase_MockHandleInfo::IncrementEndReadDataCallCount() { + base::AutoLock locker(lock_); + end_read_data_call_count_++; +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/core_test_base.h b/mojo/edk/system/core_test_base.h new file mode 100644 index 0000000..3d156e3 --- /dev/null +++ b/mojo/edk/system/core_test_base.h @@ -0,0 +1,94 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_ +#define MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_ + +#include + +#include "base/macros.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { + +class Core; + +namespace test { + +class CoreTestBase_MockHandleInfo; + +class CoreTestBase : public testing::Test { + public: + using MockHandleInfo = CoreTestBase_MockHandleInfo; + + CoreTestBase(); + ~CoreTestBase() override; + + protected: + // |info| must remain alive until the returned handle is closed. + MojoHandle CreateMockHandle(MockHandleInfo* info); + + Core* core(); + + private: + DISALLOW_COPY_AND_ASSIGN(CoreTestBase); +}; + +class CoreTestBase_MockHandleInfo { + public: + CoreTestBase_MockHandleInfo(); + ~CoreTestBase_MockHandleInfo(); + + unsigned GetCtorCallCount() const; + unsigned GetDtorCallCount() const; + unsigned GetCloseCallCount() const; + unsigned GetWriteMessageCallCount() const; + unsigned GetReadMessageCallCount() const; + unsigned GetWriteDataCallCount() const; + unsigned GetBeginWriteDataCallCount() const; + unsigned GetEndWriteDataCallCount() const; + unsigned GetReadDataCallCount() const; + unsigned GetBeginReadDataCallCount() const; + unsigned GetEndReadDataCallCount() const; + + // For use by |MockDispatcher|: + void IncrementCtorCallCount(); + void IncrementDtorCallCount(); + void IncrementCloseCallCount(); + void IncrementWriteMessageCallCount(); + void IncrementReadMessageCallCount(); + void IncrementWriteDataCallCount(); + void IncrementBeginWriteDataCallCount(); + void IncrementEndWriteDataCallCount(); + void IncrementReadDataCallCount(); + void IncrementBeginReadDataCallCount(); + void IncrementEndReadDataCallCount(); + + private: + mutable base::Lock lock_; // Protects the following members. + unsigned ctor_call_count_; + unsigned dtor_call_count_; + unsigned close_call_count_; + unsigned write_message_call_count_; + unsigned read_message_call_count_; + unsigned write_data_call_count_; + unsigned begin_write_data_call_count_; + unsigned end_write_data_call_count_; + unsigned read_data_call_count_; + unsigned begin_read_data_call_count_; + unsigned end_read_data_call_count_; + + DISALLOW_COPY_AND_ASSIGN(CoreTestBase_MockHandleInfo); +}; + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_CORE_TEST_BASE_H_ diff --git a/mojo/edk/system/core_unittest.cc b/mojo/edk/system/core_unittest.cc new file mode 100644 index 0000000..0d60b48 --- /dev/null +++ b/mojo/edk/system/core_unittest.cc @@ -0,0 +1,971 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/core.h" + +#include + +#include + +#include "base/bind.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core_test_base.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/public/cpp/system/wait.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#endif + +namespace mojo { +namespace edk { +namespace { + +const MojoHandleSignalsState kEmptyMojoHandleSignalsState = {0u, 0u}; +const MojoHandleSignalsState kFullMojoHandleSignalsState = {~0u, ~0u}; +const MojoHandleSignals kAllSignals = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; + +using CoreTest = test::CoreTestBase; + +TEST_F(CoreTest, GetTimeTicksNow) { + const MojoTimeTicks start = core()->GetTimeTicksNow(); + ASSERT_NE(static_cast(0), start) + << "GetTimeTicksNow should return nonzero value"; + test::Sleep(test::DeadlineFromMilliseconds(15)); + const MojoTimeTicks finish = core()->GetTimeTicksNow(); + // Allow for some fuzz in sleep. + ASSERT_GE((finish - start), static_cast(8000)) + << "Sleeping should result in increasing time ticks"; +} + +TEST_F(CoreTest, Basic) { + MockHandleInfo info; + + ASSERT_EQ(0u, info.GetCtorCallCount()); + MojoHandle h = CreateMockHandle(&info); + ASSERT_EQ(1u, info.GetCtorCallCount()); + ASSERT_NE(h, MOJO_HANDLE_INVALID); + + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h, nullptr, 0, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + ASSERT_EQ(0u, info.GetReadMessageCallCount()); + uint32_t num_bytes = 0; + ASSERT_EQ( + MOJO_RESULT_OK, + core()->ReadMessage(h, nullptr, &num_bytes, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetReadMessageCallCount()); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage(h, nullptr, nullptr, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(2u, info.GetReadMessageCallCount()); + + ASSERT_EQ(0u, info.GetWriteDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->WriteData(h, nullptr, nullptr, MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteDataCallCount()); + + ASSERT_EQ(0u, info.GetBeginWriteDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->BeginWriteData(h, nullptr, nullptr, + MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_EQ(1u, info.GetBeginWriteDataCallCount()); + + ASSERT_EQ(0u, info.GetEndWriteDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndWriteData(h, 0)); + ASSERT_EQ(1u, info.GetEndWriteDataCallCount()); + + ASSERT_EQ(0u, info.GetReadDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->ReadData(h, nullptr, nullptr, MOJO_READ_DATA_FLAG_NONE)); + ASSERT_EQ(1u, info.GetReadDataCallCount()); + + ASSERT_EQ(0u, info.GetBeginReadDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, + core()->BeginReadData(h, nullptr, nullptr, + MOJO_READ_DATA_FLAG_NONE)); + ASSERT_EQ(1u, info.GetBeginReadDataCallCount()); + + ASSERT_EQ(0u, info.GetEndReadDataCallCount()); + ASSERT_EQ(MOJO_RESULT_UNIMPLEMENTED, core()->EndReadData(h, 0)); + ASSERT_EQ(1u, info.GetEndReadDataCallCount()); + + ASSERT_EQ(0u, info.GetDtorCallCount()); + ASSERT_EQ(0u, info.GetCloseCallCount()); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + ASSERT_EQ(1u, info.GetCloseCallCount()); + ASSERT_EQ(1u, info.GetDtorCallCount()); +} + +TEST_F(CoreTest, InvalidArguments) { + // |Close()|: + { + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(MOJO_HANDLE_INVALID)); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(10)); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(1000000000)); + + // Test a double-close. + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + ASSERT_EQ(1u, info.GetCloseCallCount()); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(h)); + ASSERT_EQ(1u, info.GetCloseCallCount()); + } + + // |CreateMessagePipe()|: Nothing to check (apart from things that cause + // death). + + // |WriteMessage()|: + // Only check arguments checked by |Core|, namely |handle|, |handles|, and + // |num_handles|. + { + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(MOJO_HANDLE_INVALID, nullptr, 0, + nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + MojoHandle handles[2] = {MOJO_HANDLE_INVALID, MOJO_HANDLE_INVALID}; + + // Huge handle count (implausibly big on some systems -- more than can be + // stored in a 32-bit address space). + // Note: This may return either |MOJO_RESULT_INVALID_ARGUMENT| or + // |MOJO_RESULT_RESOURCE_EXHAUSTED|, depending on whether it's plausible or + // not. + ASSERT_NE( + MOJO_RESULT_OK, + core()->WriteMessage(h, nullptr, 0, handles, + std::numeric_limits::max(), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Null |bytes| with non-zero message size. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Null |handles| with non-zero handle count. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, nullptr, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Huge handle count (plausibly big). + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->WriteMessage( + h, nullptr, 0, handles, + std::numeric_limits::max() / sizeof(handles[0]), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Invalid handle in |handles|. + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Two invalid handles in |handles|. + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + // Can't send a handle over itself. Note that this will also cause |h| to be + // closed. + handles[0] = h; + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(0u, info.GetWriteMessageCallCount()); + + h = CreateMockHandle(&info); + + MockHandleInfo info2; + + // This is "okay", but |MockDispatcher| doesn't implement it. + handles[0] = CreateMockHandle(&info2); + ASSERT_EQ( + MOJO_RESULT_UNIMPLEMENTED, + core()->WriteMessage(h, nullptr, 0, handles, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + // One of the |handles| is still invalid. + handles[0] = CreateMockHandle(&info2); + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + // One of the |handles| is the same as |h|. Both handles are closed. + handles[0] = CreateMockHandle(&info2); + handles[1] = h; + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h, nullptr, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + h = CreateMockHandle(&info); + + // Can't send a handle twice in the same message. + handles[0] = CreateMockHandle(&info2); + handles[1] = handles[0]; + ASSERT_EQ( + MOJO_RESULT_BUSY, + core()->WriteMessage(h, nullptr, 0, handles, 2, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, info.GetWriteMessageCallCount()); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + } + + // |ReadMessage()|: + // Only check arguments checked by |Core|, namely |handle|, |handles|, and + // |num_handles|. + { + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadMessage(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE)); + + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + + // Okay. + uint32_t handle_count = 0; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h, nullptr, nullptr, nullptr, &handle_count, + MOJO_READ_MESSAGE_FLAG_NONE)); + // Checked by |Core|, shouldn't go through to the dispatcher. + ASSERT_EQ(1u, info.GetReadMessageCallCount()); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + } +} + +// These test invalid arguments that should cause death if we're being paranoid +// about checking arguments (which we would want to do if, e.g., we were in a +// true "kernel" situation, but we might not want to do otherwise for +// performance reasons). Probably blatant errors like passing in null pointers +// (for required pointer arguments) will still cause death, but perhaps not +// predictably. +TEST_F(CoreTest, InvalidArgumentsDeath) { +#if defined(OFFICIAL_BUILD) + const char kMemoryCheckFailedRegex[] = ""; +#else + const char kMemoryCheckFailedRegex[] = "Check failed"; +#endif + + // |CreateMessagePipe()|: + { + MojoHandle h; + ASSERT_DEATH_IF_SUPPORTED( + core()->CreateMessagePipe(nullptr, nullptr, nullptr), + kMemoryCheckFailedRegex); + ASSERT_DEATH_IF_SUPPORTED( + core()->CreateMessagePipe(nullptr, &h, nullptr), + kMemoryCheckFailedRegex); + ASSERT_DEATH_IF_SUPPORTED( + core()->CreateMessagePipe(nullptr, nullptr, &h), + kMemoryCheckFailedRegex); + } + + // |ReadMessage()|: + // Only check arguments checked by |Core|, namely |handle|, |handles|, and + // |num_handles|. + { + MockHandleInfo info; + MojoHandle h = CreateMockHandle(&info); + + uint32_t handle_count = 1; + ASSERT_DEATH_IF_SUPPORTED( + core()->ReadMessage(h, nullptr, nullptr, nullptr, &handle_count, + MOJO_READ_MESSAGE_FLAG_NONE), + kMemoryCheckFailedRegex); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h)); + } +} + +TEST_F(CoreTest, MessagePipe) { + MojoHandle h[2]; + MojoHandleSignalsState hss[2]; + + ASSERT_EQ(MOJO_RESULT_OK, core()->CreateMessagePipe(nullptr, &h[0], &h[1])); + // Should get two distinct, valid handles. + ASSERT_NE(h[0], MOJO_HANDLE_INVALID); + ASSERT_NE(h[1], MOJO_HANDLE_INVALID); + ASSERT_NE(h[0], h[1]); + + // Neither should be readable. + hss[0] = kEmptyMojoHandleSignalsState; + hss[1] = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[0], &hss[0])); + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[1], &hss[1])); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); + ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[1].satisfied_signals); + ASSERT_EQ(kAllSignals, hss[1].satisfiable_signals); + + // Try to read anyway. + char buffer[1] = {'a'}; + uint32_t buffer_size = 1; + ASSERT_EQ( + MOJO_RESULT_SHOULD_WAIT, + core()->ReadMessage(h[0], buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + // Check that it left its inputs alone. + ASSERT_EQ('a', buffer[0]); + ASSERT_EQ(1u, buffer_size); + + // Write to |h[1]|. + buffer[0] = 'b'; + ASSERT_EQ( + MOJO_RESULT_OK, + core()->WriteMessage(h[1], buffer, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for |h[0]| to become readable. + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h[0]), + MOJO_HANDLE_SIGNAL_READABLE, &hss[0])); + + // Read from |h[0]|. + // First, get only the size. + buffer_size = 0; + ASSERT_EQ( + MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->ReadMessage(h[0], nullptr, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, buffer_size); + // Then actually read it. + buffer[0] = 'c'; + buffer_size = 1; + ASSERT_EQ( + MOJO_RESULT_OK, + core()->ReadMessage(h[0], buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ('b', buffer[0]); + ASSERT_EQ(1u, buffer_size); + + // |h[0]| should no longer be readable. + hss[0] = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[0], &hss[0])); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss[0].satisfied_signals); + ASSERT_EQ(kAllSignals, hss[0].satisfiable_signals); + + // Write to |h[0]|. + buffer[0] = 'd'; + ASSERT_EQ( + MOJO_RESULT_OK, + core()->WriteMessage(h[0], buffer, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Close |h[0]|. + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h[0])); + + // Wait for |h[1]| to learn about the other end's closure. + EXPECT_EQ( + MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(h[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss[1])); + + // Check that |h[1]| is no longer writable (and will never be). + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfiable_signals); + + // Check that |h[1]| is still readable (for the moment). + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss[1].satisfiable_signals); + + // Discard a message from |h[1]|. + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + core()->ReadMessage(h[1], nullptr, nullptr, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_MAY_DISCARD)); + + // |h[1]| is no longer readable (and will never be). + hss[1] = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(h[1], &hss[1])); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[1].satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss[1].satisfiable_signals); + + // Try writing to |h[1]|. + buffer[0] = 'e'; + ASSERT_EQ( + MOJO_RESULT_FAILED_PRECONDITION, + core()->WriteMessage(h[1], buffer, 1, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h[1])); +} + +// Tests passing a message pipe handle. +TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing1) { + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast(sizeof(kHello)); + const char kWorld[] = "world!!!"; + const uint32_t kWorldSize = static_cast(sizeof(kWorld)); + char buffer[100]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t num_bytes; + MojoHandle handles[10]; + uint32_t num_handles; + MojoHandleSignalsState hss; + MojoHandle h_received; + + MojoHandle h_passing[2]; + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1])); + + // Make sure that |h_passing[]| work properly. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(0u, num_handles); + + // Make sure that you can't pass either of the message pipe's handles over + // itself. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, + &h_passing[0], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1])); + + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, + &h_passing[1], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1])); + + MojoHandle h_passed[2]; + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passed[0], &h_passed[1])); + + // Make sure that |h_passed[]| work properly. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passed[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passed[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(0u, num_handles); + + // Send |h_passed[1]| from |h_passing[0]| to |h_passing[1]|. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kWorld, kWorldSize, + &h_passed[1], 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kWorldSize, num_bytes); + ASSERT_STREQ(kWorld, buffer); + ASSERT_EQ(1u, num_handles); + h_received = handles[0]; + ASSERT_NE(h_received, MOJO_HANDLE_INVALID); + ASSERT_NE(h_received, h_passing[0]); + ASSERT_NE(h_received, h_passing[1]); + ASSERT_NE(h_received, h_passed[0]); + + // Note: We rely on the Mojo system not re-using handle values very often. + ASSERT_NE(h_received, h_passed[1]); + + // |h_passed[1]| should no longer be valid; check that trying to close it + // fails. See above note. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(h_passed[1])); + + // Write to |h_passed[0]|. Should receive on |h_received|. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passed[0], kHello, kHelloSize, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_received, buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(0u, num_handles); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passed[0])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_received)); +} + +TEST_F(CoreTest, DataPipe) { + MojoHandle ph, ch; // p is for producer and c is for consumer. + MojoHandleSignalsState hss; + + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateDataPipe(nullptr, &ph, &ch)); + // Should get two distinct, valid handles. + ASSERT_NE(ph, MOJO_HANDLE_INVALID); + ASSERT_NE(ch, MOJO_HANDLE_INVALID); + ASSERT_NE(ph, ch); + + // Producer should be never-readable, but already writable. + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ph, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Consumer should be never-writable, and not yet readable. + hss = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + EXPECT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Write. + signed char elements[2] = {'A', 'B'}; + uint32_t num_bytes = 2u; + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph, elements, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_EQ(2u, num_bytes); + + // Wait for the data to arrive to the consumer. + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + + // Consumer should now be readable. + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Peek one character. + elements[0] = -1; + elements[1] = -1; + num_bytes = 1u; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData( + ch, elements, &num_bytes, + MOJO_READ_DATA_FLAG_NONE | MOJO_READ_DATA_FLAG_PEEK)); + ASSERT_EQ('A', elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Read one character. + elements[0] = -1; + elements[1] = -1; + num_bytes = 1u; + ASSERT_EQ(MOJO_RESULT_OK, core()->ReadData(ch, elements, &num_bytes, + MOJO_READ_DATA_FLAG_NONE)); + ASSERT_EQ('A', elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Two-phase write. + void* write_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginWriteData(ph, &write_ptr, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + // We count on the default options providing a decent buffer size. + ASSERT_GE(num_bytes, 3u); + + // Trying to do a normal write during a two-phase write should fail. + elements[0] = 'X'; + num_bytes = 1u; + ASSERT_EQ(MOJO_RESULT_BUSY, + core()->WriteData(ph, elements, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + + // Actually write the data, and complete it now. + static_cast(write_ptr)[0] = 'C'; + static_cast(write_ptr)[1] = 'D'; + static_cast(write_ptr)[2] = 'E'; + ASSERT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 3u)); + + // Wait for the data to arrive to the consumer. + ASSERT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + + // Query how much data we have. + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_QUERY)); + ASSERT_GE(num_bytes, 1u); + + // Try to query with peek. Should fail. + num_bytes = 0; + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadData(ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_QUERY | MOJO_READ_DATA_FLAG_PEEK)); + ASSERT_EQ(0u, num_bytes); + + // Try to discard ten characters, in all-or-none mode. Should fail. + num_bytes = 10; + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, + core()->ReadData( + ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Try to discard two characters, in peek mode. Should fail. + num_bytes = 2; + ASSERT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + core()->ReadData(ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_PEEK)); + + // Discard a character. + num_bytes = 1; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData( + ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD | MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Ensure the 3 bytes were read. + ASSERT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + + // Try a two-phase read of the remaining three bytes with peek. Should fail. + const void* read_ptr = nullptr; + num_bytes = 3; + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + core()->BeginReadData(ch, &read_ptr, &num_bytes, + MOJO_READ_DATA_FLAG_PEEK)); + + // Read the remaining two characters, in two-phase mode (all-or-none). + num_bytes = 3; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginReadData(ch, &read_ptr, &num_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + // Note: Count on still being able to do the contiguous read here. + ASSERT_EQ(3u, num_bytes); + + // Discarding right now should fail. + num_bytes = 1; + ASSERT_EQ(MOJO_RESULT_BUSY, + core()->ReadData(ch, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_DISCARD)); + + // Actually check our data and end the two-phase read. + ASSERT_EQ('C', static_cast(read_ptr)[0]); + ASSERT_EQ('D', static_cast(read_ptr)[1]); + ASSERT_EQ('E', static_cast(read_ptr)[2]); + ASSERT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 3u)); + + // Consumer should now be no longer readable. + hss = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + EXPECT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // TODO(vtl): More. + + // Close the producer. + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ph)); + + // Wait for this to get to the consumer. + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + + // The consumer should now be never-readable. + hss = kFullMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, core()->QueryHandleSignalsState(ch, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ch)); +} + +// Tests passing data pipe producer and consumer handles. +TEST_F(CoreTest, MessagePipeBasicLocalHandlePassing2) { + const char kHello[] = "hello"; + const uint32_t kHelloSize = static_cast(sizeof(kHello)); + const char kWorld[] = "world!!!"; + const uint32_t kWorldSize = static_cast(sizeof(kWorld)); + char buffer[100]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t num_bytes; + MojoHandle handles[10]; + uint32_t num_handles; + MojoHandleSignalsState hss; + + MojoHandle h_passing[2]; + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateMessagePipe(nullptr, &h_passing[0], &h_passing[1])); + + MojoHandle ph, ch; + ASSERT_EQ(MOJO_RESULT_OK, + core()->CreateDataPipe(nullptr, &ph, &ch)); + + // Send |ch| from |h_passing[0]| to |h_passing[1]|. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(1u, num_handles); + MojoHandle ch_received = handles[0]; + ASSERT_NE(ch_received, MOJO_HANDLE_INVALID); + ASSERT_NE(ch_received, h_passing[0]); + ASSERT_NE(ch_received, h_passing[1]); + ASSERT_NE(ch_received, ph); + + // Note: We rely on the Mojo system not re-using handle values very often. + ASSERT_NE(ch_received, ch); + + // |ch| should no longer be valid; check that trying to close it fails. See + // above note. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(ch)); + + // Write to |ph|. Should receive on |ch_received|. + num_bytes = kWorldSize; + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph, kWorld, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(ch_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + num_bytes = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch_received, buffer, &num_bytes, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kWorldSize, num_bytes); + ASSERT_STREQ(kWorld, buffer); + + // Now pass |ph| in the same direction. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kWorld, kWorldSize, &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + hss = kEmptyMojoHandleSignalsState; + ASSERT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kWorldSize, num_bytes); + ASSERT_STREQ(kWorld, buffer); + ASSERT_EQ(1u, num_handles); + MojoHandle ph_received = handles[0]; + ASSERT_NE(ph_received, MOJO_HANDLE_INVALID); + ASSERT_NE(ph_received, h_passing[0]); + ASSERT_NE(ph_received, h_passing[1]); + ASSERT_NE(ph_received, ch_received); + + // Again, rely on the Mojo system not re-using handle values very often. + ASSERT_NE(ph_received, ph); + + // |ph| should no longer be valid; check that trying to close it fails. See + // above note. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, core()->Close(ph)); + + // Write to |ph_received|. Should receive on |ch_received|. + num_bytes = kHelloSize; + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteData(ph_received, kHello, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(ch_received), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + num_bytes = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadData(ch_received, buffer, &num_bytes, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + + ph = ph_received; + ph_received = MOJO_HANDLE_INVALID; + ch = ch_received; + ch_received = MOJO_HANDLE_INVALID; + + // Make sure that |ph| can't be sent if it's in a two-phase write. + void* write_ptr = nullptr; + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginWriteData(ph, &write_ptr, &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + ASSERT_GE(num_bytes, 1u); + ASSERT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // But |ch| can, even if |ph| is in a two-phase write. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ch = MOJO_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE)); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kHelloSize, num_bytes); + ASSERT_STREQ(kHello, buffer); + ASSERT_EQ(1u, num_handles); + ch = handles[0]; + ASSERT_NE(ch, MOJO_HANDLE_INVALID); + + // Complete the two-phase write. + static_cast(write_ptr)[0] = 'x'; + ASSERT_EQ(MOJO_RESULT_OK, core()->EndWriteData(ph, 1)); + + // Wait for |ch| to be readable. + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, + mojo::Wait(mojo::Handle(ch), MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Make sure that |ch| can't be sent if it's in a two-phase read. + const void* read_ptr = nullptr; + num_bytes = 1; + ASSERT_EQ(MOJO_RESULT_OK, + core()->BeginReadData(ch, &read_ptr, &num_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + ASSERT_EQ(MOJO_RESULT_BUSY, + core()->WriteMessage(h_passing[0], kHello, kHelloSize, &ch, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // But |ph| can, even if |ch| is in a two-phase read. + ASSERT_EQ(MOJO_RESULT_OK, + core()->WriteMessage(h_passing[0], kWorld, kWorldSize, &ph, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ph = MOJO_HANDLE_INVALID; + hss = kEmptyMojoHandleSignalsState; + EXPECT_EQ(MOJO_RESULT_OK, mojo::Wait(mojo::Handle(h_passing[1]), + MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + num_bytes = kBufferSize; + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + core()->ReadMessage( + h_passing[1], buffer, &num_bytes, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(kWorldSize, num_bytes); + ASSERT_STREQ(kWorld, buffer); + ASSERT_EQ(1u, num_handles); + ph = handles[0]; + ASSERT_NE(ph, MOJO_HANDLE_INVALID); + + // Complete the two-phase read. + ASSERT_EQ('x', static_cast(read_ptr)[0]); + ASSERT_EQ(MOJO_RESULT_OK, core()->EndReadData(ch, 1)); + + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[0])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(h_passing[1])); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ph)); + ASSERT_EQ(MOJO_RESULT_OK, core()->Close(ch)); +} + +struct TestAsyncWaiter { + TestAsyncWaiter() : result(MOJO_RESULT_UNKNOWN) {} + + void Awake(MojoResult r) { result = r; } + + MojoResult result; +}; + +// TODO(vtl): Test |DuplicateBufferHandle()| and |MapBuffer()|. + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.cc b/mojo/edk/system/data_pipe_consumer_dispatcher.cc new file mode 100644 index 0000000..f338732 --- /dev/null +++ b/mojo/edk/system/data_pipe_consumer_dispatcher.cc @@ -0,0 +1,562 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/data_pipe_consumer_dispatcher.h" + +#include +#include + +#include +#include +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/data_pipe_control_message.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/request_context.h" +#include "mojo/public/c/system/data_pipe.h" + +namespace mojo { +namespace edk { + +namespace { + +const uint8_t kFlagPeerClosed = 0x01; + +#pragma pack(push, 1) + +struct SerializedState { + MojoCreateDataPipeOptions options; + uint64_t pipe_id; + uint32_t read_offset; + uint32_t bytes_available; + uint8_t flags; + char padding[7]; +}; + +static_assert(sizeof(SerializedState) % 8 == 0, + "Invalid SerializedState size."); + +#pragma pack(pop) + +} // namespace + +// A PortObserver which forwards to a DataPipeConsumerDispatcher. This owns a +// reference to the dispatcher to ensure it lives as long as the observed port. +class DataPipeConsumerDispatcher::PortObserverThunk + : public NodeController::PortObserver { + public: + explicit PortObserverThunk( + scoped_refptr dispatcher) + : dispatcher_(dispatcher) {} + + private: + ~PortObserverThunk() override {} + + // NodeController::PortObserver: + void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } + + scoped_refptr dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(PortObserverThunk); +}; + +DataPipeConsumerDispatcher::DataPipeConsumerDispatcher( + NodeController* node_controller, + const ports::PortRef& control_port, + scoped_refptr shared_ring_buffer, + const MojoCreateDataPipeOptions& options, + bool initialized, + uint64_t pipe_id) + : options_(options), + node_controller_(node_controller), + control_port_(control_port), + pipe_id_(pipe_id), + watchers_(this), + shared_ring_buffer_(shared_ring_buffer) { + if (initialized) { + base::AutoLock lock(lock_); + InitializeNoLock(); + } +} + +Dispatcher::Type DataPipeConsumerDispatcher::GetType() const { + return Type::DATA_PIPE_CONSUMER; +} + +MojoResult DataPipeConsumerDispatcher::Close() { + base::AutoLock lock(lock_); + DVLOG(1) << "Closing data pipe consumer " << pipe_id_; + return CloseNoLock(); +} + +MojoResult DataPipeConsumerDispatcher::ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + base::AutoLock lock(lock_); + + if (!shared_ring_buffer_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (in_two_phase_read_) + return MOJO_RESULT_BUSY; + + const bool had_new_data = new_data_available_; + new_data_available_ = false; + + if ((flags & MOJO_READ_DATA_FLAG_QUERY)) { + if ((flags & MOJO_READ_DATA_FLAG_PEEK) || + (flags & MOJO_READ_DATA_FLAG_DISCARD)) + return MOJO_RESULT_INVALID_ARGUMENT; + DCHECK(!(flags & MOJO_READ_DATA_FLAG_DISCARD)); // Handled above. + DVLOG_IF(2, elements) + << "Query mode: ignoring non-null |elements|"; + *num_bytes = static_cast(bytes_available_); + + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return MOJO_RESULT_OK; + } + + bool discard = false; + if ((flags & MOJO_READ_DATA_FLAG_DISCARD)) { + // These flags are mutally exclusive. + if (flags & MOJO_READ_DATA_FLAG_PEEK) + return MOJO_RESULT_INVALID_ARGUMENT; + DVLOG_IF(2, elements) + << "Discard mode: ignoring non-null |elements|"; + discard = true; + } + + uint32_t max_num_bytes_to_read = *num_bytes; + if (max_num_bytes_to_read % options_.element_num_bytes != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + + bool all_or_none = flags & MOJO_READ_DATA_FLAG_ALL_OR_NONE; + uint32_t min_num_bytes_to_read = + all_or_none ? max_num_bytes_to_read : 0; + + if (min_num_bytes_to_read > bytes_available_) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION + : MOJO_RESULT_OUT_OF_RANGE; + } + + uint32_t bytes_to_read = std::min(max_num_bytes_to_read, bytes_available_); + if (bytes_to_read == 0) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION + : MOJO_RESULT_SHOULD_WAIT; + } + + if (!discard) { + uint8_t* data = static_cast(ring_buffer_mapping_->GetBase()); + CHECK(data); + + uint8_t* destination = static_cast(elements); + CHECK(destination); + + DCHECK_LE(read_offset_, options_.capacity_num_bytes); + uint32_t tail_bytes_to_copy = + std::min(options_.capacity_num_bytes - read_offset_, bytes_to_read); + uint32_t head_bytes_to_copy = bytes_to_read - tail_bytes_to_copy; + if (tail_bytes_to_copy > 0) + memcpy(destination, data + read_offset_, tail_bytes_to_copy); + if (head_bytes_to_copy > 0) + memcpy(destination + tail_bytes_to_copy, data, head_bytes_to_copy); + } + *num_bytes = bytes_to_read; + + bool peek = !!(flags & MOJO_READ_DATA_FLAG_PEEK); + if (discard || !peek) { + read_offset_ = (read_offset_ + bytes_to_read) % options_.capacity_num_bytes; + bytes_available_ -= bytes_to_read; + + base::AutoUnlock unlock(lock_); + NotifyRead(bytes_to_read); + } + + // We may have just read the last available data and thus changed the signals + // state. + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + return MOJO_RESULT_OK; +} + +MojoResult DataPipeConsumerDispatcher::BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + base::AutoLock lock(lock_); + if (!shared_ring_buffer_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (in_two_phase_read_) + return MOJO_RESULT_BUSY; + + // These flags may not be used in two-phase mode. + if ((flags & MOJO_READ_DATA_FLAG_DISCARD) || + (flags & MOJO_READ_DATA_FLAG_QUERY) || + (flags & MOJO_READ_DATA_FLAG_PEEK)) + return MOJO_RESULT_INVALID_ARGUMENT; + + const bool had_new_data = new_data_available_; + new_data_available_ = false; + + if (bytes_available_ == 0) { + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION + : MOJO_RESULT_SHOULD_WAIT; + } + + DCHECK_LT(read_offset_, options_.capacity_num_bytes); + uint32_t bytes_to_read = std::min(bytes_available_, + options_.capacity_num_bytes - read_offset_); + + CHECK(ring_buffer_mapping_); + uint8_t* data = static_cast(ring_buffer_mapping_->GetBase()); + CHECK(data); + + in_two_phase_read_ = true; + *buffer = data + read_offset_; + *buffer_num_bytes = bytes_to_read; + two_phase_max_bytes_read_ = bytes_to_read; + + if (had_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + return MOJO_RESULT_OK; +} + +MojoResult DataPipeConsumerDispatcher::EndReadData(uint32_t num_bytes_read) { + base::AutoLock lock(lock_); + if (!in_two_phase_read_) + return MOJO_RESULT_FAILED_PRECONDITION; + + if (in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + CHECK(shared_ring_buffer_); + + MojoResult rv; + if (num_bytes_read > two_phase_max_bytes_read_ || + num_bytes_read % options_.element_num_bytes != 0) { + rv = MOJO_RESULT_INVALID_ARGUMENT; + } else { + rv = MOJO_RESULT_OK; + read_offset_ = + (read_offset_ + num_bytes_read) % options_.capacity_num_bytes; + + DCHECK_GE(bytes_available_, num_bytes_read); + bytes_available_ -= num_bytes_read; + + base::AutoUnlock unlock(lock_); + NotifyRead(num_bytes_read); + } + + in_two_phase_read_ = false; + two_phase_max_bytes_read_ = 0; + + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + return rv; +} + +HandleSignalsState DataPipeConsumerDispatcher::GetHandleSignalsState() const { + base::AutoLock lock(lock_); + return GetHandleSignalsStateNoLock(); +} + +MojoResult DataPipeConsumerDispatcher::AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); +} + +MojoResult DataPipeConsumerDispatcher::RemoveWatcherRef( + WatcherDispatcher* watcher, + uintptr_t context) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); +} + +void DataPipeConsumerDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) { + base::AutoLock lock(lock_); + DCHECK(in_transit_); + *num_bytes = static_cast(sizeof(SerializedState)); + *num_ports = 1; + *num_handles = 1; +} + +bool DataPipeConsumerDispatcher::EndSerialize( + void* destination, + ports::PortName* ports, + PlatformHandle* platform_handles) { + SerializedState* state = static_cast(destination); + memcpy(&state->options, &options_, sizeof(MojoCreateDataPipeOptions)); + memset(state->padding, 0, sizeof(state->padding)); + + base::AutoLock lock(lock_); + DCHECK(in_transit_); + state->pipe_id = pipe_id_; + state->read_offset = read_offset_; + state->bytes_available = bytes_available_; + state->flags = peer_closed_ ? kFlagPeerClosed : 0; + + ports[0] = control_port_.name(); + + buffer_handle_for_transit_ = shared_ring_buffer_->DuplicatePlatformHandle(); + platform_handles[0] = buffer_handle_for_transit_.get(); + + return true; +} + +bool DataPipeConsumerDispatcher::BeginTransit() { + base::AutoLock lock(lock_); + if (in_transit_) + return false; + in_transit_ = !in_two_phase_read_; + return in_transit_; +} + +void DataPipeConsumerDispatcher::CompleteTransitAndClose() { + node_controller_->SetPortObserver(control_port_, nullptr); + + base::AutoLock lock(lock_); + DCHECK(in_transit_); + in_transit_ = false; + transferred_ = true; + ignore_result(buffer_handle_for_transit_.release()); + CloseNoLock(); +} + +void DataPipeConsumerDispatcher::CancelTransit() { + base::AutoLock lock(lock_); + DCHECK(in_transit_); + in_transit_ = false; + buffer_handle_for_transit_.reset(); + UpdateSignalsStateNoLock(); +} + +// static +scoped_refptr +DataPipeConsumerDispatcher::Deserialize(const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles) { + if (num_ports != 1 || num_handles != 1 || + num_bytes != sizeof(SerializedState)) { + return nullptr; + } + + const SerializedState* state = static_cast(data); + + NodeController* node_controller = internal::g_core->GetNodeController(); + ports::PortRef port; + if (node_controller->node()->GetPort(ports[0], &port) != ports::OK) + return nullptr; + + PlatformHandle buffer_handle; + std::swap(buffer_handle, handles[0]); + scoped_refptr ring_buffer = + PlatformSharedBuffer::CreateFromPlatformHandle( + state->options.capacity_num_bytes, + false /* read_only */, + ScopedPlatformHandle(buffer_handle)); + if (!ring_buffer) { + DLOG(ERROR) << "Failed to deserialize shared buffer handle."; + return nullptr; + } + + scoped_refptr dispatcher = + new DataPipeConsumerDispatcher(node_controller, port, ring_buffer, + state->options, false /* initialized */, + state->pipe_id); + + { + base::AutoLock lock(dispatcher->lock_); + dispatcher->read_offset_ = state->read_offset; + dispatcher->bytes_available_ = state->bytes_available; + dispatcher->new_data_available_ = state->bytes_available > 0; + dispatcher->peer_closed_ = state->flags & kFlagPeerClosed; + dispatcher->InitializeNoLock(); + dispatcher->UpdateSignalsStateNoLock(); + } + + return dispatcher; +} + +DataPipeConsumerDispatcher::~DataPipeConsumerDispatcher() { + DCHECK(is_closed_ && !shared_ring_buffer_ && !ring_buffer_mapping_ && + !in_transit_); +} + +void DataPipeConsumerDispatcher::InitializeNoLock() { + lock_.AssertAcquired(); + + if (shared_ring_buffer_) { + DCHECK(!ring_buffer_mapping_); + ring_buffer_mapping_ = + shared_ring_buffer_->Map(0, options_.capacity_num_bytes); + if (!ring_buffer_mapping_) { + DLOG(ERROR) << "Failed to map shared buffer."; + shared_ring_buffer_ = nullptr; + } + } + + base::AutoUnlock unlock(lock_); + node_controller_->SetPortObserver( + control_port_, + make_scoped_refptr(new PortObserverThunk(this))); +} + +MojoResult DataPipeConsumerDispatcher::CloseNoLock() { + lock_.AssertAcquired(); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + is_closed_ = true; + ring_buffer_mapping_.reset(); + shared_ring_buffer_ = nullptr; + + watchers_.NotifyClosed(); + if (!transferred_) { + base::AutoUnlock unlock(lock_); + node_controller_->ClosePort(control_port_); + } + + return MOJO_RESULT_OK; +} + +HandleSignalsState +DataPipeConsumerDispatcher::GetHandleSignalsStateNoLock() const { + lock_.AssertAcquired(); + + HandleSignalsState rv; + if (shared_ring_buffer_ && bytes_available_) { + if (!in_two_phase_read_) { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE; + if (new_data_available_) + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE; + } + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } else if (!peer_closed_ && shared_ring_buffer_) { + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } + + if (shared_ring_buffer_) { + if (new_data_available_ || !peer_closed_) + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE; + } + + if (peer_closed_) + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + + return rv; +} + +void DataPipeConsumerDispatcher::NotifyRead(uint32_t num_bytes) { + DVLOG(1) << "Data pipe consumer " << pipe_id_ << " notifying peer: " + << num_bytes << " bytes read. [control_port=" + << control_port_.name() << "]"; + + SendDataPipeControlMessage(node_controller_, control_port_, + DataPipeCommand::DATA_WAS_READ, num_bytes); +} + +void DataPipeConsumerDispatcher::OnPortStatusChanged() { + DCHECK(RequestContext::current()); + + base::AutoLock lock(lock_); + + // We stop observing the control port as soon it's transferred, but this can + // race with events which are raised right before that happens. This is fine + // to ignore. + if (transferred_) + return; + + DVLOG(1) << "Control port status changed for data pipe producer " << pipe_id_; + + UpdateSignalsStateNoLock(); +} + +void DataPipeConsumerDispatcher::UpdateSignalsStateNoLock() { + lock_.AssertAcquired(); + + bool was_peer_closed = peer_closed_; + size_t previous_bytes_available = bytes_available_; + + ports::PortStatus port_status; + int rv = node_controller_->node()->GetStatus(control_port_, &port_status); + if (rv != ports::OK || !port_status.receiving_messages) { + DVLOG(1) << "Data pipe consumer " << pipe_id_ << " is aware of peer closure" + << " [control_port=" << control_port_.name() << "]"; + peer_closed_ = true; + } else if (rv == ports::OK && port_status.has_messages && !in_transit_) { + ports::ScopedMessage message; + do { + int rv = node_controller_->node()->GetMessage( + control_port_, &message, nullptr); + if (rv != ports::OK) + peer_closed_ = true; + if (message) { + if (message->num_payload_bytes() < sizeof(DataPipeControlMessage)) { + peer_closed_ = true; + break; + } + + const DataPipeControlMessage* m = + static_cast( + message->payload_bytes()); + + if (m->command != DataPipeCommand::DATA_WAS_WRITTEN) { + DLOG(ERROR) << "Unexpected control message from producer."; + peer_closed_ = true; + break; + } + + if (static_cast(bytes_available_) + m->num_bytes > + options_.capacity_num_bytes) { + DLOG(ERROR) << "Producer claims to have written too many bytes."; + peer_closed_ = true; + break; + } + + DVLOG(1) << "Data pipe consumer " << pipe_id_ << " is aware that " + << m->num_bytes << " bytes were written. [control_port=" + << control_port_.name() << "]"; + + bytes_available_ += m->num_bytes; + } + } while (message); + } + + bool has_new_data = bytes_available_ != previous_bytes_available; + if (has_new_data) + new_data_available_ = true; + + if (peer_closed_ != was_peer_closed || has_new_data) + watchers_.NotifyState(GetHandleSignalsStateNoLock()); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/data_pipe_consumer_dispatcher.h b/mojo/edk/system/data_pipe_consumer_dispatcher.h new file mode 100644 index 0000000..120c7a3 --- /dev/null +++ b/mojo/edk/system/data_pipe_consumer_dispatcher.h @@ -0,0 +1,123 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watcher_set.h" + +namespace mojo { +namespace edk { + +class NodeController; + +// This is the Dispatcher implementation for the consumer handle for data +// pipes created by the Mojo primitive MojoCreateDataPipe(). This class is +// thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT DataPipeConsumerDispatcher final + : public Dispatcher { + public: + DataPipeConsumerDispatcher( + NodeController* node_controller, + const ports::PortRef& control_port, + scoped_refptr shared_ring_buffer, + const MojoCreateDataPipeOptions& options, + bool initialized, + uint64_t pipe_id); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) override; + MojoResult BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) override; + MojoResult EndReadData(uint32_t num_bytes_read) override; + HandleSignalsState GetHandleSignalsState() const override; + MojoResult AddWatcherRef(const scoped_refptr& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + static scoped_refptr + Deserialize(const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles); + + private: + class PortObserverThunk; + friend class PortObserverThunk; + + ~DataPipeConsumerDispatcher() override; + + void InitializeNoLock(); + MojoResult CloseNoLock(); + HandleSignalsState GetHandleSignalsStateNoLock() const; + void NotifyRead(uint32_t num_bytes); + void OnPortStatusChanged(); + void UpdateSignalsStateNoLock(); + + const MojoCreateDataPipeOptions options_; + NodeController* const node_controller_; + const ports::PortRef control_port_; + const uint64_t pipe_id_; + + // Guards access to the fields below. + mutable base::Lock lock_; + + WatcherSet watchers_; + + scoped_refptr shared_ring_buffer_; + std::unique_ptr ring_buffer_mapping_; + ScopedPlatformHandle buffer_handle_for_transit_; + + bool in_two_phase_read_ = false; + uint32_t two_phase_max_bytes_read_ = 0; + + bool in_transit_ = false; + bool is_closed_ = false; + bool peer_closed_ = false; + bool transferred_ = false; + + uint32_t read_offset_ = 0; + uint32_t bytes_available_ = 0; + + // Indicates whether any new data is available since the last read attempt. + bool new_data_available_ = false; + + DISALLOW_COPY_AND_ASSIGN(DataPipeConsumerDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_DATA_PIPE_CONSUMER_DISPATCHER_H_ diff --git a/mojo/edk/system/data_pipe_control_message.cc b/mojo/edk/system/data_pipe_control_message.cc new file mode 100644 index 0000000..23873b8 --- /dev/null +++ b/mojo/edk/system/data_pipe_control_message.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/edk/system/data_pipe_control_message.h" + +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/ports_message.h" + +namespace mojo { +namespace edk { + +void SendDataPipeControlMessage(NodeController* node_controller, + const ports::PortRef& port, + DataPipeCommand command, + uint32_t num_bytes) { + std::unique_ptr message = + PortsMessage::NewUserMessage(sizeof(DataPipeControlMessage), 0, 0); + CHECK(message); + + DataPipeControlMessage* data = + static_cast(message->mutable_payload_bytes()); + data->command = command; + data->num_bytes = num_bytes; + + int rv = node_controller->SendMessage(port, std::move(message)); + if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) { + DLOG(ERROR) << "Unexpected failure sending data pipe control message: " + << rv; + } +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/data_pipe_control_message.h b/mojo/edk/system/data_pipe_control_message.h new file mode 100644 index 0000000..ec84ea3 --- /dev/null +++ b/mojo/edk/system/data_pipe_control_message.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_EDK_SYSTEM_DATA_PIPE_CONTROL_MESSAGE_H_ +#define MOJO_EDK_SYSTEM_DATA_PIPE_CONTROL_MESSAGE_H_ + +#include + +#include + +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/public/c/system/macros.h" + +namespace mojo { +namespace edk { + +class NodeController; + +enum DataPipeCommand : uint32_t { + // Signal to the consumer that new data is available. + DATA_WAS_WRITTEN, + + // Signal to the producer that data has been consumed. + DATA_WAS_READ, +}; + +// Message header for messages sent over a data pipe control port. +struct MOJO_ALIGNAS(8) DataPipeControlMessage { + DataPipeCommand command; + uint32_t num_bytes; +}; + +void SendDataPipeControlMessage(NodeController* node_controller, + const ports::PortRef& port, + DataPipeCommand command, + uint32_t num_bytes); + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_DATA_PIPE_CONTROL_MESSAGE_H_ diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.cc b/mojo/edk/system/data_pipe_producer_dispatcher.cc new file mode 100644 index 0000000..b0102a6 --- /dev/null +++ b/mojo/edk/system/data_pipe_producer_dispatcher.cc @@ -0,0 +1,507 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/data_pipe_producer_dispatcher.h" + +#include +#include + +#include + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/data_pipe_control_message.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/request_context.h" +#include "mojo/public/c/system/data_pipe.h" + +namespace mojo { +namespace edk { + +namespace { + +const uint8_t kFlagPeerClosed = 0x01; + +#pragma pack(push, 1) + +struct SerializedState { + MojoCreateDataPipeOptions options; + uint64_t pipe_id; + uint32_t write_offset; + uint32_t available_capacity; + uint8_t flags; + char padding[7]; +}; + +static_assert(sizeof(SerializedState) % 8 == 0, + "Invalid SerializedState size."); + +#pragma pack(pop) + +} // namespace + +// A PortObserver which forwards to a DataPipeProducerDispatcher. This owns a +// reference to the dispatcher to ensure it lives as long as the observed port. +class DataPipeProducerDispatcher::PortObserverThunk + : public NodeController::PortObserver { + public: + explicit PortObserverThunk( + scoped_refptr dispatcher) + : dispatcher_(dispatcher) {} + + private: + ~PortObserverThunk() override {} + + // NodeController::PortObserver: + void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } + + scoped_refptr dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(PortObserverThunk); +}; + +DataPipeProducerDispatcher::DataPipeProducerDispatcher( + NodeController* node_controller, + const ports::PortRef& control_port, + scoped_refptr shared_ring_buffer, + const MojoCreateDataPipeOptions& options, + bool initialized, + uint64_t pipe_id) + : options_(options), + node_controller_(node_controller), + control_port_(control_port), + pipe_id_(pipe_id), + watchers_(this), + shared_ring_buffer_(shared_ring_buffer), + available_capacity_(options_.capacity_num_bytes) { + if (initialized) { + base::AutoLock lock(lock_); + InitializeNoLock(); + } +} + +Dispatcher::Type DataPipeProducerDispatcher::GetType() const { + return Type::DATA_PIPE_PRODUCER; +} + +MojoResult DataPipeProducerDispatcher::Close() { + base::AutoLock lock(lock_); + DVLOG(1) << "Closing data pipe producer " << pipe_id_; + return CloseNoLock(); +} + +MojoResult DataPipeProducerDispatcher::WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + base::AutoLock lock(lock_); + if (!shared_ring_buffer_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (in_two_phase_write_) + return MOJO_RESULT_BUSY; + + if (peer_closed_) + return MOJO_RESULT_FAILED_PRECONDITION; + + if (*num_bytes % options_.element_num_bytes != 0) + return MOJO_RESULT_INVALID_ARGUMENT; + if (*num_bytes == 0) + return MOJO_RESULT_OK; // Nothing to do. + + if ((flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE) && + (*num_bytes > available_capacity_)) { + // Don't return "should wait" since you can't wait for a specified amount of + // data. + return MOJO_RESULT_OUT_OF_RANGE; + } + + DCHECK_LE(available_capacity_, options_.capacity_num_bytes); + uint32_t num_bytes_to_write = std::min(*num_bytes, available_capacity_); + if (num_bytes_to_write == 0) + return MOJO_RESULT_SHOULD_WAIT; + + *num_bytes = num_bytes_to_write; + + CHECK(ring_buffer_mapping_); + uint8_t* data = static_cast(ring_buffer_mapping_->GetBase()); + CHECK(data); + + const uint8_t* source = static_cast(elements); + CHECK(source); + + DCHECK_LE(write_offset_, options_.capacity_num_bytes); + uint32_t tail_bytes_to_write = + std::min(options_.capacity_num_bytes - write_offset_, + num_bytes_to_write); + uint32_t head_bytes_to_write = num_bytes_to_write - tail_bytes_to_write; + + DCHECK_GT(tail_bytes_to_write, 0u); + memcpy(data + write_offset_, source, tail_bytes_to_write); + if (head_bytes_to_write > 0) + memcpy(data, source + tail_bytes_to_write, head_bytes_to_write); + + DCHECK_LE(num_bytes_to_write, available_capacity_); + available_capacity_ -= num_bytes_to_write; + write_offset_ = (write_offset_ + num_bytes_to_write) % + options_.capacity_num_bytes; + + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + base::AutoUnlock unlock(lock_); + NotifyWrite(num_bytes_to_write); + + return MOJO_RESULT_OK; +} + +MojoResult DataPipeProducerDispatcher::BeginWriteData( + void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + base::AutoLock lock(lock_); + if (!shared_ring_buffer_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + // These flags may not be used in two-phase mode. + if (flags & MOJO_WRITE_DATA_FLAG_ALL_OR_NONE) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (in_two_phase_write_) + return MOJO_RESULT_BUSY; + if (peer_closed_) + return MOJO_RESULT_FAILED_PRECONDITION; + + if (available_capacity_ == 0) { + return peer_closed_ ? MOJO_RESULT_FAILED_PRECONDITION + : MOJO_RESULT_SHOULD_WAIT; + } + + in_two_phase_write_ = true; + *buffer_num_bytes = std::min(options_.capacity_num_bytes - write_offset_, + available_capacity_); + DCHECK_GT(*buffer_num_bytes, 0u); + + CHECK(ring_buffer_mapping_); + uint8_t* data = static_cast(ring_buffer_mapping_->GetBase()); + *buffer = data + write_offset_; + + return MOJO_RESULT_OK; +} + +MojoResult DataPipeProducerDispatcher::EndWriteData( + uint32_t num_bytes_written) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!in_two_phase_write_) + return MOJO_RESULT_FAILED_PRECONDITION; + + DCHECK(shared_ring_buffer_); + DCHECK(ring_buffer_mapping_); + + // Note: Allow successful completion of the two-phase write even if the other + // side has been closed. + MojoResult rv = MOJO_RESULT_OK; + if (num_bytes_written > available_capacity_ || + num_bytes_written % options_.element_num_bytes != 0 || + write_offset_ + num_bytes_written > options_.capacity_num_bytes) { + rv = MOJO_RESULT_INVALID_ARGUMENT; + } else { + DCHECK_LE(num_bytes_written + write_offset_, options_.capacity_num_bytes); + available_capacity_ -= num_bytes_written; + write_offset_ = (write_offset_ + num_bytes_written) % + options_.capacity_num_bytes; + + base::AutoUnlock unlock(lock_); + NotifyWrite(num_bytes_written); + } + + in_two_phase_write_ = false; + + // If we're now writable, we *became* writable (since we weren't writable + // during the two-phase write), so notify watchers. + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + + return rv; +} + +HandleSignalsState DataPipeProducerDispatcher::GetHandleSignalsState() const { + base::AutoLock lock(lock_); + return GetHandleSignalsStateNoLock(); +} + +MojoResult DataPipeProducerDispatcher::AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); +} + +MojoResult DataPipeProducerDispatcher::RemoveWatcherRef( + WatcherDispatcher* watcher, + uintptr_t context) { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); +} + +void DataPipeProducerDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) { + base::AutoLock lock(lock_); + DCHECK(in_transit_); + *num_bytes = sizeof(SerializedState); + *num_ports = 1; + *num_handles = 1; +} + +bool DataPipeProducerDispatcher::EndSerialize( + void* destination, + ports::PortName* ports, + PlatformHandle* platform_handles) { + SerializedState* state = static_cast(destination); + memcpy(&state->options, &options_, sizeof(MojoCreateDataPipeOptions)); + memset(state->padding, 0, sizeof(state->padding)); + + base::AutoLock lock(lock_); + DCHECK(in_transit_); + state->pipe_id = pipe_id_; + state->write_offset = write_offset_; + state->available_capacity = available_capacity_; + state->flags = peer_closed_ ? kFlagPeerClosed : 0; + + ports[0] = control_port_.name(); + + buffer_handle_for_transit_ = shared_ring_buffer_->DuplicatePlatformHandle(); + platform_handles[0] = buffer_handle_for_transit_.get(); + + return true; +} + +bool DataPipeProducerDispatcher::BeginTransit() { + base::AutoLock lock(lock_); + if (in_transit_) + return false; + in_transit_ = !in_two_phase_write_; + return in_transit_; +} + +void DataPipeProducerDispatcher::CompleteTransitAndClose() { + node_controller_->SetPortObserver(control_port_, nullptr); + + base::AutoLock lock(lock_); + DCHECK(in_transit_); + transferred_ = true; + in_transit_ = false; + ignore_result(buffer_handle_for_transit_.release()); + CloseNoLock(); +} + +void DataPipeProducerDispatcher::CancelTransit() { + base::AutoLock lock(lock_); + DCHECK(in_transit_); + in_transit_ = false; + buffer_handle_for_transit_.reset(); + + HandleSignalsState state = GetHandleSignalsStateNoLock(); + watchers_.NotifyState(state); +} + +// static +scoped_refptr +DataPipeProducerDispatcher::Deserialize(const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles) { + if (num_ports != 1 || num_handles != 1 || + num_bytes != sizeof(SerializedState)) { + return nullptr; + } + + const SerializedState* state = static_cast(data); + + NodeController* node_controller = internal::g_core->GetNodeController(); + ports::PortRef port; + if (node_controller->node()->GetPort(ports[0], &port) != ports::OK) + return nullptr; + + PlatformHandle buffer_handle; + std::swap(buffer_handle, handles[0]); + scoped_refptr ring_buffer = + PlatformSharedBuffer::CreateFromPlatformHandle( + state->options.capacity_num_bytes, + false /* read_only */, + ScopedPlatformHandle(buffer_handle)); + if (!ring_buffer) { + DLOG(ERROR) << "Failed to deserialize shared buffer handle."; + return nullptr; + } + + scoped_refptr dispatcher = + new DataPipeProducerDispatcher(node_controller, port, ring_buffer, + state->options, false /* initialized */, + state->pipe_id); + + { + base::AutoLock lock(dispatcher->lock_); + dispatcher->write_offset_ = state->write_offset; + dispatcher->available_capacity_ = state->available_capacity; + dispatcher->peer_closed_ = state->flags & kFlagPeerClosed; + dispatcher->InitializeNoLock(); + dispatcher->UpdateSignalsStateNoLock(); + } + + return dispatcher; +} + +DataPipeProducerDispatcher::~DataPipeProducerDispatcher() { + DCHECK(is_closed_ && !in_transit_ && !shared_ring_buffer_ && + !ring_buffer_mapping_); +} + +void DataPipeProducerDispatcher::InitializeNoLock() { + lock_.AssertAcquired(); + + if (shared_ring_buffer_) { + ring_buffer_mapping_ = + shared_ring_buffer_->Map(0, options_.capacity_num_bytes); + if (!ring_buffer_mapping_) { + DLOG(ERROR) << "Failed to map shared buffer."; + shared_ring_buffer_ = nullptr; + } + } + + base::AutoUnlock unlock(lock_); + node_controller_->SetPortObserver( + control_port_, + make_scoped_refptr(new PortObserverThunk(this))); +} + +MojoResult DataPipeProducerDispatcher::CloseNoLock() { + lock_.AssertAcquired(); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + is_closed_ = true; + ring_buffer_mapping_.reset(); + shared_ring_buffer_ = nullptr; + + watchers_.NotifyClosed(); + if (!transferred_) { + base::AutoUnlock unlock(lock_); + node_controller_->ClosePort(control_port_); + } + + return MOJO_RESULT_OK; +} + +HandleSignalsState DataPipeProducerDispatcher::GetHandleSignalsStateNoLock() + const { + lock_.AssertAcquired(); + HandleSignalsState rv; + if (!peer_closed_) { + if (!in_two_phase_write_ && shared_ring_buffer_ && available_capacity_ > 0) + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + } else { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + } + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + return rv; +} + +void DataPipeProducerDispatcher::NotifyWrite(uint32_t num_bytes) { + DVLOG(1) << "Data pipe producer " << pipe_id_ << " notifying peer: " + << num_bytes << " bytes written. [control_port=" + << control_port_.name() << "]"; + + SendDataPipeControlMessage(node_controller_, control_port_, + DataPipeCommand::DATA_WAS_WRITTEN, num_bytes); +} + +void DataPipeProducerDispatcher::OnPortStatusChanged() { + DCHECK(RequestContext::current()); + + base::AutoLock lock(lock_); + + // We stop observing the control port as soon it's transferred, but this can + // race with events which are raised right before that happens. This is fine + // to ignore. + if (transferred_) + return; + + DVLOG(1) << "Control port status changed for data pipe producer " << pipe_id_; + + UpdateSignalsStateNoLock(); +} + +void DataPipeProducerDispatcher::UpdateSignalsStateNoLock() { + lock_.AssertAcquired(); + + bool was_peer_closed = peer_closed_; + size_t previous_capacity = available_capacity_; + + ports::PortStatus port_status; + int rv = node_controller_->node()->GetStatus(control_port_, &port_status); + if (rv != ports::OK || !port_status.receiving_messages) { + DVLOG(1) << "Data pipe producer " << pipe_id_ << " is aware of peer closure" + << " [control_port=" << control_port_.name() << "]"; + peer_closed_ = true; + } else if (rv == ports::OK && port_status.has_messages && !in_transit_) { + ports::ScopedMessage message; + do { + int rv = node_controller_->node()->GetMessage( + control_port_, &message, nullptr); + if (rv != ports::OK) + peer_closed_ = true; + if (message) { + if (message->num_payload_bytes() < sizeof(DataPipeControlMessage)) { + peer_closed_ = true; + break; + } + + const DataPipeControlMessage* m = + static_cast( + message->payload_bytes()); + + if (m->command != DataPipeCommand::DATA_WAS_READ) { + DLOG(ERROR) << "Unexpected message from consumer."; + peer_closed_ = true; + break; + } + + if (static_cast(available_capacity_) + m->num_bytes > + options_.capacity_num_bytes) { + DLOG(ERROR) << "Consumer claims to have read too many bytes."; + break; + } + + DVLOG(1) << "Data pipe producer " << pipe_id_ << " is aware that " + << m->num_bytes << " bytes were read. [control_port=" + << control_port_.name() << "]"; + + available_capacity_ += m->num_bytes; + } + } while (message); + } + + if (peer_closed_ != was_peer_closed || + available_capacity_ != previous_capacity) { + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + } +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/data_pipe_producer_dispatcher.h b/mojo/edk/system/data_pipe_producer_dispatcher.h new file mode 100644 index 0000000..1eddd5d --- /dev/null +++ b/mojo/edk/system/data_pipe_producer_dispatcher.h @@ -0,0 +1,123 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watcher_set.h" + +namespace mojo { +namespace edk { + +struct DataPipeControlMessage; +class NodeController; + +// This is the Dispatcher implementation for the producer handle for data +// pipes created by the Mojo primitive MojoCreateDataPipe(). This class is +// thread-safe. +class MOJO_SYSTEM_IMPL_EXPORT DataPipeProducerDispatcher final + : public Dispatcher { + public: + DataPipeProducerDispatcher( + NodeController* node_controller, + const ports::PortRef& port, + scoped_refptr shared_ring_buffer, + const MojoCreateDataPipeOptions& options, + bool initialized, + uint64_t pipe_id); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult WriteData(const void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) override; + MojoResult BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) override; + MojoResult EndWriteData(uint32_t num_bytes_written) override; + HandleSignalsState GetHandleSignalsState() const override; + MojoResult AddWatcherRef(const scoped_refptr& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + static scoped_refptr + Deserialize(const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles); + + private: + class PortObserverThunk; + friend class PortObserverThunk; + + ~DataPipeProducerDispatcher() override; + + void OnSharedBufferCreated(const scoped_refptr& buffer); + void InitializeNoLock(); + MojoResult CloseNoLock(); + HandleSignalsState GetHandleSignalsStateNoLock() const; + void NotifyWrite(uint32_t num_bytes); + void OnPortStatusChanged(); + void UpdateSignalsStateNoLock(); + bool ProcessMessageNoLock(const DataPipeControlMessage& message, + ScopedPlatformHandleVectorPtr handles); + + const MojoCreateDataPipeOptions options_; + NodeController* const node_controller_; + const ports::PortRef control_port_; + const uint64_t pipe_id_; + + // Guards access to the fields below. + mutable base::Lock lock_; + + WatcherSet watchers_; + + bool buffer_requested_ = false; + + scoped_refptr shared_ring_buffer_; + std::unique_ptr ring_buffer_mapping_; + ScopedPlatformHandle buffer_handle_for_transit_; + + bool in_transit_ = false; + bool is_closed_ = false; + bool peer_closed_ = false; + bool transferred_ = false; + bool in_two_phase_write_ = false; + + uint32_t write_offset_ = 0; + uint32_t available_capacity_; + + DISALLOW_COPY_AND_ASSIGN(DataPipeProducerDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_DATA_PIPE_PRODUCER_DISPATCHER_H_ diff --git a/mojo/edk/system/data_pipe_unittest.cc b/mojo/edk/system/data_pipe_unittest.cc new file mode 100644 index 0000000..79c1f75 --- /dev/null +++ b/mojo/edk/system/data_pipe_unittest.cc @@ -0,0 +1,2034 @@ +// 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 +#include + +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.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/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +const uint32_t kSizeOfOptions = + static_cast(sizeof(MojoCreateDataPipeOptions)); + +// In various places, we have to poll (since, e.g., we can't yet wait for a +// certain amount of data to be available). This is the maximum number of +// iterations (separated by a short sleep). +// TODO(vtl): Get rid of this. +const size_t kMaxPoll = 100; + +// Used in Multiprocess test. +const size_t kMultiprocessCapacity = 37; +const char kMultiprocessTestData[] = "hello i'm a string that is 36 bytes"; +const int kMultiprocessMaxIter = 5; + +// TODO(rockot): There are many uses of ASSERT where EXPECT would be more +// appropriate. Fix this. + +class DataPipeTest : public test::MojoTestBase { + public: + DataPipeTest() : producer_(MOJO_HANDLE_INVALID), + consumer_(MOJO_HANDLE_INVALID) {} + + ~DataPipeTest() override { + if (producer_ != MOJO_HANDLE_INVALID) + CHECK_EQ(MOJO_RESULT_OK, MojoClose(producer_)); + if (consumer_ != MOJO_HANDLE_INVALID) + CHECK_EQ(MOJO_RESULT_OK, MojoClose(consumer_)); + } + + MojoResult Create(const MojoCreateDataPipeOptions* options) { + return MojoCreateDataPipe(options, &producer_, &consumer_); + } + + MojoResult WriteData(const void* elements, + uint32_t* num_bytes, + bool all_or_none = false) { + return MojoWriteData(producer_, elements, num_bytes, + all_or_none ? MOJO_WRITE_DATA_FLAG_ALL_OR_NONE + : MOJO_WRITE_DATA_FLAG_NONE); + } + + MojoResult ReadData(void* elements, + uint32_t* num_bytes, + bool all_or_none = false, + bool peek = false) { + MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_NONE; + if (all_or_none) + flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE; + if (peek) + flags |= MOJO_READ_DATA_FLAG_PEEK; + return MojoReadData(consumer_, elements, num_bytes, flags); + } + + MojoResult QueryData(uint32_t* num_bytes) { + return MojoReadData(consumer_, nullptr, num_bytes, + MOJO_READ_DATA_FLAG_QUERY); + } + + MojoResult DiscardData(uint32_t* num_bytes, bool all_or_none = false) { + MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_DISCARD; + if (all_or_none) + flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE; + return MojoReadData(consumer_, nullptr, num_bytes, flags); + } + + MojoResult BeginReadData(const void** elements, + uint32_t* num_bytes, + bool all_or_none = false) { + MojoReadDataFlags flags = MOJO_READ_DATA_FLAG_NONE; + if (all_or_none) + flags |= MOJO_READ_DATA_FLAG_ALL_OR_NONE; + return MojoBeginReadData(consumer_, elements, num_bytes, flags); + } + + MojoResult EndReadData(uint32_t num_bytes_read) { + return MojoEndReadData(consumer_, num_bytes_read); + } + + MojoResult BeginWriteData(void** elements, + uint32_t* num_bytes, + bool all_or_none = false) { + MojoReadDataFlags flags = MOJO_WRITE_DATA_FLAG_NONE; + if (all_or_none) + flags |= MOJO_WRITE_DATA_FLAG_ALL_OR_NONE; + return MojoBeginWriteData(producer_, elements, num_bytes, flags); + } + + MojoResult EndWriteData(uint32_t num_bytes_written) { + return MojoEndWriteData(producer_, num_bytes_written); + } + + MojoResult CloseProducer() { + MojoResult rv = MojoClose(producer_); + producer_ = MOJO_HANDLE_INVALID; + return rv; + } + + MojoResult CloseConsumer() { + MojoResult rv = MojoClose(consumer_); + consumer_ = MOJO_HANDLE_INVALID; + return rv; + } + + MojoHandle producer_, consumer_; + + private: + DISALLOW_COPY_AND_ASSIGN(DataPipeTest); +}; + +TEST_F(DataPipeTest, Basic) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + // We can write to a data pipe handle immediately. + int32_t elements[10] = {}; + uint32_t num_bytes = 0; + + num_bytes = + static_cast(arraysize(elements) * sizeof(elements[0])); + + elements[0] = 123; + elements[1] = 456; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(&elements[0], &num_bytes)); + + // Now wait for the other side to become readable. + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfied_signals); + + elements[0] = -1; + elements[1] = -1; + ASSERT_EQ(MOJO_RESULT_OK, ReadData(&elements[0], &num_bytes)); + ASSERT_EQ(static_cast(2u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(elements[0], 123); + ASSERT_EQ(elements[1], 456); +} + +// Tests creation of data pipes with various (valid) options. +TEST_F(DataPipeTest, CreateAndMaybeTransfer) { + MojoCreateDataPipeOptions test_options[] = { + // Default options. + {}, + // Trivial element size, non-default capacity. + {kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1, // |element_num_bytes|. + 1000}, // |capacity_num_bytes|. + // Nontrivial element size, non-default capacity. + {kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 4, // |element_num_bytes|. + 4000}, // |capacity_num_bytes|. + // Nontrivial element size, default capacity. + {kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 100, // |element_num_bytes|. + 0} // |capacity_num_bytes|. + }; + for (size_t i = 0; i < arraysize(test_options); i++) { + MojoHandle producer_handle, consumer_handle; + MojoCreateDataPipeOptions* options = + i ? &test_options[i] : nullptr; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateDataPipe(options, &producer_handle, &consumer_handle)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(producer_handle)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(consumer_handle)); + } +} + +TEST_F(DataPipeTest, SimpleReadWrite) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + int32_t elements[10] = {}; + uint32_t num_bytes = 0; + + // Try reading; nothing there yet. + num_bytes = + static_cast(arraysize(elements) * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadData(elements, &num_bytes)); + + // Query; nothing there yet. + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Discard; nothing there yet. + num_bytes = static_cast(5u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, DiscardData(&num_bytes)); + + // Read with invalid |num_bytes|. + num_bytes = sizeof(elements[0]) + 1; + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, ReadData(elements, &num_bytes)); + + // Write two elements. + elements[0] = 123; + elements[1] = 456; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes)); + // It should have written everything (even without "all or none"). + ASSERT_EQ(2u * sizeof(elements[0]), num_bytes); + + // Wait. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Query. + // TODO(vtl): It's theoretically possible (though not with the current + // implementation/configured limits) that not all the data has arrived yet. + // (The theoretically-correct assertion here is that |num_bytes| is |1 * ...| + // or |2 * ...|.) + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(2 * sizeof(elements[0]), num_bytes); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes)); + ASSERT_EQ(1u * sizeof(elements[0]), num_bytes); + ASSERT_EQ(123, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Query. + // TODO(vtl): See previous TODO. (If we got 2 elements there, however, we + // should get 1 here.) + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1 * sizeof(elements[0]), num_bytes); + + // Peek one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, false, true)); + ASSERT_EQ(1u * sizeof(elements[0]), num_bytes); + ASSERT_EQ(456, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Query. Still has 1 element remaining. + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1 * sizeof(elements[0]), num_bytes); + + // Try to read two elements, with "all or none". + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, + ReadData(elements, &num_bytes, true, false)); + ASSERT_EQ(-1, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Try to read two elements, without "all or none". + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, false, false)); + ASSERT_EQ(1u * sizeof(elements[0]), num_bytes); + ASSERT_EQ(456, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Query. + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); +} + +// Note: The "basic" waiting tests test that the "wait states" are correct in +// various situations; they don't test that waiters are properly awoken on state +// changes. (For that, we need to use multiple threads.) +TEST_F(DataPipeTest, BasicProducerWaiting) { + // Note: We take advantage of the fact that current for current + // implementations capacities are strict maximums. This is not guaranteed by + // the API. + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 2 * sizeof(int32_t) // |capacity_num_bytes|. + }; + Create(&options); + MojoHandleSignalsState hss; + + // Never readable. Already writable. + hss = GetSignalsState(producer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Write two elements. + int32_t elements[2] = {123, 456}; + uint32_t num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + ASSERT_EQ(static_cast(2u * sizeof(elements[0])), num_bytes); + + // Wait for data to become available to the consumer. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Peek one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, true)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(123, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, false)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(123, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Try writing, using a two-phase write. + void* buffer = nullptr; + num_bytes = static_cast(3u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes)); + EXPECT_TRUE(buffer); + ASSERT_GE(num_bytes, static_cast(1u * sizeof(elements[0]))); + + static_cast(buffer)[0] = 789; + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(static_cast( + 1u * sizeof(elements[0])))); + + // Read one element, using a two-phase read. + const void* read_buffer = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer, &num_bytes, false)); + EXPECT_TRUE(read_buffer); + // The two-phase read should be able to read at least one element. + ASSERT_GE(num_bytes, static_cast(1u * sizeof(elements[0]))); + ASSERT_EQ(456, static_cast(read_buffer)[0]); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(static_cast( + 1u * sizeof(elements[0])))); + + // Write one element. + elements[0] = 123; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + + // Close the consumer. + CloseConsumer(); + + // It should now be never-writable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(DataPipeTest, PeerClosedProducerWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 2 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Close the consumer. + CloseConsumer(); + + // It should be signaled. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(DataPipeTest, PeerClosedConsumerWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 2 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Close the producer. + CloseProducer(); + + // It should be signaled. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(DataPipeTest, BasicConsumerWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Never writable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + EXPECT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Write two elements. + int32_t elements[2] = {123, 456}; + uint32_t num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + + // Wait for readability. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Discard one element. + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + + // Should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Peek one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true, true)); + ASSERT_EQ(456, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(456, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Write one element. + elements[0] = 789; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + + // Waiting should now succeed. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Close the producer. + CloseProducer(); + + // Should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(hss.satisfied_signals & (MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Wait for the peer closed signal. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read one element. + elements[0] = -1; + elements[1] = -1; + num_bytes = static_cast(1u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(elements, &num_bytes, true)); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + ASSERT_EQ(789, elements[0]); + ASSERT_EQ(-1, elements[1]); + + // Should be never-readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(DataPipeTest, ConsumerNewDataReadable) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + EXPECT_EQ(MOJO_RESULT_OK, Create(&options)); + + int32_t elements[2] = {123, 456}; + uint32_t num_bytes = static_cast(2u * sizeof(elements[0])); + EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + + // The consumer handle should appear to be readable and have new data. + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); + + // Now try to read a minimum of 6 elements. + int32_t read_elements[6]; + uint32_t num_read_bytes = sizeof(read_elements); + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + MojoReadData(consumer_, read_elements, &num_read_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // The consumer should still appear to be readable but not with new data. + EXPECT_TRUE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); + + // Write four more elements. + EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + EXPECT_EQ(MOJO_RESULT_OK, WriteData(elements, &num_bytes, true)); + + // The consumer handle should once again appear to be readable. + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE)); + + // Try again to read a minimum of 6 elements. Should succeed this time. + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadData(consumer_, read_elements, &num_read_bytes, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // And now the consumer is unreadable. + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_FALSE(GetSignalsState(consumer_).satisfied_signals & + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE); +} + +// Test with two-phase APIs and also closing the producer with an active +// consumer waiter. +TEST_F(DataPipeTest, ConsumerWaitingTwoPhase) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write two elements. + int32_t* elements = nullptr; + void* buffer = nullptr; + // Request room for three (but we'll only write two). + uint32_t num_bytes = static_cast(3u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&buffer, &num_bytes, false)); + EXPECT_TRUE(buffer); + EXPECT_GE(num_bytes, static_cast(3u * sizeof(elements[0]))); + elements = static_cast(buffer); + elements[0] = 123; + elements[1] = 456; + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(2u * sizeof(elements[0]))); + + // Wait for readability. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read one element. + // Request two in all-or-none mode, but only read one. + const void* read_buffer = nullptr; + num_bytes = static_cast(2u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes, true)); + EXPECT_TRUE(read_buffer); + ASSERT_EQ(static_cast(2u * sizeof(elements[0])), num_bytes); + const int32_t* read_elements = static_cast(read_buffer); + ASSERT_EQ(123, read_elements[0]); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(1u * sizeof(elements[0]))); + + // Should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read one element. + // Request three, but not in all-or-none mode. + read_buffer = nullptr; + num_bytes = static_cast(3u * sizeof(elements[0])); + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer, &num_bytes)); + EXPECT_TRUE(read_buffer); + ASSERT_EQ(static_cast(1u * sizeof(elements[0])), num_bytes); + read_elements = static_cast(read_buffer); + ASSERT_EQ(456, read_elements[0]); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(1u * sizeof(elements[0]))); + + // Close the producer. + CloseProducer(); + + // Should be never-readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +// Tests that data pipes aren't writable/readable during two-phase writes/reads. +TEST_F(DataPipeTest, BasicTwoPhaseWaiting) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // It should be writable. + hss = GetSignalsState(producer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + uint32_t num_bytes = static_cast(1u * sizeof(int32_t)); + void* write_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes)); + EXPECT_TRUE(write_ptr); + EXPECT_GE(num_bytes, static_cast(1u * sizeof(int32_t))); + + // At this point, it shouldn't be writable. + hss = GetSignalsState(producer_); + ASSERT_EQ(0u, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // It shouldn't be readable yet either (we'll wait later). + hss = GetSignalsState(consumer_); + ASSERT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + static_cast(write_ptr)[0] = 123; + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(1u * sizeof(int32_t))); + + // It should immediately be writable again. + hss = GetSignalsState(producer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // It should become readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Start another two-phase write and check that it's readable even in the + // middle of it. + num_bytes = static_cast(1u * sizeof(int32_t)); + write_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes)); + EXPECT_TRUE(write_ptr); + EXPECT_GE(num_bytes, static_cast(1u * sizeof(int32_t))); + + // It should be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // End the two-phase write without writing anything. + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(0u)); + + // Start a two-phase read. + num_bytes = static_cast(1u * sizeof(int32_t)); + const void* read_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes)); + EXPECT_TRUE(read_ptr); + ASSERT_EQ(static_cast(1u * sizeof(int32_t)), num_bytes); + + // At this point, it should still be writable. + hss = GetSignalsState(producer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // But not readable. + hss = GetSignalsState(consumer_); + ASSERT_EQ(0u, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // End the two-phase read without reading anything. + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(0u)); + + // It should be readable again. + hss = GetSignalsState(consumer_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); +} + +void Seq(int32_t start, size_t count, int32_t* out) { + for (size_t i = 0; i < count; i++) + out[i] = start + static_cast(i); +} + +TEST_F(DataPipeTest, AllOrNone) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Try writing more than the total capacity of the pipe. + uint32_t num_bytes = 20u * sizeof(int32_t); + int32_t buffer[100]; + Seq(0, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, WriteData(buffer, &num_bytes, true)); + + // Should still be empty. + num_bytes = ~0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Write some data. + num_bytes = 5u * sizeof(int32_t); + Seq(100, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true)); + ASSERT_EQ(5u * sizeof(int32_t), num_bytes); + + // Wait for data. + // TODO(vtl): There's no real guarantee that all the data will become + // available at once (except that in current implementations, with reasonable + // limits, it will). Eventually, we'll be able to wait for a specified amount + // of data to become available. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Half full. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(5u * sizeof(int32_t), num_bytes); + + // Try writing more than the available capacity of the pipe, but less than the + // total capacity. + num_bytes = 6u * sizeof(int32_t); + Seq(200, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, WriteData(buffer, &num_bytes, true)); + + // Try reading too much. + num_bytes = 11u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, ReadData(buffer, &num_bytes, true)); + int32_t expected_buffer[100]; + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much. + num_bytes = 11u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, DiscardData(&num_bytes, true)); + + // Just a little. + num_bytes = 2u * sizeof(int32_t); + Seq(300, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true)); + ASSERT_EQ(2u * sizeof(int32_t), num_bytes); + + // Just right. + num_bytes = 3u * sizeof(int32_t); + Seq(400, arraysize(buffer), buffer); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(buffer, &num_bytes, true)); + ASSERT_EQ(3u * sizeof(int32_t), num_bytes); + + // TODO(vtl): Hack (see also the TODO above): We can't currently wait for a + // specified amount of data to be available, so poll. + for (size_t i = 0; i < kMaxPoll; i++) { + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + if (num_bytes >= 10u * sizeof(int32_t)) + break; + + test::Sleep(test::EpsilonDeadline()); + } + ASSERT_EQ(10u * sizeof(int32_t), num_bytes); + + // Read half. + num_bytes = 5u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, true)); + ASSERT_EQ(5u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + Seq(100, 5, expected_buffer); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try reading too much again. + num_bytes = 6u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, ReadData(buffer, &num_bytes, true)); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much again. + num_bytes = 6u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OUT_OF_RANGE, DiscardData(&num_bytes, true)); + + // Discard a little. + num_bytes = 2u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true)); + ASSERT_EQ(2u * sizeof(int32_t), num_bytes); + + // Three left. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(3u * sizeof(int32_t), num_bytes); + + // Close the producer, then test producer-closed cases. + CloseProducer(); + + // Wait. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Try reading too much; "failed precondition" since the producer is closed. + num_bytes = 4u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + ReadData(buffer, &num_bytes, true)); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Try discarding too much; "failed precondition" again. + num_bytes = 4u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, DiscardData(&num_bytes, true)); + + // Read a little. + num_bytes = 2u * sizeof(int32_t); + memset(buffer, 0xab, sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, true)); + ASSERT_EQ(2u * sizeof(int32_t), num_bytes); + memset(expected_buffer, 0xab, sizeof(expected_buffer)); + Seq(400, 2, expected_buffer); + ASSERT_EQ(0, memcmp(buffer, expected_buffer, sizeof(buffer))); + + // Discard the remaining element. + num_bytes = 1u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OK, DiscardData(&num_bytes, true)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + + // Empty again. + num_bytes = ~0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); +} + +// Tests that |ProducerWriteData()| and |ConsumerReadData()| writes and reads, +// respectively, as much as possible, even if it may have to "wrap around" the +// internal circular buffer. (Note that the two-phase write and read need not do +// this.) +TEST_F(DataPipeTest, WrapAround) { + unsigned char test_data[1000]; + for (size_t i = 0; i < arraysize(test_data); i++) + test_data[i] = static_cast(i); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 100u // |capacity_num_bytes|. + }; + + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write 20 bytes. + uint32_t num_bytes = 20u; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(&test_data[0], &num_bytes, true)); + ASSERT_EQ(20u, num_bytes); + + // Wait for data. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE(hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read 10 bytes. + unsigned char read_buffer[1000] = {0}; + num_bytes = 10u; + ASSERT_EQ(MOJO_RESULT_OK, ReadData(read_buffer, &num_bytes, true)); + ASSERT_EQ(10u, num_bytes); + ASSERT_EQ(0, memcmp(read_buffer, &test_data[0], 10u)); + + // Check that a two-phase write can now only write (at most) 80 bytes. (This + // checks an implementation detail; this behavior is not guaranteed.) + void* write_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginWriteData(&write_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(write_buffer_ptr); + ASSERT_EQ(80u, num_bytes); + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(0)); + + size_t total_num_bytes = 0; + while (total_num_bytes < 90) { + // Wait to write. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + ASSERT_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_WRITABLE); + ASSERT_EQ(hss.satisfiable_signals, + MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + // Write as much as we can. + num_bytes = 100; + ASSERT_EQ(MOJO_RESULT_OK, + WriteData(&test_data[20 + total_num_bytes], &num_bytes, false)); + total_num_bytes += num_bytes; + } + + ASSERT_EQ(90u, total_num_bytes); + + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(100u, num_bytes); + + // Check that a two-phase read can now only read (at most) 90 bytes. (This + // checks an implementation detail; this behavior is not guaranteed.) + const void* read_buffer_ptr = nullptr; + num_bytes = 0; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(read_buffer_ptr); + ASSERT_EQ(90u, num_bytes); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(0)); + + // Read as much as possible. We should read 100 bytes. + num_bytes = static_cast(arraysize(read_buffer) * + sizeof(read_buffer[0])); + memset(read_buffer, 0, num_bytes); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(read_buffer, &num_bytes)); + ASSERT_EQ(100u, num_bytes); + ASSERT_EQ(0, memcmp(read_buffer, &test_data[10], 100u)); +} + +// Tests the behavior of writing (simple and two-phase), closing the producer, +// then reading (simple and two-phase). +TEST_F(DataPipeTest, WriteCloseProducerRead) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes, false)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Write it again, so we'll have something left over. + num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes, false)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Start two-phase write. + void* write_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginWriteData(&write_buffer_ptr, &num_bytes, false)); + EXPECT_TRUE(write_buffer_ptr); + EXPECT_GT(num_bytes, 0u); + + // TODO(vtl): (See corresponding TODO in TwoPhaseAllOrNone.) + for (size_t i = 0; i < kMaxPoll; i++) { + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + if (num_bytes >= 2u * kTestDataSize) + break; + + test::Sleep(test::EpsilonDeadline()); + } + ASSERT_EQ(2u * kTestDataSize, num_bytes); + + // Start two-phase read. + const void* read_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer_ptr, &num_bytes)); + EXPECT_TRUE(read_buffer_ptr); + ASSERT_EQ(2u * kTestDataSize, num_bytes); + + // Close the producer. + CloseProducer(); + + // The consumer can finish its two-phase read. + ASSERT_EQ(0, memcmp(read_buffer_ptr, kTestData, kTestDataSize)); + ASSERT_EQ(MOJO_RESULT_OK, EndReadData(kTestDataSize)); + + // And start another. + read_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer_ptr, &num_bytes)); + EXPECT_TRUE(read_buffer_ptr); + ASSERT_EQ(kTestDataSize, num_bytes); +} + + +// Tests the behavior of interrupting a two-phase read and write by closing the +// consumer. +TEST_F(DataPipeTest, TwoPhaseWriteReadCloseConsumer) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Start two-phase write. + void* write_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_buffer_ptr, &num_bytes)); + EXPECT_TRUE(write_buffer_ptr); + ASSERT_GT(num_bytes, kTestDataSize); + + // Wait for data. + // TODO(vtl): (See corresponding TODO in AllOrNone.) + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Start two-phase read. + const void* read_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer_ptr, &num_bytes)); + EXPECT_TRUE(read_buffer_ptr); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Close the consumer. + CloseConsumer(); + + // Wait for producer to know that the consumer is closed. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(producer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + + // Actually write some data. (Note: Premature freeing of the buffer would + // probably only be detected under ASAN or similar.) + memcpy(write_buffer_ptr, kTestData, kTestDataSize); + // Note: Even though the consumer has been closed, ending the two-phase + // write will report success. + ASSERT_EQ(MOJO_RESULT_OK, EndWriteData(kTestDataSize)); + + // But trying to write should result in failure. + num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, WriteData(kTestData, &num_bytes)); + + // As will trying to start another two-phase write. + write_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + BeginWriteData(&write_buffer_ptr, &num_bytes)); +} + +// Tests the behavior of "interrupting" a two-phase write by closing both the +// producer and the consumer. +TEST_F(DataPipeTest, TwoPhaseWriteCloseBoth) { + const uint32_t kTestDataSize = 15u; + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + // Start two-phase write. + void* write_buffer_ptr = nullptr; + uint32_t num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_buffer_ptr, &num_bytes)); + EXPECT_TRUE(write_buffer_ptr); + ASSERT_GT(num_bytes, kTestDataSize); +} + +// Tests the behavior of writing, closing the producer, and then reading (with +// and without data remaining). +TEST_F(DataPipeTest, WriteCloseProducerReadNoData) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write some data, so we'll have something to read. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Close the producer. + CloseProducer(); + + // Wait. (Note that once the consumer knows that the producer is closed, it + // must also know about all the data that was sent.) + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Peek that data. + char buffer[1000]; + num_bytes = static_cast(sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes, false, true)); + ASSERT_EQ(kTestDataSize, num_bytes); + ASSERT_EQ(0, memcmp(buffer, kTestData, kTestDataSize)); + + // Read that data. + memset(buffer, 0, 1000); + num_bytes = static_cast(sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_OK, ReadData(buffer, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + ASSERT_EQ(0, memcmp(buffer, kTestData, kTestDataSize)); + + // A second read should fail. + num_bytes = static_cast(sizeof(buffer)); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ReadData(buffer, &num_bytes)); + + // A two-phase read should also fail. + const void* read_buffer_ptr = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + BeginReadData(&read_buffer_ptr, &num_bytes)); + + // Ditto for discard. + num_bytes = 10u; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, DiscardData(&num_bytes)); +} + +// Test that during a two phase read the memory stays valid even if more data +// comes in. +TEST_F(DataPipeTest, TwoPhaseReadMemoryStable) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write some data. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Wait for the data. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Begin a two-phase read. + const void* read_buffer_ptr = nullptr; + uint32_t read_buffer_size = 0u; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_buffer_ptr, &read_buffer_size)); + + // Write more data. + const char kExtraData[] = "bye world"; + const uint32_t kExtraDataSize = static_cast(sizeof(kExtraData)); + num_bytes = kExtraDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kExtraData, &num_bytes)); + ASSERT_EQ(kExtraDataSize, num_bytes); + + // Close the producer. + CloseProducer(); + + // Wait. (Note that once the consumer knows that the producer is closed, it + // must also have received the extra data). + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Read the two phase memory to check it's still valid. + ASSERT_EQ(0, memcmp(read_buffer_ptr, kTestData, kTestDataSize)); + EndReadData(read_buffer_size); +} + +// Test that two-phase reads/writes behave correctly when given invalid +// arguments. +TEST_F(DataPipeTest, TwoPhaseMoreInvalidArguments) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 10 * sizeof(int32_t) // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // No data. + uint32_t num_bytes = 1000u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Try "ending" a two-phase write when one isn't active. + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + EndWriteData(1u * sizeof(int32_t))); + + // Wait a bit, to make sure that if a signal were (incorrectly) sent, it'd + // have time to propagate. + test::Sleep(test::EpsilonDeadline()); + + // Still no data. + num_bytes = 1000u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Try ending a two-phase write with an invalid amount (too much). + num_bytes = 0u; + void* write_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes)); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + EndWriteData(num_bytes + static_cast(sizeof(int32_t)))); + + // But the two-phase write still ended. + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndWriteData(0u)); + + // Wait a bit (as above). + test::Sleep(test::EpsilonDeadline()); + + // Still no data. + num_bytes = 1000u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Try ending a two-phase write with an invalid amount (not a multiple of the + // element size). + num_bytes = 0u; + write_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginWriteData(&write_ptr, &num_bytes)); + EXPECT_GE(num_bytes, 1u); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, EndWriteData(1u)); + + // But the two-phase write still ended. + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndWriteData(0u)); + + // Wait a bit (as above). + test::Sleep(test::EpsilonDeadline()); + + // Still no data. + num_bytes = 1000u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(0u, num_bytes); + + // Now write some data, so we'll be able to try reading. + int32_t element = 123; + num_bytes = 1u * sizeof(int32_t); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(&element, &num_bytes)); + + // Wait for data. + // TODO(vtl): (See corresponding TODO in AllOrNone.) + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // One element available. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try "ending" a two-phase read when one isn't active. + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, EndReadData(1u * sizeof(int32_t))); + + // Still one element available. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try ending a two-phase read with an invalid amount (too much). + num_bytes = 0u; + const void* read_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes)); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + EndReadData(num_bytes + static_cast(sizeof(int32_t)))); + + // Still one element available. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + + // Try ending a two-phase read with an invalid amount (not a multiple of the + // element size). + num_bytes = 0u; + read_ptr = nullptr; + ASSERT_EQ(MOJO_RESULT_OK, BeginReadData(&read_ptr, &num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); + ASSERT_EQ(123, static_cast(read_ptr)[0]); + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, EndReadData(1u)); + + // Still one element available. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, QueryData(&num_bytes)); + ASSERT_EQ(1u * sizeof(int32_t), num_bytes); +} + +// Test that a producer can be sent over a MP. +TEST_F(DataPipeTest, SendProducer) { + const char kTestData[] = "hello world"; + const uint32_t kTestDataSize = static_cast(sizeof(kTestData)); + + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1u, // |element_num_bytes|. + 1000u // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + MojoHandleSignalsState hss; + + // Write some data. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kTestData, &num_bytes)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Wait for the data. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Check the data. + const void* read_buffer = nullptr; + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer, &num_bytes, false)); + ASSERT_EQ(0, memcmp(read_buffer, kTestData, kTestDataSize)); + EndReadData(num_bytes); + + // Now send the producer over a MP so that it's serialized. + MojoHandle pipe0, pipe1; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &pipe0, &pipe1)); + + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(pipe0, nullptr, 0, &producer_, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + producer_ = MOJO_HANDLE_INVALID; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + uint32_t num_handles = 1; + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(pipe1, nullptr, 0, &producer_, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(num_handles, 1u); + + // Write more data. + const char kExtraData[] = "bye world"; + const uint32_t kExtraDataSize = static_cast(sizeof(kExtraData)); + num_bytes = kExtraDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kExtraData, &num_bytes)); + ASSERT_EQ(kExtraDataSize, num_bytes); + + // Wait for it. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + hss.satisfiable_signals); + + // Check the second write. + num_bytes = 0u; + ASSERT_EQ(MOJO_RESULT_OK, + BeginReadData(&read_buffer, &num_bytes, false)); + ASSERT_EQ(0, memcmp(read_buffer, kExtraData, kExtraDataSize)); + EndReadData(num_bytes); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipe0)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipe1)); +} + +// Ensures that if a data pipe consumer whose producer has closed is passed over +// a message pipe, the deserialized dispatcher is also marked as having a closed +// peer. +TEST_F(DataPipeTest, ConsumerWithClosedProducerSent) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + static_cast(sizeof(int32_t)), // |element_num_bytes|. + 1000 * sizeof(int32_t) // |capacity_num_bytes|. + }; + + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + // We can write to a data pipe handle immediately. + int32_t data = 123; + uint32_t num_bytes = sizeof(data); + ASSERT_EQ(MOJO_RESULT_OK, WriteData(&data, &num_bytes)); + ASSERT_EQ(MOJO_RESULT_OK, CloseProducer()); + + // Now wait for the other side to become readable and to see the peer closed. + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfiable_signals); + + // Now send the consumer over a MP so that it's serialized. + MojoHandle pipe0, pipe1; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &pipe0, &pipe1)); + + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(pipe0, nullptr, 0, &consumer_, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + consumer_ = MOJO_HANDLE_INVALID; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1, MOJO_HANDLE_SIGNAL_READABLE, &state)); + uint32_t num_handles = 1; + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(pipe1, nullptr, 0, &consumer_, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(num_handles, 1u); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumer_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED | + MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + state.satisfiable_signals); + + int32_t read_data; + ASSERT_EQ(MOJO_RESULT_OK, ReadData(&read_data, &num_bytes)); + ASSERT_EQ(sizeof(read_data), num_bytes); + ASSERT_EQ(data, read_data); + + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipe0)); + ASSERT_EQ(MOJO_RESULT_OK, MojoClose(pipe1)); +} + +bool WriteAllData(MojoHandle producer, + const void* elements, + uint32_t num_bytes) { + for (size_t i = 0; i < kMaxPoll; i++) { + // Write as much data as we can. + uint32_t write_bytes = num_bytes; + MojoResult result = MojoWriteData(producer, elements, &write_bytes, + MOJO_WRITE_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + num_bytes -= write_bytes; + elements = static_cast(elements) + write_bytes; + if (num_bytes == 0) + return true; + } else { + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, result); + } + + MojoHandleSignalsState hss = MojoHandleSignalsState(); + EXPECT_EQ(MOJO_RESULT_OK, test::MojoTestBase::WaitForSignals( + producer, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + } + + return false; +} + +// If |expect_empty| is true, expect |consumer| to be empty after reading. +bool ReadAllData(MojoHandle consumer, + void* elements, + uint32_t num_bytes, + bool expect_empty) { + for (size_t i = 0; i < kMaxPoll; i++) { + // Read as much data as we can. + uint32_t read_bytes = num_bytes; + MojoResult result = + MojoReadData(consumer, elements, &read_bytes, MOJO_READ_DATA_FLAG_NONE); + if (result == MOJO_RESULT_OK) { + num_bytes -= read_bytes; + elements = static_cast(elements) + read_bytes; + if (num_bytes == 0) { + if (expect_empty) { + // Expect no more data. + test::Sleep(test::TinyDeadline()); + MojoReadData(consumer, nullptr, &num_bytes, + MOJO_READ_DATA_FLAG_QUERY); + EXPECT_EQ(0u, num_bytes); + } + return true; + } + } else { + EXPECT_EQ(MOJO_RESULT_SHOULD_WAIT, result); + } + + MojoHandleSignalsState hss = MojoHandleSignalsState(); + EXPECT_EQ(MOJO_RESULT_OK, test::MojoTestBase::WaitForSignals( + consumer, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + // Peer could have become closed while we're still waiting for data. + EXPECT_TRUE(MOJO_HANDLE_SIGNAL_READABLE & hss.satisfied_signals); + EXPECT_TRUE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE); + EXPECT_TRUE(hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_PEER_CLOSED); + } + + return num_bytes == 0; +} + +#if !defined(OS_IOS) + +TEST_F(DataPipeTest, Multiprocess) { + const uint32_t kTestDataSize = + static_cast(sizeof(kMultiprocessTestData)); + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1, // |element_num_bytes|. + kMultiprocessCapacity // |capacity_num_bytes|. + }; + ASSERT_EQ(MOJO_RESULT_OK, Create(&options)); + + RUN_CHILD_ON_PIPE(MultiprocessClient, server_mp) + // Send some data before serialising and sending the data pipe over. + // This is the first write so we don't need to use WriteAllData. + uint32_t num_bytes = kTestDataSize; + ASSERT_EQ(MOJO_RESULT_OK, WriteData(kMultiprocessTestData, &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE)); + ASSERT_EQ(kTestDataSize, num_bytes); + + // Send child process the data pipe. + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(server_mp, nullptr, 0, &consumer_, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Send a bunch of data of varying sizes. + uint8_t buffer[100]; + int seq = 0; + for (int i = 0; i < kMultiprocessMaxIter; ++i) { + for (uint32_t size = 1; size <= kMultiprocessCapacity; size++) { + for (unsigned int j = 0; j < size; ++j) + buffer[j] = seq + j; + EXPECT_TRUE(WriteAllData(producer_, buffer, size)); + seq += size; + } + } + + // Write the test string in again. + ASSERT_TRUE(WriteAllData(producer_, kMultiprocessTestData, kTestDataSize)); + + // Swap ends. + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(server_mp, nullptr, 0, &producer_, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Receive the consumer from the other side. + producer_ = MOJO_HANDLE_INVALID; + MojoHandleSignalsState hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(server_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + MojoHandle handles[2]; + uint32_t num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(server_mp, nullptr, 0, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, num_handles); + consumer_ = handles[0]; + + // Read the test string twice. Once for when we sent it, and once for the + // other end sending it. + for (int i = 0; i < 2; ++i) { + EXPECT_TRUE(ReadAllData(consumer_, buffer, kTestDataSize, i == 1)); + EXPECT_EQ(0, memcmp(buffer, kMultiprocessTestData, kTestDataSize)); + } + + WriteMessage(server_mp, "quit"); + + // Don't have to close the consumer here because it will be done for us. + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MultiprocessClient, DataPipeTest, client_mp) { + const uint32_t kTestDataSize = + static_cast(sizeof(kMultiprocessTestData)); + + // Receive the data pipe from the other side. + MojoHandle consumer = MOJO_HANDLE_INVALID; + MojoHandleSignalsState hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + MojoHandle handles[2]; + uint32_t num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(client_mp, nullptr, 0, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, num_handles); + consumer = handles[0]; + + // Read the initial string that was sent. + int32_t buffer[100]; + EXPECT_TRUE(ReadAllData(consumer, buffer, kTestDataSize, false)); + EXPECT_EQ(0, memcmp(buffer, kMultiprocessTestData, kTestDataSize)); + + // Receive the main data and check it is correct. + int seq = 0; + uint8_t expected_buffer[100]; + for (int i = 0; i < kMultiprocessMaxIter; ++i) { + for (uint32_t size = 1; size <= kMultiprocessCapacity; ++size) { + for (unsigned int j = 0; j < size; ++j) + expected_buffer[j] = seq + j; + EXPECT_TRUE(ReadAllData(consumer, buffer, size, false)); + EXPECT_EQ(0, memcmp(buffer, expected_buffer, size)); + + seq += size; + } + } + + // Swap ends. + ASSERT_EQ(MOJO_RESULT_OK, MojoWriteMessage(client_mp, nullptr, 0, &consumer, + 1, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Receive the producer from the other side. + MojoHandle producer = MOJO_HANDLE_INVALID; + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(client_mp, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + num_handles = arraysize(handles); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(client_mp, nullptr, 0, handles, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_EQ(1u, num_handles); + producer = handles[0]; + + // Write the test string one more time. + EXPECT_TRUE(WriteAllData(producer, kMultiprocessTestData, kTestDataSize)); + + // We swapped ends, so close the producer. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + + // Wait to receive a "quit" message before exiting. + EXPECT_EQ("quit", ReadMessage(client_mp)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(WriteAndCloseProducer, DataPipeTest, h) { + MojoHandle p; + std::string message = ReadMessageWithHandles(h, &p, 1); + + // Write some data to the producer and close it. + uint32_t num_bytes = static_cast(message.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(p, message.data(), &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(num_bytes, static_cast(message.size())); + + // Close the producer before quitting. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(p)); + + // Wait for a quit message. + EXPECT_EQ("quit", ReadMessage(h)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadAndCloseConsumer, DataPipeTest, h) { + MojoHandle c; + std::string expected_message = ReadMessageWithHandles(h, &c, 1); + + // Wait for the consumer to become readable. + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); + + // Drain the consumer and expect to find the given message. + uint32_t num_bytes = static_cast(expected_message.size()); + std::vector bytes(expected_message.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoReadData(c, bytes.data(), &num_bytes, + MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(num_bytes, static_cast(bytes.size())); + + std::string message(bytes.data(), bytes.size()); + EXPECT_EQ(expected_message, message); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + + // Wait for a quit message. + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(DataPipeTest, SendConsumerAndCloseProducer) { + // Create a new data pipe. + MojoHandle p, c; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &p ,&c)); + + RUN_CHILD_ON_PIPE(WriteAndCloseProducer, producer_client) + RUN_CHILD_ON_PIPE(ReadAndCloseConsumer, consumer_client) + const std::string kMessage = "Hello, world!"; + WriteMessageWithHandles(producer_client, kMessage, &p, 1); + WriteMessageWithHandles(consumer_client, kMessage, &c, 1); + + WriteMessage(consumer_client, "quit"); + END_CHILD() + + WriteMessage(producer_client, "quit"); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndWrite, DataPipeTest, h) { + const MojoCreateDataPipeOptions options = { + kSizeOfOptions, // |struct_size|. + MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE, // |flags|. + 1, // |element_num_bytes|. + kMultiprocessCapacity // |capacity_num_bytes|. + }; + + MojoHandle p, c; + ASSERT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(&options, &p, &c)); + + const std::string kMessage = "Hello, world!"; + WriteMessageWithHandles(h, kMessage, &c, 1); + + // Write some data to the producer and close it. + uint32_t num_bytes = static_cast(kMessage.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoWriteData(p, kMessage.data(), &num_bytes, + MOJO_WRITE_DATA_FLAG_NONE)); + EXPECT_EQ(num_bytes, static_cast(kMessage.size())); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(p)); + + // Wait for a quit message. + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(DataPipeTest, CreateInChild) { + RUN_CHILD_ON_PIPE(CreateAndWrite, child) + MojoHandle c; + std::string expected_message = ReadMessageWithHandles(child, &c, 1); + + // Wait for the consumer to become readable. + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); + + // Drain the consumer and expect to find the given message. + uint32_t num_bytes = static_cast(expected_message.size()); + std::vector bytes(expected_message.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoReadData(c, bytes.data(), &num_bytes, + MOJO_READ_DATA_FLAG_NONE)); + EXPECT_EQ(num_bytes, static_cast(bytes.size())); + + std::string message(bytes.data(), bytes.size()); + EXPECT_EQ(expected_message, message); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + WriteMessage(child, "quit"); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(DataPipeStatusChangeInTransitClient, + DataPipeTest, parent) { + // This test verifies that peer closure is detectable through various + // mechanisms when it races with handle transfer. + + MojoHandle handles[6]; + EXPECT_EQ("o_O", ReadMessageWithHandles(parent, handles, 6)); + MojoHandle* producers = &handles[0]; + MojoHandle* consumers = &handles[3]; + + // Wait on producer 0 + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(producers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + // Wait on consumer 0 + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(consumers[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + base::MessageLoop message_loop; + + // Wait on producer 1 and consumer 1 using SimpleWatchers. + { + base::RunLoop run_loop; + int count = 0; + auto callback = base::Bind( + [] (base::RunLoop* loop, int* count, MojoResult result) { + EXPECT_EQ(MOJO_RESULT_OK, result); + if (++*count == 2) + loop->Quit(); + }, + &run_loop, &count); + SimpleWatcher producer_watcher(FROM_HERE, + SimpleWatcher::ArmingPolicy::AUTOMATIC); + SimpleWatcher consumer_watcher(FROM_HERE, + SimpleWatcher::ArmingPolicy::AUTOMATIC); + producer_watcher.Watch(Handle(producers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + callback); + consumer_watcher.Watch(Handle(consumers[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + callback); + run_loop.Run(); + EXPECT_EQ(2, count); + } + + // Wait on producer 2 by polling with MojoWriteData. + MojoResult result; + do { + uint32_t num_bytes = 0; + result = MojoWriteData( + producers[2], nullptr, &num_bytes, MOJO_WRITE_DATA_FLAG_NONE); + } while (result == MOJO_RESULT_OK); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + + // Wait on consumer 2 by polling with MojoReadData. + do { + char byte; + uint32_t num_bytes = 1; + result = MojoReadData( + consumers[2], &byte, &num_bytes, MOJO_READ_DATA_FLAG_NONE); + } while (result == MOJO_RESULT_SHOULD_WAIT); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + + for (size_t i = 0; i < 6; ++i) + CloseHandle(handles[i]); +} + +TEST_F(DataPipeTest, StatusChangeInTransit) { + MojoHandle producers[6]; + MojoHandle consumers[6]; + for (size_t i = 0; i < 6; ++i) + CreateDataPipe(&producers[i], &consumers[i], 1); + + RUN_CHILD_ON_PIPE(DataPipeStatusChangeInTransitClient, child) + MojoHandle handles[] = { producers[0], producers[1], producers[2], + consumers[3], consumers[4], consumers[5] }; + + // Send 3 producers and 3 consumers, and let their transfer race with their + // peers' closure. + WriteMessageWithHandles(child, "o_O", handles, 6); + + for (size_t i = 0; i < 3; ++i) + CloseHandle(consumers[i]); + for (size_t i = 3; i < 6; ++i) + CloseHandle(producers[i]); + END_CHILD() +} + +#endif // !defined(OS_IOS) + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/dispatcher.cc b/mojo/edk/system/dispatcher.cc new file mode 100644 index 0000000..7cdbe91 --- /dev/null +++ b/mojo/edk/system/dispatcher.cc @@ -0,0 +1,198 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/dispatcher.h" + +#include "base/logging.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/data_pipe_consumer_dispatcher.h" +#include "mojo/edk/system/data_pipe_producer_dispatcher.h" +#include "mojo/edk/system/message_pipe_dispatcher.h" +#include "mojo/edk/system/platform_handle_dispatcher.h" +#include "mojo/edk/system/shared_buffer_dispatcher.h" + +namespace mojo { +namespace edk { + +Dispatcher::DispatcherInTransit::DispatcherInTransit() {} + +Dispatcher::DispatcherInTransit::DispatcherInTransit( + const DispatcherInTransit& other) = default; + +Dispatcher::DispatcherInTransit::~DispatcherInTransit() {} + +MojoResult Dispatcher::WatchDispatcher(scoped_refptr dispatcher, + MojoHandleSignals signals, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::CancelWatch(uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::WriteMessage(std::unique_ptr message, + MojoWriteMessageFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::ReadMessage(std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags, + bool read_any_size) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr* new_dispatcher) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + std::unique_ptr* mapping) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::EndReadData(uint32_t num_bytes_read) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::EndWriteData(uint32_t num_bytes_written) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::AddWaitingDispatcher( + const scoped_refptr& dispatcher, + MojoHandleSignals signals, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::RemoveWaitingDispatcher( + const scoped_refptr& dispatcher) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::GetReadyDispatchers(uint32_t* count, + DispatcherVector* dispatchers, + MojoResult* results, + uintptr_t* contexts) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +HandleSignalsState Dispatcher::GetHandleSignalsState() const { + return HandleSignalsState(); +} + +MojoResult Dispatcher::AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +MojoResult Dispatcher::RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) { + return MOJO_RESULT_INVALID_ARGUMENT; +} + +void Dispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_platform_handles) { + *num_bytes = 0; + *num_ports = 0; + *num_platform_handles = 0; +} + +bool Dispatcher::EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) { + LOG(ERROR) << "Attempting to serialize a non-transferrable dispatcher."; + return true; +} + +bool Dispatcher::BeginTransit() { return true; } + +void Dispatcher::CompleteTransitAndClose() {} + +void Dispatcher::CancelTransit() {} + +// static +scoped_refptr Dispatcher::Deserialize( + Type type, + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* platform_handles, + size_t num_platform_handles) { + switch (type) { + case Type::MESSAGE_PIPE: + return MessagePipeDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + case Type::SHARED_BUFFER: + return SharedBufferDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + case Type::DATA_PIPE_CONSUMER: + return DataPipeConsumerDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + case Type::DATA_PIPE_PRODUCER: + return DataPipeProducerDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + case Type::PLATFORM_HANDLE: + return PlatformHandleDispatcher::Deserialize( + bytes, num_bytes, ports, num_ports, platform_handles, + num_platform_handles); + default: + LOG(ERROR) << "Deserializing invalid dispatcher type."; + return nullptr; + } +} + +Dispatcher::Dispatcher() {} + +Dispatcher::~Dispatcher() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/dispatcher.h b/mojo/edk/system/dispatcher.h new file mode 100644 index 0000000..db1f1f1 --- /dev/null +++ b/mojo/edk/system/dispatcher.h @@ -0,0 +1,245 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_DISPATCHER_H_ + +#include +#include + +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watch.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { + +class Dispatcher; +class MessageForTransit; + +using DispatcherVector = std::vector>; + +// A |Dispatcher| implements Mojo EDK calls that are associated with a +// particular MojoHandle. +class MOJO_SYSTEM_IMPL_EXPORT Dispatcher + : public base::RefCountedThreadSafe { + public: + struct DispatcherInTransit { + DispatcherInTransit(); + DispatcherInTransit(const DispatcherInTransit& other); + ~DispatcherInTransit(); + + scoped_refptr dispatcher; + MojoHandle local_handle; + }; + + enum class Type { + UNKNOWN = 0, + MESSAGE_PIPE, + DATA_PIPE_PRODUCER, + DATA_PIPE_CONSUMER, + SHARED_BUFFER, + WATCHER, + + // "Private" types (not exposed via the public interface): + PLATFORM_HANDLE = -1, + }; + + // All Dispatchers must minimally implement these methods. + + virtual Type GetType() const = 0; + virtual MojoResult Close() = 0; + + ///////////// Watcher API //////////////////// + + virtual MojoResult WatchDispatcher(scoped_refptr dispatcher, + MojoHandleSignals signals, + uintptr_t context); + virtual MojoResult CancelWatch(uintptr_t context); + virtual MojoResult Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states); + + ///////////// Message pipe API ///////////// + + virtual MojoResult WriteMessage(std::unique_ptr message, + MojoWriteMessageFlags flags); + + virtual MojoResult ReadMessage(std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags, + bool read_any_size); + + ///////////// Shared buffer API ///////////// + + // |options| may be null. |new_dispatcher| must not be null, but + // |*new_dispatcher| should be null (and will contain the dispatcher for the + // new handle on success). + virtual MojoResult DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr* new_dispatcher); + + virtual MojoResult MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + std::unique_ptr* mapping); + + ///////////// Data pipe consumer API ///////////// + + virtual MojoResult ReadData(void* elements, + uint32_t* num_bytes, + MojoReadDataFlags flags); + + virtual MojoResult BeginReadData(const void** buffer, + uint32_t* buffer_num_bytes, + MojoReadDataFlags flags); + + virtual MojoResult EndReadData(uint32_t num_bytes_read); + + ///////////// Data pipe producer API ///////////// + + virtual MojoResult WriteData(const void* elements, + uint32_t* num_bytes, + MojoWriteDataFlags flags); + + virtual MojoResult BeginWriteData(void** buffer, + uint32_t* buffer_num_bytes, + MojoWriteDataFlags flags); + + virtual MojoResult EndWriteData(uint32_t num_bytes_written); + + ///////////// Wait set API ///////////// + + // Adds a dispatcher to wait on. When the dispatcher satisfies |signals|, it + // will be returned in the next call to |GetReadyDispatchers()|. If + // |dispatcher| has been added, it must be removed before adding again, + // otherwise |MOJO_RESULT_ALREADY_EXISTS| will be returned. + virtual MojoResult AddWaitingDispatcher( + const scoped_refptr& dispatcher, + MojoHandleSignals signals, + uintptr_t context); + + // Removes a dispatcher to wait on. If |dispatcher| has not been added, + // |MOJO_RESULT_NOT_FOUND| will be returned. + virtual MojoResult RemoveWaitingDispatcher( + const scoped_refptr& dispatcher); + + // Returns a set of ready dispatchers. |*count| is the maximum number of + // dispatchers to return, and will contain the number of dispatchers returned + // in |dispatchers| on completion. + virtual MojoResult GetReadyDispatchers(uint32_t* count, + DispatcherVector* dispatchers, + MojoResult* results, + uintptr_t* contexts); + + ///////////// General-purpose API for all handle types ///////// + + // Gets the current handle signals state. (The default implementation simply + // returns a default-constructed |HandleSignalsState|, i.e., no signals + // satisfied or satisfiable.) Note: The state is subject to change from other + // threads. + virtual HandleSignalsState GetHandleSignalsState() const; + + // Adds a WatcherDispatcher reference to this dispatcher, to be notified of + // all subsequent changes to handle state including signal changes or closure. + // The reference is associated with a |context| for disambiguation of + // removals. + virtual MojoResult AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context); + + // Removes a WatcherDispatcher reference from this dispatcher. + virtual MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context); + + // Informs the caller of the total serialized size (in bytes) and the total + // number of platform handles and ports needed to transfer this dispatcher + // across a message pipe. + // + // Must eventually be followed by a call to EndSerializeAndClose(). Note that + // StartSerialize() and EndSerialize() are always called in sequence, and + // only between calls to BeginTransit() and either (but not both) + // CompleteTransitAndClose() or CancelTransit(). + // + // For this reason it is IMPERATIVE that the implementation ensure a + // consistent serializable state between BeginTransit() and + // CompleteTransitAndClose()/CancelTransit(). + virtual void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_platform_handles); + + // Serializes this dispatcher into |destination|, |ports|, and |handles|. + // Returns true iff successful, false otherwise. In either case the dispatcher + // will close. + // + // NOTE: Transit MAY still fail after this call returns. Implementations + // should not assume PlatformHandle ownership has transferred until + // CompleteTransitAndClose() is called. In other words, if CancelTransit() is + // called, the implementation should retain its PlatformHandles in working + // condition. + virtual bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles); + + // Does whatever is necessary to begin transit of the dispatcher. This + // should return |true| if transit is OK, or false if the underlying resource + // is deemed busy by the implementation. + virtual bool BeginTransit(); + + // Does whatever is necessary to complete transit of the dispatcher, including + // closure. This is only called upon successfully transmitting an outgoing + // message containing this serialized dispatcher. + virtual void CompleteTransitAndClose(); + + // Does whatever is necessary to cancel transit of the dispatcher. The + // dispatcher should remain in a working state and resume normal operation. + virtual void CancelTransit(); + + // Deserializes a specific dispatcher type from an incoming message. + static scoped_refptr Deserialize( + Type type, + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* platform_handles, + size_t num_platform_handles); + + protected: + friend class base::RefCountedThreadSafe; + + Dispatcher(); + virtual ~Dispatcher(); + + DISALLOW_COPY_AND_ASSIGN(Dispatcher); +}; + +// So logging macros and |DCHECK_EQ()|, etc. work. +MOJO_SYSTEM_IMPL_EXPORT inline std::ostream& operator<<(std::ostream& out, + Dispatcher::Type type) { + return out << static_cast(type); +} + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_DISPATCHER_H_ diff --git a/mojo/edk/system/handle_signals_state.h b/mojo/edk/system/handle_signals_state.h new file mode 100644 index 0000000..f241278 --- /dev/null +++ b/mojo/edk/system/handle_signals_state.h @@ -0,0 +1,13 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ +#define MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ + +#include "mojo/public/cpp/system/handle_signals_state.h" + +// TODO(rockot): Remove this header and use the C++ system library type +// directly inside the EDK. + +#endif // MOJO_EDK_SYSTEM_HANDLE_SIGNALS_STATE_H_ diff --git a/mojo/edk/system/handle_table.cc b/mojo/edk/system/handle_table.cc new file mode 100644 index 0000000..b570793 --- /dev/null +++ b/mojo/edk/system/handle_table.cc @@ -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. + +#include "mojo/edk/system/handle_table.h" + +#include + +#include + +namespace mojo { +namespace edk { + +HandleTable::HandleTable() {} + +HandleTable::~HandleTable() {} + +MojoHandle HandleTable::AddDispatcher(scoped_refptr dispatcher) { + // Oops, we're out of handles. + if (next_available_handle_ == MOJO_HANDLE_INVALID) + return MOJO_HANDLE_INVALID; + + MojoHandle handle = next_available_handle_++; + auto result = + handles_.insert(std::make_pair(handle, Entry(std::move(dispatcher)))); + DCHECK(result.second); + + return handle; +} + +bool HandleTable::AddDispatchersFromTransit( + const std::vector& dispatchers, + MojoHandle* handles) { + // Oops, we're out of handles. + if (next_available_handle_ == MOJO_HANDLE_INVALID) + return false; + + DCHECK_LE(dispatchers.size(), std::numeric_limits::max()); + // If this insertion would cause handle overflow, we're out of handles. + if (next_available_handle_ + dispatchers.size() < next_available_handle_) + return false; + + for (size_t i = 0; i < dispatchers.size(); ++i) { + MojoHandle handle = next_available_handle_++; + auto result = handles_.insert( + std::make_pair(handle, Entry(dispatchers[i].dispatcher))); + DCHECK(result.second); + handles[i] = handle; + } + + return true; +} + +scoped_refptr HandleTable::GetDispatcher(MojoHandle handle) const { + auto it = handles_.find(handle); + if (it == handles_.end()) + return nullptr; + return it->second.dispatcher; +} + +MojoResult HandleTable::GetAndRemoveDispatcher( + MojoHandle handle, + scoped_refptr* dispatcher) { + auto it = handles_.find(handle); + if (it == handles_.end()) + return MOJO_RESULT_INVALID_ARGUMENT; + if (it->second.busy) + return MOJO_RESULT_BUSY; + + *dispatcher = std::move(it->second.dispatcher); + handles_.erase(it); + return MOJO_RESULT_OK; +} + +MojoResult HandleTable::BeginTransit( + const MojoHandle* handles, + uint32_t num_handles, + std::vector* dispatchers) { + dispatchers->clear(); + dispatchers->reserve(num_handles); + for (size_t i = 0; i < num_handles; ++i) { + auto it = handles_.find(handles[i]); + if (it == handles_.end()) + return MOJO_RESULT_INVALID_ARGUMENT; + if (it->second.busy) + return MOJO_RESULT_BUSY; + + Dispatcher::DispatcherInTransit d; + d.local_handle = handles[i]; + d.dispatcher = it->second.dispatcher; + if (!d.dispatcher->BeginTransit()) + return MOJO_RESULT_BUSY; + it->second.busy = true; + dispatchers->push_back(d); + } + return MOJO_RESULT_OK; +} + +void HandleTable::CompleteTransitAndClose( + const std::vector& dispatchers) { + for (const auto& dispatcher : dispatchers) { + auto it = handles_.find(dispatcher.local_handle); + DCHECK(it != handles_.end() && it->second.busy); + handles_.erase(it); + dispatcher.dispatcher->CompleteTransitAndClose(); + } +} + +void HandleTable::CancelTransit( + const std::vector& dispatchers) { + for (const auto& dispatcher : dispatchers) { + auto it = handles_.find(dispatcher.local_handle); + DCHECK(it != handles_.end() && it->second.busy); + it->second.busy = false; + dispatcher.dispatcher->CancelTransit(); + } +} + +void HandleTable::GetActiveHandlesForTest(std::vector* handles) { + handles->clear(); + for (const auto& entry : handles_) + handles->push_back(entry.first); +} + +HandleTable::Entry::Entry() {} + +HandleTable::Entry::Entry(scoped_refptr dispatcher) + : dispatcher(std::move(dispatcher)) {} + +HandleTable::Entry::Entry(const Entry& other) = default; + +HandleTable::Entry::~Entry() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/handle_table.h b/mojo/edk/system/handle_table.h new file mode 100644 index 0000000..882d540 --- /dev/null +++ b/mojo/edk/system/handle_table.h @@ -0,0 +1,75 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_HANDLE_TABLE_H_ +#define MOJO_EDK_SYSTEM_HANDLE_TABLE_H_ + +#include + +#include + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { + +class HandleTable { + public: + HandleTable(); + ~HandleTable(); + + MojoHandle AddDispatcher(scoped_refptr dispatcher); + + // Inserts multiple dispatchers received from message transit, populating + // |handles| with their newly allocated handles. Returns |true| on success. + bool AddDispatchersFromTransit( + const std::vector& dispatchers, + MojoHandle* handles); + + scoped_refptr GetDispatcher(MojoHandle handle) const; + MojoResult GetAndRemoveDispatcher(MojoHandle, + scoped_refptr* dispatcher); + + // Marks handles as busy and populates |dispatchers|. Returns MOJO_RESULT_BUSY + // if any of the handles are already in transit; MOJO_RESULT_INVALID_ARGUMENT + // if any of the handles are invalid; or MOJO_RESULT_OK if successful. + MojoResult BeginTransit( + const MojoHandle* handles, + uint32_t num_handles, + std::vector* dispatchers); + + void CompleteTransitAndClose( + const std::vector& dispatchers); + void CancelTransit( + const std::vector& dispatchers); + + void GetActiveHandlesForTest(std::vector *handles); + + private: + struct Entry { + Entry(); + explicit Entry(scoped_refptr dispatcher); + Entry(const Entry& other); + ~Entry(); + + scoped_refptr dispatcher; + bool busy = false; + }; + + using HandleMap = base::hash_map; + + HandleMap handles_; + + uint32_t next_available_handle_ = 1; + + DISALLOW_COPY_AND_ASSIGN(HandleTable); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_HANDLE_TABLE_H_ diff --git a/mojo/edk/system/mach_port_relay.cc b/mojo/edk/system/mach_port_relay.cc new file mode 100644 index 0000000..f05cf22 --- /dev/null +++ b/mojo/edk/system/mach_port_relay.cc @@ -0,0 +1,248 @@ +// 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/edk/system/mach_port_relay.h" + +#include + +#include + +#include "base/logging.h" +#include "base/mac/mach_port_util.h" +#include "base/mac/scoped_mach_port.h" +#include "base/metrics/histogram_macros.h" +#include "base/process/process.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +namespace { + +// Errors that can occur in the broker (privileged parent) process. +// These match tools/metrics/histograms.xml. +// This enum is append-only. +enum class BrokerUMAError : int { + SUCCESS = 0, + // Couldn't get a task port for the process with a given pid. + ERROR_TASK_FOR_PID = 1, + // Couldn't make a port with receive rights in the destination process. + ERROR_MAKE_RECEIVE_PORT = 2, + // Couldn't change the attributes of a Mach port. + ERROR_SET_ATTRIBUTES = 3, + // Couldn't extract a right from the destination. + ERROR_EXTRACT_DEST_RIGHT = 4, + // Couldn't send a Mach port in a call to mach_msg(). + ERROR_SEND_MACH_PORT = 5, + // Couldn't extract a right from the source. + ERROR_EXTRACT_SOURCE_RIGHT = 6, + ERROR_MAX +}; + +// Errors that can occur in a child process. +// These match tools/metrics/histograms.xml. +// This enum is append-only. +enum class ChildUMAError : int { + SUCCESS = 0, + // An error occurred while trying to receive a Mach port with mach_msg(). + ERROR_RECEIVE_MACH_MESSAGE = 1, + ERROR_MAX +}; + +void ReportBrokerError(BrokerUMAError error) { + UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.BrokerError", + static_cast(error), + static_cast(BrokerUMAError::ERROR_MAX)); +} + +void ReportChildError(ChildUMAError error) { + UMA_HISTOGRAM_ENUMERATION("Mojo.MachPortRelay.ChildError", + static_cast(error), + static_cast(ChildUMAError::ERROR_MAX)); +} + +} // namespace + +// static +bool MachPortRelay::ReceivePorts(PlatformHandleVector* handles) { + DCHECK(handles); + + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = handles->data() + i; + DCHECK(handle->type != PlatformHandle::Type::MACH); + if (handle->type != PlatformHandle::Type::MACH_NAME) + continue; + + if (handle->port == MACH_PORT_NULL) { + handle->type = PlatformHandle::Type::MACH; + continue; + } + + base::mac::ScopedMachReceiveRight message_port(handle->port); + base::mac::ScopedMachSendRight received_port( + base::ReceiveMachPort(message_port.get())); + if (received_port.get() == MACH_PORT_NULL) { + ReportChildError(ChildUMAError::ERROR_RECEIVE_MACH_MESSAGE); + handle->port = MACH_PORT_NULL; + LOG(ERROR) << "Error receiving mach port"; + return false; + } + + ReportChildError(ChildUMAError::SUCCESS); + handle->port = received_port.release(); + handle->type = PlatformHandle::Type::MACH; + } + + return true; +} + +MachPortRelay::MachPortRelay(base::PortProvider* port_provider) + : port_provider_(port_provider) { + DCHECK(port_provider); + port_provider_->AddObserver(this); +} + +MachPortRelay::~MachPortRelay() { + port_provider_->RemoveObserver(this); +} + +bool MachPortRelay::SendPortsToProcess(Channel::Message* message, + base::ProcessHandle process) { + DCHECK(message); + mach_port_t task_port = port_provider_->TaskForPid(process); + if (task_port == MACH_PORT_NULL) { + // Callers check the port provider for the task port before calling this + // function, in order to queue pending messages. Therefore, if this fails, + // it should be considered a genuine, bona fide, electrified, six-car error. + ReportBrokerError(BrokerUMAError::ERROR_TASK_FOR_PID); + return false; + } + + size_t num_sent = 0; + bool error = false; + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + // Message should have handles, otherwise there's no point in calling this + // function. + DCHECK(handles); + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = &(*handles)[i]; + DCHECK(handle->type != PlatformHandle::Type::MACH_NAME); + if (handle->type != PlatformHandle::Type::MACH) + continue; + + if (handle->port == MACH_PORT_NULL) { + handle->type = PlatformHandle::Type::MACH_NAME; + num_sent++; + continue; + } + + mach_port_name_t intermediate_port; + base::MachCreateError error_code; + intermediate_port = base::CreateIntermediateMachPort( + task_port, base::mac::ScopedMachSendRight(handle->port), &error_code); + if (intermediate_port == MACH_PORT_NULL) { + BrokerUMAError uma_error; + switch (error_code) { + case base::MachCreateError::ERROR_MAKE_RECEIVE_PORT: + uma_error = BrokerUMAError::ERROR_MAKE_RECEIVE_PORT; + break; + case base::MachCreateError::ERROR_SET_ATTRIBUTES: + uma_error = BrokerUMAError::ERROR_SET_ATTRIBUTES; + break; + case base::MachCreateError::ERROR_EXTRACT_DEST_RIGHT: + uma_error = BrokerUMAError::ERROR_EXTRACT_DEST_RIGHT; + break; + case base::MachCreateError::ERROR_SEND_MACH_PORT: + uma_error = BrokerUMAError::ERROR_SEND_MACH_PORT; + break; + } + ReportBrokerError(uma_error); + handle->port = MACH_PORT_NULL; + error = true; + break; + } + + ReportBrokerError(BrokerUMAError::SUCCESS); + handle->port = intermediate_port; + handle->type = PlatformHandle::Type::MACH_NAME; + num_sent++; + } + DCHECK(error || num_sent); + message->SetHandles(std::move(handles)); + + return !error; +} + +bool MachPortRelay::ExtractPortRights(Channel::Message* message, + base::ProcessHandle process) { + DCHECK(message); + + mach_port_t task_port = port_provider_->TaskForPid(process); + if (task_port == MACH_PORT_NULL) { + ReportBrokerError(BrokerUMAError::ERROR_TASK_FOR_PID); + return false; + } + + size_t num_received = 0; + bool error = false; + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + // Message should have handles, otherwise there's no point in calling this + // function. + DCHECK(handles); + for (size_t i = 0; i < handles->size(); i++) { + PlatformHandle* handle = handles->data() + i; + DCHECK(handle->type != PlatformHandle::Type::MACH); + if (handle->type != PlatformHandle::Type::MACH_NAME) + continue; + + if (handle->port == MACH_PORT_NULL) { + handle->type = PlatformHandle::Type::MACH; + num_received++; + continue; + } + + mach_port_t extracted_right = MACH_PORT_NULL; + mach_msg_type_name_t extracted_right_type; + kern_return_t kr = + mach_port_extract_right(task_port, handle->port, + MACH_MSG_TYPE_MOVE_SEND, + &extracted_right, &extracted_right_type); + if (kr != KERN_SUCCESS) { + ReportBrokerError(BrokerUMAError::ERROR_EXTRACT_SOURCE_RIGHT); + error = true; + break; + } + + ReportBrokerError(BrokerUMAError::SUCCESS); + DCHECK_EQ(static_cast(MACH_MSG_TYPE_PORT_SEND), + extracted_right_type); + handle->port = extracted_right; + handle->type = PlatformHandle::Type::MACH; + num_received++; + } + DCHECK(error || num_received); + message->SetHandles(std::move(handles)); + + return !error; +} + +void MachPortRelay::AddObserver(Observer* observer) { + base::AutoLock locker(observers_lock_); + bool inserted = observers_.insert(observer).second; + DCHECK(inserted); +} + +void MachPortRelay::RemoveObserver(Observer* observer) { + base::AutoLock locker(observers_lock_); + observers_.erase(observer); +} + +void MachPortRelay::OnReceivedTaskPort(base::ProcessHandle process) { + base::AutoLock locker(observers_lock_); + for (auto* observer : observers_) + observer->OnProcessReady(process); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/mach_port_relay.h b/mojo/edk/system/mach_port_relay.h new file mode 100644 index 0000000..87bc56c --- /dev/null +++ b/mojo/edk/system/mach_port_relay.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_EDK_SYSTEM_MACH_PORT_RELAY_H_ +#define MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_ + +#include + +#include "base/macros.h" +#include "base/process/port_provider_mac.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/channel.h" + +namespace mojo { +namespace edk { + +// The MachPortRelay is used by a privileged process, usually the root process, +// to manipulate Mach ports in a child process. Ports can be added to and +// extracted from a child process that has registered itself with the +// |base::PortProvider| used by this class. +class MachPortRelay : public base::PortProvider::Observer { + public: + class Observer { + public: + // Called by the MachPortRelay to notify observers that a new process is + // ready for Mach ports to be sent/received. There are no guarantees about + // the thread this is called on, including the presence of a MessageLoop. + // Implementations must not call AddObserver() or RemoveObserver() during + // this function, as doing so will deadlock. + virtual void OnProcessReady(base::ProcessHandle process) = 0; + }; + + // Used by a child process to receive Mach ports from a sender (privileged) + // process. Each Mach port in |handles| is interpreted as an intermediate Mach + // port. It replaces each Mach port with the final Mach port received from the + // intermediate port. This method takes ownership of the intermediate Mach + // port and gives ownership of the final Mach port to the caller. Any handles + // that are not Mach ports will remain unchanged, and the number and ordering + // of handles is preserved. + // Returns |false| on failure and there is no guarantee about whether a Mach + // port is intermediate or final. + // + // See SendPortsToProcess() for the definition of intermediate and final Mach + // ports. + static bool ReceivePorts(PlatformHandleVector* handles); + + explicit MachPortRelay(base::PortProvider* port_provider); + ~MachPortRelay() override; + + // Sends the Mach ports attached to |message| to |process|. + // For each Mach port attached to |message|, a new Mach port, the intermediate + // port, is created in |process|. The message's Mach port is then sent over + // this intermediate port and the message is modified to refer to the name of + // the intermediate port. The Mach port received over the intermediate port in + // the child is referred to as the final Mach port. + // Returns |false| on failure and |message| may contain a mix of actual Mach + // ports and names. + bool SendPortsToProcess(Channel::Message* message, + base::ProcessHandle process); + + // Extracts the Mach ports attached to |message| from |process|. + // Any Mach ports attached to |message| are names and not actual Mach ports + // that are valid in this process. For each of those Mach port names, a send + // right is extracted from |process| and the port name is replaced with the + // send right. + // Returns |false| on failure and |message| may contain a mix of actual Mach + // ports and names. + bool ExtractPortRights(Channel::Message* message, + base::ProcessHandle process); + + // Observer interface. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + + base::PortProvider* port_provider() const { return port_provider_; } + + private: + // base::PortProvider::Observer implementation. + void OnReceivedTaskPort(base::ProcessHandle process) override; + + base::PortProvider* const port_provider_; + + base::Lock observers_lock_; + std::set observers_; + + DISALLOW_COPY_AND_ASSIGN(MachPortRelay); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MACH_PORT_RELAY_H_ diff --git a/mojo/edk/system/mapping_table.cc b/mojo/edk/system/mapping_table.cc new file mode 100644 index 0000000..8509443 --- /dev/null +++ b/mojo/edk/system/mapping_table.cc @@ -0,0 +1,48 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/mapping_table.h" + +#include "base/logging.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/configuration.h" + +namespace mojo { +namespace edk { + +MappingTable::MappingTable() { +} + +MappingTable::~MappingTable() { + // This should usually not be reached (the only instance should be owned by + // the singleton |Core|, which lives forever), except in tests. +} + +MojoResult MappingTable::AddMapping( + std::unique_ptr mapping) { + DCHECK(mapping); + + if (address_to_mapping_map_.size() >= + GetConfiguration().max_mapping_table_sze) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + void* address = mapping->GetBase(); + DCHECK(address_to_mapping_map_.find(address) == + address_to_mapping_map_.end()); + address_to_mapping_map_[address] = mapping.release(); + return MOJO_RESULT_OK; +} + +MojoResult MappingTable::RemoveMapping(void* address) { + AddressToMappingMap::iterator it = address_to_mapping_map_.find(address); + if (it == address_to_mapping_map_.end()) + return MOJO_RESULT_INVALID_ARGUMENT; + PlatformSharedBufferMapping* mapping_to_delete = it->second; + address_to_mapping_map_.erase(it); + delete mapping_to_delete; + return MOJO_RESULT_OK; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/mapping_table.h b/mojo/edk/system/mapping_table.h new file mode 100644 index 0000000..00167e3 --- /dev/null +++ b/mojo/edk/system/mapping_table.h @@ -0,0 +1,57 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_MAPPING_TABLE_H_ +#define MOJO_EDK_SYSTEM_MAPPING_TABLE_H_ + +#include + +#include +#include + +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { + +namespace edk { +class Core; +class PlatformSharedBufferMapping; + +// Test-only function (defined/used in embedder/test_embedder.cc). Declared here +// so it can be friended. +namespace internal { +bool ShutdownCheckNoLeaks(Core*); +} + +// This class provides the (global) table of memory mappings (owned by |Core|), +// which maps mapping base addresses to |PlatformSharedBufferMapping|s. +// +// This class is NOT thread-safe; locking is left to |Core|. +class MOJO_SYSTEM_IMPL_EXPORT MappingTable { + public: + MappingTable(); + ~MappingTable(); + + // Tries to add a mapping. (Takes ownership of the mapping in all cases; on + // failure, it will be destroyed.) + MojoResult AddMapping(std::unique_ptr mapping); + MojoResult RemoveMapping(void* address); + + private: + friend bool internal::ShutdownCheckNoLeaks(Core*); + + using AddressToMappingMap = + base::hash_map; + AddressToMappingMap address_to_mapping_map_; + + DISALLOW_COPY_AND_ASSIGN(MappingTable); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MAPPING_TABLE_H_ diff --git a/mojo/edk/system/message_for_transit.cc b/mojo/edk/system/message_for_transit.cc new file mode 100644 index 0000000..26658e1 --- /dev/null +++ b/mojo/edk/system/message_for_transit.cc @@ -0,0 +1,136 @@ +// 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/edk/system/message_for_transit.h" + +#include + +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +namespace { + +static_assert(sizeof(MessageForTransit::MessageHeader) % 8 == 0, + "Invalid MessageHeader size."); +static_assert(sizeof(MessageForTransit::DispatcherHeader) % 8 == 0, + "Invalid DispatcherHeader size."); + +} // namespace + +MessageForTransit::~MessageForTransit() {} + +// static +MojoResult MessageForTransit::Create( + std::unique_ptr* message, + uint32_t num_bytes, + const Dispatcher::DispatcherInTransit* dispatchers, + uint32_t num_dispatchers) { + // A structure for retaining information about every Dispatcher that will be + // sent with this message. + struct DispatcherInfo { + uint32_t num_bytes; + uint32_t num_ports; + uint32_t num_handles; + }; + + // This is only the base header size. It will grow as we accumulate the + // size of serialized state for each dispatcher. + size_t header_size = sizeof(MessageHeader) + + num_dispatchers * sizeof(DispatcherHeader); + size_t num_ports = 0; + size_t num_handles = 0; + + std::vector dispatcher_info(num_dispatchers); + for (size_t i = 0; i < num_dispatchers; ++i) { + Dispatcher* d = dispatchers[i].dispatcher.get(); + d->StartSerialize(&dispatcher_info[i].num_bytes, + &dispatcher_info[i].num_ports, + &dispatcher_info[i].num_handles); + header_size += dispatcher_info[i].num_bytes; + num_ports += dispatcher_info[i].num_ports; + num_handles += dispatcher_info[i].num_handles; + } + + // We now have enough information to fully allocate the message storage. + std::unique_ptr msg = PortsMessage::NewUserMessage( + header_size + num_bytes, num_ports, num_handles); + if (!msg) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + // Populate the message header with information about serialized dispatchers. + // + // The front of the message is always a MessageHeader followed by a + // DispatcherHeader for each dispatcher to be sent. + MessageHeader* header = + static_cast(msg->mutable_payload_bytes()); + DispatcherHeader* dispatcher_headers = + reinterpret_cast(header + 1); + + // Serialized dispatcher state immediately follows the series of + // DispatcherHeaders. + char* dispatcher_data = + reinterpret_cast(dispatcher_headers + num_dispatchers); + + header->num_dispatchers = num_dispatchers; + + // |header_size| is the total number of bytes preceding the message payload, + // including all dispatcher headers and serialized dispatcher state. + DCHECK_LE(header_size, std::numeric_limits::max()); + header->header_size = static_cast(header_size); + + if (num_dispatchers > 0) { + ScopedPlatformHandleVectorPtr handles( + new PlatformHandleVector(num_handles)); + size_t port_index = 0; + size_t handle_index = 0; + bool fail = false; + for (size_t i = 0; i < num_dispatchers; ++i) { + Dispatcher* d = dispatchers[i].dispatcher.get(); + DispatcherHeader* dh = &dispatcher_headers[i]; + const DispatcherInfo& info = dispatcher_info[i]; + + // Fill in the header for this dispatcher. + dh->type = static_cast(d->GetType()); + dh->num_bytes = info.num_bytes; + dh->num_ports = info.num_ports; + dh->num_platform_handles = info.num_handles; + + // Fill in serialized state, ports, and platform handles. We'll cancel + // the send if the dispatcher implementation rejects for some reason. + if (!d->EndSerialize(static_cast(dispatcher_data), + msg->mutable_ports() + port_index, + handles->data() + handle_index)) { + fail = true; + break; + } + + dispatcher_data += info.num_bytes; + port_index += info.num_ports; + handle_index += info.num_handles; + } + + if (fail) { + // Release any platform handles we've accumulated. Their dispatchers + // retain ownership when message creation fails, so these are not actually + // leaking. + handles->clear(); + return MOJO_RESULT_INVALID_ARGUMENT; + } + + // Take ownership of all the handles and move them into message storage. + msg->SetHandles(std::move(handles)); + } + + message->reset(new MessageForTransit(std::move(msg))); + return MOJO_RESULT_OK; +} + +MessageForTransit::MessageForTransit(std::unique_ptr message) + : message_(std::move(message)) { +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/message_for_transit.h b/mojo/edk/system/message_for_transit.h new file mode 100644 index 0000000..6103a77 --- /dev/null +++ b/mojo/edk/system/message_for_transit.h @@ -0,0 +1,115 @@ +// 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_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_ +#define MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_ + +#include + +#include + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +// MessageForTransit holds onto a PortsMessage which may be sent via +// |MojoWriteMessage()| or which may have been received on a pipe endpoint. +// Instances of this class are exposed to Mojo system API consumers via the +// opaque pointers used with |MojoCreateMessage()|, |MojoDestroyMessage()|, +// |MojoWriteMessageNew()|, and |MojoReadMessageNew()|. +class MOJO_SYSTEM_IMPL_EXPORT MessageForTransit { + public: +#pragma pack(push, 1) + // Header attached to every message. + struct MessageHeader { + // The number of serialized dispatchers included in this header. + uint32_t num_dispatchers; + + // Total size of the header, including serialized dispatcher data. + uint32_t header_size; + }; + + // Header for each dispatcher in a message, immediately following the message + // header. + struct DispatcherHeader { + // The type of the dispatcher, correpsonding to the Dispatcher::Type enum. + int32_t type; + + // The size of the serialized dispatcher, not including this header. + uint32_t num_bytes; + + // The number of ports needed to deserialize this dispatcher. + uint32_t num_ports; + + // The number of platform handles needed to deserialize this dispatcher. + uint32_t num_platform_handles; + }; +#pragma pack(pop) + + ~MessageForTransit(); + + // A static constructor for building outbound messages. + static MojoResult Create( + std::unique_ptr* message, + uint32_t num_bytes, + const Dispatcher::DispatcherInTransit* dispatchers, + uint32_t num_dispatchers); + + // A static constructor for wrapping inbound messages. + static std::unique_ptr WrapPortsMessage( + std::unique_ptr message) { + return base::WrapUnique(new MessageForTransit(std::move(message))); + } + + const void* bytes() const { + DCHECK(message_); + return static_cast( + static_cast(message_->payload_bytes()) + + header()->header_size); + } + + void* mutable_bytes() { + DCHECK(message_); + return static_cast( + static_cast(message_->mutable_payload_bytes()) + + header()->header_size); + } + + size_t num_bytes() const { + size_t header_size = header()->header_size; + DCHECK_GE(message_->num_payload_bytes(), header_size); + return message_->num_payload_bytes() - header_size; + } + + size_t num_handles() const { return header()->num_dispatchers; } + + const PortsMessage& ports_message() const { return *message_; } + + std::unique_ptr TakePortsMessage() { + return std::move(message_); + } + + private: + explicit MessageForTransit(std::unique_ptr message); + + const MessageForTransit::MessageHeader* header() const { + DCHECK(message_); + return static_cast( + message_->payload_bytes()); + } + + std::unique_ptr message_; + + DISALLOW_COPY_AND_ASSIGN(MessageForTransit); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MESSAGE_FOR_TRANSIT_H_ diff --git a/mojo/edk/system/message_pipe_dispatcher.cc b/mojo/edk/system/message_pipe_dispatcher.cc new file mode 100644 index 0000000..1db56c0 --- /dev/null +++ b/mojo/edk/system/message_pipe_dispatcher.cc @@ -0,0 +1,554 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/message_pipe_dispatcher.h" + +#include +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/message_for_transit.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/ports/message_filter.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/request_context.h" + +namespace mojo { +namespace edk { + +namespace { + +using DispatcherHeader = MessageForTransit::DispatcherHeader; +using MessageHeader = MessageForTransit::MessageHeader; + +#pragma pack(push, 1) + +struct SerializedState { + uint64_t pipe_id; + int8_t endpoint; + char padding[7]; +}; + +static_assert(sizeof(SerializedState) % 8 == 0, + "Invalid SerializedState size."); + +#pragma pack(pop) + +} // namespace + +// A PortObserver which forwards to a MessagePipeDispatcher. This owns a +// reference to the MPD to ensure it lives as long as the observed port. +class MessagePipeDispatcher::PortObserverThunk + : public NodeController::PortObserver { + public: + explicit PortObserverThunk(scoped_refptr dispatcher) + : dispatcher_(dispatcher) {} + + private: + ~PortObserverThunk() override {} + + // NodeController::PortObserver: + void OnPortStatusChanged() override { dispatcher_->OnPortStatusChanged(); } + + scoped_refptr dispatcher_; + + DISALLOW_COPY_AND_ASSIGN(PortObserverThunk); +}; + +// A MessageFilter used by ReadMessage to determine whether a message should +// actually be consumed yet. +class ReadMessageFilter : public ports::MessageFilter { + public: + // Creates a new ReadMessageFilter which captures and potentially modifies + // various (unowned) local state within MessagePipeDispatcher::ReadMessage. + ReadMessageFilter(bool read_any_size, + bool may_discard, + uint32_t* num_bytes, + uint32_t* num_handles, + bool* no_space, + bool* invalid_message) + : read_any_size_(read_any_size), + may_discard_(may_discard), + num_bytes_(num_bytes), + num_handles_(num_handles), + no_space_(no_space), + invalid_message_(invalid_message) {} + + ~ReadMessageFilter() override {} + + // ports::MessageFilter: + bool Match(const ports::Message& m) override { + const PortsMessage& message = static_cast(m); + if (message.num_payload_bytes() < sizeof(MessageHeader)) { + *invalid_message_ = true; + return true; + } + + const MessageHeader* header = + static_cast(message.payload_bytes()); + if (header->header_size > message.num_payload_bytes()) { + *invalid_message_ = true; + return true; + } + + uint32_t bytes_to_read = 0; + uint32_t bytes_available = + static_cast(message.num_payload_bytes()) - + header->header_size; + if (num_bytes_) { + bytes_to_read = std::min(*num_bytes_, bytes_available); + *num_bytes_ = bytes_available; + } + + uint32_t handles_to_read = 0; + uint32_t handles_available = header->num_dispatchers; + if (num_handles_) { + handles_to_read = std::min(*num_handles_, handles_available); + *num_handles_ = handles_available; + } + + if (handles_to_read < handles_available || + (!read_any_size_ && bytes_to_read < bytes_available)) { + *no_space_ = true; + return may_discard_; + } + + return true; + } + + private: + const bool read_any_size_; + const bool may_discard_; + uint32_t* const num_bytes_; + uint32_t* const num_handles_; + bool* const no_space_; + bool* const invalid_message_; + + DISALLOW_COPY_AND_ASSIGN(ReadMessageFilter); +}; + +#if DCHECK_IS_ON() + +// A MessageFilter which never matches a message. Used to peek at the size of +// the next available message on a port, for debug logging only. +class PeekSizeMessageFilter : public ports::MessageFilter { + public: + PeekSizeMessageFilter() {} + ~PeekSizeMessageFilter() override {} + + // ports::MessageFilter: + bool Match(const ports::Message& message) override { + message_size_ = message.num_payload_bytes(); + return false; + } + + size_t message_size() const { return message_size_; } + + private: + size_t message_size_ = 0; + + DISALLOW_COPY_AND_ASSIGN(PeekSizeMessageFilter); +}; + +#endif // DCHECK_IS_ON() + +MessagePipeDispatcher::MessagePipeDispatcher(NodeController* node_controller, + const ports::PortRef& port, + uint64_t pipe_id, + int endpoint) + : node_controller_(node_controller), + port_(port), + pipe_id_(pipe_id), + endpoint_(endpoint), + watchers_(this) { + DVLOG(2) << "Creating new MessagePipeDispatcher for port " << port.name() + << " [pipe_id=" << pipe_id << "; endpoint=" << endpoint << "]"; + + node_controller_->SetPortObserver( + port_, + make_scoped_refptr(new PortObserverThunk(this))); +} + +bool MessagePipeDispatcher::Fuse(MessagePipeDispatcher* other) { + node_controller_->SetPortObserver(port_, nullptr); + node_controller_->SetPortObserver(other->port_, nullptr); + + ports::PortRef port0; + { + base::AutoLock lock(signal_lock_); + port0 = port_; + port_closed_.Set(true); + watchers_.NotifyClosed(); + } + + ports::PortRef port1; + { + base::AutoLock lock(other->signal_lock_); + port1 = other->port_; + other->port_closed_.Set(true); + other->watchers_.NotifyClosed(); + } + + // Both ports are always closed by this call. + int rv = node_controller_->MergeLocalPorts(port0, port1); + return rv == ports::OK; +} + +Dispatcher::Type MessagePipeDispatcher::GetType() const { + return Type::MESSAGE_PIPE; +} + +MojoResult MessagePipeDispatcher::Close() { + base::AutoLock lock(signal_lock_); + DVLOG(2) << "Closing message pipe " << pipe_id_ << " endpoint " << endpoint_ + << " [port=" << port_.name() << "]"; + return CloseNoLock(); +} + +MojoResult MessagePipeDispatcher::WriteMessage( + std::unique_ptr message, + MojoWriteMessageFlags flags) { + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + size_t num_bytes = message->num_bytes(); + int rv = node_controller_->SendMessage(port_, message->TakePortsMessage()); + + DVLOG(4) << "Sent message on pipe " << pipe_id_ << " endpoint " << endpoint_ + << " [port=" << port_.name() << "; rv=" << rv + << "; num_bytes=" << num_bytes << "]"; + + if (rv != ports::OK) { + if (rv == ports::ERROR_PORT_UNKNOWN || + rv == ports::ERROR_PORT_STATE_UNEXPECTED || + rv == ports::ERROR_PORT_CANNOT_SEND_PEER) { + return MOJO_RESULT_INVALID_ARGUMENT; + } else if (rv == ports::ERROR_PORT_PEER_CLOSED) { + return MOJO_RESULT_FAILED_PRECONDITION; + } + + NOTREACHED(); + return MOJO_RESULT_UNKNOWN; + } + + return MOJO_RESULT_OK; +} + +MojoResult MessagePipeDispatcher::ReadMessage( + std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags, + bool read_any_size) { + // We can't read from a port that's closed or in transit! + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + bool no_space = false; + bool may_discard = flags & MOJO_READ_MESSAGE_FLAG_MAY_DISCARD; + bool invalid_message = false; + + // Grab a message if the provided handles buffer is large enough. If the input + // |num_bytes| is provided and |read_any_size| is false, we also ensure + // that it specifies a size at least as large as the next available payload. + // + // If |read_any_size| is true, the input value of |*num_bytes| is ignored. + // This flag exists to support both new and old API behavior. + + ports::ScopedMessage ports_message; + ReadMessageFilter filter(read_any_size, may_discard, num_bytes, num_handles, + &no_space, &invalid_message); + int rv = node_controller_->node()->GetMessage(port_, &ports_message, &filter); + + if (invalid_message) + return MOJO_RESULT_UNKNOWN; + + if (rv != ports::OK && rv != ports::ERROR_PORT_PEER_CLOSED) { + if (rv == ports::ERROR_PORT_UNKNOWN || + rv == ports::ERROR_PORT_STATE_UNEXPECTED) + return MOJO_RESULT_INVALID_ARGUMENT; + + NOTREACHED(); + return MOJO_RESULT_UNKNOWN; // TODO: Add a better error code here? + } + + if (no_space) { + if (may_discard) { + // May have been the last message on the pipe. Need to update signals just + // in case. + base::AutoLock lock(signal_lock_); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + } + // |*num_handles| (and/or |*num_bytes| if |read_any_size| is false) wasn't + // sufficient to hold this message's data. The message will still be in + // queue unless MOJO_READ_MESSAGE_FLAG_MAY_DISCARD was set. + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + if (!ports_message) { + // No message was available in queue. + + if (rv == ports::OK) + return MOJO_RESULT_SHOULD_WAIT; + + // Peer is closed and there are no more messages to read. + DCHECK_EQ(rv, ports::ERROR_PORT_PEER_CLOSED); + return MOJO_RESULT_FAILED_PRECONDITION; + } + + // Alright! We have a message and the caller has provided sufficient storage + // in which to receive it. + + { + // We need to update anyone watching our signals in case that was the last + // available message. + base::AutoLock lock(signal_lock_); + watchers_.NotifyState(GetHandleSignalsStateNoLock()); + } + + std::unique_ptr msg( + static_cast(ports_message.release())); + + const MessageHeader* header = + static_cast(msg->payload_bytes()); + const DispatcherHeader* dispatcher_headers = + reinterpret_cast(header + 1); + + if (header->num_dispatchers > std::numeric_limits::max()) + return MOJO_RESULT_UNKNOWN; + + // Deserialize dispatchers. + if (header->num_dispatchers > 0) { + CHECK(handles); + std::vector dispatchers(header->num_dispatchers); + size_t data_payload_index = sizeof(MessageHeader) + + header->num_dispatchers * sizeof(DispatcherHeader); + if (data_payload_index > header->header_size) + return MOJO_RESULT_UNKNOWN; + const char* dispatcher_data = reinterpret_cast( + dispatcher_headers + header->num_dispatchers); + size_t port_index = 0; + size_t platform_handle_index = 0; + ScopedPlatformHandleVectorPtr msg_handles = msg->TakeHandles(); + const size_t num_msg_handles = msg_handles ? msg_handles->size() : 0; + for (size_t i = 0; i < header->num_dispatchers; ++i) { + const DispatcherHeader& dh = dispatcher_headers[i]; + Type type = static_cast(dh.type); + + size_t next_payload_index = data_payload_index + dh.num_bytes; + if (msg->num_payload_bytes() < next_payload_index || + next_payload_index < data_payload_index) { + return MOJO_RESULT_UNKNOWN; + } + + size_t next_port_index = port_index + dh.num_ports; + if (msg->num_ports() < next_port_index || next_port_index < port_index) + return MOJO_RESULT_UNKNOWN; + + size_t next_platform_handle_index = + platform_handle_index + dh.num_platform_handles; + if (num_msg_handles < next_platform_handle_index || + next_platform_handle_index < platform_handle_index) { + return MOJO_RESULT_UNKNOWN; + } + + PlatformHandle* out_handles = + num_msg_handles ? msg_handles->data() + platform_handle_index + : nullptr; + dispatchers[i].dispatcher = Dispatcher::Deserialize( + type, dispatcher_data, dh.num_bytes, msg->ports() + port_index, + dh.num_ports, out_handles, dh.num_platform_handles); + if (!dispatchers[i].dispatcher) + return MOJO_RESULT_UNKNOWN; + + dispatcher_data += dh.num_bytes; + data_payload_index = next_payload_index; + port_index = next_port_index; + platform_handle_index = next_platform_handle_index; + } + + if (!node_controller_->core()->AddDispatchersFromTransit(dispatchers, + handles)) + return MOJO_RESULT_UNKNOWN; + } + + CHECK(msg); + *message = MessageForTransit::WrapPortsMessage(std::move(msg)); + return MOJO_RESULT_OK; +} + +HandleSignalsState +MessagePipeDispatcher::GetHandleSignalsState() const { + base::AutoLock lock(signal_lock_); + return GetHandleSignalsStateNoLock(); +} + +MojoResult MessagePipeDispatcher::AddWatcherRef( + const scoped_refptr& watcher, + uintptr_t context) { + base::AutoLock lock(signal_lock_); + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Add(watcher, context, GetHandleSignalsStateNoLock()); +} + +MojoResult MessagePipeDispatcher::RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) { + base::AutoLock lock(signal_lock_); + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + return watchers_.Remove(watcher, context); +} + +void MessagePipeDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) { + *num_bytes = static_cast(sizeof(SerializedState)); + *num_ports = 1; + *num_handles = 0; +} + +bool MessagePipeDispatcher::EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) { + SerializedState* state = static_cast(destination); + state->pipe_id = pipe_id_; + state->endpoint = static_cast(endpoint_); + memset(state->padding, 0, sizeof(state->padding)); + ports[0] = port_.name(); + return true; +} + +bool MessagePipeDispatcher::BeginTransit() { + base::AutoLock lock(signal_lock_); + if (in_transit_ || port_closed_) + return false; + in_transit_.Set(true); + return in_transit_; +} + +void MessagePipeDispatcher::CompleteTransitAndClose() { + node_controller_->SetPortObserver(port_, nullptr); + + base::AutoLock lock(signal_lock_); + port_transferred_ = true; + in_transit_.Set(false); + CloseNoLock(); +} + +void MessagePipeDispatcher::CancelTransit() { + base::AutoLock lock(signal_lock_); + in_transit_.Set(false); + + // Something may have happened while we were waiting for potential transit. + watchers_.NotifyState(GetHandleSignalsStateNoLock()); +} + +// static +scoped_refptr MessagePipeDispatcher::Deserialize( + const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles) { + if (num_ports != 1 || num_handles || num_bytes != sizeof(SerializedState)) + return nullptr; + + const SerializedState* state = static_cast(data); + + ports::PortRef port; + CHECK_EQ( + ports::OK, + internal::g_core->GetNodeController()->node()->GetPort(ports[0], &port)); + + return new MessagePipeDispatcher(internal::g_core->GetNodeController(), port, + state->pipe_id, state->endpoint); +} + +MessagePipeDispatcher::~MessagePipeDispatcher() { + DCHECK(port_closed_ && !in_transit_); +} + +MojoResult MessagePipeDispatcher::CloseNoLock() { + signal_lock_.AssertAcquired(); + if (port_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + port_closed_.Set(true); + watchers_.NotifyClosed(); + + if (!port_transferred_) { + base::AutoUnlock unlock(signal_lock_); + node_controller_->ClosePort(port_); + } + + return MOJO_RESULT_OK; +} + +HandleSignalsState MessagePipeDispatcher::GetHandleSignalsStateNoLock() const { + HandleSignalsState rv; + + ports::PortStatus port_status; + if (node_controller_->node()->GetStatus(port_, &port_status) != ports::OK) { + CHECK(in_transit_ || port_transferred_ || port_closed_); + return HandleSignalsState(); + } + + if (port_status.has_messages) { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_READABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } + if (port_status.receiving_messages) + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + if (!port_status.peer_closed) { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_WRITABLE; + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_READABLE; + } else { + rv.satisfied_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + } + rv.satisfiable_signals |= MOJO_HANDLE_SIGNAL_PEER_CLOSED; + return rv; +} + +void MessagePipeDispatcher::OnPortStatusChanged() { + DCHECK(RequestContext::current()); + + base::AutoLock lock(signal_lock_); + + // We stop observing our port as soon as it's transferred, but this can race + // with events which are raised right before that happens. This is fine to + // ignore. + if (port_transferred_) + return; + +#if DCHECK_IS_ON() + ports::PortStatus port_status; + if (node_controller_->node()->GetStatus(port_, &port_status) == ports::OK) { + if (port_status.has_messages) { + ports::ScopedMessage unused; + PeekSizeMessageFilter filter; + node_controller_->node()->GetMessage(port_, &unused, &filter); + DVLOG(4) << "New message detected on message pipe " << pipe_id_ + << " endpoint " << endpoint_ << " [port=" << port_.name() + << "; size=" << filter.message_size() << "]"; + } + if (port_status.peer_closed) { + DVLOG(2) << "Peer closure detected on message pipe " << pipe_id_ + << " endpoint " << endpoint_ << " [port=" << port_.name() << "]"; + } + } +#endif + + watchers_.NotifyState(GetHandleSignalsStateNoLock()); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/message_pipe_dispatcher.h b/mojo/edk/system/message_pipe_dispatcher.h new file mode 100644 index 0000000..574ad66 --- /dev/null +++ b/mojo/edk/system/message_pipe_dispatcher.h @@ -0,0 +1,115 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ + +#include + +#include +#include + +#include "base/macros.h" +#include "mojo/edk/system/atomic_flag.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/message_for_transit.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/watcher_set.h" + +namespace mojo { +namespace edk { + +class NodeController; + +class MessagePipeDispatcher : public Dispatcher { + public: + // Constructs a MessagePipeDispatcher permanently tied to a specific port. + // |connected| must indicate the state of the port at construction time; if + // the port is initialized with a peer, |connected| must be true. Otherwise it + // must be false. + // + // A MessagePipeDispatcher may not be transferred while in a disconnected + // state, and one can never return to a disconnected once connected. + // + // |pipe_id| is a unique identifier which can be used to track pipe endpoints + // as they're passed around. |endpoint| is either 0 or 1 and again is only + // used for tracking pipes (one side is always 0, the other is always 1.) + MessagePipeDispatcher(NodeController* node_controller, + const ports::PortRef& port, + uint64_t pipe_id, + int endpoint); + + // Fuses this pipe with |other|. Returns |true| on success or |false| on + // failure. Regardless of the return value, both dispatchers are closed by + // this call. + bool Fuse(MessagePipeDispatcher* other); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult WriteMessage(std::unique_ptr message, + MojoWriteMessageFlags flags) override; + MojoResult ReadMessage(std::unique_ptr* message, + uint32_t* num_bytes, + MojoHandle* handles, + uint32_t* num_handles, + MojoReadMessageFlags flags, + bool read_any_size) override; + HandleSignalsState GetHandleSignalsState() const override; + MojoResult AddWatcherRef(const scoped_refptr& watcher, + uintptr_t context) override; + MojoResult RemoveWatcherRef(WatcherDispatcher* watcher, + uintptr_t context) override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + static scoped_refptr Deserialize( + const void* data, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles); + + private: + class PortObserverThunk; + friend class PortObserverThunk; + + ~MessagePipeDispatcher() override; + + MojoResult CloseNoLock(); + HandleSignalsState GetHandleSignalsStateNoLock() const; + void OnPortStatusChanged(); + + // These are safe to access from any thread without locking. + NodeController* const node_controller_; + const ports::PortRef port_; + const uint64_t pipe_id_; + const int endpoint_; + + // Guards access to all the fields below. + mutable base::Lock signal_lock_; + + // This is not the same is |port_transferred_|. It's only held true between + // BeginTransit() and Complete/CancelTransit(). + AtomicFlag in_transit_; + + bool port_transferred_ = false; + AtomicFlag port_closed_; + WatcherSet watchers_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipeDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_MESSAGE_PIPE_DISPATCHER_H_ diff --git a/mojo/edk/system/message_pipe_perftest.cc b/mojo/edk/system/message_pipe_perftest.cc new file mode 100644 index 0000000..9866c47 --- /dev/null +++ b/mojo/edk/system/message_pipe_perftest.cc @@ -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. + +#include +#include + +#include +#include + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/stringprintf.h" +#include "base/test/perf_time_logger.h" +#include "base/threading/thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/edk/test/test_utils.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +class MessagePipePerfTest : public test::MojoTestBase { + public: + MessagePipePerfTest() : message_count_(0), message_size_(0) {} + + void SetUpMeasurement(int message_count, size_t message_size) { + message_count_ = message_count; + message_size_ = message_size; + payload_ = std::string(message_size, '*'); + read_buffer_.resize(message_size * 2); + } + + protected: + void WriteWaitThenRead(MojoHandle mp) { + CHECK_EQ(MojoWriteMessage(mp, payload_.data(), + static_cast(payload_.size()), nullptr, + 0, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + uint32_t read_buffer_size = static_cast(read_buffer_.size()); + CHECK_EQ(MojoReadMessage(mp, &read_buffer_[0], &read_buffer_size, nullptr, + nullptr, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(read_buffer_size, static_cast(payload_.size())); + } + + void SendQuitMessage(MojoHandle mp) { + CHECK_EQ(MojoWriteMessage(mp, "", 0, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + } + + void Measure(MojoHandle mp) { + // Have one ping-pong to ensure channel being established. + WriteWaitThenRead(mp); + + std::string test_name = + base::StringPrintf("IPC_Perf_%dx_%u", message_count_, + static_cast(message_size_)); + base::PerfTimeLogger logger(test_name.c_str()); + + for (int i = 0; i < message_count_; ++i) + WriteWaitThenRead(mp); + + logger.Done(); + } + + protected: + void RunPingPongServer(MojoHandle mp) { + // This values are set to align with one at ipc_pertests.cc for comparison. + const size_t kMsgSize[5] = {12, 144, 1728, 20736, 248832}; + const int kMessageCount[5] = {50000, 50000, 50000, 12000, 1000}; + + for (size_t i = 0; i < 5; i++) { + SetUpMeasurement(kMessageCount[i], kMsgSize[i]); + Measure(mp); + } + + SendQuitMessage(mp); + } + + static int RunPingPongClient(MojoHandle mp) { + std::string buffer(1000000, '\0'); + int rv = 0; + while (true) { + // Wait for our end of the message pipe to be readable. + HandleSignalsState hss; + MojoResult result = WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE, &hss); + if (result != MOJO_RESULT_OK) { + rv = result; + break; + } + + uint32_t read_size = static_cast(buffer.size()); + CHECK_EQ(MojoReadMessage(mp, &buffer[0], + &read_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + + // Empty message indicates quit. + if (read_size == 0) + break; + + CHECK_EQ(MojoWriteMessage(mp, &buffer[0], + read_size, + nullptr, 0, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + } + + return rv; + } + + private: + int message_count_; + size_t message_size_; + std::string payload_; + std::string read_buffer_; + std::unique_ptr perf_logger_; + + DISALLOW_COPY_AND_ASSIGN(MessagePipePerfTest); +}; + +TEST_F(MessagePipePerfTest, PingPong) { + MojoHandle server_handle, client_handle; + CreateMessagePipe(&server_handle, &client_handle); + + base::Thread client_thread("PingPongClient"); + client_thread.Start(); + client_thread.task_runner()->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&RunPingPongClient), client_handle)); + + RunPingPongServer(server_handle); +} + +// For each message received, sends a reply message with the same contents +// repeated twice, until the other end is closed or it receives "quitquitquit" +// (which it doesn't reply to). It'll return the number of messages received, +// not including any "quitquitquit" message, modulo 100. +DEFINE_TEST_CLIENT_WITH_PIPE(PingPongClient, MessagePipePerfTest, h) { + return RunPingPongClient(h); +} + +// Repeatedly sends messages as previous one got replied by the child. +// Waits for the child to close its end before quitting once specified +// number of messages has been sent. +TEST_F(MessagePipePerfTest, MultiprocessPingPong) { + RUN_CHILD_ON_PIPE(PingPongClient, h) + RunPingPongServer(h); + END_CHILD() +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/message_pipe_unittest.cc b/mojo/edk/system/message_pipe_unittest.cc new file mode 100644 index 0000000..e6f1ff6 --- /dev/null +++ b/mojo/edk/system/message_pipe_unittest.cc @@ -0,0 +1,699 @@ +// 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 +#include + +#include "base/memory/ref_counted.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/core.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { +namespace { + +const MojoHandleSignals kAllSignals = MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED; +static const char kHelloWorld[] = "hello world"; + +class MessagePipeTest : public test::MojoTestBase { + public: + MessagePipeTest() { + CHECK_EQ(MOJO_RESULT_OK, MojoCreateMessagePipe(nullptr, &pipe0_, &pipe1_)); + } + + ~MessagePipeTest() override { + if (pipe0_ != MOJO_HANDLE_INVALID) + CHECK_EQ(MOJO_RESULT_OK, MojoClose(pipe0_)); + if (pipe1_ != MOJO_HANDLE_INVALID) + CHECK_EQ(MOJO_RESULT_OK, MojoClose(pipe1_)); + } + + MojoResult WriteMessage(MojoHandle message_pipe_handle, + const void* bytes, + uint32_t num_bytes) { + return MojoWriteMessage(message_pipe_handle, bytes, num_bytes, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + } + + MojoResult ReadMessage(MojoHandle message_pipe_handle, + void* bytes, + uint32_t* num_bytes, + bool may_discard = false) { + return MojoReadMessage(message_pipe_handle, bytes, num_bytes, nullptr, 0, + may_discard ? MOJO_READ_MESSAGE_FLAG_MAY_DISCARD : + MOJO_READ_MESSAGE_FLAG_NONE); + } + + MojoHandle pipe0_, pipe1_; + + private: + DISALLOW_COPY_AND_ASSIGN(MessagePipeTest); +}; + +using FuseMessagePipeTest = test::MojoTestBase; + +TEST_F(MessagePipeTest, WriteData) { + ASSERT_EQ(MOJO_RESULT_OK, + WriteMessage(pipe0_, kHelloWorld, sizeof(kHelloWorld))); +} + +// Tests: +// - only default flags +// - reading messages from a port +// - when there are no/one/two messages available for that port +// - with buffer size 0 (and null buffer) -- should get size +// - with too-small buffer -- should get size +// - also verify that buffers aren't modified when/where they shouldn't be +// - writing messages to a port +// - in the obvious scenarios (as above) +// - to a port that's been closed +// - writing a message to a port, closing the other (would be the source) port, +// and reading it +TEST_F(MessagePipeTest, Basic) { + int32_t buffer[2]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t buffer_size; + + // Nothing to read yet on port 0. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe0_, buffer, &buffer_size)); + ASSERT_EQ(kBufferSize, buffer_size); + ASSERT_EQ(123, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Ditto for port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe1_, buffer, &buffer_size)); + + // Write from port 1 (to port 0). + buffer[0] = 789012345; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read from port 0. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe0_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(789012345, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe0_, buffer, &buffer_size)); + + // Write two messages from port 0 (to port 1). + buffer[0] = 123456789; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0]))); + buffer[0] = 234567890; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0]))); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read from port 1 with buffer size 0 (should get the size of next message). + // Also test that giving a null buffer is okay when the buffer size is 0. + buffer_size = 0; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe1_, nullptr, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + + // Read from port 1 with buffer size 1 (too small; should get the size of next + // message). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = 1; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(123, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read from port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(123456789, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read again from port 1. + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(234567890, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read again from port 1 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, ReadMessage(pipe1_, buffer, &buffer_size)); + + // Write from port 0 (to port 1). + buffer[0] = 345678901; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, sizeof(buffer[0]))); + + // Close port 0. + MojoClose(pipe0_); + pipe0_ = MOJO_HANDLE_INVALID; + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &state)); + + // Try to write from port 1 (to port 0). + buffer[0] = 456789012; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + // Read from port 1; should still get message (even though port 0 was closed). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(345678901, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read again from port 1 -- it should be empty (and port 0 is closed). + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + ReadMessage(pipe1_, buffer, &buffer_size)); +} + +TEST_F(MessagePipeTest, CloseWithQueuedIncomingMessages) { + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t buffer_size; + + // Write some messages from port 1 (to port 0). + for (int32_t i = 0; i < 5; i++) { + buffer[0] = i; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, kBufferSize)); + } + + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Port 0 shouldn't be empty. + buffer_size = 0; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe0_, nullptr, &buffer_size)); + ASSERT_EQ(kBufferSize, buffer_size); + + // Close port 0 first, which should have outstanding (incoming) messages. + MojoClose(pipe0_); + MojoClose(pipe1_); + pipe0_ = pipe1_ = MOJO_HANDLE_INVALID; +} + +TEST_F(MessagePipeTest, DiscardMode) { + int32_t buffer[2]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t buffer_size; + + // Write from port 1 (to port 0). + buffer[0] = 789012345; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + MojoHandleSignalsState state; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read/discard from port 0 (no buffer); get size. + buffer_size = 0; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe0_, nullptr, &buffer_size, true)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, + ReadMessage(pipe0_, buffer, &buffer_size, true)); + + // Write from port 1 (to port 0). + buffer[0] = 890123456; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, + WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read from port 0 (buffer big enough). + buffer[0] = 123; + buffer[1] = 456; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe0_, buffer, &buffer_size, true)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + ASSERT_EQ(890123456, buffer[0]); + ASSERT_EQ(456, buffer[1]); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, + ReadMessage(pipe0_, buffer, &buffer_size, true)); + + // Write from port 1 (to port 0). + buffer[0] = 901234567; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Read/discard from port 0 (buffer too small); get size. + buffer_size = 1; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe0_, buffer, &buffer_size, true)); + ASSERT_EQ(static_cast(sizeof(buffer[0])), buffer_size); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, + ReadMessage(pipe0_, buffer, &buffer_size, true)); + + // Write from port 1 (to port 0). + buffer[0] = 123456789; + buffer[1] = 0; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe1_, buffer, sizeof(buffer[0]))); + + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe0_, MOJO_HANDLE_SIGNAL_READABLE, &state)); + + // Discard from port 0. + buffer_size = 1; + ASSERT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + ReadMessage(pipe0_, nullptr, 0, true)); + + // Read again from port 0 -- it should be empty. + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_SHOULD_WAIT, + ReadMessage(pipe0_, buffer, &buffer_size, true)); +} + +TEST_F(MessagePipeTest, BasicWaiting) { + MojoHandleSignalsState hss; + + int32_t buffer[1]; + const uint32_t kBufferSize = static_cast(sizeof(buffer)); + uint32_t buffer_size; + + // Always writable (until the other port is closed). Not yet readable. Peer + // not closed. + hss = GetSignalsState(pipe0_); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + hss = MojoHandleSignalsState(); + + // Write from port 0 (to port 1), to make port 1 readable. + buffer[0] = 123456789; + ASSERT_EQ(MOJO_RESULT_OK, WriteMessage(pipe0_, buffer, kBufferSize)); + + // Port 1 should already be readable now. + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + // ... and still writable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + hss.satisfied_signals); + ASSERT_EQ(kAllSignals, hss.satisfiable_signals); + + // Close port 0. + MojoClose(pipe0_); + pipe0_ = MOJO_HANDLE_INVALID; + + // Port 1 should be signaled with peer closed. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_PEER_CLOSED, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Port 1 should not be writable. + hss = MojoHandleSignalsState(); + + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_WRITABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // But it should still be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + hss.satisfiable_signals); + + // Read from port 1. + buffer[0] = 0; + buffer_size = kBufferSize; + ASSERT_EQ(MOJO_RESULT_OK, ReadMessage(pipe1_, buffer, &buffer_size)); + ASSERT_EQ(123456789, buffer[0]); + + // Now port 1 should no longer be readable. + hss = MojoHandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(pipe1_, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); +} + +TEST_F(MessagePipeTest, InvalidMessageObjects) { + // null message + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoFreeMessage(MOJO_MESSAGE_HANDLE_INVALID)); + + // null message + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoGetMessageBuffer(MOJO_MESSAGE_HANDLE_INVALID, nullptr)); + + // Non-zero num_handles with null handles array. + ASSERT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoAllocMessage(0, nullptr, 1, MOJO_ALLOC_MESSAGE_FLAG_NONE, + nullptr)); +} + +TEST_F(MessagePipeTest, AllocAndFreeMessage) { + const std::string kMessage = "Hello, world."; + MojoMessageHandle message = MOJO_MESSAGE_HANDLE_INVALID; + ASSERT_EQ(MOJO_RESULT_OK, + MojoAllocMessage(static_cast(kMessage.size()), nullptr, 0, + MOJO_ALLOC_MESSAGE_FLAG_NONE, &message)); + ASSERT_NE(MOJO_MESSAGE_HANDLE_INVALID, message); + ASSERT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); +} + +TEST_F(MessagePipeTest, WriteAndReadMessageObject) { + const std::string kMessage = "Hello, world."; + MojoMessageHandle message = MOJO_MESSAGE_HANDLE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, + MojoAllocMessage(static_cast(kMessage.size()), nullptr, 0, + MOJO_ALLOC_MESSAGE_FLAG_NONE, &message)); + ASSERT_NE(MOJO_MESSAGE_HANDLE_INVALID, message); + + void* buffer = nullptr; + EXPECT_EQ(MOJO_RESULT_OK, MojoGetMessageBuffer(message, &buffer)); + ASSERT_TRUE(buffer); + memcpy(buffer, kMessage.data(), kMessage.size()); + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWriteMessageNew(a, message, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE)); + uint32_t num_bytes = 0; + uint32_t num_handles = 0; + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessageNew(b, &message, &num_bytes, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE)); + ASSERT_NE(MOJO_MESSAGE_HANDLE_INVALID, message); + EXPECT_EQ(static_cast(kMessage.size()), num_bytes); + EXPECT_EQ(0u, num_handles); + + EXPECT_EQ(MOJO_RESULT_OK, MojoGetMessageBuffer(message, &buffer)); + ASSERT_TRUE(buffer); + + EXPECT_EQ(0, strncmp(static_cast(buffer), kMessage.data(), + num_bytes)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +#if !defined(OS_IOS) + +const size_t kPingPongHandlesPerIteration = 50; +const size_t kPingPongIterations = 500; + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(HandlePingPong, MessagePipeTest, h) { + // Waits for a handle to become readable and writes it back to the sender. + for (size_t i = 0; i < kPingPongIterations; i++) { + MojoHandle handles[kPingPongHandlesPerIteration]; + ReadMessageWithHandles(h, handles, kPingPongHandlesPerIteration); + WriteMessageWithHandles(h, "", handles, kPingPongHandlesPerIteration); + } + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE)); + char msg[4]; + uint32_t num_bytes = 4; + EXPECT_EQ(MOJO_RESULT_OK, ReadMessage(h, msg, &num_bytes)); +} + +// This test is flaky: http://crbug.com/585784 +TEST_F(MessagePipeTest, DISABLED_DataPipeConsumerHandlePingPong) { + MojoHandle p, c[kPingPongHandlesPerIteration]; + for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i) { + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &p, &c[i])); + MojoClose(p); + } + + RUN_CHILD_ON_PIPE(HandlePingPong, h) + for (size_t i = 0; i < kPingPongIterations; i++) { + WriteMessageWithHandles(h, "", c, kPingPongHandlesPerIteration); + ReadMessageWithHandles(h, c, kPingPongHandlesPerIteration); + } + WriteMessage(h, "quit", 4); + END_CHILD() + for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i) + MojoClose(c[i]); +} + +// This test is flaky: http://crbug.com/585784 +TEST_F(MessagePipeTest, DISABLED_DataPipeProducerHandlePingPong) { + MojoHandle p[kPingPongHandlesPerIteration], c; + for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i) { + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateDataPipe(nullptr, &p[i], &c)); + MojoClose(c); + } + + RUN_CHILD_ON_PIPE(HandlePingPong, h) + for (size_t i = 0; i < kPingPongIterations; i++) { + WriteMessageWithHandles(h, "", p, kPingPongHandlesPerIteration); + ReadMessageWithHandles(h, p, kPingPongHandlesPerIteration); + } + WriteMessage(h, "quit", 4); + END_CHILD() + for (size_t i = 0; i < kPingPongHandlesPerIteration; ++i) + MojoClose(p[i]); +} + +TEST_F(MessagePipeTest, SharedBufferHandlePingPong) { + MojoHandle buffers[kPingPongHandlesPerIteration]; + for (size_t i = 0; i +#include +#include +#include + +#include +#include +#include + +#include "base/bind.h" +#include "base/containers/hash_tables.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/run_loop.h" +#include "base/strings/string_split.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/edk/test/test_utils.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/cpp/system/simple_watcher.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace mojo { +namespace edk { +namespace { + +class MultiprocessMessagePipeTest : public test::MojoTestBase { + protected: + // Convenience class for tests which will control command-driven children. + // See the CommandDrivenClient definition below. + class CommandDrivenClientController { + public: + explicit CommandDrivenClientController(MojoHandle h) : h_(h) {} + + void Send(const std::string& command) { + WriteMessage(h_, command); + EXPECT_EQ("ok", ReadMessage(h_)); + } + + void SendHandle(const std::string& name, MojoHandle p) { + WriteMessageWithHandles(h_, "take:" + name, &p, 1); + EXPECT_EQ("ok", ReadMessage(h_)); + } + + MojoHandle RetrieveHandle(const std::string& name) { + WriteMessage(h_, "return:" + name); + MojoHandle p; + EXPECT_EQ("ok", ReadMessageWithHandles(h_, &p, 1)); + return p; + } + + void Exit() { WriteMessage(h_, "exit"); } + + private: + MojoHandle h_; + }; +}; + +class MultiprocessMessagePipeTestWithPeerSupport + : public MultiprocessMessagePipeTest, + public testing::WithParamInterface { + protected: + void SetUp() override { + test::MojoTestBase::SetUp(); + set_launch_type(GetParam()); + } +}; + +// For each message received, sends a reply message with the same contents +// repeated twice, until the other end is closed or it receives "quitquitquit" +// (which it doesn't reply to). It'll return the number of messages received, +// not including any "quitquitquit" message, modulo 100. +DEFINE_TEST_CLIENT_WITH_PIPE(EchoEcho, MultiprocessMessagePipeTest, h) { + const std::string quitquitquit("quitquitquit"); + int rv = 0; + for (;; rv = (rv + 1) % 100) { + // Wait for our end of the message pipe to be readable. + HandleSignalsState hss; + MojoResult result = WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss); + if (result != MOJO_RESULT_OK) { + // It was closed, probably. + CHECK_EQ(result, MOJO_RESULT_FAILED_PRECONDITION); + CHECK_EQ(hss.satisfied_signals, MOJO_HANDLE_SIGNAL_PEER_CLOSED); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_PEER_CLOSED); + break; + } else { + CHECK((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + CHECK((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + } + + std::string read_buffer(1000, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + VLOG(2) << "Child got: " << read_buffer; + + if (read_buffer == quitquitquit) { + VLOG(2) << "Child quitting."; + break; + } + + std::string write_buffer = read_buffer + read_buffer; + CHECK_EQ(MojoWriteMessage(h, write_buffer.data(), + static_cast(write_buffer.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + } + + return rv; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, Basic) { + RUN_CHILD_ON_PIPE(EchoEcho, h) + std::string hello("hello"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, hello.data(), + static_cast(hello.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + // The child may or may not have closed its end of the message pipe and died + // (and we may or may not know it yet), so our end may or may not appear as + // writable. + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(1000, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], + &read_buffer_size, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + VLOG(2) << "Parent got: " << read_buffer; + ASSERT_EQ(hello + hello, read_buffer); + + std::string quitquitquit("quitquitquit"); + CHECK_EQ(MojoWriteMessage(h, quitquitquit.data(), + static_cast(quitquitquit.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + END_CHILD_AND_EXPECT_EXIT_CODE(1 % 100); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, QueueMessages) { + static const size_t kNumMessages = 1001; + RUN_CHILD_ON_PIPE(EchoEcho, h) + for (size_t i = 0; i < kNumMessages; i++) { + std::string write_buffer(i, 'A' + (i % 26)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, write_buffer.data(), + static_cast(write_buffer.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE)); + } + + for (size_t i = 0; i < kNumMessages; i++) { + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + // The child may or may not have closed its end of the message pipe and + // died (and we may or may not know it yet), so our end may or may not + // appear as writable. + ASSERT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + ASSERT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(kNumMessages * 2, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + ASSERT_EQ(MojoReadMessage(h, &read_buffer[0], + &read_buffer_size, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + + ASSERT_EQ(std::string(i * 2, 'A' + (i % 26)), read_buffer); + } + + const std::string quitquitquit("quitquitquit"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, quitquitquit.data(), + static_cast(quitquitquit.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for it to become readable, which should fail (since we sent + // "quitquitquit"). + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + END_CHILD_AND_EXPECT_EXIT_CODE(static_cast(kNumMessages % 100)); +} + +DEFINE_TEST_CLIENT_WITH_PIPE(CheckSharedBuffer, MultiprocessMessagePipeTest, + h) { + // Wait for the first message from our parent. + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + // In this test, the parent definitely doesn't close its end of the message + // pipe before we do. + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + // It should have a shared buffer. + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast(read_buffer.size()); + MojoHandle handles[10]; + uint32_t num_handlers = arraysize(handles); // Maximum number to receive + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], + &num_bytes, &handles[0], + &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(num_bytes); + CHECK_EQ(read_buffer, std::string("go 1")); + CHECK_EQ(num_handlers, 1u); + + // Make a mapping. + void* buffer; + CHECK_EQ(MojoMapBuffer(handles[0], 0, 100, &buffer, + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE), + MOJO_RESULT_OK); + + // Write some stuff to the shared buffer. + static const char kHello[] = "hello"; + memcpy(buffer, kHello, sizeof(kHello)); + + // We should be able to close the dispatcher now. + MojoClose(handles[0]); + + // And send a message to signal that we've written stuff. + const std::string go2("go 2"); + CHECK_EQ(MojoWriteMessage(h, go2.data(), + static_cast(go2.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + + // Now wait for our parent to send us a message. + hss = HandleSignalsState(); + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + read_buffer = std::string(100, '\0'); + num_bytes = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], &num_bytes, + nullptr, 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(num_bytes); + CHECK_EQ(read_buffer, std::string("go 3")); + + // It should have written something to the shared buffer. + static const char kWorld[] = "world!!!"; + CHECK_EQ(memcmp(buffer, kWorld, sizeof(kWorld)), 0); + + // And we're done. + + return 0; +} + +TEST_F(MultiprocessMessagePipeTest, SharedBufferPassing) { + RUN_CHILD_ON_PIPE(CheckSharedBuffer, h) + // Make a shared buffer. + MojoCreateSharedBufferOptions options; + options.struct_size = sizeof(options); + options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + MojoHandle shared_buffer; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateSharedBuffer(&options, 100, &shared_buffer)); + + // Send the shared buffer. + const std::string go1("go 1"); + + MojoHandle duplicated_shared_buffer; + ASSERT_EQ(MOJO_RESULT_OK, + MojoDuplicateBufferHandle( + shared_buffer, + nullptr, + &duplicated_shared_buffer)); + MojoHandle handles[1]; + handles[0] = duplicated_shared_buffer; + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, &go1[0], + static_cast(go1.size()), &handles[0], + arraysize(handles), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for a message from the child. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast(read_buffer.size()); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessage(h, &read_buffer[0], + &num_bytes, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE)); + read_buffer.resize(num_bytes); + ASSERT_EQ(std::string("go 2"), read_buffer); + + // After we get it, the child should have written something to the shared + // buffer. + static const char kHello[] = "hello"; + void* buffer; + CHECK_EQ(MojoMapBuffer(shared_buffer, 0, 100, &buffer, + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE), + MOJO_RESULT_OK); + ASSERT_EQ(0, memcmp(buffer, kHello, sizeof(kHello))); + + // Now we'll write some stuff to the shared buffer. + static const char kWorld[] = "world!!!"; + memcpy(buffer, kWorld, sizeof(kWorld)); + + // And send a message to signal that we've written stuff. + const std::string go3("go 3"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, &go3[0], + static_cast(go3.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for |h| to become readable, which should fail. + hss = HandleSignalsState(); + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + END_CHILD() +} + +DEFINE_TEST_CLIENT_WITH_PIPE(CheckPlatformHandleFile, + MultiprocessMessagePipeTest, h) { + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + std::string read_buffer(100, '\0'); + uint32_t num_bytes = static_cast(read_buffer.size()); + MojoHandle handles[255]; // Maximum number to receive. + uint32_t num_handlers = arraysize(handles); + + CHECK_EQ(MojoReadMessage(h, &read_buffer[0], + &num_bytes, &handles[0], + &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + + read_buffer.resize(num_bytes); + char hello[32]; + int num_handles = 0; + sscanf(read_buffer.c_str(), "%s %d", hello, &num_handles); + CHECK_EQ(std::string("hello"), std::string(hello)); + CHECK_GT(num_handles, 0); + + for (int i = 0; i < num_handles; ++i) { + ScopedPlatformHandle h; + CHECK_EQ(PassWrappedPlatformHandle(handles[i], &h), MOJO_RESULT_OK); + CHECK(h.is_valid()); + MojoClose(handles[i]); + + base::ScopedFILE fp(test::FILEFromPlatformHandle(std::move(h), "r")); + CHECK(fp); + std::string fread_buffer(100, '\0'); + size_t bytes_read = + fread(&fread_buffer[0], 1, fread_buffer.size(), fp.get()); + fread_buffer.resize(bytes_read); + CHECK_EQ(fread_buffer, "world"); + } + + return 0; +} + +class MultiprocessMessagePipeTestWithPipeCount + : public MultiprocessMessagePipeTest, + public testing::WithParamInterface {}; + +TEST_P(MultiprocessMessagePipeTestWithPipeCount, PlatformHandlePassing) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + RUN_CHILD_ON_PIPE(CheckPlatformHandleFile, h) + std::vector handles; + + size_t pipe_count = GetParam(); + for (size_t i = 0; i < pipe_count; ++i) { + base::FilePath unused; + base::ScopedFILE fp( + CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + const std::string world("world"); + CHECK_EQ(fwrite(&world[0], 1, world.size(), fp.get()), world.size()); + fflush(fp.get()); + rewind(fp.get()); + MojoHandle handle; + ASSERT_EQ( + CreatePlatformHandleWrapper( + ScopedPlatformHandle(test::PlatformHandleFromFILE(std::move(fp))), + &handle), + MOJO_RESULT_OK); + handles.push_back(handle); + } + + char message[128]; + snprintf(message, sizeof(message), "hello %d", + static_cast(pipe_count)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, message, + static_cast(strlen(message)), + &handles[0], + static_cast(handles.size()), + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for it to become readable, which should fail. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfied_signals); + ASSERT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, hss.satisfiable_signals); + END_CHILD() +} + +// Android multi-process tests are not executing the new process. This is flaky. +#if !defined(OS_ANDROID) +INSTANTIATE_TEST_CASE_P(PipeCount, + MultiprocessMessagePipeTestWithPipeCount, + // TODO(rockot): Re-enable the 140-pipe case when + // ChannelPosix has support for sending lots of handles. + testing::Values(1u, 128u /*, 140u*/)); +#endif + +DEFINE_TEST_CLIENT_WITH_PIPE(CheckMessagePipe, MultiprocessMessagePipeTest, h) { + // Wait for the first message from our parent. + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + // In this test, the parent definitely doesn't close its end of the message + // pipe before we do. + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + // It should have a message pipe. + MojoHandle handles[10]; + uint32_t num_handlers = arraysize(handles); + CHECK_EQ(MojoReadMessage(h, nullptr, + nullptr, &handles[0], + &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_handlers, 1u); + + // Read data from the received message pipe. + CHECK_EQ(WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(handles[0], &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("hello")); + + // Now write some data into the message pipe. + std::string write_buffer = "world"; + CHECK_EQ(MojoWriteMessage(handles[0], write_buffer.data(), + static_cast(write_buffer.size()), nullptr, + 0u, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + MojoClose(handles[0]); + return 0; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipePassing) { + RUN_CHILD_ON_PIPE(CheckMessagePipe, h) + MojoCreateSharedBufferOptions options; + options.struct_size = sizeof(options); + options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + MojoHandle mp1, mp2; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &mp1, &mp2)); + + // Write a string into one end of the new message pipe and send the other + // end. + const std::string hello("hello"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp1, &hello[0], + static_cast(hello.size()), nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, nullptr, 0, &mp2, 1, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for a message from the child. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("world")); + + MojoClose(mp1); + END_CHILD() +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MessagePipeTwoPassing) { + RUN_CHILD_ON_PIPE(CheckMessagePipe, h) + MojoHandle mp1, mp2; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &mp2, &mp1)); + + // Write a string into one end of the new message pipe and send the other + // end. + const std::string hello("hello"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp1, &hello[0], + static_cast(hello.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, nullptr, 0u, &mp2, 1u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for a message from the child. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("world")); + END_CHILD(); +} + +DEFINE_TEST_CLIENT_WITH_PIPE(DataPipeConsumer, MultiprocessMessagePipeTest, h) { + // Wait for the first message from our parent. + HandleSignalsState hss; + CHECK_EQ(WaitForSignals(h, MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + // In this test, the parent definitely doesn't close its end of the message + // pipe before we do. + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + // It should have a message pipe. + MojoHandle handles[10]; + uint32_t num_handlers = arraysize(handles); + CHECK_EQ(MojoReadMessage(h, nullptr, + nullptr, &handles[0], + &num_handlers, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_handlers, 1u); + + // Read data from the received message pipe. + CHECK_EQ(WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_READABLE, &hss), + MOJO_RESULT_OK); + CHECK_EQ(hss.satisfied_signals, + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE); + CHECK_EQ(hss.satisfiable_signals, MOJO_HANDLE_SIGNAL_READABLE | + MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(handles[0], &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("hello")); + + // Now write some data into the message pipe. + std::string write_buffer = "world"; + CHECK_EQ(MojoWriteMessage(handles[0], write_buffer.data(), + static_cast(write_buffer.size()), + nullptr, 0u, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + MojoClose(handles[0]); + return 0; +} + +TEST_F(MultiprocessMessagePipeTest, DataPipeConsumer) { + RUN_CHILD_ON_PIPE(DataPipeConsumer, h) + MojoCreateSharedBufferOptions options; + options.struct_size = sizeof(options); + options.flags = MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + MojoHandle mp1, mp2; + ASSERT_EQ(MOJO_RESULT_OK, + MojoCreateMessagePipe(nullptr, &mp2, &mp1)); + + // Write a string into one end of the new message pipe and send the other + // end. + const std::string hello("hello"); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(mp1, &hello[0], + static_cast(hello.size()), nullptr, 0u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoWriteMessage(h, nullptr, 0, &mp2, 1u, + MOJO_WRITE_MESSAGE_FLAG_NONE)); + + // Wait for a message from the child. + HandleSignalsState hss; + ASSERT_EQ(MOJO_RESULT_OK, + WaitForSignals(mp1, MOJO_HANDLE_SIGNAL_READABLE, &hss)); + EXPECT_TRUE((hss.satisfied_signals & MOJO_HANDLE_SIGNAL_READABLE)); + EXPECT_TRUE((hss.satisfiable_signals & MOJO_HANDLE_SIGNAL_READABLE)); + + std::string read_buffer(100, '\0'); + uint32_t read_buffer_size = static_cast(read_buffer.size()); + CHECK_EQ(MojoReadMessage(mp1, &read_buffer[0], + &read_buffer_size, nullptr, + 0, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + read_buffer.resize(read_buffer_size); + CHECK_EQ(read_buffer, std::string("world")); + + MojoClose(mp1); + END_CHILD(); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, CreateMessagePipe) { + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + VerifyTransmission(p0, p1, std::string(10 * 1024 * 1024, 'a')); + VerifyTransmission(p1, p0, std::string(10 * 1024 * 1024, 'e')); + + CloseHandle(p0); + CloseHandle(p1); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, PassMessagePipeLocal) { + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + VerifyTransmission(p0, p1, "testing testing"); + VerifyTransmission(p1, p0, "one two three"); + + MojoHandle p2, p3; + + CreateMessagePipe(&p2, &p3); + VerifyTransmission(p2, p3, "testing testing"); + VerifyTransmission(p3, p2, "one two three"); + + // Pass p2 over p0 to p1. + const std::string message = "ceci n'est pas une pipe"; + WriteMessageWithHandles(p0, message, &p2, 1); + EXPECT_EQ(message, ReadMessageWithHandles(p1, &p2, 1)); + + CloseHandle(p0); + CloseHandle(p1); + + // Verify that the received handle (now in p2) still works. + VerifyTransmission(p2, p3, "Easy come, easy go; will you let me go?"); + VerifyTransmission(p3, p2, "Bismillah! NO! We will not let you go!"); + + CloseHandle(p2); + CloseHandle(p3); +} + +// Echos the primordial channel until "exit". +DEFINE_TEST_CLIENT_WITH_PIPE(ChannelEchoClient, MultiprocessMessagePipeTest, + h) { + for (;;) { + std::string message = ReadMessage(h); + if (message == "exit") + break; + WriteMessage(h, message); + } + return 0; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, MultiprocessChannelPipe) { + RUN_CHILD_ON_PIPE(ChannelEchoClient, h) + VerifyEcho(h, "in an interstellar burst"); + VerifyEcho(h, "i am back to save the universe"); + VerifyEcho(h, std::string(10 * 1024 * 1024, 'o')); + + WriteMessage(h, "exit"); + END_CHILD() +} + +// Receives a pipe handle from the primordial channel and echos on it until +// "exit". Used to test simple pipe transfer across processes via channels. +DEFINE_TEST_CLIENT_WITH_PIPE(EchoServiceClient, MultiprocessMessagePipeTest, + h) { + MojoHandle p; + ReadMessageWithHandles(h, &p, 1); + for (;;) { + std::string message = ReadMessage(p); + if (message == "exit") + break; + WriteMessage(p, message); + } + return 0; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, + PassMessagePipeCrossProcess) { + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + RUN_CHILD_ON_PIPE(EchoServiceClient, h) + + // Pass one end of the pipe to the other process. + WriteMessageWithHandles(h, "here take this", &p1, 1); + + VerifyEcho(p0, "and you may ask yourself"); + VerifyEcho(p0, "where does that highway go?"); + VerifyEcho(p0, std::string(20 * 1024 * 1024, 'i')); + + WriteMessage(p0, "exit"); + END_CHILD() + CloseHandle(p0); +} + +// Receives a pipe handle from the primordial channel and reads new handles +// from it. Each read handle establishes a new echo channel. +DEFINE_TEST_CLIENT_WITH_PIPE(EchoServiceFactoryClient, + MultiprocessMessagePipeTest, h) { + MojoHandle p; + ReadMessageWithHandles(h, &p, 1); + + std::vector handles(2); + handles[0] = Handle(h); + handles[1] = Handle(p); + std::vector signals(2, MOJO_HANDLE_SIGNAL_READABLE); + for (;;) { + size_t index; + CHECK_EQ( + mojo::WaitMany(handles.data(), signals.data(), handles.size(), &index), + MOJO_RESULT_OK); + DCHECK_LE(index, handles.size()); + if (index == 0) { + // If data is available on the first pipe, it should be an exit command. + EXPECT_EQ(std::string("exit"), ReadMessage(h)); + break; + } else if (index == 1) { + // If the second pipe, it should be a new handle requesting echo service. + MojoHandle echo_request; + ReadMessageWithHandles(p, &echo_request, 1); + handles.push_back(Handle(echo_request)); + signals.push_back(MOJO_HANDLE_SIGNAL_READABLE); + } else { + // Otherwise it was one of our established echo pipes. Echo! + WriteMessage(handles[index].value(), ReadMessage(handles[index].value())); + } + } + + for (size_t i = 1; i < handles.size(); ++i) + CloseHandle(handles[i].value()); + + return 0; +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, + PassMoarMessagePipesCrossProcess) { + MojoHandle echo_factory_proxy, echo_factory_request; + CreateMessagePipe(&echo_factory_proxy, &echo_factory_request); + + MojoHandle echo_proxy_a, echo_request_a; + CreateMessagePipe(&echo_proxy_a, &echo_request_a); + + MojoHandle echo_proxy_b, echo_request_b; + CreateMessagePipe(&echo_proxy_b, &echo_request_b); + + MojoHandle echo_proxy_c, echo_request_c; + CreateMessagePipe(&echo_proxy_c, &echo_request_c); + + RUN_CHILD_ON_PIPE(EchoServiceFactoryClient, h) + WriteMessageWithHandles( + h, "gief factory naow plz", &echo_factory_request, 1); + + WriteMessageWithHandles(echo_factory_proxy, "give me an echo service plz!", + &echo_request_a, 1); + WriteMessageWithHandles(echo_factory_proxy, "give me one too!", + &echo_request_b, 1); + + VerifyEcho(echo_proxy_a, "i came here for an argument"); + VerifyEcho(echo_proxy_a, "shut your festering gob"); + VerifyEcho(echo_proxy_a, "mumble mumble mumble"); + + VerifyEcho(echo_proxy_b, "wubalubadubdub"); + VerifyEcho(echo_proxy_b, "wubalubadubdub"); + + WriteMessageWithHandles(echo_factory_proxy, "hook me up also thanks", + &echo_request_c, 1); + + VerifyEcho(echo_proxy_a, "the frobinators taste like frobinators"); + VerifyEcho(echo_proxy_b, "beep bop boop"); + VerifyEcho(echo_proxy_c, "zzzzzzzzzzzzzzzzzzzzzzzzzz"); + + WriteMessage(h, "exit"); + END_CHILD() + + CloseHandle(echo_factory_proxy); + CloseHandle(echo_proxy_a); + CloseHandle(echo_proxy_b); + CloseHandle(echo_proxy_c); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, + ChannelPipesWithMultipleChildren) { + RUN_CHILD_ON_PIPE(ChannelEchoClient, a) + RUN_CHILD_ON_PIPE(ChannelEchoClient, b) + VerifyEcho(a, "hello child 0"); + VerifyEcho(b, "hello child 1"); + + WriteMessage(a, "exit"); + WriteMessage(b, "exit"); + END_CHILD() + END_CHILD() +} + +// Reads and turns a pipe handle some number of times to create lots of +// transient proxies. +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(PingPongPipeClient, + MultiprocessMessagePipeTest, h) { + const size_t kNumBounces = 50; + MojoHandle p0, p1; + ReadMessageWithHandles(h, &p0, 1); + ReadMessageWithHandles(h, &p1, 1); + for (size_t i = 0; i < kNumBounces; ++i) { + WriteMessageWithHandles(h, "", &p1, 1); + ReadMessageWithHandles(h, &p1, 1); + } + WriteMessageWithHandles(h, "", &p0, 1); + WriteMessage(p1, "bye"); + MojoClose(p1); + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, PingPongPipe) { + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + + RUN_CHILD_ON_PIPE(PingPongPipeClient, h) + const size_t kNumBounces = 50; + WriteMessageWithHandles(h, "", &p0, 1); + WriteMessageWithHandles(h, "", &p1, 1); + for (size_t i = 0; i < kNumBounces; ++i) { + ReadMessageWithHandles(h, &p1, 1); + WriteMessageWithHandles(h, "", &p1, 1); + } + ReadMessageWithHandles(h, &p0, 1); + WriteMessage(h, "quit"); + END_CHILD() + + EXPECT_EQ("bye", ReadMessage(p0)); + + // We should still be able to observe peer closure from the other end. + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(p0, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +// Parses commands from the parent pipe and does whatever it's asked to do. +DEFINE_TEST_CLIENT_WITH_PIPE(CommandDrivenClient, MultiprocessMessagePipeTest, + h) { + base::hash_map named_pipes; + for (;;) { + MojoHandle p; + auto parts = base::SplitString(ReadMessageWithOptionalHandle(h, &p), ":", + base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); + CHECK(!parts.empty()); + std::string command = parts[0]; + if (command == "take") { + // Take a pipe. + CHECK_EQ(parts.size(), 2u); + CHECK_NE(p, MOJO_HANDLE_INVALID); + named_pipes[parts[1]] = p; + WriteMessage(h, "ok"); + } else if (command == "return") { + // Return a pipe. + CHECK_EQ(parts.size(), 2u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + p = named_pipes[parts[1]]; + CHECK_NE(p, MOJO_HANDLE_INVALID); + named_pipes.erase(parts[1]); + WriteMessageWithHandles(h, "ok", &p, 1); + } else if (command == "say") { + // Say something to a named pipe. + CHECK_EQ(parts.size(), 3u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + p = named_pipes[parts[1]]; + CHECK_NE(p, MOJO_HANDLE_INVALID); + CHECK(!parts[2].empty()); + WriteMessage(p, parts[2]); + WriteMessage(h, "ok"); + } else if (command == "hear") { + // Expect to read something from a named pipe. + CHECK_EQ(parts.size(), 3u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + p = named_pipes[parts[1]]; + CHECK_NE(p, MOJO_HANDLE_INVALID); + CHECK(!parts[2].empty()); + CHECK_EQ(parts[2], ReadMessage(p)); + WriteMessage(h, "ok"); + } else if (command == "pass") { + // Pass one named pipe over another named pipe. + CHECK_EQ(parts.size(), 3u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + p = named_pipes[parts[1]]; + MojoHandle carrier = named_pipes[parts[2]]; + CHECK_NE(p, MOJO_HANDLE_INVALID); + CHECK_NE(carrier, MOJO_HANDLE_INVALID); + named_pipes.erase(parts[1]); + WriteMessageWithHandles(carrier, "got a pipe for ya", &p, 1); + WriteMessage(h, "ok"); + } else if (command == "catch") { + // Expect to receive one named pipe from another named pipe. + CHECK_EQ(parts.size(), 3u); + CHECK_EQ(p, MOJO_HANDLE_INVALID); + MojoHandle carrier = named_pipes[parts[2]]; + CHECK_NE(carrier, MOJO_HANDLE_INVALID); + ReadMessageWithHandles(carrier, &p, 1); + CHECK_NE(p, MOJO_HANDLE_INVALID); + named_pipes[parts[1]] = p; + WriteMessage(h, "ok"); + } else if (command == "exit") { + CHECK_EQ(parts.size(), 1u); + break; + } + } + + for (auto& pipe : named_pipes) + CloseHandle(pipe.second); + + return 0; +} + +TEST_F(MultiprocessMessagePipeTest, ChildToChildPipes) { + RUN_CHILD_ON_PIPE(CommandDrivenClient, h0) + RUN_CHILD_ON_PIPE(CommandDrivenClient, h1) + CommandDrivenClientController a(h0); + CommandDrivenClientController b(h1); + + // Create a pipe and pass each end to a different client. + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + a.SendHandle("x", p0); + b.SendHandle("y", p1); + + // Make sure they can talk. + a.Send("say:x:hello"); + b.Send("hear:y:hello"); + + b.Send("say:y:i love multiprocess pipes!"); + a.Send("hear:x:i love multiprocess pipes!"); + + a.Exit(); + b.Exit(); + END_CHILD() + END_CHILD() +} + +TEST_F(MultiprocessMessagePipeTest, MoreChildToChildPipes) { + RUN_CHILD_ON_PIPE(CommandDrivenClient, h0) + RUN_CHILD_ON_PIPE(CommandDrivenClient, h1) + RUN_CHILD_ON_PIPE(CommandDrivenClient, h2) + RUN_CHILD_ON_PIPE(CommandDrivenClient, h3) + CommandDrivenClientController a(h0), b(h1), c(h2), d(h3); + + // Connect a to b and c to d + + MojoHandle p0, p1; + + CreateMessagePipe(&p0, &p1); + a.SendHandle("b_pipe", p0); + b.SendHandle("a_pipe", p1); + + MojoHandle p2, p3; + + CreateMessagePipe(&p2, &p3); + c.SendHandle("d_pipe", p2); + d.SendHandle("c_pipe", p3); + + // Connect b to c via a and d + MojoHandle p4, p5; + CreateMessagePipe(&p4, &p5); + a.SendHandle("d_pipe", p4); + d.SendHandle("a_pipe", p5); + + // Have |a| pass its new |d|-pipe to |b|. It will eventually connect + // to |c|. + a.Send("pass:d_pipe:b_pipe"); + b.Send("catch:c_pipe:a_pipe"); + + // Have |d| pass its new |a|-pipe to |c|. It will now be connected to + // |b|. + d.Send("pass:a_pipe:c_pipe"); + c.Send("catch:b_pipe:d_pipe"); + + // Make sure b and c and talk. + b.Send("say:c_pipe:it's a beautiful day"); + c.Send("hear:b_pipe:it's a beautiful day"); + + // Create x and y and have b and c exchange them. + MojoHandle x, y; + CreateMessagePipe(&x, &y); + b.SendHandle("x", x); + c.SendHandle("y", y); + b.Send("pass:x:c_pipe"); + c.Send("pass:y:b_pipe"); + b.Send("catch:y:c_pipe"); + c.Send("catch:x:b_pipe"); + + // Make sure the pipe still works in both directions. + b.Send("say:y:hello"); + c.Send("hear:x:hello"); + c.Send("say:x:goodbye"); + b.Send("hear:y:goodbye"); + + // Take both pipes back. + y = c.RetrieveHandle("x"); + x = b.RetrieveHandle("y"); + + VerifyTransmission(x, y, "still works"); + VerifyTransmission(y, x, "in both directions"); + + CloseHandle(x); + CloseHandle(y); + + a.Exit(); + b.Exit(); + c.Exit(); + d.Exit(); + END_CHILD() + END_CHILD() + END_CHILD() + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceivePipeWithClosedPeer, + MultiprocessMessagePipeTest, h) { + MojoHandle p; + EXPECT_EQ("foo", ReadMessageWithHandles(h, &p, 1)); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, SendPipeThenClosePeer) { + RUN_CHILD_ON_PIPE(ReceivePipeWithClosedPeer, h) + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + // Send |a| and immediately close |b|. The child should observe closure. + WriteMessageWithHandles(h, "foo", &a, 1); + MojoClose(b); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(SendOtherChildPipeWithClosedPeer, + MultiprocessMessagePipeTest, h) { + // Create a new pipe and send one end to the parent, who will connect it to + // a client running ReceivePipeWithClosedPeerFromOtherChild. + MojoHandle application_proxy, application_request; + CreateMessagePipe(&application_proxy, &application_request); + WriteMessageWithHandles(h, "c2a plz", &application_request, 1); + + // Create another pipe and send one end to the remote "application". + MojoHandle service_proxy, service_request; + CreateMessagePipe(&service_proxy, &service_request); + WriteMessageWithHandles(application_proxy, "c2s lol", &service_request, 1); + + // Immediately close the service proxy. The "application" should detect this. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(service_proxy)); + + // Wait for quit. + EXPECT_EQ("quit", ReadMessage(h)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceivePipeWithClosedPeerFromOtherChild, + MultiprocessMessagePipeTest, h) { + // Receive a pipe from the parent. This is akin to an "application request". + MojoHandle application_client; + EXPECT_EQ("c2a", ReadMessageWithHandles(h, &application_client, 1)); + + // Receive a pipe from the "application" "client". + MojoHandle service_client; + EXPECT_EQ("c2s lol", + ReadMessageWithHandles(application_client, &service_client, 1)); + + // Wait for the service client to signal closure. + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(service_client, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(service_client)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(application_client)); +} + +#if defined(OS_ANDROID) +// Android multi-process tests are not executing the new process. This is flaky. +#define MAYBE_SendPipeWithClosedPeerBetweenChildren \ + DISABLED_SendPipeWithClosedPeerBetweenChildren +#else +#define MAYBE_SendPipeWithClosedPeerBetweenChildren \ + SendPipeWithClosedPeerBetweenChildren +#endif +TEST_F(MultiprocessMessagePipeTest, + MAYBE_SendPipeWithClosedPeerBetweenChildren) { + RUN_CHILD_ON_PIPE(SendOtherChildPipeWithClosedPeer, kid_a) + RUN_CHILD_ON_PIPE(ReceivePipeWithClosedPeerFromOtherChild, kid_b) + // Receive an "application request" from the first child and forward it + // to the second child. + MojoHandle application_request; + EXPECT_EQ("c2a plz", + ReadMessageWithHandles(kid_a, &application_request, 1)); + + WriteMessageWithHandles(kid_b, "c2a", &application_request, 1); + END_CHILD() + + WriteMessage(kid_a, "quit"); + END_CHILD() +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, SendClosePeerSend) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + // Send |a| over |c|, immediately close |b|, then send |a| back over |d|. + WriteMessageWithHandles(c, "foo", &a, 1); + EXPECT_EQ("foo", ReadMessageWithHandles(d, &a, 1)); + WriteMessageWithHandles(d, "bar", &a, 1); + EXPECT_EQ("bar", ReadMessageWithHandles(c, &a, 1)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + + // We should be able to detect peer closure on |a|. + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(a, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(WriteCloseSendPeerClient, + MultiprocessMessagePipeTest, h) { + MojoHandle pipe[2]; + EXPECT_EQ("foo", ReadMessageWithHandles(h, pipe, 2)); + + // Write some messages to the first endpoint and then close it. + WriteMessage(pipe[0], "baz"); + WriteMessage(pipe[0], "qux"); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(pipe[0])); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + // Pass the orphaned endpoint over another pipe before passing it back to + // the parent, just for some extra proxying goodness. + WriteMessageWithHandles(c, "foo", &pipe[1], 1); + EXPECT_EQ("foo", ReadMessageWithHandles(d, &pipe[1], 1)); + + // And finally pass it back to the parent. + WriteMessageWithHandles(h, "bar", &pipe[1], 1); + + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_P(MultiprocessMessagePipeTestWithPeerSupport, WriteCloseSendPeer) { + MojoHandle pipe[2]; + CreateMessagePipe(&pipe[0], &pipe[1]); + + RUN_CHILD_ON_PIPE(WriteCloseSendPeerClient, h) + // Pass the pipe to the child. + WriteMessageWithHandles(h, "foo", pipe, 2); + + // Read back an endpoint which should have messages on it. + MojoHandle p; + EXPECT_EQ("bar", ReadMessageWithHandles(h, &p, 1)); + + EXPECT_EQ("baz", ReadMessage(p)); + EXPECT_EQ("qux", ReadMessage(p)); + + // Expect to have peer closure signaled. + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(p, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + WriteMessage(h, "quit"); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(MessagePipeStatusChangeInTransitClient, + MultiprocessMessagePipeTest, parent) { + // This test verifies that peer closure is detectable through various + // mechanisms when it races with handle transfer. + MojoHandle handles[4]; + EXPECT_EQ("o_O", ReadMessageWithHandles(parent, handles, 4)); + + EXPECT_EQ(MOJO_RESULT_OK, + WaitForSignals(handles[0], MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + base::MessageLoop message_loop; + + // Wait on handle 1 using a SimpleWatcher. + { + base::RunLoop run_loop; + SimpleWatcher watcher(FROM_HERE, SimpleWatcher::ArmingPolicy::AUTOMATIC); + watcher.Watch(Handle(handles[1]), MOJO_HANDLE_SIGNAL_PEER_CLOSED, + base::Bind( + [](base::RunLoop* loop, MojoResult result) { + EXPECT_EQ(MOJO_RESULT_OK, result); + loop->Quit(); + }, + &run_loop)); + run_loop.Run(); + } + + // Wait on handle 2 by polling with MojoReadMessage. + MojoResult result; + do { + result = MojoReadMessage(handles[2], nullptr, nullptr, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE); + } while (result == MOJO_RESULT_SHOULD_WAIT); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + + // Wait on handle 3 by polling with MojoWriteMessage. + do { + result = MojoWriteMessage(handles[3], nullptr, 0, nullptr, 0, + MOJO_WRITE_MESSAGE_FLAG_NONE); + } while (result == MOJO_RESULT_OK); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + + for (size_t i = 0; i < 4; ++i) + CloseHandle(handles[i]); +} + +TEST_F(MultiprocessMessagePipeTest, MessagePipeStatusChangeInTransit) { + MojoHandle local_handles[4]; + MojoHandle sent_handles[4]; + for (size_t i = 0; i < 4; ++i) + CreateMessagePipe(&local_handles[i], &sent_handles[i]); + + RUN_CHILD_ON_PIPE(MessagePipeStatusChangeInTransitClient, child) + // Send 4 handles and let their transfer race with their peers' closure. + WriteMessageWithHandles(child, "o_O", sent_handles, 4); + for (size_t i = 0; i < 4; ++i) + CloseHandle(local_handles[i]); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(BadMessageClient, MultiprocessMessagePipeTest, + parent) { + MojoHandle pipe; + EXPECT_EQ("hi", ReadMessageWithHandles(parent, &pipe, 1)); + WriteMessage(pipe, "derp"); + EXPECT_EQ("bye", ReadMessage(parent)); +} + +void OnProcessError(std::string* out_error, const std::string& error) { + *out_error = error; +} + +TEST_F(MultiprocessMessagePipeTest, NotifyBadMessage) { + const std::string kFirstErrorMessage = "everything is terrible!"; + const std::string kSecondErrorMessage = "not the bits you're looking for"; + + std::string first_process_error; + std::string second_process_error; + + set_process_error_callback(base::Bind(&OnProcessError, &first_process_error)); + RUN_CHILD_ON_PIPE(BadMessageClient, child1) + set_process_error_callback(base::Bind(&OnProcessError, + &second_process_error)); + RUN_CHILD_ON_PIPE(BadMessageClient, child2) + MojoHandle a, b, c, d; + CreateMessagePipe(&a, &b); + CreateMessagePipe(&c, &d); + WriteMessageWithHandles(child1, "hi", &b, 1); + WriteMessageWithHandles(child2, "hi", &d, 1); + + // Read a message from the pipe we sent to child1 and flag it as bad. + ASSERT_EQ(MOJO_RESULT_OK, WaitForSignals(a, MOJO_HANDLE_SIGNAL_READABLE)); + uint32_t num_bytes = 0; + MojoMessageHandle message; + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessageNew(a, &message, &num_bytes, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoNotifyBadMessage(message, kFirstErrorMessage.data(), + kFirstErrorMessage.size())); + EXPECT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); + + // Read a message from the pipe we sent to child2 and flag it as bad. + ASSERT_EQ(MOJO_RESULT_OK, WaitForSignals(c, MOJO_HANDLE_SIGNAL_READABLE)); + ASSERT_EQ(MOJO_RESULT_OK, + MojoReadMessageNew(c, &message, &num_bytes, nullptr, 0, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoNotifyBadMessage(message, kSecondErrorMessage.data(), + kSecondErrorMessage.size())); + EXPECT_EQ(MOJO_RESULT_OK, MojoFreeMessage(message)); + + WriteMessage(child2, "bye"); + END_CHILD(); + + WriteMessage(child1, "bye"); + END_CHILD() + + // The error messages should match the processes which triggered them. + EXPECT_NE(std::string::npos, first_process_error.find(kFirstErrorMessage)); + EXPECT_NE(std::string::npos, second_process_error.find(kSecondErrorMessage)); +} +INSTANTIATE_TEST_CASE_P( + , + MultiprocessMessagePipeTestWithPeerSupport, + testing::Values(test::MojoTestBase::LaunchType::CHILD, + test::MojoTestBase::LaunchType::PEER, + test::MojoTestBase::LaunchType::NAMED_CHILD, + test::MojoTestBase::LaunchType::NAMED_PEER)); +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/node_channel.cc b/mojo/edk/system/node_channel.cc new file mode 100644 index 0000000..b0f770d --- /dev/null +++ b/mojo/edk/system/node_channel.cc @@ -0,0 +1,905 @@ +// 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/edk/system/node_channel.h" + +#include +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "mojo/edk/system/channel.h" +#include "mojo/edk/system/request_context.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + +namespace mojo { +namespace edk { + +namespace { + +template +T Align(T t) { + const auto k = kChannelMessageAlignment; + return t + (k - (t % k)) % k; +} + +// NOTE: Please ONLY append messages to the end of this enum. +enum class MessageType : uint32_t { + ACCEPT_CHILD, + ACCEPT_PARENT, + ADD_BROKER_CLIENT, + BROKER_CLIENT_ADDED, + ACCEPT_BROKER_CLIENT, + PORTS_MESSAGE, + REQUEST_PORT_MERGE, + REQUEST_INTRODUCTION, + INTRODUCE, +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + RELAY_PORTS_MESSAGE, +#endif + BROADCAST, +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + PORTS_MESSAGE_FROM_RELAY, +#endif + ACCEPT_PEER, +}; + +struct Header { + MessageType type; + uint32_t padding; +}; + +static_assert(IsAlignedForChannelMessage(sizeof(Header)), + "Invalid header size."); + +struct AcceptChildData { + ports::NodeName parent_name; + ports::NodeName token; +}; + +struct AcceptParentData { + ports::NodeName token; + ports::NodeName child_name; +}; + +struct AcceptPeerData { + ports::NodeName token; + ports::NodeName peer_name; + ports::PortName port_name; +}; + +// This message may include a process handle on plaforms that require it. +struct AddBrokerClientData { + ports::NodeName client_name; +#if !defined(OS_WIN) + uint32_t process_handle; + uint32_t padding; +#endif +}; + +#if !defined(OS_WIN) +static_assert(sizeof(base::ProcessHandle) == sizeof(uint32_t), + "Unexpected pid size"); +static_assert(sizeof(AddBrokerClientData) % kChannelMessageAlignment == 0, + "Invalid AddBrokerClientData size."); +#endif + +// This data is followed by a platform channel handle to the broker. +struct BrokerClientAddedData { + ports::NodeName client_name; +}; + +// This data may be followed by a platform channel handle to the broker. If not, +// then the parent is the broker and its channel should be used as such. +struct AcceptBrokerClientData { + ports::NodeName broker_name; +}; + +// This is followed by arbitrary payload data which is interpreted as a token +// string for port location. +struct RequestPortMergeData { + ports::PortName connector_port_name; +}; + +// Used for both REQUEST_INTRODUCTION and INTRODUCE. +// +// For INTRODUCE the message also includes a valid platform handle for a channel +// the receiver may use to communicate with the named node directly, or an +// invalid platform handle if the node is unknown to the sender or otherwise +// cannot be introduced. +struct IntroductionData { + ports::NodeName name; +}; + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) +// This struct is followed by the full payload of a message to be relayed. +struct RelayPortsMessageData { + ports::NodeName destination; +}; + +// This struct is followed by the full payload of a relayed message. +struct PortsMessageFromRelayData { + ports::NodeName source; +}; +#endif + +template +Channel::MessagePtr CreateMessage(MessageType type, + size_t payload_size, + size_t num_handles, + DataType** out_data) { + Channel::MessagePtr message( + new Channel::Message(sizeof(Header) + payload_size, num_handles)); + Header* header = reinterpret_cast(message->mutable_payload()); + header->type = type; + header->padding = 0; + *out_data = reinterpret_cast(&header[1]); + return message; +} + +template +bool GetMessagePayload(const void* bytes, + size_t num_bytes, + DataType** out_data) { + static_assert(sizeof(DataType) > 0, "DataType must have non-zero size."); + if (num_bytes < sizeof(Header) + sizeof(DataType)) + return false; + *out_data = reinterpret_cast( + static_cast(bytes) + sizeof(Header)); + return true; +} + +} // namespace + +// static +scoped_refptr NodeChannel::Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner, + const ProcessErrorCallback& process_error_callback) { +#if defined(OS_NACL_SFI) + LOG(FATAL) << "Multi-process not yet supported on NaCl-SFI"; + return nullptr; +#else + return new NodeChannel(delegate, std::move(connection_params), io_task_runner, + process_error_callback); +#endif +} + +// static +Channel::MessagePtr NodeChannel::CreatePortsMessage(size_t payload_size, + void** payload, + size_t num_handles) { + return CreateMessage(MessageType::PORTS_MESSAGE, payload_size, num_handles, + payload); +} + +// static +void NodeChannel::GetPortsMessageData(Channel::Message* message, + void** data, + size_t* num_data_bytes) { + *data = reinterpret_cast(message->mutable_payload()) + 1; + *num_data_bytes = message->payload_size() - sizeof(Header); +} + +void NodeChannel::Start() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) + relay->AddObserver(this); +#endif + + base::AutoLock lock(channel_lock_); + // ShutDown() may have already been called, in which case |channel_| is null. + if (channel_) + channel_->Start(); +} + +void NodeChannel::ShutDown() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) + relay->RemoveObserver(this); +#endif + + base::AutoLock lock(channel_lock_); + if (channel_) { + channel_->ShutDown(); + channel_ = nullptr; + } +} + +void NodeChannel::LeakHandleOnShutdown() { + base::AutoLock lock(channel_lock_); + if (channel_) { + channel_->LeakHandle(); + } +} + +void NodeChannel::NotifyBadMessage(const std::string& error) { + if (!process_error_callback_.is_null()) + process_error_callback_.Run("Received bad user message: " + error); +} + +void NodeChannel::SetRemoteProcessHandle(base::ProcessHandle process_handle) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + base::AutoLock lock(remote_process_handle_lock_); + DCHECK_EQ(base::kNullProcessHandle, remote_process_handle_); + CHECK_NE(remote_process_handle_, base::GetCurrentProcessHandle()); + remote_process_handle_ = process_handle; +#if defined(OS_WIN) + DCHECK(!scoped_remote_process_handle_.is_valid()); + scoped_remote_process_handle_.reset(PlatformHandle(process_handle)); +#endif +} + +bool NodeChannel::HasRemoteProcessHandle() { + base::AutoLock lock(remote_process_handle_lock_); + return remote_process_handle_ != base::kNullProcessHandle; +} + +base::ProcessHandle NodeChannel::CopyRemoteProcessHandle() { + base::AutoLock lock(remote_process_handle_lock_); +#if defined(OS_WIN) + if (remote_process_handle_ != base::kNullProcessHandle) { + // Privileged nodes use this to pass their childrens' process handles to the + // broker on launch. + HANDLE handle = remote_process_handle_; + BOOL result = DuplicateHandle( + base::GetCurrentProcessHandle(), remote_process_handle_, + base::GetCurrentProcessHandle(), &handle, 0, FALSE, + DUPLICATE_SAME_ACCESS); + DPCHECK(result); + return handle; + } + return base::kNullProcessHandle; +#else + return remote_process_handle_; +#endif +} + +void NodeChannel::SetRemoteNodeName(const ports::NodeName& name) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + remote_node_name_ = name; +} + +void NodeChannel::AcceptChild(const ports::NodeName& parent_name, + const ports::NodeName& token) { + AcceptChildData* data; + Channel::MessagePtr message = CreateMessage( + MessageType::ACCEPT_CHILD, sizeof(AcceptChildData), 0, &data); + data->parent_name = parent_name; + data->token = token; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::AcceptParent(const ports::NodeName& token, + const ports::NodeName& child_name) { + AcceptParentData* data; + Channel::MessagePtr message = CreateMessage( + MessageType::ACCEPT_PARENT, sizeof(AcceptParentData), 0, &data); + data->token = token; + data->child_name = child_name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::AcceptPeer(const ports::NodeName& sender_name, + const ports::NodeName& token, + const ports::PortName& port_name) { + AcceptPeerData* data; + Channel::MessagePtr message = + CreateMessage(MessageType::ACCEPT_PEER, sizeof(AcceptPeerData), 0, &data); + data->token = token; + data->peer_name = sender_name; + data->port_name = port_name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::AddBrokerClient(const ports::NodeName& client_name, + base::ProcessHandle process_handle) { + AddBrokerClientData* data; + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector()); +#if defined(OS_WIN) + handles->push_back(PlatformHandle(process_handle)); +#endif + Channel::MessagePtr message = CreateMessage( + MessageType::ADD_BROKER_CLIENT, sizeof(AddBrokerClientData), + handles->size(), &data); + message->SetHandles(std::move(handles)); + data->client_name = client_name; +#if !defined(OS_WIN) + data->process_handle = process_handle; + data->padding = 0; +#endif + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::BrokerClientAdded(const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel) { + BrokerClientAddedData* data; + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector()); + if (broker_channel.is_valid()) + handles->push_back(broker_channel.release()); + Channel::MessagePtr message = CreateMessage( + MessageType::BROKER_CLIENT_ADDED, sizeof(BrokerClientAddedData), + handles->size(), &data); + message->SetHandles(std::move(handles)); + data->client_name = client_name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::AcceptBrokerClient(const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel) { + AcceptBrokerClientData* data; + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector()); + if (broker_channel.is_valid()) + handles->push_back(broker_channel.release()); + Channel::MessagePtr message = CreateMessage( + MessageType::ACCEPT_BROKER_CLIENT, sizeof(AcceptBrokerClientData), + handles->size(), &data); + message->SetHandles(std::move(handles)); + data->broker_name = broker_name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::PortsMessage(Channel::MessagePtr message) { + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::RequestPortMerge(const ports::PortName& connector_port_name, + const std::string& token) { + RequestPortMergeData* data; + Channel::MessagePtr message = CreateMessage( + MessageType::REQUEST_PORT_MERGE, + sizeof(RequestPortMergeData) + token.size(), 0, &data); + data->connector_port_name = connector_port_name; + memcpy(data + 1, token.data(), token.size()); + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::RequestIntroduction(const ports::NodeName& name) { + IntroductionData* data; + Channel::MessagePtr message = CreateMessage( + MessageType::REQUEST_INTRODUCTION, sizeof(IntroductionData), 0, &data); + data->name = name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::Introduce(const ports::NodeName& name, + ScopedPlatformHandle channel_handle) { + IntroductionData* data; + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector()); + if (channel_handle.is_valid()) + handles->push_back(channel_handle.release()); + Channel::MessagePtr message = CreateMessage( + MessageType::INTRODUCE, sizeof(IntroductionData), handles->size(), &data); + message->SetHandles(std::move(handles)); + data->name = name; + WriteChannelMessage(std::move(message)); +} + +void NodeChannel::Broadcast(Channel::MessagePtr message) { + DCHECK(!message->has_handles()); + void* data; + Channel::MessagePtr broadcast_message = CreateMessage( + MessageType::BROADCAST, message->data_num_bytes(), 0, &data); + memcpy(data, message->data(), message->data_num_bytes()); + WriteChannelMessage(std::move(broadcast_message)); +} + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) +void NodeChannel::RelayPortsMessage(const ports::NodeName& destination, + Channel::MessagePtr message) { +#if defined(OS_WIN) + DCHECK(message->has_handles()); + + // Note that this is only used on Windows, and on Windows all platform + // handles are included in the message data. We blindly copy all the data + // here and the relay node (the parent) will duplicate handles as needed. + size_t num_bytes = sizeof(RelayPortsMessageData) + message->data_num_bytes(); + RelayPortsMessageData* data; + Channel::MessagePtr relay_message = CreateMessage( + MessageType::RELAY_PORTS_MESSAGE, num_bytes, 0, &data); + data->destination = destination; + memcpy(data + 1, message->data(), message->data_num_bytes()); + + // When the handles are duplicated in the parent, the source handles will + // be closed. If the parent never receives this message then these handles + // will leak, but that means something else has probably broken and the + // sending process won't likely be around much longer. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + handles->clear(); + +#else + DCHECK(message->has_mach_ports()); + + // On OSX, the handles are extracted from the relayed message and attached to + // the wrapper. The broker then takes the handles attached to the wrapper and + // moves them back to the relayed message. This is necessary because the + // message may contain fds which need to be attached to the outer message so + // that they can be transferred to the broker. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + size_t num_bytes = sizeof(RelayPortsMessageData) + message->data_num_bytes(); + RelayPortsMessageData* data; + Channel::MessagePtr relay_message = CreateMessage( + MessageType::RELAY_PORTS_MESSAGE, num_bytes, handles->size(), &data); + data->destination = destination; + memcpy(data + 1, message->data(), message->data_num_bytes()); + relay_message->SetHandles(std::move(handles)); +#endif // defined(OS_WIN) + + WriteChannelMessage(std::move(relay_message)); +} + +void NodeChannel::PortsMessageFromRelay(const ports::NodeName& source, + Channel::MessagePtr message) { + size_t num_bytes = sizeof(PortsMessageFromRelayData) + + message->payload_size(); + PortsMessageFromRelayData* data; + Channel::MessagePtr relayed_message = CreateMessage( + MessageType::PORTS_MESSAGE_FROM_RELAY, num_bytes, message->num_handles(), + &data); + data->source = source; + if (message->payload_size()) + memcpy(data + 1, message->payload(), message->payload_size()); + relayed_message->SetHandles(message->TakeHandles()); + WriteChannelMessage(std::move(relayed_message)); +} +#endif // defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + +NodeChannel::NodeChannel(Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner, + const ProcessErrorCallback& process_error_callback) + : delegate_(delegate), + io_task_runner_(io_task_runner), + process_error_callback_(process_error_callback) +#if !defined(OS_NACL_SFI) + , + channel_( + Channel::Create(this, std::move(connection_params), io_task_runner_)) +#endif +{ +} + +NodeChannel::~NodeChannel() { + ShutDown(); +} + +void NodeChannel::OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + RequestContext request_context(RequestContext::Source::SYSTEM); + + // Ensure this NodeChannel stays alive through the extent of this method. The + // delegate may have the only other reference to this object and it may choose + // to drop it here in response to, e.g., a malformed message. + scoped_refptr keepalive = this; + +#if defined(OS_WIN) + // If we receive handles from a known process, rewrite them to our own + // process. This can occur when a privileged node receives handles directly + // from a privileged descendant. + { + base::AutoLock lock(remote_process_handle_lock_); + if (handles && remote_process_handle_ != base::kNullProcessHandle) { + // Note that we explicitly mark the handles as being owned by the sending + // process before rewriting them, in order to accommodate RewriteHandles' + // internal sanity checks. + for (auto& handle : *handles) + handle.owning_process = remote_process_handle_; + if (!Channel::Message::RewriteHandles(remote_process_handle_, + base::GetCurrentProcessHandle(), + handles.get())) { + DLOG(ERROR) << "Received one or more invalid handles."; + } + } else if (handles) { + // Handles received by an unknown process must already be owned by us. + for (auto& handle : *handles) + handle.owning_process = base::GetCurrentProcessHandle(); + } + } +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // If we're not the root, receive any mach ports from the message. If we're + // the root, the only message containing mach ports should be a + // RELAY_PORTS_MESSAGE. + { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (handles && !relay) { + if (!MachPortRelay::ReceivePorts(handles.get())) { + LOG(ERROR) << "Error receiving mach ports."; + } + } + } +#endif // defined(OS_WIN) + + + if (payload_size <= sizeof(Header)) { + delegate_->OnChannelError(remote_node_name_, this); + return; + } + + const Header* header = static_cast(payload); + switch (header->type) { + case MessageType::ACCEPT_CHILD: { + const AcceptChildData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + delegate_->OnAcceptChild(remote_node_name_, data->parent_name, + data->token); + return; + } + break; + } + + case MessageType::ACCEPT_PARENT: { + const AcceptParentData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + delegate_->OnAcceptParent(remote_node_name_, data->token, + data->child_name); + return; + } + break; + } + + case MessageType::ADD_BROKER_CLIENT: { + const AddBrokerClientData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + ScopedPlatformHandle process_handle; +#if defined(OS_WIN) + if (!handles || handles->size() != 1) { + DLOG(ERROR) << "Dropping invalid AddBrokerClient message."; + break; + } + process_handle = ScopedPlatformHandle(handles->at(0)); + handles->clear(); + delegate_->OnAddBrokerClient(remote_node_name_, data->client_name, + process_handle.release().handle); +#else + if (handles && handles->size() != 0) { + DLOG(ERROR) << "Dropping invalid AddBrokerClient message."; + break; + } + delegate_->OnAddBrokerClient(remote_node_name_, data->client_name, + data->process_handle); +#endif + return; + } + break; + } + + case MessageType::BROKER_CLIENT_ADDED: { + const BrokerClientAddedData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + ScopedPlatformHandle broker_channel; + if (!handles || handles->size() != 1) { + DLOG(ERROR) << "Dropping invalid BrokerClientAdded message."; + break; + } + broker_channel = ScopedPlatformHandle(handles->at(0)); + handles->clear(); + delegate_->OnBrokerClientAdded(remote_node_name_, data->client_name, + std::move(broker_channel)); + return; + } + break; + } + + case MessageType::ACCEPT_BROKER_CLIENT: { + const AcceptBrokerClientData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + ScopedPlatformHandle broker_channel; + if (handles && handles->size() > 1) { + DLOG(ERROR) << "Dropping invalid AcceptBrokerClient message."; + break; + } + if (handles && handles->size() == 1) { + broker_channel = ScopedPlatformHandle(handles->at(0)); + handles->clear(); + } + delegate_->OnAcceptBrokerClient(remote_node_name_, data->broker_name, + std::move(broker_channel)); + return; + } + break; + } + + case MessageType::PORTS_MESSAGE: { + size_t num_handles = handles ? handles->size() : 0; + Channel::MessagePtr message( + new Channel::Message(payload_size, num_handles)); + message->SetHandles(std::move(handles)); + memcpy(message->mutable_payload(), payload, payload_size); + delegate_->OnPortsMessage(remote_node_name_, std::move(message)); + return; + } + + case MessageType::REQUEST_PORT_MERGE: { + const RequestPortMergeData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + // Don't accept an empty token. + size_t token_size = payload_size - sizeof(*data) - sizeof(Header); + if (token_size == 0) + break; + std::string token(reinterpret_cast(data + 1), token_size); + delegate_->OnRequestPortMerge(remote_node_name_, + data->connector_port_name, token); + return; + } + break; + } + + case MessageType::REQUEST_INTRODUCTION: { + const IntroductionData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + delegate_->OnRequestIntroduction(remote_node_name_, data->name); + return; + } + break; + } + + case MessageType::INTRODUCE: { + const IntroductionData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + if (handles && handles->size() > 1) { + DLOG(ERROR) << "Dropping invalid introduction message."; + break; + } + ScopedPlatformHandle channel_handle; + if (handles && handles->size() == 1) { + channel_handle = ScopedPlatformHandle(handles->at(0)); + handles->clear(); + } + delegate_->OnIntroduce(remote_node_name_, data->name, + std::move(channel_handle)); + return; + } + break; + } + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + case MessageType::RELAY_PORTS_MESSAGE: { + base::ProcessHandle from_process; + { + base::AutoLock lock(remote_process_handle_lock_); + from_process = remote_process_handle_; + } + const RelayPortsMessageData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + // Don't try to relay an empty message. + if (payload_size <= sizeof(Header) + sizeof(RelayPortsMessageData)) + break; + + const void* message_start = data + 1; + Channel::MessagePtr message = Channel::Message::Deserialize( + message_start, payload_size - sizeof(Header) - sizeof(*data)); + if (!message) { + DLOG(ERROR) << "Dropping invalid relay message."; + break; + } + #if defined(OS_MACOSX) && !defined(OS_IOS) + message->SetHandles(std::move(handles)); + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (!relay) { + LOG(ERROR) << "Receiving mach ports without a port relay from " + << remote_node_name_ << ". Dropping message."; + break; + } + { + base::AutoLock lock(pending_mach_messages_lock_); + if (relay->port_provider()->TaskForPid(from_process) == + MACH_PORT_NULL) { + pending_relay_messages_.push( + std::make_pair(data->destination, std::move(message))); + break; + } + } + #endif + delegate_->OnRelayPortsMessage(remote_node_name_, from_process, + data->destination, std::move(message)); + return; + } + break; + } +#endif + + case MessageType::BROADCAST: { + if (payload_size <= sizeof(Header)) + break; + const void* data = static_cast( + reinterpret_cast(payload) + 1); + Channel::MessagePtr message = + Channel::Message::Deserialize(data, payload_size - sizeof(Header)); + if (!message || message->has_handles()) { + DLOG(ERROR) << "Dropping invalid broadcast message."; + break; + } + delegate_->OnBroadcast(remote_node_name_, std::move(message)); + return; + } + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + case MessageType::PORTS_MESSAGE_FROM_RELAY: + const PortsMessageFromRelayData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + size_t num_bytes = payload_size - sizeof(*data); + if (num_bytes < sizeof(Header)) + break; + num_bytes -= sizeof(Header); + + size_t num_handles = handles ? handles->size() : 0; + Channel::MessagePtr message( + new Channel::Message(num_bytes, num_handles)); + message->SetHandles(std::move(handles)); + if (num_bytes) + memcpy(message->mutable_payload(), data + 1, num_bytes); + delegate_->OnPortsMessageFromRelay( + remote_node_name_, data->source, std::move(message)); + return; + } + break; + +#endif // defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + + case MessageType::ACCEPT_PEER: { + const AcceptPeerData* data; + if (GetMessagePayload(payload, payload_size, &data)) { + delegate_->OnAcceptPeer(remote_node_name_, data->token, data->peer_name, + data->port_name); + return; + } + break; + } + + default: + break; + } + + DLOG(ERROR) << "Received invalid message. Closing channel."; + delegate_->OnChannelError(remote_node_name_, this); +} + +void NodeChannel::OnChannelError() { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + RequestContext request_context(RequestContext::Source::SYSTEM); + + ShutDown(); + // |OnChannelError()| may cause |this| to be destroyed, but still need access + // to the name name after that destruction. So may a copy of + // |remote_node_name_| so it can be used if |this| becomes destroyed. + ports::NodeName node_name = remote_node_name_; + delegate_->OnChannelError(node_name, this); +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +void NodeChannel::OnProcessReady(base::ProcessHandle process) { + io_task_runner_->PostTask(FROM_HERE, base::Bind( + &NodeChannel::ProcessPendingMessagesWithMachPorts, this)); +} + +void NodeChannel::ProcessPendingMessagesWithMachPorts() { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + DCHECK(relay); + + base::ProcessHandle remote_process_handle; + { + base::AutoLock lock(remote_process_handle_lock_); + remote_process_handle = remote_process_handle_; + } + PendingMessageQueue pending_writes; + PendingRelayMessageQueue pending_relays; + { + base::AutoLock lock(pending_mach_messages_lock_); + pending_writes.swap(pending_write_messages_); + pending_relays.swap(pending_relay_messages_); + } + + while (!pending_writes.empty()) { + Channel::MessagePtr message = std::move(pending_writes.front()); + pending_writes.pop(); + if (!relay->SendPortsToProcess(message.get(), remote_process_handle)) { + LOG(ERROR) << "Error on sending mach ports. Remote process is likely " + << "gone. Dropping message."; + return; + } + + base::AutoLock lock(channel_lock_); + if (!channel_) { + DLOG(ERROR) << "Dropping message on closed channel."; + break; + } else { + channel_->Write(std::move(message)); + } + } + + // Ensure this NodeChannel stays alive while flushing relay messages. + scoped_refptr keepalive = this; + + while (!pending_relays.empty()) { + ports::NodeName destination = pending_relays.front().first; + Channel::MessagePtr message = std::move(pending_relays.front().second); + pending_relays.pop(); + delegate_->OnRelayPortsMessage(remote_node_name_, remote_process_handle, + destination, std::move(message)); + } +} +#endif + +void NodeChannel::WriteChannelMessage(Channel::MessagePtr message) { +#if defined(OS_WIN) + // Map handles to the destination process. Note: only messages from a + // privileged node should contain handles on Windows. If an unprivileged + // node needs to send handles, it should do so via RelayPortsMessage which + // stashes the handles in the message in such a way that they go undetected + // here (they'll be unpacked and duplicated by a privileged parent.) + + if (message->has_handles()) { + base::ProcessHandle remote_process_handle; + { + base::AutoLock lock(remote_process_handle_lock_); + remote_process_handle = remote_process_handle_; + } + + // Rewrite outgoing handles if we have a handle to the destination process. + if (remote_process_handle != base::kNullProcessHandle) { + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + if (!Channel::Message::RewriteHandles(base::GetCurrentProcessHandle(), + remote_process_handle, + handles.get())) { + DLOG(ERROR) << "Failed to duplicate one or more outgoing handles."; + } + message->SetHandles(std::move(handles)); + } + } +#elif defined(OS_MACOSX) && !defined(OS_IOS) + // On OSX, we need to transfer mach ports to the destination process before + // transferring the message itself. + if (message->has_mach_ports()) { + MachPortRelay* relay = delegate_->GetMachPortRelay(); + if (relay) { + base::ProcessHandle remote_process_handle; + { + base::AutoLock lock(remote_process_handle_lock_); + // Expect that the receiving node is a child. + DCHECK(remote_process_handle_ != base::kNullProcessHandle); + remote_process_handle = remote_process_handle_; + } + { + base::AutoLock lock(pending_mach_messages_lock_); + if (relay->port_provider()->TaskForPid(remote_process_handle) == + MACH_PORT_NULL) { + // It is also possible for TaskForPid() to return MACH_PORT_NULL when + // the process has started, then died. In that case, the queued + // message will never be processed. But that's fine since we're about + // to die anyway. + pending_write_messages_.push(std::move(message)); + return; + } + } + + if (!relay->SendPortsToProcess(message.get(), remote_process_handle)) { + LOG(ERROR) << "Error on sending mach ports. Remote process is likely " + << "gone. Dropping message."; + return; + } + } + } +#endif + + base::AutoLock lock(channel_lock_); + if (!channel_) + DLOG(ERROR) << "Dropping message on closed channel."; + else + channel_->Write(std::move(message)); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/node_channel.h b/mojo/edk/system/node_channel.h new file mode 100644 index 0000000..95dc341 --- /dev/null +++ b/mojo/edk/system/node_channel.h @@ -0,0 +1,219 @@ +// 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_EDK_SYSTEM_NODE_CHANNEL_H_ +#define MOJO_EDK_SYSTEM_NODE_CHANNEL_H_ + +#include +#include +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/process/process_handle.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/connection_params.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/channel.h" +#include "mojo/edk/system/ports/name.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + +namespace mojo { +namespace edk { + +// Wraps a Channel to send and receive Node control messages. +class NodeChannel : public base::RefCountedThreadSafe, + public Channel::Delegate +#if defined(OS_MACOSX) && !defined(OS_IOS) + , public MachPortRelay::Observer +#endif + { + public: + class Delegate { + public: + virtual ~Delegate() {} + virtual void OnAcceptChild(const ports::NodeName& from_node, + const ports::NodeName& parent_name, + const ports::NodeName& token) = 0; + virtual void OnAcceptParent(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& child_name) = 0; + virtual void OnAddBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& client_name, + base::ProcessHandle process_handle) = 0; + virtual void OnBrokerClientAdded(const ports::NodeName& from_node, + const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel) = 0; + virtual void OnAcceptBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel) = 0; + virtual void OnPortsMessage(const ports::NodeName& from_node, + Channel::MessagePtr message) = 0; + virtual void OnRequestPortMerge(const ports::NodeName& from_node, + const ports::PortName& connector_port_name, + const std::string& token) = 0; + virtual void OnRequestIntroduction(const ports::NodeName& from_node, + const ports::NodeName& name) = 0; + virtual void OnIntroduce(const ports::NodeName& from_node, + const ports::NodeName& name, + ScopedPlatformHandle channel_handle) = 0; + virtual void OnBroadcast(const ports::NodeName& from_node, + Channel::MessagePtr message) = 0; +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + virtual void OnRelayPortsMessage(const ports::NodeName& from_node, + base::ProcessHandle from_process, + const ports::NodeName& destination, + Channel::MessagePtr message) = 0; + virtual void OnPortsMessageFromRelay(const ports::NodeName& from_node, + const ports::NodeName& source_node, + Channel::MessagePtr message) = 0; +#endif + virtual void OnAcceptPeer(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& peer_name, + const ports::PortName& port_name) = 0; + virtual void OnChannelError(const ports::NodeName& node, + NodeChannel* channel) = 0; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + virtual MachPortRelay* GetMachPortRelay() = 0; +#endif + }; + + static scoped_refptr Create( + Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner, + const ProcessErrorCallback& process_error_callback); + + static Channel::MessagePtr CreatePortsMessage(size_t payload_size, + void** payload, + size_t num_handles); + + static void GetPortsMessageData(Channel::Message* message, void** data, + size_t* num_data_bytes); + + // Start receiving messages. + void Start(); + + // Permanently stop the channel from sending or receiving messages. + void ShutDown(); + + // Leaks the pipe handle instead of closing it on shutdown. + void LeakHandleOnShutdown(); + + // Invokes the bad message callback for this channel, if any. + void NotifyBadMessage(const std::string& error); + + // Note: On Windows, we take ownership of the remote process handle. + void SetRemoteProcessHandle(base::ProcessHandle process_handle); + bool HasRemoteProcessHandle(); + // Note: The returned |ProcessHandle| is owned by the caller and should be + // freed if necessary. + base::ProcessHandle CopyRemoteProcessHandle(); + + // Used for context in Delegate calls (via |from_node| arguments.) + void SetRemoteNodeName(const ports::NodeName& name); + + void AcceptChild(const ports::NodeName& parent_name, + const ports::NodeName& token); + void AcceptParent(const ports::NodeName& token, + const ports::NodeName& child_name); + void AcceptPeer(const ports::NodeName& sender_name, + const ports::NodeName& token, + const ports::PortName& port_name); + void AddBrokerClient(const ports::NodeName& client_name, + base::ProcessHandle process_handle); + void BrokerClientAdded(const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel); + void AcceptBrokerClient(const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel); + void PortsMessage(Channel::MessagePtr message); + void RequestPortMerge(const ports::PortName& connector_port_name, + const std::string& token); + void RequestIntroduction(const ports::NodeName& name); + void Introduce(const ports::NodeName& name, + ScopedPlatformHandle channel_handle); + void Broadcast(Channel::MessagePtr message); + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + // Relay the message to the specified node via this channel. This is used to + // pass windows handles between two processes that do not have permission to + // duplicate handles into the other's address space. The relay process is + // assumed to have that permission. + void RelayPortsMessage(const ports::NodeName& destination, + Channel::MessagePtr message); + + // Sends a message to its destination from a relay. This is interpreted by the + // receiver similarly to PortsMessage, but the original source node is + // provided as additional message metadata from the (trusted) relay node. + void PortsMessageFromRelay(const ports::NodeName& source, + Channel::MessagePtr message); +#endif + + private: + friend class base::RefCountedThreadSafe; + + using PendingMessageQueue = std::queue; + using PendingRelayMessageQueue = + std::queue>; + + NodeChannel(Delegate* delegate, + ConnectionParams connection_params, + scoped_refptr io_task_runner, + const ProcessErrorCallback& process_error_callback); + ~NodeChannel() override; + + // Channel::Delegate: + void OnChannelMessage(const void* payload, + size_t payload_size, + ScopedPlatformHandleVectorPtr handles) override; + void OnChannelError() override; + +#if defined(OS_MACOSX) && !defined(OS_IOS) + // MachPortRelay::Observer: + void OnProcessReady(base::ProcessHandle process) override; + + void ProcessPendingMessagesWithMachPorts(); +#endif + + void WriteChannelMessage(Channel::MessagePtr message); + + Delegate* const delegate_; + const scoped_refptr io_task_runner_; + const ProcessErrorCallback process_error_callback_; + + base::Lock channel_lock_; + scoped_refptr channel_; + + // Must only be accessed from |io_task_runner_|'s thread. + ports::NodeName remote_node_name_; + + base::Lock remote_process_handle_lock_; + base::ProcessHandle remote_process_handle_ = base::kNullProcessHandle; +#if defined(OS_WIN) + ScopedPlatformHandle scoped_remote_process_handle_; +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) + base::Lock pending_mach_messages_lock_; + PendingMessageQueue pending_write_messages_; + PendingRelayMessageQueue pending_relay_messages_; +#endif + + DISALLOW_COPY_AND_ASSIGN(NodeChannel); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_NODE_CHANNEL_H_ diff --git a/mojo/edk/system/node_controller.cc b/mojo/edk/system/node_controller.cc new file mode 100644 index 0000000..73b16b1 --- /dev/null +++ b/mojo/edk/system/node_controller.cc @@ -0,0 +1,1470 @@ +// 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/edk/system/node_controller.h" + +#include +#include + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram_macros.h" +#include "base/process/process_handle.h" +#include "base/rand_util.h" +#include "base/time/time.h" +#include "base/timer/elapsed_timer.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/embedder/named_platform_channel_pair.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "mojo/edk/system/broker.h" +#include "mojo/edk/system/broker_host.h" +#include "mojo/edk/system/core.h" +#include "mojo/edk/system/ports_message.h" +#include "mojo/edk/system/request_context.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "mojo/edk/system/mach_port_relay.h" +#endif + +#if !defined(OS_NACL) +#include "crypto/random.h" +#endif + +namespace mojo { +namespace edk { + +namespace { + +#if defined(OS_NACL) +template +void GenerateRandomName(T* out) { base::RandBytes(out, sizeof(T)); } +#else +template +void GenerateRandomName(T* out) { crypto::RandBytes(out, sizeof(T)); } +#endif + +ports::NodeName GetRandomNodeName() { + ports::NodeName name; + GenerateRandomName(&name); + return name; +} + +void RecordPeerCount(size_t count) { + DCHECK_LE(count, static_cast(std::numeric_limits::max())); + + // 8k is the maximum number of file descriptors allowed in Chrome. + UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.Node.ConnectedPeers", + static_cast(count), + 1 /* min */, + 8000 /* max */, + 50 /* bucket count */); +} + +void RecordPendingChildCount(size_t count) { + DCHECK_LE(count, static_cast(std::numeric_limits::max())); + + // 8k is the maximum number of file descriptors allowed in Chrome. + UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.Node.PendingChildren", + static_cast(count), + 1 /* min */, + 8000 /* max */, + 50 /* bucket count */); +} + +bool ParsePortsMessage(Channel::Message* message, + void** data, + size_t* num_data_bytes, + size_t* num_header_bytes, + size_t* num_payload_bytes, + size_t* num_ports_bytes) { + DCHECK(data && num_data_bytes && num_header_bytes && num_payload_bytes && + num_ports_bytes); + + NodeChannel::GetPortsMessageData(message, data, num_data_bytes); + if (!*num_data_bytes) + return false; + + if (!ports::Message::Parse(*data, *num_data_bytes, num_header_bytes, + num_payload_bytes, num_ports_bytes)) { + return false; + } + + return true; +} + +// Used by NodeController to watch for shutdown. Since no IO can happen once +// the IO thread is killed, the NodeController can cleanly drop all its peers +// at that time. +class ThreadDestructionObserver : + public base::MessageLoop::DestructionObserver { + public: + static void Create(scoped_refptr task_runner, + const base::Closure& callback) { + if (task_runner->RunsTasksOnCurrentThread()) { + // Owns itself. + new ThreadDestructionObserver(callback); + } else { + task_runner->PostTask(FROM_HERE, + base::Bind(&Create, task_runner, callback)); + } + } + + private: + explicit ThreadDestructionObserver(const base::Closure& callback) + : callback_(callback) { + base::MessageLoop::current()->AddDestructionObserver(this); + } + + ~ThreadDestructionObserver() override { + base::MessageLoop::current()->RemoveDestructionObserver(this); + } + + // base::MessageLoop::DestructionObserver: + void WillDestroyCurrentMessageLoop() override { + callback_.Run(); + delete this; + } + + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadDestructionObserver); +}; + +} // namespace + +NodeController::~NodeController() {} + +NodeController::NodeController(Core* core) + : core_(core), + name_(GetRandomNodeName()), + node_(new ports::Node(name_, this)) { + DVLOG(1) << "Initializing node " << name_; +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +void NodeController::CreateMachPortRelay( + base::PortProvider* port_provider) { + base::AutoLock lock(mach_port_relay_lock_); + DCHECK(!mach_port_relay_); + mach_port_relay_.reset(new MachPortRelay(port_provider)); +} +#endif + +void NodeController::SetIOTaskRunner( + scoped_refptr task_runner) { + io_task_runner_ = task_runner; + ThreadDestructionObserver::Create( + io_task_runner_, + base::Bind(&NodeController::DropAllPeers, base::Unretained(this))); +} + +void NodeController::ConnectToChild( + base::ProcessHandle process_handle, + ConnectionParams connection_params, + const std::string& child_token, + const ProcessErrorCallback& process_error_callback) { + // Generate the temporary remote node name here so that it can be associated + // with the embedder's child_token. If an error occurs in the child process + // after it is launched, but before any reserved ports are connected, this can + // be used to clean up any dangling ports. + ports::NodeName node_name; + GenerateRandomName(&node_name); + + { + base::AutoLock lock(reserved_ports_lock_); + bool inserted = pending_child_tokens_.insert( + std::make_pair(node_name, child_token)).second; + DCHECK(inserted); + } + +#if defined(OS_WIN) + // On Windows, we need to duplicate the process handle because we have no + // control over its lifetime and it may become invalid by the time the posted + // task runs. + HANDLE dup_handle = INVALID_HANDLE_VALUE; + BOOL ok = ::DuplicateHandle( + base::GetCurrentProcessHandle(), process_handle, + base::GetCurrentProcessHandle(), &dup_handle, + 0, FALSE, DUPLICATE_SAME_ACCESS); + DPCHECK(ok); + process_handle = dup_handle; +#endif + + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&NodeController::ConnectToChildOnIOThread, + base::Unretained(this), process_handle, + base::Passed(&connection_params), node_name, + process_error_callback)); +} + +void NodeController::CloseChildPorts(const std::string& child_token) { + std::vector ports_to_close; + { + std::vector port_tokens; + base::AutoLock lock(reserved_ports_lock_); + for (const auto& port : reserved_ports_) { + if (port.second.child_token == child_token) { + DVLOG(1) << "Closing reserved port " << port.second.port.name(); + ports_to_close.push_back(port.second.port); + port_tokens.push_back(port.first); + } + } + + for (const auto& token : port_tokens) + reserved_ports_.erase(token); + } + + for (const auto& port : ports_to_close) + node_->ClosePort(port); + + // Ensure local port closure messages are processed. + AcceptIncomingMessages(); +} + +void NodeController::ClosePeerConnection(const std::string& peer_token) { + io_task_runner_->PostTask( + FROM_HERE, base::Bind(&NodeController::ClosePeerConnectionOnIOThread, + base::Unretained(this), peer_token)); +} + +void NodeController::ConnectToParent(ConnectionParams connection_params) { +#if !defined(OS_MACOSX) && !defined(OS_NACL_SFI) + // Use the bootstrap channel for the broker and receive the node's channel + // synchronously as the first message from the broker. + base::ElapsedTimer timer; + broker_.reset(new Broker(connection_params.TakeChannelHandle())); + ScopedPlatformHandle platform_handle = broker_->GetParentPlatformHandle(); + UMA_HISTOGRAM_TIMES("Mojo.System.GetParentPlatformHandleSyncTime", + timer.Elapsed()); + + if (!platform_handle.is_valid()) { + // Most likely the browser side of the channel has already been closed and + // the broker was unable to negotiate a NodeChannel pipe. In this case we + // can cancel parent connection. + DVLOG(1) << "Cannot connect to invalid parent channel."; + CancelPendingPortMerges(); + return; + } + connection_params = ConnectionParams(std::move(platform_handle)); +#endif + + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NodeController::ConnectToParentOnIOThread, + base::Unretained(this), base::Passed(&connection_params))); +} + +void NodeController::ConnectToPeer(ConnectionParams connection_params, + const ports::PortRef& port, + const std::string& peer_token) { + ports::NodeName node_name; + GenerateRandomName(&node_name); + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NodeController::ConnectToPeerOnIOThread, + base::Unretained(this), base::Passed(&connection_params), + node_name, port, peer_token)); +} + +void NodeController::SetPortObserver(const ports::PortRef& port, + scoped_refptr observer) { + node_->SetUserData(port, std::move(observer)); +} + +void NodeController::ClosePort(const ports::PortRef& port) { + SetPortObserver(port, nullptr); + int rv = node_->ClosePort(port); + DCHECK_EQ(rv, ports::OK) << " Failed to close port: " << port.name(); + + AcceptIncomingMessages(); +} + +int NodeController::SendMessage(const ports::PortRef& port, + std::unique_ptr message) { + ports::ScopedMessage ports_message(message.release()); + int rv = node_->SendMessage(port, std::move(ports_message)); + + AcceptIncomingMessages(); + return rv; +} + +void NodeController::ReservePort(const std::string& token, + const ports::PortRef& port, + const std::string& child_token) { + DVLOG(2) << "Reserving port " << port.name() << "@" << name_ << " for token " + << token; + + base::AutoLock lock(reserved_ports_lock_); + auto result = reserved_ports_.insert( + std::make_pair(token, ReservedPort{port, child_token})); + DCHECK(result.second); +} + +void NodeController::MergePortIntoParent(const std::string& token, + const ports::PortRef& port) { + bool was_merged = false; + { + // This request may be coming from within the process that reserved the + // "parent" side (e.g. for Chrome single-process mode), so if this token is + // reserved locally, merge locally instead. + base::AutoLock lock(reserved_ports_lock_); + auto it = reserved_ports_.find(token); + if (it != reserved_ports_.end()) { + node_->MergePorts(port, name_, it->second.port.name()); + reserved_ports_.erase(it); + was_merged = true; + } + } + if (was_merged) { + AcceptIncomingMessages(); + return; + } + + scoped_refptr parent; + bool reject_merge = false; + { + // Hold |pending_port_merges_lock_| while getting |parent|. Otherwise, + // there is a race where the parent can be set, and |pending_port_merges_| + // be processed between retrieving |parent| and adding the merge to + // |pending_port_merges_|. + base::AutoLock lock(pending_port_merges_lock_); + parent = GetParentChannel(); + if (reject_pending_merges_) { + reject_merge = true; + } else if (!parent) { + pending_port_merges_.push_back(std::make_pair(token, port)); + return; + } + } + if (reject_merge) { + node_->ClosePort(port); + DVLOG(2) << "Rejecting port merge for token " << token + << " due to closed parent channel."; + AcceptIncomingMessages(); + return; + } + + parent->RequestPortMerge(port.name(), token); +} + +int NodeController::MergeLocalPorts(const ports::PortRef& port0, + const ports::PortRef& port1) { + int rv = node_->MergeLocalPorts(port0, port1); + AcceptIncomingMessages(); + return rv; +} + +scoped_refptr NodeController::CreateSharedBuffer( + size_t num_bytes) { +#if !defined(OS_MACOSX) && !defined(OS_NACL_SFI) + // Shared buffer creation failure is fatal, so always use the broker when we + // have one. This does mean that a non-root process that has children will use + // the broker for shared buffer creation even though that process is + // privileged. + if (broker_) { + return broker_->GetSharedBuffer(num_bytes); + } +#endif + return PlatformSharedBuffer::Create(num_bytes); +} + +void NodeController::RequestShutdown(const base::Closure& callback) { + { + base::AutoLock lock(shutdown_lock_); + shutdown_callback_ = callback; + shutdown_callback_flag_.Set(true); + } + + AttemptShutdownIfRequested(); +} + +void NodeController::NotifyBadMessageFrom(const ports::NodeName& source_node, + const std::string& error) { + scoped_refptr peer = GetPeerChannel(source_node); + if (peer) + peer->NotifyBadMessage(error); +} + +void NodeController::ConnectToChildOnIOThread( + base::ProcessHandle process_handle, + ConnectionParams connection_params, + ports::NodeName token, + const ProcessErrorCallback& process_error_callback) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + +#if !defined(OS_MACOSX) && !defined(OS_NACL) + PlatformChannelPair node_channel; + ScopedPlatformHandle server_handle = node_channel.PassServerHandle(); + // BrokerHost owns itself. + BrokerHost* broker_host = + new BrokerHost(process_handle, connection_params.TakeChannelHandle()); + bool channel_ok = broker_host->SendChannel(node_channel.PassClientHandle()); + +#if defined(OS_WIN) + if (!channel_ok) { + // On Windows the above operation may fail if the channel is crossing a + // session boundary. In that case we fall back to a named pipe. + NamedPlatformChannelPair named_channel; + server_handle = named_channel.PassServerHandle(); + broker_host->SendNamedChannel(named_channel.handle().name); + } +#else + CHECK(channel_ok); +#endif // defined(OS_WIN) + + scoped_refptr channel = + NodeChannel::Create(this, ConnectionParams(std::move(server_handle)), + io_task_runner_, process_error_callback); + +#else // !defined(OS_MACOSX) && !defined(OS_NACL) + scoped_refptr channel = + NodeChannel::Create(this, std::move(connection_params), io_task_runner_, + process_error_callback); +#endif // !defined(OS_MACOSX) && !defined(OS_NACL) + + // We set up the child channel with a temporary name so it can be identified + // as a pending child if it writes any messages to the channel. We may start + // receiving messages from it (though we shouldn't) as soon as Start() is + // called below. + + pending_children_.insert(std::make_pair(token, channel)); + RecordPendingChildCount(pending_children_.size()); + + channel->SetRemoteNodeName(token); + channel->SetRemoteProcessHandle(process_handle); + channel->Start(); + + channel->AcceptChild(name_, token); +} + +void NodeController::ConnectToParentOnIOThread( + ConnectionParams connection_params) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + { + base::AutoLock lock(parent_lock_); + DCHECK(parent_name_ == ports::kInvalidNodeName); + + // At this point we don't know the parent's name, so we can't yet insert it + // into our |peers_| map. That will happen as soon as we receive an + // AcceptChild message from them. + bootstrap_parent_channel_ = + NodeChannel::Create(this, std::move(connection_params), io_task_runner_, + ProcessErrorCallback()); + // Prevent the parent pipe handle from being closed on shutdown. Pipe + // closure is used by the parent to detect the child process has exited. + // Relying on message pipes to be closed is not enough because the parent + // may see the message pipe closure before the child is dead, causing the + // child process to be unexpectedly SIGKILL'd. + bootstrap_parent_channel_->LeakHandleOnShutdown(); + } + bootstrap_parent_channel_->Start(); +} + +void NodeController::ConnectToPeerOnIOThread(ConnectionParams connection_params, + ports::NodeName token, + ports::PortRef port, + const std::string& peer_token) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + scoped_refptr channel = NodeChannel::Create( + this, std::move(connection_params), io_task_runner_, {}); + peer_connections_.insert( + {token, PeerConnection{channel, port, peer_token}}); + peers_by_token_.insert({peer_token, token}); + + channel->SetRemoteNodeName(token); + channel->Start(); + + channel->AcceptPeer(name_, token, port.name()); +} + +void NodeController::ClosePeerConnectionOnIOThread( + const std::string& peer_token) { + RequestContext request_context(RequestContext::Source::SYSTEM); + auto peer = peers_by_token_.find(peer_token); + // The connection may already be closed. + if (peer == peers_by_token_.end()) + return; + + // |peer| may be removed so make a copy of |name|. + ports::NodeName name = peer->second; + DropPeer(name, nullptr); +} + +scoped_refptr NodeController::GetPeerChannel( + const ports::NodeName& name) { + base::AutoLock lock(peers_lock_); + auto it = peers_.find(name); + if (it == peers_.end()) + return nullptr; + return it->second; +} + +scoped_refptr NodeController::GetParentChannel() { + ports::NodeName parent_name; + { + base::AutoLock lock(parent_lock_); + parent_name = parent_name_; + } + return GetPeerChannel(parent_name); +} + +scoped_refptr NodeController::GetBrokerChannel() { + ports::NodeName broker_name; + { + base::AutoLock lock(broker_lock_); + broker_name = broker_name_; + } + return GetPeerChannel(broker_name); +} + +void NodeController::AddPeer(const ports::NodeName& name, + scoped_refptr channel, + bool start_channel) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + DCHECK(name != ports::kInvalidNodeName); + DCHECK(channel); + + channel->SetRemoteNodeName(name); + + OutgoingMessageQueue pending_messages; + { + base::AutoLock lock(peers_lock_); + if (peers_.find(name) != peers_.end()) { + // This can happen normally if two nodes race to be introduced to each + // other. The losing pipe will be silently closed and introduction should + // not be affected. + DVLOG(1) << "Ignoring duplicate peer name " << name; + return; + } + + auto result = peers_.insert(std::make_pair(name, channel)); + DCHECK(result.second); + + DVLOG(2) << "Accepting new peer " << name << " on node " << name_; + + RecordPeerCount(peers_.size()); + + auto it = pending_peer_messages_.find(name); + if (it != pending_peer_messages_.end()) { + std::swap(pending_messages, it->second); + pending_peer_messages_.erase(it); + } + } + + if (start_channel) + channel->Start(); + + // Flush any queued message we need to deliver to this node. + while (!pending_messages.empty()) { + channel->PortsMessage(std::move(pending_messages.front())); + pending_messages.pop(); + } +} + +void NodeController::DropPeer(const ports::NodeName& name, + NodeChannel* channel) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + { + base::AutoLock lock(peers_lock_); + auto it = peers_.find(name); + + if (it != peers_.end()) { + ports::NodeName peer = it->first; + peers_.erase(it); + DVLOG(1) << "Dropped peer " << peer; + } + + pending_peer_messages_.erase(name); + pending_children_.erase(name); + + RecordPeerCount(peers_.size()); + RecordPendingChildCount(pending_children_.size()); + } + + std::vector ports_to_close; + { + // Clean up any reserved ports. + base::AutoLock lock(reserved_ports_lock_); + auto it = pending_child_tokens_.find(name); + if (it != pending_child_tokens_.end()) { + const std::string& child_token = it->second; + + std::vector port_tokens; + for (const auto& port : reserved_ports_) { + if (port.second.child_token == child_token) { + DVLOG(1) << "Closing reserved port: " << port.second.port.name(); + ports_to_close.push_back(port.second.port); + port_tokens.push_back(port.first); + } + } + + // We have to erase reserved ports in a two-step manner because the usual + // manner of using the returned iterator from map::erase isn't technically + // valid in C++11 (although it is in C++14). + for (const auto& token : port_tokens) + reserved_ports_.erase(token); + + pending_child_tokens_.erase(it); + } + } + + bool is_parent; + { + base::AutoLock lock(parent_lock_); + is_parent = (name == parent_name_ || channel == bootstrap_parent_channel_); + } + + // If the error comes from the parent channel, we also need to cancel any + // port merge requests, so that errors can be propagated to the message + // pipes. + if (is_parent) + CancelPendingPortMerges(); + + auto peer = peer_connections_.find(name); + if (peer != peer_connections_.end()) { + peers_by_token_.erase(peer->second.peer_token); + ports_to_close.push_back(peer->second.local_port); + peer_connections_.erase(peer); + } + + for (const auto& port : ports_to_close) + node_->ClosePort(port); + + node_->LostConnectionToNode(name); + + AcceptIncomingMessages(); +} + +void NodeController::SendPeerMessage(const ports::NodeName& name, + ports::ScopedMessage message) { + Channel::MessagePtr channel_message = + static_cast(message.get())->TakeChannelMessage(); + + scoped_refptr peer = GetPeerChannel(name); +#if defined(OS_WIN) + if (channel_message->has_handles()) { + // If we're sending a message with handles we aren't the destination + // node's parent or broker (i.e. we don't know its process handle), ask + // the broker to relay for us. + scoped_refptr broker = GetBrokerChannel(); + if (!peer || !peer->HasRemoteProcessHandle()) { + if (broker) { + broker->RelayPortsMessage(name, std::move(channel_message)); + } else { + base::AutoLock lock(broker_lock_); + pending_relay_messages_[name].emplace(std::move(channel_message)); + } + return; + } + } +#elif defined(OS_MACOSX) && !defined(OS_IOS) + if (channel_message->has_mach_ports()) { + // Messages containing Mach ports are always routed through the broker, even + // if the broker process is the intended recipient. + bool use_broker = false; + { + base::AutoLock lock(parent_lock_); + use_broker = (bootstrap_parent_channel_ || + parent_name_ != ports::kInvalidNodeName); + } + if (use_broker) { + scoped_refptr broker = GetBrokerChannel(); + if (broker) { + broker->RelayPortsMessage(name, std::move(channel_message)); + } else { + base::AutoLock lock(broker_lock_); + pending_relay_messages_[name].emplace(std::move(channel_message)); + } + return; + } + } +#endif // defined(OS_WIN) + + if (peer) { + peer->PortsMessage(std::move(channel_message)); + return; + } + + // If we don't know who the peer is and we are the broker, we can only assume + // the peer is invalid, i.e., it's either a junk name or has already been + // disconnected. + scoped_refptr broker = GetBrokerChannel(); + if (!broker) { + DVLOG(1) << "Dropping message for unknown peer: " << name; + return; + } + + // If we aren't the broker, assume we just need to be introduced and queue + // until that can be either confirmed or denied by the broker. + bool needs_introduction = false; + { + base::AutoLock lock(peers_lock_); + auto& queue = pending_peer_messages_[name]; + needs_introduction = queue.empty(); + queue.emplace(std::move(channel_message)); + } + if (needs_introduction) + broker->RequestIntroduction(name); +} + +void NodeController::AcceptIncomingMessages() { + // This is an impactically large value which should never be reached in + // practice. See the CHECK below for usage. + constexpr size_t kMaxAcceptedMessages = 1000000; + + size_t num_messages_accepted = 0; + while (incoming_messages_flag_) { + // TODO: We may need to be more careful to avoid starving the rest of the + // thread here. Revisit this if it turns out to be a problem. One + // alternative would be to schedule a task to continue pumping messages + // after flushing once. + + messages_lock_.Acquire(); + if (incoming_messages_.empty()) { + messages_lock_.Release(); + break; + } + + // libstdc++'s deque creates an internal buffer on construction, even when + // the size is 0. So avoid creating it until it is necessary. + std::queue messages; + std::swap(messages, incoming_messages_); + incoming_messages_flag_.Set(false); + messages_lock_.Release(); + + num_messages_accepted += messages.size(); + while (!messages.empty()) { + node_->AcceptMessage(std::move(messages.front())); + messages.pop(); + } + + // This is effectively a safeguard against potential bugs which might lead + // to runaway message cycles. If any such cycles arise, we'll start seeing + // crash reports from this location. + CHECK_LE(num_messages_accepted, kMaxAcceptedMessages); + } + + if (num_messages_accepted >= 4) { + // Note: We avoid logging this histogram for the vast majority of cases. + // See https://crbug.com/685763 for more context. + UMA_HISTOGRAM_CUSTOM_COUNTS("Mojo.System.MessagesAcceptedPerEvent", + static_cast(num_messages_accepted), + 1 /* min */, + 500 /* max */, + 50 /* bucket count */); + } + + AttemptShutdownIfRequested(); +} + +void NodeController::ProcessIncomingMessages() { + RequestContext request_context(RequestContext::Source::SYSTEM); + + { + base::AutoLock lock(messages_lock_); + // Allow a new incoming messages processing task to be posted. This can't be + // done after AcceptIncomingMessages() otherwise a message might be missed. + // Doing it here may result in at most two tasks existing at the same time; + // this running one, and one pending in the task runner. + incoming_messages_task_posted_ = false; + } + + AcceptIncomingMessages(); +} + +void NodeController::DropAllPeers() { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + std::vector> all_peers; + { + base::AutoLock lock(parent_lock_); + if (bootstrap_parent_channel_) { + // |bootstrap_parent_channel_| isn't null'd here becuase we rely on its + // existence to determine whether or not this is the root node. Once + // bootstrap_parent_channel_->ShutDown() has been called, + // |bootstrap_parent_channel_| is essentially a dead object and it doesn't + // matter if it's deleted now or when |this| is deleted. + // Note: |bootstrap_parent_channel_| is only modified on the IO thread. + all_peers.push_back(bootstrap_parent_channel_); + } + } + + { + base::AutoLock lock(peers_lock_); + for (const auto& peer : peers_) + all_peers.push_back(peer.second); + for (const auto& peer : pending_children_) + all_peers.push_back(peer.second); + peers_.clear(); + pending_children_.clear(); + pending_peer_messages_.clear(); + peer_connections_.clear(); + } + + for (const auto& peer : all_peers) + peer->ShutDown(); + + if (destroy_on_io_thread_shutdown_) + delete this; +} + +void NodeController::GenerateRandomPortName(ports::PortName* port_name) { + GenerateRandomName(port_name); +} + +void NodeController::AllocMessage(size_t num_header_bytes, + ports::ScopedMessage* message) { + message->reset(new PortsMessage(num_header_bytes, 0, 0, nullptr)); +} + +void NodeController::ForwardMessage(const ports::NodeName& node, + ports::ScopedMessage message) { + DCHECK(message); + bool schedule_pump_task = false; + if (node == name_) { + // NOTE: We need to avoid re-entering the Node instance within + // ForwardMessage. Because ForwardMessage is only ever called + // (synchronously) in response to Node's ClosePort, SendMessage, or + // AcceptMessage, we flush the queue after calling any of those methods. + base::AutoLock lock(messages_lock_); + // |io_task_runner_| may be null in tests or processes that don't require + // multi-process Mojo. + schedule_pump_task = incoming_messages_.empty() && io_task_runner_ && + !incoming_messages_task_posted_; + incoming_messages_task_posted_ |= schedule_pump_task; + incoming_messages_.emplace(std::move(message)); + incoming_messages_flag_.Set(true); + } else { + SendPeerMessage(node, std::move(message)); + } + + if (schedule_pump_task) { + // Normally, the queue is processed after the action that added the local + // message is done (i.e. SendMessage, ClosePort, etc). However, it's also + // possible for a local message to be added as a result of a remote message, + // and OnChannelMessage() doesn't process this queue (although + // OnPortsMessage() does). There may also be other code paths, now or added + // in the future, which cause local messages to be added but don't process + // this message queue. + // + // Instead of adding a call to AcceptIncomingMessages() on every possible + // code path, post a task to the IO thread to process the queue. If the + // current call stack processes the queue, this may end up doing nothing. + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NodeController::ProcessIncomingMessages, + base::Unretained(this))); + } +} + +void NodeController::BroadcastMessage(ports::ScopedMessage message) { + CHECK_EQ(message->num_ports(), 0u); + Channel::MessagePtr channel_message = + static_cast(message.get())->TakeChannelMessage(); + CHECK(!channel_message->has_handles()); + + scoped_refptr broker = GetBrokerChannel(); + if (broker) + broker->Broadcast(std::move(channel_message)); + else + OnBroadcast(name_, std::move(channel_message)); +} + +void NodeController::PortStatusChanged(const ports::PortRef& port) { + scoped_refptr user_data; + node_->GetUserData(port, &user_data); + + PortObserver* observer = static_cast(user_data.get()); + if (observer) { + observer->OnPortStatusChanged(); + } else { + DVLOG(2) << "Ignoring status change for " << port.name() << " because it " + << "doesn't have an observer."; + } +} + +void NodeController::OnAcceptChild(const ports::NodeName& from_node, + const ports::NodeName& parent_name, + const ports::NodeName& token) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + scoped_refptr parent; + { + base::AutoLock lock(parent_lock_); + if (bootstrap_parent_channel_ && parent_name_ == ports::kInvalidNodeName) { + parent_name_ = parent_name; + parent = bootstrap_parent_channel_; + } + } + + if (!parent) { + DLOG(ERROR) << "Unexpected AcceptChild message from " << from_node; + DropPeer(from_node, nullptr); + return; + } + + parent->SetRemoteNodeName(parent_name); + parent->AcceptParent(token, name_); + + // NOTE: The child does not actually add its parent as a peer until + // receiving an AcceptBrokerClient message from the broker. The parent + // will request that said message be sent upon receiving AcceptParent. + + DVLOG(1) << "Child " << name_ << " accepting parent " << parent_name; +} + +void NodeController::OnAcceptParent(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& child_name) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + auto it = pending_children_.find(from_node); + if (it == pending_children_.end() || token != from_node) { + DLOG(ERROR) << "Received unexpected AcceptParent message from " + << from_node; + DropPeer(from_node, nullptr); + return; + } + + { + base::AutoLock lock(reserved_ports_lock_); + auto it = pending_child_tokens_.find(from_node); + if (it != pending_child_tokens_.end()) { + std::string token = std::move(it->second); + pending_child_tokens_.erase(it); + pending_child_tokens_[child_name] = std::move(token); + } + } + + scoped_refptr channel = it->second; + pending_children_.erase(it); + + DCHECK(channel); + + DVLOG(1) << "Parent " << name_ << " accepted child " << child_name; + + AddPeer(child_name, channel, false /* start_channel */); + + // TODO(rockot/amistry): We could simplify child initialization if we could + // synchronously get a new async broker channel from the broker. For now we do + // it asynchronously since it's only used to facilitate handle passing, not + // handle creation. + scoped_refptr broker = GetBrokerChannel(); + if (broker) { + // Inform the broker of this new child. + broker->AddBrokerClient(child_name, channel->CopyRemoteProcessHandle()); + } else { + // If we have no broker, either we need to wait for one, or we *are* the + // broker. + scoped_refptr parent = GetParentChannel(); + if (!parent) { + base::AutoLock lock(parent_lock_); + parent = bootstrap_parent_channel_; + } + + if (!parent) { + // Yes, we're the broker. We can initialize the child directly. + channel->AcceptBrokerClient(name_, ScopedPlatformHandle()); + } else { + // We aren't the broker, so wait for a broker connection. + base::AutoLock lock(broker_lock_); + pending_broker_clients_.push(child_name); + } + } +} + +void NodeController::OnAddBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& client_name, + base::ProcessHandle process_handle) { +#if defined(OS_WIN) + // Scoped handle to avoid leaks on error. + ScopedPlatformHandle scoped_process_handle = + ScopedPlatformHandle(PlatformHandle(process_handle)); +#endif + scoped_refptr sender = GetPeerChannel(from_node); + if (!sender) { + DLOG(ERROR) << "Ignoring AddBrokerClient from unknown sender."; + return; + } + + if (GetPeerChannel(client_name)) { + DLOG(ERROR) << "Ignoring AddBrokerClient for known client."; + DropPeer(from_node, nullptr); + return; + } + + PlatformChannelPair broker_channel; + ConnectionParams connection_params(broker_channel.PassServerHandle()); + scoped_refptr client = + NodeChannel::Create(this, std::move(connection_params), io_task_runner_, + ProcessErrorCallback()); + +#if defined(OS_WIN) + // The broker must have a working handle to the client process in order to + // properly copy other handles to and from the client. + if (!scoped_process_handle.is_valid()) { + DLOG(ERROR) << "Broker rejecting client with invalid process handle."; + return; + } + client->SetRemoteProcessHandle(scoped_process_handle.release().handle); +#else + client->SetRemoteProcessHandle(process_handle); +#endif + + AddPeer(client_name, client, true /* start_channel */); + + DVLOG(1) << "Broker " << name_ << " accepting client " << client_name + << " from peer " << from_node; + + sender->BrokerClientAdded(client_name, broker_channel.PassClientHandle()); +} + +void NodeController::OnBrokerClientAdded(const ports::NodeName& from_node, + const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel) { + scoped_refptr client = GetPeerChannel(client_name); + if (!client) { + DLOG(ERROR) << "BrokerClientAdded for unknown child " << client_name; + return; + } + + // This should have come from our own broker. + if (GetBrokerChannel() != GetPeerChannel(from_node)) { + DLOG(ERROR) << "BrokerClientAdded from non-broker node " << from_node; + return; + } + + DVLOG(1) << "Child " << client_name << " accepted by broker " << from_node; + + client->AcceptBrokerClient(from_node, std::move(broker_channel)); +} + +void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel) { + // This node should already have a parent in bootstrap mode. + ports::NodeName parent_name; + scoped_refptr parent; + { + base::AutoLock lock(parent_lock_); + parent_name = parent_name_; + parent = bootstrap_parent_channel_; + bootstrap_parent_channel_ = nullptr; + } + DCHECK(parent_name == from_node); + DCHECK(parent); + + std::queue pending_broker_clients; + std::unordered_map + pending_relay_messages; + { + base::AutoLock lock(broker_lock_); + broker_name_ = broker_name; + std::swap(pending_broker_clients, pending_broker_clients_); + std::swap(pending_relay_messages, pending_relay_messages_); + } + DCHECK(broker_name != ports::kInvalidNodeName); + + // It's now possible to add both the broker and the parent as peers. + // Note that the broker and parent may be the same node. + scoped_refptr broker; + if (broker_name == parent_name) { + DCHECK(!broker_channel.is_valid()); + broker = parent; + } else { + DCHECK(broker_channel.is_valid()); + broker = + NodeChannel::Create(this, ConnectionParams(std::move(broker_channel)), + io_task_runner_, ProcessErrorCallback()); + AddPeer(broker_name, broker, true /* start_channel */); + } + + AddPeer(parent_name, parent, false /* start_channel */); + + { + // Complete any port merge requests we have waiting for the parent. + base::AutoLock lock(pending_port_merges_lock_); + for (const auto& request : pending_port_merges_) + parent->RequestPortMerge(request.second.name(), request.first); + pending_port_merges_.clear(); + } + + // Feed the broker any pending children of our own. + while (!pending_broker_clients.empty()) { + const ports::NodeName& child_name = pending_broker_clients.front(); + auto it = pending_children_.find(child_name); + DCHECK(it != pending_children_.end()); + broker->AddBrokerClient(child_name, it->second->CopyRemoteProcessHandle()); + pending_broker_clients.pop(); + } + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + // Have the broker relay any messages we have waiting. + for (auto& entry : pending_relay_messages) { + const ports::NodeName& destination = entry.first; + auto& message_queue = entry.second; + while (!message_queue.empty()) { + broker->RelayPortsMessage(destination, std::move(message_queue.front())); + message_queue.pop(); + } + } +#endif + + DVLOG(1) << "Child " << name_ << " accepted by broker " << broker_name; +} + +void NodeController::OnPortsMessage(const ports::NodeName& from_node, + Channel::MessagePtr channel_message) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + void* data; + size_t num_data_bytes, num_header_bytes, num_payload_bytes, num_ports_bytes; + if (!ParsePortsMessage(channel_message.get(), &data, &num_data_bytes, + &num_header_bytes, &num_payload_bytes, + &num_ports_bytes)) { + DropPeer(from_node, nullptr); + return; + } + + CHECK(channel_message); + std::unique_ptr ports_message( + new PortsMessage(num_header_bytes, + num_payload_bytes, + num_ports_bytes, + std::move(channel_message))); + ports_message->set_source_node(from_node); + node_->AcceptMessage(ports::ScopedMessage(ports_message.release())); + AcceptIncomingMessages(); +} + +void NodeController::OnRequestPortMerge( + const ports::NodeName& from_node, + const ports::PortName& connector_port_name, + const std::string& token) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + DVLOG(2) << "Node " << name_ << " received RequestPortMerge for token " + << token << " and port " << connector_port_name << "@" << from_node; + + ports::PortRef local_port; + { + base::AutoLock lock(reserved_ports_lock_); + auto it = reserved_ports_.find(token); + if (it == reserved_ports_.end()) { + DVLOG(1) << "Ignoring request to connect to port for unknown token " + << token; + return; + } + local_port = it->second.port; + reserved_ports_.erase(it); + } + + int rv = node_->MergePorts(local_port, from_node, connector_port_name); + if (rv != ports::OK) + DLOG(ERROR) << "MergePorts failed: " << rv; + + AcceptIncomingMessages(); +} + +void NodeController::OnRequestIntroduction(const ports::NodeName& from_node, + const ports::NodeName& name) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + scoped_refptr requestor = GetPeerChannel(from_node); + if (from_node == name || name == ports::kInvalidNodeName || !requestor) { + DLOG(ERROR) << "Rejecting invalid OnRequestIntroduction message from " + << from_node; + DropPeer(from_node, nullptr); + return; + } + + scoped_refptr new_friend = GetPeerChannel(name); + if (!new_friend) { + // We don't know who they're talking about! + requestor->Introduce(name, ScopedPlatformHandle()); + } else { + PlatformChannelPair new_channel; + requestor->Introduce(name, new_channel.PassServerHandle()); + new_friend->Introduce(from_node, new_channel.PassClientHandle()); + } +} + +void NodeController::OnIntroduce(const ports::NodeName& from_node, + const ports::NodeName& name, + ScopedPlatformHandle channel_handle) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + if (!channel_handle.is_valid()) { + node_->LostConnectionToNode(name); + + DVLOG(1) << "Could not be introduced to peer " << name; + base::AutoLock lock(peers_lock_); + pending_peer_messages_.erase(name); + return; + } + + scoped_refptr channel = + NodeChannel::Create(this, ConnectionParams(std::move(channel_handle)), + io_task_runner_, ProcessErrorCallback()); + + DVLOG(1) << "Adding new peer " << name << " via parent introduction."; + AddPeer(name, channel, true /* start_channel */); +} + +void NodeController::OnBroadcast(const ports::NodeName& from_node, + Channel::MessagePtr message) { + DCHECK(!message->has_handles()); + + void* data; + size_t num_data_bytes, num_header_bytes, num_payload_bytes, num_ports_bytes; + if (!ParsePortsMessage(message.get(), &data, &num_data_bytes, + &num_header_bytes, &num_payload_bytes, + &num_ports_bytes)) { + DropPeer(from_node, nullptr); + return; + } + + // Broadcast messages must not contain ports. + if (num_ports_bytes > 0) { + DropPeer(from_node, nullptr); + return; + } + + base::AutoLock lock(peers_lock_); + for (auto& iter : peers_) { + // Copy and send the message to each known peer. + Channel::MessagePtr peer_message( + new Channel::Message(message->payload_size(), 0)); + memcpy(peer_message->mutable_payload(), message->payload(), + message->payload_size()); + iter.second->PortsMessage(std::move(peer_message)); + } +} + +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) +void NodeController::OnRelayPortsMessage(const ports::NodeName& from_node, + base::ProcessHandle from_process, + const ports::NodeName& destination, + Channel::MessagePtr message) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + if (GetBrokerChannel()) { + // Only the broker should be asked to relay a message. + LOG(ERROR) << "Non-broker refusing to relay message."; + DropPeer(from_node, nullptr); + return; + } + + // The parent should always know which process this came from. + DCHECK(from_process != base::kNullProcessHandle); + +#if defined(OS_WIN) + // Rewrite the handles to this (the parent) process. If the message is + // destined for another child process, the handles will be rewritten to that + // process before going out (see NodeChannel::WriteChannelMessage). + // + // TODO: We could avoid double-duplication. + // + // Note that we explicitly mark the handles as being owned by the sending + // process before rewriting them, in order to accommodate RewriteHandles' + // internal sanity checks. + ScopedPlatformHandleVectorPtr handles = message->TakeHandles(); + for (size_t i = 0; i < handles->size(); ++i) + (*handles)[i].owning_process = from_process; + if (!Channel::Message::RewriteHandles(from_process, + base::GetCurrentProcessHandle(), + handles.get())) { + DLOG(ERROR) << "Failed to relay one or more handles."; + } + message->SetHandles(std::move(handles)); +#else + MachPortRelay* relay = GetMachPortRelay(); + if (!relay) { + LOG(ERROR) << "Receiving Mach ports without a port relay from " + << from_node << ". Dropping message."; + return; + } + if (!relay->ExtractPortRights(message.get(), from_process)) { + // NodeChannel should ensure that MachPortRelay is ready for the remote + // process. At this point, if the port extraction failed, either something + // went wrong in the mach stuff, or the remote process died. + LOG(ERROR) << "Error on receiving Mach ports " << from_node + << ". Dropping message."; + return; + } +#endif // defined(OS_WIN) + + if (destination == name_) { + // Great, we can deliver this message locally. + OnPortsMessage(from_node, std::move(message)); + return; + } + + scoped_refptr peer = GetPeerChannel(destination); + if (peer) + peer->PortsMessageFromRelay(from_node, std::move(message)); + else + DLOG(ERROR) << "Dropping relay message for unknown node " << destination; +} + +void NodeController::OnPortsMessageFromRelay(const ports::NodeName& from_node, + const ports::NodeName& source_node, + Channel::MessagePtr message) { + if (GetPeerChannel(from_node) != GetBrokerChannel()) { + LOG(ERROR) << "Refusing relayed message from non-broker node."; + DropPeer(from_node, nullptr); + return; + } + + OnPortsMessage(source_node, std::move(message)); +} +#endif + +void NodeController::OnAcceptPeer(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& peer_name, + const ports::PortName& port_name) { + DCHECK(io_task_runner_->RunsTasksOnCurrentThread()); + + auto it = peer_connections_.find(from_node); + if (it == peer_connections_.end()) { + DLOG(ERROR) << "Received unexpected AcceptPeer message from " << from_node; + DropPeer(from_node, nullptr); + return; + } + + scoped_refptr channel = std::move(it->second.channel); + ports::PortRef local_port = it->second.local_port; + std::string peer_token = std::move(it->second.peer_token); + peer_connections_.erase(it); + DCHECK(channel); + + // If the peer connection is a self connection (which is used in tests), + // drop the channel to it and skip straight to merging the ports. + if (name_ == peer_name) { + peers_by_token_.erase(peer_token); + } else { + peers_by_token_[peer_token] = peer_name; + peer_connections_.insert( + {peer_name, PeerConnection{nullptr, local_port, peer_token}}); + DVLOG(1) << "Node " << name_ << " accepted peer " << peer_name; + + AddPeer(peer_name, channel, false /* start_channel */); + } + + // We need to choose one side to initiate the port merge. It doesn't matter + // who does it as long as they don't both try. Simple solution: pick the one + // with the "smaller" port name. + if (local_port.name() < port_name) { + node()->MergePorts(local_port, peer_name, port_name); + } +} + +void NodeController::OnChannelError(const ports::NodeName& from_node, + NodeChannel* channel) { + if (io_task_runner_->RunsTasksOnCurrentThread()) { + DropPeer(from_node, channel); + // DropPeer may have caused local port closures, so be sure to process any + // pending local messages. + AcceptIncomingMessages(); + } else { + io_task_runner_->PostTask( + FROM_HERE, + base::Bind(&NodeController::OnChannelError, base::Unretained(this), + from_node, channel)); + } +} + +#if defined(OS_MACOSX) && !defined(OS_IOS) +MachPortRelay* NodeController::GetMachPortRelay() { + { + base::AutoLock lock(parent_lock_); + // Return null if we're not the root. + if (bootstrap_parent_channel_ || parent_name_ != ports::kInvalidNodeName) + return nullptr; + } + + base::AutoLock lock(mach_port_relay_lock_); + return mach_port_relay_.get(); +} +#endif + +void NodeController::CancelPendingPortMerges() { + std::vector ports_to_close; + + { + base::AutoLock lock(pending_port_merges_lock_); + reject_pending_merges_ = true; + for (const auto& port : pending_port_merges_) + ports_to_close.push_back(port.second); + pending_port_merges_.clear(); + } + + for (const auto& port : ports_to_close) + node_->ClosePort(port); +} + +void NodeController::DestroyOnIOThreadShutdown() { + destroy_on_io_thread_shutdown_ = true; +} + +void NodeController::AttemptShutdownIfRequested() { + if (!shutdown_callback_flag_) + return; + + base::Closure callback; + { + base::AutoLock lock(shutdown_lock_); + if (shutdown_callback_.is_null()) + return; + if (!node_->CanShutdownCleanly( + ports::Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)) { + DVLOG(2) << "Unable to cleanly shut down node " << name_; + return; + } + + callback = shutdown_callback_; + shutdown_callback_.Reset(); + shutdown_callback_flag_.Set(false); + } + + DCHECK(!callback.is_null()); + + callback.Run(); +} + +NodeController::PeerConnection::PeerConnection() = default; + +NodeController::PeerConnection::PeerConnection( + const PeerConnection& other) = default; + +NodeController::PeerConnection::PeerConnection( + PeerConnection&& other) = default; + +NodeController::PeerConnection::PeerConnection( + scoped_refptr channel, + const ports::PortRef& local_port, + const std::string& peer_token) + : channel(std::move(channel)), + local_port(local_port), + peer_token(peer_token) {} + +NodeController::PeerConnection::~PeerConnection() = default; + +NodeController::PeerConnection& NodeController::PeerConnection:: +operator=(const PeerConnection& other) = default; + +NodeController::PeerConnection& NodeController::PeerConnection:: +operator=(PeerConnection&& other) = default; + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/node_controller.h b/mojo/edk/system/node_controller.h new file mode 100644 index 0000000..46a2d61 --- /dev/null +++ b/mojo/edk/system/node_controller.h @@ -0,0 +1,378 @@ +// 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_EDK_SYSTEM_NODE_CONTROLLER_H_ +#define MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_ + +#include +#include +#include +#include +#include +#include + +#include "base/callback.h" +#include "base/containers/hash_tables.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/task_runner.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/atomic_flag.h" +#include "mojo/edk/system/node_channel.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/ports/node.h" +#include "mojo/edk/system/ports/node_delegate.h" + +namespace base { +class PortProvider; +} + +namespace mojo { +namespace edk { + +class Broker; +class Core; +class MachPortRelay; +class PortsMessage; + +// The owner of ports::Node which facilitates core EDK implementation. All +// public interface methods are safe to call from any thread. +class NodeController : public ports::NodeDelegate, + public NodeChannel::Delegate { + public: + class PortObserver : public ports::UserData { + public: + virtual void OnPortStatusChanged() = 0; + + protected: + ~PortObserver() override {} + }; + + // |core| owns and out-lives us. + explicit NodeController(Core* core); + ~NodeController() override; + + const ports::NodeName& name() const { return name_; } + Core* core() const { return core_; } + ports::Node* node() const { return node_.get(); } + scoped_refptr io_task_runner() const { + return io_task_runner_; + } + +#if defined(OS_MACOSX) && !defined(OS_IOS) + // Create the relay used to transfer mach ports between processes. + void CreateMachPortRelay(base::PortProvider* port_provider); +#endif + + // Called exactly once, shortly after construction, and before any other + // methods are called on this object. + void SetIOTaskRunner(scoped_refptr io_task_runner); + + // Connects this node to a child node. This node will initiate a handshake. + void ConnectToChild(base::ProcessHandle process_handle, + ConnectionParams connection_params, + const std::string& child_token, + const ProcessErrorCallback& process_error_callback); + + // Closes all reserved ports which associated with the child process + // |child_token|. + void CloseChildPorts(const std::string& child_token); + + // Close a connection to a peer associated with |peer_token|. + void ClosePeerConnection(const std::string& peer_token); + + // Connects this node to a parent node. The parent node will initiate a + // handshake. + void ConnectToParent(ConnectionParams connection_params); + + // Connects this node to a peer node. On success, |port| will be merged with + // the corresponding port in the peer node. + void ConnectToPeer(ConnectionParams connection_params, + const ports::PortRef& port, + const std::string& peer_token); + + // Sets a port's observer. If |observer| is null the port's current observer + // is removed. + void SetPortObserver(const ports::PortRef& port, + scoped_refptr observer); + + // Closes a port. Use this in lieu of calling Node::ClosePort() directly, as + // it ensures the port's observer has also been removed. + void ClosePort(const ports::PortRef& port); + + // Sends a message on a port to its peer. + int SendMessage(const ports::PortRef& port_ref, + std::unique_ptr message); + + // Reserves a local port |port| associated with |token|. A peer holding a copy + // of |token| can merge one of its own ports into this one. + void ReservePort(const std::string& token, const ports::PortRef& port, + const std::string& child_token); + + // Merges a local port |port| into a port reserved by |token| in the parent. + void MergePortIntoParent(const std::string& token, + const ports::PortRef& port); + + // Merges two local ports together. + int MergeLocalPorts(const ports::PortRef& port0, const ports::PortRef& port1); + + // Creates a new shared buffer for use in the current process. + scoped_refptr CreateSharedBuffer(size_t num_bytes); + + // Request that the Node be shut down cleanly. This may take an arbitrarily + // long time to complete, at which point |callback| will be called. + // + // Note that while it is safe to continue using the NodeController's public + // interface after requesting shutdown, you do so at your own risk and there + // is NO guarantee that new messages will be sent or ports will complete + // transfer. + void RequestShutdown(const base::Closure& callback); + + // Notifies the NodeController that we received a bad message from the given + // node. + void NotifyBadMessageFrom(const ports::NodeName& source_node, + const std::string& error); + + private: + friend Core; + + using NodeMap = std::unordered_map>; + using OutgoingMessageQueue = std::queue; + + struct ReservedPort { + ports::PortRef port; + const std::string child_token; + }; + + struct PeerConnection { + PeerConnection(); + PeerConnection(const PeerConnection& other); + PeerConnection(PeerConnection&& other); + PeerConnection(scoped_refptr channel, + const ports::PortRef& local_port, + const std::string& peer_token); + ~PeerConnection(); + + PeerConnection& operator=(const PeerConnection& other); + PeerConnection& operator=(PeerConnection&& other); + + + scoped_refptr channel; + ports::PortRef local_port; + std::string peer_token; + }; + + void ConnectToChildOnIOThread( + base::ProcessHandle process_handle, + ConnectionParams connection_params, + ports::NodeName token, + const ProcessErrorCallback& process_error_callback); + void ConnectToParentOnIOThread(ConnectionParams connection_params); + + void ConnectToPeerOnIOThread(ConnectionParams connection_params, + ports::NodeName token, + ports::PortRef port, + const std::string& peer_token); + void ClosePeerConnectionOnIOThread(const std::string& node_name); + + scoped_refptr GetPeerChannel(const ports::NodeName& name); + scoped_refptr GetParentChannel(); + scoped_refptr GetBrokerChannel(); + + void AddPeer(const ports::NodeName& name, + scoped_refptr channel, + bool start_channel); + void DropPeer(const ports::NodeName& name, NodeChannel* channel); + void SendPeerMessage(const ports::NodeName& name, + ports::ScopedMessage message); + void AcceptIncomingMessages(); + void ProcessIncomingMessages(); + void DropAllPeers(); + + // ports::NodeDelegate: + void GenerateRandomPortName(ports::PortName* port_name) override; + void AllocMessage(size_t num_header_bytes, + ports::ScopedMessage* message) override; + void ForwardMessage(const ports::NodeName& node, + ports::ScopedMessage message) override; + void BroadcastMessage(ports::ScopedMessage message) override; + void PortStatusChanged(const ports::PortRef& port) override; + + // NodeChannel::Delegate: + void OnAcceptChild(const ports::NodeName& from_node, + const ports::NodeName& parent_name, + const ports::NodeName& token) override; + void OnAcceptParent(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& child_name) override; + void OnAddBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& client_name, + base::ProcessHandle process_handle) override; + void OnBrokerClientAdded(const ports::NodeName& from_node, + const ports::NodeName& client_name, + ScopedPlatformHandle broker_channel) override; + void OnAcceptBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& broker_name, + ScopedPlatformHandle broker_channel) override; + void OnPortsMessage(const ports::NodeName& from_node, + Channel::MessagePtr message) override; + void OnRequestPortMerge(const ports::NodeName& from_node, + const ports::PortName& connector_port_name, + const std::string& token) override; + void OnRequestIntroduction(const ports::NodeName& from_node, + const ports::NodeName& name) override; + void OnIntroduce(const ports::NodeName& from_node, + const ports::NodeName& name, + ScopedPlatformHandle channel_handle) override; + void OnBroadcast(const ports::NodeName& from_node, + Channel::MessagePtr message) override; +#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS)) + void OnRelayPortsMessage(const ports::NodeName& from_node, + base::ProcessHandle from_process, + const ports::NodeName& destination, + Channel::MessagePtr message) override; + void OnPortsMessageFromRelay(const ports::NodeName& from_node, + const ports::NodeName& source_node, + Channel::MessagePtr message) override; +#endif + void OnAcceptPeer(const ports::NodeName& from_node, + const ports::NodeName& token, + const ports::NodeName& peer_name, + const ports::PortName& port_name) override; + void OnChannelError(const ports::NodeName& from_node, + NodeChannel* channel) override; +#if defined(OS_MACOSX) && !defined(OS_IOS) + MachPortRelay* GetMachPortRelay() override; +#endif + + // Cancels all pending port merges. These are merges which are supposed to + // be requested from the parent ASAP, and they may be cancelled if the + // connection to the parent is broken or never established. + void CancelPendingPortMerges(); + + // Marks this NodeController for destruction when the IO thread shuts down. + // This is used in case Core is torn down before the IO thread. Must only be + // called on the IO thread. + void DestroyOnIOThreadShutdown(); + + // If there is a registered shutdown callback (meaning shutdown has been + // requested, this checks the Node's status to see if clean shutdown is + // possible. If so, shutdown is performed and the shutdown callback is run. + void AttemptShutdownIfRequested(); + + // These are safe to access from any thread as long as the Node is alive. + Core* const core_; + const ports::NodeName name_; + const std::unique_ptr node_; + scoped_refptr io_task_runner_; + + // Guards |peers_| and |pending_peer_messages_|. + base::Lock peers_lock_; + + // Channels to known peers, including parent and children, if any. + NodeMap peers_; + + // Outgoing message queues for peers we've heard of but can't yet talk to. + std::unordered_map + pending_peer_messages_; + + // Guards |reserved_ports_| and |pending_child_tokens_|. + base::Lock reserved_ports_lock_; + + // Ports reserved by token. Key is the port token. + base::hash_map reserved_ports_; + // TODO(amistry): This _really_ needs to be a bimap. Unfortunately, we don't + // have one yet :( + std::unordered_map pending_child_tokens_; + + // Guards |pending_port_merges_| and |reject_pending_merges_|. + base::Lock pending_port_merges_lock_; + + // A set of port merge requests awaiting parent connection. + std::vector> pending_port_merges_; + + // Indicates that new merge requests should be rejected because the parent has + // disconnected. + bool reject_pending_merges_ = false; + + // Guards |parent_name_| and |bootstrap_parent_channel_|. + base::Lock parent_lock_; + + // The name of our parent node, if any. + ports::NodeName parent_name_; + + // A temporary reference to the parent channel before we know their name. + scoped_refptr bootstrap_parent_channel_; + + // Guards |broker_name_|, |pending_broker_clients_|, and + // |pending_relay_messages_|. + base::Lock broker_lock_; + + // The name of our broker node, if any. + ports::NodeName broker_name_; + + // A queue of pending child names waiting to be connected to a broker. + std::queue pending_broker_clients_; + + // Messages waiting to be relayed by the broker once it's known. + std::unordered_map + pending_relay_messages_; + + // Guards |incoming_messages_| and |incoming_messages_task_posted_|. + base::Lock messages_lock_; + std::queue incoming_messages_; + // Ensures that there is only one incoming messages task posted to the IO + // thread. + bool incoming_messages_task_posted_ = false; + // Flag to fast-path checking |incoming_messages_|. + AtomicFlag incoming_messages_flag_; + + // Guards |shutdown_callback_|. + base::Lock shutdown_lock_; + + // Set by RequestShutdown(). If this is non-null, the controller will + // begin polling the Node to see if clean shutdown is possible any time the + // Node's state is modified by the controller. + base::Closure shutdown_callback_; + // Flag to fast-path checking |shutdown_callback_|. + AtomicFlag shutdown_callback_flag_; + + // All other fields below must only be accessed on the I/O thread, i.e., the + // thread on which core_->io_task_runner() runs tasks. + + // Channels to children during handshake. + NodeMap pending_children_; + + using PeerNodeMap = + std::unordered_map; + PeerNodeMap peer_connections_; + + // Maps from peer token to node name, pending or not. + std::unordered_map peers_by_token_; + + // Indicates whether this object should delete itself on IO thread shutdown. + // Must only be accessed from the IO thread. + bool destroy_on_io_thread_shutdown_ = false; + +#if !defined(OS_MACOSX) && !defined(OS_NACL_SFI) + // Broker for sync shared buffer creation in children. + std::unique_ptr broker_; +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) + base::Lock mach_port_relay_lock_; + // Relay for transferring mach ports to/from children. + std::unique_ptr mach_port_relay_; +#endif + + DISALLOW_COPY_AND_ASSIGN(NodeController); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_NODE_CONTROLLER_H_ diff --git a/mojo/edk/system/options_validation.h b/mojo/edk/system/options_validation.h new file mode 100644 index 0000000..e1b337d --- /dev/null +++ b/mojo/edk/system/options_validation.h @@ -0,0 +1,97 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Functions to help with verifying various |Mojo...Options| structs from the +// (public, C) API. These are "extensible" structs, which all have |struct_size| +// as their first member. All fields (other than |struct_size|) are optional, +// but any |flags| specified must be known to the system (otherwise, an error of +// |MOJO_RESULT_UNIMPLEMENTED| should be returned). + +#ifndef MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_ +#define MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_ + +#include +#include + +#include + +#include "base/logging.h" +#include "base/macros.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { + +template +class UserOptionsReader { + public: + // Constructor from a |const* Options| (which it checks -- this constructor + // has side effects!). + // Note: We initialize |options_reader_| without checking, since we do a check + // in |GetSizeForReader()|. + explicit UserOptionsReader(const Options* options) { + CHECK(options && IsAligned(options)); + options_ = GetSizeForReader(options) == 0 ? nullptr : options; + static_assert(offsetof(Options, struct_size) == 0, + "struct_size not first member of Options"); + // TODO(vtl): Enable when MSVC supports this (C++11 extended sizeof): + // static_assert(sizeof(Options::struct_size) == sizeof(uint32_t), + // "Options::struct_size not a uint32_t"); + // (Or maybe assert that its type is uint32_t?) + } + + bool is_valid() const { return !!options_; } + + const Options& options() const { + DCHECK(is_valid()); + return *options_; + } + + // Checks that the given (variable-size) |options| passed to the constructor + // (plausibly) has a member at the given offset with the given size. You + // probably want to use |OPTIONS_STRUCT_HAS_MEMBER()| instead. + bool HasMember(size_t offset, size_t size) const { + DCHECK(is_valid()); + // We assume that |offset| and |size| are reasonable, since they should come + // from |offsetof(Options, some_member)| and |sizeof(Options::some_member)|, + // respectively. + return options().struct_size >= offset + size; + } + + private: + static inline size_t GetSizeForReader(const Options* options) { + uint32_t struct_size = *reinterpret_cast(options); + if (struct_size < sizeof(uint32_t)) + return 0; + + return std::min(static_cast(struct_size), sizeof(Options)); + } + + template + static bool IsAligned(const void* pointer) { + return reinterpret_cast(pointer) % alignment == 0; + } + + const Options* options_; + + DISALLOW_COPY_AND_ASSIGN(UserOptionsReader); +}; + +// Macro to invoke |UserOptionsReader::HasMember()| parametrized by +// member name instead of offset and size. +// +// (We can't just give |HasMember()| a member pointer template argument instead, +// since there's no good/strictly-correct way to get an offset from that.) +// +// TODO(vtl): With C++11, use |sizeof(Options::member)| instead of (the +// contortion below). We might also be able to pull out the type |Options| from +// |reader| (using |decltype|) instead of requiring a parameter. +#define OPTIONS_STRUCT_HAS_MEMBER(Options, member, reader) \ + reader.HasMember(offsetof(Options, member), sizeof(reader.options().member)) + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_OPTIONS_VALIDATION_H_ diff --git a/mojo/edk/system/options_validation_unittest.cc b/mojo/edk/system/options_validation_unittest.cc new file mode 100644 index 0000000..a01a92c --- /dev/null +++ b/mojo/edk/system/options_validation_unittest.cc @@ -0,0 +1,134 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/options_validation.h" + +#include +#include + +#include "mojo/public/c/system/macros.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +// Declare a test options struct just as we do in actual public headers. + +using TestOptionsFlags = uint32_t; + +static_assert(MOJO_ALIGNOF(int64_t) == 8, "int64_t has weird alignment"); +struct MOJO_ALIGNAS(8) TestOptions { + uint32_t struct_size; + TestOptionsFlags flags; + uint32_t member1; + uint32_t member2; +}; +static_assert(sizeof(TestOptions) == 16, "TestOptions has wrong size"); + +const uint32_t kSizeOfTestOptions = static_cast(sizeof(TestOptions)); + +TEST(OptionsValidationTest, Valid) { + { + const TestOptions kOptions = {kSizeOfTestOptions}; + UserOptionsReader reader(&kOptions); + EXPECT_TRUE(reader.is_valid()); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } + { + const TestOptions kOptions = {static_cast( + offsetof(TestOptions, struct_size) + sizeof(uint32_t))}; + UserOptionsReader reader(&kOptions); + EXPECT_TRUE(reader.is_valid()); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } + + { + const TestOptions kOptions = { + static_cast(offsetof(TestOptions, flags) + sizeof(uint32_t))}; + UserOptionsReader reader(&kOptions); + EXPECT_TRUE(reader.is_valid()); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_FALSE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } + { + MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {}; + TestOptions* options = reinterpret_cast(buf); + options->struct_size = kSizeOfTestOptions + 1; + UserOptionsReader reader(options); + EXPECT_TRUE(reader.is_valid()); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } + { + MOJO_ALIGNAS(8) char buf[sizeof(TestOptions) + 100] = {}; + TestOptions* options = reinterpret_cast(buf); + options->struct_size = kSizeOfTestOptions + 4; + UserOptionsReader reader(options); + EXPECT_TRUE(reader.is_valid()); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, flags, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member1, reader)); + EXPECT_TRUE(OPTIONS_STRUCT_HAS_MEMBER(TestOptions, member2, reader)); + } +} + +TEST(OptionsValidationTest, Invalid) { + // Size too small: + for (size_t i = 0; i < sizeof(uint32_t); i++) { + TestOptions options = {static_cast(i)}; + UserOptionsReader reader(&options); + EXPECT_FALSE(reader.is_valid()) << i; + } +} + +// These test invalid arguments that should cause death if we're being paranoid +// about checking arguments (which we would want to do if, e.g., we were in a +// true "kernel" situation, but we might not want to do otherwise for +// performance reasons). Probably blatant errors like passing in null pointers +// (for required pointer arguments) will still cause death, but perhaps not +// predictably. +TEST(OptionsValidationTest, InvalidDeath) { +#if defined(OFFICIAL_BUILD) + const char kMemoryCheckFailedRegex[] = ""; +#else + const char kMemoryCheckFailedRegex[] = "Check failed"; +#endif + + // Null: + EXPECT_DEATH_IF_SUPPORTED( + { UserOptionsReader reader((nullptr)); }, + kMemoryCheckFailedRegex); + + // Unaligned: + EXPECT_DEATH_IF_SUPPORTED( + { + UserOptionsReader reader( + reinterpret_cast(1)); + }, + kMemoryCheckFailedRegex); + // Note: The current implementation checks the size only after checking the + // alignment versus that required for the |uint32_t| size, so it won't die in + // the expected way if you pass, e.g., 4. So we have to manufacture a valid + // pointer at an offset of alignment 4. + EXPECT_DEATH_IF_SUPPORTED( + { + uint32_t buffer[100] = {}; + TestOptions* options = (reinterpret_cast(buffer) % 8 == 0) + ? reinterpret_cast(&buffer[1]) + : reinterpret_cast(&buffer[0]); + options->struct_size = static_cast(sizeof(TestOptions)); + UserOptionsReader reader(options); + }, + kMemoryCheckFailedRegex); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/platform_handle_dispatcher.cc b/mojo/edk/system/platform_handle_dispatcher.cc new file mode 100644 index 0000000..3e708c2 --- /dev/null +++ b/mojo/edk/system/platform_handle_dispatcher.cc @@ -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. + +#include "mojo/edk/system/platform_handle_dispatcher.h" + +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/platform_handle_vector.h" + +namespace mojo { +namespace edk { + +// static +scoped_refptr PlatformHandleDispatcher::Create( + ScopedPlatformHandle platform_handle) { + return new PlatformHandleDispatcher(std::move(platform_handle)); +} + +ScopedPlatformHandle PlatformHandleDispatcher::PassPlatformHandle() { + return std::move(platform_handle_); +} + +Dispatcher::Type PlatformHandleDispatcher::GetType() const { + return Type::PLATFORM_HANDLE; +} + +MojoResult PlatformHandleDispatcher::Close() { + base::AutoLock lock(lock_); + if (is_closed_ || in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + is_closed_ = true; + platform_handle_.reset(); + return MOJO_RESULT_OK; +} + +void PlatformHandleDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) { + *num_bytes = 0; + *num_ports = 0; + *num_handles = 1; +} + +bool PlatformHandleDispatcher::EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) { + base::AutoLock lock(lock_); + if (is_closed_) + return false; + handles[0] = platform_handle_.get(); + return true; +} + +bool PlatformHandleDispatcher::BeginTransit() { + base::AutoLock lock(lock_); + if (in_transit_) + return false; + in_transit_ = !is_closed_; + return in_transit_; +} + +void PlatformHandleDispatcher::CompleteTransitAndClose() { + base::AutoLock lock(lock_); + + in_transit_ = false; + is_closed_ = true; + + // The system has taken ownership of our handle. + ignore_result(platform_handle_.release()); +} + +void PlatformHandleDispatcher::CancelTransit() { + base::AutoLock lock(lock_); + in_transit_ = false; +} + +// static +scoped_refptr PlatformHandleDispatcher::Deserialize( + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles) { + if (num_bytes || num_ports || num_handles != 1) + return nullptr; + + PlatformHandle handle; + std::swap(handle, handles[0]); + + return PlatformHandleDispatcher::Create(ScopedPlatformHandle(handle)); +} + +PlatformHandleDispatcher::PlatformHandleDispatcher( + ScopedPlatformHandle platform_handle) + : platform_handle_(std::move(platform_handle)) {} + +PlatformHandleDispatcher::~PlatformHandleDispatcher() { + DCHECK(is_closed_ && !in_transit_); + DCHECK(!platform_handle_.is_valid()); +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/platform_handle_dispatcher.h b/mojo/edk/system/platform_handle_dispatcher.h new file mode 100644 index 0000000..a36c7a0 --- /dev/null +++ b/mojo/edk/system/platform_handle_dispatcher.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_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { +namespace edk { + +class MOJO_SYSTEM_IMPL_EXPORT PlatformHandleDispatcher : public Dispatcher { + public: + static scoped_refptr Create( + ScopedPlatformHandle platform_handle); + + ScopedPlatformHandle PassPlatformHandle(); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + static scoped_refptr Deserialize( + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* handles, + size_t num_handles); + + private: + PlatformHandleDispatcher(ScopedPlatformHandle platform_handle); + ~PlatformHandleDispatcher() override; + + base::Lock lock_; + bool in_transit_ = false; + bool is_closed_ = false; + ScopedPlatformHandle platform_handle_; + + DISALLOW_COPY_AND_ASSIGN(PlatformHandleDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PLATFORM_HANDLE_DISPATCHER_H_ diff --git a/mojo/edk/system/platform_handle_dispatcher_unittest.cc b/mojo/edk/system/platform_handle_dispatcher_unittest.cc new file mode 100644 index 0000000..7a94262 --- /dev/null +++ b/mojo/edk/system/platform_handle_dispatcher_unittest.cc @@ -0,0 +1,123 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/platform_handle_dispatcher.h" + +#include +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_file.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +TEST(PlatformHandleDispatcherTest, Basic) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + static const char kHelloWorld[] = "hello world"; + + base::FilePath unused; + base::ScopedFILE fp( + CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + ASSERT_TRUE(fp); + EXPECT_EQ(sizeof(kHelloWorld), + fwrite(kHelloWorld, 1, sizeof(kHelloWorld), fp.get())); + + ScopedPlatformHandle h(test::PlatformHandleFromFILE(std::move(fp))); + EXPECT_FALSE(fp); + ASSERT_TRUE(h.is_valid()); + + scoped_refptr dispatcher = + PlatformHandleDispatcher::Create(std::move(h)); + EXPECT_FALSE(h.is_valid()); + EXPECT_EQ(Dispatcher::Type::PLATFORM_HANDLE, dispatcher->GetType()); + + h = dispatcher->PassPlatformHandle(); + EXPECT_TRUE(h.is_valid()); + + fp = test::FILEFromPlatformHandle(std::move(h), "rb"); + EXPECT_FALSE(h.is_valid()); + EXPECT_TRUE(fp); + + rewind(fp.get()); + char read_buffer[1000] = {}; + EXPECT_EQ(sizeof(kHelloWorld), + fread(read_buffer, 1, sizeof(read_buffer), fp.get())); + EXPECT_STREQ(kHelloWorld, read_buffer); + + // Try getting the handle again. (It should fail cleanly.) + h = dispatcher->PassPlatformHandle(); + EXPECT_FALSE(h.is_valid()); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +TEST(PlatformHandleDispatcherTest, Serialization) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + + static const char kFooBar[] = "foo bar"; + + base::FilePath unused; + base::ScopedFILE fp( + CreateAndOpenTemporaryFileInDir(temp_dir.GetPath(), &unused)); + EXPECT_EQ(sizeof(kFooBar), fwrite(kFooBar, 1, sizeof(kFooBar), fp.get())); + + scoped_refptr dispatcher = + PlatformHandleDispatcher::Create( + test::PlatformHandleFromFILE(std::move(fp))); + + uint32_t num_bytes = 0; + uint32_t num_ports = 0; + uint32_t num_handles = 0; + EXPECT_TRUE(dispatcher->BeginTransit()); + dispatcher->StartSerialize(&num_bytes, &num_ports, &num_handles); + + EXPECT_EQ(0u, num_bytes); + EXPECT_EQ(0u, num_ports); + EXPECT_EQ(1u, num_handles); + + ScopedPlatformHandleVectorPtr handles(new PlatformHandleVector(1)); + EXPECT_TRUE(dispatcher->EndSerialize(nullptr, nullptr, handles->data())); + dispatcher->CompleteTransitAndClose(); + + EXPECT_TRUE(handles->at(0).is_valid()); + + ScopedPlatformHandle handle = dispatcher->PassPlatformHandle(); + EXPECT_FALSE(handle.is_valid()); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, dispatcher->Close()); + + dispatcher = static_cast( + Dispatcher::Deserialize(Dispatcher::Type::PLATFORM_HANDLE, nullptr, + num_bytes, nullptr, num_ports, handles->data(), + 1).get()); + + EXPECT_FALSE(handles->at(0).is_valid()); + EXPECT_TRUE(dispatcher->GetType() == Dispatcher::Type::PLATFORM_HANDLE); + + fp = test::FILEFromPlatformHandle(dispatcher->PassPlatformHandle(), "rb"); + EXPECT_TRUE(fp); + + rewind(fp.get()); + char read_buffer[1000] = {}; + EXPECT_EQ(sizeof(kFooBar), + fread(read_buffer, 1, sizeof(read_buffer), fp.get())); + EXPECT_STREQ(kFooBar, read_buffer); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/platform_wrapper_unittest.cc b/mojo/edk/system/platform_wrapper_unittest.cc new file mode 100644 index 0000000..f29d62b --- /dev/null +++ b/mojo/edk/system/platform_wrapper_unittest.cc @@ -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. + +#include +#include + +#include +#include +#include + +#include "base/files/file.h" +#include "base/files/file_util.h" +#include "base/memory/shared_memory.h" +#include "base/process/process_handle.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/platform_handle.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include +#endif + +#if defined(OS_POSIX) +#define SIMPLE_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR +#elif defined(OS_WIN) +#define SIMPLE_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE +#endif + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#define SHARED_BUFFER_PLATFORM_HANDLE_TYPE MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT +#else +#define SHARED_BUFFER_PLATFORM_HANDLE_TYPE SIMPLE_PLATFORM_HANDLE_TYPE +#endif + +uint64_t PlatformHandleValueFromPlatformFile(base::PlatformFile file) { +#if defined(OS_WIN) + return reinterpret_cast(file); +#else + return static_cast(file); +#endif +} + +base::PlatformFile PlatformFileFromPlatformHandleValue(uint64_t value) { +#if defined(OS_WIN) + return reinterpret_cast(value); +#else + return static_cast(value); +#endif +} + +namespace mojo { +namespace edk { +namespace { + +using PlatformWrapperTest = test::MojoTestBase; + +TEST_F(PlatformWrapperTest, WrapPlatformHandle) { + // Create a temporary file and write a message to it. + base::FilePath temp_file_path; + ASSERT_TRUE(base::CreateTemporaryFile(&temp_file_path)); + const std::string kMessage = "Hello, world!"; + EXPECT_EQ(base::WriteFile(temp_file_path, kMessage.data(), + static_cast(kMessage.size())), + static_cast(kMessage.size())); + + RUN_CHILD_ON_PIPE(ReadPlatformFile, h) + // Open the temporary file for reading, wrap its handle, and send it to + // the child along with the expected message to be read. + base::File file(temp_file_path, + base::File::FLAG_OPEN | base::File::FLAG_READ); + EXPECT_TRUE(file.IsValid()); + + MojoHandle wrapped_handle; + MojoPlatformHandle os_file; + os_file.struct_size = sizeof(MojoPlatformHandle); + os_file.type = SIMPLE_PLATFORM_HANDLE_TYPE; + os_file.value = + PlatformHandleValueFromPlatformFile(file.TakePlatformFile()); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWrapPlatformHandle(&os_file, &wrapped_handle)); + + WriteMessageWithHandles(h, kMessage, &wrapped_handle, 1); + END_CHILD() + + base::DeleteFile(temp_file_path, false); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadPlatformFile, PlatformWrapperTest, h) { + // Read a message and a wrapped file handle; unwrap the handle. + MojoHandle wrapped_handle; + std::string message = ReadMessageWithHandles(h, &wrapped_handle, 1); + + MojoPlatformHandle platform_handle; + platform_handle.struct_size = sizeof(MojoPlatformHandle); + ASSERT_EQ(MOJO_RESULT_OK, + MojoUnwrapPlatformHandle(wrapped_handle, &platform_handle)); + EXPECT_EQ(SIMPLE_PLATFORM_HANDLE_TYPE, platform_handle.type); + base::File file(PlatformFileFromPlatformHandleValue(platform_handle.value)); + + // Expect to read the same message from the file. + std::vector data(message.size()); + EXPECT_EQ(file.ReadAtCurrentPos(data.data(), static_cast(data.size())), + static_cast(data.size())); + EXPECT_TRUE(std::equal(message.begin(), message.end(), data.begin())); +} + +TEST_F(PlatformWrapperTest, WrapPlatformSharedBufferHandle) { + // Allocate a new platform shared buffer and write a message into it. + const std::string kMessage = "Hello, world!"; + base::SharedMemory buffer; + buffer.CreateAndMapAnonymous(kMessage.size()); + CHECK(buffer.memory()); + memcpy(buffer.memory(), kMessage.data(), kMessage.size()); + + RUN_CHILD_ON_PIPE(ReadPlatformSharedBuffer, h) + // Wrap the shared memory handle and send it to the child along with the + // expected message. + base::SharedMemoryHandle memory_handle = + base::SharedMemory::DuplicateHandle(buffer.handle()); + MojoPlatformHandle os_buffer; + os_buffer.struct_size = sizeof(MojoPlatformHandle); + os_buffer.type = SHARED_BUFFER_PLATFORM_HANDLE_TYPE; +#if defined(OS_MACOSX) && !defined(OS_IOS) + os_buffer.value = static_cast(memory_handle.GetMemoryObject()); +#elif defined(OS_POSIX) + os_buffer.value = static_cast(memory_handle.fd); +#elif defined(OS_WIN) + os_buffer.value = reinterpret_cast(memory_handle.GetHandle()); +#endif + + MojoHandle wrapped_handle; + ASSERT_EQ(MOJO_RESULT_OK, + MojoWrapPlatformSharedBufferHandle( + &os_buffer, kMessage.size(), + MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE, + &wrapped_handle)); + WriteMessageWithHandles(h, kMessage, &wrapped_handle, 1); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadPlatformSharedBuffer, PlatformWrapperTest, + h) { + // Read a message and a wrapped shared buffer handle. + MojoHandle wrapped_handle; + std::string message = ReadMessageWithHandles(h, &wrapped_handle, 1); + + // Check the message in the buffer + ExpectBufferContents(wrapped_handle, 0, message); + + // Now unwrap the buffer and verify that the base::SharedMemoryHandle also + // works as expected. + MojoPlatformHandle os_buffer; + os_buffer.struct_size = sizeof(MojoPlatformHandle); + size_t size; + MojoPlatformSharedBufferHandleFlags flags; + ASSERT_EQ(MOJO_RESULT_OK, + MojoUnwrapPlatformSharedBufferHandle(wrapped_handle, &os_buffer, + &size, &flags)); + bool read_only = flags & MOJO_PLATFORM_SHARED_BUFFER_HANDLE_FLAG_NONE; + EXPECT_FALSE(read_only); + +#if defined(OS_MACOSX) && !defined(OS_IOS) + ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_MACH_PORT, os_buffer.type); + base::SharedMemoryHandle memory_handle( + static_cast(os_buffer.value), size, + base::GetCurrentProcId()); +#elif defined(OS_POSIX) + ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_FILE_DESCRIPTOR, os_buffer.type); + base::SharedMemoryHandle memory_handle(static_cast(os_buffer.value), + false); +#elif defined(OS_WIN) + ASSERT_EQ(MOJO_PLATFORM_HANDLE_TYPE_WINDOWS_HANDLE, os_buffer.type); + base::SharedMemoryHandle memory_handle( + reinterpret_cast(os_buffer.value), base::GetCurrentProcId()); +#endif + + base::SharedMemory memory(memory_handle, read_only); + memory.Map(message.size()); + ASSERT_TRUE(memory.memory()); + + EXPECT_TRUE(std::equal(message.begin(), message.end(), + static_cast(memory.memory()))); +} + +TEST_F(PlatformWrapperTest, InvalidHandle) { + // Wrap an invalid platform handle and expect to unwrap the same. + + MojoHandle wrapped_handle; + MojoPlatformHandle invalid_handle; + invalid_handle.struct_size = sizeof(MojoPlatformHandle); + invalid_handle.type = MOJO_PLATFORM_HANDLE_TYPE_INVALID; + EXPECT_EQ(MOJO_RESULT_OK, + MojoWrapPlatformHandle(&invalid_handle, &wrapped_handle)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoUnwrapPlatformHandle(wrapped_handle, &invalid_handle)); + EXPECT_EQ(MOJO_PLATFORM_HANDLE_TYPE_INVALID, invalid_handle.type); +} + +TEST_F(PlatformWrapperTest, InvalidArgument) { + // Try to wrap an invalid MojoPlatformHandle struct and expect an error. + MojoHandle wrapped_handle; + MojoPlatformHandle platform_handle; + platform_handle.struct_size = 0; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWrapPlatformHandle(&platform_handle, &wrapped_handle)); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/BUILD.gn b/mojo/edk/system/ports/BUILD.gn new file mode 100644 index 0000000..5c82761 --- /dev/null +++ b/mojo/edk/system/ports/BUILD.gn @@ -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. + +import("//testing/test.gni") + +source_set("ports") { + sources = [ + "event.cc", + "event.h", + "message.cc", + "message.h", + "message_filter.h", + "message_queue.cc", + "message_queue.h", + "name.cc", + "name.h", + "node.cc", + "node.h", + "node_delegate.h", + "port.cc", + "port.h", + "port_ref.cc", + "port_ref.h", + "user_data.h", + ] + + public_deps = [ + "//base", + ] +} + +source_set("tests") { + testonly = true + + sources = [ + "ports_unittest.cc", + ] + + deps = [ + ":ports", + "//base", + "//base/test:test_support", + "//testing/gtest", + ] +} diff --git a/mojo/edk/system/ports/event.cc b/mojo/edk/system/ports/event.cc new file mode 100644 index 0000000..2e22086 --- /dev/null +++ b/mojo/edk/system/ports/event.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/edk/system/ports/event.h" + +#include + +namespace mojo { +namespace edk { +namespace ports { + +namespace { + +const size_t kPortsMessageAlignment = 8; + +static_assert(sizeof(PortDescriptor) % kPortsMessageAlignment == 0, + "Invalid PortDescriptor size."); + +static_assert(sizeof(EventHeader) % kPortsMessageAlignment == 0, + "Invalid EventHeader size."); + +static_assert(sizeof(UserEventData) % kPortsMessageAlignment == 0, + "Invalid UserEventData size."); + +static_assert(sizeof(ObserveProxyEventData) % kPortsMessageAlignment == 0, + "Invalid ObserveProxyEventData size."); + +static_assert(sizeof(ObserveProxyAckEventData) % kPortsMessageAlignment == 0, + "Invalid ObserveProxyAckEventData size."); + +static_assert(sizeof(ObserveClosureEventData) % kPortsMessageAlignment == 0, + "Invalid ObserveClosureEventData size."); + +static_assert(sizeof(MergePortEventData) % kPortsMessageAlignment == 0, + "Invalid MergePortEventData size."); + +} // namespace + +PortDescriptor::PortDescriptor() { + memset(padding, 0, sizeof(padding)); +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/event.h b/mojo/edk/system/ports/event.h new file mode 100644 index 0000000..a66dfc1 --- /dev/null +++ b/mojo/edk/system/ports/event.h @@ -0,0 +1,111 @@ +// 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_EDK_SYSTEM_PORTS_EVENT_H_ +#define MOJO_EDK_SYSTEM_PORTS_EVENT_H_ + +#include + +#include "mojo/edk/system/ports/message.h" +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { +namespace ports { + +#pragma pack(push, 1) + +// TODO: Add static assertions of alignment. + +struct PortDescriptor { + PortDescriptor(); + + NodeName peer_node_name; + PortName peer_port_name; + NodeName referring_node_name; + PortName referring_port_name; + uint64_t next_sequence_num_to_send; + uint64_t next_sequence_num_to_receive; + uint64_t last_sequence_num_to_receive; + bool peer_closed; + char padding[7]; +}; + +enum struct EventType : uint32_t { + kUser, + kPortAccepted, + kObserveProxy, + kObserveProxyAck, + kObserveClosure, + kMergePort, +}; + +struct EventHeader { + EventType type; + uint32_t padding; + PortName port_name; +}; + +struct UserEventData { + uint64_t sequence_num; + uint32_t num_ports; + uint32_t padding; +}; + +struct ObserveProxyEventData { + NodeName proxy_node_name; + PortName proxy_port_name; + NodeName proxy_to_node_name; + PortName proxy_to_port_name; +}; + +struct ObserveProxyAckEventData { + uint64_t last_sequence_num; +}; + +struct ObserveClosureEventData { + uint64_t last_sequence_num; +}; + +struct MergePortEventData { + PortName new_port_name; + PortDescriptor new_port_descriptor; +}; + +#pragma pack(pop) + +inline const EventHeader* GetEventHeader(const Message& message) { + return static_cast(message.header_bytes()); +} + +inline EventHeader* GetMutableEventHeader(Message* message) { + return static_cast(message->mutable_header_bytes()); +} + +template +inline const EventData* GetEventData(const Message& message) { + return reinterpret_cast( + reinterpret_cast(GetEventHeader(message) + 1)); +} + +template +inline EventData* GetMutableEventData(Message* message) { + return reinterpret_cast( + reinterpret_cast(GetMutableEventHeader(message) + 1)); +} + +inline const PortDescriptor* GetPortDescriptors(const UserEventData* event) { + return reinterpret_cast( + reinterpret_cast(event + 1)); +} + +inline PortDescriptor* GetMutablePortDescriptors(UserEventData* event) { + return reinterpret_cast(reinterpret_cast(event + 1)); +} + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_EVENT_H_ diff --git a/mojo/edk/system/ports/message.cc b/mojo/edk/system/ports/message.cc new file mode 100644 index 0000000..5d3c000 --- /dev/null +++ b/mojo/edk/system/ports/message.cc @@ -0,0 +1,100 @@ +// 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 + +#include + +#include "base/logging.h" +#include "mojo/edk/system/ports/event.h" + +namespace mojo { +namespace edk { +namespace ports { + +// static +bool Message::Parse(const void* bytes, + size_t num_bytes, + size_t* num_header_bytes, + size_t* num_payload_bytes, + size_t* num_ports_bytes) { + if (num_bytes < sizeof(EventHeader)) + return false; + const EventHeader* header = static_cast(bytes); + switch (header->type) { + case EventType::kUser: + // See below. + break; + case EventType::kPortAccepted: + *num_header_bytes = sizeof(EventHeader); + break; + case EventType::kObserveProxy: + *num_header_bytes = sizeof(EventHeader) + sizeof(ObserveProxyEventData); + break; + case EventType::kObserveProxyAck: + *num_header_bytes = + sizeof(EventHeader) + sizeof(ObserveProxyAckEventData); + break; + case EventType::kObserveClosure: + *num_header_bytes = sizeof(EventHeader) + sizeof(ObserveClosureEventData); + break; + case EventType::kMergePort: + *num_header_bytes = sizeof(EventHeader) + sizeof(MergePortEventData); + break; + default: + return false; + } + + if (header->type == EventType::kUser) { + if (num_bytes < sizeof(EventHeader) + sizeof(UserEventData)) + return false; + const UserEventData* event_data = + reinterpret_cast( + reinterpret_cast(header + 1)); + if (event_data->num_ports > std::numeric_limits::max()) + return false; + *num_header_bytes = sizeof(EventHeader) + + sizeof(UserEventData) + + event_data->num_ports * sizeof(PortDescriptor); + *num_ports_bytes = event_data->num_ports * sizeof(PortName); + if (num_bytes < *num_header_bytes + *num_ports_bytes) + return false; + *num_payload_bytes = num_bytes - *num_header_bytes - *num_ports_bytes; + } else { + if (*num_header_bytes != num_bytes) + return false; + *num_payload_bytes = 0; + *num_ports_bytes = 0; + } + + return true; +} + +Message::Message(size_t num_payload_bytes, size_t num_ports) + : Message(sizeof(EventHeader) + sizeof(UserEventData) + + num_ports * sizeof(PortDescriptor), + num_payload_bytes, num_ports * sizeof(PortName)) { + num_ports_ = num_ports; +} + +Message::Message(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes) + : start_(nullptr), + num_header_bytes_(num_header_bytes), + num_ports_bytes_(num_ports_bytes), + num_payload_bytes_(num_payload_bytes) { +} + +void Message::InitializeUserMessageHeader(void* start) { + start_ = static_cast(start); + memset(start_, 0, num_header_bytes_); + GetMutableEventHeader(this)->type = EventType::kUser; + GetMutableEventData(this)->num_ports = + static_cast(num_ports_); +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/message.h b/mojo/edk/system/ports/message.h new file mode 100644 index 0000000..95fa046 --- /dev/null +++ b/mojo/edk/system/ports/message.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_EDK_SYSTEM_PORTS_MESSAGE_H_ +#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_ + +#include + +#include + +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { +namespace ports { + +// A message consists of a header (array of bytes), payload (array of bytes) +// and an array of ports. The header is used by the Node implementation. +// +// This class is designed to be subclassed, and the subclass is responsible for +// providing the underlying storage. The header size will be aligned, and it +// should be followed in memory by the array of ports and finally the payload. +// +// NOTE: This class does not manage the lifetime of the ports it references. +class Message { + public: + virtual ~Message() {} + + // Inspect the message at |bytes| and return the size of each section. Returns + // |false| if the message is malformed and |true| otherwise. + static bool Parse(const void* bytes, + size_t num_bytes, + size_t* num_header_bytes, + size_t* num_payload_bytes, + size_t* num_ports_bytes); + + void* mutable_header_bytes() { return start_; } + const void* header_bytes() const { return start_; } + size_t num_header_bytes() const { return num_header_bytes_; } + + void* mutable_payload_bytes() { + return start_ + num_header_bytes_ + num_ports_bytes_; + } + const void* payload_bytes() const { + return const_cast(this)->mutable_payload_bytes(); + } + size_t num_payload_bytes() const { return num_payload_bytes_; } + + PortName* mutable_ports() { + return reinterpret_cast(start_ + num_header_bytes_); + } + const PortName* ports() const { + return const_cast(this)->mutable_ports(); + } + size_t num_ports_bytes() const { return num_ports_bytes_; } + size_t num_ports() const { return num_ports_bytes_ / sizeof(PortName); } + + protected: + // Constructs a new Message base for a user message. + // + // Note: You MUST call InitializeUserMessageHeader() before this Message is + // ready for transmission. + Message(size_t num_payload_bytes, size_t num_ports); + + // Constructs a new Message base for an internal message. Do NOT call + // InitializeUserMessageHeader() when using this constructor. + Message(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes); + + Message(const Message& other) = delete; + void operator=(const Message& other) = delete; + + // Initializes the header in a newly allocated message buffer to carry a + // user message. + void InitializeUserMessageHeader(void* start); + + // Note: storage is [header][ports][payload]. + char* start_ = nullptr; + size_t num_ports_ = 0; + size_t num_header_bytes_ = 0; + size_t num_ports_bytes_ = 0; + size_t num_payload_bytes_ = 0; +}; + +using ScopedMessage = std::unique_ptr; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_MESSAGE_H_ diff --git a/mojo/edk/system/ports/message_filter.h b/mojo/edk/system/ports/message_filter.h new file mode 100644 index 0000000..bf8fa21 --- /dev/null +++ b/mojo/edk/system/ports/message_filter.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_EDK_SYSTEM_PORTS_MESSAGE_FILTER_H_ +#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_FILTER_H_ + +namespace mojo { +namespace edk { +namespace ports { + +class Message; + +// An interface which can be implemented to filter port messages according to +// arbitrary policy. +class MessageFilter { + public: + virtual ~MessageFilter() {} + + // Returns true of |message| should be accepted by whomever is applying this + // filter. See MessageQueue::GetNextMessage(), for example. + virtual bool Match(const Message& message) = 0; +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_MESSAGE_FILTER_H_ diff --git a/mojo/edk/system/ports/message_queue.cc b/mojo/edk/system/ports/message_queue.cc new file mode 100644 index 0000000..defb1b6 --- /dev/null +++ b/mojo/edk/system/ports/message_queue.cc @@ -0,0 +1,87 @@ +// 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/edk/system/ports/message_queue.h" + +#include + +#include "base/logging.h" +#include "mojo/edk/system/ports/event.h" +#include "mojo/edk/system/ports/message_filter.h" + +namespace mojo { +namespace edk { +namespace ports { + +inline uint64_t GetSequenceNum(const ScopedMessage& message) { + return GetEventData(*message)->sequence_num; +} + +// Used by std::{push,pop}_heap functions +inline bool operator<(const ScopedMessage& a, const ScopedMessage& b) { + return GetSequenceNum(a) > GetSequenceNum(b); +} + +MessageQueue::MessageQueue() : MessageQueue(kInitialSequenceNum) {} + +MessageQueue::MessageQueue(uint64_t next_sequence_num) + : next_sequence_num_(next_sequence_num) { + // The message queue is blocked waiting for a message with sequence number + // equal to |next_sequence_num|. +} + +MessageQueue::~MessageQueue() { +#if DCHECK_IS_ON() + size_t num_leaked_ports = 0; + for (const auto& message : heap_) + num_leaked_ports += message->num_ports(); + DVLOG_IF(1, num_leaked_ports > 0) + << "Leaking " << num_leaked_ports << " ports in unreceived messages"; +#endif +} + +bool MessageQueue::HasNextMessage() const { + return !heap_.empty() && GetSequenceNum(heap_[0]) == next_sequence_num_; +} + +void MessageQueue::GetNextMessage(ScopedMessage* message, + MessageFilter* filter) { + if (!HasNextMessage() || (filter && !filter->Match(*heap_[0].get()))) { + message->reset(); + return; + } + + std::pop_heap(heap_.begin(), heap_.end()); + *message = std::move(heap_.back()); + heap_.pop_back(); + + next_sequence_num_++; +} + +void MessageQueue::AcceptMessage(ScopedMessage message, + bool* has_next_message) { + DCHECK(GetEventHeader(*message)->type == EventType::kUser); + + // TODO: Handle sequence number roll-over. + + heap_.emplace_back(std::move(message)); + std::push_heap(heap_.begin(), heap_.end()); + + if (!signalable_) { + *has_next_message = false; + } else { + *has_next_message = (GetSequenceNum(heap_[0]) == next_sequence_num_); + } +} + +void MessageQueue::GetReferencedPorts(std::deque* port_names) { + for (const auto& message : heap_) { + for (size_t i = 0; i < message->num_ports(); ++i) + port_names->push_back(message->ports()[i]); + } +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/message_queue.h b/mojo/edk/system/ports/message_queue.h new file mode 100644 index 0000000..d9a47ed --- /dev/null +++ b/mojo/edk/system/ports/message_queue.h @@ -0,0 +1,73 @@ +// 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_EDK_SYSTEM_PORTS_MESSAGE_QUEUE_H_ +#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_QUEUE_H_ + +#include + +#include +#include +#include +#include + +#include "base/macros.h" +#include "mojo/edk/system/ports/message.h" + +namespace mojo { +namespace edk { +namespace ports { + +const uint64_t kInitialSequenceNum = 1; +const uint64_t kInvalidSequenceNum = std::numeric_limits::max(); + +class MessageFilter; + +// An incoming message queue for a port. MessageQueue keeps track of the highest +// known sequence number and can indicate whether the next sequential message is +// available. Thus the queue enforces message ordering for the consumer without +// enforcing it for the producer (see AcceptMessage() below.) +class MessageQueue { + public: + explicit MessageQueue(); + explicit MessageQueue(uint64_t next_sequence_num); + ~MessageQueue(); + + void set_signalable(bool value) { signalable_ = value; } + + uint64_t next_sequence_num() const { return next_sequence_num_; } + + bool HasNextMessage() const; + + // Gives ownership of the message. If |filter| is non-null, the next message + // will only be retrieved if the filter successfully matches it. + void GetNextMessage(ScopedMessage* message, MessageFilter* filter); + + // Takes ownership of the message. Note: Messages are ordered, so while we + // have added a message to the queue, we may still be waiting on a message + // ahead of this one before we can let any of the messages be returned by + // GetNextMessage. + // + // Furthermore, once has_next_message is set to true, it will remain false + // until GetNextMessage is called enough times to return a null message. + // In other words, has_next_message acts like an edge trigger. + // + void AcceptMessage(ScopedMessage message, bool* has_next_message); + + // Returns all of the ports referenced by messages in this message queue. + void GetReferencedPorts(std::deque* ports); + + private: + std::vector heap_; + uint64_t next_sequence_num_; + bool signalable_ = true; + + DISALLOW_COPY_AND_ASSIGN(MessageQueue); +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_MESSAGE_QUEUE_H_ diff --git a/mojo/edk/system/ports/name.cc b/mojo/edk/system/ports/name.cc new file mode 100644 index 0000000..ea17698 --- /dev/null +++ b/mojo/edk/system/ports/name.cc @@ -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. + +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { +namespace ports { + +extern const PortName kInvalidPortName = {0, 0}; + +extern const NodeName kInvalidNodeName = {0, 0}; + +std::ostream& operator<<(std::ostream& stream, const Name& name) { + std::ios::fmtflags flags(stream.flags()); + stream << std::hex << std::uppercase << name.v1; + if (name.v2 != 0) + stream << '.' << name.v2; + stream.flags(flags); + return stream; +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/name.h b/mojo/edk/system/ports/name.h new file mode 100644 index 0000000..72e41b9 --- /dev/null +++ b/mojo/edk/system/ports/name.h @@ -0,0 +1,74 @@ +// 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_EDK_SYSTEM_PORTS_NAME_H_ +#define MOJO_EDK_SYSTEM_PORTS_NAME_H_ + +#include + +#include +#include + +#include "base/hash.h" + +namespace mojo { +namespace edk { +namespace ports { + +struct Name { + Name(uint64_t v1, uint64_t v2) : v1(v1), v2(v2) {} + uint64_t v1, v2; +}; + +inline bool operator==(const Name& a, const Name& b) { + return a.v1 == b.v1 && a.v2 == b.v2; +} + +inline bool operator!=(const Name& a, const Name& b) { + return !(a == b); +} + +inline bool operator<(const Name& a, const Name& b) { + return std::tie(a.v1, a.v2) < std::tie(b.v1, b.v2); +} + +std::ostream& operator<<(std::ostream& stream, const Name& name); + +struct PortName : Name { + PortName() : Name(0, 0) {} + PortName(uint64_t v1, uint64_t v2) : Name(v1, v2) {} +}; + +extern const PortName kInvalidPortName; + +struct NodeName : Name { + NodeName() : Name(0, 0) {} + NodeName(uint64_t v1, uint64_t v2) : Name(v1, v2) {} +}; + +extern const NodeName kInvalidNodeName; + +} // namespace ports +} // namespace edk +} // namespace mojo + +namespace std { + +template <> +struct hash { + std::size_t operator()(const mojo::edk::ports::PortName& name) const { + return base::HashInts64(name.v1, name.v2); + } +}; + +template <> +struct hash { + std::size_t operator()(const mojo::edk::ports::NodeName& name) const { + return base::HashInts64(name.v1, name.v2); + } +}; + +} // namespace std + +#endif // MOJO_EDK_SYSTEM_PORTS_NAME_H_ diff --git a/mojo/edk/system/ports/node.cc b/mojo/edk/system/ports/node.cc new file mode 100644 index 0000000..f9a3feb --- /dev/null +++ b/mojo/edk/system/ports/node.cc @@ -0,0 +1,1385 @@ +// 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/edk/system/ports/node.h" + +#include + +#include + +#include "base/atomicops.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/ports/node_delegate.h" + +namespace mojo { +namespace edk { +namespace ports { + +namespace { + +int DebugError(const char* message, int error_code) { + CHECK(false) << "Oops: " << message; + return error_code; +} + +#define OOPS(x) DebugError(#x, x) + +bool CanAcceptMoreMessages(const Port* port) { + // Have we already doled out the last message (i.e., do we expect to NOT + // receive further messages)? + uint64_t next_sequence_num = port->message_queue.next_sequence_num(); + if (port->state == Port::kClosed) + return false; + if (port->peer_closed || port->remove_proxy_on_last_message) { + if (port->last_sequence_num_to_receive == next_sequence_num - 1) + return false; + } + return true; +} + +} // namespace + +class Node::LockedPort { + public: + explicit LockedPort(Port* port) : port_(port) { + port_->lock.AssertAcquired(); + } + + Port* get() const { return port_; } + Port* operator->() const { return port_; } + + private: + Port* const port_; +}; + +Node::Node(const NodeName& name, NodeDelegate* delegate) + : name_(name), + delegate_(delegate) { +} + +Node::~Node() { + if (!ports_.empty()) + DLOG(WARNING) << "Unclean shutdown for node " << name_; +} + +bool Node::CanShutdownCleanly(ShutdownPolicy policy) { + base::AutoLock ports_lock(ports_lock_); + + if (policy == ShutdownPolicy::DONT_ALLOW_LOCAL_PORTS) { +#if DCHECK_IS_ON() + for (auto entry : ports_) { + DVLOG(2) << "Port " << entry.first << " referencing node " + << entry.second->peer_node_name << " is blocking shutdown of " + << "node " << name_ << " (state=" << entry.second->state << ")"; + } +#endif + return ports_.empty(); + } + + DCHECK_EQ(policy, ShutdownPolicy::ALLOW_LOCAL_PORTS); + + // NOTE: This is not efficient, though it probably doesn't need to be since + // relatively few ports should be open during shutdown and shutdown doesn't + // need to be blazingly fast. + bool can_shutdown = true; + for (auto entry : ports_) { + base::AutoLock lock(entry.second->lock); + if (entry.second->peer_node_name != name_ && + entry.second->state != Port::kReceiving) { + can_shutdown = false; +#if DCHECK_IS_ON() + DVLOG(2) << "Port " << entry.first << " referencing node " + << entry.second->peer_node_name << " is blocking shutdown of " + << "node " << name_ << " (state=" << entry.second->state << ")"; +#else + // Exit early when not debugging. + break; +#endif + } + } + + return can_shutdown; +} + +int Node::GetPort(const PortName& port_name, PortRef* port_ref) { + scoped_refptr port = GetPort(port_name); + if (!port) + return ERROR_PORT_UNKNOWN; + + *port_ref = PortRef(port_name, std::move(port)); + return OK; +} + +int Node::CreateUninitializedPort(PortRef* port_ref) { + PortName port_name; + delegate_->GenerateRandomPortName(&port_name); + + scoped_refptr port(new Port(kInitialSequenceNum, kInitialSequenceNum)); + int rv = AddPortWithName(port_name, port); + if (rv != OK) + return rv; + + *port_ref = PortRef(port_name, std::move(port)); + return OK; +} + +int Node::InitializePort(const PortRef& port_ref, + const NodeName& peer_node_name, + const PortName& peer_port_name) { + Port* port = port_ref.port(); + + { + base::AutoLock lock(port->lock); + if (port->state != Port::kUninitialized) + return ERROR_PORT_STATE_UNEXPECTED; + + port->state = Port::kReceiving; + port->peer_node_name = peer_node_name; + port->peer_port_name = peer_port_name; + } + + delegate_->PortStatusChanged(port_ref); + + return OK; +} + +int Node::CreatePortPair(PortRef* port0_ref, PortRef* port1_ref) { + int rv; + + rv = CreateUninitializedPort(port0_ref); + if (rv != OK) + return rv; + + rv = CreateUninitializedPort(port1_ref); + if (rv != OK) + return rv; + + rv = InitializePort(*port0_ref, name_, port1_ref->name()); + if (rv != OK) + return rv; + + rv = InitializePort(*port1_ref, name_, port0_ref->name()); + if (rv != OK) + return rv; + + return OK; +} + +int Node::SetUserData(const PortRef& port_ref, + scoped_refptr user_data) { + Port* port = port_ref.port(); + + base::AutoLock lock(port->lock); + if (port->state == Port::kClosed) + return ERROR_PORT_STATE_UNEXPECTED; + + port->user_data = std::move(user_data); + + return OK; +} + +int Node::GetUserData(const PortRef& port_ref, + scoped_refptr* user_data) { + Port* port = port_ref.port(); + + base::AutoLock lock(port->lock); + if (port->state == Port::kClosed) + return ERROR_PORT_STATE_UNEXPECTED; + + *user_data = port->user_data; + + return OK; +} + +int Node::ClosePort(const PortRef& port_ref) { + std::deque referenced_port_names; + + ObserveClosureEventData data; + + NodeName peer_node_name; + PortName peer_port_name; + Port* port = port_ref.port(); + { + // We may need to erase the port, which requires ports_lock_ to be held, + // but ports_lock_ must be acquired before any individual port locks. + base::AutoLock ports_lock(ports_lock_); + + base::AutoLock lock(port->lock); + if (port->state == Port::kUninitialized) { + // If the port was not yet initialized, there's nothing interesting to do. + ErasePort_Locked(port_ref.name()); + return OK; + } + + if (port->state != Port::kReceiving) + return ERROR_PORT_STATE_UNEXPECTED; + + port->state = Port::kClosed; + + // We pass along the sequence number of the last message sent from this + // port to allow the peer to have the opportunity to consume all inbound + // messages before notifying the embedder that this port is closed. + data.last_sequence_num = port->next_sequence_num_to_send - 1; + + peer_node_name = port->peer_node_name; + peer_port_name = port->peer_port_name; + + // If the port being closed still has unread messages, then we need to take + // care to close those ports so as to avoid leaking memory. + port->message_queue.GetReferencedPorts(&referenced_port_names); + + ErasePort_Locked(port_ref.name()); + } + + DVLOG(2) << "Sending ObserveClosure from " << port_ref.name() << "@" << name_ + << " to " << peer_port_name << "@" << peer_node_name; + + delegate_->ForwardMessage( + peer_node_name, + NewInternalMessage(peer_port_name, EventType::kObserveClosure, data)); + + for (const auto& name : referenced_port_names) { + PortRef ref; + if (GetPort(name, &ref) == OK) + ClosePort(ref); + } + return OK; +} + +int Node::GetStatus(const PortRef& port_ref, PortStatus* port_status) { + Port* port = port_ref.port(); + + base::AutoLock lock(port->lock); + + if (port->state != Port::kReceiving) + return ERROR_PORT_STATE_UNEXPECTED; + + port_status->has_messages = port->message_queue.HasNextMessage(); + port_status->receiving_messages = CanAcceptMoreMessages(port); + port_status->peer_closed = port->peer_closed; + return OK; +} + +int Node::GetMessage(const PortRef& port_ref, + ScopedMessage* message, + MessageFilter* filter) { + *message = nullptr; + + DVLOG(4) << "GetMessage for " << port_ref.name() << "@" << name_; + + Port* port = port_ref.port(); + { + base::AutoLock lock(port->lock); + + // This could also be treated like the port being unknown since the + // embedder should no longer be referring to a port that has been sent. + if (port->state != Port::kReceiving) + return ERROR_PORT_STATE_UNEXPECTED; + + // Let the embedder get messages until there are no more before reporting + // that the peer closed its end. + if (!CanAcceptMoreMessages(port)) + return ERROR_PORT_PEER_CLOSED; + + port->message_queue.GetNextMessage(message, filter); + } + + // Allow referenced ports to trigger PortStatusChanged calls. + if (*message) { + for (size_t i = 0; i < (*message)->num_ports(); ++i) { + const PortName& new_port_name = (*message)->ports()[i]; + scoped_refptr new_port = GetPort(new_port_name); + + DCHECK(new_port) << "Port " << new_port_name << "@" << name_ + << " does not exist!"; + + base::AutoLock lock(new_port->lock); + + DCHECK(new_port->state == Port::kReceiving); + new_port->message_queue.set_signalable(true); + } + } + + return OK; +} + +int Node::SendMessage(const PortRef& port_ref, ScopedMessage message) { + int rv = SendMessageInternal(port_ref, &message); + if (rv != OK) { + // If send failed, close all carried ports. Note that we're careful not to + // close the sending port itself if it happened to be one of the encoded + // ports (an invalid but possible condition.) + for (size_t i = 0; i < message->num_ports(); ++i) { + if (message->ports()[i] == port_ref.name()) + continue; + + PortRef port; + if (GetPort(message->ports()[i], &port) == OK) + ClosePort(port); + } + } + return rv; +} + +int Node::AcceptMessage(ScopedMessage message) { + const EventHeader* header = GetEventHeader(*message); + switch (header->type) { + case EventType::kUser: + return OnUserMessage(std::move(message)); + case EventType::kPortAccepted: + return OnPortAccepted(header->port_name); + case EventType::kObserveProxy: + return OnObserveProxy( + header->port_name, + *GetEventData(*message)); + case EventType::kObserveProxyAck: + return OnObserveProxyAck( + header->port_name, + GetEventData(*message)->last_sequence_num); + case EventType::kObserveClosure: + return OnObserveClosure( + header->port_name, + GetEventData(*message)->last_sequence_num); + case EventType::kMergePort: + return OnMergePort(header->port_name, + *GetEventData(*message)); + } + return OOPS(ERROR_NOT_IMPLEMENTED); +} + +int Node::MergePorts(const PortRef& port_ref, + const NodeName& destination_node_name, + const PortName& destination_port_name) { + Port* port = port_ref.port(); + MergePortEventData data; + { + base::AutoLock lock(port->lock); + + DVLOG(1) << "Sending MergePort from " << port_ref.name() << "@" << name_ + << " to " << destination_port_name << "@" << destination_node_name; + + // Send the port-to-merge over to the destination node so it can be merged + // into the port cycle atomically there. + data.new_port_name = port_ref.name(); + WillSendPort(LockedPort(port), destination_node_name, &data.new_port_name, + &data.new_port_descriptor); + } + delegate_->ForwardMessage( + destination_node_name, + NewInternalMessage(destination_port_name, + EventType::kMergePort, data)); + return OK; +} + +int Node::MergeLocalPorts(const PortRef& port0_ref, const PortRef& port1_ref) { + Port* port0 = port0_ref.port(); + Port* port1 = port1_ref.port(); + int rv; + { + // |ports_lock_| must be held when acquiring overlapping port locks. + base::AutoLock ports_lock(ports_lock_); + base::AutoLock port0_lock(port0->lock); + base::AutoLock port1_lock(port1->lock); + + DVLOG(1) << "Merging local ports " << port0_ref.name() << "@" << name_ + << " and " << port1_ref.name() << "@" << name_; + + if (port0->state != Port::kReceiving || port1->state != Port::kReceiving) + rv = ERROR_PORT_STATE_UNEXPECTED; + else + rv = MergePorts_Locked(port0_ref, port1_ref); + } + + if (rv != OK) { + ClosePort(port0_ref); + ClosePort(port1_ref); + } + + return rv; +} + +int Node::LostConnectionToNode(const NodeName& node_name) { + // We can no longer send events to the given node. We also can't expect any + // PortAccepted events. + + DVLOG(1) << "Observing lost connection from node " << name_ + << " to node " << node_name; + + DestroyAllPortsWithPeer(node_name, kInvalidPortName); + return OK; +} + +int Node::OnUserMessage(ScopedMessage message) { + PortName port_name = GetEventHeader(*message)->port_name; + const auto* event = GetEventData(*message); + +#if DCHECK_IS_ON() + std::ostringstream ports_buf; + for (size_t i = 0; i < message->num_ports(); ++i) { + if (i > 0) + ports_buf << ","; + ports_buf << message->ports()[i]; + } + + DVLOG(4) << "AcceptMessage " << event->sequence_num + << " [ports=" << ports_buf.str() << "] at " + << port_name << "@" << name_; +#endif + + scoped_refptr port = GetPort(port_name); + + // Even if this port does not exist, cannot receive anymore messages or is + // buffering or proxying messages, we still need these ports to be bound to + // this node. When the message is forwarded, these ports will get transferred + // following the usual method. If the message cannot be accepted, then the + // newly bound ports will simply be closed. + + for (size_t i = 0; i < message->num_ports(); ++i) { + int rv = AcceptPort(message->ports()[i], GetPortDescriptors(event)[i]); + if (rv != OK) + return rv; + } + + bool has_next_message = false; + bool message_accepted = false; + + if (port) { + // We may want to forward messages once the port lock is held, so we must + // acquire |ports_lock_| first. + base::AutoLock ports_lock(ports_lock_); + base::AutoLock lock(port->lock); + + // Reject spurious messages if we've already received the last expected + // message. + if (CanAcceptMoreMessages(port.get())) { + message_accepted = true; + port->message_queue.AcceptMessage(std::move(message), &has_next_message); + + if (port->state == Port::kBuffering) { + has_next_message = false; + } else if (port->state == Port::kProxying) { + has_next_message = false; + + // Forward messages. We forward messages in sequential order here so + // that we maintain the message queue's notion of next sequence number. + // That's useful for the proxy removal process as we can tell when this + // port has seen all of the messages it is expected to see. + int rv = ForwardMessages_Locked(LockedPort(port.get()), port_name); + if (rv != OK) + return rv; + + MaybeRemoveProxy_Locked(LockedPort(port.get()), port_name); + } + } + } + + if (!message_accepted) { + DVLOG(2) << "Message not accepted!\n"; + // Close all newly accepted ports as they are effectively orphaned. + for (size_t i = 0; i < message->num_ports(); ++i) { + PortRef port_ref; + if (GetPort(message->ports()[i], &port_ref) == OK) { + ClosePort(port_ref); + } else { + DLOG(WARNING) << "Cannot close non-existent port!\n"; + } + } + } else if (has_next_message) { + PortRef port_ref(port_name, port); + delegate_->PortStatusChanged(port_ref); + } + + return OK; +} + +int Node::OnPortAccepted(const PortName& port_name) { + scoped_refptr port = GetPort(port_name); + if (!port) + return ERROR_PORT_UNKNOWN; + + DVLOG(2) << "PortAccepted at " << port_name << "@" << name_ + << " pointing to " + << port->peer_port_name << "@" << port->peer_node_name; + + return BeginProxying(PortRef(port_name, std::move(port))); +} + +int Node::OnObserveProxy(const PortName& port_name, + const ObserveProxyEventData& event) { + if (port_name == kInvalidPortName) { + // An ObserveProxy with an invalid target port name is a broadcast used to + // inform ports when their peer (which was itself a proxy) has become + // defunct due to unexpected node disconnection. + // + // Receiving ports affected by this treat it as equivalent to peer closure. + // Proxies affected by this can be removed and will in turn broadcast their + // own death with a similar message. + CHECK_EQ(event.proxy_to_node_name, kInvalidNodeName); + CHECK_EQ(event.proxy_to_port_name, kInvalidPortName); + DestroyAllPortsWithPeer(event.proxy_node_name, event.proxy_port_name); + return OK; + } + + // The port may have already been closed locally, in which case the + // ObserveClosure message will contain the last_sequence_num field. + // We can then silently ignore this message. + scoped_refptr port = GetPort(port_name); + if (!port) { + DVLOG(1) << "ObserveProxy: " << port_name << "@" << name_ << " not found"; + return OK; + } + + DVLOG(2) << "ObserveProxy at " << port_name << "@" << name_ << ", proxy at " + << event.proxy_port_name << "@" + << event.proxy_node_name << " pointing to " + << event.proxy_to_port_name << "@" + << event.proxy_to_node_name; + + { + base::AutoLock lock(port->lock); + + if (port->peer_node_name == event.proxy_node_name && + port->peer_port_name == event.proxy_port_name) { + if (port->state == Port::kReceiving) { + port->peer_node_name = event.proxy_to_node_name; + port->peer_port_name = event.proxy_to_port_name; + + ObserveProxyAckEventData ack; + ack.last_sequence_num = port->next_sequence_num_to_send - 1; + + delegate_->ForwardMessage( + event.proxy_node_name, + NewInternalMessage(event.proxy_port_name, + EventType::kObserveProxyAck, + ack)); + } else { + // As a proxy ourselves, we don't know how to honor the ObserveProxy + // event or to populate the last_sequence_num field of ObserveProxyAck. + // Afterall, another port could be sending messages to our peer now + // that we've sent out our own ObserveProxy event. Instead, we will + // send an ObserveProxyAck indicating that the ObserveProxy event + // should be re-sent (last_sequence_num set to kInvalidSequenceNum). + // However, this has to be done after we are removed as a proxy. + // Otherwise, we might just find ourselves back here again, which + // would be akin to a busy loop. + + DVLOG(2) << "Delaying ObserveProxyAck to " + << event.proxy_port_name << "@" << event.proxy_node_name; + + ObserveProxyAckEventData ack; + ack.last_sequence_num = kInvalidSequenceNum; + + port->send_on_proxy_removal.reset( + new std::pair( + event.proxy_node_name, + NewInternalMessage(event.proxy_port_name, + EventType::kObserveProxyAck, + ack))); + } + } else { + // Forward this event along to our peer. Eventually, it should find the + // port referring to the proxy. + delegate_->ForwardMessage( + port->peer_node_name, + NewInternalMessage(port->peer_port_name, + EventType::kObserveProxy, + event)); + } + } + return OK; +} + +int Node::OnObserveProxyAck(const PortName& port_name, + uint64_t last_sequence_num) { + DVLOG(2) << "ObserveProxyAck at " << port_name << "@" << name_ + << " (last_sequence_num=" << last_sequence_num << ")"; + + scoped_refptr port = GetPort(port_name); + if (!port) + return ERROR_PORT_UNKNOWN; // The port may have observed closure first, so + // this is not an "Oops". + + { + base::AutoLock lock(port->lock); + + if (port->state != Port::kProxying) + return OOPS(ERROR_PORT_STATE_UNEXPECTED); + + if (last_sequence_num == kInvalidSequenceNum) { + // Send again. + InitiateProxyRemoval(LockedPort(port.get()), port_name); + return OK; + } + + // We can now remove this port once we have received and forwarded the last + // message addressed to this port. + port->remove_proxy_on_last_message = true; + port->last_sequence_num_to_receive = last_sequence_num; + } + TryRemoveProxy(PortRef(port_name, std::move(port))); + return OK; +} + +int Node::OnObserveClosure(const PortName& port_name, + uint64_t last_sequence_num) { + // OK if the port doesn't exist, as it may have been closed already. + scoped_refptr port = GetPort(port_name); + if (!port) + return OK; + + // This message tells the port that it should no longer expect more messages + // beyond last_sequence_num. This message is forwarded along until we reach + // the receiving end, and this message serves as an equivalent to + // ObserveProxyAck. + + bool notify_delegate = false; + ObserveClosureEventData forwarded_data; + NodeName peer_node_name; + PortName peer_port_name; + bool try_remove_proxy = false; + { + base::AutoLock lock(port->lock); + + port->peer_closed = true; + port->last_sequence_num_to_receive = last_sequence_num; + + DVLOG(2) << "ObserveClosure at " << port_name << "@" << name_ + << " (state=" << port->state << ") pointing to " + << port->peer_port_name << "@" << port->peer_node_name + << " (last_sequence_num=" << last_sequence_num << ")"; + + // We always forward ObserveClosure, even beyond the receiving port which + // cares about it. This ensures that any dead-end proxies beyond that port + // are notified to remove themselves. + + if (port->state == Port::kReceiving) { + notify_delegate = true; + + // When forwarding along the other half of the port cycle, this will only + // reach dead-end proxies. Tell them we've sent our last message so they + // can go away. + // + // TODO: Repurposing ObserveClosure for this has the desired result but + // may be semantically confusing since the forwarding port is not actually + // closed. Consider replacing this with a new event type. + forwarded_data.last_sequence_num = port->next_sequence_num_to_send - 1; + } else { + // We haven't yet reached the receiving peer of the closed port, so + // forward the message along as-is. + forwarded_data.last_sequence_num = last_sequence_num; + + // See about removing the port if it is a proxy as our peer won't be able + // to participate in proxy removal. + port->remove_proxy_on_last_message = true; + if (port->state == Port::kProxying) + try_remove_proxy = true; + } + + DVLOG(2) << "Forwarding ObserveClosure from " + << port_name << "@" << name_ << " to peer " + << port->peer_port_name << "@" << port->peer_node_name + << " (last_sequence_num=" << forwarded_data.last_sequence_num + << ")"; + + peer_node_name = port->peer_node_name; + peer_port_name = port->peer_port_name; + } + if (try_remove_proxy) + TryRemoveProxy(PortRef(port_name, port)); + + delegate_->ForwardMessage( + peer_node_name, + NewInternalMessage(peer_port_name, EventType::kObserveClosure, + forwarded_data)); + + if (notify_delegate) { + PortRef port_ref(port_name, std::move(port)); + delegate_->PortStatusChanged(port_ref); + } + return OK; +} + +int Node::OnMergePort(const PortName& port_name, + const MergePortEventData& event) { + scoped_refptr port = GetPort(port_name); + + DVLOG(1) << "MergePort at " << port_name << "@" << name_ << " (state=" + << (port ? port->state : -1) << ") merging with proxy " + << event.new_port_name + << "@" << name_ << " pointing to " + << event.new_port_descriptor.peer_port_name << "@" + << event.new_port_descriptor.peer_node_name << " referred by " + << event.new_port_descriptor.referring_port_name << "@" + << event.new_port_descriptor.referring_node_name; + + bool close_target_port = false; + bool close_new_port = false; + + // Accept the new port. This is now the receiving end of the other port cycle + // to be merged with ours. + int rv = AcceptPort(event.new_port_name, event.new_port_descriptor); + if (rv != OK) { + close_target_port = true; + } else if (port) { + // BeginProxying_Locked may call MaybeRemoveProxy_Locked, which in turn + // needs to hold |ports_lock_|. We also acquire multiple port locks within. + base::AutoLock ports_lock(ports_lock_); + base::AutoLock lock(port->lock); + + if (port->state != Port::kReceiving) { + close_new_port = true; + } else { + scoped_refptr new_port = GetPort_Locked(event.new_port_name); + base::AutoLock new_port_lock(new_port->lock); + DCHECK(new_port->state == Port::kReceiving); + + // Both ports are locked. Now all we have to do is swap their peer + // information and set them up as proxies. + + PortRef port0_ref(port_name, port); + PortRef port1_ref(event.new_port_name, new_port); + int rv = MergePorts_Locked(port0_ref, port1_ref); + if (rv == OK) + return rv; + + close_new_port = true; + close_target_port = true; + } + } else { + close_new_port = true; + } + + if (close_target_port) { + PortRef target_port; + rv = GetPort(port_name, &target_port); + DCHECK(rv == OK); + + ClosePort(target_port); + } + + if (close_new_port) { + PortRef new_port; + rv = GetPort(event.new_port_name, &new_port); + DCHECK(rv == OK); + + ClosePort(new_port); + } + + return ERROR_PORT_STATE_UNEXPECTED; +} + +int Node::AddPortWithName(const PortName& port_name, scoped_refptr port) { + base::AutoLock lock(ports_lock_); + + if (!ports_.insert(std::make_pair(port_name, std::move(port))).second) + return OOPS(ERROR_PORT_EXISTS); // Suggests a bad UUID generator. + + DVLOG(2) << "Created port " << port_name << "@" << name_; + return OK; +} + +void Node::ErasePort(const PortName& port_name) { + base::AutoLock lock(ports_lock_); + ErasePort_Locked(port_name); +} + +void Node::ErasePort_Locked(const PortName& port_name) { + ports_lock_.AssertAcquired(); + ports_.erase(port_name); + DVLOG(2) << "Deleted port " << port_name << "@" << name_; +} + +scoped_refptr Node::GetPort(const PortName& port_name) { + base::AutoLock lock(ports_lock_); + return GetPort_Locked(port_name); +} + +scoped_refptr Node::GetPort_Locked(const PortName& port_name) { + ports_lock_.AssertAcquired(); + auto iter = ports_.find(port_name); + if (iter == ports_.end()) + return nullptr; + +#if (defined(OS_ANDROID) || defined(__ANDROID__)) && defined(ARCH_CPU_ARM64) + // Workaround for https://crbug.com/665869. + base::subtle::MemoryBarrier(); +#endif + + return iter->second; +} + +int Node::SendMessageInternal(const PortRef& port_ref, ScopedMessage* message) { + ScopedMessage& m = *message; + for (size_t i = 0; i < m->num_ports(); ++i) { + if (m->ports()[i] == port_ref.name()) + return ERROR_PORT_CANNOT_SEND_SELF; + } + + Port* port = port_ref.port(); + NodeName peer_node_name; + { + // We must acquire |ports_lock_| before grabbing any port locks, because + // WillSendMessage_Locked may need to lock multiple ports out of order. + base::AutoLock ports_lock(ports_lock_); + base::AutoLock lock(port->lock); + + if (port->state != Port::kReceiving) + return ERROR_PORT_STATE_UNEXPECTED; + + if (port->peer_closed) + return ERROR_PORT_PEER_CLOSED; + + int rv = WillSendMessage_Locked(LockedPort(port), port_ref.name(), m.get()); + if (rv != OK) + return rv; + + // Beyond this point there's no sense in returning anything but OK. Even if + // message forwarding or acceptance fails, there's nothing the embedder can + // do to recover. Assume that failure beyond this point must be treated as a + // transport failure. + + peer_node_name = port->peer_node_name; + } + + if (peer_node_name != name_) { + delegate_->ForwardMessage(peer_node_name, std::move(m)); + return OK; + } + + int rv = AcceptMessage(std::move(m)); + if (rv != OK) { + // See comment above for why we don't return an error in this case. + DVLOG(2) << "AcceptMessage failed: " << rv; + } + + return OK; +} + +int Node::MergePorts_Locked(const PortRef& port0_ref, + const PortRef& port1_ref) { + Port* port0 = port0_ref.port(); + Port* port1 = port1_ref.port(); + + ports_lock_.AssertAcquired(); + port0->lock.AssertAcquired(); + port1->lock.AssertAcquired(); + + CHECK(port0->state == Port::kReceiving); + CHECK(port1->state == Port::kReceiving); + + // Ports cannot be merged with their own receiving peer! + if (port0->peer_node_name == name_ && + port0->peer_port_name == port1_ref.name()) + return ERROR_PORT_STATE_UNEXPECTED; + + if (port1->peer_node_name == name_ && + port1->peer_port_name == port0_ref.name()) + return ERROR_PORT_STATE_UNEXPECTED; + + // Only merge if both ports have never sent a message. + if (port0->next_sequence_num_to_send == kInitialSequenceNum && + port1->next_sequence_num_to_send == kInitialSequenceNum) { + // Swap the ports' peer information and switch them both into buffering + // (eventually proxying) mode. + + std::swap(port0->peer_node_name, port1->peer_node_name); + std::swap(port0->peer_port_name, port1->peer_port_name); + + port0->state = Port::kBuffering; + if (port0->peer_closed) + port0->remove_proxy_on_last_message = true; + + port1->state = Port::kBuffering; + if (port1->peer_closed) + port1->remove_proxy_on_last_message = true; + + int rv1 = BeginProxying_Locked(LockedPort(port0), port0_ref.name()); + int rv2 = BeginProxying_Locked(LockedPort(port1), port1_ref.name()); + + if (rv1 == OK && rv2 == OK) { + // If either merged port had a closed peer, its new peer needs to be + // informed of this. + if (port1->peer_closed) { + ObserveClosureEventData data; + data.last_sequence_num = port0->last_sequence_num_to_receive; + delegate_->ForwardMessage( + port0->peer_node_name, + NewInternalMessage(port0->peer_port_name, + EventType::kObserveClosure, data)); + } + + if (port0->peer_closed) { + ObserveClosureEventData data; + data.last_sequence_num = port1->last_sequence_num_to_receive; + delegate_->ForwardMessage( + port1->peer_node_name, + NewInternalMessage(port1->peer_port_name, + EventType::kObserveClosure, data)); + } + + return OK; + } + + // If either proxy failed to initialize (e.g. had undeliverable messages + // or ended up in a bad state somehow), we keep the system in a consistent + // state by undoing the peer swap. + std::swap(port0->peer_node_name, port1->peer_node_name); + std::swap(port0->peer_port_name, port1->peer_port_name); + port0->remove_proxy_on_last_message = false; + port1->remove_proxy_on_last_message = false; + port0->state = Port::kReceiving; + port1->state = Port::kReceiving; + } + + return ERROR_PORT_STATE_UNEXPECTED; +} + +void Node::WillSendPort(const LockedPort& port, + const NodeName& to_node_name, + PortName* port_name, + PortDescriptor* port_descriptor) { + port->lock.AssertAcquired(); + + PortName local_port_name = *port_name; + + PortName new_port_name; + delegate_->GenerateRandomPortName(&new_port_name); + + // Make sure we don't send messages to the new peer until after we know it + // exists. In the meantime, just buffer messages locally. + DCHECK(port->state == Port::kReceiving); + port->state = Port::kBuffering; + + // If we already know our peer is closed, we already know this proxy can + // be removed once it receives and forwards its last expected message. + if (port->peer_closed) + port->remove_proxy_on_last_message = true; + + *port_name = new_port_name; + + port_descriptor->peer_node_name = port->peer_node_name; + port_descriptor->peer_port_name = port->peer_port_name; + port_descriptor->referring_node_name = name_; + port_descriptor->referring_port_name = local_port_name; + port_descriptor->next_sequence_num_to_send = port->next_sequence_num_to_send; + port_descriptor->next_sequence_num_to_receive = + port->message_queue.next_sequence_num(); + port_descriptor->last_sequence_num_to_receive = + port->last_sequence_num_to_receive; + port_descriptor->peer_closed = port->peer_closed; + memset(port_descriptor->padding, 0, sizeof(port_descriptor->padding)); + + // Configure the local port to point to the new port. + port->peer_node_name = to_node_name; + port->peer_port_name = new_port_name; +} + +int Node::AcceptPort(const PortName& port_name, + const PortDescriptor& port_descriptor) { + scoped_refptr port = make_scoped_refptr( + new Port(port_descriptor.next_sequence_num_to_send, + port_descriptor.next_sequence_num_to_receive)); + port->state = Port::kReceiving; + port->peer_node_name = port_descriptor.peer_node_name; + port->peer_port_name = port_descriptor.peer_port_name; + port->last_sequence_num_to_receive = + port_descriptor.last_sequence_num_to_receive; + port->peer_closed = port_descriptor.peer_closed; + + DVLOG(2) << "Accepting port " << port_name << " [peer_closed=" + << port->peer_closed << "; last_sequence_num_to_receive=" + << port->last_sequence_num_to_receive << "]"; + + // A newly accepted port is not signalable until the message referencing the + // new port finds its way to the consumer (see GetMessage). + port->message_queue.set_signalable(false); + + int rv = AddPortWithName(port_name, std::move(port)); + if (rv != OK) + return rv; + + // Allow referring port to forward messages. + delegate_->ForwardMessage( + port_descriptor.referring_node_name, + NewInternalMessage(port_descriptor.referring_port_name, + EventType::kPortAccepted)); + return OK; +} + +int Node::WillSendMessage_Locked(const LockedPort& port, + const PortName& port_name, + Message* message) { + ports_lock_.AssertAcquired(); + port->lock.AssertAcquired(); + + DCHECK(message); + + // Messages may already have a sequence number if they're being forwarded + // by a proxy. Otherwise, use the next outgoing sequence number. + uint64_t* sequence_num = + &GetMutableEventData(message)->sequence_num; + if (*sequence_num == 0) + *sequence_num = port->next_sequence_num_to_send++; + +#if DCHECK_IS_ON() + std::ostringstream ports_buf; + for (size_t i = 0; i < message->num_ports(); ++i) { + if (i > 0) + ports_buf << ","; + ports_buf << message->ports()[i]; + } +#endif + + if (message->num_ports() > 0) { + // Note: Another thread could be trying to send the same ports, so we need + // to ensure that they are ours to send before we mutate their state. + + std::vector> ports; + ports.resize(message->num_ports()); + + { + for (size_t i = 0; i < message->num_ports(); ++i) { + ports[i] = GetPort_Locked(message->ports()[i]); + DCHECK(ports[i]); + + ports[i]->lock.Acquire(); + int error = OK; + if (ports[i]->state != Port::kReceiving) + error = ERROR_PORT_STATE_UNEXPECTED; + else if (message->ports()[i] == port->peer_port_name) + error = ERROR_PORT_CANNOT_SEND_PEER; + + if (error != OK) { + // Oops, we cannot send this port. + for (size_t j = 0; j <= i; ++j) + ports[i]->lock.Release(); + // Backpedal on the sequence number. + port->next_sequence_num_to_send--; + return error; + } + } + } + + PortDescriptor* port_descriptors = + GetMutablePortDescriptors(GetMutableEventData(message)); + + for (size_t i = 0; i < message->num_ports(); ++i) { + WillSendPort(LockedPort(ports[i].get()), + port->peer_node_name, + message->mutable_ports() + i, + port_descriptors + i); + } + + for (size_t i = 0; i < message->num_ports(); ++i) + ports[i]->lock.Release(); + } + +#if DCHECK_IS_ON() + DVLOG(4) << "Sending message " + << GetEventData(*message)->sequence_num + << " [ports=" << ports_buf.str() << "]" + << " from " << port_name << "@" << name_ + << " to " << port->peer_port_name << "@" << port->peer_node_name; +#endif + + GetMutableEventHeader(message)->port_name = port->peer_port_name; + return OK; +} + +int Node::BeginProxying_Locked(const LockedPort& port, + const PortName& port_name) { + ports_lock_.AssertAcquired(); + port->lock.AssertAcquired(); + + if (port->state != Port::kBuffering) + return OOPS(ERROR_PORT_STATE_UNEXPECTED); + + port->state = Port::kProxying; + + int rv = ForwardMessages_Locked(LockedPort(port), port_name); + if (rv != OK) + return rv; + + // We may have observed closure while buffering. In that case, we can advance + // to removing the proxy without sending out an ObserveProxy message. We + // already know the last expected message, etc. + + if (port->remove_proxy_on_last_message) { + MaybeRemoveProxy_Locked(LockedPort(port), port_name); + + // Make sure we propagate closure to our current peer. + ObserveClosureEventData data; + data.last_sequence_num = port->last_sequence_num_to_receive; + delegate_->ForwardMessage( + port->peer_node_name, + NewInternalMessage(port->peer_port_name, + EventType::kObserveClosure, data)); + } else { + InitiateProxyRemoval(LockedPort(port), port_name); + } + + return OK; +} + +int Node::BeginProxying(PortRef port_ref) { + Port* port = port_ref.port(); + { + base::AutoLock ports_lock(ports_lock_); + base::AutoLock lock(port->lock); + + if (port->state != Port::kBuffering) + return OOPS(ERROR_PORT_STATE_UNEXPECTED); + + port->state = Port::kProxying; + + int rv = ForwardMessages_Locked(LockedPort(port), port_ref.name()); + if (rv != OK) + return rv; + } + + bool should_remove; + NodeName peer_node_name; + ScopedMessage closure_message; + { + base::AutoLock lock(port->lock); + if (port->state != Port::kProxying) + return OOPS(ERROR_PORT_STATE_UNEXPECTED); + + should_remove = port->remove_proxy_on_last_message; + if (should_remove) { + // Make sure we propagate closure to our current peer. + ObserveClosureEventData data; + data.last_sequence_num = port->last_sequence_num_to_receive; + peer_node_name = port->peer_node_name; + closure_message = NewInternalMessage(port->peer_port_name, + EventType::kObserveClosure, data); + } else { + InitiateProxyRemoval(LockedPort(port), port_ref.name()); + } + } + + if (should_remove) { + TryRemoveProxy(port_ref); + delegate_->ForwardMessage(peer_node_name, std::move(closure_message)); + } + + return OK; +} + +int Node::ForwardMessages_Locked(const LockedPort& port, + const PortName &port_name) { + ports_lock_.AssertAcquired(); + port->lock.AssertAcquired(); + + for (;;) { + ScopedMessage message; + port->message_queue.GetNextMessage(&message, nullptr); + if (!message) + break; + + int rv = WillSendMessage_Locked(LockedPort(port), port_name, message.get()); + if (rv != OK) + return rv; + + delegate_->ForwardMessage(port->peer_node_name, std::move(message)); + } + return OK; +} + +void Node::InitiateProxyRemoval(const LockedPort& port, + const PortName& port_name) { + port->lock.AssertAcquired(); + + // To remove this node, we start by notifying the connected graph that we are + // a proxy. This allows whatever port is referencing this node to skip it. + // Eventually, this node will receive ObserveProxyAck (or ObserveClosure if + // the peer was closed in the meantime). + + ObserveProxyEventData data; + data.proxy_node_name = name_; + data.proxy_port_name = port_name; + data.proxy_to_node_name = port->peer_node_name; + data.proxy_to_port_name = port->peer_port_name; + + delegate_->ForwardMessage( + port->peer_node_name, + NewInternalMessage(port->peer_port_name, EventType::kObserveProxy, data)); +} + +void Node::MaybeRemoveProxy_Locked(const LockedPort& port, + const PortName& port_name) { + // |ports_lock_| must be held so we can potentilaly ErasePort_Locked(). + ports_lock_.AssertAcquired(); + port->lock.AssertAcquired(); + + DCHECK(port->state == Port::kProxying); + + // Make sure we have seen ObserveProxyAck before removing the port. + if (!port->remove_proxy_on_last_message) + return; + + if (!CanAcceptMoreMessages(port.get())) { + // This proxy port is done. We can now remove it! + ErasePort_Locked(port_name); + + if (port->send_on_proxy_removal) { + NodeName to_node = port->send_on_proxy_removal->first; + ScopedMessage& message = port->send_on_proxy_removal->second; + + delegate_->ForwardMessage(to_node, std::move(message)); + port->send_on_proxy_removal.reset(); + } + } else { + DVLOG(2) << "Cannot remove port " << port_name << "@" << name_ + << " now; waiting for more messages"; + } +} + +void Node::TryRemoveProxy(PortRef port_ref) { + Port* port = port_ref.port(); + bool should_erase = false; + ScopedMessage msg; + NodeName to_node; + { + base::AutoLock lock(port->lock); + + // Port already removed. Nothing to do. + if (port->state == Port::kClosed) + return; + + DCHECK(port->state == Port::kProxying); + + // Make sure we have seen ObserveProxyAck before removing the port. + if (!port->remove_proxy_on_last_message) + return; + + if (!CanAcceptMoreMessages(port)) { + // This proxy port is done. We can now remove it! + should_erase = true; + + if (port->send_on_proxy_removal) { + to_node = port->send_on_proxy_removal->first; + msg = std::move(port->send_on_proxy_removal->second); + port->send_on_proxy_removal.reset(); + } + } else { + DVLOG(2) << "Cannot remove port " << port_ref.name() << "@" << name_ + << " now; waiting for more messages"; + } + } + + if (should_erase) + ErasePort(port_ref.name()); + + if (msg) + delegate_->ForwardMessage(to_node, std::move(msg)); +} + +void Node::DestroyAllPortsWithPeer(const NodeName& node_name, + const PortName& port_name) { + // Wipes out all ports whose peer node matches |node_name| and whose peer port + // matches |port_name|. If |port_name| is |kInvalidPortName|, only the peer + // node is matched. + + std::vector ports_to_notify; + std::vector dead_proxies_to_broadcast; + std::deque referenced_port_names; + + { + base::AutoLock ports_lock(ports_lock_); + + for (auto iter = ports_.begin(); iter != ports_.end(); ++iter) { + Port* port = iter->second.get(); + { + base::AutoLock port_lock(port->lock); + + if (port->peer_node_name == node_name && + (port_name == kInvalidPortName || + port->peer_port_name == port_name)) { + if (!port->peer_closed) { + // Treat this as immediate peer closure. It's an exceptional + // condition akin to a broken pipe, so we don't care about losing + // messages. + + port->peer_closed = true; + port->last_sequence_num_to_receive = + port->message_queue.next_sequence_num() - 1; + + if (port->state == Port::kReceiving) + ports_to_notify.push_back(PortRef(iter->first, port)); + } + + // We don't expect to forward any further messages, and we don't + // expect to receive a Port{Accepted,Rejected} event. Because we're + // a proxy with no active peer, we cannot use the normal proxy removal + // procedure of forward-propagating an ObserveProxy. Instead we + // broadcast our own death so it can be back-propagated. This is + // inefficient but rare. + if (port->state != Port::kReceiving) { + dead_proxies_to_broadcast.push_back(iter->first); + iter->second->message_queue.GetReferencedPorts( + &referenced_port_names); + } + } + } + } + + for (const auto& proxy_name : dead_proxies_to_broadcast) { + ports_.erase(proxy_name); + DVLOG(2) << "Forcibly deleted port " << proxy_name << "@" << name_; + } + } + + // Wake up any receiving ports who have just observed simulated peer closure. + for (const auto& port : ports_to_notify) + delegate_->PortStatusChanged(port); + + for (const auto& proxy_name : dead_proxies_to_broadcast) { + // Broadcast an event signifying that this proxy is no longer functioning. + ObserveProxyEventData event; + event.proxy_node_name = name_; + event.proxy_port_name = proxy_name; + event.proxy_to_node_name = kInvalidNodeName; + event.proxy_to_port_name = kInvalidPortName; + delegate_->BroadcastMessage(NewInternalMessage( + kInvalidPortName, EventType::kObserveProxy, event)); + + // Also process death locally since the port that points this closed one + // could be on the current node. + // Note: Although this is recursive, only a single port is involved which + // limits the expected branching to 1. + DestroyAllPortsWithPeer(name_, proxy_name); + } + + // Close any ports referenced by the closed proxies. + for (const auto& name : referenced_port_names) { + PortRef ref; + if (GetPort(name, &ref) == OK) + ClosePort(ref); + } +} + +ScopedMessage Node::NewInternalMessage_Helper(const PortName& port_name, + const EventType& type, + const void* data, + size_t num_data_bytes) { + ScopedMessage message; + delegate_->AllocMessage(sizeof(EventHeader) + num_data_bytes, &message); + + EventHeader* header = GetMutableEventHeader(message.get()); + header->port_name = port_name; + header->type = type; + header->padding = 0; + + if (num_data_bytes) + memcpy(header + 1, data, num_data_bytes); + + return message; +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/node.h b/mojo/edk/system/ports/node.h new file mode 100644 index 0000000..55b8d27 --- /dev/null +++ b/mojo/edk/system/ports/node.h @@ -0,0 +1,228 @@ +// 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_EDK_SYSTEM_PORTS_NODE_H_ +#define MOJO_EDK_SYSTEM_PORTS_NODE_H_ + +#include +#include + +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/ports/event.h" +#include "mojo/edk/system/ports/message.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/ports/port.h" +#include "mojo/edk/system/ports/port_ref.h" +#include "mojo/edk/system/ports/user_data.h" + +#undef SendMessage // Gah, windows + +namespace mojo { +namespace edk { +namespace ports { + +enum : int { + OK = 0, + ERROR_PORT_UNKNOWN = -10, + ERROR_PORT_EXISTS = -11, + ERROR_PORT_STATE_UNEXPECTED = -12, + ERROR_PORT_CANNOT_SEND_SELF = -13, + ERROR_PORT_PEER_CLOSED = -14, + ERROR_PORT_CANNOT_SEND_PEER = -15, + ERROR_NOT_IMPLEMENTED = -100, +}; + +struct PortStatus { + bool has_messages; + bool receiving_messages; + bool peer_closed; +}; + +class MessageFilter; +class NodeDelegate; + +class Node { + public: + enum class ShutdownPolicy { + DONT_ALLOW_LOCAL_PORTS, + ALLOW_LOCAL_PORTS, + }; + + // Does not take ownership of the delegate. + Node(const NodeName& name, NodeDelegate* delegate); + ~Node(); + + // Returns true iff there are no open ports referring to another node or ports + // in the process of being transferred from this node to another. If this + // returns false, then to ensure clean shutdown, it is necessary to keep the + // node alive and continue routing messages to it via AcceptMessage. This + // method may be called again after AcceptMessage to check if the Node is now + // ready to be destroyed. + // + // If |policy| is set to |ShutdownPolicy::ALLOW_LOCAL_PORTS|, this will return + // |true| even if some ports remain alive, as long as none of them are proxies + // to another node. + bool CanShutdownCleanly( + ShutdownPolicy policy = ShutdownPolicy::DONT_ALLOW_LOCAL_PORTS); + + // Lookup the named port. + int GetPort(const PortName& port_name, PortRef* port_ref); + + // Creates a port on this node. Before the port can be used, it must be + // initialized using InitializePort. This method is useful for bootstrapping + // a connection between two nodes. Generally, ports are created using + // CreatePortPair instead. + int CreateUninitializedPort(PortRef* port_ref); + + // Initializes a newly created port. + int InitializePort(const PortRef& port_ref, + const NodeName& peer_node_name, + const PortName& peer_port_name); + + // Generates a new connected pair of ports bound to this node. These ports + // are initialized and ready to go. + int CreatePortPair(PortRef* port0_ref, PortRef* port1_ref); + + // User data associated with the port. + int SetUserData(const PortRef& port_ref, scoped_refptr user_data); + int GetUserData(const PortRef& port_ref, + scoped_refptr* user_data); + + // Prevents further messages from being sent from this port or delivered to + // this port. The port is removed, and the port's peer is notified of the + // closure after it has consumed all pending messages. + int ClosePort(const PortRef& port_ref); + + // Returns the current status of the port. + int GetStatus(const PortRef& port_ref, PortStatus* port_status); + + // Returns the next available message on the specified port or returns a null + // message if there are none available. Returns ERROR_PORT_PEER_CLOSED to + // indicate that this port's peer has closed. In such cases GetMessage may + // be called until it yields a null message, indicating that no more messages + // may be read from the port. + // + // If |filter| is non-null, the next available message is returned only if it + // is matched by the filter. If the provided filter does not match the next + // available message, GetMessage() behaves as if there is no message + // available. Ownership of |filter| is not taken, and it must outlive the + // extent of this call. + int GetMessage(const PortRef& port_ref, + ScopedMessage* message, + MessageFilter* filter); + + // Sends a message from the specified port to its peer. Note that the message + // notification may arrive synchronously (via PortStatusChanged() on the + // delegate) if the peer is local to this Node. + int SendMessage(const PortRef& port_ref, ScopedMessage message); + + // Corresponding to NodeDelegate::ForwardMessage. + int AcceptMessage(ScopedMessage message); + + // Called to merge two ports with each other. If you have two independent + // port pairs A <=> B and C <=> D, the net result of merging B and C is a + // single connected port pair A <=> D. + // + // Note that the behavior of this operation is undefined if either port to be + // merged (B or C above) has ever been read from or written to directly, and + // this must ONLY be called on one side of the merge, though it doesn't matter + // which side. + // + // It is safe for the non-merged peers (A and D above) to be transferred, + // closed, and/or written to before, during, or after the merge. + int MergePorts(const PortRef& port_ref, + const NodeName& destination_node_name, + const PortName& destination_port_name); + + // Like above but merges two ports local to this node. Because both ports are + // local this can also verify that neither port has been written to before the + // merge. If this fails for any reason, both ports are closed. Otherwise OK + // is returned and the ports' receiving peers are connected to each other. + int MergeLocalPorts(const PortRef& port0_ref, const PortRef& port1_ref); + + // Called to inform this node that communication with another node is lost + // indefinitely. This triggers cleanup of ports bound to this node. + int LostConnectionToNode(const NodeName& node_name); + + private: + class LockedPort; + + // Note: Functions that end with _Locked require |ports_lock_| to be held + // before calling. + int OnUserMessage(ScopedMessage message); + int OnPortAccepted(const PortName& port_name); + int OnObserveProxy(const PortName& port_name, + const ObserveProxyEventData& event); + int OnObserveProxyAck(const PortName& port_name, uint64_t last_sequence_num); + int OnObserveClosure(const PortName& port_name, uint64_t last_sequence_num); + int OnMergePort(const PortName& port_name, const MergePortEventData& event); + + int AddPortWithName(const PortName& port_name, scoped_refptr port); + void ErasePort(const PortName& port_name); + void ErasePort_Locked(const PortName& port_name); + scoped_refptr GetPort(const PortName& port_name); + scoped_refptr GetPort_Locked(const PortName& port_name); + + int SendMessageInternal(const PortRef& port_ref, ScopedMessage* message); + int MergePorts_Locked(const PortRef& port0_ref, const PortRef& port1_ref); + void WillSendPort(const LockedPort& port, + const NodeName& to_node_name, + PortName* port_name, + PortDescriptor* port_descriptor); + int AcceptPort(const PortName& port_name, + const PortDescriptor& port_descriptor); + + int WillSendMessage_Locked(const LockedPort& port, + const PortName& port_name, + Message* message); + int BeginProxying_Locked(const LockedPort& port, const PortName& port_name); + int BeginProxying(PortRef port_ref); + int ForwardMessages_Locked(const LockedPort& port, const PortName& port_name); + void InitiateProxyRemoval(const LockedPort& port, const PortName& port_name); + void MaybeRemoveProxy_Locked(const LockedPort& port, + const PortName& port_name); + void TryRemoveProxy(PortRef port_ref); + void DestroyAllPortsWithPeer(const NodeName& node_name, + const PortName& port_name); + + ScopedMessage NewInternalMessage_Helper(const PortName& port_name, + const EventType& type, + const void* data, + size_t num_data_bytes); + + ScopedMessage NewInternalMessage(const PortName& port_name, + const EventType& type) { + return NewInternalMessage_Helper(port_name, type, nullptr, 0); + } + + template + ScopedMessage NewInternalMessage(const PortName& port_name, + const EventType& type, + const EventData& data) { + return NewInternalMessage_Helper(port_name, type, &data, sizeof(data)); + } + + const NodeName name_; + NodeDelegate* const delegate_; + + // Guards |ports_| as well as any operation which needs to hold multiple port + // locks simultaneously. Usage of this is subtle: it must NEVER be acquired + // after a Port lock is acquired, and it must ALWAYS be acquired before + // calling WillSendMessage_Locked or ForwardMessages_Locked. + base::Lock ports_lock_; + std::unordered_map> ports_; + + DISALLOW_COPY_AND_ASSIGN(Node); +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_NODE_H_ diff --git a/mojo/edk/system/ports/node_delegate.h b/mojo/edk/system/ports/node_delegate.h new file mode 100644 index 0000000..8547302 --- /dev/null +++ b/mojo/edk/system/ports/node_delegate.h @@ -0,0 +1,48 @@ +// 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_EDK_SYSTEM_PORTS_NODE_DELEGATE_H_ +#define MOJO_EDK_SYSTEM_PORTS_NODE_DELEGATE_H_ + +#include + +#include "mojo/edk/system/ports/message.h" +#include "mojo/edk/system/ports/name.h" +#include "mojo/edk/system/ports/port_ref.h" + +namespace mojo { +namespace edk { +namespace ports { + +class NodeDelegate { + public: + virtual ~NodeDelegate() {} + + // Port names should be difficult to guess. + virtual void GenerateRandomPortName(PortName* port_name) = 0; + + // Allocate a message, including a header that can be used by the Node + // implementation. |num_header_bytes| will be aligned. The newly allocated + // memory need not be zero-filled. + virtual void AllocMessage(size_t num_header_bytes, + ScopedMessage* message) = 0; + + // Forward a message asynchronously to the specified node. This method MUST + // NOT synchronously call any methods on Node. + virtual void ForwardMessage(const NodeName& node, ScopedMessage message) = 0; + + // Broadcast a message to all nodes. + virtual void BroadcastMessage(ScopedMessage message) = 0; + + // Indicates that the port's status has changed recently. Use Node::GetStatus + // to query the latest status of the port. Note, this event could be spurious + // if another thread is simultaneously modifying the status of the port. + virtual void PortStatusChanged(const PortRef& port_ref) = 0; +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_NODE_DELEGATE_H_ diff --git a/mojo/edk/system/ports/port.cc b/mojo/edk/system/ports/port.cc new file mode 100644 index 0000000..e4403ae --- /dev/null +++ b/mojo/edk/system/ports/port.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/edk/system/ports/port.h" + +namespace mojo { +namespace edk { +namespace ports { + +Port::Port(uint64_t next_sequence_num_to_send, + uint64_t next_sequence_num_to_receive) + : state(kUninitialized), + next_sequence_num_to_send(next_sequence_num_to_send), + last_sequence_num_to_receive(0), + message_queue(next_sequence_num_to_receive), + remove_proxy_on_last_message(false), + peer_closed(false) {} + +Port::~Port() {} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/port.h b/mojo/edk/system/ports/port.h new file mode 100644 index 0000000..ea53d43 --- /dev/null +++ b/mojo/edk/system/ports/port.h @@ -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. + +#ifndef MOJO_EDK_SYSTEM_PORTS_PORT_H_ +#define MOJO_EDK_SYSTEM_PORTS_PORT_H_ + +#include +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/ports/message_queue.h" +#include "mojo/edk/system/ports/user_data.h" + +namespace mojo { +namespace edk { +namespace ports { + +class Port : public base::RefCountedThreadSafe { + public: + enum State { + kUninitialized, + kReceiving, + kBuffering, + kProxying, + kClosed + }; + + base::Lock lock; + State state; + NodeName peer_node_name; + PortName peer_port_name; + uint64_t next_sequence_num_to_send; + uint64_t last_sequence_num_to_receive; + MessageQueue message_queue; + std::unique_ptr> send_on_proxy_removal; + scoped_refptr user_data; + bool remove_proxy_on_last_message; + bool peer_closed; + + Port(uint64_t next_sequence_num_to_send, + uint64_t next_sequence_num_to_receive); + + private: + friend class base::RefCountedThreadSafe; + + ~Port(); + + DISALLOW_COPY_AND_ASSIGN(Port); +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_PORT_H_ diff --git a/mojo/edk/system/ports/port_ref.cc b/mojo/edk/system/ports/port_ref.cc new file mode 100644 index 0000000..675754d --- /dev/null +++ b/mojo/edk/system/ports/port_ref.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/edk/system/ports/port_ref.h" + +#include "mojo/edk/system/ports/port.h" + +namespace mojo { +namespace edk { +namespace ports { + +PortRef::~PortRef() { +} + +PortRef::PortRef() { +} + +PortRef::PortRef(const PortName& name, scoped_refptr port) + : name_(name), port_(std::move(port)) {} + +PortRef::PortRef(const PortRef& other) + : name_(other.name_), port_(other.port_) { +} + +PortRef& PortRef::operator=(const PortRef& other) { + if (&other != this) { + name_ = other.name_; + port_ = other.port_; + } + return *this; +} + +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/port_ref.h b/mojo/edk/system/ports/port_ref.h new file mode 100644 index 0000000..59036c3 --- /dev/null +++ b/mojo/edk/system/ports/port_ref.h @@ -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. + +#ifndef MOJO_EDK_SYSTEM_PORTS_PORT_REF_H_ +#define MOJO_EDK_SYSTEM_PORTS_PORT_REF_H_ + +#include "base/memory/ref_counted.h" +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { +namespace ports { + +class Port; +class Node; + +class PortRef { + public: + ~PortRef(); + PortRef(); + PortRef(const PortName& name, scoped_refptr port); + + PortRef(const PortRef& other); + PortRef& operator=(const PortRef& other); + + const PortName& name() const { return name_; } + + private: + friend class Node; + Port* port() const { return port_.get(); } + + PortName name_; + scoped_refptr port_; +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_PORT_REF_H_ diff --git a/mojo/edk/system/ports/ports_unittest.cc b/mojo/edk/system/ports/ports_unittest.cc new file mode 100644 index 0000000..cb48b3e --- /dev/null +++ b/mojo/edk/system/ports/ports_unittest.cc @@ -0,0 +1,1478 @@ +// 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 +#include +#include +#include + +#include +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/rand_util.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "mojo/edk/system/ports/event.h" +#include "mojo/edk/system/ports/node.h" +#include "mojo/edk/system/ports/node_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace ports { +namespace test { + +namespace { + +bool MessageEquals(const ScopedMessage& message, const base::StringPiece& s) { + return !strcmp(static_cast(message->payload_bytes()), s.data()); +} + +class TestMessage : public Message { + public: + static ScopedMessage NewUserMessage(size_t num_payload_bytes, + size_t num_ports) { + return ScopedMessage(new TestMessage(num_payload_bytes, num_ports)); + } + + TestMessage(size_t num_payload_bytes, size_t num_ports) + : Message(num_payload_bytes, num_ports) { + start_ = new char[num_header_bytes_ + num_ports_bytes_ + num_payload_bytes]; + InitializeUserMessageHeader(start_); + } + + TestMessage(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes) + : Message(num_header_bytes, + num_payload_bytes, + num_ports_bytes) { + start_ = new char[num_header_bytes + num_payload_bytes + num_ports_bytes]; + } + + ~TestMessage() override { + delete[] start_; + } +}; + +class TestNode; + +class MessageRouter { + public: + virtual ~MessageRouter() {} + + virtual void GeneratePortName(PortName* name) = 0; + virtual void ForwardMessage(TestNode* from_node, + const NodeName& node_name, + ScopedMessage message) = 0; + virtual void BroadcastMessage(TestNode* from_node, ScopedMessage message) = 0; +}; + +class TestNode : public NodeDelegate { + public: + explicit TestNode(uint64_t id) + : node_name_(id, 1), + node_(node_name_, this), + node_thread_(base::StringPrintf("Node %" PRIu64 " thread", id)), + messages_available_event_( + base::WaitableEvent::ResetPolicy::AUTOMATIC, + base::WaitableEvent::InitialState::NOT_SIGNALED), + idle_event_( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::SIGNALED) { + } + + ~TestNode() override { + StopWhenIdle(); + node_thread_.Stop(); + } + + const NodeName& name() const { return node_name_; } + + // NOTE: Node is thread-safe. + Node& node() { return node_; } + + base::WaitableEvent& idle_event() { return idle_event_; } + + bool IsIdle() { + base::AutoLock lock(lock_); + return started_ && !dispatching_ && + (incoming_messages_.empty() || (block_on_event_ && blocked_)); + } + + void BlockOnEvent(EventType type) { + base::AutoLock lock(lock_); + blocked_event_type_ = type; + block_on_event_ = true; + } + + void Unblock() { + base::AutoLock lock(lock_); + block_on_event_ = false; + messages_available_event_.Signal(); + } + + void Start(MessageRouter* router) { + router_ = router; + node_thread_.Start(); + node_thread_.task_runner()->PostTask( + FROM_HERE, + base::Bind(&TestNode::ProcessMessages, base::Unretained(this))); + } + + void StopWhenIdle() { + base::AutoLock lock(lock_); + should_quit_ = true; + messages_available_event_.Signal(); + } + + void WakeUp() { messages_available_event_.Signal(); } + + int SendStringMessage(const PortRef& port, const std::string& s) { + size_t size = s.size() + 1; + ScopedMessage message = TestMessage::NewUserMessage(size, 0); + memcpy(message->mutable_payload_bytes(), s.data(), size); + return node_.SendMessage(port, std::move(message)); + } + + int SendStringMessageWithPort(const PortRef& port, + const std::string& s, + const PortName& sent_port_name) { + size_t size = s.size() + 1; + ScopedMessage message = TestMessage::NewUserMessage(size, 1); + memcpy(message->mutable_payload_bytes(), s.data(), size); + message->mutable_ports()[0] = sent_port_name; + return node_.SendMessage(port, std::move(message)); + } + + int SendStringMessageWithPort(const PortRef& port, + const std::string& s, + const PortRef& sent_port) { + return SendStringMessageWithPort(port, s, sent_port.name()); + } + + void set_drop_messages(bool value) { + base::AutoLock lock(lock_); + drop_messages_ = value; + } + + void set_save_messages(bool value) { + base::AutoLock lock(lock_); + save_messages_ = value; + } + + bool ReadMessage(const PortRef& port, ScopedMessage* message) { + return node_.GetMessage(port, message, nullptr) == OK && *message; + } + + bool GetSavedMessage(ScopedMessage* message) { + base::AutoLock lock(lock_); + if (saved_messages_.empty()) { + message->reset(); + return false; + } + std::swap(*message, saved_messages_.front()); + saved_messages_.pop(); + return true; + } + + void EnqueueMessage(ScopedMessage message) { + idle_event_.Reset(); + + // NOTE: This may be called from ForwardMessage and thus must not reenter + // |node_|. + base::AutoLock lock(lock_); + incoming_messages_.emplace(std::move(message)); + messages_available_event_.Signal(); + } + + void GenerateRandomPortName(PortName* port_name) override { + DCHECK(router_); + router_->GeneratePortName(port_name); + } + + void AllocMessage(size_t num_header_bytes, ScopedMessage* message) override { + message->reset(new TestMessage(num_header_bytes, 0, 0)); + } + + void ForwardMessage(const NodeName& node_name, + ScopedMessage message) override { + { + base::AutoLock lock(lock_); + if (drop_messages_) { + DVLOG(1) << "Dropping ForwardMessage from node " + << node_name_ << " to " << node_name; + + base::AutoUnlock unlock(lock_); + ClosePortsInMessage(message.get()); + return; + } + } + + DCHECK(router_); + DVLOG(1) << "ForwardMessage from node " + << node_name_ << " to " << node_name; + router_->ForwardMessage(this, node_name, std::move(message)); + } + + void BroadcastMessage(ScopedMessage message) override { + router_->BroadcastMessage(this, std::move(message)); + } + + void PortStatusChanged(const PortRef& port) override { + // The port may be closed, in which case we ignore the notification. + base::AutoLock lock(lock_); + if (!save_messages_) + return; + + for (;;) { + ScopedMessage message; + { + base::AutoUnlock unlock(lock_); + if (!ReadMessage(port, &message)) + break; + } + + saved_messages_.emplace(std::move(message)); + } + } + + void ClosePortsInMessage(Message* message) { + for (size_t i = 0; i < message->num_ports(); ++i) { + PortRef port; + ASSERT_EQ(OK, node_.GetPort(message->ports()[i], &port)); + EXPECT_EQ(OK, node_.ClosePort(port)); + } + } + + private: + void ProcessMessages() { + for (;;) { + messages_available_event_.Wait(); + + base::AutoLock lock(lock_); + + if (should_quit_) + return; + + dispatching_ = true; + while (!incoming_messages_.empty()) { + if (block_on_event_ && + GetEventHeader(*incoming_messages_.front())->type == + blocked_event_type_) { + blocked_ = true; + // Go idle if we hit a blocked event type. + break; + } else { + blocked_ = false; + } + ScopedMessage message = std::move(incoming_messages_.front()); + incoming_messages_.pop(); + + // NOTE: AcceptMessage() can re-enter this object to call any of the + // NodeDelegate interface methods. + base::AutoUnlock unlock(lock_); + node_.AcceptMessage(std::move(message)); + } + + dispatching_ = false; + started_ = true; + idle_event_.Signal(); + }; + } + + const NodeName node_name_; + Node node_; + MessageRouter* router_ = nullptr; + + base::Thread node_thread_; + base::WaitableEvent messages_available_event_; + base::WaitableEvent idle_event_; + + // Guards fields below. + base::Lock lock_; + bool started_ = false; + bool dispatching_ = false; + bool should_quit_ = false; + bool drop_messages_ = false; + bool save_messages_ = false; + bool blocked_ = false; + bool block_on_event_ = false; + EventType blocked_event_type_; + std::queue incoming_messages_; + std::queue saved_messages_; +}; + +class PortsTest : public testing::Test, public MessageRouter { + public: + void AddNode(TestNode* node) { + { + base::AutoLock lock(lock_); + nodes_[node->name()] = node; + } + node->Start(this); + } + + void RemoveNode(TestNode* node) { + { + base::AutoLock lock(lock_); + nodes_.erase(node->name()); + } + + for (const auto& entry : nodes_) + entry.second->node().LostConnectionToNode(node->name()); + } + + // Waits until all known Nodes are idle. Message forwarding and processing + // is handled in such a way that idleness is a stable state: once all nodes in + // the system are idle, they will remain idle until the test explicitly + // initiates some further event (e.g. sending a message, closing a port, or + // removing a Node). + void WaitForIdle() { + for (;;) { + base::AutoLock global_lock(global_lock_); + bool all_nodes_idle = true; + for (const auto& entry : nodes_) { + if (!entry.second->IsIdle()) + all_nodes_idle = false; + entry.second->WakeUp(); + } + if (all_nodes_idle) + return; + + // Wait for any Node to signal that it's idle. + base::AutoUnlock global_unlock(global_lock_); + std::vector events; + for (const auto& entry : nodes_) + events.push_back(&entry.second->idle_event()); + base::WaitableEvent::WaitMany(events.data(), events.size()); + } + } + + void CreatePortPair(TestNode* node0, + PortRef* port0, + TestNode* node1, + PortRef* port1) { + if (node0 == node1) { + EXPECT_EQ(OK, node0->node().CreatePortPair(port0, port1)); + } else { + EXPECT_EQ(OK, node0->node().CreateUninitializedPort(port0)); + EXPECT_EQ(OK, node1->node().CreateUninitializedPort(port1)); + EXPECT_EQ(OK, node0->node().InitializePort(*port0, node1->name(), + port1->name())); + EXPECT_EQ(OK, node1->node().InitializePort(*port1, node0->name(), + port0->name())); + } + } + + private: + // MessageRouter: + void GeneratePortName(PortName* name) override { + base::AutoLock lock(lock_); + name->v1 = next_port_id_++; + name->v2 = 0; + } + + void ForwardMessage(TestNode* from_node, + const NodeName& node_name, + ScopedMessage message) override { + base::AutoLock global_lock(global_lock_); + base::AutoLock lock(lock_); + // Drop messages from nodes that have been removed. + if (nodes_.find(from_node->name()) == nodes_.end()) { + from_node->ClosePortsInMessage(message.get()); + return; + } + + auto it = nodes_.find(node_name); + if (it == nodes_.end()) { + DVLOG(1) << "Node not found: " << node_name; + return; + } + + it->second->EnqueueMessage(std::move(message)); + } + + void BroadcastMessage(TestNode* from_node, ScopedMessage message) override { + base::AutoLock global_lock(global_lock_); + base::AutoLock lock(lock_); + + // Drop messages from nodes that have been removed. + if (nodes_.find(from_node->name()) == nodes_.end()) + return; + + for (const auto& entry : nodes_) { + TestNode* node = entry.second; + // Broadcast doesn't deliver to the local node. + if (node == from_node) + continue; + + // NOTE: We only need to support broadcast of events. Events have no + // payload or ports bytes. + ScopedMessage new_message( + new TestMessage(message->num_header_bytes(), 0, 0)); + memcpy(new_message->mutable_header_bytes(), message->header_bytes(), + message->num_header_bytes()); + node->EnqueueMessage(std::move(new_message)); + } + } + + base::MessageLoop message_loop_; + + // Acquired before any operation which makes a Node busy, and before testing + // if all nodes are idle. + base::Lock global_lock_; + + base::Lock lock_; + uint64_t next_port_id_ = 1; + std::map nodes_; +}; + +} // namespace + +TEST_F(PortsTest, Basic1) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "hello", a1)); + EXPECT_EQ(OK, node0.node().ClosePort(a0)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, Basic2) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + PortRef b0, b1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&b0, &b1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "hello", b1)); + EXPECT_EQ(OK, node0.SendStringMessage(b0, "hello again")); + + EXPECT_EQ(OK, node0.node().ClosePort(b0)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, Basic3) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "hello", a1)); + EXPECT_EQ(OK, node0.SendStringMessage(a0, "hello again")); + + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "foo", a0)); + + PortRef b0, b1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&b0, &b1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "bar", b1)); + EXPECT_EQ(OK, node0.SendStringMessage(b0, "baz")); + + EXPECT_EQ(OK, node0.node().ClosePort(b0)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, LostConnectionToNode1) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + node1.set_drop_messages(true); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + // Transfer a port to node1 and simulate a lost connection to node1. + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "foo", a1)); + + WaitForIdle(); + + RemoveNode(&node1); + + WaitForIdle(); + + EXPECT_EQ(OK, node0.node().ClosePort(a0)); + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, LostConnectionToNode2) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "take a1", a1)); + + WaitForIdle(); + + node1.set_drop_messages(true); + + RemoveNode(&node1); + + WaitForIdle(); + + // a0 should have eventually detected peer closure after node loss. + ScopedMessage message; + EXPECT_EQ(ERROR_PORT_PEER_CLOSED, + node0.node().GetMessage(a0, &message, nullptr)); + EXPECT_FALSE(message); + + EXPECT_EQ(OK, node0.node().ClosePort(a0)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + + EXPECT_EQ(OK, node1.node().GetMessage(x1, &message, nullptr)); + EXPECT_TRUE(message); + node1.ClosePortsInMessage(message.get()); + + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, LostConnectionToNodeWithSecondaryProxy) { + // Tests that a proxy gets cleaned up when its indirect peer lives on a lost + // node. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + TestNode node2(2); + AddNode(&node2); + + // Create A-B spanning nodes 0 and 1 and C-D spanning 1 and 2. + PortRef A, B, C, D; + CreatePortPair(&node0, &A, &node1, &B); + CreatePortPair(&node1, &C, &node2, &D); + + // Create E-F and send F over A to node 1. + PortRef E, F; + EXPECT_EQ(OK, node0.node().CreatePortPair(&E, &F)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(A, ".", F)); + + WaitForIdle(); + + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(B, &message)); + ASSERT_EQ(1u, message->num_ports()); + + EXPECT_EQ(OK, node1.node().GetPort(message->ports()[0], &F)); + + // Send F over C to node 2 and then simulate node 2 loss from node 1. Node 1 + // will trivially become aware of the loss, and this test verifies that the + // port A on node 0 will eventually also become aware of it. + + // Make sure node2 stops processing events when it encounters an ObserveProxy. + node2.BlockOnEvent(EventType::kObserveProxy); + + EXPECT_EQ(OK, node1.SendStringMessageWithPort(C, ".", F)); + WaitForIdle(); + + // Simulate node 1 and 2 disconnecting. + EXPECT_EQ(OK, node1.node().LostConnectionToNode(node2.name())); + + // Let node2 continue processing events and wait for everyone to go idle. + node2.Unblock(); + WaitForIdle(); + + // Port F should be gone. + EXPECT_EQ(ERROR_PORT_UNKNOWN, node1.node().GetPort(F.name(), &F)); + + // Port E should have detected peer closure despite the fact that there is + // no longer a continuous route from F to E over which the event could travel. + PortStatus status; + EXPECT_EQ(OK, node0.node().GetStatus(E, &status)); + EXPECT_TRUE(status.peer_closed); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(B)); + EXPECT_EQ(OK, node1.node().ClosePort(C)); + EXPECT_EQ(OK, node0.node().ClosePort(E)); + + WaitForIdle(); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, LostConnectionToNodeWithLocalProxy) { + // Tests that a proxy gets cleaned up when its direct peer lives on a lost + // node and it's predecessor lives on the same node. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef A, B; + CreatePortPair(&node0, &A, &node1, &B); + + PortRef C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&C, &D)); + + // Send D but block node0 on an ObserveProxy event. + node0.BlockOnEvent(EventType::kObserveProxy); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(A, ".", D)); + + // node0 won't collapse the proxy but node1 will receive the message before + // going idle. + WaitForIdle(); + + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(B, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + EXPECT_EQ(OK, node1.node().GetPort(message->ports()[0], &E)); + + RemoveNode(&node1); + + node0.Unblock(); + WaitForIdle(); + + // Port C should have detected peer closure. + PortStatus status; + EXPECT_EQ(OK, node0.node().GetStatus(C, &status)); + EXPECT_TRUE(status.peer_closed); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(B)); + EXPECT_EQ(OK, node0.node().ClosePort(C)); + EXPECT_EQ(OK, node1.node().ClosePort(E)); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, GetMessage1) { + TestNode node(0); + AddNode(&node); + + PortRef a0, a1; + EXPECT_EQ(OK, node.node().CreatePortPair(&a0, &a1)); + + ScopedMessage message; + EXPECT_EQ(OK, node.node().GetMessage(a0, &message, nullptr)); + EXPECT_FALSE(message); + + EXPECT_EQ(OK, node.node().ClosePort(a1)); + + WaitForIdle(); + + EXPECT_EQ(ERROR_PORT_PEER_CLOSED, + node.node().GetMessage(a0, &message, nullptr)); + EXPECT_FALSE(message); + + EXPECT_EQ(OK, node.node().ClosePort(a0)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, GetMessage2) { + TestNode node(0); + AddNode(&node); + + PortRef a0, a1; + EXPECT_EQ(OK, node.node().CreatePortPair(&a0, &a1)); + + EXPECT_EQ(OK, node.SendStringMessage(a1, "1")); + + ScopedMessage message; + EXPECT_EQ(OK, node.node().GetMessage(a0, &message, nullptr)); + + ASSERT_TRUE(message); + EXPECT_TRUE(MessageEquals(message, "1")); + + EXPECT_EQ(OK, node.node().ClosePort(a0)); + EXPECT_EQ(OK, node.node().ClosePort(a1)); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, GetMessage3) { + TestNode node(0); + AddNode(&node); + + PortRef a0, a1; + EXPECT_EQ(OK, node.node().CreatePortPair(&a0, &a1)); + + const char* kStrings[] = { + "1", + "2", + "3" + }; + + for (size_t i = 0; i < sizeof(kStrings)/sizeof(kStrings[0]); ++i) + EXPECT_EQ(OK, node.SendStringMessage(a1, kStrings[i])); + + ScopedMessage message; + for (size_t i = 0; i < sizeof(kStrings)/sizeof(kStrings[0]); ++i) { + EXPECT_EQ(OK, node.node().GetMessage(a0, &message, nullptr)); + ASSERT_TRUE(message); + EXPECT_TRUE(MessageEquals(message, kStrings[i])); + } + + EXPECT_EQ(OK, node.node().ClosePort(a0)); + EXPECT_EQ(OK, node.node().ClosePort(a1)); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, Delegation1) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + PortRef x0, x1; + CreatePortPair(&node0, &x0, &node1, &x1); + + // In this test, we send a message to a port that has been moved. + + PortRef a0, a1; + EXPECT_EQ(OK, node0.node().CreatePortPair(&a0, &a1)); + EXPECT_EQ(OK, node0.SendStringMessageWithPort(x0, "a1", a1)); + WaitForIdle(); + + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(x1, &message)); + ASSERT_EQ(1u, message->num_ports()); + EXPECT_TRUE(MessageEquals(message, "a1")); + + // This is "a1" from the point of view of node1. + PortName a2_name = message->ports()[0]; + EXPECT_EQ(OK, node1.SendStringMessageWithPort(x1, "a2", a2_name)); + EXPECT_EQ(OK, node0.SendStringMessage(a0, "hello")); + + WaitForIdle(); + + ASSERT_TRUE(node0.ReadMessage(x0, &message)); + ASSERT_EQ(1u, message->num_ports()); + EXPECT_TRUE(MessageEquals(message, "a2")); + + // This is "a2" from the point of view of node1. + PortName a3_name = message->ports()[0]; + + PortRef a3; + EXPECT_EQ(OK, node0.node().GetPort(a3_name, &a3)); + + ASSERT_TRUE(node0.ReadMessage(a3, &message)); + EXPECT_EQ(0u, message->num_ports()); + EXPECT_TRUE(MessageEquals(message, "hello")); + + EXPECT_EQ(OK, node0.node().ClosePort(a0)); + EXPECT_EQ(OK, node0.node().ClosePort(a3)); + + EXPECT_EQ(OK, node0.node().ClosePort(x0)); + EXPECT_EQ(OK, node1.node().ClosePort(x1)); + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, Delegation2) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + for (int i = 0; i < 100; ++i) { + // Setup pipe a<->b between node0 and node1. + PortRef A, B; + CreatePortPair(&node0, &A, &node1, &B); + + PortRef C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&C, &D)); + + PortRef E, F; + EXPECT_EQ(OK, node0.node().CreatePortPair(&E, &F)); + + node1.set_save_messages(true); + + // Pass D over A to B. + EXPECT_EQ(OK, node0.SendStringMessageWithPort(A, "1", D)); + + // Pass F over C to D. + EXPECT_EQ(OK, node0.SendStringMessageWithPort(C, "1", F)); + + // This message should find its way to node1. + EXPECT_EQ(OK, node0.SendStringMessage(E, "hello")); + + WaitForIdle(); + + EXPECT_EQ(OK, node0.node().ClosePort(C)); + EXPECT_EQ(OK, node0.node().ClosePort(E)); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(B)); + + bool got_hello = false; + ScopedMessage message; + while (node1.GetSavedMessage(&message)) { + node1.ClosePortsInMessage(message.get()); + if (MessageEquals(message, "hello")) { + got_hello = true; + break; + } + } + + EXPECT_TRUE(got_hello); + + WaitForIdle(); // Because closing ports may have generated tasks. + } + + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, SendUninitialized) { + TestNode node(0); + AddNode(&node); + + PortRef x0; + EXPECT_EQ(OK, node.node().CreateUninitializedPort(&x0)); + EXPECT_EQ(ERROR_PORT_STATE_UNEXPECTED, node.SendStringMessage(x0, "oops")); + EXPECT_EQ(OK, node.node().ClosePort(x0)); + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, SendFailure) { + TestNode node(0); + AddNode(&node); + + node.set_save_messages(true); + + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + + // Try to send A over itself. + + EXPECT_EQ(ERROR_PORT_CANNOT_SEND_SELF, + node.SendStringMessageWithPort(A, "oops", A)); + + // Try to send B over A. + + EXPECT_EQ(ERROR_PORT_CANNOT_SEND_PEER, + node.SendStringMessageWithPort(A, "nope", B)); + + // B should be closed immediately. + EXPECT_EQ(ERROR_PORT_UNKNOWN, node.node().GetPort(B.name(), &B)); + + WaitForIdle(); + + // There should have been no messages accepted. + ScopedMessage message; + EXPECT_FALSE(node.GetSavedMessage(&message)); + + EXPECT_EQ(OK, node.node().ClosePort(A)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, DontLeakUnreceivedPorts) { + TestNode node(0); + AddNode(&node); + + PortRef A, B, C, D; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node.node().CreatePortPair(&C, &D)); + + EXPECT_EQ(OK, node.SendStringMessageWithPort(A, "foo", D)); + + EXPECT_EQ(OK, node.node().ClosePort(C)); + EXPECT_EQ(OK, node.node().ClosePort(A)); + EXPECT_EQ(OK, node.node().ClosePort(B)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, AllowShutdownWithLocalPortsOpen) { + TestNode node(0); + AddNode(&node); + + PortRef A, B, C, D; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node.node().CreatePortPair(&C, &D)); + + EXPECT_EQ(OK, node.SendStringMessageWithPort(A, "foo", D)); + + ScopedMessage message; + EXPECT_TRUE(node.ReadMessage(B, &message)); + ASSERT_EQ(1u, message->num_ports()); + EXPECT_TRUE(MessageEquals(message, "foo")); + PortRef E; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &E)); + + EXPECT_TRUE( + node.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + + WaitForIdle(); + + EXPECT_TRUE( + node.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + EXPECT_FALSE(node.node().CanShutdownCleanly()); + + EXPECT_EQ(OK, node.node().ClosePort(A)); + EXPECT_EQ(OK, node.node().ClosePort(B)); + EXPECT_EQ(OK, node.node().ClosePort(C)); + EXPECT_EQ(OK, node.node().ClosePort(E)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, ProxyCollapse1) { + TestNode node(0); + AddNode(&node); + + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + + PortRef X, Y; + EXPECT_EQ(OK, node.node().CreatePortPair(&X, &Y)); + + ScopedMessage message; + + // Send B and receive it as C. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", B)); + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef C; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &C)); + + // Send C and receive it as D. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", C)); + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef D; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &D)); + + // Send D and receive it as E. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", D)); + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &E)); + + EXPECT_EQ(OK, node.node().ClosePort(X)); + EXPECT_EQ(OK, node.node().ClosePort(Y)); + + EXPECT_EQ(OK, node.node().ClosePort(A)); + EXPECT_EQ(OK, node.node().ClosePort(E)); + + // The node should not idle until all proxies are collapsed. + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, ProxyCollapse2) { + TestNode node(0); + AddNode(&node); + + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + + PortRef X, Y; + EXPECT_EQ(OK, node.node().CreatePortPair(&X, &Y)); + + ScopedMessage message; + + // Send B and A to create proxies in each direction. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", B)); + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", A)); + + EXPECT_EQ(OK, node.node().ClosePort(X)); + EXPECT_EQ(OK, node.node().ClosePort(Y)); + + // At this point we have a scenario with: + // + // D -> [B] -> C -> [A] + // + // Ensure that the proxies can collapse. The sent ports will be closed + // eventually as a result of Y's closure. + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, SendWithClosedPeer) { + // This tests that if a port is sent when its peer is already known to be + // closed, the newly created port will be aware of that peer closure, and the + // proxy will eventually collapse. + + TestNode node(0); + AddNode(&node); + + // Send a message from A to B, then close A. + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node.SendStringMessage(A, "hey")); + EXPECT_EQ(OK, node.node().ClosePort(A)); + + // Now send B over X-Y as new port C. + PortRef X, Y; + EXPECT_EQ(OK, node.node().CreatePortPair(&X, &Y)); + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", B)); + ScopedMessage message; + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef C; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &C)); + + EXPECT_EQ(OK, node.node().ClosePort(X)); + EXPECT_EQ(OK, node.node().ClosePort(Y)); + + WaitForIdle(); + + // C should have received the message originally sent to B, and it should also + // be aware of A's closure. + + ASSERT_TRUE(node.ReadMessage(C, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + PortStatus status; + EXPECT_EQ(OK, node.node().GetStatus(C, &status)); + EXPECT_FALSE(status.receiving_messages); + EXPECT_FALSE(status.has_messages); + EXPECT_TRUE(status.peer_closed); + + node.node().ClosePort(C); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, SendWithClosedPeerSent) { + // This tests that if a port is closed while some number of proxies are still + // routing messages (directly or indirectly) to it, that the peer port is + // eventually notified of the closure, and the dead-end proxies will + // eventually be removed. + + TestNode node(0); + AddNode(&node); + + PortRef X, Y; + EXPECT_EQ(OK, node.node().CreatePortPair(&X, &Y)); + + PortRef A, B; + EXPECT_EQ(OK, node.node().CreatePortPair(&A, &B)); + + ScopedMessage message; + + // Send A as new port C. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", A)); + + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef C; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &C)); + + // Send C as new port D. + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", C)); + + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef D; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &D)); + + // Send a message to B through D, then close D. + EXPECT_EQ(OK, node.SendStringMessage(D, "hey")); + EXPECT_EQ(OK, node.node().ClosePort(D)); + + // Now send B as new port E. + + EXPECT_EQ(OK, node.SendStringMessageWithPort(X, "foo", B)); + EXPECT_EQ(OK, node.node().ClosePort(X)); + + ASSERT_TRUE(node.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + ASSERT_EQ(OK, node.node().GetPort(message->ports()[0], &E)); + + EXPECT_EQ(OK, node.node().ClosePort(Y)); + + WaitForIdle(); + + // E should receive the message originally sent to B, and it should also be + // aware of D's closure. + + ASSERT_TRUE(node.ReadMessage(E, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + PortStatus status; + EXPECT_EQ(OK, node.node().GetStatus(E, &status)); + EXPECT_FALSE(status.receiving_messages); + EXPECT_FALSE(status.has_messages); + EXPECT_TRUE(status.peer_closed); + + EXPECT_EQ(OK, node.node().ClosePort(E)); + + WaitForIdle(); + + EXPECT_TRUE(node.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePorts) { + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Write a message on A. + EXPECT_EQ(OK, node0.SendStringMessage(A, "hey")); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect all proxies to be gone once idle. + EXPECT_TRUE( + node0.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + EXPECT_TRUE( + node1.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + + // Expect D to have received the message sent on A. + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(D, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + // No more ports should be open. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortWithClosedPeer1) { + // This tests that the right thing happens when initiating a merge on a port + // whose peer has already been closed. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Write a message on A. + EXPECT_EQ(OK, node0.SendStringMessage(A, "hey")); + + // Close A. + EXPECT_EQ(OK, node0.node().ClosePort(A)); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect all proxies to be gone once idle. node0 should have no ports since + // A was explicitly closed. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE( + node1.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + + // Expect D to have received the message sent on A. + ScopedMessage message; + ASSERT_TRUE(node1.ReadMessage(D, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + // No more ports should be open. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortWithClosedPeer2) { + // This tests that the right thing happens when merging into a port whose peer + // has already been closed. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Write a message on D and close it. + EXPECT_EQ(OK, node0.SendStringMessage(D, "hey")); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect all proxies to be gone once idle. node1 should have no ports since + // D was explicitly closed. + EXPECT_TRUE( + node0.node().CanShutdownCleanly(Node::ShutdownPolicy::ALLOW_LOCAL_PORTS)); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); + + // Expect A to have received the message sent on D. + ScopedMessage message; + ASSERT_TRUE(node0.ReadMessage(A, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + EXPECT_EQ(OK, node0.node().ClosePort(A)); + + // No more ports should be open. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortsWithClosedPeers) { + // This tests that no residual ports are left behind if two ports are merged + // when both of their peers have been closed. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Close A and D. + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + WaitForIdle(); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect everything to have gone away. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortsWithMovedPeers) { + // This tests that ports can be merged successfully even if their peers are + // moved around. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + // Set up another pair X-Y for moving ports on node0. + PortRef X, Y; + EXPECT_EQ(OK, node0.node().CreatePortPair(&X, &Y)); + + ScopedMessage message; + + // Move A to new port E. + EXPECT_EQ(OK, node0.SendStringMessageWithPort(X, "foo", A)); + ASSERT_TRUE(node0.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + ASSERT_EQ(OK, node0.node().GetPort(message->ports()[0], &E)); + + EXPECT_EQ(OK, node0.node().ClosePort(X)); + EXPECT_EQ(OK, node0.node().ClosePort(Y)); + + // Write messages on E and D. + EXPECT_EQ(OK, node0.SendStringMessage(E, "hey")); + EXPECT_EQ(OK, node1.SendStringMessage(D, "hi")); + + // Initiate a merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + WaitForIdle(); + + // Expect to receive D's message on E and E's message on D. + ASSERT_TRUE(node0.ReadMessage(E, &message)); + EXPECT_TRUE(MessageEquals(message, "hi")); + ASSERT_TRUE(node1.ReadMessage(D, &message)); + EXPECT_TRUE(MessageEquals(message, "hey")); + + // Close E and D. + EXPECT_EQ(OK, node0.node().ClosePort(E)); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + + WaitForIdle(); + + // Expect everything to have gone away. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +TEST_F(PortsTest, MergePortsFailsGracefully) { + // This tests that the system remains in a well-defined state if something + // goes wrong during port merge. + + TestNode node0(0); + AddNode(&node0); + + TestNode node1(1); + AddNode(&node1); + + // Setup two independent port pairs, A-B on node0 and C-D on node1. + PortRef A, B, C, D; + EXPECT_EQ(OK, node0.node().CreatePortPair(&A, &B)); + EXPECT_EQ(OK, node1.node().CreatePortPair(&C, &D)); + + ScopedMessage message; + PortRef X, Y; + EXPECT_EQ(OK, node1.node().CreatePortPair(&X, &Y)); + + // Block the merge from proceeding until we can do something stupid with port + // C. This avoids the test logic racing with async merge logic. + node1.BlockOnEvent(EventType::kMergePort); + + // Initiate the merge between B and C. + EXPECT_EQ(OK, node0.node().MergePorts(B, node1.name(), C.name())); + + // Move C to a new port E. This is not a sane use of Node's public API but + // is still hypothetically possible. It allows us to force a merge failure + // because C will be in an invalid state by the term the merge is processed. + // As a result, B should be closed. + EXPECT_EQ(OK, node1.SendStringMessageWithPort(X, "foo", C)); + + node1.Unblock(); + + ASSERT_TRUE(node1.ReadMessage(Y, &message)); + ASSERT_EQ(1u, message->num_ports()); + PortRef E; + ASSERT_EQ(OK, node1.node().GetPort(message->ports()[0], &E)); + + EXPECT_EQ(OK, node1.node().ClosePort(X)); + EXPECT_EQ(OK, node1.node().ClosePort(Y)); + + WaitForIdle(); + + // C goes away as a result of normal proxy removal. B should have been closed + // cleanly by the failed MergePorts. + EXPECT_EQ(ERROR_PORT_UNKNOWN, node1.node().GetPort(C.name(), &C)); + EXPECT_EQ(ERROR_PORT_UNKNOWN, node0.node().GetPort(B.name(), &B)); + + // Close A, D, and E. + EXPECT_EQ(OK, node0.node().ClosePort(A)); + EXPECT_EQ(OK, node1.node().ClosePort(D)); + EXPECT_EQ(OK, node1.node().ClosePort(E)); + + WaitForIdle(); + + // Expect everything to have gone away. + EXPECT_TRUE(node0.node().CanShutdownCleanly()); + EXPECT_TRUE(node1.node().CanShutdownCleanly()); +} + +} // namespace test +} // namespace ports +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports/user_data.h b/mojo/edk/system/ports/user_data.h new file mode 100644 index 0000000..73e7d17 --- /dev/null +++ b/mojo/edk/system/ports/user_data.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_EDK_SYSTEM_PORTS_USER_DATA_H_ +#define MOJO_EDK_SYSTEM_PORTS_USER_DATA_H_ + +#include "base/memory/ref_counted.h" + +namespace mojo { +namespace edk { +namespace ports { + +class UserData : public base::RefCountedThreadSafe { + protected: + friend class base::RefCountedThreadSafe; + + virtual ~UserData() {} +}; + +} // namespace ports +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_USER_DATA_H_ diff --git a/mojo/edk/system/ports_message.cc b/mojo/edk/system/ports_message.cc new file mode 100644 index 0000000..5f3e8c0 --- /dev/null +++ b/mojo/edk/system/ports_message.cc @@ -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. + +#include "mojo/edk/system/ports_message.h" + +#include "base/memory/ptr_util.h" +#include "mojo/edk/system/node_channel.h" + +namespace mojo { +namespace edk { + +// static +std::unique_ptr PortsMessage::NewUserMessage( + size_t num_payload_bytes, + size_t num_ports, + size_t num_handles) { + return base::WrapUnique( + new PortsMessage(num_payload_bytes, num_ports, num_handles)); +} + +PortsMessage::~PortsMessage() {} + +PortsMessage::PortsMessage(size_t num_payload_bytes, + size_t num_ports, + size_t num_handles) + : ports::Message(num_payload_bytes, num_ports) { + size_t size = num_header_bytes_ + num_ports_bytes_ + num_payload_bytes; + void* ptr; + channel_message_ = NodeChannel::CreatePortsMessage(size, &ptr, num_handles); + InitializeUserMessageHeader(ptr); +} + +PortsMessage::PortsMessage(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes, + Channel::MessagePtr channel_message) + : ports::Message(num_header_bytes, + num_payload_bytes, + num_ports_bytes) { + if (channel_message) { + channel_message_ = std::move(channel_message); + void* data; + size_t num_data_bytes; + NodeChannel::GetPortsMessageData(channel_message_.get(), &data, + &num_data_bytes); + start_ = static_cast(data); + } else { + // TODO: Clean this up. In practice this branch of the constructor should + // only be reached from Node-internal calls to AllocMessage, which never + // carry ports or non-header bytes. + CHECK_EQ(num_payload_bytes, 0u); + CHECK_EQ(num_ports_bytes, 0u); + void* ptr; + channel_message_ = + NodeChannel::CreatePortsMessage(num_header_bytes, &ptr, 0); + start_ = static_cast(ptr); + } +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/ports_message.h b/mojo/edk/system/ports_message.h new file mode 100644 index 0000000..542b981 --- /dev/null +++ b/mojo/edk/system/ports_message.h @@ -0,0 +1,69 @@ +// 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_EDK_SYSTEM_PORTS_MESSAGE_H__ +#define MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__ + +#include +#include + +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/system/channel.h" +#include "mojo/edk/system/ports/message.h" +#include "mojo/edk/system/ports/name.h" + +namespace mojo { +namespace edk { + +class NodeController; + +class PortsMessage : public ports::Message { + public: + static std::unique_ptr NewUserMessage(size_t num_payload_bytes, + size_t num_ports, + size_t num_handles); + + ~PortsMessage() override; + + size_t num_handles() const { return channel_message_->num_handles(); } + bool has_handles() const { return channel_message_->has_handles(); } + + void SetHandles(ScopedPlatformHandleVectorPtr handles) { + channel_message_->SetHandles(std::move(handles)); + } + + ScopedPlatformHandleVectorPtr TakeHandles() { + return channel_message_->TakeHandles(); + } + + Channel::MessagePtr TakeChannelMessage() { + return std::move(channel_message_); + } + + void set_source_node(const ports::NodeName& name) { source_node_ = name; } + const ports::NodeName& source_node() const { return source_node_; } + + private: + friend class NodeController; + + // Construct a new user PortsMessage backed by a new Channel::Message. + PortsMessage(size_t num_payload_bytes, size_t num_ports, size_t num_handles); + + // Construct a new PortsMessage backed by a Channel::Message. If + // |channel_message| is null, a new one is allocated internally. + PortsMessage(size_t num_header_bytes, + size_t num_payload_bytes, + size_t num_ports_bytes, + Channel::MessagePtr channel_message); + + Channel::MessagePtr channel_message_; + + // The node name from which this message was received, if known. + ports::NodeName source_node_ = ports::kInvalidNodeName; +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_PORTS_MESSAGE_H__ diff --git a/mojo/edk/system/request_context.cc b/mojo/edk/system/request_context.cc new file mode 100644 index 0000000..5de65d7 --- /dev/null +++ b/mojo/edk/system/request_context.cc @@ -0,0 +1,110 @@ +// 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/edk/system/request_context.h" + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/threading/thread_local.h" + +namespace mojo { +namespace edk { + +namespace { + +base::LazyInstance>::Leaky + g_current_context = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +RequestContext::RequestContext() : RequestContext(Source::LOCAL_API_CALL) {} + +RequestContext::RequestContext(Source source) + : source_(source), tls_context_(g_current_context.Pointer()) { + // We allow nested RequestContexts to exist as long as they aren't actually + // used for anything. + if (!tls_context_->Get()) + tls_context_->Set(this); +} + +RequestContext::~RequestContext() { + if (IsCurrent()) { + // NOTE: Callbacks invoked by this destructor are allowed to initiate new + // EDK requests on this thread, so we need to reset the thread-local context + // pointer before calling them. We persist the original notification source + // since we're starting over at the bottom of the stack. + tls_context_->Set(nullptr); + + MojoWatcherNotificationFlags flags = MOJO_WATCHER_NOTIFICATION_FLAG_NONE; + if (source_ == Source::SYSTEM) + flags |= MOJO_WATCHER_NOTIFICATION_FLAG_FROM_SYSTEM; + + // We send all cancellation notifications first. This is necessary because + // it's possible that cancelled watches have other pending notifications + // attached to this RequestContext. + // + // From the application's perspective the watch is cancelled as soon as this + // notification is received, and dispatching the cancellation notification + // updates some internal Watch state to ensure no further notifications + // fire. Because notifications on a single Watch are mutually exclusive, + // this is sufficient to guarantee that MOJO_RESULT_CANCELLED is the last + // notification received; which is the guarantee the API makes. + for (const scoped_refptr& watch : + watch_cancel_finalizers_.container()) { + static const HandleSignalsState closed_state = {0, 0}; + + // Establish a new RequestContext to capture and run any new notifications + // triggered by the callback invocation. + RequestContext inner_context(source_); + watch->InvokeCallback(MOJO_RESULT_CANCELLED, closed_state, flags); + } + + for (const WatchNotifyFinalizer& watch : + watch_notify_finalizers_.container()) { + RequestContext inner_context(source_); + watch.watch->InvokeCallback(watch.result, watch.state, flags); + } + } else { + // It should be impossible for nested contexts to have finalizers. + DCHECK(watch_notify_finalizers_.container().empty()); + DCHECK(watch_cancel_finalizers_.container().empty()); + } +} + +// static +RequestContext* RequestContext::current() { + DCHECK(g_current_context.Pointer()->Get()); + return g_current_context.Pointer()->Get(); +} + +void RequestContext::AddWatchNotifyFinalizer(scoped_refptr watch, + MojoResult result, + const HandleSignalsState& state) { + DCHECK(IsCurrent()); + watch_notify_finalizers_->push_back( + WatchNotifyFinalizer(std::move(watch), result, state)); +} + +void RequestContext::AddWatchCancelFinalizer(scoped_refptr watch) { + DCHECK(IsCurrent()); + watch_cancel_finalizers_->push_back(std::move(watch)); +} + +bool RequestContext::IsCurrent() const { + return tls_context_->Get() == this; +} + +RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer( + scoped_refptr watch, + MojoResult result, + const HandleSignalsState& state) + : watch(std::move(watch)), result(result), state(state) {} + +RequestContext::WatchNotifyFinalizer::WatchNotifyFinalizer( + const WatchNotifyFinalizer& other) = default; + +RequestContext::WatchNotifyFinalizer::~WatchNotifyFinalizer() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/request_context.h b/mojo/edk/system/request_context.h new file mode 100644 index 0000000..d1f43bd --- /dev/null +++ b/mojo/edk/system/request_context.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_EDK_SYSTEM_REQUEST_CONTEXT_H_ +#define MOJO_EDK_SYSTEM_REQUEST_CONTEXT_H_ + +#include "base/containers/stack_container.h" +#include "base/macros.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/edk/system/watch.h" + +namespace base { +template class ThreadLocalPointer; +} + +namespace mojo { +namespace edk { + +// A RequestContext is a thread-local object which exists for the duration of +// a single system API call. It is constructed immediately upon EDK entry and +// destructed immediately before returning to the caller, after any internal +// locks have been released. +// +// NOTE: It is legal to construct a RequestContext while another one already +// exists on the current thread, but it is not safe to use the nested context +// for any reason. Therefore it is important to always use +// |RequestContext::current()| rather than referring to any local instance +// directly. +class MOJO_SYSTEM_IMPL_EXPORT RequestContext { + public: + // Identifies the source of the current stack frame's RequestContext. + enum class Source { + LOCAL_API_CALL, + SYSTEM, + }; + + // Constructs a RequestContext with a LOCAL_API_CALL Source. + RequestContext(); + + explicit RequestContext(Source source); + ~RequestContext(); + + // Returns the current thread-local RequestContext. + static RequestContext* current(); + + Source source() const { return source_; } + + // Adds a finalizer to this RequestContext corresponding to a watch callback + // which should be triggered in response to some handle state change. If + // the WatcherDispatcher hasn't been closed by the time this RequestContext is + // destroyed, its WatchCallback will be invoked with |result| and |state| + // arguments. + void AddWatchNotifyFinalizer(scoped_refptr watch, + MojoResult result, + const HandleSignalsState& state); + + // Adds a finalizer to this RequestContext corresponding to a watch callback + // which should be triggered to notify of watch cancellation. This appends to + // a separate finalizer list from AddWatchNotifyFinalizer, as pending + // cancellations must always preempt other pending notifications. + void AddWatchCancelFinalizer(scoped_refptr watch); + + private: + // Is this request context the current one? + bool IsCurrent() const; + + struct WatchNotifyFinalizer { + WatchNotifyFinalizer(scoped_refptr watch, + MojoResult result, + const HandleSignalsState& state); + WatchNotifyFinalizer(const WatchNotifyFinalizer& other); + ~WatchNotifyFinalizer(); + + scoped_refptr watch; + MojoResult result; + HandleSignalsState state; + }; + + // NOTE: This upper bound was chosen somewhat arbitrarily after observing some + // rare worst-case behavior in Chrome. A vast majority of RequestContexts only + // ever accumulate 0 or 1 finalizers. + static const size_t kStaticWatchFinalizersCapacity = 8; + + using WatchNotifyFinalizerList = + base::StackVector; + using WatchCancelFinalizerList = + base::StackVector, kStaticWatchFinalizersCapacity>; + + const Source source_; + + WatchNotifyFinalizerList watch_notify_finalizers_; + WatchCancelFinalizerList watch_cancel_finalizers_; + + // Pointer to the TLS context. Although this can easily be accessed via the + // global LazyInstance, accessing a LazyInstance has a large cost relative to + // the rest of this class and its usages. + base::ThreadLocalPointer* tls_context_; + + DISALLOW_COPY_AND_ASSIGN(RequestContext); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_REQUEST_CONTEXT_H_ diff --git a/mojo/edk/system/shared_buffer_dispatcher.cc b/mojo/edk/system/shared_buffer_dispatcher.cc new file mode 100644 index 0000000..df39105 --- /dev/null +++ b/mojo/edk/system/shared_buffer_dispatcher.cc @@ -0,0 +1,339 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/shared_buffer_dispatcher.h" + +#include +#include + +#include +#include +#include + +#include "base/logging.h" +#include "mojo/edk/embedder/embedder_internal.h" +#include "mojo/edk/system/configuration.h" +#include "mojo/edk/system/node_controller.h" +#include "mojo/edk/system/options_validation.h" + +namespace mojo { +namespace edk { + +namespace { + +#pragma pack(push, 1) + +struct SerializedState { + uint64_t num_bytes; + uint32_t flags; + uint32_t padding; +}; + +const uint32_t kSerializedStateFlagsReadOnly = 1 << 0; + +#pragma pack(pop) + +static_assert(sizeof(SerializedState) % 8 == 0, + "Invalid SerializedState size."); + +} // namespace + +// static +const MojoCreateSharedBufferOptions + SharedBufferDispatcher::kDefaultCreateOptions = { + static_cast(sizeof(MojoCreateSharedBufferOptions)), + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE}; + +// static +MojoResult SharedBufferDispatcher::ValidateCreateOptions( + const MojoCreateSharedBufferOptions* in_options, + MojoCreateSharedBufferOptions* out_options) { + const MojoCreateSharedBufferOptionsFlags kKnownFlags = + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE; + + *out_options = kDefaultCreateOptions; + if (!in_options) + return MOJO_RESULT_OK; + + UserOptionsReader reader(in_options); + if (!reader.is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!OPTIONS_STRUCT_HAS_MEMBER(MojoCreateSharedBufferOptions, flags, reader)) + return MOJO_RESULT_OK; + if ((reader.options().flags & ~kKnownFlags)) + return MOJO_RESULT_UNIMPLEMENTED; + out_options->flags = reader.options().flags; + + // Checks for fields beyond |flags|: + + // (Nothing here yet.) + + return MOJO_RESULT_OK; +} + +// static +MojoResult SharedBufferDispatcher::Create( + const MojoCreateSharedBufferOptions& /*validated_options*/, + NodeController* node_controller, + uint64_t num_bytes, + scoped_refptr* result) { + if (!num_bytes) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_bytes > GetConfiguration().max_shared_memory_num_bytes) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + scoped_refptr shared_buffer; + if (node_controller) { + shared_buffer = + node_controller->CreateSharedBuffer(static_cast(num_bytes)); + } else { + shared_buffer = + PlatformSharedBuffer::Create(static_cast(num_bytes)); + } + if (!shared_buffer) + return MOJO_RESULT_RESOURCE_EXHAUSTED; + + *result = CreateInternal(std::move(shared_buffer)); + return MOJO_RESULT_OK; +} + +// static +MojoResult SharedBufferDispatcher::CreateFromPlatformSharedBuffer( + const scoped_refptr& shared_buffer, + scoped_refptr* result) { + if (!shared_buffer) + return MOJO_RESULT_INVALID_ARGUMENT; + + *result = CreateInternal(shared_buffer); + return MOJO_RESULT_OK; +} + +// static +scoped_refptr SharedBufferDispatcher::Deserialize( + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* platform_handles, + size_t num_platform_handles) { + if (num_bytes != sizeof(SerializedState)) { + LOG(ERROR) << "Invalid serialized shared buffer dispatcher (bad size)"; + return nullptr; + } + + const SerializedState* serialization = + static_cast(bytes); + if (!serialization->num_bytes) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (invalid num_bytes)"; + return nullptr; + } + + if (!platform_handles || num_platform_handles != 1 || num_ports) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (missing handles)"; + return nullptr; + } + + // Starts off invalid, which is what we want. + PlatformHandle platform_handle; + // We take ownership of the handle, so we have to invalidate the one in + // |platform_handles|. + std::swap(platform_handle, *platform_handles); + + // Wrapping |platform_handle| in a |ScopedPlatformHandle| means that it'll be + // closed even if creation fails. + bool read_only = (serialization->flags & kSerializedStateFlagsReadOnly); + scoped_refptr shared_buffer( + PlatformSharedBuffer::CreateFromPlatformHandle( + static_cast(serialization->num_bytes), read_only, + ScopedPlatformHandle(platform_handle))); + if (!shared_buffer) { + LOG(ERROR) + << "Invalid serialized shared buffer dispatcher (invalid num_bytes?)"; + return nullptr; + } + + return CreateInternal(std::move(shared_buffer)); +} + +scoped_refptr +SharedBufferDispatcher::PassPlatformSharedBuffer() { + base::AutoLock lock(lock_); + if (!shared_buffer_ || in_transit_) + return nullptr; + + scoped_refptr retval = shared_buffer_; + shared_buffer_ = nullptr; + return retval; +} + +Dispatcher::Type SharedBufferDispatcher::GetType() const { + return Type::SHARED_BUFFER; +} + +MojoResult SharedBufferDispatcher::Close() { + base::AutoLock lock(lock_); + if (in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + shared_buffer_ = nullptr; + return MOJO_RESULT_OK; +} + +MojoResult SharedBufferDispatcher::DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr* new_dispatcher) { + MojoDuplicateBufferHandleOptions validated_options; + MojoResult result = ValidateDuplicateOptions(options, &validated_options); + if (result != MOJO_RESULT_OK) + return result; + + // Note: Since this is "duplicate", we keep our ref to |shared_buffer_|. + base::AutoLock lock(lock_); + if (in_transit_) + return MOJO_RESULT_INVALID_ARGUMENT; + + if ((validated_options.flags & + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY) && + (!shared_buffer_->IsReadOnly())) { + // If a read-only duplicate is requested and |shared_buffer_| is not + // read-only, make a read-only duplicate of |shared_buffer_|. + scoped_refptr read_only_buffer = + shared_buffer_->CreateReadOnlyDuplicate(); + if (!read_only_buffer) + return MOJO_RESULT_FAILED_PRECONDITION; + DCHECK(read_only_buffer->IsReadOnly()); + *new_dispatcher = CreateInternal(std::move(read_only_buffer)); + return MOJO_RESULT_OK; + } + + *new_dispatcher = CreateInternal(shared_buffer_); + return MOJO_RESULT_OK; +} + +MojoResult SharedBufferDispatcher::MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + std::unique_ptr* mapping) { + if (offset > static_cast(std::numeric_limits::max())) + return MOJO_RESULT_INVALID_ARGUMENT; + if (num_bytes > static_cast(std::numeric_limits::max())) + return MOJO_RESULT_INVALID_ARGUMENT; + + base::AutoLock lock(lock_); + DCHECK(shared_buffer_); + if (in_transit_ || + !shared_buffer_->IsValidMap(static_cast(offset), + static_cast(num_bytes))) { + return MOJO_RESULT_INVALID_ARGUMENT; + } + + DCHECK(mapping); + *mapping = shared_buffer_->MapNoCheck(static_cast(offset), + static_cast(num_bytes)); + if (!*mapping) { + LOG(ERROR) << "Unable to map: read_only" << shared_buffer_->IsReadOnly(); + return MOJO_RESULT_RESOURCE_EXHAUSTED; + } + + return MOJO_RESULT_OK; +} + +void SharedBufferDispatcher::StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_platform_handles) { + *num_bytes = sizeof(SerializedState); + *num_ports = 0; + *num_platform_handles = 1; +} + +bool SharedBufferDispatcher::EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) { + SerializedState* serialization = + static_cast(destination); + base::AutoLock lock(lock_); + serialization->num_bytes = + static_cast(shared_buffer_->GetNumBytes()); + serialization->flags = + (shared_buffer_->IsReadOnly() ? kSerializedStateFlagsReadOnly : 0); + serialization->padding = 0; + + handle_for_transit_ = shared_buffer_->DuplicatePlatformHandle(); + if (!handle_for_transit_.is_valid()) { + shared_buffer_ = nullptr; + return false; + } + handles[0] = handle_for_transit_.get(); + return true; +} + +bool SharedBufferDispatcher::BeginTransit() { + base::AutoLock lock(lock_); + if (in_transit_) + return false; + in_transit_ = static_cast(shared_buffer_); + return in_transit_; +} + +void SharedBufferDispatcher::CompleteTransitAndClose() { + base::AutoLock lock(lock_); + in_transit_ = false; + shared_buffer_ = nullptr; + ignore_result(handle_for_transit_.release()); +} + +void SharedBufferDispatcher::CancelTransit() { + base::AutoLock lock(lock_); + in_transit_ = false; + handle_for_transit_.reset(); +} + +SharedBufferDispatcher::SharedBufferDispatcher( + scoped_refptr shared_buffer) + : shared_buffer_(shared_buffer) { + DCHECK(shared_buffer_); +} + +SharedBufferDispatcher::~SharedBufferDispatcher() { + DCHECK(!shared_buffer_ && !in_transit_); +} + +// static +MojoResult SharedBufferDispatcher::ValidateDuplicateOptions( + const MojoDuplicateBufferHandleOptions* in_options, + MojoDuplicateBufferHandleOptions* out_options) { + const MojoDuplicateBufferHandleOptionsFlags kKnownFlags = + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; + static const MojoDuplicateBufferHandleOptions kDefaultOptions = { + static_cast(sizeof(MojoDuplicateBufferHandleOptions)), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}; + + *out_options = kDefaultOptions; + if (!in_options) + return MOJO_RESULT_OK; + + UserOptionsReader reader(in_options); + if (!reader.is_valid()) + return MOJO_RESULT_INVALID_ARGUMENT; + + if (!OPTIONS_STRUCT_HAS_MEMBER(MojoDuplicateBufferHandleOptions, flags, + reader)) + return MOJO_RESULT_OK; + if ((reader.options().flags & ~kKnownFlags)) + return MOJO_RESULT_UNIMPLEMENTED; + out_options->flags = reader.options().flags; + + // Checks for fields beyond |flags|: + + // (Nothing here yet.) + + return MOJO_RESULT_OK; +} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/shared_buffer_dispatcher.h b/mojo/edk/system/shared_buffer_dispatcher.h new file mode 100644 index 0000000..6015595 --- /dev/null +++ b/mojo/edk/system/shared_buffer_dispatcher.h @@ -0,0 +1,127 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ + +#include +#include + +#include + +#include "base/macros.h" +#include "mojo/edk/embedder/platform_handle_vector.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/system_impl_export.h" + +namespace mojo { + +namespace edk { +class NodeController; + +class MOJO_SYSTEM_IMPL_EXPORT SharedBufferDispatcher final : public Dispatcher { + public: + // The default options to use for |MojoCreateSharedBuffer()|. (Real uses + // should obtain this via |ValidateCreateOptions()| with a null |in_options|; + // this is exposed directly for testing convenience.) + static const MojoCreateSharedBufferOptions kDefaultCreateOptions; + + // Validates and/or sets default options for |MojoCreateSharedBufferOptions|. + // If non-null, |in_options| must point to a struct of at least + // |in_options->struct_size| bytes. |out_options| must point to a (current) + // |MojoCreateSharedBufferOptions| and will be entirely overwritten on success + // (it may be partly overwritten on failure). + static MojoResult ValidateCreateOptions( + const MojoCreateSharedBufferOptions* in_options, + MojoCreateSharedBufferOptions* out_options); + + // Static factory method: |validated_options| must be validated (obviously). + // On failure, |*result| will be left as-is. + // TODO(vtl): This should probably be made to return a scoped_refptr and have + // a MojoResult out parameter instead. + static MojoResult Create( + const MojoCreateSharedBufferOptions& validated_options, + NodeController* node_controller, + uint64_t num_bytes, + scoped_refptr* result); + + // Create a |SharedBufferDispatcher| from |shared_buffer|. + static MojoResult CreateFromPlatformSharedBuffer( + const scoped_refptr& shared_buffer, + scoped_refptr* result); + + // The "opposite" of SerializeAndClose(). Called by Dispatcher::Deserialize(). + static scoped_refptr Deserialize( + const void* bytes, + size_t num_bytes, + const ports::PortName* ports, + size_t num_ports, + PlatformHandle* platform_handles, + size_t num_platform_handles); + + // Passes the underlying platform shared buffer. This dispatcher must be + // closed after calling this function. + scoped_refptr PassPlatformSharedBuffer(); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult DuplicateBufferHandle( + const MojoDuplicateBufferHandleOptions* options, + scoped_refptr* new_dispatcher) override; + MojoResult MapBuffer( + uint64_t offset, + uint64_t num_bytes, + MojoMapBufferFlags flags, + std::unique_ptr* mapping) override; + void StartSerialize(uint32_t* num_bytes, + uint32_t* num_ports, + uint32_t* num_platform_handles) override; + bool EndSerialize(void* destination, + ports::PortName* ports, + PlatformHandle* handles) override; + bool BeginTransit() override; + void CompleteTransitAndClose() override; + void CancelTransit() override; + + private: + static scoped_refptr CreateInternal( + scoped_refptr shared_buffer) { + return make_scoped_refptr( + new SharedBufferDispatcher(std::move(shared_buffer))); + } + + explicit SharedBufferDispatcher( + scoped_refptr shared_buffer); + ~SharedBufferDispatcher() override; + + // Validates and/or sets default options for + // |MojoDuplicateBufferHandleOptions|. If non-null, |in_options| must point to + // a struct of at least |in_options->struct_size| bytes. |out_options| must + // point to a (current) |MojoDuplicateBufferHandleOptions| and will be + // entirely overwritten on success (it may be partly overwritten on failure). + static MojoResult ValidateDuplicateOptions( + const MojoDuplicateBufferHandleOptions* in_options, + MojoDuplicateBufferHandleOptions* out_options); + + // Guards access to |shared_buffer_|. + base::Lock lock_; + + bool in_transit_ = false; + + // We keep a copy of the buffer's platform handle during transit so we can + // close it if something goes wrong. + ScopedPlatformHandle handle_for_transit_; + + scoped_refptr shared_buffer_; + + DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_SHARED_BUFFER_DISPATCHER_H_ diff --git a/mojo/edk/system/shared_buffer_dispatcher_unittest.cc b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc new file mode 100644 index 0000000..c95bdc3 --- /dev/null +++ b/mojo/edk/system/shared_buffer_dispatcher_unittest.cc @@ -0,0 +1,312 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/shared_buffer_dispatcher.h" + +#include +#include + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/platform_shared_buffer.h" +#include "mojo/edk/system/dispatcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +// NOTE(vtl): There's currently not much to test for in +// |SharedBufferDispatcher::ValidateCreateOptions()|, but the tests should be +// expanded if/when options are added, so I've kept the general form of the +// tests from data_pipe_unittest.cc. + +const uint32_t kSizeOfCreateOptions = sizeof(MojoCreateSharedBufferOptions); + +// Does a cursory sanity check of |validated_options|. Calls +// |ValidateCreateOptions()| on already-validated options. The validated options +// should be valid, and the revalidated copy should be the same. +void RevalidateCreateOptions( + const MojoCreateSharedBufferOptions& validated_options) { + EXPECT_EQ(kSizeOfCreateOptions, validated_options.struct_size); + // Nothing to check for flags. + + MojoCreateSharedBufferOptions revalidated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::ValidateCreateOptions( + &validated_options, &revalidated_options)); + EXPECT_EQ(validated_options.struct_size, revalidated_options.struct_size); + EXPECT_EQ(validated_options.flags, revalidated_options.flags); +} + +class SharedBufferDispatcherTest : public testing::Test { + public: + SharedBufferDispatcherTest() {} + ~SharedBufferDispatcherTest() override {} + + private: + DISALLOW_COPY_AND_ASSIGN(SharedBufferDispatcherTest); +}; + +// Tests valid inputs to |ValidateCreateOptions()|. +TEST_F(SharedBufferDispatcherTest, ValidateCreateOptionsValid) { + // Default options. + { + MojoCreateSharedBufferOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::ValidateCreateOptions( + nullptr, &validated_options)); + RevalidateCreateOptions(validated_options); + } + + // Different flags. + MojoCreateSharedBufferOptionsFlags flags_values[] = { + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE}; + for (size_t i = 0; i < arraysize(flags_values); i++) { + const MojoCreateSharedBufferOptionsFlags flags = flags_values[i]; + + // Different capacities (size 1). + for (uint32_t capacity = 1; capacity <= 100 * 1000 * 1000; capacity *= 10) { + MojoCreateSharedBufferOptions options = { + kSizeOfCreateOptions, // |struct_size|. + flags // |flags|. + }; + MojoCreateSharedBufferOptions validated_options = {}; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::ValidateCreateOptions( + &options, &validated_options)) + << capacity; + RevalidateCreateOptions(validated_options); + EXPECT_EQ(options.flags, validated_options.flags); + } + } +} + +TEST_F(SharedBufferDispatcherTest, ValidateCreateOptionsInvalid) { + // Invalid |struct_size|. + { + MojoCreateSharedBufferOptions options = { + 1, // |struct_size|. + MOJO_CREATE_SHARED_BUFFER_OPTIONS_FLAG_NONE // |flags|. + }; + MojoCreateSharedBufferOptions unused; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + SharedBufferDispatcher::ValidateCreateOptions( + &options, &unused)); + } + + // Unknown |flags|. + { + MojoCreateSharedBufferOptions options = { + kSizeOfCreateOptions, // |struct_size|. + ~0u // |flags|. + }; + MojoCreateSharedBufferOptions unused; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + SharedBufferDispatcher::ValidateCreateOptions( + &options, &unused)); + } +} + +TEST_F(SharedBufferDispatcherTest, CreateAndMapBuffer) { + scoped_refptr dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher)); + ASSERT_TRUE(dispatcher); + EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher->GetType()); + + // Make a couple of mappings. + std::unique_ptr mapping1; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer( + 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping1)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->GetBase()); + EXPECT_EQ(100u, mapping1->GetLength()); + // Write something. + static_cast(mapping1->GetBase())[50] = 'x'; + + std::unique_ptr mapping2; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer( + 50, 50, MOJO_MAP_BUFFER_FLAG_NONE, &mapping2)); + ASSERT_TRUE(mapping2); + ASSERT_TRUE(mapping2->GetBase()); + EXPECT_EQ(50u, mapping2->GetLength()); + EXPECT_EQ('x', static_cast(mapping2->GetBase())[0]); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); + + // Check that we can still read/write to mappings after the dispatcher has + // gone away. + static_cast(mapping2->GetBase())[1] = 'y'; + EXPECT_EQ('y', static_cast(mapping1->GetBase())[51]); +} + +TEST_F(SharedBufferDispatcherTest, CreateAndMapBufferFromPlatformBuffer) { + scoped_refptr platform_shared_buffer = + PlatformSharedBuffer::Create(100); + ASSERT_TRUE(platform_shared_buffer); + scoped_refptr dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, + SharedBufferDispatcher::CreateFromPlatformSharedBuffer( + platform_shared_buffer, &dispatcher)); + ASSERT_TRUE(dispatcher); + EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher->GetType()); + + // Make a couple of mappings. + std::unique_ptr mapping1; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer( + 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping1)); + ASSERT_TRUE(mapping1); + ASSERT_TRUE(mapping1->GetBase()); + EXPECT_EQ(100u, mapping1->GetLength()); + // Write something. + static_cast(mapping1->GetBase())[50] = 'x'; + + std::unique_ptr mapping2; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->MapBuffer( + 50, 50, MOJO_MAP_BUFFER_FLAG_NONE, &mapping2)); + ASSERT_TRUE(mapping2); + ASSERT_TRUE(mapping2->GetBase()); + EXPECT_EQ(50u, mapping2->GetLength()); + EXPECT_EQ('x', static_cast(mapping2->GetBase())[0]); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); + + // Check that we can still read/write to mappings after the dispatcher has + // gone away. + static_cast(mapping2->GetBase())[1] = 'y'; + EXPECT_EQ('y', static_cast(mapping1->GetBase())[51]); +} + +TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandle) { + scoped_refptr dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher1)); + + // Map and write something. + std::unique_ptr mapping; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->MapBuffer( + 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + static_cast(mapping->GetBase())[0] = 'x'; + mapping.reset(); + + // Duplicate |dispatcher1| and then close it. + scoped_refptr dispatcher2; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->DuplicateBufferHandle( + nullptr, &dispatcher2)); + ASSERT_TRUE(dispatcher2); + EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher2->GetType()); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); + + // Map |dispatcher2| and read something. + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->MapBuffer( + 0, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_EQ('x', static_cast(mapping->GetBase())[0]); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close()); +} + +TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsValid) { + scoped_refptr dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher1)); + + MojoDuplicateBufferHandleOptions options[] = { + {sizeof(MojoDuplicateBufferHandleOptions), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}, + {sizeof(MojoDuplicateBufferHandleOptions), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY}, + {sizeof(MojoDuplicateBufferHandleOptionsFlags), ~0u}}; + for (size_t i = 0; i < arraysize(options); i++) { + scoped_refptr dispatcher2; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->DuplicateBufferHandle( + &options[i], &dispatcher2)); + ASSERT_TRUE(dispatcher2); + EXPECT_EQ(Dispatcher::Type::SHARED_BUFFER, dispatcher2->GetType()); + { + std::unique_ptr mapping; + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->MapBuffer(0, 100, 0, &mapping)); + } + EXPECT_EQ(MOJO_RESULT_OK, dispatcher2->Close()); + } + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); +} + +TEST_F(SharedBufferDispatcherTest, DuplicateBufferHandleOptionsInvalid) { + scoped_refptr dispatcher1; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher1)); + + // Invalid |struct_size|. + { + MojoDuplicateBufferHandleOptions options = { + 1u, MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE}; + scoped_refptr dispatcher2; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher1->DuplicateBufferHandle(&options, &dispatcher2)); + EXPECT_FALSE(dispatcher2); + } + + // Unknown |flags|. + { + MojoDuplicateBufferHandleOptions options = { + sizeof(MojoDuplicateBufferHandleOptions), ~0u}; + scoped_refptr dispatcher2; + EXPECT_EQ(MOJO_RESULT_UNIMPLEMENTED, + dispatcher1->DuplicateBufferHandle(&options, &dispatcher2)); + EXPECT_FALSE(dispatcher2); + } + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher1->Close()); +} + +TEST_F(SharedBufferDispatcherTest, CreateInvalidNumBytes) { + // Size too big. + scoped_refptr dispatcher; + EXPECT_EQ(MOJO_RESULT_RESOURCE_EXHAUSTED, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, nullptr, + std::numeric_limits::max(), &dispatcher)); + EXPECT_FALSE(dispatcher); + + // Zero size. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, nullptr, 0, + &dispatcher)); + EXPECT_FALSE(dispatcher); +} + +TEST_F(SharedBufferDispatcherTest, MapBufferInvalidArguments) { + scoped_refptr dispatcher; + EXPECT_EQ(MOJO_RESULT_OK, SharedBufferDispatcher::Create( + SharedBufferDispatcher::kDefaultCreateOptions, + nullptr, 100, &dispatcher)); + + std::unique_ptr mapping; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(0, 101, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(1, 100, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + dispatcher->MapBuffer(0, 0, MOJO_MAP_BUFFER_FLAG_NONE, &mapping)); + EXPECT_FALSE(mapping); + + EXPECT_EQ(MOJO_RESULT_OK, dispatcher->Close()); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/shared_buffer_unittest.cc b/mojo/edk/system/shared_buffer_unittest.cc new file mode 100644 index 0000000..3a72872 --- /dev/null +++ b/mojo/edk/system/shared_buffer_unittest.cc @@ -0,0 +1,318 @@ +// 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 + +#include +#include + +#include "base/logging.h" +#include "base/memory/shared_memory.h" +#include "base/strings/string_piece.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +using SharedBufferTest = test::MojoTestBase; + +TEST_F(SharedBufferTest, CreateSharedBuffer) { + const std::string message = "hello"; + MojoHandle h = CreateBuffer(message.size()); + WriteToBuffer(h, 0, message); + ExpectBufferContents(h, 0, message); +} + +TEST_F(SharedBufferTest, DuplicateSharedBuffer) { + const std::string message = "hello"; + MojoHandle h = CreateBuffer(message.size()); + WriteToBuffer(h, 0, message); + + MojoHandle dupe = DuplicateBuffer(h, false); + ExpectBufferContents(dupe, 0, message); +} + +TEST_F(SharedBufferTest, PassSharedBufferLocal) { + const std::string message = "hello"; + MojoHandle h = CreateBuffer(message.size()); + WriteToBuffer(h, 0, message); + + MojoHandle dupe = DuplicateBuffer(h, false); + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + + WriteMessageWithHandles(p0, "...", &dupe, 1); + EXPECT_EQ("...", ReadMessageWithHandles(p1, &dupe, 1)); + + ExpectBufferContents(dupe, 0, message); +} + +#if !defined(OS_IOS) + +// Reads a single message with a shared buffer handle, maps the buffer, copies +// the message contents into it, then exits. +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CopyToBufferClient, SharedBufferTest, h) { + MojoHandle b; + std::string message = ReadMessageWithHandles(h, &b, 1); + WriteToBuffer(b, 0, message); + + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(SharedBufferTest, PassSharedBufferCrossProcess) { + const std::string message = "hello"; + MojoHandle b = CreateBuffer(message.size()); + + RUN_CHILD_ON_PIPE(CopyToBufferClient, h) + MojoHandle dupe = DuplicateBuffer(b, false); + WriteMessageWithHandles(h, message, &dupe, 1); + WriteMessage(h, "quit"); + END_CHILD() + + ExpectBufferContents(b, 0, message); +} + +// Creates a new buffer, maps it, writes a message contents to it, unmaps it, +// and finally passes it back to the parent. +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateBufferClient, SharedBufferTest, h) { + std::string message = ReadMessage(h); + MojoHandle b = CreateBuffer(message.size()); + WriteToBuffer(b, 0, message); + WriteMessageWithHandles(h, "have a buffer", &b, 1); + + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(SharedBufferTest, PassSharedBufferFromChild) { + const std::string message = "hello"; + MojoHandle b; + RUN_CHILD_ON_PIPE(CreateBufferClient, h) + WriteMessage(h, message); + ReadMessageWithHandles(h, &b, 1); + WriteMessage(h, "quit"); + END_CHILD() + + ExpectBufferContents(b, 0, message); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndPassBuffer, SharedBufferTest, h) { + // Receive a pipe handle over the primordial pipe. This will be connected to + // another child process. + MojoHandle other_child; + std::string message = ReadMessageWithHandles(h, &other_child, 1); + + // Create a new shared buffer. + MojoHandle b = CreateBuffer(message.size()); + + // Send a copy of the buffer to the parent and the other child. + MojoHandle dupe = DuplicateBuffer(b, false); + WriteMessageWithHandles(h, "", &b, 1); + WriteMessageWithHandles(other_child, "", &dupe, 1); + + EXPECT_EQ("quit", ReadMessage(h)); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveAndEditBuffer, SharedBufferTest, h) { + // Receive a pipe handle over the primordial pipe. This will be connected to + // another child process (running CreateAndPassBuffer). + MojoHandle other_child; + std::string message = ReadMessageWithHandles(h, &other_child, 1); + + // Receive a shared buffer from the other child. + MojoHandle b; + ReadMessageWithHandles(other_child, &b, 1); + + // Write the message from the parent into the buffer and exit. + WriteToBuffer(b, 0, message); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ("quit", ReadMessage(h)); +} + +TEST_F(SharedBufferTest, PassSharedBufferFromChildToChild) { + const std::string message = "hello"; + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + + MojoHandle b; + RUN_CHILD_ON_PIPE(CreateAndPassBuffer, h0) + RUN_CHILD_ON_PIPE(ReceiveAndEditBuffer, h1) + // Send one end of the pipe to each child. The first child will create + // and pass a buffer to the second child and back to us. The second child + // will write our message into the buffer. + WriteMessageWithHandles(h0, message, &p0, 1); + WriteMessageWithHandles(h1, message, &p1, 1); + + // Receive the buffer back from the first child. + ReadMessageWithHandles(h0, &b, 1); + + WriteMessage(h1, "quit"); + END_CHILD() + WriteMessage(h0, "quit"); + END_CHILD() + + // The second child should have written this message. + ExpectBufferContents(b, 0, message); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndPassBufferParent, SharedBufferTest, + parent) { + RUN_CHILD_ON_PIPE(CreateAndPassBuffer, child) + // Read a pipe from the parent and forward it to our child. + MojoHandle pipe; + std::string message = ReadMessageWithHandles(parent, &pipe, 1); + + WriteMessageWithHandles(child, message, &pipe, 1); + + // Read a buffer handle from the child and pass it back to the parent. + MojoHandle buffer; + EXPECT_EQ("", ReadMessageWithHandles(child, &buffer, 1)); + WriteMessageWithHandles(parent, "", &buffer, 1); + + EXPECT_EQ("quit", ReadMessage(parent)); + WriteMessage(child, "quit"); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReceiveAndEditBufferParent, SharedBufferTest, + parent) { + RUN_CHILD_ON_PIPE(ReceiveAndEditBuffer, child) + // Read a pipe from the parent and forward it to our child. + MojoHandle pipe; + std::string message = ReadMessageWithHandles(parent, &pipe, 1); + WriteMessageWithHandles(child, message, &pipe, 1); + + EXPECT_EQ("quit", ReadMessage(parent)); + WriteMessage(child, "quit"); + END_CHILD() +} + +#if defined(OS_ANDROID) || defined(OS_MACOSX) +// Android multi-process tests are not executing the new process. This is flaky. +// Passing shared memory handles between cousins is not currently supported on +// OSX. +#define MAYBE_PassHandleBetweenCousins DISABLED_PassHandleBetweenCousins +#else +#define MAYBE_PassHandleBetweenCousins PassHandleBetweenCousins +#endif +TEST_F(SharedBufferTest, MAYBE_PassHandleBetweenCousins) { + const std::string message = "hello"; + MojoHandle p0, p1; + CreateMessagePipe(&p0, &p1); + + // Spawn two children who will each spawn their own child. Make sure the + // grandchildren (cousins to each other) can pass platform handles. + MojoHandle b; + RUN_CHILD_ON_PIPE(CreateAndPassBufferParent, child1) + RUN_CHILD_ON_PIPE(ReceiveAndEditBufferParent, child2) + MojoHandle pipe[2]; + CreateMessagePipe(&pipe[0], &pipe[1]); + + WriteMessageWithHandles(child1, message, &pipe[0], 1); + WriteMessageWithHandles(child2, message, &pipe[1], 1); + + // Receive the buffer back from the first child. + ReadMessageWithHandles(child1, &b, 1); + + WriteMessage(child2, "quit"); + END_CHILD() + WriteMessage(child1, "quit"); + END_CHILD() + + // The second grandchild should have written this message. + ExpectBufferContents(b, 0, message); +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(ReadAndMapWriteSharedBuffer, + SharedBufferTest, h) { + // Receive the shared buffer. + MojoHandle b; + EXPECT_EQ("hello", ReadMessageWithHandles(h, &b, 1)); + + // Read from the bufer. + ExpectBufferContents(b, 0, "hello"); + + // Extract the shared memory handle and try to map it writable. + base::SharedMemoryHandle shm_handle; + bool read_only = false; + ASSERT_EQ(MOJO_RESULT_OK, + PassSharedMemoryHandle(b, &shm_handle, nullptr, &read_only)); + base::SharedMemory shared_memory(shm_handle, false); + EXPECT_TRUE(read_only); + EXPECT_FALSE(shared_memory.Map(1234)); + + EXPECT_EQ("quit", ReadMessage(h)); + WriteMessage(h, "ok"); +} + +#if defined(OS_ANDROID) +// Android multi-process tests are not executing the new process. This is flaky. +#define MAYBE_CreateAndPassReadOnlyBuffer DISABLED_CreateAndPassReadOnlyBuffer +#else +#define MAYBE_CreateAndPassReadOnlyBuffer CreateAndPassReadOnlyBuffer +#endif +TEST_F(SharedBufferTest, MAYBE_CreateAndPassReadOnlyBuffer) { + RUN_CHILD_ON_PIPE(ReadAndMapWriteSharedBuffer, h) + // Create a new shared buffer. + MojoHandle b = CreateBuffer(1234); + WriteToBuffer(b, 0, "hello"); + + // Send a read-only copy of the buffer to the child. + MojoHandle dupe = DuplicateBuffer(b, true /* read_only */); + WriteMessageWithHandles(h, "hello", &dupe, 1); + + WriteMessage(h, "quit"); + EXPECT_EQ("ok", ReadMessage(h)); + END_CHILD() +} + +DEFINE_TEST_CLIENT_TEST_WITH_PIPE(CreateAndPassReadOnlyBuffer, + SharedBufferTest, h) { + // Create a new shared buffer. + MojoHandle b = CreateBuffer(1234); + WriteToBuffer(b, 0, "hello"); + + // Send a read-only copy of the buffer to the parent. + MojoHandle dupe = DuplicateBuffer(b, true /* read_only */); + WriteMessageWithHandles(h, "", &dupe, 1); + + EXPECT_EQ("quit", ReadMessage(h)); + WriteMessage(h, "ok"); +} + +#if defined(OS_ANDROID) +// Android multi-process tests are not executing the new process. This is flaky. +#define MAYBE_CreateAndPassFromChildReadOnlyBuffer \ + DISABLED_CreateAndPassFromChildReadOnlyBuffer +#else +#define MAYBE_CreateAndPassFromChildReadOnlyBuffer \ + CreateAndPassFromChildReadOnlyBuffer +#endif +TEST_F(SharedBufferTest, MAYBE_CreateAndPassFromChildReadOnlyBuffer) { + RUN_CHILD_ON_PIPE(CreateAndPassReadOnlyBuffer, h) + MojoHandle b; + EXPECT_EQ("", ReadMessageWithHandles(h, &b, 1)); + ExpectBufferContents(b, 0, "hello"); + + // Extract the shared memory handle and try to map it writable. + base::SharedMemoryHandle shm_handle; + bool read_only = false; + ASSERT_EQ(MOJO_RESULT_OK, + PassSharedMemoryHandle(b, &shm_handle, nullptr, &read_only)); + base::SharedMemory shared_memory(shm_handle, false); + EXPECT_TRUE(read_only); + EXPECT_FALSE(shared_memory.Map(1234)); + + WriteMessage(h, "quit"); + EXPECT_EQ("ok", ReadMessage(h)); + END_CHILD() +} + +#endif // !defined(OS_IOS) + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/signals_unittest.cc b/mojo/edk/system/signals_unittest.cc new file mode 100644 index 0000000..e8b0cd1 --- /dev/null +++ b/mojo/edk/system/signals_unittest.cc @@ -0,0 +1,76 @@ +// 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/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/buffer.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/functions.h" +#include "mojo/public/c/system/message_pipe.h" +#include "mojo/public/c/system/types.h" + +namespace mojo { +namespace edk { +namespace { + +using SignalsTest = test::MojoTestBase; + +TEST_F(SignalsTest, QueryInvalidArguments) { + MojoHandleSignalsState state = {0, 0}; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoQueryHandleSignalsState(MOJO_HANDLE_INVALID, &state)); + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoQueryHandleSignalsState(a, nullptr)); +} + +TEST_F(SignalsTest, QueryMessagePipeSignals) { + MojoHandleSignalsState state = {0, 0}; + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(a, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + WriteMessage(a, "ok"); + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_READABLE)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE, + state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ("ok", ReadMessage(b)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_WRITABLE, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_WRITABLE | + MOJO_HANDLE_SIGNAL_PEER_CLOSED, + state.satisfiable_signals); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + EXPECT_EQ(MOJO_RESULT_OK, WaitForSignals(b, MOJO_HANDLE_SIGNAL_PEER_CLOSED)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(b, &state)); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfied_signals); + EXPECT_EQ(MOJO_HANDLE_SIGNAL_PEER_CLOSED, state.satisfiable_signals); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/system_impl_export.h b/mojo/edk/system/system_impl_export.h new file mode 100644 index 0000000..5bbf005 --- /dev/null +++ b/mojo/edk/system/system_impl_export.h @@ -0,0 +1,29 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_ +#define MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) +#define MOJO_SYSTEM_IMPL_EXPORT __declspec(dllexport) +#else +#define MOJO_SYSTEM_IMPL_EXPORT __declspec(dllimport) +#endif // defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(MOJO_SYSTEM_IMPL_IMPLEMENTATION) +#define MOJO_SYSTEM_IMPL_EXPORT __attribute__((visibility("default"))) +#else +#define MOJO_SYSTEM_IMPL_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define MOJO_SYSTEM_IMPL_EXPORT +#endif + +#endif // MOJO_EDK_SYSTEM_SYSTEM_IMPL_EXPORT_H_ diff --git a/mojo/edk/system/test_utils.cc b/mojo/edk/system/test_utils.cc new file mode 100644 index 0000000..4a39cf7 --- /dev/null +++ b/mojo/edk/system/test_utils.cc @@ -0,0 +1,76 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/system/test_utils.h" + +#include + +#include + +#include "base/logging.h" +#include "base/test/test_timeouts.h" +#include "base/threading/platform_thread.h" // For |Sleep()|. +#include "build/build_config.h" + +namespace mojo { +namespace edk { +namespace test { + +MojoDeadline DeadlineFromMilliseconds(unsigned milliseconds) { + return static_cast(milliseconds) * 1000; +} + +MojoDeadline EpsilonDeadline() { +// Originally, our epsilon timeout was 10 ms, which was mostly fine but flaky on +// some Windows bots. I don't recall ever seeing flakes on other bots. At 30 ms +// tests seem reliable on Windows bots, but not at 25 ms. We'd like this timeout +// to be as small as possible (see the description in the .h file). +// +// Currently, |tiny_timeout()| is usually 100 ms (possibly scaled under ASAN, +// etc.). Based on this, set it to (usually be) 30 ms on Windows and 20 ms +// elsewhere. +#if defined(OS_WIN) || defined(OS_ANDROID) + return (TinyDeadline() * 3) / 10; +#else + return (TinyDeadline() * 2) / 10; +#endif +} + +MojoDeadline TinyDeadline() { + return static_cast( + TestTimeouts::tiny_timeout().InMicroseconds()); +} + +MojoDeadline ActionDeadline() { + return static_cast( + TestTimeouts::action_timeout().InMicroseconds()); +} + +void Sleep(MojoDeadline deadline) { + CHECK_LE(deadline, + static_cast(std::numeric_limits::max())); + base::PlatformThread::Sleep( + base::TimeDelta::FromMicroseconds(static_cast(deadline))); +} + +Stopwatch::Stopwatch() { +} + +Stopwatch::~Stopwatch() { +} + +void Stopwatch::Start() { + start_time_ = base::TimeTicks::Now(); +} + +MojoDeadline Stopwatch::Elapsed() { + int64_t result = (base::TimeTicks::Now() - start_time_).InMicroseconds(); + // |DCHECK_GE|, not |CHECK_GE|, since this may be performance-important. + DCHECK_GE(result, 0); + return static_cast(result); +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/test_utils.h b/mojo/edk/system/test_utils.h new file mode 100644 index 0000000..1c90dc1 --- /dev/null +++ b/mojo/edk/system/test_utils.h @@ -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. + +#ifndef MOJO_EDK_SYSTEM_TEST_UTILS_H_ +#define MOJO_EDK_SYSTEM_TEST_UTILS_H_ + +#include "base/macros.h" +#include "base/time/time.h" +#include "mojo/public/c/system/types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace test { + +MojoDeadline DeadlineFromMilliseconds(unsigned milliseconds); + +// A timeout smaller than |TestTimeouts::tiny_timeout()|, as a |MojoDeadline|. +// Warning: This may lead to flakiness, but this is unavoidable if, e.g., you're +// trying to ensure that functions with timeouts are reasonably accurate. We +// want this to be as small as possible without causing too much flakiness. +MojoDeadline EpsilonDeadline(); + +// |TestTimeouts::tiny_timeout()|, as a |MojoDeadline|. (Expect this to be on +// the order of 100 ms.) +MojoDeadline TinyDeadline(); + +// |TestTimeouts::action_timeout()|, as a |MojoDeadline|. (Expect this to be on +// the order of 10 s.) +MojoDeadline ActionDeadline(); + +// Sleeps for at least the specified duration. +void Sleep(MojoDeadline deadline); + +// Stopwatch ------------------------------------------------------------------- + +// A simple "stopwatch" for measuring time elapsed from a given starting point. +class Stopwatch { + public: + Stopwatch(); + ~Stopwatch(); + + void Start(); + // Returns the amount of time elapsed since the last call to |Start()| (in + // microseconds). + MojoDeadline Elapsed(); + + private: + base::TimeTicks start_time_; + + DISALLOW_COPY_AND_ASSIGN(Stopwatch); +}; + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_TEST_UTILS_H_ diff --git a/mojo/edk/system/watch.cc b/mojo/edk/system/watch.cc new file mode 100644 index 0000000..cf08ac3 --- /dev/null +++ b/mojo/edk/system/watch.cc @@ -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. + +#include "mojo/edk/system/watch.h" + +#include "mojo/edk/system/request_context.h" +#include "mojo/edk/system/watcher_dispatcher.h" + +namespace mojo { +namespace edk { + +Watch::Watch(const scoped_refptr& watcher, + const scoped_refptr& dispatcher, + uintptr_t context, + MojoHandleSignals signals) + : watcher_(watcher), + dispatcher_(dispatcher), + context_(context), + signals_(signals) {} + +bool Watch::NotifyState(const HandleSignalsState& state, + bool allowed_to_call_callback) { + AssertWatcherLockAcquired(); + + // NOTE: This method must NEVER call into |dispatcher_| directly, because it + // may be called while |dispatcher_| holds a lock. + + MojoResult rv = MOJO_RESULT_SHOULD_WAIT; + RequestContext* const request_context = RequestContext::current(); + if (state.satisfies(signals_)) { + rv = MOJO_RESULT_OK; + if (allowed_to_call_callback && rv != last_known_result_) { + request_context->AddWatchNotifyFinalizer(this, MOJO_RESULT_OK, state); + } + } else if (!state.can_satisfy(signals_)) { + rv = MOJO_RESULT_FAILED_PRECONDITION; + if (allowed_to_call_callback && rv != last_known_result_) { + request_context->AddWatchNotifyFinalizer( + this, MOJO_RESULT_FAILED_PRECONDITION, state); + } + } + + last_known_signals_state_ = + *static_cast(&state); + last_known_result_ = rv; + return ready(); +} + +void Watch::Cancel() { + RequestContext::current()->AddWatchCancelFinalizer(this); +} + +void Watch::InvokeCallback(MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags) { + // We hold the lock through invocation to ensure that only one notification + // callback runs for this context at any given time. + base::AutoLock lock(notification_lock_); + if (result == MOJO_RESULT_CANCELLED) { + // Make sure cancellation is the last notification we dispatch. + DCHECK(!is_cancelled_); + is_cancelled_ = true; + } else if (is_cancelled_) { + return; + } + + // NOTE: This will acquire |watcher_|'s internal lock. It's safe because a + // thread can only enter InvokeCallback() from within a RequestContext + // destructor where no dispatcher locks are held. + watcher_->InvokeWatchCallback(context_, result, state, flags); +} + +Watch::~Watch() {} + +#if DCHECK_IS_ON() +void Watch::AssertWatcherLockAcquired() const { + watcher_->lock_.AssertAcquired(); +} +#endif + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/watch.h b/mojo/edk/system/watch.h new file mode 100644 index 0000000..f277de9 --- /dev/null +++ b/mojo/edk/system/watch.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_EDK_SYSTEM_WATCH_H_ +#define MOJO_EDK_SYSTEM_WATCH_H_ + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/atomic_flag.h" +#include "mojo/edk/system/handle_signals_state.h" + +namespace mojo { +namespace edk { + +class Dispatcher; +class WatcherDispatcher; + +// Encapsulates the state associated with a single watch context within a +// watcher. +// +// Every Watch has its own cancellation state, and is captured by RequestContext +// notification finalizers to avoid redundant context resolution during +// finalizer execution. +class Watch : public base::RefCountedThreadSafe { + public: + // Constructs a Watch which represents a watch within |watcher| associated + // with |context|, watching |dispatcher| for |signals|. + Watch(const scoped_refptr& watcher, + const scoped_refptr& dispatcher, + uintptr_t context, + MojoHandleSignals signals); + + // Notifies the Watch of a potential state change. + // + // If |allowed_to_call_callback| is true, this may add a notification + // finalizer to the current RequestContext to invoke the watcher's callback + // with this watch's context. See return values below. + // + // This is called directly by WatcherDispatcher whenever the Watch's observed + // dispatcher notifies the WatcherDispatcher of a state change. + // + // Returns |true| if the Watch entered or remains in a ready state as a result + // of the state change. If |allowed_to_call_callback| was true in this case, + // the Watch will have also attached a notification finalizer to the current + // RequestContext. + // + // Returns |false| if the + bool NotifyState(const HandleSignalsState& state, + bool allowed_to_call_callback); + + // Notifies the watch of cancellation ASAP. This will always be the last + // notification sent for the watch. + void Cancel(); + + // Finalizer method for RequestContexts. This method is invoked once for every + // notification finalizer added to a RequestContext by this object. This calls + // down into the WatcherDispatcher to do the actual notification call. + void InvokeCallback(MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags); + + const scoped_refptr& dispatcher() const { return dispatcher_; } + uintptr_t context() const { return context_; } + + MojoResult last_known_result() const { + AssertWatcherLockAcquired(); + return last_known_result_; + } + + MojoHandleSignalsState last_known_signals_state() const { + AssertWatcherLockAcquired(); + return last_known_signals_state_; + } + + bool ready() const { + AssertWatcherLockAcquired(); + return last_known_result_ == MOJO_RESULT_OK || + last_known_result_ == MOJO_RESULT_FAILED_PRECONDITION; + } + + private: + friend class base::RefCountedThreadSafe; + + ~Watch(); + +#if DCHECK_IS_ON() + void AssertWatcherLockAcquired() const; +#else + void AssertWatcherLockAcquired() const {} +#endif + + const scoped_refptr watcher_; + const scoped_refptr dispatcher_; + const uintptr_t context_; + const MojoHandleSignals signals_; + + // The result code with which this Watch would notify if currently armed, + // based on the last known signaling state of |dispatcher_|. Guarded by the + // owning WatcherDispatcher's lock. + MojoResult last_known_result_ = MOJO_RESULT_UNKNOWN; + + // The last known signaling state of |dispatcher_|. Guarded by the owning + // WatcherDispatcher's lock. + MojoHandleSignalsState last_known_signals_state_ = {0, 0}; + + // Guards |is_cancelled_| below and mutually excludes individual watch + // notification executions for this same watch context. + // + // Note that this should only be acquired from a RequestContext finalizer to + // ensure that no other internal locks are already held. + base::Lock notification_lock_; + + // Guarded by |notification_lock_|. + bool is_cancelled_ = false; + + DISALLOW_COPY_AND_ASSIGN(Watch); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_WATCH_H_ diff --git a/mojo/edk/system/watcher_dispatcher.cc b/mojo/edk/system/watcher_dispatcher.cc new file mode 100644 index 0000000..409dd2a --- /dev/null +++ b/mojo/edk/system/watcher_dispatcher.cc @@ -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. + +#include "mojo/edk/system/watcher_dispatcher.h" + +#include +#include +#include + +#include "base/macros.h" +#include "base/memory/ptr_util.h" +#include "mojo/edk/system/watch.h" + +namespace mojo { +namespace edk { + +WatcherDispatcher::WatcherDispatcher(MojoWatcherCallback callback) + : callback_(callback) {} + +void WatcherDispatcher::NotifyHandleState(Dispatcher* dispatcher, + const HandleSignalsState& state) { + base::AutoLock lock(lock_); + auto it = watched_handles_.find(dispatcher); + if (it == watched_handles_.end()) + return; + + // Maybe fire a notification to the watch assoicated with this dispatcher, + // provided we're armed it cares about the new state. + if (it->second->NotifyState(state, armed_)) { + ready_watches_.insert(it->second.get()); + + // If we were armed and got here, we notified the watch. Disarm. + armed_ = false; + } else { + ready_watches_.erase(it->second.get()); + } +} + +void WatcherDispatcher::NotifyHandleClosed(Dispatcher* dispatcher) { + scoped_refptr watch; + { + base::AutoLock lock(lock_); + auto it = watched_handles_.find(dispatcher); + if (it == watched_handles_.end()) + return; + + watch = std::move(it->second); + + // Wipe out all state associated with the closed dispatcher. + watches_.erase(watch->context()); + ready_watches_.erase(watch.get()); + watched_handles_.erase(it); + } + + // NOTE: It's important that this is called outside of |lock_| since it + // acquires internal Watch locks. + watch->Cancel(); +} + +void WatcherDispatcher::InvokeWatchCallback( + uintptr_t context, + MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags) { + { + // We avoid holding the lock during dispatch. It's OK for notification + // callbacks to close this watcher, and it's OK for notifications to race + // with closure, if for example the watcher is closed from another thread + // between this test and the invocation of |callback_| below. + // + // Because cancellation synchronously blocks all future notifications, and + // because notifications themselves are mutually exclusive for any given + // context, we still guarantee that a single MOJO_RESULT_CANCELLED result + // is the last notification received for any given context. + // + // This guarantee is sufficient to make safe, synchronized, per-context + // state management possible in user code. + base::AutoLock lock(lock_); + if (closed_ && result != MOJO_RESULT_CANCELLED) + return; + } + + callback_(context, result, static_cast(state), flags); +} + +Dispatcher::Type WatcherDispatcher::GetType() const { + return Type::WATCHER; +} + +MojoResult WatcherDispatcher::Close() { + // We swap out all the watched handle information onto the stack so we can + // call into their dispatchers without our own lock held. + std::map> watches; + { + base::AutoLock lock(lock_); + DCHECK(!closed_); + closed_ = true; + std::swap(watches, watches_); + watched_handles_.clear(); + } + + // Remove all refs from our watched dispatchers and fire cancellations. + for (auto& entry : watches) { + entry.second->dispatcher()->RemoveWatcherRef(this, entry.first); + entry.second->Cancel(); + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::WatchDispatcher( + scoped_refptr dispatcher, + MojoHandleSignals signals, + uintptr_t context) { + // NOTE: Because it's critical to avoid acquiring any other dispatcher locks + // while |lock_| is held, we defer adding oursevles to the dispatcher until + // after we've updated all our own relevant state and released |lock_|. + { + base::AutoLock lock(lock_); + if (watches_.count(context) || watched_handles_.count(dispatcher.get())) + return MOJO_RESULT_ALREADY_EXISTS; + + scoped_refptr watch = new Watch(this, dispatcher, context, signals); + watches_.insert({context, watch}); + auto result = + watched_handles_.insert(std::make_pair(dispatcher.get(), watch)); + DCHECK(result.second); + } + + MojoResult rv = dispatcher->AddWatcherRef(this, context); + if (rv != MOJO_RESULT_OK) { + // Oops. This was not a valid handle to watch. Undo the above work and + // fail gracefully. + base::AutoLock lock(lock_); + watches_.erase(context); + watched_handles_.erase(dispatcher.get()); + return rv; + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::CancelWatch(uintptr_t context) { + // We may remove the last stored ref to the Watch below, so we retain + // a reference on the stack. + scoped_refptr watch; + { + base::AutoLock lock(lock_); + auto it = watches_.find(context); + if (it == watches_.end()) + return MOJO_RESULT_NOT_FOUND; + watch = it->second; + watches_.erase(it); + } + + // Mark the watch as cancelled so no further notifications get through. + watch->Cancel(); + + // We remove the watcher ref for this context before updating any more + // internal watcher state, ensuring that we don't receiving further + // notifications for this context. + watch->dispatcher()->RemoveWatcherRef(this, context); + + { + base::AutoLock lock(lock_); + auto handle_it = watched_handles_.find(watch->dispatcher().get()); + DCHECK(handle_it != watched_handles_.end()); + ready_watches_.erase(handle_it->second.get()); + watched_handles_.erase(handle_it); + } + + return MOJO_RESULT_OK; +} + +MojoResult WatcherDispatcher::Arm( + uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) { + base::AutoLock lock(lock_); + if (num_ready_contexts && + (!ready_contexts || !ready_results || !ready_signals_states)) { + return MOJO_RESULT_INVALID_ARGUMENT; + } + + if (watched_handles_.empty()) + return MOJO_RESULT_NOT_FOUND; + + if (ready_watches_.empty()) { + // Fast path: No watches are ready to notify, so we're done. + armed_ = true; + return MOJO_RESULT_OK; + } + + if (num_ready_contexts) { + DCHECK_LE(ready_watches_.size(), std::numeric_limits::max()); + *num_ready_contexts = std::min( + *num_ready_contexts, static_cast(ready_watches_.size())); + + WatchSet::const_iterator next_ready_iter = ready_watches_.begin(); + if (last_watch_to_block_arming_) { + // Find the next watch to notify in simple round-robin order on the + // |ready_watches_| map, wrapping around to the beginning if necessary. + next_ready_iter = ready_watches_.find(last_watch_to_block_arming_); + if (next_ready_iter != ready_watches_.end()) + ++next_ready_iter; + if (next_ready_iter == ready_watches_.end()) + next_ready_iter = ready_watches_.begin(); + } + + for (size_t i = 0; i < *num_ready_contexts; ++i) { + const Watch* const watch = *next_ready_iter; + ready_contexts[i] = watch->context(); + ready_results[i] = watch->last_known_result(); + ready_signals_states[i] = watch->last_known_signals_state(); + + // Iterate and wrap around. + last_watch_to_block_arming_ = watch; + ++next_ready_iter; + if (next_ready_iter == ready_watches_.end()) + next_ready_iter = ready_watches_.begin(); + } + } + + return MOJO_RESULT_FAILED_PRECONDITION; +} + +WatcherDispatcher::~WatcherDispatcher() {} + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/watcher_dispatcher.h b/mojo/edk/system/watcher_dispatcher.h new file mode 100644 index 0000000..605a315 --- /dev/null +++ b/mojo/edk/system/watcher_dispatcher.h @@ -0,0 +1,101 @@ +// 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_EDK_SYSTEM_WATCHER_DISPATCHER_H_ +#define MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_ + +#include +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "mojo/edk/system/dispatcher.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/system_impl_export.h" +#include "mojo/public/c/system/watcher.h" + +namespace mojo { +namespace edk { + +class Watch; + +// The dispatcher type which backs watcher handles. +class WatcherDispatcher : public Dispatcher { + public: + // Constructs a new WatcherDispatcher which invokes |callback| when a + // registered watch observes some relevant state change. + explicit WatcherDispatcher(MojoWatcherCallback callback); + + // Methods used by watched dispatchers to notify watchers of events. + void NotifyHandleState(Dispatcher* dispatcher, + const HandleSignalsState& state); + void NotifyHandleClosed(Dispatcher* dispatcher); + + // Method used by RequestContext (indirectly, via Watch) to complete + // notification operations from a safe stack frame to avoid reentrancy. + void InvokeWatchCallback(uintptr_t context, + MojoResult result, + const HandleSignalsState& state, + MojoWatcherNotificationFlags flags); + + // Dispatcher: + Type GetType() const override; + MojoResult Close() override; + MojoResult WatchDispatcher(scoped_refptr dispatcher, + MojoHandleSignals signals, + uintptr_t context) override; + MojoResult CancelWatch(uintptr_t context) override; + MojoResult Arm(uint32_t* num_ready_contexts, + uintptr_t* ready_contexts, + MojoResult* ready_results, + MojoHandleSignalsState* ready_signals_states) override; + + private: + friend class Watch; + + using WatchSet = std::set; + + ~WatcherDispatcher() override; + + const MojoWatcherCallback callback_; + + // Guards access to the fields below. + // + // NOTE: This may be acquired while holding another dispatcher's lock, as + // watched dispatchers call into WatcherDispatcher methods which lock this + // when issuing state change notifications. WatcherDispatcher must therefore + // take caution to NEVER acquire other dispatcher locks while this is held. + base::Lock lock_; + + bool armed_ = false; + bool closed_ = false; + + // A mapping from context to Watch. + std::map> watches_; + + // A mapping from watched dispatcher to Watch. + std::map> watched_handles_; + + // The set of all Watch instances which are currently ready to signal. This is + // used for efficient arming behavior, as it allows for O(1) discovery of + // whether or not arming can succeed and quick determination of who's + // responsible if it can't. + WatchSet ready_watches_; + + // Tracks the last Watch whose state was returned by Arm(). This is used to + // ensure consistent round-robin behavior in the event that multiple Watches + // remain ready over the span of several Arm() attempts. + // + // NOTE: This pointer is only used to index |ready_watches_| and may point to + // an invalid object. It must therefore never be dereferenced. + const Watch* last_watch_to_block_arming_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(WatcherDispatcher); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_WATCHER_DISPATCHER_H_ diff --git a/mojo/edk/system/watcher_set.cc b/mojo/edk/system/watcher_set.cc new file mode 100644 index 0000000..0355b58 --- /dev/null +++ b/mojo/edk/system/watcher_set.cc @@ -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. + +#include "mojo/edk/system/watcher_set.h" + +#include + +namespace mojo { +namespace edk { + +WatcherSet::WatcherSet(Dispatcher* owner) : owner_(owner) {} + +WatcherSet::~WatcherSet() = default; + +void WatcherSet::NotifyState(const HandleSignalsState& state) { + // Avoid notifying watchers if they have already seen this state. + if (last_known_state_.has_value() && state.equals(last_known_state_.value())) + return; + last_known_state_ = state; + for (const auto& entry : watchers_) + entry.first->NotifyHandleState(owner_, state); +} + +void WatcherSet::NotifyClosed() { + for (const auto& entry : watchers_) + entry.first->NotifyHandleClosed(owner_); +} + +MojoResult WatcherSet::Add(const scoped_refptr& watcher, + uintptr_t context, + const HandleSignalsState& current_state) { + auto it = watchers_.find(watcher.get()); + if (it == watchers_.end()) { + auto result = + watchers_.insert(std::make_pair(watcher.get(), Entry{watcher})); + it = result.first; + } + + if (!it->second.contexts.insert(context).second) + return MOJO_RESULT_ALREADY_EXISTS; + + if (last_known_state_.has_value() && + !current_state.equals(last_known_state_.value())) { + // This new state may be relevant to everyone, in which case we just + // notify everyone. + NotifyState(current_state); + } else { + // Otherwise only notify the newly added Watcher. + watcher->NotifyHandleState(owner_, current_state); + } + return MOJO_RESULT_OK; +} + +MojoResult WatcherSet::Remove(WatcherDispatcher* watcher, uintptr_t context) { + auto it = watchers_.find(watcher); + if (it == watchers_.end()) + return MOJO_RESULT_NOT_FOUND; + + ContextSet& contexts = it->second.contexts; + auto context_it = contexts.find(context); + if (context_it == contexts.end()) + return MOJO_RESULT_NOT_FOUND; + + contexts.erase(context_it); + if (contexts.empty()) + watchers_.erase(it); + + return MOJO_RESULT_OK; +} + +WatcherSet::Entry::Entry(const scoped_refptr& dispatcher) + : dispatcher(dispatcher) {} + +WatcherSet::Entry::Entry(Entry&& other) = default; + +WatcherSet::Entry::~Entry() = default; + +WatcherSet::Entry& WatcherSet::Entry::operator=(Entry&& other) = default; + +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/system/watcher_set.h b/mojo/edk/system/watcher_set.h new file mode 100644 index 0000000..2b7ef2c --- /dev/null +++ b/mojo/edk/system/watcher_set.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_EDK_SYSTEM_WATCHER_SET_H_ +#define MOJO_EDK_SYSTEM_WATCHER_SET_H_ + +#include + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/optional.h" +#include "mojo/edk/system/handle_signals_state.h" +#include "mojo/edk/system/watcher_dispatcher.h" + +namespace mojo { +namespace edk { + +// A WatcherSet maintains a set of references to WatcherDispatchers to be +// notified when a handle changes state. +// +// Dispatchers which may be watched by a watcher should own a WatcherSet and +// notify it of all relevant state changes. +class WatcherSet { + public: + // |owner| is the Dispatcher who owns this WatcherSet. + explicit WatcherSet(Dispatcher* owner); + ~WatcherSet(); + + // Notifies all watchers of the handle's current signals state. + void NotifyState(const HandleSignalsState& state); + + // Notifies all watchers that this handle has been closed. + void NotifyClosed(); + + // Adds a new watcher+context. + MojoResult Add(const scoped_refptr& watcher, + uintptr_t context, + const HandleSignalsState& current_state); + + // Removes a watcher+context. + MojoResult Remove(WatcherDispatcher* watcher, uintptr_t context); + + private: + using ContextSet = std::set; + + struct Entry { + Entry(const scoped_refptr& dispatcher); + Entry(Entry&& other); + ~Entry(); + + Entry& operator=(Entry&& other); + + scoped_refptr dispatcher; + ContextSet contexts; + + private: + DISALLOW_COPY_AND_ASSIGN(Entry); + }; + + Dispatcher* const owner_; + std::map watchers_; + base::Optional last_known_state_; + + DISALLOW_COPY_AND_ASSIGN(WatcherSet); +}; + +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_SYSTEM_WATCHER_SET_H_ diff --git a/mojo/edk/system/watcher_unittest.cc b/mojo/edk/system/watcher_unittest.cc new file mode 100644 index 0000000..dd396cd --- /dev/null +++ b/mojo/edk/system/watcher_unittest.cc @@ -0,0 +1,1637 @@ +// 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 + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/macros.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 "base/time/time.h" +#include "mojo/edk/test/mojo_test_base.h" +#include "mojo/public/c/system/data_pipe.h" +#include "mojo/public/c/system/types.h" +#include "mojo/public/c/system/watcher.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace mojo { +namespace edk { +namespace { + +using WatcherTest = test::MojoTestBase; + +class WatchHelper { + public: + using ContextCallback = + base::Callback; + + WatchHelper() {} + ~WatchHelper() {} + + MojoResult CreateWatcher(MojoHandle* handle) { + return MojoCreateWatcher(&Notify, handle); + } + + uintptr_t CreateContext(const ContextCallback& callback) { + return CreateContextWithCancel(callback, base::Closure()); + } + + uintptr_t CreateContextWithCancel(const ContextCallback& callback, + const base::Closure& cancel_callback) { + auto context = base::MakeUnique(callback); + NotificationContext* raw_context = context.get(); + raw_context->SetCancelCallback(base::Bind( + [](std::unique_ptr context, + const base::Closure& cancel_callback) { + if (cancel_callback) + cancel_callback.Run(); + }, + base::Passed(&context), cancel_callback)); + return reinterpret_cast(raw_context); + } + + private: + class NotificationContext { + public: + explicit NotificationContext(const ContextCallback& callback) + : callback_(callback) {} + + ~NotificationContext() {} + + void SetCancelCallback(const base::Closure& cancel_callback) { + cancel_callback_ = cancel_callback; + } + + void Notify(MojoResult result, MojoHandleSignalsState state) { + if (result == MOJO_RESULT_CANCELLED) + cancel_callback_.Run(); + else + callback_.Run(result, state); + } + + private: + const ContextCallback callback_; + base::Closure cancel_callback_; + + DISALLOW_COPY_AND_ASSIGN(NotificationContext); + }; + + static void Notify(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + reinterpret_cast(context)->Notify(result, state); + } + + DISALLOW_COPY_AND_ASSIGN(WatchHelper); +}; + +class ThreadedRunner : public base::SimpleThread { + public: + explicit ThreadedRunner(const base::Closure& callback) + : SimpleThread("ThreadedRunner"), callback_(callback) {} + ~ThreadedRunner() override {} + + void Run() override { callback_.Run(); } + + private: + const base::Closure callback_; + + DISALLOW_COPY_AND_ASSIGN(ThreadedRunner); +}; + +void ExpectNoNotification(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + NOTREACHED(); +} + +void ExpectOnlyCancel(uintptr_t context, + MojoResult result, + MojoHandleSignalsState state, + MojoWatcherNotificationFlags flags) { + EXPECT_EQ(result, MOJO_RESULT_CANCELLED); +} + +TEST_F(WatcherTest, InvalidArguments) { + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoCreateWatcher(&ExpectNoNotification, nullptr)); + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + + // Try to watch unwatchable handles. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWatch(w, w, MOJO_HANDLE_SIGNAL_READABLE, 0)); + MojoHandle buffer_handle = CreateBuffer(42); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoWatch(w, buffer_handle, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + // Try to cancel a watch on an invalid watcher handle. + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, MojoCancelWatch(buffer_handle, 0)); + + // Try to arm an invalid handle. + EXPECT_EQ( + MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(MOJO_HANDLE_INVALID, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(buffer_handle, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(buffer_handle)); + + // Try to arm with a non-null count but at least one null output buffer. + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult ready_result; + MojoHandleSignalsState ready_state; + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, nullptr, &ready_result, + &ready_state)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, nullptr, + &ready_state)); + EXPECT_EQ(MOJO_RESULT_INVALID_ARGUMENT, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, + &ready_result, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchMessagePipeReadable) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |b| multiple times should notify exactly once. + WriteMessage(b, kMessage1); + WriteMessage(b, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteMessage(b, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_a_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Flush the three messages from above. + EXPECT_EQ(kMessage1, ReadMessage(a)); + EXPECT_EQ(kMessage2, ReadMessage(a)); + EXPECT_EQ(kMessage3, ReadMessage(a)); + + // Now we can rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, CloseWatchedMessagePipeHandle) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_a_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + + // Test that closing a watched handle fires an appropriate notification, even + // when the watcher is unarmed. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatchedMessagePipeHandlePeer) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + + // Test that closing a watched handle's peer with an armed watcher fires an + // appropriate notification. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + event.Wait(); + + // And now arming should fail with correct information about |a|'s state. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_a_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, WatchDataPipeConsumerReadable) { + constexpr size_t kTestPipeCapacity = 64; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t readable_consumer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |producer| multiple times should notify exactly once. + WriteData(producer, kMessage1); + WriteData(producer, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteData(producer, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_consumer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Flush the three messages from above. + EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1)); + EXPECT_EQ(kMessage2, ReadData(consumer, sizeof(kMessage2) - 1)); + EXPECT_EQ(kMessage3, ReadData(consumer, sizeof(kMessage3) - 1)); + + // Now we can rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, WatchDataPipeConsumerNewDataReadable) { + constexpr size_t kTestPipeCapacity = 64; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_new_data_notifications = 0; + const uintptr_t new_data_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* notification_count, MojoResult result, + MojoHandleSignalsState state) { + *notification_count += 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_new_data_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE, + new_data_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "hey hey hey hey"; + const char kMessage2[] = "i said hey"; + const char kMessage3[] = "what's goin' on?"; + + // Writing to |producer| multiple times should notify exactly once. + WriteData(producer, kMessage1); + WriteData(producer, kMessage2); + event.Wait(); + + // This also shouldn't fire a notification; the watcher is still disarmed. + WriteData(producer, kMessage3); + + // Arming should fail with relevant information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(new_data_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + + // Attempt to read more data than is available. Should fail but clear the + // NEW_DATA_READABLE signal. + char large_buffer[512]; + uint32_t large_read_size = 512; + EXPECT_EQ(MOJO_RESULT_OUT_OF_RANGE, + MojoReadData(consumer, large_buffer, &large_read_size, + MOJO_READ_DATA_FLAG_ALL_OR_NONE)); + + // Attempt to arm again. Should succeed. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Write more data. Should notify. + event.Reset(); + WriteData(producer, kMessage1); + event.Wait(); + + // Reading some data should clear NEW_DATA_READABLE again so we can rearm. + EXPECT_EQ(kMessage1, ReadData(consumer, sizeof(kMessage1) - 1)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(2, num_new_data_notifications); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, WatchDataPipeProducerWritable) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + // Half the capacity of the data pipe. + const char kTestData[] = "aaaa"; + static_assert((sizeof(kTestData) - 1) * 2 == kTestPipeCapacity, + "Invalid test data for this test."); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_expected_notifications = 1; + const uintptr_t writable_producer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, int* expected_count, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }, + &event, &num_expected_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + + // The producer is already writable, so arming should fail with relevant + // information. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Write some data, but don't fill the pipe yet. Arming should fail again. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Write more data, filling the pipe to capacity. Arming should succeed now. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Now read from the pipe, making the producer writable again. Should notify. + EXPECT_EQ(kTestData, ReadData(consumer, sizeof(kTestData) - 1)); + event.Wait(); + + // Arming should fail again. + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Fill the pipe once more and arm the watcher. Should succeed. + WriteData(producer, kTestData); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +}; + +TEST_F(WatcherTest, CloseWatchedDataPipeConsumerHandle) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_consumer_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + + // Closing the consumer should fire a cancellation notification. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatcherDataPipeConsumerHandlePeer) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t readable_consumer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, consumer, MOJO_HANDLE_SIGNAL_READABLE, + readable_consumer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Closing the producer should fire a notification for an unsatisfiable watch. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + event.Wait(); + + // Now attempt to rearm and expect appropriate error feedback. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(readable_consumer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); +} + +TEST_F(WatcherTest, CloseWatchedDataPipeProducerHandle) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t writable_producer_context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + + // Closing the consumer should fire a cancellation notification. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatchedDataPipeProducerHandlePeer) { + constexpr size_t kTestPipeCapacity = 8; + MojoHandle producer, consumer; + CreateDataPipe(&producer, &consumer, kTestPipeCapacity); + + const char kTestMessageFullCapacity[] = "xxxxxxxx"; + static_assert(sizeof(kTestMessageFullCapacity) - 1 == kTestPipeCapacity, + "Invalid test message size for this test."); + + // Make the pipe unwritable initially. + WriteData(producer, kTestMessageFullCapacity); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + const uintptr_t writable_producer_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, result); + event->Signal(); + }, + &event)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, producer, MOJO_HANDLE_SIGNAL_WRITABLE, + writable_producer_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Closing the consumer should fire a notification for an unsatisfiable watch, + // as the full data pipe can never be read from again and is therefore + // permanently full and unwritable. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(consumer)); + event.Wait(); + + // Now attempt to rearm and expect appropriate error feedback. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_producer_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_WRITABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(producer)); +} + +TEST_F(WatcherTest, ArmWithNoWatches) { + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchDuplicateContext) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, 0)); + EXPECT_EQ(MOJO_RESULT_ALREADY_EXISTS, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, CancelUnknownWatch) { + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectNoNotification, &w)); + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, MojoCancelWatch(w, 1234)); +} + +TEST_F(WatcherTest, ArmWithWatchAlreadySatisfied) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_WRITABLE, 0)); + + // |a| is always writable, so we can never arm this watcher. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(0u, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, ArmWithWatchAlreadyUnsatisfiable) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, 0)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + + // |b| is closed and never wrote any messages, so |a| won't be readable again. + // MojoArmWatcher() should fail, incidcating as much. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = kMaxReadyContexts; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(0u, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & + MOJO_HANDLE_SIGNAL_PEER_CLOSED); + EXPECT_FALSE(ready_states[0].satisfiable_signals & + MOJO_HANDLE_SIGNAL_READABLE); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); +} + +TEST_F(WatcherTest, MultipleWatches) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + base::WaitableEvent a_event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent b_event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + WatchHelper helper; + int num_a_notifications = 0; + int num_b_notifications = 0; + auto notify_callback = + base::Bind([](base::WaitableEvent* event, int* notification_count, + MojoResult result, MojoHandleSignalsState state) { + *notification_count += 1; + EXPECT_EQ(MOJO_RESULT_OK, result); + event->Signal(); + }); + uintptr_t readable_a_context = helper.CreateContext( + base::Bind(notify_callback, &a_event, &num_a_notifications)); + uintptr_t readable_b_context = helper.CreateContext( + base::Bind(notify_callback, &b_event, &num_b_notifications)); + + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + // Add two independent watch contexts to watch for |a| or |b| readability. + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, readable_b_context)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + const char kMessage1[] = "things are happening"; + const char kMessage2[] = "ok. ok. ok. ok."; + const char kMessage3[] = "plz wake up"; + + // Writing to |b| should signal |a|'s watch. + WriteMessage(b, kMessage1); + a_event.Wait(); + a_event.Reset(); + + // Subsequent messages on |b| should not trigger another notification. + WriteMessage(b, kMessage2); + WriteMessage(b, kMessage3); + + // Messages on |a| also shouldn't trigger |b|'s notification, since the + // watcher should be disarmed by now. + WriteMessage(a, kMessage1); + WriteMessage(a, kMessage2); + WriteMessage(a, kMessage3); + + // Arming should fail. Since we only ask for at most one context's information + // that's all we should get back. Which one we get is unspecified. + constexpr size_t kMaxReadyContexts = 10; + uint32_t num_ready_contexts = 1; + uintptr_t ready_contexts[kMaxReadyContexts]; + MojoResult ready_results[kMaxReadyContexts]; + MojoHandleSignalsState ready_states[kMaxReadyContexts]; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_TRUE(ready_contexts[0] == readable_a_context || + ready_contexts[0] == readable_b_context); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Now try arming again, verifying that both contexts are returned. + num_ready_contexts = kMaxReadyContexts; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(2u, num_ready_contexts); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[1]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_TRUE(ready_states[1].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + EXPECT_TRUE((ready_contexts[0] == readable_a_context && + ready_contexts[1] == readable_b_context) || + (ready_contexts[0] == readable_b_context && + ready_contexts[1] == readable_a_context)); + + // Flush out the test messages so we should be able to successfully rearm. + EXPECT_EQ(kMessage1, ReadMessage(a)); + EXPECT_EQ(kMessage2, ReadMessage(a)); + EXPECT_EQ(kMessage3, ReadMessage(a)); + EXPECT_EQ(kMessage1, ReadMessage(b)); + EXPECT_EQ(kMessage2, ReadMessage(b)); + EXPECT_EQ(kMessage3, ReadMessage(b)); + + // Add a watch which is always satisfied, so we can't arm. Arming should fail + // with only this new watch's information. + uintptr_t writable_c_context = helper.CreateContext(base::Bind( + [](MojoResult result, MojoHandleSignalsState state) { NOTREACHED(); })); + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_WRITABLE, writable_c_context)); + num_ready_contexts = kMaxReadyContexts; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, ready_contexts, + ready_results, ready_states)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(writable_c_context, ready_contexts[0]); + EXPECT_EQ(MOJO_RESULT_OK, ready_results[0]); + EXPECT_TRUE(ready_states[0].satisfied_signals & MOJO_HANDLE_SIGNAL_WRITABLE); + + // Cancel the new watch and arming should succeed once again. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, writable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, NotifyOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hello a"; + static const char kTestMessageToB[] = "hello b"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](MojoHandle w, MojoHandle a, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ("hello a", ReadMessage(a)); + + // Re-arm the watcher and signal |b|. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + WriteMessage(a, kTestMessageToB); + }, + w, a)); + + uintptr_t readable_b_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToB, ReadMessage(b)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + event->Signal(); + }, + &event, w, b)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Send a message to |a|. The relevant watch context should be notified, and + // should in turn send a message to |b|, waking up the other context. The + // second context signals |event|. + WriteMessage(b, kTestMessageToA); + event.Wait(); +} + +TEST_F(WatcherTest, NotifySelfFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hello a"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + int expected_notifications = 10; + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](int* expected_count, MojoHandle w, MojoHandle a, MojoHandle b, + base::WaitableEvent* event, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ("hello a", ReadMessage(a)); + + EXPECT_GT(*expected_count, 0); + *expected_count -= 1; + if (*expected_count == 0) { + event->Signal(); + return; + } else { + // Re-arm the watcher and signal |a| again. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + WriteMessage(b, kTestMessageToA); + } + }, + &expected_notifications, w, a, b, &event)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Send a message to |a|. When the watch above is notified, it will rearm and + // send another message to |a|. This will happen until + // |expected_notifications| reaches 0. + WriteMessage(b, kTestMessageToA); + event.Wait(); +} + +TEST_F(WatcherTest, ImplicitCancelOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hi a"; + static const char kTestMessageToC[] = "hi c"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind([](MojoResult result, MojoHandleSignalsState state) { + NOTREACHED(); + }), + base::Bind([](base::WaitableEvent* event) { event->Signal(); }, &event)); + + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](MojoHandle w, MojoHandle a, MojoHandle b, MojoHandle c, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + + // Now rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Must result in exactly ONE notification on the above context, for + // CANCELLED only. Because we cannot dispatch notifications until the + // stack unwinds, and because we must never dispatch non-cancellation + // notifications for a handle once it's been closed, we must be certain + // that cancellation due to closure preemptively invalidates any + // pending non-cancellation notifications queued on the current + // RequestContext, such as the one resulting from the WriteMessage here. + WriteMessage(b, kTestMessageToA); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + // Rearming should be fine since |a|'s watch should already be + // implicitly cancelled (even though the notification will not have + // been invoked yet.) + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Nothing interesting should happen as a result of this. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + }, + w, a, b, c)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_READABLE, readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, ExplicitCancelOtherFromNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hi a"; + static const char kTestMessageToC[] = "hi c"; + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + WatchHelper helper; + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](MojoResult result, MojoHandleSignalsState state) { NOTREACHED(); })); + + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, uintptr_t readable_a_context, MojoHandle w, + MojoHandle a, MojoHandle b, MojoHandle c, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + + // Now rearm the watcher. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Should result in no notifications on the above context, because the + // watch will have been cancelled by the time the notification callback + // can execute. + WriteMessage(b, kTestMessageToA); + WriteMessage(b, kTestMessageToA); + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + + // Rearming should be fine now. + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // Nothing interesting should happen as a result of these. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + + event->Signal(); + }, + &event, readable_a_context, w, a, b, c)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, c, MOJO_HANDLE_SIGNAL_READABLE, readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, NestedCancellation) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle c, d; + CreateMessagePipe(&c, &d); + + static const char kTestMessageToA[] = "hey a"; + static const char kTestMessageToC[] = "hey c"; + static const char kTestMessageToD[] = "hey d"; + + // This is a tricky test. It establishes a watch on |b| using one watcher and + // watches on |c| and |d| using another watcher. + // + // A message is written to |d| to wake up |c|'s watch, and the notification + // handler for that event does the following: + // 1. Writes to |a| to eventually wake up |b|'s watcher. + // 2. Rearms |c|'s watcher. + // 3. Writes to |d| to eventually wake up |c|'s watcher again. + // + // Meanwhile, |b|'s watch notification handler cancels |c|'s watch altogether + // before writing to |c| to wake up |d|. + // + // The net result should be that |c|'s context only gets notified once (from + // the first write to |d| above) and everyone else gets notified as expected. + + MojoHandle b_watcher; + MojoHandle cd_watcher; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&b_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&cd_watcher)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + uintptr_t readable_d_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle d, MojoResult result, + MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToD, ReadMessage(d)); + event->Signal(); + }, + &event, d)); + + static int num_expected_c_notifications = 1; + uintptr_t readable_c_context = helper.CreateContext(base::Bind( + [](MojoHandle cd_watcher, MojoHandle a, MojoHandle c, MojoHandle d, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_GT(num_expected_c_notifications--, 0); + + // Trigger an eventual |readable_b_context| notification. + WriteMessage(a, kTestMessageToA); + + EXPECT_EQ(kTestMessageToC, ReadMessage(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoArmWatcher(cd_watcher, nullptr, nullptr, + nullptr, nullptr)); + + // Trigger another eventual |readable_c_context| notification. + WriteMessage(d, kTestMessageToC); + }, + cd_watcher, a, c, d)); + + uintptr_t readable_b_context = helper.CreateContext(base::Bind( + [](MojoHandle cd_watcher, uintptr_t readable_c_context, MojoHandle c, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(cd_watcher, readable_c_context)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoArmWatcher(cd_watcher, nullptr, nullptr, + nullptr, nullptr)); + + WriteMessage(c, kTestMessageToD); + }, + cd_watcher, readable_c_context, c)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, + readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(cd_watcher, c, MOJO_HANDLE_SIGNAL_READABLE, + readable_c_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(cd_watcher, d, MOJO_HANDLE_SIGNAL_READABLE, + readable_d_context)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(b_watcher, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(cd_watcher, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(d, kTestMessageToC); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(cd_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(c)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(d)); +} + +TEST_F(WatcherTest, CancelSelfInNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + static uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + + // There should be no problem cancelling this watch from its own + // notification invocation. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + + // Arming should fail because there are no longer any registered + // watches on the watcher. + EXPECT_EQ(MOJO_RESULT_NOT_FOUND, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // And closing |a| should be fine (and should not invoke this + // notification with MOJO_RESULT_CANCELLED) for the same reason. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + event->Signal(); + }, + &event, w, a)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, CloseWatcherInNotificationCallback) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA1[] = "hey a"; + static const char kTestMessageToA2[] = "hey a again"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA1, ReadMessage(a)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // There should be no problem closing this watcher from its own + // notification callback. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + + // And these should not trigger more notifications, because |w| has been + // closed already. + WriteMessage(b, kTestMessageToA2); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + event->Signal(); + }, + &event, w, a, b)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA1); + event.Wait(); +} + +TEST_F(WatcherTest, CloseWatcherAfterImplicitCancel) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + uintptr_t readable_a_context = helper.CreateContext(base::Bind( + [](base::WaitableEvent* event, MojoHandle w, MojoHandle a, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + // This will cue up a notification for |MOJO_RESULT_CANCELLED|... + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + + // ...but it should never fire because we close the watcher here. + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + + event->Signal(); + }, + &event, w, a)); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, OtherThreadCancelDuringNotification) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent wait_for_notification( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + base::WaitableEvent wait_for_cancellation( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + static bool callback_done = false; + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_notification, MojoHandle w, + MojoHandle a, MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + + wait_for_notification->Signal(); + + // Give the other thread sufficient time to race with the completion + // of this callback. There should be no race, since the cancellation + // notification must be mutually exclusive to this notification. + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); + + callback_done = true; + }, + &wait_for_notification, w, a), + base::Bind( + [](base::WaitableEvent* wait_for_cancellation) { + EXPECT_TRUE(callback_done); + wait_for_cancellation->Signal(); + }, + &wait_for_cancellation)); + + ThreadedRunner runner(base::Bind( + [](base::WaitableEvent* wait_for_notification, + base::WaitableEvent* wait_for_cancellation, MojoHandle w, + uintptr_t readable_a_context) { + wait_for_notification->Wait(); + + // Cancel the watch while the notification is still running. + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, readable_a_context)); + + wait_for_cancellation->Wait(); + + EXPECT_TRUE(callback_done); + }, + &wait_for_notification, &wait_for_cancellation, w, readable_a_context)); + runner.Start(); + + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(w, nullptr, nullptr, nullptr, nullptr)); + + WriteMessage(b, kTestMessageToA); + runner.Join(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +TEST_F(WatcherTest, WatchesCancelEachOtherFromNotifications) { + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + static const char kTestMessageToA[] = "hey a"; + static const char kTestMessageToB[] = "hey b"; + + base::WaitableEvent wait_for_a_to_notify( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_b_to_notify( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_a_to_cancel( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + base::WaitableEvent wait_for_b_to_cancel( + base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + + MojoHandle a_watcher; + MojoHandle b_watcher; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&a_watcher)); + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&b_watcher)); + + // We set up two watchers, one on |a| and one on |b|. They cancel each other + // from within their respective watch notifications. This should be safe, + // i.e., it should not deadlock, in spite of the fact that we also guarantee + // mutually exclusive notification execution (including cancellations) on any + // given watch. + bool a_cancelled = false; + bool b_cancelled = false; + static uintptr_t readable_b_context; + uintptr_t readable_a_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_a_to_notify, + base::WaitableEvent* wait_for_b_to_notify, MojoHandle b_watcher, + MojoHandle a, MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToA, ReadMessage(a)); + wait_for_a_to_notify->Signal(); + wait_for_b_to_notify->Wait(); + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(b_watcher, readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b_watcher)); + }, + &wait_for_a_to_notify, &wait_for_b_to_notify, b_watcher, a), + base::Bind( + [](base::WaitableEvent* wait_for_a_to_cancel, + base::WaitableEvent* wait_for_b_to_cancel, bool* a_cancelled) { + *a_cancelled = true; + wait_for_a_to_cancel->Signal(); + wait_for_b_to_cancel->Wait(); + }, + &wait_for_a_to_cancel, &wait_for_b_to_cancel, &a_cancelled)); + + readable_b_context = helper.CreateContextWithCancel( + base::Bind( + [](base::WaitableEvent* wait_for_a_to_notify, + base::WaitableEvent* wait_for_b_to_notify, + uintptr_t readable_a_context, MojoHandle a_watcher, MojoHandle b, + MojoResult result, MojoHandleSignalsState state) { + EXPECT_EQ(MOJO_RESULT_OK, result); + EXPECT_EQ(kTestMessageToB, ReadMessage(b)); + wait_for_b_to_notify->Signal(); + wait_for_a_to_notify->Wait(); + EXPECT_EQ(MOJO_RESULT_OK, + MojoCancelWatch(a_watcher, readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a_watcher)); + }, + &wait_for_a_to_notify, &wait_for_b_to_notify, readable_a_context, + a_watcher, b), + base::Bind( + [](base::WaitableEvent* wait_for_a_to_cancel, + base::WaitableEvent* wait_for_b_to_cancel, bool* b_cancelled) { + *b_cancelled = true; + wait_for_b_to_cancel->Signal(); + wait_for_a_to_cancel->Wait(); + }, + &wait_for_a_to_cancel, &wait_for_b_to_cancel, &b_cancelled)); + + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(a_watcher, a, MOJO_HANDLE_SIGNAL_READABLE, + readable_a_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(a_watcher, nullptr, nullptr, nullptr, nullptr)); + EXPECT_EQ(MOJO_RESULT_OK, MojoWatch(b_watcher, b, MOJO_HANDLE_SIGNAL_READABLE, + readable_b_context)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoArmWatcher(b_watcher, nullptr, nullptr, nullptr, nullptr)); + + ThreadedRunner runner( + base::Bind([](MojoHandle b) { WriteMessage(b, kTestMessageToA); }, b)); + runner.Start(); + + WriteMessage(a, kTestMessageToB); + + wait_for_a_to_cancel.Wait(); + wait_for_b_to_cancel.Wait(); + runner.Join(); + + EXPECT_TRUE(a_cancelled); + EXPECT_TRUE(b_cancelled); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, AlwaysCancel) { + // Basic sanity check to ensure that all possible ways to cancel a watch + // result in a final MOJO_RESULT_CANCELLED notification. + + MojoHandle a, b; + CreateMessagePipe(&a, &b); + + MojoHandle w; + WatchHelper helper; + EXPECT_EQ(MOJO_RESULT_OK, helper.CreateWatcher(&w)); + + base::WaitableEvent event(base::WaitableEvent::ResetPolicy::MANUAL, + base::WaitableEvent::InitialState::NOT_SIGNALED); + const base::Closure signal_event = + base::Bind(&base::WaitableEvent::Signal, base::Unretained(&event)); + + // Cancel via |MojoCancelWatch()|. + uintptr_t context = helper.CreateContextWithCancel( + WatchHelper::ContextCallback(), signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoCancelWatch(w, context)); + event.Wait(); + event.Reset(); + + // Cancel by closing the watched handle. + context = helper.CreateContextWithCancel(WatchHelper::ContextCallback(), + signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, a, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(a)); + event.Wait(); + event.Reset(); + + // Cancel by closing the watcher handle. + context = helper.CreateContextWithCancel(WatchHelper::ContextCallback(), + signal_event); + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, b, MOJO_HANDLE_SIGNAL_READABLE, context)); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); + event.Wait(); + + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(b)); +} + +TEST_F(WatcherTest, ArmFailureCirculation) { + // Sanity check to ensure that all ready handles will eventually be returned + // over a finite number of calls to MojoArmWatcher(). + + constexpr size_t kNumTestPipes = 100; + constexpr size_t kNumTestHandles = kNumTestPipes * 2; + MojoHandle handles[kNumTestHandles]; + + // Create a bunch of pipes and make sure they're all readable. + for (size_t i = 0; i < kNumTestPipes; ++i) { + CreateMessagePipe(&handles[i], &handles[i + kNumTestPipes]); + WriteMessage(handles[i], "hey"); + WriteMessage(handles[i + kNumTestPipes], "hay"); + WaitForSignals(handles[i], MOJO_HANDLE_SIGNAL_READABLE); + WaitForSignals(handles[i + kNumTestPipes], MOJO_HANDLE_SIGNAL_READABLE); + } + + // Create a watcher and watch all of them. + MojoHandle w; + EXPECT_EQ(MOJO_RESULT_OK, MojoCreateWatcher(&ExpectOnlyCancel, &w)); + for (size_t i = 0; i < kNumTestHandles; ++i) { + EXPECT_EQ(MOJO_RESULT_OK, + MojoWatch(w, handles[i], MOJO_HANDLE_SIGNAL_READABLE, i)); + } + + // Keep trying to arm |w| until every watch gets an entry in |ready_contexts|. + // If MojoArmWatcher() is well-behaved, this should terminate eventually. + std::set ready_contexts; + while (ready_contexts.size() < kNumTestHandles) { + uint32_t num_ready_contexts = 1; + uintptr_t ready_context; + MojoResult ready_result; + MojoHandleSignalsState ready_state; + EXPECT_EQ(MOJO_RESULT_FAILED_PRECONDITION, + MojoArmWatcher(w, &num_ready_contexts, &ready_context, + &ready_result, &ready_state)); + EXPECT_EQ(1u, num_ready_contexts); + EXPECT_EQ(MOJO_RESULT_OK, ready_result); + ready_contexts.insert(ready_context); + } + + for (size_t i = 0; i < kNumTestHandles; ++i) + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(handles[i])); + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(w)); +} + +} // namespace +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/BUILD.gn b/mojo/edk/test/BUILD.gn new file mode 100644 index 0000000..a15456a --- /dev/null +++ b/mojo/edk/test/BUILD.gn @@ -0,0 +1,131 @@ +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//testing/test.gni") + +static_library("test_support") { + testonly = true + sources = [ + "mojo_test_base.cc", + "mojo_test_base.h", + "test_utils.h", + "test_utils_posix.cc", + "test_utils_win.cc", + ] + + if (!is_ios) { + sources += [ + "multiprocess_test_helper.cc", + "multiprocess_test_helper.h", + ] + } + + deps = [ + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/public/cpp/system", + "//testing/gtest", + ] +} + +source_set("run_all_unittests") { + testonly = true + sources = [ + "run_all_unittests.cc", + ] + + deps = [ + ":test_support", + ":test_support_impl", + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/public/c/test_support", + "//testing/gtest", + ] + + if (is_linux && !is_component_build) { + public_configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ] + } +} + +source_set("run_all_perftests") { + testonly = true + deps = [ + ":test_support_impl", + "//base", + "//base/test:test_support", + "//mojo/edk/system", + "//mojo/edk/test:test_support", + "//mojo/public/c/test_support", + ] + + sources = [ + "run_all_perftests.cc", + ] + + if (is_linux && !is_component_build) { + public_configs = [ "//build/config/gcc:rpath_for_built_shared_libraries" ] + } +} + +static_library("test_support_impl") { + testonly = true + deps = [ + "//base", + "//base/test:test_support", + "//mojo/public/c/test_support", + "//mojo/public/cpp/system", + ] + + sources = [ + "test_support_impl.cc", + "test_support_impl.h", + ] +} + +# Public SDK test targets follow. These targets are not defined within the +# public SDK itself as running the unittests requires the EDK. +# TODO(vtl): These don't really belong here. (They should be converted to +# apptests, but even apart from that these targets belong somewhere else.) + +group("public_tests") { + testonly = true + deps = [ + ":mojo_public_bindings_unittests", + ":mojo_public_system_perftests", + ":mojo_public_system_unittests", + ] +} + +test("mojo_public_bindings_perftests") { + deps = [ + ":run_all_perftests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/bindings/tests:perftests", + ] +} + +test("mojo_public_bindings_unittests") { + deps = [ + ":run_all_unittests", + "//mojo/edk/test:test_support", + "//mojo/public/cpp/bindings/tests", + ] +} + +test("mojo_public_system_perftests") { + deps = [ + ":run_all_perftests", + "//mojo/public/c/system/tests:perftests", + ] +} + +test("mojo_public_system_unittests") { + deps = [ + ":run_all_unittests", + "//mojo/public/cpp/system/tests", + ] +} diff --git a/mojo/edk/test/mojo_test_base.cc b/mojo/edk/test/mojo_test_base.cc new file mode 100644 index 0000000..71a5e3b --- /dev/null +++ b/mojo/edk/test/mojo_test_base.cc @@ -0,0 +1,327 @@ +// 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/edk/test/mojo_test_base.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/synchronization/waitable_event.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/system/handle_signals_state.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/watcher.h" +#include "mojo/public/cpp/system/wait.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_port_broker.h" +#endif + +namespace mojo { +namespace edk { +namespace test { + +#if defined(OS_MACOSX) && !defined(OS_IOS) +namespace { +base::MachPortBroker* g_mach_broker = nullptr; +} +#endif + +MojoTestBase::MojoTestBase() { +#if defined(OS_MACOSX) && !defined(OS_IOS) + if (!g_mach_broker) { + g_mach_broker = new base::MachPortBroker("mojo_test"); + CHECK(g_mach_broker->Init()); + SetMachPortProvider(g_mach_broker); + } +#endif +} + +MojoTestBase::~MojoTestBase() {} + +MojoTestBase::ClientController& MojoTestBase::StartClient( + const std::string& client_name) { + clients_.push_back(base::MakeUnique( + client_name, this, process_error_callback_, launch_type_)); + return *clients_.back(); +} + +MojoTestBase::ClientController::ClientController( + const std::string& client_name, + MojoTestBase* test, + const ProcessErrorCallback& process_error_callback, + LaunchType launch_type) { +#if !defined(OS_IOS) +#if defined(OS_MACOSX) + // This lock needs to be held while launching the child because the Mach port + // broker only allows task ports to be received from known child processes. + // However, it can only know the child process's pid after the child has + // launched. To prevent a race where the child process sends its task port + // before the pid has been registered, the lock needs to be held over both + // launch and child pid registration. + base::AutoLock lock(g_mach_broker->GetLock()); +#endif + helper_.set_process_error_callback(process_error_callback); + pipe_ = helper_.StartChild(client_name, launch_type); +#if defined(OS_MACOSX) + g_mach_broker->AddPlaceholderForPid(helper_.test_child().Handle()); +#endif +#endif +} + +MojoTestBase::ClientController::~ClientController() { + CHECK(was_shutdown_) + << "Test clients should be waited on explicitly with WaitForShutdown()."; +} + +void MojoTestBase::ClientController::ClosePeerConnection() { +#if !defined(OS_IOS) + helper_.ClosePeerConnection(); +#endif +} + +int MojoTestBase::ClientController::WaitForShutdown() { + was_shutdown_ = true; +#if !defined(OS_IOS) + int retval = helper_.WaitForChildShutdown(); +#if defined(OS_MACOSX) + base::AutoLock lock(g_mach_broker->GetLock()); + g_mach_broker->InvalidatePid(helper_.test_child().Handle()); +#endif + return retval; +#else + NOTREACHED(); + return 1; +#endif +} + +// static +void MojoTestBase::CloseHandle(MojoHandle h) { + EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h)); +} + +// static +void MojoTestBase::CreateMessagePipe(MojoHandle *p0, MojoHandle* p1) { + MojoCreateMessagePipe(nullptr, p0, p1); + CHECK_NE(*p0, MOJO_HANDLE_INVALID); + CHECK_NE(*p1, MOJO_HANDLE_INVALID); +} + +// static +void MojoTestBase::WriteMessageWithHandles(MojoHandle mp, + const std::string& message, + const MojoHandle *handles, + uint32_t num_handles) { + CHECK_EQ(MojoWriteMessage(mp, message.data(), + static_cast(message.size()), + handles, num_handles, MOJO_WRITE_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); +} + +// static +void MojoTestBase::WriteMessage(MojoHandle mp, const std::string& message) { + WriteMessageWithHandles(mp, message, nullptr, 0); +} + +// static +std::string MojoTestBase::ReadMessageWithHandles( + MojoHandle mp, + MojoHandle* handles, + uint32_t expected_num_handles) { + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + uint32_t message_size = 0; + uint32_t num_handles = 0; + CHECK_EQ(MojoReadMessage(mp, nullptr, &message_size, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_RESOURCE_EXHAUSTED); + CHECK_EQ(expected_num_handles, num_handles); + + std::string message(message_size, 'x'); + CHECK_EQ(MojoReadMessage(mp, &message[0], &message_size, handles, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(message_size, message.size()); + CHECK_EQ(num_handles, expected_num_handles); + + return message; +} + +// static +std::string MojoTestBase::ReadMessageWithOptionalHandle(MojoHandle mp, + MojoHandle* handle) { + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + uint32_t message_size = 0; + uint32_t num_handles = 0; + CHECK_EQ(MojoReadMessage(mp, nullptr, &message_size, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_RESOURCE_EXHAUSTED); + CHECK(num_handles == 0 || num_handles == 1); + + CHECK(handle); + + std::string message(message_size, 'x'); + CHECK_EQ(MojoReadMessage(mp, &message[0], &message_size, handle, + &num_handles, MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(message_size, message.size()); + CHECK(num_handles == 0 || num_handles == 1); + + if (num_handles) + CHECK_NE(*handle, MOJO_HANDLE_INVALID); + else + *handle = MOJO_HANDLE_INVALID; + + return message; +} + +// static +std::string MojoTestBase::ReadMessage(MojoHandle mp) { + return ReadMessageWithHandles(mp, nullptr, 0); +} + +// static +void MojoTestBase::ReadMessage(MojoHandle mp, + char* data, + size_t num_bytes) { + CHECK_EQ(WaitForSignals(mp, MOJO_HANDLE_SIGNAL_READABLE), MOJO_RESULT_OK); + + uint32_t message_size = 0; + uint32_t num_handles = 0; + CHECK_EQ(MojoReadMessage(mp, nullptr, &message_size, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_RESOURCE_EXHAUSTED); + CHECK_EQ(num_handles, 0u); + CHECK_EQ(message_size, num_bytes); + + CHECK_EQ(MojoReadMessage(mp, data, &message_size, nullptr, &num_handles, + MOJO_READ_MESSAGE_FLAG_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_handles, 0u); + CHECK_EQ(message_size, num_bytes); +} + +// static +void MojoTestBase::VerifyTransmission(MojoHandle source, + MojoHandle dest, + const std::string& message) { + WriteMessage(source, message); + + // We don't use EXPECT_EQ; failures on really long messages make life hard. + EXPECT_TRUE(message == ReadMessage(dest)); +} + +// static +void MojoTestBase::VerifyEcho(MojoHandle mp, + const std::string& message) { + VerifyTransmission(mp, mp, message); +} + +// static +MojoHandle MojoTestBase::CreateBuffer(uint64_t size) { + MojoHandle h; + EXPECT_EQ(MojoCreateSharedBuffer(nullptr, size, &h), MOJO_RESULT_OK); + return h; +} + +// static +MojoHandle MojoTestBase::DuplicateBuffer(MojoHandle h, bool read_only) { + MojoHandle new_handle; + MojoDuplicateBufferHandleOptions options = { + sizeof(MojoDuplicateBufferHandleOptions), + MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_NONE + }; + if (read_only) + options.flags |= MOJO_DUPLICATE_BUFFER_HANDLE_OPTIONS_FLAG_READ_ONLY; + EXPECT_EQ(MOJO_RESULT_OK, + MojoDuplicateBufferHandle(h, &options, &new_handle)); + return new_handle; +} + +// static +void MojoTestBase::WriteToBuffer(MojoHandle h, + size_t offset, + const base::StringPiece& s) { + char* data; + EXPECT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(h, offset, s.size(), reinterpret_cast(&data), + MOJO_MAP_BUFFER_FLAG_NONE)); + memcpy(data, s.data(), s.size()); + EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(static_cast(data))); +} + +// static +void MojoTestBase::ExpectBufferContents(MojoHandle h, + size_t offset, + const base::StringPiece& s) { + char* data; + EXPECT_EQ(MOJO_RESULT_OK, + MojoMapBuffer(h, offset, s.size(), reinterpret_cast(&data), + MOJO_MAP_BUFFER_FLAG_NONE)); + EXPECT_EQ(s, base::StringPiece(data, s.size())); + EXPECT_EQ(MOJO_RESULT_OK, MojoUnmapBuffer(static_cast(data))); +} + +// static +void MojoTestBase::CreateDataPipe(MojoHandle *p0, + MojoHandle* p1, + size_t capacity) { + MojoCreateDataPipeOptions options; + options.struct_size = static_cast(sizeof(options)); + options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE; + options.element_num_bytes = 1; + options.capacity_num_bytes = static_cast(capacity); + + MojoCreateDataPipe(&options, p0, p1); + CHECK_NE(*p0, MOJO_HANDLE_INVALID); + CHECK_NE(*p1, MOJO_HANDLE_INVALID); +} + +// static +void MojoTestBase::WriteData(MojoHandle producer, const std::string& data) { + CHECK_EQ(WaitForSignals(producer, MOJO_HANDLE_SIGNAL_WRITABLE), + MOJO_RESULT_OK); + uint32_t num_bytes = static_cast(data.size()); + CHECK_EQ(MojoWriteData(producer, data.data(), &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_bytes, static_cast(data.size())); +} + +// static +std::string MojoTestBase::ReadData(MojoHandle consumer, size_t size) { + CHECK_EQ(WaitForSignals(consumer, MOJO_HANDLE_SIGNAL_READABLE), + MOJO_RESULT_OK); + std::vector buffer(size); + uint32_t num_bytes = static_cast(size); + CHECK_EQ(MojoReadData(consumer, buffer.data(), &num_bytes, + MOJO_WRITE_DATA_FLAG_ALL_OR_NONE), + MOJO_RESULT_OK); + CHECK_EQ(num_bytes, static_cast(size)); + + return std::string(buffer.data(), buffer.size()); +} + +// static +MojoHandleSignalsState MojoTestBase::GetSignalsState(MojoHandle handle) { + MojoHandleSignalsState signals_state; + CHECK_EQ(MOJO_RESULT_OK, MojoQueryHandleSignalsState(handle, &signals_state)); + return signals_state; +} + +// static +MojoResult MojoTestBase::WaitForSignals(MojoHandle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* state) { + return Wait(Handle(handle), signals, state); +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/mojo_test_base.h b/mojo/edk/test/mojo_test_base.h new file mode 100644 index 0000000..35e2c2b --- /dev/null +++ b/mojo/edk/test/mojo_test_base.h @@ -0,0 +1,249 @@ +// 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_EDK_TEST_MOJO_TEST_BASE_H_ +#define MOJO_EDK_TEST_MOJO_TEST_BASE_H_ + +#include +#include +#include + +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/test/multiprocess_test_helper.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 edk { +namespace test { + +class MojoTestBase : public testing::Test { + public: + MojoTestBase(); + ~MojoTestBase() override; + + using LaunchType = MultiprocessTestHelper::LaunchType; + using HandlerCallback = base::Callback; + + class ClientController { + public: + ClientController(const std::string& client_name, + MojoTestBase* test, + const ProcessErrorCallback& process_error_callback, + LaunchType launch_type); + ~ClientController(); + + MojoHandle pipe() const { return pipe_.get().value(); } + + void ClosePeerConnection(); + int WaitForShutdown(); + + private: + friend class MojoTestBase; + +#if !defined(OS_IOS) + MultiprocessTestHelper helper_; +#endif + ScopedMessagePipeHandle pipe_; + bool was_shutdown_ = false; + + DISALLOW_COPY_AND_ASSIGN(ClientController); + }; + + // Set the callback to handle bad messages received from test client + // processes. This can be set to a different callback before starting each + // client. + void set_process_error_callback(const ProcessErrorCallback& callback) { + process_error_callback_ = callback; + } + + ClientController& StartClient(const std::string& client_name); + + template + void StartClientWithHandler(const std::string& client_name, + HandlerFunc handler) { + int expected_exit_code = 0; + ClientController& c = StartClient(client_name); + handler(c.pipe(), &expected_exit_code); + EXPECT_EQ(expected_exit_code, c.WaitForShutdown()); + } + + // Closes a handle and expects success. + static void CloseHandle(MojoHandle h); + + ////// Message pipe test utilities /////// + + // Creates a new pipe, returning endpoint handles in |p0| and |p1|. + static void CreateMessagePipe(MojoHandle* p0, MojoHandle* p1); + + // Writes a string to the pipe, transferring handles in the process. + static void WriteMessageWithHandles(MojoHandle mp, + const std::string& message, + const MojoHandle* handles, + uint32_t num_handles); + + // Writes a string to the pipe with no handles. + static void WriteMessage(MojoHandle mp, const std::string& message); + + // Reads a string from the pipe, expecting to read an exact number of handles + // in the process. Returns the read string. + static std::string ReadMessageWithHandles(MojoHandle mp, + MojoHandle* handles, + uint32_t expected_num_handles); + + // Reads a string from the pipe, expecting either zero or one handles. + // If no handle is read, |handle| will be reset. + static std::string ReadMessageWithOptionalHandle(MojoHandle mp, + MojoHandle* handle); + + // Reads a string from the pipe, expecting to read no handles. + // Returns the string. + static std::string ReadMessage(MojoHandle mp); + + // Reads a string from the pipe, expecting to read no handles and exactly + // |num_bytes| bytes, which are read into |data|. + static void ReadMessage(MojoHandle mp, char* data, size_t num_bytes); + + // Writes |message| to |in| and expects to read it back from |out|. + static void VerifyTransmission(MojoHandle in, + MojoHandle out, + const std::string& message); + + // Writes |message| to |mp| and expects to read it back from the same handle. + static void VerifyEcho(MojoHandle mp, const std::string& message); + + //////// Shared buffer test utilities ///////// + + // Creates a new shared buffer. + static MojoHandle CreateBuffer(uint64_t size); + + // Duplicates a shared buffer to a new handle. + static MojoHandle DuplicateBuffer(MojoHandle h, bool read_only); + + // Maps a buffer, writes some data into it, and unmaps it. + static void WriteToBuffer(MojoHandle h, + size_t offset, + const base::StringPiece& s); + + // Maps a buffer, tests the value of some of its contents, and unmaps it. + static void ExpectBufferContents(MojoHandle h, + size_t offset, + const base::StringPiece& s); + + //////// Data pipe test utilities ///////// + + // Creates a new data pipe. + static void CreateDataPipe(MojoHandle* producer, + MojoHandle* consumer, + size_t capacity); + + // Writes data to a data pipe. + static void WriteData(MojoHandle producer, const std::string& data); + + // Reads data from a data pipe. + static std::string ReadData(MojoHandle consumer, size_t size); + + // Queries the signals state of |handle|. + static MojoHandleSignalsState GetSignalsState(MojoHandle handle); + + // Helper to block the calling thread waiting for signals to be raised. + static MojoResult WaitForSignals(MojoHandle handle, + MojoHandleSignals signals, + MojoHandleSignalsState* state = nullptr); + + void set_launch_type(LaunchType launch_type) { launch_type_ = launch_type; } + + private: + friend class ClientController; + + std::vector> clients_; + + ProcessErrorCallback process_error_callback_; + + LaunchType launch_type_ = LaunchType::CHILD; + + DISALLOW_COPY_AND_ASSIGN(MojoTestBase); +}; + +// Launches a new child process running the test client |client_name| connected +// to a new message pipe bound to |pipe_name|. |pipe_name| is automatically +// closed on test teardown. +#define RUN_CHILD_ON_PIPE(client_name, pipe_name) \ + StartClientWithHandler( \ + #client_name, \ + [&](MojoHandle pipe_name, int *expected_exit_code) { { + +// Waits for the client to terminate and expects a return code of zero. +#define END_CHILD() \ + } \ + *expected_exit_code = 0; \ + }); + +// Wait for the client to terminate with a specific return code. +#define END_CHILD_AND_EXPECT_EXIT_CODE(code) \ + } \ + *expected_exit_code = code; \ + }); + +// Use this to declare the child process's "main()" function for tests using +// MojoTestBase and MultiprocessTestHelper. It returns an |int|, which will +// will be the process's exit code (but see the comment about +// WaitForChildShutdown()). +// +// The function is defined as a subclass of |test_base| to facilitate shared +// code between test clients and to allow clients to spawn children themselves. +// +// |pipe_name| will be bound to the MojoHandle of a message pipe connected +// to the parent process (see RUN_CHILD_ON_PIPE above.) This pipe handle is +// automatically closed on test client teardown. +#if !defined(OS_IOS) +#define DEFINE_TEST_CLIENT_WITH_PIPE(client_name, test_base, pipe_name) \ + class client_name##_MainFixture : public test_base { \ + void TestBody() override {} \ + public: \ + int Main(MojoHandle); \ + }; \ + MULTIPROCESS_TEST_MAIN_WITH_SETUP( \ + client_name##TestChildMain, \ + ::mojo::edk::test::MultiprocessTestHelper::ChildSetup) { \ + client_name##_MainFixture test; \ + return ::mojo::edk::test::MultiprocessTestHelper::RunClientMain( \ + base::Bind(&client_name##_MainFixture::Main, \ + base::Unretained(&test))); \ + } \ + int client_name##_MainFixture::Main(MojoHandle pipe_name) + +// This is a version of DEFINE_TEST_CLIENT_WITH_PIPE which can be used with +// gtest ASSERT/EXPECT macros. +#define DEFINE_TEST_CLIENT_TEST_WITH_PIPE(client_name, test_base, pipe_name) \ + class client_name##_MainFixture : public test_base { \ + void TestBody() override {} \ + public: \ + void Main(MojoHandle); \ + }; \ + MULTIPROCESS_TEST_MAIN_WITH_SETUP( \ + client_name##TestChildMain, \ + ::mojo::edk::test::MultiprocessTestHelper::ChildSetup) { \ + client_name##_MainFixture test; \ + return ::mojo::edk::test::MultiprocessTestHelper::RunClientTestMain( \ + base::Bind(&client_name##_MainFixture::Main, \ + base::Unretained(&test))); \ + } \ + void client_name##_MainFixture::Main(MojoHandle pipe_name) +#else // !defined(OS_IOS) +#define DEFINE_TEST_CLIENT_WITH_PIPE(client_name, test_base, pipe_name) +#define DEFINE_TEST_CLIENT_TEST_WITH_PIPE(client_name, test_base, pipe_name) +#endif // !defined(OS_IOS) + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_TEST_MOJO_TEST_BASE_H_ diff --git a/mojo/edk/test/multiprocess_test_helper.cc b/mojo/edk/test/multiprocess_test_helper.cc new file mode 100644 index 0000000..cf37782 --- /dev/null +++ b/mojo/edk/test/multiprocess_test_helper.cc @@ -0,0 +1,263 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/test/multiprocess_test_helper.h" + +#include +#include +#include + +#include "base/base_paths.h" +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/path_service.h" +#include "base/process/kill.h" +#include "base/process/process_handle.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "base/task_runner.h" +#include "base/threading/thread_task_runner_handle.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/named_platform_handle.h" +#include "mojo/edk/embedder/named_platform_handle_utils.h" +#include "mojo/edk/embedder/pending_process_connection.h" +#include "mojo/edk/embedder/platform_channel_pair.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_WIN) +#include "base/win/windows_version.h" +#elif defined(OS_MACOSX) && !defined(OS_IOS) +#include "base/mac/mach_port_broker.h" +#endif + +namespace mojo { +namespace edk { +namespace test { + +namespace { + +const char kMojoPrimordialPipeToken[] = "mojo-primordial-pipe-token"; +const char kMojoNamedPipeName[] = "mojo-named-pipe-name"; + +template +int RunClientFunction(Func handler, bool pass_pipe_ownership_to_main) { + CHECK(MultiprocessTestHelper::primordial_pipe.is_valid()); + ScopedMessagePipeHandle pipe = + std::move(MultiprocessTestHelper::primordial_pipe); + MessagePipeHandle pipe_handle = + pass_pipe_ownership_to_main ? pipe.release() : pipe.get(); + return handler(pipe_handle.value()); +} + +} // namespace + +MultiprocessTestHelper::MultiprocessTestHelper() {} + +MultiprocessTestHelper::~MultiprocessTestHelper() { + CHECK(!test_child_.process.IsValid()); +} + +ScopedMessagePipeHandle MultiprocessTestHelper::StartChild( + const std::string& test_child_name, + LaunchType launch_type) { + return StartChildWithExtraSwitch(test_child_name, std::string(), + std::string(), launch_type); +} + +ScopedMessagePipeHandle MultiprocessTestHelper::StartChildWithExtraSwitch( + const std::string& test_child_name, + const std::string& switch_string, + const std::string& switch_value, + LaunchType launch_type) { + CHECK(!test_child_name.empty()); + CHECK(!test_child_.process.IsValid()); + + std::string test_child_main = test_child_name + "TestChildMain"; + + // Manually construct the new child's commandline to avoid copying unwanted + // values. + base::CommandLine command_line( + base::GetMultiProcessTestChildBaseCommandLine().GetProgram()); + + std::set uninherited_args; + uninherited_args.insert("mojo-platform-channel-handle"); + uninherited_args.insert(switches::kTestChildProcess); + + // Copy commandline switches from the parent process, except for the + // multiprocess client name and mojo message pipe handle; this allows test + // clients to spawn other test clients. + for (const auto& entry : + base::CommandLine::ForCurrentProcess()->GetSwitches()) { + if (uninherited_args.find(entry.first) == uninherited_args.end()) + command_line.AppendSwitchNative(entry.first, entry.second); + } + + PlatformChannelPair channel; + NamedPlatformHandle named_pipe; + HandlePassingInformation handle_passing_info; + if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) { + channel.PrepareToPassClientHandleToChildProcess(&command_line, + &handle_passing_info); + } else if (launch_type == LaunchType::NAMED_CHILD || + launch_type == LaunchType::NAMED_PEER) { +#if defined(OS_POSIX) + base::FilePath temp_dir; + CHECK(base::PathService::Get(base::DIR_TEMP, &temp_dir)); + named_pipe = NamedPlatformHandle( + temp_dir.AppendASCII(GenerateRandomToken()).value()); +#else + named_pipe = NamedPlatformHandle(GenerateRandomToken()); +#endif + command_line.AppendSwitchNative(kMojoNamedPipeName, named_pipe.name); + } + + if (!switch_string.empty()) { + CHECK(!command_line.HasSwitch(switch_string)); + if (!switch_value.empty()) + command_line.AppendSwitchASCII(switch_string, switch_value); + else + command_line.AppendSwitch(switch_string); + } + + base::LaunchOptions options; +#if defined(OS_POSIX) + options.fds_to_remap = &handle_passing_info; +#elif defined(OS_WIN) + options.start_hidden = true; + if (base::win::GetVersion() >= base::win::VERSION_VISTA) + options.handles_to_inherit = &handle_passing_info; + else + options.inherit_handles = true; +#else +#error "Not supported yet." +#endif + + // NOTE: In the case of named pipes, it's important that the server handle be + // created before the child process is launched; otherwise the server binding + // the pipe path can race with child's connection to the pipe. + ScopedPlatformHandle server_handle; + if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) { + server_handle = channel.PassServerHandle(); + } else if (launch_type == LaunchType::NAMED_CHILD || + launch_type == LaunchType::NAMED_PEER) { + server_handle = CreateServerHandle(named_pipe); + } + + PendingProcessConnection process; + ScopedMessagePipeHandle pipe; + if (launch_type == LaunchType::CHILD || + launch_type == LaunchType::NAMED_CHILD) { + std::string pipe_token; + pipe = process.CreateMessagePipe(&pipe_token); + command_line.AppendSwitchASCII(kMojoPrimordialPipeToken, pipe_token); + } else if (launch_type == LaunchType::PEER || + launch_type == LaunchType::NAMED_PEER) { + peer_token_ = mojo::edk::GenerateRandomToken(); + pipe = ConnectToPeerProcess(std::move(server_handle), peer_token_); + } + + test_child_ = + base::SpawnMultiProcessTestChild(test_child_main, command_line, options); + if (launch_type == LaunchType::CHILD || launch_type == LaunchType::PEER) + channel.ChildProcessLaunched(); + + if (launch_type == LaunchType::CHILD || + launch_type == LaunchType::NAMED_CHILD) { + DCHECK(server_handle.is_valid()); + process.Connect(test_child_.process.Handle(), + ConnectionParams(std::move(server_handle)), + process_error_callback_); + } + + CHECK(test_child_.process.IsValid()); + return pipe; +} + +int MultiprocessTestHelper::WaitForChildShutdown() { + CHECK(test_child_.process.IsValid()); + + int rv = -1; + WaitForMultiprocessTestChildExit(test_child_.process, + TestTimeouts::action_timeout(), &rv); + test_child_.process.Close(); + return rv; +} + +void MultiprocessTestHelper::ClosePeerConnection() { + DCHECK(!peer_token_.empty()); + ::mojo::edk::ClosePeerConnection(peer_token_); + peer_token_.clear(); +} + +bool MultiprocessTestHelper::WaitForChildTestShutdown() { + return WaitForChildShutdown() == 0; +} + +// static +void MultiprocessTestHelper::ChildSetup() { + CHECK(base::CommandLine::InitializedForCurrentProcess()); + + std::string primordial_pipe_token = + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + kMojoPrimordialPipeToken); + NamedPlatformHandle named_pipe( + base::CommandLine::ForCurrentProcess()->GetSwitchValueNative( + kMojoNamedPipeName)); + if (!primordial_pipe_token.empty()) { + primordial_pipe = CreateChildMessagePipe(primordial_pipe_token); +#if defined(OS_MACOSX) && !defined(OS_IOS) + CHECK(base::MachPortBroker::ChildSendTaskPortToParent("mojo_test")); +#endif + if (named_pipe.is_valid()) { + SetParentPipeHandle(CreateClientHandle(named_pipe)); + } else { + SetParentPipeHandle( + PlatformChannelPair::PassClientHandleFromParentProcess( + *base::CommandLine::ForCurrentProcess())); + } + } else { + if (named_pipe.is_valid()) { + primordial_pipe = ConnectToPeerProcess(CreateClientHandle(named_pipe)); + } else { + primordial_pipe = ConnectToPeerProcess( + PlatformChannelPair::PassClientHandleFromParentProcess( + *base::CommandLine::ForCurrentProcess())); + } + } +} + +// static +int MultiprocessTestHelper::RunClientMain( + const base::Callback& main, + bool pass_pipe_ownership_to_main) { + return RunClientFunction( + [main](MojoHandle handle) { return main.Run(handle); }, + pass_pipe_ownership_to_main); +} + +// static +int MultiprocessTestHelper::RunClientTestMain( + const base::Callback& main) { + return RunClientFunction( + [main](MojoHandle handle) { + main.Run(handle); + return (::testing::Test::HasFatalFailure() || + ::testing::Test::HasNonfatalFailure()) + ? 1 + : 0; + }, + true /* close_pipe_on_exit */); +} + +// static +mojo::ScopedMessagePipeHandle MultiprocessTestHelper::primordial_pipe; + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/multiprocess_test_helper.h b/mojo/edk/test/multiprocess_test_helper.h new file mode 100644 index 0000000..dc1c9bc --- /dev/null +++ b/mojo/edk/test/multiprocess_test_helper.h @@ -0,0 +1,110 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_ +#define MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_ + +#include + +#include "base/callback.h" +#include "base/macros.h" +#include "base/process/process.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/public/cpp/system/message_pipe.h" +#include "testing/multiprocess_func_list.h" + +namespace mojo { + +namespace edk { + +namespace test { + +class MultiprocessTestHelper { + public: + using HandlerCallback = base::Callback; + + enum class LaunchType { + // Launch the child process as a child in the mojo system. + CHILD, + + // Launch the child process as an unrelated peer process in the mojo system. + PEER, + + // Launch the child process as a child in the mojo system, using a named + // pipe. + NAMED_CHILD, + + // Launch the child process as an unrelated peer process in the mojo + // system, using a named pipe. + NAMED_PEER, + }; + + MultiprocessTestHelper(); + ~MultiprocessTestHelper(); + + // Start a child process and run the "main" function "named" |test_child_name| + // declared using |MOJO_MULTIPROCESS_TEST_CHILD_MAIN()| or + // |MOJO_MULTIPROCESS_TEST_CHILD_TEST()| (below). + ScopedMessagePipeHandle StartChild( + const std::string& test_child_name, + LaunchType launch_type = LaunchType::CHILD); + + // Like |StartChild()|, but appends an extra switch (with ASCII value) to the + // command line. (The switch must not already be present in the default + // command line.) + ScopedMessagePipeHandle StartChildWithExtraSwitch( + const std::string& test_child_name, + const std::string& switch_string, + const std::string& switch_value, + LaunchType launch_type); + + void set_process_error_callback(const ProcessErrorCallback& callback) { + process_error_callback_ = callback; + } + + void ClosePeerConnection(); + + // Wait for the child process to terminate. + // Returns the exit code of the child process. Note that, though it's declared + // to be an |int|, the exit code is subject to mangling by the OS. E.g., we + // usually return -1 on error in the child (e.g., if |test_child_name| was not + // found), but this is mangled to 255 on Linux. You should only rely on codes + // 0-127 being preserved, and -1 being outside the range 0-127. + int WaitForChildShutdown(); + + // Like |WaitForChildShutdown()|, but returns true on success (exit code of 0) + // and false otherwise. You probably want to do something like + // |EXPECT_TRUE(WaitForChildTestShutdown());|. + bool WaitForChildTestShutdown(); + + const base::Process& test_child() const { return test_child_.process; } + + // Used by macros in mojo/edk/test/mojo_test_base.h to support multiprocess + // test client initialization. + static void ChildSetup(); + static int RunClientMain(const base::Callback& main, + bool pass_pipe_ownership_to_main = false); + static int RunClientTestMain(const base::Callback& main); + + // For use (and only valid) in the child process: + static mojo::ScopedMessagePipeHandle primordial_pipe; + + private: + // Valid after |StartChild()| and before |WaitForChildShutdown()|. + base::SpawnChildResult test_child_; + + ProcessErrorCallback process_error_callback_; + + std::string peer_token_; + + DISALLOW_COPY_AND_ASSIGN(MultiprocessTestHelper); +}; + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_TEST_MULTIPROCESS_TEST_HELPER_H_ diff --git a/mojo/edk/test/multiprocess_test_helper_unittest.cc b/mojo/edk/test/multiprocess_test_helper_unittest.cc new file mode 100644 index 0000000..f7e9e83 --- /dev/null +++ b/mojo/edk/test/multiprocess_test_helper_unittest.cc @@ -0,0 +1,165 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/test/multiprocess_test_helper.h" + +#include + +#include + +#include "base/logging.h" +#include "build/build_config.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" +#include "mojo/edk/system/test_utils.h" +#include "mojo/edk/test/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +#if defined(OS_POSIX) +#include +#endif + +namespace mojo { +namespace edk { +namespace test { +namespace { + +bool IsNonBlocking(const PlatformHandle& handle) { +#if defined(OS_WIN) + // Haven't figured out a way to query whether a HANDLE was created with + // FILE_FLAG_OVERLAPPED. + return true; +#else + return fcntl(handle.handle, F_GETFL) & O_NONBLOCK; +#endif +} + +bool WriteByte(const PlatformHandle& handle, char c) { + size_t bytes_written = 0; + BlockingWrite(handle, &c, 1, &bytes_written); + return bytes_written == 1; +} + +bool ReadByte(const PlatformHandle& handle, char* c) { + size_t bytes_read = 0; + BlockingRead(handle, c, 1, &bytes_read); + return bytes_read == 1; +} + +using MultiprocessTestHelperTest = testing::Test; + +TEST_F(MultiprocessTestHelperTest, RunChild) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + + helper.StartChild("RunChild"); + EXPECT_EQ(123, helper.WaitForChildShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_MAIN(RunChild) { + CHECK(MultiprocessTestHelper::client_platform_handle.is_valid()); + return 123; +} + +TEST_F(MultiprocessTestHelperTest, TestChildMainNotFound) { + MultiprocessTestHelper helper; + helper.StartChild("NoSuchTestChildMain"); + int result = helper.WaitForChildShutdown(); + EXPECT_FALSE(result >= 0 && result <= 127); +} + +TEST_F(MultiprocessTestHelperTest, PassedChannel) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + helper.StartChild("PassedChannel"); + + // Take ownership of the handle. + ScopedPlatformHandle handle = std::move(helper.server_platform_handle); + + // The handle should be non-blocking. + EXPECT_TRUE(IsNonBlocking(handle.get())); + + // Write a byte. + const char c = 'X'; + EXPECT_TRUE(WriteByte(handle.get(), c)); + + // It'll echo it back to us, incremented. + char d = 0; + EXPECT_TRUE(ReadByte(handle.get(), &d)); + EXPECT_EQ(c + 1, d); + + // And return it, incremented again. + EXPECT_EQ(c + 2, helper.WaitForChildShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_MAIN(PassedChannel) { + CHECK(MultiprocessTestHelper::client_platform_handle.is_valid()); + + // Take ownership of the handle. + ScopedPlatformHandle handle = + std::move(MultiprocessTestHelper::client_platform_handle); + + // The handle should be non-blocking. + EXPECT_TRUE(IsNonBlocking(handle.get())); + + // Read a byte. + char c = 0; + EXPECT_TRUE(ReadByte(handle.get(), &c)); + + // Write it back, incremented. + c++; + EXPECT_TRUE(WriteByte(handle.get(), c)); + + // And return it, incremented again. + c++; + return static_cast(c); +} + +TEST_F(MultiprocessTestHelperTest, ChildTestPasses) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + helper.StartChild("ChildTestPasses"); + EXPECT_TRUE(helper.WaitForChildTestShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestPasses) { + ASSERT_TRUE(MultiprocessTestHelper::client_platform_handle.is_valid()); + EXPECT_TRUE( + IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get())); +} + +TEST_F(MultiprocessTestHelperTest, ChildTestFailsAssert) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + helper.StartChild("ChildTestFailsAssert"); + EXPECT_FALSE(helper.WaitForChildTestShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestFailsAssert) { + ASSERT_FALSE(MultiprocessTestHelper::client_platform_handle.is_valid()) + << "DISREGARD: Expected failure in child process"; + ASSERT_FALSE( + IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get())) + << "Not reached"; + CHECK(false) << "Not reached"; +} + +TEST_F(MultiprocessTestHelperTest, ChildTestFailsExpect) { + MultiprocessTestHelper helper; + EXPECT_TRUE(helper.server_platform_handle.is_valid()); + helper.StartChild("ChildTestFailsExpect"); + EXPECT_FALSE(helper.WaitForChildTestShutdown()); +} + +MOJO_MULTIPROCESS_TEST_CHILD_TEST(ChildTestFailsExpect) { + EXPECT_FALSE(MultiprocessTestHelper::client_platform_handle.is_valid()) + << "DISREGARD: Expected failure #1 in child process"; + EXPECT_FALSE( + IsNonBlocking(MultiprocessTestHelper::client_platform_handle.get())) + << "DISREGARD: Expected failure #2 in child process"; +} + +} // namespace +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/run_all_perftests.cc b/mojo/edk/test/run_all_perftests.cc new file mode 100644 index 0000000..3ce3b47 --- /dev/null +++ b/mojo/edk/test/run_all_perftests.cc @@ -0,0 +1,26 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/command_line.h" +#include "base/test/multiprocess_test.h" +#include "base/test/perf_test_suite.h" +#include "base/test/test_io_thread.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/edk/test/multiprocess_test_helper.h" +#include "mojo/edk/test/test_support_impl.h" +#include "mojo/public/tests/test_support_private.h" + +int main(int argc, char** argv) { + base::PerfTestSuite test(argc, argv); + + mojo::edk::Init(); + base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart); + mojo::edk::ScopedIPCSupport ipc_support( + test_io_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + mojo::test::TestSupport::Init(new mojo::edk::test::TestSupportImpl()); + + return test.Run(); +} diff --git a/mojo/edk/test/run_all_unittests.cc b/mojo/edk/test/run_all_unittests.cc new file mode 100644 index 0000000..a057825 --- /dev/null +++ b/mojo/edk/test/run_all_unittests.cc @@ -0,0 +1,49 @@ +// 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 + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/test/launcher/unit_test_launcher.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_io_thread.h" +#include "base/test/test_suite.h" +#include "mojo/edk/embedder/embedder.h" +#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/edk/test/multiprocess_test_helper.h" +#include "mojo/edk/test/test_support_impl.h" +#include "mojo/public/tests/test_support_private.h" +#include "testing/gtest/include/gtest/gtest.h" + +int main(int argc, char** argv) { +#if !defined(OS_ANDROID) + // Silence death test thread warnings on Linux. We can afford to run our death + // tests a little more slowly (< 10 ms per death test on a Z620). + // On android, we need to run in the default mode, as the threadsafe mode + // relies on execve which is not available. + testing::GTEST_FLAG(death_test_style) = "threadsafe"; +#endif +#if defined(OS_ANDROID) + // On android, the test framework has a signal handler that will print a + // [ CRASH ] line when the application crashes. This breaks death test has the + // test runner will consider the death of the child process a test failure. + // Removing the signal handler solves this issue. + signal(SIGABRT, SIG_DFL); +#endif + + base::TestSuite test_suite(argc, argv); + + mojo::edk::Init(); + + mojo::test::TestSupport::Init(new mojo::edk::test::TestSupportImpl()); + base::TestIOThread test_io_thread(base::TestIOThread::kAutoStart); + + mojo::edk::ScopedIPCSupport ipc_support( + test_io_thread.task_runner(), + mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + return base::LaunchUnitTests( + argc, argv, + base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite))); +} diff --git a/mojo/edk/test/test_support_impl.cc b/mojo/edk/test/test_support_impl.cc new file mode 100644 index 0000000..25684e4 --- /dev/null +++ b/mojo/edk/test/test_support_impl.cc @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/test/test_support_impl.h" + +#include +#include +#include + +#include + +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/test/perf_log.h" + +namespace mojo { +namespace edk { +namespace test { +namespace { + +base::FilePath ResolveSourceRootRelativePath(const char* relative_path) { + base::FilePath path; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &path)) + return base::FilePath(); + + for (const base::StringPiece& component : base::SplitStringPiece( + relative_path, "/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + if (!component.empty()) + path = path.AppendASCII(component); + } + + return path; +} + +} // namespace + +TestSupportImpl::TestSupportImpl() { +} + +TestSupportImpl::~TestSupportImpl() { +} + +void TestSupportImpl::LogPerfResult(const char* test_name, + const char* sub_test_name, + double value, + const char* units) { + DCHECK(test_name); + if (sub_test_name) { + std::string name = base::StringPrintf("%s/%s", test_name, sub_test_name); + base::LogPerfResult(name.c_str(), value, units); + } else { + base::LogPerfResult(test_name, value, units); + } +} + +FILE* TestSupportImpl::OpenSourceRootRelativeFile(const char* relative_path) { + return base::OpenFile(ResolveSourceRootRelativePath(relative_path), "rb"); +} + +char** TestSupportImpl::EnumerateSourceRootRelativeDirectory( + const char* relative_path) { + std::vector names; + base::FileEnumerator e(ResolveSourceRootRelativePath(relative_path), false, + base::FileEnumerator::FILES); + for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) + names.push_back(name.BaseName().AsUTF8Unsafe()); + + // |names.size() + 1| for null terminator. + char** rv = static_cast(calloc(names.size() + 1, sizeof(char*))); + for (size_t i = 0; i < names.size(); ++i) + rv[i] = base::strdup(names[i].c_str()); + return rv; +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/test_support_impl.h b/mojo/edk/test/test_support_impl.h new file mode 100644 index 0000000..ebb5ce6 --- /dev/null +++ b/mojo/edk/test/test_support_impl.h @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_ +#define MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_ + +#include + +#include "base/macros.h" +#include "mojo/public/tests/test_support_private.h" + +namespace mojo { +namespace edk { +namespace test { + +class TestSupportImpl : public mojo::test::TestSupport { + public: + TestSupportImpl(); + ~TestSupportImpl() override; + + void LogPerfResult(const char* test_name, + const char* sub_test_name, + double value, + const char* units) override; + FILE* OpenSourceRootRelativeFile(const char* relative_path) override; + char** EnumerateSourceRootRelativeDirectory( + const char* relative_path) override; + + private: + DISALLOW_COPY_AND_ASSIGN(TestSupportImpl); +}; + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_TEST_TEST_SUPPORT_IMPL_H_ diff --git a/mojo/edk/test/test_utils.h b/mojo/edk/test/test_utils.h new file mode 100644 index 0000000..939171b --- /dev/null +++ b/mojo/edk/test/test_utils.h @@ -0,0 +1,55 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MOJO_EDK_TEST_TEST_UTILS_H_ +#define MOJO_EDK_TEST_TEST_UTILS_H_ + +#include +#include + +#include + +#include "base/files/scoped_file.h" +#include "mojo/edk/embedder/platform_handle.h" +#include "mojo/edk/embedder/scoped_platform_handle.h" + +namespace mojo { +namespace edk { +namespace test { + +// On success, |bytes_written| is updated to the number of bytes written; +// otherwise it is untouched. +bool BlockingWrite(const PlatformHandle& handle, + const void* buffer, + size_t bytes_to_write, + size_t* bytes_written); + +// On success, |bytes_read| is updated to the number of bytes read; otherwise it +// is untouched. +bool BlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read); + +// If the read is done successfully or would block, the function returns true +// and updates |bytes_read| to the number of bytes read (0 if the read would +// block); otherwise it returns false and leaves |bytes_read| untouched. +// |handle| must already be in non-blocking mode. +bool NonBlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read); + +// Gets a (scoped) |PlatformHandle| from the given (scoped) |FILE|. +ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp); + +// Gets a (scoped) |FILE| from a (scoped) |PlatformHandle|. +base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h, + const char* mode); + +} // namespace test +} // namespace edk +} // namespace mojo + +#endif // MOJO_EDK_TEST_TEST_UTILS_H_ diff --git a/mojo/edk/test/test_utils_posix.cc b/mojo/edk/test/test_utils_posix.cc new file mode 100644 index 0000000..60d5db5 --- /dev/null +++ b/mojo/edk/test/test_utils_posix.cc @@ -0,0 +1,94 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/test/test_utils.h" + +#include +#include +#include + +#include "base/posix/eintr_wrapper.h" + +namespace mojo { +namespace edk { +namespace test { + +bool BlockingWrite(const PlatformHandle& handle, + const void* buffer, + size_t bytes_to_write, + size_t* bytes_written) { + int original_flags = fcntl(handle.handle, F_GETFL); + if (original_flags == -1 || + fcntl(handle.handle, F_SETFL, original_flags & (~O_NONBLOCK)) != 0) { + return false; + } + + ssize_t result = HANDLE_EINTR(write(handle.handle, buffer, bytes_to_write)); + + fcntl(handle.handle, F_SETFL, original_flags); + + if (result < 0) + return false; + + *bytes_written = result; + return true; +} + +bool BlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read) { + int original_flags = fcntl(handle.handle, F_GETFL); + if (original_flags == -1 || + fcntl(handle.handle, F_SETFL, original_flags & (~O_NONBLOCK)) != 0) { + return false; + } + + ssize_t result = HANDLE_EINTR(read(handle.handle, buffer, buffer_size)); + + fcntl(handle.handle, F_SETFL, original_flags); + + if (result < 0) + return false; + + *bytes_read = result; + return true; +} + +bool NonBlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read) { + ssize_t result = HANDLE_EINTR(read(handle.handle, buffer, buffer_size)); + + if (result < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) + return false; + + *bytes_read = 0; + } else { + *bytes_read = result; + } + + return true; +} + +ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) { + CHECK(fp); + int rv = dup(fileno(fp.get())); + PCHECK(rv != -1) << "dup"; + return ScopedPlatformHandle(PlatformHandle(rv)); +} + +base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h, + const char* mode) { + CHECK(h.is_valid()); + base::ScopedFILE rv(fdopen(h.release().handle, mode)); + PCHECK(rv) << "fdopen"; + return rv; +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/edk/test/test_utils_win.cc b/mojo/edk/test/test_utils_win.cc new file mode 100644 index 0000000..17bf5bb --- /dev/null +++ b/mojo/edk/test/test_utils_win.cc @@ -0,0 +1,115 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "mojo/edk/test/test_utils.h" + +#include +#include +#include +#include +#include + +namespace mojo { +namespace edk { +namespace test { + +bool BlockingWrite(const PlatformHandle& handle, + const void* buffer, + size_t bytes_to_write, + size_t* bytes_written) { + OVERLAPPED overlapped = {0}; + DWORD bytes_written_dword = 0; + + if (!WriteFile(handle.handle, buffer, static_cast(bytes_to_write), + &bytes_written_dword, &overlapped)) { + if (GetLastError() != ERROR_IO_PENDING || + !GetOverlappedResult(handle.handle, &overlapped, &bytes_written_dword, + TRUE)) { + return false; + } + } + + *bytes_written = bytes_written_dword; + return true; +} + +bool BlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read) { + OVERLAPPED overlapped = {0}; + DWORD bytes_read_dword = 0; + + if (!ReadFile(handle.handle, buffer, static_cast(buffer_size), + &bytes_read_dword, &overlapped)) { + if (GetLastError() != ERROR_IO_PENDING || + !GetOverlappedResult(handle.handle, &overlapped, &bytes_read_dword, + TRUE)) { + return false; + } + } + + *bytes_read = bytes_read_dword; + return true; +} + +bool NonBlockingRead(const PlatformHandle& handle, + void* buffer, + size_t buffer_size, + size_t* bytes_read) { + OVERLAPPED overlapped = {0}; + DWORD bytes_read_dword = 0; + + if (!ReadFile(handle.handle, buffer, static_cast(buffer_size), + &bytes_read_dword, &overlapped)) { + if (GetLastError() != ERROR_IO_PENDING) + return false; + + CancelIo(handle.handle); + + if (!GetOverlappedResult(handle.handle, &overlapped, &bytes_read_dword, + TRUE)) { + *bytes_read = 0; + return true; + } + } + + *bytes_read = bytes_read_dword; + return true; +} + +ScopedPlatformHandle PlatformHandleFromFILE(base::ScopedFILE fp) { + CHECK(fp); + + HANDLE rv = INVALID_HANDLE_VALUE; + PCHECK(DuplicateHandle( + GetCurrentProcess(), + reinterpret_cast(_get_osfhandle(_fileno(fp.get()))), + GetCurrentProcess(), &rv, 0, TRUE, DUPLICATE_SAME_ACCESS)) + << "DuplicateHandle"; + return ScopedPlatformHandle(PlatformHandle(rv)); +} + +base::ScopedFILE FILEFromPlatformHandle(ScopedPlatformHandle h, + const char* mode) { + CHECK(h.is_valid()); + // Microsoft's documentation for |_open_osfhandle()| only discusses these + // flags (and |_O_WTEXT|). Hmmm. + int flags = 0; + if (strchr(mode, 'a')) + flags |= _O_APPEND; + if (strchr(mode, 'r')) + flags |= _O_RDONLY; + if (strchr(mode, 't')) + flags |= _O_TEXT; + base::ScopedFILE rv(_fdopen( + _open_osfhandle(reinterpret_cast(h.release().handle), flags), + mode)); + PCHECK(rv) << "_fdopen"; + return rv; +} + +} // namespace test +} // namespace edk +} // namespace mojo diff --git a/mojo/public/BUILD.gn b/mojo/public/BUILD.gn new file mode 100644 index 0000000..3baf667 --- /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 0000000..2e49995 --- /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 0000000..972bb2e --- /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 0000000..08185c7 --- /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 0000000..2abe80f --- /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 0000000..285e0d7 --- /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 + +#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 0000000..03c0652 --- /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 0000000..f51e36c --- /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 + +#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 0000000..d0656c6 --- /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 +#include + +#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 0000000..917c69c --- /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 + +// 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 0000000..b759bc7 --- /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 + +#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 0000000..7449c2e --- /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 + +#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 0000000..335cc02 --- /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 0000000..775f667 --- /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 0000000..bace63c --- /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 0000000..cab465b --- /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 +#include +#include + +#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 +#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(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(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(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(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 writers; + for (unsigned i = 0; i < num_writers; i++) + writers.push_back(new MessagePipeWriterThread(h0_, num_bytes)); + + std::vector 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(num_bytes)); + mojo::test::LogPerfResult( + "MessagePipe_Threaded_Writes", sub_test_name, + 1000000.0 * static_cast(num_writes) / (end_time - start_time), + "writes/second"); + mojo::test::LogPerfResult( + "MessagePipe_Threaded_Reads", sub_test_name, + 1000000.0 * static_cast(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(microseconds / 1000000), // Seconds. + static_cast(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 0000000..a9da255 --- /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 +#include + +#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(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(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(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(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(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(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(sizeof(buffer)); + EXPECT_EQ(MOJO_RESULT_OK, + MojoReadMessage(h0, buffer, &buffer_size, nullptr, nullptr, + MOJO_READ_MESSAGE_FLAG_NONE)); + EXPECT_EQ(static_cast(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(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(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(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(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(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(pointer)[0]); + EXPECT_EQ('y', static_cast(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 0000000..3164649 --- /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 +#include +#include + +// 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 0000000..fb9ff76 --- /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 +#include +#include + +#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 0000000..67c568f --- /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 +#include +#include + +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 0000000..e61bb46 --- /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 +#include + +#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(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 0000000..15813b6 --- /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 + +#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(-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 0000000..e32856b --- /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 + +#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 0000000..e2abd58 --- /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 0000000..8e50441 --- /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 + +#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 0000000..bd87965 --- /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 0000000..36eba44 --- /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 0000000..b37267a --- /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; +using LoggerRequest = mojo::InterfaceRequest; + +} // 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`** and **`InterfaceRequest`**. + +In the world of Mojo bindings libraries these are effectively strongly-typed +message pipe endpoints. If an `InterfacePtr` 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` is essentially just a typed container to hold the other +end of an `InterfacePtr`'s pipe -- the receiving end -- until it can be +routed to some implementation which will **bind** it. The `InterfaceRequest` +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` constructor can also take an explicit +`InterfacePtr*` 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`. This is +similar to an `InterfaceRequest` in that it merely holds onto a pipe handle +and cannot actually read or write messages on the pipe. Both this type and +`InterfaceRequest` are safe to move freely from thread to thread, whereas a +bound `InterfacePtr` is bound to a single thread. + +An `InterfacePtr` may be unbound by calling its `PassInterface()` method, +which returns a new `InterfacePtrInfo`. Conversely, an `InterfacePtr` may +bind (and thus take ownership of) an `InterfacePtrInfo` so that interface +calls can be made on the pipe. + +The thread-bound nature of `InterfacePtr` 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`. A `mojo::Binding` bridges an implementation of `T` +with a single bound message pipe endpoint (via a `mojo::InterfaceRequest`), +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 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; + + 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 binding_; + std::vector 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`, +`mojo::InterfacePtr`, *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` 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; +using TableRequest = mojo::InterfaceRequest
; + +class Database { + public: + virtual ~Database() {} + + virtual void AddTable(TableRequest table); +}; + +using DatabasePtr = mojo::InterfacePtr; +using DatabaseRequest = mojo::InterfaceRequest; + +} // 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 binding_; + std::map 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(std::move(table))); + } + + private: + mojo::Binding binding_; + std::vector> tables_; +}; +``` + +Pretty straightforward. The `Table&` Mojom paramter to `AddTable` translates to +a C++ `db::mojom::TableRequest`, aliased from +`mojo::InterfaceRequest`, 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(), + 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 provider_binding_; + mojo::BindingSet 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 listeners_; + std::map 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 { + 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::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`. 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` | `mojo::ScopedMessagePipeHandle` +| `handle` | `mojo::ScopedDataPipeConsumerHandle` +| `handle` | `mojo::ScopedDataPipeProducerHandle` +| `handle` | `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` | Value or reference to any type `T` that has a `mojo::ArrayTraits` specialization defined. By default this includes `std::vector`, `mojo::CArray`, and `WTF::Vector` (Blink). +| `map` | Value or reference to any type `T` that has a `mojo::MapTraits` specialization defined. By default this includes `std::map`, `mojo::unordered_map`, and `WTF::HashMap` (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& 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 0000000..d02a884 --- /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 + +#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 +class ArrayDataViewImpl; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::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 +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::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 +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + static_assert(sizeof(T) == sizeof(int32_t), "Unexpected enum size"); + + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + T operator[](size_t index) const { return static_cast(data_->at(index)); } + + const T* data() const { return reinterpret_cast(data_->storage()); } + + template + bool Read(size_t index, U* output) { + return Deserialize(data_->at(index), output); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + template + U Take(size_t index) { + U result; + bool ret = Deserialize(&data_->at(index), &result, context_); + DCHECK(ret); + return result; + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + T Take(size_t index) { + T result; + bool ret = Deserialize(&data_->at(index), &result, context_); + DCHECK(ret); + return result; + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::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 + bool Read(size_t index, U* output) { + return Deserialize(data_->at(index).Get(), output, context_); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +template +class ArrayDataViewImpl< + T, + typename std::enable_if< + BelongsTo::value>::type> { + public: + using Data_ = typename MojomTypeTraits>::Data; + + ArrayDataViewImpl(Data_* data, SerializationContext* context) + : data_(data), context_(context) {} + + void GetDataView(size_t index, T* output) { + *output = T(&data_->at(index), context_); + } + + template + bool Read(size_t index, U* output) { + return Deserialize(&data_->at(index), output, context_); + } + + protected: + Data_* data_; + SerializationContext* context_; +}; + +} // namespace internal + +template +class MapDataView; + +template +class ArrayDataView : public internal::ArrayDataViewImpl { + public: + using Element = T; + using Data_ = typename internal::ArrayDataViewImpl::Data_; + + ArrayDataView() : internal::ArrayDataViewImpl(nullptr, nullptr) {} + + ArrayDataView(Data_* data, internal::SerializationContext* context) + : internal::ArrayDataViewImpl(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 + // bool Read(size_t index, U* output); + + // Handles: + // T Take(size_t index); + + // Interfaces: + // template + // U Take(size_t index); + + // Object types: + // void GetDataView(size_t index, T* output); + // template + // bool Read(size_t index, U* output); + + private: + template + 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 0000000..594b2e0 --- /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 +// struct ArrayTraits> { +// 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& input); +// static void SetToNull(Container* output); +// +// static size_t GetSize(const Container& 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& input); +// static const T* GetData(const Container& 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& input); +// static Iterator GetBegin(Container& 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& input, size_t index); +// static const T& GetAt(const Container& input, size_t index); +// +// // Returning false results in deserialization failure and causes the +// // message pipe receiving it to be disconnected. +// static bool Resize(Container& input, size_t size); +// }; +// +template +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 0000000..3ff694b --- /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 +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 +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 +struct ArrayTraits> { + using Element = T; + + static bool IsNull(const CArray& input) { return !input.data; } + + static void SetToNull(CArray* output) { output->data = nullptr; } + + static size_t GetSize(const CArray& input) { return input.size; } + + static T* GetData(CArray& input) { return input.data; } + + static const T* GetData(const CArray& input) { return input.data; } + + static T& GetAt(CArray& input, size_t index) { return input.data[index]; } + + static const T& GetAt(const CArray& input, size_t index) { + return input.data[index]; + } + + static bool Resize(CArray& input, size_t size) { + if (size > input.max_size) + return false; + + input.size = size; + return true; + } +}; + +template +struct ArrayTraits> { + using Element = T; + + static bool IsNull(const ConstCArray& input) { return !input.data; } + + static size_t GetSize(const ConstCArray& input) { return input.size; } + + static const T* GetData(const ConstCArray& input) { return input.data; } + + static const T& GetAt(const ConstCArray& 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 0000000..dec47bf --- /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 +#include +#include + +#include "mojo/public/cpp/bindings/array_traits.h" + +namespace mojo { + +template +struct ArrayTraits> { + using Element = T; + + static bool IsNull(const std::vector& input) { + // std::vector<> is always converted to non-null mojom array. + return false; + } + + static void SetToNull(std::vector* output) { + // std::vector<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const std::vector& input) { return input.size(); } + + static T* GetData(std::vector& input) { return input.data(); } + + static const T* GetData(const std::vector& input) { return input.data(); } + + static typename std::vector::reference GetAt(std::vector& input, + size_t index) { + return input[index]; + } + + static typename std::vector::const_reference GetAt( + const std::vector& input, + size_t index) { + return input[index]; + } + + static inline bool Resize(std::vector& input, size_t size) { + // Instead of calling std::vector::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>. + // 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 temp(size); + input.swap(temp); + } + + return true; + } +}; + +// This ArrayTraits specialization is used only for serialization. +template +struct ArrayTraits> { + using Element = T; + using ConstIterator = typename std::set::const_iterator; + + static bool IsNull(const std::set& input) { + // std::set<> is always converted to non-null mojom array. + return false; + } + + static size_t GetSize(const std::set& input) { return input.size(); } + + static ConstIterator GetBegin(const std::set& input) { + return input.begin(); + } + static void AdvanceIterator(ConstIterator& iterator) { + ++iterator; + } + static const T& GetValue(ConstIterator& iterator) { + return *iterator; + } +}; + +template +struct MapValuesArrayView { + explicit MapValuesArrayView(const std::map& map) : map(map) {} + const std::map& map; +}; + +// Convenience function to create a MapValuesArrayView<> that infers the +// template arguments from its argument type. +template +MapValuesArrayView MapValuesToArray(const std::map& map) { + return MapValuesArrayView(map); +} + +// This ArrayTraits specialization is used only for serialization and converts +// a map into an array, discarding the keys. +template +struct ArrayTraits> { + using Element = V; + using ConstIterator = typename std::map::const_iterator; + + static bool IsNull(const MapValuesArrayView& input) { + // std::map<> is always converted to non-null mojom array. + return false; + } + + static size_t GetSize(const MapValuesArrayView& input) { + return input.map.size(); + } + static ConstIterator GetBegin(const MapValuesArrayView& 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 0000000..6e20735 --- /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 +struct ArrayTraits> { + using Element = U; + + static bool IsNull(const WTF::Vector& input) { + // WTF::Vector<> is always converted to non-null mojom array. + return false; + } + + static void SetToNull(WTF::Vector* output) { + // WTF::Vector<> doesn't support null state. Set it to empty instead. + output->clear(); + } + + static size_t GetSize(const WTF::Vector& input) { return input.size(); } + + static U* GetData(WTF::Vector& input) { return input.data(); } + + static const U* GetData(const WTF::Vector& input) { return input.data(); } + + static U& GetAt(WTF::Vector& input, size_t index) { return input[index]; } + + static const U& GetAt(const WTF::Vector& input, size_t index) { + return input[index]; + } + + static bool Resize(WTF::Vector& 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 0000000..5941166 --- /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 +#include +#include + +#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 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 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 payload_validator, + bool expect_sync_requests, + scoped_refptr runner, + uint32_t interface_version); + + std::unique_ptr 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 > +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* ptr_info, + scoped_refptr 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 request, + scoped_refptr 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* ptr_info, + scoped_refptr 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 request, + scoped_refptr 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 Unbind() { + DCHECK(endpoint_client_); + + AssociatedInterfaceRequest 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_ 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 0000000..59600c6 --- /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 +struct BindingSetTraits> { + using ProxyType = AssociatedInterfacePtr; + using RequestType = AssociatedInterfaceRequest; + using BindingType = AssociatedBinding; + using ImplPointerType = typename BindingType::ImplPointerType; +}; + +template +using AssociatedBindingSet = + BindingSetBase, 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 0000000..14e78ec --- /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 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 controller_getter_; + scoped_refptr 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 0000000..d33c277 --- /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 + +#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 { + 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& 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 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; + + // 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 0000000..8806a3e --- /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 + +#include +#include + +#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 +class AssociatedInterfacePtr { + public: + using InterfaceType = Interface; + using PtrInfoType = AssociatedInterfacePtrInfo; + + // 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*) for more + // details. + void Bind(AssociatedInterfacePtrInfo info, + scoped_refptr 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& 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 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* 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 + 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 + bool operator==(const AssociatedInterfacePtr& other) const = delete; + template + bool operator!=(const AssociatedInterfacePtr& other) const = delete; + + typedef internal::AssociatedInterfacePtrState 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 +AssociatedInterfaceRequest MakeRequest( + AssociatedInterfacePtr* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + AssociatedInterfacePtrInfo 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 +AssociatedInterfaceRequest MakeRequest( + AssociatedInterfacePtrInfo* ptr_info) { + ScopedInterfaceEndpointHandle handle0; + ScopedInterfaceEndpointHandle handle1; + ScopedInterfaceEndpointHandle::CreatePairPendingAssociation(&handle0, + &handle1); + + ptr_info->set_handle(std::move(handle0)); + ptr_info->set_version(0); + + AssociatedInterfaceRequest 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 +AssociatedInterfaceRequest MakeIsolatedRequest( + AssociatedInterfacePtr* ptr) { + MessagePipe pipe; + scoped_refptr router0 = + new internal::MultiplexRouter(std::move(pipe.handle0), + internal::MultiplexRouter::MULTI_INTERFACE, + false, base::ThreadTaskRunnerHandle::Get()); + scoped_refptr 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(std::move(endpoint0), + Interface::Version_)); + + AssociatedInterfaceRequest 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 0000000..3c6ca54 --- /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 +#include + +#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 +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 0000000..c37636c --- /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 +#include + +#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 +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 +AssociatedInterfaceRequest MakeAssociatedRequest( + ScopedInterfaceEndpointHandle handle) { + AssociatedInterfaceRequest 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 0000000..88d2f4b --- /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 +#include + +#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 request) +// : binding_(this, std::move(request)) {} +// +// // Foo implementation here. +// +// private: +// Binding binding_; +// }; +// +// class MyFooFactory : public InterfaceFactory { +// public: +// void Create(..., InterfaceRequest 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 > +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 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* ptr, + scoped_refptr 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 request, + scoped_refptr 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 CreateInterfacePtrAndBind( + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + InterfacePtr 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 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* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + MessagePipe pipe; + ptr->Bind(InterfacePtrInfo(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 request, + scoped_refptr 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 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 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 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 0000000..919f9c0 --- /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 +#include + +#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 +struct BindingSetTraits; + +template +struct BindingSetTraits> { + using ProxyType = InterfacePtr; + using RequestType = InterfaceRequest; + using BindingType = Binding; + using ImplPointerType = typename BindingType::ImplPointerType; + + static RequestType MakeRequest(ProxyType* proxy) { + return mojo::MakeRequest(proxy); + } +}; + +using BindingId = size_t; + +template +struct BindingSetContextTraits { + using Type = ContextType; + + static constexpr bool SupportsContext() { return true; } +}; + +template <> +struct BindingSetContextTraits { + // 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 +class BindingSetBase { + public: + using ContextTraits = BindingSetContextTraits; + using Context = typename ContextTraits::Type; + using PreDispatchCallback = base::Callback; + using Traits = BindingSetTraits; + 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(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(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 = 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> bindings_; + const Context* dispatch_context_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(BindingSetBase); +}; + +template +using BindingSet = BindingSetBase, 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 0000000..9fd7a27 --- /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 0000000..203ab34 --- /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 +#include +#include + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { + +template +struct HasCloneMethod { + template + static char Test(decltype(&U::Clone)); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + internal::EnsureTypeIsComplete check_t_; +}; + +template ::value> +struct CloneTraits; + +template +T Clone(const T& input); + +template +struct CloneTraits { + static T Clone(const T& input) { return input.Clone(); } +}; + +template +struct CloneTraits { + static T Clone(const T& input) { return input; } +}; + +template +struct CloneTraits, false> { + static base::Optional Clone(const base::Optional& input) { + if (!input) + return base::nullopt; + + return base::Optional(mojo::Clone(*input)); + } +}; + +template +struct CloneTraits, false> { + static std::vector Clone(const std::vector& input) { + std::vector result; + result.reserve(input.size()); + for (const auto& element : input) + result.push_back(mojo::Clone(element)); + + return result; + } +}; + +template +struct CloneTraits, false> { + static std::unordered_map Clone(const std::unordered_map& input) { + std::unordered_map result; + for (const auto& element : input) { + result.insert(std::make_pair(mojo::Clone(element.first), + mojo::Clone(element.second))); + } + return result; + } +}; + +template +T Clone(const T& input) { + return CloneTraits::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 0000000..306e99e --- /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; + +} // 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 0000000..cb065c1 --- /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 + +#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 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 task_runner_; + std::unique_ptr 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 lock_; + + std::unique_ptr 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 weak_self_; + base::WeakPtrFactory 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 0000000..c04e8ad --- /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 + +#include + +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 0000000..2c528f3 --- /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 { +// 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 +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 0000000..1262f39 --- /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 +#include + +#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 + inline void Append(Args&&... args); + + void Append(std::unique_ptr 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> filters_; + + MessageReceiver* sink_; + + DISALLOW_COPY_AND_ASSIGN(FilterChain); +}; + +template +inline void FilterChain::Append(Args&&... args) { + Append(base::MakeUnique(std::forward(args)...)); +} + +template <> +inline void FilterChain::Append() { +} + +} // 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 0000000..ef12254 --- /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 +class AssociatedInterfacePtrInfoDataView {}; + +template +class AssociatedInterfaceRequestDataView {}; + +template +class InterfacePtrDataView {}; + +template +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 0000000..b519fe9 --- /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 + +#include +#include + +#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 payload_validator, + bool expect_sync_requests, + scoped_refptr 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 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 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& 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& 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>; + + 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>; + + // 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 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 task_runner_; + + internal::ControlMessageProxy control_message_proxy_; + internal::ControlMessageHandler control_message_handler_; + + base::ThreadChecker thread_checker_; + + base::WeakPtrFactory 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 0000000..8d99d4a --- /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 0000000..53475d6 --- /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 + +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 0000000..e88be74 --- /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 + +#include +#include + +#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 +class InterfacePtr { + public: + using InterfaceType = Interface; + using PtrInfoType = InterfacePtrInfo; + + // 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 info, + scoped_refptr 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& 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 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* 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 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 + bool operator==(const InterfacePtr& other) const = delete; + template + bool operator!=(const InterfacePtr& other) const = delete; + + typedef internal::InterfacePtrState 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 +InterfacePtr MakeProxy( + InterfacePtrInfo info, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + InterfacePtr 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 0000000..0b2d808 --- /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 +#include + +#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 +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 0000000..09a2682 --- /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 +#include + +#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 class Ptr> +class PtrSet { + public: + PtrSet() {} + ~PtrSet() { CloseAll(); } + + void AddPtr(Ptr ptr) { + auto weak_interface_ptr = new Element(std::move(ptr)); + ptrs_.push_back(weak_interface_ptr->GetWeakPtr()); + ClearNullPtrs(); + } + + template + 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 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 GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + static void DeleteElement(Element* element) { delete element; } + + Ptr ptr_; + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(Element); + }; + + void ClearNullPtrs() { + ptrs_.erase(std::remove_if(ptrs_.begin(), ptrs_.end(), + [](const base::WeakPtr& p) { + return p.get() == nullptr; + }), + ptrs_.end()); + } + + std::vector> ptrs_; +}; + +} // namespace internal + +template +using InterfacePtrSet = internal::PtrSet; + +template +using AssociatedInterfacePtrSet = + internal::PtrSet; + +} // 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 0000000..29d8836 --- /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 +#include + +#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 +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* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + MessagePipe pipe; + ptr->Bind(InterfacePtrInfo(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 +InterfaceRequest MakeRequest(ScopedMessagePipeHandle handle) { + InterfaceRequest 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_request(&source); +// collector->RegisterSource(std::move(source)); +// CreateSource(std::move(source_request)); // Create implementation locally. +// +template +InterfaceRequest MakeRequest( + InterfacePtr* ptr, + scoped_refptr runner = + base::ThreadTaskRunnerHandle::Get()) { + return InterfaceRequest(ptr, runner); +} + +// Fuses an InterfaceRequest endpoint with an InterfacePtrInfo endpoint. +// Returns |true| on success or |false| on failure. +template +bool FuseInterface(InterfaceRequest request, + InterfacePtrInfo 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 0000000..dd24eac --- /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 +#include + +#include + +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::BitRef::~BitRef() { +} + +ArrayDataTraits::BitRef::BitRef(uint8_t* storage, uint8_t mask) + : storage_(storage), mask_(mask) { +} + +ArrayDataTraits::BitRef& ArrayDataTraits::BitRef::operator=( + bool value) { + if (value) { + *storage_ |= mask_; + } else { + *storage_ &= ~mask_; + } + return *this; +} + +ArrayDataTraits::BitRef& ArrayDataTraits::BitRef::operator=( + const BitRef& value) { + return (*this) = static_cast(value); +} + +ArrayDataTraits::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 0000000..eecfcfb --- /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 +#include + +#include +#include + +#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 +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 +struct ArrayDataTraits { + using StorageType = T; + using Ref = T&; + using ConstRef = const T&; + + static const uint32_t kMaxNumElements = + (std::numeric_limits::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 { + // 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; + 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::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. 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 +struct ArraySerializationHelper; + +template +struct ArraySerializationHelper { + using ElementType = typename ArrayDataTraits::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 +struct ArraySerializationHelper { + using ElementType = typename ArrayDataTraits::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::value || + std::is_same::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 +struct ArraySerializationHelper, false, false> { + using ElementType = typename ArrayDataTraits>::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::Run(elements[i], validation_context, + validate_params->element_validate_params)) { + return false; + } + } + return true; + } + + private: + template ::value || + IsSpecializationOf::value> + struct ValidateCaller { + static bool Run(const Pointer& 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 + struct ValidateCaller { + static bool Run(const Pointer& data, + ValidationContext* validation_context, + const ContainerValidateParams* validate_params) { + return ValidateContainer(data, validation_context, validate_params); + } + }; +}; + +template +struct ArraySerializationHelper { + using ElementType = typename ArrayDataTraits::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 +class Array_Data { + public: + using Traits = ArrayDataTraits; + using StorageType = typename Traits::StorageType; + using Ref = typename Traits::Ref; + using ConstRef = typename Traits::ConstRef; + using Helper = ArraySerializationHelper< + T, + IsUnionDataType::value, + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value>; + using Element = T; + + // Returns null if |num_elements| or the corresponding storage size cannot be + // stored in uint32_t. + static Array_Data* New(size_t num_elements, Buffer* buf) { + if (num_elements > Traits::kMaxNumElements) + return nullptr; + + uint32_t num_bytes = + Traits::GetStorageSize(static_cast(num_elements)); + return new (buf->Allocate(num_bytes)) + Array_Data(num_bytes, static_cast(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(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* object = static_cast*>(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(header_.num_elements)); + return Traits::ToRef(storage(), offset); + } + + ConstRef at(size_t offset) const { + DCHECK(offset < static_cast(header_.num_elements)); + return Traits::ToConstRef(storage(), offset); + } + + StorageType* storage() { + return reinterpret_cast(reinterpret_cast(this) + + sizeof(*this)); + } + + const StorageType* storage() const { + return reinterpret_cast( + reinterpret_cast(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::StorageType follow. +}; +static_assert(sizeof(Array_Data) == 8, "Bad sizeof(Array_Data)"); + +// UTF-8 encoded +using String_Data = Array_Data; + +} // 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 0000000..d2f8ecf --- /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 +#include // For |memcpy()|. + +#include +#include +#include +#include + +#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 ::value> +class ArrayIterator {}; + +// Used as the UserTypeIterator template parameter of ArraySerializer. +template +class ArrayIterator { + public: + using IteratorType = decltype( + CallGetBeginIfExists(std::declval())); + + explicit ArrayIterator(MaybeConstUserType& input) + : input_(input), iter_(CallGetBeginIfExists(input)) {} + ~ArrayIterator() {} + + size_t GetSize() const { return Traits::GetSize(input_); } + + using GetNextResult = + decltype(Traits::GetValue(std::declval())); + GetNextResult GetNext() { + GetNextResult value = Traits::GetValue(iter_); + Traits::AdvanceIterator(iter_); + return value; + } + + using GetDataIfExistsResult = decltype( + CallGetDataIfExists(std::declval())); + GetDataIfExistsResult GetDataIfExists() { + return CallGetDataIfExists(input_); + } + + private: + MaybeConstUserType& input_; + IteratorType iter_; +}; + +// Used as the UserTypeIterator template parameter of ArraySerializer. +template +class ArrayIterator { + 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(), 0)); + GetNextResult GetNext() { + DCHECK_LT(iter_, Traits::GetSize(input_)); + return Traits::GetAt(input_, iter_++); + } + + using GetDataIfExistsResult = decltype( + CallGetDataIfExists(std::declval())); + GetDataIfExistsResult GetDataIfExists() { + return CallGetDataIfExists(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 +struct ArraySerializer; + +// Handles serialization and deserialization of arrays of pod types. +template +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using DataElement = typename Data::Element; + using Element = typename MojomType::Element; + using Traits = ArrayTraits; + + static_assert(std::is_same::value, + "Incorrect array serializer"); + static_assert(std::is_same::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 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 +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using DataElement = typename Data::Element; + using Element = typename MojomType::Element; + using Traits = ArrayTraits; + + 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(input->GetNext(), output->storage() + i); + } + + static bool DeserializeElements(Data* input, + UserType* output, + SerializationContext* context) { + if (!Traits::Resize(*output, input->size())) + return false; + ArrayIterator iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize(input->at(i), &iterator.GetNext())) + return false; + } + return true; + } +}; + +// Serializes and deserializes arrays of bools. +template +struct ArraySerializer::value>::type> { + using UserType = typename std::remove_const::type; + using Traits = ArrayTraits; + using Data = typename MojomTypeTraits::Data; + + static_assert(std::is_same::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 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 +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if< + BelongsTo::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using Element = typename MojomType::Element; + using Traits = ArrayTraits; + + static size_t GetSerializedSize(UserTypeIterator* input, + SerializationContext* context) { + size_t element_count = input->GetSize(); + if (BelongsTo::value) { + for (size_t i = 0; i < element_count; ++i) { + typename UserTypeIterator::GetNextResult next = input->GetNext(); + size_t size = PrepareToSerialize(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(next, &output->at(i), context); + + static const ValidationError kError = + BelongsTo::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 iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + bool result = + Deserialize(&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 +struct ArraySerializer::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using Element = typename MojomType::Element; + using DataElementPtr = typename MojomTypeTraits::Data*; + using Traits = ArrayTraits; + + 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(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::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 iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize(input->at(i).Get(), &iterator.GetNext(), + context)) + return false; + } + return true; + } + + private: + template ::value> + struct SerializeCaller { + template + static void Run(InputElementType&& input, + Buffer* buf, + DataElementPtr* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + Serialize(std::forward(input), buf, output, context); + } + }; + + template + struct SerializeCaller { + template + static void Run(InputElementType&& input, + Buffer* buf, + DataElementPtr* output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + Serialize(std::forward(input), buf, output, + validate_params, context); + } + }; +}; + +// Handles serialization and deserialization of arrays of unions. +template +struct ArraySerializer< + MojomType, + MaybeConstUserType, + UserTypeIterator, + typename std::enable_if::value>::type> { + using UserType = typename std::remove_const::type; + using Data = typename MojomTypeTraits::Data; + using Element = typename MojomType::Element; + using Traits = ArrayTraits; + + 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(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(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 iterator(*output); + for (size_t i = 0; i < input->size(); ++i) { + if (!Deserialize(&input->at(i), &iterator.GetNext(), context)) + return false; + } + return true; + } +}; + +template +struct Serializer, MaybeConstUserType> { + using UserType = typename std::remove_const::type; + using Traits = ArrayTraits; + using Impl = ArraySerializer, + MaybeConstUserType, + ArrayIterator>; + using Data = typename MojomTypeTraits>::Data; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists(input)) + return 0; + ArrayIterator iterator(input); + return Impl::GetSerializedSize(&iterator, context); + } + + static void Serialize(MaybeConstUserType& input, + Buffer* buf, + Data** output, + const ContainerValidateParams* validate_params, + SerializationContext* context) { + if (!CallIsNullIfExists(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 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(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 0000000..6788e68 --- /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 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 payload_validator, + bool expect_sync_requests, + scoped_refptr 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 0000000..3e95eeb --- /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 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 0000000..f4a9aa2 --- /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 0000000..78281ed --- /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 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 0000000..a4b5188 --- /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 + +#include // For |std::swap()|. +#include +#include +#include + +#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 +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& 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 info, + scoped_refptr 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 PassInterface() { + ScopedInterfaceEndpointHandle handle = endpoint_client_->PassHandle(); + endpoint_client_.reset(); + proxy_.reset(); + return AssociatedInterfacePtrInfo(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 responder) { + endpoint_client_->AcceptWithResponder(&message, std::move(responder)); + } + + private: + using Proxy = typename Interface::Proxy_; + + void OnQueryVersion(const base::Callback& callback, + uint32_t version) { + version_ = version; + callback.Run(version); + } + + std::unique_ptr endpoint_client_; + std::unique_ptr 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 0000000..b34cb47 --- /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 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 runner, + const char* interface_name, + std::unique_ptr 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 0000000..0b0dbee --- /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 +#include +#include + +#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 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 runner, + const char* interface_name, + std::unique_ptr request_validator, + bool passes_associated_kinds, + bool has_sync_methods, + MessageReceiverWithResponderStatus* stub, + uint32_t interface_version); + + scoped_refptr router_; + std::unique_ptr endpoint_client_; +}; + +template +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 runner) { + BindingStateBase::BindInternal( + std::move(handle), runner, Interface::Name_, + base::MakeUnique(), + Interface::PassesAssociatedKinds_, Interface::HasSyncMethods_, &stub_, + Interface::Version_); + } + + InterfaceRequest Unbind() { + endpoint_client_.reset(); + InterfaceRequest request = + MakeRequest(router_->PassMessagePipe()); + router_ = nullptr; + return request; + } + + Interface* impl() { return ImplRefTraits::GetRawPointer(&stub_.sink()); } + + private: + typename Interface::template Stub_ 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 0000000..631daec --- /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 + +#include + +#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 +class ArrayDataView; + +template +class AssociatedInterfacePtrInfoDataView; + +template +class AssociatedInterfaceRequestDataView; + +template +class InterfacePtrDataView; + +template +class InterfaceRequestDataView; + +template +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(-1); + +// A serialized union always takes 16 bytes: +// 4-byte size + 4-byte tag + 8-byte payload. +const uint32_t kUnionDataSize = 16; + +template +class Array_Data; + +template +class Map_Data; + +class NativeStruct_Data; + +using String_Data = Array_Data; + +inline size_t Align(size_t size) { + return (size + 7) & ~0x7; +} + +inline bool IsAligned(const void* ptr) { + return !(reinterpret_cast(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(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(ptr); + const char* p_slot = reinterpret_cast(offset); + DCHECK(p_obj > p_slot); + + *offset = static_cast(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(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 +struct Pointer { + using BaseType = T; + + void Set(T* ptr) { EncodePointer(ptr, &offset); } + const T* Get() const { return static_cast(DecodePointer(&offset)); } + T* Get() { + return static_cast(const_cast(DecodePointer(&offset))); + } + + bool is_null() const { return offset == 0; } + + uint64_t offset; +}; +static_assert(sizeof(Pointer) == 8, "Bad_sizeof(Pointer)"); + +using GenericPointer = Pointer; + +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 +T FetchAndReset(T* ptr) { + T temp = *ptr; + *ptr = T(); + return temp; +} + +template +struct IsUnionDataType { + private: + template + static YesType Test(const typename U::MojomUnionDataType*); + + template + static NoType Test(...); + + EnsureTypeIsComplete check_t_; + + public: + static const bool value = + sizeof(Test(0)) == sizeof(YesType) && !IsConst::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(static_cast(x) & + static_cast(y)); +} + +inline constexpr MojomTypeCategory operator|(MojomTypeCategory x, + MojomTypeCategory y) { + return static_cast(static_cast(x) | + static_cast(y)); +} + +template ::value> +struct MojomTypeTraits { + using Data = T; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::POD; +}; + +template +struct MojomTypeTraits, false> { + using Data = Array_Data::DataAsArrayElement>; + using DataAsArrayElement = Pointer; + + static const MojomTypeCategory category = MojomTypeCategory::ARRAY; +}; + +template +struct MojomTypeTraits, false> { + using Data = AssociatedInterface_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::ASSOCIATED_INTERFACE; +}; + +template +struct MojomTypeTraits, false> { + using Data = AssociatedEndpointHandle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::ASSOCIATED_INTERFACE_REQUEST; +}; + +template <> +struct MojomTypeTraits { + using Data = bool; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::BOOLEAN; +}; + +template +struct MojomTypeTraits { + using Data = int32_t; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::ENUM; +}; + +template +struct MojomTypeTraits, false> { + using Data = Handle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::HANDLE; +}; + +template +struct MojomTypeTraits, false> { + using Data = Interface_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = MojomTypeCategory::INTERFACE; +}; + +template +struct MojomTypeTraits, false> { + using Data = Handle_Data; + using DataAsArrayElement = Data; + + static const MojomTypeCategory category = + MojomTypeCategory::INTERFACE_REQUEST; +}; + +template +struct MojomTypeTraits, false> { + using Data = Map_Data::DataAsArrayElement, + typename MojomTypeTraits::DataAsArrayElement>; + using DataAsArrayElement = Pointer; + + static const MojomTypeCategory category = MojomTypeCategory::MAP; +}; + +template <> +struct MojomTypeTraits { + using Data = internal::NativeStruct_Data; + using DataAsArrayElement = Pointer; + + static const MojomTypeCategory category = MojomTypeCategory::STRUCT; +}; + +template <> +struct MojomTypeTraits { + using Data = String_Data; + using DataAsArrayElement = Pointer; + + static const MojomTypeCategory category = MojomTypeCategory::STRING; +}; + +template +struct BelongsTo { + static const bool value = + static_cast(MojomTypeTraits::category & categories) != 0; +}; + +template +struct EnumHashImpl { + static_assert(std::is_enum::value, "Incorrect hash function."); + + size_t operator()(T input) const { + using UnderlyingType = typename base::underlying_type::type; + return std::hash()(static_cast(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 0000000..213a445 --- /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 + +#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(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(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 0000000..d93e45e --- /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 +#include + +#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>::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); + ~ActiveDispatchTracker(); + + void NotifyBeginNesting(); + + private: + const base::WeakPtr 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( + 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), 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 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 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 weak_self = weak_self_; + + Message message; + const MojoResult rv = ReadMessage(message_pipe_.get(), &message); + *read_result = rv; + + if (rv == MOJO_RESULT_OK) { + base::Optional 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 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 0000000..1b7bb78 --- /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 +#include +#include + +#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 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 responder) { + interface_control::internal::RunMessageParams_Data* params = + reinterpret_cast( + message->mutable_payload()); + interface_control::RunMessageParamsPtr params_ptr; + Deserialize(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( + 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( + 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( + 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 0000000..5d1f716 --- /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 + +#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 responder) override; + + private: + bool Run(Message* message, + std::unique_ptr 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 0000000..d082b49 --- /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 +#include +#include + +#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; + +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( + 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( + params_ptr, &context); + MessageBuilder builder(interface_control::kRunMessageId, + Message::kFlagExpectsResponse, size, 0); + + interface_control::internal::RunMessageParams_Data* params = nullptr; + Serialize( + params_ptr, builder.buffer(), ¶ms, &context); + std::unique_ptr responder = + base::MakeUnique(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( + 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& 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& 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 0000000..2f9314e --- /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 + +#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& 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 0000000..53c7dce --- /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 +#include +#include + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { +namespace internal { + +template +struct HasEqualsMethod { + template + static char Test(decltype(&U::Equals)); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template ::value> +struct EqualsTraits; + +template +bool Equals(const T& a, const T& b); + +template +struct EqualsTraits { + static bool Equals(const T& a, const T& b) { return a.Equals(b); } +}; + +template +struct EqualsTraits { + static bool Equals(const T& a, const T& b) { return a == b; } +}; + +template +struct EqualsTraits, false> { + static bool Equals(const base::Optional& a, const base::Optional& b) { + if (!a && !b) + return true; + if (!a || !b) + return false; + + return internal::Equals(*a, *b); + } +}; + +template +struct EqualsTraits, false> { + static bool Equals(const std::vector& a, const std::vector& 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 +struct EqualsTraits, false> { + static bool Equals(const std::unordered_map& a, + const std::unordered_map& 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 +bool Equals(const T& a, const T& b) { + return EqualsTraits::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 0000000..5d919fe --- /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 + +#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 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 0000000..725a193 --- /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 + +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 0000000..070b0c8 --- /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 + +#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 0000000..14ed21f --- /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 + +#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 +struct Serializer, + AssociatedInterfacePtrInfo> { + static_assert(std::is_base_of::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const AssociatedInterfacePtrInfo& input, + SerializationContext* context) { + if (input.handle().is_valid()) + context->associated_endpoint_count++; + return 0; + } + + static void Serialize(AssociatedInterfacePtrInfo& 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(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* 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 +struct Serializer, + AssociatedInterfaceRequest> { + static_assert(std::is_base_of::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const AssociatedInterfaceRequest& input, + SerializationContext* context) { + if (input.handle().is_valid()) + context->associated_endpoint_count++; + return 0; + } + + static void Serialize(AssociatedInterfaceRequest& 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(context->associated_endpoint_handles.size()); + context->associated_endpoint_handles.push_back(input.PassHandle()); + } else { + output->value = kEncodedInvalidHandleValue; + } + } + + static bool Deserialize(AssociatedEndpointHandle_Data* input, + AssociatedInterfaceRequest* 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 +struct Serializer, InterfacePtr> { + static_assert(std::is_base_of::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const InterfacePtr& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(InterfacePtr& input, + Interface_Data* output, + SerializationContext* context) { + InterfacePtrInfo info = input.PassInterface(); + output->handle = context->handles.AddHandle(info.PassHandle().release()); + output->version = info.version(); + } + + static bool Deserialize(Interface_Data* input, + InterfacePtr* output, + SerializationContext* context) { + output->Bind(InterfacePtrInfo( + context->handles.TakeHandleAs(input->handle), + input->version)); + return true; + } +}; + +template +struct Serializer, InterfaceRequest> { + static_assert(std::is_base_of::value, "Interface type mismatch."); + + static size_t PrepareToSerialize(const InterfaceRequest& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(InterfaceRequest& input, + Handle_Data* output, + SerializationContext* context) { + *output = context->handles.AddHandle(input.PassMessagePipe().release()); + } + + static bool Deserialize(Handle_Data* input, + InterfaceRequest* output, + SerializationContext* context) { + output->Bind(context->handles.TakeHandleAs(*input)); + return true; + } +}; + +template +struct Serializer, ScopedHandleBase> { + static size_t PrepareToSerialize(const ScopedHandleBase& input, + SerializationContext* context) { + return 0; + } + + static void Serialize(ScopedHandleBase& input, + Handle_Data* output, + SerializationContext* context) { + *output = context->handles.AddHandle(input.release()); + } + + static bool Deserialize(Handle_Data* input, + ScopedHandleBase* output, + SerializationContext* context) { + *output = context->handles.TakeHandleAs(*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 0000000..93280d6 --- /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 +#include +#include +#include + +#include "base/optional.h" +#include "mojo/public/cpp/bindings/lib/template_util.h" + +namespace mojo { +namespace internal { + +template +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()(value) + (seed << 6) + (seed >> 2)); +} + +template +struct HasHashMethod { + template + static char Test(decltype(&U::Hash)); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template ::value> +struct HashTraits; + +template +size_t Hash(size_t seed, const T& value); + +template +struct HashTraits { + static size_t Hash(size_t seed, const T& value) { return value.Hash(seed); } +}; + +template +struct HashTraits { + static size_t Hash(size_t seed, const T& value) { + return HashCombine(seed, value); + } +}; + +template +struct HashTraits, false> { + static size_t Hash(size_t seed, const std::vector& value) { + for (const auto& element : value) { + seed = HashCombine(seed, element); + } + return seed; + } +}; + +template +struct HashTraits>, false> { + static size_t Hash(size_t seed, const base::Optional>& value) { + if (!value) + return HashCombine(seed, 0); + + return Hash(seed, *value); + } +}; + +template +size_t Hash(size_t seed, const T& value) { + return HashTraits::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 0000000..4682e72 --- /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 + +#include + +#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& 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& endpoint_client, + scoped_refptr 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 endpoint_client_; + bool accept_was_invoked_; + scoped_refptr 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 payload_validator, + bool expect_sync_requests, + scoped_refptr 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(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 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 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(&response_received))); + + base::WeakPtr 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& 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& 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 responder = + base::MakeUnique(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 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 0000000..fa54979 --- /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 + +#include // For |std::swap()|. +#include +#include +#include + +#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 +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& 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 info, + scoped_refptr 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 PassInterface() { + endpoint_client_.reset(); + proxy_.reset(); + return InterfacePtrInfo( + 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 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& callback, + uint32_t version) { + version_ = version; + callback.Run(version); + } + + scoped_refptr router_; + + std::unique_ptr endpoint_client_; + std::unique_ptr 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 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 0000000..f8e3d29 --- /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 +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(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> keys; + Pointer> values; + + private: + Map_Data() { + header_.num_bytes = sizeof(*this); + header_.version = 0; + } + ~Map_Data() = delete; +}; +static_assert(sizeof(Map_Data) == 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 0000000..718a763 --- /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 +#include + +#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 +class MapReaderBase { + public: + using UserType = typename std::remove_const::type; + using Traits = MapTraits; + using MaybeConstIterator = + decltype(Traits::GetBegin(std::declval())); + + 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 +class MapKeyReader : public MapReaderBase { + public: + using Base = MapReaderBase; + using Traits = typename Base::Traits; + using MaybeConstIterator = typename Base::MaybeConstIterator; + + explicit MapKeyReader(MaybeConstUserType& input) : Base(input) {} + ~MapKeyReader() {} + + using GetNextResult = + decltype(Traits::GetKey(std::declval())); + GetNextResult GetNext() { + GetNextResult key = Traits::GetKey(this->iter_); + Traits::AdvanceIterator(this->iter_); + return key; + } +}; + +// Used as the UserTypeReader template parameter of ArraySerializer. +template +class MapValueReader : public MapReaderBase { + public: + using Base = MapReaderBase; + using Traits = typename Base::Traits; + using MaybeConstIterator = typename Base::MaybeConstIterator; + + explicit MapValueReader(MaybeConstUserType& input) : Base(input) {} + ~MapValueReader() {} + + using GetNextResult = + decltype(Traits::GetValue(std::declval())); + GetNextResult GetNext() { + GetNextResult value = Traits::GetValue(this->iter_); + Traits::AdvanceIterator(this->iter_); + return value; + } +}; + +template +struct Serializer, MaybeConstUserType> { + using UserType = typename std::remove_const::type; + using Traits = MapTraits; + using UserKey = typename Traits::Key; + using UserValue = typename Traits::Value; + using Data = typename MojomTypeTraits>::Data; + using KeyArraySerializer = ArraySerializer, + std::vector, + MapKeyReader>; + using ValueArraySerializer = + ArraySerializer, + std::vector, + MapValueReader>; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists(input)) + return 0; + + size_t struct_overhead = sizeof(Data); + MapKeyReader key_reader(input); + size_t keys_size = + KeyArraySerializer::GetSerializedSize(&key_reader, context); + MapValueReader 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(input)) { + *output = nullptr; + return; + } + + auto result = Data::New(buf); + if (result) { + auto keys_ptr = MojomTypeTraits>::Data::New( + Traits::GetSize(input), buf); + if (keys_ptr) { + MapKeyReader 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>::Data::New( + Traits::GetSize(input), buf); + if (values_ptr) { + MapValueReader 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(output); + + std::vector keys; + std::vector 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 0000000..06091fe --- /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* 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* 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 0000000..e5f3808 --- /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 +#include +#include + +#include +#include + +#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>:: + DestructorAtExit g_tls_message_dispatch_context = LAZY_INSTANCE_INITIALIZER; + +base::LazyInstance>:: + 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* 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(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(header_v2()->payload.Get()); + if (!payload) { + num_bytes = 0; + } else { + auto payload_end = + reinterpret_cast(header_v2()->payload_interface_ids.Get()); + if (!payload_end) + payload_end = reinterpret_cast(data() + data_num_bytes()); + DCHECK_GE(payload_end, payload); + num_bytes = payload_end - payload; + } + } + DCHECK_LE(num_bytes, std::numeric_limits::max()); + return static_cast(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(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(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::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::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 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(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 0000000..cc12ef6 --- /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 + +#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::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 0000000..96d5140 --- /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 + +#include + +#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 0000000..6806a73 --- /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 +void Allocate(Buffer* buf, Header** header) { + *header = static_cast(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::GetStorageSize( + static_cast(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(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 0000000..8a4d5c4 --- /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 +#include + +#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 0000000..9f8c627 --- /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(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 0000000..6693198 --- /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 + +#include + +#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 +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> 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& GetBadMessageCallback(); + + private: + MessageDispatchContext* outer_context_; + Message* message_; + base::Callback 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 0000000..ff7c678 --- /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 + +#include + +#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, + 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& disconnect_reason() const { + return disconnect_reason_; + } + void set_disconnect_reason( + const base::Optional& 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 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() override { + router_->AssertLockAcquired(); + + DCHECK(!client_); + DCHECK(closed_); + DCHECK(peer_closed_); + DCHECK(!sync_watcher_); + } + + void OnSyncEventSignaled() { + DCHECK(task_runner_->BelongsToCurrentThread()); + scoped_refptr router_protector(router_); + + MayAutoLock locker(&router_->lock_); + scoped_refptr 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 disconnect_reason_; + + // The task runner on which |client_|'s methods can be called. + scoped_refptr 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 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 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 CreateMessageTask( + MessageWrapper message_wrapper) { + Task* task = new Task(MESSAGE); + task->message_wrapper = std::move(message_wrapper); + return base::WrapUnique(task); + } + static std::unique_ptr 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 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 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 header_validator = + base::MakeUnique(); + 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& 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 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 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& 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 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(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 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 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 0000000..cac138b --- /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 + +#include +#include +#include +#include + +#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 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& reason) override; + InterfaceEndpointController* AttachEndpointClient( + const ScopedInterfaceEndpointHandle& handle, + InterfaceEndpointClient* endpoint_client, + scoped_refptr 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& 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 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 lock_; + PipeControlMessageHandler control_message_handler_; + + // NOTE: It is unsafe to call into this object while holding |lock_|. + PipeControlMessageProxy control_message_proxy_; + + std::map> endpoints_; + uint32_t next_interface_id_value_; + + std::deque> tasks_; + // It refers to tasks in |tasks_| and doesn't own any of them. + std::map> sync_message_tasks_; + + bool posted_to_process_tasks_; + scoped_refptr 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 0000000..dcafce2 --- /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 0000000..4faf957 --- /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 +#include + +#include + +#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 +struct NativeEnumSerializerImpl { + using UserType = typename std::remove_const::type; + using Traits = IPC::ParamTraits; + + // 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(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(&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(input); + } + static bool Deserialize(int32_t input, NativeEnum* output) { + *output = static_cast(input); + return true; + } +}; + +template <> +struct NativeEnumSerializerImpl + : public UnmappedNativeEnumSerializerImpl {}; + +template <> +struct NativeEnumSerializerImpl + : public UnmappedNativeEnumSerializerImpl {}; + +template +struct Serializer + : public NativeEnumSerializerImpl {}; + +} // 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 0000000..7b1a1a6 --- /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 0000000..0e5d245 --- /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::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 0000000..1c7cd81 --- /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 + +#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 data; + + private: + NativeStruct_Data() = delete; + ~NativeStruct_Data() = delete; +}; + +static_assert(sizeof(Array_Data) == sizeof(NativeStruct_Data), + "Mismatched NativeStruct_Data and Array_Data 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 0000000..fa0dbf3 --- /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>(input->data, + context); +} + +// static +void UnmappedNativeStructSerializerImpl::Serialize( + const NativeStructPtr& input, + Buffer* buffer, + NativeStruct_Data** output, + SerializationContext* context) { + if (!input) { + *output = nullptr; + return; + } + + Array_Data* data = nullptr; + const ContainerValidateParams params(0, false, nullptr); + internal::Serialize>(input->data, buffer, &data, + ¶ms, context); + *output = reinterpret_cast(data); +} + +// static +bool UnmappedNativeStructSerializerImpl::Deserialize( + NativeStruct_Data* input, + NativeStructPtr* output, + SerializationContext* context) { + Array_Data* data = reinterpret_cast*>(input); + + NativeStructPtr result(NativeStruct::New()); + if (!internal::Deserialize>(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 0000000..457435b --- /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 +#include + +#include + +#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 +struct NativeStructSerializerImpl { + using UserType = typename std::remove_const::type; + using Traits = IPC::ParamTraits; + + 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::max()); + + // Allocate a uint8 array, initialize its header, and copy the Pickle in. + ArrayHeader* header = + reinterpret_cast(buffer->Allocate(total_size)); + header->num_bytes = static_cast(total_size); + header->num_elements = static_cast(pickle.payload_size()); + memcpy(reinterpret_cast(header) + sizeof(ArrayHeader), + pickle.payload(), pickle.payload_size()); + + *out = reinterpret_cast(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(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(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 + : public UnmappedNativeStructSerializerImpl {}; + +template <> +struct NativeStructSerializerImpl + : public UnmappedNativeStructSerializerImpl {}; + +template +struct Serializer + : public NativeStructSerializerImpl {}; + +} // 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 0000000..d451c05 --- /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( + 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 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 0000000..1029c2c --- /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 +#include + +#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( + 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& reason) { + Message message(ConstructPeerEndpointClosedMessage(id, reason)); + ignore_result(receiver_->Accept(&message)); +} + +// static +Message PipeControlMessageProxy::ConstructPeerEndpointClosedMessage( + InterfaceId id, + const base::Optional& 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 0000000..c134507 --- /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 { + public: + State() = default; + + State(InterfaceId id, + scoped_refptr group_controller) + : id_(id), group_controller_(group_controller) {} + + void InitPendingState(scoped_refptr peer) { + DCHECK(!lock_); + DCHECK(!pending_association_); + + lock_.emplace(); + pending_association_ = true; + peer_state_ = std::move(peer); + } + + void Close(const base::Optional& reason) { + scoped_refptr cached_group_controller; + InterfaceId cached_id = kInvalidInterfaceId; + scoped_refptr 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 peer_group_controller) { + scoped_refptr 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& disconnect_reason() const { + internal::MayAutoLock locker(&lock_); + return disconnect_reason_; + } + + private: + friend class base::RefCountedThreadSafe; + + ~State() { + DCHECK(!pending_association_); + DCHECK(!IsValidInterfaceId(id_)); + } + + // Called by the peer, maybe from a different thread. + void OnAssociated(InterfaceId id, + scoped_refptr 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& 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 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 lock_; + + bool pending_association_ = false; + base::Optional disconnect_reason_; + + scoped_refptr peer_state_; + + AssociationEventCallback association_event_handler_; + scoped_refptr runner_; + + InterfaceId id_ = kInvalidInterfaceId; + scoped_refptr 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& +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 group_controller) + : state_(new State(id, std::move(group_controller))) { + DCHECK(!IsValidInterfaceId(state_->id()) || state_->group_controller()); +} + +bool ScopedInterfaceEndpointHandle::NotifyAssociation( + InterfaceId id, + scoped_refptr peer_group_controller) { + return state_->NotifyAssociation(id, peer_group_controller); +} + +void ScopedInterfaceEndpointHandle::ResetInternal( + const base::Optional& reason) { + scoped_refptr new_state(new State); + state_->Close(reason); + state_.swap(new_state); +} + +base::Callback +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 0000000..2a7d288 --- /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 + +#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 +DataArrayType StructSerializeImpl(UserType* input) { + static_assert(BelongsTo::value, + "Unexpected type."); + + SerializationContext context; + size_t size = PrepareToSerialize(*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::Data* data = nullptr; + Serialize(*input, &buffer, &data, &context); + + if (need_copy) { + memcpy(&result.front(), result_buffer, size); + free(result_buffer); + } + + return result; +} + +template +bool StructDeserializeImpl(const DataArrayType& input, + UserType* output, + bool (*validate_func)(const void*, + ValidationContext*)) { + static_assert(BelongsTo::value, + "Unexpected type."); + using DataType = typename MojomTypeTraits::Data; + + // TODO(sammc): Use DataArrayType::empty() once WTF::Vector::empty() exists. + void* input_buffer = + input.size() == 0 + ? nullptr + : const_cast(reinterpret_cast(&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(input_buffer); + SerializationContext context; + result = Deserialize(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 0000000..e2fd5c6 --- /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 + +#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::max()); + data.value = static_cast(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* 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 0000000..a34fe3d --- /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 + +#include +#include +#include + +#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 + ScopedHandleBase 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* other); + + private: + // Handles are owned by this object. + std::vector 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> 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 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 0000000..55c9982 --- /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 +struct Serializer; + +template +struct IsOptionalWrapper { + static const bool value = IsSpecializationOf< + base::Optional, + typename std::remove_const< + typename std::remove_reference::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 ::value>::type* = nullptr> +size_t PrepareToSerialize(InputUserType&& input, Args&&... args) { + return Serializer::type>:: + PrepareToSerialize(std::forward(input), + std::forward(args)...); +} + +template ::value>::type* = nullptr> +void Serialize(InputUserType&& input, Args&&... args) { + Serializer::type>:: + Serialize(std::forward(input), + std::forward(args)...); +} + +template ::value>::type* = nullptr> +bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) { + return Serializer::Deserialize( + std::forward(input), output, std::forward(args)...); +} + +// Specialization that unwraps base::Optional<>. +template ::value>::type* = nullptr> +size_t PrepareToSerialize(InputUserType&& input, Args&&... args) { + if (!input) + return 0; + return PrepareToSerialize(*input, std::forward(args)...); +} + +template ::value>::type* = nullptr> +void Serialize(InputUserType&& input, + Buffer* buffer, + DataType** output, + Args&&... args) { + if (!input) { + *output = nullptr; + return; + } + Serialize(*input, buffer, output, std::forward(args)...); +} + +template ::value>::type* = nullptr> +bool Deserialize(DataType&& input, InputUserType* output, Args&&... args) { + if (!input) { + *output = base::nullopt; + return true; + } + if (!*output) + output->emplace(); + return Deserialize(std::forward(input), &output->value(), + std::forward(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 0000000..4820a01 --- /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 +#include + +#include + +#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 +struct HasIsNullMethod { + template + static char Test(decltype(U::IsNull)*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template < + typename Traits, + typename UserType, + typename std::enable_if::value>::type* = nullptr> +bool CallIsNullIfExists(const UserType& input) { + return Traits::IsNull(input); +} + +template < + typename Traits, + typename UserType, + typename std::enable_if::value>::type* = nullptr> +bool CallIsNullIfExists(const UserType& input) { + return false; +} +template +struct HasSetToNullMethod { + template + static char Test(decltype(U::SetToNull)*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template < + typename Traits, + typename UserType, + typename std::enable_if::value>::type* = nullptr> +bool CallSetToNullIfExists(UserType* output) { + Traits::SetToNull(output); + return true; +} + +template ::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 +struct HasSetUpContextMethod { + template + static char Test(decltype(U::SetUpContext)*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template ::value> +struct CustomContextHelper; + +template +struct CustomContextHelper { + template + static void* SetUp(MaybeConstUserType& input, SerializationContext* context) { + void* custom_context = Traits::SetUpContext(input); + if (!context->custom_contexts) + context->custom_contexts.reset(new std::queue()); + 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 + static void TearDown(MaybeConstUserType& input, void* custom_context) { + Traits::TearDownContext(input, custom_context); + } +}; + +template +struct CustomContextHelper { + template + static void* SetUp(MaybeConstUserType& input, SerializationContext* context) { + return nullptr; + } + + static void* GetNext(SerializationContext* context) { return nullptr; } + + template + static void TearDown(MaybeConstUserType& input, void* custom_context) { + DCHECK(!custom_context); + } +}; + +template +ReturnType CallWithContext(ReturnType (*f)(ParamType, void*), + InputUserType&& input, + void* context) { + return f(std::forward(input), context); +} + +template +ReturnType CallWithContext(ReturnType (*f)(ParamType), + InputUserType&& input, + void* context) { + return f(std::forward(input)); +} + +template +struct HasGetBeginMethod { + template + static char Test(decltype(U::GetBegin(std::declval()))*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + HasGetBeginMethod::value>::type* = nullptr> +decltype(Traits::GetBegin(std::declval())) +CallGetBeginIfExists(MaybeConstUserType& input) { + return Traits::GetBegin(input); +} + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + !HasGetBeginMethod::value>::type* = nullptr> +size_t CallGetBeginIfExists(MaybeConstUserType& input) { + return 0; +} + +template +struct HasGetDataMethod { + template + static char Test(decltype(U::GetData(std::declval()))*); + template + static int Test(...); + static const bool value = sizeof(Test(0)) == sizeof(char); + + private: + EnsureTypeIsComplete check_t_; +}; + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + HasGetDataMethod::value>::type* = nullptr> +decltype(Traits::GetData(std::declval())) +CallGetDataIfExists(MaybeConstUserType& input) { + return Traits::GetData(input); +} + +template < + typename Traits, + typename MaybeConstUserType, + typename std::enable_if< + !HasGetDataMethod::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 0000000..6e0c758 --- /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 +#include + +#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 +struct Serializer { + using UserType = typename std::remove_const::type; + using Traits = StringTraits; + + static size_t PrepareToSerialize(MaybeConstUserType& input, + SerializationContext* context) { + if (CallIsNullIfExists(input)) + return 0; + + void* custom_context = CustomContextHelper::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(input)) { + *output = nullptr; + return; + } + + void* custom_context = CustomContextHelper::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::TearDown(input, custom_context); + } + + static bool Deserialize(String_Data* input, + UserType* output, + SerializationContext* context) { + if (!input) + return CallSetToNullIfExists(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 0000000..95ff6cc --- /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 + +#include "base/strings/utf_string_conversions.h" + +namespace mojo { + +// static +void* StringTraits::SetUpContext(const base::string16& input) { + return new std::string(base::UTF16ToUTF8(input)); +} + +// static +void StringTraits::TearDownContext(const base::string16& input, + void* context) { + delete static_cast(context); +} + +// static +size_t StringTraits::GetSize(const base::string16& input, + void* context) { + return static_cast(context)->size(); +} + +// static +const char* StringTraits::GetData(const base::string16& input, + void* context) { + return static_cast(context)->data(); +} + +// static +bool StringTraits::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 0000000..203f6f5 --- /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 + +#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(context); + +#if DCHECK_IS_ON() + DCHECK_EQ(adaptor->original_size_in_bytes, input.charactersSizeInBytes()); +#endif + return adaptor; +} + +} // namespace + +// static +void StringTraits::SetToNull(WTF::String* output) { + if (output->isNull()) + return; + + WTF::String result; + output->swap(result); +} + +// static +void* StringTraits::SetUpContext(const WTF::String& input) { + return new UTF8AdaptorInfo(input); +} + +// static +void StringTraits::TearDownContext(const WTF::String& input, + void* context) { + delete ToAdaptor(input, context); +} + +// static +size_t StringTraits::GetSize(const WTF::String& input, + void* context) { + return ToAdaptor(input, context)->utf8_adaptor.length(); +} + +// static +const char* StringTraits::GetData(const WTF::String& input, + void* context) { + return ToAdaptor(input, context)->utf8_adaptor.data(); +} + +// static +bool StringTraits::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 0000000..585a8f0 --- /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>::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 0000000..b1c97e3 --- /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(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 0000000..fd3df39 --- /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>::Leaky + g_current_sync_handle_watcher = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +// static +scoped_refptr SyncHandleRegistry::current() { + scoped_refptr 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 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 0000000..f20af56 --- /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(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 0000000..5151123 --- /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 + +namespace mojo { +namespace internal { + +template +struct IntegralConstant { + static const T value = v; +}; + +template +const T IntegralConstant::value; + +typedef IntegralConstant TrueType; +typedef IntegralConstant FalseType; + +template +struct IsConst : FalseType {}; +template +struct IsConst : TrueType {}; + +template +struct IsPointer : FalseType {}; +template +struct IsPointer : TrueType {}; + +template +struct EnableIf {}; + +template +struct EnableIf { + 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 +struct IsMoveOnlyType { + static const bool value = std::is_constructible::value && + !std::is_constructible::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 +struct IsSame { + static bool const value = false; +}; +template +struct IsSame { + static bool const value = true; +}; + +template +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 +struct IsBaseOf { + private: + static Derived* CreateDerived(); + static char(&Check(Base*))[1]; + static char(&Check(...))[2]; + + EnsureTypeIsComplete check_base_; + EnsureTypeIsComplete check_derived_; + + public: + static bool const value = sizeof Check(CreateDerived()) == 1 && + !IsSame::value; +}; + +template +struct RemovePointer { + typedef T type; +}; +template +struct RemovePointer { + typedef T type; +}; + +template