diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ea477d8..b5e8f96d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/apps/blinkt/device.c b/apps/blinkt/device.c index e8abd201..cb7caaa4 100644 --- a/apps/blinkt/device.c +++ b/apps/blinkt/device.c @@ -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; - } 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; - Device_Inc_Database_Revision(); - status = true; - } - } + 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 { - /* The device does not support the specified object type. */ - data->error_class = ERROR_CLASS_OBJECT; - data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + /* fill in the error values */ + status = create_object_process( + data, object_supported, object_exists, NULL, NULL, NULL); + } + if (status) { + Device_Inc_Database_Revision(); } return status; diff --git a/apps/create-object/main.c b/apps/create-object/main.c index 4a9499e1..cea2db0d 100644 --- a/apps/create-object/main.c +++ b/apps/create-object/main.c @@ -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" - "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"); + printf( + "object-instance:\n" + "This is the object instance number of the object that\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)) { diff --git a/apps/piface/device.c b/apps/piface/device.c index b26f802d..23eca7c7 100644 --- a/apps/piface/device.c +++ b/apps/piface/device.c @@ -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; - } 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; - Device_Inc_Database_Revision(); - status = true; - } - } + 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 { - /* The device does not support the specified object type. */ - data->error_class = ERROR_CLASS_OBJECT; - data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + /* fill in the error values */ + status = create_object_process( + data, object_supported, object_exists, NULL, NULL, NULL); + } + if (status) { + Device_Inc_Database_Revision(); } return status; diff --git a/src/bacnet/basic/object/channel.c b/src/bacnet/basic/object/channel.c index df28b69c..f7bd8e1d 100644 --- a/src/bacnet/basic/object/channel.c +++ b/src/bacnet/basic/object/channel.c @@ -721,9 +721,12 @@ 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_Internal_Callback(&wp_data); + status = write_property_bacnet_array_valid(&wp_data); if (status) { - wp_data.error_code = ERROR_CODE_SUCCESS; + status = Write_Property_Internal_Callback(&wp_data); + if (status) { + wp_data.error_code = ERROR_CODE_SUCCESS; + } } debug_printf( "channel[%lu].Channel_Write_Member[%u] " diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 6e75ecc8..7b1869f4 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -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; - } 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; - Device_Inc_Database_Revision(); - status = true; - } - } + 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 { - /* The device does not support the specified object type. */ - data->error_class = ERROR_CLASS_OBJECT; - data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + /* fill in the error values */ + status = create_object_process( + data, object_supported, object_exists, NULL, NULL, NULL); + } + if (status) { + Device_Inc_Database_Revision(); } return status; diff --git a/src/bacnet/basic/object/loop.c b/src/bacnet/basic/object/loop.c index 39222788..62ba6194 100644 --- a/src/bacnet/basic/object/loop.c +++ b/src/bacnet/basic/object/loop.c @@ -2051,9 +2051,12 @@ 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_Internal_Callback(&wp_data); + status = write_property_bacnet_array_valid(&wp_data); if (status) { - wp_data.error_code = ERROR_CODE_SUCCESS; + status = Write_Property_Internal_Callback(&wp_data); + if (status) { + wp_data.error_code = ERROR_CODE_SUCCESS; + } } } } diff --git a/src/bacnet/basic/object/timer.c b/src/bacnet/basic/object/timer.c index 0a686add..078efafc 100644 --- a/src/bacnet/basic/object/timer.c +++ b/src/bacnet/basic/object/timer.c @@ -496,9 +496,12 @@ static bool Timer_Write_Members( wp_data.application_data, sizeof(wp_data.application_data), value); if (Write_Property_Internal_Callback) { - status = Write_Property_Internal_Callback(&wp_data); + status = write_property_bacnet_array_valid(&wp_data); if (status) { - wp_data.error_code = ERROR_CODE_SUCCESS; + status = Write_Property_Internal_Callback(&wp_data); + if (status) { + wp_data.error_code = ERROR_CODE_SUCCESS; + } } } } diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c index e6ccec61..8545195e 100644 --- a/src/bacnet/basic/server/bacnet_device.c +++ b/src/bacnet/basic/server/bacnet_device.c @@ -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; - } 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; - Device_Inc_Database_Revision(); - status = true; - } - } + 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 { - /* The device does not support the specified object type. */ - data->error_class = ERROR_CLASS_OBJECT; - data->error_code = ERROR_CODE_UNSUPPORTED_OBJECT_TYPE; + /* fill in the error values */ + status = create_object_process( + data, object_supported, object_exists, NULL, NULL, NULL); + } + if (status) { + Device_Inc_Database_Revision(); } return status; diff --git a/src/bacnet/basic/service/h_create_object.c b/src/bacnet/basic/service/h_create_object.c index af9fb242..dffbcfd5 100644 --- a/src/bacnet/basic/service/h_create_object.c +++ b/src/bacnet/basic/service/h_create_object.c @@ -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. diff --git a/src/bacnet/basic/service/s_create_object.c b/src/bacnet/basic/service/s_create_object.c index b4ff51b1..6cda8ce6 100644 --- a/src/bacnet/basic/service/s_create_object.c +++ b/src/bacnet/basic/service/s_create_object.c @@ -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; diff --git a/src/bacnet/create_object.c b/src/bacnet/create_object.c index 7bcb8a6f..5cf9f14e 100644 --- a/src/bacnet/create_object.c +++ b/src/bacnet/create_object.c @@ -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); - 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 = data->application_data_len; + if (apdu) { + memmove(apdu, data->application_data, len); + } + apdu_len += len; + if (apdu) { + apdu += len; } 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; +} diff --git a/src/bacnet/create_object.h b/src/bacnet/create_object.h index f7ead44c..a7f24a8d 100644 --- a/src/bacnet/create_object.h +++ b/src/bacnet/create_object.h @@ -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 */ diff --git a/test/bacnet/basic/object/device/CMakeLists.txt b/test/bacnet/basic/object/device/CMakeLists.txt index 057421bf..01a649c6 100644 --- a/test/bacnet/basic/object/device/CMakeLists.txt +++ b/test/bacnet/basic/object/device/CMakeLists.txt @@ -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 diff --git a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt index ce9a3e12..b4b5f3ad 100644 --- a/test/bacnet/basic/server/bacnet_device/CMakeLists.txt +++ b/test/bacnet/basic/server/bacnet_device/CMakeLists.txt @@ -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 diff --git a/test/bacnet/basic/server/bacnet_device/src/main.c b/test/bacnet/basic/server/bacnet_device/src/main.c index 9df8294f..efd5eacb 100644 --- a/test/bacnet/basic/server/bacnet_device/src/main.c +++ b/test/bacnet/basic/server/bacnet_device/src/main.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; diff --git a/test/bacnet/create_object/CMakeLists.txt b/test/bacnet/create_object/CMakeLists.txt index 2c21fbe0..c9425180 100644 --- a/test/bacnet/create_object/CMakeLists.txt +++ b/test/bacnet/create_object/CMakeLists.txt @@ -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 diff --git a/test/bacnet/create_object/src/main.c b/test/bacnet/create_object/src/main.c index 25131e75..a91f547d 100644 --- a/test/bacnet/create_object/src/main.c +++ b/test/bacnet/create_object/src/main.c @@ -10,15 +10,64 @@ #include #include #include +#include + +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);