| /* |
| * 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" |
| #include "chpp/services.h" |
| #ifdef CHPP_CLIENT_ENABLED_LOOPBACK |
| #include "chpp/clients/loopback.h" |
| #endif |
| #ifdef CHPP_CLIENT_ENABLED_TIMESYNC |
| #include "chpp/clients/timesync.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" |
| #include "chpp/services/timesync.h" |
| #include "chpp/time.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 chppDatagramLenIsOk(struct ChppAppState *context, |
| const struct ChppAppHeader *rxHeader, |
| size_t len); |
| static ChppDispatchFunction *chppGetDispatchFunction( |
| struct ChppAppState *context, uint8_t handle, enum ChppMessageType type); |
| #ifdef CHPP_CLIENT_ENABLED_DISCOVERY |
| static ChppNotifierFunction *chppGetClientResetNotifierFunction( |
| struct ChppAppState *context, uint8_t index); |
| #endif // CHPP_CLIENT_ENABLED_DISCOVERY |
| static ChppNotifierFunction *chppGetServiceResetNotifierFunction( |
| struct ChppAppState *context, uint8_t index); |
| 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 struct ChppEndpointState *chppServiceStateOfHandle( |
| struct ChppAppState *appContext, uint8_t handle); |
| static inline struct ChppEndpointState *chppClientStateOfHandle( |
| struct ChppAppState *appContext, uint8_t handle); |
| static struct ChppEndpointState *chppClientOrServiceStateOfHandle( |
| 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 State of the app layer. |
| * @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) { |
| const 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_TIMESYNC: { |
| dispatchResult = chppDispatchTimesyncClientRequest(context, buf, len); |
| break; |
| } |
| |
| case CHPP_HANDLE_DISCOVERY: { |
| dispatchResult = chppDispatchDiscoveryClientRequest(context, buf, len); |
| break; |
| } |
| |
| default: { |
| handleValid = false; |
| } |
| } |
| |
| if (dispatchResult == false) { |
| CHPP_LOGE("H#%" PRIu8 " unknown request. cmd=%#x, 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 State of the app layer. |
| * @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) { |
| CHPP_DEBUG_NOT_NULL(buf); |
| // Possibly unused if compiling without the clients below enabled |
| UNUSED_VAR(context); |
| UNUSED_VAR(len); |
| |
| const 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_TIMESYNC |
| case CHPP_HANDLE_TIMESYNC: { |
| dispatchResult = chppDispatchTimesyncServiceResponse(context, buf, len); |
| break; |
| } |
| #endif // CHPP_CLIENT_ENABLED_TIMESYNC |
| |
| #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("H#%" PRIu8 " unknown response. cmd=%#x, ID=%" PRIu8 |
| ", len=%" PRIuSIZE, |
| rxHeader->handle, rxHeader->command, rxHeader->transaction, len); |
| } |
| |
| return handleValid; |
| } |
| |
| /** |
| * Verifies if the length of a Rx Datagram from the transport layer is |
| * sufficient for the associated service/client. |
| * |
| * @param context State of the app layer. |
| * @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, |
| const struct ChppAppHeader *rxHeader, |
| size_t len) { |
| CHPP_DEBUG_NOT_NULL(context); |
| CHPP_DEBUG_NOT_NULL(rxHeader); |
| |
| 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_TIMESYNC: |
| case CHPP_HANDLE_DISCOVERY: |
| minLen = sizeof(struct ChppAppHeader); |
| break; |
| |
| default: |
| // len remains SIZE_MAX |
| CHPP_LOGE("Invalid H#%" PRIu8, handle); |
| return false; |
| } |
| |
| } 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_RESPONSE: |
| case CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION: { |
| const struct ChppService *service = |
| chppServiceOfHandle(context, handle); |
| if (service != NULL) { |
| minLen = service->minLength; |
| } |
| break; |
| } |
| case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE: |
| case CHPP_MESSAGE_TYPE_SERVICE_REQUEST: |
| case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: { |
| const struct ChppClient *client = chppClientOfHandle(context, handle); |
| if (client != NULL) { |
| minLen = client->minLength; |
| } |
| break; |
| } |
| default: |
| CHPP_LOGE("Invalid type=%d or H#%" PRIu8, messageType, handle); |
| return false; |
| } |
| } |
| |
| if (len < minLen) { |
| CHPP_LOGE("Datagram len=%" PRIuSIZE " < %" PRIuSIZE " for H#%" PRIu8, len, |
| minLen, handle); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Returns the dispatch function of a particular negotiated client/service |
| * handle and message type. |
| * |
| * Returns null if it is unsupported by the service. |
| * |
| * @param context State of the app layer. |
| * @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. |
| */ |
| static ChppDispatchFunction *chppGetDispatchFunction( |
| struct ChppAppState *context, uint8_t handle, enum ChppMessageType type) { |
| CHPP_DEBUG_NOT_NULL(context); |
| // chppDatagramLenIsOk() has already confirmed that the handle # is valid. |
| // Therefore, no additional checks are necessary for chppClientOfHandle(), |
| // chppServiceOfHandle(), or chppClientOrServiceStateOfHandle(). |
| |
| // Make sure the client is open before it can receive any message: |
| switch (CHPP_APP_GET_MESSAGE_TYPE(type)) { |
| case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE: |
| case CHPP_MESSAGE_TYPE_SERVICE_REQUEST: |
| case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: { |
| struct ChppEndpointState *clientState = |
| chppClientStateOfHandle(context, handle); |
| if (clientState->openState == CHPP_OPEN_STATE_CLOSED) { |
| CHPP_LOGE("RX service response but client closed"); |
| return NULL; |
| } |
| break; |
| } |
| default: |
| // no check needed on the service side |
| break; |
| } |
| |
| switch (CHPP_APP_GET_MESSAGE_TYPE(type)) { |
| case CHPP_MESSAGE_TYPE_CLIENT_REQUEST: |
| return chppServiceOfHandle(context, handle)->requestDispatchFunctionPtr; |
| case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE: |
| return chppClientOfHandle(context, handle)->responseDispatchFunctionPtr; |
| case CHPP_MESSAGE_TYPE_SERVICE_REQUEST: |
| return chppClientOfHandle(context, handle)->requestDispatchFunctionPtr; |
| case CHPP_MESSAGE_TYPE_CLIENT_RESPONSE: |
| return chppServiceOfHandle(context, handle)->responseDispatchFunctionPtr; |
| case CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION: |
| return chppServiceOfHandle(context, handle) |
| ->notificationDispatchFunctionPtr; |
| case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: |
| return chppClientOfHandle(context, handle) |
| ->notificationDispatchFunctionPtr; |
| } |
| |
| return NULL; |
| } |
| |
| #ifdef CHPP_CLIENT_ENABLED_DISCOVERY |
| /** |
| * Returns the reset notification function pointer of a particular negotiated |
| * client. |
| * |
| * Returns null for clients that do not need or support a reset notification. |
| * |
| * @param context State of the app layer. |
| * @param index Index of the registered client. |
| * |
| * @return Pointer to the reset notification function. |
| */ |
| static ChppNotifierFunction *chppGetClientResetNotifierFunction( |
| struct ChppAppState *context, uint8_t index) { |
| CHPP_DEBUG_NOT_NULL(context); |
| return context->registeredClients[index]->resetNotifierFunctionPtr; |
| } |
| #endif // CHPP_CLIENT_ENABLED_DISCOVERY |
| |
| /** |
| * Returns the reset function pointer of a particular registered service. |
| * |
| * Returns null for services that do not need or support a reset notification. |
| * |
| * @param context State of the app layer. |
| * @param index Index of the registered service. |
| * |
| * @return Pointer to the reset function. |
| */ |
| ChppNotifierFunction *chppGetServiceResetNotifierFunction( |
| struct ChppAppState *context, uint8_t index) { |
| CHPP_DEBUG_NOT_NULL(context); |
| return context->registeredServices[index]->resetNotifierFunctionPtr; |
| } |
| |
| /** |
| * Returns a pointer to the ChppService struct of the service matched to a |
| * negotiated handle. |
| * |
| * Returns null if a service doesn't exist for the handle. |
| * |
| * @param context State of the app layer. |
| * @param handle Handle number. |
| * |
| * @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_NOT_NULL(context); |
| uint8_t serviceIndex = CHPP_SERVICE_INDEX_OF_HANDLE(handle); |
| if (serviceIndex < context->registeredServiceCount) { |
| return context->registeredServices[serviceIndex]; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * Returns a pointer to the ChppClient struct of the client matched to a |
| * negotiated handle. |
| * |
| * Returns null if a client doesn't exist for the handle. |
| * |
| * @param context State of the app layer. |
| * @param handle Handle number. |
| * |
| * @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_NOT_NULL(context); |
| uint8_t serviceIndex = CHPP_SERVICE_INDEX_OF_HANDLE(handle); |
| if (serviceIndex < context->discoveredServiceCount) { |
| uint8_t clientIndex = context->clientIndexOfServiceIndex[serviceIndex]; |
| if (clientIndex < context->registeredClientCount) { |
| return context->registeredClients[clientIndex]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * Returns the service state for a given handle. |
| * |
| * The caller must pass a valid handle. |
| * |
| * @param context State of the app layer. |
| * @param handle Handle number for the service. |
| * |
| * @return Pointer to a ChppEndpointState. |
| */ |
| static inline struct ChppEndpointState *chppServiceStateOfHandle( |
| struct ChppAppState *context, uint8_t handle) { |
| CHPP_DEBUG_NOT_NULL(context); |
| CHPP_DEBUG_ASSERT(CHPP_SERVICE_INDEX_OF_HANDLE(handle) < |
| context->registeredServiceCount); |
| |
| const uint8_t serviceIdx = CHPP_SERVICE_INDEX_OF_HANDLE(handle); |
| return context->registeredServiceStates[serviceIdx]; |
| } |
| |
| /** |
| * Returns a pointer to the client state for a given handle. |
| * |
| * The caller must pass a valid handle. |
| * |
| * @param context State of the app layer. |
| * @param handle Handle number for the service. |
| * |
| * @return Pointer to the endpoint state. |
| */ |
| static inline struct ChppEndpointState *chppClientStateOfHandle( |
| struct ChppAppState *context, uint8_t handle) { |
| CHPP_DEBUG_NOT_NULL(context); |
| CHPP_DEBUG_ASSERT(CHPP_SERVICE_INDEX_OF_HANDLE(handle) < |
| context->registeredClientCount); |
| const uint8_t serviceIdx = CHPP_SERVICE_INDEX_OF_HANDLE(handle); |
| const uint8_t clientIdx = context->clientIndexOfServiceIndex[serviceIdx]; |
| return context->registeredClientStates[clientIdx]->context; |
| } |
| |
| /** |
| * Returns a pointer to the client or service state for a given handle. |
| * |
| * The caller must pass a valid handle. |
| * |
| * @param appContext State of the app layer. |
| * @param handle Handle number for the service. |
| * @param type Message type (indicates if this is for a client or service). |
| * |
| * @return Pointer to the endpoint state (NULL if wrong type). |
| */ |
| static struct ChppEndpointState *chppClientOrServiceStateOfHandle( |
| 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_RESPONSE: |
| case CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION: |
| return chppServiceStateOfHandle(appContext, handle); |
| case CHPP_MESSAGE_TYPE_SERVICE_REQUEST: |
| case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE: |
| case CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION: |
| return chppClientStateOfHandle(appContext, handle); |
| default: |
| CHPP_LOGE("Unknown type=0x%" PRIx8 " (H#%" 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. |
| * |
| * Predefined requests are only sent by the client side. |
| * Predefined responses are only sent by the service side. |
| * |
| * @param context State of the app layer. |
| * @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) { |
| CHPP_DEBUG_NOT_NULL(context); |
| CHPP_DEBUG_NOT_NULL(buf); |
| |
| struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf; |
| bool success = false; |
| |
| switch (CHPP_APP_GET_MESSAGE_TYPE(rxHeader->type)) { |
| case CHPP_MESSAGE_TYPE_CLIENT_REQUEST: { |
| success = chppProcessPredefinedClientRequest(context, buf, len); |
| break; |
| } |
| case CHPP_MESSAGE_TYPE_SERVICE_RESPONSE: { |
| success = chppProcessPredefinedServiceResponse(context, buf, len); |
| break; |
| } |
| default: |
| // Predefined client/services do not use |
| // - notifications, |
| // - service requests / client responses |
| break; |
| } |
| |
| if (!success) { |
| CHPP_LOGE("H#%" PRIu8 " undefined msg type=0x%" PRIx8 " (len=%" PRIuSIZE |
| ", 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 |
| * client or service. |
| * |
| * The datagram is processed by the dispatch function matching the datagram |
| * type. @see ChppService and ChppClient. |
| * |
| * If a request dispatch function returns an error (anything different from |
| * CHPP_APP_ERROR_NONE) then an error response is automatically sent back to the |
| * remote endpoint. |
| * |
| * @param appContext State of the app layer. |
| * @param buf Input data. Cannot be null. |
| * @param len Length of input data in bytes. |
| */ |
| static void chppProcessNegotiatedHandleDatagram(struct ChppAppState *appContext, |
| uint8_t *buf, size_t len) { |
| CHPP_DEBUG_NOT_NULL(appContext); |
| CHPP_DEBUG_NOT_NULL(buf); |
| |
| const struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf; |
| enum ChppMessageType messageType = CHPP_APP_GET_MESSAGE_TYPE(rxHeader->type); |
| |
| // Could be either the client or the service state depending on the message |
| // type. |
| struct ChppEndpointState *endpointState = chppClientOrServiceStateOfHandle( |
| appContext, rxHeader->handle, messageType); |
| if (endpointState == NULL) { |
| CHPP_LOGE("H#%" PRIu8 " missing ctx (msg=0x%" PRIx8 " len=%" PRIuSIZE |
| ", ID=%" PRIu8 ")", |
| rxHeader->handle, rxHeader->type, len, rxHeader->transaction); |
| chppEnqueueTxErrorDatagram(appContext->transportContext, |
| CHPP_TRANSPORT_ERROR_APPLAYER); |
| CHPP_DEBUG_ASSERT(false); |
| return; |
| } |
| |
| ChppDispatchFunction *dispatchFunc = |
| chppGetDispatchFunction(appContext, rxHeader->handle, messageType); |
| if (dispatchFunc == NULL) { |
| CHPP_LOGE("H#%" PRIu8 " unsupported msg=0x%" PRIx8 " (len=%" PRIuSIZE |
| ", ID=%" PRIu8 ")", |
| rxHeader->handle, rxHeader->type, len, rxHeader->transaction); |
| chppEnqueueTxErrorDatagram(appContext->transportContext, |
| CHPP_TRANSPORT_ERROR_APPLAYER); |
| return; |
| } |
| |
| // All good. Dispatch datagram and possibly notify a waiting client |
| enum ChppAppErrorCode error = dispatchFunc(endpointState->context, buf, len); |
| |
| if (error != CHPP_APP_ERROR_NONE) { |
| CHPP_LOGE("RX dispatch err=0x%" PRIx16 " H#%" PRIu8 " type=0x%" PRIx8 |
| " ID=%" PRIu8 " cmd=0x%" PRIx16 " len=%" PRIuSIZE, |
| error, rxHeader->handle, rxHeader->type, rxHeader->transaction, |
| rxHeader->command, len); |
| |
| // Requests require a dispatch failure response. |
| if (messageType == CHPP_MESSAGE_TYPE_CLIENT_REQUEST || |
| messageType == CHPP_MESSAGE_TYPE_SERVICE_REQUEST) { |
| struct ChppAppHeader *response = |
| chppAllocResponseFixed(rxHeader, struct ChppAppHeader); |
| if (response != NULL) { |
| response->error = (uint8_t)error; |
| chppEnqueueTxDatagramOrFail(appContext->transportContext, response, |
| sizeof(*response)); |
| } |
| } |
| return; |
| } |
| |
| // Datagram is a response. |
| // Check for synchronous operation and notify waiting endpoint if needed. |
| if (messageType == CHPP_MESSAGE_TYPE_SERVICE_RESPONSE || |
| messageType == CHPP_MESSAGE_TYPE_CLIENT_RESPONSE) { |
| struct ChppSyncResponse *syncResponse = &endpointState->syncResponse; |
| chppMutexLock(&syncResponse->mutex); |
| syncResponse->ready = true; |
| CHPP_LOGD("Finished dispatching a response -> synchronous notification"); |
| chppConditionVariableSignal(&syncResponse->condVar); |
| chppMutexUnlock(&syncResponse->mutex); |
| } |
| } |
| |
| /************************************************ |
| * 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_DEBUG_NOT_NULL(transportContext); |
| |
| CHPP_LOGD("App Init"); |
| |
| memset(appContext, 0, sizeof(*appContext)); |
| |
| appContext->clientServiceSet = clientServiceSet; |
| appContext->transportContext = transportContext; |
| appContext->nextClientRequestTimeoutNs = CHPP_TIME_MAX; |
| appContext->nextServiceRequestTimeoutNs = CHPP_TIME_MAX; |
| |
| chppPalSystemApiInit(appContext); |
| |
| #ifdef CHPP_SERVICE_ENABLED |
| chppRegisterCommonServices(appContext); |
| #endif |
| |
| #ifdef CHPP_CLIENT_ENABLED |
| chppRegisterCommonClients(appContext); |
| chppInitBasicClients(appContext); |
| #endif |
| } |
| |
| void chppAppDeinit(struct ChppAppState *appContext) { |
| CHPP_LOGD("App deinit"); |
| |
| #ifdef CHPP_CLIENT_ENABLED |
| chppDeinitMatchedClients(appContext); |
| chppDeinitBasicClients(appContext); |
| chppDeregisterCommonClients(appContext); |
| #endif |
| |
| #ifdef CHPP_SERVICE_ENABLED |
| chppDeregisterCommonServices(appContext); |
| #endif |
| |
| chppPalSystemApiDeinit(appContext); |
| } |
| |
| void chppAppProcessRxDatagram(struct ChppAppState *context, uint8_t *buf, |
| size_t len) { |
| CHPP_DEBUG_NOT_NULL(context); |
| CHPP_DEBUG_NOT_NULL(buf); |
| |
| const struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf; |
| |
| if (len == 0) { |
| CHPP_DEBUG_ASSERT_LOG(false, "App rx w/ len 0"); |
| |
| } else if (len < sizeof(struct ChppAppHeader)) { |
| uint8_t *handle = (uint8_t *)buf; |
| CHPP_LOGD("RX datagram len=%" PRIuSIZE " H#%" PRIu8, len, *handle); |
| |
| } else if (rxHeader->error != CHPP_APP_ERROR_NONE) { |
| CHPP_LOGE("RX datagram len=%" PRIuSIZE " H#%" PRIu8 " type=0x%" PRIx8 |
| " ID=%" PRIu8 " ERR=%" PRIu8 " cmd=0x%" PRIx16, |
| len, rxHeader->handle, rxHeader->type, rxHeader->transaction, |
| rxHeader->error, rxHeader->command); |
| } else { |
| CHPP_LOGD("RX datagram len=%" PRIuSIZE " H#%" PRIu8 " type=0x%" PRIx8 |
| " ID=%" PRIu8 " err=%" PRIu8 " cmd=0x%" PRIx16, |
| len, rxHeader->handle, rxHeader->type, rxHeader->transaction, |
| rxHeader->error, rxHeader->command); |
| } |
| |
| if (!chppDatagramLenIsOk(context, rxHeader, len)) { |
| chppEnqueueTxErrorDatagram(context->transportContext, |
| CHPP_TRANSPORT_ERROR_APPLAYER); |
| |
| } else { |
| 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); |
| } |
| } |
| |
| chppDatagramProcessDoneCb(context->transportContext, buf); |
| } |
| |
| void chppAppProcessReset(struct ChppAppState *context) { |
| CHPP_DEBUG_NOT_NULL(context); |
| |
| #ifdef CHPP_CLIENT_ENABLED_DISCOVERY |
| if (!context->isDiscoveryComplete) { |
| chppInitiateDiscovery(context); |
| |
| } else { |
| // Notify matched clients that a reset happened |
| 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 |
| ChppNotifierFunction *ResetNotifierFunction = |
| chppGetClientResetNotifierFunction(context, clientIndex); |
| |
| CHPP_LOGD("Client #%" PRIu8 " (H#%d) reset notifier found=%d", |
| clientIndex, CHPP_SERVICE_HANDLE_OF_INDEX(i), |
| (ResetNotifierFunction != NULL)); |
| |
| if (ResetNotifierFunction != NULL) { |
| ResetNotifierFunction( |
| context->registeredClientStates[clientIndex]->context); |
| } |
| } |
| } |
| } |
| #endif // CHPP_CLIENT_ENABLED_DISCOVERY |
| |
| // Notify registered services that a reset happened |
| for (uint8_t i = 0; i < context->registeredServiceCount; i++) { |
| ChppNotifierFunction *ResetNotifierFunction = |
| chppGetServiceResetNotifierFunction(context, i); |
| |
| CHPP_LOGD("Service #%" PRIu8 " (H#%d) reset notifier found=%d", i, |
| CHPP_SERVICE_HANDLE_OF_INDEX(i), (ResetNotifierFunction != NULL)); |
| |
| if (ResetNotifierFunction != NULL) { |
| ResetNotifierFunction(context->registeredServiceStates[i]->context); |
| } |
| } |
| |
| #ifdef CHPP_CLIENT_ENABLED_TIMESYNC |
| // Reinitialize time offset |
| chppTimesyncClientReset(context); |
| #endif |
| } |
| |
| 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]); |
| } |
| |
| uint8_t chppAppErrorToChreError(uint8_t chppError) { |
| switch (chppError) { |
| case CHPP_APP_ERROR_NONE: |
| case CHPP_APP_ERROR_INVALID_ARG: |
| case CHPP_APP_ERROR_BUSY: |
| case CHPP_APP_ERROR_OOM: |
| case CHPP_APP_ERROR_UNSUPPORTED: |
| case CHPP_APP_ERROR_TIMEOUT: |
| case CHPP_APP_ERROR_DISABLED: |
| case CHPP_APP_ERROR_RATELIMITED: { |
| // CHRE and CHPP error values are identical in these cases |
| return chppError; |
| } |
| default: { |
| return CHRE_ERROR; |
| } |
| } |
| } |
| |
| uint8_t chppAppShortResponseErrorHandler(uint8_t *buf, size_t len, |
| const char *responseName) { |
| CHPP_DEBUG_NOT_NULL(buf); |
| CHPP_DEBUG_NOT_NULL(responseName); |
| |
| CHPP_ASSERT(len >= sizeof(struct ChppAppHeader)); |
| const struct ChppAppHeader *rxHeader = (struct ChppAppHeader *)buf; |
| |
| if (rxHeader->error == CHPP_APP_ERROR_NONE) { |
| CHPP_LOGE("%s resp short len=%" PRIuSIZE, responseName, len); |
| return CHRE_ERROR; |
| } |
| |
| CHPP_LOGD("%s resp short len=%" PRIuSIZE, responseName, len); |
| return chppAppErrorToChreError(rxHeader->error); |
| } |
| |
| struct ChppAppHeader *chppAllocNotification(uint8_t type, size_t len) { |
| CHPP_ASSERT(len >= sizeof(struct ChppAppHeader)); |
| CHPP_ASSERT(type == CHPP_MESSAGE_TYPE_CLIENT_NOTIFICATION || |
| type == CHPP_MESSAGE_TYPE_SERVICE_NOTIFICATION); |
| |
| struct ChppAppHeader *notification = chppMalloc(len); |
| if (notification != NULL) { |
| notification->type = type; |
| notification->handle = CHPP_HANDLE_NONE; |
| notification->transaction = 0; |
| notification->error = CHPP_APP_ERROR_NONE; |
| notification->command = CHPP_APP_COMMAND_NONE; |
| } else { |
| CHPP_LOG_OOM(); |
| } |
| return notification; |
| } |
| |
| struct ChppAppHeader *chppAllocRequest(uint8_t type, |
| struct ChppEndpointState *endpointState, |
| size_t len) { |
| CHPP_ASSERT(len >= sizeof(struct ChppAppHeader)); |
| CHPP_ASSERT(type == CHPP_MESSAGE_TYPE_CLIENT_REQUEST || |
| type == CHPP_MESSAGE_TYPE_SERVICE_REQUEST); |
| CHPP_DEBUG_NOT_NULL(endpointState); |
| |
| struct ChppAppHeader *request = chppMalloc(len); |
| if (request != NULL) { |
| request->handle = endpointState->handle; |
| request->type = type; |
| request->transaction = endpointState->transaction; |
| request->error = CHPP_APP_ERROR_NONE; |
| request->command = CHPP_APP_COMMAND_NONE; |
| |
| endpointState->transaction++; |
| } else { |
| CHPP_LOG_OOM(); |
| } |
| return request; |
| } |
| |
| struct ChppAppHeader *chppAllocResponse( |
| const struct ChppAppHeader *requestHeader, size_t len) { |
| CHPP_ASSERT(len >= sizeof(struct ChppAppHeader)); |
| CHPP_DEBUG_NOT_NULL(requestHeader); |
| uint8_t type = requestHeader->type; |
| CHPP_ASSERT(type == CHPP_MESSAGE_TYPE_CLIENT_REQUEST || |
| type == CHPP_MESSAGE_TYPE_SERVICE_REQUEST); |
| |
| struct ChppAppHeader *response = chppMalloc(len); |
| if (response != NULL) { |
| *response = *requestHeader; |
| response->type = type == CHPP_MESSAGE_TYPE_CLIENT_REQUEST |
| ? CHPP_MESSAGE_TYPE_SERVICE_RESPONSE |
| : CHPP_MESSAGE_TYPE_CLIENT_RESPONSE; |
| response->error = CHPP_APP_ERROR_NONE; |
| } else { |
| CHPP_LOG_OOM(); |
| } |
| return response; |
| } |
| |
| void chppTimestampIncomingRequest(struct ChppIncomingRequestState *inReqState, |
| const struct ChppAppHeader *requestHeader) { |
| CHPP_DEBUG_NOT_NULL(inReqState); |
| CHPP_DEBUG_NOT_NULL(requestHeader); |
| if (inReqState->responseTimeNs == CHPP_TIME_NONE && |
| inReqState->requestTimeNs != CHPP_TIME_NONE) { |
| CHPP_LOGE("RX dupe req t=%" PRIu64, |
| inReqState->requestTimeNs / CHPP_NSEC_PER_MSEC); |
| } |
| inReqState->requestTimeNs = chppGetCurrentTimeNs(); |
| inReqState->responseTimeNs = CHPP_TIME_NONE; |
| inReqState->transaction = requestHeader->transaction; |
| } |
| |
| void chppTimestampOutgoingRequest(struct ChppAppState *appState, |
| struct ChppOutgoingRequestState *outReqState, |
| const struct ChppAppHeader *requestHeader, |
| uint64_t timeoutNs) { |
| CHPP_DEBUG_NOT_NULL(appState); |
| CHPP_DEBUG_NOT_NULL(outReqState); |
| CHPP_DEBUG_NOT_NULL(requestHeader); |
| enum ChppMessageType msgType = requestHeader->type; |
| enum ChppEndpointType endpointType = |
| msgType == CHPP_MESSAGE_TYPE_CLIENT_REQUEST ? CHPP_ENDPOINT_CLIENT |
| : CHPP_ENDPOINT_SERVICE; |
| |
| CHPP_ASSERT(msgType == CHPP_MESSAGE_TYPE_CLIENT_REQUEST || |
| msgType == CHPP_MESSAGE_TYPE_SERVICE_REQUEST); |
| |
| if (outReqState->requestState == CHPP_REQUEST_STATE_REQUEST_SENT) { |
| CHPP_LOGE("Dupe req ID=%" PRIu8 " existing ID=%" PRIu8 " from t=%" PRIu64, |
| requestHeader->transaction, outReqState->transaction, |
| outReqState->requestTimeNs / CHPP_NSEC_PER_MSEC); |
| |
| // Clear a possible pending timeout from the previous request |
| outReqState->responseTimeNs = CHPP_TIME_MAX; |
| chppRecalculateNextTimeout(appState, endpointType); |
| } |
| |
| outReqState->requestTimeNs = chppGetCurrentTimeNs(); |
| outReqState->requestState = CHPP_REQUEST_STATE_REQUEST_SENT; |
| outReqState->transaction = requestHeader->transaction; |
| |
| uint64_t *nextRequestTimeoutNs = |
| getNextRequestTimeoutNs(appState, endpointType); |
| |
| if (timeoutNs == CHPP_REQUEST_TIMEOUT_INFINITE) { |
| outReqState->responseTimeNs = CHPP_TIME_MAX; |
| |
| } else { |
| outReqState->responseTimeNs = timeoutNs + outReqState->requestTimeNs; |
| |
| *nextRequestTimeoutNs = |
| MIN(*nextRequestTimeoutNs, outReqState->responseTimeNs); |
| } |
| |
| CHPP_LOGD("Timestamp req ID=%" PRIu8 " at t=%" PRIu64 " timeout=%" PRIu64 |
| " (requested=%" PRIu64 "), next timeout=%" PRIu64, |
| outReqState->transaction, |
| outReqState->requestTimeNs / CHPP_NSEC_PER_MSEC, |
| outReqState->responseTimeNs / CHPP_NSEC_PER_MSEC, |
| timeoutNs / CHPP_NSEC_PER_MSEC, |
| *nextRequestTimeoutNs / CHPP_NSEC_PER_MSEC); |
| } |
| |
| bool chppTimestampIncomingResponse(struct ChppAppState *appState, |
| struct ChppOutgoingRequestState *outReqState, |
| const struct ChppAppHeader *responseHeader) { |
| CHPP_DEBUG_NOT_NULL(appState); |
| CHPP_DEBUG_NOT_NULL(outReqState); |
| CHPP_DEBUG_NOT_NULL(responseHeader); |
| |
| uint8_t type = responseHeader->type; |
| |
| CHPP_ASSERT(type == CHPP_MESSAGE_TYPE_CLIENT_RESPONSE || |
| type == CHPP_MESSAGE_TYPE_SERVICE_RESPONSE); |
| |
| bool success = false; |
| uint64_t responseTime = chppGetCurrentTimeNs(); |
| |
| switch (outReqState->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, |
| outReqState->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, |
| outReqState->requestTimeNs / CHPP_NSEC_PER_MSEC); |
| break; |
| } |
| |
| case CHPP_REQUEST_STATE_REQUEST_SENT: { |
| if (responseHeader->transaction != outReqState->transaction) { |
| CHPP_LOGE("Invalid resp ID=%" PRIu8 " at t=%" PRIu64 |
| " expected=%" PRIu8, |
| responseHeader->transaction, |
| responseTime / CHPP_NSEC_PER_MSEC, outReqState->transaction); |
| } else { |
| outReqState->requestState = (responseTime > outReqState->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)", |
| outReqState->transaction, |
| outReqState->requestTimeNs / CHPP_NSEC_PER_MSEC, |
| responseTime / CHPP_NSEC_PER_MSEC, |
| outReqState->responseTimeNs / CHPP_NSEC_PER_MSEC, |
| (responseTime - outReqState->requestTimeNs) / CHPP_NSEC_PER_MSEC, |
| (responseTime > outReqState->responseTimeNs) ? "yes" : "no"); |
| } |
| break; |
| } |
| |
| default: { |
| CHPP_DEBUG_ASSERT_LOG(false, "Invalid req state"); |
| } |
| } |
| |
| if (success) { |
| // When the received request is the next one that was expected |
| // to timeout we need to recompute the timeout considering the |
| // other pending requests. |
| enum ChppEndpointType endpointType = |
| type == CHPP_MESSAGE_TYPE_SERVICE_RESPONSE ? CHPP_ENDPOINT_CLIENT |
| : CHPP_ENDPOINT_SERVICE; |
| if (outReqState->responseTimeNs == |
| *getNextRequestTimeoutNs(appState, endpointType)) { |
| chppRecalculateNextTimeout(appState, endpointType); |
| } |
| outReqState->responseTimeNs = responseTime; |
| } |
| return success; |
| } |
| |
| uint64_t chppTimestampOutgoingResponse( |
| struct ChppIncomingRequestState *inReqState) { |
| CHPP_DEBUG_NOT_NULL(inReqState); |
| |
| uint64_t previousResponseTime = inReqState->responseTimeNs; |
| inReqState->responseTimeNs = chppGetCurrentTimeNs(); |
| return previousResponseTime; |
| } |
| |
| bool chppSendTimestampedResponseOrFail( |
| struct ChppAppState *appState, struct ChppIncomingRequestState *inReqState, |
| void *buf, size_t len) { |
| CHPP_DEBUG_NOT_NULL(appState); |
| CHPP_DEBUG_NOT_NULL(inReqState); |
| CHPP_DEBUG_NOT_NULL(buf); |
| uint64_t previousResponseTime = chppTimestampOutgoingResponse(inReqState); |
| |
| if (inReqState->requestTimeNs == CHPP_TIME_NONE) { |
| CHPP_LOGE("TX response w/ no req t=%" PRIu64, |
| inReqState->responseTimeNs / CHPP_NSEC_PER_MSEC); |
| |
| } else if (previousResponseTime != CHPP_TIME_NONE) { |
| CHPP_LOGW("TX additional response t=%" PRIu64 " for req t=%" PRIu64, |
| inReqState->responseTimeNs / CHPP_NSEC_PER_MSEC, |
| inReqState->requestTimeNs / CHPP_NSEC_PER_MSEC); |
| |
| } else { |
| CHPP_LOGD("Sending initial response at t=%" PRIu64 |
| " for request at t=%" PRIu64 " (RTT=%" PRIu64 ")", |
| inReqState->responseTimeNs / CHPP_NSEC_PER_MSEC, |
| inReqState->requestTimeNs / CHPP_NSEC_PER_MSEC, |
| (inReqState->responseTimeNs - inReqState->requestTimeNs) / |
| CHPP_NSEC_PER_MSEC); |
| } |
| |
| return chppEnqueueTxDatagramOrFail(appState->transportContext, buf, len); |
| } |
| |
| bool chppSendTimestampedRequestOrFail( |
| struct ChppEndpointState *endpointState, |
| struct ChppOutgoingRequestState *outReqState, void *buf, size_t len, |
| uint64_t timeoutNs) { |
| CHPP_DEBUG_NOT_NULL(outReqState); |
| CHPP_DEBUG_NOT_NULL(buf); |
| CHPP_ASSERT(len >= sizeof(struct ChppAppHeader)); |
| |
| if (timeoutNs < CHPP_TRANSPORT_TX_TIMEOUT_NS) { |
| // The app layer sits above the transport layer. |
| // Request timeout (app layer) should be longer than the transport timeout. |
| CHPP_LOGW("Request timeout (%" PRIu64 |
| "ns) should be longer than the transport timeout (%" PRIu64 "ns)", |
| timeoutNs, (uint64_t)CHPP_TRANSPORT_TX_TIMEOUT_NS); |
| } |
| |
| chppTimestampOutgoingRequest(endpointState->appContext, outReqState, buf, |
| timeoutNs); |
| endpointState->syncResponse.ready = false; |
| |
| bool success = chppEnqueueTxDatagramOrFail( |
| endpointState->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) { |
| outReqState->requestState = CHPP_REQUEST_STATE_NONE; |
| } |
| |
| return success; |
| } |
| |
| bool chppWaitForResponseWithTimeout( |
| struct ChppSyncResponse *syncResponse, |
| struct ChppOutgoingRequestState *outReqState, uint64_t timeoutNs) { |
| CHPP_DEBUG_NOT_NULL(syncResponse); |
| CHPP_DEBUG_NOT_NULL(outReqState); |
| |
| bool result = true; |
| |
| chppMutexLock(&syncResponse->mutex); |
| |
| while (result && !syncResponse->ready) { |
| result = chppConditionVariableTimedWait(&syncResponse->condVar, |
| &syncResponse->mutex, timeoutNs); |
| } |
| if (!syncResponse->ready) { |
| outReqState->requestState = CHPP_REQUEST_STATE_RESPONSE_TIMEOUT; |
| CHPP_LOGE("Response timeout after %" PRIu64 " ms", |
| timeoutNs / CHPP_NSEC_PER_MSEC); |
| result = false; |
| } |
| |
| chppMutexUnlock(&syncResponse->mutex); |
| |
| return result; |
| } |
| |
| struct ChppEndpointState *getRegisteredEndpointState( |
| struct ChppAppState *appState, uint8_t index, enum ChppEndpointType type) { |
| CHPP_DEBUG_NOT_NULL(appState); |
| CHPP_DEBUG_ASSERT(index < getRegisteredEndpointCount(appState, type)); |
| |
| return type == CHPP_ENDPOINT_CLIENT |
| ? appState->registeredClientStates[index] |
| : appState->registeredServiceStates[index]; |
| } |
| |
| uint16_t getRegisteredEndpointOutReqCount(struct ChppAppState *appState, |
| uint8_t index, |
| enum ChppEndpointType type) { |
| CHPP_DEBUG_NOT_NULL(appState); |
| CHPP_DEBUG_ASSERT(index < getRegisteredEndpointCount(appState, type)); |
| |
| return type == CHPP_ENDPOINT_CLIENT |
| ? appState->registeredClients[index]->outReqCount |
| : appState->registeredServices[index]->outReqCount; |
| } |
| |
| uint8_t getRegisteredEndpointCount(struct ChppAppState *appState, |
| enum ChppEndpointType type) { |
| return type == CHPP_ENDPOINT_CLIENT ? appState->registeredClientCount |
| : appState->registeredServiceCount; |
| } |
| |
| void chppRecalculateNextTimeout(struct ChppAppState *appState, |
| enum ChppEndpointType type) { |
| CHPP_DEBUG_NOT_NULL(appState); |
| |
| uint64_t timeoutNs = CHPP_TIME_MAX; |
| |
| const uint8_t endpointCount = getRegisteredEndpointCount(appState, type); |
| |
| for (uint8_t endpointIdx = 0; endpointIdx < endpointCount; endpointIdx++) { |
| uint16_t reqCount = |
| getRegisteredEndpointOutReqCount(appState, endpointIdx, type); |
| struct ChppEndpointState *endpointState = |
| getRegisteredEndpointState(appState, endpointIdx, type); |
| struct ChppOutgoingRequestState *reqStates = endpointState->outReqStates; |
| for (uint16_t cmdIdx = 0; cmdIdx < reqCount; cmdIdx++) { |
| struct ChppOutgoingRequestState *reqState = &reqStates[cmdIdx]; |
| |
| if (reqState->requestState == CHPP_REQUEST_STATE_REQUEST_SENT) { |
| timeoutNs = MIN(timeoutNs, reqState->responseTimeNs); |
| } |
| } |
| } |
| |
| CHPP_LOGD("nextReqTimeout=%" PRIu64, timeoutNs / CHPP_NSEC_PER_MSEC); |
| |
| if (type == CHPP_ENDPOINT_CLIENT) { |
| appState->nextClientRequestTimeoutNs = timeoutNs; |
| } else { |
| appState->nextServiceRequestTimeoutNs = timeoutNs; |
| } |
| } |
| |
| uint64_t *getNextRequestTimeoutNs(struct ChppAppState *appState, |
| enum ChppEndpointType type) { |
| return type == CHPP_ENDPOINT_CLIENT ? &appState->nextClientRequestTimeoutNs |
| : &appState->nextServiceRequestTimeoutNs; |
| } |
| |
| void chppCloseOpenRequests(struct ChppEndpointState *endpointState, |
| enum ChppEndpointType type, bool clearOnly) { |
| CHPP_DEBUG_NOT_NULL(endpointState); |
| |
| bool recalcNeeded = false; |
| |
| struct ChppAppState *appState = endpointState->appContext; |
| const uint8_t enpointIdx = endpointState->index; |
| const uint16_t cmdCount = |
| getRegisteredEndpointOutReqCount(appState, enpointIdx, type); |
| |
| for (uint16_t cmdIdx = 0; cmdIdx < cmdCount; cmdIdx++) { |
| if (endpointState->outReqStates[cmdIdx].requestState == |
| CHPP_REQUEST_STATE_REQUEST_SENT) { |
| recalcNeeded = true; |
| |
| CHPP_LOGE("Closing open req #%" PRIu16 " clear %d", cmdIdx, clearOnly); |
| |
| if (clearOnly) { |
| endpointState->outReqStates[cmdIdx].requestState = |
| CHPP_REQUEST_STATE_RESPONSE_TIMEOUT; |
| } else { |
| struct ChppAppHeader *response = |
| chppMalloc(sizeof(struct ChppAppHeader)); |
| if (response == NULL) { |
| CHPP_LOG_OOM(); |
| } else { |
| // Simulate receiving a timeout response. |
| response->handle = endpointState->handle; |
| response->type = type == CHPP_ENDPOINT_CLIENT |
| ? CHPP_MESSAGE_TYPE_SERVICE_RESPONSE |
| : CHPP_MESSAGE_TYPE_CLIENT_RESPONSE; |
| response->transaction = |
| endpointState->outReqStates[cmdIdx].transaction; |
| response->error = CHPP_APP_ERROR_TIMEOUT; |
| response->command = cmdIdx; |
| |
| chppAppProcessRxDatagram(appState, (uint8_t *)response, |
| sizeof(struct ChppAppHeader)); |
| } |
| } |
| } |
| } |
| if (recalcNeeded) { |
| chppRecalculateNextTimeout(appState, type); |
| } |
| } |