blob: 535337540c763c5be8db6345227a2626d57dd553 [file] [log] [blame]
/*
* Copyright (C) 2024 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.
*/
#pragma once
#include <unistd.h>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <aidl/android/hardware/contexthub/BnContextHub.h>
#include <android-base/thread_annotations.h>
#include "pw_result/result.h"
#include "pw_status/status.h"
namespace android::hardware::contexthub::common::implementation {
using ::aidl::android::hardware::contexthub::EndpointId;
using ::aidl::android::hardware::contexthub::EndpointInfo;
using ::aidl::android::hardware::contexthub::HubInfo;
using ::aidl::android::hardware::contexthub::IEndpointCallback;
/**
* Stores host and embedded MessageHub objects and maintains global mappings.
*/
class MessageHubManager {
public:
/**
* Represents a host-side MessageHub. Clients of the IContextHub (V4+)
* interface each get a HostHub instance.
*/
class HostHub {
public:
~HostHub();
/**
* Sets the callback for sending endpoint events back to the HAL client
*
* @param callback The callback provided by the client
* @return pw::OkStatus() on success.
*/
pw::Status setCallback(std::shared_ptr<IEndpointCallback> callback)
EXCLUDES(mManager.mLock);
/**
* Returns the callback registered in setCallback()
*
* @return The previously registered callback
*/
std::shared_ptr<IEndpointCallback> getCallback() const
EXCLUDES(mManager.mLock);
/**
* Adds an endpoint to this message hub
*
* @param self Self-reference for mapping from hub id
* @param info Description of the endpoint
* @return pw::OkStatus() on success
*/
pw::Status addEndpoint(std::weak_ptr<HostHub> self,
const EndpointInfo &info) EXCLUDES(mManager.mLock);
/**
* Removes an endpoint from this message hub
*
* @param info Id of endpoint to remove
* @return pw::OkStatus() on success
*/
pw::Status removeEndpoint(const EndpointId &info) EXCLUDES(mManager.mLock);
/**
* Reserves a session id range to be used by this message hub
*
* @param size The size of this range, max 1024
* @return A pair of the smallest and largest id in the range on success
*/
pw::Result<std::pair<uint16_t, uint16_t>> reserveSessionIdRange(
uint16_t size) EXCLUDES(mManager.mLock);
/**
* Opens a session between the given endpoints with given session id
*
* The session is pending until updated by the destination endpoint.
*
* @param self Self-reference to be stored in session state
* @param localId The id of an endpoint hosted by this hub
* @param remoteId The id of the remote endpoint
* @param sessionId The id to be used for this session. Must be in the range
* allocated to this hub
* @return On success, returns a possibly null reference to the HostHub
* which hosted an endpoint on a pruned session with the same id. If not
* null, the HostHub should be notified that the session has been closed.
*/
pw::Result<std::shared_ptr<HostHub>> openSession(
std::weak_ptr<HostHub> self, const EndpointId &localId,
const EndpointId &remoteId, uint16_t sessionId)
EXCLUDES(mManager.mLock);
/**
* Acks a pending session.
*
* @param id Session id
* @return pw::OkStatus() on success, pw::Status::Unavailable() if the
* session is gone due to an endpoint going down.
*/
pw::Status ackSession(uint16_t id) EXCLUDES(mManager.mLock);
/**
* Checks that a session is open.
*
* @param id Session id
* @return pw::OkStatus() on success, pw::Status::Unavailable() if the
* session is gone due to an endpoint going down.
*/
pw::Status checkSessionOpen(uint16_t id) EXCLUDES(mManager.mLock);
/**
* Removes the given session and any local and global mappings
*
* @param id The session id
* @return pw::OkStatus() on success
*/
pw::Status closeSession(uint16_t id) EXCLUDES(mManager.mLock);
/**
* Returns the registered id of this message hub.
*
* @return kId
*/
int64_t id() const;
private:
friend class MessageHubManager;
// Cookie associated with each registered client callback.
struct DeathRecipientCookie {
MessageHubManager *manager;
pid_t pid;
};
static constexpr uint16_t kSessionIdMaxRange = 1024;
static constexpr int64_t kHubIdInvalid = 0;
HostHub(MessageHubManager &manager, pid_t pid)
: mManager(manager), kPid(pid) {}
// Unlinks this hub from the manager, destroying internal references.
// Returns the id so that it can be propagated to CHRE.
int64_t unlinkFromManager() EXCLUDES(mManager.mLock);
// Unlink the current callback from the manager's death recipient.
void unlinkCallbackIfNecessaryLocked() REQUIRES(mManager.mLock);
// Returns pw::OkStatus() if the hub is in a valid state.
pw::Status checkValidLocked() REQUIRES(mManager.mLock);
// Returns a shared_ptr to the given endpoint.
pw::Result<std::shared_ptr<EndpointInfo>> getEndpointLocked(
const EndpointId &id) REQUIRES(mManager.mLock);
// Returns pw::OkStatus() if the session id is in range for this hub.
bool sessionIdInRangeLocked(uint16_t id) REQUIRES(mManager.mLock);
MessageHubManager &mManager;
const pid_t kPid;
// Hub id, set when the first endpoint is registered.
int64_t kId GUARDED_BY(mManager.mLock) = kHubIdInvalid;
// Callback to HAL client.
std::shared_ptr<IEndpointCallback> mCallback GUARDED_BY(mManager.mLock);
// Cookie associated with mCallback.
DeathRecipientCookie *mCookie GUARDED_BY(mManager.mLock);
// Used to lookup a host endpoint. Owns the associated EndpointInfo.
std::unordered_map<int64_t, std::shared_ptr<EndpointInfo>> mIdToEndpoint
GUARDED_BY(mManager.mLock);
// Session id ranges allocated to this HostHub. The ranges are stored as a
// pair of the lowest and highest id in the range.
std::vector<std::pair<uint16_t, uint16_t>> mSessionIdRanges
GUARDED_BY(mManager.mLock);
// Set in unlinkFromManager().
bool mUnlinked GUARDED_BY(mManager.mLock) = false;
};
// Callback registered to pass up the id of a host hub which disconnected.
using HostHubDownCb = std::function<void(int64_t hubId)>;
// The base session id for sessions initiated from host endpoints.
static constexpr uint16_t kHostSessionIdBase = 0x8000;
explicit MessageHubManager(HostHubDownCb cb);
~MessageHubManager() = default;
/**
* Retrieves the HostHub instance for the calling process
*
* This API should be used for any HostHub lookup coming from the
* IContextHub interface. The first call to this API by any client process
* will trigger the creation of a HostHub for that client.
*
* @param pid The caller's system process id
* @return shared_ptr to the HostHub instance
*/
std::shared_ptr<HostHub> getHostHubByPid(pid_t pid) EXCLUDES(mLock);
/**
* Retrieves the HostHub instance for the given EndpointId
*
* @param id The endpoint id hosted by the returned hub
* @return shared_ptr to the HostHub instance
*/
std::shared_ptr<HostHub> getHostHubByEndpointId(const EndpointId &id)
EXCLUDES(mLock);
/**
* Checks that a given session is open and returns its HostHub.
*
* @param id Session id
* @return A strong reference to the HostHub. pw::Status::Unavailable()
* indicates that the session has been pruned.
*/
pw::Result<std::shared_ptr<HostHub>> checkSessionOpenAndGetHostHub(
uint16_t id) EXCLUDES(mLock);
/**
* Acks a session open request.
*
* This is called both when the destination endpoint approves and also when
* MessageRouter gives a final ack on a session initiated from an embedded
* endpoint. See the documentation on the Session class.
*
* @param id Session id
* @return A strong reference to the HostHub. pw::Status::Unavailable()
* indicates that the session has been pruned.
*/
pw::Result<std::shared_ptr<HostHub>> ackSessionAndGetHostHub(uint16_t id)
EXCLUDES(mLock);
/**
* Apply the given function to each host hub.
*
* @param fn The function to apply.
*/
void forEachHostHub(std::function<void(HostHub &hub)> fn);
/**
* Wipes and initializes the cache of embedded hubs and endpoints
*
* This should only be called once during startup as it invalidates session
* state (i.e. existing sessions will be pruned).
*
* @param hubs The list of message hubs
* @param endpoints The list of endpoints
*/
void initEmbeddedHubsAndEndpoints(const std::vector<HubInfo> &hubs,
const std::vector<EndpointInfo> &endpoints)
EXCLUDES(mLock);
/**
* Adds the given hub to the cache
*
* Ignored if the hub already exists
*
* @param hub The hub to add
*/
void addEmbeddedHub(const HubInfo &hub) EXCLUDES(mLock);
/**
* Removes the hub with given id from the cache
*
* @param id The id of the hub to remove
* @return The ids of all endpoints on the embedded hub
*/
std::vector<EndpointId> removeEmbeddedHub(int64_t id) EXCLUDES(mLock);
/**
* Returns the cached list of embedded message hubs
*
* @return HubInfo for every embedded message hub
*/
std::vector<HubInfo> getEmbeddedHubs() const EXCLUDES(mLock);
/**
* Adds an embedded endpoint to the cache
*
* Ignored if the endpoint already exists
*
* @param endpoint The endpoint to add
*/
void addEmbeddedEndpoint(const EndpointInfo &endpoint);
/**
* Removes an embedded endpoint from the cache
*
* @param id The id of the endpoint to remove
*/
void removeEmbeddedEndpoint(const EndpointId &endpoint);
/**
* Returns a list of embedded endpoints
*
* @return EndpointInfo for every embedded endpoint
*/
std::vector<EndpointInfo> getEmbeddedEndpoints() const EXCLUDES(mLock);
private:
// Callback invoked when a client goes down.
using UnlinkToDeathFn = std::function<bool(
const std::shared_ptr<IEndpointCallback> &callback, void *cookie)>;
// Represents an embedded MessageHub. Stores the hub details as well as a map
// of all endpoints hosted by the hub.
struct EmbeddedHub {
std::unordered_map<int64_t, std::shared_ptr<EndpointInfo>> idToEndpoint;
HubInfo info;
};
// Represents a session between a host and embedded endpoint. Only stores weak
// references to the endpoints and HostHub owning the host endpoint. Must be
// converted to a SessionStrongRef to temporarily access state. The weak
// references expire when the associated entity is unregistered. A
// SessionStrongRef cannot be created if any reference has expired.
//
// A Session is created on an openSession() request (triggered either by a
// local or remote endpoint) with mPendingDestination unset via a call to
// ackSession*() from the destination endpoint. For Sessions started by
// embedded endpoints, an additional ackSession*() must be received from the
// CHRE MessageRouter after passing it the ack from the destination host
// endpoint. This unsets mPendingMessageRouter. A session is only open for
// messages once both mPendingDestination and mPendingMessageRouter are unset.
struct SessionStrongRef;
class Session {
public:
Session(std::weak_ptr<HostHub> hub, std::weak_ptr<EndpointInfo> local,
std::weak_ptr<EndpointInfo> remote, bool hostInitiated)
: mHub(hub),
mLocal(local),
mRemote(remote),
mPendingMessageRouter(!hostInitiated) {}
private:
friend struct SessionStrongRef;
std::weak_ptr<HostHub> mHub;
std::weak_ptr<EndpointInfo> mLocal;
std::weak_ptr<EndpointInfo> mRemote;
bool mPendingDestination = true;
bool mPendingMessageRouter;
};
// A strong reference to a Session's underlying endpoints and HostHub as well
// as Session metadata. A SessionStrongRef should be created and destroyed
// within a single critical section.
struct SessionStrongRef {
std::shared_ptr<HostHub> hub;
std::shared_ptr<EndpointInfo> local;
std::shared_ptr<EndpointInfo> remote;
bool &pendingDestination;
bool &pendingMessageRouter;
SessionStrongRef(Session &session)
: hub(session.mHub.lock()),
local(session.mLocal.lock()),
remote(session.mRemote.lock()),
pendingDestination(session.mPendingDestination),
pendingMessageRouter(session.mPendingMessageRouter) {}
operator bool() const {
return hub && local && remote;
}
};
// The hub id reserved for the ContextHub service.
static constexpr int64_t kContextHubServiceHubId = 0x416e64726f696400;
// The Linux uid of the system_server.
static constexpr uid_t kSystemServerUid = 1000;
// Invoked on client death. Cleans up references to the client.
static void onClientDeath(void *cookie);
// Retrieves a strong reference to the session with given id.
pw::Result<SessionStrongRef> checkSessionLocked(uint16_t id) REQUIRES(mLock);
// Adds an embedded endpoint to the cache.
void addEmbeddedEndpointLocked(const EndpointInfo &endpoint) REQUIRES(mLock);
// Returns true if the embedded endpoint with given id is in the cache.
pw::Result<std::shared_ptr<EndpointInfo>> getEmbeddedEndpointLocked(
const EndpointId &id) REQUIRES(mLock);
// Callback to pass up the id of a host hub for a client that disconnected.
HostHubDownCb mHostHubDownCb;
// Death recipient handling clients' disconnections.
ndk::ScopedAIBinder_DeathRecipient mDeathRecipient;
// Guards hub, endpoint, and session state.
mutable std::mutex mLock;
// Map of EmbeddedHubs.
std::unordered_map<int64_t, EmbeddedHub> mIdToEmbeddedHub GUARDED_BY(mLock);
// Used to look up the HostHub associated with the client on IContextHub
// calls.
std::unordered_map<pid_t, std::shared_ptr<HostHub>> mPidToHostHub
GUARDED_BY(mLock);
// Used when an embedded endpoint wants to start a session with an endpoint
// hosted by a specific HostHub.
std::unordered_map<int64_t, std::weak_ptr<HostHub>> mIdToHostHub
GUARDED_BY(mLock);
// Used to lookup the host endpoint to receive a message on an endpoint
// session.
std::unordered_map<uint16_t, Session> mIdToSession GUARDED_BY(mLock);
// Next session id from which to allocate ranges.
uint16_t mNextSessionId GUARDED_BY(mLock) = kHostSessionIdBase;
};
} // namespace android::hardware::contexthub::common::implementation