/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "chre_api/chre/version.h" #include "chre_host/file_stream.h" #include "chre_host/hal_client.h" #include "chre_host/log.h" #include "chre_host/napp_header.h" using ::aidl::android::hardware::contexthub::AsyncEventType; using ::aidl::android::hardware::contexthub::BnContextHubCallback; using ::aidl::android::hardware::contexthub::ContextHubInfo; using ::aidl::android::hardware::contexthub::ContextHubMessage; using ::aidl::android::hardware::contexthub::HostEndpointInfo; using ::aidl::android::hardware::contexthub::IContextHub; using ::aidl::android::hardware::contexthub::MessageDeliveryStatus; using ::aidl::android::hardware::contexthub::NanoappBinary; using ::aidl::android::hardware::contexthub::NanoappInfo; using ::aidl::android::hardware::contexthub::NanSessionRequest; using ::aidl::android::hardware::contexthub::Setting; using ::android::chre::HalClient; using ::android::chre::NanoAppBinaryHeader; using ::android::chre::readFileContents; using ::android::internal::ToString; using ::ndk::ScopedAStatus; namespace { // A default id 0 is used for every command requiring a context hub id. When // this is not the case the id number should be one of the arguments of the // commands. constexpr uint32_t kContextHubId = 0; constexpr int32_t kLoadTransactionId = 1; constexpr int32_t kUnloadTransactionId = 2; // Though IContextHub.aidl says loading operation is capped at 30s to finish, // multiclient HAL can terminate a load/unload transaction after 5s to avoid // blocking other load/unload transactions. constexpr auto kTimeOutThresholdInSec = std::chrono::seconds(5); // 34a3a27e-9b83-4098-b564-e83b0c28d4bb constexpr std::array kUuid = {0x34, 0xa3, 0xa2, 0x7e, 0x9b, 0x83, 0x40, 0x98, 0xb5, 0x64, 0xe8, 0x3b, 0x0c, 0x28, 0xd4, 0xbb}; // Locations should be searched in the sequence defined below: const char *kPredefinedNanoappPaths[] = { "/vendor/etc/chre/", "/vendor/dsp/adsp/", "/vendor/dsp/sdsp/", "/vendor/lib/rfsa/adsp/", }; const std::string kClientName{"ChreAidlHalClient"}; inline void throwError(const std::string &message) { throw std::system_error{std::error_code(), message}; } bool isValidHexNumber(const std::string &number) { if (number.empty() || (number.substr(0, 2) != "0x" && number.substr(0, 2) != "0X")) { return false; } for (int i = 2; i < number.size(); i++) { if (!isxdigit(number[i])) { throwError("Hex app id " + number + " contains invalid character."); } } return number.size() > 2; } uint16_t verifyAndConvertEndpointHexId(const std::string &number) { // host endpoint id must be a 16-bits long hex number. if (isValidHexNumber(number)) { int convertedNumber = std::stoi(number, /* idx= */ nullptr, /* base= */ 16); if (convertedNumber < std::numeric_limits::max()) { return static_cast(convertedNumber); } } throwError("host endpoint id must be a 16-bits long hex number."); return 0; // code never reached. } bool isValidNanoappHexId(const std::string &number) { if (!isValidHexNumber(number)) { return false; } // Once the input has the hex prefix, an exception will be thrown if it is // malformed because it shouldn't be treated as an app name anymore. if (number.size() > 18) { throwError("Hex app id must has a length of [3, 18] including the prefix."); } return true; } std::string parseAppVersion(uint32_t version) { std::ostringstream stringStream; stringStream << std::hex << "0x" << version << std::dec << " (v" << CHRE_EXTRACT_MAJOR_VERSION(version) << "." << CHRE_EXTRACT_MINOR_VERSION(version) << "." << CHRE_EXTRACT_PATCH_VERSION(version) << ")"; return stringStream.str(); } std::string parseTransactionId(int32_t transactionId) { switch (transactionId) { case kLoadTransactionId: return "Loading"; case kUnloadTransactionId: return "Unloading"; default: return "Unknown"; } } class ContextHubCallback : public BnContextHubCallback { public: ScopedAStatus handleNanoappInfo( const std::vector &appInfo) override { std::cout << appInfo.size() << " nanoapps loaded" << std::endl; for (const NanoappInfo &app : appInfo) { std::cout << "appId: 0x" << std::hex << app.nanoappId << std::dec << " {" << "\n\tappVersion: " << parseAppVersion(app.nanoappVersion) << "\n\tenabled: " << (app.enabled ? "true" : "false") << "\n\tpermissions: " << ToString(app.permissions) << "\n\trpcServices: " << ToString(app.rpcServices) << "\n}" << std::endl; } resetPromise(); return ScopedAStatus::ok(); } ScopedAStatus handleContextHubMessage( const ContextHubMessage &message, const std::vector & /*msgContentPerms*/) override { std::cout << "Received a message!" << std::endl << " From: 0x" << std::hex << message.nanoappId << std::endl << " To: 0x" << static_cast(message.hostEndPoint) << std::endl << " Body: (type " << message.messageType << " size " << message.messageBody.size() << ") 0x"; for (const uint8_t &data : message.messageBody) { std::cout << std::hex << static_cast(data); } std::cout << std::endl << std::endl; resetPromise(); return ScopedAStatus::ok(); } ScopedAStatus handleContextHubAsyncEvent(AsyncEventType event) override { std::cout << "Received async event " << toString(event) << std::endl; resetPromise(); return ScopedAStatus::ok(); } // Called after loading/unloading a nanoapp. ScopedAStatus handleTransactionResult(int32_t transactionId, bool success) override { std::cout << parseTransactionId(transactionId) << " transaction is " << (success ? "successful" : "failed") << std::endl; resetPromise(); return ScopedAStatus::ok(); } ScopedAStatus handleNanSessionRequest( const NanSessionRequest & /* request */) override { resetPromise(); return ScopedAStatus::ok(); } ScopedAStatus handleMessageDeliveryStatus( char16_t /* hostEndPointId */, const MessageDeliveryStatus & /* messageDeliveryStatus */) override { resetPromise(); return ScopedAStatus::ok(); } ScopedAStatus getUuid(std::array *out_uuid) override { *out_uuid = kUuid; return ScopedAStatus::ok(); } ScopedAStatus getName(std::string *out_name) override { *out_name = kClientName; return ScopedAStatus::ok(); } void resetPromise() { promise.set_value(); promise = std::promise{}; } // TODO(b/247124878): // This promise is shared among all the HAL callbacks to simplify the // implementation. This is based on the assumption that every command should // get a response before timeout and the first callback triggered is for the // response. // // In very rare cases, however, the assumption doesn't hold: // - multiple callbacks are triggered by a command and come back out of order // - one command is timed out and the user typed in another command then the // first callback for the first command is triggered // Once we have a chance we should consider refactor this design to let each // callback use their specific promises. std::promise promise; }; std::shared_ptr gContextHub = nullptr; std::shared_ptr gCallback = nullptr; void registerHostCallback() { if (gCallback != nullptr) { gCallback.reset(); } gCallback = ContextHubCallback::make(); if (!gContextHub->registerCallback(kContextHubId, gCallback).isOk()) { throwError("Failed to register the callback"); } } /** Initializes gContextHub and register gCallback. */ std::shared_ptr getContextHub() { if (gContextHub == nullptr) { auto aidlServiceName = std::string() + IContextHub::descriptor + "/default"; ndk::SpAIBinder binder( AServiceManager_waitForService(aidlServiceName.c_str())); if (binder.get() == nullptr) { throwError("Could not find Context Hub HAL"); } gContextHub = IContextHub::fromBinder(binder); } if (gCallback == nullptr) { registerHostCallback(); } return gContextHub; } void printNanoappHeader(const NanoAppBinaryHeader &header) { std::cout << " {" << "\n\tappId: 0x" << std::hex << header.appId << std::dec << "\n\tappVersion: " << parseAppVersion(header.appVersion) << "\n\tflags: " << header.flags << "\n\ttarget CHRE API version: " << static_cast(header.targetChreApiMajorVersion) << "." << static_cast(header.targetChreApiMinorVersion) << "\n}" << std::endl; } std::unique_ptr findHeaderByName( const std::string &appName, const std::string &binaryPath) { DIR *dir = opendir(binaryPath.c_str()); if (dir == nullptr) { return nullptr; } std::regex regex(appName + ".napp_header"); std::cmatch match; std::unique_ptr result = nullptr; for (struct dirent *entry; (entry = readdir(dir)) != nullptr;) { if (!std::regex_match(entry->d_name, match, regex)) { continue; } std::ifstream input(std::string(binaryPath) + "/" + entry->d_name, std::ios::binary); result = std::make_unique(); input.read(reinterpret_cast(result.get()), sizeof(NanoAppBinaryHeader)); break; } closedir(dir); return result; } void readNanoappHeaders(std::map &nanoapps, const std::string &binaryPath) { DIR *dir = opendir(binaryPath.c_str()); if (dir == nullptr) { return; } std::regex regex("(\\w+)\\.napp_header"); std::cmatch match; for (struct dirent *entry; (entry = readdir(dir)) != nullptr;) { if (!std::regex_match(entry->d_name, match, regex)) { continue; } std::ifstream input(std::string(binaryPath) + "/" + entry->d_name, std::ios::binary); input.read(reinterpret_cast(&nanoapps[match[1]]), sizeof(NanoAppBinaryHeader)); } closedir(dir); } void verifyStatus(const std::string &operation, const ScopedAStatus &status) { if (!status.isOk()) { gCallback->resetPromise(); throwError(operation + " fails with abnormal status " + ToString(status.getMessage()) + " error code " + ToString(status.getServiceSpecificError())); } } void verifyStatusAndSignal(const std::string &operation, const ScopedAStatus &status, const std::future &future_signal) { verifyStatus(operation, status); std::future_status future_status = future_signal.wait_for(kTimeOutThresholdInSec); if (future_status != std::future_status::ready) { gCallback->resetPromise(); throwError(operation + " doesn't finish within " + ToString(kTimeOutThresholdInSec.count()) + " seconds"); } } /** Finds the .napp_header file associated to the nanoapp. * * This function guarantees to return a non-null {@link NanoAppBinaryHeader} * pointer. In case a .napp_header file cannot be found an exception will be * raised. * * @param pathAndName name of the nanoapp that might be prefixed with it path. * It will be normalized to the format of .so at the end. * For example, "abc" will be changed to "/path/to/abc.so". * @return a unique pointer to the {@link NanoAppBinaryHeader} found */ std::unique_ptr findHeaderAndNormalizePath( std::string &pathAndName) { // To match the file pattern of [path][.so] std::regex pathNameRegex("(.*?)(\\w+)(\\.so)?"); std::smatch smatch; if (!std::regex_match(pathAndName, smatch, pathNameRegex)) { throwError("Invalid nanoapp: " + pathAndName); } std::string fullPath = smatch[1]; std::string appName = smatch[2]; // absolute path is provided: if (!fullPath.empty() && fullPath[0] == '/') { auto result = findHeaderByName(appName, fullPath); if (result == nullptr) { throwError("Unable to find the nanoapp header for " + pathAndName); } pathAndName = fullPath + appName + ".so"; return result; } // relative path is searched form predefined locations: for (const std::string &predefinedPath : kPredefinedNanoappPaths) { auto result = findHeaderByName(appName, predefinedPath); if (result == nullptr) { continue; } pathAndName = predefinedPath + appName + ".so"; return result; } throwError("Unable to find the nanoapp header for " + pathAndName); return nullptr; } int64_t getNanoappIdFrom(std::string &appIdOrName) { int64_t appId; if (isValidNanoappHexId(appIdOrName)) { appId = std::stoll(appIdOrName, nullptr, 16); } else { // Treat the appIdOrName as the app name and try again appId = static_cast(findHeaderAndNormalizePath(appIdOrName)->appId); } return appId; } void getAllContextHubs() { std::vector hubs{}; getContextHub()->getContextHubs(&hubs); if (hubs.empty()) { std::cerr << "Failed to get any context hub." << std::endl; return; } for (const auto &hub : hubs) { std::cout << "Context Hub " << hub.id << ": " << std::endl << " Name: " << hub.name << std::endl << " Vendor: " << hub.vendor << std::endl << " Max support message length (bytes): " << hub.maxSupportedMessageLengthBytes << std::endl << " Version: " << static_cast(hub.chreApiMajorVersion) << "." << static_cast(hub.chreApiMinorVersion) << std::endl << " Chre platform id: 0x" << std::hex << hub.chrePlatformId << std::endl; } } void loadNanoapp(std::string &pathAndName) { auto header = findHeaderAndNormalizePath(pathAndName); std::vector soBuffer{}; if (!readFileContents(pathAndName.c_str(), soBuffer)) { throwError("Failed to open the content of " + pathAndName); } NanoappBinary binary; binary.nanoappId = static_cast(header->appId); binary.customBinary = soBuffer; binary.flags = static_cast(header->flags); binary.targetChreApiMajorVersion = static_cast(header->targetChreApiMajorVersion); binary.targetChreApiMinorVersion = static_cast(header->targetChreApiMinorVersion); binary.nanoappVersion = static_cast(header->appVersion); auto status = getContextHub()->loadNanoapp(kContextHubId, binary, kLoadTransactionId); verifyStatusAndSignal(/* operation= */ "loading nanoapp " + pathAndName, status, gCallback->promise.get_future()); } void unloadNanoapp(std::string &appIdOrName) { auto appId = getNanoappIdFrom(appIdOrName); auto status = getContextHub()->unloadNanoapp(kContextHubId, appId, kUnloadTransactionId); verifyStatusAndSignal(/* operation= */ "unloading nanoapp " + appIdOrName, status, gCallback->promise.get_future()); } void queryNanoapps() { auto status = getContextHub()->queryNanoapps(kContextHubId); verifyStatusAndSignal(/* operation= */ "querying nanoapps", status, gCallback->promise.get_future()); } HostEndpointInfo createHostEndpointInfo(const std::string &hexEndpointId) { uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexEndpointId); return { .hostEndpointId = hostEndpointId, .type = HostEndpointInfo::Type::NATIVE, .packageName = "chre_aidl_hal_client", .attributionTag{}, }; } void onEndpointConnected(const std::string &hexEndpointId) { auto contextHub = getContextHub(); HostEndpointInfo info = createHostEndpointInfo(hexEndpointId); // connect the endpoint to HAL verifyStatus(/* operation= */ "connect endpoint", contextHub->onHostEndpointConnected(info)); std::cout << "Connected." << std::endl; } void onEndpointDisconnected(const std::string &hexEndpointId) { auto contextHub = getContextHub(); uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexEndpointId); // disconnect the endpoint from HAL verifyStatus(/* operation= */ "disconnect endpoint", contextHub->onHostEndpointDisconnected(hostEndpointId)); std::cout << "Disconnected." << std::endl; } ContextHubMessage createContextHubMessage(const std::string &hexHostEndpointId, std::string &appIdOrName, const std::string &hexPayload) { if (!isValidHexNumber(hexPayload)) { throwError("Invalid hex payload."); } auto appId = getNanoappIdFrom(appIdOrName); uint16_t hostEndpointId = verifyAndConvertEndpointHexId(hexHostEndpointId); ContextHubMessage contextHubMessage = { .nanoappId = appId, .hostEndPoint = hostEndpointId, .messageBody = {}, .permissions = {}, }; // populate the payload for (int i = 2; i < hexPayload.size(); i += 2) { contextHubMessage.messageBody.push_back( std::stoi(hexPayload.substr(i, 2), /* idx= */ nullptr, /* base= */ 16)); } return contextHubMessage; } /** Sends a hexPayload from hexHostEndpointId to appIdOrName. */ void sendMessageToNanoapp(const std::string &hexHostEndpointId, std::string &appIdOrName, const std::string &hexPayload) { ContextHubMessage contextHubMessage = createContextHubMessage(hexHostEndpointId, appIdOrName, hexPayload); // send the message auto contextHub = getContextHub(); auto status = contextHub->sendMessageToHub(kContextHubId, contextHubMessage); verifyStatusAndSignal(/* operation= */ "sending a message to " + appIdOrName, status, gCallback->promise.get_future()); } void changeSetting(const std::string &setting, bool enabled) { auto contextHub = getContextHub(); int settingType = std::stoi(setting); if (settingType < 1 || settingType > 7) { throwError("setting type must be within [1, 7]."); } ScopedAStatus status = contextHub->onSettingChanged(static_cast(settingType), enabled); std::cout << "onSettingChanged is called to " << (enabled ? "enable" : "disable") << " setting type " << settingType << std::endl; verifyStatus("change setting", status); } void enableTestModeOnContextHub() { auto status = getContextHub()->setTestMode(/* enable= */ true); verifyStatus(/* operation= */ "enabling test mode", status); std::cout << "Test mode is enabled" << std::endl; } void disableTestModeOnContextHub() { auto status = getContextHub()->setTestMode(/* enable= */false); verifyStatus(/* operation= */ "disabling test mode", status); std::cout << "Test mode is disabled" << std::endl; } void getAllPreloadedNanoappIds() { std::vector appIds{}; verifyStatus("get preloaded nanoapp ids", getContextHub()->getPreloadedNanoappIds(kContextHubId, &appIds)); for (const auto &appId : appIds) { std::cout << "0x" << std::hex << appId << std::endl; } } // Please keep Command in alphabetical order enum Command { connect, connectEndpoint, disableSetting, disableTestMode, disconnectEndpoint, enableSetting, enableTestMode, getContextHubs, getPreloadedNanoappIds, list, load, query, registerCallback, sendMessage, unload, unsupported }; struct CommandInfo { Command cmd; u_int8_t numOfArgs; // including cmd; std::string argsFormat; std::string usage; }; const std::map kAllCommands{ {"connect", {.cmd = connect, .numOfArgs = 1, .argsFormat = "", .usage = "connect to HAL using hal_client library and keep the session " "alive while user can execute other commands. Use 'exit' to " "quit the session."}}, {"connectEndpoint", {.cmd = connectEndpoint, .numOfArgs = 2, .argsFormat = "", .usage = "associate an endpoint with the current client and notify HAL."}}, {"disableSetting", {.cmd = disableSetting, .numOfArgs = 2, .argsFormat = "", .usage = "disable a setting identified by a number defined in " "android/hardware/contexthub/Setting.aidl."}}, {"disableTestMode", {.cmd = disableTestMode, .numOfArgs = 1, .argsFormat = "", .usage = "disable test mode."}}, {"disconnectEndpoint", {.cmd = disconnectEndpoint, .numOfArgs = 2, .argsFormat = "", .usage = "remove an endpoint with the current client and notify HAL."}}, {"enableSetting", {.cmd = enableSetting, .numOfArgs = 2, .argsFormat = "", .usage = "enable a setting identified by a number defined in " "android/hardware/contexthub/Setting.aidl."}}, {"enableTestMode", {.cmd = enableTestMode, .numOfArgs = 1, .argsFormat = "", .usage = "enable test mode."}}, {"getContextHubs", {.cmd = getContextHubs, .numOfArgs = 1, .argsFormat = "", .usage = "get all the context hubs."}}, {"getPreloadedNanoappIds", {.cmd = getPreloadedNanoappIds, .numOfArgs = 1, .argsFormat = "", .usage = "get a list of ids for the preloaded nanoapps."}}, {"list", {.cmd = list, .numOfArgs = 2, .argsFormat = "", .usage = "list all the nanoapps' header info in the path."}}, {"load", {.cmd = load, .numOfArgs = 2, .argsFormat = "", .usage = "load the nanoapp specified by the name. If an absolute path is " "not provided the default locations are searched."}}, {"query", {.cmd = query, .numOfArgs = 1, .argsFormat = "", .usage = "show all loaded nanoapps (system apps excluded)."}}, {"registerCallback", {.cmd = registerCallback, .numOfArgs = 1, .argsFormat = "", .usage = "register a callback for the current client."}}, {"sendMessage", {.cmd = sendMessage, .numOfArgs = 4, .argsFormat = " ", .usage = "send a payload to a nanoapp. If an absolute path is not " "provided the default locations are searched."}}, {"unload", {.cmd = unload, .numOfArgs = 2, .argsFormat = "", .usage = "unload the nanoapp specified by either the nanoapp id or the " "app name. If an absolute path is not provided the default " "locations are searched."}}, }; void fillSupportedCommandMap( const std::unordered_set &supportedCommands, std::map &supportedCommandMap) { std::copy_if(kAllCommands.begin(), kAllCommands.end(), std::inserter(supportedCommandMap, supportedCommandMap.begin()), [&](auto const &kv_pair) { return supportedCommands.find(kv_pair.first) != supportedCommands.end(); }); } void printUsage(const std::map &supportedCommands) { constexpr uint32_t kCommandLength = 40; std::cout << std::left << "Usage: COMMAND [ARGUMENTS]" << std::endl; for (auto const &kv_pair : supportedCommands) { std::string cmdLine = kv_pair.first + " " + kv_pair.second.argsFormat; std::cout << std::setw(kCommandLength) << cmdLine; if (cmdLine.size() > kCommandLength) { std::cout << std::endl << std::string(kCommandLength, ' '); } std::cout << " - " + kv_pair.second.usage << std::endl; } std::cout << std::endl; } Command parseCommand( const std::vector &cmdLine, const std::map &supportedCommandMap) { if (cmdLine.empty() || supportedCommandMap.find(cmdLine[0]) == supportedCommandMap.end()) { return unsupported; } auto cmdInfo = supportedCommandMap.at(cmdLine[0]); return cmdLine.size() == cmdInfo.numOfArgs ? cmdInfo.cmd : unsupported; } void executeCommand(std::vector cmdLine) { switch (parseCommand(cmdLine, kAllCommands)) { case connectEndpoint: { onEndpointConnected(cmdLine[1]); break; } case disableSetting: { changeSetting(cmdLine[1], false); break; } case disableTestMode: { disableTestModeOnContextHub(); break; } case disconnectEndpoint: { onEndpointDisconnected(cmdLine[1]); break; } case enableSetting: { changeSetting(cmdLine[1], true); break; } case enableTestMode: { enableTestModeOnContextHub(); break; } case getContextHubs: { getAllContextHubs(); break; } case getPreloadedNanoappIds: { getAllPreloadedNanoappIds(); break; } case list: { std::map nanoapps{}; readNanoappHeaders(nanoapps, cmdLine[1]); for (const auto &entity : nanoapps) { std::cout << entity.first; printNanoappHeader(entity.second); } break; } case load: { loadNanoapp(cmdLine[1]); break; } case query: { queryNanoapps(); break; } case registerCallback: { registerHostCallback(); break; } case sendMessage: { sendMessageToNanoapp(cmdLine[1], cmdLine[2], cmdLine[3]); break; } case unload: { unloadNanoapp(cmdLine[1]); break; } default: printUsage(kAllCommands); } } std::vector getCommandLine() { std::string input; std::cout << "> "; std::getline(std::cin, input); input.push_back('\n'); std::vector result{}; for (int begin = 0, end = 0; end < input.size();) { if (isspace(input[begin])) { end = begin = begin + 1; continue; } if (!isspace(input[end])) { end += 1; continue; } result.push_back(input.substr(begin, end - begin)); begin = end; } return result; } void connectToHal() { if (gCallback == nullptr) { gCallback = ContextHubCallback::make(); } std::unique_ptr halClient = HalClient::create(gCallback); if (halClient == nullptr || !halClient->connect()) { LOGE("Failed to init the connection to HAL."); return; } std::unordered_set supportedCommands = { "connectEndpoint", "disconnectEndpoint", "query", "sendMessage"}; std::map supportedCommandMap{}; fillSupportedCommandMap(supportedCommands, supportedCommandMap); while (true) { auto cmdLine = getCommandLine(); if (cmdLine.empty()) { continue; } if (cmdLine.size() == 1 && cmdLine[0] == "exit") { break; } try { switch (parseCommand(cmdLine, supportedCommandMap)) { case connectEndpoint: { HostEndpointInfo info = createHostEndpointInfo(/* hexEndpointId= */ cmdLine[1]); verifyStatus(/* operation= */ "connect endpoint", halClient->connectEndpoint(info)); break; } case query: { verifyStatusAndSignal(/* operation= */ "querying nanoapps", halClient->queryNanoapps(), gCallback->promise.get_future()); break; } case disconnectEndpoint: { uint16_t hostEndpointId = verifyAndConvertEndpointHexId(/* number= */ cmdLine[1]); verifyStatus(/* operation= */ "disconnect endpoint", halClient->disconnectEndpoint(hostEndpointId)); break; } case sendMessage: { ContextHubMessage message = createContextHubMessage( /* hexHostEndpointId= */ cmdLine[1], /* appIdOrName= */ cmdLine[2], /* hexPayload= */ cmdLine[3]); verifyStatusAndSignal( /* operation= */ "sending a message to " + cmdLine[2], halClient->sendMessage(message), gCallback->promise.get_future()); break; } default: printUsage(supportedCommandMap); } } catch (std::system_error &e) { std::cerr << e.what() << std::endl; } } } } // anonymous namespace int main(int argc, char *argv[]) { // Start binder thread pool to enable callbacks. ABinderProcess_startThreadPool(); std::vector cmdLine{}; for (int i = 1; i < argc; i++) { cmdLine.emplace_back(argv[i]); } try { if (cmdLine.size() == 1 && cmdLine[0] == "connect") { connectToHal(); return 0; } executeCommand(cmdLine); } catch (std::system_error &e) { std::cerr << e.what() << std::endl; return -1; } return 0; }