blob: b6fb892b4f805e82cf320b511d90f918553fea56 [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 "chre_host/preloaded_nanoapp_loader.h"
#include <chre_host/host_protocol_host.h>
#include <fstream>
#include "chre_host/config_util.h"
#include "chre_host/file_stream.h"
#include "chre_host/fragmented_load_transaction.h"
#include "chre_host/log.h"
#include "hal_client_id.h"
namespace android::chre {
using android::chre::readFileContents;
using android::hardware::contexthub::common::implementation::kHalId;
void PreloadedNanoappLoader::getPreloadedNanoappIds(
std::vector<uint64_t> &out_preloadedNanoappIds) {
std::vector<std::string> nanoappNames;
std::string directory;
out_preloadedNanoappIds.clear();
bool success =
getPreloadedNanoappsFromConfigFile(mConfigPath, directory, nanoappNames);
if (!success) {
LOGE("Failed to parse preloaded nanoapps config file");
}
for (const std::string &nanoappName : nanoappNames) {
std::string headerFile = directory + "/" + nanoappName + ".napp_header";
std::vector<uint8_t> headerBuffer;
if (!readFileContents(headerFile.c_str(), headerBuffer)) {
LOGE("Cannot read header file: %s", headerFile.c_str());
continue;
}
if (headerBuffer.size() != sizeof(NanoAppBinaryHeader)) {
LOGE("Header size mismatch");
continue;
}
const auto *appHeader =
reinterpret_cast<const NanoAppBinaryHeader *>(headerBuffer.data());
out_preloadedNanoappIds.emplace_back(appHeader->appId);
}
}
void PreloadedNanoappLoader::loadPreloadedNanoapps() {
std::string directory;
std::vector<std::string> nanoapps;
bool success =
getPreloadedNanoappsFromConfigFile(mConfigPath, directory, nanoapps);
if (!success) {
LOGE("Failed to load any preloaded nanoapp");
} else {
mIsPreloadingOngoing = true;
for (uint32_t i = 0; i < nanoapps.size(); ++i) {
loadPreloadedNanoapp(directory, nanoapps[i], i);
}
mIsPreloadingOngoing = false;
}
}
void PreloadedNanoappLoader::loadPreloadedNanoapp(const std::string &directory,
const std::string &name,
uint32_t transactionId) {
std::vector<uint8_t> headerBuffer;
std::vector<uint8_t> nanoappBuffer;
std::string headerFilename = directory + "/" + name + ".napp_header";
std::string nanoappFilename = directory + "/" + name + ".so";
if (!readFileContents(headerFilename.c_str(), headerBuffer) ||
!readFileContents(nanoappFilename.c_str(), nanoappBuffer) ||
!loadNanoapp(headerBuffer, nanoappBuffer, transactionId)) {
LOGE("Failed to load nanoapp: '%s'", name.c_str());
}
}
bool PreloadedNanoappLoader::loadNanoapp(const std::vector<uint8_t> &header,
const std::vector<uint8_t> &nanoapp,
uint32_t transactionId) {
if (header.size() != sizeof(NanoAppBinaryHeader)) {
LOGE("Nanoapp binary's header size is incorrect");
return false;
}
const auto *appHeader =
reinterpret_cast<const NanoAppBinaryHeader *>(header.data());
// Build the target API version from major and minor.
uint32_t targetApiVersion = (appHeader->targetChreApiMajorVersion << 24) |
(appHeader->targetChreApiMinorVersion << 16);
return sendFragmentedLoadAndWaitForEachResponse(
appHeader->appId, appHeader->appVersion, appHeader->flags,
targetApiVersion, nanoapp.data(), nanoapp.size(), transactionId);
}
bool PreloadedNanoappLoader::sendFragmentedLoadAndWaitForEachResponse(
uint64_t appId, uint32_t appVersion, uint32_t appFlags,
uint32_t appTargetApiVersion, const uint8_t *appBinary, size_t appSize,
uint32_t transactionId) {
std::vector<uint8_t> binary(appSize);
std::copy(appBinary, appBinary + appSize, binary.begin());
FragmentedLoadTransaction transaction(transactionId, appId, appVersion,
appFlags, appTargetApiVersion, binary);
while (!transaction.isComplete()) {
auto nextRequest = transaction.getNextRequest();
auto future = sendFragmentedLoadRequest(nextRequest);
if (!waitAndVerifyFuture(future, nextRequest)) {
return false;
}
}
return true;
}
bool PreloadedNanoappLoader::waitAndVerifyFuture(
std::future<bool> &future, const FragmentedLoadRequest &request) {
if (!future.valid()) {
LOGE("Failed to send out the fragmented load fragment");
return false;
}
if (future.wait_for(kTimeoutInMs) != std::future_status::ready) {
LOGE(
"Waiting for response of fragment %zu transaction %d times out "
"after %lld ms",
request.fragmentId, request.transactionId, kTimeoutInMs.count());
return false;
}
if (!future.get()) {
LOGE(
"Received a failure result for loading fragment %zu of "
"transaction %d",
request.fragmentId, request.transactionId);
return false;
}
return true;
}
bool PreloadedNanoappLoader::verifyFragmentLoadResponse(
const ::chre::fbs::LoadNanoappResponseT &response) const {
if (!response.success) {
LOGE("Loading nanoapp binary fragment %d of transaction %u failed.",
response.fragment_id, response.transaction_id);
// TODO(b/247124878): Report metrics.
return false;
}
if (mPreloadedNanoappPendingTransaction.transactionId !=
response.transaction_id) {
LOGE(
"Fragmented load response with transactionId %u but transactionId "
"%u is expected",
response.transaction_id,
mPreloadedNanoappPendingTransaction.transactionId);
return false;
}
if (mPreloadedNanoappPendingTransaction.fragmentId != response.fragment_id) {
LOGE(
"Fragmented load response with unexpected fragment id %u while "
"%zu is expected",
response.fragment_id, mPreloadedNanoappPendingTransaction.fragmentId);
return false;
}
return true;
}
bool PreloadedNanoappLoader::onLoadNanoappResponse(
const ::chre::fbs::LoadNanoappResponseT &response, HalClientId clientId) {
std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
if (clientId != kHalId || !mFragmentedLoadPromise.has_value()) {
LOGE(
"Received an unexpected preload nanoapp %s response for client %d "
"transaction %u fragment %u",
response.success ? "success" : "failure", clientId,
response.transaction_id, response.fragment_id);
return false;
}
// set value for the future instance
mFragmentedLoadPromise->set_value(verifyFragmentLoadResponse(response));
// reset the promise as the value can only be retrieved once from it
mFragmentedLoadPromise = std::nullopt;
return true;
}
std::future<bool> PreloadedNanoappLoader::sendFragmentedLoadRequest(
::android::chre::FragmentedLoadRequest &request) {
flatbuffers::FlatBufferBuilder builder(request.binary.size() + 128);
// TODO(b/247124878): Confirm if respondBeforeStart can be set to true on all
// the devices.
HostProtocolHost::encodeFragmentedLoadNanoappRequest(
builder, request, /* respondBeforeStart= */ true);
HostProtocolHost::mutateHostClientId(builder.GetBufferPointer(),
builder.GetSize(), kHalId);
std::unique_lock<std::mutex> lock(mPreloadedNanoappsMutex);
if (!mConnection->sendMessage(builder.GetBufferPointer(),
builder.GetSize())) {
// Returns an invalid future to indicate the failure
return std::future<bool>{};
}
mPreloadedNanoappPendingTransaction = {
.transactionId = request.transactionId,
.fragmentId = request.fragmentId,
};
mFragmentedLoadPromise = std::make_optional<std::promise<bool>>();
return mFragmentedLoadPromise->get_future();
}
} // namespace android::chre