From eedfa58a553bc449023c5c5a6cd7bcde196b4d5d Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Sat, 4 Apr 2020 11:31:54 -0500 Subject: [PATCH] Feature/refactor bacnet ipv4 add unit tests (#64) * refactor BACnet/IPv4 BVLC into encode-decode library with unit tests Added Read-Broadcast-Distribution-Table encoding and unit test. Added Read-Broadcast-Distribution-Table-Ack encoding and unit test. Added Read-Foreign-Device-Table-Ack encoding and unit test. Added some BDT/FDT diff copy functions with unit tests Added some FDT add and delete entry functions with unit tests Added some BDT set append and clear entry functions with unit tests Added some BIPv4 address conversion functions with unit tests Added non-BBMD handling unit test Added basic unit test for BBMD handler Added BBMD broadcast mask get set and unit tests Added IPv6 maintenance timer. Added ReadFDT app Fixed ReadBDT app Added Who-Is to Makefile for individual app build Fixed debugging code blocks projects by swapping bip.c for h_bbmd.c module. Ported BACnet/IPv4 to refactored BVLC for Linux, BSD, Windows Fix datalink debug for DLENV module Improve BIPv4 linux driver debug info Added BDT mask functions Reduce debug info clutter in Who-Is app by using environment option Fix TTL seconds upper bounds addition Fix CIDR prefix calculation on Linux BIPv4. Convert BSD BIPv4 to BVLCv4 Fix CMake build for BIPv4 (Linux, BSD, Windows) Added [U]nsigned to 0xBAC0 constants Cleanup POSIX and Win32 API sockets Remove unnecessary file scope variable initialization Fix routed NPDU to depend on datalink; fix warning Remove OS dependent network code from gateway Enable BBMD client in library by default Co-authored-by: Steve Karg Co-authored-by: Steve Karg --- CMakeLists.txt | 6 +- Makefile | 12 + apps/Makefile | 21 +- apps/epics/main.c | 5 +- apps/gateway/main.c | 66 +- apps/initrouter/main.c | 4 +- apps/piface/main.c | 6 +- apps/readbdt/main.c | 16 +- apps/readfdt/Makefile | 47 + apps/readfdt/main.c | 190 + apps/router-ipv6/Makefile | 2 +- apps/router-ipv6/main.c | 10 +- apps/router/main.c | 4 +- apps/server/main.c | 6 +- apps/whois/main.c | 50 +- apps/whoisrouter/main.c | 4 +- ports/arduino_uno/bip-init.c | 10 +- ports/arduino_uno/bip.c | 4 +- ports/arduino_uno/bip.h | 7 +- ports/bdk-atxx4-mstp/nvdata.h | 2 +- ports/bsd/bip-init.c | 482 ++- ports/esp32/src/bip_init.c | 10 +- ports/linux/bacport.h | 7 +- ports/linux/bip-init.c | 550 ++- ports/linux/bip6.c | 2 +- ports/lwip/bip.c | 18 +- ports/uip/bip.c | 2 +- ports/win32/bacport.h | 10 +- ports/win32/bip-init.c | 810 +++-- ports/win32/bip6.c | 2 +- src/Makefile | 2 +- src/bacnet/bacenum.h | 27 - src/bacnet/basic/bbmd/h_bbmd.c | 1394 ++++++++ src/bacnet/basic/bbmd/h_bbmd.h | 122 + src/bacnet/basic/bbmd/h_bbmd.mak | 47 + src/bacnet/basic/bbmd6/h_bbmd6.c | 20 +- src/bacnet/basic/bbmd6/h_bbmd6.h | 9 + src/bacnet/basic/binding/address.c | 2 +- src/bacnet/basic/npdu/h_routed_npdu.c | 4 +- src/bacnet/basic/service/h_apdu.c | 2 +- src/bacnet/datalink/bip.c | 385 -- src/bacnet/datalink/bip.h | 166 +- src/bacnet/datalink/bvlc.c | 4628 ++++++++++++++++--------- src/bacnet/datalink/bvlc.h | 664 +++- src/bacnet/datalink/bvlc6.h | 31 +- src/bacnet/datalink/datalink.c | 5 + src/bacnet/datalink/datalink.h | 24 +- src/bacnet/datalink/dlenv.c | 231 +- src/bacnet/datalink/dlenv.h | 16 +- test/Makefile | 5 + test/bvlc.mak | 7 +- 51 files changed, 7178 insertions(+), 2978 deletions(-) create mode 100644 apps/readfdt/Makefile create mode 100644 apps/readfdt/main.c create mode 100644 src/bacnet/basic/bbmd/h_bbmd.c create mode 100644 src/bacnet/basic/bbmd/h_bbmd.h create mode 100644 src/bacnet/basic/bbmd/h_bbmd.mak delete mode 100644 src/bacnet/datalink/bip.c 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