blob: 4f357494645089e2377e1b2668e99014cfc20afc [file] [log] [blame]
/*
* 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 <aidl/android/hardware/contexthub/BnContextHubCallback.h>
#include <aidl/android/hardware/contexthub/IContextHub.h>
#include <aidl/android/hardware/contexthub/NanoappBinary.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <dirent.h>
#include <utils/String16.h>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <future>
#include <map>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
#include "chre_api/chre/version.h"
#include "chre_host/file_stream.h"
#include "chre_host/hal_client.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::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<uint8_t, 16> 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<uint16_t>::max()) {
return static_cast<uint16_t>(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<NanoappInfo> &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<std::string> & /*msgContentPerms*/) override {
std::cout << "Received a message!" << std::endl
<< " From: 0x" << std::hex << message.nanoappId << std::endl
<< " To: 0x" << 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<uint16_t>(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 getUuid(std::array<uint8_t, 16> *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<void>{};
}
// 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<void> promise;
};
std::shared_ptr<IContextHub> gContextHub = nullptr;
std::shared_ptr<ContextHubCallback> gCallback = nullptr;
void registerHostCallback() {
if (gCallback != nullptr) {
gCallback.reset();
}
gCallback = ContextHubCallback::make<ContextHubCallback>();
if (!gContextHub->registerCallback(kContextHubId, gCallback).isOk()) {
throwError("Failed to register the callback");
}
}
/** Initializes gContextHub and register gCallback. */
std::shared_ptr<IContextHub> 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<int>(header.targetChreApiMajorVersion) << "."
<< static_cast<int>(header.targetChreApiMinorVersion) << "\n}"
<< std::endl;
}
std::unique_ptr<NanoAppBinaryHeader> 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<NanoAppBinaryHeader> 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<NanoAppBinaryHeader>();
input.read(reinterpret_cast<char *>(result.get()),
sizeof(NanoAppBinaryHeader));
break;
}
closedir(dir);
return result;
}
void readNanoappHeaders(std::map<std::string, NanoAppBinaryHeader> &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<char *>(&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<void> &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 <absolute-path><name>.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<NanoAppBinaryHeader> findHeaderAndNormalizePath(
std::string &pathAndName) {
// To match the file pattern of [path]<name>[.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<int64_t>(findHeaderAndNormalizePath(appIdOrName)->appId);
}
return appId;
}
void getAllContextHubs() {
std::vector<ContextHubInfo> 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<uint32_t>(hub.chreApiMajorVersion)
<< "." << static_cast<uint32_t>(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<uint8_t> soBuffer{};
if (!readFileContents(pathAndName.c_str(), soBuffer)) {
throwError("Failed to open the content of " + pathAndName);
}
NanoappBinary binary;
binary.nanoappId = static_cast<int64_t>(header->appId);
binary.customBinary = soBuffer;
binary.flags = static_cast<int32_t>(header->flags);
binary.targetChreApiMajorVersion =
static_cast<int8_t>(header->targetChreApiMajorVersion);
binary.targetChreApiMinorVersion =
static_cast<int8_t>(header->targetChreApiMinorVersion);
binary.nanoappVersion = static_cast<int32_t>(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<Setting>(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(true);
verifyStatus(/* operation= */ "enabling test mode", status);
std::cout << "Test mode is enabled" << std::endl;
}
void disableTestModeOnContextHub() {
auto status = getContextHub()->setTestMode(false);
verifyStatus(/* operation= */ "disabling test mode", status);
std::cout << "Test mode is disabled" << std::endl;
}
void getAllPreloadedNanoappIds() {
std::vector<int64_t> 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<std::string, CommandInfo> 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 = "<HEX_ENDPOINT_ID>",
.usage =
"associate an endpoint with the current client and notify HAL."}},
{"disableSetting",
{.cmd = disableSetting,
.numOfArgs = 2,
.argsFormat = "<SETTING>",
.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 = "<HEX_ENDPOINT_ID>",
.usage = "remove an endpoint with the current client and notify HAL."}},
{"enableSetting",
{.cmd = enableSetting,
.numOfArgs = 2,
.argsFormat = "<SETTING>",
.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 = "</PATH/TO/NANOAPPS>",
.usage = "list all the nanoapps' header info in the path."}},
{"load",
{.cmd = load,
.numOfArgs = 2,
.argsFormat = "<APP_NAME | /PATH/TO/APP_NAME>",
.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 = "<HEX_ENDPOINT_ID> <HEX_NANOAPP_ID | APP_NAME | "
"/PATH/TO/APP_NAME> <HEX_PAYLOAD>",
.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 = "<HEX_NANOAPP_ID | APP_NAME | /PATH/TO/APP_NAME>",
.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<std::string> &supportedCommands,
std::map<std::string, CommandInfo> &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<std::string, CommandInfo> &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<std::string> &cmdLine,
const std::map<std::string, CommandInfo> &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<std::string> 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<std::string, NanoAppBinaryHeader> 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<std::string> getCommandLine() {
std::string input;
std::cout << "> ";
std::getline(std::cin, input);
input.push_back('\n');
std::vector<std::string> 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<ContextHubCallback>();
}
std::unique_ptr<HalClient> halClient = HalClient::create(gCallback);
if (halClient == nullptr) {
LOGE("Failed to init the connection to HAL.");
return;
}
std::unordered_set<std::string> supportedCommands = {
"connectEndpoint", "disconnectEndpoint", "query", "sendMessage"};
std::map<std::string, CommandInfo> 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<std::string> 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;
}