1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "mojo/public/cpp/platform/platform_handle.h"
6 #include "base/files/file.h"
7 #include "base/files/platform_file.h"
8 #include "base/files/scoped_temp_dir.h"
9 #include "base/logging.h"
10 #include "base/macros.h"
11 #include "base/memory/unsafe_shared_memory_region.h"
12 #include "base/rand_util.h"
13 #include "base/unguessable_token.h"
14 #include "build/build_config.h"
15 #include "mojo/public/c/system/platform_handle.h"
16 #include "mojo/public/cpp/system/platform_handle.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18
19 #if defined(OS_MACOSX) && !defined(OS_IOS)
20 #include <mach/mach_vm.h>
21 #endif
22
23 #if defined(OS_WIN)
24 #include "base/win/scoped_handle.h"
25 #else
26 #include "base/files/scoped_file.h"
27 #endif
28
29 namespace mojo {
30 namespace {
31
32 // Different types of platform handles are supported on different platforms.
33 // We run all PlatformHandle once for each type of handle available on the
34 // target platform.
35 enum class HandleType {
36 #if defined(OS_WIN) || defined(OS_FUCHSIA)
37 kHandle,
38 #endif
39 #if defined(OS_POSIX) || defined(OS_FUCHSIA)
40 kFileDescriptor,
41 #endif
42 #if defined(OS_MACOSX) && !defined(OS_IOS)
43 kMachPort,
44 #endif
45 };
46
47 // Different types of test modes we support in order to exercise the available
48 // handles types. Fuchsia zx_handle tests and Mac Mach port tests use shared
49 // memory for setup and validation. Everything else uses platform files.
50 // See |SetUp()| below.
51 enum class TestType {
52 kFile,
53 kSharedMemory,
54 };
55
56 const std::string kTestData = "some data to validate";
57
58 class PlatformHandleTest : public testing::Test,
59 public testing::WithParamInterface<HandleType> {
60 public:
61 PlatformHandleTest() = default;
62
SetUp()63 void SetUp() override {
64 test_type_ = TestType::kFile;
65
66 #if defined(OS_FUCHSIA)
67 if (GetParam() == HandleType::kHandle)
68 test_type_ = TestType::kSharedMemory;
69 #elif defined(OS_MACOSX) && !defined(OS_IOS)
70 if (GetParam() == HandleType::kMachPort)
71 test_type_ = TestType::kSharedMemory;
72 #endif
73
74 if (test_type_ == TestType::kFile)
75 test_handle_ = SetUpFile();
76 #if defined(OS_FUCHSIA) || (defined(OS_MACOSX) && !defined(OS_IOS))
77 else
78 test_handle_ = SetUpSharedMemory();
79 #endif
80 }
81
82 // Extracts the contents of a file or shared memory object, given a generic
83 // PlatformHandle wrapping it. Used to verify that a |handle| refers to some
84 // expected platform object.
GetObjectContents(PlatformHandle & handle)85 std::string GetObjectContents(PlatformHandle& handle) {
86 if (test_type_ == TestType::kFile)
87 return GetFileContents(handle);
88 #if defined(OS_FUCHSIA) || (defined(OS_MACOSX) && !defined(OS_IOS))
89 else
90 return GetSharedMemoryContents(handle);
91 #endif
92 NOTREACHED();
93 return std::string();
94 }
95
96 protected:
test_handle()97 PlatformHandle& test_handle() { return test_handle_; }
98
99 private:
100 // Creates a platform file with some test data in it. Leaves the file open
101 // with cursor positioned at the beginning, and returns it as a generic
102 // PlatformHandle.
SetUpFile()103 PlatformHandle SetUpFile() {
104 CHECK(temp_dir_.CreateUniqueTempDir());
105 base::File test_file(temp_dir_.GetPath().AppendASCII("test"),
106 base::File::FLAG_CREATE | base::File::FLAG_WRITE |
107 base::File::FLAG_READ);
108 test_file.WriteAtCurrentPos(kTestData.data(), kTestData.size());
109
110 #if defined(OS_WIN)
111 return PlatformHandle(
112 base::win::ScopedHandle(test_file.TakePlatformFile()));
113 #else
114 return PlatformHandle(base::ScopedFD(test_file.TakePlatformFile()));
115 #endif
116 }
117
118 // Returns the contents of a platform file referenced by |handle|. Used to
119 // verify that |handle| is in fact the platform file handle it's expected to
120 // be. See |GetObjectContents()|.
GetFileContents(PlatformHandle & handle)121 std::string GetFileContents(PlatformHandle& handle) {
122 #if defined(OS_WIN)
123 // We must temporarily release ownership of the handle due to how File
124 // interacts with ScopedHandle.
125 base::File file(handle.TakeHandle().Take());
126 #else
127 base::File file(handle.GetFD().get());
128 #endif
129 std::vector<char> buffer(kTestData.size());
130 file.Read(0, buffer.data(), buffer.size());
131 std::string contents(buffer.begin(), buffer.end());
132
133 // Let |handle| retain ownership.
134 #if defined(OS_WIN)
135 handle = PlatformHandle(base::win::ScopedHandle(file.TakePlatformFile()));
136 #else
137 ignore_result(file.TakePlatformFile());
138 #endif
139
140 return contents;
141 }
142
143 #if defined(OS_FUCHSIA) || (defined(OS_MACOSX) && !defined(OS_IOS))
144 // Creates a shared memory region with some test data in it. Leaves the
145 // handle open and returns it as a generic PlatformHandle.
SetUpSharedMemory()146 PlatformHandle SetUpSharedMemory() {
147 auto region = base::UnsafeSharedMemoryRegion::Create(kTestData.size());
148 auto mapping = region.Map();
149 memcpy(mapping.memory(), kTestData.data(), kTestData.size());
150 auto generic_region =
151 base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
152 std::move(region));
153 shm_guid_ = generic_region.GetGUID();
154 return PlatformHandle(generic_region.PassPlatformHandle());
155 }
156
157 // Extracts data stored in a shared memory object referenced by |handle|. Used
158 // to verify that |handle| does in fact reference a shared memory object when
159 // expected. See |GetObjectContents()|.
GetSharedMemoryContents(const PlatformHandle & handle)160 std::string GetSharedMemoryContents(const PlatformHandle& handle) {
161 base::subtle::PlatformSharedMemoryRegion::ScopedPlatformHandle
162 region_handle(
163 #if defined(OS_FUCHSIA)
164 handle.GetHandle().get()
165 #elif defined(OS_MACOSX) && !defined(OS_IOS)
166 handle.GetMachPort().get()
167 #endif
168 );
169 auto generic_region = base::subtle::PlatformSharedMemoryRegion::Take(
170 std::move(region_handle),
171 base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe,
172 kTestData.size(), shm_guid_);
173 auto region =
174 base::UnsafeSharedMemoryRegion::Deserialize(std::move(generic_region));
175 auto mapping = region.Map();
176 std::string contents(static_cast<char*>(mapping.memory()),
177 kTestData.size());
178
179 // Let |handle| retain ownership.
180 generic_region = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
181 std::move(region));
182 region_handle = generic_region.PassPlatformHandle();
183 ignore_result(region_handle.release());
184
185 return contents;
186 }
187 #endif // defined(OS_FUCHSIA) || (defined(OS_MACOSX) && !defined(OS_IOS))
188
189 base::ScopedTempDir temp_dir_;
190 TestType test_type_;
191 PlatformHandle test_handle_;
192
193 // Needed to reconstitute a base::PlatformSharedMemoryRegion from an unwrapped
194 // PlatformHandle.
195 base::UnguessableToken shm_guid_;
196
197 DISALLOW_COPY_AND_ASSIGN(PlatformHandleTest);
198 };
199
TEST_P(PlatformHandleTest,BasicConstruction)200 TEST_P(PlatformHandleTest, BasicConstruction) {
201 EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
202 }
203
TEST_P(PlatformHandleTest,Move)204 TEST_P(PlatformHandleTest, Move) {
205 EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
206
207 auto new_handle = std::move(test_handle());
208 EXPECT_FALSE(test_handle().is_valid());
209 EXPECT_TRUE(new_handle.is_valid());
210
211 EXPECT_EQ(kTestData, GetObjectContents(new_handle));
212 }
213
TEST_P(PlatformHandleTest,Reset)214 TEST_P(PlatformHandleTest, Reset) {
215 auto handle = std::move(test_handle());
216 EXPECT_TRUE(handle.is_valid());
217 handle.reset();
218 EXPECT_FALSE(handle.is_valid());
219 }
220
TEST_P(PlatformHandleTest,Clone)221 TEST_P(PlatformHandleTest, Clone) {
222 EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
223
224 auto clone = test_handle().Clone();
225 EXPECT_TRUE(clone.is_valid());
226 EXPECT_EQ(kTestData, GetObjectContents(clone));
227 clone.reset();
228 EXPECT_FALSE(clone.is_valid());
229
230 EXPECT_TRUE(test_handle().is_valid());
231 EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
232 }
233
234 // This is really testing system library stuff, but we conveniently have all
235 // this handle type parameterization already in place here.
TEST_P(PlatformHandleTest,CStructConversion)236 TEST_P(PlatformHandleTest, CStructConversion) {
237 EXPECT_EQ(kTestData, GetObjectContents(test_handle()));
238
239 MojoPlatformHandle c_handle;
240 PlatformHandle::ToMojoPlatformHandle(std::move(test_handle()), &c_handle);
241
242 PlatformHandle handle = PlatformHandle::FromMojoPlatformHandle(&c_handle);
243 EXPECT_EQ(kTestData, GetObjectContents(handle));
244 }
245
246 INSTANTIATE_TEST_CASE_P(,
247 PlatformHandleTest,
248 #if defined(OS_WIN)
249 testing::Values(HandleType::kHandle)
250 #elif defined(OS_FUCHSIA)
251 testing::Values(HandleType::kHandle,
252 HandleType::kFileDescriptor)
253 #elif defined(OS_MACOSX) && !defined(OS_IOS)
254 testing::Values(HandleType::kFileDescriptor,
255 HandleType::kMachPort)
256 #elif defined(OS_POSIX)
257 testing::Values(HandleType::kFileDescriptor)
258 #endif
259 );
260
261 } // namespace
262 } // namespace mojo
263