blob: 888ce5f770b1d079e5adf2b4cd7cd317101c996e [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.
*/
#ifndef ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_
#define ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_
#include "chre/platform/shared/host_protocol_common.h"
#include "chre_host/fragmented_load_transaction.h"
#include "chre_host/log.h"
#include "chre_host/preloaded_nanoapp_loader.h"
#include "hal_client_id.h"
#include <sys/types.h>
#include <cstddef>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <aidl/android/hardware/contexthub/ContextHubMessage.h>
#include <aidl/android/hardware/contexthub/IContextHub.h>
#include <aidl/android/hardware/contexthub/IContextHubCallback.h>
using aidl::android::hardware::contexthub::ContextHubMessage;
using aidl::android::hardware::contexthub::HostEndpointInfo;
using aidl::android::hardware::contexthub::IContextHubCallback;
using android::chre::FragmentedLoadTransaction;
using HostEndpointId = uint16_t;
namespace android::hardware::contexthub::common::implementation {
/**
* A class managing clients for Context Hub HAL.
*
* A HAL client is defined as a user calling the IContextHub API. The main
* purpose of this class are:
* - to assign a unique HalClientId identifying each client;
* - to maintain a mapping between a HAL client and its states defined in
* HalClient;
* - to track the ongoing load/unload transactions
*
* There are 3 types of ids HalClientManager will track: client uuid, HAL client
* id and host endpoint id.
* - A uuid uniquely identifies a client when it registers its callback.
* After a callback is registered, a HAL client id is created and will be
* used to identify the client in the following API calls from/to it
* - A client id identifies a HAL client, which is the layer beneath the host
* apps, such as ContextHubService. Multiple apps with different host
* endpoint IDs can have the same client ID.
* - A host endpoint id, which is defined at
* hardware/interfaces/contexthub/aidl/android/hardware/contexthub/ContextHubMessage.aidl,
* identifies a host app that communicates with a HAL client.
*
* For a host endpoint connected to ContextHubService, its endpoint id is kept
*in the form below during the communication with CHRE.
*
* 0 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |0| endpoint_id |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* For vendor host endpoints, the client id is embedded into the endpoint id
* before sending a message to CHRE. When that happens, the highest bit is set
* to 1 and the endpoint id is mutated to the format below:
*
* 0 1
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |1| client_id |endpoint_id|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* Note that HalClientManager is not responsible for generating endpoint ids,
* which should be managed by HAL clients themselves.
*/
class HalClientManager {
public:
struct HalClient {
static constexpr pid_t PID_UNSET = 0;
explicit HalClient(const std::string &uuid, const HalClientId clientId)
: HalClient(uuid, clientId, /* pid= */ PID_UNSET,
/* callback= */ nullptr,
/* deathRecipientCookie= */ nullptr) {}
explicit HalClient(std::string uuid, const HalClientId clientId, pid_t pid,
const std::shared_ptr<IContextHubCallback> &callback,
void *deathRecipientCookie)
: uuid{std::move(uuid)},
clientId{clientId},
pid{pid},
callback{callback},
deathRecipientCookie{deathRecipientCookie} {}
/** Resets the client's fields except uuid and clientId. */
void reset(pid_t processId,
const std::shared_ptr<IContextHubCallback> &contextHubCallback,
void *cookie) {
pid = processId;
callback = contextHubCallback;
deathRecipientCookie = cookie;
endpointIds.clear();
}
const std::string uuid;
const HalClientId clientId;
pid_t pid{};
std::shared_ptr<IContextHubCallback> callback{};
// cookie is used by the death recipient's linked callback
void *deathRecipientCookie{};
std::unordered_set<HostEndpointId> endpointIds{};
};
using DeadClientUnlinker = std::function<bool(
const std::shared_ptr<IContextHubCallback> &callback, void *cookie)>;
explicit HalClientManager(DeadClientUnlinker deadClientUnlinker,
const std::string &clientIdMappingFilePath);
virtual ~HalClientManager() = default;
/** Disable copy constructor and copy assignment to avoid duplicates. */
HalClientManager(HalClientManager &) = delete;
void operator=(const HalClientManager &) = delete;
/**
* Gets the client id allocated to the current HAL client.
*
* The current HAL client is identified by its process id. If the process
* doesn't have any client id assigned, HalClientManager will create one
* mapped to its process id.
*
* @param pid process id of the current client
*
* @return client id assigned to the calling process, or
* ::chre::kHostClientIdUnspecified if the process id is not found.
*/
HalClientId getClientId(pid_t pid);
/**
* Gets the callback for the current HAL client identified by the clientId.
*
* @return callback previously registered. nullptr is returned if the clientId
* is not found.
*/
std::shared_ptr<IContextHubCallback> getCallback(HalClientId clientId);
/**
* Registers a IContextHubCallback function mapped to the current client's
* client id. @p deathRecipient and @p deathRecipientCookie are used to unlink
* the previous registered callback for the same client, if any.
*
* @param pid process id of the current client
* @param callback a function incurred to handle the client death event.
* @param deathRecipientCookie the data used by the callback.
*
* @return true if success, otherwise false.
*/
bool registerCallback(pid_t pid,
const std::shared_ptr<IContextHubCallback> &callback,
void *deathRecipientCookie);
/**
* Registers a FragmentedLoadTransaction for the current HAL client.
*
* At this moment only one active transaction, either load or unload, is
* supported.
*
* @param pid process id of the current client
* @param transaction the transaction being registered
*
* @return true if success, otherwise false.
*/
bool registerPendingLoadTransaction(
pid_t pid, std::unique_ptr<chre::FragmentedLoadTransaction> transaction);
/**
* Returns true if the load transaction matches the arguments provided.
*/
bool isPendingLoadTransactionExpected(HalClientId clientId,
uint32_t transactionId,
uint32_t currentFragmentId) {
const std::lock_guard<std::mutex> lock(mLock);
return isPendingLoadTransactionMatchedLocked(clientId, transactionId,
currentFragmentId);
}
/**
* Clears the pending load transaction.
*
* This function is called to proactively clear out a pending load transaction
* that is not timed out yet.
*
*/
void resetPendingLoadTransaction();
/**
* Gets the next FragmentedLoadRequest from PendingLoadTransaction if it's
* available.
*
* @return an optional FragmentedLoadRequest, std::nullopt if unavailable.
*/
std::optional<chre::FragmentedLoadRequest> getNextFragmentedLoadRequest();
/**
* Registers the current HAL client as having a pending unload transaction.
*
* At this moment only one active transaction, either load or unload, is
* supported.
*
* @param pid process id of the current client
* @param transaction the transaction being registered
*
* @return true if success, otherwise false.
*/
bool registerPendingUnloadTransaction(pid_t pid, uint32_t transactionId);
/**
* Clears the pending unload transaction.
*
* This function is called to proactively clear out a pending unload
* transaction that is not timed out yet. @p clientId and @p
* transactionId must match the existing pending transaction.
*
* @param clientId the client id of the caller.
* @param transactionId unique id of the transaction.
*
* @return true if the pending transaction is cleared, otherwise false.
*/
bool resetPendingUnloadTransaction(HalClientId clientId,
uint32_t transactionId);
/**
* Registers an endpoint id when it is connected to HAL.
*
* @param pid process id of the current HAL client
* @param endpointId the endpointId being registered
*
* @return true if success, otherwise false.
*/
bool registerEndpointId(pid_t pid, const HostEndpointId &endpointId);
/**
* Removes an endpoint id when it is disconnected to HAL.
*
* @param pid process id of the current HAL client
* @param endpointId the endpointId being registered
*
* @return true if success, otherwise false.
*/
bool removeEndpointId(pid_t pid, const HostEndpointId &endpointId);
/**
* Mutates the endpoint id if the hal client is not the framework service.
*
* @param pid process id of the current HAL client
* @param endpointId the endpointId being registered
*
* @return true if success, otherwise false.
*/
bool mutateEndpointIdFromHostIfNeeded(pid_t pid, HostEndpointId &endpointId);
/** Returns the original endpoint id sent by the host client. */
static HostEndpointId convertToOriginalEndpointId(
const HostEndpointId &endpointId);
/**
* Gets all the connected endpoints for the client identified by the @p pid.
*
* @return the pointer to the endpoint id set if the client is identifiable,
* otherwise nullptr.
*/
const std::unordered_set<HostEndpointId> *getAllConnectedEndpoints(pid_t pid);
/** Sends a message to every connected endpoints. */
void sendMessageForAllCallbacks(
const ContextHubMessage &message,
const std::vector<std::string> &messageParams);
std::shared_ptr<IContextHubCallback> getCallbackForEndpoint(
HostEndpointId mutatedEndpointId);
/**
* Handles the client death event.
*
* @param pid of the client that loses the binder connection to the HAL.
*/
void handleClientDeath(pid_t pid);
/** Handles CHRE restart event. */
void handleChreRestart();
protected:
static constexpr char kSystemServerUuid[] =
"9a17008d6bf1445a90116d21bd985b6c";
static constexpr char kVendorClientUuid[] = "vendor-client";
static constexpr char kJsonClientId[] = "ClientId";
static constexpr char kJsonUuid[] = "uuid";
static constexpr int64_t kTransactionTimeoutThresholdMs = 5000; // 5 seconds
static constexpr uint8_t kNumOfBitsForEndpointId = 6;
static constexpr HostEndpointId kMaxVendorEndpointId =
(1 << kNumOfBitsForEndpointId) - 1;
// The endpoint id is from a vendor client if the highest bit is set to 1.
static constexpr HostEndpointId kVendorEndpointIdBitMask = 0x8000;
struct PendingTransaction {
PendingTransaction(HalClientId clientId, uint32_t transactionId,
int64_t registeredTimeMs) {
this->clientId = clientId;
this->transactionId = transactionId;
this->registeredTimeMs = registeredTimeMs;
}
HalClientId clientId;
uint32_t transactionId;
int64_t registeredTimeMs;
};
/**
* PendingLoadTransaction tracks ongoing load transactions.
*/
struct PendingLoadTransaction : public PendingTransaction {
PendingLoadTransaction(
HalClientId clientId, int64_t registeredTimeMs,
uint32_t currentFragmentId,
std::unique_ptr<chre::FragmentedLoadTransaction> transaction)
: PendingTransaction(clientId, transaction->getTransactionId(),
registeredTimeMs) {
this->currentFragmentId = currentFragmentId;
this->transaction = std::move(transaction);
}
uint32_t currentFragmentId; // the fragment id being sent out.
std::unique_ptr<chre::FragmentedLoadTransaction> transaction;
[[nodiscard]] std::string toString() const {
using android::internal::ToString;
return "[Load transaction: client id " + ToString(clientId) +
", Transaction id " + ToString(transaction->getTransactionId()) +
", fragment id " + ToString(currentFragmentId) + "]";
}
};
/**
* Creates a client id to uniquely identify a HAL client.
*
* A file is maintained on the device for the mappings between client names
* and client ids so that if a client has connected to HAL before the same
* client id is always assigned to it.
*
* mLock must be held when this function is called.
*
*/
virtual bool createClientLocked(
const std::string &uuid, pid_t pid,
const std::shared_ptr<IContextHubCallback> &callback,
void *deathRecipientCookie);
/**
* Returns true if @p clientId and @p transactionId match the
* corresponding values in @p transaction.
*
* mLock must be held when this function is called.
*/
static bool isPendingTransactionMatchedLocked(
HalClientId clientId, uint32_t transactionId,
const std::optional<PendingTransaction> &transaction) {
return transaction.has_value() && transaction->clientId == clientId &&
transaction->transactionId == transactionId;
}
/**
* Returns true if the load transaction is expected.
*
* mLock must be held when this function is called.
*/
bool isPendingLoadTransactionMatchedLocked(HalClientId clientId,
uint32_t transactionId,
uint32_t currentFragmentId);
/**
* Checks if the transaction registration is allowed and clears out any stale
* pending transaction if possible.
*
* This function is called when registering a new transaction. The reason that
* we still proceed when there is already a pending transaction is because we
* don't want a stale one, for whatever reason, to block future transactions.
* However, every transaction is guaranteed to have up to
* kTransactionTimeoutThresholdMs to finish.
*
* mLock must be held when this function is called.
*
* @param clientId id of the client trying to register the transaction
*
* @return true if registration is allowed, otherwise false.
*/
bool isNewTransactionAllowedLocked(HalClientId clientId);
/** Returns true if the endpoint id is within the accepted range. */
[[nodiscard]] static inline bool isValidEndpointId(
const HalClient *client, const HostEndpointId &endpointId) {
return client->uuid == kSystemServerUuid ||
endpointId <= kMaxVendorEndpointId;
}
// TODO(b/290375569): isSystemServerConnectedLocked() and getUuidLocked() are
// temporary solutions to get a pseudo-uuid. callback->getUuid() should be
// used after flag-guarding the changes.
inline bool isSystemServerConnectedLocked() {
HalClient *client = getClientByUuidLocked(kSystemServerUuid);
return client != nullptr && client->pid != 0;
}
inline std::string getUuidLocked() {
return isSystemServerConnectedLocked() ? kVendorClientUuid
: kSystemServerUuid;
}
HalClient *getClientByField(
const std::function<bool(const HalClient &client)> &fieldMatcher);
HalClient *getClientByClientIdLocked(HalClientId clientId);
HalClient *getClientByUuidLocked(const std::string &uuid);
HalClient *getClientByProcessIdLocked(pid_t pid);
DeadClientUnlinker mDeadClientUnlinker{};
std::string mClientMappingFilePath{};
// next available client id
HalClientId mNextClientId = 1;
// The lock guarding the access to clients' states and pending transactions
std::mutex mLock;
std::vector<HalClient> mClients{};
// States tracking pending transactions
std::optional<PendingLoadTransaction> mPendingLoadTransaction = std::nullopt;
std::optional<PendingTransaction> mPendingUnloadTransaction = std::nullopt;
};
} // namespace android::hardware::contexthub::common::implementation
#endif // ANDROID_HARDWARE_CONTEXTHUB_COMMON_HAL_CLIENT_MANAGER_H_