| /* | 
 |  * 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" | 
 | #ifdef CHPP_CLIENT_ENABLED_VENDOR | 
 | #include "chpp/platform/vendor_clients.h" | 
 | #endif | 
 | #ifdef CHPP_SERVICE_ENABLED_VENDOR | 
 | #include "chpp/platform/vendor_services.h" | 
 | #endif | 
 | #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); | 
 | #ifdef CHPP_SERVICE_ENABLED_VENDOR | 
 |   chppRegisterVendorServices(appContext); | 
 | #endif | 
 | #endif | 
 |  | 
 | #ifdef CHPP_CLIENT_ENABLED | 
 |   chppRegisterCommonClients(appContext); | 
 | #ifdef CHPP_CLIENT_ENABLED_VENDOR | 
 |   chppRegisterVendorClients(appContext); | 
 | #endif | 
 |   chppInitBasicClients(appContext); | 
 | #endif | 
 | } | 
 |  | 
 | void chppAppDeinit(struct ChppAppState *appContext) { | 
 |   CHPP_LOGD("App deinit"); | 
 |  | 
 | #ifdef CHPP_CLIENT_ENABLED | 
 |   chppDeinitMatchedClients(appContext); | 
 |   chppDeinitBasicClients(appContext); | 
 |   chppDeregisterCommonClients(appContext); | 
 | #ifdef CHPP_CLIENT_ENABLED_VENDOR | 
 |   chppDeregisterVendorClients(appContext); | 
 | #endif | 
 | #endif | 
 |  | 
 | #ifdef CHPP_SERVICE_ENABLED | 
 |   chppDeregisterCommonServices(appContext); | 
 | #ifdef CHPP_SERVICE_ENABLED_VENDOR | 
 |   chppDeregisterVendorServices(appContext); | 
 | #endif | 
 | #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); | 
 |  | 
 |   // Hold the mutex to avoid concurrent read of a partially modified outReqState | 
 |   // structure by the RX thread | 
 |   chppMutexLock(&appState->transportContext->mutex); | 
 |  | 
 |   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); | 
 |   } | 
 |  | 
 |   chppMutexUnlock(&appState->transportContext->mutex); | 
 |  | 
 |   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); | 
 |   } | 
 | } |