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