diff --git a/obuspa/Makefile b/obuspa/Makefile index 26ccc03cd..384b01405 100644 --- a/obuspa/Makefile +++ b/obuspa/Makefile @@ -5,13 +5,13 @@ include $(TOPDIR)/rules.mk PKG_NAME:=obuspa -PKG_VERSION:=9.0.4.11 +PKG_VERSION:=9.0.4.12 LOCAL_DEV:=0 ifneq ($(LOCAL_DEV),1) PKG_SOURCE_PROTO:=git PKG_SOURCE_URL:=https://dev.iopsys.eu/bbf/obuspa.git -PKG_SOURCE_VERSION:=79e066a3997b46ea3bcc48c4589c5a4c4cb05630 +PKG_SOURCE_VERSION:=9bd0c3c895cbcf34b922329c55a8262180b1fa86 PKG_MAINTAINER:=Vivek Dutta PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz PKG_MIRROR_HASH:=skip diff --git a/obuspa/files/etc/obuspa/usp_utils.sh b/obuspa/files/etc/obuspa/usp_utils.sh index c32837015..b5c0c2ffe 100755 --- a/obuspa/files/etc/obuspa/usp_utils.sh +++ b/obuspa/files/etc/obuspa/usp_utils.sh @@ -3,6 +3,7 @@ CTRUST_RESET_FILE="/tmp/obuspa/ctrust_reset" VENDOR_PREFIX_FILE="/etc/obuspa/vendor_prefix" FW_DEFAULT_ROLE_DIR="/etc/users/roles" +SECURE_ROLES="" mkdir -p /tmp/obuspa/ @@ -145,7 +146,7 @@ configure_permission() configure_roles() { - local rinst rname + local rinst rname is_secure if [ "$#" -ne 2 ]; then echo "Illegal number of parameters" @@ -154,6 +155,7 @@ configure_roles() json_select $2 json_get_var rname name + json_get_var is_secure secure_role if [ "${rname}" = "full_access" ]; then rinst=1 @@ -167,13 +169,21 @@ configure_roles() db_add Device.LocalAgent.ControllerTrust.Role.${rinst}.Enable 1 db_add Device.LocalAgent.ControllerTrust.Role.${rinst}.Name ${rname} + if [ "${is_secure}" = "1" ] || [ "${is_secure}" = "true" ]; then + if [ -z "${SECURE_ROLES}" ]; then + SECURE_ROLES="Device.LocalAgent.ControllerTrust.Role.${rinst}" + else + SECURE_ROLES="${SECURE_ROLES},Device.LocalAgent.ControllerTrust.Role.${rinst}" + fi + fi + json_for_each_item configure_permission permission "${name}" ${rinst} json_select .. } configure_roles_dir() { - local rinst rname + local rinst rname is_secure if [ "$#" -ne 1 ]; then echo "Illegal number of parameters" @@ -195,11 +205,28 @@ configure_roles_dir() return 0 fi fi + json_get_var is_secure secure_role db_add Device.LocalAgent.ControllerTrust.Role.${rinst}.Alias cpe-${rinst} db_add Device.LocalAgent.ControllerTrust.Role.${rinst}.Enable 1 db_add Device.LocalAgent.ControllerTrust.Role.${rinst}.Name ${rname} + if [ "${is_secure}" = "1" ] || [ "${is_secure}" = "true" ]; then + if [ -z "${SECURE_ROLES}" ]; then + SECURE_ROLES="Device.LocalAgent.ControllerTrust.Role.${rinst}" + else + SECURE_ROLES="${SECURE_ROLES},Device.LocalAgent.ControllerTrust.Role.${rinst}" + fi + fi + + if [ "${is_secure}" = "1" ] || [ "${is_secure}" = "true" ]; then + if [ -z "${SECURE_ROLES}" ]; then + SECURE_ROLES="Device.LocalAgent.ControllerTrust.Role.${rinst}" + else + SECURE_ROLES="${SECURE_ROLES},Device.LocalAgent.ControllerTrust.Role.${rinst}" + fi + fi + json_for_each_item configure_permission permission "${name}" "$((rinst))" json_select .. } @@ -214,6 +241,8 @@ configure_ctrust_role() fi mkdir -p /tmp/obuspa/ + SECURE_ROLES="" + if [ -f "${1}" ]; then json_init json_load_file "${1}" @@ -227,6 +256,11 @@ configure_ctrust_role() configure_roles_dir "${f/.json/}" done fi + + if [ -n "${SECURE_ROLES}" ]; then + db_add Device.LocalAgent.ControllerTrust.SecuredRoles "${SECURE_ROLES}" + fi } # configure_ctrust_role "${@}" + diff --git a/obuspa/patches/0005-CTrust-SecureRoles.patch b/obuspa/patches/0005-CTrust-SecureRoles.patch new file mode 100644 index 000000000..c49e18b52 --- /dev/null +++ b/obuspa/patches/0005-CTrust-SecureRoles.patch @@ -0,0 +1,562 @@ +Index: obuspa-9.0.4.3/src/core/data_model.c +=================================================================== +--- obuspa-9.0.4.3.orig/src/core/data_model.c ++++ obuspa-9.0.4.3/src/core/data_model.c +@@ -57,6 +57,7 @@ + #include "iso8601.h" + #include "group_get_vector.h" + #include "plugin.h" ++#include "device_ctrust.h" + + #ifdef ENABLE_COAP + #include "usp_coap.h" +@@ -507,6 +508,14 @@ int DATA_MODEL_GetParameterValue(char *p + return USP_ERR_INVALID_PATH; + } + ++ // Check if the parameter is secured and the controller has a secured role, and if the SHOW_PASSWORD flag is not set ++ if (!(flags & SHOW_PASSWORD) && node->registered.param_info.type_flags & DM_SECURE && !DEVICE_CTRUST_IsControllerSecured()) ++ { ++ // Return an empty string for secured parameters when controller doesn't have secured role ++ *buf = '\0'; ++ return USP_ERR_OK; ++ } ++ + // NOTE: We do not check 'is_qualified_instance' here, because the only time it would be unqualified, is if the + // path represented a multi-instance object. If path does represent this, then it will be caught below (switch statement) + +@@ -537,8 +546,8 @@ int DATA_MODEL_GetParameterValue(char *p + break; + + case kDMNodeType_DBParam_Secure: +- // Return an empty string, if special flag is not set +- if ((flags & SHOW_PASSWORD)==0) ++ // Return an empty string if the parameter is secured and the controller has a secured role, and if the SHOW_PASSWORD flag is not set ++ if (!(flags & SHOW_PASSWORD) && node->registered.param_info.type_flags & DM_SECURE && !DEVICE_CTRUST_IsControllerSecured()) + { + *buf = '\0'; + break; +Index: obuspa-9.0.4.3/src/core/device_ctrust.c +=================================================================== +--- obuspa-9.0.4.3.orig/src/core/device_ctrust.c ++++ obuspa-9.0.4.3/src/core/device_ctrust.c +@@ -64,6 +64,7 @@ + #include "text_utils.h" + #include "dm_inst_vector.h" + #include "database.h" ++#include "device_ctrust.h" + + //------------------------------------------------------------------------------ + // Location of the controller trust tables within the data model +@@ -228,6 +229,7 @@ credential_t *FindCredentialByCertInstan + int Get_CredentialRole(dm_req_t *req, char *buf, int len); + int Get_CredentialCertificate(dm_req_t *req, char *buf, int len); + int Get_CredentialNumEntries(dm_req_t *req, char *buf, int len); ++int Validate_SecuredRoles(dm_req_t *req, char *value); + + #ifndef REMOVE_DEVICE_SECURITY + int InitChallengeTable(); +@@ -347,6 +349,10 @@ int DEVICE_CTRUST_Init(void) + challenge_response_input_args, NUM_ELEM(challenge_response_input_args), + NULL, 0); + #endif ++ ++ // Register Device.LocalAgent.ControllerTrust.SecuredRoles parameter ++ err |= USP_REGISTER_DBParam_ReadWrite(DEVICE_CTRUST_ROOT ".SecuredRoles", "", Validate_SecuredRoles, NULL, DM_STRING); ++ + // Exit if any errors occurred + if (err != USP_ERR_OK) + { +@@ -2793,3 +2799,128 @@ exit: + return err; + } + #endif // REMOVE_DEVICE_SECURITY ++ ++ ++/*********************************************************************//** ++** ++** Validate_SecuredRoles ++** ++** Validates Device.LocalAgent.ControllerTrust.SecuredRoles ++** Each list item MUST be the Path Name of a row in the Device.LocalAgent.ControllerTrust.Role table ++** ++** \param req - pointer to structure identifying the parameter ++** \param value - value that the controller would like to set the parameter to ++** ++** \return USP_ERR_OK if successful ++** ++**************************************************************************/ ++int Validate_SecuredRoles(dm_req_t *req, char *value) ++{ ++ char *role_path; ++ char *saveptr; ++ char *str; ++ char temp[MAX_DM_PATH]; ++ int role_instance; ++ int err; ++ ++ // Empty string is valid ++ if (*value == '\0') ++ { ++ return USP_ERR_OK; ++ } ++ ++ // Copy the value as strtok_r modifies the string ++ USP_STRNCPY(temp, value, sizeof(temp)); ++ ++ // Iterate through comma-separated list ++ str = temp; ++ role_path = strtok_r(str, ",", &saveptr); ++ while (role_path != NULL) ++ { ++ // Trim whitespace ++ role_path = TEXT_UTILS_TrimBuffer(role_path); ++ ++ // Verify that this path exists in the Role table using DM_ACCESS_ValidateReference ++ err = DM_ACCESS_ValidateReference(role_path, "Device.LocalAgent.ControllerTrust.Role.{i}", &role_instance); ++ if (err != USP_ERR_OK) ++ { ++ USP_ERR_SetMessage("%s: Role path '%s' does not exist in Device.LocalAgent.ControllerTrust.Role table", __FUNCTION__, role_path); ++ return USP_ERR_INVALID_VALUE; ++ } ++ ++ role_path = strtok_r(NULL, ",", &saveptr); ++ } ++ ++ return USP_ERR_OK; ++} ++ ++/*********************************************************************//** ++** ++** DEVICE_CTRUST_IsControllerSecured ++** ++** Determines whether the specified controller has a secured role ++** ++** \param combined_role - pointer to structure containing the role indexes for this controller ++** ++** \return true if the controller has a secured role, false otherwise ++** ++**************************************************************************/ ++bool DEVICE_CTRUST_IsControllerSecured() ++{ ++ char secured_roles[MAX_DM_PATH]; ++ char *role_path; ++ char *saveptr; ++ char *str; ++ char temp[MAX_DM_PATH]; ++ int err; ++ role_t *role; ++ int role_instance; ++ combined_role_t combined_role; ++ ++ // Exit if unable to get the secured roles ++ err = DATA_MODEL_GetParameterValue("Device.LocalAgent.ControllerTrust.SecuredRoles", secured_roles, sizeof(secured_roles), 0); ++ if (err != USP_ERR_OK) ++ { ++ return false; ++ } ++ ++ // Empty string means no secured roles ++ if (*secured_roles == '\0') ++ { ++ return false; ++ } ++ ++ MSG_HANDLER_GetMsgRole(&combined_role); ++ // Copy the value as strtok_r modifies the string ++ USP_STRNCPY(temp, secured_roles, sizeof(temp)); ++ ++ // Iterate through comma-separated list ++ str = temp; ++ role_path = strtok_r(str, ",", &saveptr); ++ while (role_path != NULL) ++ { ++ // Trim whitespace ++ role_path = TEXT_UTILS_TrimBuffer(role_path); ++ ++ // Extract the instance number from the role path ++ err = DM_ACCESS_ValidateReference(role_path, "Device.LocalAgent.ControllerTrust.Role.{i}", &role_instance); ++ if (err == USP_ERR_OK) ++ { ++ // Find the role in our internal array ++ role = FindRoleByInstance(role_instance); ++ if (role != NULL) ++ { ++ // Check if this role matches either the inherited or assigned role ++ if ((role - roles == combined_role.inherited_index) || ++ (role - roles == combined_role.assigned_index)) ++ { ++ return true; ++ } ++ } ++ } ++ ++ role_path = strtok_r(NULL, ",", &saveptr); ++ } ++ ++ return false; ++} +Index: obuspa-9.0.4.3/src/core/device_ctrust.h +=================================================================== +--- /dev/null ++++ obuspa-9.0.4.3/src/core/device_ctrust.h +@@ -0,0 +1,48 @@ ++/* ++ * ++ * Copyright (C) 2019-2025, Broadband Forum ++ * Copyright (C) 2016-2025, CommScope, Inc ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * 3. Neither the name of the copyright holder nor the names of its ++ * contributors may be used to endorse or promote products derived from ++ * this software without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF ++ * THE POSSIBILITY OF SUCH DAMAGE. ++ * ++ */ ++ ++/** ++ * \file device_ctrust.h ++ * ++ * Header file containing the API functions provided by Controller Trust component ++ * ++ */ ++#ifndef DEVICE_CTRUST_H ++#define DEVICE_CTRUST_H ++ ++#include "device.h" ++ ++bool DEVICE_CTRUST_IsControllerSecured(void); ++ ++#endif +Index: obuspa-9.0.4.3/src/include/usp_api.h +=================================================================== +--- obuspa-9.0.4.3.orig/src/include/usp_api.h ++++ obuspa-9.0.4.3/src/include/usp_api.h +@@ -418,6 +418,7 @@ typedef struct + #define DM_DECIMAL 0x00000100 // 64 bit floating point number (double) + #define DM_LONG 0x00000200 // 64 bit signed integer (long long) + #define DM_VALUE_CHANGE_WILL_IGNORE 0x00000400 // Do not emit value change notifications for this parameter ++#define DM_SECURE 0x00000800 // secure parameter + + //------------------------------------------------------------------------- + // Functions to register the data model +Index: obuspa-9.0.4.3/src/core/group_get_vector.c +=================================================================== +--- obuspa-9.0.4.3.orig/src/core/group_get_vector.c ++++ obuspa-9.0.4.3/src/core/group_get_vector.c +@@ -49,6 +49,16 @@ + #include "group_get_vector.h" + #include "int_vector.h" + #include "data_model.h" ++#include "device_ctrust.h" // Added to use DEVICE_CTRUST_IsControllerSecured() ++ ++//------------------------------------------------------------------------------ ++// New function to check secure flag and controller state ++static int IsSecuredParamNotAccessible(char *path) ++{ ++ dm_instances_t inst; ++ dm_node_t *node = DM_PRIV_GetNodeFromPath(path, &inst, NULL, 0); ++ return (node && (node->registered.param_info.type_flags & DM_SECURE) && !DEVICE_CTRUST_IsControllerSecured()); ++} + + //------------------------------------------------------------------------------ + // Forward declarations. Note these are not static, because we need them in the symbol table for USP_LOG_Callstack() to show them +@@ -282,14 +292,14 @@ void GROUP_GET_VECTOR_GetValues(group_ge + return; + #endif + +- // Iterate over all parameters, getting them if non grouped, otherwise adding them to the relevant group to get ++ // Iterate over all parameters, getting them if non-grouped, otherwise adding them to the relevant group to get + memset(ggv_indexes, 0, sizeof(ggv_indexes)); + for (i=0; i < ggv->num_entries; i++) + { + gge = &ggv->vector[i]; + if (gge->group_id == NON_GROUPED) + { +- // If the parameter is not grouped, then get its value now. ++ // For non-grouped parameters, directly call DATA_MODEL_GetParameterValue which handles secure parameters internally + gge->err_code = DATA_MODEL_GetParameterValue(gge->path, buf, sizeof(buf), 0); + if (gge->err_code != USP_ERR_OK) + { +@@ -320,7 +330,6 @@ void GROUP_GET_VECTOR_GetValues(group_ge + chunk_size = MIN(GROUP_GET_CHUNK_SIZE, iv->num_entries - start_index); + GetParameterGroup(i, ggv, iv, start_index, chunk_size); + } +- + } + } + +@@ -378,88 +387,101 @@ void GetParameterGroup(int group_id, gro + return; + } + +- // Add all parameters to get in this group to a key value vector +- // NOTE: We form the key value vector manually to avoid copying the param paths. +- // Ownership of the param paths stay with the group get vector +- params.num_entries = chunk_size; +- params.vector = USP_MALLOC(sizeof(kv_pair_t) * chunk_size); ++ // Prepare a mapping for non-secure parameters and process secure ones directly ++ int non_secure_count = 0; ++ int *non_secure_map = USP_MALLOC(chunk_size * sizeof(int)); + for (i=0; i < chunk_size; i++) + { + index = iv->vector[start_index + i]; + gge = &ggv->vector[index]; + USP_ASSERT(gge->path != NULL); +- +- kv = ¶ms.vector[i]; +- kv->key = gge->path; +- kv->value = NULL; ++ if (IsSecuredParamNotAccessible(gge->path)) ++ { ++ // For secure parameter when controller is not secured, return empty value ++ gge->value = USP_STRDUP(""); ++ gge->err_code = USP_ERR_OK; ++ } ++ else ++ { ++ non_secure_map[non_secure_count] = index; ++ non_secure_count++; ++ } + } + +- // Exit if group callback fails +- USP_ERR_ClearMessage(); +- err = get_group_cb(group_id, ¶ms); +- if (err != USP_ERR_OK) ++ // If there are non-secure parameters, call the group callback for them ++ if (non_secure_count > 0) + { +- // Mark all results for params in this group with an error +- usp_err_msg = USP_ERR_GetMessage(); +- for (i=0; i < chunk_size; i++) ++ params.num_entries = non_secure_count; ++ params.vector = USP_MALLOC(sizeof(kv_pair_t) * non_secure_count); ++ for (i=0; i < non_secure_count; i++) + { +- index = iv->vector[start_index + i]; ++ index = non_secure_map[i]; + gge = &ggv->vector[index]; +- gge->err_code = USP_ERR_INTERNAL_ERROR; ++ USP_ASSERT(gge->path != NULL); ++ kv = ¶ms.vector[i]; ++ kv->key = gge->path; ++ kv->value = NULL; ++ } + +- // Assign an error message to this param +- if (usp_err_msg[0] != '\0') +- { +- gge->err_msg = USP_STRDUP(usp_err_msg); +- } +- else ++ USP_ERR_ClearMessage(); ++ err = get_group_cb(group_id, ¶ms); ++ if (err != USP_ERR_OK) ++ { ++ // Mark all non-secure results with an error ++ usp_err_msg = USP_ERR_GetMessage(); ++ for (i=0; i < non_secure_count; i++) + { +- // Form an error message if none was provided +- USP_SNPRINTF(err_msg, sizeof(err_msg), "%s: Get group callback failed for param %s", __FUNCTION__, gge->path); +- gge->err_msg = USP_STRDUP(err_msg); ++ index = non_secure_map[i]; ++ gge = &ggv->vector[index]; ++ gge->err_code = USP_ERR_INTERNAL_ERROR; ++ if (usp_err_msg[0] != '\0') ++ { ++ gge->err_msg = USP_STRDUP(usp_err_msg); ++ } ++ else ++ { ++ USP_SNPRINTF(err_msg, sizeof(err_msg), "%s: Get group callback failed for param %s", __FUNCTION__, gge->path); ++ gge->err_msg = USP_STRDUP(err_msg); ++ } ++ USP_SAFE_FREE(params.vector[i].value); + } +- +- // NOTE: The group get might have populated a value for some params, so free these values +- USP_SAFE_FREE(params.vector[i].value); ++ USP_FREE(params.vector); ++ USP_FREE(non_secure_map); ++ return; + } +- goto exit; +- } + +- // Move all parameter values obtained to the group get vector +- // NOTE: Ownership of the value string transfers from the params vector to the group get vector +- usp_err_msg = USP_ERR_GetMessage(); +- empty_count = 0; +- for (i=0; i < chunk_size; i++) +- { +- kv = ¶ms.vector[i]; +- index = iv->vector[start_index + i]; +- gge = &ggv->vector[index]; +- +- if (kv->value != NULL) +- { +- gge->value = kv->value; +- } +- else ++ // Move all parameter values obtained to the group get vector for non-secure parameters ++ usp_err_msg = USP_ERR_GetMessage(); ++ empty_count = 0; ++ for (i=0; i < non_secure_count; i++) + { +- // If this is the first parameter with no value, and an error message has been set, then use the error message +- if ((usp_err_msg[0] != '\0') && (empty_count == 0)) ++ index = non_secure_map[i]; ++ gge = &ggv->vector[index]; ++ kv = ¶ms.vector[i]; ++ ++ if (kv->value != NULL) + { +- USP_SNPRINTF(err_msg, sizeof(err_msg), "%s", usp_err_msg); ++ gge->value = kv->value; + } + else + { +- USP_SNPRINTF(err_msg, sizeof(err_msg), "%s: Get group callback did not provide a value for param %s", __FUNCTION__, gge->path); ++ if ((usp_err_msg[0] != '\0') && (empty_count == 0)) ++ { ++ USP_SNPRINTF(err_msg, sizeof(err_msg), "%s", usp_err_msg); ++ } ++ else ++ { ++ USP_SNPRINTF(err_msg, sizeof(err_msg), "%s: Get group callback did not provide a value for param %s", __FUNCTION__, gge->path); ++ } ++ gge->err_code = USP_ERR_INTERNAL_ERROR; ++ gge->err_msg = USP_STRDUP(err_msg); ++ empty_count++; + } +- gge->err_code = USP_ERR_INTERNAL_ERROR; +- gge->err_msg = USP_STRDUP(err_msg); +- empty_count++; + } ++ USP_FREE(params.vector); + } + +-exit: +- // Destroy the key-value vector. +- // As ownership of all strings in it have transferred to the group get vector, we only have to free the array itself +- USP_FREE(params.vector); ++ USP_FREE(non_secure_map); + } + + /*********************************************************************//** +@@ -486,9 +508,10 @@ void GetParametersIndividually(group_get + for (i=0; i < ggv->num_entries; i++) + { + gge = &ggv->vector[i]; ++ + if (gge->group_id == NON_GROUPED) + { +- // Non-grouped parameters can directly call DATA_MODEL_GetParameterValue() ++ // For non-grouped parameters, directly call DATA_MODEL_GetParameterValue which handles secure parameters internally + gge->err_code = DATA_MODEL_GetParameterValue(gge->path, buf, sizeof(buf), 0); + if (gge->err_code == USP_ERR_OK) + { +@@ -497,42 +520,51 @@ void GetParametersIndividually(group_get + } + else + { +- // Grouped parameters cannot call DATA_MODEL_GetParameterValue(), as that would cause infinite recursion +- get_group_cb = group_vendor_hooks[gge->group_id].get_group_cb; +- if (get_group_cb == NULL) ++ // For grouped parameters, check if the parameter is secure and the controller is not secured ++ if (IsSecuredParamNotAccessible(gge->path)) + { +- // Set an error message, if no group callback registered for this parameter +- USP_ERR_SetMessage("%s: No registered group callback to get param %s", __FUNCTION__, gge->path); +- gge->err_code = USP_ERR_INTERNAL_ERROR; ++ gge->value = USP_STRDUP(""); ++ gge->err_code = USP_ERR_OK; + } + else + { +- // Get this grouped parameter individually using the group get callback +- pv.num_entries = 1; +- pv.vector = ¶m; +- param.key = gge->path; +- param.value = NULL; +- +- USP_ERR_ClearMessage(); +- gge->err_code = get_group_cb(gge->group_id, &pv); +- if (gge->err_code != USP_ERR_OK) ++ // Grouped parameters cannot call DATA_MODEL_GetParameterValue(), as that would cause infinite recursion ++ get_group_cb = group_vendor_hooks[gge->group_id].get_group_cb; ++ if (get_group_cb == NULL) + { +- USP_ERR_ReplaceEmptyMessage("%s: group get failed for '%s' (%s)", __FUNCTION__, gge->path, USP_ERR_UspErrToString(gge->err_code)); +- USP_SAFE_FREE(param.value) ++ // Set an error message, if no group callback registered for this parameter ++ USP_ERR_SetMessage("%s: No registered group callback to get param %s", __FUNCTION__, gge->path); ++ gge->err_code = USP_ERR_INTERNAL_ERROR; + } + else + { +- if (param.value != NULL) ++ // Get this grouped parameter individually using the group get callback ++ pv.num_entries = 1; ++ pv.vector = ¶m; ++ param.key = gge->path; ++ param.value = NULL; ++ ++ USP_ERR_ClearMessage(); ++ gge->err_code = get_group_cb(gge->group_id, &pv); ++ if (gge->err_code != USP_ERR_OK) + { +- // Move ownership of the returned string from param.value to gge->value +- gge->value = param.value; +- param.value = NULL; // not strictly necessary ++ USP_ERR_ReplaceEmptyMessage("%s: group get failed for '%s' (%s)", __FUNCTION__, gge->path, USP_ERR_UspErrToString(gge->err_code)); ++ USP_SAFE_FREE(param.value) + } + else + { +- // If no value was returned, then this is also reported as an error in the group get array +- USP_ERR_ReplaceEmptyMessage("%s: Get group callback did not provide a value for param %s", __FUNCTION__, gge->path); +- gge->err_code = USP_ERR_INTERNAL_ERROR; ++ if (param.value != NULL) ++ { ++ // Move ownership of the returned string from param.value to gge->value ++ gge->value = param.value; ++ param.value = NULL; // not strictly necessary ++ } ++ else ++ { ++ // If no value was returned, then this is also reported as an error in the group get array ++ USP_ERR_ReplaceEmptyMessage("%s: Get group callback did not provide a value for param %s", __FUNCTION__, gge->path); ++ gge->err_code = USP_ERR_INTERNAL_ERROR; ++ } + } + } + } +@@ -545,3 +577,4 @@ void GetParametersIndividually(group_get + } + } + } ++