1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <aidl/android/hardware/contexthub/BnContextHubCallback.h>
18 #include <aidl/android/hardware/contexthub/IContextHub.h>
19 #include <aidl/android/hardware/contexthub/NanoappBinary.h>
20 #include <android/binder_manager.h>
21 #include <android/binder_process.h>
22 #include <dirent.h>
23 #include <utils/String16.h>
24
25 #include <cctype>
26 #include <filesystem>
27 #include <fstream>
28 #include <future>
29 #include <map>
30 #include <regex>
31 #include <stdexcept>
32 #include <string>
33 #include <unordered_set>
34 #include <vector>
35
36 #include "chre_api/chre/version.h"
37 #include "chre_host/file_stream.h"
38 #include "chre_host/hal_client.h"
39 #include "chre_host/log.h"
40 #include "chre_host/napp_header.h"
41
42 using ::aidl::android::hardware::contexthub::AsyncEventType;
43 using ::aidl::android::hardware::contexthub::BnContextHubCallback;
44 using ::aidl::android::hardware::contexthub::ContextHubInfo;
45 using ::aidl::android::hardware::contexthub::ContextHubMessage;
46 using ::aidl::android::hardware::contexthub::HostEndpointInfo;
47 using ::aidl::android::hardware::contexthub::IContextHub;
48 using ::aidl::android::hardware::contexthub::MessageDeliveryStatus;
49 using ::aidl::android::hardware::contexthub::NanoappBinary;
50 using ::aidl::android::hardware::contexthub::NanoappInfo;
51 using ::aidl::android::hardware::contexthub::NanSessionRequest;
52 using ::aidl::android::hardware::contexthub::Setting;
53 using ::android::chre::HalClient;
54 using ::android::chre::NanoAppBinaryHeader;
55 using ::android::chre::readFileContents;
56 using ::android::internal::ToString;
57 using ::ndk::ScopedAStatus;
58
59 namespace {
60 // A default id 0 is used for every command requiring a context hub id. When
61 // this is not the case the id number should be one of the arguments of the
62 // commands.
63 constexpr uint32_t kContextHubId = 0;
64 constexpr int32_t kLoadTransactionId = 1;
65 constexpr int32_t kUnloadTransactionId = 2;
66
67 // Though IContextHub.aidl says loading operation is capped at 30s to finish,
68 // multiclient HAL can terminate a load/unload transaction after 5s to avoid
69 // blocking other load/unload transactions.
70 constexpr auto kTimeOutThresholdInSec = std::chrono::seconds(5);
71
72 // 34a3a27e-9b83-4098-b564-e83b0c28d4bb
73 constexpr std::array<uint8_t, 16> kUuid = {0x34, 0xa3, 0xa2, 0x7e, 0x9b, 0x83,
74 0x40, 0x98, 0xb5, 0x64, 0xe8, 0x3b,
75 0x0c, 0x28, 0xd4, 0xbb};
76
77 // Locations should be searched in the sequence defined below:
78 const char *kPredefinedNanoappPaths[] = {
79 "/vendor/etc/chre/",
80 "/vendor/dsp/adsp/",
81 "/vendor/dsp/sdsp/",
82 "/vendor/lib/rfsa/adsp/",
83 };
84
85 const std::string kClientName{"ChreAidlHalClient"};
86
throwError(const std::string & message)87 inline void throwError(const std::string &message) {
88 throw std::system_error{std::error_code(), message};
89 }
90
isValidHexNumber(const std::string & number)91 bool isValidHexNumber(const std::string &number) {
92 if (number.empty() ||
93 (number.substr(0, 2) != "0x" && number.substr(0, 2) != "0X")) {
94 return false;
95 }
96 for (int i = 2; i < number.size(); i++) {
97 if (!isxdigit(number[i])) {
98 throwError("Hex app id " + number + " contains invalid character.");
99 }
100 }
101 return number.size() > 2;
102 }
103
verifyAndConvertEndpointHexId(const std::string & number)104 uint16_t verifyAndConvertEndpointHexId(const std::string &number) {
105 // host endpoint id must be a 16-bits long hex number.
106 if (isValidHexNumber(number)) {
107 int convertedNumber = std::stoi(number, /* idx= */ nullptr, /* base= */ 16);
108 if (convertedNumber < std::numeric_limits<uint16_t>::max()) {
109 return static_cast<uint16_t>(convertedNumber);
110 }
111 }
112 throwError("host endpoint id must be a 16-bits long hex number.");
113 return 0; // code never reached.
114 }
115
isValidNanoappHexId(const std::string & number)116 bool isValidNanoappHexId(const std::string &number) {
117 if (!isValidHexNumber(number)) {
118 return false;
119 }
120 // Once the input has the hex prefix, an exception will be thrown if it is
121 // malformed because it shouldn't be treated as an app name anymore.
122 if (number.size() > 18) {
123 throwError("Hex app id must has a length of [3, 18] including the prefix.");
124 }
125 return true;
126 }
127
parseAppVersion(uint32_t version)128 std::string parseAppVersion(uint32_t version) {
129 std::ostringstream stringStream;
130 stringStream << std::hex << "0x" << version << std::dec << " (v"
131 << CHRE_EXTRACT_MAJOR_VERSION(version) << "."
132 << CHRE_EXTRACT_MINOR_VERSION(version) << "."
133 << CHRE_EXTRACT_PATCH_VERSION(version) << ")";
134 return stringStream.str();
135 }
136
parseTransactionId(int32_t transactionId)137 std::string parseTransactionId(int32_t transactionId) {
138 switch (transactionId) {
139 case kLoadTransactionId:
140 return "Loading";
141 case kUnloadTransactionId:
142 return "Unloading";
143 default:
144 return "Unknown";
145 }
146 }
147
148 class ContextHubCallback : public BnContextHubCallback {
149 public:
handleNanoappInfo(const std::vector<NanoappInfo> & appInfo)150 ScopedAStatus handleNanoappInfo(
151 const std::vector<NanoappInfo> &appInfo) override {
152 std::cout << appInfo.size() << " nanoapps loaded" << std::endl;
153 for (const NanoappInfo &app : appInfo) {
154 std::cout << "appId: 0x" << std::hex << app.nanoappId << std::dec << " {"
155 << "\n\tappVersion: " << parseAppVersion(app.nanoappVersion)
156 << "\n\tenabled: " << (app.enabled ? "true" : "false")
157 << "\n\tpermissions: " << ToString(app.permissions)
158 << "\n\trpcServices: " << ToString(app.rpcServices) << "\n}"
159 << std::endl;
160 }
161 resetPromise();
162 return ScopedAStatus::ok();
163 }
164
handleContextHubMessage(const ContextHubMessage & message,const std::vector<std::string> &)165 ScopedAStatus handleContextHubMessage(
166 const ContextHubMessage &message,
167 const std::vector<std::string> & /*msgContentPerms*/) override {
168 std::cout << "Received a message!" << std::endl
169 << " From: 0x" << std::hex << message.nanoappId << std::endl
170 << " To: 0x" << static_cast<int>(message.hostEndPoint)
171 << std::endl
172 << " Body: (type " << message.messageType << " size "
173 << message.messageBody.size() << ") 0x";
174 for (const uint8_t &data : message.messageBody) {
175 std::cout << std::hex << static_cast<uint16_t>(data);
176 }
177 std::cout << std::endl << std::endl;
178 resetPromise();
179 return ScopedAStatus::ok();
180 }
181
handleContextHubAsyncEvent(AsyncEventType event)182 ScopedAStatus handleContextHubAsyncEvent(AsyncEventType event) override {
183 std::cout << "Received async event " << toString(event) << std::endl;
184 resetPromise();
185 return ScopedAStatus::ok();
186 }
187
188 // Called after loading/unloading a nanoapp.
handleTransactionResult(int32_t transactionId,bool success)189 ScopedAStatus handleTransactionResult(int32_t transactionId,
190 bool success) override {
191 std::cout << parseTransactionId(transactionId) << " transaction is "
192 << (success ? "successful" : "failed") << std::endl;
193 resetPromise();
194 return ScopedAStatus::ok();
195 }
196
handleNanSessionRequest(const NanSessionRequest &)197 ScopedAStatus handleNanSessionRequest(
198 const NanSessionRequest & /* request */) override {
199 resetPromise();
200 return ScopedAStatus::ok();
201 }
202
handleMessageDeliveryStatus(char16_t,const MessageDeliveryStatus &)203 ScopedAStatus handleMessageDeliveryStatus(
204 char16_t /* hostEndPointId */,
205 const MessageDeliveryStatus & /* messageDeliveryStatus */) override {
206 resetPromise();
207 return ScopedAStatus::ok();
208 }
209
getUuid(std::array<uint8_t,16> * out_uuid)210 ScopedAStatus getUuid(std::array<uint8_t, 16> *out_uuid) override {
211 *out_uuid = kUuid;
212 return ScopedAStatus::ok();
213 }
214
getName(std::string * out_name)215 ScopedAStatus getName(std::string *out_name) override {
216 *out_name = kClientName;
217 return ScopedAStatus::ok();
218 }
219
resetPromise()220 void resetPromise() {
221 promise.set_value();
222 promise = std::promise<void>{};
223 }
224
225 // TODO(b/247124878):
226 // This promise is shared among all the HAL callbacks to simplify the
227 // implementation. This is based on the assumption that every command should
228 // get a response before timeout and the first callback triggered is for the
229 // response.
230 //
231 // In very rare cases, however, the assumption doesn't hold:
232 // - multiple callbacks are triggered by a command and come back out of order
233 // - one command is timed out and the user typed in another command then the
234 // first callback for the first command is triggered
235 // Once we have a chance we should consider refactor this design to let each
236 // callback use their specific promises.
237 std::promise<void> promise;
238 };
239
240 std::shared_ptr<IContextHub> gContextHub = nullptr;
241 std::shared_ptr<ContextHubCallback> gCallback = nullptr;
242
registerHostCallback()243 void registerHostCallback() {
244 if (gCallback != nullptr) {
245 gCallback.reset();
246 }
247 gCallback = ContextHubCallback::make<ContextHubCallback>();
248 if (!gContextHub->registerCallback(kContextHubId, gCallback).isOk()) {
249 throwError("Failed to register the callback");
250 }
251 }
252
253 /** Initializes gContextHub and register gCallback. */
getContextHub()254 std::shared_ptr<IContextHub> getContextHub() {
255 if (gContextHub == nullptr) {
256 auto aidlServiceName = std::string() + IContextHub::descriptor + "/default";
257 ndk::SpAIBinder binder(
258 AServiceManager_waitForService(aidlServiceName.c_str()));
259 if (binder.get() == nullptr) {
260 throwError("Could not find Context Hub HAL");
261 }
262 gContextHub = IContextHub::fromBinder(binder);
263 }
264 if (gCallback == nullptr) {
265 registerHostCallback();
266 }
267 return gContextHub;
268 }
269
printNanoappHeader(const NanoAppBinaryHeader & header)270 void printNanoappHeader(const NanoAppBinaryHeader &header) {
271 std::cout << " {"
272 << "\n\tappId: 0x" << std::hex << header.appId << std::dec
273 << "\n\tappVersion: " << parseAppVersion(header.appVersion)
274 << "\n\tflags: " << header.flags << "\n\ttarget CHRE API version: "
275 << static_cast<int>(header.targetChreApiMajorVersion) << "."
276 << static_cast<int>(header.targetChreApiMinorVersion) << "\n}"
277 << std::endl;
278 }
279
findHeaderByName(const std::string & appName,const std::string & binaryPath)280 std::unique_ptr<NanoAppBinaryHeader> findHeaderByName(
281 const std::string &appName, const std::string &binaryPath) {
282 DIR *dir = opendir(binaryPath.c_str());
283 if (dir == nullptr) {
284 return nullptr;
285 }
286 std::regex regex(appName + ".napp_header");
287 std::cmatch match;
288
289 std::unique_ptr<NanoAppBinaryHeader> result = nullptr;
290 for (struct dirent *entry; (entry = readdir(dir)) != nullptr;) {
291 if (!std::regex_match(entry->d_name, match, regex)) {
292 continue;
293 }
294 std::ifstream input(std::string(binaryPath) + "/" + entry->d_name,
295 std::ios::binary);
296 result = std::make_unique<NanoAppBinaryHeader>();
297 input.read(reinterpret_cast<char *>(result.get()),
298 sizeof(NanoAppBinaryHeader));
299 break;
300 }
301 closedir(dir);
302 return result;
303 }
304
readNanoappHeaders(std::map<std::string,NanoAppBinaryHeader> & nanoapps,const std::string & binaryPath)305 void readNanoappHeaders(std::map<std::string, NanoAppBinaryHeader> &nanoapps,
306 const std::string &binaryPath) {
307 DIR *dir = opendir(binaryPath.c_str());
308 if (dir == nullptr) {
309 return;
310 }
311 std::regex regex("(\\w+)\\.napp_header");
312 std::cmatch match;
313 for (struct dirent *entry; (entry = readdir(dir)) != nullptr;) {
314 if (!std::regex_match(entry->d_name, match, regex)) {
315 continue;
316 }
317 std::ifstream input(std::string(binaryPath) + "/" + entry->d_name,
318 std::ios::binary);
319 input.read(reinterpret_cast<char *>(&nanoapps[match[1]]),
320 sizeof(NanoAppBinaryHeader));
321 }
322 closedir(dir);
323 }
324
verifyStatus(const std::string & operation,const ScopedAStatus & status)325 void verifyStatus(const std::string &operation, const ScopedAStatus &status) {
326 if (!status.isOk()) {
327 gCallback->resetPromise();
328 throwError(operation + " fails with abnormal status " +
329 ToString(status.getMessage()) + " error code " +
330 ToString(status.getServiceSpecificError()));
331 }
332 }
333
verifyStatusAndSignal(const std::string & operation,const ScopedAStatus & status,const std::future<void> & future_signal)334 void verifyStatusAndSignal(const std::string &operation,
335 const ScopedAStatus &status,
336 const std::future<void> &future_signal) {
337 verifyStatus(operation, status);
338 std::future_status future_status =
339 future_signal.wait_for(kTimeOutThresholdInSec);
340 if (future_status != std::future_status::ready) {
341 gCallback->resetPromise();
342 throwError(operation + " doesn't finish within " +
343 ToString(kTimeOutThresholdInSec.count()) + " seconds");
344 }
345 }
346
347 /** Finds the .napp_header file associated to the nanoapp.
348 *
349 * This function guarantees to return a non-null {@link NanoAppBinaryHeader}
350 * pointer. In case a .napp_header file cannot be found an exception will be
351 * raised.
352 *
353 * @param pathAndName name of the nanoapp that might be prefixed with it path.
354 * It will be normalized to the format of <absolute-path><name>.so at the end.
355 * For example, "abc" will be changed to "/path/to/abc.so".
356 * @return a unique pointer to the {@link NanoAppBinaryHeader} found
357 */
findHeaderAndNormalizePath(std::string & pathAndName)358 std::unique_ptr<NanoAppBinaryHeader> findHeaderAndNormalizePath(
359 std::string &pathAndName) {
360 // To match the file pattern of [path]<name>[.so]
361 std::regex pathNameRegex("(.*?)(\\w+)(\\.so)?");
362 std::smatch smatch;
363 if (!std::regex_match(pathAndName, smatch, pathNameRegex)) {
364 throwError("Invalid nanoapp: " + pathAndName);
365 }
366 std::string fullPath = smatch[1];
367 std::string appName = smatch[2];
368 // absolute path is provided:
369 if (!fullPath.empty() && fullPath[0] == '/') {
370 auto result = findHeaderByName(appName, fullPath);
371 if (result == nullptr) {
372 throwError("Unable to find the nanoapp header for " + pathAndName);
373 }
374 pathAndName = fullPath + appName + ".so";
375 return result;
376 }
377 // relative path is searched form predefined locations:
378 for (const std::string &predefinedPath : kPredefinedNanoappPaths) {
379 auto result = findHeaderByName(appName, predefinedPath);
380 if (result == nullptr) {
381 continue;
382 }
383 pathAndName = predefinedPath + appName + ".so";
384 return result;
385 }
386 throwError("Unable to find the nanoapp header for " + pathAndName);
387 return nullptr;
388 }
389
getNanoappIdFrom(std::string & appIdOrName)390 int64_t getNanoappIdFrom(std::string &appIdOrName) {
391 int64_t appId;
392 if (isValidNanoappHexId(appIdOrName)) {
393 appId = std::stoll(appIdOrName, nullptr, 16);
394 } else {
395 // Treat the appIdOrName as the app name and try again
396 appId =
397 static_cast<int64_t>(findHeaderAndNormalizePath(appIdOrName)->appId);
398 }
399 return appId;
400 }
401
getAllContextHubs()402 void getAllContextHubs() {
403 std::vector<ContextHubInfo> hubs{};
404 getContextHub()->getContextHubs(&hubs);
405 if (hubs.empty()) {
406 std::cerr << "Failed to get any context hub." << std::endl;
407 return;
408 }
409 for (const auto &hub : hubs) {
410 std::cout << "Context Hub " << hub.id << ": " << std::endl
411 << " Name: " << hub.name << std::endl
412 << " Vendor: " << hub.vendor << std::endl
413 << " Max support message length (bytes): "
414 << hub.maxSupportedMessageLengthBytes << std::endl
415 << " Version: " << static_cast<uint32_t>(hub.chreApiMajorVersion)
416 << "." << static_cast<uint32_t>(hub.chreApiMinorVersion)
417 << std::endl
418 << " Chre platform id: 0x" << std::hex << hub.chrePlatformId
419 << std::endl;
420 }
421 }
422
loadNanoapp(std::string & pathAndName)423 void loadNanoapp(std::string &pathAndName) {
424 auto header = findHeaderAndNormalizePath(pathAndName);
425 std::vector<uint8_t> soBuffer{};
426 if (!readFileContents(pathAndName.c_str(), soBuffer)) {
427 throwError("Failed to open the content of " + pathAndName);
428 }
429 NanoappBinary binary;
430 binary.nanoappId = static_cast<int64_t>(header->appId);
431 binary.customBinary = soBuffer;
432 binary.flags = static_cast<int32_t>(header->flags);
433 binary.targetChreApiMajorVersion =
434 static_cast<int8_t>(header->targetChreApiMajorVersion);
435 binary.targetChreApiMinorVersion =
436 static_cast<int8_t>(header->targetChreApiMinorVersion);
437 binary.nanoappVersion = static_cast<int32_t>(header->appVersion);
438
439 auto status =
440 getContextHub()->loadNanoapp(kContextHubId, binary, kLoadTransactionId);
441 verifyStatusAndSignal(/* operation= */ "loading nanoapp " + pathAndName,
442 status, gCallback->promise.get_future());
443 }
444
unloadNanoapp(std::string & appIdOrName)445 void unloadNanoapp(std::string &appIdOrName) {
446 auto appId = getNanoappIdFrom(appIdOrName);
447 auto status = getContextHub()->unloadNanoapp(kContextHubId, appId,
448 kUnloadTransactionId);
449 verifyStatusAndSignal(/* operation= */ "unloading nanoapp " + appIdOrName,
450 status, gCallback->promise.get_future());
451 }
452
queryNanoapps()453 void queryNanoapps() {
454 auto status = getContextHub()->queryNanoapps(kContextHubId);
455 verifyStatusAndSignal(/* operation= */ "querying nanoapps", status,
456 gCallback->promise.get_future());
457 }
458
createHostEndpointInfo(const std::string & hexEndpointId)459 HostEndpointInfo createHostEndpointInfo(const std::string &hexEndpointId) {
460 uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexEndpointId);
461 return {
462 .hostEndpointId = hostEndpointId,
463 .type = HostEndpointInfo::Type::NATIVE,
464 .packageName = "chre_aidl_hal_client",
465 .attributionTag{},
466 };
467 }
468
onEndpointConnected(const std::string & hexEndpointId)469 void onEndpointConnected(const std::string &hexEndpointId) {
470 auto contextHub = getContextHub();
471 HostEndpointInfo info = createHostEndpointInfo(hexEndpointId);
472 // connect the endpoint to HAL
473 verifyStatus(/* operation= */ "connect endpoint",
474 contextHub->onHostEndpointConnected(info));
475 std::cout << "Connected." << std::endl;
476 }
477
onEndpointDisconnected(const std::string & hexEndpointId)478 void onEndpointDisconnected(const std::string &hexEndpointId) {
479 auto contextHub = getContextHub();
480 uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexEndpointId);
481 // disconnect the endpoint from HAL
482 verifyStatus(/* operation= */ "disconnect endpoint",
483 contextHub->onHostEndpointDisconnected(hostEndpointId));
484 std::cout << "Disconnected." << std::endl;
485 }
486
createContextHubMessage(const std::string & hexHostEndpointId,std::string & appIdOrName,const std::string & hexPayload)487 ContextHubMessage createContextHubMessage(const std::string &hexHostEndpointId,
488 std::string &appIdOrName,
489 const std::string &hexPayload) {
490 if (!isValidHexNumber(hexPayload)) {
491 throwError("Invalid hex payload.");
492 }
493 auto appId = getNanoappIdFrom(appIdOrName);
494 uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexHostEndpointId);
495 ContextHubMessage contextHubMessage = {
496 .nanoappId = appId,
497 .hostEndPoint = hostEndpointId,
498 .messageBody = {},
499 .permissions = {},
500 };
501 // populate the payload
502 for (int i = 2; i < hexPayload.size(); i += 2) {
503 contextHubMessage.messageBody.push_back(
504 std::stoi(hexPayload.substr(i, 2), /* idx= */ nullptr, /* base= */ 16));
505 }
506 return contextHubMessage;
507 }
508
509 /** Sends a hexPayload from hexHostEndpointId to appIdOrName. */
sendMessageToNanoapp(const std::string & hexHostEndpointId,std::string & appIdOrName,const std::string & hexPayload)510 void sendMessageToNanoapp(const std::string &hexHostEndpointId,
511 std::string &appIdOrName,
512 const std::string &hexPayload) {
513 ContextHubMessage contextHubMessage =
514 createContextHubMessage(hexHostEndpointId, appIdOrName, hexPayload);
515 // send the message
516 auto contextHub = getContextHub();
517 auto status = contextHub->sendMessageToHub(kContextHubId, contextHubMessage);
518 verifyStatusAndSignal(/* operation= */ "sending a message to " + appIdOrName,
519 status, gCallback->promise.get_future());
520 }
521
changeSetting(const std::string & setting,bool enabled)522 void changeSetting(const std::string &setting, bool enabled) {
523 auto contextHub = getContextHub();
524 int settingType = std::stoi(setting);
525 if (settingType < 1 || settingType > 7) {
526 throwError("setting type must be within [1, 7].");
527 }
528 ScopedAStatus status =
529 contextHub->onSettingChanged(static_cast<Setting>(settingType), enabled);
530 std::cout << "onSettingChanged is called to "
531 << (enabled ? "enable" : "disable") << " setting type "
532 << settingType << std::endl;
533 verifyStatus("change setting", status);
534 }
535
enableTestModeOnContextHub()536 void enableTestModeOnContextHub() {
537 auto status = getContextHub()->setTestMode(/* enable= */ true);
538 verifyStatus(/* operation= */ "enabling test mode", status);
539 std::cout << "Test mode is enabled" << std::endl;
540 }
541
disableTestModeOnContextHub()542 void disableTestModeOnContextHub() {
543 auto status = getContextHub()->setTestMode(/* enable= */false);
544 verifyStatus(/* operation= */ "disabling test mode", status);
545 std::cout << "Test mode is disabled" << std::endl;
546 }
547
getAllPreloadedNanoappIds()548 void getAllPreloadedNanoappIds() {
549 std::vector<int64_t> appIds{};
550 verifyStatus("get preloaded nanoapp ids",
551 getContextHub()->getPreloadedNanoappIds(kContextHubId, &appIds));
552 for (const auto &appId : appIds) {
553 std::cout << "0x" << std::hex << appId << std::endl;
554 }
555 }
556
557 // Please keep Command in alphabetical order
558 enum Command {
559 connect,
560 connectEndpoint,
561 disableSetting,
562 disableTestMode,
563 disconnectEndpoint,
564 enableSetting,
565 enableTestMode,
566 getContextHubs,
567 getPreloadedNanoappIds,
568 list,
569 load,
570 query,
571 registerCallback,
572 sendMessage,
573 unload,
574 unsupported
575 };
576
577 struct CommandInfo {
578 Command cmd;
579 u_int8_t numOfArgs; // including cmd;
580 std::string argsFormat;
581 std::string usage;
582 };
583
584 const std::map<std::string, CommandInfo> kAllCommands{
585 {"connect",
586 {.cmd = connect,
587 .numOfArgs = 1,
588 .argsFormat = "",
589 .usage = "connect to HAL using hal_client library and keep the session "
590 "alive while user can execute other commands. Use 'exit' to "
591 "quit the session."}},
592 {"connectEndpoint",
593 {.cmd = connectEndpoint,
594 .numOfArgs = 2,
595 .argsFormat = "<HEX_ENDPOINT_ID>",
596 .usage =
597 "associate an endpoint with the current client and notify HAL."}},
598 {"disableSetting",
599 {.cmd = disableSetting,
600 .numOfArgs = 2,
601 .argsFormat = "<SETTING>",
602 .usage = "disable a setting identified by a number defined in "
603 "android/hardware/contexthub/Setting.aidl."}},
604 {"disableTestMode",
605 {.cmd = disableTestMode,
606 .numOfArgs = 1,
607 .argsFormat = "",
608 .usage = "disable test mode."}},
609 {"disconnectEndpoint",
610 {.cmd = disconnectEndpoint,
611 .numOfArgs = 2,
612 .argsFormat = "<HEX_ENDPOINT_ID>",
613 .usage = "remove an endpoint with the current client and notify HAL."}},
614 {"enableSetting",
615 {.cmd = enableSetting,
616 .numOfArgs = 2,
617 .argsFormat = "<SETTING>",
618 .usage = "enable a setting identified by a number defined in "
619 "android/hardware/contexthub/Setting.aidl."}},
620 {"enableTestMode",
621 {.cmd = enableTestMode,
622 .numOfArgs = 1,
623 .argsFormat = "",
624 .usage = "enable test mode."}},
625 {"getContextHubs",
626 {.cmd = getContextHubs,
627 .numOfArgs = 1,
628 .argsFormat = "",
629 .usage = "get all the context hubs."}},
630 {"getPreloadedNanoappIds",
631 {.cmd = getPreloadedNanoappIds,
632 .numOfArgs = 1,
633 .argsFormat = "",
634 .usage = "get a list of ids for the preloaded nanoapps."}},
635 {"list",
636 {.cmd = list,
637 .numOfArgs = 2,
638 .argsFormat = "</PATH/TO/NANOAPPS>",
639 .usage = "list all the nanoapps' header info in the path."}},
640 {"load",
641 {.cmd = load,
642 .numOfArgs = 2,
643 .argsFormat = "<APP_NAME | /PATH/TO/APP_NAME>",
644 .usage = "load the nanoapp specified by the name. If an absolute path is "
645 "not provided the default locations are searched."}},
646 {"query",
647 {.cmd = query,
648 .numOfArgs = 1,
649 .argsFormat = "",
650 .usage = "show all loaded nanoapps (system apps excluded)."}},
651 {"registerCallback",
652 {.cmd = registerCallback,
653 .numOfArgs = 1,
654 .argsFormat = "",
655 .usage = "register a callback for the current client."}},
656 {"sendMessage",
657 {.cmd = sendMessage,
658 .numOfArgs = 4,
659 .argsFormat = "<HEX_ENDPOINT_ID> <HEX_NANOAPP_ID | APP_NAME | "
660 "/PATH/TO/APP_NAME> <HEX_PAYLOAD>",
661 .usage = "send a payload to a nanoapp. If an absolute path is not "
662 "provided the default locations are searched."}},
663 {"unload",
664 {.cmd = unload,
665 .numOfArgs = 2,
666 .argsFormat = "<HEX_NANOAPP_ID | APP_NAME | /PATH/TO/APP_NAME>",
667 .usage = "unload the nanoapp specified by either the nanoapp id or the "
668 "app name. If an absolute path is not provided the default "
669 "locations are searched."}},
670 };
671
fillSupportedCommandMap(const std::unordered_set<std::string> & supportedCommands,std::map<std::string,CommandInfo> & supportedCommandMap)672 void fillSupportedCommandMap(
673 const std::unordered_set<std::string> &supportedCommands,
674 std::map<std::string, CommandInfo> &supportedCommandMap) {
675 std::copy_if(kAllCommands.begin(), kAllCommands.end(),
676 std::inserter(supportedCommandMap, supportedCommandMap.begin()),
677 [&](auto const &kv_pair) {
678 return supportedCommands.find(kv_pair.first) !=
679 supportedCommands.end();
680 });
681 }
682
printUsage(const std::map<std::string,CommandInfo> & supportedCommands)683 void printUsage(const std::map<std::string, CommandInfo> &supportedCommands) {
684 constexpr uint32_t kCommandLength = 40;
685 std::cout << std::left << "Usage: COMMAND [ARGUMENTS]" << std::endl;
686 for (auto const &kv_pair : supportedCommands) {
687 std::string cmdLine = kv_pair.first + " " + kv_pair.second.argsFormat;
688 std::cout << std::setw(kCommandLength) << cmdLine;
689 if (cmdLine.size() > kCommandLength) {
690 std::cout << std::endl << std::string(kCommandLength, ' ');
691 }
692 std::cout << " - " + kv_pair.second.usage << std::endl;
693 }
694 std::cout << std::endl;
695 }
696
parseCommand(const std::vector<std::string> & cmdLine,const std::map<std::string,CommandInfo> & supportedCommandMap)697 Command parseCommand(
698 const std::vector<std::string> &cmdLine,
699 const std::map<std::string, CommandInfo> &supportedCommandMap) {
700 if (cmdLine.empty() ||
701 supportedCommandMap.find(cmdLine[0]) == supportedCommandMap.end()) {
702 return unsupported;
703 }
704 auto cmdInfo = supportedCommandMap.at(cmdLine[0]);
705 return cmdLine.size() == cmdInfo.numOfArgs ? cmdInfo.cmd : unsupported;
706 }
707
executeCommand(std::vector<std::string> cmdLine)708 void executeCommand(std::vector<std::string> cmdLine) {
709 switch (parseCommand(cmdLine, kAllCommands)) {
710 case connectEndpoint: {
711 onEndpointConnected(cmdLine[1]);
712 break;
713 }
714 case disableSetting: {
715 changeSetting(cmdLine[1], false);
716 break;
717 }
718 case disableTestMode: {
719 disableTestModeOnContextHub();
720 break;
721 }
722 case disconnectEndpoint: {
723 onEndpointDisconnected(cmdLine[1]);
724 break;
725 }
726 case enableSetting: {
727 changeSetting(cmdLine[1], true);
728 break;
729 }
730 case enableTestMode: {
731 enableTestModeOnContextHub();
732 break;
733 }
734 case getContextHubs: {
735 getAllContextHubs();
736 break;
737 }
738 case getPreloadedNanoappIds: {
739 getAllPreloadedNanoappIds();
740 break;
741 }
742 case list: {
743 std::map<std::string, NanoAppBinaryHeader> nanoapps{};
744 readNanoappHeaders(nanoapps, cmdLine[1]);
745 for (const auto &entity : nanoapps) {
746 std::cout << entity.first;
747 printNanoappHeader(entity.second);
748 }
749 break;
750 }
751 case load: {
752 loadNanoapp(cmdLine[1]);
753 break;
754 }
755 case query: {
756 queryNanoapps();
757 break;
758 }
759 case registerCallback: {
760 registerHostCallback();
761 break;
762 }
763 case sendMessage: {
764 sendMessageToNanoapp(cmdLine[1], cmdLine[2], cmdLine[3]);
765 break;
766 }
767 case unload: {
768 unloadNanoapp(cmdLine[1]);
769 break;
770 }
771 default:
772 printUsage(kAllCommands);
773 }
774 }
775
getCommandLine()776 std::vector<std::string> getCommandLine() {
777 std::string input;
778 std::cout << "> ";
779 std::getline(std::cin, input);
780 input.push_back('\n');
781 std::vector<std::string> result{};
782 for (int begin = 0, end = 0; end < input.size();) {
783 if (isspace(input[begin])) {
784 end = begin = begin + 1;
785 continue;
786 }
787 if (!isspace(input[end])) {
788 end += 1;
789 continue;
790 }
791 result.push_back(input.substr(begin, end - begin));
792 begin = end;
793 }
794 return result;
795 }
796
connectToHal()797 void connectToHal() {
798 if (gCallback == nullptr) {
799 gCallback = ContextHubCallback::make<ContextHubCallback>();
800 }
801 std::unique_ptr<HalClient> halClient = HalClient::create(gCallback);
802 if (halClient == nullptr || !halClient->connect()) {
803 LOGE("Failed to init the connection to HAL.");
804 return;
805 }
806 std::unordered_set<std::string> supportedCommands = {
807 "connectEndpoint", "disconnectEndpoint", "query", "sendMessage"};
808 std::map<std::string, CommandInfo> supportedCommandMap{};
809 fillSupportedCommandMap(supportedCommands, supportedCommandMap);
810
811 while (true) {
812 auto cmdLine = getCommandLine();
813 if (cmdLine.empty()) {
814 continue;
815 }
816 if (cmdLine.size() == 1 && cmdLine[0] == "exit") {
817 break;
818 }
819 try {
820 switch (parseCommand(cmdLine, supportedCommandMap)) {
821 case connectEndpoint: {
822 HostEndpointInfo info =
823 createHostEndpointInfo(/* hexEndpointId= */ cmdLine[1]);
824 verifyStatus(/* operation= */ "connect endpoint",
825 halClient->connectEndpoint(info));
826 break;
827 }
828
829 case query: {
830 verifyStatusAndSignal(/* operation= */ "querying nanoapps",
831 halClient->queryNanoapps(),
832 gCallback->promise.get_future());
833 break;
834 }
835
836 case disconnectEndpoint: {
837 uint16_t hostEndpointId =
838 verifyAndConvertEndpointHexId(/* number= */ cmdLine[1]);
839 verifyStatus(/* operation= */ "disconnect endpoint",
840 halClient->disconnectEndpoint(hostEndpointId));
841 break;
842 }
843 case sendMessage: {
844 ContextHubMessage message = createContextHubMessage(
845 /* hexHostEndpointId= */ cmdLine[1],
846 /* appIdOrName= */ cmdLine[2], /* hexPayload= */ cmdLine[3]);
847 verifyStatusAndSignal(
848 /* operation= */ "sending a message to " + cmdLine[2],
849 halClient->sendMessage(message), gCallback->promise.get_future());
850 break;
851 }
852 default:
853 printUsage(supportedCommandMap);
854 }
855 } catch (std::system_error &e) {
856 std::cerr << e.what() << std::endl;
857 }
858 }
859 }
860 } // anonymous namespace
861
main(int argc,char * argv[])862 int main(int argc, char *argv[]) {
863 // Start binder thread pool to enable callbacks.
864 ABinderProcess_startThreadPool();
865
866 std::vector<std::string> cmdLine{};
867 for (int i = 1; i < argc; i++) {
868 cmdLine.emplace_back(argv[i]);
869 }
870 try {
871 if (cmdLine.size() == 1 && cmdLine[0] == "connect") {
872 connectToHal();
873 return 0;
874 }
875 executeCommand(cmdLine);
876 } catch (std::system_error &e) {
877 std::cerr << e.what() << std::endl;
878 return -1;
879 }
880 return 0;
881 }
882