diff --git a/CHANGELOG.md b/CHANGELOG.md index 590909ab..dfe3e2a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ The git repositories are hosted at the following sites: * * -## [Unreleased] - 2026-03-16 +## [Unreleased] - 2026-03-18 ### Security @@ -53,6 +53,10 @@ The git repositories are hosted at the following sites: ### Added +* Added new functions for duplicating and copying octet string buffers. + Refactor existing OctetString Value object to enable writes to object name + description, and present-value, with functional tests to ensure + the correctness of the new features. (#1266) * Added octetstring_length_value_same() API for comparing an OctetString to value and len parameters. Added verification tests. (#1264) * Added property_list_read_only_member function to check for READ-ONLY diff --git a/src/bacnet/bacdcode.h b/src/bacnet/bacdcode.h index 159924ed..ed185899 100644 --- a/src/bacnet/bacdcode.h +++ b/src/bacnet/bacdcode.h @@ -88,19 +88,6 @@ typedef struct BACnetTag { uint32_t len_value_type; } BACNET_TAG; -typedef struct BACnetCharacterStringBuffer { - uint8_t encoding; - char *buffer; - size_t buffer_size; - uint32_t buffer_length; -} BACNET_CHARACTER_STRING_BUFFER; - -typedef struct BACnetOctetStringBuffer { - uint8_t *buffer; - size_t buffer_size; - uint32_t buffer_length; -} BACNET_OCTET_STRING_BUFFER; - /* max size of a BACnet tag */ #define BACNET_TAG_SIZE 7 diff --git a/src/bacnet/bacstr.c b/src/bacnet/bacstr.c index 1cab8a8f..e9e3a4cb 100644 --- a/src/bacnet/bacstr.c +++ b/src/bacnet/bacstr.c @@ -1377,6 +1377,102 @@ bool octetstring_value_same( } #endif +/** + * @brief Duplicates an octet string value and length to a buffer structure. + * @param dest Pointer to destination buffer structure. + * @param value Pointer to the byte array to be copied to the buffer structure. + * @param length Count of bytes to be copied to the buffer structure. + * @return true if copy is successful. + */ +bool octetstring_buffer_duplicate( + BACNET_OCTET_STRING_BUFFER *dest, const uint8_t *value, size_t length) +{ + uint8_t *new_buffer = NULL; + bool status = false; + + if (dest && value) { + if (dest->buffer && (length <= dest->buffer_size)) { + dest->buffer_length = length; + if (length > 0) { + memcpy(dest->buffer, value, length); + } + status = true; + } else if (length > 0) { + new_buffer = realloc(dest->buffer, length); + if (new_buffer) { + dest->buffer = new_buffer; + dest->buffer_length = length; + dest->buffer_size = length; + memcpy(dest->buffer, value, length); + status = true; + } + } else { + /* length is zero, so just set the length and return true */ + dest->buffer_length = 0; + status = true; + } + } + + return status; +} + +/** + * @brief Duplicates an octet string value to a buffer structure. + * @param dest Pointer to destination buffer structure. + * @param src Pointer to source octet string structure. + * @return true if copy is successful. + */ +bool octetstring_to_buffer_duplicate( + BACNET_OCTET_STRING_BUFFER *dest, const BACNET_OCTET_STRING *src) +{ + if (!src) { + return false; + } + return octetstring_buffer_duplicate(dest, src->value, src->length); +} + +/** + * @brief Copies an octet string value to a buffer structure. + * @param dest Pointer to destination buffer structure. + * @param src Pointer to source octet string structure. + * @return true if copy is successful. + */ +bool octetstring_to_buffer_copy( + BACNET_OCTET_STRING_BUFFER *dest, const BACNET_OCTET_STRING *src) +{ + bool status = false; + + if (dest && src) { + if (src->length <= dest->buffer_size) { + dest->buffer_length = src->length; + if (src->length > 0) { + memcpy(dest->buffer, src->value, src->length); + } + status = true; + } + } + + return status; +} + +/** + * @brief Copies an octet string value from a buffer structure. + * @param dest Pointer to destination octet string structure. + * @param src Pointer to source buffer structure. + * @return true if copy is successful. + */ +bool octetstring_from_buffer_copy( + BACNET_OCTET_STRING *dest, const BACNET_OCTET_STRING_BUFFER *src) +{ + if (dest && src && src->buffer && + (src->buffer_length <= sizeof(dest->value))) { + memcpy(dest->value, src->buffer, src->buffer_length); + dest->length = src->buffer_length; + return true; + } + return false; +} + /** * @brief Compare two strings, case sensitive or insensitive, with length limit * @details The strncmp() function compares, at most, the first n characters diff --git a/src/bacnet/bacstr.h b/src/bacnet/bacstr.h index 1e15615e..af48b5d5 100644 --- a/src/bacnet/bacstr.h +++ b/src/bacnet/bacstr.h @@ -21,17 +21,34 @@ typedef struct BACnet_Bit_String { uint8_t value[MAX_BITSTRING_BYTES]; } BACNET_BIT_STRING; +/* fixed size buffer version of Character String */ typedef struct BACnet_Character_String { size_t length; uint8_t encoding; char value[MAX_CHARACTER_STRING_BYTES]; } BACNET_CHARACTER_STRING; +/* fixed size buffer version of Octet String */ typedef struct BACnet_Octet_String { size_t length; uint8_t value[MAX_OCTET_STRING_BYTES]; } BACNET_OCTET_STRING; +/* buffer pointer version of Character String */ +typedef struct BACnetCharacterStringBuffer { + uint8_t encoding; + char *buffer; + size_t buffer_size; + uint32_t buffer_length; +} BACNET_CHARACTER_STRING_BUFFER; + +/* buffer pointer version of Octet String */ +typedef struct BACnetOctetStringBuffer { + uint8_t *buffer; + size_t buffer_size; + uint32_t buffer_length; +} BACNET_OCTET_STRING_BUFFER; + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -183,6 +200,19 @@ bool octetstring_value_same( const BACNET_OCTET_STRING *octet_string1, const BACNET_OCTET_STRING *octet_string2); +BACNET_STACK_EXPORT +bool octetstring_buffer_duplicate( + BACNET_OCTET_STRING_BUFFER *dest, const uint8_t *value, size_t length); +BACNET_STACK_EXPORT +bool octetstring_to_buffer_duplicate( + BACNET_OCTET_STRING_BUFFER *dest, const BACNET_OCTET_STRING *src); +BACNET_STACK_EXPORT +bool octetstring_to_buffer_copy( + BACNET_OCTET_STRING_BUFFER *dest, const BACNET_OCTET_STRING *src); +BACNET_STACK_EXPORT +bool octetstring_from_buffer_copy( + BACNET_OCTET_STRING *dest, const BACNET_OCTET_STRING_BUFFER *src); + BACNET_STACK_EXPORT int bacnet_strcmp(const char *a, const char *b); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/object/osv.c b/src/bacnet/basic/object/osv.c index b93cf834..0dbdae6a 100644 --- a/src/bacnet/basic/object/osv.c +++ b/src/bacnet/basic/object/osv.c @@ -15,18 +15,20 @@ /* BACnet Stack API */ #include "bacnet/bacdcode.h" #include "bacnet/bacapp.h" +#include "bacnet/bacstr.h" #include "bacnet/bactext.h" #include "bacnet/basic/object/device.h" #include "bacnet/basic/services.h" #include "bacnet/basic/sys/keylist.h" #include "bacnet/basic/object/osv.h" -typedef struct octetstring_value_descr { +struct object_data { unsigned Event_State : 3; - bool Out_Of_Service; - BACNET_OCTET_STRING Present_Value; - const char *Object_Name; -} OCTETSTRING_VALUE_DESCR; + bool Out_Of_Service : 1; + BACNET_OCTET_STRING_BUFFER Present_Value; + char *Object_Name; + char *Description; +}; /* Key List for storing object data sorted by instance number */ static OS_Keylist Object_List = NULL; @@ -97,8 +99,7 @@ void OctetString_Value_Writable_Property_List( * @param object_instance Object instance number. * @return Pointer to object descriptor, or NULL if not found. */ -static OCTETSTRING_VALUE_DESCR * -OctetString_Value_Object(uint32_t object_instance) +static struct object_data *OctetString_Value_Object(uint32_t object_instance) { return Keylist_Data(Object_List, object_instance); } @@ -111,7 +112,7 @@ OctetString_Value_Object(uint32_t object_instance) */ uint32_t OctetString_Value_Create(uint32_t object_instance) { - OCTETSTRING_VALUE_DESCR *pObject = NULL; + struct object_data *pObject = NULL; int index = 0; if (!Object_List) { @@ -124,7 +125,7 @@ uint32_t OctetString_Value_Create(uint32_t object_instance) } pObject = OctetString_Value_Object(object_instance); if (!pObject) { - pObject = calloc(1, sizeof(OCTETSTRING_VALUE_DESCR)); + pObject = calloc(1, sizeof(struct object_data)); if (!pObject) { return BACNET_MAX_INSTANCE; } @@ -133,8 +134,6 @@ uint32_t OctetString_Value_Create(uint32_t object_instance) free(pObject); return BACNET_MAX_INSTANCE; } - octetstring_init(&pObject->Present_Value, NULL, 0); - pObject->Out_Of_Service = false; pObject->Event_State = EVENT_STATE_NORMAL; } @@ -148,10 +147,13 @@ uint32_t OctetString_Value_Create(uint32_t object_instance) */ bool OctetString_Value_Delete(uint32_t object_instance) { - OCTETSTRING_VALUE_DESCR *pObject = NULL; + struct object_data *pObject = NULL; pObject = Keylist_Data_Delete(Object_List, object_instance); if (pObject) { + free(pObject->Description); + free(pObject->Object_Name); + free(pObject->Present_Value.buffer); free(pObject); return true; } @@ -231,42 +233,19 @@ bool OctetString_Value_Present_Value_Set( const BACNET_OCTET_STRING *value, uint8_t priority) { - OCTETSTRING_VALUE_DESCR *pObject = NULL; + struct object_data *pObject = NULL; bool status = false; (void)priority; pObject = OctetString_Value_Object(object_instance); if (pObject) { - octetstring_copy(&pObject->Present_Value, value); + octetstring_to_buffer_duplicate(&pObject->Present_Value, value); status = true; } return status; } -/** - * @brief Sets the present value length for an Octet String Value object. - * @param object_instance Object instance number. - * @param value Pointer to octet string value. - * @param length Length of the octet string value. - * @param priority Write priority (1..16). - * @return true if values are within range and present value length is set. - */ -bool OctetString_Value_Present_Value_Length_Set( - uint32_t object_instance, uint8_t *value, size_t length, uint8_t priority) -{ - OCTETSTRING_VALUE_DESCR *pObject = NULL; - bool status = false; - - (void)priority; - pObject = OctetString_Value_Object(object_instance); - if (pObject) { - status = octetstring_init(&pObject->Present_Value, value, length); - } - - return status; -} - /** * @brief Gets the present value for an Octet String Value object. * @param object_instance Object instance number. @@ -276,40 +255,70 @@ bool OctetString_Value_Present_Value_Length_Set( bool OctetString_Value_Present_Value_Get( uint32_t object_instance, BACNET_OCTET_STRING *value) { - OCTETSTRING_VALUE_DESCR *pObject = NULL; + struct object_data *pObject = NULL; bool status = false; pObject = OctetString_Value_Object(object_instance); if (pObject) { - status = octetstring_copy(value, &pObject->Present_Value); + status = octetstring_from_buffer_copy(value, &pObject->Present_Value); } return status; } /** - * @brief Gets the present value length for an Octet String Value object. + * @brief Sets the present value buffer and length for this object. + * @param object_instance Object instance number. + * @param value Pointer to octet string value. + * @param length Length of the octet string value. + * @param priority Write priority (1..16). + * @return true if values are within range and present value length is set. + */ +bool OctetString_Value_Present_Value_Buffer_Set( + uint32_t object_instance, uint8_t *value, size_t length, uint8_t priority) +{ + struct object_data *pObject = NULL; + bool status = false; + + (void)priority; + pObject = OctetString_Value_Object(object_instance); + if (pObject) { + status = octetstring_buffer_duplicate( + &pObject->Present_Value, value, length); + } + + return status; +} + +/** + * @brief Gets the present value buffer and length for this object. * @param object_instance Object instance number. * @param value Pointer to buffer to receive octet string value. * @param value_size Size of the value buffer. * @param length Pointer to receive length of the octet string value. * @return true if object exists and value length is returned. */ -bool OctetString_Value_Present_Value_Length( +bool OctetString_Value_Present_Value_Buffer_Get( uint32_t object_instance, uint8_t *value, size_t value_size, size_t *length) { - OCTETSTRING_VALUE_DESCR *pObject = NULL; - size_t value_length = 0; + struct object_data *pObject = NULL; bool status = false; + size_t copy_length = 0; pObject = OctetString_Value_Object(object_instance); if (pObject) { - value_length = octetstring_length(&pObject->Present_Value); if (length) { - *length = value_length; + *length = pObject->Present_Value.buffer_length; } - if (value && (value_size >= value_length)) { - memcpy(value, pObject->Present_Value.value, value_length); + if (value) { + if (value_size > pObject->Present_Value.buffer_length) { + copy_length = pObject->Present_Value.buffer_length; + } else { + copy_length = value_size; + } + if (copy_length > 0) { + memcpy(value, pObject->Present_Value.buffer, copy_length); + } } status = true; } @@ -328,7 +337,7 @@ bool OctetString_Value_Object_Name( { char text[32] = ""; bool status = false; - OCTETSTRING_VALUE_DESCR *pObject = NULL; + struct object_data *pObject = NULL; pObject = OctetString_Value_Object(object_instance); if (pObject) { @@ -356,12 +365,13 @@ bool OctetString_Value_Object_Name( bool OctetString_Value_Name_Set(uint32_t object_instance, const char *new_name) { bool status = false; /* return value */ - OCTETSTRING_VALUE_DESCR *pObject; + struct object_data *pObject; pObject = OctetString_Value_Object(object_instance); if (pObject) { status = true; - pObject->Object_Name = new_name; + free(pObject->Object_Name); + pObject->Object_Name = bacnet_strdup(new_name); } return status; @@ -375,7 +385,7 @@ bool OctetString_Value_Name_Set(uint32_t object_instance, const char *new_name) const char *OctetString_Value_Name_ASCII(uint32_t object_instance) { const char *name = NULL; - OCTETSTRING_VALUE_DESCR *pObject; + struct object_data *pObject; pObject = OctetString_Value_Object(object_instance); if (pObject) { @@ -385,6 +395,54 @@ const char *OctetString_Value_Name_ASCII(uint32_t object_instance) return name; } +/** + * For a given object instance-number, returns the description + * + * @param object_instance - object-instance number of the object + * + * @return description text or NULL if not found + */ +const char *OctetString_Value_Description(uint32_t object_instance) +{ + const char *name = NULL; + const struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + if (pObject->Description) { + name = pObject->Description; + } else { + name = ""; + } + } + + return name; +} + +/** + * For a given object instance-number, sets the description + * + * @param object_instance - object-instance number of the object + * @param new_name - holds the description to be set + * + * @return true if description was set + */ +bool OctetString_Value_Description_Set( + uint32_t object_instance, const char *new_name) +{ + bool status = false; /* return value */ + struct object_data *pObject; + + pObject = Keylist_Data(Object_List, object_instance); + if (pObject) { + status = true; + free(pObject->Description); + pObject->Description = bacnet_strdup(new_name); + } + + return status; +} + /** * For a given object instance-number, returns the out-of-service * property value @@ -396,7 +454,7 @@ const char *OctetString_Value_Name_ASCII(uint32_t object_instance) bool OctetString_Value_Out_Of_Service(uint32_t object_instance) { bool value = false; - OCTETSTRING_VALUE_DESCR *pObject; + struct object_data *pObject; pObject = OctetString_Value_Object(object_instance); if (pObject) { @@ -416,7 +474,7 @@ bool OctetString_Value_Out_Of_Service(uint32_t object_instance) */ bool OctetString_Value_Out_Of_Service_Set(uint32_t object_instance, bool value) { - OCTETSTRING_VALUE_DESCR *pObject; + struct object_data *pObject; pObject = OctetString_Value_Object(object_instance); if (pObject) { @@ -452,7 +510,6 @@ int OctetString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) break; case PROP_OBJECT_NAME: - case PROP_DESCRIPTION: OctetString_Value_Object_Name( rpdata->object_instance, &char_string); apdu_len = @@ -487,10 +544,18 @@ int OctetString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) encode_application_enumerated(&apdu[0], EVENT_STATE_NORMAL); break; + case PROP_DESCRIPTION: + characterstring_init_ansi( + &char_string, + OctetString_Value_Description(rpdata->object_instance)); + apdu_len = encode_application_character_string(apdu, &char_string); + break; + case PROP_OUT_OF_SERVICE: state = OctetString_Value_Out_Of_Service(rpdata->object_instance); apdu_len = encode_application_boolean(&apdu[0], state); break; + default: rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY; @@ -501,6 +566,70 @@ int OctetString_Value_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 OctetString_Value_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 OctetString_Value_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; +} + /** * @brief Processes a write-property request for an Octet String Value object. * @param wp_data Write property request/response context. @@ -551,7 +680,22 @@ bool OctetString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data) } } break; - + case PROP_OBJECT_NAME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_CHARACTER_STRING); + if (status) { + status = OctetString_Value_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 = OctetString_Value_Description_Write( + wp_data, &value.type.Character_String); + } + break; case PROP_OUT_OF_SERVICE: status = write_property_type_valid( wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN); diff --git a/src/bacnet/basic/object/osv.h b/src/bacnet/basic/object/osv.h index 13b18f3f..bbdc6bde 100644 --- a/src/bacnet/basic/object/osv.h +++ b/src/bacnet/basic/object/osv.h @@ -65,10 +65,10 @@ BACNET_STACK_EXPORT BACNET_OCTET_STRING *OctetString_Value_Present_Value(uint32_t object_instance); BACNET_STACK_EXPORT -bool OctetString_Value_Present_Value_Length_Set( +bool OctetString_Value_Present_Value_Buffer_Set( uint32_t object_instance, uint8_t *value, size_t length, uint8_t priority); BACNET_STACK_EXPORT -bool OctetString_Value_Present_Value_Length( +bool OctetString_Value_Present_Value_Buffer_Get( uint32_t object_instance, uint8_t *value, size_t value_size, @@ -83,7 +83,7 @@ bool OctetString_Value_Encode_Value_List( uint32_t object_instance, BACNET_PROPERTY_VALUE *value_list); BACNET_STACK_EXPORT -char *OctetString_Value_Description(uint32_t instance); +const char *OctetString_Value_Description(uint32_t instance); BACNET_STACK_EXPORT bool OctetString_Value_Description_Set(uint32_t instance, const char *new_name); diff --git a/src/bacnet/basic/object/structured_view.c b/src/bacnet/basic/object/structured_view.c index eac8f4df..9cee412b 100644 --- a/src/bacnet/basic/object/structured_view.c +++ b/src/bacnet/basic/object/structured_view.c @@ -203,7 +203,7 @@ bool Structured_View_Object_Name( { bool status = false; struct object_data *pObject; - char name_text[48] = "Structured-View-4194303"; + char name_text[48] = ""; pObject = Keylist_Data(Object_List, object_instance); if (pObject) { @@ -293,7 +293,7 @@ const char *Structured_View_Description(uint32_t object_instance) * @param object_instance - object-instance number of the object * @param new_name - holds the description to be set * - * @return true if object-name was set + * @return true if description was set */ bool Structured_View_Description_Set( uint32_t object_instance, const char *new_name) diff --git a/test/bacnet/bacstr/src/main.c b/test/bacnet/bacstr/src/main.c index fe6bd7f4..c648bd53 100644 --- a/test/bacnet/bacstr/src/main.c +++ b/test/bacnet/bacstr/src/main.c @@ -123,6 +123,38 @@ static void testBitString(void) zassert_equal( bitstring_bits_capacity(&bit_string), (MAX_BITSTRING_BYTES * 8), NULL); zassert_equal(bitstring_bits_capacity(NULL), 0, NULL); + + /* bitstring_init_ascii negative tests */ + /* test NULL bit_string pointer */ + status = bitstring_init_ascii(NULL, "1111"); + zassert_false(status, "NULL bit_string should return false"); + /* test empty ascii string */ + status = bitstring_init_ascii(&bit_string, ""); + zassert_true(status, "Empty string should return true"); + zassert_equal( + bitstring_bits_used(&bit_string), 0, "Empty string should have 0 bits"); + /* test ascii string with only invalid characters */ + status = bitstring_init_ascii(&bit_string, "xyz-._:"); + zassert_false(status, "String with only invalid chars should return false"); + /* test string that exceeds capacity by 10 bits */ + char overflow_string[((MAX_BITSTRING_BYTES * 8) + 10) + 1] = { 0 }; + memset(overflow_string, '1', ((MAX_BITSTRING_BYTES * 8) + 10)); + status = bitstring_init_ascii(&bit_string, overflow_string); + zassert_false(status, "String exceeding capacity should return false"); + /* test valid string at exact capacity boundary */ + char capacity_string[(MAX_BITSTRING_BYTES * 8) + 1] = { 0 }; + memset(capacity_string, '1', MAX_BITSTRING_BYTES * 8); + status = bitstring_init_ascii(&bit_string, capacity_string); + zassert_true(status, "String at exact capacity should return true"); + zassert_equal( + bitstring_bits_used(&bit_string), MAX_BITSTRING_BYTES * 8, + "Should have max bits"); + /* test string with mixed valid and invalid characters */ + status = bitstring_init_ascii(&bit_string, "1a1b0c0d1e0"); + zassert_true(status, "Mixed valid/invalid chars should skip invalid ones"); + zassert_equal( + bitstring_bits_used(&bit_string), 6, + "Should have 6 bits from valid chars: 110010"); } /** @@ -503,11 +535,19 @@ static void testOctetString(void) { BACNET_OCTET_STRING bacnet_string; BACNET_OCTET_STRING bacnet_string_twin; + BACNET_OCTET_STRING_BUFFER octet_string_buffer; + BACNET_OCTET_STRING_BUFFER octet_string_buffer_src; uint8_t *value = NULL; uint8_t test_value[MAX_APDU] = "Patricia"; uint8_t test_value_twin[MAX_APDU] = "PATRICIA"; uint8_t test_append_value[MAX_APDU] = " and the Kids"; uint8_t test_append_string[MAX_APDU] = ""; + uint8_t duplicate_value[] = { 0x01, 0x23, 0x45, 0x67, 0x89 }; + uint8_t duplicate_value_realloc[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B }; + uint8_t from_buffer_value[] = { 0xA5, 0x5A, 0xC3 }; + uint8_t max_octet_string_value[MAX_OCTET_STRING_BYTES] = { 0 }; + uint8_t over_limit_value[MAX_OCTET_STRING_BYTES + 1] = { 0 }; const char *hex_value_valid = "1234567890ABCDEF"; const char *hex_value_skips = "12:34:56:78:90:AB:CD:EF"; const char *hex_value_odd = "1234567890ABCDE"; @@ -634,6 +674,156 @@ static void testOctetString(void) zassert_true(status, NULL); status = octetstring_value_same(&bacnet_string_twin, &bacnet_string); zassert_true(status, NULL); + + /* to buffer duplicate */ + memset(&octet_string_buffer, 0, sizeof(octet_string_buffer)); + octet_string_buffer.buffer_size = 8; + octet_string_buffer.buffer = malloc(octet_string_buffer.buffer_size); + zassert_not_null(octet_string_buffer.buffer, NULL); + test_length = sizeof(duplicate_value); + status = octetstring_init(&bacnet_string, duplicate_value, test_length); + zassert_true(status, NULL); + status = + octetstring_to_buffer_duplicate(&octet_string_buffer, &bacnet_string); + zassert_true(status, NULL); + zassert_equal(octet_string_buffer.buffer_length, test_length, NULL); + zassert_equal( + memcmp(octet_string_buffer.buffer, duplicate_value, test_length), 0, + NULL); + status = octetstring_to_buffer_duplicate(NULL, &bacnet_string); + zassert_false(status, NULL); + status = octetstring_to_buffer_duplicate(&octet_string_buffer, NULL); + zassert_false(status, NULL); + status = octetstring_init( + &bacnet_string, duplicate_value_realloc, + sizeof(duplicate_value_realloc)); + zassert_true(status, NULL); + status = + octetstring_to_buffer_duplicate(&octet_string_buffer, &bacnet_string); + zassert_true(status, NULL); + zassert_equal( + octet_string_buffer.buffer_size, sizeof(duplicate_value_realloc), NULL); + zassert_equal( + octet_string_buffer.buffer_length, sizeof(duplicate_value_realloc), + NULL); + zassert_equal( + memcmp( + octet_string_buffer.buffer, duplicate_value_realloc, + sizeof(duplicate_value_realloc)), + 0, NULL); + status = octetstring_init(&bacnet_string, NULL, 0); + zassert_true(status, NULL); + status = + octetstring_to_buffer_duplicate(&octet_string_buffer, &bacnet_string); + zassert_true(status, NULL); + zassert_equal(octet_string_buffer.buffer_length, 0, NULL); + free(octet_string_buffer.buffer); + + /* from buffer copy */ + memset(&octet_string_buffer_src, 0, sizeof(octet_string_buffer_src)); + octet_string_buffer_src.buffer = from_buffer_value; + octet_string_buffer_src.buffer_size = sizeof(from_buffer_value); + octet_string_buffer_src.buffer_length = sizeof(from_buffer_value); + status = + octetstring_from_buffer_copy(&bacnet_string, &octet_string_buffer_src); + zassert_true(status, NULL); + zassert_equal(bacnet_string.length, sizeof(from_buffer_value), NULL); + zassert_equal( + memcmp( + bacnet_string.value, from_buffer_value, sizeof(from_buffer_value)), + 0, NULL); + octet_string_buffer_src.buffer_length = 0; + status = + octetstring_from_buffer_copy(&bacnet_string, &octet_string_buffer_src); + zassert_true(status, NULL); + zassert_equal(bacnet_string.length, 0, NULL); + for (i = 0; i < MAX_OCTET_STRING_BYTES; i++) { + max_octet_string_value[i] = (uint8_t)i; + } + octet_string_buffer_src.buffer = max_octet_string_value; + octet_string_buffer_src.buffer_size = sizeof(max_octet_string_value); + octet_string_buffer_src.buffer_length = sizeof(max_octet_string_value); + status = + octetstring_from_buffer_copy(&bacnet_string, &octet_string_buffer_src); + zassert_true(status, NULL); + zassert_equal(bacnet_string.length, sizeof(max_octet_string_value), NULL); + zassert_equal( + memcmp( + bacnet_string.value, max_octet_string_value, + sizeof(max_octet_string_value)), + 0, NULL); + status = octetstring_from_buffer_copy(NULL, &octet_string_buffer_src); + zassert_false(status, NULL); + status = octetstring_from_buffer_copy(&bacnet_string, NULL); + zassert_false(status, NULL); + status = octetstring_init( + &bacnet_string, duplicate_value, sizeof(duplicate_value)); + zassert_true(status, NULL); + for (i = 0; i < sizeof(over_limit_value); i++) { + over_limit_value[i] = (uint8_t)i; + } + octet_string_buffer_src.buffer = over_limit_value; + octet_string_buffer_src.buffer_size = sizeof(over_limit_value); + octet_string_buffer_src.buffer_length = sizeof(over_limit_value); + status = + octetstring_from_buffer_copy(&bacnet_string, &octet_string_buffer_src); + zassert_false(status, NULL); + zassert_equal(bacnet_string.length, sizeof(duplicate_value), NULL); + zassert_equal( + memcmp(bacnet_string.value, duplicate_value, sizeof(duplicate_value)), + 0, NULL); + + /* to buffer copy (no reallocation, fixed-size copy) */ + memset(&octet_string_buffer, 0, sizeof(octet_string_buffer)); + octet_string_buffer.buffer_size = 8; + octet_string_buffer.buffer = malloc(octet_string_buffer.buffer_size); + zassert_not_null(octet_string_buffer.buffer, NULL); + test_length = sizeof(duplicate_value); + status = octetstring_init(&bacnet_string, duplicate_value, test_length); + zassert_true(status, NULL); + status = octetstring_to_buffer_copy(&octet_string_buffer, &bacnet_string); + zassert_true(status, NULL); + zassert_equal(octet_string_buffer.buffer_length, test_length, NULL); + zassert_equal(octet_string_buffer.buffer_size, 8, NULL); + zassert_equal( + memcmp(octet_string_buffer.buffer, duplicate_value, test_length), 0, + NULL); + status = octetstring_to_buffer_copy(NULL, &bacnet_string); + zassert_false(status, NULL); + status = octetstring_to_buffer_copy(&octet_string_buffer, NULL); + zassert_false(status, NULL); + /* test copy with empty string */ + status = octetstring_init(&bacnet_string, NULL, 0); + zassert_true(status, NULL); + status = octetstring_to_buffer_copy(&octet_string_buffer, &bacnet_string); + zassert_true(status, NULL); + zassert_equal(octet_string_buffer.buffer_length, 0, NULL); + zassert_equal(octet_string_buffer.buffer_size, 8, NULL); + /* test copy failure when data exceeds buffer size */ + status = octetstring_init( + &bacnet_string, duplicate_value_realloc, + sizeof(duplicate_value_realloc)); + zassert_true(status, NULL); + status = octetstring_to_buffer_copy(&octet_string_buffer, &bacnet_string); + zassert_false(status, "Copy should fail when src length > buffer_size"); + zassert_equal( + octet_string_buffer.buffer_size, 8, + "Buffer size should not change on failed copy"); + /* test copy fits exactly at buffer boundary */ + octet_string_buffer.buffer_size = sizeof(duplicate_value); + status = octetstring_init( + &bacnet_string, duplicate_value, sizeof(duplicate_value)); + zassert_true(status, NULL); + status = octetstring_to_buffer_copy(&octet_string_buffer, &bacnet_string); + zassert_true(status, "Copy should succeed when size equals buffer_size"); + zassert_equal( + octet_string_buffer.buffer_length, sizeof(duplicate_value), NULL); + zassert_equal( + memcmp( + octet_string_buffer.buffer, duplicate_value, + sizeof(duplicate_value)), + 0, NULL); + free(octet_string_buffer.buffer); } /**