blob: 353e2badc8d6b56abf4bedaeea2d0e70b65541de [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/app.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "chpp/clients.h"
#include "chpp/clients/discovery.h"
#ifdef CHPP_CLIENT_ENABLED_LOOPBACK
#include "chpp/clients/loopback.h"
#endif
#include "chpp/log.h"
#include "chpp/macros.h"
#include "chpp/notifier.h"
#include "chpp/pal_api.h"
#include "chpp/services.h"
#include "chpp/services/discovery.h"
#include "chpp/services/loopback.h"
#include "chpp/services/nonhandle.h"
/************************************************
* Prototypes
***********************************************/
static bool chppProcessPredefinedClientRequest(struct ChppAppState *context,
uint8_t *buf, size_t len);
static bool chppProcessPredefinedServiceResponse(struct ChppAppState *context,
uint8_t *buf, size_t len);
static bool chppProcessPredefinedClientNotification(
struct ChppAppState *context, uint8_t *buf, size_t len);
static bool chppProcessPredefinedServiceNotification(
struct ChppAppState *context, uint8_t *buf, size_t len);
static bool chppDatagramLenIsOk(struct ChppAppState *context,
struct ChppAppHeader *rxHeader, size_t len);
ChppDispatchFunction *chppGetDispatchFunction(struct ChppAppState *context,
uint8_t handle,
enum ChppMessageType type);
static inline const struct ChppService *chppServiceOfHandle(
struct ChppAppState *appContext, uint8_t handle);
static inline const struct ChppClient *chppClientOfHandle(
struct ChppAppState *appContext, uint8_t handle);
static inline void *chppServiceContextOfHandle(struct ChppAppState *appContext,
uint8_t handle);
static inline void *chppClientContextOfHandle(struct ChppAppState *appContext,
uint8_t handle);
static void *chppClientServiceContextOfHandle(struct ChppAppState *appContext,
uint8_t handle,
enum ChppMessageType type);
static void chppProcessPredefinedHandleDatagram(struct ChppAppState *context,
uint8_t *buf, size_t len);
static void chppProcessNegotiatedHandleDatagram(struct ChppAppState *context,
uint8_t *buf, size_t len);
/************************************************
* Private Functions
***********************************************/
/**
* Processes a client request that is determined to be for a predefined CHPP
* service.
*
* @param context Maintains status for each app layer instance.
* @param buf Input data. Cannot be null.
* @param len Length of input data in bytes.
*
* @return False if handle is invalid. True otherwise.
*/
static bool chppProcessPredefinedClientRequest(struct ChppAppState *context,
uint8_t *buf, size_t len) {
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
bool handleValid = true;
bool dispatchResult = true;
switch (rxHeader->handle) {
case CHPP_HANDLE_LOOPBACK: {
dispatchResult = chppDispatchLoopbackClientRequest(context, buf, len);
break;
}
case CHPP_HANDLE_DISCOVERY: {
dispatchResult = chppDispatchDiscoveryClientRequest(context, buf, len);
break;
}
default: {
handleValid = false;
}
}
if (dispatchResult == false) {
CHPP_LOGE("Handle=%" PRIu8
" received unknown client request. command=%#x, transaction ID="
"%" PRIu8,
rxHeader->handle, rxHeader->command, rxHeader->transaction);
}
return handleValid;
}
/**
* Processes a service response that is determined to be for a predefined CHPP
* client.
*
* @param context Maintains status for each app layer instance.
* @param buf Input data. Cannot be null.
* @param len Length of input data in bytes.
*
* @return False if handle is invalid. True otherwise.
*/
static bool chppProcessPredefinedServiceResponse(struct ChppAppState *context,
uint8_t *buf, size_t len) {
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
bool handleValid = true;
bool dispatchResult = true;
switch (rxHeader->handle) {
#ifdef CHPP_CLIENT_ENABLED_LOOPBACK
case CHPP_HANDLE_LOOPBACK: {
dispatchResult = chppDispatchLoopbackServiceResponse(context, buf, len);
break;
}
#endif // CHPP_CLIENT_ENABLED_LOOPBACK
#ifdef CHPP_CLIENT_ENABLED_DISCOVERY
case CHPP_HANDLE_DISCOVERY: {
dispatchResult = chppDispatchDiscoveryServiceResponse(context, buf, len);
break;
}
#endif // CHPP_CLIENT_ENABLED_DISCOVERY
default: {
handleValid = false;
}
}
if (dispatchResult == false) {
CHPP_LOGE("Handle=%" PRIu8
" received unknown server response. command=%#x, transaction ID="
"%" PRIu8,
rxHeader->handle, rxHeader->command, rxHeader->transaction);
}
return handleValid;
}
/**
* Processes a client notification that is determined to be for a predefined
* CHPP service.
*
* @param context Maintains status for each app layer instance.
* @param buf Input data. Cannot be null.
* @param len Length of input data in bytes.
*
* @return False if handle is invalid. True otherwise.
*/
static bool chppProcessPredefinedClientNotification(
struct ChppAppState *context, uint8_t *buf, size_t len) {
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
bool handleValid = true;
bool dispatchResult = true;
// No predefined services support these yet
handleValid = false;
UNUSED_VAR(context);
UNUSED_VAR(len);
UNUSED_VAR(rxHeader);
UNUSED_VAR(dispatchResult);
return handleValid;
}
/**
* Processes a service notification that is determined to be for a predefined
* CHPP client.
*
* @param context Maintains status for each app layer instance.
* @param buf Input data. Cannot be null.
* @param len Length of input data in bytes.
*
* @return False if handle is invalid. True otherwise.
*/
static bool chppProcessPredefinedServiceNotification(
struct ChppAppState *context, uint8_t *buf, size_t len) {
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
bool handleValid = true;
bool dispatchResult = true;
// No predefined clients support these yet
handleValid = false;
UNUSED_VAR(context);
UNUSED_VAR(len);
UNUSED_VAR(rxHeader);
UNUSED_VAR(dispatchResult);
return handleValid;
}
/**
* Verifies if the length of a Rx Datagram from the transport layer is
* sufficient for the associated service.
*
* @param context Maintains status for each app layer instance.
* @param rxHeader The pointer to the datagram RX header.
* @param len Length of the datagram in bytes.
*
* @return true if length is ok.
*/
static bool chppDatagramLenIsOk(struct ChppAppState *context,
struct ChppAppHeader *rxHeader, size_t len) {
size_t minLen = SIZE_MAX;
uint8_t handle = rxHeader->handle;
if (handle < CHPP_HANDLE_NEGOTIATED_RANGE_START) { // Predefined
switch (handle) {
case CHPP_HANDLE_NONE:
minLen = sizeof_member(struct ChppAppHeader, handle);
break;
case CHPP_HANDLE_LOOPBACK:
minLen = sizeof_member(struct ChppAppHeader, handle) +
sizeof_member(struct ChppAppHeader, type);
break;
case CHPP_HANDLE_DISCOVERY:
minLen = sizeof(struct ChppAppHeader);
break;
default:
CHPP_LOGE("Invalid predefined handle %" PRIu8, handle);
}
} else { // Negotiated
enum ChppMessageType messageType =
CHPP_APP_GET_MESSAGE_TYPE(rxHeader->type);
switch (messageType) {
case CHPP_MESSAGE_TYPE_CLIENT_REQUEST:
case CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION: {
minLen = chppServiceOfHandle(context, handle)->minLength;
break;
}
case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE:
case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: {
minLen = chppClientOfHandle(context, handle)->minLength;
break;
}
default: {
CHPP_LOGE("Invalid message type %d", messageType);
break;
}
}
}
if (len < minLen) {
CHPP_LOGE("Received datagram too short for handle=%" PRIu8
", len=%" PRIuSIZE " < %" PRIuSIZE,
handle, len, minLen);
}
return (len >= minLen);
}
/**
* Returns the dispatch function of a particular negotiated client/service
* handle and message type. This shall be null if it is unsupported by the
* service.
*
* @param context Maintains status for each app layer instance.
* @param handle Handle number for the client/service.
* @param type Message type.
*
* @return Pointer to a function that dispatches incoming datagrams for any
* particular client/service.
*/
ChppDispatchFunction *chppGetDispatchFunction(struct ChppAppState *context,
uint8_t handle,
enum ChppMessageType type) {
switch (CHPP_APP_GET_MESSAGE_TYPE(type)) {
case CHPP_MESSAGE_TYPE_CLIENT_REQUEST: {
return chppServiceOfHandle(context, handle)->requestDispatchFunctionPtr;
break;
}
case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE: {
return chppClientOfHandle(context, handle)->responseDispatchFunctionPtr;
break;
}
case CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION: {
return chppServiceOfHandle(context, handle)
->notificationDispatchFunctionPtr;
break;
}
case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: {
return chppClientOfHandle(context, handle)
->notificationDispatchFunctionPtr;
break;
}
default: {
return NULL;
}
}
}
/**
* Returns a pointer to the ChppService struct of a particular negotiated
* service handle.
*
* @param context Maintains status for each app layer instance.
* @param handle Handle number for the service.
*
* @return Pointer to the ChppService struct of a particular service handle.
*/
static inline const struct ChppService *chppServiceOfHandle(
struct ChppAppState *context, uint8_t handle) {
CHPP_DEBUG_ASSERT(CHPP_SERVICE_INDEX_OF_HANDLE(handle) <
context->registeredServiceCount);
return context->registeredServices[CHPP_SERVICE_INDEX_OF_HANDLE(handle)];
}
/**
* Returns a pointer to the ChppClient struct of a particular negotiated
* handle. Returns null if a client doesn't exist for the handle.
*
* @param context Maintains status for each app layer instance.
* @param handle Handle number for the service.
*
* @return Pointer to the ChppClient struct matched to a particular handle.
*/
static inline const struct ChppClient *chppClientOfHandle(
struct ChppAppState *context, uint8_t handle) {
CHPP_DEBUG_ASSERT(CHPP_SERVICE_INDEX_OF_HANDLE(handle) <
context->registeredClientCount);
return context->registeredClients[context->clientIndexOfServiceIndex
[CHPP_SERVICE_INDEX_OF_HANDLE(handle)]];
}
/**
* Returns a pointer to the service struct of a particular negotiated service
* handle.
*
* @param context Maintains status for each app layer instance.
* @param handle Handle number for the service.
*
* @return Pointer to the context struct of the service.
*/
static inline void *chppServiceContextOfHandle(struct ChppAppState *context,
uint8_t handle) {
CHPP_DEBUG_ASSERT(CHPP_SERVICE_INDEX_OF_HANDLE(handle) <
context->registeredServiceCount);
return context
->registeredServiceContexts[CHPP_SERVICE_INDEX_OF_HANDLE(handle)];
}
/**
* Returns a pointer to the client struct of a particular negotiated client
* handle.
*
* @param context Maintains status for each app layer instance.
* @param handle Handle number for the service.
*
* @return Pointer to the ChppService struct of the client.
*/
static inline void *chppClientContextOfHandle(struct ChppAppState *context,
uint8_t handle) {
CHPP_DEBUG_ASSERT(CHPP_SERVICE_INDEX_OF_HANDLE(handle) <
context->registeredClientCount);
return context
->registeredClientContexts[context->clientIndexOfServiceIndex
[CHPP_SERVICE_INDEX_OF_HANDLE(handle)]];
}
/**
* Returns a pointer to the client/service struct of a particular negotiated
* client/service handle.
*
* @param appContext Maintains status for each app layer instance.
* @param handle Handle number for the service.
* @param type Message type (indicates if this is for a client or service).
*
* @return Pointer to the client/service struct of the service handle.
*/
static void *chppClientServiceContextOfHandle(struct ChppAppState *appContext,
uint8_t handle,
enum ChppMessageType type) {
switch (CHPP_APP_GET_MESSAGE_TYPE(type)) {
case CHPP_MESSAGE_TYPE_CLIENT_REQUEST:
case CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION: {
return chppServiceContextOfHandle(appContext, handle);
break;
}
case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE:
case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: {
return chppClientContextOfHandle(appContext, handle);
break;
}
default: {
CHPP_LOGE("Cannot provide context for unknown message type=0x%" PRIx8
" (handle=%" PRIu8 ")",
type, handle);
return NULL;
}
}
}
/**
* Processes a received datagram that is determined to be for a predefined CHPP
* service. Responds with an error if unsuccessful.
*
* @param context Maintains status for each app layer instance.
* @param buf Input data. Cannot be null.
* @param len Length of input data in bytes.
*/
static void chppProcessPredefinedHandleDatagram(struct ChppAppState *context,
uint8_t *buf, size_t len) {
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
bool success = true;
switch (CHPP_APP_GET_MESSAGE_TYPE(rxHeader->type)) {
case CHPP_MESSAGE_TYPE_CLIENT_REQUEST: {
success = chppProcessPredefinedClientRequest(context, buf, len);
break;
}
case CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION: {
success = chppProcessPredefinedClientNotification(context, buf, len);
break;
}
case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE: {
success = chppProcessPredefinedServiceResponse(context, buf, len);
break;
}
case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: {
success = chppProcessPredefinedServiceNotification(context, buf, len);
break;
}
default: {
success = false;
}
}
if (success == false) {
CHPP_LOGE("Predefined handle=%" PRIu8
" does not support message type=0x%" PRIx8 " (len=%" PRIuSIZE
", transaction ID=%" PRIu8 ")",
rxHeader->handle, rxHeader->type, len, rxHeader->transaction);
chppEnqueueTxErrorDatagram(context->transportContext,
CHPP_TRANSPORT_ERROR_APPLAYER);
}
}
/**
* Processes a received datagram that is determined to be for a negotiated CHPP
* service. Responds with an error if unsuccessful.
*
* @param context Maintains status for each app layer instance.
* @param buf Input data. Cannot be null.
* @param len Length of input data in bytes.
*/
static void chppProcessNegotiatedHandleDatagram(struct ChppAppState *context,
uint8_t *buf, size_t len) {
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
enum ChppMessageType messageType = CHPP_APP_GET_MESSAGE_TYPE(rxHeader->type);
void *clientServiceContext =
chppClientServiceContextOfHandle(context, rxHeader->handle, messageType);
if (clientServiceContext == NULL) {
CHPP_LOGE("Negotiated handle=%" PRIu8 " for RX message type=0x%" PRIx8
" is missing context (len=%" PRIuSIZE ", transaction ID=%" PRIu8
")",
rxHeader->handle, rxHeader->type, len, rxHeader->transaction);
chppEnqueueTxErrorDatagram(context->transportContext,
CHPP_TRANSPORT_ERROR_APPLAYER);
CHPP_DEBUG_ASSERT(false);
} else {
ChppDispatchFunction *dispatchFunc =
chppGetDispatchFunction(context, rxHeader->handle, messageType);
if (dispatchFunc == NULL) {
CHPP_LOGE("Negotiated handle=%" PRIu8
" does not support RX message type=0x%" PRIx8 " (len=%" PRIuSIZE
", transaction ID=%" PRIu8 ")",
rxHeader->handle, rxHeader->type, len, rxHeader->transaction);
chppEnqueueTxErrorDatagram(context->transportContext,
CHPP_TRANSPORT_ERROR_APPLAYER);
} else {
// All good. Dispatch datagram and possibly notify a waiting client
enum ChppAppErrorCode error =
dispatchFunc(clientServiceContext, buf, len);
if (error != CHPP_APP_ERROR_NONE) {
CHPP_LOGE("Dispatching RX datagram failed. error=0x%" PRIx16
" handle=0x%" PRIx8 ", type =0x%" PRIx8
", transaction ID=%" PRIu8 ", command=0x%" PRIx16
", len=%" PRIuSIZE,
error, rxHeader->handle, rxHeader->type,
rxHeader->transaction, rxHeader->command, len);
// Only client requests require a dispatch failure response.
if (messageType == CHPP_MESSAGE_TYPE_CLIENT_REQUEST) {
struct ChppAppHeader *response =
chppAllocServiceResponseFixed(rxHeader, struct ChppAppHeader);
if (response == NULL) {
CHPP_LOG_OOM();
} else {
response->error = CHPP_ATTR_AND_ERROR_TO_PACKET_CODE(
CHPP_TRANSPORT_ATTR_NONE, error);
chppEnqueueTxDatagramOrFail(context->transportContext, response,
sizeof(*response));
}
}
} else if (messageType == CHPP_MESSAGE_TYPE_SERVICE_RESPONSE) {
// Datagram is a service response. Check for synchronous operation and
// notify waiting client if needed.
struct ChppClientState *clientContext =
(struct ChppClientState *)clientServiceContext;
chppMutexLock(&clientContext->responseMutex);
clientContext->responseReady = true;
CHPP_LOGD(
"Finished dispatching a service response. Notifying a potential "
"synchronous client");
chppConditionVariableSignal(&clientContext->responseCondVar);
chppMutexUnlock(&clientContext->responseMutex);
}
}
}
}
/************************************************
* Public Functions
***********************************************/
void chppAppInit(struct ChppAppState *appContext,
struct ChppTransportState *transportContext) {
// Default initialize all service/clients
struct ChppClientServiceSet set;
memset(&set, 0xff, sizeof(set)); // set all bits to 1
chppAppInitWithClientServiceSet(appContext, transportContext, set);
}
void chppAppInitWithClientServiceSet(
struct ChppAppState *appContext,
struct ChppTransportState *transportContext,
struct ChppClientServiceSet clientServiceSet) {
CHPP_NOT_NULL(appContext);
CHPP_NOT_NULL(transportContext);
CHPP_LOGI("Initializing the CHPP app layer");
// Don't reset entire ChppAppState to avoid clearing non-transient
// contents e.g. discovery mutex/condvar/states.
appContext->registeredServiceCount = 0;
memset(appContext->registeredServices, 0,
sizeof(appContext->registeredServices));
memset(appContext->registeredServiceContexts, 0,
sizeof(appContext->registeredServiceContexts));
appContext->registeredClientCount = 0;
memset(appContext->registeredClients, 0,
sizeof(appContext->registeredClients));
memset(appContext->registeredClientContexts, 0,
sizeof(appContext->registeredClientContexts));
memset(appContext->clientIndexOfServiceIndex, 0,
sizeof(appContext->clientIndexOfServiceIndex));
appContext->clientServiceSet = clientServiceSet;
appContext->transportContext = transportContext;
#ifdef CHPP_CLIENT_ENABLED_DISCOVERY
chppDiscoveryInit(appContext);
#endif // CHPP_CLIENT_ENABLED_DISCOVERY
chppPalSystemApiInit(appContext);
#ifdef CHPP_SERVICE_ENABLED
chppRegisterCommonServices(appContext);
#endif
#ifdef CHPP_CLIENT_ENABLED
chppRegisterCommonClients(appContext);
#endif
}
void chppAppDeinit(struct ChppAppState *appContext) {
chppAppDeinitTransient(appContext);
#ifdef CHPP_CLIENT_ENABLED_DISCOVERY
// Discovery should only be deinitialized on true CHPP app deinit
// (shutdown), since a client may be waiting on discovery completion
// during a transient deinit (reset).
chppDiscoveryDeinit(appContext);
#endif // CHPP_CLIENT_ENABLED_DISCOVERY
}
void chppAppDeinitTransient(struct ChppAppState *appContext) {
CHPP_NOT_NULL(appContext);
CHPP_LOGI("Deinitializing the CHPP app layer");
#ifdef CHPP_CLIENT_ENABLED
chppDeregisterCommonClients(appContext);
#endif
#ifdef CHPP_SERVICE_ENABLED
chppDeregisterCommonServices(appContext);
#endif
chppPalSystemApiDeinit(appContext);
}
void chppProcessRxDatagram(struct ChppAppState *context, uint8_t *buf,
size_t len) {
struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf;
if (len == 0) {
CHPP_LOGE("chppProcessRxDatagram called with payload length of 0");
CHPP_DEBUG_ASSERT(false);
} else if (len < sizeof(struct ChppAppHeader)) {
uint8_t *handle = (uint8_t *)buf;
CHPP_LOGD("App layer RX datagram (len=%" PRIuSIZE ") for handle=%" PRIu8,
len, *handle);
} else {
CHPP_LOGD("App layer RX datagram (len=%" PRIuSIZE ") for handle=%" PRIu8
", type=0x%" PRIx8 ", transaction ID=%" PRIu8 ", error=%" PRIu8
", command=0x%" PRIx16,
len, rxHeader->handle, rxHeader->type, rxHeader->transaction,
rxHeader->error, rxHeader->command);
}
if (chppDatagramLenIsOk(context, rxHeader, len)) {
if (rxHeader->handle == CHPP_HANDLE_NONE) {
chppDispatchNonHandle(context, buf, len);
} else if (rxHeader->handle < CHPP_HANDLE_NEGOTIATED_RANGE_START) {
chppProcessPredefinedHandleDatagram(context, buf, len);
} else {
chppProcessNegotiatedHandleDatagram(context, buf, len);
}
}
chppAppProcessDoneCb(context->transportContext, buf);
}
void chppUuidToStr(const uint8_t uuid[CHPP_SERVICE_UUID_LEN],
char strOut[CHPP_SERVICE_UUID_STRING_LEN]) {
snprintf(
strOut, CHPP_SERVICE_UUID_STRING_LEN,
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7],
uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14],
uuid[15]);
}