blob: 4c3df54e073ab339eeb751a9a82f2500a5c1814c [file] [log] [blame]
/*
* Copyright (C) 2020 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 "chpp/clients.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "chpp/app.h"
#ifdef CHPP_CLIENT_ENABLED_DISCOVERY
#include "chpp/clients/discovery.h"
#endif
#ifdef CHPP_CLIENT_ENABLED_GNSS
#include "chpp/clients/gnss.h"
#endif
#ifdef CHPP_CLIENT_ENABLED_LOOPBACK
#include "chpp/clients/loopback.h"
#endif
#ifdef CHPP_CLIENT_ENABLED_TIMESYNC
#include "chpp/clients/timesync.h"
#endif
#ifdef CHPP_CLIENT_ENABLED_WIFI
#include "chpp/clients/wifi.h"
#endif
#ifdef CHPP_CLIENT_ENABLED_WWAN
#include "chpp/clients/wwan.h"
#endif
#include "chpp/log.h"
#include "chpp/macros.h"
#include "chpp/memory.h"
#include "chpp/time.h"
#include "chpp/transport.h"
/************************************************
* Prototypes
***********************************************/
static bool chppIsClientApiReady(struct ChppEndpointState *clientState);
static ChppClientDeinitFunction *chppGetClientDeinitFunction(
struct ChppAppState *context, uint8_t index);
/************************************************
* Private Functions
***********************************************/
/**
* Determines whether a client is ready to accept commands via its API (i.e. is
* initialized and opened). If the client is in the process of reopening, it
* will wait for the client to reopen.
*
* @param clientState State of the client sending the client request.
*
* @return Indicates whether the client is ready.
*/
static bool chppIsClientApiReady(struct ChppEndpointState *clientState) {
CHPP_DEBUG_NOT_NULL(clientState);
bool result = false;
if (clientState->initialized) {
switch (clientState->openState) {
case (CHPP_OPEN_STATE_CLOSED):
case (CHPP_OPEN_STATE_WAITING_TO_OPEN): {
// result remains false
break;
}
case (CHPP_OPEN_STATE_OPENED): {
result = true;
break;
}
case (CHPP_OPEN_STATE_OPENING): {
// Allow the open request to go through
clientState->openState = CHPP_OPEN_STATE_WAITING_TO_OPEN;
result = true;
break;
}
}
}
if (!result) {
CHPP_LOGE("Client not ready (everInit=%d, init=%d, open=%" PRIu8 ")",
clientState->everInitialized, clientState->initialized,
clientState->openState);
}
return result;
}
/**
* Returns the deinitialization function pointer of a particular negotiated
* client.
*
* @param context Maintains status for each app layer instance.
* @param index Index of the registered client.
*
* @return Pointer to the match notification function.
*/
static ChppClientDeinitFunction *chppGetClientDeinitFunction(
struct ChppAppState *context, uint8_t index) {
CHPP_DEBUG_NOT_NULL(context);
return context->registeredClients[index]->deinitFunctionPtr;
}
/************************************************
* Public Functions
***********************************************/
void chppRegisterCommonClients(struct ChppAppState *context) {
UNUSED_VAR(context);
CHPP_DEBUG_NOT_NULL(context);
CHPP_LOGD("Registering Clients");
#ifdef CHPP_CLIENT_ENABLED_WWAN
if (context->clientServiceSet.wwanClient) {
chppRegisterWwanClient(context);
}
#endif
#ifdef CHPP_CLIENT_ENABLED_WIFI
if (context->clientServiceSet.wifiClient) {
chppRegisterWifiClient(context);
}
#endif
#ifdef CHPP_CLIENT_ENABLED_GNSS
if (context->clientServiceSet.gnssClient) {
chppRegisterGnssClient(context);
}
#endif
}
void chppDeregisterCommonClients(struct ChppAppState *context) {
UNUSED_VAR(context);
CHPP_DEBUG_NOT_NULL(context);
CHPP_LOGD("Deregistering Clients");
#ifdef CHPP_CLIENT_ENABLED_WWAN
if (context->clientServiceSet.wwanClient) {
chppDeregisterWwanClient(context);
}
#endif
#ifdef CHPP_CLIENT_ENABLED_WIFI
if (context->clientServiceSet.wifiClient) {
chppDeregisterWifiClient(context);
}
#endif
#ifdef CHPP_CLIENT_ENABLED_GNSS
if (context->clientServiceSet.gnssClient) {
chppDeregisterGnssClient(context);
}
#endif
}
void chppRegisterClient(struct ChppAppState *appContext, void *clientContext,
struct ChppEndpointState *clientState,
struct ChppOutgoingRequestState *outReqStates,
const struct ChppClient *newClient) {
CHPP_NOT_NULL(newClient);
CHPP_DEBUG_NOT_NULL(appContext);
CHPP_DEBUG_NOT_NULL(clientContext);
CHPP_DEBUG_NOT_NULL(clientState);
CHPP_DEBUG_NOT_NULL(newClient);
if (appContext->registeredClientCount >= CHPP_MAX_REGISTERED_CLIENTS) {
CHPP_LOGE("Max clients registered: %" PRIu8,
appContext->registeredClientCount);
return;
}
clientState->appContext = appContext;
clientState->outReqStates = outReqStates;
clientState->index = appContext->registeredClientCount;
clientState->context = clientContext;
appContext->registeredClientStates[appContext->registeredClientCount] =
clientState;
appContext->registeredClients[appContext->registeredClientCount] = newClient;
char uuidText[CHPP_SERVICE_UUID_STRING_LEN];
chppUuidToStr(newClient->descriptor.uuid, uuidText);
CHPP_LOGD("Client # %" PRIu8 " UUID=%s, version=%" PRIu8 ".%" PRIu8
".%" PRIu16 ", min_len=%" PRIuSIZE,
appContext->registeredClientCount, uuidText,
newClient->descriptor.version.major,
newClient->descriptor.version.minor,
newClient->descriptor.version.patch, newClient->minLength);
appContext->registeredClientCount++;
}
void chppInitBasicClients(struct ChppAppState *context) {
UNUSED_VAR(context);
CHPP_DEBUG_NOT_NULL(context);
CHPP_LOGD("Initializing basic clients");
#ifdef CHPP_CLIENT_ENABLED_LOOPBACK
if (context->clientServiceSet.loopbackClient) {
chppLoopbackClientInit(context);
}
#endif
#ifdef CHPP_CLIENT_ENABLED_TIMESYNC
chppTimesyncClientInit(context);
#endif
#ifdef CHPP_CLIENT_ENABLED_DISCOVERY
chppDiscoveryInit(context);
#endif
}
void chppClientInit(struct ChppEndpointState *clientState, uint8_t handle) {
CHPP_DEBUG_NOT_NULL(clientState);
CHPP_ASSERT_LOG(!clientState->initialized,
"Client H#%" PRIu8 " already initialized", handle);
if (!clientState->everInitialized) {
clientState->handle = handle;
chppMutexInit(&clientState->syncResponse.mutex);
chppConditionVariableInit(&clientState->syncResponse.condVar);
clientState->everInitialized = true;
}
clientState->initialized = true;
}
void chppClientDeinit(struct ChppEndpointState *clientState) {
CHPP_DEBUG_NOT_NULL(clientState);
CHPP_ASSERT_LOG(clientState->initialized,
"Client H#%" PRIu8 " already deinitialized",
clientState->handle);
clientState->initialized = false;
}
void chppDeinitBasicClients(struct ChppAppState *context) {
UNUSED_VAR(context);
CHPP_DEBUG_NOT_NULL(context);
CHPP_LOGD("Deinitializing basic clients");
#ifdef CHPP_CLIENT_ENABLED_LOOPBACK
if (context->clientServiceSet.loopbackClient) {
chppLoopbackClientDeinit(context);
}
#endif
#ifdef CHPP_CLIENT_ENABLED_TIMESYNC
chppTimesyncClientDeinit(context);
#endif
#ifdef CHPP_CLIENT_ENABLED_DISCOVERY
chppDiscoveryDeinit(context);
#endif
}
void chppDeinitMatchedClients(struct ChppAppState *context) {
CHPP_DEBUG_NOT_NULL(context);
CHPP_LOGD("Deinitializing matched clients");
for (uint8_t i = 0; i < context->discoveredServiceCount; i++) {
uint8_t clientIndex = context->clientIndexOfServiceIndex[i];
if (clientIndex != CHPP_CLIENT_INDEX_NONE) {
// Discovered service has a matched client
ChppClientDeinitFunction *clientDeinitFunction =
chppGetClientDeinitFunction(context, clientIndex);
CHPP_LOGD("Client #%" PRIu8 " (H#%d) deinit fp found=%d", clientIndex,
CHPP_SERVICE_HANDLE_OF_INDEX(i),
(clientDeinitFunction != NULL));
if (clientDeinitFunction != NULL) {
clientDeinitFunction(
context->registeredClientStates[clientIndex]->context);
}
}
}
}
struct ChppAppHeader *chppAllocClientRequest(
struct ChppEndpointState *clientState, size_t len) {
CHPP_DEBUG_NOT_NULL(clientState);
return chppAllocRequest(CHPP_MESSAGE_TYPE_CLIENT_REQUEST, clientState, len);
}
struct ChppAppHeader *chppAllocClientRequestCommand(
struct ChppEndpointState *clientState, uint16_t command) {
struct ChppAppHeader *request =
chppAllocClientRequest(clientState, sizeof(struct ChppAppHeader));
if (request != NULL) {
request->command = command;
}
return request;
}
bool chppClientSendTimestampedRequestOrFail(
struct ChppEndpointState *clientState,
struct ChppOutgoingRequestState *outReqState, void *buf, size_t len,
uint64_t timeoutNs) {
CHPP_DEBUG_NOT_NULL(clientState);
CHPP_DEBUG_NOT_NULL(outReqState);
CHPP_DEBUG_NOT_NULL(buf);
if (!chppIsClientApiReady(clientState)) {
CHPP_FREE_AND_NULLIFY(buf);
return false;
}
return chppSendTimestampedRequestOrFail(clientState, outReqState, buf, len,
timeoutNs);
}
bool chppClientSendTimestampedRequestAndWait(
struct ChppEndpointState *clientState,
struct ChppOutgoingRequestState *outReqState, void *buf, size_t len) {
return chppClientSendTimestampedRequestAndWaitTimeout(
clientState, outReqState, buf, len, CHPP_REQUEST_TIMEOUT_DEFAULT);
}
bool chppClientSendTimestampedRequestAndWaitTimeout(
struct ChppEndpointState *clientState,
struct ChppOutgoingRequestState *outReqState, void *buf, size_t len,
uint64_t timeoutNs) {
bool result = chppClientSendTimestampedRequestOrFail(
clientState, outReqState, buf, len, CHPP_REQUEST_TIMEOUT_INFINITE);
if (!result) {
return false;
}
return chppWaitForResponseWithTimeout(&clientState->syncResponse, outReqState,
timeoutNs);
}
void chppClientPseudoOpen(struct ChppEndpointState *clientState) {
clientState->pseudoOpen = true;
}
bool chppClientSendOpenRequest(struct ChppEndpointState *clientState,
struct ChppOutgoingRequestState *openReqState,
uint16_t openCommand, bool blocking) {
CHPP_NOT_NULL(clientState);
CHPP_NOT_NULL(openReqState);
bool result = false;
uint8_t priorState = clientState->openState;
#ifdef CHPP_CLIENT_ENABLED_TIMESYNC
chppTimesyncMeasureOffset(clientState->appContext);
#endif
struct ChppAppHeader *request =
chppAllocClientRequestCommand(clientState, openCommand);
if (request == NULL) {
return false;
}
clientState->openState = CHPP_OPEN_STATE_OPENING;
if (blocking) {
CHPP_LOGD("Opening service - blocking");
result = chppClientSendTimestampedRequestAndWait(clientState, openReqState,
request, sizeof(*request));
} else {
CHPP_LOGD("Opening service - non-blocking");
result = chppClientSendTimestampedRequestOrFail(
clientState, openReqState, request, sizeof(*request),
CHPP_REQUEST_TIMEOUT_INFINITE);
}
if (!result) {
CHPP_LOGE("Service open fail from state=%" PRIu8 " psudo=%d blocking=%d",
priorState, clientState->pseudoOpen, blocking);
clientState->openState = CHPP_OPEN_STATE_CLOSED;
} else if (blocking) {
result = (clientState->openState == CHPP_OPEN_STATE_OPENED);
}
return result;
}
void chppClientProcessOpenResponse(struct ChppEndpointState *clientState,
uint8_t *buf, size_t len) {
CHPP_DEBUG_NOT_NULL(clientState);
CHPP_DEBUG_NOT_NULL(buf);
UNUSED_VAR(len); // Necessary depending on assert macro below
// Assert condition already guaranteed by chppAppProcessRxDatagram() but
// checking again since this is a public function
CHPP_ASSERT(len >= sizeof(struct ChppAppHeader));
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
if (rxHeader->error != CHPP_APP_ERROR_NONE) {
CHPP_LOGE("Service open failed at service");
clientState->openState = CHPP_OPEN_STATE_CLOSED;
} else {
CHPP_LOGD("Service open succeeded at service");
clientState->openState = CHPP_OPEN_STATE_OPENED;
}
}
void chppClientCloseOpenRequests(struct ChppEndpointState *clientState,
const struct ChppClient *client,
bool clearOnly) {
UNUSED_VAR(client);
chppCloseOpenRequests(clientState, CHPP_ENDPOINT_CLIENT, clearOnly);
}
struct ChppAppHeader *chppAllocClientNotification(size_t len) {
return chppAllocNotification(CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION, len);
}