/* * 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 #include #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 &out_preloadedNanoappIds) { std::vector 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 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(headerBuffer.data()); out_preloadedNanoappIds.emplace_back(appHeader->appId); } } void PreloadedNanoappLoader::loadPreloadedNanoapps() { std::string directory; std::vector 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 headerBuffer; std::vector 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 &header, const std::vector &nanoapp, uint32_t transactionId) { if (header.size() != sizeof(NanoAppBinaryHeader)) { LOGE("Nanoapp binary's header size is incorrect"); return false; } const auto *appHeader = reinterpret_cast(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 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 &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 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 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 lock(mPreloadedNanoappsMutex); if (!mConnection->sendMessage(builder.GetBufferPointer(), builder.GetSize())) { // Returns an invalid future to indicate the failure return std::future{}; } mPreloadedNanoappPendingTransaction = { .transactionId = request.transactionId, .fragmentId = request.fragmentId, }; mFragmentedLoadPromise = std::make_optional>(); return mFragmentedLoadPromise->get_future(); } } // namespace android::chre