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