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