diff --git a/.gitignore b/.gitignore index 89f85416..25b102f0 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,6 @@ apps/piface/libpifacedigital/ /apps/mstpcrc/mstpcrc /apps/add-list-element/bacale /apps/remove-list-element/bacrle +/apps/create-object/bacco +/apps/delete-object/bacdo diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c567b9d..d04ad7ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,10 +227,14 @@ add_library(${PROJECT_NAME} src/bacnet/basic/service/h_awf.h src/bacnet/basic/service/h_ccov.c src/bacnet/basic/service/h_ccov.h + src/bacnet/basic/service/h_create_object.c + src/bacnet/basic/service/h_create_object.h src/bacnet/basic/service/h_cov.c src/bacnet/basic/service/h_cov.h src/bacnet/basic/service/h_dcc.c src/bacnet/basic/service/h_dcc.h + src/bacnet/basic/service/h_delete_object.c + src/bacnet/basic/service/h_delete_object.h src/bacnet/basic/service/h_gas_a.c src/bacnet/basic/service/h_gas_a.h src/bacnet/basic/service/h_get_alarm_sum.c @@ -287,10 +291,14 @@ add_library(${PROJECT_NAME} src/bacnet/basic/service/s_awfs.h src/bacnet/basic/service/s_cevent.c src/bacnet/basic/service/s_cevent.h + src/bacnet/basic/service/s_create_object.c + src/bacnet/basic/service/s_create_object.h src/bacnet/basic/service/s_cov.c src/bacnet/basic/service/s_cov.h src/bacnet/basic/service/s_dcc.c src/bacnet/basic/service/s_dcc.h + src/bacnet/basic/service/s_delete_object.c + src/bacnet/basic/service/s_delete_object.h src/bacnet/basic/service/s_error.c src/bacnet/basic/service/s_error.h src/bacnet/basic/service/s_get_alarm_sum.c @@ -358,6 +366,8 @@ add_library(${PROJECT_NAME} src/bacnet/config.h src/bacnet/cov.c src/bacnet/cov.h + src/bacnet/create_object.c + src/bacnet/create_object.h src/bacnet/credential_authentication_factor.c src/bacnet/credential_authentication_factor.h src/bacnet/datalink/arcnet.h @@ -387,6 +397,8 @@ add_library(${PROJECT_NAME} src/bacnet/datetime.h src/bacnet/dcc.c src/bacnet/dcc.h + src/bacnet/delete_object.c + src/bacnet/delete_object.h src/bacnet/event.c src/bacnet/event.h src/bacnet/get_alarm_sum.c @@ -560,9 +572,15 @@ if(BACNET_STACK_BUILD_APPS) add_executable(add-list-element apps/add-list-element/main.c) target_link_libraries(add-list-element PRIVATE ${PROJECT_NAME}) + add_executable(create-object apps/create-object/main.c) + target_link_libraries(create-object PRIVATE ${PROJECT_NAME}) + add_executable(dcc apps/dcc/main.c) target_link_libraries(dcc PRIVATE ${PROJECT_NAME}) + add_executable(delete-object apps/delete-object/main.c) + target_link_libraries(delete-object PRIVATE ${PROJECT_NAME}) + add_executable(epics apps/epics/main.c) target_link_libraries(epics PRIVATE ${PROJECT_NAME}) diff --git a/apps/Makefile b/apps/Makefile index f246cc9c..75712c5a 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -177,7 +177,8 @@ endif SUBDIRS = lib readprop writeprop readfile writefile reinit server dcc \ whohas whois iam ucov scov timesync epics readpropm readrange \ writepropm uptransfer getevent uevent abort error event ack-alarm \ - server-client add-list-element remove-list-element + server-client add-list-element remove-list-element create-object \ + delete-object ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) SUBDIRS += whoisrouter iamrouter initrouter whatisnetnum netnumis diff --git a/apps/create-object/Makefile b/apps/create-object/Makefile new file mode 100644 index 00000000..5bb2fed9 --- /dev/null +++ b/apps/create-object/Makefile @@ -0,0 +1,39 @@ +#Makefile to build BACnet Application using GCC compiler + +# Executable file name +TARGET = bacco +# BACnet objects that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +SRC = main.c \ + $(BACNET_OBJECT_DIR)/client/device-client.c \ + $(BACNET_OBJECT_DIR)/netport.c + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +OBJS += ${SRC:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +.PHONY: depend +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +.PHONY: clean +clean: + rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET} + +.PHONY: include +include: .depend diff --git a/apps/create-object/main.c b/apps/create-object/main.c new file mode 100644 index 00000000..0a1336a9 --- /dev/null +++ b/apps/create-object/main.c @@ -0,0 +1,387 @@ +/** + * @file + * @brief Application to send a BACnet CreateObject + * @author Steve Karg + * @date August 2023 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include + +#define PRINT_ENABLED 1 + +#include "bacnet/bacdef.h" +#include "bacnet/config.h" +#include "bacnet/bactext.h" +#include "bacnet/bacdest.h" +#include "bacnet/bacerror.h" +#include "bacnet/iam.h" +#include "bacnet/arf.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/create_object.h" +#include "bacnet/abort.h" +#include "bacnet/reject.h" +#include "bacnet/whois.h" +#include "bacnet/version.h" +/* some demo stuff needed */ +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/dlenv.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; +/* target device data for the request */ +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; +/* needed to filter incoming messages */ +static uint8_t Request_Invoke_ID = 0; +static BACNET_ADDRESS Target_Address; +/* needed for return value of main application */ +static bool Error_Detected = false; +/* Used for verbose */ +static bool Verbose = false; + +static void MyPrintHandler( + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_ERROR_CLASS error_class, + BACNET_ERROR_CODE error_code, + uint32_t first_failed_element_number) +{ + printf("[{\n \"%s\": {\n" + " \"object-type\": \"%s\",\n \"object-instance\": %lu,\n" + " \"error-class\": \"%s\",\n \"error-code\": \"%s\"", + bactext_confirmed_service_name(SERVICE_CONFIRMED_CREATE_OBJECT), + bactext_object_type_name(object_type), + (unsigned long)object_instance, + bactext_error_class_name((int)error_class), + bactext_error_code_name((int)error_code)); + if (first_failed_element_number > 0) { + printf(",\n \"first-failed-element-number\": %lu", + (unsigned long)first_failed_element_number); + } + printf("\n }\n}]\n"); +} + +static void MyCreateObjectErrorHandler(BACNET_ADDRESS *src, + uint8_t invoke_id, + uint8_t service_choice, + uint8_t *service_request, + uint16_t service_len) +{ + int len = 0; + BACNET_CREATE_OBJECT_DATA data; + + (void)service_choice; + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + len = create_object_error_ack_service_decode(service_request, + service_len, &data); + if (len > 0) { + MyPrintHandler(data.object_type, data.object_instance, + data.error_class, data.error_code, + data.first_failed_element_number); + } + Error_Detected = true; + } +} + +static void MyCreateObjectAckHandler(uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data) +{ + BACNET_CREATE_OBJECT_DATA data; + int len = 0; + + if (address_match(&Target_Address, src) && + (service_data->invoke_id == Request_Invoke_ID)) { + len = create_object_ack_service_decode( + service_request, service_len, &data); + if (len < 0) { + MyPrintHandler(Target_Object_Type, Target_Object_Instance, + ERROR_CLASS_SERVICES, ERROR_CODE_REJECT_OTHER, 0); + } else { + MyPrintHandler(data.object_type, data.object_instance, + ERROR_CLASS_SERVICES, ERROR_CODE_SUCCESS, 0); + } + } else { + if (Verbose) { + printf("CreateObjectACK - not matched\n"); + } + } +} + +static void MyAbortHandler( + BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + (void)server; + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + MyPrintHandler(Target_Object_Type, Target_Object_Instance, + ERROR_CLASS_SERVICES, abort_convert_to_error_code(abort_reason), 0); + Error_Detected = true; + } +} + +static void MyRejectHandler( + BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + MyPrintHandler(Target_Object_Type, Target_Object_Instance, + ERROR_CLASS_SERVICES, + reject_convert_to_error_code(reject_reason), 0); + Error_Detected = true; + } +} + +static void Init_Service_Handlers(void) +{ + Device_Init(NULL); + /* we need to handle who-is + to support dynamic device binding to us */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + /* handle i-am to support binding to other devices */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); + /* set the handler for all the services we don't implement + It is required to send the proper reject message... */ + apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); + /* we must implement read property - it's required! */ + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); + /* handle the ack or error coming back from confirmed request */ + apdu_set_confirmed_ack_handler( + SERVICE_CONFIRMED_CREATE_OBJECT, MyCreateObjectAckHandler); + apdu_set_complex_error_handler( + SERVICE_CONFIRMED_CREATE_OBJECT, MyCreateObjectErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +static void print_usage(char *filename) +{ + printf( + "Usage: %s device-instance object-type [object-instance]\n", filename); + printf(" [--dnet][--dadr][--mac]\n"); + printf(" [--version][--help][--verbose]\n"); +} + +static void print_help(char *filename) +{ + printf("Create an object in a BACnet device.\n"); + printf("\n"); + printf("device-instance:\n" + "BACnet Device Object Instance number that you are\n" + "trying to communicate to. This number will be used\n" + "to try and bind with the device using Who-Is and\n" + "I-Am services. For example, if you were writing\n" + "Device Object 123, the device-instance would be 123.\n"); + printf("\n"); + printf("object-type:\n" + "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"); + 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("\n"); + printf("Example:\n" + "If you want to CreateObject of an Analog Input 1\n" + "send the following command:\n" + "%s 123 0 1\n", + filename); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + unsigned max_apdu = 0; + struct mstimer apdu_timer; + struct mstimer maintenance_timer; + bool found = false; + unsigned object_type = 0; + unsigned object_instance = 0; + long dnet = -1; + BACNET_MAC_ADDRESS mac = { 0 }; + BACNET_MAC_ADDRESS adr = { 0 }; + BACNET_ADDRESS dest = { 0 }; + bool specific_address = false; + unsigned int target_args = 0; + int argi = 0; + char *filename = NULL; + + filename = filename_remove_path(argv[0]); + for (argi = 1; argi < argc; argi++) { + if (strcmp(argv[argi], "--help") == 0) { + print_usage(filename); + print_help(filename); + return 0; + } + if (strcmp(argv[argi], "--version") == 0) { + printf("%s %s\n", filename, BACNET_VERSION_TEXT); + printf("Copyright (C) 2023 by Steve Karg and others.\n" + "This is free software; see the source for copying " + "conditions.\n" + "There is NO warranty; not even for MERCHANTABILITY or\n" + "FITNESS FOR A PARTICULAR PURPOSE.\n"); + return 0; + } + if (strcmp(argv[argi], "--mac") == 0) { + if (++argi < argc) { + if (bacnet_address_mac_from_ascii(&mac, argv[argi])) { + specific_address = true; + } + } + } else if (strcmp(argv[argi], "--dnet") == 0) { + if (++argi < argc) { + dnet = strtol(argv[argi], NULL, 0); + if ((dnet >= 0) && (dnet <= UINT16_MAX)) { + specific_address = true; + } + } + } else if (strcmp(argv[argi], "--dadr") == 0) { + if (++argi < argc) { + if (bacnet_address_mac_from_ascii(&adr, argv[argi])) { + specific_address = true; + } + } + } else if (strcmp(argv[argi], "--verbose") == 0) { + Verbose = true; + } else { + if (target_args == 0) { + object_instance = strtoul(argv[argi], NULL, 0); + if (object_instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, + "device-instance=%u - it must be less than %u\n", + object_instance, BACNET_MAX_INSTANCE); + return 1; + } + Target_Device_Object_Instance = object_instance; + target_args++; + } else if (target_args == 1) { + if (bactext_object_type_strtol(argv[argi], &object_type) == + false) { + fprintf(stderr, "object-type=%s invalid\n", argv[argi]); + return 1; + } + Target_Object_Type = object_type; + target_args++; + } else if (target_args == 2) { + object_instance = strtoul(argv[argi], NULL, 0); + if (object_instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, + "object-instance=%u - it must be less than %u\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE); + return 1; + } + Target_Object_Instance = object_instance; + if (Verbose) { + printf("Instance=%u=%s\n", object_instance, argv[argi]); + } + target_args++; + } + } + } + if (target_args < 2) { + print_usage(filename); + return 0; + } + /* setup my info */ + address_init(); + if (specific_address) { + bacnet_address_init(&dest, &mac, dnet, &adr); + address_add(Target_Device_Object_Instance, MAX_APDU, &dest); + } + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + dlenv_init(); + atexit(datalink_cleanup); + mstimer_init(); + mstimer_set(&apdu_timer, apdu_timeout()); + mstimer_set(&maintenance_timer, 1000); + /* try to bind with the device */ + found = address_bind_request( + Target_Device_Object_Instance, &max_apdu, &Target_Address); + if (found) { + if (Verbose) { + printf("Found Device %u in address_cache.\n", + Target_Device_Object_Instance); + } + } else { + Send_WhoIs( + Target_Device_Object_Instance, Target_Device_Object_Instance); + } + /* loop forever */ + for (;;) { + if (found) { + /* device is bound! */ + if (Request_Invoke_ID == 0) { + if (Verbose) { + printf("Sending CreateObject to Device %u.\n", + Target_Device_Object_Instance); + } + Request_Invoke_ID = + Send_Create_Object_Request(Target_Device_Object_Instance, + Target_Object_Type, Target_Object_Instance); + } else if (tsm_invoke_id_free(Request_Invoke_ID)) { + break; + } else if (tsm_invoke_id_failed(Request_Invoke_ID)) { + MyPrintHandler(Target_Object_Type, Target_Object_Instance, + ERROR_CLASS_COMMUNICATION, ERROR_CODE_ABORT_TSM_TIMEOUT, 0); + tsm_free_invoke_id(Request_Invoke_ID); + Error_Detected = true; + /* abort */ + break; + } + } else { + found = address_bind_request( + Target_Device_Object_Instance, &max_apdu, &Target_Address); + } + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (mstimer_expired(&maintenance_timer)) { + mstimer_reset(&maintenance_timer); + tsm_timer_milliseconds(mstimer_interval(&maintenance_timer)); + } + if (mstimer_expired(&apdu_timer)) { + MyPrintHandler(Target_Object_Type, Target_Object_Instance, + ERROR_CLASS_COMMUNICATION, + ERROR_CODE_ABORT_APPLICATION_EXCEEDED_REPLY_TIME, 0); + Error_Detected = true; + } + if (Error_Detected) { + break; + } + } + if (Error_Detected) { + return 1; + } + + return 0; +} diff --git a/apps/delete-object/Makefile b/apps/delete-object/Makefile new file mode 100644 index 00000000..9c3c5605 --- /dev/null +++ b/apps/delete-object/Makefile @@ -0,0 +1,39 @@ +#Makefile to build BACnet Application using GCC compiler + +# Executable file name +TARGET = bacdo +# BACnet objects that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +SRC = main.c \ + $(BACNET_OBJECT_DIR)/client/device-client.c \ + $(BACNET_OBJECT_DIR)/netport.c + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +OBJS += ${SRC:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +.PHONY: depend +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +.PHONY: clean +clean: + rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET} + +.PHONY: include +include: .depend diff --git a/apps/delete-object/main.c b/apps/delete-object/main.c new file mode 100644 index 00000000..33004347 --- /dev/null +++ b/apps/delete-object/main.c @@ -0,0 +1,347 @@ +/** + * @file + * @brief Application to send a BACnet DeleteObject + * @author Steve Karg + * @date August 2023 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include + +#define PRINT_ENABLED 1 + +#include "bacnet/bacdef.h" +#include "bacnet/config.h" +#include "bacnet/bactext.h" +#include "bacnet/bacdest.h" +#include "bacnet/bacerror.h" +#include "bacnet/iam.h" +#include "bacnet/arf.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/create_object.h" +#include "bacnet/whois.h" +#include "bacnet/abort.h" +#include "bacnet/reject.h" +#include "bacnet/version.h" +/* some demo stuff needed */ +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/dlenv.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; +/* target device data for the request */ +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; +/* needed to filter incoming messages */ +static uint8_t Request_Invoke_ID = 0; +static BACNET_ADDRESS Target_Address; +/* needed for return value of main application */ +static bool Error_Detected = false; +/* Used for verbose */ +static bool Verbose = false; + +static void MyPrintHandler( + BACNET_ERROR_CLASS error_class, + BACNET_ERROR_CODE error_code) +{ + printf("[{\n \"%s\": {\n" + " \"object-type\": \"%s\",\n \"object-instance\": %lu,\n" + " \"error-class\": \"%s\",\n \"error-code\": \"%s\"\n }\n}]\n", + bactext_confirmed_service_name(SERVICE_CONFIRMED_DELETE_OBJECT), + bactext_object_type_name(Target_Object_Type), + (unsigned long)Target_Object_Instance, + bactext_error_class_name((int)error_class), + bactext_error_code_name((int)error_code)); +} + +static void MyErrorHandler(BACNET_ADDRESS *src, + uint8_t invoke_id, + BACNET_ERROR_CLASS error_class, + BACNET_ERROR_CODE error_code) +{ + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + MyPrintHandler(error_class, error_code); + Error_Detected = true; + } +} + +static void MySimpleAckHandler( + BACNET_ADDRESS *src, uint8_t invoke_id) +{ + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + MyPrintHandler(ERROR_CLASS_SERVICES, ERROR_CODE_SUCCESS); + } +} + +static void MyAbortHandler( + BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + (void)server; + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + MyPrintHandler(ERROR_CLASS_SERVICES, + abort_convert_to_error_code(abort_reason)); + Error_Detected = true; + } +} + +static void MyRejectHandler( + BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t reject_reason) +{ + if (address_match(&Target_Address, src) && + (invoke_id == Request_Invoke_ID)) { + MyPrintHandler(ERROR_CLASS_SERVICES, + reject_convert_to_error_code(reject_reason)); + Error_Detected = true; + } +} + +static void Init_Service_Handlers(void) +{ + Device_Init(NULL); + /* we need to handle who-is + to support dynamic device binding to us */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is); + /* handle i-am to support binding to other devices */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_bind); + /* set the handler for all the services we don't implement + It is required to send the proper reject message... */ + apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service); + /* we must implement read property - it's required! */ + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property); + /* handle the ack or error coming back from confirmed request */ + apdu_set_confirmed_simple_ack_handler( + SERVICE_CONFIRMED_DELETE_OBJECT, MySimpleAckHandler); + apdu_set_error_handler(SERVICE_CONFIRMED_DELETE_OBJECT, MyErrorHandler); + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +static void print_usage(char *filename) +{ + printf("Usage: %s device-instance object-type object-instance\n", + filename); + printf(" [--dnet][--dadr][--mac]\n"); + printf(" [--version][--help][--verbose]\n"); +} + +static void print_help(char *filename) +{ + printf("Create an object in a BACnet device.\n"); + printf("\n"); + printf("device-instance:\n" + "BACnet Device Object Instance number that you are\n" + "trying to communicate to. This number will be used\n" + "to try and bind with the device using Who-Is and\n" + "I-Am services. For example, if you were writing\n" + "Device Object 123, the device-instance would be 123.\n"); + printf("\n"); + printf("object-type:\n" + "The object type is object that you are deleting. 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"); + printf("\n"); + printf("object-instance:\n" + "This is the object instance number of the object that\n" + "you are deleting. For example, if you were deleting\n" + "Analog Output 2, the object-instance would be 2.\n"); + printf("\n"); + printf("Example:\n" + "If you want to DeleteObject an Analog Input 1\n" + "send the following command:\n" + "%s 123 0 1\n", + filename); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + unsigned max_apdu = 0; + struct mstimer apdu_timer; + struct mstimer maintenance_timer; + bool found = false; + unsigned object_type = 0; + unsigned object_instance = 0; + long dnet = -1; + BACNET_MAC_ADDRESS mac = { 0 }; + BACNET_MAC_ADDRESS adr = { 0 }; + BACNET_ADDRESS dest = { 0 }; + bool specific_address = false; + unsigned int target_args = 0; + int argi = 0; + char *filename = NULL; + + filename = filename_remove_path(argv[0]); + for (argi = 1; argi < argc; argi++) { + if (strcmp(argv[argi], "--help") == 0) { + print_usage(filename); + print_help(filename); + return 0; + } + if (strcmp(argv[argi], "--version") == 0) { + printf("%s %s\n", filename, BACNET_VERSION_TEXT); + printf("Copyright (C) 2023 by Steve Karg and others.\n" + "This is free software; see the source for copying " + "conditions.\n" + "There is NO warranty; not even for MERCHANTABILITY or\n" + "FITNESS FOR A PARTICULAR PURPOSE.\n"); + return 0; + } + if (strcmp(argv[argi], "--mac") == 0) { + if (++argi < argc) { + if (bacnet_address_mac_from_ascii(&mac, argv[argi])) { + specific_address = true; + } + } + } else if (strcmp(argv[argi], "--dnet") == 0) { + if (++argi < argc) { + dnet = strtol(argv[argi], NULL, 0); + if ((dnet >= 0) && (dnet <= UINT16_MAX)) { + specific_address = true; + } + } + } else if (strcmp(argv[argi], "--dadr") == 0) { + if (++argi < argc) { + if (bacnet_address_mac_from_ascii(&adr, argv[argi])) { + specific_address = true; + } + } + } else if (strcmp(argv[argi], "--verbose") == 0) { + Verbose = true; + } else { + if (target_args == 0) { + object_instance = strtoul(argv[argi], NULL, 0); + if (object_instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, + "device-instance=%u - it must be less than %u\n", + object_instance, BACNET_MAX_INSTANCE); + return 1; + } + Target_Device_Object_Instance = object_instance; + target_args++; + } else if (target_args == 1) { + if (bactext_object_type_strtol(argv[argi], &object_type) == + false) { + fprintf(stderr, "object-type=%s invalid\n", argv[argi]); + return 1; + } + Target_Object_Type = object_type; + target_args++; + } else if (target_args == 2) { + object_instance = strtoul(argv[argi], NULL, 0); + if (object_instance >= BACNET_MAX_INSTANCE) { + fprintf(stderr, + "object-instance=%u - it must be less than %u\n", + Target_Device_Object_Instance, BACNET_MAX_INSTANCE); + return 1; + } + Target_Object_Instance = object_instance; + if (Verbose) { + printf("Instance=%u=%s\n", object_instance, argv[argi]); + } + target_args++; + } + } + } + if (target_args < 2) { + print_usage(filename); + return 0; + } + /* setup my info */ + address_init(); + if (specific_address) { + bacnet_address_init(&dest, &mac, dnet, &adr); + address_add(Target_Device_Object_Instance, MAX_APDU, &dest); + } + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + dlenv_init(); + atexit(datalink_cleanup); + mstimer_init(); + mstimer_set(&apdu_timer, apdu_timeout()); + mstimer_set(&maintenance_timer, 1000); + /* try to bind with the device */ + found = address_bind_request( + Target_Device_Object_Instance, &max_apdu, &Target_Address); + if (found) { + if (Verbose) { + printf("Found Device %u in address_cache.\n", + Target_Device_Object_Instance); + } + } else { + Send_WhoIs( + Target_Device_Object_Instance, Target_Device_Object_Instance); + } + /* loop forever */ + for (;;) { + if (found) { + /* device is bound! */ + if (Request_Invoke_ID == 0) { + if (Verbose) { + printf("Sending DeleteObject to Device %u.\n", + Target_Device_Object_Instance); + } + Request_Invoke_ID = Send_Delete_Object_Request( + Target_Device_Object_Instance, Target_Object_Type, + Target_Object_Instance); + } else if (tsm_invoke_id_free(Request_Invoke_ID)) { + break; + } else if (tsm_invoke_id_failed(Request_Invoke_ID)) { + MyPrintHandler(ERROR_CLASS_COMMUNICATION, + ERROR_CODE_ABORT_TSM_TIMEOUT); + tsm_free_invoke_id(Request_Invoke_ID); + Error_Detected = true; + /* abort */ + break; + } + } else { + found = address_bind_request( + Target_Device_Object_Instance, &max_apdu, &Target_Address); + } + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (mstimer_expired(&maintenance_timer)) { + mstimer_reset(&maintenance_timer); + tsm_timer_milliseconds(mstimer_interval(&maintenance_timer)); + } + if (mstimer_expired(&apdu_timer)) { + MyPrintHandler(ERROR_CLASS_COMMUNICATION, + ERROR_CODE_ABORT_APPLICATION_EXCEEDED_REPLY_TIME); + Error_Detected = true; + } + if (Error_Detected) { + break; + } + } + if (Error_Detected) { + return 1; + } + + return 0; +} diff --git a/apps/server/main.c b/apps/server/main.c index 1acb0fb7..682030ed 100644 --- a/apps/server/main.c +++ b/apps/server/main.c @@ -144,6 +144,10 @@ static void Init_Service_Handlers(void) #if defined(BACNET_TIME_MASTER) handler_timesync_init(); #endif + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_CREATE_OBJECT, handler_create_object); + apdu_set_confirmed_handler( + SERVICE_CONFIRMED_DELETE_OBJECT, handler_delete_object); } static void print_usage(const char *filename) diff --git a/src/bacnet/abort.c b/src/bacnet/abort.c index bb52e0c2..83db9696 100644 --- a/src/bacnet/abort.c +++ b/src/bacnet/abort.c @@ -73,6 +73,21 @@ BACNET_ABORT_REASON abort_convert_error_code(BACNET_ERROR_CODE error_code) case ERROR_CODE_ABORT_INSUFFICIENT_SECURITY: abort_code = ABORT_REASON_INSUFFICIENT_SECURITY; break; + case ERROR_CODE_ABORT_WINDOW_SIZE_OUT_OF_RANGE: + abort_code = ABORT_REASON_WINDOW_SIZE_OUT_OF_RANGE; + break; + case ERROR_CODE_ABORT_APPLICATION_EXCEEDED_REPLY_TIME: + abort_code = ABORT_REASON_APPLICATION_EXCEEDED_REPLY_TIME; + break; + case ERROR_CODE_ABORT_OUT_OF_RESOURCES: + abort_code = ABORT_REASON_OUT_OF_RESOURCES; + break; + case ERROR_CODE_ABORT_TSM_TIMEOUT: + abort_code = ABORT_REASON_TSM_TIMEOUT; + break; + case ERROR_CODE_ABORT_APDU_TOO_LONG: + abort_code = ABORT_REASON_APDU_TOO_LONG; + break; case ERROR_CODE_ABORT_PROPRIETARY: abort_code = ABORT_REASON_PROPRIETARY_FIRST; break; @@ -85,6 +100,38 @@ BACNET_ABORT_REASON abort_convert_error_code(BACNET_ERROR_CODE error_code) return (abort_code); } +/** + * @brief Determine if a BACnetErrorCode is a BACnetAbortReason + * @param error_code #BACNET_ERROR_CODE enumeration + * @return true if the BACnet Error Code is a BACnet abort reason + */ +bool abort_valid_error_code(BACNET_ERROR_CODE error_code) +{ + bool status = false; + + switch (error_code) { + case ERROR_CODE_ABORT_OTHER: + case ERROR_CODE_ABORT_BUFFER_OVERFLOW: + case ERROR_CODE_ABORT_INVALID_APDU_IN_THIS_STATE: + case ERROR_CODE_ABORT_PREEMPTED_BY_HIGHER_PRIORITY_TASK: + case ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED: + case ERROR_CODE_ABORT_SECURITY_ERROR: + case ERROR_CODE_ABORT_INSUFFICIENT_SECURITY: + case ERROR_CODE_ABORT_WINDOW_SIZE_OUT_OF_RANGE: + case ERROR_CODE_ABORT_APPLICATION_EXCEEDED_REPLY_TIME: + case ERROR_CODE_ABORT_OUT_OF_RESOURCES: + case ERROR_CODE_ABORT_TSM_TIMEOUT: + case ERROR_CODE_ABORT_APDU_TOO_LONG: + case ERROR_CODE_ABORT_PROPRIETARY: + status = true; + break; + default: + break; + } + + return status; +} + /** * @brief Convert error-code from abort-reason * @@ -101,6 +148,9 @@ BACNET_ERROR_CODE abort_convert_to_error_code(BACNET_ABORT_REASON abort_code) BACNET_ERROR_CODE error_code = ERROR_CODE_ABORT_OTHER; switch (abort_code) { + case ABORT_REASON_OTHER: + error_code = ERROR_CODE_ABORT_OTHER; + break; case ABORT_REASON_BUFFER_OVERFLOW: error_code = ERROR_CODE_ABORT_BUFFER_OVERFLOW; break; @@ -119,8 +169,20 @@ BACNET_ERROR_CODE abort_convert_to_error_code(BACNET_ABORT_REASON abort_code) case ABORT_REASON_INSUFFICIENT_SECURITY: error_code = ERROR_CODE_ABORT_INSUFFICIENT_SECURITY; break; - case ABORT_REASON_OTHER: - error_code = ERROR_CODE_ABORT_OTHER; + case ABORT_REASON_WINDOW_SIZE_OUT_OF_RANGE: + error_code = ERROR_CODE_ABORT_WINDOW_SIZE_OUT_OF_RANGE; + break; + case ABORT_REASON_APPLICATION_EXCEEDED_REPLY_TIME: + error_code = ERROR_CODE_ABORT_APPLICATION_EXCEEDED_REPLY_TIME; + break; + case ABORT_REASON_OUT_OF_RESOURCES: + error_code = ERROR_CODE_ABORT_OUT_OF_RESOURCES; + break; + case ABORT_REASON_TSM_TIMEOUT: + error_code = ERROR_CODE_ABORT_TSM_TIMEOUT; + break; + case ABORT_REASON_APDU_TOO_LONG: + error_code = ERROR_CODE_ABORT_APDU_TOO_LONG; break; default: if ((abort_code >= ABORT_REASON_PROPRIETARY_FIRST) && diff --git a/src/bacnet/abort.h b/src/bacnet/abort.h index 2c9c411d..763a8201 100644 --- a/src/bacnet/abort.h +++ b/src/bacnet/abort.h @@ -21,8 +21,8 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *********************************************************************/ -#ifndef ABORT_H -#define ABORT_H +#ifndef BACNET_ABORT_H +#define BACNET_ABORT_H #include #include @@ -37,6 +37,9 @@ extern "C" { BACNET_ABORT_REASON abort_convert_error_code( BACNET_ERROR_CODE error_code); BACNET_STACK_EXPORT + bool abort_valid_error_code( + BACNET_ERROR_CODE error_code); + BACNET_STACK_EXPORT BACNET_ERROR_CODE abort_convert_to_error_code( BACNET_ABORT_REASON abort_code); diff --git a/src/bacnet/bacapp.c b/src/bacnet/bacapp.c index b8e0e8c6..e669dfb0 100644 --- a/src/bacnet/bacapp.c +++ b/src/bacnet/bacapp.c @@ -39,7 +39,7 @@ #include /* for isalnum */ #include #include -#if (__STDC_VERSION__ >= 199901L) && defined (__STDC_ISO_10646__) +#if (__STDC_VERSION__ >= 199901L) && defined(__STDC_ISO_10646__) #include #include #endif @@ -198,8 +198,8 @@ int bacapp_encode_application_data( break; case BACNET_APPLICATION_TAG_DESTINATION: /* BACnetDestination */ - apdu_len = bacnet_destination_encode( - apdu, &value->type.Destination); + apdu_len = + bacnet_destination_encode(apdu, &value->type.Destination); break; #endif default: @@ -351,8 +351,8 @@ int bacapp_decode_data(uint8_t *apdu, break; case BACNET_APPLICATION_TAG_DESTINATION: /* BACnetDestination */ - len = bacnet_destination_decode(apdu, len_value_type, - &value->type.Destination); + len = bacnet_destination_decode( + apdu, len_value_type, &value->type.Destination); break; #endif default: @@ -684,8 +684,8 @@ int bacapp_encode_context_data_value(uint8_t *apdu, break; case BACNET_APPLICATION_TAG_DESTINATION: /* BACnetDestination */ - apdu_len = bacnet_destination_context_encode(apdu, - context_tag_number, &value->type.Destination); + apdu_len = bacnet_destination_context_encode( + apdu, context_tag_number, &value->type.Destination); break; #endif default: @@ -1535,8 +1535,8 @@ int bacapp_data_len( } do { if (bacnet_is_opening_tag(apdu, apdu_len_max)) { - len = bacnet_tag_number_and_value_decode(apdu, - apdu_len_max - apdu_len, &tag_number, &value); + len = bacnet_tag_number_and_value_decode( + apdu, apdu_len_max - apdu_len, &tag_number, &value); if (opening_tag_number_counter == 0) { opening_tag_number = tag_number; opening_tag_number_counter = 1; @@ -1546,8 +1546,8 @@ int bacapp_data_len( opening_tag_number_counter++; } } else if (bacnet_is_closing_tag(apdu, apdu_len_max)) { - len = bacnet_tag_number_and_value_decode(apdu, - apdu_len_max - apdu_len, &tag_number, &value); + len = bacnet_tag_number_and_value_decode( + apdu, apdu_len_max - apdu_len, &tag_number, &value); if (tag_number == opening_tag_number) { if (opening_tag_number_counter > 0) { opening_tag_number_counter--; @@ -1585,7 +1585,6 @@ int bacapp_data_len( } } while (opening_tag_number_counter > 0); - return total_len; } @@ -1603,13 +1602,14 @@ static int bacapp_snprintf_date(char *str, size_t str_len, BACNET_DATE *bdate) { int ret_val = 0; int slen = 0; + const char *weekday_text, *month_text; + weekday_text = bactext_day_of_week_name(bdate->wday); + month_text = bactext_month_name(bdate->month); /* false positive cppcheck - snprintf allows null pointers */ /* cppcheck-suppress nullPointer */ /* cppcheck-suppress ctunullpointer */ - slen = snprintf(str, str_len, "%s, %s", - bactext_day_of_week_name(bdate->wday), - bactext_month_name(bdate->month)); + slen = snprintf(str, str_len, "%s, %s", weekday_text, month_text); if (str) { str += slen; if (str_len >= slen) { @@ -1873,7 +1873,7 @@ int bacapp_snprintf_value( #if defined(BACAPP_OCTET_STRING) || defined(BACAPP_TYPES_EXTRA) uint8_t *octet_str; #endif -#if (__STDC_VERSION__ >= 199901L) && defined (__STDC_ISO_10646__) +#if (__STDC_VERSION__ >= 199901L) && defined(__STDC_ISO_10646__) /* Wide character (decoded from multi-byte character). */ wchar_t wc; /* Wide character length in bytes. */ @@ -1953,7 +1953,7 @@ int bacapp_snprintf_value( } } ret_val += slen; -#if (__STDC_VERSION__ >= 199901L) && defined (__STDC_ISO_10646__) +#if (__STDC_VERSION__ >= 199901L) && defined(__STDC_ISO_10646__) if (characterstring_encoding(&value->type.Character_String) == CHARACTER_UTF8) { while (len > 0) { @@ -2201,14 +2201,16 @@ int bacapp_snprintf_value( break; case BACNET_APPLICATION_TAG_TIMESTAMP: /*ISO 8601 format */ - slen = snprintf(str, str_len, "%04u-%02u-%02uT%02u:%02u:%02u.%03u", - (unsigned) value->type.Time_Stamp.value.dateTime.date.year, - (unsigned) value->type.Time_Stamp.value.dateTime.date.month, - (unsigned) value->type.Time_Stamp.value.dateTime.date.day, - (unsigned) value->type.Time_Stamp.value.dateTime.time.hour, - (unsigned) value->type.Time_Stamp.value.dateTime.time.min, - (unsigned) value->type.Time_Stamp.value.dateTime.time.sec, - (unsigned) value->type.Time_Stamp.value.dateTime.time.hundredths); + slen = snprintf(str, str_len, + "%04u-%02u-%02uT%02u:%02u:%02u.%03u", + (unsigned)value->type.Time_Stamp.value.dateTime.date.year, + (unsigned)value->type.Time_Stamp.value.dateTime.date.month, + (unsigned)value->type.Time_Stamp.value.dateTime.date.day, + (unsigned)value->type.Time_Stamp.value.dateTime.time.hour, + (unsigned)value->type.Time_Stamp.value.dateTime.time.min, + (unsigned)value->type.Time_Stamp.value.dateTime.time.sec, + (unsigned) + value->type.Time_Stamp.value.dateTime.time.hundredths); ret_val += slen; break; case BACNET_APPLICATION_TAG_LIGHTING_COMMAND: @@ -2279,7 +2281,8 @@ int bacapp_snprintf_value( break; case BACNET_APPLICATION_TAG_DESTINATION: /* BACnetWeeklySchedule */ - ret_val = bacnet_destination_to_ascii(&value->type.Destination, str, str_len); + ret_val = bacnet_destination_to_ascii( + &value->type.Destination, str, str_len); break; case BACNET_APPLICATION_TAG_HOST_N_PORT: if (value->type.Host_Address.host_ip_address) { @@ -2803,8 +2806,8 @@ bool bacapp_parse_application_data(BACNET_APPLICATION_TAG tag_number, } break; case BACNET_APPLICATION_TAG_DESTINATION: - status = bacnet_destination_from_ascii(&value->type.Destination, - argv); + status = bacnet_destination_from_ascii( + &value->type.Destination, argv); break; #endif default: @@ -2868,6 +2871,231 @@ void bacapp_property_value_list_init(BACNET_PROPERTY_VALUE *value, size_t count) } } +/** + * @brief Link an array of BACNET_PROPERTY_VALUE elements. + * The linked-list is used prior to encoding or decoding + * the APDU data into the structure. + * + * @param value_list - Pointer to the first BACNET_PROPERTY_VALUE element in + * an array + * @param count - number of BACNET_PROPERTY_VALUE elements to link + */ +void bacapp_property_value_list_link( + BACNET_PROPERTY_VALUE *value_list, size_t count) +{ + BACNET_PROPERTY_VALUE *current_value_list = NULL; + + if (value_list) { + while (count) { + if (count > 1) { + current_value_list = value_list; + value_list++; + current_value_list->next = value_list; + } else { + value_list->next = NULL; + } + count--; + } + } +} + +/** + * @brief Encode 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 for encoded values, or NULL for length + * @param value Pointer to the service data used for encoding values + * + * @return Bytes encoded or zero on error. + */ +int bacapp_property_value_encode(uint8_t *apdu, BACNET_PROPERTY_VALUE *value) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + BACNET_APPLICATION_DATA_VALUE *app_data = NULL; + + if (value) { + /* tag 0 - propertyIdentifier */ + len = encode_context_enumerated(apdu, 0, value->propertyIdentifier); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* tag 1 - propertyArrayIndex OPTIONAL */ + if (value->propertyArrayIndex != BACNET_ARRAY_ALL) { + len = encode_context_unsigned(apdu, 1, value->propertyArrayIndex); + apdu_len += len; + if (apdu) { + apdu += len; + } + } + /* tag 2 - value */ + /* abstract syntax gets enclosed in a context tag */ + len = encode_opening_tag(apdu, 2); + apdu_len += len; + if (apdu) { + apdu += len; + } + app_data = &value->value; + while (app_data != NULL) { + len = bacapp_encode_application_data(apdu, app_data); + apdu_len += len; + if (apdu) { + apdu += len; + } + app_data = app_data->next; + } + len = encode_closing_tag(apdu, 2); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* tag 3 - priority OPTIONAL */ + if (value->priority != BACNET_NO_PRIORITY) { + len = encode_context_unsigned(apdu, 3, value->priority); + apdu_len += len; + } + } + + return apdu_len; +} + +/** + * @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 service data used for encoding values + * + * @return Bytes decoded or BACNET_STATUS_ERROR on error. + */ +int bacapp_property_value_decode( + uint8_t *apdu, uint32_t apdu_size, BACNET_PROPERTY_VALUE *value) +{ + int len = 0; + int apdu_len = 0; + int tag_len = 0; + uint32_t enumerated_value = 0; + BACNET_UNSIGNED_INTEGER unsigned_value = 0; + BACNET_PROPERTY_ID property_identifier = PROP_ALL; + BACNET_APPLICATION_DATA_VALUE *app_data = NULL; + + /* property-identifier [0] BACnetPropertyIdentifier */ + len = bacnet_enumerated_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 0, &enumerated_value); + if (len == BACNET_STATUS_ERROR) { + return BACNET_STATUS_ERROR; + } + property_identifier = enumerated_value; + if (value) { + value->propertyIdentifier = property_identifier; + } + apdu_len += len; + /* property-array-index [1] Unsigned OPTIONAL */ + if (bacnet_is_context_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 1, NULL)) { + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 1, &unsigned_value); + if (len == BACNET_STATUS_ERROR) { + return BACNET_STATUS_ERROR; + } else if (unsigned_value > UINT32_MAX) { + return BACNET_STATUS_ERROR; + } else { + apdu_len += len; + if (value) { + value->propertyArrayIndex = unsigned_value; + } + } + } 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)) { + if (value) { + apdu_len += len; + app_data = &value->value; + while (app_data != NULL) { + len = bacapp_decode_application_data( + &apdu[apdu_len], apdu_size - apdu_len, app_data); + if (len < 0) { + return BACNET_STATUS_ERROR; + } + apdu_len += len; + if (bacnet_is_closing_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 2, &len)) { + break; + } + app_data = app_data->next; + } + } else { + /* this len function needs to start at the opening tag + to match opening/closing tags like a stack. + However, it returns the len between the tags. + Therefore, store the length of the opening tag first */ + tag_len = len; + len = bacapp_data_len(&apdu[apdu_len], apdu_size - apdu_len, + (BACNET_PROPERTY_ID)property_identifier); + apdu_len += len; + /* add the opening tag length to the totals */ + apdu_len += tag_len; + } + if (bacnet_is_closing_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 2, &len)) { + apdu_len += len; + } else { + return BACNET_STATUS_ERROR; + } + } 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, NULL)) { + len = bacnet_unsigned_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 3, &unsigned_value); + if (len == BACNET_STATUS_ERROR) { + return BACNET_STATUS_ERROR; + } else if (unsigned_value > UINT8_MAX) { + return BACNET_STATUS_ERROR; + } else { + apdu_len += len; + if (value) { + value->priority = unsigned_value; + } + } + } else { + if (value) { + value->priority = BACNET_NO_PRIORITY; + } + } + + return apdu_len; +} + /* generic - can be used by other unit tests returns true if matching or same, false if different */ bool bacapp_same_value(BACNET_APPLICATION_DATA_VALUE *value, @@ -2922,7 +3150,8 @@ bool bacapp_same_value(BACNET_APPLICATION_DATA_VALUE *value, #endif #if defined(BACAPP_DOUBLE) case BACNET_APPLICATION_TAG_DOUBLE: - if (!islessgreater(test_value->type.Double,value->type.Double)) { + if (!islessgreater( + test_value->type.Double, value->type.Double)) { status = true; } break; diff --git a/src/bacnet/bacapp.h b/src/bacnet/bacapp.h index 9adb5c8f..60de7c61 100644 --- a/src/bacnet/bacapp.h +++ b/src/bacnet/bacapp.h @@ -155,6 +155,21 @@ extern "C" { BACNET_PROPERTY_VALUE *value, size_t count); + BACNET_STACK_EXPORT + void bacapp_property_value_list_link( + BACNET_PROPERTY_VALUE *value_list, + size_t count); + + BACNET_STACK_EXPORT + int bacapp_property_value_encode( + uint8_t *apdu, + BACNET_PROPERTY_VALUE *value); + BACNET_STACK_EXPORT + int bacapp_property_value_decode( + uint8_t *apdu, + uint32_t apdu_size, + BACNET_PROPERTY_VALUE *value); + BACNET_STACK_EXPORT int bacapp_encode_data( uint8_t * apdu, diff --git a/src/bacnet/bacdcode.c b/src/bacnet/bacdcode.c index 9a6f3c20..11f3218a 100644 --- a/src/bacnet/bacdcode.c +++ b/src/bacnet/bacdcode.c @@ -696,6 +696,39 @@ bool decode_is_context_tag_with_length( return (bool)(IS_CONTEXT_SPECIFIC(*apdu) && (my_tag_number == tag_number)); } +/** + * @brief Returns true if the tag is context specific + * and matches, as defined in clause 20.2.1.3.2 Constructed + * Data. This function returns the tag length as well. + * + * @param apdu Pointer to the tag begin. + * @param apdu_size - number of bytes in the buffer + * @param tag_number Tag number, that has been decoded before. + * @param tag_length Pointer to a variable, or NULL. + * Returns the length of the tag in bytes if not NULL. + * + * @return true on a match, false otherwise. + */ +bool bacnet_is_context_tag_number( + uint8_t *apdu, uint32_t apdu_size, uint8_t tag_number, int *tag_length) +{ + bool match = false; + uint8_t my_tag_number = 0; + int len; + + len = bacnet_tag_number_decode(apdu, apdu_size, &my_tag_number); + if ((len > 0) && (my_tag_number == tag_number)) { + if (IS_CONTEXT_SPECIFIC(apdu[0])) { + if (tag_length) { + *tag_length = len; + } + match = true; + } + } + + return match; +} + /** * @brief Returns true if the tag does match and it * is an opening tag as well. @@ -715,6 +748,39 @@ bool decode_is_opening_tag_number(uint8_t *apdu, uint8_t tag_number) return (bool)(IS_OPENING_TAG(apdu[0]) && (my_tag_number == tag_number)); } +/** + * @brief Returns true if the tag does match and it + * is an opening tag as well. + * As defined in clause 20.2.1.3.2 Constructed Data. + * + * @param apdu Pointer to the tag begin. + * @param apdu_size - number of bytes in the buffer + * @param tag_number Tag number, that has been decoded before. + * @param tag_length Pointer to a variable, or NULL. + * Returns the length of the tag in bytes if not NULL. + * + * @return true on a match, false otherwise. + */ +bool bacnet_is_opening_tag_number( + uint8_t *apdu, uint32_t apdu_size, uint8_t tag_number, int *tag_length) +{ + bool match = false; + uint8_t my_tag_number = 0; + int len; + + len = bacnet_tag_number_decode(apdu, apdu_size, &my_tag_number); + if ((len > 0) && (my_tag_number == tag_number)) { + if (IS_OPENING_TAG(apdu[0])) { + match = true; + if (tag_length) { + *tag_length = len; + } + } + } + + return match; +} + /** * @brief Returns true if the tag does match and it * is an closing tag as well. @@ -733,6 +799,39 @@ bool decode_is_closing_tag_number(uint8_t *apdu, uint8_t tag_number) return (bool)(IS_CLOSING_TAG(apdu[0]) && (my_tag_number == tag_number)); } +/** + * @brief Returns true if the tag does match and it + * is a closing tag as well. + * As defined in clause 20.2.1.3.2 Constructed Data. + * + * @param apdu Pointer to the tag begin. + * @param apdu_size - number of bytes in the buffer + * @param tag_number Tag number, that has been decoded before. + * @param tag_length Pointer to a variable, or NULL. + * Returns the length of the tag in bytes if not NULL. + * + * @return true on a match, false otherwise. + */ +bool bacnet_is_closing_tag_number( + uint8_t *apdu, uint32_t apdu_size, uint8_t tag_number, int *tag_length) +{ + bool match = false; + uint8_t my_tag_number = 0; + int len; + + len = bacnet_tag_number_decode(apdu, apdu_size, &my_tag_number); + if ((len > 0) && (my_tag_number == tag_number)) { + if (IS_CLOSING_TAG(apdu[0])) { + match = true; + if (tag_length) { + *tag_length = len; + } + } + } + + return match; +} + /** * @brief Encode an boolean value. * From clause 20.2.3 Encoding of a Boolean Value @@ -852,6 +951,60 @@ bool decode_boolean(uint32_t len_value) return boolean_value; } +/** + * @brief Decode the Boolean Value when context encoded + * From clause 20.2.3 Encoding of a Boolean Value + * and 20.2.1 General Rules for Encoding BACnet Tags + * + * @note The Boolean datatype differs from the other datatypes + * in that the encoding of a context-tagged Boolean value is not the + * same as the encoding of an application-tagged Boolean value. + * This is done so that the application-tagged value may be encoded + * in a single octet, without a contents octet. While this same encoding + * could have been used for the context-tagged case, doing + * so would require that the context be known in order to distinguish + * between a length or a value in the length/value/type field. + * This was considered to be undesirable. + * + * @param apdu - buffer to hold the bytes + * @param apdu_size - number of bytes in the buffer to decode + * @param tag_value - context tag number expected + * @param boolean_value - decoded Boolean Value, if decoded + * + * @return number of bytes decoded, zero if wrong tag number, + * or #BACNET_STATUS_ERROR (-1) if malformed + */ +int bacnet_boolean_context_decode( + uint8_t *apdu, uint32_t apdu_size, uint8_t tag_value, bool *boolean_value) +{ + int apdu_len = 0; + int len = 0; + + if (bacnet_is_context_tag_number( + &apdu[apdu_len], apdu_size, tag_value, &len) && + !bacnet_is_closing_tag(&apdu[apdu_len], apdu_size)) { + if (len > 0) { + apdu_len += len; + if (apdu_len < apdu_size) { + if (boolean_value) { + if (apdu[apdu_len]) { + *boolean_value = true; + } else { + *boolean_value = false; + } + } + apdu_len++; + } else { + return BACNET_STATUS_ERROR; + } + } else { + return BACNET_STATUS_ERROR; + } + } + + return apdu_len; +} + /** * @brief Encode a Null value. * From clause 20.2.2 Encoding of a Null Value diff --git a/src/bacnet/bacdcode.h b/src/bacnet/bacdcode.h index a6851e88..0d413ed6 100644 --- a/src/bacnet/bacdcode.h +++ b/src/bacnet/bacdcode.h @@ -62,6 +62,9 @@ extern "C" { uint32_t len_value_type); BACNET_STACK_EXPORT +bool bacnet_is_context_tag_number( + uint8_t *apdu, uint32_t apdu_size, uint8_t tag_number, int *tag_length); +BACNET_STACK_EXPORT bool bacnet_is_opening_tag(uint8_t *apdu, uint32_t apdu_size); BACNET_STACK_EXPORT bool bacnet_is_closing_tag(uint8_t *apdu, uint32_t apdu_size); @@ -96,6 +99,12 @@ bool bacnet_is_context_specific(uint8_t *apdu, uint32_t apdu_size); uint32_t * value); BACNET_STACK_EXPORT + bool bacnet_is_opening_tag_number( + uint8_t *apdu, uint32_t apdu_size, uint8_t tag_number, int *tag_length); + BACNET_STACK_EXPORT + bool bacnet_is_closing_tag_number( + uint8_t *apdu, uint32_t apdu_size, uint8_t tag_number, int *tag_length); + BACNET_STACK_EXPORT int bacnet_tag_number_and_value_decode( uint8_t * apdu, uint32_t apdu_len_remaining, @@ -162,6 +171,11 @@ bool bacnet_is_context_specific(uint8_t *apdu, uint32_t apdu_size); uint8_t tag_number, bool * boolean_value); + BACNET_STACK_EXPORT + int bacnet_boolean_context_decode( + uint8_t *apdu, uint32_t apdu_size, uint8_t tag_value, + bool *boolean_value); + /* from clause 20.2.10 Encoding of a Bit String Value */ /* returns the number of apdu bytes consumed */ BACNET_STACK_EXPORT diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index a73014b6..b93acd8c 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1687,10 +1687,15 @@ typedef enum { ABORT_REASON_SEGMENTATION_NOT_SUPPORTED = 4, ABORT_REASON_SECURITY_ERROR = 5, ABORT_REASON_INSUFFICIENT_SECURITY = 6, + ABORT_REASON_WINDOW_SIZE_OUT_OF_RANGE = 7, + ABORT_REASON_APPLICATION_EXCEEDED_REPLY_TIME = 8, + ABORT_REASON_OUT_OF_RESOURCES = 9, + ABORT_REASON_TSM_TIMEOUT = 10, + ABORT_REASON_APDU_TOO_LONG = 11, /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ /* Enumerated values 64-255 may be used by others subject to */ /* the procedures and constraints described in Clause 23. */ - MAX_BACNET_ABORT_REASON = 7, + MAX_BACNET_ABORT_REASON = 12, ABORT_REASON_PROPRIETARY_FIRST = 64, ABORT_REASON_PROPRIETARY_LAST = 255 } BACNET_ABORT_REASON; @@ -1706,10 +1711,11 @@ typedef enum { REJECT_REASON_TOO_MANY_ARGUMENTS = 7, REJECT_REASON_UNDEFINED_ENUMERATION = 8, REJECT_REASON_UNRECOGNIZED_SERVICE = 9, + REJECT_REASON_INVALID_DATA_ENCODING = 10, /* Enumerated values 0-63 are reserved for definition by ASHRAE. */ /* Enumerated values 64-255 may be used by others subject to */ /* the procedures and constraints described in Clause 23. */ - MAX_BACNET_REJECT_REASON = 10, + MAX_BACNET_REJECT_REASON = 11, REJECT_REASON_PROPRIETARY_FIRST = 64, REJECT_REASON_PROPRIETARY_LAST = 255 } BACNET_REJECT_REASON; diff --git a/src/bacnet/bactext.c b/src/bacnet/bactext.c index e7b703f5..5c07ebee 100644 --- a/src/bacnet/bactext.c +++ b/src/bacnet/bactext.c @@ -1074,6 +1074,7 @@ INDTEXT_DATA bacnet_reject_reason_names[] = { { REJECT_REASON_OTHER, "Other" }, { REJECT_REASON_TOO_MANY_ARGUMENTS, "Too Many Arguments" }, { REJECT_REASON_UNDEFINED_ENUMERATION, "Undefined Enumeration" }, { REJECT_REASON_UNRECOGNIZED_SERVICE, "Unrecognized Service" }, + { REJECT_REASON_INVALID_DATA_ENCODING, "invalid-data-encoding" }, { REJECT_REASON_PROPRIETARY_FIRST, "Proprietary" }, { 0, NULL } }; const char *bactext_reject_reason_name(unsigned index) @@ -1091,6 +1092,12 @@ INDTEXT_DATA bacnet_abort_reason_names[] = { { ABORT_REASON_OTHER, "Other" }, { ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, "Segmentation Not Supported" }, { ABORT_REASON_SECURITY_ERROR, "Security Error" }, { ABORT_REASON_INSUFFICIENT_SECURITY, "Insufficient Security" }, + { ABORT_REASON_WINDOW_SIZE_OUT_OF_RANGE, "window-size-out-of-range" }, + { ABORT_REASON_APPLICATION_EXCEEDED_REPLY_TIME, + "application-exceeded-reply-time" }, + { ABORT_REASON_OUT_OF_RESOURCES, "out-of-resources" }, + { ABORT_REASON_TSM_TIMEOUT, "tsm-timeout" }, + { ABORT_REASON_APDU_TOO_LONG, "apdu-too-long" }, { ABORT_REASON_PROPRIETARY_FIRST, "Proprietary" }, { 0, NULL } }; const char *bactext_abort_reason_name(unsigned index) @@ -1104,7 +1111,8 @@ INDTEXT_DATA bacnet_error_class_names[] = { { ERROR_CLASS_DEVICE, "device" }, { ERROR_CLASS_OBJECT, "object" }, { ERROR_CLASS_PROPERTY, "property" }, { ERROR_CLASS_RESOURCES, "resources" }, { ERROR_CLASS_SECURITY, "security" }, { ERROR_CLASS_SERVICES, "services" }, - { ERROR_CLASS_VT, "vt" }, { 0, NULL } }; + { ERROR_CLASS_VT, "vt" }, { ERROR_CLASS_COMMUNICATION, "communication" }, + { 0, NULL } }; const char *bactext_error_class_name(unsigned index) { diff --git a/src/bacnet/basic/object/ao.c b/src/bacnet/basic/object/ao.c index 49e10ff8..90d40677 100644 --- a/src/bacnet/basic/object/ao.c +++ b/src/bacnet/basic/object/ao.c @@ -1196,15 +1196,24 @@ void Analog_Output_Write_Present_Value_Callback_Set( /** * @brief Creates a Analog Value object * @param object_instance - object-instance number of the object - * @return true if the object-instance was created + * @return the object-instance that was created, or BACNET_MAX_INSTANCE */ -bool Analog_Output_Create(uint32_t object_instance) +uint32_t Analog_Output_Create(uint32_t object_instance) { - bool status = false; struct object_data *pObject = NULL; int index = 0; unsigned priority = 0; + if (object_instance > BACNET_MAX_INSTANCE) { + return BACNET_MAX_INSTANCE; + } else if (object_instance == BACNET_MAX_INSTANCE) { + /* wildcard instance */ + /* the Object_Identifier property of the newly created object + shall be initialized to a value that is unique within the + responding BACnet-user device. The method used to generate + the object identifier is a local matter.*/ + object_instance = Keylist_Next_Empty_Key(Object_List, 1); + } pObject = Keylist_Data(Object_List, object_instance); if (!pObject) { pObject = calloc(1, sizeof(struct object_data)); @@ -1227,13 +1236,17 @@ bool Analog_Output_Create(uint32_t object_instance) /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); if (index >= 0) { - status = true; Device_Inc_Database_Revision(); + } else { + free(pObject); + return BACNET_MAX_INSTANCE; } + } else { + return BACNET_MAX_INSTANCE; } } - return status; + return object_instance; } /** diff --git a/src/bacnet/basic/object/ao.h b/src/bacnet/basic/object/ao.h index 4be79a14..16d04179 100644 --- a/src/bacnet/basic/object/ao.h +++ b/src/bacnet/basic/object/ao.h @@ -191,7 +191,7 @@ extern "C" { BACNET_WRITE_PROPERTY_DATA * wp_data); BACNET_STACK_EXPORT - bool Analog_Output_Create( + uint32_t Analog_Output_Create( uint32_t object_instance); BACNET_STACK_EXPORT bool Analog_Output_Delete( diff --git a/src/bacnet/basic/object/bacfile.c b/src/bacnet/basic/object/bacfile.c index cd942a02..51c2b939 100644 --- a/src/bacnet/basic/object/bacfile.c +++ b/src/bacnet/basic/object/bacfile.c @@ -977,16 +977,25 @@ bool bacfile_read_ack_record_data( /** - * @brief Creates an object + * @brief Creates a File object * @param object_instance - object-instance number of the object - * @return true if the object-instance was created + * @return the object-instance that was created, or BACNET_MAX_INSTANCE */ -bool bacfile_create(uint32_t object_instance) +uint32_t bacfile_create(uint32_t object_instance) { - bool status = false; struct object_data *pObject = NULL; int index = 0; + if (object_instance > BACNET_MAX_INSTANCE) { + return BACNET_MAX_INSTANCE; + } else if (object_instance == BACNET_MAX_INSTANCE) { + /* wildcard instance */ + /* the Object_Identifier property of the newly created object + shall be initialized to a value that is unique within the + responding BACnet-user device. The method used to generate + the object identifier is a local matter.*/ + object_instance = Keylist_Next_Empty_Key(Object_List, 1); + } pObject = Keylist_Data(Object_List, object_instance); if (!pObject) { pObject = calloc(1, sizeof(struct object_data)); @@ -1002,13 +1011,17 @@ bool bacfile_create(uint32_t object_instance) /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); if (index >= 0) { - status = true; Device_Inc_Database_Revision(); + } else { + free(pObject); + return BACNET_MAX_INSTANCE; } + } else { + return BACNET_MAX_INSTANCE; } } - return status; + return object_instance; } /** diff --git a/src/bacnet/basic/object/bacfile.h b/src/bacnet/basic/object/bacfile.h index e93a0f46..e2150ce5 100644 --- a/src/bacnet/basic/object/bacfile.h +++ b/src/bacnet/basic/object/bacfile.h @@ -175,7 +175,7 @@ extern "C" { uint32_t buffer_size); BACNET_STACK_EXPORT - bool bacfile_create( + uint32_t bacfile_create( uint32_t object_instance); BACNET_STACK_EXPORT bool bacfile_delete( diff --git a/src/bacnet/basic/object/bo.c b/src/bacnet/basic/object/bo.c index 15325ecc..909703a1 100644 --- a/src/bacnet/basic/object/bo.c +++ b/src/bacnet/basic/object/bo.c @@ -1170,16 +1170,25 @@ void Binary_Output_Write_Present_Value_Callback_Set( } /** - * @brief Determines a object write-enabled flag state + * @brief Creates a Binary Output object * @param object_instance - object-instance number of the object - * @return write-enabled status flag + * @return the object-instance that was created, or BACNET_MAX_INSTANCE */ -bool Binary_Output_Create(uint32_t object_instance) +uint32_t Binary_Output_Create(uint32_t object_instance) { - bool status = false; struct object_data *pObject = NULL; int index = 0; + if (object_instance > BACNET_MAX_INSTANCE) { + return BACNET_MAX_INSTANCE; + } else if (object_instance == BACNET_MAX_INSTANCE) { + /* wildcard instance */ + /* the Object_Identifier property of the newly created object + shall be initialized to a value that is unique within the + responding BACnet-user device. The method used to generate + the object identifier is a local matter.*/ + object_instance = Keylist_Next_Empty_Key(Object_List, 1); + } pObject = Keylist_Data(Object_List, object_instance); if (!pObject) { pObject = calloc(1, sizeof(struct object_data)); @@ -1194,13 +1203,17 @@ bool Binary_Output_Create(uint32_t object_instance) /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); if (index >= 0) { - status = true; Device_Inc_Database_Revision(); + } else { + free(pObject); + return BACNET_MAX_INSTANCE; } + } else { + return BACNET_MAX_INSTANCE; } } - return status; + return object_instance; } /** diff --git a/src/bacnet/basic/object/bo.h b/src/bacnet/basic/object/bo.h index 94c01eab..94ae0078 100644 --- a/src/bacnet/basic/object/bo.h +++ b/src/bacnet/basic/object/bo.h @@ -228,7 +228,7 @@ extern "C" { BACNET_WRITE_PROPERTY_DATA * wp_data); BACNET_STACK_EXPORT - bool Binary_Output_Create( + uint32_t Binary_Output_Create( uint32_t object_instance); BACNET_STACK_EXPORT bool Binary_Output_Delete( diff --git a/src/bacnet/basic/object/client/device-client.c b/src/bacnet/basic/object/client/device-client.c index 84a84dc3..bb74d75a 100644 --- a/src/bacnet/basic/object/client/device-client.c +++ b/src/bacnet/basic/object/client/device-client.c @@ -117,7 +117,8 @@ static object_functions_t Object_Table[] = { Device_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, #if (BACNET_PROTOCOL_REVISION >= 17) { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, Network_Port_Index_To_Instance, Network_Port_Valid_Instance, @@ -125,7 +126,8 @@ static object_functions_t Object_Table[] = { Network_Port_Write_Property, Network_Port_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, #endif { MAX_BACNET_OBJECT_TYPE, NULL /* Init */, NULL /* Count */, NULL /* Index_To_Instance */, NULL /* Valid_Instance */, @@ -134,7 +136,8 @@ static object_functions_t Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ } + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, }; /** Glue function to let the Device object, when called by a handler, diff --git a/src/bacnet/basic/object/color_object.c b/src/bacnet/basic/object/color_object.c index 90e8a114..2f275217 100644 --- a/src/bacnet/basic/object/color_object.c +++ b/src/bacnet/basic/object/color_object.c @@ -827,15 +827,25 @@ void Color_Write_Disable(uint32_t object_instance) } /** - * Creates a Color object + * @brief Creates a Color object * @param object_instance - object-instance number of the object + * @return the object-instance that was created, or BACNET_MAX_INSTANCE */ -bool Color_Create(uint32_t object_instance) +uint32_t Color_Create(uint32_t object_instance) { - bool status = false; struct object_data *pObject = NULL; int index = 0; + if (object_instance > BACNET_MAX_INSTANCE) { + return BACNET_MAX_INSTANCE; + } else if (object_instance == BACNET_MAX_INSTANCE) { + /* wildcard instance */ + /* the Object_Identifier property of the newly created object + shall be initialized to a value that is unique within the + responding BACnet-user device. The method used to generate + the object identifier is a local matter.*/ + object_instance = Keylist_Next_Empty_Key(Object_List, 1); + } pObject = Keylist_Data(Object_List, object_instance); if (!pObject) { pObject = calloc(1, sizeof(struct object_data)); @@ -856,13 +866,17 @@ bool Color_Create(uint32_t object_instance) /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); if (index >= 0) { - status = true; Device_Inc_Database_Revision(); + } else { + free(pObject); + return BACNET_MAX_INSTANCE; } + } else { + return BACNET_MAX_INSTANCE; } } - return status; + return object_instance; } /** diff --git a/src/bacnet/basic/object/color_object.h b/src/bacnet/basic/object/color_object.h index 58498185..6c035a5d 100644 --- a/src/bacnet/basic/object/color_object.h +++ b/src/bacnet/basic/object/color_object.h @@ -119,7 +119,7 @@ BACNET_STACK_EXPORT void Color_Write_Disable(uint32_t instance); BACNET_STACK_EXPORT -bool Color_Create(uint32_t object_instance); +uint32_t Color_Create(uint32_t object_instance); BACNET_STACK_EXPORT bool Color_Delete(uint32_t object_instance); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/object/color_temperature.c b/src/bacnet/basic/object/color_temperature.c index efcb331f..6530e1d3 100644 --- a/src/bacnet/basic/object/color_temperature.c +++ b/src/bacnet/basic/object/color_temperature.c @@ -1021,15 +1021,25 @@ void Color_Temperature_Write_Disable(uint32_t object_instance) } /** - * Creates a Color object + * @brief Creates a Color Temperature object * @param object_instance - object-instance number of the object + * @return the object-instance that was created, or BACNET_MAX_INSTANCE */ -bool Color_Temperature_Create(uint32_t object_instance) +uint32_t Color_Temperature_Create(uint32_t object_instance) { - bool status = false; struct object_data *pObject = NULL; int index = 0; + if (object_instance > BACNET_MAX_INSTANCE) { + return BACNET_MAX_INSTANCE; + } else if (object_instance == BACNET_MAX_INSTANCE) { + /* wildcard instance */ + /* the Object_Identifier property of the newly created object + shall be initialized to a value that is unique within the + responding BACnet-user device. The method used to generate + the object identifier is a local matter.*/ + object_instance = Keylist_Next_Empty_Key(Object_List, 1); + } pObject = Keylist_Data(Object_List, object_instance); if (!pObject) { pObject = calloc(1, sizeof(struct object_data)); @@ -1051,13 +1061,17 @@ bool Color_Temperature_Create(uint32_t object_instance) /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); if (index >= 0) { - status = true; Device_Inc_Database_Revision(); + } else { + free(pObject); + return BACNET_MAX_INSTANCE; } + } else { + return BACNET_MAX_INSTANCE; } } - return status; + return object_instance; } /** diff --git a/src/bacnet/basic/object/color_temperature.h b/src/bacnet/basic/object/color_temperature.h index 4dd3664d..accc519e 100644 --- a/src/bacnet/basic/object/color_temperature.h +++ b/src/bacnet/basic/object/color_temperature.h @@ -151,7 +151,7 @@ BACNET_STACK_EXPORT void Color_Temperature_Write_Disable(uint32_t instance); BACNET_STACK_EXPORT -bool Color_Temperature_Create(uint32_t object_instance); +uint32_t Color_Temperature_Create(uint32_t object_instance); BACNET_STACK_EXPORT bool Color_Temperature_Delete(uint32_t object_instance); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index 43a65bbc..a51cebeb 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -98,7 +98,8 @@ static object_functions_t My_Object_Table[] = { Device_Property_Lists, DeviceGetRRInfo, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, - NULL /* Remove_List_Element */ }, + NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, #if (BACNET_PROTOCOL_REVISION >= 17) { OBJECT_NETWORK_PORT, Network_Port_Init, Network_Port_Count, Network_Port_Index_To_Instance, Network_Port_Valid_Instance, @@ -106,7 +107,8 @@ static object_functions_t My_Object_Table[] = { Network_Port_Write_Property, Network_Port_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, #endif { OBJECT_ANALOG_INPUT, Analog_Input_Init, Analog_Input_Count, Analog_Input_Index_To_Instance, Analog_Input_Valid_Instance, @@ -115,14 +117,16 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, Analog_Input_Encode_Value_List, Analog_Input_Change_Of_Value, Analog_Input_Change_Of_Value_Clear, Analog_Input_Intrinsic_Reporting, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_ANALOG_OUTPUT, Analog_Output_Init, Analog_Output_Count, Analog_Output_Index_To_Instance, Analog_Output_Valid_Instance, Analog_Output_Object_Name, Analog_Output_Read_Property, Analog_Output_Write_Property, Analog_Output_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Analog_Output_Create, Analog_Output_Delete}, { OBJECT_ANALOG_VALUE, Analog_Value_Init, Analog_Value_Count, Analog_Value_Index_To_Instance, Analog_Value_Valid_Instance, Analog_Value_Object_Name, Analog_Value_Read_Property, @@ -130,7 +134,8 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, Analog_Value_Encode_Value_List, Analog_Value_Change_Of_Value, Analog_Value_Change_Of_Value_Clear, Analog_Value_Intrinsic_Reporting, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_BINARY_INPUT, Binary_Input_Init, Binary_Input_Count, Binary_Input_Index_To_Instance, Binary_Input_Valid_Instance, Binary_Input_Object_Name, Binary_Input_Read_Property, @@ -138,21 +143,24 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, Binary_Input_Encode_Value_List, Binary_Input_Change_Of_Value, Binary_Input_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_BINARY_OUTPUT, Binary_Output_Init, Binary_Output_Count, Binary_Output_Index_To_Instance, Binary_Output_Valid_Instance, Binary_Output_Object_Name, Binary_Output_Read_Property, Binary_Output_Write_Property, Binary_Output_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Binary_Output_Create, Binary_Output_Delete}, { OBJECT_BINARY_VALUE, Binary_Value_Init, Binary_Value_Count, Binary_Value_Index_To_Instance, Binary_Value_Valid_Instance, Binary_Value_Object_Name, Binary_Value_Read_Property, Binary_Value_Write_Property, Binary_Value_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_CHARACTERSTRING_VALUE, CharacterString_Value_Init, CharacterString_Value_Count, CharacterString_Value_Index_To_Instance, CharacterString_Value_Valid_Instance, CharacterString_Value_Object_Name, @@ -163,20 +171,23 @@ static object_functions_t My_Object_Table[] = { CharacterString_Value_Change_Of_Value, CharacterString_Value_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, - NULL /* Remove_List_Element */ }, + NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_COMMAND, Command_Init, Command_Count, Command_Index_To_Instance, Command_Valid_Instance, Command_Object_Name, Command_Read_Property, Command_Write_Property, Command_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_INTEGER_VALUE, Integer_Value_Init, Integer_Value_Count, Integer_Value_Index_To_Instance, Integer_Value_Valid_Instance, Integer_Value_Object_Name, Integer_Value_Read_Property, Integer_Value_Write_Property, Integer_Value_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, #if defined(INTRINSIC_REPORTING) { OBJECT_NOTIFICATION_CLASS, Notification_Class_Init, Notification_Class_Count, Notification_Class_Index_To_Instance, @@ -186,7 +197,8 @@ static object_functions_t My_Object_Table[] = { NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, Notification_Class_Add_List_Element, - Notification_Class_Remove_List_Element }, + Notification_Class_Remove_List_Element, + NULL /* Create */, NULL /* Delete */ }, #endif { OBJECT_LIFE_SAFETY_POINT, Life_Safety_Point_Init, Life_Safety_Point_Count, Life_Safety_Point_Index_To_Instance, Life_Safety_Point_Valid_Instance, @@ -194,21 +206,24 @@ static object_functions_t My_Object_Table[] = { Life_Safety_Point_Write_Property, Life_Safety_Point_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_LOAD_CONTROL, Load_Control_Init, Load_Control_Count, Load_Control_Index_To_Instance, Load_Control_Valid_Instance, Load_Control_Object_Name, Load_Control_Read_Property, Load_Control_Write_Property, Load_Control_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_MULTI_STATE_INPUT, Multistate_Input_Init, Multistate_Input_Count, Multistate_Input_Index_To_Instance, Multistate_Input_Valid_Instance, Multistate_Input_Object_Name, Multistate_Input_Read_Property, Multistate_Input_Write_Property, Multistate_Input_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_MULTI_STATE_OUTPUT, Multistate_Output_Init, Multistate_Output_Count, Multistate_Output_Index_To_Instance, Multistate_Output_Valid_Instance, Multistate_Output_Object_Name, @@ -216,7 +231,8 @@ static object_functions_t My_Object_Table[] = { Multistate_Output_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Multistate_Output_Create, Multistate_Output_Delete}, { OBJECT_MULTI_STATE_VALUE, Multistate_Value_Init, Multistate_Value_Count, Multistate_Value_Index_To_Instance, Multistate_Value_Valid_Instance, Multistate_Value_Object_Name, Multistate_Value_Read_Property, @@ -224,14 +240,16 @@ static object_functions_t My_Object_Table[] = { NULL /* ReadRangeInfo */, NULL /* Iterator */, Multistate_Value_Encode_Value_List, Multistate_Value_Change_Of_Value, Multistate_Value_Change_Of_Value_Clear, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_TRENDLOG, Trend_Log_Init, Trend_Log_Count, Trend_Log_Index_To_Instance, Trend_Log_Valid_Instance, Trend_Log_Object_Name, Trend_Log_Read_Property, Trend_Log_Write_Property, Trend_Log_Property_Lists, TrendLogGetRRInfo, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, #if (BACNET_PROTOCOL_REVISION >= 14) { OBJECT_LIGHTING_OUTPUT, Lighting_Output_Init, Lighting_Output_Count, Lighting_Output_Index_To_Instance, Lighting_Output_Valid_Instance, @@ -239,13 +257,15 @@ static object_functions_t My_Object_Table[] = { Lighting_Output_Write_Property, Lighting_Output_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_CHANNEL, Channel_Init, Channel_Count, Channel_Index_To_Instance, Channel_Valid_Instance, Channel_Object_Name, Channel_Read_Property, Channel_Write_Property, Channel_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, #endif #if (BACNET_PROTOCOL_REVISION >= 24) { OBJECT_COLOR, Color_Init, Color_Count, Color_Index_To_Instance, @@ -253,14 +273,16 @@ static object_functions_t My_Object_Table[] = { Color_Write_Property, Color_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Color_Create, Color_Delete}, { OBJECT_COLOR_TEMPERATURE, Color_Temperature_Init, Color_Temperature_Count, Color_Temperature_Index_To_Instance, Color_Temperature_Valid_Instance, Color_Temperature_Object_Name, Color_Temperature_Read_Property, Color_Temperature_Write_Property, Color_Temperature_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + Color_Temperature_Create, Color_Temperature_Delete}, #endif #if defined(BACFILE) { OBJECT_FILE, bacfile_init, bacfile_count, bacfile_index_to_instance, @@ -268,7 +290,8 @@ static object_functions_t My_Object_Table[] = { bacfile_write_property, BACfile_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + bacfile_create, bacfile_delete}, #endif { OBJECT_OCTETSTRING_VALUE, OctetString_Value_Init, OctetString_Value_Count, OctetString_Value_Index_To_Instance, OctetString_Value_Valid_Instance, @@ -276,7 +299,8 @@ static object_functions_t My_Object_Table[] = { OctetString_Value_Write_Property, OctetString_Value_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_POSITIVE_INTEGER_VALUE, PositiveInteger_Value_Init, PositiveInteger_Value_Count, PositiveInteger_Value_Index_To_Instance, PositiveInteger_Value_Valid_Instance, PositiveInteger_Value_Object_Name, @@ -285,28 +309,32 @@ static object_functions_t My_Object_Table[] = { PositiveInteger_Value_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_SCHEDULE, Schedule_Init, Schedule_Count, Schedule_Index_To_Instance, Schedule_Valid_Instance, Schedule_Object_Name, Schedule_Read_Property, Schedule_Write_Property, Schedule_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, NULL /* Add_List_Element */, - NULL /* Remove_List_Element */ }, + NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { OBJECT_ACCUMULATOR, Accumulator_Init, Accumulator_Count, Accumulator_Index_To_Instance, Accumulator_Valid_Instance, Accumulator_Object_Name, Accumulator_Read_Property, Accumulator_Write_Property, Accumulator_Property_Lists, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ }, + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, { MAX_BACNET_OBJECT_TYPE, NULL /* Init */, NULL /* Count */, NULL /* Index_To_Instance */, NULL /* Valid_Instance */, NULL /* Object_Name */, NULL /* Read_Property */, NULL /* Write_Property */, NULL /* Property_Lists */, NULL /* ReadRangeInfo */, NULL /* Iterator */, NULL /* Value_Lists */, NULL /* COV */, NULL /* COV Clear */, NULL /* Intrinsic Reporting */, - NULL /* Add_List_Element */, NULL /* Remove_List_Element */ } + NULL /* Add_List_Element */, NULL /* Remove_List_Element */, + NULL /* Create */, NULL /* Delete */ }, }; /** Glue function to let the Device object, when called by a handler, @@ -1892,6 +1920,108 @@ void Device_COV_Clear(BACNET_OBJECT_TYPE object_type, uint32_t object_instance) } } +/** + * @brief Creates a child object, if supported + * @ingroup ObjHelpers + * @param data - CreateObject data, including error codes if failures + * @return true if object has been created + */ +bool Device_Create_Object( + BACNET_CREATE_OBJECT_DATA *data) +{ + bool status = false; + struct object_functions *pObject = NULL; + uint32_t object_instance; + + pObject = Device_Objects_Find_Functions(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 && + 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; + 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; +} + +/** + * @brief Deletes a child object, if supported + * @ingroup ObjHelpers + * @param data - DeleteObject data, including error codes if failures + * @return true if object has been deleted + */ +bool Device_Delete_Object( + BACNET_DELETE_OBJECT_DATA *data) +{ + bool status = false; + struct object_functions *pObject = NULL; + + pObject = Device_Objects_Find_Functions(data->object_type); + if (pObject != NULL) { + if (!pObject->Object_Delete) { + /* The device supports the object type + but does not support the deletion of the + object for some reason.*/ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; + } else if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(data->object_instance)) { + /* The object being deleted must already exist */ + status = pObject->Object_Delete(data->object_instance); + if (!status) { + /* The object exists but cannot be deleted. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_OBJECT_DELETION_NOT_PERMITTED; + } + } else { + /* The object to be deleted does not exist. */ + data->error_class = ERROR_CLASS_OBJECT; + data->error_code = ERROR_CODE_UNKNOWN_OBJECT; + } + } 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; +} + #if defined(INTRINSIC_REPORTING) void Device_local_reporting(void) { @@ -1966,17 +2096,13 @@ void Device_Init(object_functions_t *object_table) pObject++; } /* create some dynamically created objects as examples */ - Analog_Output_Create(1); - Binary_Output_Create(1); - Multistate_Output_Create(1); -#if (BACNET_PROTOCOL_REVISION >= 24) - Color_Create(1); - Color_Temperature_Create(1); -#endif -#if defined(BACFILE) - bacfile_create(1); - bacfile_pathname_set(1, "temp_1.txt"); -#endif + pObject = Object_Table; + while (pObject->Object_Type < MAX_BACNET_OBJECT_TYPE) { + if (pObject->Object_Create) { + pObject->Object_Create(BACNET_MAX_INSTANCE); + } + pObject++; + } } bool DeviceGetRRInfo(BACNET_READ_RANGE_DATA *pRequest, /* Info on the request */ diff --git a/src/bacnet/basic/object/device.h b/src/bacnet/basic/object/device.h index 4a3f3fce..477522f3 100644 --- a/src/bacnet/basic/object/device.h +++ b/src/bacnet/basic/object/device.h @@ -34,6 +34,8 @@ #include "bacnet/bacnet_stack_exports.h" #include "bacnet/bacdef.h" #include "bacnet/bacenum.h" +#include "bacnet/create_object.h" +#include "bacnet/delete_object.h" #include "bacnet/list_element.h" #include "bacnet/wp.h" #include "bacnet/rd.h" @@ -168,6 +170,8 @@ typedef struct object_functions { object_intrinsic_reporting_function Object_Intrinsic_Reporting; list_element_function Object_Add_List_Element; list_element_function Object_Remove_List_Element; + create_object_function Object_Create; + delete_object_function Object_Delete; } object_functions_t; /* String Lengths - excluding any nul terminator */ @@ -316,6 +320,13 @@ extern "C" { BACNET_ARRAY_INDEX array_index, uint8_t *apdu); + BACNET_STACK_EXPORT + bool Device_Create_Object( + BACNET_CREATE_OBJECT_DATA *data); + BACNET_STACK_EXPORT + bool Device_Delete_Object( + BACNET_DELETE_OBJECT_DATA *data); + BACNET_STACK_EXPORT unsigned Device_Count( void); diff --git a/src/bacnet/basic/object/mso.c b/src/bacnet/basic/object/mso.c index 4379c7cb..032828af 100644 --- a/src/bacnet/basic/object/mso.c +++ b/src/bacnet/basic/object/mso.c @@ -1152,15 +1152,24 @@ void Multistate_Output_Write_Present_Value_Callback_Set( /** * @brief Creates a new object and adds it to the object list * @param object_instance - object-instance number of the object - * @return true if the object is created + * @return the object-instance that was created, or BACNET_MAX_INSTANCE */ -bool Multistate_Output_Create(uint32_t object_instance) +uint32_t Multistate_Output_Create(uint32_t object_instance) { - bool status = false; struct object_data *pObject = NULL; int index = 0; unsigned priority = 0; + if (object_instance > BACNET_MAX_INSTANCE) { + return BACNET_MAX_INSTANCE; + } else if (object_instance == BACNET_MAX_INSTANCE) { + /* wildcard instance */ + /* the Object_Identifier property of the newly created object + shall be initialized to a value that is unique within the + responding BACnet-user device. The method used to generate + the object identifier is a local matter.*/ + object_instance = Keylist_Next_Empty_Key(Object_List, 1); + } pObject = Keylist_Data(Object_List, object_instance); if (!pObject) { pObject = calloc(1, sizeof(struct object_data)); @@ -1178,13 +1187,17 @@ bool Multistate_Output_Create(uint32_t object_instance) /* add to list */ index = Keylist_Data_Add(Object_List, object_instance, pObject); if (index >= 0) { - status = true; Device_Inc_Database_Revision(); + } else { + free(pObject); + return BACNET_MAX_INSTANCE; } + } else { + return BACNET_MAX_INSTANCE; } } - return status; + return object_instance; } /** diff --git a/src/bacnet/basic/object/mso.h b/src/bacnet/basic/object/mso.h index be8c58dd..c9550092 100644 --- a/src/bacnet/basic/object/mso.h +++ b/src/bacnet/basic/object/mso.h @@ -174,7 +174,7 @@ extern "C" { uint32_t value); BACNET_STACK_EXPORT - bool Multistate_Output_Create( + uint32_t Multistate_Output_Create( uint32_t object_instance); BACNET_STACK_EXPORT bool Multistate_Output_Delete( diff --git a/src/bacnet/basic/service/h_create_object.c b/src/bacnet/basic/service/h_create_object.c new file mode 100644 index 00000000..8742b287 --- /dev/null +++ b/src/bacnet/basic/service/h_create_object.c @@ -0,0 +1,124 @@ +/** + * @file + * @brief CreateObject service application handlers + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacerror.h" +#include "bacnet/apdu.h" +#include "bacnet/npdu.h" +#include "bacnet/abort.h" +#include "bacnet/reject.h" +#include "bacnet/create_object.h" +/* basic objects, services, TSM, and datalink */ +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/datalink/datalink.h" + +/** + * @brief Handler for a CreateObject service request. + * This handler will be invoked by apdu_handler() if it has been enabled + * via call to apdu_set_confirmed_handler(). + * This handler builds a response packet, which is + * - an Abort if + * - the message is segmented + * - if decoding fails + * - a SimpleACK if Device_Create_Object() succeeds + * - an Error if Device_Create_Object() fails + * + * @param service_request [in] The contents of the service request. + * @param service_len [in] The length of the service_request. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param service_data [in] The BACNET_CONFIRMED_SERVICE_DATA information + * decoded from the APDU header of this message. + */ +void handler_create_object(uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_DATA *service_data) +{ + BACNET_CREATE_OBJECT_DATA data = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + BACNET_ADDRESS my_address = { 0 }; + int len = 0; + bool status = true; + int pdu_len = 0; + int bytes_sent = 0; + + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu( + &Handler_Transmit_Buffer[0], src, &my_address, &npdu_data); + debug_perror("CreateObject: Received Request!\n"); + if (service_data->segmented_message) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, + true); + debug_perror("CreateObject: Segmented message. Sending Abort!\n"); + status = false; + } + if (status) { + /* decode the service request only */ + len = create_object_decode_service_request( + service_request, service_len, &data); + if (len > 0) { + debug_perror("CreateObject: type=%lu instance=%lu\n", + (unsigned long)data.object_type, + (unsigned long)data.object_instance); + } else { + debug_perror("CreateObject: Unable to decode request!\n"); + } + if (len <= 0) { + /* bad decoding or something we didn't understand */ + if (len == BACNET_STATUS_ABORT) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + abort_convert_error_code(data.error_code), true); + debug_perror("CreateObject: Sending Abort!\n"); + } else if (len == BACNET_STATUS_REJECT) { + len = reject_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + reject_convert_error_code(data.error_code)); + debug_perror("CreateObject: Sending Reject!\n"); + } + } else { + if (Device_Create_Object(&data)) { + len = + create_object_ack_encode(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, &data); + debug_perror("CreateObject: Sending ACK!\n"); + } else { + len = create_object_error_ack_encode( + &Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, &data); + debug_perror("CreateObject: Sending Error!\n"); + } + } + } + if (len > 0) { + /* Send PDU */ + pdu_len += len; + bytes_sent = datalink_send_pdu( + src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + } + if (bytes_sent <= 0) { + debug_perror( + "CreateObject: Failed to send PDU (%s)!\n", strerror(errno)); + } + + return; +} diff --git a/src/bacnet/basic/service/h_create_object.h b/src/bacnet/basic/service/h_create_object.h new file mode 100644 index 00000000..f2889f15 --- /dev/null +++ b/src/bacnet/basic/service/h_create_object.h @@ -0,0 +1,34 @@ +/** + * @file + * @brief API for CreateObject service handlers + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef HANDLER_CREATE_OBJECT_H +#define HANDLER_CREATE_OBJECT_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacdef.h" +#include "bacnet/apdu.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + BACNET_STACK_EXPORT + void handler_create_object( + uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_DATA *service_data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/service/h_delete_object.c b/src/bacnet/basic/service/h_delete_object.c new file mode 100644 index 00000000..a561b8aa --- /dev/null +++ b/src/bacnet/basic/service/h_delete_object.c @@ -0,0 +1,119 @@ +/** + * @file + * @brief DeleteObject service application handlers + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacerror.h" +#include "bacnet/apdu.h" +#include "bacnet/npdu.h" +#include "bacnet/abort.h" +#include "bacnet/reject.h" +#include "bacnet/delete_object.h" +/* basic objects, services, TSM, and datalink */ +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/datalink/datalink.h" + +/** + * @brief Handler for a DeleteObject Service request. + * This handler will be invoked by apdu_handler() if it has been enabled + * via call to apdu_set_confirmed_handler(). + * This handler builds a response packet, which is + * - an Abort if + * - the message is segmented + * - if decoding fails + * - a SimpleACK if Device_Delete_Object() succeeds + * - an Error if Device_Delete_Object() fails + * + * @param service_request [in] The contents of the service request. + * @param service_len [in] The length of the service_request. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param service_data [in] The BACNET_CONFIRMED_SERVICE_DATA information + * decoded from the APDU header of this message. + */ +void handler_delete_object(uint8_t *service_request, + uint16_t service_len, + BACNET_ADDRESS *src, + BACNET_CONFIRMED_SERVICE_DATA *service_data) +{ + BACNET_DELETE_OBJECT_DATA data = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + BACNET_ADDRESS my_address = { 0 }; + int len = 0; + bool status = true; + int pdu_len = 0; + int bytes_sent = 0; + + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, false, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu( + &Handler_Transmit_Buffer[0], src, &my_address, &npdu_data); + debug_perror("DeleteObject: Received Request!\n"); + if (service_data->segmented_message) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_SEGMENTATION_NOT_SUPPORTED, + true); + debug_perror("DeleteObject: Segmented message. Sending Abort!\n"); + status = false; + } + if (status) { + /* decode the service request only */ + len = delete_object_decode_service_request( + service_request, service_len, &data); + if (len > 0) { + debug_perror("DeleteObject: type=%lu instance=%lu\n", + (unsigned long)data.object_type, + (unsigned long)data.object_instance); + } else { + debug_perror("DeleteObject: Unable to decode request!\n"); + } + /* bad decoding or something we didn't understand - send an abort */ + if (len <= 0) { + len = abort_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, ABORT_REASON_OTHER, true); + debug_perror("DeleteObject: Bad Encoding. Sending Abort!\n"); + status = false; + } + if (status) { + if (Device_Delete_Object(&data)) { + len = encode_simple_ack(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_DELETE_OBJECT); + debug_perror("DeleteObject: Sending Simple Ack!\n"); + } else { + len = bacerror_encode_apdu(&Handler_Transmit_Buffer[pdu_len], + service_data->invoke_id, + SERVICE_CONFIRMED_DELETE_OBJECT, + data.error_class, data.error_code); + debug_perror("DeleteObject: Sending Error!\n"); + } + } + } + if (len > 0) { + /* Send PDU */ + pdu_len += len; + bytes_sent = datalink_send_pdu( + src, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + } + if (bytes_sent <= 0) { + debug_perror( + "DeleteObject: Failed to send PDU (%s)!\n", strerror(errno)); + } + + return; +} diff --git a/src/bacnet/basic/service/h_delete_object.h b/src/bacnet/basic/service/h_delete_object.h new file mode 100644 index 00000000..deb24619 --- /dev/null +++ b/src/bacnet/basic/service/h_delete_object.h @@ -0,0 +1,34 @@ +/** + * @file + * @brief API for DeleteObject service handlers + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef HANDLER_DELETE_OBJECT_H +#define HANDLER_DELETE_OBJECT_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacdef.h" +#include "bacnet/apdu.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + BACNET_STACK_EXPORT + void handler_delete_object( + uint8_t * service_request, + uint16_t service_len, + BACNET_ADDRESS * src, + BACNET_CONFIRMED_SERVICE_DATA *service_data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/service/s_create_object.c b/src/bacnet/basic/service/s_create_object.c new file mode 100644 index 00000000..36485e25 --- /dev/null +++ b/src/bacnet/basic/service/s_create_object.c @@ -0,0 +1,135 @@ +/** + * @file + * @brief CreateObject service initiation + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacerror.h" +#include "bacnet/apdu.h" +#include "bacnet/npdu.h" +#include "bacnet/abort.h" +#include "bacnet/bactext.h" +#include "bacnet/dcc.h" +#include "bacnet/create_object.h" +#include "bacnet/whois.h" +/* some demo stuff needed */ +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" + +/** + * @brief Send a CreateObject service message + * @param device_id [in] ID of the destination device + * @param object_type [in] Type of the object whose property is to be written. + * @param object_instance [in] Instance # of the object to be written. + * @return invoke id of outgoing message, or 0 on failure. + * @return the invoke ID for confirmed request, or zero on failure + */ +uint8_t Send_Create_Object_Request_Data( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_VALUE *values) +{ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_CREATE_OBJECT_DATA data = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + uint8_t service = SERVICE_CONFIRMED_CREATE_OBJECT; + + if (!dcc_communication_enabled()) { + return 0; + } + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) { + invoke_id = tsm_next_free_invokeID(); + } + if (invoke_id) { + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu( + &Handler_Transmit_Buffer[0], &dest, &my_address, &npdu_data); + /* encode the APDU header portion of the packet */ + Handler_Transmit_Buffer[pdu_len++] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + Handler_Transmit_Buffer[pdu_len++] = + encode_max_segs_max_apdu(0, MAX_APDU); + Handler_Transmit_Buffer[pdu_len++] = invoke_id; + Handler_Transmit_Buffer[pdu_len++] = service; + /* encode the APDU service */ + data.object_type = object_type; + data.object_instance = object_instance; + data.list_of_initial_values = values; + /* get the length of the APDU */ + len = create_object_encode_service_request(NULL, &data); + pdu_len += len; + /* will it fit in the sender and our buffer? + note: if there is a bottleneck router in between + us and the destination, we won't know unless + we have a way to check for that and update the + max_apdu in the address binding table. */ + if (((unsigned)pdu_len < max_apdu) && + (pdu_len < sizeof(Handler_Transmit_Buffer))) { + /* shift back to the service portion of the buffer */ + pdu_len -= len; + len = create_object_encode_service_request( + &Handler_Transmit_Buffer[pdu_len], &data); + pdu_len += len; + tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, + &npdu_data, &Handler_Transmit_Buffer[0], (uint16_t)pdu_len); + bytes_sent = datalink_send_pdu( + &dest, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + if (bytes_sent <= 0) { + debug_perror("%s service: Failed to Send %i/%i (%s)!\n", + bactext_confirmed_service_name(service), bytes_sent, + pdu_len, strerror(errno)); + } + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; + debug_perror("%s service: Failed to Send " + "(exceeds destination maximum APDU)!\n", + bactext_confirmed_service_name(service)); + } + } + + return invoke_id; +} + +/** + * @brief Send a CreateObject service message + * @param device_id [in] ID of the destination device + * @param object_type [in] Type of the object whose property is to be written. + * @param object_instance [in] Instance # of the object to be written. + * @return invoke id of outgoing message, or 0 on failure. + * @return the invoke ID for confirmed request, or zero on failure + */ +uint8_t Send_Create_Object_Request( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance) +{ + return Send_Create_Object_Request_Data(device_id, object_type, object_instance, NULL); +} diff --git a/src/bacnet/basic/service/s_create_object.h b/src/bacnet/basic/service/s_create_object.h new file mode 100644 index 00000000..13e4cf6d --- /dev/null +++ b/src/bacnet/basic/service/s_create_object.h @@ -0,0 +1,43 @@ +/** + * @file + * @brief CreateObject service initiation + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef SEND_CREATE_OBJECT_H +#define SEND_CREATE_OBJECT_H + +#include +#include +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacapp.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/apdu.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +uint8_t Send_Create_Object_Request_Data( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance, + BACNET_PROPERTY_VALUE *values); + +BACNET_STACK_EXPORT +uint8_t Send_Create_Object_Request( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/service/s_delete_object.c b/src/bacnet/basic/service/s_delete_object.c new file mode 100644 index 00000000..aebe7c2d --- /dev/null +++ b/src/bacnet/basic/service/s_delete_object.c @@ -0,0 +1,117 @@ +/** + * @file + * @brief DeleteObject service initiation + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacerror.h" +#include "bacnet/apdu.h" +#include "bacnet/npdu.h" +#include "bacnet/abort.h" +#include "bacnet/bactext.h" +#include "bacnet/dcc.h" +#include "bacnet/delete_object.h" +#include "bacnet/whois.h" +/* some demo stuff needed */ +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" + +/** + * @brief Send a CreateObject service message + * @param device_id [in] ID of the destination device + * @param object_type [in] Type of the object whose property is to be written. + * @param object_instance [in] Instance # of the object to be written. + * @return invoke id of outgoing message, or 0 on failure. + * @return the invoke ID for confirmed request, or zero on failure + */ +uint8_t Send_Delete_Object_Request( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance) +{ + BACNET_ADDRESS dest; + BACNET_ADDRESS my_address; + unsigned max_apdu = 0; + uint8_t invoke_id = 0; + bool status = false; + int len = 0; + int pdu_len = 0; + int bytes_sent = 0; + BACNET_DELETE_OBJECT_DATA data = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + uint8_t service = SERVICE_CONFIRMED_DELETE_OBJECT; + + if (!dcc_communication_enabled()) { + return 0; + } + /* is the device bound? */ + status = address_get_by_device(device_id, &max_apdu, &dest); + /* is there a tsm available? */ + if (status) { + invoke_id = tsm_next_free_invokeID(); + } + if (invoke_id) { + /* encode the NPDU portion of the packet */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_data(&npdu_data, true, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu( + &Handler_Transmit_Buffer[0], &dest, &my_address, &npdu_data); + /* encode the APDU header portion of the packet */ + Handler_Transmit_Buffer[pdu_len++] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + Handler_Transmit_Buffer[pdu_len++] = + encode_max_segs_max_apdu(0, MAX_APDU); + Handler_Transmit_Buffer[pdu_len++] = invoke_id; + Handler_Transmit_Buffer[pdu_len++] = service; + /* encode the APDU service */ + data.object_type = object_type; + data.object_instance = object_instance; + /* get the length of the APDU */ + len = delete_object_encode_service_request(NULL, &data); + pdu_len += len; + /* will it fit in the sender and our buffer? + note: if there is a bottleneck router in between + us and the destination, we won't know unless + we have a way to check for that and update the + max_apdu in the address binding table. */ + if (((unsigned)pdu_len < max_apdu) && + (pdu_len < sizeof(Handler_Transmit_Buffer))) { + /* shift back to the service portion of the buffer */ + pdu_len -= len; + len = delete_object_encode_service_request( + &Handler_Transmit_Buffer[pdu_len], &data); + pdu_len += len; + tsm_set_confirmed_unsegmented_transaction(invoke_id, &dest, + &npdu_data, &Handler_Transmit_Buffer[0], (uint16_t)pdu_len); + bytes_sent = datalink_send_pdu( + &dest, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + if (bytes_sent <= 0) { + debug_perror("%s service: Failed to Send %i/%i (%s)!\n", + bactext_confirmed_service_name(service), bytes_sent, + pdu_len, strerror(errno)); + } + } else { + tsm_free_invoke_id(invoke_id); + invoke_id = 0; + debug_perror("%s service: Failed to Send " + "(exceeds destination maximum APDU)!\n", + bactext_confirmed_service_name(service)); + } + } + + return invoke_id; +} diff --git a/src/bacnet/basic/service/s_delete_object.h b/src/bacnet/basic/service/s_delete_object.h new file mode 100644 index 00000000..651cb5ed --- /dev/null +++ b/src/bacnet/basic/service/s_delete_object.h @@ -0,0 +1,36 @@ +/** + * @file + * @brief DeleteObject service initiation + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef SEND_DELETE_OBJECT_H +#define SEND_DELETE_OBJECT_H + +#include +#include +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacapp.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacenum.h" +#include "bacnet/apdu.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +uint8_t Send_Delete_Object_Request( + uint32_t device_id, + BACNET_OBJECT_TYPE object_type, + uint32_t object_instance); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/services.h b/src/bacnet/basic/services.h index dedda1e6..9755dfdd 100644 --- a/src/bacnet/basic/services.h +++ b/src/bacnet/basic/services.h @@ -41,7 +41,9 @@ #include "bacnet/basic/service/h_awf.h" #include "bacnet/basic/service/h_ccov.h" #include "bacnet/basic/service/h_cov.h" +#include "bacnet/basic/service/h_create_object.h" #include "bacnet/basic/service/h_dcc.h" +#include "bacnet/basic/service/h_delete_object.h" #include "bacnet/basic/service/h_gas_a.h" #include "bacnet/basic/service/h_get_alarm_sum.h" #include "bacnet/basic/service/h_getevent.h" @@ -73,7 +75,9 @@ #include "bacnet/basic/service/s_awfs.h" #include "bacnet/basic/service/s_cevent.h" #include "bacnet/basic/service/s_cov.h" +#include "bacnet/basic/service/s_create_object.h" #include "bacnet/basic/service/s_dcc.h" +#include "bacnet/basic/service/s_delete_object.h" #include "bacnet/basic/service/s_error.h" #include "bacnet/basic/service/s_get_alarm_sum.h" #include "bacnet/basic/service/s_get_event.h" diff --git a/src/bacnet/basic/sys/platform.h b/src/bacnet/basic/sys/platform.h index 9a6c23f6..6e52f206 100644 --- a/src/bacnet/basic/sys/platform.h +++ b/src/bacnet/basic/sys/platform.h @@ -18,12 +18,17 @@ #ifndef BACNET_SYS_PLATFORM_H #define BACNET_SYS_PLATFORM_H +#include #include #ifndef islessgreater #define islessgreater(x, y) ((x) < (y) || (x) > (y)) #endif +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(array) ((size_t)(sizeof(array) / sizeof((array)[0]))) +#endif + /* marking some code as 'deprecated' */ # if defined(_MSC_VER) # define BACNET_STACK_DEPRECATED(message) __declspec(deprecated(message)) diff --git a/src/bacnet/cov.c b/src/bacnet/cov.c index cb224a2b..0872443b 100644 --- a/src/bacnet/cov.c +++ b/src/bacnet/cov.c @@ -50,88 +50,94 @@ Unconfirmed COV Notification */ /** - * Encode APDU for notification. - * - * @param apdu Pointer to the buffer. + * @brief Encode APDU for COV Notification. + * @param apdu Pointer to the buffer, or NULL for length * @param data Pointer to the data to encode. - * * @return bytes encoded or zero on error. */ -static int notify_encode_apdu( - uint8_t *apdu, unsigned max_apdu_len, BACNET_COV_DATA *data) +int cov_notify_encode_apdu(uint8_t *apdu, BACNET_COV_DATA *data) { 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 */ - BACNET_APPLICATION_DATA_VALUE *app_data = NULL; - (void)max_apdu_len; if (apdu) { /* tag 0 - subscriberProcessIdentifier */ - len = encode_context_unsigned( - &apdu[apdu_len], 0, data->subscriberProcessIdentifier); + len = + encode_context_unsigned(apdu, 0, data->subscriberProcessIdentifier); apdu_len += len; + if (apdu) { + apdu += len; + } /* tag 1 - initiatingDeviceIdentifier */ - len = encode_context_object_id(&apdu[apdu_len], 1, OBJECT_DEVICE, - data->initiatingDeviceIdentifier); + len = encode_context_object_id( + apdu, 1, OBJECT_DEVICE, data->initiatingDeviceIdentifier); apdu_len += len; + if (apdu) { + apdu += len; + } /* tag 2 - monitoredObjectIdentifier */ - len = encode_context_object_id(&apdu[apdu_len], 2, + len = encode_context_object_id(apdu, 2, data->monitoredObjectIdentifier.type, data->monitoredObjectIdentifier.instance); apdu_len += len; + if (apdu) { + apdu += len; + } /* tag 3 - timeRemaining */ - len = encode_context_unsigned(&apdu[apdu_len], 3, data->timeRemaining); + len = encode_context_unsigned(apdu, 3, data->timeRemaining); apdu_len += len; + if (apdu) { + apdu += len; + } /* tag 4 - listOfValues */ - len = encode_opening_tag(&apdu[apdu_len], 4); + len = encode_opening_tag(apdu, 4); apdu_len += len; + if (apdu) { + apdu += len; + } /* the first value includes a pointer to the next value, etc */ - /* FIXME: for small implementations, we might try a partial - approach like the rpm.c where the values are encoded with - a separate function */ value = data->listOfValues; while (value != NULL) { - /* tag 0 - propertyIdentifier */ - len = encode_context_enumerated( - &apdu[apdu_len], 0, value->propertyIdentifier); + len = bacapp_property_value_encode(apdu, value); apdu_len += len; - /* tag 1 - propertyArrayIndex OPTIONAL */ - if (value->propertyArrayIndex != BACNET_ARRAY_ALL) { - len = encode_context_unsigned( - &apdu[apdu_len], 1, value->propertyArrayIndex); - apdu_len += len; - } - /* tag 2 - value */ - /* abstract syntax gets enclosed in a context tag */ - len = encode_opening_tag(&apdu[apdu_len], 2); - apdu_len += len; - app_data = &value->value; - while (app_data != NULL) { - len = bacapp_encode_application_data(&apdu[apdu_len], app_data); - apdu_len += len; - app_data = app_data->next; - } - - len = encode_closing_tag(&apdu[apdu_len], 2); - apdu_len += len; - /* tag 3 - priority OPTIONAL */ - if (value->priority != BACNET_NO_PRIORITY) { - len = encode_context_unsigned( - &apdu[apdu_len], 3, value->priority); - 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[apdu_len], 4); + len = encode_closing_tag(apdu, 4); apdu_len += len; } return apdu_len; } +/** + * Encode APDU for notification. + * + * @param apdu Pointer to the buffer for encoding into + * @param apdu_size number of bytes available in the buffer + * @param data Pointer to the data to encode. + * + * @return bytes encoded or zero if unable to encode + */ +static int notify_encode_apdu( + uint8_t *apdu, unsigned apdu_size, BACNET_COV_DATA *data) +{ + int apdu_len = 0; /* total length of the apdu, return value */ + + apdu_len = cov_notify_encode_apdu(NULL, data); + if (apdu_len > apdu_size) { + apdu_len = 0; + } else { + apdu_len = cov_notify_encode_apdu(apdu, data); + } + + return apdu_len; +} + /** * Encode APDU for confirmed notification. * @@ -158,7 +164,7 @@ int ccov_notify_encode_apdu(uint8_t *apdu, apdu_len = 4; len = notify_encode_apdu(&apdu[apdu_len], max_apdu_len - apdu_len, data); - if (len < 0) { + if (len <= 0) { /* return the error */ apdu_len = len; } else { @@ -190,7 +196,7 @@ int ucov_notify_encode_apdu( apdu_len = 2; len = notify_encode_apdu(&apdu[apdu_len], max_apdu_len - apdu_len, data); - if (len < 0) { + if (len <= 0) { /* return the error */ apdu_len = len; } else { @@ -202,164 +208,120 @@ int ucov_notify_encode_apdu( } /** - * Decode the COV-service request only. - * Note: COV and Unconfirmed COV are the same. + * @brief Decode the COV-service request only. * + * ConfirmedCOVNotification-Request ::= SEQUENCE { + * subscriber-process-identifier [0] Unsigned32, + * initiating-device-identifier [1] BACnetObjectIdentifier, + * monitored-object-identifier [2] BACnetObjectIdentifier, + * time-remaining [3] Unsigned, + * list-of-values [4] SEQUENCE OF BACnetPropertyValue + * } + * + * @note: COV and Unconfirmed COV are the same. * @param apdu Pointer to the buffer. - * @param apdu_len Count of valid bytes in the buffer. - * @param data Pointer to the data to store the decoded values. + * @param apdu_size Number of valid bytes in the buffer. + * @param data Pointer to the data to store the decoded values, or NULL * - * @return Bytes decoded or Zero/BACNET_STATUS_ERROR on error. + * @return Bytes decoded or BACNET_STATUS_ERROR on error. */ int cov_notify_decode_service_request( - uint8_t *apdu, unsigned apdu_len, BACNET_COV_DATA *data) + uint8_t *apdu, unsigned apdu_size, BACNET_COV_DATA *data) { int len = 0; /* return value */ - int app_len = 0; - uint8_t tag_number = 0; - uint32_t len_value = 0; - BACNET_UNSIGNED_INTEGER decoded_value = 0; /* for decoding */ - BACNET_OBJECT_TYPE decoded_type = OBJECT_NONE; /* for decoding */ - uint32_t property = 0; /* for decoding */ - BACNET_PROPERTY_VALUE *value = NULL; /* value in list */ - BACNET_APPLICATION_DATA_VALUE *app_data = NULL; + int value_len = 0, tag_len = 0; + BACNET_UNSIGNED_INTEGER decoded_value = 0; + BACNET_OBJECT_TYPE decoded_type = OBJECT_NONE; + uint32_t decoded_instance = 0; + BACNET_PROPERTY_ID property_identifier = PROP_ALL; + BACNET_PROPERTY_VALUE *value = NULL; - if ((apdu_len > 2) && data) { - /* tag 0 - subscriberProcessIdentifier */ - if (decode_is_context_tag(&apdu[len], 0)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &decoded_value); + /* subscriber-process-identifier [0] Unsigned32 */ + value_len = bacnet_unsigned_context_decode( + &apdu[len], apdu_size - len, 0, &decoded_value); + if (value_len > 0) { + if (data) { data->subscriberProcessIdentifier = decoded_value; - } else { + } + len += value_len; + } else { + return BACNET_STATUS_ERROR; + } + /* initiating-device-identifier [1] BACnetObjectIdentifier */ + value_len = bacnet_object_id_context_decode( + &apdu[len], apdu_size - len, 1, &decoded_type, &decoded_instance); + if (value_len > 0) { + if (decoded_type != OBJECT_DEVICE) { return BACNET_STATUS_ERROR; } - /* tag 1 - initiatingDeviceIdentifier */ - if (len >= (int)apdu_len) { - return BACNET_STATUS_ERROR; + if (data) { + data->initiatingDeviceIdentifier = decoded_instance; } - if (decode_is_context_tag(&apdu[len], 1)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_object_id( - &apdu[len], &decoded_type, &data->initiatingDeviceIdentifier); - if (decoded_type != OBJECT_DEVICE) { - return BACNET_STATUS_ERROR; - } - } else { - return BACNET_STATUS_ERROR; - } - /* tag 2 - monitoredObjectIdentifier */ - if (len >= (int)apdu_len) { - return BACNET_STATUS_ERROR; - } - if (decode_is_context_tag(&apdu[len], 2)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_object_id(&apdu[len], &decoded_type, - &data->monitoredObjectIdentifier.instance); + len += value_len; + } else { + return BACNET_STATUS_ERROR; + } + /* monitored-object-identifier [2] BACnetObjectIdentifier */ + value_len = bacnet_object_id_context_decode( + &apdu[len], apdu_size - len, 2, &decoded_type, &decoded_instance); + if (value_len > 0) { + if (data) { data->monitoredObjectIdentifier.type = decoded_type; - } else { - return BACNET_STATUS_ERROR; + data->monitoredObjectIdentifier.instance = decoded_instance; } - /* tag 3 - timeRemaining */ - if (len >= (int)apdu_len) { - return BACNET_STATUS_ERROR; - } - if (decode_is_context_tag(&apdu[len], 3)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &decoded_value); + len += value_len; + } else { + return BACNET_STATUS_ERROR; + } + /* time-remaining [3] Unsigned */ + value_len = bacnet_unsigned_context_decode( + &apdu[len], apdu_size - len, 3, &decoded_value); + if (value_len > 0) { + if (data) { data->timeRemaining = decoded_value; + } + len += value_len; + } else { + return BACNET_STATUS_ERROR; + } + /* list-of-values [4] SEQUENCE OF BACnetPropertyValue */ + if (bacnet_is_opening_tag_number( + &apdu[len], apdu_size - len, 4, &tag_len)) { + if (data) { + len += tag_len; + /* the first value includes a pointer to the next value, etc */ + value = data->listOfValues; + while (value != NULL) { + value_len = bacapp_property_value_decode( + &apdu[len], apdu_size - len, value); + if (value_len == BACNET_STATUS_ERROR) { + return BACNET_STATUS_ERROR; + } else { + len += value_len; + } + /* end of list? */ + if (bacnet_is_closing_tag_number( + &apdu[len], apdu_size - len, 4, &tag_len)) { + len += tag_len; + value->next = NULL; + break; + } + /* is there another one to decode? */ + value = value->next; + if (value == NULL) { + /* out of room to store next value */ + return BACNET_STATUS_ERROR; + } + } } else { - return BACNET_STATUS_ERROR; - } - /* tag 4: opening context tag - listOfValues */ - if (!decode_is_opening_tag_number(&apdu[len], 4)) { - return BACNET_STATUS_ERROR; - } - /* a tag number of 4 is not extended so only one octet */ - len++; - /* the first value includes a pointer to the next value, etc */ - value = data->listOfValues; - if (value == NULL) { - /* no space to store any values */ - return BACNET_STATUS_ERROR; - } - while (value != NULL) { - /* tag 0 - propertyIdentifier */ - if (len >= (int)apdu_len) { - return BACNET_STATUS_ERROR; - } - if (decode_is_context_tag(&apdu[len], 0)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_enumerated(&apdu[len], len_value, &property); - value->propertyIdentifier = (BACNET_PROPERTY_ID)property; - } else { - return BACNET_STATUS_ERROR; - } - /* tag 1 - propertyArrayIndex OPTIONAL */ - if (len >= (int)apdu_len) { - return BACNET_STATUS_ERROR; - } - if (decode_is_context_tag(&apdu[len], 1)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &decoded_value); - value->propertyArrayIndex = decoded_value; - } else { - value->propertyArrayIndex = BACNET_ARRAY_ALL; - } - /* tag 2: opening context tag - value */ - if (len >= (int)apdu_len) { - return BACNET_STATUS_ERROR; - } - if (!decode_is_opening_tag_number(&apdu[len], 2)) { - return BACNET_STATUS_ERROR; - } - /* a tag number of 2 is not extended so only one octet */ - len++; - app_data = &value->value; - while (!decode_is_closing_tag_number(&apdu[len], 2)) { - if (app_data == NULL) { - /* out of room to store more values */ - return BACNET_STATUS_ERROR; - } - app_len = bacapp_decode_application_data( - &apdu[len], apdu_len - len, app_data); - if (app_len < 0) { - return BACNET_STATUS_ERROR; - } - len += app_len; - - app_data = app_data->next; - } - /* a tag number of 2 is not extended so only one octet */ - len++; - /* tag 3 - priority OPTIONAL */ - if (len >= (int)apdu_len) { - return BACNET_STATUS_ERROR; - } - if (decode_is_context_tag(&apdu[len], 3)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &decoded_value); - value->priority = (uint8_t)decoded_value; - } else { - value->priority = BACNET_NO_PRIORITY; - } - /* end of list? */ - if (decode_is_closing_tag_number(&apdu[len], 4)) { - value->next = NULL; - break; - } - /* is there another one to decode? */ - value = value->next; - if (value == NULL) { - /* out of room to store more values */ - return BACNET_STATUS_ERROR; - } + /* this len function needs to start at the opening tag + to match opening/closing tags like a stack. + However, it returns the len between the tags. */ + value_len = bacapp_data_len(&apdu[len], apdu_size - len, + (BACNET_PROPERTY_ID)property_identifier); + len += value_len; + /* add the opening tag length to the totals */ + len += tag_len; } } @@ -442,79 +404,112 @@ int cov_subscribe_encode_apdu(uint8_t *apdu, } /** - * Decode the subscribe-service request only. + * @brief Decode the subscribe-service request only. + * + * SubscribeCOV-Request ::= SEQUENCE { + * subscriberProcessIdentifier [0] Unsigned32, + * monitoredObjectIdentifier [1] BACnetObjectIdentifier, + * issueConfirmedNotifications [2] BOOLEAN OPTIONAL, + * lifetime [3] Unsigned OPTIONAL + * } * * @param apdu Pointer to the buffer. - * @param apdu_len Count of valid bytes in the buffer. + * @param apdu_size number of valid bytes in the buffer. * @param data Pointer to the data to store the decoded values. * * @return Bytes decoded or Zero/BACNET_STATUS_ERROR on error. */ int cov_subscribe_decode_service_request( - uint8_t *apdu, unsigned apdu_len, BACNET_SUBSCRIBE_COV_DATA *data) + uint8_t *apdu, unsigned apdu_size, BACNET_SUBSCRIBE_COV_DATA *data) { int len = 0; /* return value */ - uint8_t tag_number = 0; - uint32_t len_value = 0; - BACNET_UNSIGNED_INTEGER unsigned_value = 0; + int value_len = 0; + BACNET_UNSIGNED_INTEGER decoded_value = 0; BACNET_OBJECT_TYPE decoded_type = OBJECT_NONE; + uint32_t decoded_instance = 0; + bool decoded_boolean = false; - if ((apdu_len > 2) && data) { - /* tag 0 - subscriberProcessIdentifier */ - if (decode_is_context_tag(&apdu[len], 0)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &unsigned_value); - data->subscriberProcessIdentifier = unsigned_value; - } else { + /* subscriberProcessIdentifier [0] Unsigned32 */ + value_len = bacnet_unsigned_context_decode( + &apdu[len], apdu_size - len, 0, &decoded_value); + if (value_len > 0) { + if (data) { + data->subscriberProcessIdentifier = decoded_value; + } + len += value_len; + } else { + if (data) { data->error_code = ERROR_CODE_REJECT_INVALID_TAG; - return BACNET_STATUS_REJECT; } - /* tag 1 - monitoredObjectIdentifier */ - if ((unsigned)len >= apdu_len) { - return BACNET_STATUS_REJECT; - } - if (decode_is_context_tag(&apdu[len], 1)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_object_id(&apdu[len], &decoded_type, - &data->monitoredObjectIdentifier.instance); + return BACNET_STATUS_ERROR; + } + /* monitoredObjectIdentifier [1] BACnetObjectIdentifier */ + value_len = bacnet_object_id_context_decode( + &apdu[len], apdu_size - len, 1, &decoded_type, &decoded_instance); + if (value_len > 0) { + if (data) { data->monitoredObjectIdentifier.type = decoded_type; - } else { - data->error_code = ERROR_CODE_REJECT_INVALID_TAG; - return BACNET_STATUS_REJECT; + data->monitoredObjectIdentifier.instance = decoded_instance; } - /* optional parameters - if missing, means cancellation */ - if ((unsigned)len < apdu_len) { - /* tag 2 - issueConfirmedNotifications - optional */ - if (decode_is_context_tag(&apdu[len], 2)) { - data->cancellationRequest = false; - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - data->issueConfirmedNotifications = - decode_context_boolean(&apdu[len]); - len += len_value; - } else { - data->cancellationRequest = true; + len += value_len; + } else { + if (data) { + data->error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_ERROR; + } + if ((unsigned)len < apdu_size) { + if (data) { + /* does not indicate a cancellation request */ + data->cancellationRequest = false; + } + /* issueConfirmedNotifications [2] BOOLEAN OPTIONAL */ + value_len = bacnet_boolean_context_decode( + &apdu[len], apdu_size - len, 2, &decoded_boolean); + if (value_len > 0) { + if (data) { + data->issueConfirmedNotifications = decoded_boolean; } - /* tag 3 - lifetime - optional */ - if ((unsigned)len < apdu_len) { - if (decode_is_context_tag(&apdu[len], 3)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += - decode_unsigned(&apdu[len], len_value, &unsigned_value); - data->lifetime = unsigned_value; - } else { - data->lifetime = 0; - } - } else { - data->lifetime = 0; + len += value_len; + } else if (value_len == 0) { + /* invalid tag */ + if (data) { + data->issueConfirmedNotifications = false; } } else { + if (data) { + data->error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_ERROR; + } + } else { + /* If both the 'Issue Confirmed Notifications' and + 'Lifetime' parameters are absent, then this shall + indicate a cancellation request. */ + if (data) { data->cancellationRequest = true; } } + if ((unsigned)len < apdu_size) { + /* lifetime [3] Unsigned OPTIONAL */ + value_len = bacnet_unsigned_context_decode( + &apdu[len], apdu_size - len, 3, &decoded_value); + if (value_len > 0) { + if (data) { + data->lifetime = decoded_value; + } + len += value_len; + } else { + if (data) { + data->error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_ERROR; + } + } else { + if (data) { + data->lifetime = 0; + } + } return len; } diff --git a/src/bacnet/cov.h b/src/bacnet/cov.h index 92c450fb..eebb318d 100644 --- a/src/bacnet/cov.h +++ b/src/bacnet/cov.h @@ -67,6 +67,11 @@ typedef struct BACnet_COV_Notification { extern "C" { #endif /* __cplusplus */ + BACNET_STACK_EXPORT + int cov_notify_encode_apdu( + uint8_t *apdu, BACNET_COV_DATA *data); + + BACNET_STACK_EXPORT int ucov_notify_encode_apdu( uint8_t * apdu, unsigned max_apdu_len, diff --git a/src/bacnet/create_object.c b/src/bacnet/create_object.c new file mode 100644 index 00000000..bf50e15e --- /dev/null +++ b/src/bacnet/create_object.c @@ -0,0 +1,435 @@ +/** + * @file + * @brief CreateObject service encode and decode + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include +#include +#include "bacnet/bacapp.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacerror.h" +#include "bacnet/create_object.h" + +/** + * @brief Encode the CreateObject service request + * + * CreateObject-Request ::= SEQUENCE { + * object-specifier [0] CHOICE { + * object-type [0] BACnetObjectType, + * object-identifier [1] BACnetObjectIdentifier + * }, + * list-of-initial-values [1] SEQUENCE OF BACnetPropertyValue OPTIONAL + * } + * + * @param apdu Pointer to the buffer for encoded values + * @param data Pointer to the service data used for encoding values + * + * @return Bytes encoded or zero on error. + */ +int create_object_encode_service_request( + uint8_t *apdu, BACNET_CREATE_OBJECT_DATA *data) +{ + 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] */ + len = encode_opening_tag(apdu, 0); + apdu_len += len; + if (apdu) { + apdu += len; + } + if (data->object_instance >= BACNET_MAX_INSTANCE) { + /* object-type [0] BACnetObjectType */ + len = encode_context_enumerated(apdu, 0, data->object_type); + apdu_len += len; + if (apdu) { + apdu += len; + } + } else { + /* object-identifier [1] BACnetObjectIdentifier */ + len = encode_context_object_id( + apdu, 1, data->object_type, data->object_instance); + apdu_len += len; + if (apdu) { + apdu += len; + } + } + len = encode_closing_tag(apdu, 0); + apdu_len += len; + if (apdu) { + apdu += len; + } + if (data->list_of_initial_values) { + /* 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 = encode_closing_tag(apdu, 1); + apdu_len += len; + } + } + + return apdu_len; +} + +/** + * @brief Decode the CreateObject service request + * + * CreateObject-Request ::= SEQUENCE { + * object-specifier [0] CHOICE { + * object-type [0] BACnetObjectType, + * object-identifier [1] BACnetObjectIdentifier + * }, + * list-of-initial-values [1] SEQUENCE OF BACnetPropertyValue OPTIONAL + * } + * + * @param apdu Pointer to the buffer for decoding. + * @param apdu_len Count of valid bytes in the buffer. + * @param data Pointer to the property decoded data to be stored + * + * @return Bytes decoded or BACNET_STATUS_REJECT on error. + */ +int create_object_decode_service_request( + uint8_t *apdu, uint32_t apdu_size, BACNET_CREATE_OBJECT_DATA *data) +{ + int len = 0; + int apdu_len = 0; + 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; + + /* object-specifier [0] CHOICE */ + if (!bacnet_is_opening_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 0, &len)) { + if (data) { + data->error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_REJECT; + } + apdu_len += len; + /* CHOICE of Tag [0] or [1] */ + /* object-identifier [1] BACnetObjectIdentifier */ + len = bacnet_object_id_context_decode(&apdu[apdu_len], apdu_size - apdu_len, + 1, &object_type, &object_instance); + if ((len != BACNET_STATUS_ERROR) && (len != 0)) { + if ((object_type >= MAX_BACNET_OBJECT_TYPE) || + (object_instance >= BACNET_MAX_INSTANCE)) { + if (data) { + data->error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + if (data) { + data->object_instance = object_instance; + data->object_type = object_type; + } + apdu_len += len; + } else { + /* object-type [0] BACnetObjectType */ + len = bacnet_enumerated_context_decode( + &apdu[apdu_len], apdu_size - apdu_len, 0, &enumerated_value); + if ((len != BACNET_STATUS_ERROR) && (len != 0)) { + if (enumerated_value >= MAX_BACNET_OBJECT_TYPE) { + if (data) { + data->error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + if (data) { + data->object_instance = BACNET_MAX_INSTANCE; + data->object_type = enumerated_value; + } + apdu_len += len; + } else { + if (data) { + data->error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_REJECT; + } + } + if (!bacnet_is_closing_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 0, &len)) { + if (data) { + data->error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_REJECT; + } + 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 == BACNET_STATUS_ERROR) { + if (data) { + data->error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_REJECT; + } + apdu_len += len; + if (!bacnet_is_closing_tag_number( + &apdu[apdu_len], apdu_size - apdu_len, 0, &len)) { + if (data) { + data->error_code = ERROR_CODE_REJECT_INVALID_TAG; + } + return BACNET_STATUS_REJECT; + } + apdu_len += len; + } + + return apdu_len; +} + +/** + * @brief Encode a CreateObject-ACK APDU service data + * + * CreateObject-ACK ::= BACnetObjectIdentifier + * + * @param apdu Pointer to the buffer for encoding, or NULL for length + * @param data Pointer to the property data to be encoded. + * @return number of bytes encoded + */ +int create_object_ack_service_encode( + uint8_t *apdu, BACNET_CREATE_OBJECT_DATA *data) +{ + /* BACnetObjectIdentifier */ + return encode_application_object_id( + apdu, data->object_type, data->object_instance); +} + +/** + * @brief Encode a CreateObject-ACK APDU + * + * CreateObject-ACK ::= BACnetObjectIdentifier + * + * @param apdu Pointer to the buffer for encoding, or NULL for length + * @param invoke_id original invoke id from request + * @param data Pointer to the property data to be encoded. + * @return number of bytes encoded + */ +int create_object_ack_encode( + uint8_t *apdu, uint8_t invoke_id, BACNET_CREATE_OBJECT_DATA *data) +{ + int apdu_len = 3; /* total length of the apdu, return value */ + + if (apdu) { + /* service */ + apdu[0] = PDU_TYPE_COMPLEX_ACK; + /* original invoke id from request */ + apdu[1] = invoke_id; + /* service choice */ + apdu[2] = SERVICE_CONFIRMED_CREATE_OBJECT; + apdu += apdu_len; + } + apdu_len += create_object_ack_service_encode(apdu, data); + + return apdu_len; +} + +/** + * @brief Decoding for CreateObject-ACK APDU service data + * CreateObject-ACK ::= BACnetObjectIdentifier + * + * @param apdu Pointer to the buffer for decoding. + * @param apdu_size size of the buffer for decoding. + * @param data Pointer to the property data to be encoded. + * @return Bytes encoded or BACNET_STATUS_REJECT on error. + */ +int create_object_ack_service_decode( + uint8_t *apdu, uint16_t apdu_size, BACNET_CREATE_OBJECT_DATA *data) +{ + int apdu_len = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + + apdu_len = bacnet_object_id_application_decode( + apdu, apdu_size, &object_type, &object_instance); + if (apdu_len > 0) { + if (data) { + data->object_instance = object_instance; + data->object_type = object_type; + } + } + + return apdu_len; +} + +/** + * @brief Encode a CreateObject-Error ACK APDU + * + * CreateObject-Error ::= SEQUENCE { + * error-type [0] Error, + * first-failed-element-number [1] Unsigned + * } + * + * @param apdu Pointer to the buffer for encoding. + * @param data Pointer to the property data to be encoded. + * @return Bytes encoded or zero on error. + */ +int create_object_error_ack_service_encode( + uint8_t *apdu, BACNET_CREATE_OBJECT_DATA *data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + len = encode_opening_tag(apdu, 0); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_application_enumerated(apdu, data->error_class); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_application_enumerated(apdu, data->error_code); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_closing_tag(apdu, 0); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_context_unsigned(apdu, 1, data->first_failed_element_number); + apdu_len += len; + + return apdu_len; +} + +/** + * @brief Encode an Error acknowledge in the APDU. + * @param apdu [in] The APDU buffer. + * @param invoke_id [in] Invoked service ID. + * @param data [in] Data of the invoked property. + * @return number of bytes encoded + */ +int create_object_error_ack_encode( + uint8_t *apdu, uint8_t invoke_id, BACNET_CREATE_OBJECT_DATA *data) +{ + int len = 3; + + if (apdu) { + apdu[0] = PDU_TYPE_ERROR; + apdu[1] = invoke_id; + apdu[2] = SERVICE_CONFIRMED_CREATE_OBJECT; + apdu += len; + } + len += create_object_error_ack_service_encode(apdu, data); + + return len; +} + +/** + * @brief Decode a CreateObject-Error ACK APDU + * + * CreateObject-Error ::= SEQUENCE { + * error-type [0] Error, + * first-failed-element-number [1] Unsigned + * } + * + * @param apdu Pointer to the buffer for decoding. + * @param apdu_size size of the buffer for decoding. + * @param data Pointer to the property data to be encoded. + * @return Bytes encoded or BACNET_STATUS_REJECT on error. + */ +int create_object_error_ack_service_decode( + uint8_t *apdu, uint16_t apdu_size, BACNET_CREATE_OBJECT_DATA *data) +{ + int len = 0, apdu_len = 0; + BACNET_ERROR_CLASS error_class = ERROR_CLASS_SERVICES; + BACNET_ERROR_CODE error_code = ERROR_CODE_SUCCESS; + BACNET_UNSIGNED_INTEGER first_failed_element_number = 0; + + if (!apdu) { + return BACNET_STATUS_REJECT; + } + if (data) { + data->first_failed_element_number = 0; + data->error_class = ERROR_CLASS_SERVICES; + data->error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + if (apdu_size < apdu_len) { + return BACNET_STATUS_REJECT; + } + /* Opening Context tag 0 - Error */ + if (decode_is_opening_tag_number(apdu, 0)) { + /* opening tag 0 is 1 byte */ + len = 1; + apdu_len += len; + apdu += len; + } else { + return BACNET_STATUS_REJECT; + } + if (apdu_size < apdu_len) { + return BACNET_STATUS_REJECT; + } + len = bacerror_decode_error_class_and_code( + apdu, apdu_size - apdu_len, &error_class, &error_code); + if (len > 0) { + if (data) { + data->error_class = error_class; + data->error_code = error_code; + } + apdu_len += len; + apdu += len; + } else { + return BACNET_STATUS_REJECT; + } + if (apdu_size < apdu_len) { + return BACNET_STATUS_REJECT; + } + /* Closing Context tag 0 - Error */ + if (decode_is_closing_tag_number(apdu, 0)) { + /* closing tag 0 is 1 byte */ + len = 1; + apdu_len += len; + apdu += len; + } else { + return BACNET_STATUS_REJECT; + } + if (apdu_size < apdu_len) { + return BACNET_STATUS_REJECT; + } + len = bacnet_unsigned_context_decode( + apdu, apdu_size - apdu_len, 1, &first_failed_element_number); + if (len > 0) { + if (data) { + data->first_failed_element_number = first_failed_element_number; + } + apdu_len += len; + } else { + return BACNET_STATUS_REJECT; + } + + return apdu_len; +} diff --git a/src/bacnet/create_object.h b/src/bacnet/create_object.h new file mode 100644 index 00000000..16e8511f --- /dev/null +++ b/src/bacnet/create_object.h @@ -0,0 +1,93 @@ +/** + * @file + * @brief API for CreateObject service encode and decode + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_CREATE_OBJECT_H +#define BACNET_CREATE_OBJECT_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacapp.h" + +/** + * CreateObject-Request ::= SEQUENCE { + * object-specifier [0] CHOICE { + * object-type [0] BACnetObjectType, + * object-identifier [1] BACnetObjectIdentifier + * }, + * list-of-initial-values [1] SEQUENCE OF BACnetPropertyValue OPTIONAL + * } + */ +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; + BACNET_UNSIGNED_INTEGER first_failed_element_number; +} BACNET_CREATE_OBJECT_DATA; + +/** + * @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 + */ +typedef uint32_t (*create_object_function)(uint32_t object_instance); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +int create_object_encode_service_request( + uint8_t *apdu, BACNET_CREATE_OBJECT_DATA *data); +BACNET_STACK_EXPORT +int create_object_encode_service_ack_encode( + uint8_t *apdu, BACNET_CREATE_OBJECT_DATA *data); +BACNET_STACK_EXPORT +int create_object_decode_service_request( + uint8_t *apdu, uint32_t apdu_size, BACNET_CREATE_OBJECT_DATA *data); + +BACNET_STACK_EXPORT +int create_object_ack_service_encode( + uint8_t *apdu, BACNET_CREATE_OBJECT_DATA *data); +BACNET_STACK_EXPORT +int create_object_ack_service_decode( + uint8_t *apdu, uint16_t apdu_size, BACNET_CREATE_OBJECT_DATA *data); +BACNET_STACK_EXPORT +int create_object_ack_encode( + uint8_t *apdu, uint8_t invoke_id, BACNET_CREATE_OBJECT_DATA *data); + +BACNET_STACK_EXPORT +int create_object_error_ack_service_encode( + uint8_t *apdu, BACNET_CREATE_OBJECT_DATA *data); +BACNET_STACK_EXPORT +int create_object_error_ack_service_decode( + uint8_t *apdu, uint16_t apdu_size, BACNET_CREATE_OBJECT_DATA *data); +BACNET_STACK_EXPORT +int create_object_error_ack_encode( + uint8_t *apdu, uint8_t invoke_id, BACNET_CREATE_OBJECT_DATA *data); + +BACNET_STACK_EXPORT +int create_object_ack_encode( + uint8_t *apdu, uint8_t invoke_id, + BACNET_CREATE_OBJECT_DATA *data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif \ No newline at end of file diff --git a/src/bacnet/delete_object.c b/src/bacnet/delete_object.c new file mode 100644 index 00000000..13255785 --- /dev/null +++ b/src/bacnet/delete_object.c @@ -0,0 +1,92 @@ +/** + * @file + * @brief DeleteObject service encode and decode + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include +#include +#include "bacnet/bacapp.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacerror.h" +#include "bacnet/delete_object.h" + +/** + * @brief Encode the DeleteObject service request + * + * DeleteObject-Request ::= SEQUENCE { + * object-identifier BACnetObjectIdentifier + * } + * + * @param apdu Pointer to the buffer for encoded values, or NULL for length + * @param data Pointer to the service data used for encoding values + * + * @return Bytes encoded or zero on error. + */ +int delete_object_encode_service_request( + uint8_t *apdu, BACNET_DELETE_OBJECT_DATA *data) +{ + int len = 0; /* length of each encoding */ + int apdu_len = 0; /* total length of the apdu, return value */ + + if (data) { + /* object-identifier BACnetObjectIdentifier */ + len = encode_application_object_id( + apdu, data->object_type, data->object_instance); + apdu_len += len; + } + + return apdu_len; +} + +/** + * @brief Decode the DeleteObject service request + * + * DeleteObject-Request ::= SEQUENCE { + * object-identifier BACnetObjectIdentifier + * } + * + * @param apdu Pointer to the buffer for decoding. + * @param apdu_size Count of valid bytes in the buffer. + * @param data Pointer to the property decoded data to be stored + * + * @return Bytes decoded or BACNET_STATUS_REJECT on error. + */ +int delete_object_decode_service_request( + uint8_t *apdu, uint32_t apdu_size, BACNET_DELETE_OBJECT_DATA *data) +{ + int len = 0; + int apdu_len = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + + /* object-identifier BACnetObjectIdentifier */ + len = bacnet_object_id_application_decode(&apdu[apdu_len], + apdu_size - apdu_len, &object_type, &object_instance); + if (len == BACNET_STATUS_ERROR) { + if (data) { + data->error_code = ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER; + } + return BACNET_STATUS_REJECT; + } else { + if ((object_type >= MAX_BACNET_OBJECT_TYPE) || + (object_instance > BACNET_MAX_INSTANCE)) { + if (data) { + data->error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + } + return BACNET_STATUS_REJECT; + } + if (data) { + data->object_instance = object_instance; + data->object_type = object_type; + } + apdu_len += len; + } + + return apdu_len; +} diff --git a/src/bacnet/delete_object.h b/src/bacnet/delete_object.h new file mode 100644 index 00000000..6e1c8bce --- /dev/null +++ b/src/bacnet/delete_object.h @@ -0,0 +1,56 @@ +/** + * @file + * @brief API for DeleteObject service encode and decode + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#ifndef BACNET_DELETE_OBJECT_H +#define BACNET_DELETE_OBJECT_H + +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacenum.h" +#include "bacnet/bacdcode.h" +#include "bacnet/bacdef.h" +#include "bacnet/bacapp.h" + +/** + * DeleteObject-Request ::= SEQUENCE { + * object-identifier BACnetObjectIdentifier + */ +typedef struct BACnet_Delete_Object_Data { + uint32_t object_instance; + BACNET_OBJECT_TYPE object_type; + /* application layer stores specific abort/reject/error */ + BACNET_ERROR_CLASS error_class; + BACNET_ERROR_CODE error_code; +} BACNET_DELETE_OBJECT_DATA; + +/** + * @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 + */ +typedef bool (*delete_object_function)(uint32_t object_instance); + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +int delete_object_encode_service_request( + uint8_t *apdu, BACNET_DELETE_OBJECT_DATA *data); +BACNET_STACK_EXPORT +int delete_object_decode_service_request( + uint8_t *apdu, uint32_t apdu_size, BACNET_DELETE_OBJECT_DATA *data); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif \ No newline at end of file diff --git a/src/bacnet/event.c b/src/bacnet/event.c index 19189cfd..ae4ea3af 100644 --- a/src/bacnet/event.c +++ b/src/bacnet/event.c @@ -41,178 +41,52 @@ /** @file event.c Encode/Decode Event Notifications */ /** - * Parse the array of complex-event-type notification parameters - * - * @param len - parse position inside APDU - * @param apdu_len - APDU total length + * @brief Decode the array of complex-event-type notification parameters * @param apdu - apdu buffer + * @param apdu_size - APDU total length * @param data - the event data struct to store the results in - * @return new value of "len", or negative on error + * @return number of apdu bytes decoded, or BACNET_STATUS_ERROR on error. */ -static int parse_complex_event_type_values(int len, unsigned apdu_len, uint8_t *apdu, BACNET_EVENT_NOTIFICATION_DATA *data) +static int complex_event_type_values_decode( + uint8_t *apdu, unsigned apdu_size, BACNET_EVENT_NOTIFICATION_DATA *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 - } - */ - - /* TODO this is mostly copied from "cov_notify_decode_service_request" - extract to a common function? */ - - uint8_t tag_number = 0; - uint32_t len_value = 0; - uint32_t property = 0; - BACNET_UNSIGNED_INTEGER decoded_value = 0; - BACNET_APPLICATION_DATA_VALUE *app_data = NULL; - int app_len = 0; + int len = 0; /* return value */ + BACNET_PROPERTY_VALUE *value; + int value_len = 0, tag_len = 0; #if (BACNET_DECODE_COMPLEX_EVENT_TYPE_PARAMETERS == 1) - BACNET_PROPERTY_VALUE *values; - BACNET_PROPERTY_VALUE *value; /* we want to extract the values */ - values = data->notificationParams.complexEventType.values; - bacapp_property_value_list_init(values, BACNET_COMPLEX_EVENT_TYPE_MAX_PARAMETERS); - value = values; - for(;;) { - /* tag 0 - propertyIdentifier */ - if (len >= (int)apdu_len) { - return -1; - } - if (decode_is_context_tag(&apdu[len], 0)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_enumerated(&apdu[len], len_value, &property); - value->propertyIdentifier = (BACNET_PROPERTY_ID)property; - } else { - return -1; - } - /* tag 1 - propertyArrayIndex OPTIONAL */ - if (len >= (int)apdu_len) { - return -1; - } - if (decode_is_context_tag(&apdu[len], 1)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &decoded_value); - value->propertyArrayIndex = decoded_value; - } else { - value->propertyArrayIndex = BACNET_ARRAY_ALL; - } - /* tag 2: opening context tag - value */ - if (len >= (int)apdu_len) { - return -1; - } - if (!decode_is_opening_tag_number(&apdu[len], 2)) { - return -1; - } - /* a tag number of 2 is not extended so only one octet */ - len++; - app_data = &value->value; - while (!decode_is_closing_tag_number(&apdu[len], 2)) { - if (app_data == NULL) { - /* out of room to store more values */ - return -1; - } - app_len = bacapp_decode_application_data( - &apdu[len], apdu_len - len, app_data); - if (app_len < 0) { - return -1; - } - len += app_len; - - app_data = app_data->next; - } - /* a tag number of 2 is not extended so only one octet */ - len++; - /* tag 3 - priority OPTIONAL */ - if (len >= (int)apdu_len) { - return -1; - } - if (decode_is_context_tag(&apdu[len], 3)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &decoded_value); - value->priority = (uint8_t)decoded_value; - } else { - value->priority = BACNET_NO_PRIORITY; - } - /* end of list? */ - if (decode_is_closing_tag_number(&apdu[len], 6)) { - break; - } - value = value->next; - if (value == NULL) { - /* out of room to store more values */ - return BACNET_STATUS_ERROR; - } - } + value = data->notificationParams.complexEventType.values; + bacapp_property_value_list_init( + value, BACNET_COMPLEX_EVENT_TYPE_MAX_PARAMETERS); #else /* we just want to discard the complex values */ BACNET_PROPERTY_VALUE dummyValue; - BACNET_PROPERTY_VALUE *value; - bacapp_property_value_list_init(&dummyValue, 1); value = &dummyValue; - for(;;) { - /* tag 0 - propertyIdentifier */ - if (len >= (int)apdu_len) { - return -1; - } - if (decode_is_context_tag(&apdu[len], 0)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_enumerated(&apdu[len], len_value, &property); +#endif + while (value != NULL) { + value_len = + bacapp_property_value_decode(&apdu[len], apdu_size - len, value); + if (value_len == BACNET_STATUS_ERROR) { + return BACNET_STATUS_ERROR; } else { - return -1; - } - /* tag 1 - propertyArrayIndex OPTIONAL */ - if (len >= (int)apdu_len) { - return -1; - } - if (decode_is_context_tag(&apdu[len], 1)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &decoded_value); - } - /* tag 2: opening context tag - value */ - if (len >= (int)apdu_len) { - return -1; - } - if (!decode_is_opening_tag_number(&apdu[len], 2)) { - return -1; - } - /* a tag number of 2 is not extended so only one octet */ - len++; - while (!decode_is_closing_tag_number(&apdu[len], 2)) { - app_data = &value->value; - app_len = bacapp_decode_application_data( - &apdu[len], apdu_len - len, app_data); - if (app_len < 0) { - return -1; - } - len += app_len; - } - /* a tag number of 2 is not extended so only one octet */ - len++; - /* tag 3 - priority OPTIONAL */ - if (len >= (int)apdu_len) { - return -1; - } - if (decode_is_context_tag(&apdu[len], 3)) { - len += decode_tag_number_and_value( - &apdu[len], &tag_number, &len_value); - len += decode_unsigned(&apdu[len], len_value, &decoded_value); + len += value_len; } /* end of list? */ - if (decode_is_closing_tag_number(&apdu[len], 6)) { + if (bacnet_is_closing_tag_number( + &apdu[len], apdu_size - len, 6, &tag_len)) { + len += tag_len; + value->next = NULL; break; } + /* is there another one to decode? */ + value = value->next; + if (value == NULL) { + /* out of room to store next value */ + return BACNET_STATUS_ERROR; + } } -#endif return len; } @@ -850,10 +724,10 @@ int event_notify_decode_service_request( len += section_length; if (-1 == - (section_length = - decode_context_bitstring(&apdu[len], 1, - &data->notificationParams - .changeOfBitstring.statusFlags))) { + (section_length = decode_context_bitstring( + &apdu[len], 1, + &data->notificationParams.changeOfBitstring + .statusFlags))) { return -1; } len += section_length; @@ -865,8 +739,8 @@ int event_notify_decode_service_request( (section_length = bacapp_decode_context_property_state( &apdu[len], 0, - &data->notificationParams.changeOfState - .newState))) { + &data->notificationParams + .changeOfState.newState))) { return -1; } len += section_length; @@ -874,8 +748,8 @@ int event_notify_decode_service_request( if (-1 == (section_length = decode_context_bitstring(&apdu[len], 1, - &data->notificationParams.changeOfState - .statusFlags))) { + &data->notificationParams + .changeOfState.statusFlags))) { return -1; } len += section_length; @@ -925,8 +799,8 @@ int event_notify_decode_service_request( if (-1 == (section_length = decode_context_bitstring(&apdu[len], 1, - &data->notificationParams.changeOfValue - .statusFlags))) { + &data->notificationParams + .changeOfValue.statusFlags))) { return -1; } len += section_length; @@ -948,8 +822,9 @@ int event_notify_decode_service_request( switch (tag_number) { case BACNET_APPLICATION_TAG_ENUMERATED: if (-1 == - (section_length = decode_enumerated( - &apdu[len], len_value, &enum_value))) { + (section_length = + decode_enumerated(&apdu[len], + len_value, &enum_value))) { return -1; } data->notificationParams.commandFailure @@ -978,10 +853,10 @@ int event_notify_decode_service_request( len++; if (-1 == - (section_length = - decode_context_bitstring(&apdu[len], 1, - &data->notificationParams.commandFailure - .statusFlags))) { + (section_length = decode_context_bitstring( + &apdu[len], 1, + &data->notificationParams.commandFailure + .statusFlags))) { return -1; } len += section_length; @@ -1001,8 +876,9 @@ int event_notify_decode_service_request( switch (tag_number) { case BACNET_APPLICATION_TAG_ENUMERATED: if (-1 == - (section_length = decode_enumerated( - &apdu[len], len_value, &enum_value))) { + (section_length = + decode_enumerated(&apdu[len], + len_value, &enum_value))) { return -1; } data->notificationParams.commandFailure @@ -1034,7 +910,8 @@ int event_notify_decode_service_request( case EVENT_FLOATING_LIMIT: if (-1 == - (section_length = decode_context_real(&apdu[len], 0, + (section_length = decode_context_real( + &apdu[len], 0, &data->notificationParams.floatingLimit .referenceValue))) { return -1; @@ -1044,13 +921,14 @@ int event_notify_decode_service_request( if (-1 == (section_length = decode_context_bitstring(&apdu[len], 1, - &data->notificationParams.floatingLimit - .statusFlags))) { + &data->notificationParams + .floatingLimit.statusFlags))) { return -1; } len += section_length; if (-1 == - (section_length = decode_context_real(&apdu[len], 2, + (section_length = decode_context_real( + &apdu[len], 2, &data->notificationParams.floatingLimit .setPointValue))) { return -1; @@ -1058,9 +936,10 @@ int event_notify_decode_service_request( len += section_length; if (-1 == - (section_length = decode_context_real(&apdu[len], 3, - &data->notificationParams.floatingLimit - .errorLimit))) { + (section_length = + decode_context_real(&apdu[len], 3, + &data->notificationParams + .floatingLimit.errorLimit))) { return -1; } len += section_length; @@ -1068,9 +947,10 @@ int event_notify_decode_service_request( case EVENT_OUT_OF_RANGE: if (-1 == - (section_length = decode_context_real(&apdu[len], 0, - &data->notificationParams.outOfRange - .exceedingValue))) { + (section_length = + decode_context_real(&apdu[len], 0, + &data->notificationParams.outOfRange + .exceedingValue))) { return -1; } len += section_length; @@ -1084,17 +964,19 @@ int event_notify_decode_service_request( } len += section_length; if (-1 == - (section_length = decode_context_real(&apdu[len], 2, - &data->notificationParams.outOfRange - .deadband))) { + (section_length = + decode_context_real(&apdu[len], 2, + &data->notificationParams.outOfRange + .deadband))) { return -1; } len += section_length; if (-1 == - (section_length = decode_context_real(&apdu[len], 3, - &data->notificationParams.outOfRange - .exceededLimit))) { + (section_length = + decode_context_real(&apdu[len], 3, + &data->notificationParams.outOfRange + .exceededLimit))) { return -1; } len += section_length; @@ -1106,7 +988,8 @@ int event_notify_decode_service_request( &apdu[len], 0, &enum_value))) { return -1; } - data->notificationParams.changeOfLifeSafety.newState = + data->notificationParams.changeOfLifeSafety + .newState = (BACNET_LIFE_SAFETY_STATE)enum_value; len += section_length; @@ -1115,15 +998,15 @@ int event_notify_decode_service_request( &apdu[len], 1, &enum_value))) { return -1; } - data->notificationParams.changeOfLifeSafety.newMode = - (BACNET_LIFE_SAFETY_MODE)enum_value; + data->notificationParams.changeOfLifeSafety + .newMode = (BACNET_LIFE_SAFETY_MODE)enum_value; len += section_length; if (-1 == (section_length = decode_context_bitstring( &apdu[len], 2, - &data->notificationParams.changeOfLifeSafety - .statusFlags))) { + &data->notificationParams + .changeOfLifeSafety.statusFlags))) { return -1; } len += section_length; @@ -1145,7 +1028,8 @@ int event_notify_decode_service_request( (section_length = bacapp_decode_context_device_obj_property_ref( &apdu[len], 0, - &data->notificationParams.bufferReady + &data->notificationParams + .bufferReady .bufferProperty))) { return -1; } @@ -1190,7 +1074,8 @@ int event_notify_decode_service_request( len += section_length; if (unsigned_value <= UINT32_MAX) { data->notificationParams.unsignedRange - .exceedingValue = (uint32_t)unsigned_value; + .exceedingValue = + (uint32_t)unsigned_value; } else { return BACNET_STATUS_ERROR; } @@ -1201,8 +1086,8 @@ int event_notify_decode_service_request( if (-1 == (section_length = decode_context_bitstring(&apdu[len], 1, - &data->notificationParams.unsignedRange - .statusFlags))) { + &data->notificationParams + .unsignedRange.statusFlags))) { return -1; } len += section_length; @@ -1213,7 +1098,8 @@ int event_notify_decode_service_request( len += section_length; if (unsigned_value <= UINT32_MAX) { data->notificationParams.unsignedRange - .exceededLimit = (uint32_t)unsigned_value; + .exceededLimit = + (uint32_t)unsigned_value; } else { return BACNET_STATUS_ERROR; } @@ -1234,26 +1120,28 @@ int event_notify_decode_service_request( if (-1 == (section_length = decode_context_bitstring(&apdu[len], 1, - &data->notificationParams.accessEvent - .statusFlags))) { + &data->notificationParams + .accessEvent.statusFlags))) { + return -1; + } + len += section_length; + + if (-1 == + (section_length = decode_context_unsigned( + &apdu[len], 2, + &data->notificationParams.accessEvent + .accessEventTag))) { return -1; } len += section_length; if (-1 == (section_length = - decode_context_unsigned(&apdu[len], 2, - &data->notificationParams.accessEvent - .accessEventTag))) { - return -1; - } - len += section_length; - - if (-1 == - (section_length = bacapp_decode_context_timestamp( - &apdu[len], 3, - &data->notificationParams.accessEvent - .accessEventTime))) { + bacapp_decode_context_timestamp( + &apdu[len], 3, + &data->notificationParams + .accessEvent + .accessEventTime))) { return -1; } len += section_length; @@ -1262,7 +1150,8 @@ int event_notify_decode_service_request( (section_length = bacapp_decode_context_device_obj_ref( &apdu[len], 4, - &data->notificationParams.accessEvent + &data->notificationParams + .accessEvent .accessCredential))) { return -1; } @@ -1290,7 +1179,8 @@ int event_notify_decode_service_request( len++; is_complex_event_type = true; - len = parse_complex_event_type_values(len, apdu_len, apdu, data); + len = complex_event_type_values_decode( + &apdu[len], apdu_len - len, data); if (len < 0) { return -1; } @@ -1298,8 +1188,8 @@ int event_notify_decode_service_request( return -1; } - if (decode_is_closing_tag_number( - &apdu[len], is_complex_event_type ? 6 : (uint8_t)data->eventType)) { + if (decode_is_closing_tag_number(&apdu[len], + is_complex_event_type ? 6 : (uint8_t)data->eventType)) { len++; } else { return -1; diff --git a/src/bacnet/reject.c b/src/bacnet/reject.c index c999685b..79c53b09 100644 --- a/src/bacnet/reject.c +++ b/src/bacnet/reject.c @@ -77,6 +77,9 @@ BACNET_REJECT_REASON reject_convert_error_code(BACNET_ERROR_CODE error_code) case ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE: reject_code = REJECT_REASON_UNRECOGNIZED_SERVICE; break; + case ERROR_CODE_INVALID_DATA_ENCODING: + reject_code = REJECT_REASON_INVALID_DATA_ENCODING; + break; case ERROR_CODE_REJECT_PROPRIETARY: reject_code = REJECT_REASON_PROPRIETARY_FIRST; break; @@ -89,6 +92,38 @@ BACNET_REJECT_REASON reject_convert_error_code(BACNET_ERROR_CODE error_code) return (reject_code); } +/** + * @brief Determine if a BACnetErrorCode is a BACnetRejectReason + * @param error_code #BACNET_ERROR_CODE enumeration + * @return true if the BACnet Error Code is a BACnet abort reason + */ +bool reject_valid_error_code(BACNET_ERROR_CODE error_code) +{ + bool status = false; + + switch (error_code) { + case ERROR_CODE_REJECT_OTHER: + case ERROR_CODE_REJECT_BUFFER_OVERFLOW: + case ERROR_CODE_REJECT_INCONSISTENT_PARAMETERS: + case ERROR_CODE_REJECT_INVALID_PARAMETER_DATA_TYPE: + case ERROR_CODE_REJECT_INVALID_TAG: + case ERROR_CODE_REJECT_MISSING_REQUIRED_PARAMETER: + case ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE: + case ERROR_CODE_REJECT_TOO_MANY_ARGUMENTS: + case ERROR_CODE_REJECT_UNDEFINED_ENUMERATION: + case ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE: + case ERROR_CODE_INVALID_DATA_ENCODING: + case ERROR_CODE_REJECT_PROPRIETARY: + status = true; + break; + default: + break; + } + + return status; +} + + /** * @brief Convert a reject code to BACnet Error code * @param reject_code - code to be converted @@ -100,6 +135,9 @@ BACNET_ERROR_CODE reject_convert_to_error_code(BACNET_REJECT_REASON reject_code) BACNET_ERROR_CODE error_code = ERROR_CODE_REJECT_OTHER; switch (reject_code) { + case REJECT_REASON_OTHER: + error_code = ERROR_CODE_REJECT_OTHER; + break; case REJECT_REASON_BUFFER_OVERFLOW: error_code = ERROR_CODE_REJECT_BUFFER_OVERFLOW; break; @@ -127,8 +165,8 @@ BACNET_ERROR_CODE reject_convert_to_error_code(BACNET_REJECT_REASON reject_code) case REJECT_REASON_UNRECOGNIZED_SERVICE: error_code = ERROR_CODE_REJECT_UNRECOGNIZED_SERVICE; break; - case REJECT_REASON_OTHER: - error_code = ERROR_CODE_REJECT_OTHER; + case REJECT_REASON_INVALID_DATA_ENCODING: + error_code = ERROR_CODE_INVALID_DATA_ENCODING; break; default: if ((reject_code >= REJECT_REASON_PROPRIETARY_FIRST) && diff --git a/src/bacnet/reject.h b/src/bacnet/reject.h index 964db721..9b798a39 100644 --- a/src/bacnet/reject.h +++ b/src/bacnet/reject.h @@ -21,8 +21,8 @@ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *********************************************************************/ -#ifndef REJECT_H -#define REJECT_H +#ifndef BACNET_REJECT_H +#define BACNET_REJECT_H #include #include @@ -37,6 +37,9 @@ extern "C" { BACNET_REJECT_REASON reject_convert_error_code( BACNET_ERROR_CODE error_code); BACNET_STACK_EXPORT + bool reject_valid_error_code( + BACNET_ERROR_CODE error_code); + BACNET_STACK_EXPORT BACNET_ERROR_CODE reject_convert_to_error_code( BACNET_REJECT_REASON reject_code); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4ee71aa9..aa0b7318 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,8 +53,10 @@ list(APPEND testdirs bacnet/bacstr bacnet/bactimevalue bacnet/cov + bacnet/create_object bacnet/datetime bacnet/dcc + bacnet/delete_object bacnet/event bacnet/getalarm bacnet/getevent @@ -62,6 +64,7 @@ list(APPEND testdirs bacnet/ihave bacnet/indtext bacnet/lighting + bacnet/list_element bacnet/lso bacnet/memcopy bacnet/npdu diff --git a/test/bacnet/bacapp/CMakeLists.txt b/test/bacnet/bacapp/CMakeLists.txt index 49012ddf..6244aae0 100644 --- a/test/bacnet/bacapp/CMakeLists.txt +++ b/test/bacnet/bacapp/CMakeLists.txt @@ -7,7 +7,6 @@ project(test_${basename} VERSION 1.0.0 LANGUAGES C) - string(REGEX REPLACE "/test/bacnet/[a-zA-Z_/-]*$" "/src" @@ -25,6 +24,7 @@ add_compile_definitions( CONFIG_ZTEST=1 PRINT_ENABLED=1 BACAPP_ALL=1 + BACAPP_PRINT_ENABLED=1 ) include_directories( diff --git a/test/bacnet/bacapp/src/main.c b/test/bacnet/bacapp/src/main.c index 5ae4abfa..047caf84 100644 --- a/test/bacnet/bacapp/src/main.c +++ b/test/bacnet/bacapp/src/main.c @@ -17,46 +17,46 @@ static const BACNET_APPLICATION_TAG tag_list[] = { BACNET_APPLICATION_TAG_NULL, - #if defined(BACAPP_BOOLEAN) +#if defined(BACAPP_BOOLEAN) BACNET_APPLICATION_TAG_BOOLEAN, - #endif - #if defined(BACAPP_UNSIGNED) +#endif +#if defined(BACAPP_UNSIGNED) BACNET_APPLICATION_TAG_UNSIGNED_INT, - #endif - #if defined(BACAPP_SIGNED) +#endif +#if defined(BACAPP_SIGNED) BACNET_APPLICATION_TAG_SIGNED_INT, - #endif - #if defined(BACAPP_REAL) +#endif +#if defined(BACAPP_REAL) BACNET_APPLICATION_TAG_REAL, - #endif - #if defined(BACAPP_DOUBLE) +#endif +#if defined(BACAPP_DOUBLE) BACNET_APPLICATION_TAG_DOUBLE, - #endif - #if defined(BACAPP_OCTET_STRING) +#endif +#if defined(BACAPP_OCTET_STRING) BACNET_APPLICATION_TAG_OCTET_STRING, - #endif - #if defined(BACAPP_CHARACTER_STRING) +#endif +#if defined(BACAPP_CHARACTER_STRING) BACNET_APPLICATION_TAG_CHARACTER_STRING, - #endif - #if defined(BACAPP_BIT_STRING) +#endif +#if defined(BACAPP_BIT_STRING) BACNET_APPLICATION_TAG_BIT_STRING, - #endif - #if defined(BACAPP_ENUMERATED) +#endif +#if defined(BACAPP_ENUMERATED) BACNET_APPLICATION_TAG_ENUMERATED, - #endif - #if defined(BACAPP_DATE) +#endif +#if defined(BACAPP_DATE) BACNET_APPLICATION_TAG_DATE, - #endif - #if defined(BACAPP_TIME) +#endif +#if defined(BACAPP_TIME) BACNET_APPLICATION_TAG_TIME, - #endif - #if defined(BACAPP_OBJECT_ID) +#endif +#if defined(BACAPP_OBJECT_ID) BACNET_APPLICATION_TAG_OBJECT_ID, - #endif - #if defined(BACAPP_TYPES_EXTRA) +#endif +#if defined(BACAPP_TYPES_EXTRA) BACNET_APPLICATION_TAG_LIGHTING_COMMAND, BACNET_APPLICATION_TAG_HOST_N_PORT, - #endif +#endif }; /** @@ -71,15 +71,16 @@ static void test_bacapp_decode_application_data(void) #endif { uint8_t apdu[128] = { 0 }; - //unsigned max_apdu_len = sizeof(apdu); + // unsigned max_apdu_len = sizeof(apdu); BACNET_APPLICATION_DATA_VALUE value = { 0 }; - zassert_equal(bacapp_decode_application_data(NULL, sizeof(apdu), &value), 0, NULL); + zassert_equal( + bacapp_decode_application_data(NULL, sizeof(apdu), &value), 0, NULL); zassert_equal(bacapp_decode_application_data(apdu, 0, &value), 0, NULL); - zassert_equal(bacapp_decode_application_data(apdu, sizeof(apdu), NULL), 0, NULL); + zassert_equal( + bacapp_decode_application_data(apdu, sizeof(apdu), NULL), 0, NULL); } - #if defined(CONFIG_ZTEST_NEW_API) ZTEST(bacapp_tests, test_bacapp_decode_data_len) #else @@ -90,58 +91,89 @@ static void test_bacapp_decode_data_len(void) uint32_t len_value_type = 0; int expected_value = 0; - zassert_equal(bacapp_decode_data_len(NULL, BACNET_APPLICATION_TAG_NULL, sizeof(apdu)), 0, NULL); - zassert_equal(bacapp_decode_data_len(apdu, UINT8_MAX, sizeof(apdu)), 0, NULL); + zassert_equal( + bacapp_decode_data_len(NULL, BACNET_APPLICATION_TAG_NULL, sizeof(apdu)), + 0, NULL); + zassert_equal( + bacapp_decode_data_len(apdu, UINT8_MAX, sizeof(apdu)), 0, NULL); - expected_value = (int) (~0U >> 1); /* INT_MAX is not universally defined */ - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_UNSIGNED_INT, UINT32_MAX), expected_value, NULL); + expected_value = (int)(~0U >> 1); /* INT_MAX is not universally defined */ + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_UNSIGNED_INT, UINT32_MAX), + expected_value, NULL); - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_NULL, sizeof(apdu)), 0, NULL); - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_BOOLEAN, sizeof(apdu)), 0, NULL); + zassert_equal( + bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_NULL, sizeof(apdu)), + 0, NULL); + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_BOOLEAN, sizeof(apdu)), + 0, NULL); len_value_type = INT32_MAX - 1; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_UNSIGNED_INT, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len(apdu, + BACNET_APPLICATION_TAG_UNSIGNED_INT, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 2; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_SIGNED_INT, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_SIGNED_INT, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 5; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_REAL, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_REAL, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 9; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_DOUBLE, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_DOUBLE, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 13; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_OCTET_STRING, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len(apdu, + BACNET_APPLICATION_TAG_OCTET_STRING, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 17; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_CHARACTER_STRING, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len(apdu, + BACNET_APPLICATION_TAG_CHARACTER_STRING, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 19; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_BIT_STRING, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_BIT_STRING, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 23; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_ENUMERATED, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_ENUMERATED, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 29; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_DATE, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_DATE, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 31; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_TIME, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_TIME, len_value_type), + expected_value, NULL); len_value_type = INT32_MAX - 37; - expected_value = (int) len_value_type; - zassert_equal(bacapp_decode_data_len(apdu, BACNET_APPLICATION_TAG_OBJECT_ID, len_value_type), expected_value, NULL); + expected_value = (int)len_value_type; + zassert_equal(bacapp_decode_data_len( + apdu, BACNET_APPLICATION_TAG_OBJECT_ID, len_value_type), + expected_value, NULL); } #if defined(CONFIG_ZTEST_NEW_API) @@ -164,16 +196,16 @@ static void test_bacapp_copy(void) zassert_equal(dest_value.tag, src_value.tag, NULL); zassert_equal(dest_value.next, src_value.next, NULL); - for (i = 0; i < sizeof(tag_list)/sizeof(tag_list[0]); ++i) { + for (i = 0; i < sizeof(tag_list) / sizeof(tag_list[0]); ++i) { BACNET_APPLICATION_TAG tag = tag_list[i]; bool result; bool expected_result = true; - #if ! defined(BACAPP_NULL) +#if !defined(BACAPP_NULL) if (tag == BACNET_APPLICATION_TAG_NULL) { expected_result = false; } - #endif +#endif memset(&src_value, 0, sizeof(src_value)); src_value.next = NULL; @@ -210,7 +242,8 @@ static void test_bacapp_value_list_init(void) bacapp_value_list_init(&value[0], 0); zassert_equal(memcmp(&value[0], &value[1], sizeof(value[1])), 0, NULL); /* Verify one structure is initialized correctly */ - for (max_count = 1; max_count < sizeof(value)/sizeof(value[0]); ++max_count) { + for (max_count = 1; max_count < sizeof(value) / sizeof(value[0]); + ++max_count) { memset(value, 0, sizeof(value)); max_count = 1; bacapp_value_list_init(&value[0], max_count); @@ -219,20 +252,24 @@ static void test_bacapp_value_list_init(void) zassert_equal(value[count].tag, BACNET_APPLICATION_TAG_NULL, NULL); zassert_equal(value[count].context_specific, 0, NULL); zassert_equal(value[count].context_tag, 0, NULL); - zassert_equal(value[count].next, ((count + 1 >= max_count) ? NULL : &value[count + 1]), NULL); + zassert_equal(value[count].next, + ((count + 1 >= max_count) ? NULL : &value[count + 1]), NULL); } } } #if defined(CONFIG_ZTEST_NEW_API) -ZTEST(bacapp_tests, test_bacapp_property_value_list_init) +ZTEST(bacapp_tests, test_bacapp_property_value_list) #else -static void test_bacapp_property_value_list_init(void) +static void test_bacapp_property_value_list(void) #endif { BACNET_PROPERTY_VALUE value[2] = { { 0 } }; size_t max_count = 0; size_t count = 0; + int len, test_len; + uint8_t apdu[480]; + bool status; /* Verify NULL ptr is properly handled */ bacapp_property_value_list_init(NULL, 1); @@ -243,16 +280,50 @@ static void test_bacapp_property_value_list_init(void) zassert_equal(memcmp(&value[0], &value[1], sizeof(value[1])), 0, NULL); /* Verify one structure is initialized correctly */ - for (max_count = 1; max_count < sizeof(value)/sizeof(value[0]); ++max_count) { + for (max_count = 1; max_count < ARRAY_SIZE(value); ++max_count) { memset(value, 0, sizeof(value)); max_count = 1; bacapp_property_value_list_init(&value[0], max_count); for (count = 0; count < max_count; ++count) { - zassert_equal(value[count].propertyIdentifier, MAX_BACNET_PROPERTY_ID, NULL); - zassert_equal(value[count].propertyArrayIndex, BACNET_ARRAY_ALL, NULL); + zassert_equal( + value[count].propertyIdentifier, MAX_BACNET_PROPERTY_ID, NULL); + zassert_equal( + value[count].propertyArrayIndex, BACNET_ARRAY_ALL, NULL); zassert_equal(value[count].priority, BACNET_NO_PRIORITY, NULL); - zassert_equal(value[count].next, ((count + 1 >= max_count) ? NULL : &value[count + 1]), NULL); + zassert_equal(value[count].next, + ((count + 1 >= max_count) ? NULL : &value[count + 1]), NULL); + } + } + bacapp_property_value_list_link(value, ARRAY_SIZE(value)); + value[0].propertyIdentifier = 1; + value[0].propertyArrayIndex = 1; + value[0].priority = 1; + status = bacapp_parse_application_data( + BACNET_APPLICATION_TAG_UNSIGNED_INT, "1", &value[0].value); + zassert_true(status, NULL); + test_len = bacapp_property_value_encode(NULL, &value[0]); + zassert_true(test_len > 0, NULL); + len = bacapp_property_value_encode(apdu, &value[0]); + zassert_true(len > 0, NULL); + test_len = bacapp_property_value_decode(apdu, sizeof(apdu), &value[1]); + zassert_equal(len, test_len, "len=%d test_len=%d", len, test_len); + test_len = bacapp_property_value_decode(apdu, sizeof(apdu), NULL); + zassert_equal(len, test_len, "len=%d test_len=%d", len, test_len); + while (len) { + len--; + test_len = bacapp_property_value_decode(apdu, len, &value[1]); + if (test_len != BACNET_STATUS_ERROR) { + /* shorter packet leaves off the OPTIONAL priority */ + zassert_equal(len, test_len, "len=%d test_len=%d", len, test_len); + zassert_equal(value[1].priority, BACNET_NO_PRIORITY, "priority=%u", + (unsigned)value[1].priority); + } else { + zassert_equal(test_len, BACNET_STATUS_ERROR, "len=%d test_len=%d", + len, test_len); + test_len = bacapp_property_value_decode(apdu, len, NULL); + zassert_equal(test_len, BACNET_STATUS_ERROR, "len=%d test_len=%d", + len, test_len); } } } @@ -280,7 +351,6 @@ static void test_bacapp_same_value(void) zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - test_value.tag = BACNET_APPLICATION_TAG_BOOLEAN; value.tag = test_value.tag; #if defined(BACAPP_BOOLEAN) @@ -291,10 +361,9 @@ static void test_bacapp_same_value(void) value.type.Boolean = !test_value.type.Boolean; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_UNSIGNED_INT; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_UNSIGNED) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else @@ -303,10 +372,9 @@ static void test_bacapp_same_value(void) value.type.Unsigned_Int = ~test_value.type.Unsigned_Int; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_SIGNED_INT; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_SIGNED) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else @@ -315,10 +383,9 @@ static void test_bacapp_same_value(void) value.type.Signed_Int = test_value.type.Signed_Int + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_REAL; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_REAL) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else @@ -327,10 +394,9 @@ static void test_bacapp_same_value(void) value.type.Real = test_value.type.Real + 1.0f; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_DOUBLE; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_DOUBLE) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else @@ -339,10 +405,9 @@ static void test_bacapp_same_value(void) value.type.Double = test_value.type.Double + 1.0; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_ENUMERATED; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_ENUMERATED) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else @@ -351,17 +416,16 @@ static void test_bacapp_same_value(void) value.type.Enumerated = test_value.type.Enumerated + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_DATE; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_DATE) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Date.day = test_value.type.Date.day + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); @@ -371,104 +435,97 @@ static void test_bacapp_same_value(void) zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Date.month = test_value.type.Date.month + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Date.year = test_value.type.Date.year + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_TIME; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_TIME) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Time.hour = test_value.type.Time.hour + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Time.min = test_value.type.Time.min + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Time.sec = test_value.type.Time.sec + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Time.hundredths = test_value.type.Time.hundredths + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_OBJECT_ID; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_OBJECT_ID) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Object_Id.type = test_value.type.Object_Id.type + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ value.type.Object_Id.instance = test_value.type.Object_Id.instance + 1; zassert_false(bacapp_same_value(&value, &test_value), NULL); - memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_CHARACTER_STRING; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_CHARACTER_STRING) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - //TODO: Verify .type.Character_String value compared - + // TODO: Verify .type.Character_String value compared memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_OCTET_STRING; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_OCTET_STRING) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - //TODO: Verify .type.Octet_String value compared - + // TODO: Verify .type.Octet_String value compared memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_BIT_STRING; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_BIT_STRING) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - //TODO: Verify .type.Bit_String value compared - + // TODO: Verify .type.Bit_String value compared memset(&test_value, 0, sizeof(test_value)); test_value.tag = BACNET_APPLICATION_TAG_LIGHTING_COMMAND; - value = test_value; /* Struct copy */ + value = test_value; /* Struct copy */ #if defined(BACAPP_TYPES_EXTRA) zassert_true(bacapp_same_value(&value, &test_value), NULL); #else zassert_false(bacapp_same_value(&value, &test_value), NULL); #endif - //TODO: Verify .type.Lighting_Command value compared + // TODO: Verify .type.Lighting_Command value compared } - /** * @brief Test */ @@ -759,7 +816,8 @@ static void testBACnetApplicationDataLength(void) /** * @brief Test */ -static bool verifyBACnetApplicationDataValue(BACNET_APPLICATION_DATA_VALUE *value) +static bool verifyBACnetApplicationDataValue( + BACNET_APPLICATION_DATA_VALUE *value) { uint8_t apdu[480] = { 0 }; int apdu_len = 0; @@ -780,8 +838,7 @@ static bool verifyBACnetApplicationDataValue(BACNET_APPLICATION_DATA_VALUE *valu * @brief Test */ static bool verifyBACnetComplexDataValue( - BACNET_APPLICATION_DATA_VALUE *value, - BACNET_PROPERTY_ID prop) + BACNET_APPLICATION_DATA_VALUE *value, BACNET_PROPERTY_ID prop) { uint8_t apdu[480] = { 0 }; int apdu_len = 0; @@ -792,8 +849,8 @@ static bool verifyBACnetComplexDataValue( zassert_true(apdu_len > 0, NULL); null_len = bacapp_encode_application_data(NULL, value); zassert_equal(apdu_len, null_len, NULL); - apdu_len = bacapp_decode_generic_property(&apdu[0], apdu_len, - &test_value, prop); + apdu_len = + bacapp_decode_generic_property(&apdu[0], apdu_len, &test_value, prop); zassert_true(apdu_len != BACNET_STATUS_ERROR, NULL); return bacapp_same_value(value, &test_value); @@ -1053,8 +1110,8 @@ static void testBACnetApplicationData(void) BACNET_APPLICATION_TAG_HOST_N_PORT, "192.168.1.1:47808", &value); zassert_true(status, NULL); status = verifyBACnetComplexDataValue(&value, PROP_FD_BBMD_ADDRESS); - status = verifyBACnetComplexDataValue(&value, - PROP_BACNET_IP_GLOBAL_ADDRESS); + status = + verifyBACnetComplexDataValue(&value, PROP_BACNET_IP_GLOBAL_ADDRESS); return; } @@ -1074,16 +1131,16 @@ static void test_bacapp_context_data(void) int apdu_len, null_len; unsigned i = 0; - for (i = 0; i < sizeof(tag_list)/sizeof(tag_list[0]); i++) { + for (i = 0; i < sizeof(tag_list) / sizeof(tag_list[0]); i++) { BACNET_APPLICATION_TAG tag = tag_list[i]; value.tag = tag; - null_len = bacapp_encode_context_data_value(NULL, - context_tag_number, &value); - apdu_len = bacapp_encode_context_data_value(apdu, - context_tag_number, &value); + null_len = + bacapp_encode_context_data_value(NULL, context_tag_number, &value); + apdu_len = + bacapp_encode_context_data_value(apdu, context_tag_number, &value); if (apdu_len != null_len) { - printf("bacapp: NULL len=%d != APDU len=%d for tag=%s", - null_len, apdu_len, bactext_application_tag_name(tag)); + printf("bacapp: NULL len=%d != APDU len=%d for tag=%s", null_len, + apdu_len, bactext_application_tag_name(tag)); } zassert_equal(apdu_len, null_len, NULL); } @@ -1114,8 +1171,8 @@ static void test_bacapp_sprintf_data(void) zassert_true(status, NULL); str_len = bacapp_snprintf_value(NULL, 0, &object_value); if (str_len > 0) { - char str[str_len+1]; - bacapp_snprintf_value(str, str_len+1, &object_value); + char str[str_len + 1]; + bacapp_snprintf_value(str, str_len + 1, &object_value); zassert_mem_equal(str, "Null", str_len, NULL); } } @@ -1124,25 +1181,23 @@ static void test_bacapp_sprintf_data(void) * @} */ - #if defined(CONFIG_ZTEST_NEW_API) ZTEST_SUITE(bacapp_tests, NULL, NULL, NULL, NULL, NULL); #else void test_main(void) { ztest_test_suite(bacapp_tests, - ztest_unit_test(test_bacapp_decode_application_data), - ztest_unit_test(test_bacapp_decode_data_len), - ztest_unit_test(test_bacapp_copy), - ztest_unit_test(test_bacapp_value_list_init), - ztest_unit_test(test_bacapp_property_value_list_init), - ztest_unit_test(test_bacapp_same_value), - ztest_unit_test(testBACnetApplicationData), - ztest_unit_test(testBACnetApplicationDataLength), - ztest_unit_test(testBACnetApplicationData_Safe), - ztest_unit_test(test_bacapp_context_data), - ztest_unit_test(test_bacapp_sprintf_data) - ); + ztest_unit_test(test_bacapp_decode_application_data), + ztest_unit_test(test_bacapp_decode_data_len), + ztest_unit_test(test_bacapp_copy), + ztest_unit_test(test_bacapp_value_list_init), + ztest_unit_test(test_bacapp_property_value_list), + ztest_unit_test(test_bacapp_same_value), + ztest_unit_test(testBACnetApplicationData), + ztest_unit_test(testBACnetApplicationDataLength), + ztest_unit_test(testBACnetApplicationData_Safe), + ztest_unit_test(test_bacapp_context_data), + ztest_unit_test(test_bacapp_sprintf_data)); ztest_run_test_suite(bacapp_tests); } diff --git a/test/bacnet/basic/object/lc/src/main.c b/test/bacnet/basic/object/lc/src/main.c index ae6dd5f7..ae3f0616 100644 --- a/test/bacnet/basic/object/lc/src/main.c +++ b/test/bacnet/basic/object/lc/src/main.c @@ -27,177 +27,6 @@ * @{ */ -#if 0 -/* Mocks */ - -void bacapp_value_list_init( - BACNET_APPLICATION_DATA_VALUE *value, - size_t count) -{ -} - -void bacapp_property_value_list_init( - BACNET_PROPERTY_VALUE *value, - size_t count) -{ -} - - -int bacapp_encode_data( - uint8_t * apdu, - BACNET_APPLICATION_DATA_VALUE * value) -{ - return -1; -} - -int bacapp_decode_data( - uint8_t * apdu, - uint8_t tag_data_type, - uint32_t len_value_type, - BACNET_APPLICATION_DATA_VALUE * value) -{ - return -1; -} - - -int bacapp_decode_application_data( - uint8_t * apdu, - unsigned max_apdu_len, - BACNET_APPLICATION_DATA_VALUE * value) -{ - return -1; -} - - -bool bacapp_decode_application_data_safe( - uint8_t * new_apdu, - uint32_t new_apdu_len, - BACNET_APPLICATION_DATA_VALUE * value) -{ - return false; -} - - -int bacapp_encode_application_data( - uint8_t * apdu, - BACNET_APPLICATION_DATA_VALUE * value) -{ - return -1; -} - - -int bacapp_decode_context_data( - uint8_t * apdu, - unsigned max_apdu_len, - BACNET_APPLICATION_DATA_VALUE * value, - BACNET_PROPERTY_ID property) -{ - return -1; -} - - -int bacapp_encode_context_data( - uint8_t * apdu, - BACNET_APPLICATION_DATA_VALUE * value, - BACNET_PROPERTY_ID property) -{ - return -1; -} - - -int bacapp_encode_context_data_value( - uint8_t * apdu, - uint8_t context_tag_number, - BACNET_APPLICATION_DATA_VALUE * value) -{ - return -1; -} - - -BACNET_APPLICATION_TAG bacapp_context_tag_type( - BACNET_PROPERTY_ID property, - uint8_t tag_number) -{ - return MAX_BACNET_APPLICATION_TAG; -} - - -bool bacapp_copy( - BACNET_APPLICATION_DATA_VALUE * dest_value, - BACNET_APPLICATION_DATA_VALUE * src_value) -{ - return false; -} - - -int bacapp_data_len( - uint8_t * apdu, - unsigned max_apdu_len, - BACNET_PROPERTY_ID property) -{ - return -1; -} - -int bacapp_decode_data_len( - uint8_t * apdu, - uint8_t tag_data_type, - uint32_t len_value_type) -{ - return -1; -} - -int bacapp_decode_application_data_len( - uint8_t * apdu, - unsigned max_apdu_len) -{ - return -1; -} - -int bacapp_decode_context_data_len( - uint8_t * apdu, - unsigned max_apdu_len, - BACNET_PROPERTY_ID property) -{ - return -1; -} - -int bacapp_snprintf_value( - char *str, - size_t str_len, - BACNET_OBJECT_PROPERTY_VALUE * object_value) -{ - return -1; -} - - -#ifdef BACAPP_PRINT_ENABLED -bool bacapp_parse_application_data( - BACNET_APPLICATION_TAG tag_number, - const char *argv, - BACNET_APPLICATION_DATA_VALUE * value) -{ - return false; -} - -bool bacapp_print_value( - FILE * stream, - BACNET_OBJECT_PROPERTY_VALUE * value) -{ - return false; -} - -#endif - -bool bacapp_same_value( - BACNET_APPLICATION_DATA_VALUE * value, - BACNET_APPLICATION_DATA_VALUE * test_value) -{ - return false; -} -#endif - - - /** * @brief Test */ diff --git a/test/bacnet/cov/src/main.c b/test/bacnet/cov/src/main.c index 2810ea04..c01059e6 100644 --- a/test/bacnet/cov/src/main.c +++ b/test/bacnet/cov/src/main.c @@ -9,7 +9,7 @@ */ #include -//#include +// #include #include /** @@ -127,24 +127,19 @@ static int cov_subscribe_property_decode_apdu(uint8_t *apdu, } /* dummy function stubs */ -static void testCOVNotifyData( - BACNET_COV_DATA *data, BACNET_COV_DATA *test_data) +static void testCOVNotifyData(BACNET_COV_DATA *data, BACNET_COV_DATA *test_data) { BACNET_PROPERTY_VALUE *value = NULL; BACNET_PROPERTY_VALUE *test_value = NULL; - zassert_equal( - test_data->subscriberProcessIdentifier, - data->subscriberProcessIdentifier, NULL); - zassert_equal( - test_data->initiatingDeviceIdentifier, - data->initiatingDeviceIdentifier, NULL); - zassert_equal( - test_data->monitoredObjectIdentifier.type, - data->monitoredObjectIdentifier.type, NULL); - zassert_equal( - test_data->monitoredObjectIdentifier.instance, - data->monitoredObjectIdentifier.instance, NULL); + zassert_equal(test_data->subscriberProcessIdentifier, + data->subscriberProcessIdentifier, NULL); + zassert_equal(test_data->initiatingDeviceIdentifier, + data->initiatingDeviceIdentifier, NULL); + zassert_equal(test_data->monitoredObjectIdentifier.type, + data->monitoredObjectIdentifier.type, NULL); + zassert_equal(test_data->monitoredObjectIdentifier.instance, + data->monitoredObjectIdentifier.instance, NULL); zassert_equal(test_data->timeRemaining, data->timeRemaining, NULL); /* test the listOfValues in some clever manner */ value = data->listOfValues; @@ -152,10 +147,12 @@ static void testCOVNotifyData( while (value) { zassert_not_null(test_value, NULL); if (test_value) { - zassert_equal( - test_value->propertyIdentifier, value->propertyIdentifier, NULL); - zassert_equal( - test_value->propertyArrayIndex, value->propertyArrayIndex, NULL); + zassert_equal(test_value->propertyIdentifier, + value->propertyIdentifier, "property=%u test_property=%u", + (unsigned)value->propertyIdentifier, + (unsigned)test_value->propertyIdentifier); + zassert_equal(test_value->propertyArrayIndex, + value->propertyArrayIndex, NULL); zassert_equal(test_value->priority, value->priority, NULL); zassert_true( bacapp_same_value(&test_value->value, &value->value), NULL); @@ -170,14 +167,15 @@ static void testUCOVNotifyData(BACNET_COV_DATA *data) uint8_t apdu[480] = { 0 }; int len = 0; int apdu_len = 0; - BACNET_COV_DATA test_data; + BACNET_COV_DATA test_data = { 0 }; BACNET_PROPERTY_VALUE value_list[5] = { { 0 } }; len = ucov_notify_encode_apdu(&apdu[0], sizeof(apdu), data); zassert_true(len > 0, NULL); apdu_len = len; - cov_data_value_list_link(&test_data, &value_list[0], 5); + cov_data_value_list_link( + &test_data, &value_list[0], ARRAY_SIZE(value_list)); len = ucov_notify_decode_apdu(&apdu[0], apdu_len, &test_data); zassert_not_equal(len, -1, NULL); testCOVNotifyData(data, &test_data); @@ -188,7 +186,7 @@ static void testCCOVNotifyData(uint8_t invoke_id, BACNET_COV_DATA *data) uint8_t apdu[480] = { 0 }; int len = 0; int apdu_len = 0; - BACNET_COV_DATA test_data; + BACNET_COV_DATA test_data = { 0 }; BACNET_PROPERTY_VALUE value_list[2] = { { 0 } }; uint8_t test_invoke_id = 0; @@ -239,42 +237,36 @@ static void testCOVNotify(void) } static void testCOVSubscribeData( - BACNET_SUBSCRIBE_COV_DATA *data, - BACNET_SUBSCRIBE_COV_DATA *test_data) + BACNET_SUBSCRIBE_COV_DATA *data, BACNET_SUBSCRIBE_COV_DATA *test_data) { + zassert_equal(test_data->subscriberProcessIdentifier, + data->subscriberProcessIdentifier, NULL); + zassert_equal(test_data->monitoredObjectIdentifier.type, + data->monitoredObjectIdentifier.type, NULL); + zassert_equal(test_data->monitoredObjectIdentifier.instance, + data->monitoredObjectIdentifier.instance, NULL); zassert_equal( - test_data->subscriberProcessIdentifier, - data->subscriberProcessIdentifier, NULL); - zassert_equal( - test_data->monitoredObjectIdentifier.type, - data->monitoredObjectIdentifier.type, NULL); - zassert_equal( - test_data->monitoredObjectIdentifier.instance, - data->monitoredObjectIdentifier.instance, NULL); - zassert_equal(test_data->cancellationRequest, data->cancellationRequest, NULL); + test_data->cancellationRequest, data->cancellationRequest, NULL); if (test_data->cancellationRequest != data->cancellationRequest) { printf("cancellation request failed!\n"); } if (!test_data->cancellationRequest) { - zassert_equal( - test_data->issueConfirmedNotifications, - data->issueConfirmedNotifications, NULL); + zassert_equal(test_data->issueConfirmedNotifications, + data->issueConfirmedNotifications, NULL); zassert_equal(test_data->lifetime, data->lifetime, NULL); } } static void testCOVSubscribePropertyData( - BACNET_SUBSCRIBE_COV_DATA *data, - BACNET_SUBSCRIBE_COV_DATA *test_data) + BACNET_SUBSCRIBE_COV_DATA *data, BACNET_SUBSCRIBE_COV_DATA *test_data) { testCOVSubscribeData(data, test_data); + zassert_equal(test_data->monitoredProperty.propertyIdentifier, + data->monitoredProperty.propertyIdentifier, NULL); + zassert_equal(test_data->monitoredProperty.propertyArrayIndex, + data->monitoredProperty.propertyArrayIndex, NULL); zassert_equal( - test_data->monitoredProperty.propertyIdentifier, - data->monitoredProperty.propertyIdentifier, NULL); - zassert_equal( - test_data->monitoredProperty.propertyArrayIndex, - data->monitoredProperty.propertyArrayIndex, NULL); - zassert_equal(test_data->covIncrementPresent, data->covIncrementPresent, NULL); + test_data->covIncrementPresent, data->covIncrementPresent, NULL); if (test_data->covIncrementPresent) { zassert_equal(test_data->covIncrement, data->covIncrement, NULL); } @@ -286,7 +278,7 @@ static void testCOVSubscribeEncoding( uint8_t apdu[480] = { 0 }; int len = 0; int apdu_len = 0; - BACNET_SUBSCRIBE_COV_DATA test_data; + BACNET_SUBSCRIBE_COV_DATA test_data = { 0 }; uint8_t test_invoke_id = 0; len = cov_subscribe_encode_apdu(&apdu[0], sizeof(apdu), invoke_id, data); @@ -328,7 +320,7 @@ static void testCOVSubscribe(void) #endif { uint8_t invoke_id = 12; - BACNET_SUBSCRIBE_COV_DATA data; + BACNET_SUBSCRIBE_COV_DATA data = { 0 }; data.subscriberProcessIdentifier = 1; data.monitoredObjectIdentifier.type = OBJECT_ANALOG_INPUT; @@ -375,17 +367,14 @@ static void testCOVSubscribeProperty(void) * @} */ - #if defined(CONFIG_ZTEST_NEW_API) ZTEST_SUITE(cov_tests, NULL, NULL, NULL, NULL, NULL); #else void test_main(void) { - ztest_test_suite(cov_tests, - ztest_unit_test(testCOVNotify), - ztest_unit_test(testCOVSubscribe), - ztest_unit_test(testCOVSubscribeProperty) - ); + ztest_test_suite(cov_tests, ztest_unit_test(testCOVNotify), + ztest_unit_test(testCOVSubscribe), + ztest_unit_test(testCOVSubscribeProperty)); ztest_run_test_suite(cov_tests); } diff --git a/test/bacnet/create_object/CMakeLists.txt b/test/bacnet/create_object/CMakeLists.txt new file mode 100644 index 00000000..828305f6 --- /dev/null +++ b/test/bacnet/create_object/CMakeLists.txt @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/create_object.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacaddr.c + ${SRC_DIR}/bacnet/bacapp.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacdest.c + ${SRC_DIR}/bacnet/bacdevobjpropref.c + ${SRC_DIR}/bacnet/bacerror.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/memcopy.c + ${SRC_DIR}/bacnet/weeklyschedule.c + ${SRC_DIR}/bacnet/bactimevalue.c + ${SRC_DIR}/bacnet/dailyschedule.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/create_object/src/main.c b/test/bacnet/create_object/src/main.c new file mode 100644 index 00000000..3214d539 --- /dev/null +++ b/test/bacnet/create_object/src/main.c @@ -0,0 +1,139 @@ +/** + * @file + * @brief Unit test for CreateObject service encode and decode API + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +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; + + null_len = create_object_encode_service_request(NULL, data); + apdu_len = create_object_encode_service_request(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); + test_len = create_object_decode_service_request(apdu, apdu_len, &test_data); + zassert_equal(test_len, null_len, NULL); + zassert_equal( + apdu_len, test_len, "apdu_len=%d test_len=%d", apdu_len, test_len); + while (test_len) { + test_len--; + len = create_object_decode_service_request(apdu, test_len, &test_data); + zassert_equal( + len, BACNET_STATUS_REJECT, "len=%d test_len=%d", len, test_len); + } +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(create_object_tests, test_CreateObject) +#else +static void test_CreateObject(void) +#endif +{ + BACNET_CREATE_OBJECT_DATA data = { 0 }; + int len = 0, apdu_len = 0, null_len = 0, test_len = 0; + + test_CreateObjectCodec(&data); + data.object_instance = BACNET_MAX_INSTANCE; + test_CreateObjectCodec(&data); +} + +static void test_CreateObjectAckCodec(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; + uint8_t invoke_id = 0; + + null_len = create_object_ack_service_encode(NULL, data); + apdu_len = create_object_ack_service_encode(apdu, data); + zassert_equal(apdu_len, null_len, NULL); + zassert_true(apdu_len != BACNET_STATUS_ERROR, NULL); + null_len = create_object_ack_service_decode(apdu, apdu_len, NULL); + test_len = create_object_ack_service_decode(apdu, apdu_len, &test_data); + zassert_equal(test_len, null_len, NULL); + zassert_equal( + apdu_len, test_len, "apdu_len=%d test_len=%d", apdu_len, test_len); + while (test_len) { + test_len--; + len = create_object_ack_service_decode(apdu, test_len, &test_data); + zassert_equal( + len, BACNET_STATUS_ERROR, "len=%d test_len=%d", len, test_len); + } + null_len = create_object_ack_encode(NULL, invoke_id, data); + apdu_len = create_object_ack_encode(apdu, invoke_id, data); + zassert_equal(apdu_len, null_len, NULL); + zassert_true(apdu_len > 0, NULL); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(create_object_tests, test_CreateObject) +#else +static void test_CreateObjectACK(void) +#endif +{ + BACNET_CREATE_OBJECT_DATA data = { 0 }; + int len = 0, apdu_len = 0, null_len = 0, test_len = 0; + + test_CreateObjectAckCodec(&data); + data.object_instance = BACNET_MAX_INSTANCE; + test_CreateObjectAckCodec(&data); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(create_object_tests, test_CreateObjectError) +#else +static void test_CreateObjectError(void) +#endif +{ + uint8_t apdu[MAX_APDU] = { 0 }; + BACNET_CREATE_OBJECT_DATA data = { 0 }, test_data = { 0 }; + int len = 0, apdu_len = 0, null_len = 0, test_len = 0; + uint8_t invoke_id = 0; + + data.error_class = ERROR_CLASS_SERVICES; + data.error_code = ERROR_CODE_REJECT_PARAMETER_OUT_OF_RANGE; + data.first_failed_element_number = 0; + 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); + test_len = create_object_error_ack_service_decode(apdu, apdu_len, &test_data); + zassert_equal(apdu_len, test_len, NULL); + zassert_equal(test_data.error_class, data.error_class, NULL); + zassert_equal(test_data.error_code, data.error_code, NULL); + zassert_equal(test_data.first_failed_element_number, + data.first_failed_element_number, NULL); + while (test_len) { + test_len--; + len = create_object_error_ack_service_decode(apdu, test_len, &test_data); + zassert_equal( + len, BACNET_STATUS_REJECT, "len=%d test_len=%d", len, test_len); + } + null_len = create_object_error_ack_encode(NULL, invoke_id, &data); + apdu_len = create_object_error_ack_encode(apdu, invoke_id, &data); + zassert_equal(apdu_len, null_len, NULL); + zassert_true(apdu_len > 0, NULL); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(create_object_tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite(create_object_tests, ztest_unit_test(test_CreateObject), + ztest_unit_test(test_CreateObjectACK), + ztest_unit_test(test_CreateObjectError)); + + ztest_run_test_suite(create_object_tests); +} +#endif diff --git a/test/bacnet/delete_object/CMakeLists.txt b/test/bacnet/delete_object/CMakeLists.txt new file mode 100644 index 00000000..f2d2bd69 --- /dev/null +++ b/test/bacnet/delete_object/CMakeLists.txt @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +set(ZTST_DIR "${TST_DIR}/ztest/src") + +add_compile_definitions( + BIG_ENDIAN=0 + CONFIG_ZTEST=1 + ) + +include_directories( + ${SRC_DIR} + ${TST_DIR}/ztest/include + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/delete_object.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacaddr.c + ${SRC_DIR}/bacnet/bacapp.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacdest.c + ${SRC_DIR}/bacnet/bacdevobjpropref.c + ${SRC_DIR}/bacnet/bacerror.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bactext.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/datetime.c + ${SRC_DIR}/bacnet/basic/sys/days.c + ${SRC_DIR}/bacnet/indtext.c + ${SRC_DIR}/bacnet/hostnport.c + ${SRC_DIR}/bacnet/lighting.c + ${SRC_DIR}/bacnet/timestamp.c + ${SRC_DIR}/bacnet/memcopy.c + ${SRC_DIR}/bacnet/weeklyschedule.c + ${SRC_DIR}/bacnet/bactimevalue.c + ${SRC_DIR}/bacnet/dailyschedule.c + # Test and test library files + ./src/main.c + ${ZTST_DIR}/ztest_mock.c + ${ZTST_DIR}/ztest.c + ) diff --git a/test/bacnet/delete_object/src/main.c b/test/bacnet/delete_object/src/main.c new file mode 100644 index 00000000..052ef912 --- /dev/null +++ b/test/bacnet/delete_object/src/main.c @@ -0,0 +1,60 @@ +/** + * @file + * @brief Unit test for DeleteObject service encode and decode API + * @author Steve Karg + * @date August 2023 + * @section LICENSE + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include + +static void test_DeleteObjectCodec(BACNET_DELETE_OBJECT_DATA *data) +{ + uint8_t apdu[MAX_APDU] = { 0 }; + BACNET_DELETE_OBJECT_DATA test_data = { 0 }; + int len = 0, apdu_len = 0, null_len = 0, test_len = 0; + + null_len = delete_object_encode_service_request(NULL, data); + apdu_len = delete_object_encode_service_request(apdu, data); + zassert_equal(apdu_len, null_len, NULL); + zassert_true(apdu_len != BACNET_STATUS_ERROR, NULL); + null_len = delete_object_decode_service_request(apdu, apdu_len, NULL); + test_len = delete_object_decode_service_request(apdu, apdu_len, &test_data); + zassert_equal(test_len, null_len, NULL); + zassert_equal( + apdu_len, test_len, "apdu_len=%d test_len=%d", apdu_len, test_len); + while (test_len) { + test_len--; + len = delete_object_decode_service_request(apdu, test_len, &test_data); + zassert_equal( + len, BACNET_STATUS_REJECT, "len=%d test_len=%d", len, test_len); + } +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST(delete_object_tests, test_DeleteObject) +#else +static void test_DeleteObject(void) +#endif +{ + BACNET_DELETE_OBJECT_DATA data = { 0 }; + int len = 0, apdu_len = 0, null_len = 0, test_len = 0; + + test_DeleteObjectCodec(&data); + data.object_instance = BACNET_MAX_INSTANCE; + test_DeleteObjectCodec(&data); +} + +#if defined(CONFIG_ZTEST_NEW_API) +ZTEST_SUITE(delete_object_tests, NULL, NULL, NULL, NULL, NULL); +#else +void test_main(void) +{ + ztest_test_suite(delete_object_tests, ztest_unit_test(test_DeleteObject)); + + ztest_run_test_suite(delete_object_tests); +} +#endif diff --git a/test/bacnet/list_element/src/main.c b/test/bacnet/list_element/src/main.c index 09cc8bfb..f9e7a314 100644 --- a/test/bacnet/list_element/src/main.c +++ b/test/bacnet/list_element/src/main.c @@ -6,7 +6,7 @@ * * SPDX-License-Identifier: MIT */ -#include +#include #include #include