Fixed CreateObject service list-of-initial-values encoding and decoding. (#1199)

Fixes the CreateObject service list-of-initial-values encoding and decoding by refactoring the data structure to be similar to WriteProperty. The implementation changes from using a linked list of property values to using a flat buffer approach with delayed decoding.

Changes:

* Refactored BACNET_CREATE_OBJECT_DATA structure to use an application_data buffer instead of a linked list for initial values
* Added a new create_object_process() and create_object_initializer_list_process() functions to centralize object creation logic and error reporting
* Updated all device implementations to use the new centralized creation functions
* Enhanced the create-object example application to support command-line specification of initial property values
* Added comprehensive test coverage for the new encoding/decoding and processing functions
This commit is contained in:
Steve Karg
2026-01-15 14:50:59 -06:00
committed by GitHub
parent 21daea9eb4
commit 5802bd0ecc
18 changed files with 921 additions and 212 deletions
+2
View File
@@ -83,6 +83,8 @@ The git repositories are hosted at the following sites:
### Fixed
* Fixed CreateObject service list-of-initial-values encoding and decoding.
Changed the data structure to be similar to WriteProperty. (#1199)
* Fixed lighting-output object blink warn to honor blink-warn-enable.
Fixed the blink warn logic for a non-zero percent value blink inhibit.
Fixed the warn relinquish to actually relinquish. (#1192)
+14 -39
View File
@@ -1959,51 +1959,26 @@ bool Device_Create_Object(BACNET_CREATE_OBJECT_DATA *data)
{
bool status = false;
struct object_functions *pObject = NULL;
uint32_t object_instance;
bool object_exists = false;
bool object_supported = false;
pObject = Device_Object_Functions_Find(data->object_type);
if (pObject != NULL) {
if (!pObject->Object_Create) {
/* The device supports the object type and may have
sufficient space, but does not support the creation of the
object for some other reason.*/
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED;
} else if (
pObject->Object_Valid_Instance &&
if (pObject->Object_Valid_Instance &&
pObject->Object_Valid_Instance(data->object_instance)) {
/* The object being created already exists */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS;
object_exists = true;
}
object_supported = true;
status = create_object_process(
data, object_supported, object_exists, pObject->Object_Create,
pObject->Object_Delete, pObject->Object_Write_Property);
} else {
if (data->list_of_initial_values) {
/* FIXME: add support for writing to list of initial values */
/* A property specified by the Property_Identifier in the
List of Initial Values does not support initialization
during the CreateObject service. */
data->first_failed_element_number = 1;
data->error_class = ERROR_CLASS_PROPERTY;
data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
/* and the object shall not be created */
} else {
object_instance = pObject->Object_Create(data->object_instance);
if (object_instance == BACNET_MAX_INSTANCE) {
/* The device cannot allocate the space needed
for the new object.*/
data->error_class = ERROR_CLASS_RESOURCES;
data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;
} else {
/* required by ACK */
data->object_instance = object_instance;
/* fill in the error values */
status = create_object_process(
data, object_supported, object_exists, NULL, NULL, NULL);
}
if (status) {
Device_Inc_Database_Revision();
status = true;
}
}
}
} else {
/* The device does not support the specified object type. */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE;
}
return status;
+244 -16
View File
@@ -47,6 +47,7 @@ static uint8_t Rx_Buf[MAX_MPDU] = { 0 };
static uint32_t Target_Device_Object_Instance = BACNET_MAX_INSTANCE;
static BACNET_OBJECT_TYPE Target_Object_Type;
static uint32_t Target_Object_Instance = BACNET_MAX_INSTANCE;
static BACNET_PROPERTY_VALUE *Target_Initial_Values;
/* needed to filter incoming messages */
static uint8_t Request_Invoke_ID = 0;
static BACNET_ADDRESS Target_Address;
@@ -86,7 +87,7 @@ static void MyCreateObjectErrorHandler(
uint16_t service_len)
{
int len = 0;
BACNET_CREATE_OBJECT_DATA data;
BACNET_CREATE_OBJECT_DATA data = { 0 };
(void)service_choice;
if (address_match(&Target_Address, src) &&
@@ -95,7 +96,7 @@ static void MyCreateObjectErrorHandler(
service_request, service_len, &data);
if (len > 0) {
MyPrintHandler(
data.object_type, data.object_instance, data.error_class,
Target_Object_Type, Target_Object_Instance, data.error_class,
data.error_code, data.first_failed_element_number);
}
Error_Detected = true;
@@ -108,7 +109,7 @@ static void MyCreateObjectAckHandler(
BACNET_ADDRESS *src,
BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
{
BACNET_CREATE_OBJECT_DATA data;
BACNET_CREATE_OBJECT_DATA data = { 0 };
int len = 0;
if (address_match(&Target_Address, src) &&
@@ -183,7 +184,10 @@ static void Init_Service_Handlers(void)
static void print_usage(const char *filename)
{
printf(
"Usage: %s device-instance object-type [object-instance]\n", filename);
"Usage: %s device-instance object-type [object-instance]\n"
"[property[index] priority tag value\n"
"[property[index] priority tag value ...]]\n",
filename);
printf(" [--dnet][--dadr][--mac]\n");
printf(" [--version][--help][--verbose]\n");
}
@@ -203,20 +207,72 @@ static void print_help(const char *filename)
"The object type is object that you are creating. It\n"
"can be defined either as the object-type name string\n"
"as defined in the BACnet specification, or as the\n"
"integer value of the enumeration BACNET_OBJECT_TYPE\n"
"in bacenum.h. For example if you were reading Analog\n"
"Output 2, the object-type would be analog-output or 1.\n");
"integer value of the enumeration. For example if \n"
"you were reading Analog Output 2, the object-type\n"
"would be analog-output or 1.\n");
printf("\n");
printf("object-instance (optional):\n"
printf(
"object-instance:\n"
"This is the object instance number of the object that\n"
"you are creating. For example, if you were writing\n"
"Analog Output 2, the object-instance would be 2.\n");
"you are creating. For example, if you were creating\n"
"Analog Output 2, the object-instance would be 2.\n"
"To create the next available instance number, use\n"
"the instance number %u\n",
BACNET_MAX_INSTANCE);
printf("\n");
printf("property[index]:\n"
"The property is an integer value or name of the\n"
"Property Identifier enumeration as defined in the\n"
"BACnet specification. It is the property you are initializing.\n"
"For example, if you were initializing the Present Value\n"
"property, use present-value or 85 as the property.\n");
printf("[index]: optional\n"
"This integer parameter is the index number of an array.\n"
"If the property is an array, individual elements can be\n"
"initialized if supported.\n");
printf("\n");
printf("priority:\n"
"This parameter is used for setting the priority of the\n"
"initialization. If Priority 0 is given, no priority is used.\n"
"The BACnet specification states that the value is written at the\n"
"lowest priority (16) if the object property supports priorities\n"
"when no priority is sent.\n");
printf("\n");
printf(
"tag:\n"
"Tag is an integer value representing the data type of the value.\n"
"0=NULL, 1=BOOLEAN, 2=UNSIGNED INTEGER, 3=SIGNED INTEGER, 4=REAL,\n"
"5=DOUBLE, 6=OCTET STRING, 7=CHARACTER STRING, 8=BIT STRING,\n"
"9=ENUMERATED, 10=DATE, 11=TIME, 12=OBJECT IDENTIFIER.\n"
"Context tags are created using two tags in a row. The context tag\n"
"is preceded by a C: Ctag tag. C2 4 creates a context 2 tagged "
"REAL.\n");
printf("Complex data use the property argument and a tag number -1 to\n"
"lookup the appropriate internal application tag for the value.\n"
"The complex data value argument varies in its construction.\n");
printf("\n");
printf(
"value:\n"
"The value is an ASCII representation of some type of data that you\n"
"are initializing. It is encoded using the tag information provided.\n"
"For example, if you were writing a REAL value of 100.0, you would\n"
"use 100.0 as the value.\n");
printf("\n");
printf(
"Here is a brief overview of BACnet property and tags:\n"
"Certain properties are expected to be written with certain \n"
"application tags, so you probably need to know which ones to use\n"
"with each property of each object. It is almost safe to say that\n"
"given a property and an object and a table, the tag could be looked\n"
"up automatically. There may be a few exceptions to this, such as\n"
"the Any property type in the schedule object and the Present Value\n"
"accepting REAL, BOOLEAN, NULL, etc.\n");
printf("\n");
printf(
"Example:\n"
"If you want to CreateObject of an Analog Input 1\n"
"If you want to create Analog Input 1 in device 123,\n"
"send the following command:\n"
"%s 123 0 1\n",
"%s 123 analog-input 1\n",
filename);
}
@@ -229,13 +285,22 @@ int main(int argc, char *argv[])
struct mstimer apdu_timer;
struct mstimer maintenance_timer;
bool found = false;
unsigned long object_type = 0;
uint32_t object_type = 0;
unsigned long object_instance = 0;
long dnet = -1;
BACNET_MAC_ADDRESS mac = { 0 };
BACNET_MAC_ADDRESS adr = { 0 };
BACNET_ADDRESS dest = { 0 };
bool specific_address = false;
BACNET_PROPERTY_VALUE *initial_value = NULL;
int scan_count = 0;
char name[80] = { 0 };
unsigned array_value = 0;
unsigned long unsigned_value = 0;
uint32_t found_index = 0;
long property_tag = 0, context_tag = 0;
long priority = 0;
char *value_string = NULL;
unsigned int target_args = 0;
int argi = 0;
const char *filename = NULL;
@@ -295,7 +360,7 @@ int main(int argc, char *argv[])
Target_Device_Object_Instance = object_instance;
target_args++;
} else if (target_args == 1) {
if (!bacnet_strtoul(argv[argi], &object_type)) {
if (!bactext_object_type_strtol(argv[argi], &object_type)) {
fprintf(stderr, "object-type=%s invalid\n", argv[argi]);
return 1;
}
@@ -317,6 +382,169 @@ int main(int argc, char *argv[])
printf("Instance=%lu=%s\n", object_instance, argv[argi]);
}
target_args++;
} else {
/* list of initial values */
/* expecting tuple: property[index] priority [Ctag] tag value
index is optional, priority=0 when optional,
value can be NULL */
if (!Target_Initial_Values) {
Target_Initial_Values =
calloc(1, sizeof(BACNET_PROPERTY_VALUE));
initial_value = Target_Initial_Values;
} else {
initial_value->next =
calloc(1, sizeof(BACNET_PROPERTY_VALUE));
initial_value = initial_value->next;
}
if (!initial_value) {
fprintf(
stderr, "out of memory initializing initial values\n");
return 1;
}
/* Property */
initial_value->propertyArrayIndex = BACNET_ARRAY_ALL;
if (isalpha(argv[argi][0])) {
/* choose a property by name with optional [] to denote
* array */
scan_count =
sscanf(argv[argi], "%79[^[][%u]", name, &array_value);
if (scan_count < 1) {
fprintf(
stderr, "parse: missing property: %s.", argv[argi]);
return 1;
}
if (scan_count == 2) {
initial_value->propertyArrayIndex = array_value;
}
if (!bactext_property_strtol(name, &found_index)) {
fprintf(
stderr, "parse: invalid property name: %s.",
argv[argi]);
return 1;
}
initial_value->propertyIdentifier = found_index;
} else {
/* choose a property by number */
scan_count = sscanf(
argv[argi], "%lu[%u]", &unsigned_value, &array_value);
if (scan_count < 1) {
fprintf(
stderr, "parse: missing property: %s.", argv[argi]);
return 1;
}
if (unsigned_value > MAX_BACNET_PROPERTY_ID) {
fprintf(
stderr,
"parse: Invalid property: %s. Must be 0-%d.",
argv[argi], MAX_BACNET_PROPERTY_ID);
return 1;
}
if (scan_count == 2) {
initial_value->propertyArrayIndex = array_value;
}
initial_value->propertyIdentifier =
(BACNET_PROPERTY_ID)unsigned_value;
}
if (++argi >= argc) {
fprintf(stderr, "Error: missing tag-value pair\n");
return 1;
}
/* Priority */
if (!bacnet_strtol(argv[argi], &priority)) {
fprintf(stderr, "Error: priority=%s invalid\n", argv[argi]);
return 1;
}
if (priority < 0 || priority > BACNET_MAX_PRIORITY) {
fprintf(
stderr, "Error: priority=%ld must be within 0 and %u\n",
priority, BACNET_MAX_PRIORITY);
return 1;
}
initial_value->priority = (uint8_t)priority;
if (++argi >= argc) {
fprintf(
stderr, "Error: missing value for tag-value pair\n");
return 1;
}
/* Tag */
if (toupper(argv[argi][0]) == 'C') {
/* special case for context tag: [Ctag] tag */
if (!bacnet_strtol(&argv[argi][1], &context_tag)) {
fprintf(
stderr, "Error: context tag=%s invalid\n",
&argv[argi][1]);
return 1;
}
if (context_tag < 0 || context_tag > UINT8_MAX) {
fprintf(
stderr,
"Error: context tag=%ld must be within 0 and %u\n",
context_tag, UINT8_MAX);
return 1;
}
if (++argi >= argc) {
fprintf(
stderr,
"Error: missing tag-value pair after context "
"tag\n");
return 1;
}
if (!bacnet_strtol(argv[argi], &property_tag)) {
fprintf(stderr, "Error: tag=%s invalid\n", argv[argi]);
return 1;
}
if (property_tag < 0 ||
property_tag > MAX_BACNET_APPLICATION_TAG) {
fprintf(
stderr, "Error: tag=%ld must be within 0 and %d\n",
property_tag, MAX_BACNET_APPLICATION_TAG);
return 1;
}
initial_value->value.context_tag = (uint8_t)property_tag;
initial_value->value.context_specific = true;
} else {
initial_value->value.context_specific = false;
property_tag = strtol(argv[argi], NULL, 0);
if (property_tag < 0) {
/* use -1 to lookup a known context tag */
property_tag = bacapp_known_property_tag(
Target_Object_Type,
initial_value->propertyIdentifier);
if (property_tag < 0 || property_tag > UINT8_MAX) {
fprintf(
stderr, "Error: tag for property=%lu unknown\n",
(unsigned long)
initial_value->propertyIdentifier);
return 1;
}
} else if (property_tag >= MAX_BACNET_APPLICATION_TAG) {
fprintf(
stderr,
"Error: tag=%ld - it must be less than %u\n",
property_tag, MAX_BACNET_APPLICATION_TAG);
return 1;
}
initial_value->value.tag = (uint8_t)property_tag;
}
if (++argi >= argc) {
fprintf(
stderr, "Error: missing value for tag-value pair\n");
return 1;
}
/* Value */
value_string = argv[argi];
if (Verbose) {
printf("tag=%ld value=%s\n", property_tag, value_string);
}
if (!bacapp_parse_application_data(
property_tag, value_string, &initial_value->value)) {
/* FIXME: show the expected entry format for the tag */
fprintf(
stderr, "Error: parser for %s is not implemented\n",
bactext_property_name(
initial_value->propertyIdentifier));
return 1;
}
}
}
}
@@ -360,9 +588,9 @@ int main(int argc, char *argv[])
"Sending CreateObject to Device %u.\n",
Target_Device_Object_Instance);
}
Request_Invoke_ID = Send_Create_Object_Request(
Request_Invoke_ID = Send_Create_Object_Request_Data(
Target_Device_Object_Instance, Target_Object_Type,
Target_Object_Instance);
Target_Object_Instance, Target_Initial_Values);
} else if (tsm_invoke_id_free(Request_Invoke_ID)) {
break;
} else if (tsm_invoke_id_failed(Request_Invoke_ID)) {
+14 -39
View File
@@ -1891,51 +1891,26 @@ bool Device_Create_Object(BACNET_CREATE_OBJECT_DATA *data)
{
bool status = false;
struct object_functions *pObject = NULL;
uint32_t object_instance;
bool object_exists = false;
bool object_supported = false;
pObject = Device_Object_Functions_Find(data->object_type);
if (pObject != NULL) {
if (!pObject->Object_Create) {
/* The device supports the object type and may have
sufficient space, but does not support the creation of the
object for some other reason.*/
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED;
} else if (
pObject->Object_Valid_Instance &&
if (pObject->Object_Valid_Instance &&
pObject->Object_Valid_Instance(data->object_instance)) {
/* The object being created already exists */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS;
object_exists = true;
}
object_supported = true;
status = create_object_process(
data, object_supported, object_exists, pObject->Object_Create,
pObject->Object_Delete, pObject->Object_Write_Property);
} else {
if (data->list_of_initial_values) {
/* FIXME: add support for writing to list of initial values */
/* A property specified by the Property_Identifier in the
List of Initial Values does not support initialization
during the CreateObject service. */
data->first_failed_element_number = 1;
data->error_class = ERROR_CLASS_PROPERTY;
data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
/* and the object shall not be created */
} else {
object_instance = pObject->Object_Create(data->object_instance);
if (object_instance == BACNET_MAX_INSTANCE) {
/* The device cannot allocate the space needed
for the new object.*/
data->error_class = ERROR_CLASS_RESOURCES;
data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;
} else {
/* required by ACK */
data->object_instance = object_instance;
/* fill in the error values */
status = create_object_process(
data, object_supported, object_exists, NULL, NULL, NULL);
}
if (status) {
Device_Inc_Database_Revision();
status = true;
}
}
}
} else {
/* The device does not support the specified object type. */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE;
}
return status;
+3
View File
@@ -721,10 +721,13 @@ static bool Channel_Write_Members(
"channel[%lu].Channel_Write_Member[%u] coerced\n",
(unsigned long)object_instance, m);
if (Write_Property_Internal_Callback) {
status = write_property_bacnet_array_valid(&wp_data);
if (status) {
status = Write_Property_Internal_Callback(&wp_data);
if (status) {
wp_data.error_code = ERROR_CODE_SUCCESS;
}
}
debug_printf(
"channel[%lu].Channel_Write_Member[%u] "
"%s\n",
+14 -39
View File
@@ -2339,51 +2339,26 @@ bool Device_Create_Object(BACNET_CREATE_OBJECT_DATA *data)
{
bool status = false;
struct object_functions *pObject = NULL;
uint32_t object_instance;
bool object_exists = false;
bool object_supported = false;
pObject = Device_Object_Functions_Find(data->object_type);
if (pObject != NULL) {
if (!pObject->Object_Create) {
/* The device supports the object type and may have
sufficient space, but does not support the creation of the
object for some other reason.*/
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED;
} else if (
pObject->Object_Valid_Instance &&
if (pObject->Object_Valid_Instance &&
pObject->Object_Valid_Instance(data->object_instance)) {
/* The object being created already exists */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS;
object_exists = true;
}
object_supported = true;
status = create_object_process(
data, object_supported, object_exists, pObject->Object_Create,
pObject->Object_Delete, pObject->Object_Write_Property);
} else {
if (data->list_of_initial_values) {
/* FIXME: add support for writing to list of initial values */
/* A property specified by the Property_Identifier in the
List of Initial Values does not support initialization
during the CreateObject service. */
data->first_failed_element_number = 1;
data->error_class = ERROR_CLASS_PROPERTY;
data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
/* and the object shall not be created */
} else {
object_instance = pObject->Object_Create(data->object_instance);
if (object_instance == BACNET_MAX_INSTANCE) {
/* The device cannot allocate the space needed
for the new object.*/
data->error_class = ERROR_CLASS_RESOURCES;
data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;
} else {
/* required by ACK */
data->object_instance = object_instance;
/* fill in the error values */
status = create_object_process(
data, object_supported, object_exists, NULL, NULL, NULL);
}
if (status) {
Device_Inc_Database_Revision();
status = true;
}
}
}
} else {
/* The device does not support the specified object type. */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE;
}
return status;
+3
View File
@@ -2051,6 +2051,8 @@ static bool Loop_Write_Manipulated_Variable(
wp_data.application_data_len =
encode_application_real(wp_data.application_data, value);
if (Write_Property_Internal_Callback) {
status = write_property_bacnet_array_valid(&wp_data);
if (status) {
status = Write_Property_Internal_Callback(&wp_data);
if (status) {
wp_data.error_code = ERROR_CODE_SUCCESS;
@@ -2058,6 +2060,7 @@ static bool Loop_Write_Manipulated_Variable(
}
}
}
}
return status;
}
+3
View File
@@ -496,6 +496,8 @@ static bool Timer_Write_Members(
wp_data.application_data, sizeof(wp_data.application_data),
value);
if (Write_Property_Internal_Callback) {
status = write_property_bacnet_array_valid(&wp_data);
if (status) {
status = Write_Property_Internal_Callback(&wp_data);
if (status) {
wp_data.error_code = ERROR_CODE_SUCCESS;
@@ -504,6 +506,7 @@ static bool Timer_Write_Members(
}
}
}
}
return status;
}
+14 -39
View File
@@ -2763,51 +2763,26 @@ bool Device_Create_Object(BACNET_CREATE_OBJECT_DATA *data)
{
bool status = false;
struct object_functions *pObject = NULL;
uint32_t object_instance;
bool object_exists = false;
bool object_supported = false;
pObject = Device_Object_Functions_Find(data->object_type);
if (pObject != NULL) {
if (!pObject->Object_Create) {
/* The device supports the object type and may have
sufficient space, but does not support the creation of the
object for some other reason.*/
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED;
} else if (
pObject->Object_Valid_Instance &&
if (pObject->Object_Valid_Instance &&
pObject->Object_Valid_Instance(data->object_instance)) {
/* The object being created already exists */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS;
object_exists = true;
}
object_supported = true;
status = create_object_process(
data, object_supported, object_exists, pObject->Object_Create,
pObject->Object_Delete, pObject->Object_Write_Property);
} else {
if (data->list_of_initial_values) {
/* FIXME: add support for writing to list of initial values */
/* A property specified by the Property_Identifier in the
List of Initial Values does not support initialization
during the CreateObject service. */
data->first_failed_element_number = 1;
data->error_class = ERROR_CLASS_PROPERTY;
data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
/* and the object shall not be created */
} else {
object_instance = pObject->Object_Create(data->object_instance);
if (object_instance == BACNET_MAX_INSTANCE) {
/* The device cannot allocate the space needed
for the new object.*/
data->error_class = ERROR_CLASS_RESOURCES;
data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;
} else {
/* required by ACK */
data->object_instance = object_instance;
/* fill in the error values */
status = create_object_process(
data, object_supported, object_exists, NULL, NULL, NULL);
}
if (status) {
Device_Inc_Database_Revision();
status = true;
}
}
}
} else {
/* The device does not support the specified object type. */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE;
}
return status;
+1 -1
View File
@@ -34,7 +34,7 @@
* - an Abort if
* - the message is segmented
* - if decoding fails
* - a SimpleACK if Device_Create_Object() succeeds
* - a ComplexACK if Device_Create_Object() succeeds
* - an Error if Device_Create_Object() fails
*
* @param service_request [in] The contents of the service request.
+13 -1
View File
@@ -56,6 +56,8 @@ uint8_t Send_Create_Object_Request_Data(
BACNET_CREATE_OBJECT_DATA data = { 0 };
BACNET_NPDU_DATA npdu_data = { 0 };
uint8_t service = SERVICE_CONFIRMED_CREATE_OBJECT;
BACNET_PROPERTY_VALUE *value;
uint8_t *application_data = NULL;
if (!dcc_communication_enabled()) {
return 0;
@@ -81,7 +83,17 @@ uint8_t Send_Create_Object_Request_Data(
/* encode the APDU service */
data.object_type = object_type;
data.object_instance = object_instance;
data.list_of_initial_values = values;
data.application_data_len = 0;
value = values;
#if BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED
application_data = data.application_data;
#endif
while (value) {
len = create_object_encode_initial_value(
application_data, data.application_data_len, value);
data.application_data_len += len;
value = value->next;
}
/* get the length of the APDU */
len = create_object_encode_service_request(NULL, &data);
pdu_len += len;
+329 -21
View File
@@ -15,6 +15,151 @@
#include "bacnet/bacerror.h"
#include "bacnet/create_object.h"
/**
* @brief Encode one value for CreateObject List-of-Initial-Values
* @param apdu Pointer to the buffer for encoded values
* @param offset Offset into the buffer to start encoding
* @param value Pointer to the property value used for encoding
* @return Bytes encoded or zero on error.
*/
int create_object_encode_initial_value(
uint8_t *apdu, int offset, const BACNET_PROPERTY_VALUE *value)
{
if (apdu) {
apdu += offset;
}
return bacapp_property_value_encode(apdu, value);
}
/**
* @brief Decode one BACnetPropertyValue value
*
* BACnetPropertyValue ::= SEQUENCE {
* property-identifier [0] BACnetPropertyIdentifier,
* property-array-index [1] Unsigned OPTIONAL,
* -- used only with array datatypes
* -- if omitted with an array the entire array is referenced
* property-value [2] ABSTRACT-SYNTAX.&Type,
* -- any datatype appropriate for the specified property
* priority [3] Unsigned (1..16) OPTIONAL
* -- used only when property is commandable
* }
*
* @param apdu Pointer to the buffer of encoded value
* @param apdu_size Size of the buffer holding the encode value
* @param value Pointer to the data used for decoding one value
* @return number of bytes decoded or BACNET_STATUS_ERROR on error.
*/
int create_object_decode_initial_value(
const uint8_t *apdu,
uint32_t apdu_size,
BACNET_CREATE_OBJECT_PROPERTY_VALUE *value)
{
int len = 0;
int apdu_len = 0;
uint32_t enumerated_value = 0;
uint32_t len_value_type = 0;
BACNET_UNSIGNED_INTEGER unsigned_value = 0;
BACNET_PROPERTY_ID property_identifier = PROP_ALL;
int imax = 0;
if (!apdu) {
return BACNET_STATUS_ERROR;
}
/* property-identifier [0] BACnetPropertyIdentifier */
len = bacnet_enumerated_context_decode(
&apdu[apdu_len], apdu_size - apdu_len, 0, &enumerated_value);
if (len > 0) {
property_identifier = enumerated_value;
if (value) {
value->propertyIdentifier = property_identifier;
}
apdu_len += len;
} else {
return BACNET_STATUS_ERROR;
}
/* property-array-index [1] Unsigned OPTIONAL */
if (bacnet_is_context_tag_number(
&apdu[apdu_len], apdu_size - apdu_len, 1, &len, &len_value_type)) {
apdu_len += len;
len = bacnet_unsigned_decode(
&apdu[apdu_len], apdu_size - apdu_len, len_value_type,
&unsigned_value);
if (len > 0) {
if (unsigned_value > UINT32_MAX) {
return BACNET_STATUS_ERROR;
} else {
apdu_len += len;
if (value) {
value->propertyArrayIndex = unsigned_value;
}
}
} else {
return BACNET_STATUS_ERROR;
}
} else {
if (value) {
value->propertyArrayIndex = BACNET_ARRAY_ALL;
}
}
/* property-value [2] ABSTRACT-SYNTAX.&Type */
if (bacnet_is_opening_tag_number(
&apdu[apdu_len], apdu_size - apdu_len, 2, &len)) {
/* determine the length of the data within the tags */
imax =
bacnet_enclosed_data_length(&apdu[apdu_len], apdu_size - apdu_len);
if (imax == BACNET_STATUS_ERROR) {
return BACNET_STATUS_ERROR;
}
/* count the opening tag number length after finding enclosed length */
apdu_len += len;
if (imax > MAX_APDU) {
/* not enough size in application_data to store the data chunk */
return BACNET_STATUS_ERROR;
} else if (value) {
/* point to the data from the APDU */
value->application_data = &apdu[apdu_len];
value->application_data_len = imax;
}
/* add on the data length */
apdu_len += imax;
if (!bacnet_is_closing_tag_number(
&apdu[apdu_len], apdu_size - apdu_len, 2, &len)) {
return BACNET_STATUS_ERROR;
}
/* count the closing tag number length */
apdu_len += len;
} else {
return BACNET_STATUS_ERROR;
}
/* priority [3] Unsigned (1..16) OPTIONAL */
if (bacnet_is_context_tag_number(
&apdu[apdu_len], apdu_size - apdu_len, 3, &len, &len_value_type)) {
apdu_len += len;
len = bacnet_unsigned_decode(
&apdu[apdu_len], apdu_size - apdu_len, len_value_type,
&unsigned_value);
if (len > 0) {
if (unsigned_value > UINT8_MAX) {
return BACNET_STATUS_ERROR;
} else {
apdu_len += len;
if (value) {
value->priority = unsigned_value;
}
}
} else {
return BACNET_STATUS_ERROR;
}
} else {
if (value) {
value->priority = BACNET_NO_PRIORITY;
}
}
return apdu_len;
}
/**
* @brief Encode the CreateObject service request
*
@@ -36,7 +181,6 @@ int create_object_encode_service_request(
{
int len = 0; /* length of each encoding */
int apdu_len = 0; /* total length of the apdu, return value */
BACNET_PROPERTY_VALUE *value = NULL; /* value in list */
if (data) {
/* object-specifier [0] */
@@ -66,29 +210,27 @@ int create_object_encode_service_request(
if (apdu) {
apdu += len;
}
if (data->list_of_initial_values) {
#if BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED
if ((data->application_data_len > 0) &&
(data->application_data_len <= sizeof(data->application_data))) {
/* list-of-initial-values [1] OPTIONAL */
len = encode_opening_tag(apdu, 1);
apdu_len += len;
if (apdu) {
apdu += len;
}
/* the first value includes a pointer to the next value, etc */
value = data->list_of_initial_values;
while (value != NULL) {
/* SEQUENCE OF BACnetPropertyValue */
len = bacapp_property_value_encode(apdu, value);
len = data->application_data_len;
if (apdu) {
memmove(apdu, data->application_data, len);
}
apdu_len += len;
if (apdu) {
apdu += len;
}
/* is there another one to encode? */
/* FIXME: check to see if there is room in the APDU */
value = value->next;
}
len = encode_closing_tag(apdu, 1);
apdu_len += len;
}
#endif
}
return apdu_len;
@@ -141,7 +283,7 @@ int create_object_decode_service_request(
BACNET_OBJECT_TYPE object_type = OBJECT_NONE;
uint32_t object_instance = 0;
uint32_t enumerated_value = 0;
BACNET_PROPERTY_VALUE *list_of_initial_values = NULL;
int imax = 0;
/* object-specifier [0] CHOICE */
if (!bacnet_is_opening_tag_number(
@@ -203,27 +345,41 @@ int create_object_decode_service_request(
apdu_len += len;
/* list-of-initial-values [1] SEQUENCE OF BACnetPropertyValue OPTIONAL */
if (bacnet_is_opening_tag_number(
&apdu[apdu_len], apdu_size - apdu_len, 0, &len)) {
apdu_len += len;
if (data) {
list_of_initial_values = data->list_of_initial_values;
}
len = bacapp_property_value_decode(
&apdu[apdu_len], apdu_size - apdu_len, list_of_initial_values);
if (len <= 0) {
&apdu[apdu_len], apdu_size - apdu_len, 1, &len)) {
/* determine the length of the data within the tags */
imax =
bacnet_enclosed_data_length(&apdu[apdu_len], apdu_size - apdu_len);
if (imax == BACNET_STATUS_ERROR) {
if (data) {
data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
/* count the opening tag number length after finding enclosed length */
apdu_len += len;
if (imax > MAX_APDU) {
/* not enough size in application_data to store the data chunk */
if (data) {
data->error_code = ERROR_CODE_REJECT_BUFFER_OVERFLOW;
}
return BACNET_STATUS_REJECT;
} else if (data) {
/* copy the data from the APDU */
#if BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED
memmove(data->application_data, &apdu[apdu_len], (size_t)imax);
#endif
data->application_data_len = imax;
}
/* add on the data length */
apdu_len += imax;
if (!bacnet_is_closing_tag_number(
&apdu[apdu_len], apdu_size - apdu_len, 0, &len)) {
&apdu[apdu_len], apdu_size - apdu_len, 1, &len)) {
if (data) {
data->error_code = ERROR_CODE_REJECT_INVALID_TAG;
}
return BACNET_STATUS_REJECT;
}
/* count the closing tag number length */
apdu_len += len;
}
@@ -441,3 +597,155 @@ int create_object_error_ack_service_decode(
return apdu_len;
}
/**
* @brief Initialize the created object with the provided initializers.
* @param data [in] The Create Object data containing the initial values.
* @param write_property [in] Function pointer to the Write Property handler.
* @return true if successful, false on error.
*/
bool create_object_initializer_list_process(
BACNET_CREATE_OBJECT_DATA *data, write_property_function write_property)
{
BACNET_WRITE_PROPERTY_DATA wp_data = { 0 };
BACNET_CREATE_OBJECT_PROPERTY_VALUE value = { 0 };
int len = 0, apdu_len = 0;
uint8_t *application_data = NULL;
if (!data) {
return false;
}
if (!write_property) {
return false;
}
data->first_failed_element_number = 1;
wp_data.object_type = data->object_type;
wp_data.object_instance = data->object_instance;
wp_data.error_class = ERROR_CLASS_PROPERTY;
wp_data.error_code = ERROR_CODE_SUCCESS;
while (data->application_data_len > apdu_len) {
#if BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED
application_data = &data->application_data[apdu_len];
#endif
len = create_object_decode_initial_value(
application_data, data->application_data_len - apdu_len, &value);
if (len <= 0) {
return false;
}
wp_data.object_property = value.propertyIdentifier;
wp_data.array_index = value.propertyArrayIndex;
memmove(
&wp_data.application_data[0], value.application_data,
(size_t)value.application_data_len);
wp_data.application_data_len = value.application_data_len;
wp_data.priority = value.priority;
if (!write_property_bacnet_array_valid(&wp_data)) {
return false;
}
/* write the property - use the provided function */
if (!write_property(&wp_data)) {
/* report the error */
data->error_class = wp_data.error_class;
data->error_code = wp_data.error_code;
return false;
}
data->first_failed_element_number++;
apdu_len += len;
}
return true;
}
/**
* @brief Process the CreateObject request.
* @param data [in,out] The Create Object data containing the request details.
* @param object_supported [in] Flag indicating if the object type is supported.
* @param object_exists [in] Flag indicating if the object already exists.
* @param create_object [in] Function pointer to the Create Object handler.
* @param delete_object [in] Function pointer to the Delete Object handler.
* @param write_property [in] Function pointer to the Write Property handler.
* @return true if successful, false on error.
*/
bool create_object_process(
BACNET_CREATE_OBJECT_DATA *data,
bool object_supported,
bool object_exists,
create_object_function create_object,
delete_object_function delete_object,
write_property_function write_property)
{
bool status = false;
uint32_t object_instance;
if (!data) {
return false;
}
if (!object_supported) {
/* The device does not support the specified object type. */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE;
} else if (!create_object) {
/* The device supports the object type and may have
sufficient space, but does not support the creation of the
object for some other reason.*/
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED;
} else if (object_exists) {
/* The object being created already exists */
data->error_class = ERROR_CLASS_OBJECT;
data->error_code = ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS;
} else {
if (data->application_data_len) {
/* The optional 'List of Initial Values' parameter is included */
object_instance = create_object(data->object_instance);
if (object_instance == BACNET_MAX_INSTANCE) {
/* The device cannot allocate the space needed
for the new object.*/
data->error_class = ERROR_CLASS_RESOURCES;
data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;
} else if (write_property) {
/* set the created object instance */
data->object_instance = object_instance;
/* If the optional 'List of Initial Values' parameter
is included, then all properties in the list shall
be initialized as indicated. */
data->error_class = ERROR_CLASS_PROPERTY;
data->error_code = ERROR_CODE_SUCCESS;
if (!create_object_initializer_list_process(
data, write_property)) {
/* initialization failed - remove the object */
if (delete_object) {
(void)delete_object(object_instance);
}
if (data->error_code == ERROR_CODE_SUCCESS) {
/* A property specified by the Property_Identifier
in the List of Initial Values does not support
initialization during the CreateObject service.*/
data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
}
} else {
data->first_failed_element_number = 0;
status = true;
}
} else {
/* cannot initialize without write property handler */
data->error_code = ERROR_CODE_WRITE_ACCESS_DENIED;
}
} else {
object_instance = create_object(data->object_instance);
if (object_instance == BACNET_MAX_INSTANCE) {
/* The device cannot allocate the space needed
for the new object.*/
data->error_class = ERROR_CLASS_RESOURCES;
data->error_code = ERROR_CODE_NO_SPACE_FOR_OBJECT;
} else {
/* required by ACK */
data->object_instance = object_instance;
data->first_failed_element_number = 0;
status = true;
}
}
}
return status;
}
+53 -2
View File
@@ -15,6 +15,12 @@
/* BACnet Stack API */
#include "bacnet/bacdcode.h"
#include "bacnet/bacapp.h"
#include "bacnet/delete_object.h"
#include "bacnet/wp.h"
#ifndef BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED
#define BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED 1
#endif
/**
* CreateObject-Request ::= SEQUENCE {
@@ -29,13 +35,42 @@ typedef struct BACnet_Create_Object_Data {
/* note: use BACNET_MAX_INSTANCE to choose CHOICE=[0] object_type */
uint32_t object_instance;
BACNET_OBJECT_TYPE object_type;
/* simple linked list of values */
BACNET_PROPERTY_VALUE *list_of_initial_values;
BACNET_ERROR_CLASS error_class;
BACNET_ERROR_CODE error_code;
#if BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED
/* list of values similar to WriteProperty - decoded later */
uint8_t application_data[MAX_APDU];
#endif
int application_data_len;
/* This parameter, of type Unsigned, shall convey the numerical
position, starting at 1, of the offending 'Initial Value' in the
'List of Initial Values' parameter received in the request.
If the request is considered invalid for reasons other than the 'List of
Initial Values' parameter, the 'First Failed Element Number' shall
be equal to zero. */
BACNET_UNSIGNED_INTEGER first_failed_element_number;
} BACNET_CREATE_OBJECT_DATA;
/**
* BACnetPropertyValue ::= SEQUENCE {
* property-identifier [0] BACnetPropertyIdentifier,
* property-array-index [1] Unsigned OPTIONAL,
* -- used only with array datatypes
* -- if omitted with an array the entire array is referenced
* property-value [2] ABSTRACT-SYNTAX.&Type,
* -- any datatype appropriate for the specified property
* priority [3] Unsigned (1..16) OPTIONAL
* -- used only when property is commandable
* }
*/
typedef struct BACnet_Create_Object_Property_Value {
BACNET_PROPERTY_ID propertyIdentifier;
BACNET_ARRAY_INDEX propertyArrayIndex;
const uint8_t *application_data;
int application_data_len;
uint8_t priority;
} BACNET_CREATE_OBJECT_PROPERTY_VALUE;
/**
* @brief CreateObject service handler for an object
* @ingroup ObjHelpers
@@ -49,6 +84,10 @@ typedef uint32_t (*create_object_function)(uint32_t object_instance);
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
int create_object_encode_initial_value(
uint8_t *apdu, int offset, const BACNET_PROPERTY_VALUE *value);
BACNET_STACK_EXPORT
size_t create_object_service_request_encode(
uint8_t *apdu, size_t apdu_size, BACNET_CREATE_OBJECT_DATA *data);
@@ -82,6 +121,18 @@ BACNET_STACK_EXPORT
int create_object_error_ack_encode(
uint8_t *apdu, uint8_t invoke_id, const BACNET_CREATE_OBJECT_DATA *data);
BACNET_STACK_EXPORT
bool create_object_initializer_list_process(
BACNET_CREATE_OBJECT_DATA *data, write_property_function write_property);
BACNET_STACK_EXPORT
bool create_object_process(
BACNET_CREATE_OBJECT_DATA *data,
bool object_supported,
bool object_exists,
create_object_function create_object,
delete_object_function delete_object,
write_property_function write_property);
#ifdef __cplusplus
}
#endif /* __cplusplus */
@@ -92,7 +92,9 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/datalink/bvlc.c
${SRC_DIR}/bacnet/datalink/bvlc6.c
${SRC_DIR}/bacnet/cov.c
${SRC_DIR}/bacnet/create_object.c
${SRC_DIR}/bacnet/datetime.c
${SRC_DIR}/bacnet/delete_object.c
${SRC_DIR}/bacnet/dcc.c
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
@@ -97,7 +97,9 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/datalink/bvlc.c
${SRC_DIR}/bacnet/datalink/bvlc6.c
${SRC_DIR}/bacnet/cov.c
${SRC_DIR}/bacnet/create_object.c
${SRC_DIR}/bacnet/datetime.c
${SRC_DIR}/bacnet/delete_object.c
${SRC_DIR}/bacnet/dcc.c
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
@@ -376,7 +376,7 @@ static void testDevice(void)
count = Device_Object_List_Count();
create_data.object_type = OBJECT_ANALOG_VALUE;
create_data.object_instance = BACNET_MAX_INSTANCE;
create_data.list_of_initial_values = NULL;
create_data.application_data_len = 0;
create_data.error_class = ERROR_CLASS_DEVICE;
create_data.error_code = ERROR_CODE_SUCCESS;
create_data.first_failed_element_number = 0;
@@ -410,7 +410,7 @@ static void testDevice(void)
/* known object without create */
create_data.object_type = OBJECT_DEVICE;
create_data.object_instance = BACNET_MAX_INSTANCE;
create_data.list_of_initial_values = NULL;
create_data.application_data_len = 0;
create_data.error_class = ERROR_CLASS_DEVICE;
create_data.error_code = ERROR_CODE_SUCCESS;
create_data.first_failed_element_number = 0;
+2
View File
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/indtext.c
${SRC_DIR}/bacnet/hostnport.c
${SRC_DIR}/bacnet/lighting.c
${SRC_DIR}/bacnet/proplist.c
${SRC_DIR}/bacnet/shed_level.c
${SRC_DIR}/bacnet/timer_value.c
${SRC_DIR}/bacnet/timestamp.c
@@ -64,6 +65,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/wp.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
+196 -3
View File
@@ -10,15 +10,64 @@
#include <zephyr/ztest.h>
#include <bacnet/bacdest.h>
#include <bacnet/create_object.h>
#include <bacnet/bactext.h>
static uint32_t Test_Create_Object_Returned_Instance = BACNET_MAX_INSTANCE;
static uint32_t Test_Delete_Object_Instance = BACNET_MAX_INSTANCE;
static BACNET_WRITE_PROPERTY_DATA Test_Write_Property_Data;
static bool Test_Write_Property_Return_Status;
/**
* @brief CreateObject service handler for an object
* @ingroup ObjHelpers
* @param object_instance [in] instance number of the object to create,
* or BACNET_MAX_INSTANCE to create the next free object instance
* @return object instance number created, or BACNET_MAX_INSTANCE if not
*/
static uint32_t test_create_object_function(uint32_t object_instance)
{
(void)object_instance;
return Test_Create_Object_Returned_Instance;
}
/**
* @brief DeleteObject service handler for an object
* @ingroup ObjHelpers
* @param object_instance [in] instance number of the object to delete
* @return true if the object instance is deleted
*/
static bool test_delete_object_function(uint32_t object_instance)
{
Test_Delete_Object_Instance = object_instance;
return true;
}
/** Attempts to write a new value to one property for this object type
* of a given instance.
* A function template; @see device.c for assignment to object types.
* @ingroup ObjHelpers
*
* @param wp_data [in] Pointer to the BACnet_Write_Property_Data structure,
* which is packed with the information from the WP request.
* @return The length of the apdu encoded or -1 for error or
* -2 for abort message.
*/
static bool test_write_property_function(BACNET_WRITE_PROPERTY_DATA *wp_data)
{
Test_Write_Property_Data = *wp_data;
return Test_Write_Property_Return_Status;
}
static void test_CreateObjectCodec(BACNET_CREATE_OBJECT_DATA *data)
{
uint8_t apdu[MAX_APDU] = { 0 };
BACNET_CREATE_OBJECT_DATA test_data = { 0 };
int len = 0, apdu_len = 0, null_len = 0, test_len = 0;
bool object_supported = false, object_exists = false, status = false;
null_len = create_object_encode_service_request(NULL, data);
apdu_len = create_object_encode_service_request(apdu, data);
test_len = create_object_service_request_encode(NULL, 0, data);
zassert_equal(test_len, 0, NULL);
null_len = create_object_service_request_encode(NULL, sizeof(apdu), data);
apdu_len = create_object_service_request_encode(apdu, sizeof(apdu), data);
zassert_equal(apdu_len, null_len, NULL);
zassert_true(apdu_len != BACNET_STATUS_ERROR, NULL);
null_len = create_object_decode_service_request(apdu, apdu_len, NULL);
@@ -29,9 +78,101 @@ static void test_CreateObjectCodec(BACNET_CREATE_OBJECT_DATA *data)
while (test_len) {
test_len--;
len = create_object_decode_service_request(apdu, test_len, &test_data);
if (data->object_instance == BACNET_MAX_INSTANCE) {
if (test_len == 4) {
/* optional list-of-values */
continue;
}
} else {
if (test_len == 7) {
/* optional list-of-values */
continue;
}
}
zassert_equal(
len, BACNET_STATUS_REJECT, "len=%d test_len=%d", len, test_len);
}
/* test service processing */
apdu_len = create_object_service_request_encode(apdu, sizeof(apdu), data);
test_len = create_object_decode_service_request(apdu, apdu_len, &test_data);
zassert_equal(test_len, apdu_len, NULL);
/* processing - error case */
status = create_object_process(
NULL, object_supported, object_exists, NULL, NULL, NULL);
zassert_equal(status, false, NULL);
/* processing - error case */
status = create_object_process(
data, object_supported, object_exists, NULL, NULL, NULL);
zassert_equal(status, false, NULL);
zassert_equal(data->error_class, ERROR_CLASS_OBJECT, NULL);
zassert_equal(data->error_code, ERROR_CODE_UNSUPPORTED_OBJECT_TYPE, NULL);
/* processing - error case */
object_supported = true;
object_exists = true;
status = create_object_process(
data, object_supported, object_exists, test_create_object_function,
test_delete_object_function, test_write_property_function);
zassert_equal(status, false, NULL);
zassert_equal(data->error_class, ERROR_CLASS_OBJECT, NULL);
zassert_equal(
data->error_code, ERROR_CODE_OBJECT_IDENTIFIER_ALREADY_EXISTS, NULL);
/* processing - error case */
object_supported = true;
object_exists = false;
status = create_object_process(
data, object_supported, object_exists, NULL,
test_delete_object_function, test_write_property_function);
zassert_equal(status, false, NULL);
zassert_equal(data->error_class, ERROR_CLASS_OBJECT, NULL);
zassert_equal(
data->error_code, ERROR_CODE_DYNAMIC_CREATION_NOT_SUPPORTED, NULL);
/* processing - error case */
object_supported = true;
object_exists = false;
Test_Create_Object_Returned_Instance = BACNET_MAX_INSTANCE;
status = create_object_process(
data, object_supported, object_exists, test_create_object_function,
test_delete_object_function, test_write_property_function);
zassert_equal(status, false, NULL);
zassert_equal(data->error_class, ERROR_CLASS_RESOURCES, NULL);
zassert_equal(data->error_code, ERROR_CODE_NO_SPACE_FOR_OBJECT, NULL);
/* processing - error case */
object_supported = true;
object_exists = false;
Test_Create_Object_Returned_Instance = 12345;
Test_Write_Property_Return_Status = false;
status = create_object_process(
data, object_supported, object_exists, test_create_object_function,
test_delete_object_function, test_write_property_function);
if (data->application_data_len) {
zassert_equal(status, false, NULL);
zassert_equal(
data->error_class, ERROR_CLASS_PROPERTY, "error_class=%s",
bactext_error_class_name(data->error_class));
zassert_equal(
data->error_code, ERROR_CODE_WRITE_ACCESS_DENIED, "error_code=%s",
bactext_error_code_name(data->error_code));
} else {
zassert_equal(status, true, NULL);
}
/* processing - successful case */
object_supported = true;
object_exists = false;
if (data->object_instance != BACNET_MAX_INSTANCE) {
Test_Create_Object_Returned_Instance = data->object_instance;
} else {
Test_Create_Object_Returned_Instance = 12345;
}
Test_Write_Property_Return_Status = true;
status = create_object_process(
data, object_supported, object_exists, test_create_object_function,
test_delete_object_function, test_write_property_function);
zassert_equal(status, true, NULL);
zassert_equal(
data->object_instance, Test_Create_Object_Returned_Instance, NULL);
zassert_equal(
data->first_failed_element_number, 0, "first_failed_element_number=%u",
data->first_failed_element_number);
}
#if defined(CONFIG_ZTEST_NEW_API)
@@ -40,8 +181,55 @@ ZTEST(create_object_tests, test_CreateObject)
static void test_CreateObject(void)
#endif
{
BACNET_CREATE_OBJECT_DATA data = { 0 };
BACNET_CREATE_OBJECT_DATA data = {
.object_instance = 1,
.object_type = OBJECT_ANALOG_OUTPUT,
.application_data_len = 0,
.application_data = { 0 },
.error_class = ERROR_CLASS_PROPERTY,
.error_code = ERROR_CODE_SUCCESS,
};
BACNET_PROPERTY_VALUE value[2] = {
{ .next = NULL,
.priority = BACNET_NO_PRIORITY,
.propertyArrayIndex = BACNET_ARRAY_ALL,
.propertyIdentifier = PROP_OBJECT_NAME,
.value = { .tag = BACNET_APPLICATION_TAG_CHARACTER_STRING,
.type.Character_String = { .encoding = CHARACTER_UTF8,
.length = 4,
.value = "Test" } } },
{ .next = NULL,
.priority = 1,
.propertyArrayIndex = BACNET_ARRAY_ALL,
.propertyIdentifier = PROP_PRESENT_VALUE,
.value = { .tag = BACNET_APPLICATION_TAG_REAL, .type.Real = 42.0 } },
};
int len = 0;
/* encode two initial values */
len = create_object_encode_initial_value(
data.application_data, len, &value[0]);
data.application_data_len += len;
len = create_object_encode_initial_value(
data.application_data, len, &value[1]);
data.application_data_len += len;
/* test encoding and decoding of CreateObject service */
test_CreateObjectCodec(&data);
data.object_instance = BACNET_MAX_INSTANCE;
test_CreateObjectCodec(&data);
/* validate the last write */
zassert_equal(
Test_Write_Property_Data.object_instance, data.object_instance, NULL);
zassert_equal(Test_Write_Property_Data.object_type, data.object_type, NULL);
zassert_equal(
Test_Write_Property_Data.array_index, value[1].propertyArrayIndex,
NULL);
zassert_equal(
Test_Write_Property_Data.object_property, value[1].propertyIdentifier,
NULL);
/* test with no initial values */
data.object_instance = 1;
data.application_data_len = 0;
test_CreateObjectCodec(&data);
data.object_instance = BACNET_MAX_INSTANCE;
test_CreateObjectCodec(&data);
@@ -105,6 +293,11 @@ static void test_CreateObjectError(void)
null_len = create_object_error_ack_service_encode(NULL, &data);
apdu_len = create_object_error_ack_service_encode(apdu, &data);
zassert_equal(apdu_len, null_len, NULL);
null_len =
create_object_error_ack_service_decode(NULL, apdu_len, &test_data);
zassert_equal(null_len, BACNET_STATUS_REJECT, NULL);
null_len = create_object_error_ack_service_decode(apdu, apdu_len, NULL);
zassert_equal(apdu_len, null_len, NULL);
test_len =
create_object_error_ack_service_decode(apdu, apdu_len, &test_data);
zassert_equal(apdu_len, test_len, NULL);