You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

599 lines
19 KiB

/*
* 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 ChppClientState *clientState);
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 whetherthe client is ready.
*/
static bool chppIsClientApiReady(struct ChppClientState *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.
*/
ChppClientDeinitFunction *chppGetClientDeinitFunction(
struct ChppAppState *context, uint8_t index) {
return context->registeredClients[index]->deinitFunctionPtr;
}
/************************************************
* Public Functions
***********************************************/
void chppRegisterCommonClients(struct ChppAppState *context) {
UNUSED_VAR(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_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 ChppClientState *clientState,
struct ChppRequestResponseState *rRStates,
const struct ChppClient *newClient) {
CHPP_NOT_NULL(newClient);
if (appContext->registeredClientCount >= CHPP_MAX_REGISTERED_CLIENTS) {
CHPP_LOGE("Max clients registered: %" PRIu8,
appContext->registeredClientCount);
} else {
clientState->appContext = appContext;
clientState->rRStates = rRStates;
clientState->index = appContext->registeredClientCount;
appContext->registeredClientContexts[appContext->registeredClientCount] =
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_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 ChppClientState *clientState, uint8_t handle) {
CHPP_ASSERT_LOG(!clientState->initialized,
"Client H#%" PRIu8 " already initialized", handle);
if (!clientState->everInitialized) {
clientState->handle = handle;
chppMutexInit(&clientState->responseMutex);
chppConditionVariableInit(&clientState->responseCondVar);
clientState->everInitialized = true;
}
clientState->initialized = true;
}
void chppClientDeinit(struct ChppClientState *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_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_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->registeredClientContexts[clientIndex]);
}
}
}
}
struct ChppAppHeader *chppAllocClientRequest(
struct ChppClientState *clientState, size_t len) {
CHPP_ASSERT(len >= CHPP_APP_MIN_LEN_HEADER_WITH_TRANSACTION);
struct ChppAppHeader *result = chppMalloc(len);
if (result != NULL) {
result->handle = clientState->handle;
result->type = CHPP_MESSAGE_TYPE_CLIENT_REQUEST;
result->transaction = clientState->transaction;
result->error = CHPP_APP_ERROR_NONE;
result->command = CHPP_APP_COMMAND_NONE;
clientState->transaction++;
}
return result;
}
struct ChppAppHeader *chppAllocClientRequestCommand(
struct ChppClientState *clientState, uint16_t command) {
struct ChppAppHeader *result =
chppAllocClientRequest(clientState, sizeof(struct ChppAppHeader));
if (result != NULL) {
result->command = command;
}
return result;
}
void chppClientTimestampRequest(struct ChppClientState *clientState,
struct ChppRequestResponseState *rRState,
struct ChppAppHeader *requestHeader,
uint64_t timeoutNs) {
if (rRState->requestState == CHPP_REQUEST_STATE_REQUEST_SENT) {
CHPP_LOGE("Dupe req ID=%" PRIu8 " existing ID=%" PRIu8 " from t=%" PRIu64,
requestHeader->transaction, rRState->transaction,
rRState->requestTimeNs / CHPP_NSEC_PER_MSEC);
// Clear a possible pending timeout from the previous request
rRState->responseTimeNs = CHPP_TIME_MAX;
chppClientRecalculateNextTimeout(clientState->appContext);
}
rRState->requestTimeNs = chppGetCurrentTimeNs();
rRState->requestState = CHPP_REQUEST_STATE_REQUEST_SENT;
rRState->transaction = requestHeader->transaction;
if (timeoutNs == CHPP_CLIENT_REQUEST_TIMEOUT_INFINITE) {
rRState->responseTimeNs = CHPP_TIME_MAX;
} else {
rRState->responseTimeNs = timeoutNs + rRState->requestTimeNs;
clientState->appContext->nextRequestTimeoutNs = MIN(
clientState->appContext->nextRequestTimeoutNs, rRState->responseTimeNs);
}
CHPP_LOGD("Timestamp req ID=%" PRIu8 " at t=%" PRIu64 " timeout=%" PRIu64
" (requested=%" PRIu64 "), next timeout=%" PRIu64,
rRState->transaction, rRState->requestTimeNs / CHPP_NSEC_PER_MSEC,
rRState->responseTimeNs / CHPP_NSEC_PER_MSEC,
timeoutNs / CHPP_NSEC_PER_MSEC,
clientState->appContext->nextRequestTimeoutNs / CHPP_NSEC_PER_MSEC);
}
bool chppClientTimestampResponse(struct ChppClientState *clientState,
struct ChppRequestResponseState *rRState,
const struct ChppAppHeader *responseHeader) {
bool success = false;
uint64_t responseTime = chppGetCurrentTimeNs();
switch (rRState->requestState) {
case CHPP_REQUEST_STATE_NONE: {
CHPP_LOGE("Resp with no req t=%" PRIu64,
responseTime / CHPP_NSEC_PER_MSEC);
break;
}
case CHPP_REQUEST_STATE_RESPONSE_RCV: {
CHPP_LOGE("Extra resp at t=%" PRIu64 " for req t=%" PRIu64,
responseTime / CHPP_NSEC_PER_MSEC,
rRState->requestTimeNs / CHPP_NSEC_PER_MSEC);
break;
}
case CHPP_REQUEST_STATE_RESPONSE_TIMEOUT: {
CHPP_LOGE("Late resp at t=%" PRIu64 " for req t=%" PRIu64,
responseTime / CHPP_NSEC_PER_MSEC,
rRState->requestTimeNs / CHPP_NSEC_PER_MSEC);
break;
}
case CHPP_REQUEST_STATE_REQUEST_SENT: {
if (responseHeader->transaction != rRState->transaction) {
CHPP_LOGE("Invalid resp ID=%" PRIu8 " at t=%" PRIu64
" expected=%" PRIu8,
responseHeader->transaction,
responseTime / CHPP_NSEC_PER_MSEC, rRState->transaction);
} else {
rRState->requestState = (responseTime > rRState->responseTimeNs)
? CHPP_REQUEST_STATE_RESPONSE_TIMEOUT
: CHPP_REQUEST_STATE_RESPONSE_RCV;
success = true;
CHPP_LOGD(
"Timestamp resp ID=%" PRIu8 " req t=%" PRIu64 " resp t=%" PRIu64
" timeout t=%" PRIu64 " (RTT=%" PRIu64 ", timeout = %s)",
rRState->transaction, rRState->requestTimeNs / CHPP_NSEC_PER_MSEC,
responseTime / CHPP_NSEC_PER_MSEC,
rRState->responseTimeNs / CHPP_NSEC_PER_MSEC,
(responseTime - rRState->requestTimeNs) / CHPP_NSEC_PER_MSEC,
(responseTime > rRState->responseTimeNs) ? "yes" : "no");
}
break;
}
default: {
CHPP_DEBUG_ASSERT(false);
}
}
if (success) {
if (rRState->responseTimeNs ==
clientState->appContext->nextRequestTimeoutNs) {
// This was the next upcoming timeout
chppClientRecalculateNextTimeout(clientState->appContext);
}
rRState->responseTimeNs = responseTime;
}
return success;
}
bool chppSendTimestampedRequestOrFail(struct ChppClientState *clientState,
struct ChppRequestResponseState *rRState,
void *buf, size_t len,
uint64_t timeoutNs) {
CHPP_ASSERT(len >= CHPP_APP_MIN_LEN_HEADER_WITH_TRANSACTION);
if (!chppIsClientApiReady(clientState)) {
CHPP_FREE_AND_NULLIFY(buf);
return false;
}
chppClientTimestampRequest(clientState, rRState, buf, timeoutNs);
clientState->responseReady = false;
bool success = chppEnqueueTxDatagramOrFail(
clientState->appContext->transportContext, buf, len);
// Failure to enqueue a TX datagram means that a request was known to be not
// transmitted. We explicitly set requestState to be in the NONE state, so
// that unintended app layer timeouts do not occur.
if (!success) {
rRState->requestState = CHPP_REQUEST_STATE_NONE;
}
return success;
}
bool chppSendTimestampedRequestAndWait(struct ChppClientState *clientState,
struct ChppRequestResponseState *rRState,
void *buf, size_t len) {
return chppSendTimestampedRequestAndWaitTimeout(
clientState, rRState, buf, len, CHPP_CLIENT_REQUEST_TIMEOUT_DEFAULT);
}
bool chppSendTimestampedRequestAndWaitTimeout(
struct ChppClientState *clientState,
struct ChppRequestResponseState *rRState, void *buf, size_t len,
uint64_t timeoutNs) {
bool result = chppSendTimestampedRequestOrFail(
clientState, rRState, buf, len, CHPP_CLIENT_REQUEST_TIMEOUT_INFINITE);
if (result) {
chppMutexLock(&clientState->responseMutex);
while (result && !clientState->responseReady) {
result = chppConditionVariableTimedWait(&clientState->responseCondVar,
&clientState->responseMutex,
timeoutNs);
}
if (!clientState->responseReady) {
rRState->requestState = CHPP_REQUEST_STATE_RESPONSE_TIMEOUT;
CHPP_LOGE("Response timeout after %" PRIu64 " ms",
timeoutNs / CHPP_NSEC_PER_MSEC);
result = false;
}
chppMutexUnlock(&clientState->responseMutex);
}
return result;
}
void chppClientPseudoOpen(struct ChppClientState *clientState) {
clientState->pseudoOpen = true;
}
bool chppClientSendOpenRequest(struct ChppClientState *clientState,
struct ChppRequestResponseState *openRRState,
uint16_t openCommand, bool blocking) {
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) {
CHPP_LOG_OOM();
} else {
clientState->openState = CHPP_OPEN_STATE_OPENING;
if (blocking) {
CHPP_LOGD("Opening service - blocking");
result = chppSendTimestampedRequestAndWait(clientState, openRRState,
request, sizeof(*request));
} else {
CHPP_LOGD("Opening service - non-blocking");
result = chppSendTimestampedRequestOrFail(
clientState, openRRState, request, sizeof(*request),
CHPP_CLIENT_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 ChppClientState *clientState,
uint8_t *buf, size_t len) {
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_LOGI("Service open succeeded at service");
clientState->openState = CHPP_OPEN_STATE_OPENED;
}
}
void chppClientRecalculateNextTimeout(struct ChppAppState *context) {
context->nextRequestTimeoutNs = CHPP_TIME_MAX;
for (uint8_t clientIdx = 0; clientIdx < context->registeredClientCount;
clientIdx++) {
for (uint16_t cmdIdx = 0;
cmdIdx < context->registeredClients[clientIdx]->rRStateCount;
cmdIdx++) {
struct ChppRequestResponseState *rRState =
&context->registeredClientStates[clientIdx]->rRStates[cmdIdx];
if (rRState->requestState == CHPP_REQUEST_STATE_REQUEST_SENT) {
context->nextRequestTimeoutNs =
MIN(context->nextRequestTimeoutNs, rRState->responseTimeNs);
}
}
}
CHPP_LOGD("nextReqTimeout=%" PRIu64,
context->nextRequestTimeoutNs / CHPP_NSEC_PER_MSEC);
}
void chppClientCloseOpenRequests(struct ChppClientState *clientState,
const struct ChppClient *client,
bool clearOnly) {
bool recalcNeeded = false;
for (uint16_t cmdIdx = 0; cmdIdx < client->rRStateCount; cmdIdx++) {
if (clientState->rRStates[cmdIdx].requestState ==
CHPP_REQUEST_STATE_REQUEST_SENT) {
recalcNeeded = true;
CHPP_LOGE("Closing open req #%" PRIu16 " clear %d", cmdIdx, clearOnly);
if (clearOnly) {
clientState->rRStates[cmdIdx].requestState =
CHPP_REQUEST_STATE_RESPONSE_TIMEOUT;
} else {
struct ChppAppHeader *response =
chppMalloc(sizeof(struct ChppAppHeader));
if (response == NULL) {
CHPP_LOG_OOM();
} else {
response->handle = clientState->handle;
response->type = CHPP_MESSAGE_TYPE_SERVICE_RESPONSE;
response->transaction = clientState->rRStates[cmdIdx].transaction;
response->error = CHPP_APP_ERROR_TIMEOUT;
response->command = cmdIdx;
chppAppProcessRxDatagram(clientState->appContext, (uint8_t *)response,
sizeof(struct ChppAppHeader));
}
}
}
}
if (recalcNeeded) {
chppClientRecalculateNextTimeout(clientState->appContext);
}
}