diff --git a/Makefile b/Makefile index 3edbafe9..961408b4 100644 --- a/Makefile +++ b/Makefile @@ -81,6 +81,10 @@ readbdt: readfdt: $(MAKE) -s -C apps $@ +.PHONY: writebdt +writebdt: + $(MAKE) -s -C apps $@ + .PHONY: server server: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index ac4ab0e9..8b94cb93 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -188,7 +188,7 @@ SUBDIRS = readprop writeprop readfile writefile reinit server dcc \ writepropm uptransfer getevent uevent abort error event ack-alarm ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) - SUBDIRS += whoisrouter iamrouter initrouter readbdt readfdt + SUBDIRS += whoisrouter iamrouter initrouter readbdt readfdt writebdt endif ifeq (${BACNET_PORT},linux) @@ -281,6 +281,10 @@ uevent: whois: $(MAKE) -b -C $@ +.PHONY: writebdt +writebdt: + $(MAKE) -b -C $@ + .PHONY: router router: $(MAKE) -s -b -C $@ diff --git a/apps/writebdt/Makefile b/apps/writebdt/Makefile new file mode 100644 index 00000000..ec5b3ab4 --- /dev/null +++ b/apps/writebdt/Makefile @@ -0,0 +1,47 @@ +#Makefile to build BACnet Application for the GCC Port + +TARGET = bacwbdt +# 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 +BACNET_BASIC_SRC += \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_apdu.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_iam.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_noserv.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_rp.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/h_whois.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/s_iam.c \ + $(BACNET_SRC_DIR)/bacnet/basic/service/s_whois.c + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +SRCS = $(SRC) $(BACNET_SRC) $(BACNET_BASIC_SRC) $(BACNET_PORT_SRC) + +OBJS += ${SRCS:.c=.o} + +.PHONY: all +all: Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +.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 + +.PHONY: include +include: .depend + diff --git a/apps/writebdt/main.c b/apps/writebdt/main.c new file mode 100644 index 00000000..d2cf88d8 --- /dev/null +++ b/apps/writebdt/main.c @@ -0,0 +1,246 @@ +/************************************************************************** + * + * Copyright (C) 2020 Steve Karg + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + *********************************************************************/ + +/* command line tool that sends a BACnet BVLC message, and displays the reply */ +#include +#include +#include +#include +#include /* for time */ +#include +#include /* for toupper */ +#include "bacnet/bactext.h" +#include "bacnet/iam.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/bvlc.h" +/* some demo stuff needed */ +#ifndef DEBUG_ENABLED +#define DEBUG_ENABLED 0 +#endif +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/services.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 }; + +/* targets interpreted from the command line options */ +static BACNET_IP_ADDRESS Target_BBMD_Address; +#ifndef MAX_BBMD_ENTRIES +#define MAX_BBMD_ENTRIES 128 +#endif +static BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY + BBMD_Table_Entry[MAX_BBMD_ENTRIES]; + +static bool Error_Detected = false; + +static void MyAbortHandler( + BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t abort_reason, bool server) +{ + /* FIXME: verify src and invoke id */ + (void)src; + (void)invoke_id; + (void)server; + printf("BACnet Abort: %s\r\n", bactext_abort_reason_name(abort_reason)); + Error_Detected = true; +} + +static void MyRejectHandler( + BACNET_ADDRESS *src, uint8_t invoke_id, uint8_t reject_reason) +{ + /* FIXME: verify src and invoke id */ + (void)src; + (void)invoke_id; + printf("BACnet Reject: %s\r\n", bactext_reject_reason_name(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); + /* 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 reply (request) coming back */ + apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, handler_i_am_add); + /* handle any errors coming back */ + apdu_set_abort_handler(MyAbortHandler); + apdu_set_reject_handler(MyRejectHandler); +} + +int main(int argc, char *argv[]) +{ + BACNET_ADDRESS src = { 0 }; /* address where message came from */ + uint16_t pdu_len = 0; + unsigned timeout = 100; /* milliseconds */ + time_t total_seconds = 0; + time_t elapsed_seconds = 0; + time_t last_seconds = 0; + time_t current_seconds = 0; + time_t timeout_seconds = 0; + long port = 0; + int argi = 0; + unsigned bdti = 0; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry; + unsigned a[4] = { 0 }, p = 0, m[4] = { 0 }; + int c = 0; + uint16_t result_code = 0; + + + if (argc < 2) { + printf("Usage: %s IP port []\r\n", + filename_remove_path(argv[0])); + return 0; + } + if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) { + printf( + "Send a Write-Broadcast-Distribution-Table message to a BBMD.\r\n" + "\r\n" + "IP:\r\n" + "IP address of the BBMD in dotted decimal notation\r\n" + "[port]\r\n" + "optional BACnet/IP port number (default=47808=0xBAC0)\r\n" + "\r\n" + "To send a Write-Broadcast-Distribution-Table message to a BBMD\r\n" + "at 192.168.0.1 using port 47808 table entry 10.0.0.1:47808\r\n" + "%s 192.168.0.1 47808 10.0.0.1:47808\r\n", + filename_remove_path(argv[0])); + return 0; + } + /* decode the command line parameters */ + if (argc > 1) { + argi = 1; + if (!bip_get_addr_by_name(argv[argi], &Target_BBMD_Address)) { + fprintf(stderr, "IP=%s - failed to convert address.\r\n", + argv[argi]); + return 1; + } + } + if (argc > 2) { + argi = 2; + port = strtol(argv[argi], NULL, 0); + if ((port > 0) && (port <= 65535)) { + Target_BBMD_Address.port = (uint16_t)port; + } else { + fprintf( + stderr, "port=%ld - port must be between 0-65535.\r\n", port); + return 1; + } + } else { + Target_BBMD_Address.port = 0xBAC0U; + } + bvlc_broadcast_distribution_table_link_array( + &BBMD_Table_Entry[0], MAX_BBMD_ENTRIES); + bdti = 0; + argi = 3; + while (argc > argi) { + bdt_entry = &BBMD_Table_Entry[bdti]; + c = sscanf(argv[argi], "%3u.%3u.%3u.%3u:%5u:%3u.%3u.%3u.%3u", + &a[0], &a[1], &a[2], &a[3], &p, &m[0], &m[1], &m[2], &m[3]); + if ((c == 4) || (c == 5) || (c == 9)) { + bvlc_address_set(&bdt_entry->dest_address, a[0], a[1], a[2], a[3]); + if ((c == 5) || (c == 9)) { + bdt_entry->dest_address.port = (uint16_t)p; + } else { + bdt_entry->dest_address.port = 0xBAC0U; + } + if (c == 9) { + bvlc_broadcast_distribution_mask_set( + &bdt_entry->broadcast_mask, m[0], m[1], m[2], m[3]); + } else { + bvlc_broadcast_distribution_mask_set( + &bdt_entry->broadcast_mask, 255, 255, 255, 255); + } + bdt_entry->valid = true; + bdti++; + if (bdti >= MAX_BBMD_ENTRIES) { + break; + } + } + argi++; + } + /* setup my info */ + Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); + Init_Service_Handlers(); + address_init(); + dlenv_init(); + atexit(datalink_cleanup); + /* configure the timeout values */ + last_seconds = time(NULL); + timeout_seconds = apdu_timeout() / 1000; + /* send the request */ + bvlc_bbmd_write_bdt(&Target_BBMD_Address, &BBMD_Table_Entry[0]); + /* loop forever */ + for (;;) { + /* increment timer - exit if timed out */ + current_seconds = time(NULL); + /* returns 0 bytes on timeout */ + pdu_len = datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, timeout); + if (bvlc_get_function_code() != BVLC_INVALID) { + if (bvlc_get_function_code() == BVLC_RESULT) { + result_code = bvlc_get_last_result(); + printf("BVLC Result: %s\n", + bvlc_result_code_name(result_code)); + break; + } + bvlc_set_function_code(BVLC_INVALID); + } + /* process */ + if (pdu_len) { + npdu_handler(&src, &Rx_Buf[0], pdu_len); + } + if (Error_Detected) { + break; + } + /* increment timer - exit if timed out */ + elapsed_seconds = current_seconds - last_seconds; + if (elapsed_seconds) { + datalink_maintenance_timer(elapsed_seconds); + } + total_seconds += elapsed_seconds; + if (total_seconds > timeout_seconds) { + break; + } + /* keep track of time for next check */ + last_seconds = current_seconds; + } + + return 0; +} diff --git a/src/bacnet/basic/bbmd/h_bbmd.c b/src/bacnet/basic/bbmd/h_bbmd.c index e53be901..412fc2b9 100644 --- a/src/bacnet/basic/bbmd/h_bbmd.c +++ b/src/bacnet/basic/bbmd/h_bbmd.c @@ -66,9 +66,9 @@ /** enable debugging */ static bool BVLC_Debug = false; /** result from a client request */ -static uint16_t BVLC_Result_Code = BVLC_RESULT_SUCCESSFUL_COMPLETION; +static uint16_t BVLC_Result_Code = BVLC_RESULT_INVALID; /** incoming function */ -static uint8_t BVLC_Function_Code = BVLC_RESULT; +static uint8_t BVLC_Function_Code = BVLC_INVALID; /** Global IP address for NAT handling */ static BACNET_IP_ADDRESS BVLC_Global_Address; /** Flag to indicate if NAT handling is enabled/disabled */ @@ -1183,6 +1183,20 @@ int bvlc_bbmd_read_bdt(BACNET_IP_ADDRESS *bbmd_addr) return bip_send_mpdu(bbmd_addr, &BVLC_Buffer[0], BVLC_Buffer_Len); } +/** + * Write the BDT to the indicated BBMD + * @param bbmd_addr - IPv4 address of BBMD with which to read + * @return Positive number (of bytes sent) on success + */ +int bvlc_bbmd_write_bdt(BACNET_IP_ADDRESS *bbmd_addr, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + BVLC_Buffer_Len = bvlc_encode_write_broadcast_distribution_table( + &BVLC_Buffer[0], sizeof(BVLC_Buffer), bdt_list); + + return bip_send_mpdu(bbmd_addr, &BVLC_Buffer[0], BVLC_Buffer_Len); +} + /** * Read the FDT from the indicated BBMD * @param bbmd_addr - IPv4 address of BBMD with which to read @@ -1211,6 +1225,15 @@ uint16_t bvlc_get_last_result(void) return BVLC_Result_Code; } +/** + * Sets the last BVLL Result we received + * @param result code + */ +void bvlc_set_last_result(uint16_t result_code) +{ + BVLC_Result_Code = result_code; +} + /** * Returns the current BVLL Function Code we are processing. * We have to store this higher layer code for when the lower layers @@ -1224,6 +1247,15 @@ uint8_t bvlc_get_function_code(void) return BVLC_Function_Code; } +/** + * Sets the current BVLL Function Code + * @param A BVLC_ code, such as BVLC_ORIGINAL_UNICAST_NPDU. + */ +void bvlc_set_function_code(uint8_t function_code) +{ + BVLC_Function_Code = function_code; +} + #if BBMD_ENABLED /** * @brief Get handle to broadcast distribution table (BDT). diff --git a/src/bacnet/basic/bbmd/h_bbmd.h b/src/bacnet/basic/bbmd/h_bbmd.h index 73feeb69..16214e84 100644 --- a/src/bacnet/basic/bbmd/h_bbmd.h +++ b/src/bacnet/basic/bbmd/h_bbmd.h @@ -68,9 +68,13 @@ int bvlc_send_pdu(BACNET_ADDRESS *dest, BACNET_STACK_EXPORT uint16_t bvlc_get_last_result(void); +BACNET_STACK_EXPORT +void bvlc_set_last_result(uint16_t result_code); BACNET_STACK_EXPORT uint8_t bvlc_get_function_code(void); +BACNET_STACK_EXPORT +void bvlc_set_function_code(uint8_t function_code); BACNET_STACK_EXPORT void bvlc_maintenance_timer(uint16_t seconds); @@ -84,6 +88,9 @@ void bvlc_debug_enable(void); /* send a Read BDT request */ BACNET_STACK_EXPORT int bvlc_bbmd_read_bdt(BACNET_IP_ADDRESS *bbmd_addr); +BACNET_STACK_EXPORT +int bvlc_bbmd_write_bdt(BACNET_IP_ADDRESS *bbmd_addr, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list); /* send a Read FDT request */ BACNET_STACK_EXPORT int bvlc_bbmd_read_fdt(BACNET_IP_ADDRESS *bbmd_addr); diff --git a/src/bacnet/datalink/bvlc.c b/src/bacnet/datalink/bvlc.c index 2387877d..7b8d48f5 100644 --- a/src/bacnet/datalink/bvlc.c +++ b/src/bacnet/datalink/bvlc.c @@ -2305,6 +2305,44 @@ int bvlc_decode_foreign_device_table_entry(uint8_t *pdu, return bytes_consumed; } +/** + * @brief Get a text name for each BVLC result code + * @param result_code - BVLC result code + * @return ASCII text name for each BVLC result code or empty string + */ +const char *bvlc_result_code_name(uint16_t result_code) +{ + const char *name = ""; + + switch (result_code) { + case BVLC_RESULT_SUCCESSFUL_COMPLETION: + name = "Successful Completion"; + break; + case BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK: + name= "Write-Broadcast-Distribution-Table NAK"; + break; + case BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK: + name = "Read-Broadcast-Distribution-Table NAK"; + break; + case BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK: + name = "Register-Foreign-Device NAK"; + break; + case BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK: + name = "Read-Foreign-Device-Table NAK"; + break; + case BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK: + name = "Delete-Foreign-Device-Table-Entry NAK"; + break; + case BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK: + name = "Distribute-Broadcast-To-Network NAK"; + break; + default: + break; + } + + return name; +} + #ifdef BAC_TEST #include #include diff --git a/src/bacnet/datalink/bvlc.h b/src/bacnet/datalink/bvlc.h index 2d35f7b6..0cd7cb5d 100644 --- a/src/bacnet/datalink/bvlc.h +++ b/src/bacnet/datalink/bvlc.h @@ -54,6 +54,7 @@ #define BVLC_ORIGINAL_UNICAST_NPDU 10 #define BVLC_ORIGINAL_BROADCAST_NPDU 11 #define BVLC_SECURE_BVLL 12 +#define BVLC_INVALID 255 /** @} */ @@ -68,6 +69,7 @@ #define BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK 0x0040 #define BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK 0x0050 #define BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK 0x0060 +#define BVLC_RESULT_INVALID 0xFFFF /* number of bytes in the IPv4 address */ #define IP_ADDRESS_MAX 4 @@ -491,6 +493,9 @@ extern "C" { uint16_t npdu_size, uint16_t *npdu_len); + BACNET_STACK_EXPORT + const char *bvlc_result_code_name(uint16_t result_code); + #ifdef BAC_TEST #include "ctest.h" BACNET_STACK_EXPORT