Feature/octetstring value and length api (#1264)

* feat: add octetstring_length_value_same function for length and content comparison

* feat: add tests for characterstring UTF-8 validation

* feat: add tests for octetstring_init_ascii_epics validation

* feat: add test for bacnet_strdup string duplication validation

* feat: add utf8_isvalid test cases

* feat: add octet string value API for present-value octet-string value and length, and out-of-service properties
This commit is contained in:
Steve Karg
2026-03-16 17:04:23 -05:00
committed by GitHub
parent 07cf52384b
commit a755a8db4d
6 changed files with 563 additions and 59 deletions
+37 -13
View File
@@ -1324,7 +1324,38 @@ size_t octetstring_capacity(const BACNET_OCTET_STRING *octet_string)
}
/**
* @brief Returns true if the same length and contents.
* @brief Returns true if the same length and value contents.
*
* @param octet_string1 Pointer to the first octet string.
* @param length Length of the second octet string.
* @param value Pointer to the value of the second octet string.
*
* @return true if the octet strings are the same, false otherwise.
*/
bool octetstring_length_value_same(
const BACNET_OCTET_STRING *octet_string1,
size_t length,
const uint8_t *value)
{
size_t i = 0; /* loop counter */
if (octet_string1 && value) {
if ((octet_string1->length == length) &&
(octet_string1->length <= MAX_OCTET_STRING_BYTES)) {
for (i = 0; i < octet_string1->length; i++) {
if (octet_string1->value[i] != value[i]) {
return false;
}
}
return true;
}
}
return false;
}
/**
* @brief Returns true if the same length and value contents.
*
* @param octet_string1 Pointer to the first octet string.
* @param octet_string2 Pointer to the second octet string.
@@ -1335,21 +1366,14 @@ bool octetstring_value_same(
const BACNET_OCTET_STRING *octet_string1,
const BACNET_OCTET_STRING *octet_string2)
{
size_t i = 0; /* loop counter */
bool status = false;
if (octet_string1 && octet_string2) {
if ((octet_string1->length == octet_string2->length) &&
(octet_string1->length <= MAX_OCTET_STRING_BYTES)) {
for (i = 0; i < octet_string1->length; i++) {
if (octet_string1->value[i] != octet_string2->value[i]) {
return false;
}
}
return true;
}
if (octet_string2) {
status = octetstring_length_value_same(
octet_string1, octet_string2->length, octet_string2->value);
}
return false;
return status;
}
#endif
+5 -1
View File
@@ -173,7 +173,11 @@ BACNET_STACK_EXPORT
size_t octetstring_length(const BACNET_OCTET_STRING *octet_string);
BACNET_STACK_EXPORT
size_t octetstring_capacity(const BACNET_OCTET_STRING *octet_string);
/* returns true if the same length and contents */
BACNET_STACK_EXPORT
bool octetstring_length_value_same(
const BACNET_OCTET_STRING *octet_string1,
size_t length,
const uint8_t *value);
BACNET_STACK_EXPORT
bool octetstring_value_same(
const BACNET_OCTET_STRING *octet_string1,
+118 -34
View File
@@ -21,6 +21,13 @@
#include "bacnet/basic/sys/keylist.h"
#include "bacnet/basic/object/osv.h"
typedef struct octetstring_value_descr {
unsigned Event_State : 3;
bool Out_Of_Service;
BACNET_OCTET_STRING Present_Value;
const char *Object_Name;
} OCTETSTRING_VALUE_DESCR;
/* Key List for storing object data sorted by instance number */
static OS_Keylist Object_List = NULL;
@@ -237,22 +244,77 @@ bool OctetString_Value_Present_Value_Set(
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.
* @return Pointer to present value, or NULL if object does not exist.
* @param value Pointer to octet string structure to receive the value.
* @return true if object exists and value is returned.
*/
BACNET_OCTET_STRING *OctetString_Value_Present_Value(uint32_t object_instance)
bool OctetString_Value_Present_Value_Get(
uint32_t object_instance, BACNET_OCTET_STRING *value)
{
BACNET_OCTET_STRING *value = NULL;
OCTETSTRING_VALUE_DESCR *pObject = NULL;
bool status = false;
pObject = OctetString_Value_Object(object_instance);
if (pObject) {
value = &pObject->Present_Value;
status = octetstring_copy(value, &pObject->Present_Value);
}
return value;
return status;
}
/**
* @brief Gets the present value length for an Octet String Value 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(
uint32_t object_instance, uint8_t *value, size_t value_size, size_t *length)
{
OCTETSTRING_VALUE_DESCR *pObject = NULL;
size_t value_length = 0;
bool status = false;
pObject = OctetString_Value_Object(object_instance);
if (pObject) {
value_length = octetstring_length(&pObject->Present_Value);
if (length) {
*length = value_length;
}
if (value && (value_size >= value_length)) {
memcpy(value, pObject->Present_Value.value, value_length);
}
status = true;
}
return status;
}
/**
@@ -323,6 +385,47 @@ const char *OctetString_Value_Name_ASCII(uint32_t object_instance)
return name;
}
/**
* For a given object instance-number, returns the out-of-service
* property value
*
* @param object_instance - object-instance number of the object
*
* @return out-of-service property value
*/
bool OctetString_Value_Out_Of_Service(uint32_t object_instance)
{
bool value = false;
OCTETSTRING_VALUE_DESCR *pObject;
pObject = OctetString_Value_Object(object_instance);
if (pObject) {
value = pObject->Out_Of_Service;
}
return value;
}
/**
* For a given object instance-number, sets the out-of-service property value
*
* @param object_instance - object-instance number of the object
* @param value - boolean out-of-service value
*
* @return true if the out-of-service property value was set
*/
bool OctetString_Value_Out_Of_Service_Set(uint32_t object_instance, bool value)
{
OCTETSTRING_VALUE_DESCR *pObject;
pObject = OctetString_Value_Object(object_instance);
if (pObject) {
pObject->Out_Of_Service = value;
return true;
}
return false;
}
/**
* @brief Encodes a read-property response for an Octet String Value object.
* @param rpdata Read property request/response context.
@@ -331,27 +434,17 @@ const char *OctetString_Value_Name_ASCII(uint32_t object_instance)
int OctetString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
{
int apdu_len = 0; /* return value */
BACNET_BIT_STRING bit_string;
BACNET_CHARACTER_STRING char_string;
BACNET_OCTET_STRING *real_value = NULL;
BACNET_BIT_STRING bit_string = { 0 };
BACNET_CHARACTER_STRING char_string = { 0 };
BACNET_OCTET_STRING octet_value = { 0 };
bool state = false;
uint8_t *apdu = NULL;
OCTETSTRING_VALUE_DESCR *pObject = NULL;
if ((rpdata == NULL) || (rpdata->application_data == NULL) ||
(rpdata->application_data_len == 0)) {
return 0;
}
apdu = rpdata->application_data;
pObject = OctetString_Value_Object(rpdata->object_instance);
if (!pObject) {
rpdata->error_class = ERROR_CLASS_OBJECT;
rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT;
return BACNET_STATUS_ERROR;
}
switch (rpdata->object_property) {
case PROP_OBJECT_IDENTIFIER:
apdu_len = encode_application_object_id(
@@ -372,9 +465,9 @@ int OctetString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
break;
case PROP_PRESENT_VALUE:
real_value =
OctetString_Value_Present_Value(rpdata->object_instance);
apdu_len = encode_application_octet_string(&apdu[0], real_value);
OctetString_Value_Present_Value_Get(
rpdata->object_instance, &octet_value);
apdu_len = encode_application_octet_string(&apdu[0], &octet_value);
break;
case PROP_STATUS_FLAGS:
@@ -384,7 +477,7 @@ int OctetString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
bitstring_set_bit(&bit_string, STATUS_FLAG_OVERRIDDEN, false);
bitstring_set_bit(
&bit_string, STATUS_FLAG_OUT_OF_SERVICE,
pObject->Out_Of_Service);
OctetString_Value_Out_Of_Service(rpdata->object_instance));
apdu_len = encode_application_bitstring(&apdu[0], &bit_string);
break;
@@ -395,7 +488,7 @@ int OctetString_Value_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
break;
case PROP_OUT_OF_SERVICE:
state = pObject->Out_Of_Service;
state = OctetString_Value_Out_Of_Service(rpdata->object_instance);
apdu_len = encode_application_boolean(&apdu[0], state);
break;
default:
@@ -418,7 +511,6 @@ bool OctetString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
bool status = false; /* return value */
int len = 0;
BACNET_APPLICATION_DATA_VALUE value = { 0 };
OCTETSTRING_VALUE_DESCR *pObject = NULL;
if (wp_data == NULL) {
return false;
@@ -426,24 +518,15 @@ bool OctetString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
if (wp_data->application_data_len == 0) {
return false;
}
/* decode the some of the request */
len = bacapp_decode_application_data(
wp_data->application_data, wp_data->application_data_len, &value);
/* 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;
}
pObject = OctetString_Value_Object(wp_data->object_instance);
if (!pObject) {
wp_data->error_class = ERROR_CLASS_OBJECT;
wp_data->error_code = ERROR_CODE_UNKNOWN_OBJECT;
return false;
}
switch (wp_data->object_property) {
case PROP_PRESENT_VALUE:
status = write_property_type_valid(
@@ -473,7 +556,8 @@ bool OctetString_Value_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
status = write_property_type_valid(
wp_data, &value, BACNET_APPLICATION_TAG_BOOLEAN);
if (status) {
pObject->Out_Of_Service = value.type.Boolean;
OctetString_Value_Out_Of_Service_Set(
wp_data->object_instance, value.type.Boolean);
}
break;
default:
+15 -8
View File
@@ -20,13 +20,6 @@
extern "C" {
#endif /* __cplusplus */
typedef struct octetstring_value_descr {
unsigned Event_State : 3;
bool Out_Of_Service;
BACNET_OCTET_STRING Present_Value;
const char *Object_Name;
} OCTETSTRING_VALUE_DESCR;
BACNET_STACK_EXPORT
void OctetString_Value_Property_Lists(
const int32_t **pRequired,
@@ -65,8 +58,22 @@ bool OctetString_Value_Present_Value_Set(
const BACNET_OCTET_STRING *value,
uint8_t priority);
BACNET_STACK_EXPORT
bool OctetString_Value_Present_Value_Get(
uint32_t object_instance, BACNET_OCTET_STRING *value);
BACNET_STACK_DEPRECATED("Use OctetString_Value_Present_Value_Get() instead")
BACNET_STACK_EXPORT
BACNET_OCTET_STRING *OctetString_Value_Present_Value(uint32_t object_instance);
BACNET_STACK_EXPORT
bool OctetString_Value_Present_Value_Length_Set(
uint32_t object_instance, uint8_t *value, size_t length, uint8_t priority);
BACNET_STACK_EXPORT
bool OctetString_Value_Present_Value_Length(
uint32_t object_instance,
uint8_t *value,
size_t value_size,
size_t *length);
BACNET_STACK_EXPORT
bool OctetString_Value_Change_Of_Value(uint32_t instance);
BACNET_STACK_EXPORT
@@ -83,7 +90,7 @@ bool OctetString_Value_Description_Set(uint32_t instance, const char *new_name);
BACNET_STACK_EXPORT
bool OctetString_Value_Out_Of_Service(uint32_t instance);
BACNET_STACK_EXPORT
void OctetString_Value_Out_Of_Service_Set(uint32_t instance, bool oos_flag);
bool OctetString_Value_Out_Of_Service_Set(uint32_t instance, bool oos_flag);
BACNET_STACK_EXPORT
uint32_t OctetString_Value_Create(uint32_t object_instance);