1 /*
2 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include <fcntl.h>
17 #include <sys/mman.h>
18 #include "gtest/gtest.h"
19 #include "ipc_transactor.h"
20
21 using namespace OHOS::uitest;
22 using namespace std;
23
24 using PipeFds = int32_t[2];
25
PrintEnd()26 static void PrintEnd()
27 {
28 cout << "...End...";
29 }
30
RedirectStdoutToPipe(string_view procName,PipeFds fds)31 static void RedirectStdoutToPipe(string_view procName, PipeFds fds)
32 {
33 close(fds[0]);
34 close(STDOUT_FILENO);
35 close(STDERR_FILENO);
36 dup2(fds[1], STDOUT_FILENO);
37 cout << "...Start..." << procName;
38 (void)atexit(PrintEnd);
39 }
40
WaitPidAndReadPipe(pid_t pid,PipeFds fds)41 static string WaitPidAndReadPipe(pid_t pid, PipeFds fds)
42 {
43 string separatorLine = "============================================\n";
44 string output = separatorLine;
45 close(fds[1]);
46 int32_t status = 0;
47 waitpid(pid, &status, 0);
48 char buf[1024] = {0};
49 auto size = read(fds[0], buf, sizeof(buf));
50 output.append(string(buf, size)).append("\n").append(separatorLine);
51 return output;
52 }
53
54 struct InitConnTestParams {
55 // preset conditions
56 bool tokenLegal;
57 uint32_t clientDelay;
58 uint32_t serverDelay;
59 // expected stats
60 bool expectConnSuccess;
61 // case description
62 string_view description;
63 };
64
65 // the enviroment of situations to test
66 static constexpr InitConnTestParams ParamSuite[] = {
67 // connection timeout: 5000ms
68 {true, 0000, 0000, true, "NormalConnection"},
69 {true, 4000, 0000, true, "NormalConnection-ClientDelayInTolerance"},
70 {true, 0000, 4000, true, "NormalConnection-ServerDelayInTolerance"},
71 {false, 000, 0000, false, "BadConnection-IllegalToken"},
72 {true, 6000, 0000, false, "BadConnection-ClientDelayOutofTolerance"},
73 {true, 0000, 6000, false, "BadConnection-ServerDelayOutofTolerance"},
74 };
75 static constexpr uint32_t suiteSize = 6U;
76
77 class ApiTransactorConnectionTest : public ::testing::TestWithParam<uint32_t> {};
78
TEST_P(ApiTransactorConnectionTest,testInitConnection)79 TEST_P(ApiTransactorConnectionTest, testInitConnection)
80 {
81 size_t idx = GetParam();
82 const auto params = ParamSuite[idx];
83 auto token = string("testInitConnection_#") + string(params.description);
84 // fork and run server
85 auto pid = fork();
86 ASSERT_NE(pid, -1);
87 if (pid == 0) {
88 this_thread::sleep_for(chrono::milliseconds(params.serverDelay));
89 ApiTransactor server(true);
90 server.InitAndConnectPeer(token, nullptr);
91 exit(0); // call exit explicity, to avoid hangs for unkown reason
92 }
93 // fork and run client
94 int32_t fds[2];
95 ASSERT_EQ(pipe(fds), 0);
96 pid = fork();
97 ASSERT_NE(pid, -1);
98 if (pid == 0) {
99 RedirectStdoutToPipe(token + "-client", fds);
100 // client runs in parent process
101 this_thread::sleep_for(chrono::milliseconds(params.clientDelay));
102 if (!params.tokenLegal) {
103 token.append("_bad");
104 }
105 ApiTransactor client(false);
106 auto success = client.InitAndConnectPeer(token, nullptr);
107 ASSERT_EQ(success, params.expectConnSuccess);
108 exit(0); // call exit explicity, to avoid hangs for unkown reason
109 }
110 // receive output of client in main process and do assertions
111 auto clientOutput = WaitPidAndReadPipe(pid, fds);
112 cout << clientOutput << endl;
113 ASSERT_TRUE(clientOutput.find(" FAILED ") == string::npos);
114 }
115
116 INSTANTIATE_TEST_SUITE_P(_, ApiTransactorConnectionTest, testing::Range(0U, suiteSize, 1U));
117
TEST(ApiTransactorTest,testApiTransaction)118 TEST(ApiTransactorTest, testApiTransaction)
119 {
120 constexpr string_view token = "testAapiTransaction";
121 constexpr uint32_t serverLifeMs = 100;
122 // fork and run server
123 auto pid = fork();
124 ASSERT_NE(pid, -1);
125 if (pid == 0) {
126 auto executor = [](const ApiCallInfo &call, ApiReplyInfo &result) {
127 string ret = call.apiId_;
128 ret.append("/").append(call.callerObjRef_).append("/");
129 ret.append(call.paramList_.at(0).get<string>());
130 result.resultValue_ = ret;
131 result.exception_ = ApiCallErr(ERR_API_USAGE, "MockedError");
132 };
133 ApiTransactor server(true);
134 server.InitAndConnectPeer(token, executor);
135 this_thread::sleep_for(chrono::milliseconds(serverLifeMs));
136 exit(0);
137 }
138 // fork and run client
139 int32_t fds[2];
140 ASSERT_EQ(pipe(fds), 0);
141 pid = fork();
142 ASSERT_NE(pid, -1);
143 if (pid == 0) {
144 RedirectStdoutToPipe("testApiTransaction-client", fds);
145 ApiTransactor client(false);
146 auto connSuccess = client.InitAndConnectPeer(token, nullptr);
147 ASSERT_EQ(connSuccess, true);
148 auto call = ApiCallInfo {.apiId_ = "testApi", .callerObjRef_ = "testObject"};
149 call.paramList_.emplace_back("testParam");
150 ApiReplyInfo result;
151 client.Transact(call, result);
152 // check1. call_data and reply_data are correctly deleviderd
153 ASSERT_EQ(result.resultValue_.type(), nlohmann::detail::value_t::string);
154 ASSERT_EQ(result.resultValue_.get<string>(), "testApi/testObject/testParam");
155 ASSERT_EQ(result.exception_.code_, ERR_API_USAGE);
156 ASSERT_EQ(result.exception_.message_, "MockedError");
157 // check2. connection state change after server died
158 this_thread::sleep_for(chrono::milliseconds(serverLifeMs << 1)); // wait server expired
159 ASSERT_EQ(client.GetConnectionStat(), DISCONNECTED);
160 // check3. transaction after connection dead gives connection died error
161 client.Transact(call, result);
162 ASSERT_EQ(result.exception_.code_, ERR_INTERNAL);
163 ASSERT_TRUE(result.exception_.message_.find("ipc connection is dead") != string::npos);
164 exit(0);
165 }
166 // receive output of client in main process and do assertions
167 auto clientOutput = WaitPidAndReadPipe(pid, fds);
168 cout << clientOutput << endl;
169 ASSERT_TRUE(clientOutput.find(" FAILED ") == string::npos);
170 }
171
172 #ifdef MFD_ALLOW_SEALING
TEST(ApiTransactorTest,testHandleFdParam)173 TEST(ApiTransactorTest, testHandleFdParam)
174 {
175 constexpr string_view token = "testHandleFdParam";
176 constexpr uint32_t serverLifeMs = 100;
177 // fork and run server
178 auto pid = fork();
179 ASSERT_NE(pid, -1);
180 if (pid == 0) {
181 auto executor = [token](const ApiCallInfo &call, ApiReplyInfo &result) {
182 // read the file size and return it
183 auto fd = call.paramList_.at(0).get<int32_t>();
184 if (fcntl(fd, F_GETFD) == -1 && errno == EBADF) {
185 result.exception_ = ApiCallErr(ERR_INVALID_INPUT, "Bad fd");
186 } else {
187 write(fd, token.data(), token.length());
188 }
189 };
190 ApiTransactor server(true);
191 server.InitAndConnectPeer(token, executor);
192 this_thread::sleep_for(chrono::milliseconds(serverLifeMs));
193 exit(0);
194 }
195 // fork and run client
196 int32_t fds[2];
197 ASSERT_EQ(pipe(fds), 0);
198 pid = fork();
199 ASSERT_NE(pid, -1);
200 if (pid == 0) {
201 RedirectStdoutToPipe("testHandleFdParam-client", fds);
202 ApiTransactor client(false);
203 auto connSuccess = client.InitAndConnectPeer(token, nullptr);
204 ASSERT_EQ(connSuccess, true);
205 auto call = ApiCallInfo {.apiId_ = "testApi"};
206 auto fd = memfd_create("dummy_file", MFD_ALLOW_SEALING);
207 ASSERT_TRUE(fd > 0) << "Failed to create memfd";
208 ftruncate(fd, 0);
209 call.paramList_.push_back(fd);
210 call.fdParamIndex_ = 0;
211 ApiReplyInfo result;
212 client.Transact(call, result);
213 auto fileSize = lseek(fd, 0, SEEK_END);
214 // check get correct file size from reply
215 ASSERT_EQ(result.exception_.code_, NO_ERROR);
216 ASSERT_EQ(fileSize, token.length());
217 exit(0);
218 }
219 // receive output of client in main process and do assertions
220 auto clientOutput = WaitPidAndReadPipe(pid, fds);
221 cout << clientOutput << endl;
222 ASSERT_TRUE(clientOutput.find(" FAILED ") == string::npos);
223 }
224 #endif
225
TEST(ApiTransactorTest,testDetectConcurrentTransaction)226 TEST(ApiTransactorTest, testDetectConcurrentTransaction)
227 {
228 constexpr string_view token = "testDetectConcurrentTransaction";
229 static constexpr uint32_t apiCostMs = 100; // mock the timecost of api invocation
230 // fork and run server
231 auto pid = fork();
232 ASSERT_NE(pid, -1);
233 if (pid == 0) {
234 auto executor = [](const ApiCallInfo &call, ApiReplyInfo &result) {
235 this_thread::sleep_for(chrono::milliseconds(apiCostMs));
236 };
237 ApiTransactor server(true);
238 if (server.InitAndConnectPeer(token, executor)) {
239 this_thread::sleep_for(chrono::milliseconds(apiCostMs << 1));
240 }
241 exit(0);
242 }
243 // fork and run client
244 int32_t fds[2];
245 ASSERT_EQ(pipe(fds), 0);
246 pid = fork();
247 ASSERT_NE(pid, -1);
248 if (pid == 0) {
249 RedirectStdoutToPipe("testDetectConcurrentTransaction-client", fds);
250 ApiTransactor client(false);
251 auto connSuccess = client.InitAndConnectPeer(token, nullptr);
252 ASSERT_EQ(connSuccess, true);
253 auto call0 = ApiCallInfo {.apiId_ = "testApi0"};
254 ApiReplyInfo result0;
255 auto call1 = ApiCallInfo {.apiId_ = "testApi1"};
256 ApiReplyInfo result1;
257 auto ft = async(launch::async, [&]() { client.Transact(call0, result0); });
258 // delay to ensure invocation(call0,reply0) on going
259 this_thread::sleep_for(chrono::milliseconds(apiCostMs >> 1));
260 client.Transact(call1, result1);
261 ft.get();
262 ASSERT_EQ(result0.exception_.code_, NO_ERROR);
263 ASSERT_EQ(result1.exception_.code_, ERR_API_USAGE);
264 exit(0);
265 }
266 // receive output of client in main process and do assertions
267 auto clientOutput = WaitPidAndReadPipe(pid, fds);
268 cout << clientOutput << endl;
269 ASSERT_TRUE(clientOutput.find(" FAILED ") == string::npos);
270 }
271
272 // clientDecteServerDie is tested in above case.
273 // in this test, we need to server in parent process and client in child process
TEST(ApiTransactorTest,testServerDetectClientDeath)274 TEST(ApiTransactorTest, testServerDetectClientDeath)
275 {
276 constexpr string_view token = "testServerDetectClientDeath";
277 // fork and run client
278 auto pid = fork();
279 ASSERT_NE(pid, -1);
280 if (pid == 0) {
281 ApiTransactor client(false);
282 client.InitAndConnectPeer(token, nullptr);
283 client.Finalize();
284 exit(0);
285 }
286 // fork and run server
287 int32_t fds[2];
288 ASSERT_EQ(pipe(fds), 0);
289 pid = fork();
290 ASSERT_NE(pid, -1);
291 if (pid == 0) {
292 RedirectStdoutToPipe("testServerDetectClientDeath-server", fds);
293 ApiTransactor server(true);
294 auto success = server.InitAndConnectPeer(token, nullptr);
295 ASSERT_EQ(true, success); // connected on init
296 this_thread::sleep_for(chrono::milliseconds(100)); // wait for client expired
297 ASSERT_EQ(server.GetConnectionStat(), DISCONNECTED);
298 exit(0);
299 }
300 // receive output of client in main process and do assertions
301 auto clientOutput = WaitPidAndReadPipe(pid, fds);
302 cout << clientOutput << endl;
303 ASSERT_TRUE(clientOutput.find(" FAILED ") == string::npos);
304 }