diff --git a/include/ut_control_plane.h b/include/ut_control_plane.h index 00bcead..07c4120 100644 --- a/include/ut_control_plane.h +++ b/include/ut_control_plane.h @@ -50,8 +50,33 @@ typedef struct typedef void ut_controlPlane_instance_t; /*!< Handle to a control plane instance */ -/** @brief Callback function type for handling control plane messages. */ -typedef void (*ut_control_callback_t)( char *key, ut_kvp_instance_t *instance, void *userData ); +/** + * @typedef ut_control_callback_t + * @brief Synchronous control-plane message callback. + * + * This callback is invoked when a registered message key is received. + * The call is synchronous; return promptly. + * + * ## Lifetime & ownership + * - `instance` is a framework-owned handle valid **only for the duration of + * this callback**. Do not store it or any pointers into its internals. + * - To retain payload beyond the callback, extract a serialized copy with + * `ut_kvp_getData()`, copy that data into your own storage/IPC buffer, and + * then `free()` the buffer returned by `ut_kvp_getData()` **before returning**. + * - Parsing (e.g., `ut_kvp_openMemory()`) should be done later, outside the + * callback, using the copy you made. + * + * ## Typical workflow + * - In-callback: fast decisions via typed getters; or stage the message by + * copying the serialized blob to a queue/shared memory for deferred work. + * - Out-of-callback: reconstruct a KVP instance from the staged bytes with + * `ut_kvp_openMemory()`. + * + * @param key Null-terminated message key that triggered the callback. + * @param instance Transient KVP handle containing message data. + * @param userData User pointer provided at registration. + */ +typedef void (*ut_control_callback_t)(char *key, ut_kvp_instance_t *instance, void *userData); /** * @brief Initializes a control plane instance. @@ -61,17 +86,32 @@ typedef void (*ut_control_callback_t)( char *key, ut_kvp_instance_t *instance, v ut_controlPlane_instance_t* UT_ControlPlane_Init( uint32_t monitorPort ); /** - * @brief Registers a callback function for a specific message key. - * @param pInstance - Handle to the control plane instance. - * @param key - Null-terminated string representing the message key to trigger the callback. - * @param callbackFunction - Callback function to be invoked when the key is received. - * @param userData - Handle to the caller instance. - * @returns Status of the registration operation (`ut_control_plane_status_t`). - * @retval UT_CONTROL_PLANE_STATUS_OK - Success - * @retval UT_CONTROL_PLANE_STATUS_INVALID_HANDLE - Invalid control plane instance handle. - * @retval UT_CONTROL_PLANE_STATUS_INVALID_PARAM - Invalid parameter passed - * @retval UT_CONTROL_PLANE_STATUS_CALLBACK_LIST_FULL - Callback list is full - */ +* @brief Registers a synchronous callback function for a specific message key. +* +* The callback is invoked immediately when the specified key is received. +* +* ## Data lifetime of callback parameters +* - The `instance` parameter provided to the callback is framework-owned and +* valid only for the duration of the callback execution. +* - Do not attempt to free or retain `instance` directly. It is released +* automatically after the callback returns. +* - If the application needs to retain message contents beyond the callback, +* it must make a copy. The common pattern is: +* - Use `ut_kvp_getData(instance)` to obtain a serialized payload (caller-owned). +* - Copy that payload into application-owned memory (e.g. queue, IPC buffer). +* - Free the buffer returned by `ut_kvp_getData()` before returning. +* - Later, reconstruct a KVP instance with `ut_kvp_openMemory()`. +* +* @param pInstance - Handle to the control plane instance. +* @param key - Null-terminated string representing the message key to trigger the callback. +* @param callbackFunction - Callback function to be invoked synchronously when the key is received. +* @param userData - User-provided handle passed back to the callback. +* @returns Status of the registration operation (`ut_control_plane_status_t`). +* @retval UT_CONTROL_PLANE_STATUS_OK - Success. +* @retval UT_CONTROL_PLANE_STATUS_INVALID_HANDLE - Invalid control plane instance handle. +* @retval UT_CONTROL_PLANE_STATUS_INVALID_PARAM - Invalid parameter passed. +* @retval UT_CONTROL_PLANE_STATUS_CALLBACK_LIST_FULL - Callback list is full. +*/ ut_control_plane_status_t UT_ControlPlane_RegisterCallbackOnMessage(ut_controlPlane_instance_t *pInstance, char *key, ut_control_callback_t callbackFunction, diff --git a/include/ut_kvp.h b/include/ut_kvp.h index 3924368..f2fa326 100644 --- a/include/ut_kvp.h +++ b/include/ut_kvp.h @@ -82,21 +82,21 @@ void ut_kvp_destroyInstance(ut_kvp_instance_t *pInstance); */ ut_kvp_status_t ut_kvp_open(ut_kvp_instance_t *pInstance, char *fileName); -/**! - * @brief Opens and parses a memory block read from a Key-Value Pair (KVP) file into a KVP instance. +/** + * @brief Parses a KVP payload from a caller-owned memory block into an instance. * - * This function opens the specified memory block, reads its contents, and parses the key-value - * pairs into the given KVP instance.The memory passed gets freed as part of destroy instance + * `pData` must point to a buffer owned by the caller (e.g., allocated with malloc). + * This function does not take ownership of `pData`. The caller is always + * responsible for freeing `pData`, regardless of success or failure. * - * @param[in] pInstance - Handle to the KVP instance where the parsed data will be stored. - * @param[in] pData - points to malloc'd memory containing KVP Data. - * @param[in] length - size of the KVP data + * @param[in] pInstance Destination KVP instance (created with `ut_kvp_createInstance()`). + * @param[in] pData Caller-owned buffer containing the serialized KVP payload (text). + * @param[in] length Size of `pData` in bytes (use `strlen(pData)+1` for text). * - * @returns Status of the operation (`ut_kvp_status_t`): - * @retval UT_KVP_STATUS_SUCCESS - The file was opened and parsed successfully. - * @retval UT_KVP_STATUS_INVALID_PARAM - One or more parameters are invalid (e.g., null pointer). - * @retval UT_KVP_STATUS_PARSING_ERROR - An error occurred while parsing the file contents. - * @retval UT_KVP_STATUS_INVALID_INSTANCE - The provided `pInstance` is not a valid KVP instance. + * @retval UT_KVP_STATUS_SUCCESS Parsed successfully. + * @retval UT_KVP_STATUS_INVALID_PARAM Invalid pointer/length. + * @retval UT_KVP_STATUS_PARSING_ERROR Malformed payload. + * @retval UT_KVP_STATUS_INVALID_INSTANCE Invalid destination instance. */ ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uint32_t length); @@ -212,13 +212,19 @@ double ut_kvp_getDoubleField( ut_kvp_instance_t *pInstance, const char *pszKey); bool ut_kvp_fieldPresent( ut_kvp_instance_t *pInstance, const char *pszKey); /** - * @brief Get the data block from the instance, user to free the instance + * @brief Returns a heap-allocated textual serialization of a KVP instance. + * + * The buffer is a NUL-terminated string representing the complete payload + * of `pInstance`. The caller owns the buffer and must `free()` it when done. + * + * If used in callbacks: obtain the blob, copy it into your own + * queue/IPC storage, then `free()` the original blob before returning. * - * Where the data is invalid, no output will occur - * Also caller needs to ensure, that they - * free the pointer to the data block + * The returned data can later be parsed with `ut_kvp_openMemory()`. + * Returns NULL on error or if no data is available. * - * @param pInstance - pointer to the KVP instance + * @param[in] pInstance KVP instance handle to serialize. + * @return char* Malloc'd NUL-terminated string, or NULL on error. */ char* ut_kvp_getData( ut_kvp_instance_t *pInstance ); diff --git a/src/ut_control_plane.c b/src/ut_control_plane.c index 20da7d1..343ecfc 100644 --- a/src/ut_control_plane.c +++ b/src/ut_control_plane.c @@ -387,21 +387,21 @@ static struct lws_protocols protocols[] = { ut_controlPlane_instance_t *UT_ControlPlane_Init( uint32_t monitorPort ) { ut_cp_instance_internal_t *pInstance; - - pInstance = malloc(sizeof(ut_cp_instance_internal_t)); - memset(pInstance, 0, sizeof(ut_cp_instance_internal_t)); - if ( pInstance == NULL ) + if ( monitorPort == 0 ) { //assert( pInstance != NULL ); - UT_CONTROL_PLANE_ERROR("Malloc was not able to provide memory\n"); + UT_CONTROL_PLANE_ERROR("port cannot be 0\n"); return NULL; } - if ( monitorPort == 0 ) + pInstance = malloc(sizeof(ut_cp_instance_internal_t)); + memset(pInstance, 0, sizeof(ut_cp_instance_internal_t)); + + if ( pInstance == NULL ) { //assert( pInstance != NULL ); - UT_CONTROL_PLANE_ERROR("port cannot be 0\n"); + UT_CONTROL_PLANE_ERROR("Malloc was not able to provide memory\n"); return NULL; } diff --git a/src/ut_kvp.c b/src/ut_kvp.c index 1e57cef..0ad72fc 100644 --- a/src/ut_kvp.c +++ b/src/ut_kvp.c @@ -98,13 +98,12 @@ void ut_kvp_destroyInstance(ut_kvp_instance_t *pInstance) ut_kvp_status_t ut_kvp_open(ut_kvp_instance_t *pInstance, char *fileName) { struct fy_node *node; - ut_kvp_instance_internal_t *pInternal = validateInstance(pInstance); - if (pInstance == NULL) { return UT_KVP_STATUS_INVALID_INSTANCE; } + ut_kvp_instance_internal_t *pInternal = validateInstance(pInstance); if (fileName == NULL) { UT_LOG_ERROR( "Invalid Param [fileName]" ); @@ -161,6 +160,7 @@ ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uin { struct fy_node *node; ut_kvp_instance_internal_t *pInternal = validateInstance(pInstance); + char *cData = NULL; if (pInstance == NULL) { @@ -173,9 +173,11 @@ ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uin return UT_KVP_STATUS_INVALID_PARAM; } + cData = strdup((const char*)pData); + if (pInternal->fy_handle) { - merge_nodes(fy_document_root(pInternal->fy_handle), fy_document_root(fy_document_build_from_malloc_string(NULL, pData, length))); + merge_nodes(fy_document_root(pInternal->fy_handle), fy_document_root(fy_document_build_from_malloc_string(NULL, cData, length))); } else { @@ -189,7 +191,7 @@ ut_kvp_status_t ut_kvp_openMemory(ut_kvp_instance_t *pInstance, char *pData, uin return UT_KVP_STATUS_PARSING_ERROR; } - struct fy_document *srcDoc = fy_document_build_from_malloc_string(NULL, pData, length); + struct fy_document *srcDoc = fy_document_build_from_malloc_string(NULL, cData, length); if(fy_document_resolve(srcDoc) != 0) { @@ -1024,6 +1026,7 @@ static void merge_nodes(struct fy_node *mainNode, struct fy_node *includeNode) if (fy_node_is_scalar(mainNode)) { fy_node_create_scalar_copy(fy_node_document(mainNode), fy_node_get_scalar(includeNode, NULL), fy_node_get_scalar_length(includeNode)); + free((void *)fy_node_get_scalar(includeNode, NULL)); } else if (fy_node_is_mapping(mainNode) && fy_node_is_mapping(includeNode)) { diff --git a/tests/Makefile b/tests/Makefile index bfcba10..5d415ff 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -40,6 +40,9 @@ BUILD_DIR = $(ROOT_DIR)/build/$(TARGET)/obj ifeq ($(TARGET),) $(info TARGET FORCED TO Linux) TARGET=linux +CFLAGS += -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=leak +CXXFLAGS += $CFLAGS +YLDFLAGS += -fsanitize=address -fsanitize=leak endif $(info TARGET [$(TARGET)]) diff --git a/tests/src/ut_test_kvp.c b/tests/src/ut_test_kvp.c index 1b8da12..7c90d69 100644 --- a/tests/src/ut_test_kvp.c +++ b/tests/src/ut_test_kvp.c @@ -174,6 +174,7 @@ void test_ut_kvp_open_memory( void ) { status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, -1); UT_ASSERT(status == UT_KVP_STATUS_PARSING_ERROR); + free(gKVPData.buffer); } /* Zero length file, that the library should reject because it can't parse it at all */ @@ -182,6 +183,8 @@ void test_ut_kvp_open_memory( void ) { status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, gKVPData.length); UT_ASSERT(status == UT_KVP_STATUS_PARSING_ERROR); + free(gKVPData.buffer); + gKVPData.length = 0; } /* Positive Tests */ @@ -190,6 +193,8 @@ void test_ut_kvp_open_memory( void ) { status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, gKVPData.length); UT_ASSERT(status == UT_KVP_STATUS_SUCCESS); + free(gKVPData.buffer); + gKVPData.length = 0; } UT_LOG_STEP("ut_kvp_openMemory( gpMainTestInstance, %s ) - Postive", KVP_VALID_TEST_NOT_VALID_YAML_FORMATTED_FILE); @@ -197,6 +202,8 @@ void test_ut_kvp_open_memory( void ) { status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, gKVPData.length); UT_ASSERT(status == UT_KVP_STATUS_SUCCESS); + free(gKVPData.buffer); + gKVPData.length = 0; } UT_LOG_STEP("ut_kvp_openMemory( gpMainTestInstance, %s ) - Postive", KVP_VALID_TEST_JSON_FILE); @@ -204,6 +211,8 @@ void test_ut_kvp_open_memory( void ) { status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, gKVPData.length); UT_ASSERT(status == UT_KVP_STATUS_SUCCESS); + free(gKVPData.buffer); + gKVPData.length = 0; } } @@ -415,7 +424,6 @@ void test_ut_kvp_dataByte( void ) { int bytes_count = 0; unsigned char *output_bytes; - test_ut_memory_t kvpdata; /* Check for NULL_PARAM */ UT_LOG_STEP("ut_kvp_getDataBytes() - Check for NULL_PARAM - First Argument"); @@ -440,10 +448,6 @@ void test_ut_kvp_dataByte( void ) UT_ASSERT(bytes_count == 0); /* Positive tests */ - if (read_file_into_memory(KVP_VALID_TEST_YAML_FILE, &kvpdata) == 0) - { - printf("\nYAML file is = \n%s\n", kvpdata.buffer); - } UT_LOG_STEP("ut_kvp_getDataBytes() - checkBytesSpace for valid output_bytes and bytes_count"); output_bytes = ut_kvp_getDataBytes(gpMainTestInstance, "decodeTest/checkBytesSpace", &bytes_count); UT_ASSERT(output_bytes != NULL); @@ -845,6 +849,8 @@ void test_ut_kvp_add_multiple_profile_using_open_memory(void) { status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, gKVPData.length); UT_ASSERT(status == UT_KVP_STATUS_SUCCESS); + free(gKVPData.buffer); + gKVPData.length = 0; } UT_LOG_STEP("ut_kvp_openMemory( gpMainTestInstance, %s ) - Postive", KVP_VALID_TEST_SINGLE_INCLUDE_FILE_YAML); @@ -852,6 +858,8 @@ void test_ut_kvp_add_multiple_profile_using_open_memory(void) { status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, gKVPData.length); UT_ASSERT(status == UT_KVP_STATUS_SUCCESS); + free(gKVPData.buffer); + gKVPData.length = 0; } UT_LOG_STEP("ut_kvp_openMemory( gpMainTestInstance, %s ) - Postive", KVP_VALID_TEST_DEPTH_CHECK_INCLUDE_YAML); @@ -859,6 +867,8 @@ void test_ut_kvp_add_multiple_profile_using_open_memory(void) { status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, gKVPData.length); UT_ASSERT(status == UT_KVP_STATUS_SUCCESS); + free(gKVPData.buffer); + gKVPData.length = 0; } char* kvpData = ut_kvp_getData(gpMainTestInstance); @@ -963,7 +973,7 @@ static void create_delete_kvp_memory_instance_for_given_file(const char* filenam { test_ut_memory_t kvpMemory; ut_kvp_instance_t *pInstance = NULL; - ut_kvp_status_t status; + ut_kvp_status_t status = UT_KVP_STATUS_MAX; char* kvpData; pInstance = ut_kvp_createInstance(); @@ -978,6 +988,8 @@ static void create_delete_kvp_memory_instance_for_given_file(const char* filenam { status = ut_kvp_openMemory(pInstance, kvpMemory.buffer, kvpMemory.length); UT_ASSERT(status == UT_KVP_STATUS_SUCCESS); + free(kvpMemory.buffer); + kvpMemory.length = 0; } if ( status != UT_KVP_STATUS_SUCCESS ) @@ -1105,7 +1117,7 @@ static int test_ut_kvp_createGlobalJSONInstance( void ) static int test_ut_kvp_createGlobalYAMLInstanceForMallocedData( void ) { - ut_kvp_status_t status; + ut_kvp_status_t status = UT_KVP_STATUS_MAX; test_ut_memory_t kvpMemory; gpMainTestInstance = ut_kvp_createInstance(); @@ -1120,6 +1132,8 @@ static int test_ut_kvp_createGlobalYAMLInstanceForMallocedData( void ) { status = ut_kvp_openMemory(gpMainTestInstance, kvpMemory.buffer, kvpMemory.length); assert(status == UT_KVP_STATUS_SUCCESS); + free(kvpMemory.buffer); + kvpMemory.length = 0; } if ( status != UT_KVP_STATUS_SUCCESS ) @@ -1133,7 +1147,7 @@ static int test_ut_kvp_createGlobalYAMLInstanceForMallocedData( void ) static int test_ut_kvp_createGlobalJSONInstanceForMallocedData( void ) { - ut_kvp_status_t status; + ut_kvp_status_t status = UT_KVP_STATUS_MAX; test_ut_memory_t kvpMemory; gpMainTestInstance = ut_kvp_createInstance(); @@ -1148,6 +1162,8 @@ static int test_ut_kvp_createGlobalJSONInstanceForMallocedData( void ) { status = ut_kvp_openMemory(gpMainTestInstance, kvpMemory.buffer, kvpMemory.length); assert(status == UT_KVP_STATUS_SUCCESS); + free(kvpMemory.buffer); + kvpMemory.length = 0; } if ( status != UT_KVP_STATUS_SUCCESS )