Add octet string buffer duplication functions and refactor related structures (#1266)
* Add new functions for duplicating and copying octet string buffers. * Move and update octet string value object structure with description string and add object-name and description WriteProperty handling using strdup. Add functional tests to ensure the correctness of the new features.
This commit is contained in:
+5
-1
@@ -13,7 +13,7 @@ The git repositories are hosted at the following sites:
|
||||
* <https://bacnet.sourceforge.net/>
|
||||
* <https://github.com/bacnet-stack/bacnet-stack/>
|
||||
|
||||
## [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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+199
-55
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user