Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 53 additions & 13 deletions include/ut_control_plane.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand Down
40 changes: 23 additions & 17 deletions include/ut_kvp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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 );

Expand Down
7 changes: 5 additions & 2 deletions src/ut_kvp.c
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,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)
{
Expand All @@ -173,9 +174,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
{
Expand All @@ -189,7 +192,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)
{
Expand Down
21 changes: 21 additions & 0 deletions tests/src/ut_test_kvp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand 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 */
Expand All @@ -190,20 +193,26 @@ 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);
if (read_file_into_memory(KVP_VALID_TEST_NOT_VALID_YAML_FORMATTED_FILE, &gKVPData) == 0)
{
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);
if (read_file_into_memory(KVP_VALID_TEST_JSON_FILE, &gKVPData) == 0)
{
status = ut_kvp_openMemory(gpMainTestInstance, gKVPData.buffer, gKVPData.length);
UT_ASSERT(status == UT_KVP_STATUS_SUCCESS);
free(gKVPData.buffer);
gKVPData.length = 0;
}
}

Expand Down Expand Up @@ -845,20 +854,26 @@ 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);
if (read_file_into_memory(KVP_VALID_TEST_SINGLE_INCLUDE_FILE_YAML, &gKVPData) == 0)
{
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);
if (read_file_into_memory(KVP_VALID_TEST_DEPTH_CHECK_INCLUDE_YAML, &gKVPData) == 0)
{
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);
Expand Down Expand Up @@ -978,6 +993,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 )
Expand Down Expand Up @@ -1120,6 +1137,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 )
Expand Down Expand Up @@ -1148,6 +1167,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 )
Expand Down