diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d7d38d..0a0aaa5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ The git repositories are hosted at the following sites: * https://bacnet.sourceforge.net/ * https://github.com/bacnet-stack/bacnet-stack/ -## [Unreleased] - 2026-03-04 +## [Unreleased] - 2026-03-06 ### Security @@ -52,6 +52,11 @@ The git repositories are hosted at the following sites: ### Added +* Added WriteProperty support in the basic Structured View object. + Converted Structured View internal storage to dynamically allocated + character strings and a keylist-based subordinate list that is resizable + via array index 0. Updated and expanded tests for structured view + and generic property read/write behaviors. (#1256) * Added CreateObject and DeleteObject for Octet StringValue and PositiveInteger Value objects. (#1246) * Added PROP_TIMER_RUNNING to writable properties and implement diff --git a/src/bacnet/bacstr.c b/src/bacnet/bacstr.c index 9975e776..d75dcfa2 100644 --- a/src/bacnet/bacstr.c +++ b/src/bacnet/bacstr.c @@ -995,6 +995,58 @@ bool characterstring_valid(const BACNET_CHARACTER_STRING *char_string) return valid; } +/** + * Check if the character string is valid or not. + * + * @param char_string Pointer to the character string. + * + * @return true if the string is valid, false otherwise. + */ +bool characterstring_utf8_valid(const BACNET_CHARACTER_STRING *char_string) +{ + bool valid = false; /* return value */ + + if (char_string) { + if (char_string->encoding == CHARACTER_UTF8) { + /*UTF8 check*/ + if (utf8_isvalid(char_string->value, char_string->length)) { + valid = true; + } + } + } + + return valid; +} + +/** + * Duplicate a UTF-8 BACnet character string into a C string. + * + * The function allocates a new, NUL-terminated C string and copies the + * UTF-8 encoded bytes from the given BACNET_CHARACTER_STRING into it. + * The caller owns the returned buffer and must release it with free(). + * + * @param char_string Pointer to the BACnet character string to duplicate. + * + * @return Pointer to a newly allocated NUL-terminated UTF-8 C string on + * success, or NULL if @p char_string is NULL, if the encoding + * is not CHARACTER_UTF8, or if memory allocation fails. + */ +char *characterstring_utf8_strdup(const BACNET_CHARACTER_STRING *char_string) +{ + char *str = NULL; /* return value */ + + if (char_string) { + if (char_string->encoding == CHARACTER_UTF8) { + str = calloc(char_string->length + 1, 1); + if (str != NULL) { + memcpy(str, char_string->value, char_string->length); + } + } + } + + return str; +} + #if BACNET_USE_OCTETSTRING /** * @brief Initialize an octet string with the given bytes or @@ -2144,6 +2196,9 @@ int bacnet_snprintf( */ char *bacnet_strdup(const char *s) { + if (s == NULL) { + return NULL; + } size_t size = strlen(s) + 1; char *p = malloc(size); if (p != NULL) { diff --git a/src/bacnet/bacstr.h b/src/bacnet/bacstr.h index c7127f78..b70d4c32 100644 --- a/src/bacnet/bacstr.h +++ b/src/bacnet/bacstr.h @@ -132,6 +132,10 @@ BACNET_STACK_EXPORT bool characterstring_valid(const BACNET_CHARACTER_STRING *char_string); BACNET_STACK_EXPORT bool utf8_isvalid(const char *str, size_t length); +BACNET_STACK_EXPORT +bool characterstring_utf8_valid(const BACNET_CHARACTER_STRING *char_string); +BACNET_STACK_EXPORT +char *characterstring_utf8_strdup(const BACNET_CHARACTER_STRING *char_string); /* returns false if the string exceeds capacity initialize by using length=0 */ diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 71923543..91fdeff6 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -776,7 +776,7 @@ static object_functions_t My_Object_Table[] = { Structured_View_Valid_Instance, Structured_View_Object_Name, Structured_View_Read_Property, - NULL /* Write_Property */, + Structured_View_Write_Property, Structured_View_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, diff --git a/src/bacnet/basic/object/structured_view.c b/src/bacnet/basic/object/structured_view.c index 02a1c34e..eac8f4df 100644 --- a/src/bacnet/basic/object/structured_view.c +++ b/src/bacnet/basic/object/structured_view.c @@ -33,12 +33,12 @@ #include "structured_view.h" struct object_data { - const char *Object_Name; - const char *Description; + char *Object_Name; + char *Description; BACNET_NODE_TYPE Node_Type; - const char *Node_Subtype; + char *Node_Subtype; void *Context; - BACNET_SUBORDINATE_DATA *Subordinate_List; + OS_Keylist Subordinate_List; BACNET_RELATIONSHIP Default_Subordinate_Relationship; BACNET_DEVICE_OBJECT_REFERENCE Represents; }; @@ -70,9 +70,18 @@ static const int32_t Properties_Proprietary[] = { -1 }; /* Every object shall have a Writable Property_List property which is a BACnetARRAY of property identifiers, one property identifier for each property within this object - that is always writable. */ + that is writable. */ static const int32_t Writable_Properties[] = { - /* unordered list of always writable properties */ + /* unordered list of writable properties */ + PROP_NODE_TYPE, + PROP_DEFAULT_SUBORDINATE_RELATIONSHIP, + PROP_REPRESENTS, + PROP_SUBORDINATE_LIST, + PROP_SUBORDINATE_ANNOTATIONS, + PROP_SUBORDINATE_NODE_TYPES, + PROP_SUBORDINATE_RELATIONSHIPS, + PROP_OBJECT_NAME, + PROP_DESCRIPTION, -1 }; @@ -229,7 +238,8 @@ bool Structured_View_Name_Set(uint32_t object_instance, const char *new_name) pObject = Keylist_Data(Object_List, object_instance); if (pObject) { status = true; - pObject->Object_Name = new_name; + free(pObject->Object_Name); + pObject->Object_Name = bacnet_strdup(new_name); } return status; @@ -294,7 +304,8 @@ bool Structured_View_Description_Set( pObject = Keylist_Data(Object_List, object_instance); if (pObject) { status = true; - pObject->Description = new_name; + free(pObject->Description); + pObject->Description = bacnet_strdup(new_name); } return status; @@ -376,29 +387,472 @@ bool Structured_View_Node_Subtype_Set( pObject = Keylist_Data(Object_List, object_instance); if (pObject) { status = true; - pObject->Node_Subtype = new_name; + free(pObject->Node_Subtype); + pObject->Node_Subtype = bacnet_strdup(new_name); } return status; } /** - * @brief For a given object instance-number, returns the Subordinate_List + * @brief For a given object instance-number, returns the number of + * Subordinate_List elements * @param object_instance - object-instance number of the object - * @return Subordinate_List or NULL if not found + * @return number of Subordinate_List elements */ -BACNET_SUBORDINATE_DATA * -Structured_View_Subordinate_List(uint32_t object_instance) +static unsigned int +Structured_View_Subordinate_List_Size(struct object_data *pObject) +{ + unsigned int count = 0; + + if (pObject) { + count = Keylist_Count(pObject->Subordinate_List); + } + + return count; +} + +/** + * @brief For a given object Subordinate_List, add an element to the list + * @param list - pointer to the Subordinate_List key list + * @param key - key for the new element + * @return pointer to the Subordinate_List element + */ +static BACNET_SUBORDINATE_DATA * +Structured_View_Subordinate_List_Element_Add(OS_Keylist list, KEY key) +{ + BACNET_SUBORDINATE_DATA *element = NULL; + int index; + + element = calloc(1, sizeof(BACNET_SUBORDINATE_DATA)); + if (element) { + element->next = NULL; + index = Keylist_Data_Add(list, key, element); + if (index < 0) { + free(element); + element = NULL; + } + } + + return element; +} + +/** + * @brief For a given object element, free the Subordinate_List element + * @param element - pointer to the Subordinate_List element + */ +static void +Structured_View_Subordinate_List_Element_Remove(OS_Keylist list, KEY key) +{ + BACNET_SUBORDINATE_DATA *element; + + element = Keylist_Data_Delete(list, key); + if (element) { + if (element->Annotation) { + free(element->Annotation); + } + free(element); + } +} + +/** + * @brief For a given object instance-number, free the Subordinate_List + * @param pObject - pointer to the object data + */ +static void Structured_View_Subordinate_List_Free(struct object_data *pObject) +{ + KEY key = 0; + int count = 0; + + if (pObject) { + count = Keylist_Count(pObject->Subordinate_List); + while (count > 0) { + Structured_View_Subordinate_List_Element_Remove( + pObject->Subordinate_List, key); + key++; + count--; + } + } +} + +/** + * @brief For a given object instance-number, resize the Subordinate_List + * @param pObject - pointer to the object data + * @param old_array_size - current size of the Subordinate_List + * @param new_array_size - new size of the Subordinate_List + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Structured_View_Subordinate_List_Resize( + struct object_data *pObject, BACNET_UNSIGNED_INTEGER new_array_size) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_SUCCESS; + BACNET_SUBORDINATE_DATA *element = NULL; + BACNET_UNSIGNED_INTEGER old_array_size = 0; + KEY key = 0; + + old_array_size = Structured_View_Subordinate_List_Size(pObject); + /* Array element zero is the number of elements in the list. */ + if (new_array_size < old_array_size) { + /* free the elements at the tail of the list */ + key = new_array_size; + while (key < old_array_size) { + Structured_View_Subordinate_List_Element_Remove( + pObject->Subordinate_List, key); + key++; + } + } else if (new_array_size > old_array_size) { + /* extend the list */ + key = old_array_size; + while (key < new_array_size) { + element = Structured_View_Subordinate_List_Element_Add( + pObject->Subordinate_List, key); + if (!element) { + error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; + break; + } + key++; + } + } + + return error_code; +} + +/** + * @brief For a given object instance-number, returns the number of + * Subordinate_List elements + * @param object_instance - object-instance number of the object + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @return Subordinate_List element or NULL if not found + */ +static BACNET_SUBORDINATE_DATA *Structured_View_Subordinate_List_Element( + struct object_data *pObject, BACNET_ARRAY_INDEX array_index) { BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + KEY key = 0; + + if (pObject) { + key = array_index; + subordinate_list = Keylist_Data(pObject->Subordinate_List, key); + } + + return subordinate_list; +} + +/** + * @brief Decode a BACnetARRAY property element to determine the length + * @param object_instance [in] BACnet network port object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int Structured_View_Subordinate_List_Member_Decode( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + int len = 0; struct object_data *pObject; pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - subordinate_list = pObject->Subordinate_List; + len = bacnet_device_object_reference_decode(apdu, apdu_size, NULL); } - return subordinate_list; + return len; +} + +/** + * @brief Write a value to a BACnetARRAY property element value + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param array_size [in] number of elements in the array, used writing array + * element 0 + * @param apdu [in] encoded element value + * @param apdu_size [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Structured_View_Subordinate_List_Member_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + BACNET_UNSIGNED_INTEGER array_size, + uint8_t *apdu, + size_t apdu_size) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + BACNET_DEVICE_OBJECT_REFERENCE reference = { 0 }; + BACNET_SUBORDINATE_DATA *element = NULL; + int len = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (array_index == 0) { + /* Array element zero is the number of elements in the list. */ + error_code = + Structured_View_Subordinate_List_Resize(pObject, array_size); + } else { + array_index--; /* array index is 1..N, but we want 0..(N-1) */ + len = bacnet_device_object_reference_decode( + apdu, apdu_size, &reference); + if (len > 0) { + element = Structured_View_Subordinate_List_Element( + pObject, array_index); + if (element) { + element->Device_Instance = + reference.deviceIdentifier.instance; + element->Object_Type = reference.objectIdentifier.type; + element->Object_Instance = + reference.objectIdentifier.instance; + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_OTHER; + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } + } + + return error_code; +} + +/** + * @brief Decode a BACnetARRAY property element to determine the length + * @param object_instance [in] BACnet network port object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int Structured_View_Subordinate_Annotation_Member_Decode( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + int len = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + len = bacnet_character_string_application_decode(apdu, apdu_size, NULL); + } + + return len; +} + +/** + * @brief Write a value to a BACnetARRAY property element value + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param array_size [in] number of elements in the array, used writing array + * element 0 + * @param apdu [in] encoded element value + * @param apdu_size [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Structured_View_Subordinate_Annotation_Member_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + BACNET_UNSIGNED_INTEGER array_size, + uint8_t *apdu, + size_t apdu_size) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + BACNET_CHARACTER_STRING annotation = { 0 }; + BACNET_SUBORDINATE_DATA *element = NULL; + char *annotation_string; + int len = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (array_index == 0) { + /* Array element zero is the number of elements in the list. */ + error_code = + Structured_View_Subordinate_List_Resize(pObject, array_size); + } else { + array_index--; /* array index is 1..N, but we want 0..(N-1) */ + len = bacnet_character_string_application_decode( + apdu, apdu_size, &annotation); + if (len > 0) { + if (characterstring_utf8_valid(&annotation)) { + element = Structured_View_Subordinate_List_Element( + pObject, array_index); + if (element) { + annotation_string = + characterstring_utf8_strdup(&annotation); + if (annotation_string) { + if (element->Annotation) { + free((void *)element->Annotation); + } + element->Annotation = annotation_string; + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; + } + } else { + error_code = ERROR_CODE_OTHER; + } + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } + } + + return error_code; +} + +/** + * @brief Decode a BACnetARRAY property element to determine the length + * @param object_instance [in] BACnet network port object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int Structured_View_Subordinate_Node_Type_Member_Decode( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + int len = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + len = bacnet_enumerated_application_decode(apdu, apdu_size, NULL); + } + + return len; +} + +/** + * @brief Write a value to a BACnetARRAY property element value + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param array_size [in] number of elements in the array, used writing array + * element 0 + * @param apdu [in] encoded element value + * @param apdu_size [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Structured_View_Subordinate_Node_Type_Member_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + BACNET_UNSIGNED_INTEGER array_size, + uint8_t *apdu, + size_t apdu_size) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + uint32_t node_type = 0; + BACNET_SUBORDINATE_DATA *element = NULL; + int len = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (array_index == 0) { + error_code = + Structured_View_Subordinate_List_Resize(pObject, array_size); + } else { + array_index--; /* array index is 1..N, but we want 0..(N-1) */ + len = bacnet_enumerated_application_decode( + apdu, apdu_size, &node_type); + if (len > 0) { + if (node_type > BACNET_NODE_TYPE_MAX) { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + element = Structured_View_Subordinate_List_Element( + pObject, array_index); + if (element) { + element->Node_Type = node_type; + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_OTHER; + } + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } + } + + return error_code; +} + +/** + * @brief Decode a BACnetARRAY property element to determine the length + * @param object_instance [in] BACnet network port object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int Structured_View_Subordinate_Relationship_Member_Decode( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + int len = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + len = bacnet_enumerated_application_decode(apdu, apdu_size, NULL); + } + + return len; +} + +/** + * @brief Write a value to a BACnetARRAY property element value + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param array_size [in] number of elements in the array, used writing array + * element 0 + * @param apdu [in] encoded element value + * @param apdu_size [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Structured_View_Subordinate_Relationship_Member_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + BACNET_UNSIGNED_INTEGER array_size, + uint8_t *apdu, + size_t apdu_size) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + uint32_t relationship = 0; + BACNET_SUBORDINATE_DATA *element = NULL; + int len = 0; + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (array_index == 0) { + error_code = + Structured_View_Subordinate_List_Resize(pObject, array_size); + } else { + array_index--; /* array index is 1..N, but we want 0..(N-1) */ + len = bacnet_enumerated_application_decode( + apdu, apdu_size, &relationship); + if (len > 0) { + if (relationship > BACNET_RELATIONSHIP_PROPRIETARY_MAX) { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + element = Structured_View_Subordinate_List_Element( + pObject, array_index); + if (element) { + element->Relationship = relationship; + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_OTHER; + } + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } + } + + return error_code; } /** @@ -410,10 +864,46 @@ void Structured_View_Subordinate_List_Set( uint32_t object_instance, BACNET_SUBORDINATE_DATA *subordinate_list) { struct object_data *pObject; + BACNET_SUBORDINATE_DATA *element, *data; + KEY key; pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - pObject->Subordinate_List = subordinate_list; + Structured_View_Subordinate_List_Free(pObject); + /* walk the linked list and add to Keylist */ + key = 0; + element = subordinate_list; + while (element) { + data = Structured_View_Subordinate_List_Element_Add( + pObject->Subordinate_List, key); + if (data) { + memmove(data, element, sizeof(BACNET_SUBORDINATE_DATA)); + if (element->Annotation) { + data->Annotation = bacnet_strdup(element->Annotation); + } + data->next = NULL; + } + element = element->next; + key++; + } + } +} + +/** + * @brief Convert an array of BACnetSubordinateData to linked list + * @param array pointer to element zero of the array + * @param size number of elements in the array + */ +void Structured_View_Subordinate_List_Link_Array( + BACNET_SUBORDINATE_DATA *array, size_t size) +{ + size_t i = 0; + + for (i = 0; i < size; i++) { + if (i > 0) { + array[i - 1].next = &array[i]; + } + array[i].next = NULL; } } @@ -421,8 +911,8 @@ void Structured_View_Subordinate_List_Set( * @brief For a given object instance-number, returns the * Default_Subordinate_Relationship * @param object_instance - object-instance number of the object - * @return Default_Subordinate_Relationship or BACNET_RELATIONSHIP_DEFAULT if - * not found + * @return Default_Subordinate_Relationship or BACNET_RELATIONSHIP_DEFAULT + * if not found */ BACNET_RELATIONSHIP Structured_View_Default_Subordinate_Relationship(uint32_t object_instance) @@ -442,7 +932,8 @@ Structured_View_Default_Subordinate_Relationship(uint32_t object_instance) * @brief For a given object instance-number, sets the * Default_Subordinate_Relationship * @param object_instance - object-instance number of the object - * @param relationship - holds the Default_Subordinate_Relationship to be set + * @param relationship - holds the Default_Subordinate_Relationship to be + * set * @return true if Default_Subordinate_Relationship was set */ bool Structured_View_Default_Subordinate_Relationship_Set( @@ -511,16 +1002,11 @@ bool Structured_View_Represents_Set( unsigned int Structured_View_Subordinate_List_Count(uint32_t object_instance) { unsigned int count = 0; - BACNET_SUBORDINATE_DATA *subordinate_list = NULL; struct object_data *pObject; pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - subordinate_list = pObject->Subordinate_List; - while (subordinate_list) { - count++; - subordinate_list = subordinate_list->next; - } + count = Structured_View_Subordinate_List_Size(pObject); } return count; @@ -537,23 +1023,16 @@ unsigned int Structured_View_Subordinate_List_Count(uint32_t object_instance) BACNET_SUBORDINATE_DATA *Structured_View_Subordinate_List_Member( uint32_t object_instance, BACNET_ARRAY_INDEX array_index) { - BACNET_ARRAY_INDEX index = 0; - BACNET_SUBORDINATE_DATA *subordinate_list = NULL; + BACNET_SUBORDINATE_DATA *element = NULL; struct object_data *pObject; pObject = Keylist_Data(Object_List, object_instance); if (pObject) { - subordinate_list = pObject->Subordinate_List; - while (subordinate_list) { - if (index == array_index) { - break; - } - index++; - subordinate_list = subordinate_list->next; - } + element = + Structured_View_Subordinate_List_Element(pObject, array_index); } - return subordinate_list; + return element; } /** @@ -608,7 +1087,7 @@ int Structured_View_Subordinate_Annotations_Element_Encode( Structured_View_Subordinate_List_Member(object_instance, array_index); if (subordinate_list) { /* BACnetCharacterString */ - characterstring_init_ansi(&value, subordinate_list->Annotations); + characterstring_init_ansi(&value, subordinate_list->Annotation); apdu_len = encode_application_character_string(apdu, &value); } @@ -803,6 +1282,259 @@ int Structured_View_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) return apdu_len; } +/** + * For a given object instance-number, sets the object-name + * + * @param object_instance - object-instance number of the object + * @param cstring - holds the object-name to be set + * + * @return true if object-name was set + */ +static bool Structured_View_Object_Name_Write( + BACNET_WRITE_PROPERTY_DATA *wp_data, BACNET_CHARACTER_STRING *cstring) +{ + bool status = false; /* return value */ + struct object_data *pObject; + char *utf8_name = NULL; + + pObject = Keylist_Data(Object_List, wp_data->object_instance); + if (pObject) { + utf8_name = + write_property_characterstring_utf8_strdup(wp_data, cstring); + if (utf8_name) { + free(pObject->Object_Name); + pObject->Object_Name = utf8_name; + status = true; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * For a given object instance-number, sets the description property value + * + * @param object_instance - object-instance number of the object + * @param cstring - holds the description to be set + * + * @return true if description was set + */ +static bool Structured_View_Description_Write( + BACNET_WRITE_PROPERTY_DATA *wp_data, BACNET_CHARACTER_STRING *cstring) +{ + bool status = false; /* return value */ + struct object_data *pObject; + char *utf8_name = NULL; + + pObject = Keylist_Data(Object_List, wp_data->object_instance); + if (pObject) { + utf8_name = + write_property_characterstring_utf8_strdup(wp_data, cstring); + if (utf8_name) { + free(pObject->Description); + pObject->Description = utf8_name; + status = true; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * For a given object instance-number, sets the node-subtype property value + * + * @param object_instance - object-instance number of the object + * @param cstring - holds the node-subtype to be set + * + * @return true if node-subtype was set + */ +static bool Structured_View_Node_Subtype_Write( + BACNET_WRITE_PROPERTY_DATA *wp_data, BACNET_CHARACTER_STRING *cstring) +{ + bool status = false; /* return value */ + struct object_data *pObject; + char *utf8_name = NULL; + + pObject = Keylist_Data(Object_List, wp_data->object_instance); + if (pObject) { + utf8_name = + write_property_characterstring_utf8_strdup(wp_data, cstring); + if (utf8_name) { + free(pObject->Node_Subtype); + pObject->Node_Subtype = utf8_name; + status = true; + } + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + + return status; +} + +/** + * WriteProperty handler for this object. For the given WriteProperty + * data, the application_data is loaded or the error flags are set. + * + * @param wp_data - BACNET_WRITE_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return false if an error is loaded, true if no errors + */ +bool Structured_View_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) +{ + bool status = false; /* return value */ + int len = 0; + BACNET_UNSIGNED_INTEGER array_size = 0; + BACNET_APPLICATION_DATA_VALUE value = { 0 }; + + /* decode the some of the request */ + len = bacapp_decode_known_array_property( + wp_data->application_data, wp_data->application_data_len, &value, + wp_data->object_type, wp_data->object_property, wp_data->array_index); + /* FIXME: len < application_data_len: more data? */ + if (len < 0) { + /* error while decoding - a value larger than we can handle */ + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + return false; + } + switch (wp_data->object_property) { + case PROP_OBJECT_NAME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_CHARACTER_STRING); + if (status) { + status = Structured_View_Object_Name_Write( + wp_data, &value.type.Character_String); + } + break; + case PROP_DESCRIPTION: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_CHARACTER_STRING); + if (status) { + status = Structured_View_Description_Write( + wp_data, &value.type.Character_String); + } + break; + case PROP_NODE_SUBTYPE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_CHARACTER_STRING); + if (status) { + status = Structured_View_Node_Subtype_Write( + wp_data, &value.type.Character_String); + } + break; + case PROP_NODE_TYPE: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + if (value.type.Enumerated > BACNET_NODE_TYPE_MAX) { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + status = Structured_View_Node_Type_Set( + wp_data->object_instance, value.type.Enumerated); + } + } + break; + case PROP_DEFAULT_SUBORDINATE_RELATIONSHIP: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_ENUMERATED); + if (status) { + if (value.type.Enumerated > + BACNET_RELATIONSHIP_PROPRIETARY_MAX) { + status = false; + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } else { + status = + Structured_View_Default_Subordinate_Relationship_Set( + wp_data->object_instance, value.type.Enumerated); + } + } + break; + case PROP_REPRESENTS: + status = write_property_type_valid( + wp_data, &value, + BACNET_APPLICATION_TAG_DEVICE_OBJECT_REFERENCE); + if (status) { + status = Structured_View_Represents_Set( + wp_data->object_instance, + &value.type.Device_Object_Reference); + } + break; + case PROP_SUBORDINATE_LIST: + array_size = Structured_View_Subordinate_List_Count( + wp_data->object_instance); + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + Structured_View_Subordinate_List_Member_Decode, + Structured_View_Subordinate_List_Member_Write, array_size, + wp_data->application_data, wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } + break; + case PROP_SUBORDINATE_ANNOTATIONS: + array_size = Structured_View_Subordinate_List_Count( + wp_data->object_instance); + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + Structured_View_Subordinate_Annotation_Member_Decode, + Structured_View_Subordinate_Annotation_Member_Write, array_size, + wp_data->application_data, wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } + break; + case PROP_SUBORDINATE_NODE_TYPES: + array_size = Structured_View_Subordinate_List_Count( + wp_data->object_instance); + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + Structured_View_Subordinate_Node_Type_Member_Decode, + Structured_View_Subordinate_Node_Type_Member_Write, array_size, + wp_data->application_data, wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } + break; + case PROP_SUBORDINATE_RELATIONSHIPS: + array_size = Structured_View_Subordinate_List_Count( + wp_data->object_instance); + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + Structured_View_Subordinate_Relationship_Member_Decode, + Structured_View_Subordinate_Relationship_Member_Write, + array_size, wp_data->application_data, + wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } + break; + default: + if (property_lists_member( + Properties_Required, Properties_Optional, + Properties_Proprietary, wp_data->object_property)) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_UNKNOWN_PROPERTY; + } + break; + } + + return status; +} + /** * @brief Set the context used with a specific object instance * @param object_instance [in] BACnet object instance number @@ -838,7 +1570,8 @@ void Structured_View_Context_Set(uint32_t object_instance, void *context) /** * Creates a Structured View object * @param object_instance - object-instance number of the object - * @return object_instance if the object is created, else BACNET_MAX_INSTANCE + * @return object_instance if the object is created, else + * BACNET_MAX_INSTANCE */ uint32_t Structured_View_Create(uint32_t object_instance) { @@ -867,7 +1600,7 @@ uint32_t Structured_View_Create(uint32_t object_instance) pObject->Object_Name = NULL; pObject->Description = NULL; pObject->Node_Subtype = NULL; - pObject->Subordinate_List = NULL; + pObject->Subordinate_List = Keylist_Create(); pObject->Default_Subordinate_Relationship = BACNET_RELATIONSHIP_DEFAULT; pObject->Represents.deviceIdentifier.type = OBJECT_NONE; pObject->Represents.deviceIdentifier.instance = BACNET_MAX_INSTANCE; @@ -886,7 +1619,23 @@ uint32_t Structured_View_Create(uint32_t object_instance) } /** - * Deletes an Structured View object + * @brief Free the memory used by a Structured View object + * @param pObject pointer to the object data to free + */ +static void Structured_View_Object_Free(struct object_data *pObject) +{ + if (pObject) { + free(pObject->Description); + free(pObject->Node_Subtype); + free(pObject->Object_Name); + Structured_View_Subordinate_List_Free(pObject); + Keylist_Delete(pObject->Subordinate_List); + free(pObject); + } +} + +/** + * Deletes a Structured View object * @param object_instance - object-instance number of the object * @return true if the object is deleted */ @@ -897,7 +1646,7 @@ bool Structured_View_Delete(uint32_t object_instance) pObject = Keylist_Data_Delete(Object_List, object_instance); if (pObject) { - free(pObject); + Structured_View_Object_Free(pObject); status = true; } @@ -905,7 +1654,7 @@ bool Structured_View_Delete(uint32_t object_instance) } /** - * Deletes all the Time Values and their data + * Deletes all the Structured View objects and their data */ void Structured_View_Cleanup(void) { @@ -914,15 +1663,21 @@ void Structured_View_Cleanup(void) if (Object_List) { do { pObject = Keylist_Data_Pop(Object_List); - if (pObject) { - free(pObject); - } + Structured_View_Object_Free(pObject); } while (pObject); Keylist_Delete(Object_List); Object_List = NULL; } } +/** + * @brief Returns the approximate size of each Structured View object data + */ +size_t Structured_View_Size(void) +{ + return sizeof(struct object_data); +} + /** * Initializes the Structured View object data */ diff --git a/src/bacnet/basic/object/structured_view.h b/src/bacnet/basic/object/structured_view.h index fccc7b95..23b6d89e 100644 --- a/src/bacnet/basic/object/structured_view.h +++ b/src/bacnet/basic/object/structured_view.h @@ -16,13 +16,19 @@ #include "bacnet/bacstr.h" #include "bacnet/bacdevobjpropref.h" #include "bacnet/rp.h" +#include "bacnet/wp.h" +/* 12.29.7 Subordinate_List + If the size of the Subordinate_List array is changed, + the size of the Subordinate_Annotations, Subordinate_Tags, + Subordinate_Relationships, and Subordinate_Node_Types arrays, + if present, shall also be changed to the same size. */ struct BACnetSubordinateData; typedef struct BACnetSubordinateData { uint32_t Device_Instance; BACNET_OBJECT_TYPE Object_Type; uint32_t Object_Instance; - const char *Annotations; + char *Annotation; BACNET_NODE_TYPE Node_Type; BACNET_RELATIONSHIP Relationship; /* simple linked list */ @@ -61,6 +67,8 @@ const char *Structured_View_Name_ASCII(uint32_t object_instance); BACNET_STACK_EXPORT int Structured_View_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata); +BACNET_STACK_EXPORT +bool Structured_View_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data); BACNET_STACK_EXPORT const char *Structured_View_Description(uint32_t object_instance); @@ -87,6 +95,9 @@ BACNET_STACK_EXPORT void Structured_View_Subordinate_List_Set( uint32_t object_instance, BACNET_SUBORDINATE_DATA *subordinate_list); BACNET_STACK_EXPORT +void Structured_View_Subordinate_List_Link_Array( + BACNET_SUBORDINATE_DATA *array, size_t size); +BACNET_STACK_EXPORT BACNET_SUBORDINATE_DATA *Structured_View_Subordinate_List_Member( uint32_t object_instance, BACNET_ARRAY_INDEX array_index); BACNET_STACK_EXPORT @@ -132,6 +143,8 @@ bool Structured_View_Delete(uint32_t object_instance); BACNET_STACK_EXPORT void Structured_View_Cleanup(void); BACNET_STACK_EXPORT +size_t Structured_View_Size(void); +BACNET_STACK_EXPORT void Structured_View_Init(void); #ifdef __cplusplus diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c index 9959622f..ec55c922 100644 --- a/src/bacnet/basic/server/bacnet_device.c +++ b/src/bacnet/basic/server/bacnet_device.c @@ -873,7 +873,7 @@ static object_functions_t My_Object_Table[] = { Structured_View_Valid_Instance, Structured_View_Object_Name, Structured_View_Read_Property, - NULL /* Write_Property */, + Structured_View_Write_Property, Structured_View_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, diff --git a/src/bacnet/wp.c b/src/bacnet/wp.c index 13a328f0..6d1341d9 100644 --- a/src/bacnet/wp.c +++ b/src/bacnet/wp.c @@ -361,6 +361,43 @@ bool write_property_type_valid( return (valid); } +/** + * @brief simple validation of character string value for Write Property + * @param wp_data - #BACNET_WRITE_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * @param value - #BACNET_CHARACTER_STRING data + * @return pointer to duplicated UTF-8 string, or NULL if invalid + */ +char *write_property_characterstring_utf8_strdup( + BACNET_WRITE_PROPERTY_DATA *wp_data, const BACNET_CHARACTER_STRING *value) +{ + char *str = NULL; /* return value */ + + if (characterstring_encoding(value) == CHARACTER_UTF8) { + if (utf8_isvalid(value->value, value->length)) { + str = characterstring_utf8_strdup(value); + if (!str) { + if (wp_data) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_NO_SPACE_TO_WRITE_PROPERTY; + } + } + } else { + if (wp_data) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + } else { + if (wp_data) { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_CHARACTER_SET_NOT_SUPPORTED; + } + } + + return str; +} + /** * @brief simple validation of character string value for Write Property * @param wp_data - #BACNET_WRITE_PROPERTY_DATA data, including diff --git a/src/bacnet/wp.h b/src/bacnet/wp.h index b1c9bbea..94c454af 100644 --- a/src/bacnet/wp.h +++ b/src/bacnet/wp.h @@ -104,6 +104,11 @@ bool write_property_type_valid( BACNET_WRITE_PROPERTY_DATA *wp_data, const BACNET_APPLICATION_DATA_VALUE *value, uint8_t expected_tag); + +BACNET_STACK_EXPORT +char *write_property_characterstring_utf8_strdup( + BACNET_WRITE_PROPERTY_DATA *wp_data, const BACNET_CHARACTER_STRING *value); + BACNET_STACK_EXPORT bool write_property_string_valid( BACNET_WRITE_PROPERTY_DATA *wp_data, diff --git a/test/bacnet/basic/object/structured_view/src/main.c b/test/bacnet/basic/object/structured_view/src/main.c index 2b8b7279..e114b12d 100644 --- a/test/bacnet/basic/object/structured_view/src/main.c +++ b/test/bacnet/basic/object/structured_view/src/main.c @@ -15,6 +15,22 @@ * @{ */ +static void Structured_View_Subordinate_List_Member_Same( + BACNET_SUBORDINATE_DATA *list_member_a, + BACNET_SUBORDINATE_DATA *list_member_b) +{ + zassert_equal( + list_member_a->Device_Instance, list_member_b->Device_Instance, NULL); + zassert_equal(list_member_a->Object_Type, list_member_b->Object_Type, NULL); + zassert_equal( + list_member_a->Object_Instance, list_member_b->Object_Instance, NULL); + zassert_equal(list_member_a->Node_Type, list_member_b->Node_Type, NULL); + zassert_equal( + list_member_a->Relationship, list_member_b->Relationship, NULL); + zassert_equal( + strcmp(list_member_a->Annotation, list_member_b->Annotation), 0, NULL); +} + /** * @brief Test */ @@ -28,23 +44,35 @@ static void test_object_structured_view(void) int diff = 0; unsigned count = 0, index = 0; const uint32_t instance = 123; + uint32_t test_instance = 0; const int32_t skip_fail_property_list[] = { -1 }; + const int32_t *writable_properties; + size_t test_size = 0; const char *test_name = "name-1234"; const char *test_description = "description-1234"; const char *test_node_subtype = "node-subtype-1234"; + char *sample_context = "context"; BACNET_NODE_TYPE test_node_type = BACNET_NODE_UNKNOWN; BACNET_RELATIONSHIP test_relationship = BACNET_RELATIONSHIP_DEFAULT; + BACNET_SUBORDINATE_DATA *test_list_member = NULL; BACNET_DEVICE_OBJECT_REFERENCE test_represents = { { OBJECT_DEVICE, 1234 }, { OBJECT_DEVICE, 1234 } }; - BACNET_SUBORDINATE_DATA test_subordinate_data = { - 1234, - OBJECT_DEVICE, - 1234, - "annotations-1234", - BACNET_NODE_UNKNOWN, - BACNET_RELATIONSHIP_DEFAULT, - NULL + BACNET_SUBORDINATE_DATA test_subordinate_data[] = { + { 0, OBJECT_ACCUMULATOR, 1, "watt-hours", BACNET_NODE_COLLECTION, + BACNET_RELATIONSHIP_CONTAINS, NULL }, + { 0, OBJECT_LOAD_CONTROL, 1, "demand-response", BACNET_NODE_COLLECTION, + BACNET_RELATIONSHIP_CONTAINS, NULL }, + { 0, OBJECT_CHANNEL, 1, "scene", BACNET_NODE_COLLECTION, + BACNET_RELATIONSHIP_CONTAINS, NULL }, + { 0, OBJECT_LIGHTING_OUTPUT, 1, "light", BACNET_NODE_COLLECTION, + BACNET_RELATIONSHIP_CONTAINS, NULL }, + { 0, OBJECT_BINARY_LIGHTING_OUTPUT, 1, "relay", BACNET_NODE_COLLECTION, + BACNET_RELATIONSHIP_CONTAINS, NULL }, + { 0, OBJECT_COLOR, 1, "color", BACNET_NODE_COLLECTION, + BACNET_RELATIONSHIP_CONTAINS, NULL }, + { 0, OBJECT_COLOR_TEMPERATURE, 1, "color-temperature", + BACNET_NODE_COLLECTION, BACNET_RELATIONSHIP_CONTAINS, NULL }, }; Structured_View_Init(); @@ -53,11 +81,26 @@ static void test_object_structured_view(void) zassert_true(status, NULL); index = Structured_View_Instance_To_Index(instance); zassert_equal(index, 0, NULL); + test_instance = Structured_View_Index_To_Instance(index); + zassert_equal(test_instance, instance, NULL); count = Structured_View_Count(); zassert_true(count > 0, NULL); + Structured_View_Subordinate_List_Link_Array( + test_subordinate_data, ARRAY_SIZE(test_subordinate_data)); + Structured_View_Subordinate_List_Set(instance, &test_subordinate_data[0]); + count = Structured_View_Subordinate_List_Count(instance); + zassert_equal(count, ARRAY_SIZE(test_subordinate_data), NULL); + for (index = 0; index < count; index++) { + test_list_member = + Structured_View_Subordinate_List_Member(instance, index); + zassert_not_null(test_list_member, NULL); + Structured_View_Subordinate_List_Member_Same( + test_list_member, &test_subordinate_data[index]); + } bacnet_object_properties_read_write_test( OBJECT_STRUCTURED_VIEW, instance, Structured_View_Property_Lists, - Structured_View_Read_Property, NULL, skip_fail_property_list); + Structured_View_Read_Property, Structured_View_Write_Property, + skip_fail_property_list); bacnet_object_name_ascii_test( instance, Structured_View_Name_Set, Structured_View_Name_ASCII); /* there is no WriteProperty test for structured view - use get/set */ @@ -94,11 +137,29 @@ static void test_object_structured_view(void) sizeof(test_represents)); zassert_equal(diff, 0, NULL); - Structured_View_Subordinate_List_Set(instance, &test_subordinate_data); - diff = memcmp( - Structured_View_Subordinate_List(instance), &test_subordinate_data, - sizeof(test_subordinate_data)); - zassert_equal(diff, 0, NULL); + /* WriteProperty test */ + Structured_View_Writable_Property_List(instance, &writable_properties); + zassert_not_null(writable_properties, NULL); + + /* property specific API */ + test_list_member = Structured_View_Subordinate_List_Member(instance, 0); + Structured_View_Subordinate_List_Member_Same( + test_list_member, &test_subordinate_data[0]); + + /* context API */ + Structured_View_Context_Set(instance, sample_context); + zassert_true(sample_context == Structured_View_Context_Get(instance), NULL); + zassert_true(NULL == Structured_View_Context_Get(instance + 1), NULL); + + test_size = Structured_View_Size(); + zassert_true(test_size > 0, NULL); + printf("Structured_View_Size: %zu bytes\n", test_size); + + Structured_View_Delete(instance); + status = Structured_View_Valid_Instance(instance); + zassert_false(status, NULL); + + Structured_View_Cleanup(); } /** * @} diff --git a/test/bacnet/basic/object/test/property_test.c b/test/bacnet/basic/object/test/property_test.c index c4a9d8cc..a95238dd 100644 --- a/test/bacnet/basic/object/test/property_test.c +++ b/test/bacnet/basic/object/test/property_test.c @@ -32,12 +32,15 @@ bool bacnet_object_property_write_test( { bool status = false; bool is_array, is_list; + uint32_t array_size = 0; if (property_list_member( skip_fail_property_list, wp_data->object_property)) { return true; } if (wp_data && write_property) { + array_size = wp_data->array_index; + wp_data->array_index = BACNET_ARRAY_ALL; status = write_property(wp_data); if (!status) { /* verify WriteProperty property is known */ @@ -51,8 +54,6 @@ bool bacnet_object_property_write_test( is_list = property_list_bacnet_list_member( wp_data->object_type, wp_data->object_property); if (is_array) { - wp_data->array_index = 0; - status = write_property(wp_data); if (!status) { zassert_not_equal( wp_data->error_code, ERROR_CODE_PROPERTY_IS_NOT_AN_ARRAY, @@ -60,6 +61,37 @@ bool bacnet_object_property_write_test( bactext_property_name(wp_data->object_property), bactext_error_code_name(wp_data->error_code)); } + /* write the first element of the array */ + if (array_size > 0) { + wp_data->array_index = 1; + status = write_property(wp_data); + if (status) { + zassert_equal( + wp_data->error_code, ERROR_CODE_SUCCESS, NULL); + } else { + zassert_not_equal( + wp_data->error_code, ERROR_CODE_NO_SPACE_FOR_OBJECT, + "property=%s array_index=1: error code=%s.\n", + bactext_property_name(wp_data->object_property), + bactext_error_code_name(wp_data->error_code)); + } + } + /* attempt to resize the array to the existing size */ + wp_data->application_data_len = bacnet_unsigned_application_encode( + wp_data->application_data, sizeof(wp_data->application_data), + array_size); + wp_data->array_index = 0; + status = write_property(wp_data); + if (status) { + zassert_equal(wp_data->error_code, ERROR_CODE_SUCCESS, NULL); + } else { + zassert_not_equal( + wp_data->error_code, ERROR_CODE_NO_SPACE_FOR_OBJECT, + "property=%s array_index=%u: error code=%s.\n", + bactext_property_name(wp_data->object_property), + wp_data->array_index, + bactext_error_code_name(wp_data->error_code)); + } } if (commandable) { wp_data->priority = 16; @@ -152,7 +184,7 @@ int bacnet_object_property_read_test( int read_len = 0; uint8_t *apdu; bool is_array, is_list; - BACNET_UNSIGNED_INTEGER array_size; + BACNET_UNSIGNED_INTEGER array_size = BACNET_ARRAY_ALL; BACNET_ARRAY_INDEX array_index = 0, i; BACNET_APPLICATION_DATA_VALUE value = { 0 }; @@ -271,6 +303,18 @@ int bacnet_object_property_read_test( "property '%s' array_index=%u: error code is %s.\n", bactext_property_name(rpdata->object_property), rpdata->array_index, bactext_error_code_name(rpdata->error_code)); + /* exit the test for array properties with the array index + set to the size and the ALL array elements in + the application data */ + rpdata->array_index = BACNET_ARRAY_ALL; + read_len = read_property(rpdata); + zassert_not_equal( + read_len, BACNET_STATUS_ERROR, + "property '%s' array_index=%u: error code is %s.\n", + bactext_property_name(rpdata->object_property), rpdata->array_index, + bactext_error_code_name(rpdata->error_code)); + rpdata->array_index = array_size; + len = read_len; } return len; @@ -419,7 +463,7 @@ void bacnet_object_name_ascii_test( status = ascii_set(object_instance, sample_name); zassert_true(status, NULL); test_name = ascii_get(object_instance); - zassert_equal(test_name, sample_name, NULL); + zassert_equal(bacnet_strcmp(test_name, sample_name), 0, NULL); status = ascii_set(object_instance, NULL); zassert_true(status, NULL); test_name = ascii_get(object_instance);