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
+6 -1
View File
@@ -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-12
## [Unreleased] - 2026-03-16
### Security
@@ -53,6 +53,8 @@ The git repositories are hosted at the following sites:
### Added
* 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
properties. (#1258)
* Added WriteProperty support in the basic Structured View object.
@@ -160,6 +162,9 @@ The git repositories are hosted at the following sites:
### Changed
* Changed the OctetString Value object present-value get function
to perform a copy return rather than a pointer return and deprecate
the pointer return function. (#1264)
* Changed the COV FSM handler to remiain in the IDLE state until there
is a valid subscriber. (#1257)
* Changed bacnet_array_write() write_function callback API by adding
+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);
+382 -2
View File
@@ -9,6 +9,7 @@
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
#include <zephyr/ztest.h>
#include <bacnet/bacstr.h>
@@ -265,6 +266,232 @@ static void testCharacterString(void)
zassert_false(status, NULL);
}
/**
* @brief Test utf8_isvalid function
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bacstr_tests, testUtf8IsValid)
#else
static void testUtf8IsValid(void)
#endif
{
static const char ascii_value[] = "Joshua,Mary,Anna";
static const char utf8_value[] = "Joshua😍Mary😍Anna";
static const char valid_two_byte[] = { (char)0xC2, (char)0xA9 };
static const char valid_three_byte[] = { (char)0xE2, (char)0x82,
(char)0xAC };
static const char valid_five_byte[] = { (char)0xF8, (char)0x88, (char)0x80,
(char)0x80, (char)0x80 };
static const char valid_six_byte[] = { (char)0xFC, (char)0x84, (char)0x80,
(char)0x80, (char)0x80, (char)0x80 };
static const char embedded_nul[] = { 'A', '\0', 'B' };
static const char lone_continuation[] = { (char)0x80 };
static const char truncated_multibyte[] = { 'A', (char)0xF0 };
static const char invalid_continuation[] = { (char)0xC2, 'A' };
static const char invalid_late_continuation[] = { (char)0xE2, (char)0x82,
'A' };
static const char overlong_two_byte[] = { (char)0xC0, (char)0x80 };
static const char overlong_three_byte[] = { (char)0xE0, (char)0x80,
(char)0x80 };
static const char overlong_four_byte[] = { (char)0xF0, (char)0x80,
(char)0x80, (char)0x80 };
static const char overlong_five_byte[] = { (char)0xF8, (char)0x80,
(char)0x80, (char)0x80,
(char)0x80 };
static const char overlong_six_byte[] = { (char)0xFC, (char)0x80,
(char)0x80, (char)0x80,
(char)0x80, (char)0x80 };
static const char invalid_fe[] = { (char)0xFE, (char)0x80, (char)0x80,
(char)0x80, (char)0x80, (char)0x80 };
static const char invalid_ff[] = { (char)0xFF, (char)0x80, (char)0x80,
(char)0x80, (char)0x80, (char)0x80 };
zassert_true(utf8_isvalid(NULL, 0), "Empty input should be valid");
zassert_true(
utf8_isvalid(ascii_value, strlen(ascii_value)),
"ASCII input should be valid UTF-8");
zassert_true(
utf8_isvalid(valid_two_byte, sizeof(valid_two_byte)),
"Valid 2-byte UTF-8 should pass validation");
zassert_true(
utf8_isvalid(valid_three_byte, sizeof(valid_three_byte)),
"Valid 3-byte UTF-8 should pass validation");
zassert_true(
utf8_isvalid(utf8_value, strlen(utf8_value)),
"Valid multibyte UTF-8 should pass validation");
zassert_true(
utf8_isvalid(valid_five_byte, sizeof(valid_five_byte)),
"Valid 5-byte legacy UTF-8 should pass validation");
zassert_true(
utf8_isvalid(valid_six_byte, sizeof(valid_six_byte)),
"Valid 6-byte legacy UTF-8 should pass validation");
zassert_false(utf8_isvalid(NULL, 1), "NULL input should be rejected");
zassert_false(
utf8_isvalid(embedded_nul, sizeof(embedded_nul)),
"Embedded NUL should be rejected");
zassert_false(
utf8_isvalid(lone_continuation, sizeof(lone_continuation)),
"Lone continuation byte should be rejected");
zassert_false(
utf8_isvalid(truncated_multibyte, sizeof(truncated_multibyte)),
"Truncated multibyte sequence should be rejected");
zassert_false(
utf8_isvalid(invalid_continuation, sizeof(invalid_continuation)),
"Invalid continuation byte should be rejected");
zassert_false(
utf8_isvalid(
invalid_late_continuation, sizeof(invalid_late_continuation)),
"Invalid later continuation byte should be rejected");
zassert_false(
utf8_isvalid(overlong_two_byte, sizeof(overlong_two_byte)),
"Overlong 2-byte sequence should be rejected");
zassert_false(
utf8_isvalid(overlong_three_byte, sizeof(overlong_three_byte)),
"Overlong 3-byte sequence should be rejected");
zassert_false(
utf8_isvalid(overlong_four_byte, sizeof(overlong_four_byte)),
"Overlong 4-byte sequence should be rejected");
zassert_false(
utf8_isvalid(overlong_five_byte, sizeof(overlong_five_byte)),
"Overlong 5-byte sequence should be rejected");
zassert_false(
utf8_isvalid(overlong_six_byte, sizeof(overlong_six_byte)),
"Overlong 6-byte sequence should be rejected");
zassert_false(
utf8_isvalid(invalid_fe, sizeof(invalid_fe)),
"0xFE lead byte should be rejected");
zassert_false(
utf8_isvalid(invalid_ff, sizeof(invalid_ff)),
"0xFF lead byte should be rejected");
}
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bacstr_tests, testCharacterStringUtf8Valid)
#else
static void testCharacterStringUtf8Valid(void)
#endif
{
BACNET_CHARACTER_STRING bacnet_string = { 0 };
const char *utf8_value = "Joshua😍Mary😍Anna";
const char *ascii_value = "Joshua,Mary,Anna";
bool status = false;
/* Test NULL pointer */
status = characterstring_utf8_valid(NULL);
zassert_false(status, "NULL pointer should return false");
/* Test non-UTF8 encoding - use CHARACTER_MS_DBCS (value 1) */
status = characterstring_init_ansi(&bacnet_string, utf8_value);
zassert_true(status, NULL);
/* Verify it detects UTF-8 */
zassert_equal(
characterstring_encoding(&bacnet_string), CHARACTER_UTF8, NULL);
/* Change encoding to a different non-UTF8 encoding (CHARACTER_MS_DBCS) */
status = characterstring_set_encoding(&bacnet_string, CHARACTER_MS_DBCS);
zassert_true(status, NULL);
/* Now it should fail UTF-8 validation because encoding is not UTF-8 */
status = characterstring_utf8_valid(&bacnet_string);
zassert_false(status, "Non-UTF8 encoding should return false");
/* Test valid UTF-8 string */
status = characterstring_init_ansi(&bacnet_string, utf8_value);
zassert_true(status, NULL);
zassert_equal(
characterstring_encoding(&bacnet_string), CHARACTER_UTF8, NULL);
status = characterstring_utf8_valid(&bacnet_string);
zassert_true(status, "Valid UTF-8 string should return true");
/* Test empty UTF-8 string */
status = characterstring_init(&bacnet_string, CHARACTER_UTF8, NULL, 0);
zassert_true(status, NULL);
status = characterstring_utf8_valid(&bacnet_string);
zassert_true(status, "Empty UTF-8 string should return true");
/* Test valid UTF-8 string with plain ASCII */
status = characterstring_init(
&bacnet_string, CHARACTER_UTF8, ascii_value, strlen(ascii_value));
zassert_true(status, NULL);
status = characterstring_utf8_valid(&bacnet_string);
zassert_true(status, "Valid ASCII-only UTF-8 string should return true");
}
/**
* @brief Test characterstring_utf8_strdup function
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bacstr_tests, testCharacterStringUtf8Strdup)
#else
static void testCharacterStringUtf8Strdup(void)
#endif
{
BACNET_CHARACTER_STRING bacnet_string = { 0 };
const char *utf8_value = "Joshua😍Mary😍Anna";
const char *ascii_value = "Joshua,Mary,Anna";
char *dup_string = NULL;
bool status = false;
size_t length = 0;
size_t i = 0;
/* Test NULL pointer */
dup_string = characterstring_utf8_strdup(NULL);
zassert_is_null(dup_string, "NULL pointer should return NULL");
/* Test non-UTF8 encoding - use CHARACTER_MS_DBCS (value 1) */
status = characterstring_init_ansi(&bacnet_string, utf8_value);
zassert_true(status, NULL);
/* Verify it detects UTF-8 */
zassert_equal(
characterstring_encoding(&bacnet_string), CHARACTER_UTF8, NULL);
/* Change encoding to a different non-UTF8 encoding (CHARACTER_MS_DBCS) */
status = characterstring_set_encoding(&bacnet_string, CHARACTER_MS_DBCS);
zassert_true(status, NULL);
dup_string = characterstring_utf8_strdup(&bacnet_string);
zassert_is_null(dup_string, "Non-UTF8 encoding should return NULL");
/* Test valid UTF-8 string duplication */
status = characterstring_init_ansi(&bacnet_string, utf8_value);
zassert_true(status, NULL);
zassert_equal(
characterstring_encoding(&bacnet_string), CHARACTER_UTF8, NULL);
dup_string = characterstring_utf8_strdup(&bacnet_string);
zassert_not_null(dup_string, "Valid UTF-8 string should return non-NULL");
length = characterstring_length(&bacnet_string);
/* Verify the duplicated string has correct content */
for (i = 0; i < length; i++) {
zassert_equal(
dup_string[i], characterstring_value(&bacnet_string)[i],
"Duplicated strings should match at byte %u", i);
}
/* Verify NUL-termination */
zassert_equal(dup_string[length], 0, "String should be NUL-terminated");
free(dup_string);
dup_string = NULL;
/* Test empty UTF-8 string duplication */
status = characterstring_init(&bacnet_string, CHARACTER_UTF8, NULL, 0);
zassert_true(status, NULL);
dup_string = characterstring_utf8_strdup(&bacnet_string);
zassert_not_null(dup_string, "Empty UTF-8 string should return non-NULL");
zassert_equal(dup_string[0], 0, "Empty string should be NUL-terminated");
free(dup_string);
dup_string = NULL;
/* Test valid UTF-8 string with plain ASCII */
status = characterstring_init(
&bacnet_string, CHARACTER_UTF8, ascii_value, strlen(ascii_value));
zassert_true(status, NULL);
dup_string = characterstring_utf8_strdup(&bacnet_string);
zassert_not_null(dup_string, "ASCII UTF-8 string should return non-NULL");
length = characterstring_length(&bacnet_string);
zassert_equal(
strncmp(dup_string, ascii_value, length), 0,
"Duplicated ASCII-UTF8 string should match original");
zassert_equal(dup_string[length], 0, "String should be NUL-terminated");
free(dup_string);
dup_string = NULL;
}
/**
* @brief Test encode/decode API for octet strings
*/
@@ -409,6 +636,87 @@ static void testOctetString(void)
zassert_true(status, NULL);
}
/**
* @brief Test octetstring_init_ascii_epics API
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bacstr_tests, test_octetstring_init_ascii_epics)
#else
static void test_octetstring_init_ascii_epics(void)
#endif
{
BACNET_OCTET_STRING bacnet_string;
const char *epics_valid_hex = "X'1234567890ABCDEF'";
const char *epics_valid_hex_with_colons = "X'12:34:56:78:90:AB:CD:EF'";
const char *epics_invalid_no_prefix = "1234567890ABCDEF";
const char *epics_invalid_wrong_prefix = "H'1234567890ABCDEF'";
const char *epics_invalid_odd_single = "X'1";
char epics_too_long[MAX_APDU + MAX_APDU] = "";
bool status = false;
size_t length = 0;
size_t test_length = 0;
uint8_t *value = NULL;
/* test valid EPICS format with hex string */
status = octetstring_init_ascii_epics(&bacnet_string, epics_valid_hex);
zassert_true(status, "Valid EPICS hex should return true");
length = octetstring_length(&bacnet_string);
test_length = strlen(epics_valid_hex) - 3; /* subtract X'' */
test_length = test_length / 2; /* convert hex chars to byte count */
zassert_equal(length, test_length, "Length should be 8 bytes");
value = octetstring_value(&bacnet_string);
zassert_equal(value[0], 0x12, "First byte should be 0x12");
zassert_equal(value[1], 0x34, "Second byte should be 0x34");
zassert_equal(value[7], 0xEF, "Last byte should be 0xEF");
/* test valid EPICS format with colons as separators */
status = octetstring_init_ascii_epics(
&bacnet_string, epics_valid_hex_with_colons);
zassert_true(status, "Valid EPICS hex with colons should return true");
length = octetstring_length(&bacnet_string);
zassert_equal(length, 8, "Length should be 8 bytes");
value = octetstring_value(&bacnet_string);
zassert_equal(value[0], 0x12, "First byte should be 0x12");
zassert_equal(value[1], 0x34, "Second byte should be 0x34");
/* test invalid - NULL octet string pointer */
status = octetstring_init_ascii_epics(NULL, epics_valid_hex);
zassert_false(status, "NULL octet_string pointer should return false");
/* test invalid - NULL arg pointer */
status = octetstring_init_ascii_epics(&bacnet_string, NULL);
zassert_false(status, "NULL arg pointer should return false");
/* test invalid - both NULL */
status = octetstring_init_ascii_epics(NULL, NULL);
zassert_false(status, "Both NULL pointers should return false");
/* test invalid - no X' prefix */
status =
octetstring_init_ascii_epics(&bacnet_string, epics_invalid_no_prefix);
zassert_false(status, "String without X' prefix should return false");
/* test invalid - wrong prefix */
status = octetstring_init_ascii_epics(
&bacnet_string, epics_invalid_wrong_prefix);
zassert_false(status, "String with H' prefix should return false");
/* test invalid - odd number of hex digits */
status =
octetstring_init_ascii_epics(&bacnet_string, epics_invalid_odd_single);
zassert_false(status, "Single hex digit without pair should return false");
/* test invalid - string too long */
memset(
epics_too_long, 'F',
sizeof(epics_too_long) - 1); /* -1 for null terminator */
epics_too_long[0] = 'X';
epics_too_long[1] = '\'';
epics_too_long[sizeof(epics_too_long) - 1] = 0; /* null terminate */
status = octetstring_init_ascii_epics(&bacnet_string, epics_too_long);
zassert_false(status, "String exceeding capacity should return false");
}
/**
* @brief Test encode/decode API for bacnet_stricmp
*/
@@ -828,6 +1136,73 @@ static void test_bacnet_snprintf(void)
zassert_equal(null_len, test_null_len, "null_len=%d", null_len);
}
/**
* @brief Test bacnet_strdup string duplication
*/
#if defined(CONFIG_ZTEST_NEW_API)
ZTEST(bacstr_tests, test_bacnet_strdup)
#else
static void test_bacnet_strdup(void)
#endif
{
const char *original = "Test String";
const char *empty_string = "";
const char *long_string =
"This is a longer test string with multiple words";
char *dup_string = NULL;
/* Test NULL pointer */
dup_string = bacnet_strdup(NULL);
zassert_is_null(dup_string, "NULL input should return NULL");
/* Test empty string */
dup_string = bacnet_strdup(empty_string);
zassert_not_null(dup_string, "Empty string should allocate memory");
zassert_equal(
strlen(dup_string), 0, "Duplicated empty string should have length 0");
zassert_equal(
bacnet_strcmp(dup_string, empty_string), 0,
"Duplicated string should match original");
free(dup_string);
/* Test normal string */
dup_string = bacnet_strdup(original);
zassert_not_null(dup_string, "Normal string should allocate memory");
zassert_equal(
bacnet_strcmp(dup_string, original), 0,
"Duplicated string should match original");
/* Verify different memory addresses */
zassert_not_equal(
(uintptr_t)dup_string, (uintptr_t)original,
"Duplicated string should have different address");
/* Verify string length is preserved */
zassert_equal(
strlen(dup_string), strlen(original),
"String length should be preserved");
free(dup_string);
/* Test longer string */
dup_string = bacnet_strdup(long_string);
zassert_not_null(dup_string, "Longer string should allocate memory");
zassert_equal(
bacnet_strcmp(dup_string, long_string), 0,
"Duplicated longer string should match original");
zassert_equal(
strlen(dup_string), strlen(long_string),
"Longer string length should be preserved");
free(dup_string);
/* Test string with special characters */
const char *special_chars = "Hello\tWorld\nTest!";
dup_string = bacnet_strdup(special_chars);
zassert_not_null(
dup_string, "String with special chars should allocate memory");
zassert_equal(
bacnet_strcmp(dup_string, special_chars), 0,
"Special characters should be preserved");
free(dup_string);
}
/**
* @}
*/
@@ -839,7 +1214,11 @@ void test_main(void)
{
ztest_test_suite(
bacstr_tests, ztest_unit_test(testBitString),
ztest_unit_test(testCharacterString), ztest_unit_test(testOctetString),
ztest_unit_test(testCharacterString), ztest_unit_test(testUtf8IsValid),
ztest_unit_test(testCharacterStringUtf8Valid),
ztest_unit_test(testCharacterStringUtf8Strdup),
ztest_unit_test(testOctetString),
ztest_unit_test(test_octetstring_init_ascii_epics),
ztest_unit_test(test_bacnet_stricmp),
ztest_unit_test(test_bacnet_strnicmp),
ztest_unit_test(test_bacnet_strnlen),
@@ -847,7 +1226,8 @@ void test_main(void)
ztest_unit_test(test_bacnet_string_to_x),
ztest_unit_test(test_bacnet_string_trim),
ztest_unit_test(test_bacnet_stptok),
ztest_unit_test(test_bacnet_snprintf));
ztest_unit_test(test_bacnet_snprintf),
ztest_unit_test(test_bacnet_strdup));
ztest_run_test_suite(bacstr_tests);
}
#endif