diff --git a/CMakeLists.txt b/CMakeLists.txt index d96de812..59cc3682 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,6 +125,8 @@ add_library(${PROJECT_NAME} src/bacnet/bactext.h src/bacnet/bactimevalue.c src/bacnet/bactimevalue.h + $<$:src/bacnet/basic/bbmd/h_bbmd.c> + $<$:src/bacnet/basic/bbmd/h_bbmd.h> $<$:src/bacnet/basic/bbmd6/h_bbmd6.c> $<$:src/bacnet/basic/bbmd6/h_bbmd6.h> $<$:src/bacnet/basic/bbmd6/vmac.c> @@ -348,7 +350,6 @@ add_library(${PROJECT_NAME} src/bacnet/datalink/bacsec.h src/bacnet/datalink/bip6.h $<$:src/bacnet/datalink/bip.h> - $<$:src/bacnet/datalink/bip.c> $<$:src/bacnet/datalink/bvlc6.c> $<$:src/bacnet/datalink/bvlc6.h> $<$:src/bacnet/datalink/bvlc.h> @@ -579,6 +580,9 @@ if(BACNET_STACK_BUILD_APPS) add_executable(readbdt apps/readbdt/main.c) target_link_libraries(readbdt PRIVATE ${PROJECT_NAME}) + add_executable(readfdt apps/readfdt/main.c) + target_link_libraries(readfdt PRIVATE ${PROJECT_NAME}) + add_executable(readfile apps/readfile/main.c) target_link_libraries(readfile PRIVATE ${PROJECT_NAME}) diff --git a/Makefile b/Makefile index 8482ffa9..10453aed 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,14 @@ gateway: gateway-win32: $(MAKE) BACNET_PORT=win32 -C apps gateway +.PHONY: readbdt +readbdt: + $(MAKE) -s -C apps $@ + +.PHONY: readfdt +readfdt: + $(MAKE) -s -C apps $@ + .PHONY: server server: $(MAKE) -s -C apps $@ @@ -73,6 +81,10 @@ mstpcrc: uevent: $(MAKE) -s -C apps $@ +.PHONY: whois +whois: + $(MAKE) -C apps $@ + .PHONY: writepropm writepropm: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index 4edf751b..50c7275a 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -32,15 +32,14 @@ BACDL_DEFINE=-DBACDL_BIP6=1 endif ifeq (${BACDL},) BACDL_DEFINE ?= -DBACDL_BIP=1 -BBMD_DEFINE ?= -DBBMD_ENABLED=1 +BBMD_DEFINE ?= -DBBMD_ENABLED=1 -DBBMD_CLIENT_ENABLED endif ifeq (${BBMD},server) BBMD_DEFINE=-DBBMD_ENABLED=1 endif ifeq (${BBMD},client) -BBMD_DEFINE=-DBBMD_ENABLED=1 -BBMD_DEFINE=-DBBMD_CLIENT_ENABLED +BBMD_DEFINE = -DBBMD_ENABLED=1 -DBBMD_CLIENT_ENABLED endif endif @@ -131,7 +130,7 @@ PORT_ETHERNET_SRC = \ PORT_BIP_SRC = \ $(BACNET_PORT_DIR)/bip-init.c \ $(BACNET_SRC_DIR)/bacnet/datalink/bvlc.c \ - $(BACNET_SRC_DIR)/bacnet/datalink/bip.c + $(BACNET_SRC_DIR)/bacnet/basic/bbmd/h_bbmd.c PORT_BIP6_SRC = \ $(BACNET_PORT_DIR)/bip6.c \ @@ -189,7 +188,7 @@ SUBDIRS = readprop writeprop readfile writefile reinit server dcc \ writepropm uptransfer getevent uevent abort error ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) - SUBDIRS += whoisrouter iamrouter initrouter readbdt + SUBDIRS += whoisrouter iamrouter initrouter readbdt readfdt endif ifeq (${BACNET_PORT},linux) @@ -258,10 +257,22 @@ iamrouter: initrouter: $(MAKE) -b -C $@ +.PHONY: readbdt +readbdt: + $(MAKE) -b -C $@ + +.PHONY: readfdt +readfdt: + $(MAKE) -b -C $@ + .PHONY: uevent uevent: $(MAKE) -b -C $@ +.PHONY: whois +whois: + $(MAKE) -b -C $@ + .PHONY: router router: $(MAKE) -s -b -C $@ diff --git a/apps/epics/main.c b/apps/epics/main.c index 41090959..273a6943 100644 --- a/apps/epics/main.c +++ b/apps/epics/main.c @@ -1432,7 +1432,7 @@ int main(int argc, char *argv[]) * My_BIP_Port will be non-zero in this case. */ if (My_BIP_Port > 0) { - bip_set_port(htons(My_BIP_Port)); + bip_set_port(My_BIP_Port); } #endif address_init(); @@ -1446,7 +1446,8 @@ int main(int argc, char *argv[]) #if defined(BACDL_BIP) if (My_BIP_Port > 0) { - bip_set_port(htons(0xBAC0)); /* Set back to std BACnet/IP port */ + /* Set back to std BACnet/IP port */ + bip_set_port(0xBAC0); } #endif /* try to bind with the target device */ diff --git a/apps/gateway/main.c b/apps/gateway/main.c index 046aac9f..64c70804 100644 --- a/apps/gateway/main.c +++ b/apps/gateway/main.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include "bacnet/config.h" @@ -172,19 +173,20 @@ static void Initialize_Device_Addresses() DEVICE_OBJECT_DATA *pDev = NULL; /* Setup info for the main gateway device first */ pDev = Get_Routed_Device_Object(i); + + /* we can't use datalink_get_my_address() since it is + mapped to routed_get_my_address() in this app + to get the parent device address */ #if defined(BACDL_BIP) - uint16_t myPort; - struct in_addr *netPtr; /* Lets us cast to this type */ - uint8_t *gatewayMac = NULL; - uint32_t myAddr = bip_get_addr(); - gatewayMac = pDev->bacDevAddr.mac; /* Keep pointer to the main MAC */ - memcpy(pDev->bacDevAddr.mac, &myAddr, 4); - myPort = bip_get_port(); - memcpy(&pDev->bacDevAddr.mac[4], &myPort, 2); - pDev->bacDevAddr.mac_len = 6; + bip_get_my_address(&pDev->bacDevAddr); #elif defined(BACDL_MSTP) - pDev->bacDevAddr.mac_len = 1; - pDev->bacDevAddr.mac[0] = dlmstp_mac_address(); + dlmstp_get_my_address(&pDev->bacDevAddr); +#elif defined(BACDL_ARCNET) + arcnet_get_my_address(&pDev->bacDevAddr); +#elif defined(BACDL_ETHERNET) + ethernet_get_my_address(&pDev->bacDevAddr); +#elif defined(BACDL_BIP6) + bip6_get_my_address&pDev->bacDevAddr); #else #error "No support for this Data Link Layer type " #endif @@ -193,38 +195,12 @@ static void Initialize_Device_Addresses() for (i = 1; i < MAX_NUM_DEVICES; i++) { pDev = Get_Routed_Device_Object(i); - if (pDev == NULL) + if (pDev == NULL) { continue; -#if defined(BACDL_BIP) - virtual_mac = i; - netPtr = (struct in_addr *)pDev->bacDevAddr.mac; -#if (MAX_NUM_DEVICES > 0xFFFFFF) - pDev->bacDevAddr.mac[0] = ((virtual_mac & 0xff000000) >> 24); -#else - pDev->bacDevAddr.mac[0] = gatewayMac[3]; -#endif -#if (MAX_NUM_DEVICES > 0xFFFF) - pDev->bacDevAddr.mac[1] = ((virtual_mac & 0xff0000) >> 16); -#else - pDev->bacDevAddr.mac[1] = gatewayMac[2]; -#endif -#if (MAX_NUM_DEVICES > 0xFF) - pDev->bacDevAddr.mac[2] = ((virtual_mac & 0xff00) >> 8); -#else - pDev->bacDevAddr.mac[2] = gatewayMac[1]; -#endif - pDev->bacDevAddr.mac[3] = (virtual_mac & 0xff); - memcpy(&pDev->bacDevAddr.mac[4], &myPort, 2); - pDev->bacDevAddr.mac_len = 6; - pDev->bacDevAddr.net = VIRTUAL_DNET; - memcpy(&pDev->bacDevAddr.adr[0], &pDev->bacDevAddr.mac[0], 6); - pDev->bacDevAddr.len = 6; - printf(" - Routed device [%d] ID %u at %s \n", i, - pDev->bacObj.Object_Instance_Number, inet_ntoa(*netPtr)); -#elif defined(BACDL_MSTP) - /* Todo: set MS/TP net and port #s */ - pDev->bacDevAddr.mac_len = 2; -#endif + } + virtual_mac = pDev->bacObj.Object_Instance_Number; + encode_unsigned24(&pDev->bacDevAddr.adr[0], virtual_mac); + pDev->bacDevAddr.len = 3; /* broadcast an I-Am for each routed Device now */ Send_I_Am(&Handler_Transmit_Buffer[0]); } @@ -234,7 +210,7 @@ static void Initialize_Device_Addresses() * * @see Device_Set_Object_Instance_Number, dlenv_init, Send_I_Am, * datalink_receive, npdu_handler, - * dcc_timer_seconds, bvlc_maintenance_timer, + * dcc_timer_seconds, datalink_maintenance_timer, * Load_Control_State_Machine_Handler, handler_cov_task, * tsm_timer_milliseconds * @@ -309,9 +285,7 @@ int main(int argc, char *argv[]) if (elapsed_seconds) { last_seconds = current_seconds; dcc_timer_seconds(elapsed_seconds); -#if defined(BACDL_BIP) && BBMD_ENABLED - bvlc_maintenance_timer(elapsed_seconds); -#endif + datalink_maintenance_timer(elapsed_seconds); dlenv_maintenance_timer(elapsed_seconds); Load_Control_State_Machine_Handler(); elapsed_milliseconds = elapsed_seconds * 1000; diff --git a/apps/initrouter/main.c b/apps/initrouter/main.c index 0ad847d7..82d4d67f 100644 --- a/apps/initrouter/main.c +++ b/apps/initrouter/main.c @@ -368,9 +368,7 @@ int main(int argc, char *argv[]) /* increment timer - exit if timed out */ elapsed_seconds = current_seconds - last_seconds; if (elapsed_seconds) { -#if defined(BACDL_BIP) && BBMD_ENABLED - bvlc_maintenance_timer(elapsed_seconds); -#endif + datalink_maintenance_timer(elapsed_seconds); } total_seconds += elapsed_seconds; if (total_seconds > timeout_seconds) { diff --git a/apps/piface/main.c b/apps/piface/main.c index f55ed2f2..10b00777 100644 --- a/apps/piface/main.c +++ b/apps/piface/main.c @@ -197,7 +197,7 @@ static void piface_task(void) * * @see Device_Set_Object_Instance_Number, dlenv_init, Send_I_Am, * datalink_receive, npdu_handler, - * dcc_timer_seconds, bvlc_maintenance_timer, + * dcc_timer_seconds, datalink_maintenance_timer, * handler_cov_task, * tsm_timer_milliseconds * @@ -254,9 +254,7 @@ int main(int argc, char *argv[]) if (elapsed_seconds) { last_seconds = current_seconds; dcc_timer_seconds(elapsed_seconds); -#if defined(BACDL_BIP) && BBMD_ENABLED - bvlc_maintenance_timer(elapsed_seconds); -#endif + datalink_maintenance_timer(elapsed_seconds); dlenv_maintenance_timer(elapsed_seconds); elapsed_milliseconds = elapsed_seconds * 1000; handler_cov_timer_seconds(elapsed_seconds); diff --git a/apps/readbdt/main.c b/apps/readbdt/main.c index 1933e913..cc0d056f 100644 --- a/apps/readbdt/main.c +++ b/apps/readbdt/main.c @@ -56,8 +56,7 @@ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; /* targets interpreted from the command line options */ -static uint32_t Target_BBMD_Address; -static uint16_t Target_BBMD_Port; +static BACNET_IP_ADDRESS Target_BBMD_Address; static bool Error_Detected = false; @@ -134,8 +133,7 @@ int main(int argc, char *argv[]) } /* decode the command line parameters */ if (argc > 1) { - Target_BBMD_Address = inet_addr(argv[1]); - if (Target_BBMD_Address == (-1)) { + if (!bip_get_addr_by_name(argv[1], &Target_BBMD_Address)) { fprintf(stderr, "IP=%s - failed to convert address.\r\n", argv[1]); return 1; } @@ -143,14 +141,14 @@ int main(int argc, char *argv[]) if (argc > 2) { port = strtol(argv[2], NULL, 0); if ((port > 0) && (port <= 65535)) { - Target_BBMD_Port = htons(port); + 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_Port = htons(47808); + Target_BBMD_Address.port = 0xBAC0U; } /* setup my info */ Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE); @@ -162,7 +160,7 @@ int main(int argc, char *argv[]) last_seconds = time(NULL); timeout_seconds = apdu_timeout() / 1000; /* send the request */ - bvlc_bbmd_read_bdt(Target_BBMD_Address, Target_BBMD_Port); + bvlc_bbmd_read_bdt(&Target_BBMD_Address); /* loop forever */ for (;;) { /* increment timer - exit if timed out */ @@ -178,9 +176,7 @@ int main(int argc, char *argv[]) /* increment timer - exit if timed out */ elapsed_seconds = current_seconds - last_seconds; if (elapsed_seconds) { -#if defined(BACDL_BIP) && BBMD_ENABLED - bvlc_maintenance_timer(elapsed_seconds); -#endif + datalink_maintenance_timer(elapsed_seconds); } total_seconds += elapsed_seconds; if (total_seconds > timeout_seconds) { diff --git a/apps/readfdt/Makefile b/apps/readfdt/Makefile new file mode 100644 index 00000000..409b9d7a --- /dev/null +++ b/apps/readfdt/Makefile @@ -0,0 +1,47 @@ +#Makefile to build BACnet Application for the GCC Port + +TARGET = bacrfdt +# 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/readfdt/main.c b/apps/readfdt/main.c new file mode 100644 index 00000000..17e5e75b --- /dev/null +++ b/apps/readfdt/main.c @@ -0,0 +1,190 @@ +/************************************************************************** + * + * Copyright (C) 2012 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]; + +/* targets interpreted from the command line options */ +static BACNET_IP_ADDRESS Target_BBMD_Address; + +static bool Error_Detected; + +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; + + 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 Read-Foreign-Device-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 Read-Foreign-Device-Table message to a BBMD\r\n" + "at 192.168.0.1 using port 47808:\r\n" + "%s 192.168.0.1 47808\r\n", + filename_remove_path(argv[0])); + return 0; + } + /* decode the command line parameters */ + if (argc > 1) { + if (!bip_get_addr_by_name(argv[1], &Target_BBMD_Address)) { + fprintf(stderr, "IP=%s - failed to convert address.\r\n", argv[1]); + return 1; + } + } + if (argc > 2) { + port = strtol(argv[2], 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; + } + /* 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_read_fdt(&Target_BBMD_Address); + /* 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); + /* 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/apps/router-ipv6/Makefile b/apps/router-ipv6/Makefile index e7ca9846..ff130330 100644 --- a/apps/router-ipv6/Makefile +++ b/apps/router-ipv6/Makefile @@ -25,7 +25,7 @@ PORT_BIP6_SRC = \ PORT_BIP_SRC = \ $(BACNET_PORT_DIR)/bip-init.c \ $(BACNET_SRC_DIR)/bacnet/datalink/bvlc.c \ - $(BACNET_SRC_DIR)/bacnet/datalink/bip.c + $(BACNET_SRC_DIR)/bacnet/basic/bbmd/h_bbmd.c # WARNINGS, DEBUGGING, OPTIMIZATION are defined in common apps Makefile # BACNET_DEFINES is defined in common apps Makefile diff --git a/apps/router-ipv6/main.c b/apps/router-ipv6/main.c index e37dc150..4f8dfa7c 100644 --- a/apps/router-ipv6/main.c +++ b/apps/router-ipv6/main.c @@ -998,13 +998,12 @@ static void datalink_init(void) { char *pEnv = NULL; BACNET_ADDRESS my_address = { 0 }; - extern bool BIP_Debug; /* BACnet/IP Initialization */ - BIP_Debug = true; + bip_debug_enable(); pEnv = getenv("BACNET_IP_PORT"); if (pEnv) { - bip_set_port(htons((uint16_t)strtol(pEnv, NULL, 0))); + bip_set_port((uint16_t)strtol(pEnv, NULL, 0)); } else { /* BIP_Port is statically initialized to 0xBAC0, * so if it is different, then it was programmatically altered, @@ -1012,8 +1011,8 @@ static void datalink_init(void) * Unless it is set below 1024, since: * "The range for well-known ports managed by the IANA is 0-1023." */ - if (ntohs(bip_get_port()) < 1024) { - bip_set_port(htons(0xBAC0)); + if (bip_get_port() < 1024) { + bip_set_port(0xBAC0U); } } if (!bip_init(getenv("BACNET_IFACE"))) { @@ -1169,6 +1168,7 @@ int main(int argc, char *argv[]) if (elapsed_seconds) { last_seconds = current_seconds; bvlc_maintenance_timer(elapsed_seconds); + bvlc6_maintenance_timer(elapsed_seconds); } if (Exit_Requested) { break; diff --git a/apps/router/main.c b/apps/router/main.c index 073a5eab..54654828 100644 --- a/apps/router/main.c +++ b/apps/router/main.c @@ -299,7 +299,7 @@ bool read_config(char *filepath) if (result) { current->params.bip_params.port = param; } else { - current->params.bip_params.port = 0xBAC0; + current->params.bip_params.port = 0xBAC0U; } result = config_setting_lookup_int(port, "network", (int *)¶m); @@ -477,7 +477,7 @@ bool parse_cmd(int argc, char *argv[]) } /* setup default parameters */ - current->params.bip_params.port = 0xBAC0; /* 47808 */ + current->params.bip_params.port = 0xBAC0U; /* 47808 */ current->route_info.net = get_next_free_dnet(); /* check if interface is valid */ diff --git a/apps/server/main.c b/apps/server/main.c index 7e1b6198..a505c62a 100644 --- a/apps/server/main.c +++ b/apps/server/main.c @@ -173,7 +173,7 @@ static void print_help(const char *filename) * * @see Device_Set_Object_Instance_Number, dlenv_init, Send_I_Am, * datalink_receive, npdu_handler, - * dcc_timer_seconds, bvlc_maintenance_timer, + * dcc_timer_seconds, datalink_maintenance_timer, * Load_Control_State_Machine_Handler, handler_cov_task, * tsm_timer_milliseconds * @@ -275,9 +275,7 @@ int main(int argc, char *argv[]) if (elapsed_seconds) { last_seconds = current_seconds; dcc_timer_seconds(elapsed_seconds); -#if defined(BACDL_BIP) && BBMD_ENABLED - bvlc_maintenance_timer(elapsed_seconds); -#endif + datalink_maintenance_timer(elapsed_seconds); dlenv_maintenance_timer(elapsed_seconds); Load_Control_State_Machine_Handler(); elapsed_milliseconds = elapsed_seconds * 1000; diff --git a/apps/whois/main.c b/apps/whois/main.c index fb8d90e3..1bed86af 100644 --- a/apps/whois/main.c +++ b/apps/whois/main.c @@ -60,6 +60,8 @@ static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; static int32_t Target_Object_Instance_Min = -1; static int32_t Target_Object_Instance_Max = -1; static bool Error_Detected = false; +/* debug info printing */ +static bool BACnet_Debug_Enabled; #define BAC_ADDRESS_MULT 1 @@ -130,32 +132,32 @@ static void my_i_am_handler( (void)service_len; len = iam_decode_service_request( service_request, &device_id, &max_apdu, &segmentation, &vendor_id); -#if PRINT_ENABLED - fprintf(stderr, "Received I-Am Request"); -#endif + if (BACnet_Debug_Enabled) { + fprintf(stderr, "Received I-Am Request"); + } if (len != -1) { -#if PRINT_ENABLED - fprintf(stderr, " from %lu, MAC = ", (unsigned long)device_id); - if ((src->mac_len == 6) && (src->len == 0)) { - fprintf(stderr, "%u.%u.%u.%u %02X%02X\n", (unsigned)src->mac[0], - (unsigned)src->mac[1], (unsigned)src->mac[2], - (unsigned)src->mac[3], (unsigned)src->mac[4], - (unsigned)src->mac[5]); - } else { - for (i = 0; i < src->mac_len; i++) { - fprintf(stderr, "%02X", (unsigned)src->mac[i]); - if (i < (src->mac_len - 1)) { - fprintf(stderr, ":"); + if (BACnet_Debug_Enabled) { + fprintf(stderr, " from %lu, MAC = ", (unsigned long)device_id); + if ((src->mac_len == 6) && (src->len == 0)) { + fprintf(stderr, "%u.%u.%u.%u %02X%02X\n", (unsigned)src->mac[0], + (unsigned)src->mac[1], (unsigned)src->mac[2], + (unsigned)src->mac[3], (unsigned)src->mac[4], + (unsigned)src->mac[5]); + } else { + for (i = 0; i < src->mac_len; i++) { + fprintf(stderr, "%02X", (unsigned)src->mac[i]); + if (i < (src->mac_len - 1)) { + fprintf(stderr, ":"); + } } + fprintf(stderr, "\n"); } - fprintf(stderr, "\n"); } -#endif address_table_add(device_id, max_apdu, src); } else { -#if PRINT_ENABLED - fprintf(stderr, ", but unable to decode it.\n"); -#endif + if (BACnet_Debug_Enabled) { + fprintf(stderr, ", but unable to decode it.\n"); + } } return; @@ -342,6 +344,10 @@ int main(int argc, char *argv[]) unsigned int target_args = 0; char *filename = NULL; + /* check for local environment settings */ + if (getenv("BACNET_DEBUG")) { + BACnet_Debug_Enabled = true; + } /* decode any command line parameters */ filename = filename_remove_path(argv[0]); for (argi = 1; argi < argc; argi++) { @@ -461,9 +467,7 @@ int main(int argc, char *argv[]) /* increment timer - exit if timed out */ elapsed_seconds = current_seconds - last_seconds; if (elapsed_seconds) { -#if defined(BACDL_BIP) && BBMD_ENABLED - bvlc_maintenance_timer(elapsed_seconds); -#endif + datalink_maintenance_timer(elapsed_seconds); } total_seconds += elapsed_seconds; if (total_seconds > timeout_seconds) { diff --git a/apps/whoisrouter/main.c b/apps/whoisrouter/main.c index db048968..0e9df782 100644 --- a/apps/whoisrouter/main.c +++ b/apps/whoisrouter/main.c @@ -296,9 +296,7 @@ int main(int argc, char *argv[]) /* increment timer - exit if timed out */ elapsed_seconds = current_seconds - last_seconds; if (elapsed_seconds) { -#if defined(BACDL_BIP) && BBMD_ENABLED - bvlc_maintenance_timer(elapsed_seconds); -#endif + datalink_maintenance_timer(elapsed_seconds); } total_seconds += elapsed_seconds; if (total_seconds > timeout_seconds) { diff --git a/ports/arduino_uno/bip-init.c b/ports/arduino_uno/bip-init.c index 84c1cc8a..4ea12e5e 100644 --- a/ports/arduino_uno/bip-init.c +++ b/ports/arduino_uno/bip-init.c @@ -43,7 +43,15 @@ /** @file linux/bip-init.c Initializes BACnet/IP interface (Linux). */ -bool BIP_Debug = false; +static bool BIP_Debug = false; + +/** + * @brief Enabled debug printing of BACnet/IPv4 + */ +void bip_debug_enable(void) +{ + BIP_Debug = true; +} /* gets an IP address by name, where name can be a string that is an IP address in dotted form, or diff --git a/ports/arduino_uno/bip.c b/ports/arduino_uno/bip.c index 038331d1..825a8291 100644 --- a/ports/arduino_uno/bip.c +++ b/ports/arduino_uno/bip.c @@ -130,13 +130,13 @@ uint8_t *bip_get_broadcast_addr(void) void bip_set_port(uint16_t port) { /* in network byte order */ - BIP_Port = port; + BIP_Port = htons(port); } /* returns network byte order */ uint16_t bip_get_port(void) { - return BIP_Port; + return ntohs(BIP_Port); } static int bip_decode_bip_address(BACNET_ADDRESS * bac_addr, diff --git a/ports/arduino_uno/bip.h b/ports/arduino_uno/bip.h index 7bbef39e..55f69b9b 100644 --- a/ports/arduino_uno/bip.h +++ b/ports/arduino_uno/bip.h @@ -36,8 +36,6 @@ #define BVLL_TYPE_BACNET_IP (0x81) -extern bool BIP_Debug; - #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -80,9 +78,9 @@ extern "C" { unsigned timeout); /* milliseconds to wait for a packet */ - /* use network byte order for setting */ + /* use host byte order for setting */ void bip_set_port(uint16_t port); - /* returns network byte order */ + /* returns host byte order */ uint16_t bip_get_port(void); /* use network byte order for setting */ @@ -102,6 +100,7 @@ extern "C" { an IP address in network byte order */ long bip_getaddrbyname(const char *host_name); + void bip_debug_enable(void); #ifdef __cplusplus } diff --git a/ports/bdk-atxx4-mstp/nvdata.h b/ports/bdk-atxx4-mstp/nvdata.h index 872a7fdb..9d94eb59 100644 --- a/ports/bdk-atxx4-mstp/nvdata.h +++ b/ports/bdk-atxx4-mstp/nvdata.h @@ -63,7 +63,7 @@ /*=============== SEEPROM ================*/ /* data version - use to check valid version */ -#define SEEPROM_ID 0xBAC0 +#define SEEPROM_ID 0xBAC0U #define SEEPROM_VERSION 0x0001 #define SEEPROM_BYTES_MAX (2*1024) diff --git a/ports/bsd/bip-init.c b/ports/bsd/bip-init.c index ff8bc8b4..863d95be 100644 --- a/ports/bsd/bip-init.c +++ b/ports/bsd/bip-init.c @@ -34,16 +34,387 @@ #include /* for standard integer types uint8_t etc. */ #include /* for the standard bool type. */ -#include "bacnet/bacdcode.h" -#include "bacnet/datalink/bip.h" -#include "bacport.h" #include +#include "bacnet/bacdcode.h" +#include "bacnet/bacint.h" +#include "bacnet/datalink/bip.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/bbmd/h_bbmd.h" +#include "bacport.h" -/** @file linux/bip-init.c Initializes BACnet/IP interface (BSD/MAC OS X). */ +/** + * @file + * @brief Initializes BACnet/IP interface (BSD/MAC OS X). + */ -bool BIP_Debug = true; +/* unix socket */ +static int BIP_Socket = -1; -void *get_addr_ptr( +/* NOTE: we store address and port in network byte order + since BACnet/IP uses network byte order for all address byte arrays +*/ +/* port to use - stored here in network byte order */ +/* Initialize to 0 - this will force initialization in demo apps */ +static uint16_t BIP_Port; +/* IP address - stored here in network byte order */ +static struct in_addr BIP_Address; +/* IP broadcast address - stored here in network byte order */ +static struct in_addr BIP_Broadcast_Addr; +/* enable debugging */ +static bool BIP_Debug = false; + +/** + * @brief Print the IPv4 address with debug info + * @param str - debug info string + * @param addr - IPv4 address + */ +static void debug_print_ipv4(const char *str, const struct in_addr *addr, + const unsigned int port, const unsigned int count) +{ + if (BIP_Debug) { + fprintf(stderr, "BIP: %s %s:%hu (%u bytes)\n", str, inet_ntoa(*addr), + ntohs(port), count); + fflush(stderr); + } +} + +/** + * @brief Enabled debug printing of BACnet/IPv4 + */ +void bip_debug_enable(void) +{ + BIP_Debug = true; +} + +/** + * @brief Set the BACnet IPv4 UDP port number + * @param port - IPv4 UDP port number - in host byte order + */ +void bip_set_port(uint16_t port) +{ + BIP_Port = htons(port); +} + +/** + * @brief Get the BACnet IPv4 UDP port number + * @return IPv4 UDP port number - in host byte order + */ +uint16_t bip_get_port(void) +{ + return ntohs(BIP_Port); +} + +/** + * @brief Get the IPv4 address for my interface. Used for sending src address. + * @param addr - BACnet datalink address + */ +void bip_get_my_address(BACNET_ADDRESS *addr) +{ + unsigned int i = 0; + + if (addr) { + addr->mac_len = 6; + memcpy(&addr->mac[0], &BIP_Address.s_addr, 4); + memcpy(&addr->mac[4], &BIP_Port, 2); + /* local only, no routing */ + addr->net = 0; + /* no SLEN */ + addr->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + addr->adr[i] = 0; + } + } +} + +/** + * Get the IPv4 broadcast address for my interface. + * + * @param addr - BACnet datalink address + */ +void bip_get_broadcast_address(BACNET_ADDRESS *dest) +{ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 6; + memcpy(&dest->mac[0], &BIP_Broadcast_Addr.s_addr, 4); + memcpy(&dest->mac[4], &BIP_Port, 2); + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* no SLEN */ + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + dest->adr[i] = 0; + } + } + + return; +} + +/** + * Set the BACnet/IP address + * + * @param addr - network IPv4 address + */ +bool bip_set_addr(BACNET_IP_ADDRESS *addr) +{ + /* not something we do within this driver */ + return false; +} + +/** + * @brief Get the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address was retrieved + */ +bool bip_get_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP_Address.s_addr, 4); + addr->port = ntohs(BIP_Port); + } + + return true; +} + +/** + * @brief Set the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address was set + */ +bool bip_set_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + /* not something we do within this driver */ + return false; +} + +/** + * Get the BACnet/IP address + * + * @return BACnet/IP address + */ +bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP_Broadcast_Addr.s_addr, 4); + addr->port = ntohs(BIP_Port); + } + + return true; +} + +/** + * @brief Set the BACnet/IP subnet mask CIDR prefix + * @return true if the subnet mask CIDR prefix is set + */ +bool bip_set_subnet_prefix(uint8_t prefix) +{ + /* not something we do within this driver */ + return false; +} + +/** + * @brief Get the BACnet/IP subnet mask CIDR prefix + * @return subnet mask CIDR prefix 1..32 + */ +uint8_t bip_get_subnet_prefix(void) +{ + uint32_t address = 0; + uint32_t broadcast = 0; + uint32_t test_broadcast = 0; + uint32_t mask = 0xFFFFFFFE; + uint8_t prefix = 0; + + address = BIP_Address.s_addr; + broadcast = BIP_Broadcast_Addr.s_addr; + /* calculate the subnet prefix from the broadcast address */ + for (prefix = 1; prefix <= 32; prefix++) { + test_broadcast = (address & mask) | (~mask); + if (test_broadcast == broadcast) { + break; + } + mask = mask<<1; + } + + return prefix; +} + +/** + * The send function for BACnet/IP driver layer + * + * @param dest - Points to a BACNET_IP_ADDRESS structure containing the + * destination address. + * @param mtu - the bytes of data to send + * @param mtu_len - the number of bytes of data to send + * + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +int bip_send_mpdu(BACNET_IP_ADDRESS *dest, uint8_t *mtu, uint16_t mtu_len) +{ + struct sockaddr_in bip_dest = { 0 }; + + /* assumes that the driver has already been initialized */ + if (BIP_Socket < 0) { + if (BIP_Debug) { + fprintf(stderr, "BIP: driver not initialized!\n"); + fflush(stderr); + } + return BIP_Socket; + } + /* load destination IP address */ + bip_dest.sin_family = AF_INET; + memcpy(&bip_dest.sin_addr.s_addr, &dest->address[0], 4); + bip_dest.sin_port = htons(dest->port); + /* Send the packet */ + debug_print_ipv4("Sending MPDU->", &bip_dest.sin_addr, bip_dest.sin_port, + mtu_len); + return sendto(BIP_Socket, (char *)mtu, mtu_len, 0, + (struct sockaddr *)&bip_dest, sizeof(struct sockaddr)); +} + +/** + * BACnet/IP Datalink Receive handler. + * + * @param src - returns the source address + * @param npdu - returns the NPDU buffer + * @param max_npdu -maximum size of the NPDU buffer + * @param timeout - number of milliseconds to wait for a packet + * + * @return Number of bytes received, or 0 if none or timeout. + */ +uint16_t bip_receive( + BACNET_ADDRESS *src, uint8_t *npdu, uint16_t max_npdu, unsigned timeout) +{ + uint16_t npdu_len = 0; /* return value */ + fd_set read_fds; + int max = 0; + struct timeval select_timeout; + struct sockaddr_in sin = { 0 }; + BACNET_IP_ADDRESS addr = { { 0 } }; + socklen_t sin_len = sizeof(sin); + int received_bytes = 0; + int offset = 0; + uint16_t i = 0; + + /* Make sure the socket is open */ + if (BIP_Socket < 0) { + return 0; + } + /* we could just use a non-blocking socket, but that consumes all + the CPU time. We can use a timeout; it is only supported as + a select. */ + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = + 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + FD_ZERO(&read_fds); + FD_SET(BIP_Socket, &read_fds); + max = BIP_Socket; + /* see if there is a packet for us */ + if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { + received_bytes = recvfrom(BIP_Socket, (char *)&npdu[0], max_npdu, 0, + (struct sockaddr *)&sin, &sin_len); + } else { + return 0; + } + /* See if there is a problem */ + if (received_bytes < 0) { + return 0; + } + /* no problem, just no bytes */ + if (received_bytes == 0) { + return 0; + } + /* the signature of a BACnet/IPv packet */ + if (npdu[0] != BVLL_TYPE_BACNET_IP) { + return 0; + } + /* Data link layer addressing between B/IPv4 nodes consists of a 32-bit + IPv4 address followed by a two-octet UDP port number (both of which + shall be transmitted with the most significant octet first). This + address shall be referred to as a B/IPv4 address. + */ + memcpy(&addr.address[0], &sin.sin_addr.s_addr, 4); + addr.port = ntohs(sin.sin_port); + debug_print_ipv4("Received MPDU->", &sin.sin_addr, sin.sin_port, + received_bytes); + /* pass the packet into the BBMD handler */ + offset = bvlc_handler(&addr, src, npdu, received_bytes); + if (offset > 0) { + npdu_len = received_bytes - offset; + debug_print_ipv4("Received NPDU->", &sin.sin_addr, sin.sin_port, + npdu_len); + if (npdu_len <= max_npdu) { + /* shift the buffer to return a valid NPDU */ + for (i = 0; i < npdu_len; i++) { + npdu[i] = npdu[offset + i]; + } + } else { + if (BIP_Debug) { + fprintf(stderr, "BIP: NPDU dropped!\n"); + fflush(stderr); + } + npdu_len = 0; + } + } + + return npdu_len; +} + +/** + * The common send function for BACnet/IP application layer + * + * @param dest - Points to a #BACNET_ADDRESS structure containing the + * destination address. + * @param npdu_data - Points to a BACNET_NPDU_DATA structure containing the + * destination network layer control flags and data. + * @param mtu - the bytes of data to send + * @param mtu_len - the number of bytes of data to send + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +int bip_send_pdu(BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + return bvlc_send_pdu(dest, npdu_data, pdu, pdu_len); +} + +/** + * @brief gets an IP address by hostname (or string of numbers) + * + * gets an IP address by name, where name can be a string that is an + * IP address in dotted form, or a name that is a domain name + * + * @param host_name - the host name + * @return true if the address was retrieved + */ +bool bip_get_addr_by_name(const char *host_name, BACNET_IP_ADDRESS *addr) +{ + struct hostent *host_ent; + + if ((host_ent = gethostbyname(host_name)) == NULL) { + return false; + } + + if (addr) { + /* Host addresses in a struct hostent structure are always + given in network byte order */ + /* h_addr: This is a synonym for h_addr_list[0]; + in other words, it is the first host address.*/ + memcpy(&addr->address[0], host_ent->h_addr, 4); + } + + return true; +} + +static void *get_addr_ptr( struct sockaddr *sockaddr_ptr) { void *addr_ptr; @@ -55,22 +426,6 @@ void *get_addr_ptr( return addr_ptr; } -/* gets an IP address by name, where name can be a - string that is an IP address in dotted form, or - a name that is a domain name - returns 0 if not found, or - an IP address in network byte order */ -long bip_getaddrbyname( - const char *host_name) -{ - struct hostent *host_ent; - - if ((host_ent = gethostbyname(host_name)) == NULL) - return 0; - - return *(long *) host_ent->h_addr; -} - /** Gets the local IP address and local broadcast address from the system, * and saves it into the BACnet/IP data structures. * @@ -118,6 +473,24 @@ static int get_local_address( return rv; } +/** Get the netmask of the BACnet/IP's interface via an getifaddrs() call. + * @param netmask [out] The netmask, in host order. + * @return 0 on success, else the error from the getifaddrs() call. + */ +int bip_get_local_netmask( + struct in_addr *netmask) +{ + int rv; + char *ifname = getenv("BACNET_IFACE"); /* will probably be null */ + if (ifname == NULL) + ifname = "en0"; + printf("ifname %s", ifname); + char *request = "netmask"; + rv = get_local_address(ifname, netmask, request); + + return rv; +} + /** Gets the local IP address and local broadcast address from the system, * and saves it into the BACnet/IP data structures. * @@ -137,23 +510,26 @@ void bip_set_interface( if (rv < 0) { local_address.s_addr = 0; } - bip_set_addr(local_address.s_addr); + BIP_Address.s_addr = local_address.s_addr; if (BIP_Debug) { - fprintf(stderr, "Interface: %s\n", ifname); - fprintf(stderr, "IP Address: %s\n", inet_ntoa(local_address)); + fprintf(stderr, "BIP: Interface: %s\n", ifname); + fprintf(stderr, "BIP: Address: %s\n", inet_ntoa(local_address)); + fflush(stderr); } /* setup local broadcast address */ request = "broadaddr"; rv = get_local_address(ifname, &broadcast_address, request); if (rv < 0) { - broadcast_address.s_addr = ~0; + BIP_Broadcast_Addr.s_addr = ~0; + } else { + BIP_Broadcast_Addr.s_addr = broadcast_address.s_addr; } - bip_set_broadcast_addr(broadcast_address.s_addr); if (BIP_Debug) { - fprintf(stderr, "IP Broadcast Address: %s\n", - inet_ntoa(broadcast_address)); - fprintf(stderr, "UDP Port: 0x%04X [%hu]\n", ntohs(bip_get_port()), - ntohs(bip_get_port())); + fprintf(stderr, "BIP: Broadcast Address: %s\n", + inet_ntoa(BIP_Broadcast_Addr)); + fprintf(stderr, "BIP: UDP Port: 0x%04X [%hu]\n", ntohs(BIP_Port), + ntohs(BIP_Port)); + fflush(stderr); } } @@ -190,7 +566,7 @@ bool bip_init( } /* assumes that the driver has already been initialized */ sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - bip_set_socket(sock_fd); + BIP_Socket = sock_fd; if (sock_fd < 0) return false; /* Allow us to use the same socket for sending and receiving */ @@ -201,7 +577,7 @@ bool bip_init( sizeof(sockopt)); if (status < 0) { close(sock_fd); - bip_set_socket(-1); + BIP_Socket = -1; return status; } /* allow us to send a broadcast */ @@ -210,25 +586,34 @@ bool bip_init( sizeof(sockopt)); if (status < 0) { close(sock_fd); - bip_set_socket(-1); + BIP_Socket = -1; return false; } /* bind the socket to the local port number and IP address */ sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); - sin.sin_port = bip_get_port(); + sin.sin_port = BIP_Port; memset(&(sin.sin_zero), '\0', sizeof(sin.sin_zero)); status = bind(sock_fd, (const struct sockaddr *) &sin, sizeof(struct sockaddr)); if (status < 0) { close(sock_fd); - bip_set_socket(-1); + BIP_Socket = -1; return false; } return true; } +/** + * @brief Determine if this BACnet/IP datalink is valid + * @return true if the BACnet/IP datalink is valid + */ +bool bip_valid(void) +{ + return (BIP_Socket != -1); +} + /** Cleanup and close out the BACnet/IP services by closing the socket. * @ingroup DLBIP */ @@ -237,29 +622,10 @@ void bip_cleanup( { int sock_fd = 0; - if (bip_valid()) { - sock_fd = bip_socket(); - close(sock_fd); + if (BIP_Socket != -1) { + close(BIP_Socket); } - bip_set_socket(-1); + BIP_Socket = -1; return; } - -/** Get the netmask of the BACnet/IP's interface via an getifaddrs() call. - * @param netmask [out] The netmask, in host order. - * @return 0 on success, else the error from the getifaddrs() call. - */ -int bip_get_local_netmask( - struct in_addr *netmask) -{ - int rv; - char *ifname = getenv("BACNET_IFACE"); /* will probably be null */ - if (ifname == NULL) - ifname = "en0"; - printf("ifname %s", ifname); - char *request = "netmask"; - rv = get_local_address(ifname, netmask, request); - - return rv; -} diff --git a/ports/esp32/src/bip_init.c b/ports/esp32/src/bip_init.c index 76e2ed25..7f22c03f 100644 --- a/ports/esp32/src/bip_init.c +++ b/ports/esp32/src/bip_init.c @@ -10,7 +10,7 @@ #include "bacnet/datalink/bip.h" -long bip_getaddrbyname( +long bip_get_addr_by_name( const char *host_name) { return 0; @@ -28,15 +28,15 @@ void bip_cleanup (void) bool bip_init(char *ifname) { - + tcpip_adapter_ip_info_t ip_info = { 0 }; int value = 1; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); - bip_set_interface(ifname); - bip_set_port(htons(0xBAC0)); + bip_set_interface(ifname); + bip_set_port(0xBAC0U); bip_set_addr(ip_info.ip.addr); bip_set_broadcast_addr((ip_info.ip.addr&ip_info.netmask.addr)|(~ip_info.netmask.addr)); @@ -44,7 +44,7 @@ bool bip_init(char *ifname) struct sockaddr_in saddr = { 0 }; saddr.sin_family = PF_INET; - saddr.sin_port = htons(0xBAC0); + saddr.sin_port = htons(0xBAC0U); saddr.sin_addr.s_addr = htonl(INADDR_ANY); bind(sock, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)); diff --git a/ports/linux/bacport.h b/ports/linux/bacport.h index 7157bbb1..98812bbb 100644 --- a/ports/linux/bacport.h +++ b/ports/linux/bacport.h @@ -101,12 +101,15 @@ #include #include "bacnet/bacnet_stack_exports.h" -/** @file linux/net.h Includes Linux network headers. */ +/** @file linux/bacport.h Includes Linux network headers. */ /* Local helper functions for this port */ BACNET_STACK_EXPORT extern int bip_get_local_netmask( struct in_addr *netmask); - +extern int bip_get_local_address_ioctl( + char *ifname, + struct in_addr *addr, + int request); #endif diff --git a/ports/linux/bip-init.c b/ports/linux/bip-init.c index ac929cd0..3e8fe170 100644 --- a/ports/linux/bip-init.c +++ b/ports/linux/bip-init.c @@ -32,39 +32,395 @@ ------------------------------------------- ####COPYRIGHTEND####*/ -#include /* for standard integer types uint8_t etc. */ -#include /* for the standard bool type. */ +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ #include "bacnet/bacdcode.h" +#include "bacnet/bacint.h" #include "bacnet/datalink/bip.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/bbmd/h_bbmd.h" #include "bacport.h" /** @file linux/bip-init.c Initializes BACnet/IP interface (Linux). */ -bool BIP_Debug = false; +/* unix socket */ +static int BIP_Socket = -1; -/* gets an IP address by name, where name can be a - string that is an IP address in dotted form, or - a name that is a domain name - returns 0 if not found, or - an IP address in network byte order */ -long bip_getaddrbyname( - const char *host_name) +/* NOTE: we store address and port in network byte order + since BACnet/IP uses network byte order for all address byte arrays +*/ +/* port to use - stored here in network byte order */ +/* Initialize to 0 - this will force initialization in demo apps */ +static uint16_t BIP_Port; +/* IP address - stored here in network byte order */ +static struct in_addr BIP_Address; +/* IP broadcast address - stored here in network byte order */ +static struct in_addr BIP_Broadcast_Addr; +/* enable debugging */ +static bool BIP_Debug = false; + +/** + * @brief Print the IPv4 address with debug info + * @param str - debug info string + * @param addr - IPv4 address + */ +static void debug_print_ipv4(const char *str, const struct in_addr *addr, + const unsigned int port, const unsigned int count) +{ + if (BIP_Debug) { + fprintf(stderr, "BIP: %s %s:%hu (%u bytes)\n", str, inet_ntoa(*addr), + ntohs(port), count); + fflush(stderr); + } +} + +/** + * @brief Enabled debug printing of BACnet/IPv4 + */ +void bip_debug_enable(void) +{ + BIP_Debug = true; +} + +/** + * @brief Set the BACnet IPv4 UDP port number + * @param port - IPv4 UDP port number - in host byte order + */ +void bip_set_port(uint16_t port) +{ + BIP_Port = htons(port); +} + +/** + * @brief Get the BACnet IPv4 UDP port number + * @return IPv4 UDP port number - in host byte order + */ +uint16_t bip_get_port(void) +{ + return ntohs(BIP_Port); +} + +/** + * @brief Get the IPv4 address for my interface. Used for sending src address. + * @param addr - BACnet datalink address + */ +void bip_get_my_address(BACNET_ADDRESS *addr) +{ + unsigned int i = 0; + + if (addr) { + addr->mac_len = 6; + memcpy(&addr->mac[0], &BIP_Address.s_addr, 4); + memcpy(&addr->mac[4], &BIP_Port, 2); + /* local only, no routing */ + addr->net = 0; + /* no SLEN */ + addr->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + addr->adr[i] = 0; + } + } +} + +/** + * Get the IPv4 broadcast address for my interface. + * + * @param addr - BACnet datalink address + */ +void bip_get_broadcast_address(BACNET_ADDRESS *dest) +{ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 6; + memcpy(&dest->mac[0], &BIP_Broadcast_Addr.s_addr, 4); + memcpy(&dest->mac[4], &BIP_Port, 2); + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* no SLEN */ + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + dest->adr[i] = 0; + } + } + + return; +} + +/** + * Set the BACnet/IP address + * + * @param addr - network IPv4 address + */ +bool bip_set_addr(BACNET_IP_ADDRESS *addr) +{ + /* not something we do within this driver */ + return false; +} + +/** + * @brief Get the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address was retrieved + */ +bool bip_get_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP_Address.s_addr, 4); + addr->port = ntohs(BIP_Port); + } + + return true; +} + +/** + * @brief Set the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address was set + */ +bool bip_set_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + /* not something we do within this driver */ + return false; +} + +/** + * Get the BACnet/IP address + * + * @return BACnet/IP address + */ +bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP_Broadcast_Addr.s_addr, 4); + addr->port = ntohs(BIP_Port); + } + + return true; +} + +/** + * @brief Set the BACnet/IP subnet mask CIDR prefix + * @return true if the subnet mask CIDR prefix is set + */ +bool bip_set_subnet_prefix(uint8_t prefix) +{ + /* not something we do within this driver */ + return false; +} + +/** + * @brief Get the BACnet/IP subnet mask CIDR prefix + * @return subnet mask CIDR prefix 1..32 + */ +uint8_t bip_get_subnet_prefix(void) +{ + uint32_t address = 0; + uint32_t broadcast = 0; + uint32_t test_broadcast = 0; + uint32_t mask = 0xFFFFFFFE; + uint8_t prefix = 0; + + address = BIP_Address.s_addr; + broadcast = BIP_Broadcast_Addr.s_addr; + /* calculate the subnet prefix from the broadcast address */ + for (prefix = 1; prefix <= 32; prefix++) { + test_broadcast = (address & mask) | (~mask); + if (test_broadcast == broadcast) { + break; + } + mask = mask<<1; + } + + return prefix; +} + +/** + * The send function for BACnet/IP driver layer + * + * @param dest - Points to a BACNET_IP_ADDRESS structure containing the + * destination address. + * @param mtu - the bytes of data to send + * @param mtu_len - the number of bytes of data to send + * + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +int bip_send_mpdu(BACNET_IP_ADDRESS *dest, uint8_t *mtu, uint16_t mtu_len) +{ + struct sockaddr_in bip_dest = { 0 }; + + /* assumes that the driver has already been initialized */ + if (BIP_Socket < 0) { + if (BIP_Debug) { + fprintf(stderr, "BIP: driver not initialized!\n"); + fflush(stderr); + } + return BIP_Socket; + } + /* load destination IP address */ + bip_dest.sin_family = AF_INET; + memcpy(&bip_dest.sin_addr.s_addr, &dest->address[0], 4); + bip_dest.sin_port = htons(dest->port); + /* Send the packet */ + debug_print_ipv4("Sending MPDU->", &bip_dest.sin_addr, bip_dest.sin_port, + mtu_len); + return sendto(BIP_Socket, (char *)mtu, mtu_len, 0, + (struct sockaddr *)&bip_dest, sizeof(struct sockaddr)); +} + +/** + * BACnet/IP Datalink Receive handler. + * + * @param src - returns the source address + * @param npdu - returns the NPDU buffer + * @param max_npdu -maximum size of the NPDU buffer + * @param timeout - number of milliseconds to wait for a packet + * + * @return Number of bytes received, or 0 if none or timeout. + */ +uint16_t bip_receive( + BACNET_ADDRESS *src, uint8_t *npdu, uint16_t max_npdu, unsigned timeout) +{ + uint16_t npdu_len = 0; /* return value */ + fd_set read_fds; + int max = 0; + struct timeval select_timeout; + struct sockaddr_in sin = { 0 }; + BACNET_IP_ADDRESS addr = { { 0 } }; + socklen_t sin_len = sizeof(sin); + int received_bytes = 0; + int offset = 0; + uint16_t i = 0; + + /* Make sure the socket is open */ + if (BIP_Socket < 0) { + return 0; + } + /* we could just use a non-blocking socket, but that consumes all + the CPU time. We can use a timeout; it is only supported as + a select. */ + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = + 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + FD_ZERO(&read_fds); + FD_SET(BIP_Socket, &read_fds); + max = BIP_Socket; + /* see if there is a packet for us */ + if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { + received_bytes = recvfrom(BIP_Socket, (char *)&npdu[0], max_npdu, 0, + (struct sockaddr *)&sin, &sin_len); + } else { + return 0; + } + /* See if there is a problem */ + if (received_bytes < 0) { + return 0; + } + /* no problem, just no bytes */ + if (received_bytes == 0) { + return 0; + } + /* the signature of a BACnet/IPv packet */ + if (npdu[0] != BVLL_TYPE_BACNET_IP) { + return 0; + } + /* Data link layer addressing between B/IPv4 nodes consists of a 32-bit + IPv4 address followed by a two-octet UDP port number (both of which + shall be transmitted with the most significant octet first). This + address shall be referred to as a B/IPv4 address. + */ + memcpy(&addr.address[0], &sin.sin_addr.s_addr, 4); + addr.port = ntohs(sin.sin_port); + debug_print_ipv4("Received MPDU->", &sin.sin_addr, sin.sin_port, + received_bytes); + /* pass the packet into the BBMD handler */ + offset = bvlc_handler(&addr, src, npdu, received_bytes); + if (offset > 0) { + npdu_len = received_bytes - offset; + debug_print_ipv4("Received NPDU->", &sin.sin_addr, sin.sin_port, + npdu_len); + if (npdu_len <= max_npdu) { + /* shift the buffer to return a valid NPDU */ + for (i = 0; i < npdu_len; i++) { + npdu[i] = npdu[offset + i]; + } + } else { + if (BIP_Debug) { + fprintf(stderr, "BIP: NPDU dropped!\n"); + fflush(stderr); + } + npdu_len = 0; + } + } + + return npdu_len; +} + +/** + * The common send function for BACnet/IP application layer + * + * @param dest - Points to a #BACNET_ADDRESS structure containing the + * destination address. + * @param npdu_data - Points to a BACNET_NPDU_DATA structure containing the + * destination network layer control flags and data. + * @param mtu - the bytes of data to send + * @param mtu_len - the number of bytes of data to send + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +int bip_send_pdu(BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + return bvlc_send_pdu(dest, npdu_data, pdu, pdu_len); +} + +/** + * @brief gets an IP address by hostname (or string of numbers) + * + * gets an IP address by name, where name can be a string that is an + * IP address in dotted form, or a name that is a domain name + * + * @param host_name - the host name + * @return true if the address was retrieved + */ +bool bip_get_addr_by_name(const char *host_name, BACNET_IP_ADDRESS *addr) { struct hostent *host_ent; - if ((host_ent = gethostbyname(host_name)) == NULL) - return 0; + if ((host_ent = gethostbyname(host_name)) == NULL) { + return false; + } - return *(long *) host_ent->h_addr; + if (addr) { + /* Host addresses in a struct hostent structure are always + given in network byte order */ + /* h_addr: This is a synonym for h_addr_list[0]; + in other words, it is the first host address.*/ + memcpy(&addr->address[0], host_ent->h_addr, 4); + } + + return true; } -static int get_local_ifr_ioctl( - char *ifname, - struct ifreq *ifr, - int request) +/** + * @brief Issue a specific request for an interface via an ioctl() call. + * @param ifname - the interface name + * @param ifr - interface request + * @param request - the ioctl() request + * @return 0 on success, else the error from the ioctl() call. + */ +static int get_local_ifr_ioctl(char *ifname, struct ifreq *ifr, int request) { int fd; - int rv; /* return value */ + int rv; /* return value */ strncpy(ifr->ifr_name, ifname, sizeof(ifr->ifr_name)); fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); @@ -78,70 +434,81 @@ static int get_local_ifr_ioctl( return rv; } -/* forward prototype required for compilers */ -int get_local_address_ioctl( - char *ifname, - struct in_addr *addr, - int request); - -int get_local_address_ioctl( - char *ifname, - struct in_addr *addr, - int request) +/** + * @brief Issue a specific request foor an interface via an ioctl() call. + * @param ifname - the interface name + * @param addr [out] the address in host order. + * @param request - the ioctl() request + * @return 0 on success, else the error from the ioctl() call. + */ +int bip_get_local_address_ioctl(char *ifname, struct in_addr *addr, int request) { - struct ifreq ifr = { {{0}} }; + struct ifreq ifr = { { { 0 } } }; struct sockaddr_in *tcpip_address; - int rv; /* return value */ + int rv; /* return value */ rv = get_local_ifr_ioctl(ifname, &ifr, request); if (rv >= 0) { - tcpip_address = (struct sockaddr_in *) &ifr.ifr_addr; + tcpip_address = (struct sockaddr_in *)&ifr.ifr_addr; memcpy(addr, &tcpip_address->sin_addr, sizeof(struct in_addr)); } return rv; } +/** + * @brief Get the netmask of the BACnet/IP's interface via an ioctl() call. + * @param netmask [out] The netmask, in host order. + * @return 0 on success, else the error from the ioctl() call. + */ +int bip_get_local_netmask(struct in_addr *netmask) +{ + int rv; + char *ifname = getenv("BACNET_IFACE"); /* will probably be null */ + if (ifname == NULL) { + ifname = "eth0"; + } + rv = bip_get_local_address_ioctl(ifname, netmask, SIOCGIFNETMASK); + return rv; +} + /** Gets the local IP address and local broadcast address from the system, * and saves it into the BACnet/IP data structures. * * @param ifname [in] The named interface to use for the network layer. * Eg, for Linux, ifname is eth0, ath0, arc0, and others. */ -void bip_set_interface( - char *ifname) +void bip_set_interface(char *ifname) { struct in_addr local_address; - struct in_addr broadcast_address; struct in_addr netmask; int rv = 0; /* setup local address */ - rv = get_local_address_ioctl(ifname, &local_address, SIOCGIFADDR); + rv = bip_get_local_address_ioctl(ifname, &local_address, SIOCGIFADDR); if (rv < 0) { local_address.s_addr = 0; } - bip_set_addr(local_address.s_addr); + BIP_Address.s_addr = local_address.s_addr; if (BIP_Debug) { - fprintf(stderr, "Interface: %s\n", ifname); - fprintf(stderr, "IP Address: %s\n", inet_ntoa(local_address)); + fprintf(stderr, "BIP: Interface: %s\n", ifname); + fprintf(stderr, "BIP: Address: %s\n", inet_ntoa(local_address)); + fflush(stderr); } /* setup local broadcast address */ - rv = get_local_address_ioctl(ifname, &netmask, SIOCGIFNETMASK); - + rv = bip_get_local_address_ioctl(ifname, &netmask, SIOCGIFNETMASK); if (rv < 0) { - broadcast_address.s_addr = ~0; + BIP_Broadcast_Addr.s_addr = ~0; + } else { + BIP_Broadcast_Addr = local_address; + BIP_Broadcast_Addr.s_addr |= (~netmask.s_addr); } - else { - broadcast_address = local_address; - broadcast_address.s_addr |= (~netmask.s_addr); - } - bip_set_broadcast_addr(broadcast_address.s_addr); if (BIP_Debug) { - fprintf(stderr, "IP Broadcast Address: %s\n", - inet_ntoa(broadcast_address)); - fprintf(stderr, "UDP Port: 0x%04X [%hu]\n", ntohs(bip_get_port()), - ntohs(bip_get_port())); + fprintf(stderr, "BIP: Broadcast Address: %s\n", + inet_ntoa(BIP_Broadcast_Addr)); + fprintf(stderr, "BIP: UDP Port: 0x%04X [%hu]\n", ntohs(BIP_Port), + ntohs(BIP_Port)); + fflush(stderr); } } @@ -162,87 +529,84 @@ void bip_set_interface( * @return True if the socket is successfully opened for BACnet/IP, * else False if the socket functions fail. */ -bool bip_init( - char *ifname) +bool bip_init(char *ifname) { - int status = 0; /* return from socket lib calls */ + int status = 0; /* return from socket lib calls */ struct sockaddr_in sin; int sockopt = 0; int sock_fd = -1; + char *ifname_default = "eth0"; - if (ifname) + if (ifname) { bip_set_interface(ifname); - else - bip_set_interface("eth0"); + } else { + bip_set_interface(ifname_default); + } + if (BIP_Address.s_addr == 0) { + fprintf(stderr, "BIP: Failed to get an IP address from %s!\n", + ifname?ifname:ifname_default); + fflush(stderr); + return false; + } /* assumes that the driver has already been initialized */ sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - bip_set_socket(sock_fd); - if (sock_fd < 0) + BIP_Socket = sock_fd; + if (sock_fd < 0) { return false; + } /* Allow us to use the same socket for sending and receiving */ /* This makes sure that the src port is correct when sending */ sockopt = 1; - status = - setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &sockopt, - sizeof(sockopt)); + status = setsockopt( + sock_fd, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt)); if (status < 0) { close(sock_fd); - bip_set_socket(-1); + BIP_Socket = -1; return status; } /* allow us to send a broadcast */ - status = - setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, &sockopt, - sizeof(sockopt)); + status = setsockopt( + sock_fd, SOL_SOCKET, SO_BROADCAST, &sockopt, sizeof(sockopt)); if (status < 0) { close(sock_fd); - bip_set_socket(-1); + BIP_Socket = -1; return false; } /* bind the socket to the local port number and IP address */ sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); - sin.sin_port = bip_get_port(); + sin.sin_port = BIP_Port; memset(&(sin.sin_zero), '\0', sizeof(sin.sin_zero)); status = - bind(sock_fd, (const struct sockaddr *) &sin, sizeof(struct sockaddr)); + bind(sock_fd, (const struct sockaddr *)&sin, sizeof(struct sockaddr)); if (status < 0) { close(sock_fd); - bip_set_socket(-1); + BIP_Socket = -1; return false; } + bvlc_init(); return true; } +/** + * @brief Determine if this BACnet/IP datalink is valid + * @return true if the BACnet/IP datalink is valid + */ +bool bip_valid(void) +{ + return (BIP_Socket != -1); +} + /** Cleanup and close out the BACnet/IP services by closing the socket. * @ingroup DLBIP - */ -void bip_cleanup( - void) + */ +void bip_cleanup(void) { - int sock_fd = 0; - - if (bip_valid()) { - sock_fd = bip_socket(); - close(sock_fd); + if (BIP_Socket != -1) { + close(BIP_Socket); } - bip_set_socket(-1); + BIP_Socket = -1; return; } - -/** Get the netmask of the BACnet/IP's interface via an ioctl() call. - * @param netmask [out] The netmask, in host order. - * @return 0 on success, else the error from the ioctl() call. - */ -int bip_get_local_netmask( - struct in_addr *netmask) -{ - int rv; - char *ifname = getenv("BACNET_IFACE"); /* will probably be null */ - if (ifname == NULL) - ifname = "eth0"; - rv = get_local_address_ioctl(ifname, netmask, SIOCGIFNETMASK); - return rv; -} diff --git a/ports/linux/bip6.c b/ports/linux/bip6.c index 03d8a103..d35d1f9a 100644 --- a/ports/linux/bip6.c +++ b/ports/linux/bip6.c @@ -414,7 +414,7 @@ bool bip6_init( bip6_set_interface("eth0"); } if (BIP6_Addr.port == 0) { - bip6_set_port(0xBAC0); + bip6_set_port(0xBAC0U); } debug_printf("BIP6: IPv6 UDP port: 0x%04X\n", htons(BIP6_Addr.port)); if (BIP6_Broadcast_Addr.address[0] == 0) { diff --git a/ports/lwip/bip.c b/ports/lwip/bip.c index 0e76391e..50743814 100644 --- a/ports/lwip/bip.c +++ b/ports/lwip/bip.c @@ -44,7 +44,7 @@ /** @file bip.c Configuration and Operations for BACnet/IP */ /* port to use - stored in network byte order */ -static uint16_t BIP_Port = 0xBAC0; +static uint16_t BIP_Port = 0xBAC0U; static bool BIP_Port_Changed; /* IP Address - stored in network byte order */ static struct in_addr BIP_Address; @@ -102,13 +102,17 @@ uint32_t bip_get_broadcast_addr( return BIP_Broadcast_Address.s_addr; } +/** + * @brief Set the BACnet IPv4 UDP port number + * @param port - IPv4 UDP port number - in host byte order + */ void bip_set_port( uint16_t port) -{ /* in network byte order */ - if (BIP_Port != port) { +{ + if (BIP_Port != htons(port)) { BIP_Port_Changed = true; - BIP_Port = port; -} + BIP_Port = htons(port); + } } bool bip_port_changed(void) @@ -116,11 +120,11 @@ bool bip_port_changed(void) return BIP_Port_Changed; } -/* returns network byte order */ +/* returns host byte order */ uint16_t bip_get_port( void) { - return BIP_Port; + return ntohs(BIP_Port); } static void bip_mac_to_addr( diff --git a/ports/uip/bip.c b/ports/uip/bip.c index 058078e2..ae912740 100644 --- a/ports/uip/bip.c +++ b/ports/uip/bip.c @@ -41,7 +41,7 @@ static int BIP_Socket = -1; /* port to use - stored in host byte order */ -static uint16_t BIP_Port = 0xBAC0; +static uint16_t BIP_Port = 0xBAC0U; /* IP Address - stored in host byte order */ static struct in_addr BIP_Address; /* Broadcast Address - stored in host byte order */ diff --git a/ports/win32/bacport.h b/ports/win32/bacport.h index 6d919714..b9e27e74 100644 --- a/ports/win32/bacport.h +++ b/ports/win32/bacport.h @@ -42,9 +42,15 @@ #if (!defined(USE_INADDR) || (USE_INADDR == 0)) && \ (!defined(USE_CLASSADDR) || (USE_CLASSADDR == 0)) #include +#if defined(_MSC_VER) +#pragma comment(lib, "IPHLPAPI.lib") +#endif #endif #include #include +#if defined(_MSC_VER) +#pragma comment(lib, "Ws2_32.lib") +#endif #include #include #ifdef __MINGW32__ @@ -71,10 +77,6 @@ and globals in favor of more secure versions. */ #define inline __inline #endif -#define close closesocket - -typedef int socklen_t; - #ifdef _WIN32 #define strncasecmp(x, y, z) _strnicmp(x, y, z) #endif diff --git a/ports/win32/bip-init.c b/ports/win32/bip-init.c index 0ebced82..158a9305 100644 --- a/ports/win32/bip-init.c +++ b/ports/win32/bip-init.c @@ -38,167 +38,64 @@ #include /* for standard integer types uint8_t etc. */ #include /* for the standard bool type. */ #include "bacnet/bacdcode.h" +#include "bacnet/bacint.h" #include "bacnet/config.h" #include "bacnet/datalink/bip.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/bbmd/h_bbmd.h" #include "bacport.h" -#if defined(_MSC_VER) -#pragma comment(lib, "Ws2_32.lib") -#pragma comment(lib, "IPHLPAPI.lib") +/* alternate methods of choosing broadcast address */ +#ifndef USE_INADDR +#define USE_INADDR 0 +#endif +#ifndef USE_CLASSADDR +#define USE_CLASSADDR 0 #endif -bool BIP_Debug = false; +/* Windows socket */ +static SOCKET BIP_Socket = INVALID_SOCKET; -/* gets an IP address by name, where name can be a - string that is an IP address in dotted form, or - a name that is a domain name - returns 0 if not found, or - an IP address in network byte order */ -long bip_getaddrbyname( - const char *host_name) +/* NOTE: we store address and port in network byte order + since BACnet/IP uses network byte order for all address byte arrays +*/ +/* port to use - stored here in network byte order */ +/* Initialize to 0 - this will force initialization in demo apps */ +static uint16_t BIP_Port; +/* IP address - stored here in network byte order */ +static struct in_addr BIP_Address; +/* IP broadcast address - stored here in network byte order */ +static struct in_addr BIP_Broadcast_Address; + +/* enable debugging */ +static bool BIP_Debug = false; + +/** + * @brief Print the IPv4 address with debug info + * @param str - debug info string + * @param addr - IPv4 address + */ +static void debug_print_ipv4(const char *str, const struct in_addr *addr, + const unsigned int port, const unsigned int count) { - struct hostent *host_ent; - - if ((host_ent = gethostbyname(host_name)) == NULL) - return 0; - - return *(long *) host_ent->h_addr; -} - -/* To fill a need, we invent the gethostaddr() function. */ -static long gethostaddr( - void) -{ - struct hostent *host_ent; - char host_name[255]; - - if (gethostname(host_name, sizeof(host_name)) != 0) - return -1; - - if ((host_ent = gethostbyname(host_name)) == NULL) - return -1; if (BIP_Debug) { - printf("host: %s at %u.%u.%u.%u\n", host_name, - (unsigned) ((uint8_t *) host_ent->h_addr)[0], - (unsigned) ((uint8_t *) host_ent->h_addr)[1], - (unsigned) ((uint8_t *) host_ent->h_addr)[2], - (unsigned) ((uint8_t *) host_ent->h_addr)[3]); + fprintf(stderr, "BIP: %s %s:%hu (%u bytes)\n", str, inet_ntoa(*addr), + ntohs(port), count); + fflush(stderr); } - /* note: network byte order */ - return *(long *) host_ent->h_addr; } -#if (!defined(USE_INADDR) || (USE_INADDR == 0)) && \ - (!defined(USE_CLASSADDR) || (USE_CLASSADDR == 0)) -/* returns the subnet mask in network byte order */ -static uint32_t getIpMaskForIpAddress( - uint32_t ipAddress) +/** + * @brief Enabled debug printing of BACnet/IPv4 + */ +void bip_debug_enable(void) { - /* Allocate information for up to 16 NICs */ - IP_ADAPTER_INFO AdapterInfo[16]; - /* Save memory size of buffer */ - DWORD dwBufLen = sizeof(AdapterInfo); - uint32_t ipMask = INADDR_BROADCAST; - bool found = false; - - PIP_ADAPTER_INFO pAdapterInfo; - - /* GetAdapterInfo: - [out] buffer to receive data - [in] size of receive data buffer */ - DWORD dwStatus = GetAdaptersInfo(AdapterInfo, - &dwBufLen); - if (dwStatus == ERROR_SUCCESS) { - /* Verify return value is valid, no buffer overflow - Contains pointer to current adapter info */ - pAdapterInfo = AdapterInfo; - - do { - IP_ADDR_STRING *pIpAddressInfo = &pAdapterInfo->IpAddressList; - do { - unsigned long adapterAddress = - inet_addr(pIpAddressInfo->IpAddress.String); - unsigned long adapterMask = - inet_addr(pIpAddressInfo->IpMask.String); - if (adapterAddress == ipAddress) { - ipMask = adapterMask; - found = true; - } - pIpAddressInfo = pIpAddressInfo->Next; - } while (pIpAddressInfo && !found); - /* Progress through linked list */ - pAdapterInfo = pAdapterInfo->Next; - /* Terminate on last adapter */ - } while (pAdapterInfo && !found); - } - - return ipMask; -} -#endif - -static void set_broadcast_address( - uint32_t net_address) -{ -#if defined(USE_INADDR) && USE_INADDR - /* Note: sometimes INADDR_BROADCAST does not let me get - any unicast messages. Not sure why... */ - net_address = net_address; - bip_set_broadcast_addr(INADDR_BROADCAST); -#elif defined(USE_CLASSADDR) && USE_CLASSADDR - long broadcast_address = 0; - - if (IN_CLASSA(ntohl(net_address))) - broadcast_address = - (ntohl(net_address) & ~IN_CLASSA_HOST) | IN_CLASSA_HOST; - else if (IN_CLASSB(ntohl(net_address))) - broadcast_address = - (ntohl(net_address) & ~IN_CLASSB_HOST) | IN_CLASSB_HOST; - else if (IN_CLASSC(ntohl(net_address))) - broadcast_address = - (ntohl(net_address) & ~IN_CLASSC_HOST) | IN_CLASSC_HOST; - else if (IN_CLASSD(ntohl(net_address))) - broadcast_address = - (ntohl(net_address) & ~IN_CLASSD_HOST) | IN_CLASSD_HOST; - else - broadcast_address = INADDR_BROADCAST; - bip_set_broadcast_addr(htonl(broadcast_address)); -#else - /* these are network byte order variables */ - long broadcast_address = 0; - long net_mask = 0; - - net_mask = getIpMaskForIpAddress(net_address); - if (BIP_Debug) { - struct in_addr address; - address.s_addr = net_mask; - printf("IP Mask: %s\n", inet_ntoa(address)); - } - broadcast_address = (net_address & net_mask) | (~net_mask); - bip_set_broadcast_addr(broadcast_address); -#endif -} - -/* on Windows, ifname is the dotted ip address of the interface */ -void bip_set_interface( - char *ifname) -{ - struct in_addr address; - - /* setup local address */ - if (bip_get_addr() == 0) { - bip_set_addr(inet_addr(ifname)); - } - if (BIP_Debug) { - address.s_addr = bip_get_addr(); - fprintf(stderr, "Interface: %s\n", ifname); - } - /* setup local broadcast address */ - if (bip_get_broadcast_addr() == 0) { - address.s_addr = bip_get_addr(); - set_broadcast_address(address.s_addr); - } + BIP_Debug = true; } +/** + * @brief Get the text string for Windows Error Codes + */ static char *winsock_error_code_text( int code) { @@ -316,6 +213,510 @@ static char *winsock_error_code_text( } } +/** + * @brief Print the text string for the last Windows Error Code + */ +static void print_last_error(const char *info) +{ + int Code = WSAGetLastError(); + fprintf(stderr, "BIP: %s [error code %i] %s\n", + info, Code, winsock_error_code_text(Code)); + fflush(stderr); +} + +/** + * @brief Initialize the Windows Socket Layer + */ +static void bip_init_windows(void) +{ + static bool initialized = false; + int Result; + WSADATA wd; + + if (!initialized) { + Result = WSAStartup((1 << 8) | 1, &wd); + /*Result = WSAStartup(MAKEWORD(2,2), &wd); */ + if (Result != 0) { + print_last_error("TCP/IP stack initialization failed"); + exit(1); + } + initialized = true; + atexit(bip_cleanup); + } +} + +/** + * @brief Set the BACnet IPv4 UDP port number + * @param port - IPv4 UDP port number - in host byte order + */ +void bip_set_port(uint16_t port) +{ + BIP_Port = htons(port); +} + +/** + * @brief Get the BACnet IPv4 UDP port number + * @return IPv4 UDP port number - in host byte order + */ +uint16_t bip_get_port(void) +{ + return ntohs(BIP_Port); +} + +/** + * @brief Get the IPv4 address for my interface. Used for sending src address. + * @param addr - BACnet datalink address + */ +void bip_get_my_address(BACNET_ADDRESS *addr) +{ + unsigned int i = 0; + + if (addr) { + addr->mac_len = 6; + memcpy(&addr->mac[0], &BIP_Address.s_addr, 4); + memcpy(&addr->mac[4], &BIP_Port, 2); + /* local only, no routing */ + addr->net = 0; + /* no SLEN */ + addr->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + addr->adr[i] = 0; + } + } +} + +/** + * Get the IPv4 broadcast address for my interface. + * + * @param addr - BACnet datalink address + */ +void bip_get_broadcast_address(BACNET_ADDRESS *dest) +{ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 6; + memcpy(&dest->mac[0], &BIP_Broadcast_Address.s_addr, 4); + memcpy(&dest->mac[4], &BIP_Port, 2); + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* no SLEN */ + for (i = 0; i < MAX_MAC_LEN; i++) { + /* no SADR */ + dest->adr[i] = 0; + } + } + + return; +} + +/** + * Set the BACnet/IP address + * + * @param addr - network IPv4 address + */ +bool bip_set_addr(BACNET_IP_ADDRESS *addr) +{ + /* not something we do here within this application */ + return false; +} + +/** + * @brief Get the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address was retrieved + */ +bool bip_get_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP_Address.s_addr, 4); + addr->port = ntohs(BIP_Port); + } + + return true; +} + +/** + * @brief Set the BACnet/IP address + * @param addr - network IPv4 address + * @return true if the address was set + */ +bool bip_set_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + /* not something we do within this application */ + return false; +} + +/** + * Get the BACnet/IP address + * + * @return BACnet/IP address + */ +bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + if (addr) { + memcpy(&addr->address[0], &BIP_Broadcast_Address.s_addr, 4); + addr->port = ntohs(BIP_Port); + } + + return true; +} + +/** + * @brief Set the BACnet/IP subnet mask CIDR prefix + * @return true if the subnet mask CIDR prefix is set + */ +bool bip_set_subnet_prefix(uint8_t prefix) +{ + /* not something we do within this application */ + return false; +} + +/** + * @brief Get the BACnet/IP subnet mask CIDR prefix + * @return subnet mask CIDR prefix + */ +uint8_t bip_get_subnet_prefix(void) +{ + uint32_t address = 0; + uint32_t broadcast = 0; + uint32_t test_broadcast = 0; + uint32_t mask = 0xFFFFFFFE; + uint8_t prefix = 0; + + address = BIP_Broadcast_Address.s_addr; + broadcast = BIP_Broadcast_Address.s_addr; + /* calculate the subnet prefix from the broadcast address */ + for (prefix = 1; prefix <= 32; prefix++) { + test_broadcast = (address & mask) | (~mask); + if (test_broadcast == broadcast) { + break; + } + mask = mask<<1; + } + + return prefix; +} + +/** + * The send function for BACnet/IP driver layer + * + * @param dest - Points to a BACNET_IP_ADDRESS structure containing the + * destination address. + * @param mtu - the bytes of data to send + * @param mtu_len - the number of bytes of data to send + * + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +int bip_send_mpdu(BACNET_IP_ADDRESS *dest, uint8_t *mtu, uint16_t mtu_len) +{ + struct sockaddr_in bip_dest = { 0 }; + int rv = 0; + + /* assumes that the driver has already been initialized */ + if (BIP_Socket == INVALID_SOCKET) { + if (BIP_Debug) { + fprintf(stderr, "BIP: driver not initialized!\n"); + fflush(stderr); + } + return -1; + } + /* load destination IP address */ + bip_dest.sin_family = AF_INET; + memcpy(&bip_dest.sin_addr.s_addr, &dest->address[0], 4); + bip_dest.sin_port = htons(dest->port); + /* Send the packet */ + debug_print_ipv4("Sending MPDU->", &bip_dest.sin_addr, bip_dest.sin_port, + mtu_len); + rv = sendto(BIP_Socket, (char *)mtu, mtu_len, 0, + (struct sockaddr *)&bip_dest, sizeof(struct sockaddr)); + if (rv == SOCKET_ERROR) { + print_last_error("sendto"); + } + + return rv; +} + +/** + * BACnet/IP Datalink Receive handler. + * + * @param src - returns the source address + * @param npdu - returns the NPDU buffer + * @param max_npdu -maximum size of the NPDU buffer + * @param timeout - number of milliseconds to wait for a packet + * + * @return Number of bytes received, or 0 if none or timeout. + */ +uint16_t bip_receive( + BACNET_ADDRESS *src, uint8_t *npdu, uint16_t max_npdu, unsigned timeout) +{ + uint16_t npdu_len = 0; /* return value */ + fd_set read_fds; + int max = 0; + struct timeval select_timeout; + struct sockaddr_in sin = { 0 }; + BACNET_IP_ADDRESS addr = { { 0 } }; + socklen_t sin_len = sizeof(sin); + int received_bytes = 0; + int offset = 0; + uint16_t i = 0; + + /* Make sure the socket is open */ + if (BIP_Socket == INVALID_SOCKET) { + return 0; + } + /* we could just use a non-blocking socket, but that consumes all + the CPU time. We can use a timeout; it is only supported as + a select. */ + if (timeout >= 1000) { + select_timeout.tv_sec = timeout / 1000; + select_timeout.tv_usec = + 1000 * (timeout - select_timeout.tv_sec * 1000); + } else { + select_timeout.tv_sec = 0; + select_timeout.tv_usec = 1000 * timeout; + } + FD_ZERO(&read_fds); + FD_SET(BIP_Socket, &read_fds); + max = BIP_Socket; + /* see if there is a packet for us */ + if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { + received_bytes = recvfrom(BIP_Socket, (char *)&npdu[0], max_npdu, 0, + (struct sockaddr *)&sin, &sin_len); + } else { + return 0; + } + /* See if there is a problem */ + if (received_bytes < 0) { + return 0; + } + /* no problem, just no bytes */ + if (received_bytes == 0) { + return 0; + } + /* the signature of a BACnet/IPv packet */ + if (npdu[0] != BVLL_TYPE_BACNET_IP) { + return 0; + } + /* Data link layer addressing between B/IPv4 nodes consists of a 32-bit + IPv4 address followed by a two-octet UDP port number (both of which + shall be transmitted with the most significant octet first). This + address shall be referred to as a B/IPv4 address. + */ + memcpy(&addr.address[0], &sin.sin_addr.s_addr, 4); + addr.port = ntohs(sin.sin_port); + debug_print_ipv4("Received MPDU->", &sin.sin_addr, sin.sin_port, + received_bytes); + /* pass the packet into the BBMD handler */ + offset = bvlc_handler(&addr, src, npdu, received_bytes); + if (offset > 0) { + npdu_len = received_bytes - offset; + if (npdu_len <= max_npdu) { + /* shift the buffer to return a valid NPDU */ + for (i = 0; i < npdu_len; i++) { + npdu[i] = npdu[offset + i]; + } + } else { + npdu_len = 0; + } + } + + return npdu_len; +} + +/** + * The common send function for BACnet/IP application layer + * + * @param dest - Points to a #BACNET_ADDRESS structure containing the + * destination address. + * @param npdu_data - Points to a BACNET_NPDU_DATA structure containing the + * destination network layer control flags and data. + * @param mtu - the bytes of data to send + * @param mtu_len - the number of bytes of data to send + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +int bip_send_pdu(BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + return bvlc_send_pdu(dest, npdu_data, pdu, pdu_len); +} + +/** + * @brief gets an IP address by hostname (or string of numbers) + * + * gets an IP address by name, where name can be a string that is an + * IP address in dotted form, or a name that is a domain name + * + * @param host_name - the host name + * @return true if the address was retrieved + */ +bool bip_get_addr_by_name(const char *host_name, BACNET_IP_ADDRESS *addr) +{ + struct hostent *host_ent; + + bip_init_windows(); + if ((host_ent = gethostbyname(host_name)) == NULL) { + return false; + } + + if (addr) { + /* Host addresses in a struct hostent structure are always + given in network byte order */ + /* h_addr: This is a synonym for h_addr_list[0]; + in other words, it is the first host address.*/ + memcpy(&addr->address[0], host_ent->h_addr, 4); + } + + return true; +} + +/* To fill a need, we invent the gethostaddr() function. */ +static long gethostaddr( + void) +{ + struct hostent *host_ent; + char host_name[255]; + + if (gethostname(host_name, sizeof(host_name)) != 0) { + print_last_error("gethostname"); + exit(1); + } + if ((host_ent = gethostbyname(host_name)) == NULL) { + print_last_error("gethostbyname"); + exit(1); + } + if (BIP_Debug) { + fprintf(stderr, "BIP: host %s at %u.%u.%u.%u\n", host_name, + (unsigned) ((uint8_t *) host_ent->h_addr)[0], + (unsigned) ((uint8_t *) host_ent->h_addr)[1], + (unsigned) ((uint8_t *) host_ent->h_addr)[2], + (unsigned) ((uint8_t *) host_ent->h_addr)[3]); + fflush(stderr); + } + /* note: network byte order */ + return *(long *) host_ent->h_addr; +} + +#if ((USE_INADDR == 0) || (USE_CLASSADDR == 0)) +/* returns the subnet mask in network byte order */ +static uint32_t getIpMaskForIpAddress( + uint32_t ipAddress) +{ + /* Allocate information for up to 16 NICs */ + IP_ADAPTER_INFO AdapterInfo[16]; + /* Save memory size of buffer */ + DWORD dwBufLen = sizeof(AdapterInfo); + uint32_t ipMask = INADDR_BROADCAST; + bool found = false; + + PIP_ADAPTER_INFO pAdapterInfo; + + /* GetAdapterInfo: + [out] buffer to receive data + [in] size of receive data buffer */ + DWORD dwStatus = GetAdaptersInfo(AdapterInfo, + &dwBufLen); + if (dwStatus == ERROR_SUCCESS) { + /* Verify return value is valid, no buffer overflow + Contains pointer to current adapter info */ + pAdapterInfo = AdapterInfo; + + do { + IP_ADDR_STRING *pIpAddressInfo = &pAdapterInfo->IpAddressList; + do { + unsigned long adapterAddress = + inet_addr(pIpAddressInfo->IpAddress.String); + unsigned long adapterMask = + inet_addr(pIpAddressInfo->IpMask.String); + if (adapterAddress == ipAddress) { + ipMask = adapterMask; + found = true; + } + pIpAddressInfo = pIpAddressInfo->Next; + } while (pIpAddressInfo && !found); + /* Progress through linked list */ + pAdapterInfo = pAdapterInfo->Next; + /* Terminate on last adapter */ + } while (pAdapterInfo && !found); + } + + return ipMask; +} +#endif + +static void set_broadcast_address( + uint32_t net_address) +{ +#if USE_INADDR + /* Note: sometimes INADDR_BROADCAST does not let me get + any unicast messages. Not sure why... */ + (void)net_address; + BIP_Broadcast_Address.s_addr = INADDR_BROADCAST; +#elif USE_CLASSADDR + long broadcast_address = 0; + + if (IN_CLASSA(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSA_HOST) | IN_CLASSA_HOST; + else if (IN_CLASSB(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSB_HOST) | IN_CLASSB_HOST; + else if (IN_CLASSC(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSC_HOST) | IN_CLASSC_HOST; + else if (IN_CLASSD(ntohl(net_address))) + broadcast_address = + (ntohl(net_address) & ~IN_CLASSD_HOST) | IN_CLASSD_HOST; + else + broadcast_address = INADDR_BROADCAST; + BIP_Broadcast_Address.s_addr = htonl(broadcast_address)); +#else + /* these are network byte order variables */ + long broadcast_address = 0; + long net_mask = 0; + + net_mask = getIpMaskForIpAddress(net_address); + if (BIP_Debug) { + struct in_addr address; + address.s_addr = net_mask; + fprintf(stderr, "BIP: net mask: %s\n", inet_ntoa(address)); + fflush(stderr); + } + broadcast_address = (net_address & net_mask) | (~net_mask); + BIP_Broadcast_Address.s_addr = broadcast_address; +#endif +} + +/** + * @brief Gets the local IP address and local broadcast address from the + * system, and saves it into the BACnet/IP data structures. + * + * @param ifname [in] The named interface to use for the network layer. + * Eg, for Windows, ifname is the dotted ip address of the interface + */ +void bip_set_interface( + char *ifname) +{ + bip_init_windows(); + /* setup local address */ + if (BIP_Address.s_addr == 0) { + BIP_Address.s_addr = inet_addr(ifname); + } + if (BIP_Debug) { + fprintf(stderr, "BIP: Interface: %s\n", ifname); + fprintf(stderr, "BIP: Address: %s\n", inet_ntoa(BIP_Address)); + fflush(stderr); + } + /* setup local broadcast address */ + if (BIP_Broadcast_Address.s_addr == 0) { + set_broadcast_address(BIP_Address.s_addr); + } +} + /** Initialize the BACnet/IP services at the given interface. * @ingroup DLBIP * -# Gets the local IP address and local broadcast address from the system, @@ -339,56 +740,33 @@ bool bip_init( int rv = 0; /* return from socket lib calls */ struct sockaddr_in sin = { -1 }; int value = 1; - int sock_fd = -1; - int Result; - int Code; - WSADATA wd; - struct in_addr address; - struct in_addr broadcast_address; + SOCKET sock_fd = INVALID_SOCKET; - Result = WSAStartup((1 << 8) | 1, &wd); - /*Result = WSAStartup(MAKEWORD(2,2), &wd); */ - if (Result != 0) { - Code = WSAGetLastError(); - printf("TCP/IP stack initialization failed\n" " error code: %i %s\n", - Code, winsock_error_code_text(Code)); - exit(1); - } - atexit(bip_cleanup); - - if (ifname) + bip_init_windows(); + if (ifname) { bip_set_interface(ifname); - /* has address been set? */ - address.s_addr = bip_get_addr(); - if (address.s_addr == 0) { - address.s_addr = gethostaddr(); - if (address.s_addr == (unsigned) -1) { - Code = WSAGetLastError(); - printf("Get host address failed\n" " error code: %i %s\n", Code, - winsock_error_code_text(Code)); - exit(1); - } - bip_set_addr(address.s_addr); } - if (BIP_Debug) { - fprintf(stderr, "IP Address: %s\n", inet_ntoa(address)); + /* has address been set? */ + if (BIP_Address.s_addr == 0) { + BIP_Address.s_addr = gethostaddr(); } /* has broadcast address been set? */ - if (bip_get_broadcast_addr() == 0) { - set_broadcast_address(address.s_addr); + if (BIP_Broadcast_Address.s_addr == 0) { + set_broadcast_address(BIP_Address.s_addr); } if (BIP_Debug) { - broadcast_address.s_addr = bip_get_broadcast_addr(); - fprintf(stderr, "IP Broadcast Address: %s\n", - inet_ntoa(broadcast_address)); - fprintf(stderr, "UDP Port: 0x%04X [%hu]\n", ntohs(bip_get_port()), - ntohs(bip_get_port())); + fprintf(stderr, "BIP: Address: %s\n", inet_ntoa(BIP_Address)); + fprintf(stderr, "BIP: Broadcast Address: %s\n", + inet_ntoa(BIP_Broadcast_Address)); + fprintf(stderr, "BIP: UDP Port: 0x%04X [%hu]\n", ntohs(BIP_Port), + ntohs(BIP_Port)); + fflush(stderr); } /* assumes that the driver has already been initialized */ sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - bip_set_socket(sock_fd); + BIP_Socket = sock_fd; if (sock_fd < 0) { - fprintf(stderr, "bip: failed to allocate a socket.\n"); + print_last_error("failed to allocate a socket"); return false; } /* Allow us to use the same socket for sending and receiving */ @@ -396,36 +774,23 @@ bool bip_init( rv = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &value, sizeof(value)); if (rv < 0) { - fprintf(stderr, "bip: failed to set REUSEADDR socket option.\n"); - close(sock_fd); - bip_set_socket(-1); + print_last_error("failed to set REUSEADDR socket option"); + closesocket(sock_fd); + BIP_Socket = INVALID_SOCKET; return false; } /* Enables transmission and receipt of broadcast messages on the socket. */ rv = setsockopt(sock_fd, SOL_SOCKET, SO_BROADCAST, (char *) &value, sizeof(value)); if (rv < 0) { - fprintf(stderr, "bip: failed to set BROADCAST socket option.\n"); - close(sock_fd); - bip_set_socket(-1); + print_last_error("failed to set BROADCAST socket option"); + closesocket(sock_fd); + BIP_Socket = INVALID_SOCKET; return false; } -#if 0 - /* probably only for Apple... */ - /* rebind a port that is already in use. - Note: all users of the port must specify this flag */ - rv = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, (char *) &value, - sizeof(value)); - if (rv < 0) { - fprintf(stderr, "bip: failed to set REUSEPORT socket option.\n"); - close(sock_fd); - bip_set_socket(-1); - return false; - } -#endif /* bind the socket to the local port number and IP address */ sin.sin_family = AF_INET; -#if defined(USE_INADDR) && USE_INADDR +#if USE_INADDR /* by setting sin.sin_addr.s_addr to INADDR_ANY, I am telling the IP stack to automatically fill in the IP address of the machine the process @@ -442,36 +807,49 @@ bool bip_init( #else /* or we could use the specific adapter address note: already in network byte order */ - sin.sin_addr.s_addr = address.s_addr; + sin.sin_addr.s_addr = BIP_Address.s_addr; #endif - sin.sin_port = bip_get_port(); + sin.sin_port = BIP_Port; memset(&(sin.sin_zero), '\0', sizeof(sin.sin_zero)); + if (BIP_Debug) { + fprintf(stderr, "BIP: bind %s:%hu\n", inet_ntoa(sin.sin_addr), + ntohs(sin.sin_port)); + fflush(stderr); + } rv = bind(sock_fd, (const struct sockaddr *) &sin, sizeof(struct sockaddr)); if (rv < 0) { - fprintf(stderr, "bip: failed to bind to %s port %hu\n", - inet_ntoa(sin.sin_addr), ntohs(bip_get_port())); - close(sock_fd); - bip_set_socket(-1); + print_last_error("failed to bind"); + closesocket(sock_fd); + BIP_Socket = INVALID_SOCKET; return false; } return true; } +/** + * @brief Determine if this BACnet/IP datalink is valid + * @return true if the BACnet/IP datalink is valid + */ +bool bip_valid(void) +{ + return (BIP_Socket != INVALID_SOCKET); +} + /** Cleanup and close out the BACnet/IP services by closing the socket. * @ingroup DLBIP */ void bip_cleanup( void) { - int sock_fd = 0; + SOCKET sock_fd = 0; - if (bip_valid()) { - sock_fd = bip_socket(); - close(sock_fd); + if (BIP_Socket != INVALID_SOCKET) { + sock_fd = BIP_Socket; + closesocket(sock_fd); } - bip_set_socket(-1); + BIP_Socket = INVALID_SOCKET; WSACleanup(); return; diff --git a/ports/win32/bip6.c b/ports/win32/bip6.c index 8542d7dc..55742385 100644 --- a/ports/win32/bip6.c +++ b/ports/win32/bip6.c @@ -530,7 +530,7 @@ bool bip6_init( exit(1); } if (BIP6_Addr.port == 0) { - bip6_set_port(0xBAC0); + bip6_set_port(0xBAC0U); } debug_printf("BIP6: IPv6 UDP port: 0x%04X\n", BIP6_Addr.port); bip6_set_interface(ifname); diff --git a/src/Makefile b/src/Makefile index 132d004d..e1457886 100644 --- a/src/Makefile +++ b/src/Makefile @@ -22,7 +22,7 @@ BACNET_PORT_DIR ?= $(realpath ../ports/$(BACNET_PORT)) BACNET_PORT_SRC ?= \ $(BACNET_PORT_DIR)/bip-init.c \ $(BACNET_SRC_DIR)/bacnet/datalink/bvlc.c \ - $(BACNET_SRC_DIR)/bacnet/datalink/bip.c + $(BACNET_SRC_DIR)/bacnet/basic/bbmd/h_bbmd.c # include file search paths BACNET_INCLUDES = -I$(BACNET_SRC_DIR) -I$(BACNET_PORT_DIR) diff --git a/src/bacnet/bacenum.h b/src/bacnet/bacenum.h index 31d005f9..56c72ade 100644 --- a/src/bacnet/bacenum.h +++ b/src/bacnet/bacenum.h @@ -1472,33 +1472,6 @@ typedef enum { /* for definition by ASHRAE. */ } BACNET_SERVICES_SUPPORTED; -typedef enum { - BVLC_RESULT = 0, - BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE = 1, - BVLC_READ_BROADCAST_DIST_TABLE = 2, - BVLC_READ_BROADCAST_DIST_TABLE_ACK = 3, - BVLC_FORWARDED_NPDU = 4, - BVLC_REGISTER_FOREIGN_DEVICE = 5, - BVLC_READ_FOREIGN_DEVICE_TABLE = 6, - BVLC_READ_FOREIGN_DEVICE_TABLE_ACK = 7, - BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY = 8, - BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK = 9, - BVLC_ORIGINAL_UNICAST_NPDU = 10, - BVLC_ORIGINAL_BROADCAST_NPDU = 11, - BVLC_SECURE_BVLL = 12, - MAX_BVLC_FUNCTION = 13 -} BACNET_BVLC_FUNCTION; - -typedef enum { - BVLC_RESULT_SUCCESSFUL_COMPLETION = 0x0000, - BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK = 0x0010, - BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK = 0x0020, - BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK = 0X0030, - BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK = 0x0040, - BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK = 0x0050, - BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK = 0x0060 -} BACNET_BVLC_RESULT; - /* Bit String Enumerations */ typedef enum { STATUS_FLAG_IN_ALARM = 0, diff --git a/src/bacnet/basic/bbmd/h_bbmd.c b/src/bacnet/basic/bbmd/h_bbmd.c new file mode 100644 index 00000000..c6fe0ff7 --- /dev/null +++ b/src/bacnet/basic/bbmd/h_bbmd.c @@ -0,0 +1,1394 @@ +/*####COPYRIGHTBEGIN#### + ------------------------------------------- + Copyright (C) 2020 Steve Karg + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + The Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA. + + As a special exception, if other files instantiate templates or + use macros or inline functions from this file, or you compile + this file and link it with other works to produce a work based + on this file, this file does not by itself cause the resulting + work to be covered by the GNU General Public License. However + the source code for this file must still be made available in + accordance with section (3) of the GNU General Public License. + + This exception does not invalidate any other reasons why a work + based on this file might be covered by the GNU General Public + License. + ------------------------------------------- +####COPYRIGHTEND####*/ + +#include /* for standard i/o, like printing */ +#include /* for standard integer types uint8_t etc. */ +#include /* for the standard bool type. */ +#include /* for memcpy */ +#include "bacnet/bacdcode.h" +#include "bacnet/datalink/bip.h" +#include "bacnet/datalink/bvlc.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/bbmd/h_bbmd.h" + +/* Define BBMD_ENABLED to get the functions that a + * BBMD needs to handle its services. + * Separately, define BBMD_CLIENT_ENABLED to get the + * functions that allow a client to manage a BBMD. + */ +#ifndef BBMD_ENABLED +#define BBMD_ENABLED 1 +#endif + +#if BBMD_ENABLED +#ifndef BBMD_CLIENT_ENABLED +#define BBMD_CLIENT_ENABLED 1 +#endif +#endif + +#ifndef PRINT_ENABLED +#define PRINT_ENABLED 0 +#endif + +/** enable debugging */ +static bool BVLC_Debug = false; +/** result from a client request */ +static uint16_t BVLC_Result_Code = BVLC_RESULT_SUCCESSFUL_COMPLETION; +/** incoming function */ +static uint8_t BVLC_Function_Code = BVLC_RESULT; +/** Global IP address for NAT handling */ +static BACNET_IP_ADDRESS BVLC_Global_Address; +/** Flag to indicate if NAT handling is enabled/disabled */ +static bool BVLC_NAT_Handling = false; +/** if we are a foreign device, store the remote BBMD address/port here */ +static BACNET_IP_ADDRESS Remote_BBMD; +#if BBMD_ENABLED +/* local buffer & length for sending */ +static uint8_t BVLC_Buffer[MAX_MPDU]; +static uint16_t BVLC_Buffer_Len; +/* Broadcast Distribution Table */ +#ifndef MAX_BBMD_ENTRIES +#define MAX_BBMD_ENTRIES 128 +#endif +static BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY + BBMD_Table[MAX_BBMD_ENTRIES]; +/* Foreign Device Table */ +#ifndef MAX_FD_ENTRIES +#define MAX_FD_ENTRIES 128 +#endif +static BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY FD_Table[MAX_FD_ENTRIES]; +#endif + +/** + * @brief Enabled debug printing of BACnet/IPv4 BBMD + */ +void bvlc_debug_enable(void) +{ + BVLC_Debug = true; +} + +/** + * @brief Print the IPv4 address with debug info for this module + * @param str - debug info string + * @param addr - IPv4 address + */ +static void debug_print_bip(const char *str, const BACNET_IP_ADDRESS *addr) +{ +#if PRINT_ENABLED + if (BVLC_Debug) { + printf("BVLC: %s %u.%u.%u.%u:%u\n", str, (unsigned)addr->address[0], + (unsigned)addr->address[1], (unsigned)addr->address[2], + (unsigned)addr->address[3], (unsigned)addr->port); + } +#endif +} + +/** + * @brief Print an unsigned value with debug info for this module + * @param str - debug info string + * @param value - unsigned int value + */ +static void debug_print_unsigned(const char *str, const unsigned int value) +{ +#if PRINT_ENABLED + if (BVLC_Debug) { + printf("BVLC: %s %u\n", str, value); + } +#endif +} + +/** + * @brief Print an unsigned value with debug info for this module + * @param str - debug info string + * @param value - unsigned int value + */ +static void debug_print_npdu( + const char *str, const unsigned int offset, const unsigned int length) +{ +#if PRINT_ENABLED + if (BVLC_Debug) { + printf("BVLC: %s NPDU=MTU[%u] len=%u\n", str, offset, length); + } +#endif +} + +/** + * @brief Print debug info for this module + * @param str - debug info string + */ +static void debug_print_string(const char *str) +{ +#if PRINT_ENABLED + if (BVLC_Debug) { + printf("BVLC: %s\n", str); + } +#endif +} + +#if BBMD_ENABLED +/* Define BBMD_BACKUP_FILE if the contents of the BDT + * (broadcast distribution table) are to be stored in + * a backup file, so the contents are not lost across + * power failures, shutdowns, etc... + * (this is required behaviour as defined in BACnet standard). + * + * BBMD_BACKUP_FILE should be set to the file name + * in which to store the BDT. + */ +#ifndef BBMD_BACKUP_FILE +#define BBMD_BACKUP_FILE BACnet_BDT_table +#endif +#if defined(BBMD_BACKUP_FILE) +#define tostr(a) str(a) +#define str(a) #a + +void bvlc_bdt_backup_local(void) +{ + static FILE *bdt_file_ptr = NULL; + + /* only try opening the file if not already opened previously */ + if (!bdt_file_ptr) { + bdt_file_ptr = fopen(tostr(BBMD_BACKUP_FILE), "wb"); + } + + /* if error opening file for writing -> silently abort */ + if (!bdt_file_ptr) { + return; + } + + fseek(bdt_file_ptr, 0, SEEK_SET); + fwrite(BBMD_Table, sizeof(BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY), + MAX_BBMD_ENTRIES, bdt_file_ptr); + fflush(bdt_file_ptr); +} + +void bvlc_bdt_restore_local(void) +{ + static FILE *bdt_file_ptr = NULL; + + /* only try opening the file if not already opened previously */ + if (!bdt_file_ptr) { + bdt_file_ptr = fopen(tostr(BBMD_BACKUP_FILE), "rb"); + } + + /* if error opening file for reading -> silently abort */ + if (!bdt_file_ptr) { + return; + } + + fseek(bdt_file_ptr, 0, SEEK_SET); + { + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY + BBMD_Table_tmp[MAX_BBMD_ENTRIES]; + size_t entries = 0; + + entries = fread(BBMD_Table_tmp, + sizeof(BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY), + MAX_BBMD_ENTRIES, bdt_file_ptr); + if (entries == MAX_BBMD_ENTRIES) { + /* success reading the BDT table. */ + memcpy(BBMD_Table, BBMD_Table_tmp, + sizeof(BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY) * + MAX_BBMD_ENTRIES); + } + } +} +#else +void bvlc_bdt_backup_local(void) +{ +} +void bvlc_bdt_restore_local(void) +{ +} +#endif +#endif + +/** A timer function that is called about once a second. + * + * @param seconds - number of elapsed seconds since the last call + */ +void bvlc_maintenance_timer(uint16_t seconds) +{ +#if BBMD_ENABLED + bvlc_foreign_device_table_maintenance_timer(&FD_Table[0], seconds); +#endif +} + +/** + * Compares the IP source address to my IP address + * + * @param addr - IP source address + * + * @return true if the IP from sin match me + */ +static bool bbmd_address_match_self(BACNET_IP_ADDRESS *addr) +{ + BACNET_IP_ADDRESS my_addr = { 0 }; + bool status = false; + + if (bip_get_addr(&my_addr)) { + if (!bvlc_address_different(&my_addr, addr)) { + status = true; + } + } + + return status; +} + +#if BBMD_ENABLED +/** Determines if a BDT member has a unicast mask + * + * @param addr - BDT member that is sought + * + * @return True if BDT member is found and has a unicast mask + */ +static bool bbmd_bdt_member_mask_is_unicast(BACNET_IP_ADDRESS *addr) +{ + bool unicast = false; + BACNET_IP_ADDRESS my_addr = { 0 }; + BACNET_IP_BROADCAST_DISTRIBUTION_MASK unicast_mask = { 0 }; + BACNET_IP_ADDRESS *dest_address = NULL; + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *broadcast_mask = NULL; + unsigned i = 0; /* loop counter */ + + bip_get_addr(&my_addr); + bvlc_broadcast_distribution_mask_from_host(&unicast_mask, 0xFFFFFFFFL); + for (i = 0; i < MAX_BBMD_ENTRIES; i++) { + if (BBMD_Table[i].valid) { + dest_address = &BBMD_Table[i].dest_address; + broadcast_mask = &BBMD_Table[i].broadcast_mask; + if (bvlc_address_different(&my_addr, dest_address) && + !bvlc_address_different(addr, dest_address) && + !bvlc_broadcast_distribution_mask_different( + broadcast_mask, &unicast_mask)) { + unicast = true; + break; + } + } + } + + return unicast; +} + +/** Send a BVLL Forwarded-NPDU message on its local IP subnet using + * the local B/IP broadcast address as the destination address. + * + * @param bip_src - source IP address and UDP port + * @param npdu - returns the NPDU + * @param max_npdu - amount of space available in the NPDU + * @param npdu_length - reported length of the NPDU + * @return number of bytes encoded in the Forwarded NPDU + */ +static uint16_t bbmd_forward_npdu( + BACNET_IP_ADDRESS *bip_src, uint8_t *npdu, uint16_t npdu_length) +{ + BACNET_IP_ADDRESS broadcast_address = { 0 }; + uint8_t mtu[MAX_MPDU] = { 0 }; + uint16_t mtu_len = 0; + + mtu_len = (uint16_t)bvlc_encode_forwarded_npdu( + &mtu[0], (uint16_t)sizeof(mtu), bip_src, npdu, npdu_length); + if (mtu_len > 0) { + bip_get_broadcast_addr(&broadcast_address); + bip_send_mpdu(&broadcast_address, mtu, mtu_len); + debug_printf("BVLC: Sent Forwarded-NPDU as local broadcast.\n"); + } + + return mtu_len; +} + +/** Sends all Broadcast Devices a Forwarded NPDU + * + * @param bip_src - source IP address and UDP port + * @param npdu - returns the NPDU + * @param max_npdu - amount of space available in the NPDU + * @param npdu_length - reported length of the NPDU + * @param original - was the message an original (not forwarded) + * @return number of bytes encoded in the Forwarded NPDU + */ +static uint16_t bbmd_bdt_forward_npdu(BACNET_IP_ADDRESS *bip_src, + uint8_t *npdu, + uint16_t npdu_length, + bool original) +{ + uint8_t mtu[MAX_MPDU] = { 0 }; + uint16_t mtu_len = 0; + unsigned i = 0; /* loop counter */ + BACNET_IP_ADDRESS bip_dest = { 0 }; + BACNET_IP_ADDRESS my_addr = { 0 }; + + bip_get_addr(&my_addr); + /* If we are forwarding an original broadcast message and the NAT + * handling is enabled, change the source address to NAT routers + * global IP address so the recipient can reply (local IP address + * is not accesible from internet side. + * + * If we are forwarding a message from peer BBMD or foreign device + * or the NAT handling is disabled, leave the source address as is. + */ + if (BVLC_NAT_Handling && original) { + mtu_len = (uint16_t)bvlc_encode_forwarded_npdu(&mtu[0], + (uint16_t)sizeof(mtu), &BVLC_Global_Address, npdu, npdu_length); + } else { + mtu_len = (uint16_t)bvlc_encode_forwarded_npdu( + &mtu[0], (uint16_t)sizeof(mtu), bip_src, npdu, npdu_length); + } + /* loop through the BDT and send one to each entry */ + for (i = 0; i < MAX_BBMD_ENTRIES; i++) { + if (BBMD_Table[i].valid) { + bvlc_broadcast_distribution_table_entry_forward_address( + &bip_dest, &BBMD_Table[i]); + if (!bvlc_address_different(&bip_dest, &my_addr)) { + /* don't forward to our selves */ + continue; + } + if (!bvlc_address_different(&bip_dest, bip_src)) { + /* don't forward back to origin */ + continue; + } + if (BVLC_NAT_Handling) { + if (bvlc_address_different(&bip_dest, &BVLC_Global_Address)) { + /* NAT router port forwards BACnet packets from global IP. + Packets sent to that global IP by us would end up back, + creating a loop. */ + continue; + } + } + bip_send_mpdu(&bip_dest, mtu, mtu_len); + debug_print_bip("BDT Send Forwarded-NPDU", &bip_dest); + } + } + + return mtu_len; +} + +/** Sends all Foreign Devices a Forwarded NPDU + * + * @param bip_src - source IP address and UDP port + * @param npdu - returns the NPDU + * @param max_npdu - amount of space available in the NPDU + * @param npdu_length - reported length of the NPDU + * @param original - was the message an original (not forwarded) + * @return number of bytes encoded in the Forwarded NPDU + */ +static uint16_t bbmd_fdt_forward_npdu(BACNET_IP_ADDRESS *bip_src, + uint8_t *npdu, + uint16_t npdu_length, + bool original) +{ + uint8_t mtu[MAX_MPDU] = { 0 }; + uint16_t mtu_len = 0; + unsigned i = 0; /* loop counter */ + BACNET_IP_ADDRESS bip_dest = { 0 }; + BACNET_IP_ADDRESS my_addr = { 0 }; + + bip_get_addr(&my_addr); + /* If we are forwarding an original broadcast message and the NAT + * handling is enabled, change the source address to NAT routers + * global IP address so the recipient can reply (local IP address + * is not accesible from internet side. + * + * If we are forwarding a message from peer BBMD or foreign device + * or the NAT handling is disabled, leave the source address as is. + */ + if (BVLC_NAT_Handling && original) { + mtu_len = (uint16_t)bvlc_encode_forwarded_npdu(&mtu[0], + (uint16_t)sizeof(mtu), &BVLC_Global_Address, npdu, npdu_length); + } else { + mtu_len = (uint16_t)bvlc_encode_forwarded_npdu( + &mtu[0], (uint16_t)sizeof(mtu), bip_src, npdu, npdu_length); + } + + /* loop through the FDT and send one to each entry */ + for (i = 0; i < MAX_FD_ENTRIES; i++) { + if (FD_Table[i].valid && FD_Table[i].ttl_seconds_remaining) { + bvlc_address_copy(&bip_dest, &FD_Table[i].dest_address); + if (!bvlc_address_different(&bip_dest, &my_addr)) { + /* don't forward to our selves */ + continue; + } + if (!bvlc_address_different(&bip_dest, bip_src)) { + /* don't forward back to origin */ + continue; + } + if (BVLC_NAT_Handling) { + if (bvlc_address_different(&bip_dest, &BVLC_Global_Address)) { + /* NAT router port forwards BACnet packets from global IP. + Packets sent to that global IP by us would end up back, + creating a loop. */ + continue; + } + } + bip_send_mpdu(&bip_dest, mtu, mtu_len); + debug_print_bip("FDT Send Forwarded-NPDU", &bip_dest); + } + } + + return mtu_len; +} + +/** Prints the Read-BDT-Ack NPDU + * + * @param addr - source IP address and UDP port + * @param npdu - returns the NPDU + * @param max_npdu - amount of space available in the NPDU + * @param npdu_length - reported length of the NPDU + * @param original - was the message an original (not forwarded) + */ +static void bbmd_read_bdt_ack_handler( + BACNET_IP_ADDRESS *addr, uint8_t *npdu, uint16_t npdu_length) +{ +#if PRINT_ENABLED + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY bdt_entry = { 0 }; + uint16_t offset = 0; + unsigned count = 1; + int len = 0; + + printf("BBMD: %u.%u.%u.%u:%u\n", (unsigned)addr->address[0], + (unsigned)addr->address[1], (unsigned)addr->address[2], + (unsigned)addr->address[3], (unsigned)addr->port); + while (npdu_length >= BACNET_IP_BDT_ENTRY_SIZE) { + len = bvlc_decode_broadcast_distribution_table_entry( + &npdu[offset], npdu_length, &bdt_entry); + if (len > 0) { + printf("BDT-%03u: %u.%u.%u.%u:%u %u.%u.%u.%u\n", count, + (unsigned)bdt_entry.dest_address.address[0], + (unsigned)bdt_entry.dest_address.address[1], + (unsigned)bdt_entry.dest_address.address[2], + (unsigned)bdt_entry.dest_address.address[3], + (unsigned)bdt_entry.dest_address.port, + (unsigned)bdt_entry.broadcast_mask.address[0], + (unsigned)bdt_entry.broadcast_mask.address[1], + (unsigned)bdt_entry.broadcast_mask.address[2], + (unsigned)bdt_entry.broadcast_mask.address[3]); + offset += len; + npdu_length -= len; + count++; + } else { + break; + } + } +#endif +} + +/** Prints the Read-FDT-Ack NPDU + * + * @param addr - source IP address and UDP port + * @param npdu - returns the NPDU + * @param max_npdu - amount of space available in the NPDU + * @param npdu_length - reported length of the NPDU + * @param original - was the message an original (not forwarded) + */ +static void bbmd_read_fdt_ack_handler( + BACNET_IP_ADDRESS *addr, uint8_t *npdu, uint16_t npdu_length) +{ +#if PRINT_ENABLED + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY fdt_entry = { 0 }; + uint16_t offset = 0; + unsigned count = 1; + int len = 0; + + printf("BBMD: %u.%u.%u.%u:%u\n", (unsigned)addr->address[0], + (unsigned)addr->address[1], (unsigned)addr->address[2], + (unsigned)addr->address[3], (unsigned)addr->port); + while (npdu_length >= BACNET_IP_FDT_ENTRY_SIZE) { + len = bvlc_decode_foreign_device_table_entry( + &npdu[offset], npdu_length, &fdt_entry); + if (len > 0) { + printf("FDT-%03u: %u.%u.%u.%u:%u %us %us\n", count, + (unsigned)fdt_entry.dest_address.address[0], + (unsigned)fdt_entry.dest_address.address[1], + (unsigned)fdt_entry.dest_address.address[2], + (unsigned)fdt_entry.dest_address.address[3], + (unsigned)fdt_entry.dest_address.port, + (unsigned)fdt_entry.ttl_seconds, + (unsigned)fdt_entry.ttl_seconds_remaining); + offset += len; + npdu_length -= len; + count++; + } else { + break; + } + } +#endif +} +#endif + +/** + * The common send function for BACnet/IP application layer + * + * @param dest - Points to a #BACNET_ADDRESS structure containing the + * destination address. + * @param npdu_data - Points to a BACNET_NPDU_DATA structure containing the + * destination network layer control flags and data. + * @param mtu - the bytes of data to send + * @param mtu_len - the number of bytes of data to send + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +int bvlc_send_pdu(BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + BACNET_IP_ADDRESS bvlc_dest = { 0 }; + uint8_t mtu[MAX_MPDU] = { 0 }; + uint16_t mtu_len = 0; +#if BBMD_ENABLED + BACNET_IP_ADDRESS bip_src = { 0 }; +#endif + + /* this datalink doesn't need to know the npdu data */ + (void)npdu_data; + /* handle various broadcasts: */ + if ((dest->net == BACNET_BROADCAST_NETWORK) || (dest->mac_len == 0)) { + /* mac_len = 0 is a broadcast address */ + /* net = 0 indicates local, net = 65535 indicates global */ + if (Remote_BBMD.port) { + /* we are a foreign device */ + bvlc_address_copy(&bvlc_dest, &Remote_BBMD); + mtu_len = bvlc_encode_distribute_broadcast_to_network( + mtu, sizeof(mtu), pdu, pdu_len); + debug_print_bip("Send Distribute-Broadcast-to-Network", &bvlc_dest); + } else { + bip_get_broadcast_addr(&bvlc_dest); + mtu_len = + bvlc_encode_original_broadcast(mtu, sizeof(mtu), pdu, pdu_len); + debug_print_bip("Send Original-Broadcast-NPDU", &bvlc_dest); +#if BBMD_ENABLED + if (mtu_len > 0) { + bip_get_addr(&bip_src); + bbmd_fdt_forward_npdu(&bip_src, pdu, pdu_len, true); + bbmd_bdt_forward_npdu(&bip_src, pdu, pdu_len, true); + } +#endif + } + } else if ((dest->net > 0) && (dest->len == 0)) { + /* net > 0 and net < 65535 are network specific broadcast if len = 0 */ + if (dest->mac_len == 6) { + /* network specific broadcast to address */ + bvlc_ip_address_from_bacnet_local(&bvlc_dest, dest); + } else { + bip_get_broadcast_addr(&bvlc_dest); + } + mtu_len = + bvlc_encode_original_broadcast(mtu, sizeof(mtu), pdu, pdu_len); + debug_print_bip("Send Original-Broadcast-NPDU", &bvlc_dest); + } else if (dest->mac_len == 6) { + /* valid unicast */ + bvlc_ip_address_from_bacnet_local(&bvlc_dest, dest); + mtu_len = bvlc_encode_original_unicast(mtu, sizeof(mtu), pdu, pdu_len); + debug_print_bip("Send Original-Unicast-NPDU", &bvlc_dest); + } else { + debug_print_string("Send failure. Invalid Address."); + return -1; + } + + return bip_send_mpdu(&bvlc_dest, mtu, mtu_len); +} + +/** + * The Result Code send function for BACnet/IPv4 application layer + * + * @param dest_addr - Points to a #BACNET_IP_ADDRESS structure containing the + * destination IPv4 address. + * @param result_code - BVLC result code + * + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +static int bvlc_send_result(BACNET_IP_ADDRESS *dest_addr, uint16_t result_code) +{ + uint8_t mtu[MAX_MPDU] = { 0 }; + uint16_t mtu_len = 0; + + mtu_len = bvlc_encode_result(&mtu[0], sizeof(mtu), result_code); + + return bip_send_mpdu(dest_addr, mtu, mtu_len); +} + +#if ((!BBMD_ENABLED) || (TEST)) +/** + * Use this handler when you are not a BBMD. + * Sets the BVLC_Function_Code in case it is needed later. + * + * @param addr - BACnet/IPv4 source address any NAK or reply back to. + * @param src - BACnet source address + * @param mtu - The received MTU buffer. + * @param mtu_len - How many bytes in MTU buffer. + * + * @return number of bytes offset into the NPDU for APDU, or 0 if handled + */ +static int handler_bbmd_for_non_bbmd(BACNET_IP_ADDRESS *addr, + BACNET_ADDRESS *src, + uint8_t *mtu, + uint16_t mtu_len) +{ + uint16_t result_code = BVLC_RESULT_SUCCESSFUL_COMPLETION; + uint8_t message_type = 0; + uint16_t message_length = 0; + int header_len = 0; + int function_len = 0; + uint8_t *pdu = NULL; + uint16_t pdu_len = 0; + uint16_t npdu_len = 0; + bool send_result = false; + uint16_t offset = 0; + BACNET_IP_ADDRESS fwd_address = { 0 }; + + header_len = + bvlc_decode_header(mtu, mtu_len, &message_type, &message_length); + if (header_len == 4) { + BVLC_Function_Code = message_type; + pdu = &mtu[header_len]; + pdu_len = mtu_len - header_len; + switch (BVLC_Function_Code) { + case BVLC_RESULT: + function_len = bvlc_decode_result(pdu, pdu_len, &result_code); + if (function_len) { + BVLC_Result_Code = result_code; + debug_print_unsigned( + "Received Result Code =", BVLC_Result_Code); + } + break; + case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE: + result_code = + BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK; + send_result = true; + break; + case BVLC_READ_BROADCAST_DIST_TABLE: + result_code = BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK; + send_result = true; + break; + case BVLC_READ_BROADCAST_DIST_TABLE_ACK: + result_code = BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK; + send_result = true; + break; + case BVLC_FORWARDED_NPDU: + debug_print_bip("Received Forwarded-NPDU", addr); + function_len = bvlc_decode_forwarded_npdu( + pdu, pdu_len, &fwd_address, NULL, 0, &npdu_len); + if (function_len) { + if (bbmd_address_match_self(&fwd_address)) { + /* ignore forwards from my IPv4 address */ + debug_print_string("Forwarded-NPDU is me!"); + break; + } + bvlc_ip_address_to_bacnet_local(src, &fwd_address); + offset = header_len + function_len - npdu_len; + debug_print_npdu("Forwarded-NPDU", offset, npdu_len); + } else { + debug_print_string("Forwarded-NPDU: Unable to decode!"); + } + break; + case BVLC_REGISTER_FOREIGN_DEVICE: + result_code = BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK; + send_result = true; + break; + case BVLC_READ_FOREIGN_DEVICE_TABLE: + result_code = BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK; + send_result = true; + break; + case BVLC_READ_FOREIGN_DEVICE_TABLE_ACK: + result_code = BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK; + send_result = true; + break; + case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY: + result_code = BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK; + send_result = true; + break; + case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK: + result_code = BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK; + send_result = true; + break; + case BVLC_ORIGINAL_UNICAST_NPDU: + /* This message is used to send directed NPDUs to + another B/IPv4 node or router. */ + debug_print_bip("Received Original-Unicast-NPDU", addr); + if (bbmd_address_match_self(addr)) { + /* ignore messages from my IPv4 address */ + debug_print_string("Original-Unicast-NPDU is me!"); + break; + } + function_len = bvlc_decode_original_unicast( + pdu, pdu_len, NULL, 0, &npdu_len); + if (function_len) { + bvlc_ip_address_to_bacnet_local(src, addr); + offset = header_len + function_len - npdu_len; + debug_print_npdu("Original-Unicast-NPDU", offset, npdu_len); + } else { + debug_print_string( + "Original-Unicast-NPDU: Unable to decode!"); + } + break; + case BVLC_ORIGINAL_BROADCAST_NPDU: + debug_print_bip("Received Original-Broadcast-NPDU", addr); + if (bbmd_address_match_self(addr)) { + /* ignore messages from my IPv4 address */ + debug_print_string("Original-Broadcast-NPDU is me!"); + break; + } + function_len = bvlc_decode_original_broadcast( + pdu, pdu_len, NULL, 0, &npdu_len); + if (function_len) { + bvlc_ip_address_to_bacnet_local(src, addr); + offset = header_len + function_len - npdu_len; + debug_print_npdu( + "Original-Broadcast-NPDU", offset, npdu_len); + } else { + debug_print_string( + "Original-Broadcast-NPDU: Unable to decode!"); + } + break; + case BVLC_SECURE_BVLL: + break; + default: + break; + } + if (send_result) { + bvlc_send_result(addr, result_code); + debug_print_unsigned("Sent result code =", result_code); + } + } + + return offset; +} +#endif + +#if BBMD_ENABLED +/** + * Use this handler when you are a BBMD. + * Sets the BVLC_Function_Code in case it is needed later. + * + * @param addr [in] BACnet/IPv4 source address any NAK or reply back to. + * @param src [out] BACnet style the source address interpreted VMAC + * @param mtu [in] The received MTU buffer. + * @param mtu_len [in] How many bytes in MTU buffer. + * + * @return number of bytes offset into the NPDU for APDU, or 0 if handled + */ +static int handler_bbmd_for_bbmd(BACNET_IP_ADDRESS *addr, + BACNET_ADDRESS *src, + uint8_t *mtu, + uint16_t mtu_len) +{ + uint16_t result_code = BVLC_RESULT_SUCCESSFUL_COMPLETION; + uint8_t message_type = 0; + uint16_t message_length = 0; + int header_len = 0; + int function_len = 0; + uint8_t *pdu = NULL; + uint16_t pdu_len = 0; + uint8_t *npdu = NULL; + uint16_t npdu_len = 0; + bool send_result = false; + uint16_t offset = 0; + uint16_t ttl_seconds = 0; + BACNET_IP_ADDRESS fwd_address = { 0 }; + BACNET_IP_ADDRESS broadcast_address = { 0 }; + + header_len = + bvlc_decode_header(mtu, mtu_len, &message_type, &message_length); + if (header_len != 4) { + return 0; + } + BVLC_Function_Code = message_type; + pdu = &mtu[header_len]; + pdu_len = mtu_len - header_len; + switch (BVLC_Function_Code) { + case BVLC_RESULT: + function_len = bvlc_decode_result(pdu, pdu_len, &result_code); + if (function_len) { + BVLC_Result_Code = result_code; + debug_print_unsigned( + "Received Result Code =", BVLC_Result_Code); + } + break; + case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE: + debug_print_bip("Received Write-BDT", addr); + function_len = bvlc_decode_write_broadcast_distribution_table( + pdu, pdu_len, &BBMD_Table[0]); + if (function_len > 0) { + /* BDT changed! Save backup to file */ + bvlc_bdt_backup_local(); + result_code = BVLC_RESULT_SUCCESSFUL_COMPLETION; + send_result = true; + } else { + result_code = + BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK; + send_result = true; + } + /* not an NPDU */ + offset = 0; + break; + case BVLC_READ_BROADCAST_DIST_TABLE: + debug_print_bip("Received Read-BDT", addr); + BVLC_Buffer_Len = bvlc_encode_read_broadcast_distribution_table_ack( + BVLC_Buffer, sizeof(BVLC_Buffer), &BBMD_Table[0]); + if (BVLC_Buffer_Len > 0) { + bip_send_mpdu(addr, BVLC_Buffer, BVLC_Buffer_Len); + } else { + result_code = BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK; + send_result = true; + } + /* not an NPDU */ + offset = 0; + break; + case BVLC_READ_BROADCAST_DIST_TABLE_ACK: + debug_print_bip("Received Read-BDT-Ack", addr); + bbmd_read_bdt_ack_handler(addr, pdu, pdu_len); + /* not an NPDU */ + offset = 0; + break; + case BVLC_FORWARDED_NPDU: + debug_print_bip("Received Forwarded-NPDU", addr); + /* Upon receipt of a BVLL Forwarded-NPDU message, a BBMD shall + process it according to whether it was received from a peer + BBMD as the result of a directed broadcast or a unicast + transmission. A BBMD may ascertain the method by which + Forwarded-NPDU messages will arrive by inspecting the + broadcast distribution mask field in its own BDT entry + since all BDTs are required to be identical. + If the message arrived via directed broadcast, + it was also received by the other devices on the + BBMD's subnet. In this case the BBMD merely retransmits + the message directly to each foreign device currently + in the BBMD's FDT. If the message arrived via a unicast + transmission it has not yet been received by the other + devices on the BBMD's subnet. In this case, the message is + sent to the devices on the BBMD's subnet using the + B/IP broadcast address as well as to each + foreign device currently in the BBMD's FDT. + A BBMD on a subnet with no other BACnet devices may omit + the broadcast using the B/IP broadcast address. + The method by which a BBMD determines whether or not + other BACnet devices are present is a local matter. */ + function_len = bvlc_decode_forwarded_npdu( + pdu, pdu_len, &fwd_address, NULL, 0, &npdu_len); + if (function_len) { + if (bbmd_address_match_self(&fwd_address)) { + /* ignore forwards from my IPv4 address */ + debug_print_string("Forwarded-NPDU is me!"); + break; + } + if (bbmd_bdt_member_mask_is_unicast(addr)) { + /* Upon receipt of a BVLL Forwarded-NPDU message + from a BBMD which is in the receiving BBMD's BDT, + a BBMD shall construct a BVLL Forwarded-NPDU and + transmit it via broadcast to B/IPv4 devices in the + local broadcast domain. */ + bip_get_broadcast_addr(&broadcast_address); + bip_send_mpdu(&broadcast_address, mtu, mtu_len); + } + /* In addition, the constructed BVLL Forwarded-NPDU + message shall be unicast to each foreign device in + the BBMD's FDT. */ + bbmd_fdt_forward_npdu(&fwd_address, mtu, mtu_len, false); + /* prepare the message for me! */ + bvlc_ip_address_to_bacnet_local(src, &fwd_address); + offset = header_len + function_len - npdu_len; + debug_print_npdu("Forwarded-NPDU", offset, npdu_len); + } + break; + case BVLC_REGISTER_FOREIGN_DEVICE: + debug_print_bip("Received-Register-Foreign-Device", addr); + /* Upon receipt of a BVLL Register-Foreign-Device message, a BBMD + shall start a timer with a value equal to the Time-to-Live + parameter supplied plus a fixed grace period of 30 seconds. If, + within the period during which the timer is active, another BVLL + Register-Foreign-Device message from the same device is received, + the timer shall be reset and restarted. If the time expires + without the receipt of another BVLL Register-Foreign-Device + message from the same foreign device, the FDT entry for this + device shall be cleared. */ + function_len = + bvlc_decode_register_foreign_device(pdu, pdu_len, &ttl_seconds); + if (function_len) { + if (bvlc_foreign_device_table_entry_add( + &FD_Table[0], addr, ttl_seconds)) { + result_code = BVLC_RESULT_SUCCESSFUL_COMPLETION; + send_result = true; + } else { + result_code = BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK; + send_result = true; + } + } + /* not an NPDU */ + offset = 0; + break; + case BVLC_READ_FOREIGN_DEVICE_TABLE: + debug_print_bip("Received Read-FDT", addr); + /* Upon receipt of a BVLL Read-Foreign-Device-Table message, a + BBMD shall load the contents of its FDT into a BVLL Read- + Foreign-Device-Table-Ack message and send it to the originating + device. If the BBMD is unable to perform the read of its FDT, + it shall return a BVLC-Result message to the originating device + with a result code of X'0040' indicating that the read attempt + has failed. */ + BVLC_Buffer_Len = bvlc_encode_read_foreign_device_table_ack( + BVLC_Buffer, sizeof(BVLC_Buffer), &FD_Table[0]); + if (BVLC_Buffer_Len > 0) { + bip_send_mpdu(addr, BVLC_Buffer, BVLC_Buffer_Len); + } else { + result_code = BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK; + send_result = true; + } + /* not an NPDU */ + offset = 0; + break; + case BVLC_READ_FOREIGN_DEVICE_TABLE_ACK: + debug_print_bip("Received Read-FDT-Ack", addr); + bbmd_read_fdt_ack_handler(addr, pdu, pdu_len); + /* not an NPDU */ + offset = 0; + break; + case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY: + debug_print_bip("Received Delete-FDT-Entry", addr); + /* Upon receipt of a BVLL Delete-Foreign-Device-Table-Entry + message, a BBMD shall search its foreign device table for an + entry corresponding to the B/IP address supplied in the message. + If an entry is found, it shall be deleted and the BBMD shall + return a BVLC-Result message to the originating device with a + result code of X'0000'. Otherwise, the BBMD shall return a + BVLCResult message to the originating device with a result code + of X'0050' indicating that the deletion attempt has failed. */ + function_len = + bvlc_decode_delete_foreign_device(pdu, pdu_len, &fwd_address); + if (function_len > 0) { + if (bvlc_foreign_device_table_entry_delete( + &FD_Table[0], &fwd_address)) { + result_code = BVLC_RESULT_SUCCESSFUL_COMPLETION; + send_result = true; + } else { + result_code = + BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK; + send_result = true; + } + } else { + result_code = BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK; + send_result = true; + } + /* not an NPDU */ + offset = 0; + break; + case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK: + debug_print_bip("Received Distribute-Broadcast-To-Network", addr); + /* Upon receipt of a BVLL Distribute-Broadcast-To-Network message + from a foreign device, the receiving BBMD shall transmit a + BVLL Forwarded-NPDU message on its local IP subnet using the + local B/IP broadcast address as the destination address. In + addition, a Forwarded-NPDU message shall be sent to each entry + in its BDT as described in the case of the receipt of a + BVLL Original-Broadcast-NPDU as well as directly to each foreign + device currently in the BBMD's FDT except the originating + node. If the BBMD is unable to perform the forwarding function, + it shall return a BVLC-Result message to the foreign device + with a result code of X'0060' indicating that the forwarding + attempt was unsuccessful */ + npdu_len = bbmd_forward_npdu(addr, pdu, pdu_len); + if (npdu_len > 0) { + bbmd_fdt_forward_npdu(addr, pdu, pdu_len, false); + bbmd_bdt_forward_npdu(addr, pdu, pdu_len, false); + } else { + result_code = BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK; + send_result = true; + } + /* not an NPDU */ + offset = 0; + break; + case BVLC_ORIGINAL_UNICAST_NPDU: + /* This message is used to send directed NPDUs to + another B/IPv4 node or router. */ + debug_print_bip("Received Original-Unicast-NPDU", addr); + if (bbmd_address_match_self(addr)) { + /* ignore messages from my IPv4 address */ + debug_print_string("Original-Unicast-NPDU is me!"); + break; + } + function_len = + bvlc_decode_original_unicast(pdu, pdu_len, NULL, 0, &npdu_len); + if (function_len) { + /* prepare the message for me! */ + bvlc_ip_address_to_bacnet_local(src, addr); + offset = header_len + function_len - npdu_len; + debug_print_npdu("Original-Unicast-NPDU", offset, npdu_len); + } + break; + case BVLC_ORIGINAL_BROADCAST_NPDU: + debug_print_bip("Received Original-Broadcast-NPDU", addr); + if (bbmd_address_match_self(addr)) { + /* ignore messages from my IPv4 address */ + debug_print_string("Original-Broadcast-NPDU is me!"); + break; + } + function_len = bvlc_decode_original_broadcast( + pdu, pdu_len, NULL, 0, &npdu_len); + if (function_len) { + /* prepare the message for me! */ + bvlc_ip_address_to_bacnet_local(src, addr); + offset = header_len + function_len - npdu_len; + /* Upon receipt of a BVLL Original-Broadcast-NPDU message, + a BBMD shall construct a BVLL Forwarded-NPDU message and + send it to each IP subnet in its BDT with the exception + of its own. The B/IP address to which the Forwarded-NPDU + message is sent is formed by inverting the broadcast + distribution mask in the BDT entry and logically ORing it + with the BBMD address of the same entry. This process + produces either the directed broadcast address of the remote + subnet or the unicast address of the BBMD on that subnet + depending on the contents of the broadcast distribution + mask. See J.4.3.2.. In addition, the received BACnet NPDU + shall be sent directly to each foreign device currently in + the BBMD's FDT also using the BVLL Forwarded-NPDU message. */ + npdu = &mtu[offset]; + bbmd_fdt_forward_npdu(addr, npdu, npdu_len, true); + bbmd_bdt_forward_npdu(addr, npdu, npdu_len, true); + debug_print_npdu("Original-Broadcast-NPDU", offset, npdu_len); + } + break; + case BVLC_SECURE_BVLL: + debug_print_bip("Received Secure-BVLL", addr); + break; + default: + debug_print_unsigned("Unknown BVLC =", BVLC_Function_Code); + break; + } + if (send_result) { + bvlc_send_result(addr, result_code); + debug_print_unsigned("Sent result code =", result_code); + } + + return offset; +} +#endif + +/** + * Use this handler for BACnet/IPv4 BVLC + * + * @param addr [in] IPv4 address to send any NAK back to. + * @param src [out] returns the source address + * @param npdu [in] The received buffer. + * @param npdu_len [in] How many bytes in npdu[]. + * + * @return number of bytes offset into the NPDU for APDU, or 0 if handled + */ +int bvlc_handler(BACNET_IP_ADDRESS *addr, + BACNET_ADDRESS *src, + uint8_t *npdu, + uint16_t npdu_len) +{ +#if BBMD_ENABLED + debug_print_bip("Received BVLC (BBMD Enabled)", addr); + return handler_bbmd_for_bbmd(addr, src, npdu, npdu_len); +#else + debug_print_bip("Received BVLC (BBMD Disabled)", addr); + return handler_bbmd_for_non_bbmd(addr, src, npdu, npdu_len); +#endif +} + +#if BBMD_CLIENT_ENABLED +/** Register as a foreign device with the indicated BBMD. + * @param bbmd_addr - IPv4 address of BBMD with which to register + * @param ttl_seconds - Lease time to use when registering. + * @return Positive number (of bytes sent) on success, + * 0 if no registration request is sent, or + * -1 if registration fails. + */ +int bvlc_register_with_bbmd(BACNET_IP_ADDRESS *bbmd_addr, uint16_t ttl_seconds) +{ + /* Store the BBMD address and port so that we won't broadcast locally. */ + /* We are a foreign device! */ + bvlc_address_copy(&Remote_BBMD, bbmd_addr); + BVLC_Buffer_Len = bvlc_encode_register_foreign_device( + &BVLC_Buffer[0], sizeof(BVLC_Buffer), ttl_seconds); + + return bip_send_mpdu(bbmd_addr, &BVLC_Buffer[0], BVLC_Buffer_Len); +} +#endif + +#if BBMD_CLIENT_ENABLED +/** + * Read the BDT from 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_read_bdt(BACNET_IP_ADDRESS *bbmd_addr) +{ + BVLC_Buffer_Len = bvlc_encode_read_broadcast_distribution_table( + &BVLC_Buffer[0], sizeof(BVLC_Buffer)); + + 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 + * @return Positive number (of bytes sent) on success + */ +int bvlc_bbmd_read_fdt(BACNET_IP_ADDRESS *bbmd_addr) +{ + BVLC_Buffer_Len = bvlc_encode_read_foreign_device_table( + &BVLC_Buffer[0], sizeof(BVLC_Buffer)); + + return bip_send_mpdu(bbmd_addr, &BVLC_Buffer[0], BVLC_Buffer_Len); +} +#endif + +/** + * Returns the last BVLL Result we received, either as the result of a BBMD + * request we sent, or (if not a BBMD or Client), from trying to register + * as a foreign device. + * + * @return BVLC_RESULT_SUCCESSFUL_COMPLETION on success, + * BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK if registration failed, + * or one of the other codes (if we are a BBMD). + */ +uint16_t bvlc_get_last_result(void) +{ + return BVLC_Result_Code; +} + +/** + * Returns the current BVLL Function Code we are processing. + * We have to store this higher layer code for when the lower layers + * need to know what it is, especially to differentiate between + * BVLC_ORIGINAL_UNICAST_NPDU and BVLC_ORIGINAL_BROADCAST_NPDU. + * + * @return A BVLC_ code, such as BVLC_ORIGINAL_UNICAST_NPDU. + */ +uint8_t bvlc_get_function_code(void) +{ + return BVLC_Function_Code; +} + +#if BBMD_ENABLED +/** + * @brief Get handle to broadcast distribution table (BDT). + * @return pointer to first entry of broadcast distribution table + */ +BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bvlc_bdt_list(void) +{ + return &BBMD_Table[0]; +} + +/** + * @brief Invalidate all entries in the broadcast distribution table (BDT). + */ +void bvlc_bdt_list_clear(void) +{ + bvlc_broadcast_distribution_table_valid_clear(&BBMD_Table[0]); + /* BDT changed! Save backup to file */ + bvlc_bdt_backup_local(); +} +#endif + +/** + * @brief Enable NAT handling and set the global IP address + * + * If the communication between BBMDs goes through a NAT enabled internet + * router, special considerations are needed as stated in Annex J.7.8. + * + * In short, the local IP address of the BBMD is different than the global + * address which is visible to the other BBMDs or foreign devices. This is + * why the source address in forwarded messages needs to be changed to the + * global IP address. + * + * For other considerations/limitations see Annex J.7.8. + * + * @param [in] - Global IP address visible to peer BBMDs and foreign devices + */ +void bvlc_set_global_address_for_nat(const BACNET_IP_ADDRESS *addr) +{ + bvlc_address_copy(&BVLC_Global_Address, addr); + BVLC_NAT_Handling = true; + debug_print_bip("NAT Address enabled", addr); +} + +/** + * @brief Disable NAT handling. + */ +void bvlc_disable_nat(void) +{ + BVLC_NAT_Handling = false; + debug_print_string("NAT Address disabled"); +} + +void bvlc_init(void) +{ + debug_print_string("Initializing."); +#if BBMD_ENABLED + bvlc_broadcast_distribution_table_link_array( + &BBMD_Table[0], MAX_BBMD_ENTRIES); + bvlc_foreign_device_table_link_array(&FD_Table[0], MAX_FD_ENTRIES); +#endif +} + +#ifdef TEST +#include +#include +#include "ctest.h" +static uint32_t Device_ID = 0; +static BACNET_IP_ADDRESS BIP_Addr; +static BACNET_IP_ADDRESS BIP_Broadcast_Addr; + +/* network stub functions */ +/** + * BACnet/IP Datalink Receive handler. + * + * @param src - returns the source address + * @param npdu - returns the NPDU buffer + * @param max_npdu -maximum size of the NPDU buffer + * @param timeout - number of milliseconds to wait for a packet + * + * @return Number of bytes received, or 0 if none or timeout. + */ +uint16_t bip_receive( + BACNET_ADDRESS *src, uint8_t *npdu, uint16_t max_npdu, unsigned timeout) +{ + return 0; +} + +/** + * The send function for BACnet/IPv4 driver layer + * + * @param dest - Points to a BACNET_IP_ADDRESS structure containing the + * destination address. + * @param mtu - the bytes of data to send + * @param mtu_len - the number of bytes of data to send + * + * @return Upon successful completion, returns the number of bytes sent. + * Otherwise, -1 shall be returned and errno set to indicate the error. + */ +int bip_send_mpdu(BACNET_IP_ADDRESS *dest, uint8_t *mtu, uint16_t mtu_len) +{ + return 0; +} + +/** 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. + * + * @return The Instance number used in the BACNET_OBJECT_ID for the Device. + */ +uint32_t Device_Object_Instance_Number(void) +{ + return Device_ID; +} + +/** + * Get the BACnet/IP address + * + * @return BACnet/IP address + */ +bool bip_get_addr(BACNET_IP_ADDRESS *addr) +{ + return bvlc_address_copy(addr, &BIP_Addr); +} + +/** + * Get the BACnet/IP address + * + * @return BACnet/IP address + */ +bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr) +{ + return bvlc_address_copy(addr, &BIP_Broadcast_Addr); +} + +static void test_BBMD_Result(Test *pTest) +{ + int result = 0; + uint16_t result_code[] = { BVLC_RESULT_SUCCESSFUL_COMPLETION, + BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK, + BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK, + BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK, + BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK, + BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK, + BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK }; + size_t result_code_max = sizeof(result_code) / sizeof(result_code[0]); + uint16_t test_result_code = 0; + uint8_t test_function_code = 0; + BACNET_IP_ADDRESS addr; + BACNET_ADDRESS src; + unsigned int i = 0; + uint8_t mtu[MAX_MPDU] = { 0 }; + uint16_t mtu_len = 0; + + bvlc_address_port_from_ascii(&addr, "192.168.0.1", "0xBAC0"); + for (i = 0; i < result_code_max; i++) { + mtu_len = bvlc_encode_result(&mtu[0], sizeof(mtu), result_code[i]); + result = handler_bbmd_for_non_bbmd(&addr, &src, &mtu[0], mtu_len); + /* validate that the result is handled (0) */ + ct_test(pTest, result == 0); + test_result_code = bvlc_get_last_result(); + ct_test(pTest, test_result_code == result_code[i]); + test_function_code = bvlc_get_function_code(); + ct_test(pTest, test_function_code == BVLC_RESULT); + result = handler_bbmd_for_bbmd(&addr, &src, &mtu[0], mtu_len); + /* validate that the result is handled (0) */ + ct_test(pTest, result == 0); + test_result_code = bvlc_get_last_result(); + ct_test(pTest, test_result_code == result_code[i]); + test_function_code = bvlc_get_function_code(); + ct_test(pTest, test_function_code == BVLC_RESULT); + } +} + +static void test_BBMD_Handler(Test *pTest) +{ + bool rc; + + /* individual tests */ + rc = ct_addTestFunction(pTest, test_BBMD_Result); + assert(rc); +} + +#ifdef TEST_BBMD_HANDLER +int main(void) +{ + Test *pTest; + + pTest = ct_create("BACnet Broadcast Management Device Handler", NULL); + test_BBMD_Handler(pTest); + /* configure output */ + ct_setStream(pTest, stdout); + ct_run(pTest); + (void)ct_report(pTest); + ct_destroy(pTest); + + return 0; +} +#endif +#endif diff --git a/src/bacnet/basic/bbmd/h_bbmd.h b/src/bacnet/basic/bbmd/h_bbmd.h new file mode 100644 index 00000000..ac0d4531 --- /dev/null +++ b/src/bacnet/basic/bbmd/h_bbmd.h @@ -0,0 +1,122 @@ +/** + * @file + * @author Steve Karg + * @date February 2020 + * @brief Header file for a basic BBMD for BVLC IPv4 handler + * + * @section LICENSE + * + * 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. + */ +#ifndef BVLC_HANDLER_H +#define BVLC_HANDLER_H + +#include +#include +#include +#include +#include "bacnet/bacnet_stack_exports.h" +#include "bacnet/bacdef.h" +#include "bacnet/datalink/bvlc.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* user application function prototypes */ +BACNET_STACK_EXPORT +int bvlc_handler(BACNET_IP_ADDRESS *addr, + BACNET_ADDRESS *src, + uint8_t *npdu, + uint16_t npdu_len); + +BACNET_STACK_EXPORT +int bvlc_send_pdu(BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len); + +BACNET_STACK_EXPORT +uint16_t bvlc_get_last_result(void); + +BACNET_STACK_EXPORT +uint8_t bvlc_get_function_code(void); + +BACNET_STACK_EXPORT +void bvlc_maintenance_timer(uint16_t seconds); + +BACNET_STACK_EXPORT +void bvlc_init(void); + +BACNET_STACK_EXPORT +void bvlc_debug_enable(void); + +/* send a Read BDT request */ +BACNET_STACK_EXPORT +int bvlc_bbmd_read_bdt(BACNET_IP_ADDRESS *bbmd_addr); +/* send a Read FDT request */ +BACNET_STACK_EXPORT +int bvlc_bbmd_read_fdt(BACNET_IP_ADDRESS *bbmd_addr); + +/* registers with a bbmd as a foreign device */ +BACNET_STACK_EXPORT +int bvlc_register_with_bbmd( + BACNET_IP_ADDRESS *address, uint16_t time_to_live_seconds); + +/* Local interface to manage BBMD. + * The interface user needs to handle mutual exclusion if needed i.e. + * BACnet packet is not being handled when the BBMD table is modified. + */ + +/* Get broadcast distribution table list */ +BACNET_STACK_EXPORT +BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bvlc_bdt_list(void); + +/* Invalidate all entries in the broadcast distribution table */ +BACNET_STACK_EXPORT +void bvlc_bdt_list_clear(void); + +/* Backup broadcast distribution table to a file. + * Filename is the BBMD_BACKUP_FILE constant + */ +BACNET_STACK_EXPORT +void bvlc_bdt_backup_local(void); + +/* Restore broadcast distribution from a file. + * Filename is the BBMD_BACKUP_FILE constant + */ +BACNET_STACK_EXPORT +void bvlc_bdt_restore_local(void); + +/* Set global IP address of a NAT enabled router which is used in forwarded + * messages. Enables NAT handling. + */ +BACNET_STACK_EXPORT +void bvlc_set_global_address_for_nat(const BACNET_IP_ADDRESS *addr); + +/* Disable NAT handling of BBMD. + */ +BACNET_STACK_EXPORT +void bvlc_disable_nat(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/basic/bbmd/h_bbmd.mak b/src/bacnet/basic/bbmd/h_bbmd.mak new file mode 100644 index 00000000..ee5081ee --- /dev/null +++ b/src/bacnet/basic/bbmd/h_bbmd.mak @@ -0,0 +1,47 @@ +#Makefile to build test case +CC = gcc +SRC_DIR = ../../../../src +TEST_DIR = ../../../../test +INCLUDES = -I$(SRC_DIR) -I$(TEST_DIR) +DEFINES = -DBIG_ENDIAN=0 -DBACDL_BIP -DBBMD_ENABLED=1 -DTEST -DTEST_BBMD_HANDLER + +CFLAGS = -Wall $(INCLUDES) $(DEFINES) -g + +SRCS = $(SRC_DIR)/bacnet/basic/bbmd/h_bbmd.c \ + $(SRC_DIR)/bacnet/bacdcode.c \ + $(SRC_DIR)/bacnet/bacint.c \ + $(SRC_DIR)/bacnet/bacstr.c \ + $(SRC_DIR)/bacnet/bacreal.c \ + $(SRC_DIR)/bacnet/datalink/bvlc.c \ + $(SRC_DIR)/bacnet/basic/sys/debug.c \ + $(TEST_DIR)/ctest.c + +TARGET_NAME = bbmd +ifeq ($(OS),Windows_NT) +TARGET_EXT = .exe +else +TARGET_EXT = +endif +TARGET = $(TARGET_NAME)$(TARGET_EXT) + +all: ${TARGET} + +OBJS = ${SRCS:.c=.o} + +${TARGET}: ${OBJS} + ${CC} -o $@ ${OBJS} + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +clean: + rm -rf core ${TARGET} $(OBJS) *.bak *.1 *.ini + +test: ${TARGET} + ./${TARGET} + +include: .depend diff --git a/src/bacnet/basic/bbmd6/h_bbmd6.c b/src/bacnet/basic/bbmd6/h_bbmd6.c index 6ddd22fa..a04d7b9b 100644 --- a/src/bacnet/basic/bbmd6/h_bbmd6.c +++ b/src/bacnet/basic/bbmd6/h_bbmd6.c @@ -70,31 +70,31 @@ static BACNET_IP6_BROADCAST_DISTRIBUTION_TABLE_ENTRY static BACNET_IP6_FOREIGN_DEVICE_TABLE_ENTRY FD_Table[MAX_FD6_ENTRIES]; #endif -#if defined(BACDL_BIP6) && BBMD6_ENABLED /** A timer function that is called about once a second. * * @param seconds - number of elapsed seconds since the last call */ -void bbmd6_maintenance_timer(time_t seconds) +void bvlc6_maintenance_timer(uint16_t seconds) { +#if defined(BACDL_BIP6) && BBMD6_ENABLED unsigned i = 0; - for (i = 0; i < MAX_FD_ENTRIES; i++) { + for (i = 0; i < MAX_FD6_ENTRIES; i++) { if (FD_Table[i].valid) { - if (FD_Table[i].seconds_remaining) { - if (FD_Table[i].seconds_remaining < seconds) { - FD_Table[i].seconds_remaining = 0; + if (FD_Table[i].ttl_seconds_remaining) { + if (FD_Table[i].ttl_seconds_remaining < seconds) { + FD_Table[i].ttl_seconds_remaining = 0; } else { - FD_Table[i].seconds_remaining -= seconds; + FD_Table[i].ttl_seconds_remaining -= seconds; } - if (FD_Table[i].seconds_remaining == 0) { + if (FD_Table[i].ttl_seconds_remaining == 0) { FD_Table[i].valid = false; } } } } -} #endif +} /** * Sets the IPv6 source address from a VMAC address structure @@ -1113,7 +1113,7 @@ static void test_BBMD_Result(Test *pTest) bvlc6_address_set(&addr, BIP6_MULTICAST_LINK_LOCAL, 0, 0, 0, 0, 0, 0, BIP6_MULTICAST_GROUP_ID); - addr.port = 0xBAC0; + addr.port = 0xBAC0U; bvlc6_vmac_address_set(&src, vmac_src); for (i = 0; i < 6; i++) { mtu_len = diff --git a/src/bacnet/basic/bbmd6/h_bbmd6.h b/src/bacnet/basic/bbmd6/h_bbmd6.h index c24130a6..c909b3d5 100644 --- a/src/bacnet/basic/bbmd6/h_bbmd6.h +++ b/src/bacnet/basic/bbmd6/h_bbmd6.h @@ -47,22 +47,31 @@ extern "C" { BACNET_ADDRESS * src, uint8_t * npdu, uint16_t npdu_len); + BACNET_STACK_EXPORT int bvlc6_send_pdu(BACNET_ADDRESS *dest, BACNET_NPDU_DATA *npdu_data, uint8_t *pdu, unsigned pdu_len); + BACNET_STACK_EXPORT int bvlc6_register_with_bbmd( BACNET_IP6_ADDRESS *bbmd_addr, uint32_t vmac_src, uint16_t time_to_live_seconds); + BACNET_STACK_EXPORT uint16_t bvlc6_get_last_result( void); + BACNET_STACK_EXPORT uint8_t bvlc6_get_function_code( void); + + BACNET_STACK_EXPORT + void bvlc6_maintenance_timer( + uint16_t seconds); + BACNET_STACK_EXPORT void bvlc6_init(void); diff --git a/src/bacnet/basic/binding/address.c b/src/bacnet/basic/binding/address.c index b338bc95..b893e972 100644 --- a/src/bacnet/basic/binding/address.c +++ b/src/bacnet/basic/binding/address.c @@ -270,7 +270,7 @@ bool address_mac_from_ascii(BACNET_MAC_ADDRESS *mac, char *arg) mac->adr[2] = a[2]; mac->adr[3] = a[3]; if (c == 4) { - port = 0xBAC0; + port = 0xBAC0U; } else { port = (uint16_t)p; } diff --git a/src/bacnet/basic/npdu/h_routed_npdu.c b/src/bacnet/basic/npdu/h_routed_npdu.c index faf9f7cb..bcff248d 100644 --- a/src/bacnet/basic/npdu/h_routed_npdu.c +++ b/src/bacnet/basic/npdu/h_routed_npdu.c @@ -38,13 +38,11 @@ #include "bacnet/basic/object/device.h" #include "bacnet/basic/sys/debug.h" #include "bacnet/basic/services.h" +#include "bacnet/datalink/datalink.h" #if PRINT_ENABLED #include #endif -#if defined(BACDL_BIP) -#include "bacnet/datalink/bvlc.h" -#endif /** @file h_routed_npdu.c Handles messages at the NPDU level of the BACnet * stack, including routing and network control messages. */ diff --git a/src/bacnet/basic/service/h_apdu.c b/src/bacnet/basic/service/h_apdu.c index db95b4c2..2d927c0e 100644 --- a/src/bacnet/basic/service/h_apdu.c +++ b/src/bacnet/basic/service/h_apdu.c @@ -139,7 +139,7 @@ bool apdu_service_supported(BACNET_SERVICES_SUPPORTED service_supported) /* Check to see if the current Device supports this service. */ int len = Routed_Device_Service_Approval( - service_supported, 0, NULL, 0); + confirmed_service_supported[i], 0, NULL, 0); if (len > 0) break; /* Not supported - return false */ #endif diff --git a/src/bacnet/datalink/bip.c b/src/bacnet/datalink/bip.c deleted file mode 100644 index ab78cf0e..00000000 --- a/src/bacnet/datalink/bip.c +++ /dev/null @@ -1,385 +0,0 @@ -/*####COPYRIGHTBEGIN#### - ------------------------------------------- - Copyright (C) 2005 Steve Karg - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to: - The Free Software Foundation, Inc. - 59 Temple Place - Suite 330 - Boston, MA 02111-1307, USA. - - As a special exception, if other files instantiate templates or - use macros or inline functions from this file, or you compile - this file and link it with other works to produce a work based - on this file, this file does not by itself cause the resulting - work to be covered by the GNU General Public License. However - the source code for this file must still be made available in - accordance with section (3) of the GNU General Public License. - - This exception does not invalidate any other reasons why a work - based on this file might be covered by the GNU General Public - License. - ------------------------------------------- -####COPYRIGHTEND####*/ - -#include /* for standard integer types uint8_t etc. */ -#include /* for the standard bool type. */ -#include "bacnet/bacdcode.h" -#include "bacnet/bacint.h" -#include "bacnet/datalink/bip.h" -#include "bacnet/datalink/bvlc.h" -#include "bacport.h" /* custom per port */ -#if PRINT_ENABLED -#include /* for standard i/o, like printing */ -#endif - -/** @file bip.c Configuration and Operations for BACnet/IP */ - -static int BIP_Socket = -1; -/* port to use - stored in network byte order */ -static uint16_t BIP_Port = 0; /* this will force initialization in demos */ -/* IP Address - stored in network byte order */ -static struct in_addr BIP_Address; -/* Broadcast Address - stored in network byte order */ -static struct in_addr BIP_Broadcast_Address; - -/** Setter for the BACnet/IP socket handle. - * - * @param sock_fd [in] Handle for the BACnet/IP socket. - */ -void bip_set_socket(int sock_fd) -{ - BIP_Socket = sock_fd; -} - -/** Getter for the BACnet/IP socket handle. - * - * @return The handle to the BACnet/IP socket. - */ -int bip_socket(void) -{ - return BIP_Socket; -} - -bool bip_valid(void) -{ - return (BIP_Socket != -1); -} - -void bip_set_addr(uint32_t net_address) -{ /* in network byte order */ - BIP_Address.s_addr = net_address; -} - -/* returns network byte order */ -uint32_t bip_get_addr(void) -{ - return BIP_Address.s_addr; -} - -void bip_set_broadcast_addr(uint32_t net_address) -{ /* in network byte order */ - BIP_Broadcast_Address.s_addr = net_address; -} - -/* returns network byte order */ -uint32_t bip_get_broadcast_addr(void) -{ - return BIP_Broadcast_Address.s_addr; -} - -void bip_set_port(uint16_t port) -{ /* in network byte order */ - BIP_Port = port; -} - -/* returns network byte order */ -uint16_t bip_get_port(void) -{ - return BIP_Port; -} - -static int bip_decode_bip_address(BACNET_ADDRESS *bac_addr, - struct in_addr *address, /* in network format */ - uint16_t *port) -{ /* in network format */ - int len = 0; - - if (bac_addr) { - memcpy(&address->s_addr, &bac_addr->mac[0], 4); - memcpy(port, &bac_addr->mac[4], 2); - len = 6; - } - - return len; -} - -/** Function to send a packet out the BACnet/IP socket (Annex J). - * @ingroup DLBIP - * - * @param dest [in] Destination address (may encode an IP address and port #). - * @param npdu_data [in] The NPDU header (Network) information (not used). - * @param pdu [in] Buffer of data to be sent - may be null (why?). - * @param pdu_len [in] Number of bytes in the pdu buffer. - * @return Number of bytes sent on success, negative number on failure. - */ -int bip_send_pdu(BACNET_ADDRESS *dest, /* destination address */ - BACNET_NPDU_DATA *npdu_data, /* network information */ - uint8_t *pdu, /* any data to be sent - may be null */ - unsigned pdu_len) -{ /* number of bytes of data */ - struct sockaddr_in bip_dest = { 0 }; - uint8_t mtu[MAX_MPDU] = { 0 }; - int mtu_len = 0; - int bytes_sent = 0; - /* addr and port in host format */ - struct in_addr address; - uint16_t port = 0; - - (void)npdu_data; - /* assumes that the driver has already been initialized */ - if (BIP_Socket < 0) { - return BIP_Socket; - } - - mtu[0] = BVLL_TYPE_BACNET_IP; - bip_dest.sin_family = AF_INET; - if ((dest->net == BACNET_BROADCAST_NETWORK) || (dest->mac_len == 0)) { - /* broadcast */ - address.s_addr = BIP_Broadcast_Address.s_addr; - port = BIP_Port; - mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; - } else if ((dest->net > 0) && (dest->len == 0)) { - /* network specific broadcast */ - if (dest->mac_len == 6) { - bip_decode_bip_address(dest, &address, &port); - } else { - address.s_addr = BIP_Broadcast_Address.s_addr; - port = BIP_Port; - } - mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; - } else if (dest->mac_len == 6) { - bip_decode_bip_address(dest, &address, &port); - mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU; - } else { - /* invalid address */ - return -1; - } - bip_dest.sin_addr.s_addr = address.s_addr; - bip_dest.sin_port = port; - mtu_len = 2; - mtu_len += encode_unsigned16( - &mtu[mtu_len], (uint16_t)(pdu_len + 4 /*inclusive */)); - memcpy(&mtu[mtu_len], pdu, pdu_len); - mtu_len += pdu_len; - - /* Send the packet */ - bytes_sent = sendto(BIP_Socket, (char *)mtu, mtu_len, 0, - (struct sockaddr *)&bip_dest, sizeof(struct sockaddr)); - - return bytes_sent; -} - -/** Implementation of the receive() function for BACnet/IP; receives one - * packet, verifies its BVLC header, and removes the BVLC header from - * the PDU data before returning. - * - * @param src [out] Source of the packet - who should receive any response. - * @param pdu [out] A buffer to hold the PDU portion of the received packet, - * after the BVLC portion has been stripped - * off. - * @param max_pdu [in] Size of the pdu[] buffer. - * @param timeout [in] The number of milliseconds to wait for a packet. - * @return The number of octets (remaining) in the PDU, or zero on failure. - */ -uint16_t bip_receive(BACNET_ADDRESS *src, /* source address */ - uint8_t *pdu, /* PDU data */ - uint16_t max_pdu, /* amount of space available in the PDU */ - unsigned timeout) -{ - int received_bytes = 0; - uint16_t pdu_len = 0; /* return value */ - fd_set read_fds; - int max = 0; - struct timeval select_timeout; - struct sockaddr_in sin = { 0 }; - socklen_t sin_len = sizeof(sin); - uint16_t i = 0; - int function = 0; - - /* Make sure the socket is open */ - if (BIP_Socket < 0) { - return 0; - } - - /* we could just use a non-blocking socket, but that consumes all - the CPU time. We can use a timeout; it is only supported as - a select. */ - if (timeout >= 1000) { - select_timeout.tv_sec = timeout / 1000; - select_timeout.tv_usec = - 1000 * (timeout - select_timeout.tv_sec * 1000); - } else { - select_timeout.tv_sec = 0; - select_timeout.tv_usec = 1000 * timeout; - } - FD_ZERO(&read_fds); - FD_SET(BIP_Socket, &read_fds); - max = BIP_Socket; - /* see if there is a packet for us */ - if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { - received_bytes = recvfrom(BIP_Socket, (char *)&pdu[0], max_pdu, 0, - (struct sockaddr *)&sin, &sin_len); - } else { - return 0; - } - - /* See if there is a problem */ - if (received_bytes < 0) { - return 0; - } - - /* no problem, just no bytes */ - if (received_bytes == 0) { - return 0; - } - - /* the signature of a BACnet/IP packet */ - if (pdu[0] != BVLL_TYPE_BACNET_IP) { - return 0; - } - - if (bvlc_for_non_bbmd(&sin, pdu, received_bytes) > 0) { - /* Handled, usually with a NACK. */ -#if PRINT_ENABLED - fprintf(stderr, "BIP: BVLC discarded!\n"); -#endif - return 0; - } - - function = bvlc_get_function_code(); /* aka, pdu[1] */ - if ((function == BVLC_ORIGINAL_UNICAST_NPDU) || - (function == BVLC_ORIGINAL_BROADCAST_NPDU)) { - /* ignore messages from me */ - if ((sin.sin_addr.s_addr == BIP_Address.s_addr) && - (sin.sin_port == BIP_Port)) { - pdu_len = 0; -#if 0 - fprintf(stderr, "BIP: src is me. Discarded!\n"); -#endif - } else { - /* data in src->mac[] is in network format */ - src->mac_len = 6; - memcpy(&src->mac[0], &sin.sin_addr.s_addr, 4); - memcpy(&src->mac[4], &sin.sin_port, 2); - /* FIXME: check destination address */ - /* see if it is broadcast or for us */ - /* decode the length of the PDU - length is inclusive of BVLC */ - (void)decode_unsigned16(&pdu[2], &pdu_len); - /* subtract off the BVLC header */ - pdu_len -= 4; - if (pdu_len < max_pdu) { -#if 0 - fprintf(stderr, "BIP: NPDU[%hu]:", pdu_len); -#endif - /* shift the buffer to return a valid PDU */ - for (i = 0; i < pdu_len; i++) { - pdu[i] = pdu[4 + i]; -#if 0 - fprintf(stderr, "%02X ", pdu[i]); -#endif - } -#if 0 - fprintf(stderr, "\n"); -#endif - } - /* ignore packets that are too large */ - /* clients should check my max-apdu first */ - else { - pdu_len = 0; -#if PRINT_ENABLED - fprintf(stderr, "BIP: PDU too large. Discarded!.\n"); -#endif - } - } - } else if (function == BVLC_FORWARDED_NPDU) { - memcpy(&sin.sin_addr.s_addr, &pdu[4], 4); - memcpy(&sin.sin_port, &pdu[8], 2); - if ((sin.sin_addr.s_addr == BIP_Address.s_addr) && - (sin.sin_port == BIP_Port)) { - /* ignore messages from me */ - pdu_len = 0; - } else { - /* data in src->mac[] is in network format */ - src->mac_len = 6; - memcpy(&src->mac[0], &sin.sin_addr.s_addr, 4); - memcpy(&src->mac[4], &sin.sin_port, 2); - /* FIXME: check destination address */ - /* see if it is broadcast or for us */ - /* decode the length of the PDU - length is inclusive of BVLC */ - (void)decode_unsigned16(&pdu[2], &pdu_len); - /* subtract off the BVLC header */ - pdu_len -= 10; - if (pdu_len < max_pdu) { - /* shift the buffer to return a valid PDU */ - for (i = 0; i < pdu_len; i++) { - pdu[i] = pdu[4 + 6 + i]; - } - } else { - /* ignore packets that are too large */ - /* clients should check my max-apdu first */ - pdu_len = 0; - } - } - } - - return pdu_len; -} - -void bip_get_my_address(BACNET_ADDRESS *my_address) -{ - int i = 0; - - if (my_address) { - my_address->mac_len = 6; - memcpy(&my_address->mac[0], &BIP_Address.s_addr, 4); - memcpy(&my_address->mac[4], &BIP_Port, 2); - my_address->net = 0; /* local only, no routing */ - my_address->len = 0; /* no SLEN */ - for (i = 0; i < MAX_MAC_LEN; i++) { - /* no SADR */ - my_address->adr[i] = 0; - } - } - - return; -} - -void bip_get_broadcast_address(BACNET_ADDRESS *dest) -{ /* destination address */ - int i = 0; /* counter */ - - if (dest) { - dest->mac_len = 6; - memcpy(&dest->mac[0], &BIP_Broadcast_Address.s_addr, 4); - memcpy(&dest->mac[4], &BIP_Port, 2); - dest->net = BACNET_BROADCAST_NETWORK; - dest->len = 0; /* no SLEN */ - for (i = 0; i < MAX_MAC_LEN; i++) { - /* no SADR */ - dest->adr[i] = 0; - } - } - - return; -} diff --git a/src/bacnet/datalink/bip.h b/src/bacnet/datalink/bip.h index a1a6e6d4..4ca9d1c3 100644 --- a/src/bacnet/datalink/bip.h +++ b/src/bacnet/datalink/bip.h @@ -1,26 +1,26 @@ /************************************************************************** -* -* Copyright (C) 2012 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. -*********************************************************************/ + * + * Copyright (C) 2012 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. + *********************************************************************/ #ifndef BIP_H #define BIP_H @@ -30,15 +30,13 @@ #include "bacnet/bacnet_stack_exports.h" #include "bacnet/bacdef.h" #include "bacnet/npdu.h" -#include "bacport.h" +#include "bacnet/datalink/bvlc.h" /* specific defines for BACnet/IP over Ethernet */ -#define MAX_HEADER (1 + 1 + 2) -#define MAX_MPDU (MAX_HEADER+MAX_PDU) - -#define BVLL_TYPE_BACNET_IP (0x81) - -extern bool BIP_Debug; +#define BIP_HEADER_MAX (1 + 1 + 2) +#define BIP_MPDU_MAX (BIP_HEADER_MAX + MAX_PDU) +/* for legacy demo applications */ +#define MAX_MPDU BIP_MPDU_MAX #ifdef __cplusplus extern "C" { @@ -48,88 +46,74 @@ extern "C" { /* on Linux, ifname is eth0, ath0, arc0, and others. on Windows, ifname is the dotted ip address of the interface */ BACNET_STACK_EXPORT - bool bip_init( - char *ifname); + bool bip_init(char *ifname); + BACNET_STACK_EXPORT - void bip_set_interface( - char *ifname); + void bip_set_interface(char *ifname); + BACNET_STACK_EXPORT - void bip_cleanup( - void); + void bip_cleanup(void); /* common BACnet/IP functions */ BACNET_STACK_EXPORT - void bip_set_socket( - int sock_fd); - BACNET_STACK_EXPORT - int bip_socket( - void); - BACNET_STACK_EXPORT - bool bip_valid( - void); - BACNET_STACK_EXPORT - void bip_get_broadcast_address( - BACNET_ADDRESS * dest); /* destination address */ - BACNET_STACK_EXPORT - void bip_get_my_address( - BACNET_ADDRESS * my_address); + bool bip_valid(void); - /* function to send a packet out the BACnet/IP socket */ - /* returns zero on success, non-zero on failure */ BACNET_STACK_EXPORT - int bip_send_pdu( - BACNET_ADDRESS * dest, /* destination address */ - BACNET_NPDU_DATA * npdu_data, /* network information */ - uint8_t * pdu, /* any data to be sent - may be null */ - unsigned pdu_len); /* number of bytes of data */ + void bip_get_broadcast_address(BACNET_ADDRESS *dest); - /* receives a BACnet/IP packet */ - /* returns the number of octets in the PDU, or zero on failure */ BACNET_STACK_EXPORT - uint16_t bip_receive( - BACNET_ADDRESS * src, /* source address */ - uint8_t * pdu, /* PDU data */ - uint16_t max_pdu, /* amount of space available in the PDU */ - unsigned timeout); /* milliseconds to wait for a packet */ + void bip_get_my_address(BACNET_ADDRESS *my_address); - /* use network byte order for setting */ BACNET_STACK_EXPORT - void bip_set_port( - uint16_t port); + int bip_send_pdu(BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len); + + /* implement in ports module */ + BACNET_STACK_EXPORT + int bip_send_mpdu(BACNET_IP_ADDRESS *dest, uint8_t *mtu, uint16_t mtu_len); + + BACNET_STACK_EXPORT + uint16_t bip_receive(BACNET_ADDRESS *src, + uint8_t *pdu, + uint16_t max_pdu, + unsigned timeout); + + /* use host byte order for setting UDP port */ + BACNET_STACK_EXPORT + void bip_set_port(uint16_t port); + BACNET_STACK_EXPORT bool bip_port_changed(void); - /* returns network byte order */ - BACNET_STACK_EXPORT - uint16_t bip_get_port( - void); - /* use network byte order for setting */ + /* returns host byte order of UDP port */ BACNET_STACK_EXPORT - void bip_set_addr( - uint32_t net_address); - /* returns network byte order */ - BACNET_STACK_EXPORT - uint32_t bip_get_addr( - void); + uint16_t bip_get_port(void); - /* use network byte order for setting */ BACNET_STACK_EXPORT - void bip_set_broadcast_addr( - uint32_t net_address); - /* returns network byte order */ - BACNET_STACK_EXPORT - uint32_t bip_get_broadcast_addr( - void); + bool bip_set_addr(BACNET_IP_ADDRESS *addr); - /* gets an IP address by name, where name can be a - string that is an IP address in dotted form, or - a name that is a domain name - returns 0 if not found, or - an IP address in network byte order */ BACNET_STACK_EXPORT - long bip_getaddrbyname( - const char *host_name); + bool bip_get_addr(BACNET_IP_ADDRESS *addr); + BACNET_STACK_EXPORT + bool bip_get_addr_by_name(const char *host_name, BACNET_IP_ADDRESS *addr); + + BACNET_STACK_EXPORT + bool bip_set_broadcast_addr(BACNET_IP_ADDRESS *addr); + + BACNET_STACK_EXPORT + bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr); + + BACNET_STACK_EXPORT + bool bip_set_subnet_prefix(uint8_t prefix); + + BACNET_STACK_EXPORT + uint8_t bip_get_subnet_prefix(void); + + BACNET_STACK_EXPORT + void bip_debug_enable(void); #ifdef __cplusplus } diff --git a/src/bacnet/datalink/bvlc.c b/src/bacnet/datalink/bvlc.c index c2f8fc50..be45c37f 100644 --- a/src/bacnet/datalink/bvlc.c +++ b/src/bacnet/datalink/bvlc.c @@ -1,6 +1,6 @@ /*####COPYRIGHTBEGIN#### ------------------------------------------- - Copyright (C) 2006 Steve Karg + Copyright (C) 2020 Steve Karg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -32,1807 +32,3170 @@ ------------------------------------------- ####COPYRIGHTEND####*/ -#include /* for standard integer types uint8_t etc. */ -#include /* for the standard bool type. */ -#include +#include +#include +#include #include "bacnet/bacenum.h" #include "bacnet/bacdcode.h" #include "bacnet/bacint.h" +#include "bacnet/bacdef.h" #include "bacnet/datalink/bvlc.h" -#ifndef DEBUG_ENABLED -#define DEBUG_ENABLED 0 -#endif -#include "bacnet/basic/sys/debug.h" -/** @file bvlc.c Handle the BACnet Virtual Link Control (BVLC), - * which includes: BACnet Broadcast Management Device, - * Broadcast Distribution Table, and - * Foreign Device Registration. - */ - -/** if we are a foreign device, store the - remote BBMD address/port here in network byte order */ -static struct sockaddr_in Remote_BBMD; - -/** Global IP address for NAT handling */ -static struct in_addr BVLC_Global_Address; - -/** Flag to indicate if NAT handling is enabled/disabled */ -static bool BVLC_NAT_Handling = false; - -/** result from a client request */ -BACNET_BVLC_RESULT BVLC_Result_Code = BVLC_RESULT_SUCCESSFUL_COMPLETION; - -/** The current BVLC Function Code being handled. */ -BACNET_BVLC_FUNCTION BVLC_Function_Code = BVLC_RESULT; /* A safe default */ - -/* Define BBMD_ENABLED to get the functions that a - * BBMD needs to handle its services. - * Separately, define BBMD_CLIENT_ENABLED to get the - * functions that allow a client to manage a BBMD. - */ -#if defined(BBMD_ENABLED) && BBMD_ENABLED - -#ifndef MAX_BBMD_ENTRIES -#define MAX_BBMD_ENTRIES 128 -#endif -static BBMD_TABLE_ENTRY BBMD_Table[MAX_BBMD_ENTRIES]; - -/*Each device that registers as a foreign device shall be placed -in an entry in the BBMD's Foreign Device Table (FDT). Each -entry shall consist of the 6-octet B/IP address of the registrant; -the 2-octet Time-to-Live value supplied at the time of -registration; and a 2-octet value representing the number of -seconds remaining before the BBMD will purge the registrant's FDT -entry if no re-registration occurs. This value will be initialized -to the 2-octet Time-to-Live value supplied at the time of -registration.*/ -typedef struct { - bool valid; - /* BACnet/IP address */ - struct in_addr dest_address; - /* BACnet/IP port number - not always 47808=BAC0h */ - uint16_t dest_port; - /* seconds for valid entry lifetime */ - uint16_t time_to_live; - /* our counter */ - time_t seconds_remaining; /* includes 30 second grace period */ -} FD_TABLE_ENTRY; - -#ifndef MAX_FD_ENTRIES -#define MAX_FD_ENTRIES 128 -#endif -static FD_TABLE_ENTRY FD_Table[MAX_FD_ENTRIES]; - -/* Define BBMD_BACKUP_FILE if the contents of the BDT - * (broadcast distribution table) are to be stored in - * a backup file, so the contents are not lost across - * power failures, shutdowns, etc... - * (this is required behaviour as defined in BACnet standard). - * - * BBMD_BACKUP_FILE should be set to the file name - * in which to store the BDT. - */ -#define BBMD_BACKUP_FILE BACnet_BDT_table -#if defined(BBMD_BACKUP_FILE) - -#define tostr(a) str(a) -#define str(a) #a - -void bvlc_bdt_backup_local(void) -{ - static FILE *bdt_file_ptr = NULL; - - /* only try opening the file if not already opened previously */ - if (!bdt_file_ptr) { - bdt_file_ptr = fopen(tostr(BBMD_BACKUP_FILE), "wb"); - } - - /* if error opening file for writing -> silently abort */ - if (!bdt_file_ptr) { - return; - } - - fseek(bdt_file_ptr, 0, SEEK_SET); - fwrite( - BBMD_Table, sizeof(BBMD_TABLE_ENTRY), MAX_BBMD_ENTRIES, bdt_file_ptr); - fflush(bdt_file_ptr); -} - -void bvlc_bdt_restore_local(void) -{ - static FILE *bdt_file_ptr = NULL; - - /* only try opening the file if not already opened previously */ - if (!bdt_file_ptr) { - bdt_file_ptr = fopen(tostr(BBMD_BACKUP_FILE), "rb"); - } - - /* if error opening file for reading -> silently abort */ - if (!bdt_file_ptr) { - return; - } - - fseek(bdt_file_ptr, 0, SEEK_SET); - { - BBMD_TABLE_ENTRY BBMD_Table_tmp[MAX_BBMD_ENTRIES]; - size_t entries = 0; - - entries = fread(BBMD_Table_tmp, sizeof(BBMD_TABLE_ENTRY), - MAX_BBMD_ENTRIES, bdt_file_ptr); - if (entries == MAX_BBMD_ENTRIES) { - /* success reading the BDT table. */ - memcpy(BBMD_Table, BBMD_Table_tmp, - sizeof(BBMD_TABLE_ENTRY) * MAX_BBMD_ENTRIES); - } - } -} -#else -void bvlc_bdt_backup_local(void) -{ -} -void bvlc_bdt_restore_local(void) -{ -} -#endif - -/** A timer function that is called about once a second. - * - * @param seconds - number of elapsed seconds since the last call - */ -void bvlc_maintenance_timer(time_t seconds) -{ - unsigned i = 0; - - for (i = 0; i < MAX_FD_ENTRIES; i++) { - if (FD_Table[i].valid) { - if (FD_Table[i].seconds_remaining) { - if (FD_Table[i].seconds_remaining < seconds) { - FD_Table[i].seconds_remaining = 0; - } else { - FD_Table[i].seconds_remaining -= seconds; - } - if (FD_Table[i].seconds_remaining == 0) { - FD_Table[i].valid = false; - } - } - } - } -} - -/** Copy the source internet address to the BACnet address - * - * FIXME: IPv6? - * - * @param src - returns the BACnet source address - * @param sin - source address in network order - * - * @return number of bytes decoded - */ -static void bvlc_internet_to_bacnet_address( - BACNET_ADDRESS *src, struct sockaddr_in *sin) -{ - if (src && sin) { - memcpy(&src->mac[0], &sin->sin_addr.s_addr, 4); - memcpy(&src->mac[4], &sin->sin_port, 2); - src->mac_len = (uint8_t)6; - src->net = 0; - src->len = 0; - } - - return; -} - -/** Encode the address entry. Used for both read and write entries. - * - * Addressing within B/IP Networks - * In the case of B/IP networks, six octets consisting of the four-octet - * IP address followed by a two-octet UDP port number (both of - * which shall be transmitted most significant octet first). - * Note: for local storage, the storage order is NETWORK byte order. - * Note: BACnet unsigned is encoded as most significant octet. - * - * @param pdu - buffer to extract encoded address - * @param address - address in network order - * @param port - UDP port number in network order - * - * @return number of bytes encoded - */ -static int bvlc_encode_bip_address( - uint8_t *pdu, struct in_addr *address, uint16_t port) -{ - int len = 0; - - if (pdu) { - memcpy(&pdu[0], &address->s_addr, 4); - memcpy(&pdu[4], &port, 2); - len = 6; - } - - return len; -} - -/** Decode the address entry. Used for both read and write entries. - * - * @param pdu - buffer to extract encoded address - * @param address - address in network order - * @param port - UDP port number in network order - * - * @return number of bytes decoded - */ -static int bvlc_decode_bip_address( - uint8_t *pdu, struct in_addr *address, uint16_t *port) -{ - int len = 0; - - if (pdu) { - memcpy(&address->s_addr, &pdu[0], 4); - memcpy(port, &pdu[4], 2); - len = 6; - } - - return len; -} - -/** Encode the address entry. Used for both read and write entries. +/** + * @brief Encode the BVLC header * * @param pdu - buffer to store the encoding - * @param address - address in network order - * @param port - UDP port number in network order - * @param mask - address mask in network order + * @param pdu_size - size of the buffer to store encoding + * @param message_type - BVLL Messages + * @param length - number of bytes for this message type * * @return number of bytes encoded */ -static int bvlc_encode_address_entry( - uint8_t *pdu, struct in_addr *address, uint16_t port, struct in_addr *mask) +int bvlc_encode_header( + uint8_t *pdu, uint16_t pdu_size, uint8_t message_type, uint16_t length) { - int len = 0; + int bytes_encoded = 0; - if (pdu) { - len = bvlc_encode_bip_address(pdu, address, port); - memcpy(&pdu[len], &mask->s_addr, 4); - len += 4; - } - - return len; -} -#endif - -/** Encode the BVLC Result message - * - * @param pdu - buffer to store the encoding - * @param result_code - BVLC result code - * - * @return number of bytes encoded - */ -static int bvlc_encode_bvlc_result(uint8_t *pdu, BACNET_BVLC_RESULT result_code) -{ - if (pdu) { + if (pdu && (pdu_size >= 2)) { pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_RESULT; + pdu[1] = message_type; /* The 2-octet BVLC Length field is the length, in octets, of the entire BVLL message, including the two octets of the length field itself, most significant octet first. */ - encode_unsigned16(&pdu[2], 6); - encode_unsigned16(&pdu[4], (uint16_t)result_code); + encode_unsigned16(&pdu[2], length); + bytes_encoded = 4; } - return 6; -} - -#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED -/** Encode the initial part of the Read-Broadcast-Distribution-Table message - * - * @param pdu - buffer to store the encoding - * @param entries - number of BDT entries - * - * @return number of bytes encoded - */ -int bvlc_encode_write_bdt_init(uint8_t *pdu, unsigned entries) -{ - int len = 0; - uint16_t BVLC_length = 0; - - if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - BVLC_length = 4 + (uint16_t)(entries * 10); - encode_unsigned16(&pdu[2], BVLC_length); - len = 4; - } - - return len; -} -#endif - -/** Encode a Read-Broadcast-Distribution-Table message - * - * @param pdu - buffer to store the encoding - * - * @return number of bytes encoded - */ -int bvlc_encode_read_bdt(uint8_t *pdu) -{ - int len = 0; - - if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_READ_BROADCAST_DIST_TABLE; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - encode_unsigned16(&pdu[2], 4); - len = 4; - } - - return len; + return bytes_encoded; } /** - * Read the Read-Broadcast-Distribution-Table of a BBMD + * @brief Decode the BVLC Result message * - * @param bbmd_address - IPv4 address (long) of BBMD to read, - * in network byte order. - * @param bbmd_port - Network port of BBMD to read, in network byte order - * @return Upon successful completion, returns the number of bytes sent. - * Otherwise, -1 shall be returned and errno set to indicate the error. + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param message_type - BVLL Messages + * @param message_length - number of bytes for this message type + * + * @return number of bytes decoded */ -int bvlc_bbmd_read_bdt(uint32_t bbmd_address, uint16_t bbmd_port) +int bvlc_decode_header(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *message_type, + uint16_t *message_length) { - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - int rv = 0; - struct sockaddr_in bbmd = { 0 }; + int bytes_consumed = 0; - mtu_len = bvlc_encode_read_bdt(mtu); - if (mtu_len > 0) { - bbmd.sin_addr.s_addr = bbmd_address; - bbmd.sin_port = bbmd_port; - rv = bvlc_send_mpdu(&bbmd, &mtu[0], mtu_len); - } - - return rv; -} - -#if defined(BBMD_ENABLED) && BBMD_ENABLED -/** Encode the initial part of the Read BDT Ack message - * - * @param pdu - buffer to store the encoding - * @param entries - number of BDT entries - * - * @return number of bytes encoded - */ -static int bvlc_encode_read_bdt_ack_init(uint8_t *pdu, unsigned entries) -{ - int len = 0; - uint16_t BVLC_length = 0; - - if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_READ_BROADCAST_DIST_TABLE_ACK; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - BVLC_length = 4 + (uint16_t)(entries * 10); - encode_unsigned16(&pdu[2], BVLC_length); - len = 4; - } - - return len; -} - -/** Encode a Read BDT Ack message - * - * @param pdu - buffer to store the encoding - * @param max_pdu - size of the buffer to store the encoding - * - * @return number of bytes encoded - */ -static int bvlc_encode_read_bdt_ack(uint8_t *pdu, uint16_t max_pdu) -{ - int pdu_len = 0; /* return value */ - int len = 0; - unsigned count = 0; - unsigned i; - - for (i = 0; i < MAX_BBMD_ENTRIES; i++) { - if (BBMD_Table[i].valid) { - count++; - } - } - len = bvlc_encode_read_bdt_ack_init(&pdu[0], count); - pdu_len += len; - for (i = 0; i < MAX_BBMD_ENTRIES; i++) { - if (BBMD_Table[i].valid) { - /* too much to send */ - if ((pdu_len + 10) > max_pdu) { - pdu_len = 0; - break; + if (pdu && (pdu_len >= 4)) { + if (pdu[0] == BVLL_TYPE_BACNET_IP) { + if (message_type) { + *message_type = pdu[1]; } - len = bvlc_encode_address_entry(&pdu[pdu_len], - &BBMD_Table[i].dest_address, BBMD_Table[i].dest_port, - &BBMD_Table[i].broadcast_mask); - pdu_len += len; - } - } - - return pdu_len; -} - -/** Encode a Forwarded NPDU message - * - * @param pdu - buffer to store the encoding - * @param sin - source address in network order - * @param npdu - NPDU to forward - * @param max_npdu - amount of space available in the NPDU - * @param npdu_length - size of the NPDU to forward - * - * @return number of bytes encoded - */ -static int bvlc_encode_forwarded_npdu(uint8_t *pdu, - uint16_t max_pdu, - struct sockaddr_in *sin, - uint8_t *npdu, - unsigned npdu_length) -{ - int len = 0; - - unsigned i; /* for loop counter */ - - if (pdu && sin && npdu) { - if ((npdu_length + 4 + 6) <= max_pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_FORWARDED_NPDU; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - encode_unsigned16(&pdu[2], (uint16_t)(4 + 6 + npdu_length)); - len = 4; - /* 6-octet address encoding */ - len += bvlc_encode_bip_address( - &pdu[len], &sin->sin_addr, sin->sin_port); - for (i = 0; i < npdu_length; i++) { - pdu[len] = npdu[i]; - len++; + if (message_length) { + decode_unsigned16(&pdu[2], message_length); } + bytes_consumed = 4; } } - return len; -} -#endif - -#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED -/** Encode a Read Foreign Device Table message - * - * @param pdu - buffer to store the encoding - * - * @return number of bytes encoded - */ -int bvlc_encode_read_fdt(uint8_t *pdu) -{ - int len = 0; - - if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - encode_unsigned16(&pdu[2], 4); - len = 4; - } - - return len; -} -#endif - -#if defined(BBMD_ENABLED) && BBMD_ENABLED -/** Encode the initial part of a Read Foreign Device Table Ack - * - * @param pdu - buffer to store the encoding - * @param entries - number of foreign device entries in this Ack. - * - * @return number of bytes encoded - */ -static int bvlc_encode_read_fdt_ack_init(uint8_t *pdu, unsigned entries) -{ - int len = 0; - uint16_t BVLC_length = 0; - - if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_READ_FOREIGN_DEVICE_TABLE_ACK; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - BVLC_length = 4 + (uint16_t)(entries * 10); - encode_unsigned16(&pdu[2], BVLC_length); - len = 4; - } - - return len; + return bytes_consumed; } -/** Encode a Read Foreign Device Table Ack +/** + * @brief J.2.1 BVLC-Result: Encode + * + * This message provides a mechanism to acknowledge the result + * of those BVLL service requests that require an acknowledgment, + * whether successful (ACK) or unsuccessful (NAK). * * @param pdu - buffer to store the encoding - * @param max_pdu - number of bytes available to encode + * @param pdu_size - size of the buffer to store encoding + * @param result_code - BVLC result code * * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IPv4 + * BVLC Function: 1-octet X'00' BVLC-Result + * BVLC Length: 2-octets X'0006' Length of the BVLL message + * Result Code: 2-octets X'0000' Successful completion + * X'0010' Write-Broadcast-Distribution-Table NAK + * X'0020' Read-Broadcast-Distribution-Table NAK + * X'0030' Register-Foreign-Device NAK + * X'0040' Read-Foreign-Device-Table NAK + * X'0050' Delete-Foreign-Device-Table-Entry NAK + * X'0060' Distribute-Broadcast-To-Network NAK */ -static int bvlc_encode_read_fdt_ack(uint8_t *pdu, uint16_t max_pdu) +int bvlc_encode_result(uint8_t *pdu, uint16_t pdu_size, uint16_t result_code) { - int pdu_len = 0; /* return value */ - int len = 0; - unsigned count = 0; - unsigned i; - uint16_t seconds_remaining = 0; + int bytes_encoded = 0; + const uint16_t length = 6; - for (i = 0; i < MAX_FD_ENTRIES; i++) { - if (FD_Table[i].valid) { - count++; - } - } - len = bvlc_encode_read_fdt_ack_init(&pdu[0], count); - pdu_len += len; - for (i = 0; i < MAX_FD_ENTRIES; i++) { - if (FD_Table[i].valid) { - /* too much to send */ - if ((pdu_len + 10) > max_pdu) { - pdu_len = 0; - break; - } - len = bvlc_encode_bip_address(&pdu[pdu_len], - &FD_Table[i].dest_address, FD_Table[i].dest_port); - pdu_len += len; - len = encode_unsigned16(&pdu[pdu_len], FD_Table[i].time_to_live); - pdu_len += len; - seconds_remaining = (uint16_t)FD_Table[i].seconds_remaining; - len = encode_unsigned16(&pdu[pdu_len], seconds_remaining); - pdu_len += len; + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header(pdu, pdu_size, BVLC_RESULT, length); + if (bytes_encoded == 4) { + encode_unsigned16(&pdu[4], result_code); + bytes_encoded = (int)length; } } - return pdu_len; + return bytes_encoded; } -#endif -#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED -/** Encode an Foreign Device Table entry +/** + * @brief Decode the BVLC Result message, after header is decoded * - * @param pdu - buffer to store the encoding - * @param address - in network byte order - * @param port - in network byte order + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param result_code - BVLC result code * - * @return number of bytes encoded + * @return number of bytes decoded */ -int bvlc_encode_delete_fdt_entry(uint8_t *pdu, uint32_t address, uint16_t port) +int bvlc_decode_result(uint8_t *pdu, uint16_t pdu_len, uint16_t *result_code) { - int len = 0; + int bytes_consumed = 0; + const uint16_t length = 2; - if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - encode_unsigned16(&pdu[2], 10); - /* FDT Entry */ - encode_unsigned32(&pdu[4], address); - encode_unsigned16(&pdu[8], port); - len = 10; - } - - return len; -} -#endif - -#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED -/** Encode an Original Unicast NPDU - * - * @param pdu - buffer to store the encoding - * @param npdu - NPDU portion of message - * @param npdu_length - number of bytes to encode - * - * @return number of bytes encoded - */ -int bvlc_encode_original_unicast_npdu( - uint8_t *pdu, uint8_t *npdu, unsigned npdu_length) -{ - int len = 0; /* return value */ - unsigned i = 0; /* loop counter */ - uint16_t BVLC_length = 0; - - if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_ORIGINAL_UNICAST_NPDU; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - BVLC_length = 4 + (uint16_t)npdu_length; - len = encode_unsigned16(&pdu[2], BVLC_length) + 2; - for (i = 0; i < npdu_length; i++) { - pdu[len] = npdu[i]; - len++; + if (pdu && (pdu_len >= length)) { + if (result_code) { + decode_unsigned16(&pdu[0], result_code); } + bytes_consumed = (int)length; } - return len; + return bytes_consumed; } -#endif -#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED -/** Encode an Original Broadcast NPDU - * - * @param pdu - buffer to store the encoding - * @param npdu - NPDU portion of message - * @param npdu_length - number of bytes to encode - * - * @return number of bytes encoded +/** + * @brief Copy the BVLC Broadcast Distribution Mask + * @param dst - BVLC Broadcast Distribution Mask that will be filled with src + * @param src - BVLC Broadcast Distribution Mask that will be copied into dst + * @return true if the mask was copied */ -int bvlc_encode_original_broadcast_npdu( - uint8_t *pdu, uint8_t *npdu, unsigned npdu_length) -{ - int len = 0; /* return value */ - unsigned i = 0; /* loop counter */ - uint16_t BVLC_length = 0; - - if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - BVLC_length = 4 + (uint16_t)npdu_length; - len = encode_unsigned16(&pdu[2], BVLC_length) + 2; - for (i = 0; i < npdu_length; i++) { - pdu[len] = npdu[i]; - len++; - } - } - - return len; -} -#endif - -#if defined(BBMD_ENABLED) && BBMD_ENABLED -/** Create a Broadcast Distribution Table from message - * - * @param npdu - message from which the devices are decoded - * @param npdu_length - number of bytes to decode - * - * @return true if all the entries fit in the table - */ -static bool bvlc_create_bdt(uint8_t *npdu, uint16_t npdu_length) +bool bvlc_broadcast_distribution_mask_copy( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *dst, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *src) { bool status = false; - unsigned i = 0; - uint16_t pdu_offset = 0; + unsigned int i = 0; - for (i = 0; i < MAX_BBMD_ENTRIES; i++) { - if (npdu_length >= 10) { - BBMD_Table[i].valid = true; - memcpy(&BBMD_Table[i].dest_address.s_addr, &npdu[pdu_offset], 4); - pdu_offset += 4; - memcpy(&BBMD_Table[i].dest_port, &npdu[pdu_offset], 2); - pdu_offset += 2; - memcpy(&BBMD_Table[i].broadcast_mask.s_addr, &npdu[pdu_offset], 4); - pdu_offset += 4; - npdu_length -= (4 + 2 + 4); - } else { - BBMD_Table[i].valid = false; - BBMD_Table[i].dest_address.s_addr = 0; - BBMD_Table[i].dest_port = 0; - BBMD_Table[i].broadcast_mask.s_addr = 0; + if (src && dst) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + dst->address[i] = src->address[i]; } - } - /* BDT changed! Save backup to file */ - bvlc_bdt_backup_local(); - - /* did they all fit? */ - if (npdu_length < 10) { status = true; } return status; } -/** Register a Foreign Device in the Foreign Device Table - * - * @param sin - source address in network order - * @param time_to_live - time in seconds - * - * @return true if the Foreign Device was added +/** + * @brief Compare the BVLC Broadcast Distribution Masks + * @param dst - BVLC Broadcast Distribution Mask that will be compared to src + * @param src - BVLC Broadcast Distribution Mask that will be compared to dst + * @return true if the masks are different */ -static bool bvlc_register_foreign_device( - struct sockaddr_in *sin, uint16_t time_to_live) +bool bvlc_broadcast_distribution_mask_different( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *dst, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *src) +{ + bool status = false; + unsigned int i = 0; + + if (src && dst) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + if (dst->address[i] != src->address[i]) { + status = true; + } + } + } + + return status; +} + +/** + * @brief Compare the Broadcast-Distribution-Table entry + * @param dst - Broadcast-Distribution-Table entry that will be compared to src + * @param src - Broadcast-Distribution-Table entry that will be compared to dst + * @return true if the addresses are different + */ +bool bvlc_broadcast_distribution_table_entry_different( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *dst, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *src) { - unsigned i = 0; bool status = false; - /* am I here already? If so, update my time to live... */ - for (i = 0; i < MAX_FD_ENTRIES; i++) { - if (FD_Table[i].valid) { - if ((FD_Table[i].dest_address.s_addr == sin->sin_addr.s_addr) && - (FD_Table[i].dest_port == sin->sin_port)) { + if (src && dst) { + status = bvlc_address_different(&dst->dest_address, &src->dest_address); + if (!status) { + status = bvlc_broadcast_distribution_mask_different( + &dst->broadcast_mask, &src->broadcast_mask); + } + } + + return status; +} + +/** + * @brief Copy the Broadcast-Distribution-Table entry + * @param dst - Broadcast-Distribution-Table entry that will be filled with src + * @param src - Broadcast-Distribution-Table entry that will be copied into dst + * @return true if the address was copied + */ +bool bvlc_broadcast_distribution_table_entry_copy( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *dst, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *src) +{ + bool status = false; + + if (src && dst) { + status = bvlc_address_copy(&dst->dest_address, &src->dest_address); + if (status) { + status = bvlc_broadcast_distribution_mask_copy( + &dst->broadcast_mask, &src->broadcast_mask); + } + } + + return status; +} + +/** + * @brief Count the number of valid Write-Broadcast-Distribution-Table entries + * + * @param bdt_list - first element in array BDT entries + * @return number of elements of BDT entries that are valid + */ +uint16_t bvlc_broadcast_distribution_table_valid_count( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL; + uint16_t bdt_entry_count = 0; + + /* count the number of entries */ + bdt_entry = bdt_list; + while (bdt_entry) { + if (bdt_entry->valid) { + bdt_entry_count++; + } + bdt_entry = bdt_entry->next; + } + + return bdt_entry_count; +} + +/** + * @brief Clear all Write-Broadcast-Distribution-Table entries + * @param bdt_list - first element in array BDT entries + */ +void bvlc_broadcast_distribution_table_valid_clear( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry; + + /* count the number of entries */ + bdt_entry = bdt_list; + while (bdt_entry) { + bdt_entry->valid = false; + bdt_entry = bdt_entry->next; + } +} + +/** + * @brief Count the total number of Write-Broadcast-Distribution-Table entries + * + * @param bdt_list - first element in array BDT entries + * @return number of elements of BDT entries + */ +uint16_t bvlc_broadcast_distribution_table_count( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL; + uint16_t bdt_entry_count = 0; + + /* count the number of entries */ + bdt_entry = bdt_list; + while (bdt_entry) { + bdt_entry_count++; + bdt_entry = bdt_entry->next; + } + + return bdt_entry_count; +} + +/** + * @brief Convert Write-Broadcast-Distribution-Table entry array + * into linked list. + * + * @param bdt_list - first element in array BDT entries + * @param bdt_array_size - number of array elements of BDT entries + */ +void bvlc_broadcast_distribution_table_link_array( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list, + const size_t bdt_array_size) +{ + size_t i = 0; + + for (i = 0; i < bdt_array_size; i++) { + if (i > 0) { + bdt_list[i - 1].next = &bdt_list[i]; + } + bdt_list[i].next = NULL; + } +} + +/** + * @brief Append an entry to the Broadcast-Distribution-Table + * @param bdt_list - first entry in list of BDT entries + * @param bdt_new - new entry to append to list of BDT entries + * @return true if the Broadcast-Distribution-Table entry was appended + */ +bool bvlc_broadcast_distribution_table_entry_append( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_new) +{ + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL; + bool status = false; + + bdt_entry = bdt_list; + while (bdt_entry) { + if (bdt_entry->valid) { + if (!bvlc_broadcast_distribution_table_entry_different( + bdt_entry, bdt_new)) { status = true; - FD_Table[i].time_to_live = time_to_live; - /* Upon receipt of a BVLL Register-Foreign-Device message, + break; + } + } else { + /* first empty slot! Assume the remaining are empty. */ + status = true; + /* Copy new entry to the empty slot */ + bvlc_broadcast_distribution_table_entry_copy(bdt_entry, bdt_new); + bdt_entry->valid = true; + break; + } + bdt_entry = bdt_entry->next; + } + + return status; +} + +/** + * @brief Set an entry to the Broadcast-Distribution-Table + * @param bdt_entry - first element in list of BDT entries + * @param addr - B/IPv4 address to match, along with mask + * @param mask - BVLC Broadcast Distribution Mask to match, along with addr + * @return true if the Broadcast Distribution entry was set + */ +bool bvlc_broadcast_distribution_table_entry_set( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry, + BACNET_IP_ADDRESS *addr, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask) +{ + bool status = false; + + if (bdt_entry && addr && mask) { + status = bvlc_address_copy(&bdt_entry->dest_address, addr); + if (status) { + status = bvlc_broadcast_distribution_mask_copy( + &bdt_entry->broadcast_mask, mask); + } + } + + return status; +} + +/** + * @brief Set the Broadcast-Distribution-Table entry distribution mask + * @param mask - broadcast distribution mask + * @param broadcast_mask - 32-bit broadcast mask in host byte order + * @return true if the broadcast distribution was set + */ +bool bvlc_broadcast_distribution_mask_from_host( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask, uint32_t broadcast_mask) +{ + bool status = false; + + if (mask) { + encode_unsigned32(mask->address, broadcast_mask); + status = true; + } + + return status; +} + +/** + * @brief Get the Broadcast-Distribution-Table entry distribution mask + * @param broadcast_mask - 32-bit broadcast mask in host byte order + * @param mask - broadcast distribution mask + * @return true if the broadcast distribution was retrieved + */ +bool bvlc_broadcast_distribution_mask_to_host( + uint32_t *broadcast_mask, BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask) +{ + bool status = false; + + if (broadcast_mask && mask) { + decode_unsigned32(mask->address, broadcast_mask); + status = true; + } + + return status; +} + +/** + * @brief Set the Broadcast-Distribution-Table entry distribution mask + * @param mask - broadcast distribution mask + * @param addr0 - broadcast distribution mask octet + * @param addr1 - broadcast distribution mask octet + * @param addr2 - broadcast distribution mask octet + * @param addr3 - broadcast distribution mask octet + */ +void bvlc_broadcast_distribution_mask_set( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask, + uint8_t addr0, + uint8_t addr1, + uint8_t addr2, + uint8_t addr3) +{ + if (mask) { + mask->address[0] = addr0; + mask->address[1] = addr1; + mask->address[2] = addr2; + mask->address[3] = addr3; + } +} + +/** + * @brief Get the Broadcast-Distribution-Table entry distribution mask + * @param mask - broadcast distribution mask + * @param addr0 - broadcast distribution mask octet + * @param addr1 - broadcast distribution mask octet + * @param addr2 - broadcast distribution mask octet + * @param addr3 - broadcast distribution mask octet + */ +void bvlc_broadcast_distribution_mask_get( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask, + uint8_t *addr0, + uint8_t *addr1, + uint8_t *addr2, + uint8_t *addr3) +{ + if (mask) { + if (addr0) { + *addr0 = mask->address[0]; + } + if (addr1) { + *addr1 = mask->address[1]; + } + if (addr2) { + *addr2 = mask->address[2]; + } + if (addr3) { + *addr3 = mask->address[3]; + } + } +} + +/** + * @brief Set the B/IP address for a Forwarded-NPDU message + * + * The B/IP address to which the Forwarded-NPDU message is + * sent is formed by inverting the broadcast distribution + * mask in the BDT entry and logically ORing it with the + * BBMD address of the same entry. + * + * @param addr - B/IPv4 address to match, along with mask + * @param bdt_entry - The BDT entry containing an address and mask + * @return true if the B/IPv4 address was set + */ +bool bvlc_broadcast_distribution_table_entry_forward_address( + BACNET_IP_ADDRESS *addr, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry) +{ + bool status = false; + + if (bdt_entry && addr) { + status = bvlc_address_mask( + addr, &bdt_entry->dest_address, &bdt_entry->broadcast_mask); + } + + return status; +} + + +/** + * @brief J.2.2 Write-Broadcast-Distribution-Table: encode + * + * This message provides a mechanism for initializing or updating a + * Broadcast Distribution Table (BDT) in a BACnet Broadcast Management + * Device (BBMD). + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param bdt_list - list of BDT entries + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'01' Write-Broadcast-Distribution-Table + * BVLC Length: 2-octets L Length L, in octets, of the BVLL message + * List of BDT Entries: N*10-octets + */ +int bvlc_encode_write_broadcast_distribution_table(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + int bytes_encoded = 0; + int len = 0; + uint16_t offset = 0; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL; + uint16_t bdt_entry_count = 0; + uint16_t length = 0; + + /* count the number of entries */ + bdt_entry_count = bvlc_broadcast_distribution_table_valid_count(bdt_list); + length = 4 + (bdt_entry_count * BACNET_IP_BDT_ENTRY_SIZE); + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE, length); + if (bytes_encoded == 4) { + offset = 4; + /* encode the entries */ + bdt_entry = bdt_list; + while (bdt_entry) { + if (bdt_entry->valid) { + len = bvlc_encode_broadcast_distribution_table_entry( + &pdu[offset], pdu_size - offset, bdt_entry); + offset += len; + } + bdt_entry = bdt_entry->next; + } + bytes_encoded = offset; + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the Write-Broadcast-Distribution-Table + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param bdt_list - BDT Entry list + * + * @return number of bytes decoded + */ +int bvlc_decode_write_broadcast_distribution_table(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + int bytes_consumed = 0; + int len = 0; + uint16_t offset = 0; + uint16_t pdu_bytes = 0; + uint16_t bdt_entry_count = 0; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL; + uint16_t list_len = 0; + + /* count the number of available entries */ + bdt_entry_count = bvlc_broadcast_distribution_table_count(bdt_list); + list_len = bdt_entry_count * BACNET_IP_BDT_ENTRY_SIZE; + /* will the entries fit */ + if (pdu && (pdu_len <= list_len)) { + bdt_entry = bdt_list; + while (bdt_entry) { + pdu_bytes = pdu_len - offset; + if (pdu_bytes >= BACNET_IP_BDT_ENTRY_SIZE) { + len = bvlc_decode_broadcast_distribution_table_entry( + &pdu[offset], pdu_bytes, bdt_entry); + if (len > 0) { + bdt_entry->valid = true; + } + offset += len; + } else { + bdt_entry->valid = false; + } + bdt_entry = bdt_entry->next; + } + bytes_consumed = (int)offset; + } + + return bytes_consumed; +} + +/** + * @brief J.2.3 Read-Broadcast-Distribution-Table: encode + * + * The message provides a mechanism for retrieving the contents of a BBMD's + * BDT. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param bdt_list - list of BDT entries + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'02' Read-Broadcast-Distribution-Table + * BVLC Length: 2-octets X'0004' Length, in octets, of the BVLL message + */ +int bvlc_encode_read_broadcast_distribution_table( + uint8_t *pdu, uint16_t pdu_size) +{ + int bytes_encoded = 0; + uint16_t length = 1 + 1 + 2; + + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_READ_BROADCAST_DIST_TABLE, length); + } + + return bytes_encoded; +} + +/** + * @brief J.2.4 Read-Broadcast-Distribution-Table-Ack: encode + * + * The message provides a mechanism for retrieving the contents of a BBMD's + * BDT. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param bdt_list - list of BDT entries + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'02' Read-Broadcast-Distribution-Table + * BVLC Length: 2-octets L length, in octets, of the BVLL message + * List of BDT Entries: N*10-octets + */ +int bvlc_encode_read_broadcast_distribution_table_ack(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + int bytes_encoded = 0; + int len = 0; + uint16_t offset = 0; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL; + uint16_t bdt_entry_count = 0; + uint16_t length = 0; + + /* count the number of entries */ + bdt_entry_count = bvlc_broadcast_distribution_table_valid_count(bdt_list); + length = 4 + (bdt_entry_count * BACNET_IP_BDT_ENTRY_SIZE); + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_READ_BROADCAST_DIST_TABLE_ACK, length); + if (bytes_encoded == 4) { + offset = 4; + /* encode the entries */ + bdt_entry = bdt_list; + while (bdt_entry) { + if (bdt_entry->valid) { + len = bvlc_encode_broadcast_distribution_table_entry( + &pdu[offset], pdu_size - offset, bdt_entry); + offset += len; + } + bdt_entry = bdt_entry->next; + } + bytes_encoded = (int)length; + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the Read-Broadcast-Distribution-Table-Ack + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param bdt_list - list of BDT Entries to be overwritten + * + * @return number of bytes decoded + */ +int bvlc_decode_read_broadcast_distribution_table_ack(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + int bytes_consumed = 0; + int len = 0; + uint16_t offset = 0; + uint16_t pdu_bytes = 0; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry = NULL; + + if (pdu && (pdu_len >= BACNET_IP_BDT_ENTRY_SIZE)) { + bdt_entry = bdt_list; + while (bdt_entry) { + pdu_bytes = pdu_len - offset; + if (pdu_bytes >= BACNET_IP_BDT_ENTRY_SIZE) { + len = bvlc_decode_broadcast_distribution_table_entry( + &pdu[offset], pdu_bytes, bdt_entry); + if (len > 0) { + bdt_entry->valid = true; + } + offset += len; + } else { + bdt_entry->valid = false; + } + bdt_entry = bdt_entry->next; + } + bytes_consumed = (int)offset; + } + + return bytes_consumed; +} + +/** + * @brief J.2.5 Forwarded-NPDU: Encode + * + * This BVLL message is used in broadcast messages from a BBMD + * as well as in messages forwarded to registered foreign devices. + * It contains the source address of the original node, or + * if NAT is being used, the address with which the original node + * is accessed, as well as the original BACnet NPDU + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param bip_address - Original-Source-B/IPv4-Address + * @param npdu - BACnet NPDU from Originating Device buffer + * @param npdu_len - size of the BACnet NPDU buffer + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'04' Forwarded-NPDU + * BVLC Length: 2-octets L Length of the BVLL message + * B/IP Address of Originating Device: 6-octets + * BACnet NPDU from Originating Device: N-octets (N=L-10) + */ +int bvlc_encode_forwarded_npdu(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_ADDRESS *bip_address, + uint8_t *npdu, + uint16_t npdu_len) +{ + int bytes_encoded = 0; + uint16_t length = 1 + 1 + 2 + BIP_ADDRESS_MAX; + uint16_t i = 0; + uint16_t offset = 0; + + length += npdu_len; + if (pdu && (pdu_size >= length)) { + bytes_encoded = + bvlc_encode_header(pdu, pdu_size, BVLC_FORWARDED_NPDU, length); + if (bytes_encoded == 4) { + offset = 4; + bvlc_encode_address(&pdu[offset], pdu_size - offset, bip_address); + offset += BIP_ADDRESS_MAX; + if (npdu && (length > 0)) { + for (i = 0; i < npdu_len; i++) { + pdu[offset + i] = npdu[i]; + } + } + bytes_encoded = (int)length; + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Forwarded-NPDU message, after decoded header + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param bip_address - Original-Source-B/IPv4-Address + * @param npdu - BACnet NPDU buffer + * @param npdu_size - size of the buffer for the decoded BACnet NPDU + * @param npdu_len - decoded length of the BACnet NPDU buffer + * + * @return number of bytes decoded + */ +int bvlc_decode_forwarded_npdu(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_ADDRESS *bip_address, + uint8_t *npdu, + uint16_t npdu_size, + uint16_t *npdu_len) +{ + int bytes_consumed = 0; + uint16_t length = 0; + uint16_t i = 0; + uint16_t offset = 0; + + if (pdu && (pdu_len >= BIP_ADDRESS_MAX)) { + if (bip_address) { + bvlc_decode_address(&pdu[offset], pdu_len - offset, bip_address); + } + offset += BIP_ADDRESS_MAX; + length = pdu_len - BIP_ADDRESS_MAX; + if (npdu && (length <= npdu_size)) { + for (i = 0; i < length; i++) { + npdu[i] = pdu[offset + i]; + } + } + if (npdu_len) { + *npdu_len = length; + } + bytes_consumed = (int)pdu_len; + } + + return bytes_consumed; +} + +/** + * @brief J.2.6 Register-Foreign-Device: encode + * + * This message allows a foreign device, as defined in Clause J.5.1, + * to register with a BBMD for the purpose of receiving broadcast messages. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param ttl_seconds - Time-to-Live T, in seconds + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'05' Register-Foreign-Device + * BVLC Length: 2-octets X'0006' Length of the BVLL message + * Time-to-Live: 2-octets T Time-to-Live T, in seconds + */ +int bvlc_encode_register_foreign_device( + uint8_t *pdu, uint16_t pdu_size, uint16_t ttl_seconds) +{ + int bytes_encoded = 0; + const uint16_t length = 6; + uint16_t offset = 0; + + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_REGISTER_FOREIGN_DEVICE, length); + if (bytes_encoded == 4) { + offset = 4; + encode_unsigned16(&pdu[offset], ttl_seconds); + bytes_encoded = (int)length; + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Register-Foreign-Device message, after decoded header + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param ttl_seconds - Time-to-Live T, in seconds + * + * @return number of bytes decoded + */ +int bvlc_decode_register_foreign_device( + uint8_t *pdu, uint16_t pdu_len, uint16_t *ttl_seconds) +{ + int bytes_consumed = 0; + const uint16_t length = 2; + uint16_t offset = 0; + + if (pdu && (pdu_len >= length)) { + if (ttl_seconds) { + decode_unsigned16(&pdu[offset], ttl_seconds); + } + bytes_consumed = (int)length; + } + + return bytes_consumed; +} + +/** + * @brief J.2.7 Read-Foreign-Device-Table: encode + * + * The message provides a mechanism for retrieving the contents of a BBMD's + * Foreign-Device-Table. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param bdt_list - list of FDT entries + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'06' Read-Foreign-Device-Table + * BVLC Length: 2-octets X'0004' Length, in octets, of the BVLL message + */ +int bvlc_encode_read_foreign_device_table(uint8_t *pdu, uint16_t pdu_size) +{ + int bytes_encoded = 0; + uint16_t length = 1 + 1 + 2; + + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_READ_FOREIGN_DEVICE_TABLE, length); + } + + return bytes_encoded; +} + +/** + * @brief Compare the Foreign Device Table entry + * @param entry1 - Foreign Device Table entry that will be compared to entry2 + * @param entry2 - Foreign Device Table entry that will be compared to entry1 + * @return true if the entries are different + */ +bool bvlc_foreign_device_table_entry_different( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *entry1, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *entry2) +{ + if (entry1 && entry2) { + if (bvlc_address_different( + &entry1->dest_address, &entry2->dest_address)) { + return true; + } + } + + return false; +} + +/** + * @brief Copy the Foreign Device Table entry + * @param entry1 - Foreign Device Table entry that will be filled with entry2 + * @param entry2 - Foreign Device Table entry that will be copied into entry1 + * @return true if the Foreign Device Table entry was copied + */ +bool bvlc_foreign_device_table_entry_copy( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *entry1, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *entry2) +{ + bool status = false; + + if (entry1 && entry2) { + entry1->ttl_seconds = entry2->ttl_seconds; + entry1->ttl_seconds_remaining = entry2->ttl_seconds_remaining; + status = + bvlc_address_copy(&entry1->dest_address, &entry2->dest_address); + } + + return status; +} + +/** + * @brief Foreign-Device-Table timer maintainence + * @param fdt_list - first element in list of FDT entries + * @param seconds - number of elapsed seconds since the last call + */ +void bvlc_foreign_device_table_maintenance_timer( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, uint16_t seconds) +{ + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL; + + fdt_entry = fdt_list; + while (fdt_entry) { + if (fdt_entry->valid) { + if (fdt_entry->ttl_seconds_remaining) { + if (fdt_entry->ttl_seconds_remaining < seconds) { + fdt_entry->ttl_seconds_remaining = 0; + } else { + fdt_entry->ttl_seconds_remaining -= seconds; + } + if (fdt_entry->ttl_seconds_remaining == 0) { + fdt_entry->valid = false; + } + } + } + fdt_entry = fdt_entry->next; + } +} + +/** + * @brief Delete an entry in the Foreign-Device-Table + * @param fdt_list - first element in list of FDT entries + * @param addr - B/IPv4 address to be deleted + * @return true if the Foreign Device entry was found and removed. + */ +bool bvlc_foreign_device_table_entry_delete( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, BACNET_IP_ADDRESS *addr) +{ + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL; + bool status = false; + + fdt_entry = fdt_list; + while (fdt_entry) { + if (fdt_entry->valid) { + if (!bvlc_address_different(&fdt_entry->dest_address, addr)) { + status = true; + fdt_entry->valid = false; + fdt_entry->ttl_seconds_remaining = 0; + break; + } + } + fdt_entry = fdt_entry->next; + } + + return status; +} + +/** + * @brief Add an entry to the Foreign-Device-Table + * @param fdt_list - first element in list of FDT entries + * @param addr - B/IPv4 address to be added + * @param ttl_seconds - Time-to-Live T, in seconds + * @return true if the Foreign Device entry was added or already exists + */ +bool bvlc_foreign_device_table_entry_add( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, + BACNET_IP_ADDRESS *addr, + uint16_t ttl_seconds) +{ + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL; + bool status = false; + + fdt_entry = fdt_list; + while (fdt_entry) { + if (fdt_entry->valid) { + /* am I here already? If so, update my time to live... */ + if (!bvlc_address_different(&fdt_entry->dest_address, addr)) { + status = true; + fdt_entry->ttl_seconds = ttl_seconds; + /* Upon receipt of a BVLL Register-Foreign-Device message, a BBMD shall start a timer with a value equal to the Time-to-Live parameter supplied plus a fixed grace period of 30 seconds. */ - FD_Table[i].seconds_remaining = time_to_live + 30; + if (ttl_seconds < (UINT16_MAX-30)) { + fdt_entry->ttl_seconds_remaining = ttl_seconds + 30; + } else { + fdt_entry->ttl_seconds_remaining = UINT16_MAX; + } break; } } + fdt_entry = fdt_entry->next; } if (!status) { - for (i = 0; i < MAX_FD_ENTRIES; i++) { - if (!FD_Table[i].valid) { - FD_Table[i].dest_address.s_addr = sin->sin_addr.s_addr; - FD_Table[i].dest_port = sin->sin_port; - FD_Table[i].time_to_live = time_to_live; - FD_Table[i].seconds_remaining = time_to_live + 30; - FD_Table[i].valid = true; + fdt_entry = fdt_list; + while (fdt_entry) { + if (!fdt_entry->valid) { + /* add to the first empty entry */ + bvlc_address_copy(&fdt_entry->dest_address, addr); + fdt_entry->ttl_seconds = ttl_seconds; + if (ttl_seconds < (UINT16_MAX-30)) { + fdt_entry->ttl_seconds_remaining = ttl_seconds + 30; + } else { + fdt_entry->ttl_seconds_remaining = UINT16_MAX; + } + fdt_entry->valid = true; status = true; break; } + fdt_entry = fdt_entry->next; } } return status; } -/** Delete a Foreign Device from the Foreign Device Table - * - * @param pdu - BACnet/IP address in PDU form - * - * @return true if the Foreign Device was found and removed. - */ -static bool bvlc_delete_foreign_device(uint8_t *pdu, uint16_t pdu_len) -{ - struct sockaddr_in sin = { 0 }; /* the ip address */ - bool status = false; /* return value */ - unsigned i = 0; - - if (pdu_len < 6) { - return status; - } - bvlc_decode_bip_address(pdu, &sin.sin_addr, &sin.sin_port); - for (i = 0; i < MAX_FD_ENTRIES; i++) { - if (FD_Table[i].valid) { - if ((FD_Table[i].dest_address.s_addr == sin.sin_addr.s_addr) && - (FD_Table[i].dest_port == sin.sin_port)) { - FD_Table[i].valid = false; - FD_Table[i].seconds_remaining = 0; - status = true; - break; - } - } - } - return status; -} -#endif - /** - * The common send function for bvlc functions, using b/ip. - * - * @param dest - Points to a sockaddr_in structure containing the - * destination address. The length and format of the address depend - * on the address family of the socket (AF_INET). - * The address is in network byte order. - * @param mtu - the bytes of data to send - * @param mtu_len - the number of bytes of data to send - * @return Upon successful completion, returns the number of bytes sent. - * Otherwise, -1 shall be returned and errno set to indicate the error. + * @brief Count the number of valid Foreign-Device-Table entries + * @param fdt_list - first element in list of FDT entries + * @return number of elements of FDT entries that are valid */ -int bvlc_send_mpdu(struct sockaddr_in *dest, uint8_t *mtu, uint16_t mtu_len) +uint16_t bvlc_foreign_device_table_valid_count( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list) { - struct sockaddr_in bvlc_dest = { 0 }; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL; + uint16_t entry_count = 0; - /* assumes that the driver has already been initialized */ - if (bip_socket() < 0) { - return 0; - } - /* load destination IP address */ - bvlc_dest.sin_family = AF_INET; - bvlc_dest.sin_addr.s_addr = dest->sin_addr.s_addr; - bvlc_dest.sin_port = dest->sin_port; - /* Send the packet */ - return sendto(bip_socket(), (char *)mtu, mtu_len, 0, - (struct sockaddr *)&bvlc_dest, sizeof(struct sockaddr)); -} - -#if defined(BBMD_ENABLED) && BBMD_ENABLED -/** Sends all Broadcast Devices a Forwarded NPDU - * - * @param sin - source address in network order - * @param npdu - the NPDU - * @param max_npdu - amount of space available in the NPDU - * @param npdu_length - length of the NPDU - * @param original - was the message an original (not forwarded) - */ -static void bvlc_bdt_forward_npdu( - struct sockaddr_in *sin, uint8_t *npdu, uint16_t npdu_length, bool original) -{ - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - unsigned i = 0; /* loop counter */ - struct sockaddr_in bip_dest = { 0 }; - - /* If we are forwarding an original broadcast message and the NAT - * handling is enabled, change the source address to NAT routers - * global IP address so the recipient can reply (local IP address - * is not accesible from internet side). - * - * If we are forwarding a message from peer BBMD or foreign device - * or the NAT handling is disabled, leave the source address as is. - */ - if (BVLC_NAT_Handling && original) { - struct sockaddr_in nat_addr = *sin; - nat_addr.sin_addr = BVLC_Global_Address; - mtu_len = (uint16_t)bvlc_encode_forwarded_npdu( - &mtu[0], (uint16_t)sizeof(mtu), &nat_addr, npdu, npdu_length); - } else { - mtu_len = (uint16_t)bvlc_encode_forwarded_npdu( - &mtu[0], (uint16_t)sizeof(mtu), sin, npdu, npdu_length); - } - /* loop through the BDT and send one to each entry, except us */ - for (i = 0; i < MAX_BBMD_ENTRIES; i++) { - if (BBMD_Table[i].valid) { - /* The B/IP address to which the Forwarded-NPDU message is - sent is formed by inverting the broadcast distribution - mask in the BDT entry and logically ORing it with the - BBMD address of the same entry. */ - bip_dest.sin_addr.s_addr = ((~BBMD_Table[i].broadcast_mask.s_addr) | - BBMD_Table[i].dest_address.s_addr); - bip_dest.sin_port = BBMD_Table[i].dest_port; - /* don't send to my broadcast address and same port */ - if ((bip_dest.sin_addr.s_addr == bip_get_broadcast_addr()) && - (bip_dest.sin_port == bip_get_port())) { - continue; - } - /* don't send to my ip address and same port */ - if ((bip_dest.sin_addr.s_addr == bip_get_addr()) && - (bip_dest.sin_port == bip_get_port())) { - continue; - } - /* NAT router port forwards BACnet packets from global IP to us. - * Packets sent to that global IP by us would end up back, creating - * a loop. - */ - if (BVLC_NAT_Handling && - (bip_dest.sin_addr.s_addr == BVLC_Global_Address.s_addr) && - (bip_dest.sin_port == bip_get_port())) { - continue; - } - bvlc_send_mpdu(&bip_dest, mtu, mtu_len); - debug_printf("BVLC: BDT Sent Forwarded-NPDU to %s:%04X\n", - inet_ntoa(bip_dest.sin_addr), ntohs(bip_dest.sin_port)); + /* count the number of entries */ + fdt_entry = fdt_list; + while (fdt_entry) { + if (fdt_entry->valid) { + entry_count++; } + fdt_entry = fdt_entry->next; } - return; + return entry_count; } -/** Send a BVLL Forwarded-NPDU message on its local IP subnet using - * the local B/IP broadcast address as the destination address. +/** + * @brief Count the total number of Foreign-Device-Table entries * - * @param sin - source address in network order - * @param npdu - the NPDU - * @param max_npdu - amount of space available in the NPDU - * @param npdu_length - reported length of the NPDU + * @param bdt_list - first element in array BDT entries + * @return number of elements of BDT entries */ -static void bvlc_forward_npdu( - struct sockaddr_in *sin, uint8_t *npdu, uint16_t npdu_length) +uint16_t bvlc_foreign_device_table_count( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list) { - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - struct sockaddr_in bip_dest = { 0 }; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL; + uint16_t entry_count = 0; - mtu_len = (uint16_t)bvlc_encode_forwarded_npdu( - &mtu[0], (uint16_t)sizeof(mtu), sin, npdu, npdu_length); - bip_dest.sin_addr.s_addr = bip_get_broadcast_addr(); - bip_dest.sin_port = bip_get_port(); - bvlc_send_mpdu(&bip_dest, mtu, mtu_len); - debug_printf("BVLC: Sent Forwarded-NPDU as local broadcast.\n"); -} - -/** Sends all Foreign Devices a Forwarded NPDU - * - * @param sin - source address in network order - * @param npdu - returns the NPDU - * @param max_npdu - amount of space available in the NPDU - * @param npdu_length - reported length of the NPDU - * @param original - was the message an original (not forwarded) - */ -static void bvlc_fdt_forward_npdu( - struct sockaddr_in *sin, uint8_t *npdu, uint16_t npdu_length, bool original) -{ - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - unsigned i = 0; /* loop counter */ - struct sockaddr_in bip_dest = { 0 }; - - /* If we are forwarding an original broadcast message and the NAT - * handling is enabled, change the source address to NAT routers - * global IP address so the recipient can reply (local IP address - * is not accesible from internet side. - * - * If we are forwarding a message from peer BBMD or foreign device - * or the NAT handling is disabled, leave the source address as is. - */ - if (BVLC_NAT_Handling && original) { - struct sockaddr_in nat_addr = *sin; - nat_addr.sin_addr = BVLC_Global_Address; - mtu_len = (uint16_t)bvlc_encode_forwarded_npdu( - &mtu[0], (uint16_t)sizeof(mtu), &nat_addr, npdu, npdu_length); - } else { - mtu_len = (uint16_t)bvlc_encode_forwarded_npdu( - &mtu[0], (uint16_t)sizeof(mtu), sin, npdu, npdu_length); + /* count the number of entries */ + fdt_entry = fdt_list; + while (fdt_entry) { + entry_count++; + fdt_entry = fdt_entry->next; } - /* loop through the FDT and send one to each entry */ - for (i = 0; i < MAX_FD_ENTRIES; i++) { - if (FD_Table[i].valid && FD_Table[i].seconds_remaining) { - bip_dest.sin_addr.s_addr = FD_Table[i].dest_address.s_addr; - bip_dest.sin_port = FD_Table[i].dest_port; - /* don't send to my ip address and same port */ - if ((bip_dest.sin_addr.s_addr == bip_get_addr()) && - (bip_dest.sin_port == bip_get_port())) { - continue; - } - /* don't send to src ip address and same port */ - if ((bip_dest.sin_addr.s_addr == sin->sin_addr.s_addr) && - (bip_dest.sin_port == sin->sin_port)) { - continue; - } - /* NAT router port forwards BACnet packets from global IP to us. - * Packets sent to that global IP by us would end up back, creating - * a loop. - */ - if (BVLC_NAT_Handling && - (bip_dest.sin_addr.s_addr == BVLC_Global_Address.s_addr) && - (bip_dest.sin_port == bip_get_port())) { - continue; - } - bvlc_send_mpdu(&bip_dest, mtu, mtu_len); - debug_printf("BVLC: FDT Sent Forwarded-NPDU to %s:%04X\n", - inet_ntoa(bip_dest.sin_addr), ntohs(bip_dest.sin_port)); + return entry_count; +} + +/** + * @brief Convert Foreign-Device-Table entry array into linked list. + * + * @param fdt_list - first element in array FDT entries + * @param array_size - number of array elements of FDT entries + */ +void bvlc_foreign_device_table_link_array( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, const size_t array_size) +{ + size_t i = 0; + + for (i = 0; i < array_size; i++) { + if (i > 0) { + fdt_list[i - 1].next = &fdt_list[i]; } + fdt_list[i].next = NULL; } - - return; -} -#endif - -/** Sends a BVLC Result - * - * @param dest - destination address - * @param result_code - result code to send - * - * @return number of bytes encoded to send - */ -static int bvlc_send_result( - struct sockaddr_in *dest, /* the destination address */ - BACNET_BVLC_RESULT result_code) -{ - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - - mtu_len = (uint16_t)bvlc_encode_bvlc_result(&mtu[0], result_code); - if (mtu_len) { - bvlc_send_mpdu(dest, mtu, mtu_len); - } - - return mtu_len; } -#if defined(BBMD_ENABLED) && BBMD_ENABLED -/** Sends a Read Broadcast Device Table ACK +/** + * @brief J.2.8 Read-Foreign-Device-Table-Ack: encode * - * @param dest - destination address + * This message returns the current contents of a BBMD's FDT to the requester. + * An empty FDT shall be signified by a list of length zero. * - * @return number of bytes encoded to send + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param fdt_list - list of FDT entries + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'07' Read-Foreign-Device-Table-Ack + * BVLC Length: 2-octets L length, in octets, of the BVLL message + * List of FDT Entries: N*10-octets + * + * N indicates the number of entries in the FDT whose contents are being + * returned. Each returned entry consists of the 6-octet B/IP address of + * the registrant; the 2-octet Time-to-Live value supplied at the time of + * registration; and a 2-octet value representing the number of seconds + * remaining before the BBMD will purge the registrant's FDT entry if no + * re-registration occurs. The time remaining includes the 30-second grace + * period as defined in Clause J.5.2.3. */ -static int bvlc_send_bdt(struct sockaddr_in *dest) -{ - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - - mtu_len = (uint16_t)bvlc_encode_read_bdt_ack(&mtu[0], sizeof(mtu)); - if (mtu_len) { - bvlc_send_mpdu(dest, &mtu[0], mtu_len); - } - - return mtu_len; -} - -/** Sends a Read Foreign Device Table ACK - * - * @param dest - destination address - * - * @return number of bytes encoded to send - */ -static int bvlc_send_fdt(struct sockaddr_in *dest) -{ - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - - mtu_len = (uint16_t)bvlc_encode_read_fdt_ack(&mtu[0], sizeof(mtu)); - if (mtu_len) { - bvlc_send_mpdu(dest, &mtu[0], mtu_len); - } - - return mtu_len; -} - -/** Determines if a BDT member has a unicast mask - * - * @param sin - BDT member that is sought, network byte order address - * - * @return True if BDT member is found and has a unicast mask - */ -static bool bvlc_bdt_member_mask_is_unicast(struct sockaddr_in *sin) -{ - bool unicast = false; - unsigned i = 0; /* loop counter */ - - for (i = 0; i < MAX_BBMD_ENTRIES; i++) { - if (BBMD_Table[i].valid) { - /* Skip ourself*/ - if ((BBMD_Table[i].dest_address.s_addr == bip_get_addr()) && - (BBMD_Table[i].dest_port == bip_get_port())) { - continue; - } - - /* find the source address in the table */ - if ((BBMD_Table[i].dest_address.s_addr == sin->sin_addr.s_addr) && - (BBMD_Table[i].dest_port == sin->sin_port)) { - /* unicast mask? */ - if (BBMD_Table[i].broadcast_mask.s_addr == 0xFFFFFFFFL) { - unicast = true; - break; - } - } - } - } - - return unicast; -} - -/** Receive a packet from the BACnet/IP socket (Annex J) - * - * @param src - returns the source address - * @param npdu - returns the NPDU - * @param max_npdu - amount of space available in the NPDU - * @param timeout - number of milliseconds to wait for a packet - * - * @return Number of bytes received, or 0 if none or timeout. - */ -uint16_t bvlc_receive( - BACNET_ADDRESS *src, uint8_t *npdu, uint16_t max_npdu, unsigned timeout) -{ - uint16_t npdu_len = 0; /* return value */ - fd_set read_fds; - int max = 0; - struct timeval select_timeout; - struct sockaddr_in sin = { 0 }; - struct sockaddr_in original_sin = { 0 }; - struct sockaddr_in dest = { 0 }; - socklen_t sin_len = sizeof(sin); - int received_bytes = 0; - uint16_t result_code = 0; - uint16_t i = 0; - bool status = false; - uint16_t time_to_live = 0; - - /* Make sure the socket is open */ - if (bip_socket() < 0) { - return 0; - } - - /* we could just use a non-blocking socket, but that consumes all - the CPU time. We can use a timeout; it is only supported as - a select. */ - if (timeout >= 1000) { - select_timeout.tv_sec = timeout / 1000; - select_timeout.tv_usec = - 1000 * (timeout - select_timeout.tv_sec * 1000); - } else { - select_timeout.tv_sec = 0; - select_timeout.tv_usec = 1000 * timeout; - } - FD_ZERO(&read_fds); - FD_SET(bip_socket(), &read_fds); - max = bip_socket(); - /* see if there is a packet for us */ - if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { - received_bytes = recvfrom(bip_socket(), (char *)&npdu[0], max_npdu, 0, - (struct sockaddr *)&sin, &sin_len); - } else { - return 0; - } - /* See if there is a problem */ - if (received_bytes < 0) { - return 0; - } - /* no problem, just no bytes */ - if (received_bytes == 0) { - return 0; - } - /* the signature of a BACnet/IP packet */ - if (npdu[0] != BVLL_TYPE_BACNET_IP) { - return 0; - } - BVLC_Function_Code = npdu[1]; - /* decode the length of the PDU - length is inclusive of BVLC */ - (void)decode_unsigned16(&npdu[2], &npdu_len); - if ((npdu_len < 4) || (npdu_len > (max_npdu - 4))) { - return 0; - } - /* subtract off the BVLC header */ - npdu_len -= 4; - switch (BVLC_Function_Code) { - case BVLC_RESULT: - /* Upon receipt of a BVLC-Result message containing a result code - of X'0000' indicating the successful completion of the - registration, a foreign device shall start a timer with a value - equal to the Time-to-Live parameter of the preceding Register- - Foreign-Device message. At the expiration of the timer, the - foreign device shall re-register with the BBMD by sending a BVLL - Register-Foreign-Device message */ - /* Clients can now get this result */ - (void)decode_unsigned16(&npdu[4], &result_code); - BVLC_Result_Code = (BACNET_BVLC_RESULT)result_code; - debug_printf("BVLC: Result Code=%d\n", BVLC_Result_Code); - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE: - debug_printf("BVLC: Received Write-BDT.\n"); - /* Upon receipt of a BVLL Write-Broadcast-Distribution-Table - message, a BBMD shall attempt to create or replace its BDT, - depending on whether or not a BDT has previously existed. - If the creation or replacement of the BDT is successful, the BBMD - shall return a BVLC-Result message to the originating device with - a result code of X'0000'. Otherwise, the BBMD shall return a - BVLC-Result message to the originating device with a result code - of X'0010' indicating that the write attempt has failed. */ - status = bvlc_create_bdt(&npdu[4], npdu_len); - if (status) { - bvlc_send_result(&sin, BVLC_RESULT_SUCCESSFUL_COMPLETION); - } else { - bvlc_send_result( - &sin, BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK); - } - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_READ_BROADCAST_DIST_TABLE: - debug_printf("BVLC: Received Read-BDT.\n"); - /* Upon receipt of a BVLL Read-Broadcast-Distribution-Table - message, a BBMD shall load the contents of its BDT into a BVLL - Read-Broadcast-Distribution-Table-Ack message and send it to the - originating device. If the BBMD is unable to perform the - read of its BDT, it shall return a BVLC-Result message to the - originating device with a result code of X'0020' indicating that - the read attempt has failed. */ - if (bvlc_send_bdt(&sin) <= 0) { - bvlc_send_result( - &sin, BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK); - } - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_READ_BROADCAST_DIST_TABLE_ACK: - debug_printf("BVLC: Received Read-BDT-Ack.\n"); - /* FIXME: complete the code for client side read */ - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_FORWARDED_NPDU: - /* Upon receipt of a BVLL Forwarded-NPDU message, a BBMD shall - process it according to whether it was received from a peer - BBMD as the result of a directed broadcast or a unicast - transmission. A BBMD may ascertain the method by which Forwarded- - NPDU messages will arrive by inspecting the broadcast - distribution mask field in its own BDT entry since all BDTs are - required to be identical. If the message arrived via directed - broadcast, it was also received by the other devices on the - BBMD's subnet. In this case the BBMD merely retransmits the - message directly to each foreign device currently in the BBMD's - FDT. If the message arrived via a unicast transmission it has not - yet been received by the other devices on the BBMD's subnet. In - this case, the message is sent to the devices on the BBMD's - subnet using the B/IP broadcast address as well as to each - foreign device currently in the BBMD's FDT. A BBMD on a subnet - with no other BACnet devices may omit the broadcast using the - B/IP broadcast address. The method by which a BBMD determines - whether or not other BACnet devices are present is a local - matter. */ - if (npdu_len < 6) { - return 0; - } - /* decode the 4 byte original address and 2 byte port */ - bvlc_decode_bip_address( - &npdu[4], &original_sin.sin_addr, &original_sin.sin_port); - debug_printf("BVLC: Received Forwarded-NPDU from %s:%04X.\n", - inet_ntoa(original_sin.sin_addr), ntohs(original_sin.sin_port)); - npdu_len -= 6; - /* Broadcast locally if received via unicast from a BDT member */ - if (bvlc_bdt_member_mask_is_unicast(&sin)) { - dest.sin_addr.s_addr = bip_get_broadcast_addr(); - dest.sin_port = bip_get_port(); - debug_printf( - "BVLC: Received unicast from BDT member, re-broadcasting " - "locally to %s:%04X.\n", - inet_ntoa(dest.sin_addr), ntohs(dest.sin_port)); - bvlc_send_mpdu(&dest, &npdu[0], npdu_len + 4 + 6); - } - /* use the original addr from the BVLC for src */ - dest.sin_addr.s_addr = original_sin.sin_addr.s_addr; - dest.sin_port = original_sin.sin_port; - bvlc_fdt_forward_npdu(&dest, &npdu[4 + 6], npdu_len, false); - debug_printf("BVLC: Received Forwarded-NPDU from %s:%04X.\n", - inet_ntoa(dest.sin_addr), ntohs(dest.sin_port)); - bvlc_internet_to_bacnet_address(src, &dest); - if (npdu_len < max_npdu) { - /* shift the buffer to return a valid NPDU */ - for (i = 0; i < npdu_len; i++) { - npdu[i] = npdu[4 + 6 + i]; - } - } else { - /* ignore packets that are too large */ - /* clients should check my max-apdu first */ - npdu_len = 0; - } - break; - case BVLC_REGISTER_FOREIGN_DEVICE: - /* Upon receipt of a BVLL Register-Foreign-Device message, a BBMD - shall start a timer with a value equal to the Time-to-Live - parameter supplied plus a fixed grace period of 30 seconds. If, - within the period during which the timer is active, another BVLL - Register-Foreign-Device message from the same device is received, - the timer shall be reset and restarted. If the time expires - without the receipt of another BVLL Register-Foreign-Device - message from the same foreign device, the FDT entry for this - device shall be cleared. */ - if (npdu_len < 2) { - return 0; - } - (void)decode_unsigned16(&npdu[4], &time_to_live); - if (bvlc_register_foreign_device(&sin, time_to_live)) { - bvlc_send_result(&sin, BVLC_RESULT_SUCCESSFUL_COMPLETION); - debug_printf("BVLC: Registered a Foreign Device.\n"); - } else { - bvlc_send_result(&sin, BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK); - debug_printf("BVLC: Failed to Register a Foreign Device.\n"); - } - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_READ_FOREIGN_DEVICE_TABLE: - debug_printf("BVLC: Received Read-FDT.\n"); - /* Upon receipt of a BVLL Read-Foreign-Device-Table message, a - BBMD shall load the contents of its FDT into a BVLL Read- - Foreign-Device-Table-Ack message and send it to the originating - device. If the BBMD is unable to perform the read of its FDT, - it shall return a BVLC-Result message to the originating device - with a result code of X'0040' indicating that the read attempt - has failed. */ - if (bvlc_send_fdt(&sin) <= 0) { - bvlc_send_result( - &sin, BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK); - } - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_READ_FOREIGN_DEVICE_TABLE_ACK: - debug_printf("BVLC: Received Read-FDT-Ack.\n"); - /* FIXME: complete the code for client side read */ - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY: - debug_printf("BVLC: Received Delete-FDT-Entry.\n"); - /* Upon receipt of a BVLL Delete-Foreign-Device-Table-Entry - message, a BBMD shall search its foreign device table for an - entry corresponding to the B/IP address supplied in the message. - If an entry is found, it shall be deleted and the BBMD shall - return a BVLC-Result message to the originating device with a - result code of X'0000'. Otherwise, the BBMD shall return a - BVLCResult message to the originating device with a result code - of X'0050' indicating that the deletion attempt has failed. */ - if (bvlc_delete_foreign_device(&npdu[4], npdu_len)) { - bvlc_send_result(&sin, BVLC_RESULT_SUCCESSFUL_COMPLETION); - } else { - bvlc_send_result( - &sin, BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK); - } - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK: - debug_printf("BVLC: Received Distribute-Broadcast-to-Network from " - "%s:%04X.\n", - inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); - /* Upon receipt of a BVLL Distribute-Broadcast-To-Network message - from a foreign device, the receiving BBMD shall transmit a - BVLL Forwarded-NPDU message on its local IP subnet using the - local B/IP broadcast address as the destination address. In - addition, a Forwarded-NPDU message shall be sent to each entry - in its BDT as described in the case of the receipt of a - BVLL Original-Broadcast-NPDU as well as directly to each foreign - device currently in the BBMD's FDT except the originating - node. If the BBMD is unable to perform the forwarding function, - it shall return a BVLC-Result message to the foreign device - with a result code of X'0060' indicating that the forwarding - attempt was unsuccessful */ - bvlc_forward_npdu(&sin, &npdu[4], npdu_len); - bvlc_bdt_forward_npdu(&sin, &npdu[4], npdu_len, false); - bvlc_fdt_forward_npdu(&sin, &npdu[4], npdu_len, false); - /* not an NPDU */ - npdu_len = 0; - break; - case BVLC_ORIGINAL_UNICAST_NPDU: - debug_printf("BVLC: Received Original-Unicast-NPDU.\n"); - if ((sin.sin_addr.s_addr == bip_get_addr()) && - (sin.sin_port == bip_get_port())) { - /* ignore messages from me */ - npdu_len = 0; - } else if (BVLC_NAT_Handling && - (sin.sin_addr.s_addr == BVLC_Global_Address.s_addr) && - (sin.sin_port == bip_get_port())) { - /* If the BBMD is behind a NAT router, the router forwards - packets from global IP and BACnet port to us. */ - npdu_len = 0; - } else { - bvlc_internet_to_bacnet_address(src, &sin); - if (npdu_len < max_npdu) { - /* shift the buffer to return a valid PDU */ - for (i = 0; i < npdu_len; i++) { - npdu[i] = npdu[4 + i]; - } - } else { - /* ignore packets that are too large */ - /* clients should check my max-apdu first */ - npdu_len = 0; - } - } - break; - case BVLC_ORIGINAL_BROADCAST_NPDU: - debug_printf("BVLC: Received Original-Broadcast-NPDU.\n"); - /* Upon receipt of a BVLL Original-Broadcast-NPDU message, - a BBMD shall construct a BVLL Forwarded-NPDU message and - send it to each IP subnet in its BDT with the exception - of its own. The B/IP address to which the Forwarded-NPDU - message is sent is formed by inverting the broadcast - distribution mask in the BDT entry and logically ORing it - with the BBMD address of the same entry. This process - produces either the directed broadcast address of the remote - subnet or the unicast address of the BBMD on that subnet - depending on the contents of the broadcast distribution - mask. See J.4.3.2.. In addition, the received BACnet NPDU - shall be sent directly to each foreign device currently in - the BBMD's FDT also using the BVLL Forwarded-NPDU message. */ - bvlc_internet_to_bacnet_address(src, &sin); - if (npdu_len < max_npdu) { - /* shift the buffer to return a valid PDU */ - for (i = 0; i < npdu_len; i++) { - npdu[i] = npdu[4 + i]; - } - /* if BDT or FDT entries exist, Forward the NPDU */ - bvlc_bdt_forward_npdu(&sin, &npdu[0], npdu_len, true); - bvlc_fdt_forward_npdu(&sin, &npdu[0], npdu_len, true); - } else { - /* ignore packets that are too large */ - npdu_len = 0; - } - break; - default: - break; - } - - return npdu_len; -} - -/** Send a packet out the BACnet/IP socket (Annex J) - * - * @param dest - destination address - * @param npdu_data - network information - * @param pdu - any data to be sent - may be null - * @param pdu_len - number of bytes of data - * - * @return returns number of bytes sent on success, negative number on failure - */ -int bvlc_send_pdu(BACNET_ADDRESS *dest, - BACNET_NPDU_DATA *npdu_data, - uint8_t *pdu, - unsigned pdu_len) -{ - struct sockaddr_in bvlc_dest = { 0 }; - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - /* addr and port in network format */ - struct in_addr address; - uint16_t port = 0; - uint16_t BVLC_length = 0; - - /* bip datalink doesn't need to know the npdu data */ - (void)npdu_data; - mtu[0] = BVLL_TYPE_BACNET_IP; - /* handle various broadcasts: */ - /* mac_len = 0 is a broadcast address */ - /* net = 0 indicates local, net = 65535 indicates global */ - if ((dest->net == BACNET_BROADCAST_NETWORK) || (dest->mac_len == 0)) { - /* if we are a foreign device */ - if (Remote_BBMD.sin_port) { - mtu[1] = BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK; - address.s_addr = Remote_BBMD.sin_addr.s_addr; - port = Remote_BBMD.sin_port; - debug_printf("BVLC: Sent Distribute-Broadcast-to-Network.\n"); - } else { - address.s_addr = bip_get_broadcast_addr(); - port = bip_get_port(); - mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; - debug_printf("BVLC: Sent Original-Broadcast-NPDU.\n"); - } - } else if ((dest->net > 0) && (dest->len == 0)) { - /* net > 0 and net < 65535 are network specific broadcast if len = 0 */ - if (dest->mac_len == 6) { - /* network specific broadcast */ - bvlc_decode_bip_address(&dest->mac[0], &address, &port); - } else { - address.s_addr = bip_get_broadcast_addr(); - port = bip_get_port(); - } - mtu[1] = BVLC_ORIGINAL_BROADCAST_NPDU; - debug_printf("BVLC: Sent Original-Broadcast-NPDU.\n"); - } else if (dest->mac_len == 6) { - /* valid unicast */ - bvlc_decode_bip_address(&dest->mac[0], &address, &port); - mtu[1] = BVLC_ORIGINAL_UNICAST_NPDU; - debug_printf("BVLC: Sent Original-Unicast-NPDU.\n"); - } else { - /* invalid address */ - return -1; - } - bvlc_dest.sin_addr.s_addr = address.s_addr; - bvlc_dest.sin_port = port; - BVLC_length = (uint16_t)pdu_len + 4 /*inclusive */; - mtu_len = 2; - mtu_len += (uint16_t)encode_unsigned16(&mtu[mtu_len], BVLC_length); - memcpy(&mtu[mtu_len], pdu, pdu_len); - mtu_len += (uint16_t)pdu_len; - return bvlc_send_mpdu(&bvlc_dest, mtu, mtu_len); -} -#endif - -/*********************************************** - * Functions to register us as a foreign device. - ********************************************* */ - -/** Encode foreign device registration message - * - * @param pdu - bytes for encoding the message - * in network byte order. - * @param time_to_live_seconds - Lease time to use when registering. - * @return Number of bytes encoded) on success, - * or 0 if no encoding occurred. - */ -static int bvlc_encode_register_foreign_device( - uint8_t *pdu, uint16_t time_to_live_seconds) +int bvlc_encode_read_foreign_device_table_ack(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list) { + int bytes_encoded = 0; int len = 0; + uint16_t offset = 0; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL; + uint16_t entry_count = 0; + uint16_t length = 0; + + /* count the number of entries */ + entry_count = bvlc_foreign_device_table_valid_count(fdt_list); + length = 4 + (entry_count * BACNET_IP_FDT_ENTRY_SIZE); + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_READ_FOREIGN_DEVICE_TABLE_ACK, length); + if (bytes_encoded == 4) { + offset = 4; + /* encode the entries */ + fdt_entry = fdt_list; + while (fdt_entry) { + if (fdt_entry->valid) { + len = bvlc_encode_foreign_device_table_entry( + &pdu[offset], pdu_size - offset, fdt_entry); + offset += len; + } + fdt_entry = fdt_entry->next; + } + bytes_encoded = (int)length; + } + } + + return bytes_encoded; +} + +/** + * @brief Decode Read-Foreign-Device-Table-Ack + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param fdt_list - list of FDT entries + * + * @return number of bytes decoded + */ +int bvlc_decode_read_foreign_device_table_ack(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list) +{ + int bytes_consumed = 0; + int len = 0; + uint16_t offset = 0; + uint16_t pdu_bytes = 0; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry = NULL; + + if (pdu && (pdu_len >= BACNET_IP_FDT_ENTRY_SIZE)) { + fdt_entry = fdt_list; + while (fdt_entry) { + pdu_bytes = pdu_len - offset; + if (pdu_bytes >= BACNET_IP_BDT_ENTRY_SIZE) { + len = bvlc_decode_foreign_device_table_entry( + &pdu[offset], pdu_bytes, fdt_entry); + if (len > 0) { + fdt_entry->valid = true; + } + offset += len; + } else { + fdt_entry->valid = false; + } + fdt_entry = fdt_entry->next; + } + bytes_consumed = (int)offset; + } + + return bytes_consumed; +} + +/** + * @brief J.2.9 Delete-Foreign-Device-Table-Entry: encode + * + * This message is used to delete an entry from the Foreign-Device-Table. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param ip_address - FDT Entry IP address + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'08' Delete-Foreign-Device + * BVLC Length: 2-octets X'000A' Length of the BVLL message + * FDT Entry: 6-octets The FDT entry is the B/IP address + * of the table entry to be deleted. + */ +int bvlc_encode_delete_foreign_device( + uint8_t *pdu, uint16_t pdu_size, BACNET_IP_ADDRESS *ip_address) +{ + int bytes_encoded = 0; + const uint16_t length = 0x000A; + uint16_t offset = 0; + + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY, length); + if (bytes_encoded == 4) { + offset = 4; + if (ip_address) { + bytes_encoded += bvlc_encode_address( + &pdu[offset], pdu_size - offset, ip_address); + } + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Delete-Foreign-Device message + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param ip_address - FDT Entry IP address + * + * @return number of bytes decoded + */ +int bvlc_decode_delete_foreign_device( + uint8_t *pdu, uint16_t pdu_len, BACNET_IP_ADDRESS *ip_address) +{ + int bytes_consumed = 0; + const uint16_t length = BIP_ADDRESS_MAX; + + if (pdu && (pdu_len >= length)) { + if (ip_address) { + bvlc_decode_address(&pdu[0], pdu_len, ip_address); + } + bytes_consumed = (int)length; + } + + return bytes_consumed; +} + +/** + * @brief J.2.10 Distribute-Broadcast-To-Network: encode + * + * This message provides a mechanism whereby a foreign device may cause + * a BBMD to broadcast a message on all IP subnets in the BBMD's BDT. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param npdu - BACnet NPDU from Originating Device buffer + * @param npdu_len - size of the BACnet NPDU buffer + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'09' Original-Unicast-NPDU + * BVLC Length: 2-octets L Length of the BVLL message + * BACnet NPDU from Originating Device: Variable length + */ +int bvlc_encode_distribute_broadcast_to_network( + uint8_t *pdu, uint16_t pdu_size, uint8_t *npdu, uint16_t npdu_len) +{ + int bytes_encoded = 0; + uint16_t length = 1 + 1 + 2; + uint16_t i = 0; + + length += npdu_len; + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK, length); + if (bytes_encoded == 4) { + if (npdu && (npdu_len > 0)) { + for (i = 0; i < npdu_len; i++) { + pdu[4 + i] = npdu[i]; + } + } + bytes_encoded = (int)length; + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Original-Broadcast-NPDU message + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param npdu - buffer to copy the decoded BACnet NDPU + * @param npdu_size - size of the buffer for the decoded BACnet NPDU + * @param npdu_len - decoded length of the BACnet NPDU + * + * @return number of bytes decoded + */ +int bvlc_decode_distribute_broadcast_to_network(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *npdu, + uint16_t npdu_size, + uint16_t *npdu_len) +{ + int bytes_consumed = 0; + uint16_t i = 0; if (pdu) { - pdu[0] = BVLL_TYPE_BACNET_IP; - pdu[1] = BVLC_REGISTER_FOREIGN_DEVICE; - /* The 2-octet BVLC Length field is the length, in octets, - of the entire BVLL message, including the two octets of the - length field itself, most significant octet first. */ - encode_unsigned16(&pdu[2], 6); - encode_unsigned16(&pdu[4], time_to_live_seconds); - len = 6; - } - - return len; -} - -/** Register as a foreign device with the indicated BBMD. - * @param bbmd_address - IPv4 address (long) of BBMD to register with, - * in network byte order. - * @param bbmd_port - Network port of BBMD, in network byte order - * @param time_to_live_seconds - Lease time to use when registering. - * @return Positive number (of bytes sent) on success, - * 0 if no registration request is sent, or - * -1 if registration fails. - */ -int bvlc_register_with_bbmd( - uint32_t bbmd_address, uint16_t bbmd_port, uint16_t time_to_live_seconds) -{ - uint8_t mtu[MAX_MPDU] = { 0 }; - uint16_t mtu_len = 0; - int retval = 0; - - /* Store the BBMD address and port so that we - won't broadcast locally. */ - Remote_BBMD.sin_addr.s_addr = bbmd_address; - Remote_BBMD.sin_port = bbmd_port; - /* In order for their broadcasts to get here, - we need to register our address with the remote BBMD using - Write Broadcast Distribution Table, or - register with the BBMD as a Foreign Device */ - mtu_len = (uint16_t)bvlc_encode_register_foreign_device( - &mtu[0], time_to_live_seconds); - retval = bvlc_send_mpdu(&Remote_BBMD, &mtu[0], mtu_len); - return retval; -} - -/** Note any BVLC_RESULT code, or NAK the BVLL message in the unsupported cases. - * Use this handler when you are not a BBMD. - * Sets the BVLC_Function_Code in case it is needed later. - * - * @param sout [in] Socket address to send any NAK back to. - * @param npdu [in] The received buffer. - * @param received_bytes [in] How many bytes in npdu[]. - * @return Non-zero BVLC_RESULT_ code if we sent a response (NAK) to this - * BVLC message. If zero, may need further processing. - */ -int bvlc_for_non_bbmd( - struct sockaddr_in *sout, uint8_t *npdu, uint16_t received_bytes) -{ - uint16_t result_code = 0; /* aka, BVLC_RESULT_SUCCESSFUL_COMPLETION */ - - BVLC_Function_Code = npdu[1]; /* The BVLC function */ - switch (BVLC_Function_Code) { - case BVLC_RESULT: - if (received_bytes >= 6) { - /* This is the result of our foreign device registration */ - (void)decode_unsigned16(&npdu[4], &result_code); - BVLC_Result_Code = (BACNET_BVLC_RESULT)result_code; - debug_printf("BVLC: Result Code=%d\n", BVLC_Result_Code); - /* But don't send any response */ - result_code = 0; + if ((pdu_len > 0) && (pdu_len <= npdu_size)) { + if (npdu) { + for (i = 0; i < pdu_len; i++) { + npdu[i] = pdu[i]; + } } - break; - case BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE: - result_code = BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK; - break; - case BVLC_READ_BROADCAST_DIST_TABLE: - result_code = BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK; - break; - /* case BVLC_READ_BROADCAST_DIST_TABLE_ACK: */ - case BVLC_REGISTER_FOREIGN_DEVICE: - result_code = BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK; - break; - case BVLC_READ_FOREIGN_DEVICE_TABLE: - result_code = BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK; - break; - /* case BVLC_READ_FOREIGN_DEVICE_TABLE_ACK: */ - case BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY: - result_code = BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK; - break; - case BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK: - result_code = BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK; - break; - /* case BVLC_FORWARDED_NPDU: */ - /* case BVLC_ORIGINAL_UNICAST_NPDU: */ - /* case BVLC_ORIGINAL_BROADCAST_NPDU: */ - default: - break; - } - if (result_code > 0) { - bvlc_send_result(sout, result_code); - debug_printf("BVLC: NAK code=%d\n", result_code); + } + if (npdu_len) { + *npdu_len = pdu_len; + } + bytes_consumed = (int)pdu_len; } - return result_code; + return bytes_consumed; } -/** Returns the last BVLL Result we received, either as the result of a BBMD - * request we sent, or (if not a BBMD or Client), from trying to register - * as a foreign device. +/** + * @brief J.2.11 Original-Unicast-NPDU: Encode * - * @return BVLC_RESULT_SUCCESSFUL_COMPLETION on success, - * BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK if registration failed, - * or one of the other codes (if we are a BBMD). + * This message is used to send directed NPDUs to another + * B/IP device or router. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param npdu - BACnet NPDU buffer + * @param npdu_len - size of the BACnet NPDU buffer + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IPv4 + * BVLC Function: 1-octet X'0A' Original-Unicast-NPDU + * BVLC Length: 2-octets L Length L, in octets, of the BVLL message + * BACnet NPDU: Variable length */ -BACNET_BVLC_RESULT bvlc_get_last_result(void) +int bvlc_encode_original_unicast( + uint8_t *pdu, uint16_t pdu_size, uint8_t *npdu, uint16_t npdu_len) { - return BVLC_Result_Code; -} + int bytes_encoded = 0; + uint16_t length = 4; + uint16_t i = 0; -/** Returns the current BVLL Function Code we are processing. - * We have to store this higher layer code for when the lower layers - * need to know what it is, especially to differentiate between - * BVLC_ORIGINAL_UNICAST_NPDU and BVLC_ORIGINAL_BROADCAST_NPDU. - * - * @return A BVLC_ code, such as BVLC_ORIGINAL_UNICAST_NPDU. - */ -BACNET_BVLC_FUNCTION bvlc_get_function_code(void) -{ - return BVLC_Function_Code; -} - -/** Get handle to broadcast distribution table (BDT). - * - * Do not modify the table using the returned pointer, - * use the dedicated functions instead. - * (For optimization the table is not copied to caller) - * - * @param table [out] - broadcast distribution table - * - * @return Number of valid entries in the table or -1 on error. - */ -int bvlc_get_bdt_local(const BBMD_TABLE_ENTRY **table) -{ - int count = 0; - - if (table == NULL) { - return -1; - } - - *table = BBMD_Table; - - for (count = 0; count < MAX_BBMD_ENTRIES; ++count) { - if (!BBMD_Table[count].valid) { - break; + length += npdu_len; + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_ORIGINAL_UNICAST_NPDU, length); + if (bytes_encoded == 4) { + if (npdu && (npdu_len > 0)) { + for (i = 0; i < npdu_len; i++) { + pdu[4 + i] = npdu[i]; + } + bytes_encoded = (int)length; + } } } - return count; + return bytes_encoded; } -/** Invalidate all entries in the broadcast distribution table (BDT). - */ -void bvlc_clear_bdt_local(void) -{ - int i = 0; - for (i = 0; i < MAX_BBMD_ENTRIES; ++i) { - BBMD_Table[i].valid = false; - BBMD_Table[i].dest_address.s_addr = 0; - BBMD_Table[i].dest_port = 0; - BBMD_Table[i].broadcast_mask.s_addr = 0; - } - /* BDT changed! Save backup to file */ - bvlc_bdt_backup_local(); -} - -/** Add new entry to broadcast distribution table. +/** + * @brief Decode the BVLC Original-Unicast-NPDU message, after decoding header * - * @return True if the new entry was added successfully. + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param npdu - BACnet NPDU buffer + * @param npdu_size - size of the buffer for the decoded BACnet NPDU + * @param npdu_len - decoded length of the BACnet NPDU buffer + * + * @return number of bytes decoded */ -bool bvlc_add_bdt_entry_local(BBMD_TABLE_ENTRY *entry) +int bvlc_decode_original_unicast(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *npdu, + uint16_t npdu_size, + uint16_t *npdu_len) { - bool found = false; - int i = 0; + int bytes_consumed = 0; + uint16_t i = 0; - if (entry == NULL) { + if (pdu_len >= npdu_size) { + if (pdu && npdu) { + for (i = 0; i < pdu_len; i++) { + npdu[i] = pdu[i]; + } + } + if (npdu_len) { + *npdu_len = pdu_len; + } + bytes_consumed = (int)pdu_len; + } + + return bytes_consumed; +} + +/** + * @brief J.2.12 Original-Broadcast-NPDU: Encode + * + * This message is used by B/IP devices and routers which are + * not foreign devices to broadcast NPDUs on a B/IP network + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param npdu - BACnet NPDU buffer + * @param npdu_len - size of the BACnet NPDU buffer + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IPv4 + * BVLC Function: 1-octet X'0B' Original-Broadcast-NPDU + * BVLC Length: 2-octets L Length of the BVLL message + * BACnet NPDU: Variable length + */ +int bvlc_encode_original_broadcast( + uint8_t *pdu, uint16_t pdu_size, uint8_t *npdu, uint16_t npdu_len) +{ + int bytes_encoded = 0; + uint16_t length = 4; + uint16_t i = 0; + + length += npdu_len; + if (pdu && (pdu_size >= length)) { + bytes_encoded = bvlc_encode_header( + pdu, pdu_size, BVLC_ORIGINAL_BROADCAST_NPDU, length); + if (bytes_encoded == 4) { + if (npdu && (npdu_len > 0)) { + for (i = 0; i < npdu_len; i++) { + pdu[4 + i] = npdu[i]; + } + bytes_encoded = (int)length; + } + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Original-Broadcast-NPDU message + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param npdu - buffer to copy the decoded BACnet NDPU + * @param npdu_size - size of the buffer for the decoded BACnet NPDU + * @param npdu_len - decoded length of the BACnet NPDU + * + * @return number of bytes decoded + */ +int bvlc_decode_original_broadcast(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *npdu, + uint16_t npdu_size, + uint16_t *npdu_len) +{ + int bytes_consumed = 0; + uint16_t i = 0; + + if (pdu_len >= npdu_size) { + if (pdu && npdu) { + for (i = 0; i < pdu_len; i++) { + npdu[i] = pdu[i]; + } + } + if (npdu_len) { + *npdu_len = pdu_len; + } + bytes_consumed = (int)pdu_len; + } + + return bytes_consumed; +} + +/** + * @brief J.2.13 Secure-BVLL: encode + * + * This message is used to secure BVLL messages that do not contain NPDUs. + * Its use is described in Clause 24. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param sbuf - Security Wrapper buffer + * @param sbuf_len - size of the Security Wrapper buffer + * + * @return number of bytes encoded + * + * BVLC Type: 1-octet X'81' BVLL for BACnet/IP + * BVLC Function: 1-octet X'0C' Secure-BVLL + * BVLC Length: 2-octets L Length of the BVLL message + * Security Wrapper: Variable length + */ +int bvlc_encode_secure_bvll( + uint8_t *pdu, uint16_t pdu_size, uint8_t *sbuf, uint16_t sbuf_len) +{ + int bytes_encoded = 0; + uint16_t length = 1 + 1 + 2; + uint16_t i = 0; + + length += sbuf_len; + if (pdu && (pdu_size >= length)) { + bytes_encoded = + bvlc_encode_header(pdu, pdu_size, BVLC_SECURE_BVLL, length); + if (bytes_encoded == 4) { + if (sbuf && sbuf_len) { + for (i = 0; i < sbuf_len; i++) { + pdu[4 + i] = sbuf[i]; + } + } + bytes_encoded = (int)length; + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Secure-BVLL message + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param sbuf - Security Wrapper buffer + * @param sbuf_size - size of the Security Wrapper buffer + * @param sbuf_len - number of bytes decoded into the Security Wrapper buffer + * + * @return number of bytes decoded + */ +int bvlc_decode_secure_bvll(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *sbuf, + uint16_t sbuf_size, + uint16_t *sbuf_len) +{ + int bytes_consumed = 0; + uint16_t i = 0; + + if (pdu) { + if (sbuf_len) { + *sbuf_len = pdu_len; + } + if (pdu_len) { + if (sbuf) { + for (i = 0; i < pdu_len; i++) { + sbuf[i] = pdu[i]; + } + } + } + bytes_consumed = (int)pdu_len; + } + + return bytes_consumed; +} + +/** + * @brief Encode the BVLC Address + * + * Data link layer addressing between B/IPv4 nodes consists of a 32-bit + * IPv4 address followed by a two-octet UDP port number (both of which + * shall be transmitted with the most significant octet first). This + * address shall be referred to as a B/IPv4 address. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param bip_address - B/IPv4 address + * + * @return number of bytes encoded + */ +int bvlc_encode_address( + uint8_t *pdu, uint16_t pdu_size, const BACNET_IP_ADDRESS *bip_address) +{ + int bytes_encoded = 0; + uint16_t length = BIP_ADDRESS_MAX; + unsigned i = 0; + + if (pdu && (pdu_size >= length) && bip_address) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + pdu[i] = bip_address->address[i]; + } + encode_unsigned16(&pdu[IP_ADDRESS_MAX], bip_address->port); + bytes_encoded = (int)length; + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Address + * + * Data link layer addressing between B/IPv4 nodes consists of a 32-bit + * IPv4 address followed by a two-octet UDP port number (both of which + * shall be transmitted with the most significant octet first). This + * address shall be referred to as a B/IPv4 address. + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param bip_address - B/IPv4 address + * + * @return number of bytes decoded + */ +int bvlc_decode_address( + uint8_t *pdu, uint16_t pdu_len, BACNET_IP_ADDRESS *bip_address) +{ + int bytes_consumed = 0; + uint16_t length = BIP_ADDRESS_MAX; + unsigned i = 0; + + if (pdu && (pdu_len >= length) && bip_address) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + bip_address->address[i] = pdu[i]; + } + decode_unsigned16(&pdu[IP_ADDRESS_MAX], &bip_address->port); + bytes_consumed = (int)length; + } + + return bytes_consumed; +} + +/** + * @brief Copy the BVLC Address + * + * Data link layer addressing between B/IPv4 nodes consists of a 32-bit + * IPv4 address followed by a two-octet UDP port number (both of which + * shall be transmitted with the most significant octet first). This + * address shall be referred to as a B/IPv4 address. + * + * @param dst - B/IPv4 address that will be filled with src + * @param src - B/IPv4 address that will be copied into dst + * + * @return true if the address was copied + */ +bool bvlc_address_copy(BACNET_IP_ADDRESS *dst, const BACNET_IP_ADDRESS *src) +{ + bool status = false; + unsigned int i = 0; + + if (src && dst) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + dst->address[i] = src->address[i]; + } + dst->port = src->port; + status = true; + } + + return status; +} + +/** + * @brief Compare the BVLC Address + * + * Data link layer addressing between B/IPv4 nodes consists of a 32-bit + * IPv4 address followed by a two-octet UDP port number (both of which + * shall be transmitted with the most significant octet first). This + * address shall be referred to as a B/IPv4 address. + * + * @param dst - B/IPv4 address that will be compared to src + * @param src - B/IPv4 address that will be compared to dst + * + * @return true if the addresses are different + */ +bool bvlc_address_different( + const BACNET_IP_ADDRESS *dst, const BACNET_IP_ADDRESS *src) +{ + bool status = false; + unsigned int i = 0; + + if (src && dst) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + if (dst->address[i] != src->address[i]) { + status = true; + } + } + if (dst->port != src->port) { + status = true; + } + } + + return status; +} + +/** + * @brief Apply the Broadcast Distribution Mask to an address + * @param dst - B/IPv4 address that will be masked + * @param src - B/IPv4 address that will be ORed with the mask + * @param mask - B/IPv4 broadcast distribution mask + * @return true if the addresses are different + */ +bool bvlc_address_mask( + BACNET_IP_ADDRESS *dst, const BACNET_IP_ADDRESS *src, + const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask) +{ + bool status = false; + unsigned int i = 0; + + if (src && dst && mask) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + dst->address[i] = src->address[i] | ~mask->address[i]; + } + dst->port = src->port; + } + + return status; +} + +/** + * @brief Set a BVLC Address from 4 octets + * + * Data link layer addressing between B/IPv4 nodes consists of a 32-bit + * IPv4 address followed by a two-octet UDP port number (both of which + * shall be transmitted with the most significant octet first). This + * address shall be referred to as a B/IPv4 address. + * + * @param addr - B/IPv4 address that be set + * @param addr0 - B/IPv4 address octet + * @param addr1 - B/IPv4 address octet + * @param addr2 - B/IPv4 address octet + * @param addr3 - B/IPv4 address octet + * + * @return true if the address is set + */ +bool bvlc_address_set(BACNET_IP_ADDRESS *addr, + uint8_t addr0, + uint8_t addr1, + uint8_t addr2, + uint8_t addr3) +{ + bool status = false; + + if (addr) { + addr->address[0] = addr0; + addr->address[1] = addr1; + addr->address[2] = addr2; + addr->address[3] = addr3; + status = true; + } + + return status; +} + +/** + * @brief Get a BVLC Address into 4 octets + * + * Data link layer addressing between B/IPv4 nodes consists of a 128-bit + * IPv4 address followed by a two-octet UDP port number (both of which + * shall be transmitted with the most significant octet first). This + * address shall be referred to as a B/IPv4 address. + * + * @param addr - B/IPv4 address that be set + * @param addr0 - B/IPv4 address octet + * @param addr1 - B/IPv4 address octet + * @param addr2 - B/IPv4 address octet + * @param addr3 - B/IPv4 address octet + * + * @return true if the address is set + */ +bool bvlc_address_get(BACNET_IP_ADDRESS *addr, + uint8_t *addr0, + uint8_t *addr1, + uint8_t *addr2, + uint8_t *addr3) +{ + bool status = false; + + if (addr) { + if (addr0) { + *addr0 = addr->address[0]; + } + if (addr1) { + *addr1 = addr->address[1]; + } + if (addr2) { + *addr2 = addr->address[2]; + } + if (addr3) { + *addr3 = addr->address[3]; + } + status = true; + } + + return status; +} + +/** + * @brief Convert IPv4 Address from ASCII + * + * IPv4 addresses are represented as four octets, separated by dots/periods, + * of four decimal digits. + * + * Adapted from uiplib.c uIP TCP/IP stack and the Contiki operating system. + * Thank you, Adam Dunkel, and the Swedish Institute of Computer Science. + * + * @param addr - B/IPv4 address that is set + * @param addrstr - B/IPv4 address in ASCII dotted decimal format + * + * @return true if a valid address was set + */ +bool bvlc_address_from_ascii(BACNET_IP_ADDRESS *addr, const char *addrstr) +{ + unsigned char tmp = 0; + char c = 0; + unsigned char i = 0, j = 0; + uint8_t charsread = 0; + + if (!addr) { return false; } - - /* Find first empty slot */ - for (i = 0; i < MAX_BBMD_ENTRIES; ++i) { - if (!BBMD_Table[i].valid) { - found = true; - break; - } - - /* Make sure that we are not adding a duplicate */ - if (BBMD_Table[i].dest_address.s_addr == entry->dest_address.s_addr && - BBMD_Table[i].broadcast_mask.s_addr == - entry->broadcast_mask.s_addr && - BBMD_Table[i].dest_port == entry->dest_port) { - return false; - } - } - - if (!found) { + if (!addrstr) { return false; } - - /* Copy new entry to the empty slot */ - BBMD_Table[i] = *entry; - BBMD_Table[i].valid = true; - debug_printf("BVLC: BBMD Table entry added.\n"); - - /* BDT changed! Save backup to file */ - bvlc_bdt_backup_local(); + for (i = 0; i < 4; ++i) { + j = 0; + do { + c = *addrstr; + ++j; + if (j > 4) { + return false; + } + if ((c == '.') || (c == 0) || (c == ' ')) { + addr->address[i] = tmp; + tmp = 0; + } else if ((c >= '0') && (c <= '9')) { + tmp = (tmp * 10) + (c - '0'); + } else { + return false; + } + ++addrstr; + ++charsread; + } while ((c != '.') && (c != 0) && (c != ' ')); + } return true; } -/** Enable NAT handling and set the global IP address - * @param [in] - Global IP address visible to peer BBMDs and foreign devices +/** + * @brief Convert IPv4 Address and UDP port number from ASCII + * + * @param addr - B/IPv4 address and port that is set + * @param addrstr - B/IPv4 address in ASCII dotted decimal format + * @param portstr - B/IPv4 port in 16-bit ASCII hex compressed format + * + * @return true if a valid address was set */ -void bvlc_set_global_address_for_nat(const struct in_addr *addr) +bool bvlc_address_port_from_ascii( + BACNET_IP_ADDRESS *addr, const char *addrstr, const char *portstr) { - BVLC_Global_Address = *addr; - BVLC_NAT_Handling = true; - debug_printf("BVLC: NAT Address enabled.\n"); + bool status = false; + unsigned long port = 0; + + if (bvlc_address_from_ascii(addr, addrstr)) { + port = strtoul(portstr, NULL, 0); + if (port <= UINT16_MAX) { + addr->port = port; + status = true; + } + } + + return status; } -/** Disable NAT handling. +/** + * @brief Convert IPv4 Address from network byte order 32-bit value + * + * @param dst - B/IPv4 address that is set + * @param addr - B/IPv4 address in network byte order 32-bit value */ -void bvlc_disable_nat(void) +void bvlc_address_from_network(BACNET_IP_ADDRESS *dst, uint32_t addr) { - BVLC_NAT_Handling = false; - BVLC_Global_Address.s_addr = 0; - debug_printf("BVLC: NAT Address disabled.\n"); + if (dst) { + /* copy most significant octet first, network byte order, big endian */ + encode_unsigned32(&dst->address[0], addr); + } +} + +/** + * @brief Convert IPv4 Address to local BACnet address + * + * Data link layer addressing between B/IPv4 nodes consists of a 32-bit + * IPv4 address followed by a two-octet UDP port number (both of which + * shall be transmitted with the most significant octet first). + * This address shall be referred to as a B/IPv4 address. + * + * @param dst - BACnet MAC address that is set + * @param addr - B/IPv4 address in network byte order 32-bit value + * @return true if a valid address was set + */ +bool bvlc_ip_address_to_bacnet_local( + BACNET_ADDRESS *addr, BACNET_IP_ADDRESS *ipaddr) +{ + bool status = false; + + if (addr && ipaddr) { + /* most significant octet first, network byte order, big endian */ + addr->mac[0] = ipaddr->address[0]; + addr->mac[1] = ipaddr->address[1]; + addr->mac[2] = ipaddr->address[2]; + addr->mac[3] = ipaddr->address[3]; + encode_unsigned16(&addr->mac[4], ipaddr->port); + addr->mac[6] = 0; + addr->mac_len = 6; + /* local only, no routing */ + addr->net = 0; + /* no SLEN/DLEN */ + addr->len = 0; + /* no SADR/DADR */ + addr->adr[0] = 0; + addr->adr[1] = 0; + addr->adr[2] = 0; + addr->adr[3] = 0; + addr->adr[4] = 0; + addr->adr[5] = 0; + addr->adr[6] = 0; + status = true; + } + + return status; +} + +/** + * @brief Convert IPv4 Address from local BACnet address + * @param addr - BACnet MAC address that is converted into B/IPv4 address/port + * @param ipaddr - B/IPv4 address that is set + * @return true if a valid address was set + */ +bool bvlc_ip_address_from_bacnet_local( + BACNET_IP_ADDRESS *ipaddr, BACNET_ADDRESS *addr) +{ + bool status = false; + + if (addr && ipaddr) { + if (addr->mac_len == 6) { + /* most significant octet first, network byte order, big endian */ + ipaddr->address[0] = addr->mac[0]; + ipaddr->address[1] = addr->mac[1]; + ipaddr->address[2] = addr->mac[2]; + ipaddr->address[3] = addr->mac[3]; + decode_unsigned16(&addr->mac[4], &ipaddr->port); + status = true; + } + } + + return status; +} + +/** + * @brief Convert IPv4 Address to remote BACnet address + * @param dst - BACnet MAC address that is set + * @param dnet - network number of BACnet address + * @param addr - B/IPv4 address in network byte order 32-bit value + * @return true if a valid address was set + */ +bool bvlc_ip_address_to_bacnet_remote( + BACNET_ADDRESS *addr, uint16_t dnet, BACNET_IP_ADDRESS *ipaddr) +{ + bool status = false; + + if (addr && ipaddr) { + /* don't modify local MAC or MAC len */ + /* add DNET/SNET */ + addr->net = dnet; + /* no SADR/DADR */ + /* most significant octet first, network byte order, big endian */ + addr->adr[0] = ipaddr->address[0]; + addr->adr[1] = ipaddr->address[1]; + addr->adr[2] = ipaddr->address[2]; + addr->adr[3] = ipaddr->address[3]; + encode_unsigned16(&addr->adr[4], ipaddr->port); + addr->adr[6] = 0; + /* set SLEN/DLEN for BACnet/IPv4 */ + addr->len = 6; + status = true; + } + + return status; +} + +/** + * @brief Convert IPv4 Address from remote BACnet address + * @param addr - BACnet MAC address that is converted into B/IPv4 address/port + * @param dnet - network number of BACnet address + * @param ipaddr - B/IPv4 address that is set + * @return true if a valid address was set + */ +bool bvlc_ip_address_from_bacnet_remote( + BACNET_IP_ADDRESS *ipaddr, uint16_t *dnet, BACNET_ADDRESS *addr) +{ + bool status = false; + + if (addr && ipaddr) { + if (addr->len == 6) { + /* most significant octet first, network byte order, big endian */ + ipaddr->address[0] = addr->adr[0]; + ipaddr->address[1] = addr->adr[1]; + ipaddr->address[2] = addr->adr[2]; + ipaddr->address[3] = addr->adr[3]; + decode_unsigned16(&addr->adr[4], &ipaddr->port); + if (dnet) { + *dnet = addr->net; + } + status = true; + } + } + + return status; +} + +/** + * @brief Encode the BVLC Broadcast Distribution Mask + * + * The Broadcast Distribution Mask is a 4-octet field that + * indicates how broadcast messages are to be distributed on + * the IP subnet served by the BBMD. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param bd_mask - Broadcast Distribution Mask + * + * @return number of bytes encoded + */ +int bvlc_encode_broadcast_distribution_mask(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *bd_mask) +{ + int bytes_encoded = 0; + unsigned i = 0; + + if (pdu && (pdu_size >= BACNET_IP_BDT_MASK_SIZE) && bd_mask) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + pdu[i] = bd_mask->address[i]; + } + bytes_encoded = BACNET_IP_BDT_MASK_SIZE; + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Broadcast Distribution Mask + * + * The Broadcast Distribution Mask is a 4-octet field that + * indicates how broadcast messages are to be distributed on + * the IP subnet served by the BBMD. + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param bd_mask - Broadcast Distribution Mask + * + * @return number of bytes decoded + */ +int bvlc_decode_broadcast_distribution_mask(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *bd_mask) +{ + int bytes_consumed = 0; + unsigned i = 0; + + if (pdu && (pdu_len >= BACNET_IP_BDT_MASK_SIZE)) { + if (bd_mask) { + for (i = 0; i < IP_ADDRESS_MAX; i++) { + bd_mask->address[i] = pdu[i]; + } + } + bytes_consumed = (int)BACNET_IP_BDT_MASK_SIZE; + } + + return bytes_consumed; +} + +/** + * @brief Encode the BVLC Broadcast Distribution Table Entry + * + * Each BDT entry consists of the 6-octet B/IP address of a + * BBMD followed by a 4-octet field called the broadcast distribution mask + * that indicates how broadcast messages are to be distributed on the + * IP subnet served by the BBMD + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param bdt_entry - BDT Entry + * + * @return number of bytes encoded + */ +int bvlc_encode_broadcast_distribution_table_entry(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry) +{ + int bytes_encoded = 0; + int len = 0; + int offset = 0; + + if (pdu && (pdu_size >= BACNET_IP_BDT_ENTRY_SIZE)) { + if (bdt_entry) { + len = bvlc_encode_address( + &pdu[offset], pdu_size - offset, &bdt_entry->dest_address); + if (len > 0) { + offset += len; + len = bvlc_encode_broadcast_distribution_mask(&pdu[offset], + pdu_size - offset, &bdt_entry->broadcast_mask); + } + if (len > 0) { + offset += len; + bytes_encoded = offset; + } + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Broadcast Distribution Table Entry + * + * Each BDT entry consists of the 6-octet B/IP address of a + * BBMD followed by a 4-octet field called the broadcast distribution mask + * that indicates how broadcast messages are to be distributed on the + * IP subnet served by the BBMD + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param bdt_entry - BDT Entry + * + * @return number of bytes decoded + */ +int bvlc_decode_broadcast_distribution_table_entry(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry) +{ + int bytes_consumed = 0; + int len = 0; + int offset = 0; + + if (pdu && (pdu_len >= BACNET_IP_BDT_ENTRY_SIZE)) { + if (bdt_entry) { + len = bvlc_decode_address( + &pdu[offset], pdu_len - offset, &bdt_entry->dest_address); + if (len > 0) { + offset += len; + len = bvlc_decode_broadcast_distribution_mask( + &pdu[offset], pdu_len - offset, &bdt_entry->broadcast_mask); + } + if (len > 0) { + offset += len; + bytes_consumed = offset; + } + } + } + + return bytes_consumed; +} + +/** + * @brief Encode the BVLC Foreign Device Table Entry + * + * Each BDT entry consists of the 6-octet B/IP address of the registrant; + * the 2-octet Time-to-Live value supplied at the time of registration; + * and a 2-octet value representing the number of seconds remaining. + * + * @param pdu - buffer to store the encoding + * @param pdu_size - size of the buffer to store encoding + * @param fdt_entry - Foreign Device Table (FDT) Entry + * + * @return number of bytes encoded + */ +int bvlc_encode_foreign_device_table_entry(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry) +{ + int bytes_encoded = 0; + int len = 0; + int offset = 0; + + if (pdu && (pdu_size >= BACNET_IP_FDT_ENTRY_SIZE)) { + if (fdt_entry) { + len = bvlc_encode_address( + &pdu[offset], pdu_size - offset, &fdt_entry->dest_address); + if (len > 0) { + offset += len; + len = encode_unsigned16(&pdu[offset], fdt_entry->ttl_seconds); + } + if (len > 0) { + offset += len; + len = encode_unsigned16( + &pdu[offset], fdt_entry->ttl_seconds_remaining); + } + if (len > 0) { + offset += len; + bytes_encoded = offset; + } + } + } + + return bytes_encoded; +} + +/** + * @brief Decode the BVLC Foreign Device Table Entry + * + * Each BDT entry consists of the 6-octet B/IP address of the registrant; + * the 2-octet Time-to-Live value supplied at the time of registration; + * and a 2-octet value representing the number of seconds remaining. + * + * @param pdu - buffer from which to decode the message + * @param pdu_len - length of the buffer that needs decoding + * @param fdt_entry - Foreign Device Table (FDT) Entry + * + * @return number of bytes decoded + */ +int bvlc_decode_foreign_device_table_entry(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry) +{ + int bytes_consumed = 0; + int len = 0; + int offset = 0; + + if (pdu && (pdu_len >= BACNET_IP_FDT_ENTRY_SIZE)) { + if (fdt_entry) { + len = bvlc_decode_address( + &pdu[offset], pdu_len - offset, &fdt_entry->dest_address); + if (len > 0) { + offset += len; + len = decode_unsigned16(&pdu[offset], &fdt_entry->ttl_seconds); + } + if (len > 0) { + offset += len; + len = decode_unsigned16( + &pdu[offset], &fdt_entry->ttl_seconds_remaining); + } + if (len > 0) { + offset += len; + bytes_consumed = offset; + } + } + } + + return bytes_consumed; } #ifdef TEST #include #include +#include #include "ctest.h" -/* copy the source internet address to the BACnet address */ -/* FIXME: IPv6? */ -static void bvlc_bacnet_to_internet_address( - struct sockaddr_in *sin, /* source address in network order */ - BACNET_ADDRESS *src) -{ /* returns the BACnet source address */ +static void test_BVLC_Address(Test *pTest, + BACNET_IP_ADDRESS *bip_address_1, + BACNET_IP_ADDRESS *bip_address_2) +{ + ct_test(pTest, !bvlc_address_different(bip_address_1, bip_address_2)); +} - if (src && sin) { - if (src->mac_len == 6) { - memcpy(&sin->sin_addr.s_addr, &src->mac[0], 4); - memcpy(&sin->sin_port, &src->mac[4], 2); - } +static void test_BVLC_Broadcast_Distribution_Mask(Test *pTest, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *bd_mask_1, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *bd_mask_2) +{ + ct_test(pTest, + !bvlc_broadcast_distribution_mask_different(bd_mask_1, bd_mask_2)); +} + +static void test_BVLC_Broadcast_Distribution_Table_Entry(Test *pTest, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry_1, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry_2) +{ + if (bdt_entry_1 && bdt_entry_2) { + ct_test(pTest, bdt_entry_1->valid == bdt_entry_2->valid); + test_BVLC_Address( + pTest, &bdt_entry_1->dest_address, &bdt_entry_2->dest_address); + test_BVLC_Broadcast_Distribution_Mask( + pTest, &bdt_entry_1->broadcast_mask, &bdt_entry_2->broadcast_mask); } return; } -void testBIPAddress(Test *pTest) +static void test_BVLC_Foreign_Device_Table_Entry(Test *pTest, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry_1, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry_2) { - uint8_t apdu[50] = { 0 }; - uint32_t value = 0, test_value = 0; - int len = 0, test_len = 0; - struct in_addr address; - struct in_addr test_address; - uint16_t port = 0, test_port = 0; + if (fdt_entry_1 && fdt_entry_2) { + ct_test(pTest, fdt_entry_1->valid == fdt_entry_2->valid); + test_BVLC_Address( + pTest, &fdt_entry_1->dest_address, &fdt_entry_2->dest_address); + ct_test(pTest, fdt_entry_1->ttl_seconds == fdt_entry_2->ttl_seconds); + ct_test(pTest, + fdt_entry_1->ttl_seconds_remaining == + fdt_entry_2->ttl_seconds_remaining); + } - address.s_addr = 42; - len = bvlc_encode_bip_address(&apdu[0], &address, port); - test_len = bvlc_decode_bip_address(&apdu[0], &test_address, &test_port); - ct_test(pTest, len == test_len); - ct_test(pTest, address.s_addr == test_address.s_addr); - ct_test(pTest, port == test_port); + return; } -void testInternetAddress(Test *pTest) -{ - BACNET_ADDRESS src; - BACNET_ADDRESS test_src; - struct sockaddr_in sin = { 0 }; - struct sockaddr_in test_sin = { 0 }; +static int test_BVLC_Header(Test *pTest, + uint8_t *pdu, + uint16_t pdu_len, + uint8_t *message_type, + uint16_t *message_length) - sin.sin_port = htons(0xBAC0); - sin.sin_addr.s_addr = inet_addr("192.168.0.1"); - bvlc_internet_to_bacnet_address(&src, &sin); - bvlc_bacnet_to_internet_address(&test_sin, &src); - ct_test(pTest, sin.sin_port == test_sin.sin_port); - ct_test(pTest, sin.sin_addr.s_addr == test_sin.sin_addr.s_addr); +{ + int bytes_consumed = 0; + int len = 0; + + if (pdu && message_type && message_length) { + len = bvlc_decode_header(pdu, pdu_len, message_type, message_length); + ct_test(pTest, len == 4); + bytes_consumed = len; + } + + return bytes_consumed; +} + +static void test_BVLC_Result_Code(Test *pTest, uint16_t result_code) +{ + uint8_t pdu[50] = { 0 }; + uint16_t test_result_code = 0; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, test_len = 0; + + len = bvlc_encode_result(pdu, sizeof(pdu), result_code); + ct_test(pTest, len == 6); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_RESULT); + ct_test(pTest, length == 6); + test_len += bvlc_decode_result(&pdu[4], length - 4, &test_result_code); + ct_test(pTest, len == test_len); + ct_test(pTest, result_code == test_result_code); +} + +static void test_BVLC_Result(Test *pTest) +{ + uint16_t result_code[] = { BVLC_RESULT_SUCCESSFUL_COMPLETION, + BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK, + BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK, + BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK, + BVLC_RESULT_READ_FOREIGN_DEVICE_TABLE_NAK, + BVLC_RESULT_DELETE_FOREIGN_DEVICE_TABLE_ENTRY_NAK, + BVLC_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK }; + unsigned int i = 0; + size_t result_code_max = sizeof(result_code) / sizeof(result_code[0]); + + for (i = 0; i < result_code_max; i++) { + test_BVLC_Result_Code(pTest, result_code[i]); + } +} + +static void test_BVLC_Original_Unicast_NPDU_Message( + Test *pTest, uint8_t *npdu, uint16_t npdu_len) +{ + uint8_t test_npdu[50] = { 0 }; + uint8_t pdu[60] = { 0 }; + uint16_t test_npdu_len = 0; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + uint16_t i = 0; + + len = bvlc_encode_original_unicast(pdu, sizeof(pdu), npdu, npdu_len); + msg_len = 4 + npdu_len; + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_ORIGINAL_UNICAST_NPDU); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_original_unicast( + &pdu[4], length - 4, test_npdu, sizeof(test_npdu), &test_npdu_len); + ct_test(pTest, len == test_len); + ct_test(pTest, msg_len == test_len); + ct_test(pTest, npdu_len == test_npdu_len); + for (i = 0; i < npdu_len; i++) { + ct_test(pTest, npdu[i] == test_npdu[i]); + } +} + +static void test_BVLC_Original_Unicast_NPDU(Test *pTest) +{ + uint8_t npdu[50] = { 0 }; + uint16_t npdu_len = 0; + uint16_t i = 0; + + test_BVLC_Original_Unicast_NPDU_Message(pTest, npdu, npdu_len); + /* now with some NPDU data */ + for (i = 0; i < sizeof(npdu); i++) { + npdu[i] = i; + } + npdu_len = sizeof(npdu); + test_BVLC_Original_Unicast_NPDU_Message(pTest, npdu, npdu_len); +} + +static void test_BVLC_Original_Broadcast_NPDU_Message( + Test *pTest, uint8_t *npdu, uint16_t npdu_len) +{ + uint8_t test_npdu[50] = { 0 }; + uint8_t pdu[60] = { 0 }; + uint16_t test_npdu_len = 0; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + uint16_t i = 0; + + len = bvlc_encode_original_broadcast(pdu, sizeof(pdu), npdu, npdu_len); + msg_len = 4 + npdu_len; + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_ORIGINAL_BROADCAST_NPDU); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_original_broadcast( + &pdu[4], length - 4, test_npdu, sizeof(test_npdu), &test_npdu_len); + ct_test(pTest, len == test_len); + ct_test(pTest, msg_len == test_len); + ct_test(pTest, npdu_len == test_npdu_len); + for (i = 0; i < npdu_len; i++) { + ct_test(pTest, npdu[i] == test_npdu[i]); + } +} + +static void test_BVLC_Original_Broadcast_NPDU(Test *pTest) +{ + uint8_t npdu[50] = { 0 }; + uint16_t npdu_len = 0; + uint16_t i = 0; + + test_BVLC_Original_Broadcast_NPDU_Message(pTest, npdu, npdu_len); + /* now with some NPDU data */ + for (i = 0; i < sizeof(npdu); i++) { + npdu[i] = i; + } + npdu_len = sizeof(npdu); + test_BVLC_Original_Broadcast_NPDU_Message(pTest, npdu, npdu_len); +} + +static void test_BVLC_Forwarded_NPDU_Message(Test *pTest, + uint8_t *npdu, + uint16_t npdu_len, + BACNET_IP_ADDRESS *bip_address) +{ + uint8_t test_npdu[50] = { 0 }; + uint8_t pdu[75] = { 0 }; + BACNET_IP_ADDRESS test_bip_address = { 0 }; + uint16_t test_npdu_len = 0; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + uint16_t i = 0; + + len = bvlc_encode_forwarded_npdu( + pdu, sizeof(pdu), bip_address, npdu, npdu_len); + msg_len = 1 + 1 + 2 + BIP_ADDRESS_MAX + npdu_len; + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_FORWARDED_NPDU); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_forwarded_npdu(&pdu[4], length - 4, + &test_bip_address, test_npdu, sizeof(test_npdu), &test_npdu_len); + ct_test(pTest, len == test_len); + ct_test(pTest, msg_len == test_len); + test_BVLC_Address(pTest, bip_address, &test_bip_address); + ct_test(pTest, npdu_len == test_npdu_len); + for (i = 0; i < npdu_len; i++) { + ct_test(pTest, npdu[i] == test_npdu[i]); + } +} + +static void test_BVLC_Forwarded_NPDU(Test *pTest) +{ + uint8_t npdu[50] = { 0 }; + BACNET_IP_ADDRESS bip_address = { 0 }; + uint16_t npdu_len = 0; + uint16_t i = 0; + + test_BVLC_Forwarded_NPDU_Message(pTest, npdu, npdu_len, &bip_address); + for (i = 0; i < sizeof(bip_address.address); i++) { + bip_address.address[i] = i; + } + bip_address.port = 47808; + /* now with some NPDU data */ + for (i = 0; i < sizeof(npdu); i++) { + npdu[i] = i; + } + npdu_len = sizeof(npdu); + test_BVLC_Forwarded_NPDU_Message(pTest, npdu, npdu_len, &bip_address); +} + +static void test_BVLC_Register_Foreign_Device_Message( + Test *pTest, uint16_t ttl_seconds) +{ + uint8_t pdu[60] = { 0 }; + uint16_t test_ttl_seconds = 0; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, test_len = 0; + const int msg_len = 6; + + len = bvlc_encode_register_foreign_device(pdu, sizeof(pdu), ttl_seconds); + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_REGISTER_FOREIGN_DEVICE); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_register_foreign_device( + &pdu[4], length - 4, &test_ttl_seconds); + ct_test(pTest, len == test_len); + ct_test(pTest, msg_len == test_len); + ct_test(pTest, ttl_seconds == test_ttl_seconds); +} + +static void test_BVLC_Register_Foreign_Device(Test *pTest) +{ + uint16_t ttl_seconds = 0; + + test_BVLC_Register_Foreign_Device_Message(pTest, ttl_seconds); + ttl_seconds = 600; + test_BVLC_Register_Foreign_Device_Message(pTest, ttl_seconds); +} + +static void test_BVLC_Delete_Foreign_Device_Message( + Test *pTest, BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry) +{ + uint8_t pdu[64] = { 0 }; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY test_fdt_entry = { 0 }; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, test_len = 0; + const int msg_len = 0x000A; + + len = bvlc_encode_delete_foreign_device( + pdu, sizeof(pdu), &fdt_entry->dest_address); + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_delete_foreign_device( + &pdu[4], length - 4, &test_fdt_entry.dest_address); + ct_test(pTest, len == test_len); + ct_test(pTest, msg_len == test_len); + if (msg_len != test_len) { + printf("msg:%u test:%u\n", msg_len, test_len); + } + test_BVLC_Address( + pTest, &fdt_entry->dest_address, &test_fdt_entry.dest_address); +} + +static void test_BVLC_Delete_Foreign_Device(Test *pTest) +{ + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY fdt_entry = { 0 }; + unsigned int i = 0; + + /* test with zeros */ + test_BVLC_Delete_Foreign_Device_Message(pTest, &fdt_entry); + /* test with valid values */ + for (i = 0; i < sizeof(fdt_entry.dest_address.address); i++) { + fdt_entry.dest_address.address[i] = i; + } + fdt_entry.dest_address.port = 47808; + fdt_entry.ttl_seconds = 600; + fdt_entry.ttl_seconds_remaining = 42; + fdt_entry.next = NULL; + test_BVLC_Delete_Foreign_Device_Message(pTest, &fdt_entry); +} + +static void test_BVLC_Secure_BVLL_Message( + Test *pTest, uint8_t *sbuf, uint16_t sbuf_len) +{ + uint8_t test_sbuf[50] = { 0 }; + uint8_t pdu[60] = { 0 }; + uint16_t test_sbuf_len = 0; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + uint16_t i = 0; + + len = bvlc_encode_secure_bvll(pdu, sizeof(pdu), sbuf, sbuf_len); + msg_len = 1 + 1 + 2 + sbuf_len; + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_SECURE_BVLL); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_secure_bvll( + &pdu[4], length - 4, test_sbuf, sizeof(test_sbuf), &test_sbuf_len); + ct_test(pTest, len == test_len); + ct_test(pTest, msg_len == test_len); + ct_test(pTest, sbuf_len == test_sbuf_len); + for (i = 0; i < sbuf_len; i++) { + ct_test(pTest, sbuf[i] == test_sbuf[i]); + } +} + +static void test_BVLC_Secure_BVLL(Test *pTest) +{ + uint8_t sbuf[50] = { 0 }; + uint16_t sbuf_len = 0; + uint16_t i = 0; + + test_BVLC_Secure_BVLL_Message(pTest, sbuf, sbuf_len); + /* now with some NPDU data */ + for (i = 0; i < sizeof(sbuf); i++) { + sbuf[i] = i; + } + sbuf_len = sizeof(sbuf); + test_BVLC_Secure_BVLL_Message(pTest, sbuf, sbuf_len); +} + +static void test_BVLC_Read_Broadcast_Distribution_Table_Message(Test *pTest) +{ + uint8_t pdu[60] = { 0 }; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + + len = bvlc_encode_read_broadcast_distribution_table(pdu, sizeof(pdu)); + msg_len = 1 + 1 + 2; + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_READ_BROADCAST_DIST_TABLE); + ct_test(pTest, length == msg_len); +} + +static void test_BVLC_Distribute_Broadcast_To_Network_Message( + Test *pTest, uint8_t *npdu, uint16_t npdu_len) +{ + uint8_t test_npdu[50] = { 0 }; + uint8_t pdu[60] = { 0 }; + uint16_t test_npdu_len = 0; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + uint16_t i = 0; + + len = bvlc_encode_distribute_broadcast_to_network( + pdu, sizeof(pdu), npdu, npdu_len); + msg_len = 4 + npdu_len; + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_distribute_broadcast_to_network( + &pdu[4], length - 4, test_npdu, sizeof(test_npdu), &test_npdu_len); + ct_test(pTest, len == test_len); + ct_test(pTest, msg_len == test_len); + ct_test(pTest, npdu_len == test_npdu_len); + for (i = 0; i < npdu_len; i++) { + ct_test(pTest, npdu[i] == test_npdu[i]); + } +} + +static void test_BVLC_Distribute_Broadcast_To_Network(Test *pTest) +{ + uint8_t npdu[50] = { 0 }; + uint16_t npdu_len = 0; + uint16_t i = 0; + + test_BVLC_Distribute_Broadcast_To_Network_Message(pTest, npdu, npdu_len); + /* now with some NPDU data */ + for (i = 0; i < sizeof(npdu); i++) { + npdu[i] = i; + } + npdu_len = sizeof(npdu); + test_BVLC_Distribute_Broadcast_To_Network_Message(pTest, npdu, npdu_len); +} + +static void test_BVLC_Write_Broadcast_Distribution_Table_Message(Test *pTest, + uint8_t *npdu, + uint16_t npdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + uint8_t pdu[480] = { 0 }; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *test_bdt_list = NULL; + uint16_t i = 0; + uint16_t count = 0; + + count = bvlc_broadcast_distribution_table_valid_count(bdt_list); + test_bdt_list = + calloc(count, sizeof(BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY)); + bvlc_broadcast_distribution_table_link_array(test_bdt_list, count); + /* encode the message */ + len = bvlc_encode_write_broadcast_distribution_table( + pdu, sizeof(pdu), bdt_list); + msg_len = 4 + (count * BACNET_IP_BDT_ENTRY_SIZE); + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_write_broadcast_distribution_table( + &pdu[4], length - 4, test_bdt_list); + ct_test(pTest, msg_len == test_len); + for (i = 0; i < count; i++) { + test_BVLC_Broadcast_Distribution_Table_Entry( + pTest, &bdt_list[i], &test_bdt_list[i]); + } +} + +static void test_BVLC_Write_Broadcast_Distribution_Table(Test *pTest) +{ + uint8_t npdu[480] = { 0 }; + uint16_t npdu_len = 0; + uint16_t i = 0; + uint16_t count = 0; + uint16_t test_count = 0; + bool status = false; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY bdt_list[5] = { 0 }; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY bdt_entry = { 0 }; + BACNET_IP_ADDRESS dest_address = { 0 }; + BACNET_IP_BROADCAST_DISTRIBUTION_MASK broadcast_mask = { 0 }; + + /* configure a BDT entry */ + count = sizeof(bdt_list) / sizeof(bdt_list[0]); + bvlc_broadcast_distribution_table_link_array(&bdt_list[0], count); + for (i = 0; i < count; i++) { + status = bvlc_address_port_from_ascii( + &dest_address, "192.168.0.255", "0xBAC0"); + ct_test(pTest, status); + dest_address.port += i; + broadcast_mask.address[0] = 255; + broadcast_mask.address[1] = 255; + broadcast_mask.address[2] = 255; + broadcast_mask.address[3] = 255; + status = bvlc_broadcast_distribution_table_entry_set( + &bdt_entry, &dest_address, &broadcast_mask); + ct_test(pTest, status); + status = bvlc_broadcast_distribution_table_entry_append( + &bdt_list[0], &bdt_entry); + ct_test(pTest, status); + } + test_count = bvlc_broadcast_distribution_table_count(&bdt_list[0]); + if (test_count != count) { + printf("size=%u count=%u\n", count, test_count); + } + ct_test(pTest, test_count == count); + test_count = bvlc_broadcast_distribution_table_valid_count(&bdt_list[0]); + ct_test(pTest, test_count == count); + for (i = 1; i < 5; i++) { + status = bvlc_broadcast_distribution_table_entry_different( + &bdt_list[0], &bdt_list[i]); + ct_test(pTest, status); + } + test_BVLC_Write_Broadcast_Distribution_Table_Message( + pTest, npdu, npdu_len, &bdt_list[0]); + /* now with some NPDU data */ + for (i = 0; i < sizeof(npdu); i++) { + npdu[i] = i; + } + npdu_len = sizeof(npdu); + test_BVLC_Write_Broadcast_Distribution_Table_Message( + pTest, npdu, npdu_len, &bdt_list[0]); +} + +static void test_BVLC_Read_Broadcast_Distribution_Table_Ack_Message(Test *pTest, + uint8_t *npdu, + uint16_t npdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list) +{ + uint8_t pdu[480] = { 0 }; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *test_bdt_list = NULL; + uint16_t i = 0; + uint16_t count = 0; + + count = bvlc_broadcast_distribution_table_valid_count(bdt_list); + test_bdt_list = + calloc(count, sizeof(BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY)); + bvlc_broadcast_distribution_table_link_array(test_bdt_list, count); + /* encode the message */ + len = bvlc_encode_read_broadcast_distribution_table_ack( + pdu, sizeof(pdu), bdt_list); + msg_len = 4 + (count * BACNET_IP_BDT_ENTRY_SIZE); + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_READ_BROADCAST_DIST_TABLE_ACK); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_read_broadcast_distribution_table_ack( + &pdu[4], length - 4, test_bdt_list); + ct_test(pTest, msg_len == test_len); + for (i = 0; i < count; i++) { + test_BVLC_Broadcast_Distribution_Table_Entry( + pTest, &bdt_list[i], &test_bdt_list[i]); + } +} + +static void test_BVLC_Read_Broadcast_Distribution_Table_Ack(Test *pTest) +{ + uint8_t npdu[480] = { 0 }; + uint16_t npdu_len = 0; + uint16_t i = 0; + uint16_t count = 0; + uint16_t test_count = 0; + bool status = false; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY bdt_list[5] = { 0 }; + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY bdt_entry = { 0 }; + + /* configure a BDT entry */ + count = sizeof(bdt_list) / sizeof(bdt_list[0]); + for (i = 0; i < count; i++) { + status = bvlc_address_port_from_ascii( + &bdt_entry.dest_address, "192.168.0.255", "0xBAC0"); + ct_test(pTest, status); + bdt_entry.dest_address.port += i; + bdt_entry.broadcast_mask.address[0] = 255; + bdt_entry.broadcast_mask.address[1] = 255; + bdt_entry.broadcast_mask.address[2] = 255; + bdt_entry.broadcast_mask.address[3] = 255; + status = bvlc_broadcast_distribution_table_entry_copy( + &bdt_list[i], &bdt_entry); + ct_test(pTest, status); + bdt_list[i].valid = true; + if (i > 0) { + bdt_list[i - 1].next = &bdt_list[i]; + } + bdt_list[i].next = NULL; + } + test_count = bvlc_broadcast_distribution_table_count(&bdt_list[0]); + if (test_count != count) { + printf("size=%u count=%u\n", count, test_count); + } + ct_test(pTest, test_count == count); + test_count = bvlc_broadcast_distribution_table_valid_count(&bdt_list[0]); + ct_test(pTest, test_count == count); + bvlc_broadcast_distribution_table_entry_copy(&bdt_entry, &bdt_list[0]); + status = bvlc_broadcast_distribution_table_entry_different( + &bdt_entry, &bdt_list[0]); + ct_test(pTest, !status); + test_BVLC_Read_Broadcast_Distribution_Table_Ack_Message( + pTest, npdu, npdu_len, &bdt_list[0]); + /* now with some NPDU data */ + for (i = 0; i < sizeof(npdu); i++) { + npdu[i] = i; + } + npdu_len = sizeof(npdu); + test_BVLC_Read_Broadcast_Distribution_Table_Ack_Message( + pTest, npdu, npdu_len, &bdt_list[0]); +} + +static void test_BVLC_Read_Foreign_Device_Table_Ack_Message(Test *pTest, + uint8_t *npdu, + uint16_t npdu_len, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list) +{ + uint8_t pdu[480] = { 0 }; + uint8_t message_type = 0; + uint16_t length = 0; + int len = 0, msg_len = 0, test_len = 0; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *test_fdt_list = NULL; + uint16_t i = 0; + uint16_t count = 0; + + count = bvlc_foreign_device_table_valid_count(fdt_list); + test_fdt_list = calloc(count, sizeof(BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY)); + bvlc_foreign_device_table_link_array(test_fdt_list, count); + /* encode the message */ + len = bvlc_encode_read_foreign_device_table_ack(pdu, sizeof(pdu), fdt_list); + msg_len = 4 + (count * BACNET_IP_FDT_ENTRY_SIZE); + ct_test(pTest, len == msg_len); + test_len = test_BVLC_Header(pTest, pdu, len, &message_type, &length); + ct_test(pTest, test_len == 4); + ct_test(pTest, message_type == BVLC_READ_FOREIGN_DEVICE_TABLE_ACK); + ct_test(pTest, length == msg_len); + test_len += bvlc_decode_read_foreign_device_table_ack( + &pdu[4], length - 4, test_fdt_list); + ct_test(pTest, msg_len == test_len); + for (i = 0; i < count; i++) { + test_BVLC_Foreign_Device_Table_Entry( + pTest, &fdt_list[i], &test_fdt_list[i]); + } +} + +static void test_BVLC_Read_Foreign_Device_Table_Ack(Test *pTest) +{ + uint8_t npdu[480] = { 0 }; + uint16_t npdu_len = 0; + uint16_t i = 0; + uint16_t count = 0; + uint16_t test_count = 0; + uint16_t test_port_start = 0xBAC1; + bool status = false; + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY fdt_list[5] = { 0 }; + BACNET_IP_ADDRESS dest_address = { 0 }; + + status = bvlc_address_from_ascii(&dest_address, "192.168.0.1"); + ct_test(pTest, status); + /* configure a FDT entry */ + count = sizeof(fdt_list) / sizeof(fdt_list[0]); + bvlc_foreign_device_table_link_array(fdt_list, count); + for (i = 0; i < count; i++) { + dest_address.port = test_port_start + i; + status = bvlc_foreign_device_table_entry_add( + &fdt_list[0], &dest_address, 12345); + ct_test(pTest, status); + /* add again should only update TTL */ + status = bvlc_foreign_device_table_entry_add( + &fdt_list[0], &dest_address, 12345); + ct_test(pTest, status); + } + test_count = bvlc_foreign_device_table_count(fdt_list); + ct_test(pTest, test_count == count); + if (test_count != count) { + printf("size=%u count=%u\n", count, test_count); + } + test_count = bvlc_foreign_device_table_valid_count(fdt_list); + ct_test(pTest, test_count == count); + test_BVLC_Read_Foreign_Device_Table_Ack_Message( + pTest, npdu, npdu_len, fdt_list); + /* now with some NPDU data */ + for (i = 0; i < sizeof(npdu); i++) { + npdu[i] = i; + } + npdu_len = sizeof(npdu); + test_BVLC_Read_Foreign_Device_Table_Ack_Message( + pTest, npdu, npdu_len, fdt_list); + /* cleanup */ + for (i = 0; i < count; i++) { + dest_address.port = test_port_start + i; + status = + bvlc_foreign_device_table_entry_delete(&fdt_list[0], &dest_address); + ct_test(pTest, status); + } + test_count = bvlc_foreign_device_table_valid_count(fdt_list); + ct_test(pTest, test_count == 0); +} + +static void test_BVLC_Address_Copy(Test *pTest) +{ + unsigned int i = 0; + BACNET_IP_ADDRESS src = { 0 }; + BACNET_IP_ADDRESS dst = { 0 }; + bool status = false; + + /* test with zeros */ + status = bvlc_address_copy(&dst, &src); + ct_test(pTest, status); + status = bvlc_address_different(&dst, &src); + ct_test(pTest, !status); + /* test with valid values */ + for (i = 0; i < sizeof(src.address); i++) { + src.address[i] = 1 + i; + } + src.port = 47808; + status = bvlc_address_copy(&dst, &src); + ct_test(pTest, status); + status = bvlc_address_different(&dst, &src); + ct_test(pTest, !status); + /* test for different port */ + dst.port = 47809; + status = bvlc_address_different(&dst, &src); + ct_test(pTest, status); + /* test for different address */ + dst.port = src.port; + for (i = 0; i < sizeof(src.address); i++) { + dst.address[i] = 0; + status = bvlc_address_different(&dst, &src); + ct_test(pTest, status); + dst.address[i] = 1 + i; + } +} + +static void test_BVLC_Address_Get_Set(Test *pTest) +{ + uint16_t i = 0; + BACNET_ADDRESS bsrc = { 0 }; + BACNET_IP_ADDRESS src = { 0 }; + BACNET_IP_ADDRESS dst = { 0 }; + BACNET_IP_BROADCAST_DISTRIBUTION_MASK mask = { 0 }; + BACNET_IP_BROADCAST_DISTRIBUTION_MASK test_mask = { 0 }; + const uint32_t broadcast_mask = 0x12345678; + uint32_t test_broadcast_mask = 0; + uint8_t octet0 = 192; + uint8_t octet1 = 168; + uint8_t octet2 = 1; + uint8_t octet3 = 255; + uint8_t test_octet0 = 0; + uint8_t test_octet1 = 0; + uint8_t test_octet2 = 0; + uint8_t test_octet3 = 0; + const uint16_t dnet = 12345; + uint16_t snet = 0; + bool status = false; + + for (i = 0; i < 255; i++) { + octet0 = i; + octet1 = i; + octet2 = i; + octet3 = i; + status = bvlc_address_set(&src, octet0, octet1, octet2, octet3); + ct_test(pTest, status); + status = bvlc_address_get( + &src, &test_octet0, &test_octet1, &test_octet2, &test_octet3); + ct_test(pTest, status); + ct_test(pTest, octet0 == test_octet0); + ct_test(pTest, octet1 == test_octet1); + ct_test(pTest, octet2 == test_octet2); + ct_test(pTest, octet3 == test_octet3); + } + /* test the ASCII hex to address */ + /* test invalid */ + status = bvlc_address_from_ascii(&src, "256"); + ct_test(pTest, status == false); + status = bvlc_address_from_ascii(&src, "192.168.0.1"); + ct_test(pTest, status); + status = bvlc_address_set(&dst, 192, 168, 0, 1); + ct_test(pTest, status); + status = bvlc_address_different(&dst, &src); + ct_test(pTest, status == false); + /* test zero compression */ + status = bvlc_address_from_ascii(&src, "127..."); + ct_test(pTest, status); + status = bvlc_address_set(&dst, 127, 0, 0, 0); + ct_test(pTest, status); + status = bvlc_address_different(&dst, &src); + if (status) { + status = bvlc_address_get( + &src, &test_octet0, &test_octet1, &test_octet2, &test_octet3); + printf("src:%u.%u.%u.%u\n", (unsigned)test_octet0, + (unsigned)test_octet1, (unsigned)test_octet2, + (unsigned)test_octet3); + status = bvlc_address_get( + &dst, &test_octet0, &test_octet1, &test_octet2, &test_octet3); + printf("dst:%u.%u.%u.%u\n", (unsigned)test_octet0, + (unsigned)test_octet1, (unsigned)test_octet2, + (unsigned)test_octet3); + } + ct_test(pTest, status == false); + /* BACnet to IPv4 address conversions */ + status = bvlc_address_port_from_ascii(&src, "192.168.0.1", "0xBAC0"); + ct_test(pTest, status); + status = bvlc_ip_address_to_bacnet_local(&bsrc, &src); + ct_test(pTest, status); + status = bvlc_ip_address_from_bacnet_local(&dst, &bsrc); + ct_test(pTest, status); + status = bvlc_address_different(&dst, &src); + ct_test(pTest, !status); + status = bvlc_ip_address_to_bacnet_remote(&bsrc, dnet, &src); + ct_test(pTest, status); + status = bvlc_ip_address_from_bacnet_remote(&dst, &snet, &bsrc); + ct_test(pTest, status); + ct_test(pTest, snet == dnet); + status = bvlc_ip_address_from_bacnet_remote(&dst, NULL, &bsrc); + ct_test(pTest, status); + /* Broadcast Distribution Mask conversions */ + status = bvlc_broadcast_distribution_mask_from_host(&mask, broadcast_mask); + ct_test(pTest, status); + status = + bvlc_broadcast_distribution_mask_to_host(&test_broadcast_mask, &mask); + ct_test(pTest, status); + ct_test(pTest, test_broadcast_mask == broadcast_mask); + octet0 = 0x12; + octet1 = 0x34; + octet2 = 0x56; + octet3 = 0x78; + bvlc_broadcast_distribution_mask_set( + &test_mask, octet0, octet1, octet2, octet3); + status = bvlc_broadcast_distribution_mask_different(&mask, &test_mask); + ct_test(pTest, !status); + bvlc_broadcast_distribution_mask_get( + &test_mask, &test_octet0, &test_octet1, &test_octet2, &test_octet3); + ct_test(pTest, octet0 == test_octet0); + ct_test(pTest, octet1 == test_octet1); + ct_test(pTest, octet2 == test_octet2); + ct_test(pTest, octet3 == test_octet3); +} + +void test_BVLC(Test *pTest) +{ + bool rc; + + /* individual tests */ + rc = ct_addTestFunction(pTest, test_BVLC_Result); + assert(rc); + rc = + ct_addTestFunction(pTest, test_BVLC_Write_Broadcast_Distribution_Table); + assert(rc); + rc = ct_addTestFunction( + pTest, test_BVLC_Read_Broadcast_Distribution_Table_Message); + assert(rc); + rc = ct_addTestFunction( + pTest, test_BVLC_Read_Broadcast_Distribution_Table_Ack); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Forwarded_NPDU); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Register_Foreign_Device); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Read_Foreign_Device_Table_Ack); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Delete_Foreign_Device); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Distribute_Broadcast_To_Network); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Original_Unicast_NPDU); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Original_Broadcast_NPDU); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Secure_BVLL); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Address_Copy); + assert(rc); + rc = ct_addTestFunction(pTest, test_BVLC_Address_Get_Set); + assert(rc); } #ifdef TEST_BVLC int main(void) { Test *pTest; - bool rc; - pTest = ct_create("BACnet Virtual Link Control", NULL); - /* individual tests */ - rc = ct_addTestFunction(pTest, testBIPAddress); - assert(rc); - rc = ct_addTestFunction(pTest, testInternetAddress); - assert(rc); + pTest = ct_create("BACnet Virtual Link Control IP/v4", NULL); + test_BVLC(pTest); /* configure output */ ct_setStream(pTest, stdout); ct_run(pTest); @@ -1841,6 +3204,5 @@ int main(void) return 0; } - #endif /* TEST_BBMD */ #endif /* TEST */ diff --git a/src/bacnet/datalink/bvlc.h b/src/bacnet/datalink/bvlc.h index f03360cd..b883c703 100644 --- a/src/bacnet/datalink/bvlc.h +++ b/src/bacnet/datalink/bvlc.h @@ -1,204 +1,502 @@ /************************************************************************** -* -* Copyright (C) 2012 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. -*********************************************************************/ + * + * Copyright (C) 2012 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. + *********************************************************************/ #ifndef BVLC_H #define BVLC_H #include #include #include -#include -#include "bacnet/bacnet_stack_exports.h" #include "bacnet/bacdef.h" #include "bacnet/npdu.h" -#include "bacnet/datalink/bip.h" -struct sockaddr_in; /* Defined elsewhere, needed here. */ +/** + * BVLL for BACnet/IPv4 + * @{ + */ +#define BVLL_TYPE_BACNET_IP (0x81) +/** @} */ + +/** + * B/IPv4 BVLL Messages + * @{ + */ +#define BVLC_RESULT 0 +#define BVLC_WRITE_BROADCAST_DISTRIBUTION_TABLE 1 +#define BVLC_READ_BROADCAST_DIST_TABLE 2 +#define BVLC_READ_BROADCAST_DIST_TABLE_ACK 3 +#define BVLC_FORWARDED_NPDU 4 +#define BVLC_REGISTER_FOREIGN_DEVICE 5 +#define BVLC_READ_FOREIGN_DEVICE_TABLE 6 +#define BVLC_READ_FOREIGN_DEVICE_TABLE_ACK 7 +#define BVLC_DELETE_FOREIGN_DEVICE_TABLE_ENTRY 8 +#define BVLC_DISTRIBUTE_BROADCAST_TO_NETWORK 9 +#define BVLC_ORIGINAL_UNICAST_NPDU 10 +#define BVLC_ORIGINAL_BROADCAST_NPDU 11 +#define BVLC_SECURE_BVLL 12 + +/** @} */ + +/** + * BVLC Result Code + * @{ + */ +#define BVLC_RESULT_SUCCESSFUL_COMPLETION 0x0000 +#define BVLC_RESULT_WRITE_BROADCAST_DISTRIBUTION_TABLE_NAK 0x0010 +#define BVLC_RESULT_READ_BROADCAST_DISTRIBUTION_TABLE_NAK 0x0020 +#define BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK 0X0030 +#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 + +/* number of bytes in the IPv4 address */ +#define IP_ADDRESS_MAX 4 + +/** + * BACnet IPv4 Address + * + * Data link layer addressing between B/IPv4 nodes consists of a 32-bit + * IPv4 address followed by a two-octet UDP port number (both of which + * shall be transmitted with the most significant octet first). + * This address shall be referred to as a B/IPv4 address. + * @{ + */ +typedef struct BACnet_IP_Address { + uint8_t address[IP_ADDRESS_MAX]; + uint16_t port; +} BACNET_IP_ADDRESS; +/* number of bytes in the B/IPv4 address */ +#define BIP_ADDRESS_MAX 6 + +/** + * BACnet IPv4 Broadcast Distribution Mask + * + * The Broadcast Distribution Mask is a 4-octet field that + * indicates how broadcast messages are to be distributed on + * the IP subnet served by the BBMD. + * @{ + */ +typedef struct BACnet_IP_Broadcast_Distribution_Mask { + uint8_t address[IP_ADDRESS_MAX]; +} BACNET_IP_BROADCAST_DISTRIBUTION_MASK; +/* number of bytes in the B/IPv4 Broadcast Distribution Mask */ +#define BACNET_IP_BDT_MASK_SIZE IP_ADDRESS_MAX +/** @} */ + +/** + * BACnet/IP Broadcast Distribution Table (BDT) + * + * The BDT consists of one entry for the address of the BBMD + * for the local IP subnet and an entry for the BBMD on each + * remote IP subnet to which broadcasts are to be forwarded. + * Each entry consists of the 6-octet B/IP address with which + * the BBMD is accessed and a 4-octet broadcast distribution mask. + * @{ + */ +struct BACnet_IP_Broadcast_Distribution_Table_Entry; +typedef struct BACnet_IP_Broadcast_Distribution_Table_Entry { + /* true if valid entry - false if not */ + bool valid; + /* BACnet/IP address */ + BACNET_IP_ADDRESS dest_address; + /* Broadcast Distribution Mask */ + BACNET_IP_BROADCAST_DISTRIBUTION_MASK broadcast_mask; + struct BACnet_IP_Broadcast_Distribution_Table_Entry *next; +} BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY; +/* number of bytes in a B/IPv4 roadcast Distribution Table entry */ +#define BACNET_IP_BDT_ENTRY_SIZE (BIP_ADDRESS_MAX + BACNET_IP_BDT_MASK_SIZE) +/** @} */ + +/** + * Foreign Device Table (FDT) + * + * Each device that registers as a foreign device shall be placed + * in an entry in the BBMD's Foreign Device Table (FDT). Each + * entry shall consist of the 6-octet B/IP address of the registrant; + * the 2-octet Time-to-Live value supplied at the time of + * registration; and a 2-octet value representing the number of + * seconds remaining before the BBMD will purge the registrant's FDT + * entry if no re-registration occurs. This value will be initialized + * to the 2-octet Time-to-Live value supplied at the time of + * registration. + * @{ + */ +struct BACnet_IP_Foreign_Device_Table_Entry; +typedef struct BACnet_IP_Foreign_Device_Table_Entry { + bool valid; + /* BACnet/IP address */ + BACNET_IP_ADDRESS dest_address; + /* requested time-to-live value */ + uint16_t ttl_seconds; + /* our counter - includes 30 second grace period */ + uint16_t ttl_seconds_remaining; + struct BACnet_IP_Foreign_Device_Table_Entry *next; +} BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY; +#define BACNET_IP_FDT_ENTRY_SIZE 10 +/** @} */ #ifdef __cplusplus extern "C" { - #endif /* __cplusplus */ -#if defined(BBMD_ENABLED) && BBMD_ENABLED BACNET_STACK_EXPORT - void bvlc_maintenance_timer( - time_t seconds); -#else -#define bvlc_maintenance_timer(x) + int bvlc_encode_address( + uint8_t *pdu, uint16_t pdu_size, const BACNET_IP_ADDRESS *ip_address); + + BACNET_STACK_EXPORT + int bvlc_decode_address( + uint8_t *pdu, uint16_t pdu_len, BACNET_IP_ADDRESS *ip_address); + + BACNET_STACK_EXPORT + bool bvlc_address_copy(BACNET_IP_ADDRESS *dst, const BACNET_IP_ADDRESS *src); + + BACNET_STACK_EXPORT + bool bvlc_address_different( + const BACNET_IP_ADDRESS *dst, const BACNET_IP_ADDRESS *src); + + BACNET_STACK_EXPORT + bool bvlc_address_from_ascii(BACNET_IP_ADDRESS *dst, const char *addrstr); + + BACNET_STACK_EXPORT + bool bvlc_address_port_from_ascii( + BACNET_IP_ADDRESS *dst, const char *addrstr, const char *portstr); + + BACNET_STACK_EXPORT + void bvlc_address_from_network(BACNET_IP_ADDRESS *dst, uint32_t addr); + + BACNET_STACK_EXPORT + bool bvlc_address_set(BACNET_IP_ADDRESS *addr, + uint8_t addr0, + uint8_t addr1, + uint8_t addr2, + uint8_t addr3); + + BACNET_STACK_EXPORT + bool bvlc_address_get(BACNET_IP_ADDRESS *addr, + uint8_t *addr0, + uint8_t *addr1, + uint8_t *addr2, + uint8_t *addr3); + + BACNET_STACK_EXPORT + bool bvlc_ip_address_to_bacnet_local( + BACNET_ADDRESS *addr, BACNET_IP_ADDRESS *ipaddr); + + BACNET_STACK_EXPORT + bool bvlc_ip_address_from_bacnet_local( + BACNET_IP_ADDRESS *ipaddr, BACNET_ADDRESS *addr); + + BACNET_STACK_EXPORT + bool bvlc_ip_address_to_bacnet_remote( + BACNET_ADDRESS *addr, uint16_t dnet, BACNET_IP_ADDRESS *ipaddr); + + BACNET_STACK_EXPORT + bool bvlc_ip_address_from_bacnet_remote( + BACNET_IP_ADDRESS *ipaddr, uint16_t *dnet, BACNET_ADDRESS *addr); + + BACNET_STACK_EXPORT + int bvlc_encode_broadcast_distribution_mask(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *bd_mask); + + BACNET_STACK_EXPORT + int bvlc_decode_broadcast_distribution_mask(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *bd_mask); + + BACNET_STACK_EXPORT + int bvlc_encode_broadcast_distribution_table_entry(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry); + + BACNET_STACK_EXPORT + int bvlc_decode_broadcast_distribution_table_entry(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry); + + BACNET_STACK_EXPORT + void bvlc_broadcast_distribution_table_link_array( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list, + const size_t bdt_array_size); + + BACNET_STACK_EXPORT + uint16_t bvlc_broadcast_distribution_table_count( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list); + + BACNET_STACK_EXPORT + uint16_t bvlc_broadcast_distribution_table_valid_count( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list); + + BACNET_STACK_EXPORT + void bvlc_broadcast_distribution_table_valid_clear( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list); + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_table_entry_different( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *dst, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *src); + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_table_entry_copy( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *dst, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *src); + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_mask_different( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *dst, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *src); + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_mask_copy( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *dst, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *src); + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_table_entry_append( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry); + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_table_entry_set( + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry, + BACNET_IP_ADDRESS *addr, + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask); + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_table_entry_forward_address( + BACNET_IP_ADDRESS *addr, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_entry); + + BACNET_STACK_EXPORT + bool bvlc_address_mask( + BACNET_IP_ADDRESS *dst, const BACNET_IP_ADDRESS *src, + const BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask); + + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_mask_from_host( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask, uint32_t broadcast_mask); + + BACNET_STACK_EXPORT + bool bvlc_broadcast_distribution_mask_to_host( + uint32_t *broadcast_mask, BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask); + + BACNET_STACK_EXPORT + void bvlc_broadcast_distribution_mask_set( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask, + uint8_t addr0, + uint8_t addr1, + uint8_t addr2, + uint8_t addr3); + + BACNET_STACK_EXPORT + void bvlc_broadcast_distribution_mask_get( + BACNET_IP_BROADCAST_DISTRIBUTION_MASK *mask, + uint8_t *addr0, + uint8_t *addr1, + uint8_t *addr2, + uint8_t *addr3); + + BACNET_STACK_EXPORT + int bvlc_encode_write_broadcast_distribution_table(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list); + + BACNET_STACK_EXPORT + int bvlc_decode_write_broadcast_distribution_table(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list); + + BACNET_STACK_EXPORT + int bvlc_encode_read_broadcast_distribution_table( + uint8_t *pdu, uint16_t pdu_size); + + BACNET_STACK_EXPORT + int bvlc_encode_read_broadcast_distribution_table_ack(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list); + + BACNET_STACK_EXPORT + int bvlc_decode_read_broadcast_distribution_table_ack(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY *bdt_list); + + BACNET_STACK_EXPORT + int bvlc_encode_header( + uint8_t *pdu, uint16_t pdu_size, uint8_t message_type, uint16_t length); + + BACNET_STACK_EXPORT + int bvlc_decode_header( + uint8_t *pdu, uint16_t pdu_len, uint8_t *message_type, uint16_t *length); + + + BACNET_STACK_EXPORT + void bvlc_foreign_device_table_maintenance_timer( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, uint16_t seconds); + + BACNET_STACK_EXPORT + uint16_t bvlc_foreign_device_table_valid_count( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list); + + BACNET_STACK_EXPORT + uint16_t bvlc_foreign_device_table_count( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list); + + BACNET_STACK_EXPORT + void bvlc_foreign_device_table_link_array( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, const size_t array_size); + + BACNET_STACK_EXPORT + bool bvlc_foreign_device_table_entry_different( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *dst, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *src); + + BACNET_STACK_EXPORT + bool bvlc_foreign_device_table_entry_copy( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *dst, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *src); + + BACNET_STACK_EXPORT + bool bvlc_foreign_device_table_entry_delete( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, + BACNET_IP_ADDRESS *ip_address); + + BACNET_STACK_EXPORT + bool bvlc_foreign_device_table_entry_add( + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list, + BACNET_IP_ADDRESS *ip_address, + uint16_t ttl_seconds); + + BACNET_STACK_EXPORT + int bvlc_encode_foreign_device_table_entry(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry); + + BACNET_STACK_EXPORT + int bvlc_decode_foreign_device_table_entry(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_entry); + + BACNET_STACK_EXPORT + int bvlc_encode_read_foreign_device_table(uint8_t *pdu, uint16_t pdu_size); + + BACNET_STACK_EXPORT + int bvlc_encode_read_foreign_device_table_ack(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list); + + BACNET_STACK_EXPORT + int bvlc_decode_read_foreign_device_table_ack(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_FOREIGN_DEVICE_TABLE_ENTRY *fdt_list); + + BACNET_STACK_EXPORT + int bvlc_encode_result(uint8_t *pdu, uint16_t pdu_size, uint16_t result_code); + + BACNET_STACK_EXPORT + int bvlc_decode_result(uint8_t *pdu, uint16_t pdu_len, uint16_t *result_code); + + BACNET_STACK_EXPORT + int bvlc_encode_original_unicast( + uint8_t *pdu, uint16_t pdu_size, uint8_t *npdu, uint16_t npdu_len); + + BACNET_STACK_EXPORT + int bvlc_decode_original_unicast(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *npdu, + uint16_t npdu_size, + uint16_t *npdu_len); + + BACNET_STACK_EXPORT + int bvlc_encode_original_broadcast( + uint8_t *pdu, uint16_t pdu_size, uint8_t *npdu, uint16_t npdu_len); + + BACNET_STACK_EXPORT + int bvlc_decode_original_broadcast(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *npdu, + uint16_t npdu_size, + uint16_t *npdu_len); + + BACNET_STACK_EXPORT + int bvlc_encode_forwarded_npdu(uint8_t *pdu, + uint16_t pdu_size, + BACNET_IP_ADDRESS *address, + uint8_t *npdu, + uint16_t npdu_len); + + BACNET_STACK_EXPORT + int bvlc_decode_forwarded_npdu(uint8_t *pdu, + uint16_t pdu_len, + BACNET_IP_ADDRESS *address, + uint8_t *npdu, + uint16_t npdu_size, + uint16_t *npdu_len); + + BACNET_STACK_EXPORT + int bvlc_encode_register_foreign_device( + uint8_t *pdu, uint16_t pdu_size, uint16_t ttl_seconds); + + BACNET_STACK_EXPORT + int bvlc_decode_register_foreign_device( + uint8_t *pdu, uint16_t pdu_len, uint16_t *ttl_seconds); + + BACNET_STACK_EXPORT + int bvlc_encode_delete_foreign_device( + uint8_t *pdu, uint16_t pdu_size, BACNET_IP_ADDRESS *ip_address); + + BACNET_STACK_EXPORT + int bvlc_decode_delete_foreign_device( + uint8_t *pdu, uint16_t pdu_len, BACNET_IP_ADDRESS *ip_address); + + BACNET_STACK_EXPORT + int bvlc_encode_secure_bvll( + uint8_t *pdu, uint16_t pdu_size, uint8_t *sbuf, uint16_t sbuf_len); + + BACNET_STACK_EXPORT + int bvlc_decode_secure_bvll(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *sbuf, + uint16_t sbuf_size, + uint16_t *sbuf_len); + + BACNET_STACK_EXPORT + int bvlc_encode_distribute_broadcast_to_network( + uint8_t *pdu, uint16_t pdu_size, uint8_t *npdu, uint16_t npdu_len); + + BACNET_STACK_EXPORT + int bvlc_decode_distribute_broadcast_to_network(uint8_t *pdu, + uint16_t pdu_len, + uint8_t *npdu, + uint16_t npdu_size, + uint16_t *npdu_len); + +#ifdef TEST +#include "ctest.h" + BACNET_STACK_EXPORT + void test_BVLC(Test *pTest); #endif - typedef struct { - /* true if valid entry - false if not */ - bool valid; - /* BACnet/IP address */ - struct in_addr dest_address; /* in network format */ - /* BACnet/IP port number - not always 47808=BAC0h */ - uint16_t dest_port; /* in network format */ - /* Broadcast Distribution Mask */ - struct in_addr broadcast_mask; /* in tework format */ - } BBMD_TABLE_ENTRY; - - BACNET_STACK_EXPORT - uint16_t bvlc_receive( - BACNET_ADDRESS * src, /* returns the source address */ - uint8_t * npdu, /* returns the NPDU */ - uint16_t max_npdu, /* amount of space available in the NPDU */ - unsigned timeout); /* number of milliseconds to wait for a packet */ - - BACNET_STACK_EXPORT - int bvlc_send_pdu( - BACNET_ADDRESS * dest, /* destination address */ - BACNET_NPDU_DATA * npdu_data, /* network information */ - uint8_t * pdu, /* any data to be sent - may be null */ - unsigned pdu_len); - - BACNET_STACK_EXPORT - int bvlc_send_mpdu( - struct sockaddr_in *dest, - uint8_t * mtu, - uint16_t mtu_len); - -#if defined(BBMD_CLIENT_ENABLED) && BBMD_CLIENT_ENABLED - BACNET_STACK_EXPORT - int bvlc_encode_write_bdt_init( - uint8_t * pdu, - unsigned entries); - BACNET_STACK_EXPORT - int bvlc_encode_read_fdt( - uint8_t * pdu); - BACNET_STACK_EXPORT - int bvlc_encode_delete_fdt_entry( - uint8_t * pdu, - uint32_t address, /* in network byte order */ - uint16_t port); /* in network byte order */ - BACNET_STACK_EXPORT - int bvlc_encode_original_unicast_npdu( - uint8_t * pdu, - uint8_t * npdu, - unsigned npdu_length); - BACNET_STACK_EXPORT - int bvlc_encode_original_broadcast_npdu( - uint8_t * pdu, - uint8_t * npdu, - unsigned npdu_length); -#endif - BACNET_STACK_EXPORT - int bvlc_encode_read_bdt( - uint8_t * pdu); - BACNET_STACK_EXPORT - int bvlc_bbmd_read_bdt( - uint32_t bbmd_address, - uint16_t bbmd_port); - - /* registers with a bbmd as a foreign device */ - BACNET_STACK_EXPORT - int bvlc_register_with_bbmd( - uint32_t bbmd_address, /* in network byte order */ - uint16_t bbmd_port, /* in network byte order */ - uint16_t time_to_live_seconds); - - /* Note any BVLC_RESULT code, or NAK the BVLL message in the unsupported cases. */ - BACNET_STACK_EXPORT - int bvlc_for_non_bbmd( - struct sockaddr_in *sout, - uint8_t * npdu, - uint16_t received_bytes); - - /* Returns the last BVLL Result we received, either as the result of a BBMD - * request we sent, or (if not a BBMD or Client), from trying to register - * as a foreign device. */ - BACNET_STACK_EXPORT - BACNET_BVLC_RESULT bvlc_get_last_result( - void); - - /* Returns the current BVLL Function Code we are processing. - * We have to store this higher layer code for when the lower layers - * need to know what it is, especially to differentiate between - * BVLC_ORIGINAL_UNICAST_NPDU and BVLC_ORIGINAL_BROADCAST_NPDU. */ - BACNET_STACK_EXPORT - BACNET_BVLC_FUNCTION bvlc_get_function_code( - void); - - - /* Local interface to manage BBMD. - * The interface user needs to handle mutual exclusion if needed i.e. - * BACnet packet is not being handled when the BBMD table is modified. - */ - - /* Get handle to broadcast distribution table. Returns the number of - * valid entries in the table. */ - BACNET_STACK_EXPORT - int bvlc_get_bdt_local( - const BBMD_TABLE_ENTRY** table); - - /* Invalidate all entries in the broadcast distribution table */ - BACNET_STACK_EXPORT - void bvlc_clear_bdt_local(void); - - /* Add new entry to broadcast distribution table. Returns true if the new - * entry was added successfully */ - BACNET_STACK_EXPORT - bool bvlc_add_bdt_entry_local( - BBMD_TABLE_ENTRY* entry); - - /* Backup broadcast distribution table to a file. - * Filename is the BBMD_BACKUP_FILE constant - */ - BACNET_STACK_EXPORT - void bvlc_bdt_backup_local( - void); - - /* Restore broadcast distribution from a file. - * Filename is the BBMD_BACKUP_FILE constant - */ - BACNET_STACK_EXPORT - void bvlc_bdt_restore_local( - void); - - /* NAT handling - * If the communication between BBMDs goes through a NAT enabled internet - * router, special considerations are needed as stated in Annex J.7.8. - * - * In short, the local IP address of the BBMD is different than the global - * address which is visible to the other BBMDs or foreign devices. This is - * why the source address in forwarded messages needs to be changed to the - * global IP address. - * - * For other considerations/limitations see Annex J.7.8. - */ - - /* Set global IP address of a NAT enabled router which is used in forwarded - * messages. Enables NAT handling. - */ - BACNET_STACK_EXPORT - void bvlc_set_global_address_for_nat(const struct in_addr* addr); - - /* Disable NAT handling of BBMD. - */ - BACNET_STACK_EXPORT - void bvlc_disable_nat(void); - #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/bacnet/datalink/bvlc6.h b/src/bacnet/datalink/bvlc6.h index 75cfeb18..df0cccc4 100644 --- a/src/bacnet/datalink/bvlc6.h +++ b/src/bacnet/datalink/bvlc6.h @@ -47,12 +47,12 @@ * BVLC Result Code * @{ */ -#define BVLC6_RESULT_SUCCESSFUL_COMPLETION 0x0000 -#define BVLC6_RESULT_ADDRESS_RESOLUTION_NAK 0x0030 -#define BVLC6_RESULT_VIRTUAL_ADDRESS_RESOLUTION_NAK 0x0060 -#define BVLC6_RESULT_REGISTER_FOREIGN_DEVICE_NAK 0x0090 -#define BVLC6_RESULT_DELETE_FOREIGN_DEVICE_NAK 0x00A0 -#define BVLC6_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK 0x00C0 +#define BVLC6_RESULT_SUCCESSFUL_COMPLETION 0x0000U +#define BVLC6_RESULT_ADDRESS_RESOLUTION_NAK 0x0030U +#define BVLC6_RESULT_VIRTUAL_ADDRESS_RESOLUTION_NAK 0x0060U +#define BVLC6_RESULT_REGISTER_FOREIGN_DEVICE_NAK 0x0090U +#define BVLC6_RESULT_DELETE_FOREIGN_DEVICE_NAK 0x00A0U +#define BVLC6_RESULT_DISTRIBUTE_BROADCAST_TO_NETWORK_NAK 0x00C0U /** @} */ /** @@ -71,21 +71,21 @@ * defined for B/IPv6. * @{ */ -#define BIP6_MULTICAST_GROUP_ID 0xBAC0 +#define BIP6_MULTICAST_GROUP_ID 0xBAC0U /** @} */ /** * IANA prefixes * @{ */ -#define BIP6_MULTICAST_reserved_0 0xFF00 -#define BIP6_MULTICAST_NODE_LOCAL 0xFF01 -#define BIP6_MULTICAST_LINK_LOCAL 0xFF02 -#define BIP6_MULTICAST_reserved_3 0xFF03 -#define BIP6_MULTICAST_ADMIN_LOCAL 0xFF04 -#define BIP6_MULTICAST_SITE_LOCAL 0xFF05 -#define BIP6_MULTICAST_ORG_LOCAL 0xFF08 -#define BIP6_MULTICAST_GLOBAL 0xFF0E +#define BIP6_MULTICAST_reserved_0 0xFF00U +#define BIP6_MULTICAST_NODE_LOCAL 0xFF01U +#define BIP6_MULTICAST_LINK_LOCAL 0xFF02U +#define BIP6_MULTICAST_reserved_3 0xFF03U +#define BIP6_MULTICAST_ADMIN_LOCAL 0xFF04U +#define BIP6_MULTICAST_SITE_LOCAL 0xFF05U +#define BIP6_MULTICAST_ORG_LOCAL 0xFF08U +#define BIP6_MULTICAST_GLOBAL 0xFF0EU /** @} */ /* number of bytes in the IPv6 address */ @@ -379,6 +379,7 @@ extern "C" { uint16_t pdu_size, uint32_t vmac_src, BACNET_IP6_ADDRESS *bip6_address); + BACNET_STACK_EXPORT int bvlc6_decode_delete_foreign_device( uint8_t * pdu, diff --git a/src/bacnet/datalink/datalink.c b/src/bacnet/datalink/datalink.c index 5c75ea7b..4b9c5fcc 100644 --- a/src/bacnet/datalink/datalink.c +++ b/src/bacnet/datalink/datalink.c @@ -173,4 +173,9 @@ void datalink_set_interface(char *ifname) void datalink_set(char *datalink_string) { } + +void datalink_maintenance_timer(uint16_t seconds) +{ + (void)seconds; +} #endif diff --git a/src/bacnet/datalink/datalink.h b/src/bacnet/datalink/datalink.h index b115253f..ceae524a 100644 --- a/src/bacnet/datalink/datalink.h +++ b/src/bacnet/datalink/datalink.h @@ -37,6 +37,7 @@ #define datalink_cleanup ethernet_cleanup #define datalink_get_broadcast_address ethernet_get_broadcast_address #define datalink_get_my_address ethernet_get_my_address +#define datalink_maintenance_timer(s) #elif defined(BACDL_ARCNET) #include "bacnet/datalink/arcnet.h" @@ -47,6 +48,7 @@ #define datalink_cleanup arcnet_cleanup #define datalink_get_broadcast_address arcnet_get_broadcast_address #define datalink_get_my_address arcnet_get_my_address +#define datalink_maintenance_timer(s) #elif defined(BACDL_MSTP) #include "bacnet/datalink/dlmstp.h" @@ -57,19 +59,16 @@ #define datalink_cleanup dlmstp_cleanup #define datalink_get_broadcast_address dlmstp_get_broadcast_address #define datalink_get_my_address dlmstp_get_my_address +#define datalink_maintenance_timer(s) #elif defined(BACDL_BIP) #include "bacnet/datalink/bip.h" #include "bacnet/datalink/bvlc.h" +#include "bacnet/basic/bbmd/h_bbmd.h" #define datalink_init bip_init -#if defined(BBMD_ENABLED) && BBMD_ENABLED -#define datalink_send_pdu bvlc_send_pdu -#define datalink_receive bvlc_receive -#else #define datalink_send_pdu bip_send_pdu #define datalink_receive bip_receive -#endif #define datalink_cleanup bip_cleanup #define datalink_get_broadcast_address bip_get_broadcast_address #ifdef BAC_ROUTING @@ -80,6 +79,7 @@ void routed_get_my_address( #else #define datalink_get_my_address bip_get_my_address #endif +#define datalink_maintenance_timer(s) bvlc_maintenance_timer(s) #elif defined(BACDL_BIP6) #include "bacnet/datalink/bip6.h" @@ -90,6 +90,7 @@ void routed_get_my_address( #define datalink_cleanup bip6_cleanup #define datalink_get_broadcast_address bip6_get_broadcast_address #define datalink_get_my_address bip6_get_my_address +#define datalink_maintenance_timer(s) bvlc6_maintenance_timer(s) #elif defined(BACDL_ALL) || defined(BACDL_NONE) #include "bacnet/npdu.h" @@ -107,28 +108,37 @@ extern "C" { BACNET_NPDU_DATA * npdu_data, uint8_t * pdu, unsigned pdu_len); + BACNET_STACK_EXPORT uint16_t datalink_receive( BACNET_ADDRESS * src, uint8_t * pdu, uint16_t max_pdu, unsigned timeout); + BACNET_STACK_EXPORT void datalink_cleanup( void); + BACNET_STACK_EXPORT void datalink_get_broadcast_address( BACNET_ADDRESS * dest); + BACNET_STACK_EXPORT void datalink_get_my_address( BACNET_ADDRESS * my_address); + BACNET_STACK_EXPORT void datalink_set_interface( char *ifname); + BACNET_STACK_EXPORT void datalink_set( char *datalink_string); + BACNET_STACK_EXPORT + void datalink_maintenance_timer(uint16_t seconds); + #ifdef __cplusplus } #endif /* __cplusplus */ @@ -149,9 +159,11 @@ extern "C" { * - BACDL_ETHERNET -- for Clause 7 ISO 8802-3 ("Ethernet") LAN * - BACDL_ARCNET -- for Clause 8 ARCNET LAN * - BACDL_MSTP -- for Clause 9 MASTER-SLAVE/TOKEN PASSING (MS/TP) LAN - * - BACDL_BIP -- for ANNEX J - BACnet/IP + * - BACDL_BIP -- for ANNEX J - BACnet/IPv4 + * - BACDL_BIP6 -- for ANNEX U - BACnet/IPv6 * - BACDL_ALL -- Unspecified for the build, so the transport can be * chosen at runtime from among these choices. + * - BACDL_NONE -- Unspecified for the build for unit testing * - Clause 10 POINT-TO-POINT (PTP) and Clause 11 EIA/CEA-709.1 ("LonTalk") LAN * are not currently supported by this project. *//** @defgroup DLTemplates DataLink Template Functions diff --git a/src/bacnet/datalink/dlenv.c b/src/bacnet/datalink/dlenv.c index 67c920cc..e35d12e3 100644 --- a/src/bacnet/datalink/dlenv.c +++ b/src/bacnet/datalink/dlenv.c @@ -39,53 +39,44 @@ #include "bacnet/basic/object/netport.h" #endif -#if defined(BACDL_BIP6) -#include "bacnet/datalink/bvlc6.h" -#include "bacnet/datalink/bip6.h" -#endif - /** @file dlenv.c Initialize the DataLink configuration. */ - #if defined(BACDL_BIP) /* timer used to renew Foreign Device Registration */ static uint16_t BBMD_Timer_Seconds; /* BBMD variables */ -static long bbmd_timetolive_seconds = 60000; -static long bbmd_port = 0xBAC0; -static long bbmd_address = 0; -static long bbmd_mask = 0xFFFFFFFF; -static int bbmd_result = 0; -static BBMD_TABLE_ENTRY BBMD_Table_Entry; +static uint16_t BBMD_TTL_Seconds = 60000; +static BACNET_IP_ADDRESS BBMD_Address; +static bool BBMD_Address_Valid; +static uint16_t BBMD_Result = 0; +static BACNET_IP_BROADCAST_DISTRIBUTION_TABLE_ENTRY BBMD_Table_Entry; +/* enable debugging */ +static bool BIP_DL_Debug = false; /* Simple setters for BBMD registration variables. */ -/** Sets the IPv4 address for BBMD registration. - * If not set here or provided by Environment variables, - * no BBMD registration will occur. - * @param address - IPv4 address (long) of BBMD to register with, - * in network byte order. +/** + * @brief Sets the IPv4 address for BBMD registration. + * + * If not set here or provided by Environment variables, + * no BBMD registration will occur. + * + * @param address - IPv4 address (uint32_t) of BBMD to register with, + * in network byte order. */ -void dlenv_bbmd_address_set(long address) +void dlenv_bbmd_address_set( + BACNET_IP_ADDRESS *address) { - bbmd_address = address; -} - -/** Set the port for BBMD registration. - * Default if not set is 0xBAC0. - * @param port - The port number (provided in network byte order). - */ -void dlenv_bbmd_port_set(int port) -{ - bbmd_port = port; + bvlc_address_copy(&BBMD_Address, address); + BBMD_Address_Valid = true; } /** Set the Lease Time (Time-to-Live) for BBMD registration. * Default if not set is 60000 (1000 minutes). * @param ttl_secs - The Lease Time, in seconds. */ -void dlenv_bbmd_ttl_set(int ttl_secs) +void dlenv_bbmd_ttl_set(uint16_t ttl_secs) { - bbmd_timetolive_seconds = ttl_secs; + BBMD_TTL_Seconds = ttl_secs; } /** Get the result of the last attempt to register with the indicated BBMD. @@ -98,12 +89,12 @@ void dlenv_bbmd_ttl_set(int ttl_secs) */ int dlenv_bbmd_result(void) { - if ((bbmd_result > 0) && + if ((BBMD_Result > 0) && (bvlc_get_last_result() == BVLC_RESULT_REGISTER_FOREIGN_DEVICE_NAK)) { return -1; } /* Else, show our send: */ - return bbmd_result; + return BBMD_Result; } #endif @@ -125,86 +116,124 @@ int dlenv_register_as_foreign_device(void) { int retval = 0; #if defined(BACDL_BIP) + bool bdt_entry_valid = false; + uint16_t bdt_entry_port = 0; char *pEnv = NULL; unsigned a[4] = { 0 }; char bbmd_env[32] = ""; - unsigned entry_number = 0; + unsigned entry_number = 0;\ + long long_value = 0; int c; pEnv = getenv("BACNET_BBMD_PORT"); if (pEnv) { - bbmd_port = strtol(pEnv, NULL, 0); - if (bbmd_port > 0xFFFF) { - bbmd_port = 0xBAC0; + long_value = strtol(pEnv, NULL, 0); + if (long_value <= 0xFFFF) { + BBMD_Address.port = (uint16_t)long_value; + } else { + BBMD_Address.port = 0xBAC0; } } pEnv = getenv("BACNET_BBMD_TIMETOLIVE"); if (pEnv) { - bbmd_timetolive_seconds = strtol(pEnv, NULL, 0); - if (bbmd_timetolive_seconds > 0xFFFF) { - bbmd_timetolive_seconds = 0xFFFF; + long_value = strtol(pEnv, NULL, 0); + if (long_value <= 0xFFFF) { + BBMD_TTL_Seconds = (uint16_t)long_value; + } else { + BBMD_TTL_Seconds = 0xFFFF; } } pEnv = getenv("BACNET_BBMD_ADDRESS"); if (pEnv) { - bbmd_address = bip_getaddrbyname(pEnv); + BBMD_Address_Valid = bip_get_addr_by_name(pEnv, &BBMD_Address); } - if (bbmd_address) { - struct in_addr addr; - addr.s_addr = bbmd_address; - fprintf(stderr, "Registering with BBMD at %s:%ld for %ld seconds\n", - inet_ntoa(addr), bbmd_port, bbmd_timetolive_seconds); - retval = bvlc_register_with_bbmd(bbmd_address, - htons((uint16_t)bbmd_port), (uint16_t)bbmd_timetolive_seconds); - if (retval < 0) { - fprintf(stderr, "FAILED to Register with BBMD at %s \n", - inet_ntoa(addr)); + if (BBMD_Address_Valid) { + if (BIP_DL_Debug) { + fprintf(stderr, + "Registering with BBMD at %u.%u.%u.%u:%u for %u seconds\n", + (unsigned)BBMD_Address.address[0], + (unsigned)BBMD_Address.address[1], + (unsigned)BBMD_Address.address[2], + (unsigned)BBMD_Address.address[3], + (unsigned)BBMD_Address.port, + (unsigned)BBMD_TTL_Seconds); } - BBMD_Timer_Seconds = (uint16_t)bbmd_timetolive_seconds; + retval = bvlc_register_with_bbmd(&BBMD_Address, + BBMD_TTL_Seconds); + if (retval < 0) { + fprintf(stderr, + "FAILED to Register with BBMD at %u.%u.%u.%u:%u\n", + (unsigned)BBMD_Address.address[0], + (unsigned)BBMD_Address.address[1], + (unsigned)BBMD_Address.address[2], + (unsigned)BBMD_Address.address[3], + (unsigned)BBMD_Address.port); + } + BBMD_Timer_Seconds = (uint16_t)BBMD_TTL_Seconds; } else { for (entry_number = 1; entry_number <= 128; entry_number++) { + bdt_entry_valid = false; sprintf(bbmd_env, "BACNET_BDT_ADDR_%u", entry_number); pEnv = getenv(bbmd_env); if (pEnv) { - bbmd_address = bip_getaddrbyname(pEnv); + bdt_entry_valid = bip_get_addr_by_name(pEnv, + &BBMD_Table_Entry.dest_address); + if (entry_number == 1) { + if (BIP_DL_Debug) { + fprintf(stderr, "BBMD 1 is %s=%s!\n", bbmd_env, pEnv); + } + } } else if (entry_number == 1) { /* BDT 1 is self (note: can be overridden) */ - bbmd_address = bip_get_addr(); + bdt_entry_valid = bip_get_addr(&BBMD_Table_Entry.dest_address); } - if (bbmd_address) { - bbmd_port = 0xBAC0; - sprintf(bbmd_env, "BACNET_BDT_PORT_%u", entry_number); - pEnv = getenv(bbmd_env); - if (pEnv) { - bbmd_port = strtol(pEnv, NULL, 0); - if (bbmd_port > 0xFFFF) { - bbmd_port = 0xBAC0; + if (bdt_entry_valid) { + if (entry_number != 1) { + bdt_entry_port = 0xBAC0; + sprintf(bbmd_env, "BACNET_BDT_PORT_%u", entry_number); + pEnv = getenv(bbmd_env); + if (pEnv) { + bdt_entry_port = strtol(pEnv, NULL, 0); + if (bdt_entry_port > 0xFFFF) { + bdt_entry_port = 0xBAC0; + } + /* BDT 1 is self (note: can be overridden) */ } - } else if (entry_number == 1) { - /* BDT 1 is self (note: can be overridden) */ - bbmd_port = bip_get_port(); + BBMD_Table_Entry.dest_address.port = bdt_entry_port; } - bbmd_mask = 0xFFFFFFFF; + /* broadcast mask */ + bvlc_broadcast_distribution_mask_from_host( + &BBMD_Table_Entry.broadcast_mask, 0xFFFFFFFF); sprintf(bbmd_env, "BACNET_BDT_MASK_%u", entry_number); pEnv = getenv(bbmd_env); if (pEnv) { c = sscanf( pEnv, "%3u.%3u.%3u.%3u", &a[0], &a[1], &a[2], &a[3]); if (c == 4) { - bbmd_mask = ((a[0] & 0xFF) << 24) | - ((a[1] & 0xFF) << 16) | ((a[2] & 0xFF) << 8) | - (a[3] & 0xFF); + bvlc_broadcast_distribution_mask_set( + &BBMD_Table_Entry.broadcast_mask, + a[0], a[1], a[2], a[3]); } } - BBMD_Table_Entry.valid = true; - BBMD_Table_Entry.dest_address.s_addr = bbmd_address; - BBMD_Table_Entry.dest_port = bbmd_port; - BBMD_Table_Entry.broadcast_mask.s_addr = bbmd_mask; - bvlc_add_bdt_entry_local(&BBMD_Table_Entry); + bvlc_broadcast_distribution_table_entry_append( + bvlc_bdt_list(), &BBMD_Table_Entry); + if (BIP_DL_Debug) { + fprintf(stderr, + "BBMD %4u: %u.%u.%u.%u:%u %u.%u.%u.%u\n", entry_number, + (unsigned)BBMD_Table_Entry.dest_address.address[0], + (unsigned)BBMD_Table_Entry.dest_address.address[1], + (unsigned)BBMD_Table_Entry.dest_address.address[2], + (unsigned)BBMD_Table_Entry.dest_address.address[3], + (unsigned)BBMD_Table_Entry.dest_address.port, + (unsigned)BBMD_Table_Entry.broadcast_mask.address[0], + (unsigned)BBMD_Table_Entry.broadcast_mask.address[1], + (unsigned)BBMD_Table_Entry.broadcast_mask.address[2], + (unsigned)BBMD_Table_Entry.broadcast_mask.address[3]); + } } } } - bbmd_result = retval; + BBMD_Result = retval; #endif return retval; } @@ -216,34 +245,16 @@ int dlenv_register_as_foreign_device(void) */ static void dlenv_network_port_init(void) { - uint32_t instance = 1; - uint32_t address = 0; - uint32_t broadcast = 0; - uint32_t test_broadcast = 0; - uint32_t mask = 0xFFFFFFFE; - uint16_t port = 0; - uint8_t mac[4 + 2] = { 0 }; - uint8_t prefix = 0; + const uint32_t instance = 1; + BACNET_IP_ADDRESS addr = { 0 }; Network_Port_Object_Instance_Number_Set(0, instance); Network_Port_Name_Set(instance, "BACnet/IP Port"); Network_Port_Type_Set(instance, PORT_TYPE_BIP); - port = bip_get_port(); - Network_Port_BIP_Port_Set(instance, port); - address = bip_get_addr(); - memcpy(&mac[0], &address, 4); - memcpy(&mac[4], &port, 2); - Network_Port_MAC_Address_Set(instance, &mac[0], 6); - broadcast = bip_get_broadcast_addr(); - /* calculate the subnet prefix from the broadcast address */ - for (prefix = 1; prefix <= 32; prefix++) { - test_broadcast = (address & mask) | (~mask); - if (test_broadcast == broadcast) { - break; - } - mask = mask<<1; - } - Network_Port_IP_Subnet_Prefix_Set(instance, prefix); + bip_get_addr(&addr); + Network_Port_BIP_Port_Set(instance, addr.port); + Network_Port_MAC_Address_Set(instance, &addr.address[0], 6); + Network_Port_IP_Subnet_Prefix_Set(instance, bip_get_subnet_prefix()); Network_Port_Link_Speed_Set(instance, 0.0); /* common NP data */ Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED); @@ -346,7 +357,7 @@ void dlenv_maintenance_timer(uint16_t elapsed_seconds) /* If that failed (negative), maybe just a network issue. * If nothing happened (0), may be un/misconfigured. * Set up to try again later in all cases. */ - BBMD_Timer_Seconds = (uint16_t)bbmd_timetolive_seconds; + BBMD_Timer_Seconds = (uint16_t)BBMD_TTL_Seconds; } } #endif @@ -434,12 +445,15 @@ void dlenv_init(void) } #endif #if defined(BACDL_BIP) -#if defined(BIP_DEBUG) - BIP_Debug = true; -#endif + pEnv = getenv("BACNET_IP_DEBUG"); + if (pEnv) { + bip_debug_enable(); + bvlc_debug_enable(); + BIP_DL_Debug = true; + } pEnv = getenv("BACNET_IP_PORT"); if (pEnv) { - bip_set_port(htons((uint16_t)strtol(pEnv, NULL, 0))); + bip_set_port((uint16_t)strtol(pEnv, NULL, 0)); } else { /* BIP_Port is statically initialized to 0xBAC0, * so if it is different, then it was programmatically altered, @@ -447,16 +461,15 @@ void dlenv_init(void) * Unless it is set below 1024, since: * "The range for well-known ports managed by the IANA is 0-1023." */ - if (ntohs(bip_get_port()) < 1024) { - bip_set_port(htons(0xBAC0)); + if (bip_get_port() < 1024) { + bip_set_port(0xBAC0); } } pEnv = getenv("BACNET_IP_NAT_ADDR"); if (pEnv) { - struct in_addr nat_addr; - nat_addr.s_addr = bip_getaddrbyname(pEnv); - if (nat_addr.s_addr) { - bvlc_set_global_address_for_nat(&nat_addr); + BACNET_IP_ADDRESS addr; + if (bip_get_addr_by_name(pEnv, &addr)) { + bvlc_set_global_address_for_nat(&addr); } } #elif defined(BACDL_MSTP) diff --git a/src/bacnet/datalink/dlenv.h b/src/bacnet/datalink/dlenv.h index 81318189..9f9e1e2f 100644 --- a/src/bacnet/datalink/dlenv.h +++ b/src/bacnet/datalink/dlenv.h @@ -24,6 +24,10 @@ *********************************************************************/ #ifndef DLENV_H #define DLENV_H +#include +#include +#include +#include "bacnet/datalink/bvlc.h" #include "bacnet/bacnet_stack_exports.h" @@ -34,23 +38,23 @@ extern "C" { BACNET_STACK_EXPORT void dlenv_init( void); + BACNET_STACK_EXPORT int dlenv_register_as_foreign_device( void); + BACNET_STACK_EXPORT void dlenv_maintenance_timer( uint16_t elapsed_seconds); - /* Simple setters and getter. */ BACNET_STACK_EXPORT void dlenv_bbmd_address_set( - long address); - BACNET_STACK_EXPORT - void dlenv_bbmd_port_set( - int port); + BACNET_IP_ADDRESS *address); + BACNET_STACK_EXPORT void dlenv_bbmd_ttl_set( - int ttl_secs); + uint16_t ttl_secs); + BACNET_STACK_EXPORT int dlenv_bbmd_result( void); diff --git a/test/Makefile b/test/Makefile index 127a822a..62f7fccf 100644 --- a/test/Makefile +++ b/test/Makefile @@ -65,6 +65,11 @@ bacstr: logfile bacstr.mak ( ./bacstr >> ${LOGFILE} ) $(MAKE) -s -f bacstr.mak clean +bvlc: logfile bvlc.mak + $(MAKE) -s -f bvlc.mak clean all + ( ./bvlc >> ${LOGFILE} ) + $(MAKE) -s -f bvlc.mak clean + bvlc6: logfile bvlc6.mak $(MAKE) -s -f bvlc6.mak clean all ( ./bvlc6 >> ${LOGFILE} ) diff --git a/test/bvlc.mak b/test/bvlc.mak index 31a858ea..2793ae2a 100644 --- a/test/bvlc.mak +++ b/test/bvlc.mak @@ -1,7 +1,7 @@ #Makefile to build test case CC = gcc SRC_DIR = ../src -INCLUDES = -I$(SRC_DIR) -I. -I../ports/linux +INCLUDES = -I$(SRC_DIR) -I. DEFINES = -DBACDL_BIP -DBIG_ENDIAN=0 -DTEST -DTEST_BVLC CFLAGS = -Wall $(INCLUDES) $(DEFINES) -g @@ -10,7 +10,7 @@ SRCS = $(SRC_DIR)/bacnet/bacdcode.c \ $(SRC_DIR)/bacnet/bacint.c \ $(SRC_DIR)/bacnet/bacstr.c \ $(SRC_DIR)/bacnet/bacreal.c \ - $(SRC_DIR)/bacnet/bvlc.c \ + $(SRC_DIR)/bacnet/datalink/bvlc.c \ ctest.c OBJS = ${SRCS:.c=.o} @@ -38,4 +38,7 @@ depend: clean: rm -rf core ${TARGET} $(OBJS) *.bak *.1 *.ini +test: ${TARGET} + ./${TARGET} + include: .depend