From 1d1cf9bfac16fba6f85d17d3a566288fb7407ab5 Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Fri, 6 Feb 2026 16:54:10 -0600 Subject: [PATCH] Feature/create object initial value backup (#1223) * Added example Device object and property value backup using create-object list-of-initial-values and writable properties. Initiated using ReinitializeDevice with STARTBACKUP command. Added backup and restore required properties to the Device object. * Fixed fwrite parameters in bacfile_posix_write_stream_data to return the correct number of bytes written. * Added Device Management-Backup and Restore-B (DM-BR-B) conversion to Wireshark PCAP format command line utility in apps/dmbrcap which decodes and provides a view of the binary backup file. --- CHANGELOG.md | 7 + CMakeLists.txt | 17 + Makefile | 4 + apps/Makefile | 7 +- apps/dmbrcap/Makefile | 39 ++ apps/dmbrcap/dmbrcap.txt | 4 + apps/dmbrcap/main.c | 356 ++++++++++++++ apps/server/main.c | 15 + ports/posix/bacfile-posix.c | 2 +- src/bacnet/basic/object/bacfile.c | 30 +- src/bacnet/basic/object/bacfile.h | 6 + src/bacnet/basic/object/device.c | 592 ++++++++++++++++++++++-- src/bacnet/basic/object/device.h | 30 ++ src/bacnet/basic/server/bacnet_device.c | 588 +++++++++++++++++++++-- src/bacnet/create_object.c | 244 ++++++++++ src/bacnet/create_object.h | 25 + src/bacnet/proplist.c | 3 + 17 files changed, 1897 insertions(+), 72 deletions(-) create mode 100644 apps/dmbrcap/Makefile create mode 100644 apps/dmbrcap/dmbrcap.txt create mode 100644 apps/dmbrcap/main.c diff --git a/CHANGELOG.md b/CHANGELOG.md index c60fbb26..d533c3d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,12 @@ The git repositories are hosted at the following sites: ### Added +* Added Device Management-Backup and Restore-B to example object/device.c + and basic/server/bacnet-device.c so that when ReinitializeDevice STARTBACKUP + is requested a backup file is stored in CreateObject format for all the + writable properties in the device. (#1223) +* Added apps/dmbrcap for Device Management-Backup and Restore to convert + a backup file encoded with CreateObject to Wireshark PCAP format. (#1223) * Added segmentation support functions and example changes, but no support for segmentation in the TSM or APDU handlers. (#1218) * Added channel and timer object write-property observers in blinkt app @@ -122,6 +128,7 @@ The git repositories are hosted at the following sites: ### Fixed +* Fixed bacfile-posix file write to return the number of bytes written. (#1223) * Fixed Event parsing and help text for the example uevent and event apps. Fixed initialization of event data by adding static CharacterString for message text. Fixed the event parsing to start at argument zero. (#1221) diff --git a/CMakeLists.txt b/CMakeLists.txt index a21c1435..51cf5934 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,16 @@ option( "build with uci" OFF) +option( + BACFILE + "enable bacnet file support" + ON) + +option( + BACNET_BACKUP_RESTORE + "enable backup and restore support" + ON) + set(BACNET_PROTOCOL_REVISION 28) if(NOT CMAKE_BUILD_TYPE) @@ -744,6 +754,8 @@ target_compile_definitions( $<$:BACNET_PROPERTY_LISTS=1> $<$:BAC_ROUTING> $<$:BACNET_SEGMENTATION_ENABLED=1> + $<$:BACFILE> + $<$:BACNET_BACKUP_RESTORE> $<$>:BACNET_STACK_STATIC_DEFINE> PRIVATE PRINT_ENABLED=1) @@ -1018,6 +1030,9 @@ if(BACNET_STACK_BUILD_APPS) add_executable(initrouter apps/initrouter/main.c) target_link_libraries(initrouter PRIVATE ${PROJECT_NAME}) + add_executable(dmbrcap apps/dmbrcap/main.c) + target_link_libraries(dmbrcap PRIVATE ${PROJECT_NAME}) + if(BACDL_MSTP) add_executable(mstpcap apps/mstpcap/main.c) target_link_libraries(mstpcap PRIVATE ${PROJECT_NAME}) @@ -1311,3 +1326,5 @@ message(STATUS "BACNET: BACDL_MSTP:.....................\"${BACDL_MSTP}\"") message(STATUS "BACNET: BACDL_ZIGBEE:...................\"${BACDL_ZIGBEE}\"") message(STATUS "BACNET: BACDL_ETHERNET:.................\"${BACDL_ETHERNET}\"") message(STATUS "BACNET: BACNET_SEGMENTATION_ENABLED:....\"${BACNET_SEGMENTATION_ENABLED}\"") +message(STATUS "BACNET: BACNET_BACKUP_RESTORE:..........\"${BACNET_BACKUP_RESTORE}\"") +message(STATUS "BACNET: BACFILE:........................\"${BACFILE}\"") diff --git a/Makefile b/Makefile index 8e8e6d23..b222193d 100644 --- a/Makefile +++ b/Makefile @@ -236,6 +236,10 @@ sc-hub: sc-hub-debug: $(MAKE) LEGACY=true BACDL=bsc BUILD=debug -s -C apps sc-hub +.PHONY: dmbrcap +dmbrcap: + $(MAKE) -s -C apps $@ + .PHONY: mstpcap mstpcap: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index 6529b1a8..2b74e947 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -237,6 +237,7 @@ endif BACNET_DEFINES += -DPRINT_ENABLED=1 BACNET_DEFINES += -DBACAPP_ALL BACNET_DEFINES += -DBACNET_TIME_MASTER +BACNET_DEFINES += -DBACNET_BACKUP_RESTORE BACNET_DEFINES += -DBACNET_PROPERTY_LISTS=1 BACNET_DEFINES += -DBACNET_PROTOCOL_REVISION=28 @@ -266,7 +267,7 @@ 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 create-object \ - who-am-i you-are apdu writegroup \ + who-am-i you-are apdu writegroup dmbrcap \ delete-object server-discover server-basic server-mini ifneq (,$(filter $(BACDL),bip all)) @@ -401,6 +402,10 @@ whatisnetnum: $(BACNET_LIB_TARGET) netnumis: $(BACNET_LIB_TARGET) $(MAKE) -B -C $@ +.PHONY: dmbrcap +dmbrcap: + $(MAKE) -B -C $@ + .PHONY: mstpcap mstpcap: $(MAKE) -B -C $@ diff --git a/apps/dmbrcap/Makefile b/apps/dmbrcap/Makefile new file mode 100644 index 00000000..cd529ce4 --- /dev/null +++ b/apps/dmbrcap/Makefile @@ -0,0 +1,39 @@ +#Makefile to build BACnet Application using GCC compiler + +# Executable file name +TARGET = dmbrcap +# 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/dmbrcap/dmbrcap.txt b/apps/dmbrcap/dmbrcap.txt new file mode 100644 index 00000000..bc4dda91 --- /dev/null +++ b/apps/dmbrcap/dmbrcap.txt @@ -0,0 +1,4 @@ +K.5.18 BIBB - Device Management-Backup and Restore-B (DM-BR-B) + +This tool generates a Wireshark PCAP format file from a CreateObject services +encoded backup file. diff --git a/apps/dmbrcap/main.c b/apps/dmbrcap/main.c new file mode 100644 index 00000000..1ce4fb30 --- /dev/null +++ b/apps/dmbrcap/main.c @@ -0,0 +1,356 @@ +/** + * @file + * @brief Device Management-Backup and Restore tool that generates + * a Wireshark PCAP format file from a CreateObject services encoded file. + * @author Steve Karg + * @date February 2026 + * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +#include "bacnet/bacint.h" +#include "bacnet/create_object.h" +#include "bacnet/datetime.h" +#include "bacnet/npdu.h" +#include "bacnet/basic/sys/mstimer.h" +#include "bacnet/basic/sys/filename.h" + +/* define our Data Link Type for libPCAP */ +#define DLT_CAPTURE_TYPE (1) + +static uint8_t MTU_Buffer[1501]; +static char Capture_Filename[64] = "dmbr_20260209012345.cap"; +static FILE *Capture_File_Handle = NULL; /* stream pointer */ +static FILE *Backup_File_Handle = NULL; /* stream pointer */ +static long Backup_File_Start_Position; +static long Backup_File_Packet_Counter; + +/** + * @brief Write data to the capture file. + * @param ptr Pointer to the data to be written + * @param size Size of each element to write + * @param nitems Number of items to write + * @return Number of items successfully written to the capture file + */ +static size_t data_write(const void *ptr, size_t size, size_t nitems) +{ + size_t written = 0; + + if (Capture_File_Handle) { + written = fwrite(ptr, size, nitems, Capture_File_Handle); + } + + return written; +} + +/** + * @brief Write header data to the capture file. + * @param ptr Pointer to the header data to be written + * @param size Size of each element to write + * @param nitems Number of items to write + * @return Number of items successfully written to the capture file + */ +static size_t data_write_header(const void *ptr, size_t size, size_t nitems) +{ + size_t written = 0; + + if (Capture_File_Handle) { + written = fwrite(ptr, size, nitems, Capture_File_Handle); + } + + return written; +} + +/** + * @brief Create a new capture filename based on the current date and time. + * + * Closes any existing capture file, generates a new filename using the + * current local date and time, and opens a new capture file for writing. + */ +static void filename_create_new(void) +{ + BACNET_DATE bdate; + BACNET_TIME btime; + char *filename = &Capture_Filename[0]; + size_t filename_size = sizeof(Capture_Filename); + + if (Capture_File_Handle) { + fclose(Capture_File_Handle); + } + Capture_File_Handle = NULL; + datetime_local(&bdate, &btime, NULL, NULL); + snprintf( + filename, filename_size, "dmbr_%04d%02d%02d%02d%02d%02d.cap", + (int)bdate.year, (int)bdate.month, (int)bdate.day, (int)btime.hour, + (int)btime.min, (int)btime.sec); + Capture_File_Handle = fopen(filename, "wb"); + if (Capture_File_Handle) { + fprintf(stdout, "dmbrcap: saving capture to %s\n", filename); + } else { + fprintf( + stderr, "dmbrcap: failed to open %s: %s\n", filename, + strerror(errno)); + } +} + +/** + * @brief Write the global header to the capture file in libpcap format. + * + * Writes the standard libpcap global header including magic number, + * version information, timezone, timestamp accuracy, snapshot length, + * and data link type. + */ +static void write_global_header(void) +{ + uint32_t magic_number = 0xa1b2c3d4; /* magic number */ + uint16_t version_major = 2; /* major version number */ + uint16_t version_minor = 4; /* minor version number */ + int32_t thiszone = 0; /* GMT to local correction */ + uint32_t sigfigs = 0; /* accuracy of timestamps */ + uint32_t snaplen = 65535; /* max length of captured packet, in octets */ + uint32_t network = DLT_CAPTURE_TYPE; /* data link type */ + + /* create a new file. */ + (void)data_write_header(&magic_number, sizeof(magic_number), 1); + (void)data_write_header(&version_major, sizeof(version_major), 1); + (void)data_write_header(&version_minor, sizeof(version_minor), 1); + (void)data_write_header(&thiszone, sizeof(thiszone), 1); + (void)data_write_header(&sigfigs, sizeof(sigfigs), 1); + (void)data_write_header(&snaplen, sizeof(snaplen), 1); + (void)data_write_header(&network, sizeof(network), 1); + fflush(Capture_File_Handle); +} + +/** + * @brief Write a packet record to the capture file in libpcap format. + * + * Writes a packet record with timestamp and length information followed by + * the packet data to the capture file in libpcap format. + * @param packet Pointer to the packet data to write + * @param packet_len Length of the packet data in bytes + */ +static void write_received_packet(uint8_t *packet, size_t packet_len) +{ + uint32_t ts_msec = 0; /* timestamp milliseconds */ + uint32_t ts_sec = 0; /* timestamp seconds */ + uint32_t ts_usec = 0; /* timestamp microseconds */ + uint32_t incl_len = 0; /* number of octets of packet saved in file */ + uint32_t orig_len = 0; /* actual length of packet */ + + ts_msec = mstimer_now(); + ts_sec = ts_msec / 1000; + ts_usec = (ts_msec % 1000) * 1000; + (void)data_write(&ts_sec, sizeof(ts_sec), 1); + (void)data_write(&ts_usec, sizeof(ts_usec), 1); + incl_len = packet_len; + (void)data_write(&incl_len, sizeof(incl_len), 1); + orig_len = packet_len; + (void)data_write(&orig_len, sizeof(orig_len), 1); + (void)data_write(packet, packet_len, 1); +} + +/** + * @brief Open a backup file for reading. + * + * Opens an existing backup file for reading and initializes the file + * position tracking for packet extraction. + * @param filename Path to the backup file to open + * @return true if the file was successfully opened, false otherwise + */ +static bool open_backup_file(const char *filename) +{ + /* open existing file. */ + Backup_File_Handle = fopen(filename, "rb"); + if (Backup_File_Handle) { + fprintf(stdout, "dmbrcap: reading backup from %s\n", filename); + Backup_File_Start_Position = 0; + return true; + } + fprintf( + stderr, "dmbrcap: failed to open %s: %s\n", filename, strerror(errno)); + + return false; +} + +/** + * @brief Extract and convert the next packet from the backup file. + * + * Reads the next CreateObject service request from the backup file, + * encapsulates it in BACnet NPDU and APDU headers with Ethernet framing, + * and writes the complete packet to the capture file. Advances the file + * position for the next packet extraction. + * @return Length of the packet written, or 0 if no more packets are available + */ +static size_t backup_file_packet(void) +{ + BACNET_NPDU_DATA npdu_data = { 0 }; + size_t len = 0, packet_len = 0, apdu_len = 0; + int decoded_len = 0; + uint8_t apdu[1500] = { 0 }; + + /* Ethernet SNAP Encoding*/ + /* dest MAC */ + MTU_Buffer[0] = 0xFF; + MTU_Buffer[1] = 0xFF; + MTU_Buffer[2] = 0xFF; + MTU_Buffer[3] = 0xFF; + MTU_Buffer[4] = 0xFF; + MTU_Buffer[5] = 0xFF; + /* source MAC */ + MTU_Buffer[6] = 0xFF; + MTU_Buffer[7] = 0xFF; + MTU_Buffer[8] = 0xFF; + MTU_Buffer[9] = 0xFF; + MTU_Buffer[10] = 0xFF; + MTU_Buffer[11] = 0xFF; + /* length - 12, 13 */ + /* Logical-Link Control SNAP */ + /* DSAP for SNAP */ + MTU_Buffer[14] = 0x82; + /* SSAP for SNAP */ + MTU_Buffer[15] = 0x82; + /* Control Field for SNAP */ + MTU_Buffer[16] = 0x03; + /* BACnet NPDU */ + len = npdu_encode_pdu(&MTU_Buffer[17], NULL, NULL, &npdu_data); + packet_len = 17 + len; + /* BACnet APDU header */ + MTU_Buffer[packet_len++] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST; + MTU_Buffer[packet_len++] = encode_max_segs_max_apdu(0, MAX_APDU); + MTU_Buffer[packet_len++] = Backup_File_Packet_Counter % 256; + MTU_Buffer[packet_len++] = SERVICE_CONFIRMED_CREATE_OBJECT; + /* BACnet APDU service data */ + if (Backup_File_Handle) { + (void)fseek(Backup_File_Handle, Backup_File_Start_Position, SEEK_SET); + apdu_len = + fread(apdu, sizeof(uint8_t), sizeof(apdu), Backup_File_Handle); + if (apdu_len > 0) { + decoded_len = + create_object_decode_service_request(apdu, apdu_len, NULL); + if (decoded_len > 0) { + apdu_len = decoded_len; + Backup_File_Start_Position += decoded_len; + if ((packet_len + apdu_len) > sizeof(MTU_Buffer)) { + apdu_len = sizeof(MTU_Buffer) - packet_len; + } + memcpy(&MTU_Buffer[packet_len], apdu, apdu_len); + packet_len += apdu_len; + /* Ethernet length is data only - not address or length bytes */ + encode_unsigned16(&MTU_Buffer[12], packet_len - 14); + write_received_packet(MTU_Buffer, packet_len); + } else { + packet_len = 0; + } + } else { + packet_len = 0; + } + } + + return packet_len; +} + +/** + * @brief Clean up and close all open file handles. + * + * Flushes and closes the capture file and backup file handles, preparing + * the program for exit. This function is registered as an exit handler. + */ +static void cleanup(void) +{ + if (Capture_File_Handle) { + fflush(Capture_File_Handle); + fclose(Capture_File_Handle); + } + Capture_File_Handle = NULL; + if (Backup_File_Handle) { + fclose(Backup_File_Handle); + } + Backup_File_Handle = NULL; +} + +/** + * @brief Print the command-line usage information. + * @param filename The name of the program (typically argv[0]) + */ +static void print_usage(const char *filename) +{ + printf("Usage: %s ", filename); + printf(" [--version][--help]\n"); +} + +/** + * @brief Print detailed help information for the program. + * @param filename The name of the program (typically argv[0]) + */ +static void print_help(const char *filename) +{ + printf( + "%s \n" + "convert a backup file into a capture file.\n", + filename); + printf("\n"); +} + +/** + * @brief Main entry point for the dmbrcap utility. + * + * Processes command-line arguments, opens the backup file, initializes the + * timer system, creates a new capture file with libpcap headers, reads all + * packets from the backup file and converts them to libpcap format, then + * closes all files and exits. + * @param argc Number of command-line arguments + * @param argv Array of command-line argument strings + * @return 0 on success, 1 if backup file cannot be opened or is not provided + */ +int main(int argc, char *argv[]) +{ + const char *filename = NULL; + int argi = 0; + + /* decode any command line parameters */ + 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("dmbrcap 1.0.0\n"); + printf("Copyright (C) 2026 by Steve Karg\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 (!open_backup_file(argv[argi])) { + return 1; + } + } + if (!Backup_File_Handle) { + print_usage(filename); + return 1; + } + atexit(cleanup); + mstimer_init(); + filename_create_new(); + write_global_header(); + while (backup_file_packet() > 0) { + Backup_File_Packet_Counter++; + } + if (Backup_File_Packet_Counter) { + fprintf( + stdout, "dmbrcap: wrote %u packets\n", + (unsigned)Backup_File_Packet_Counter); + } + + return 0; +} diff --git a/apps/server/main.c b/apps/server/main.c index 249997a6..1ce878c7 100644 --- a/apps/server/main.c +++ b/apps/server/main.c @@ -174,6 +174,21 @@ static void Init_Service_Handlers(void) (unsigned)object_data.object_instance); } } +#if defined(BACFILE) + /* file for backup and restore example */ + object_data.object_instance = bacfile_create(BACNET_MAX_INSTANCE); + if (object_data.object_instance != BACNET_MAX_INSTANCE) { + bacfile_pathname_set(object_data.object_instance, "backup_1.bin"); +#if defined(BACNET_BACKUP_RESTORE) + Device_Configuration_File_Set(0, object_data.object_instance); +#endif + printf( + "Created %s-%u path=%s for backup and restore (%u files).\n", + bactext_object_type_name(OBJECT_FILE), + (unsigned)object_data.object_instance, + bacfile_pathname(object_data.object_instance), bacfile_count()); + } +#endif /* we need to handle who-is to support dynamic device binding */ apdu_set_unconfirmed_handler( SERVICE_UNCONFIRMED_WHO_IS, handler_who_is_who_am_i_unicast); diff --git a/ports/posix/bacfile-posix.c b/ports/posix/bacfile-posix.c index c7c9e071..c872ac98 100644 --- a/ports/posix/bacfile-posix.c +++ b/ports/posix/bacfile-posix.c @@ -151,7 +151,7 @@ size_t bacfile_posix_write_stream_data( if (fileStartPosition != -1) { (void)fseek(pFile, fileStartPosition, SEEK_SET); } - bytes_written = fwrite(fileData, fileDataLen, 1, pFile); + bytes_written = fwrite(fileData, 1, fileDataLen, pFile); fclose(pFile); } else { debug_printf_stderr("Failed to open %s for writing!\n", pathname); diff --git a/src/bacnet/basic/object/bacfile.c b/src/bacnet/basic/object/bacfile.c index 0d166014..5df46830 100644 --- a/src/bacnet/basic/object/bacfile.c +++ b/src/bacnet/basic/object/bacfile.c @@ -533,7 +533,7 @@ void bacfile_file_size_set_callback_set(bool (*callback)(const char *, size_t)) * @param object_instance - object-instance number of the object * @param buffer - data store from the file * @param buffer_size - in bytes - * @return file size in bytes + * @return number of bytes read, or 0 if not successful */ uint32_t bacfile_read(uint32_t object_instance, uint8_t *buffer, uint32_t buffer_size) @@ -555,7 +555,7 @@ bacfile_read(uint32_t object_instance, uint8_t *buffer, uint32_t buffer_size) * @param object_instance - object-instance number of the object * @param buffer - data store for the file * @param buffer_size - in bytes - * @return file size in bytes + * @return number of bytes written, or 0 if not successful */ uint32_t bacfile_write( uint32_t object_instance, const uint8_t *buffer, uint32_t buffer_size) @@ -572,6 +572,32 @@ uint32_t bacfile_write( return (uint32_t)file_size; } +/** + * @brief Write to the file from a buffer at a given offset + * @param object_instance - object-instance number of the object + * @param offset - offset in bytes from the beginning of the file + * @param buffer - data store for the file + * @param buffer_size - in bytes + * @return number of bytes written, or 0 if not successful + */ +uint32_t bacfile_write_offset( + uint32_t object_instance, + int32_t offset, + const uint8_t *buffer, + uint32_t buffer_size) +{ + const char *pathname = NULL; + long file_size = 0; + + pathname = bacfile_pathname(object_instance); + if (pathname) { + file_size = bacfile_write_stream_data_callback( + pathname, offset, buffer, buffer_size); + } + + return (uint32_t)file_size; +} + /** * @brief Determines the file size for a given file * @param pFile - file handle diff --git a/src/bacnet/basic/object/bacfile.h b/src/bacnet/basic/object/bacfile.h index 28592674..abc677c3 100644 --- a/src/bacnet/basic/object/bacfile.h +++ b/src/bacnet/basic/object/bacfile.h @@ -121,6 +121,12 @@ bacfile_read(uint32_t object_instance, uint8_t *buffer, uint32_t buffer_size); BACNET_STACK_EXPORT uint32_t bacfile_write( uint32_t object_instance, const uint8_t *buffer, uint32_t buffer_size); +BACNET_STACK_EXPORT +uint32_t bacfile_write_offset( + uint32_t object_instance, + int32_t offset, + const uint8_t *buffer, + uint32_t buffer_size); BACNET_STACK_EXPORT void bacfile_write_stream_data_callback_set( diff --git a/src/bacnet/basic/object/device.c b/src/bacnet/basic/object/device.c index e9f098f2..33b5afaf 100644 --- a/src/bacnet/basic/object/device.c +++ b/src/bacnet/basic/object/device.c @@ -1,13 +1,14 @@ /** * @file - * @author Steve Karg - * @date 2005 * @brief Base "class" for handling all BACnet objects belonging * to a BACnet device, as well as Device-specific properties. + * @author Steve Karg + * @date 2005 * @copyright SPDX-License-Identifier: MIT */ #include #include +#include #include /* BACnet Stack defines - first */ #include "bacnet/bacdef.h" @@ -34,10 +35,10 @@ #include "bacnet/basic/object/access_rights.h" #include "bacnet/basic/object/access_user.h" #include "bacnet/basic/object/access_zone.h" -#include "bacnet/basic/object/auditlog.h" #include "bacnet/basic/object/ai.h" #include "bacnet/basic/object/ao.h" #include "bacnet/basic/object/av.h" +#include "bacnet/basic/object/auditlog.h" #include "bacnet/basic/object/bi.h" #include "bacnet/basic/object/bo.h" #include "bacnet/basic/object/bv.h" @@ -75,6 +76,9 @@ #include "bacnet/basic/object/color_object.h" #include "bacnet/basic/object/color_temperature.h" #include "bacnet/basic/object/program.h" +/* for testing */ +#include "bacnet/basic/sys/debug.h" +#include "bacnet/bactext.h" /* external prototypes */ extern int Routed_Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata); @@ -698,11 +702,21 @@ static const int32_t Device_Properties_Optional[] = { PROP_TIME_SYNCHRONIZATION_INTERVAL, PROP_ALIGN_INTERVALS, PROP_INTERVAL_OFFSET, +#endif +#if defined(BACNET_BACKUP_RESTORE) + PROP_CONFIGURATION_FILES, + PROP_BACKUP_FAILURE_TIMEOUT, + PROP_BACKUP_PREPARATION_TIME, + PROP_RESTORE_PREPARATION_TIME, + PROP_BACKUP_AND_RESTORE_STATE, #endif -1 }; -static const int32_t Device_Properties_Proprietary[] = { -1 }; +static const int32_t Device_Properties_Proprietary[] = { + /* List of Proprietary properties in this object */ + -1 +}; /* Every object shall have a Writable Property_List property which is a BACnetARRAY of property identifiers, @@ -717,8 +731,6 @@ static const int32_t Writable_Properties[] = { PROP_MODEL_NAME, PROP_LOCATION, PROP_DESCRIPTION, - PROP_PROTOCOL_SERVICES_SUPPORTED, - PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED, PROP_APDU_TIMEOUT, PROP_NUMBER_OF_APDU_RETRIES, PROP_UTC_OFFSET, @@ -730,6 +742,12 @@ static const int32_t Writable_Properties[] = { #if defined(BACDL_MSTP) PROP_MAX_INFO_FRAMES, PROP_MAX_MASTER, +#endif +#if defined(BACNET_BACKUP_RESTORE) + PROP_CONFIGURATION_FILES, + PROP_BACKUP_FAILURE_TIMEOUT, + PROP_BACKUP_PREPARATION_TIME, + PROP_RESTORE_PREPARATION_TIME, #endif PROP_TIME_OF_DEVICE_RESTART, -1 @@ -824,6 +842,7 @@ static char Location[MAX_DEV_LOC_LEN + 1] = "USA"; static char Description[MAX_DEV_DESC_LEN + 1] = "server"; static char Serial_Number[MAX_DEV_DESC_LEN + 1] = "BACnetDMcN56RBkeDJuNfxn3M44tfC2Y"; +static uint8_t Device_UUID[16]; /* static uint8_t Protocol_Version = 1; - constant, not settable */ /* static uint8_t Protocol_Revision = 4; - constant, not settable */ /* Protocol_Services_Supported - dynamically generated */ @@ -853,9 +872,6 @@ static uint32_t Interval_Offset_Minutes; /* Max_Info_Frames - rely on MS/TP subsystem, if there is one */ /* Device_Address_Binding - required, but relies on binding cache */ static uint32_t Database_Revision = 0; -/* Configuration_Files */ -/* Last_Restore_Time */ -/* Backup_Failure_Timeout */ /* Active_COV_Subscriptions */ /* Slave_Proxy_Enable */ /* Manual_Slave_Address_Binding */ @@ -867,6 +883,58 @@ static const char *Reinit_Password = "filister"; static write_property_function Device_Write_Property_Store_Callback; static list_element_function Device_Add_List_Element_Callback; static list_element_function Device_Remove_List_Element_Callback; +/* backup and restore */ +#if defined BACNET_BACKUP_RESTORE +/* number of backup files */ +#ifndef BACNET_BACKUP_FILE_COUNT +#if defined(BACFILE) +#define BACNET_BACKUP_FILE_COUNT 1 +#else +#define BACNET_BACKUP_FILE_COUNT 0 +#endif +#endif + +#if BACNET_BACKUP_FILE_COUNT +/* device A will read the Configuration_Files property of the Device object. + This property will be used to determine the files to read and in what + order the files will be read. The value of the Configuration_Files + property is not guaranteed to contain a complete or correct set of + configuration File object references before the backup request is + accepted by device B. */ +static uint32_t Configuration_Files[BACNET_BACKUP_FILE_COUNT]; +#endif +/* If the restore is successful, no other actions by device A shall + be required, and device B will update the Last_Restore_Time property + in its Device object. */ +static BACNET_TIMESTAMP Last_Restore_Time; +/* If device B does not receive any messages related to the restore + procedure from device A for the number of seconds specified + in the Backup_Failure_Timeout property of its Device object, + device B should assume that the restore procedure has been aborted, + and device B should exit restore mode.*/ +static uint16_t Backup_Failure_Timeout; +/* This property indicates the amount of time in seconds + that the device might remain unresponsive after the sending + of a ReinitializeDevice-ACK at the start of a backup procedure. + The device that initiated the backup shall either wait the + period of time specified by this property or be prepared + to encounter communication timeouts during this period. */ +static uint16_t Backup_Preparation_Time; +/* This property indicates the amount of time in seconds that the device + is allowed to remain unresponsive after the sending of a + ReinitializeDevice-ACK at the start of a restore procedure. + The restoring device shall either wait or be prepared to + encounter communication timeouts during this period.*/ +static uint16_t Restore_Preparation_Time; +/* This property indicates the amount of time in seconds that + the device is allowed to remain unresponsive after the sending + of a ReinitializeDevice-ACK at the end of a restore procedure. + The restoring device shall either wait or be prepared to + encounter communication timeouts during this period. */ +static uint16_t Restore_Completion_Time; +/* This property indicates a server device's backup and restore state. */ +static BACNET_BACKUP_STATE Backup_State = BACKUP_STATE_IDLE; +#endif #ifdef BAC_ROUTING static bool Device_Router_Mode = false; @@ -957,9 +1025,27 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) Reinitialize_State = rd_data->state; status = true; break; +#if defined BACNET_BACKUP_RESTORE case BACNET_REINIT_STARTBACKUP: - case BACNET_REINIT_ENDBACKUP: + Device_Start_Backup(); + Reinitialize_State = rd_data->state; + status = true; + break; case BACNET_REINIT_STARTRESTORE: + Device_Start_Restore(); + Reinitialize_State = rd_data->state; + status = true; + break; + case BACNET_REINIT_ENDBACKUP: + case BACNET_REINIT_ENDRESTORE: + case BACNET_REINIT_ABORTRESTORE: + Reinitialize_State = rd_data->state; + status = true; + break; +#else + case BACNET_REINIT_STARTBACKUP: + case BACNET_REINIT_STARTRESTORE: + case BACNET_REINIT_ENDBACKUP: case BACNET_REINIT_ENDRESTORE: case BACNET_REINIT_ABORTRESTORE: if (dcc_communication_disabled()) { @@ -971,6 +1057,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; } break; +#endif case BACNET_REINIT_ACTIVATE_CHANGES: /* note: activate changes *after* the simple ack is sent */ for (i = 0; i < Network_Port_Count(); i++) { @@ -1012,8 +1099,6 @@ uint32_t Device_Index_To_Instance(unsigned index) return Object_Instance_Number; } -/* methods to manipulate the data */ - /** Return the Object Instance number for our (single) Device Object. * This is a key function, widely invoked by the handler code, since * it provides "our" (ie, local) address. @@ -1097,6 +1182,71 @@ char *Device_Object_Name_ANSI(void) return (char *)characterstring_value(&My_Object_Name); } +/** + * @brief Initialize a UUID for storing the unique identifier of this device + * @note A Universally Unique IDentifier (UUID) - also called a + * Global Unique IDentifier (GUID) - is a 128-bit value, see RFC 4122. + * + * 4.4. Algorithms for Creating a UUID from Truly Random or + * Pseudo-Random Numbers + * + * The version 4 UUID is meant for generating UUIDs from truly-random or + * pseudo-random numbers. + * + * The algorithm is as follows: + * + * o Set the two most significant bits (bits 6 and 7) of the + * clock_seq_hi_and_reserved to zero and one, respectively. + * + * o Set the four most significant bits (bits 12 through 15) of the + * time_hi_and_version field to the 4-bit version number from + * Section 4.1.3. + * + * o Set all the other bits to randomly (or pseudo-randomly) chosen + * values. + */ +void Device_UUID_Init(void) +{ + unsigned i = 0; + + /* 1. Generate 16 random bytes = 128 bits */ + for (i = 0; i < sizeof(Device_UUID); i++) { + Device_UUID[i] = rand() % 256; + } + /* 2. Adjust certain bits according to RFC 4122 section 4.4. + This just means do the following + (a) set the high nibble of the 7th byte equal to 4 and + (b) set the two most significant bits of the 9th byte to 10'B, + so the high nibble will be one of {8,9,A,B}. + From http://www.cryptosys.net/pki/Uuid.c.html */ + Device_UUID[6] = 0x40 | (Device_UUID[6] & 0x0f); + Device_UUID[8] = 0x80 | (Device_UUID[8] & 0x3f); +} + +/** + * @brief Set the UUID for this device + * @param new_uuid [in] The new UUID to set + * @param length [in] The length of the new UUID + */ +void Device_UUID_Set(const uint8_t *new_uuid, size_t length) +{ + if (new_uuid && (length == sizeof(Device_UUID))) { + memcpy(Device_UUID, new_uuid, sizeof(Device_UUID)); + } +} + +/** + * @brief Get the UUID for this device + * @param uuid [out] The UUID of this device + * @param length [in] The length of the UUID + */ +void Device_UUID_Get(uint8_t *uuid, size_t length) +{ + if (uuid && (length == sizeof(Device_UUID))) { + memcpy(uuid, Device_UUID, sizeof(Device_UUID)); + } +} + BACNET_DEVICE_STATUS Device_System_Status(void) { return System_Status; @@ -1115,47 +1265,40 @@ int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local) case STATUS_DOWNLOAD_REQUIRED: case STATUS_DOWNLOAD_IN_PROGRESS: case STATUS_NON_OPERATIONAL: +#if defined BACNET_BACKUP_RESTORE + case STATUS_BACKUP_IN_PROGRESS: +#endif System_Status = status; break; - - /* Don't support backup at present so don't allow setting */ +#ifndef BACNET_BACKUP_RESTORE case STATUS_BACKUP_IN_PROGRESS: result = -2; break; - +#endif default: result = -1; break; } } else { switch (status) { - /* Allow these for the moment as a way to easily alter - * overall device operation. The lack of password protection - * or other authentication makes allowing writes to this - * property a risky facility to provide. - */ case STATUS_OPERATIONAL: case STATUS_OPERATIONAL_READ_ONLY: case STATUS_NON_OPERATIONAL: + /* Allow these for the moment as a way to easily alter + * overall device operation. The lack of password protection + * or other authentication makes allowing writes to this + * property a risky facility to provide. */ System_Status = status; break; - - /* Don't allow outsider set this - it should probably + case STATUS_DOWNLOAD_REQUIRED: + case STATUS_DOWNLOAD_IN_PROGRESS: + case STATUS_BACKUP_IN_PROGRESS: + /* Don't allow outsider set these - they should probably * be set if the device config is incomplete or * corrupted or perhaps after some sort of operator - * wipe operation. - */ - case STATUS_DOWNLOAD_REQUIRED: - /* Don't allow outsider set this - it should be set - * internally at the start of a multi packet download - * perhaps indirectly via PT or WF to a config file. - */ - case STATUS_DOWNLOAD_IN_PROGRESS: - /* Don't support backup at present so don't allow setting */ - case STATUS_BACKUP_IN_PROGRESS: + * wipe operation. */ result = -2; break; - default: result = -1; break; @@ -1170,9 +1313,17 @@ const char *Device_Vendor_Name(void) return Vendor_Name; } +bool Device_Set_Vendor_Name(const char *name, size_t length) +{ + (void)length; + Vendor_Name = name; + + return true; +} + /** Returns the Vendor ID for this Device. * See the assignments at - * http://www.bacnet.org/VendorID/BACnet%20Vendor%20IDs.htm + * https://bacnet.org/assigned-vendor-ids/ * @return The Vendor ID of this Device. */ uint16_t Device_Vendor_Identifier(void) @@ -1678,8 +1829,197 @@ uint32_t Device_Interval_Offset(void) } #endif -/* return the length of the apdu encoded or BACNET_STATUS_ERROR for error or - BACNET_STATUS_ABORT for abort message */ +bool Device_Configuration_File_Set(unsigned index, uint32_t instance) +{ + bool status = false; +#if BACNET_BACKUP_FILE_COUNT + if (index < BACNET_BACKUP_FILE_COUNT) { + Configuration_Files[index] = instance; + status = true; + } +#else + (void)index; + (void)instance; +#endif + + return status; +} + +uint32_t Device_Configuration_File(unsigned index) +{ + uint32_t instance = BACNET_MAX_INSTANCE + 1; + +#if BACNET_BACKUP_FILE_COUNT + if (index < BACNET_BACKUP_FILE_COUNT) { + instance = Configuration_Files[index]; + } +#else + (void)index; +#endif + + return instance; +} + +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +int Device_Configuration_File_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + + (void)object_instance; +#if BACNET_BACKUP_FILE_COUNT + if (array_index < BACNET_BACKUP_FILE_COUNT) { + apdu_len = encode_application_object_id( + apdu, OBJECT_FILE, Configuration_Files[array_index]); + } +#else + (void)array_index; + (void)apdu; +#endif + + return apdu_len; +} + +#if defined(BACNET_BACKUP_RESTORE) +/** + * @brief Decode a BACnetLIST property element to determine the element length + * @param object_instance [in] BACnet object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int Device_Configuration_File_Length( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + (void)object_instance; + return bacnet_object_id_application_decode(apdu, apdu_size, NULL, NULL); +} + +/** + * @brief Write a value to a BACnetLIST property element value + * using a BACnetARRAY write utility function + * @param object_instance [in] BACnet object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param application_data [in] encoded element value + * @param application_data_len [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Device_Configuration_File_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + size_t application_data_len) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + uint32_t instance = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + int len = 0; + + (void)object_instance; + if (array_index == 0) { + /* This array is not required to be resizable + through BACnet write services */ + error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (array_index <= BACNET_BACKUP_FILE_COUNT) { + len = bacnet_object_id_application_decode( + application_data, application_data_len, &object_type, &instance); + if (len > 0) { + if ((object_type == OBJECT_FILE) && + Device_Valid_Object_Id(object_type, instance)) { + if (Device_Configuration_File_Set(array_index - 1, instance)) { + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } else { + error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + + return error_code; +} + +uint16_t Device_Backup_Failure_Timeout(void) +{ + return Backup_Failure_Timeout; +} + +bool Device_Backup_Failure_Timeout_Set(uint16_t timeout) +{ + Backup_Failure_Timeout = timeout; + return true; +} + +uint16_t Device_Backup_Preparation_Time(void) +{ + return Backup_Preparation_Time; +} + +bool Device_Backup_Preparation_Time_Set(uint16_t time) +{ + Backup_Preparation_Time = time; + return true; +} + +uint16_t Device_Restore_Preparation_Time(void) +{ + return Restore_Preparation_Time; +} + +bool Device_Restore_Preparation_Time_Set(uint16_t time) +{ + Restore_Preparation_Time = time; + return true; +} + +uint16_t Device_Restore_Completion_Time(void) +{ + return Restore_Completion_Time; +} + +bool Device_Restore_Completion_Time_Set(uint16_t time) +{ + Restore_Completion_Time = time; + return true; +} + +BACNET_BACKUP_STATE Device_Backup_And_Restore_State(void) +{ + return Backup_State; +} + +bool Device_Backup_And_Restore_State_Set(BACNET_BACKUP_STATE state) +{ + Backup_State = state; + return true; +} +#endif + +/** + * ReadProperty handler for this object. For the given ReadProperty + * data, the application_data is loaded or the error flags are set. + * + * @param rpdata - BACNET_READ_PROPERTY_DATA data, including + * requested data and space for the reply, or error response. + * + * @return number of APDU bytes in the response, zero if no data, or + * BACNET_STATUS_ERROR on error. + */ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) { int apdu_len = 0; /* return value */ @@ -1869,6 +2209,45 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) apdu_len = encode_application_unsigned(&apdu[0], Device_Interval_Offset()); break; +#endif +#if defined(BACNET_BACKUP_RESTORE) + case PROP_CONFIGURATION_FILES: + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Device_Configuration_File_Encode, BACNET_BACKUP_FILE_COUNT, + &apdu[0], apdu_max); + if (apdu_len == BACNET_STATUS_ABORT) { +#if BACNET_SEGMENTATION_ENABLED + rpdata->error_code = ERROR_CODE_ABORT_BUFFER_OVERFLOW; +#else + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; +#endif + } else if (apdu_len == BACNET_STATUS_ERROR) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + break; + case PROP_BACKUP_FAILURE_TIMEOUT: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Backup_Failure_Timeout()); + break; + case PROP_BACKUP_PREPARATION_TIME: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Backup_Preparation_Time()); + break; + case PROP_RESTORE_PREPARATION_TIME: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Restore_Preparation_Time()); + break; + case PROP_RESTORE_COMPLETION_TIME: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Restore_Completion_Time()); + break; + case PROP_BACKUP_AND_RESTORE_STATE: + apdu_len = encode_application_enumerated( + &apdu[0], Device_Backup_And_Restore_State()); + break; #endif case PROP_ACTIVE_COV_SUBSCRIPTIONS: if ((apdu_len = handler_cov_encode_subscriptions( @@ -1902,7 +2281,8 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) * @param pObject - object table * @param rpdata [in,out] Structure with the requested Object & Property info * on entry, and APDU message on return. - * @return The length of the APDU on success, else BACNET_STATUS_ERROR + * @return number of APDU bytes in the response, zero if no data, or + * BACNET_STATUS_ERROR on error. */ static int Read_Property_Common( const struct object_functions *pObject, BACNET_READ_PROPERTY_DATA *rpdata) @@ -1914,7 +2294,7 @@ static int Read_Property_Common( struct special_property_list_t property_list; #endif - if ((rpdata->application_data == NULL) || + if ((rpdata == NULL) || (rpdata->application_data == NULL) || (rpdata->application_data_len == 0)) { return 0; } @@ -1956,6 +2336,9 @@ int Device_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata) int apdu_len = BACNET_STATUS_ERROR; struct object_functions *pObject = NULL; + if (!rpdata) { + return 0; + } /* initialize the default return values */ rpdata->error_class = ERROR_CLASS_OBJECT; rpdata->error_code = ERROR_CODE_UNKNOWN_OBJECT; @@ -2147,6 +2530,60 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) } } break; +#endif +#if defined(BACNET_BACKUP_RESTORE) + case PROP_BACKUP_FAILURE_TIMEOUT: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + if (value.type.Unsigned_Int <= UINT16_MAX) { + Device_Backup_Failure_Timeout_Set( + (uint16_t)value.type.Unsigned_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_BACKUP_PREPARATION_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + if (value.type.Unsigned_Int <= UINT16_MAX) { + Device_Backup_Preparation_Time_Set( + (uint16_t)value.type.Unsigned_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_RESTORE_PREPARATION_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + if (value.type.Unsigned_Int <= UINT16_MAX) { + Device_Restore_Preparation_Time_Set( + (uint16_t)value.type.Unsigned_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_CONFIGURATION_FILES: + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + Device_Configuration_File_Length, + Device_Configuration_File_Write, BACNET_BACKUP_FILE_COUNT, + wp_data->application_data, wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } + break; #endif case PROP_UTC_OFFSET: status = write_property_type_valid( @@ -2387,7 +2824,9 @@ int Device_Add_List_Element(BACNET_LIST_ELEMENT_DATA *list_element) if (pObject->Object_Add_List_Element) { status = pObject->Object_Add_List_Element(list_element); if (status) { - Device_Add_List_Element_Callback(list_element); + if (Device_Add_List_Element_Callback) { + (void)Device_Add_List_Element_Callback(list_element); + } } } else { list_element->error_class = ERROR_CLASS_PROPERTY; @@ -2433,7 +2872,9 @@ int Device_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element) if (pObject->Object_Remove_List_Element) { status = pObject->Object_Remove_List_Element(list_element); if (status) { - Device_Remove_List_Element_Callback(list_element); + if (Device_Remove_List_Element_Callback) { + (void)Device_Remove_List_Element_Callback(list_element); + } } } else { list_element->error_class = ERROR_CLASS_PROPERTY; @@ -2457,7 +2898,8 @@ int Device_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element) * @param [in] The object type to be looked up. * @param [in] The object instance number to be looked up. * @param [out] The value list - * @return True if the object instance supports this feature and value changed. + * @return True if the object instance supports this feature + * and was encoded correctly */ bool Device_Encode_Value_List( BACNET_OBJECT_TYPE object_type, @@ -2605,6 +3047,74 @@ bool Device_Delete_Object(BACNET_DELETE_OBJECT_DATA *data) return status; } +/** + * @brief Loop through the Device object-list property and export to + * a file as BACnet CreateObject services with List of Initial Values + * for every writable property + */ +void Device_Start_Backup(void) +{ +#if defined BACNET_BACKUP_RESTORE + size_t i = 0; + uint32_t object_count = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + const int32_t *writable_properties = NULL; + struct special_property_list_t property_list = { 0 }; + uint8_t object_apdu[MAX_APDU] = { 0 }; + BACNET_CREATE_OBJECT_DATA create_data = { 0 }; + bool status = false; + int32_t len = 0, offset = 0; + + Backup_State = BACKUP_STATE_PREPARING_FOR_BACKUP; + object_count = Device_Object_List_Count(); + Backup_State = BACKUP_STATE_PERFORMING_A_BACKUP; + for (i = 0; i < object_count; i++) { + /* get the object type and instance from the device object list */ + status = Device_Object_List_Identifier( + (uint32_t)(i + 1), &object_type, &object_instance); + if (status) { + Device_Objects_Property_List( + object_type, object_instance, &property_list); + (void)Device_Objects_Writable_Property_List( + object_type, object_instance, &writable_properties); + create_data.object_type = object_type; + create_data.object_instance = object_instance; + create_data.application_data_len = 0; + len = create_object_writable_properties_encode( + object_apdu, sizeof(object_apdu), &create_data, + property_list.Required.pList, property_list.Optional.pList, + property_list.Proprietary.pList, writable_properties, + Device_Read_Property); + if (len > 0) { +#if defined(BACFILE) + (void)bacfile_write_offset( + Configuration_Files[0], offset, &object_apdu[0], + (uint32_t)len); +#endif + offset += len; + } + } + } + Backup_State = BACKUP_STATE_IDLE; +#endif +} + +/** + * @brief Loop through the Device object-list property and export to + * a file as BACnet CreateObject services with List of Initial Values + * for every writable property + */ +void Device_Start_Restore(void) +{ +#if defined BACNET_BACKUP_RESTORE + BACNET_DATE_TIME bdateTime = { 0 }; + datetime_local(&bdateTime.date, &bdateTime.time, NULL, NULL); + bacapp_timestamp_datetime_set(&Last_Restore_Time, &bdateTime); + Backup_State = BACKUP_STATE_PREPARING_FOR_RESTORE; +#endif +} + #if defined(INTRINSIC_REPORTING) void Device_local_reporting(void) { diff --git a/src/bacnet/basic/object/device.h b/src/bacnet/basic/object/device.h index cc744121..0ab5268a 100644 --- a/src/bacnet/basic/object/device.h +++ b/src/bacnet/basic/object/device.h @@ -241,6 +241,31 @@ uint32_t Device_Interval_Offset(void); BACNET_STACK_EXPORT bool Device_Interval_Offset_Set(uint32_t value); +BACNET_STACK_EXPORT +bool Device_Configuration_File_Set(unsigned index, uint32_t instance); +BACNET_STACK_EXPORT +uint32_t Device_Configuration_File(unsigned index); +BACNET_STACK_EXPORT +uint16_t Device_Backup_Failure_Timeout(void); +BACNET_STACK_EXPORT +bool Device_Backup_Failure_Timeout_Set(uint16_t timeout); +BACNET_STACK_EXPORT +uint16_t Device_Backup_Preparation_Time(void); +BACNET_STACK_EXPORT +bool Device_Backup_Preparation_Time_Set(uint16_t time); +BACNET_STACK_EXPORT +uint16_t Device_Restore_Preparation_Time(void); +BACNET_STACK_EXPORT +bool Device_Restore_Preparation_Time_Set(uint16_t time); +BACNET_STACK_EXPORT +uint16_t Device_Restore_Completion_Time(void); +BACNET_STACK_EXPORT +bool Device_Restore_Completion_Time_Set(uint16_t time); +BACNET_STACK_EXPORT +BACNET_BACKUP_STATE Device_Backup_And_Restore_State(void); +BACNET_STACK_EXPORT +bool Device_Backup_And_Restore_State_Set(BACNET_BACKUP_STATE state); + BACNET_STACK_EXPORT void Device_Property_Lists( const int32_t **pRequired, @@ -305,6 +330,11 @@ bool Device_Create_Object(BACNET_CREATE_OBJECT_DATA *data); BACNET_STACK_EXPORT bool Device_Delete_Object(BACNET_DELETE_OBJECT_DATA *data); +BACNET_STACK_EXPORT +void Device_Start_Backup(void); +BACNET_STACK_EXPORT +void Device_Start_Restore(void); + BACNET_STACK_EXPORT unsigned Device_Count(void); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/server/bacnet_device.c b/src/bacnet/basic/server/bacnet_device.c index e5a218ef..702bedb6 100644 --- a/src/bacnet/basic/server/bacnet_device.c +++ b/src/bacnet/basic/server/bacnet_device.c @@ -1091,14 +1091,21 @@ static const int32_t Device_Properties_Optional[] = { #if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0) PROP_ACTIVE_COV_SUBSCRIPTIONS, #endif + PROP_SERIAL_NUMBER, + PROP_TIME_OF_DEVICE_RESTART, #if defined(BACNET_TIME_MASTER) PROP_TIME_SYNCHRONIZATION_RECIPIENTS, PROP_TIME_SYNCHRONIZATION_INTERVAL, PROP_ALIGN_INTERVALS, PROP_INTERVAL_OFFSET, #endif - PROP_SERIAL_NUMBER, - PROP_TIME_OF_DEVICE_RESTART, +#if defined(BACNET_BACKUP_RESTORE) + PROP_CONFIGURATION_FILES, + PROP_BACKUP_FAILURE_TIMEOUT, + PROP_BACKUP_PREPARATION_TIME, + PROP_RESTORE_PREPARATION_TIME, + PROP_BACKUP_AND_RESTORE_STATE, +#endif -1 }; @@ -1114,23 +1121,29 @@ static const int32_t Device_Properties_Proprietary[] = { static const int32_t Writable_Properties[] = { /* unordered list of writable properties */ PROP_OBJECT_IDENTIFIER, - PROP_NUMBER_OF_APDU_RETRIES, - PROP_APDU_TIMEOUT, - PROP_VENDOR_IDENTIFIER, - PROP_SYSTEM_STATUS, PROP_OBJECT_NAME, + PROP_SYSTEM_STATUS, + PROP_VENDOR_IDENTIFIER, + PROP_MODEL_NAME, PROP_LOCATION, PROP_DESCRIPTION, - PROP_MODEL_NAME, + PROP_APDU_TIMEOUT, + PROP_NUMBER_OF_APDU_RETRIES, + PROP_UTC_OFFSET, #if defined(BACNET_TIME_MASTER) PROP_TIME_SYNCHRONIZATION_INTERVAL, PROP_ALIGN_INTERVALS, PROP_INTERVAL_OFFSET, #endif - PROP_UTC_OFFSET, #if defined(BACDL_MSTP) PROP_MAX_INFO_FRAMES, PROP_MAX_MASTER, +#endif +#if defined(BACNET_BACKUP_RESTORE) + PROP_CONFIGURATION_FILES, + PROP_BACKUP_FAILURE_TIMEOUT, + PROP_BACKUP_PREPARATION_TIME, + PROP_RESTORE_PREPARATION_TIME, #endif PROP_TIME_OF_DEVICE_RESTART, -1 @@ -1249,6 +1262,59 @@ static uint32_t Interval_Offset_Minutes; /* Time_Synchronization_Recipients */ #endif +/* backup and restore */ +#if defined BACNET_BACKUP_RESTORE +/* number of backup files */ +#ifndef BACNET_BACKUP_FILE_COUNT +#if defined(BACFILE) +#define BACNET_BACKUP_FILE_COUNT 1 +#else +#define BACNET_BACKUP_FILE_COUNT 0 +#endif +#endif + +#if BACNET_BACKUP_FILE_COUNT +/* device A will read the Configuration_Files property of the Device object. + This property will be used to determine the files to read and in what + order the files will be read. The value of the Configuration_Files + property is not guaranteed to contain a complete or correct set of + configuration File object references before the backup request is + accepted by device B. */ +static uint32_t Configuration_Files[BACNET_BACKUP_FILE_COUNT]; +#endif +/* If the restore is successful, no other actions by device A shall + be required, and device B will update the Last_Restore_Time property + in its Device object. */ +static BACNET_TIMESTAMP Last_Restore_Time; +/* If device B does not receive any messages related to the restore + procedure from device A for the number of seconds specified + in the Backup_Failure_Timeout property of its Device object, + device B should assume that the restore procedure has been aborted, + and device B should exit restore mode.*/ +static uint16_t Backup_Failure_Timeout; +/* This property indicates the amount of time in seconds + that the device might remain unresponsive after the sending + of a ReinitializeDevice-ACK at the start of a backup procedure. + The device that initiated the backup shall either wait the + period of time specified by this property or be prepared + to encounter communication timeouts during this period. */ +static uint16_t Backup_Preparation_Time; +/* This property indicates the amount of time in seconds that the device + is allowed to remain unresponsive after the sending of a + ReinitializeDevice-ACK at the start of a restore procedure. + The restoring device shall either wait or be prepared to + encounter communication timeouts during this period.*/ +static uint16_t Restore_Preparation_Time; +/* This property indicates the amount of time in seconds that + the device is allowed to remain unresponsive after the sending + of a ReinitializeDevice-ACK at the end of a restore procedure. + The restoring device shall either wait or be prepared to + encounter communication timeouts during this period. */ +static uint16_t Restore_Completion_Time; +/* This property indicates a server device's backup and restore state. */ +static BACNET_BACKUP_STATE Backup_State = BACKUP_STATE_IDLE; +#endif + /** * @brief Sets the ReinitializeDevice password * @@ -1286,6 +1352,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) bool status = false; bool password_success = false; unsigned i; + size_t length; /* From 16.4.1.1.2 Password This optional parameter shall be a CharacterString of up to @@ -1294,7 +1361,12 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) is absent or if the password is incorrect. For those devices that do not require a password, this parameter shall be ignored.*/ if (characterstring_length(&Reinit_Password) > 0) { - if (characterstring_length(&rd_data->password) > 20) { + if (characterstring_encoding(&rd_data->password) == CHARACTER_UTF8) { + length = characterstring_utf8_length(&rd_data->password); + } else { + length = characterstring_length(&rd_data->password); + } + if (length > 20) { rd_data->error_class = ERROR_CLASS_SERVICES; rd_data->error_code = ERROR_CODE_PARAMETER_OUT_OF_RANGE; } else if (characterstring_same(&rd_data->password, &Reinit_Password)) { @@ -1309,19 +1381,46 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) if (password_success) { switch (rd_data->state) { case BACNET_REINIT_COLDSTART: - case BACNET_REINIT_WARMSTART: dcc_set_status_duration(COMMUNICATION_ENABLE, 0); - /* Note: you could use a mix of state - and password to multiple things */ /* note: you probably want to restart *after* the - simple ack has been sent from the return handler - so just set a flag from here */ + simple ack has been sent from the return handler + so just set a flag from here */ Reinitialize_State = rd_data->state; status = true; break; + case BACNET_REINIT_WARMSTART: + dcc_set_status_duration(COMMUNICATION_ENABLE, 0); + for (i = 0; i < Network_Port_Count(); i++) { + Network_Port_Changes_Pending_Activate( + Network_Port_Index_To_Instance(i)); + } + /* note: you probably want to restart *after* the + simple ack has been sent from the return handler + so just set a flag from here */ + Reinitialize_State = rd_data->state; + status = true; + break; +#if defined BACNET_BACKUP_RESTORE case BACNET_REINIT_STARTBACKUP: - case BACNET_REINIT_ENDBACKUP: + Device_Start_Backup(); + Reinitialize_State = rd_data->state; + status = true; + break; case BACNET_REINIT_STARTRESTORE: + Device_Start_Restore(); + Reinitialize_State = rd_data->state; + status = true; + break; + case BACNET_REINIT_ENDBACKUP: + case BACNET_REINIT_ENDRESTORE: + case BACNET_REINIT_ABORTRESTORE: + Reinitialize_State = rd_data->state; + status = true; + break; +#else + case BACNET_REINIT_STARTBACKUP: + case BACNET_REINIT_STARTRESTORE: + case BACNET_REINIT_ENDBACKUP: case BACNET_REINIT_ENDRESTORE: case BACNET_REINIT_ABORTRESTORE: if (dcc_communication_disabled()) { @@ -1333,6 +1432,7 @@ bool Device_Reinitialize(BACNET_REINITIALIZE_DEVICE_DATA *rd_data) ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED; } break; +#endif case BACNET_REINIT_ACTIVATE_CHANGES: /* note: activate changes *after* the simple ack is sent */ for (i = 0; i < Network_Port_Count(); i++) { @@ -1390,7 +1490,9 @@ bool Device_Set_Object_Instance_Number(uint32_t object_id) bool status = true; /* return value */ if (object_id <= BACNET_MAX_INSTANCE) { + /* Make the change and update the database revision */ Object_Instance_Number = object_id; + Device_Inc_Database_Revision(); } else { status = false; } @@ -1519,16 +1621,58 @@ BACNET_DEVICE_STATUS Device_System_Status(void) int Device_Set_System_Status(BACNET_DEVICE_STATUS status, bool local) { - /*return value - 0 = ok, -1 = bad value, -2 = not allowed */ - int result = -1; + int result = 0; /*return value - 0 = ok, -1 = bad value, -2 = not allowed */ - (void)local; - if (status < MAX_DEVICE_STATUS) { - System_Status = status; - result = 0; + /* We limit the options available depending on whether the source is + * internal or external. */ + if (local) { + switch (status) { + case STATUS_OPERATIONAL: + case STATUS_OPERATIONAL_READ_ONLY: + case STATUS_DOWNLOAD_REQUIRED: + case STATUS_DOWNLOAD_IN_PROGRESS: + case STATUS_NON_OPERATIONAL: +#if defined BACNET_BACKUP_RESTORE + case STATUS_BACKUP_IN_PROGRESS: +#endif + System_Status = status; + break; +#ifndef BACNET_BACKUP_RESTORE + case STATUS_BACKUP_IN_PROGRESS: + result = -2; + break; +#endif + default: + result = -1; + break; + } + } else { + switch (status) { + case STATUS_OPERATIONAL: + case STATUS_OPERATIONAL_READ_ONLY: + case STATUS_NON_OPERATIONAL: + /* Allow these for the moment as a way to easily alter + * overall device operation. The lack of password protection + * or other authentication makes allowing writes to this + * property a risky facility to provide. */ + System_Status = status; + break; + case STATUS_DOWNLOAD_REQUIRED: + case STATUS_DOWNLOAD_IN_PROGRESS: + case STATUS_BACKUP_IN_PROGRESS: + /* Don't allow outsider set these - they should probably + * be set if the device config is incomplete or + * corrupted or perhaps after some sort of operator + * wipe operation. */ + result = -2; + break; + default: + result = -1; + break; + } } - return result; + return (result); } const char *Device_Vendor_Name(void) @@ -1544,6 +1688,11 @@ bool Device_Set_Vendor_Name(const char *name, size_t length) return true; } +/** Returns the Vendor ID for this Device. + * See the assignments at + * https://bacnet.org/assigned-vendor-ids/ + * @return The Vendor ID of this Device. + */ uint16_t Device_Vendor_Identifier(void) { return Vendor_Identifier; @@ -1670,7 +1819,11 @@ uint8_t Device_Protocol_Revision(void) BACNET_SEGMENTATION Device_Segmentation_Supported(void) { +#if BACNET_SEGMENTATION_ENABLED + return SEGMENTATION_BOTH; +#else return SEGMENTATION_NONE; +#endif } /** @@ -1824,7 +1977,7 @@ bool Device_Valid_Object_Name( for (i = 1; i <= max_objects; i++) { check_id = Device_Object_List_Identifier(i, &type, &instance); if (check_id) { - pObject = Device_Object_Functions_Find((BACNET_OBJECT_TYPE)type); + pObject = Device_Object_Functions_Find(type); if ((pObject != NULL) && (pObject->Object_Name != NULL) && (pObject->Object_Name(instance, &object_name2) && characterstring_same(object_name1, &object_name2))) { @@ -1854,7 +2007,7 @@ bool Device_Valid_Object_Id( bool status = false; /* return value */ struct object_functions *pObject = NULL; - pObject = Device_Object_Functions_Find((BACNET_OBJECT_TYPE)object_type); + pObject = Device_Object_Functions_Find(object_type); if ((pObject != NULL) && (pObject->Object_Valid_Instance != NULL)) { status = pObject->Object_Valid_Instance(object_instance); } @@ -1877,8 +2030,13 @@ bool Device_Object_Name_Copy( bool found = false; pObject = Device_Object_Functions_Find(object_type); - if ((pObject != NULL) && (pObject->Object_Name != NULL)) { - found = pObject->Object_Name(object_instance, object_name); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_Name) { + found = pObject->Object_Name(object_instance, object_name); + } + } } return found; @@ -1989,6 +2147,187 @@ uint32_t Device_Interval_Offset(void) } #endif +bool Device_Configuration_File_Set(unsigned index, uint32_t instance) +{ + bool status = false; +#if BACNET_BACKUP_FILE_COUNT + if (index < BACNET_BACKUP_FILE_COUNT) { + Configuration_Files[index] = instance; + status = true; + } +#else + (void)index; + (void)instance; +#endif + + return status; +} + +uint32_t Device_Configuration_File(unsigned index) +{ + uint32_t instance = BACNET_MAX_INSTANCE + 1; + +#if BACNET_BACKUP_FILE_COUNT + if (index < BACNET_BACKUP_FILE_COUNT) { + instance = Configuration_Files[index]; + } +#else + (void)index; +#endif + + return instance; +} + +/** + * @brief Encode a BACnetARRAY property element + * @param object_instance [in] BACnet network port object instance number + * @param array_index [in] array index requested: + * 0 to N for individual array members + * @param apdu [out] Buffer in which the APDU contents are built, or NULL to + * return the length of buffer if it had been built + * @return The length of the apdu encoded or + * BACNET_STATUS_ERROR for ERROR_CODE_INVALID_ARRAY_INDEX + */ +int Device_Configuration_File_Encode( + uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu) +{ + int apdu_len = BACNET_STATUS_ERROR; + + (void)object_instance; +#if BACNET_BACKUP_FILE_COUNT + if (array_index < BACNET_BACKUP_FILE_COUNT) { + apdu_len = encode_application_object_id( + apdu, OBJECT_FILE, Configuration_Files[array_index]); + } +#else + (void)array_index; + (void)apdu; +#endif + + return apdu_len; +} + +#if defined BACNET_BACKUP_RESTORE +/** + * @brief Decode a BACnetLIST property element to determine the element length + * @param object_instance [in] BACnet object instance number + * @param apdu [in] Buffer in which the APDU contents are extracted + * @param apdu_size [in] The size of the APDU buffer + * @return The length of the decoded apdu, or BACNET_STATUS_ERROR on error + */ +static int Device_Configuration_File_Length( + uint32_t object_instance, uint8_t *apdu, size_t apdu_size) +{ + (void)object_instance; + return bacnet_object_id_application_decode(apdu, apdu_size, NULL, NULL); +} + +/** + * @brief Write a value to a BACnetLIST property element value + * using a BACnetARRAY write utility function + * @param object_instance [in] BACnet object instance number + * @param array_index [in] array index to write: + * 0=array size, 1 to N for individual array members + * @param application_data [in] encoded element value + * @param application_data_len [in] The size of the encoded element value + * @return BACNET_ERROR_CODE value + */ +static BACNET_ERROR_CODE Device_Configuration_File_Write( + uint32_t object_instance, + BACNET_ARRAY_INDEX array_index, + uint8_t *application_data, + size_t application_data_len) +{ + BACNET_ERROR_CODE error_code = ERROR_CODE_UNKNOWN_OBJECT; + uint32_t instance = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + int len = 0; + + (void)object_instance; + if (array_index == 0) { + /* This array is not required to be resizable + through BACnet write services */ + error_code = ERROR_CODE_WRITE_ACCESS_DENIED; + } else if (array_index <= BACNET_BACKUP_FILE_COUNT) { + len = bacnet_object_id_application_decode( + application_data, application_data_len, &object_type, &instance); + if (len > 0) { + if ((object_type == OBJECT_FILE) && + Device_Valid_Object_Id(object_type, instance)) { + if (Device_Configuration_File_Set(array_index - 1, instance)) { + error_code = ERROR_CODE_SUCCESS; + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } else { + error_code = ERROR_CODE_INVALID_DATA_TYPE; + } + } else { + error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + + return error_code; +} + +uint16_t Device_Backup_Failure_Timeout(void) +{ + return Backup_Failure_Timeout; +} + +bool Device_Backup_Failure_Timeout_Set(uint16_t timeout) +{ + Backup_Failure_Timeout = timeout; + return true; +} + +uint16_t Device_Backup_Preparation_Time(void) +{ + return Backup_Preparation_Time; +} + +bool Device_Backup_Preparation_Time_Set(uint16_t time) +{ + Backup_Preparation_Time = time; + return true; +} + +uint16_t Device_Restore_Preparation_Time(void) +{ + return Restore_Preparation_Time; +} + +bool Device_Restore_Preparation_Time_Set(uint16_t time) +{ + Restore_Preparation_Time = time; + return true; +} + +uint16_t Device_Restore_Completion_Time(void) +{ + return Restore_Completion_Time; +} + +bool Device_Restore_Completion_Time_Set(uint16_t time) +{ + Restore_Completion_Time = time; + return true; +} + +BACNET_BACKUP_STATE Device_Backup_And_Restore_State(void) +{ + return Backup_State; +} + +bool Device_Backup_And_Restore_State_Set(BACNET_BACKUP_STATE state) +{ + Backup_State = state; + return true; +} +#endif + /** * ReadProperty handler for this object. For the given ReadProperty * data, the application_data is loaded or the error flags are set. @@ -2128,8 +2467,12 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) rpdata->object_instance, rpdata->array_index, Device_Object_List_Element_Encode, count, apdu, apdu_max); if (apdu_len == BACNET_STATUS_ABORT) { +#if BACNET_SEGMENTATION_ENABLED + rpdata->error_code = ERROR_CODE_ABORT_BUFFER_OVERFLOW; +#else rpdata->error_code = ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; +#endif } else if (apdu_len == BACNET_STATUS_ERROR) { rpdata->error_class = ERROR_CLASS_PROPERTY; rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; @@ -2187,6 +2530,45 @@ int Device_Read_Property_Local(BACNET_READ_PROPERTY_DATA *rpdata) encode_application_unsigned(&apdu[0], Device_Interval_Offset()); break; #endif +#if defined(BACNET_BACKUP_RESTORE) + case PROP_CONFIGURATION_FILES: + apdu_len = bacnet_array_encode( + rpdata->object_instance, rpdata->array_index, + Device_Configuration_File_Encode, BACNET_BACKUP_FILE_COUNT, + &apdu[0], apdu_max); + if (apdu_len == BACNET_STATUS_ABORT) { +#if BACNET_SEGMENTATION_ENABLED + rpdata->error_code = ERROR_CODE_ABORT_BUFFER_OVERFLOW; +#else + rpdata->error_code = + ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED; +#endif + } else if (apdu_len == BACNET_STATUS_ERROR) { + rpdata->error_class = ERROR_CLASS_PROPERTY; + rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX; + } + break; + case PROP_BACKUP_FAILURE_TIMEOUT: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Backup_Failure_Timeout()); + break; + case PROP_BACKUP_PREPARATION_TIME: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Backup_Preparation_Time()); + break; + case PROP_RESTORE_PREPARATION_TIME: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Restore_Preparation_Time()); + break; + case PROP_RESTORE_COMPLETION_TIME: + apdu_len = encode_application_unsigned( + &apdu[0], Device_Restore_Completion_Time()); + break; + case PROP_BACKUP_AND_RESTORE_STATE: + apdu_len = encode_application_enumerated( + &apdu[0], Device_Backup_And_Restore_State()); + break; +#endif #if (BACNET_COV_SUBSCRIPTIONS_SIZE > 0) case PROP_ACTIVE_COV_SUBSCRIPTIONS: if ((apdu_len = handler_cov_encode_subscriptions( @@ -2469,6 +2851,60 @@ bool Device_Write_Property_Local(BACNET_WRITE_PROPERTY_DATA *wp_data) } } break; +#endif +#if defined(BACNET_BACKUP_RESTORE) + case PROP_BACKUP_FAILURE_TIMEOUT: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + if (value.type.Unsigned_Int <= UINT16_MAX) { + Device_Backup_Failure_Timeout_Set( + (uint16_t)value.type.Unsigned_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_BACKUP_PREPARATION_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + if (value.type.Unsigned_Int <= UINT16_MAX) { + Device_Backup_Preparation_Time_Set( + (uint16_t)value.type.Unsigned_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_RESTORE_PREPARATION_TIME: + status = write_property_type_valid( + wp_data, &value, BACNET_APPLICATION_TAG_UNSIGNED_INT); + if (status) { + if (value.type.Unsigned_Int <= UINT16_MAX) { + Device_Restore_Preparation_Time_Set( + (uint16_t)value.type.Unsigned_Int); + status = true; + } else { + wp_data->error_class = ERROR_CLASS_PROPERTY; + wp_data->error_code = ERROR_CODE_VALUE_OUT_OF_RANGE; + } + } + break; + case PROP_CONFIGURATION_FILES: + wp_data->error_code = bacnet_array_write( + wp_data->object_instance, wp_data->array_index, + Device_Configuration_File_Length, + Device_Configuration_File_Write, BACNET_BACKUP_FILE_COUNT, + wp_data->application_data, wp_data->application_data_len); + if (wp_data->error_code == ERROR_CODE_SUCCESS) { + status = true; + } + break; #endif case PROP_UTC_OFFSET: status = write_property_type_valid( @@ -2759,7 +3195,9 @@ int Device_Remove_List_Element(BACNET_LIST_ELEMENT_DATA *list_element) if (pObject->Object_Remove_List_Element) { status = pObject->Object_Remove_List_Element(list_element); if (status) { - Device_Remove_List_Element_Callback(list_element); + if (Device_Remove_List_Element_Callback) { + (void)Device_Remove_List_Element_Callback(list_element); + } } } else { list_element->error_class = ERROR_CLASS_PROPERTY; @@ -2932,6 +3370,102 @@ bool Device_Delete_Object(BACNET_DELETE_OBJECT_DATA *data) return status; } +/** + * @brief Loop through the Device object-list property and export to + * a file as BACnet CreateObject services with List of Initial Values + * for every writable property + */ +void Device_Start_Backup(void) +{ +#if defined BACNET_BACKUP_RESTORE + size_t i = 0; + uint32_t object_count = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t object_instance = 0; + const int32_t *writable_properties = NULL; + struct special_property_list_t property_list = { 0 }; + uint8_t object_apdu[MAX_APDU] = { 0 }; + BACNET_CREATE_OBJECT_DATA create_data = { 0 }; + bool status = false; + int32_t len = 0, offset = 0; + + Backup_State = BACKUP_STATE_PREPARING_FOR_BACKUP; + object_count = Device_Object_List_Count(); + Backup_State = BACKUP_STATE_PERFORMING_A_BACKUP; + for (i = 0; i < object_count; i++) { + /* get the object type and instance from the device object list */ + status = Device_Object_List_Identifier( + (uint32_t)(i + 1), &object_type, &object_instance); + if (status) { + Device_Objects_Property_List( + object_type, object_instance, &property_list); + (void)Device_Objects_Writable_Property_List( + object_type, object_instance, &writable_properties); + create_data.object_type = object_type; + create_data.object_instance = object_instance; + create_data.application_data_len = 0; + len = create_object_writable_properties_encode( + object_apdu, sizeof(object_apdu), &create_data, + property_list.Required.pList, property_list.Optional.pList, + property_list.Proprietary.pList, writable_properties, + Device_Read_Property); + if (len > 0) { +#if defined(BACFILE) + (void)bacfile_write_offset( + Configuration_Files[0], offset, &object_apdu[0], + (uint32_t)len); +#endif + offset += len; + } + } + } + Backup_State = BACKUP_STATE_IDLE; +#endif +} + +/** + * @brief Loop through the Device object-list property and export to + * a file as BACnet CreateObject services with List of Initial Values + * for every writable property + */ +void Device_Start_Restore(void) +{ +#if defined BACNET_BACKUP_RESTORE + BACNET_DATE_TIME bdateTime = { 0 }; + datetime_local(&bdateTime.date, &bdateTime.time, NULL, NULL); + bacapp_timestamp_datetime_set(&Last_Restore_Time, &bdateTime); + Backup_State = BACKUP_STATE_PREPARING_FOR_RESTORE; +#endif +} + +#if defined(INTRINSIC_REPORTING) +void Device_local_reporting(void) +{ + struct object_functions *pObject = NULL; + uint32_t objects_count = 0; + uint32_t object_instance = 0; + BACNET_OBJECT_TYPE object_type = OBJECT_NONE; + uint32_t idx = 0; + + objects_count = Device_Object_List_Count(); + + /* loop for all objects */ + for (idx = 1; idx <= objects_count; idx++) { + Device_Object_List_Identifier(idx, &object_type, &object_instance); + + pObject = Device_Object_Functions_Find(object_type); + if (pObject != NULL) { + if (pObject->Object_Valid_Instance && + pObject->Object_Valid_Instance(object_instance)) { + if (pObject->Object_Intrinsic_Reporting) { + pObject->Object_Intrinsic_Reporting(object_instance); + } + } + } + } +} +#endif + /** Looks up the requested Object to see if the functionality is supported. * @ingroup ObjHelpers * @param [in] The object type to be looked up. diff --git a/src/bacnet/create_object.c b/src/bacnet/create_object.c index 5cf9f14e..1f260de1 100644 --- a/src/bacnet/create_object.c +++ b/src/bacnet/create_object.c @@ -14,6 +14,7 @@ #include "bacnet/bacdcode.h" #include "bacnet/bacerror.h" #include "bacnet/create_object.h" +#include "bacnet/proplist.h" /** * @brief Encode one value for CreateObject List-of-Initial-Values @@ -31,6 +32,103 @@ int create_object_encode_initial_value( return bacapp_property_value_encode(apdu, value); } +/** + * @brief Encode one value for CreateObject List-of-Initial-Values + * + * 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 + * @param offset Offset into the buffer to start encoding + * @param value Pointer to the property value used for encoding + * @return Bytes encoded or zero on error. + */ +int create_object_encode_initial_value_data( + uint8_t *apdu, int offset, BACNET_CREATE_OBJECT_PROPERTY_VALUE *value) +{ + int len = 0, apdu_len = 0, i = 0; + + if (apdu) { + apdu += offset; + } + /* property-identifier [0] BACnetPropertyIdentifier */ + len = encode_context_enumerated(apdu, 0, value->propertyIdentifier); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* property-array-index [1] Unsigned OPTIONAL */ + if (value->propertyArrayIndex != BACNET_ARRAY_ALL) { + len = encode_context_unsigned(apdu, 1, value->propertyArrayIndex); + apdu_len += len; + if (apdu) { + apdu += len; + } + } + /* property-value [2] ABSTRACT-SYNTAX.&Type */ + len = encode_opening_tag(apdu, 2); + apdu_len += len; + if (apdu) { + apdu += len; + } + len = value->application_data_len; + if (apdu) { + /* data */ + for (i = 0; i < len; i++) { + apdu[i] = value->application_data[i]; + } + } + apdu_len += len; + if (apdu) { + apdu += len; + } + len = encode_closing_tag(apdu, 2); + apdu_len += len; + if (apdu) { + apdu += len; + } + /* priority [3] Unsigned (1..16) OPTIONAL */ + if (value->priority != BACNET_NO_PRIORITY) { + len = encode_context_unsigned(apdu, 3, value->priority); + apdu_len += len; + } + + return apdu_len; +} + +/** + * @brief Encode one value for CreateObject List-of-Initial-Values + * @param apdu Pointer to the buffer for encoding into + * @param apdu_size number of bytes available in the buffer + * @param data Pointer to the service data used for encoding values + * @return number of bytes encoded, or zero if unable to encode or too large + */ +size_t create_object_initial_value_data_encode( + uint8_t *apdu, + size_t apdu_size, + int offset, + BACNET_CREATE_OBJECT_PROPERTY_VALUE *value) +{ + size_t apdu_len = 0; /* total length of the apdu, return value */ + + apdu_len = create_object_encode_initial_value_data(NULL, offset, value); + if (apdu_len > apdu_size) { + apdu_len = 0; + } else { + apdu_len = create_object_encode_initial_value_data(apdu, offset, value); + } + + return apdu_len; +} + /** * @brief Decode one BACnetPropertyValue value * @@ -749,3 +847,149 @@ bool create_object_process( return status; } + +/** + * @brief Encode the CreateObject service request with writable properties + * encoded in the List of Initial Values. + * @param apdu [in] The APDU buffer. + * @param apdu_size [in] The size of the APDU buffer. + * @param data [in,out] The Create Object data containing the request details. + * @param optional_properties - list of optional properties + * @param proprietary_properties - list of proprietary properties + * @param writable_properties - list of writable properties + * @param read_property [in] Function pointer to the Read Property handler. + * @return Number of bytes encoded + */ +int create_object_writable_properties_encode( + uint8_t *apdu, + size_t apdu_size, + BACNET_CREATE_OBJECT_DATA *data, + const int32_t *required_properties, + const int32_t *optional_properties, + const int32_t *proprietary_properties, + const int32_t *writable_properties, + read_property_function read_property) +{ + int apdu_len = 0, len = 0; + size_t j = 0, priority = 0, a = 0; + BACNET_UNSIGNED_INTEGER array_count = 0; + uint32_t writable_property_count = 0; + uint8_t property_apdu[MAX_APDU] = { 0 }; + BACNET_READ_PROPERTY_DATA rpdata = { 0 }; + BACNET_CREATE_OBJECT_PROPERTY_VALUE property_value = { 0 }; + + if (!data) { + return 0; + } + writable_property_count = property_list_count(writable_properties); + if (writable_property_count == 0) { + /* no writable properties + create the object absent the List of Initial Values */ + apdu_len = create_object_service_request_encode(apdu, apdu_size, data); + } else { + data->application_data_len = 0; + for (j = 0; j < writable_property_count; j++) { + /* read each writable property value from our device */ + rpdata.application_data = property_apdu; + rpdata.application_data_len = sizeof(property_apdu); + rpdata.object_type = data->object_type; + rpdata.object_instance = data->object_instance; + rpdata.object_property = writable_properties[j]; + if (property_list_bacnet_array_member( + rpdata.object_type, rpdata.object_property)) { + /* array properties - get the size */ + rpdata.array_index = 0; + len = 0; + if (read_property) { + len = read_property(&rpdata); + } + if (len <= 0) { + continue; + } + /* convert to integer */ + len = bacnet_unsigned_application_decode( + &property_apdu[0], (uint32_t)len, &array_count); + if (len <= 0) { + continue; + } + for (a = 1; a <= array_count; a++) { + rpdata.array_index = a; + len = 0; + if (read_property) { + len = read_property(&rpdata); + } + if (len <= 0) { + continue; + } + property_value.propertyIdentifier = writable_properties[j]; + property_value.propertyArrayIndex = a; + property_value.priority = BACNET_NO_PRIORITY; + property_value.application_data_len = len; + property_value.application_data = property_apdu; + len = create_object_initial_value_data_encode( + data->application_data, sizeof(data->application_data), + data->application_data_len, &property_value); + if (len > 0) { + data->application_data_len += len; + } + } + } else if ( + property_list_commandable_member( + rpdata.object_type, rpdata.object_property) && + property_lists_member( + required_properties, optional_properties, + proprietary_properties, PROP_PRIORITY_ARRAY)) { + /* convert the priority-array index to + present-value and priority */ + for (priority = 1; priority <= BACNET_MAX_PRIORITY; + priority++) { + rpdata.object_property = PROP_PRIORITY_ARRAY; + rpdata.array_index = (uint32_t)priority; + len = 0; + if (read_property) { + len = read_property(&rpdata); + } + if (len <= 0) { + continue; + } + property_value.propertyIdentifier = PROP_PRESENT_VALUE; + property_value.propertyArrayIndex = BACNET_ARRAY_ALL; + property_value.priority = priority; + property_value.application_data_len = len; + property_value.application_data = property_apdu; + len = create_object_initial_value_data_encode( + data->application_data, sizeof(data->application_data), + data->application_data_len, &property_value); + if (len > 0) { + data->application_data_len += len; + } + } + } else { + /* non array, non-priority properties */ + rpdata.array_index = BACNET_ARRAY_ALL; + len = 0; + if (read_property) { + len = read_property(&rpdata); + } + if (len <= 0) { + continue; + } + property_value.propertyIdentifier = rpdata.object_property; + property_value.propertyArrayIndex = BACNET_ARRAY_ALL; + property_value.priority = BACNET_NO_PRIORITY; + property_value.application_data_len = len; + property_value.application_data = property_apdu; + len = create_object_initial_value_data_encode( + data->application_data, sizeof(data->application_data), + data->application_data_len, &property_value); + if (len > 0) { + data->application_data_len += len; + } + } + } + /* writable properties - create object with List of Initial Values */ + apdu_len = create_object_service_request_encode(apdu, apdu_size, data); + } + + return apdu_len; +} diff --git a/src/bacnet/create_object.h b/src/bacnet/create_object.h index a7f24a8d..c60ecf6b 100644 --- a/src/bacnet/create_object.h +++ b/src/bacnet/create_object.h @@ -16,6 +16,7 @@ #include "bacnet/bacdcode.h" #include "bacnet/bacapp.h" #include "bacnet/delete_object.h" +#include "bacnet/rp.h" #include "bacnet/wp.h" #ifndef BACNET_CREATE_OBJECT_LIST_VALUES_ENABLED @@ -87,6 +88,20 @@ extern "C" { BACNET_STACK_EXPORT int create_object_encode_initial_value( uint8_t *apdu, int offset, const BACNET_PROPERTY_VALUE *value); +BACNET_STACK_EXPORT +int create_object_encode_initial_value_data( + uint8_t *apdu, int offset, BACNET_CREATE_OBJECT_PROPERTY_VALUE *value); +BACNET_STACK_EXPORT +size_t create_object_initial_value_data_encode( + uint8_t *apdu, + size_t apdu_size, + int offset, + BACNET_CREATE_OBJECT_PROPERTY_VALUE *value); +BACNET_STACK_EXPORT +int create_object_decode_initial_value( + const uint8_t *apdu, + uint32_t apdu_size, + BACNET_CREATE_OBJECT_PROPERTY_VALUE *value); BACNET_STACK_EXPORT size_t create_object_service_request_encode( @@ -132,6 +147,16 @@ bool create_object_process( create_object_function create_object, delete_object_function delete_object, write_property_function write_property); +BACNET_STACK_EXPORT +int create_object_writable_properties_encode( + uint8_t *apdu, + size_t apdu_size, + BACNET_CREATE_OBJECT_DATA *data, + const int32_t *required_properties, + const int32_t *optional_properties, + const int32_t *proprietary_properties, + const int32_t *writable_properties, + read_property_function read_property); #ifdef __cplusplus } diff --git a/src/bacnet/proplist.c b/src/bacnet/proplist.c index e7c82ad6..49f822df 100644 --- a/src/bacnet/proplist.c +++ b/src/bacnet/proplist.c @@ -95,6 +95,9 @@ bool property_lists_member( * * @param rpdata - ReadProperty data, including requested data and * data for the reply, or error response. + * @param pListRequired - list of required properties + * @param pListOptional - list of optional properties + * @param pListProprietary - list of proprietary properties * * @return number of APDU bytes in the response, or * BACNET_STATUS_ERROR on error.