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:
+6
-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-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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user