From 1f41341c097af7b7403d5e620d4fd4bdc05b8ccc Mon Sep 17 00:00:00 2001 From: Steve Karg Date: Thu, 14 Jul 2022 15:34:45 -0500 Subject: [PATCH] Feature/what is network number handling (#304) * Add What-Is-Network-Number handling. Add What-Is-Network-Number and Network-Number-Is network layer handling. Refactor npdu_encode_npdu_network() from router specific code. Add unit test for NDPU network message Add app for What-Is-Network-Number Add app for Network-Number-Is Add send helper for What-Is-Network-Number Add send helper for Network-Number-Is * added sys/debug.c to ports builds for use of debug_printf() in npdu handler. Co-authored-by: Steve Karg --- CMakeLists.txt | 6 + Makefile | 8 + apps/Makefile | 10 +- apps/netnumis/Makefile | 40 +++ apps/netnumis/main.c | 340 ++++++++++++++++++++++++++ apps/whatisnetnum/Makefile | 40 +++ apps/whatisnetnum/main.c | 296 ++++++++++++++++++++++ ports/at91sam7s/Makefile | 1 + ports/at91sam7s/bacnet.ewp | 3 + ports/atmega168/Makefile | 1 + ports/atmega168/bacnet.cproj | 4 + ports/atmega168/bacnet.ewp | 3 + ports/bdk-atxx4-mstp/Makefile | 1 + ports/bdk-atxx4-mstp/bacnet.cproj | 4 + ports/bdk-atxx4-mstp/bacnet.ewp | 3 + ports/stm32f10x/Makefile | 1 + ports/stm32f10x/bacnet.ewp | 3 + ports/stm32f4xx/Makefile | 1 + ports/stm32f4xx/bacnet.ewp | 3 + ports/xplained/bacnet.cproj | 4 + src/bacnet/basic/npdu/h_npdu.c | 180 +++++++++++++- src/bacnet/basic/npdu/h_npdu.h | 14 ++ src/bacnet/basic/npdu/h_routed_npdu.c | 6 + src/bacnet/basic/npdu/s_router.c | 63 ++--- src/bacnet/basic/npdu/s_router.h | 12 +- src/bacnet/npdu.c | 30 +++ src/bacnet/npdu.h | 10 + test/bacnet/npdu/src/main.c | 39 ++- 28 files changed, 1080 insertions(+), 46 deletions(-) create mode 100644 apps/netnumis/Makefile create mode 100644 apps/netnumis/main.c create mode 100644 apps/whatisnetnum/Makefile create mode 100644 apps/whatisnetnum/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 84fab93c..8fc0c716 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -668,6 +668,12 @@ if(BACNET_STACK_BUILD_APPS) add_executable(whoisrouter apps/whoisrouter/main.c) target_link_libraries(whoisrouter PRIVATE ${PROJECT_NAME}) + add_executable(whatisnetnum apps/whatisnetnum/main.c) + target_link_libraries(whatisnetnum PRIVATE ${PROJECT_NAME}) + + add_executable(netnumis apps/netnumis/main.c) + target_link_libraries(netnumis PRIVATE ${PROJECT_NAME}) + add_executable(writefile apps/writefile/main.c) target_link_libraries(writefile PRIVATE ${PROJECT_NAME}) diff --git a/Makefile b/Makefile index b9b43a6e..52c1b4b9 100644 --- a/Makefile +++ b/Makefile @@ -109,6 +109,14 @@ readfdt: writebdt: $(MAKE) -s -C apps $@ +.PHONY: whatisnetnum +whatisnetnum: + $(MAKE) -s -C apps $@ + +.PHONY: netnumis +netnumis: + $(MAKE) -s -C apps $@ + .PHONY: server server: $(MAKE) -s -C apps $@ diff --git a/apps/Makefile b/apps/Makefile index 7280677a..8286502c 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -139,7 +139,7 @@ SUBDIRS = lib readprop writeprop readfile writefile reinit server dcc \ server-client ifeq (${BACDL_DEFINE},-DBACDL_BIP=1) - SUBDIRS += whoisrouter iamrouter initrouter + SUBDIRS += whoisrouter iamrouter initrouter whatisnetnum netnumis ifneq (${BBMD},none) SUBDIRS += readbdt readfdt writebdt endif @@ -213,6 +213,14 @@ iamrouter: $(BACNET_LIB_TARGET) initrouter: $(BACNET_LIB_TARGET) $(MAKE) -b -C $@ +.PHONY: whatisnetnum +whatisnetnum: $(BACNET_LIB_TARGET) + $(MAKE) -b -C $@ + +.PHONY: netnumis +netnumis: $(BACNET_LIB_TARGET) + $(MAKE) -b -C $@ + .PHONY: mstpcap mstpcap: $(MAKE) -b -C $@ diff --git a/apps/netnumis/Makefile b/apps/netnumis/Makefile new file mode 100644 index 00000000..9518a174 --- /dev/null +++ b/apps/netnumis/Makefile @@ -0,0 +1,40 @@ +#Makefile to build BACnet Application using GCC compiler + +# Executable file name +TARGET = bacnni +# BACnet objects that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +SRC = main.c \ + $(BACNET_OBJECT_DIR)/client/device-client.c \ + $(BACNET_OBJECT_DIR)/netport.c + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +OBJS += ${SRC:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +.PHONY: depend +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +.PHONY: clean +clean: + rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET} + +.PHONY: include +include: .depend + diff --git a/apps/netnumis/main.c b/apps/netnumis/main.c new file mode 100644 index 00000000..b9276ab4 --- /dev/null +++ b/apps/netnumis/main.c @@ -0,0 +1,340 @@ +/** + * @file + * @brief Application to send a BACnet Network Network-Number-Is message + * @author Steve Karg + * @date July 2022 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bacnet/bactext.h" +#include "bacnet/iam.h" +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +/* some demo stuff needed */ +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/dlenv.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* where the message gets sent */ +static BACNET_ADDRESS Target_Router_Address; +static int32_t Target_Network_Number = 0; +static int32_t Target_Network_Number_Status = 0; + +static bool Error_Detected = false; + +/** + * Handler for an Abort PDU. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param invoke_id [in] the invokeID from the rejected message + * @param abort_reason [in] the reason for the message abort + * @param server + */ +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; +} + +/** + * @brief Handler for a Reject PDU. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param invoke_id [in] the invokeID from the rejected message + * @param reject_reason [in] the reason for the rejection + */ +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; +} + +/** + * @brief Handler for a Network messages + * @param npdu_data - NPDU options + * @param npdu - NPDU data + * @param npdu_len - NPDU data length + */ +static void My_Router_Handler(BACNET_ADDRESS *src, + BACNET_NPDU_DATA *npdu_data, + uint8_t *npdu, + uint16_t npdu_len) +{ + uint16_t dnet = 0; + uint16_t len = 0; + uint16_t j = 0; + + switch (npdu_data->network_message_type) { + case NETWORK_MESSAGE_WHAT_IS_NETWORK_NUMBER: + printf("What-Is-Network-Number from "); + for (j = 0; j < MAX_MAC_LEN; j++) { + if (j < src->mac_len) { + printf("%02X", src->mac[j]); + } + } + printf("\n"); + break; + case NETWORK_MESSAGE_NETWORK_NUMBER_IS: + printf("Network-Number-Is from "); + for (j = 0; j < MAX_MAC_LEN; j++) { + if (j < src->mac_len) { + printf("%02X", src->mac[j]); + } + } + if (src->net == 0) { + /* It shall be transmitted with a local broadcast address, + and shall never be routed. */ + if (npdu_len >= 2) { + len += decode_unsigned16(npdu, &dnet); + printf(": network number = %u\n", (unsigned)dnet); + } else { + printf(": network number = missing!\n"); + } + } else { + /* Devices shall ignore Network-Number-Is messages that + contain SNET/SADR or DNET/DADR information In the NPCI or + that are sent with a local unicast address. */ + if (npdu_len >= 2) { + len += decode_unsigned16(npdu, &dnet); + printf(": network number = %u. SNET=%u\n", + (unsigned)dnet, (unsigned)src->net); + } else { + printf(": network number = missing! SNET=%u\n", src->net); + } + } + break; + case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK: + case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK: + case NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK: + case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK: + case NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK: + case NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK: + case NETWORK_MESSAGE_INIT_RT_TABLE: + case NETWORK_MESSAGE_INIT_RT_TABLE_ACK: + case NETWORK_MESSAGE_ESTABLISH_CONNECTION_TO_NETWORK: + case NETWORK_MESSAGE_DISCONNECT_CONNECTION_TO_NETWORK: + default: + break; + } +} + +static void My_NPDU_Handler(BACNET_ADDRESS *src, /* source address */ + uint8_t *pdu, /* PDU data */ + uint16_t pdu_len) +{ /* length PDU */ + int apdu_offset = 0; + BACNET_ADDRESS dest = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + + apdu_offset = npdu_decode(&pdu[0], &dest, src, &npdu_data); + if (npdu_data.network_layer_message) { + My_Router_Handler(src, &npdu_data, &pdu[apdu_offset], + (uint16_t)(pdu_len - apdu_offset)); + } else if ((apdu_offset > 0) && (apdu_offset <= pdu_len)) { + if ((npdu_data.protocol_version == BACNET_PROTOCOL_VERSION) && + ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK))) { + /* only handle the version that we know how to handle */ + /* and we are not a router, so ignore messages with + routing information cause they are not for us */ + apdu_handler( + src, &pdu[apdu_offset], (uint16_t)(pdu_len - apdu_offset)); + } else { + if (dest.net) { + debug_printf("NPDU: DNET=%d. Discarded!\n", dest.net); + } else { + debug_printf("NPDU: BACnet Protocol Version=%d. Discarded!\n", + npdu_data.protocol_version); + } + } + } + + return; +} + +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); +} + +static void address_parse(BACNET_ADDRESS *dst, int argc, char *argv[]) +{ + int dnet = 0; + unsigned mac[6]; + int count = 0; + int index = 0; + + if (argc > 0) { + count = sscanf(argv[0], "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], + &mac[3], &mac[4], &mac[5]); + dst->mac_len = count; + for (index = 0; index < MAX_MAC_LEN; index++) { + if (index < count) { + dst->mac[index] = mac[index]; + } else { + dst->mac[index] = 0; + } + } + } + if (argc > 1) { + count = sscanf(argv[1], "%d", &dnet); + dst->net = dnet; + } + if (dnet) { + if (argc > 2) { + count = sscanf(argv[2], "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], + &mac[2], &mac[3], &mac[4], &mac[5]); + dst->len = count; + for (index = 0; index < MAX_MAC_LEN; index++) { + if (index < count) { + dst->adr[index] = mac[index]; + } else { + dst->adr[index] = 0; + } + } + } else { + fprintf(stderr, "A non-zero DNET requires a DADR.\r\n"); + } + } else { + dst->len = 0; + for (index = 0; index < MAX_MAC_LEN; index++) { + dst->adr[index] = 0; + } + } +} + +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; + + if (argc < 3) { + printf("Usage: %s DNET status [MAC]\r\n", filename_remove_path(argv[0])); + return 0; + } + if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) { + printf("Send BACnet What-Is-Network-Number message to a network.\r\n" + "\r\n" + "DNET:\r\n" + "BACnet destination network number 0-65535\r\n" + "To omit the BACnet destination network number, use -1.\r\n" + "Network Number Status:\r\n" + "0=learned\r\n" + "1=configured\r\n" + "MAC:\r\n" + "Optional MAC address of router for unicast message\r\n" + "Format: xx[:xx:xx:xx:xx:xx] [dnet xx[:xx:xx:xx:xx:xx]]\r\n" + "Use hexidecimal MAC addresses.\r\n" + "\r\n" + "To send a What-Is-Network-Number request to DNET 86:\r\n" + "%s 86\r\n" + "To send a What-Is-Network-Number request to all devices:\r\n" + "%s -1\r\n", + filename_remove_path(argv[0]), filename_remove_path(argv[0])); + return 0; + } + /* decode the command line parameters */ + if (argc > 1) { + Target_Network_Number = strtol(argv[1], NULL, 0); + if (Target_Network_Number > UINT16_MAX) { + fprintf(stderr, "DNET=%d - it must be 0 to 65535\r\n", + Target_Network_Number); + return 1; + } + } + if (argc > 2) { + Target_Network_Number_Status = strtol(argv[2], NULL, 0); + if (Target_Network_Number > UINT8_MAX) { + fprintf(stderr, "status=%d - it must be 0 to 255\r\n", + Target_Network_Number_Status); + return 1; + } + } + if (argc > 3) { + address_parse(&Target_Router_Address, argc - 2, &argv[2]); + } else { + datalink_get_broadcast_address(&Target_Router_Address); + } + /* 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 */ + Send_Network_Number_Is( + &Target_Router_Address, Target_Network_Number, + Target_Network_Number_Status); + /* 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) { + My_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/whatisnetnum/Makefile b/apps/whatisnetnum/Makefile new file mode 100644 index 00000000..e5ecde88 --- /dev/null +++ b/apps/whatisnetnum/Makefile @@ -0,0 +1,40 @@ +#Makefile to build BACnet Application using GCC compiler + +# Executable file name +TARGET = bacwinn +# BACnet objects that are used with this app +BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object +SRC = main.c \ + $(BACNET_OBJECT_DIR)/client/device-client.c \ + $(BACNET_OBJECT_DIR)/netport.c + +# TARGET_EXT is defined in apps/Makefile as .exe or nothing +TARGET_BIN = ${TARGET}$(TARGET_EXT) + +OBJS += ${SRC:.c=.o} + +all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN} + +${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET} + ${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@ + size $@ + cp $@ ../../bin + +${BACNET_LIB_TARGET}: + ( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s ) + +.c.o: + ${CC} -c ${CFLAGS} $*.c -o $@ + +.PHONY: depend +depend: + rm -f .depend + ${CC} -MM ${CFLAGS} *.c >> .depend + +.PHONY: clean +clean: + rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET} + +.PHONY: include +include: .depend + diff --git a/apps/whatisnetnum/main.c b/apps/whatisnetnum/main.c new file mode 100644 index 00000000..8e09e06a --- /dev/null +++ b/apps/whatisnetnum/main.c @@ -0,0 +1,296 @@ +/** + * @file + * @brief Application to send a BACnet Network What-Is-Network-Number + * @author Steve Karg + * @date July 2022 + * + * SPDX-License-Identifier: MIT + */ +#include +#include +#include +#include +#include /* for time */ +#include +#include "bacnet/bactext.h" +#include "bacnet/iam.h" +#include "bacnet/config.h" +#include "bacnet/bacdef.h" +#include "bacnet/npdu.h" +#include "bacnet/apdu.h" +/* some demo stuff needed */ +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/binding/address.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/services.h" +#include "bacnet/basic/sys/filename.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" +#include "bacnet/datalink/dlenv.h" + +/* buffer used for receive */ +static uint8_t Rx_Buf[MAX_MPDU] = { 0 }; + +/* where the message gets sent */ +static BACNET_ADDRESS Target_Router_Address; + +static bool Error_Detected = false; + +/** + * Handler for an Abort PDU. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param invoke_id [in] the invokeID from the rejected message + * @param abort_reason [in] the reason for the message abort + * @param server + */ +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; +} + +/** + * @brief Handler for a Reject PDU. + * @param src [in] BACNET_ADDRESS of the source of the message + * @param invoke_id [in] the invokeID from the rejected message + * @param reject_reason [in] the reason for the rejection + */ +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 My_Router_Handler(BACNET_ADDRESS *src, + BACNET_NPDU_DATA *npdu_data, + uint8_t *npdu, + uint16_t npdu_len) +{ + uint16_t dnet = 0; + uint16_t len = 0; + uint16_t j = 0; + + switch (npdu_data->network_message_type) { + case NETWORK_MESSAGE_WHAT_IS_NETWORK_NUMBER: + break; + case NETWORK_MESSAGE_NETWORK_NUMBER_IS: + printf("Network-Number-Is from "); + for (j = 0; j < MAX_MAC_LEN; j++) { + if (j < src->mac_len) { + printf("%02X", src->mac[j]); + } + } + if (src->net == 0) { + /* It shall be transmitted with a local broadcast address, + and shall never be routed. */ + if (npdu_len >= 2) { + len += decode_unsigned16(npdu, &dnet); + printf(": network number = %u\n", (unsigned)dnet); + } else { + printf(": network number = missing!\n"); + } + } else { + /* Devices shall ignore Network-Number-Is messages that + contain SNET/SADR or DNET/DADR information In the NPCI or + that are sent with a local unicast address. */ + if (npdu_len >= 2) { + len += decode_unsigned16(npdu, &dnet); + printf(": network number = %u. SNET=%u\n", + (unsigned)dnet, (unsigned)src->net); + } else { + printf(": network number = missing! SNET=%u\n", src->net); + } + } + break; + case NETWORK_MESSAGE_WHO_IS_ROUTER_TO_NETWORK: + case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK: + case NETWORK_MESSAGE_I_COULD_BE_ROUTER_TO_NETWORK: + case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK: + case NETWORK_MESSAGE_ROUTER_BUSY_TO_NETWORK: + case NETWORK_MESSAGE_ROUTER_AVAILABLE_TO_NETWORK: + case NETWORK_MESSAGE_INIT_RT_TABLE: + case NETWORK_MESSAGE_INIT_RT_TABLE_ACK: + case NETWORK_MESSAGE_ESTABLISH_CONNECTION_TO_NETWORK: + case NETWORK_MESSAGE_DISCONNECT_CONNECTION_TO_NETWORK: + default: + break; + } +} + +static void My_NPDU_Handler(BACNET_ADDRESS *src, /* source address */ + uint8_t *pdu, /* PDU data */ + uint16_t pdu_len) +{ /* length PDU */ + int apdu_offset = 0; + BACNET_ADDRESS dest = { 0 }; + BACNET_NPDU_DATA npdu_data = { 0 }; + + apdu_offset = npdu_decode(&pdu[0], &dest, src, &npdu_data); + if (npdu_data.network_layer_message) { + My_Router_Handler(src, &npdu_data, &pdu[apdu_offset], + (uint16_t)(pdu_len - apdu_offset)); + } else if ((apdu_offset > 0) && (apdu_offset <= pdu_len)) { + if ((npdu_data.protocol_version == BACNET_PROTOCOL_VERSION) && + ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK))) { + /* only handle the version that we know how to handle */ + /* and we are not a router, so ignore messages with + routing information cause they are not for us */ + apdu_handler( + src, &pdu[apdu_offset], (uint16_t)(pdu_len - apdu_offset)); + } else { + if (dest.net) { + debug_printf("NPDU: DNET=%d. Discarded!\n", dest.net); + } else { + debug_printf("NPDU: BACnet Protocol Version=%d. Discarded!\n", + npdu_data.protocol_version); + } + } + } + + return; +} + +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); +} + +static void address_parse(BACNET_ADDRESS *dst, int argc, char *argv[]) +{ + int dnet = 0; + unsigned mac[6]; + int count = 0; + int index = 0; + + if (argc > 0) { + count = sscanf(argv[0], "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], + &mac[3], &mac[4], &mac[5]); + dst->mac_len = count; + for (index = 0; index < MAX_MAC_LEN; index++) { + if (index < count) { + dst->mac[index] = mac[index]; + } else { + dst->mac[index] = 0; + } + } + } + if (argc > 1) { + count = sscanf(argv[1], "%d", &dnet); + dst->net = dnet; + } + if (dnet) { + if (argc > 2) { + count = sscanf(argv[2], "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], + &mac[2], &mac[3], &mac[4], &mac[5]); + dst->len = count; + for (index = 0; index < MAX_MAC_LEN; index++) { + if (index < count) { + dst->adr[index] = mac[index]; + } else { + dst->adr[index] = 0; + } + } + } else { + fprintf(stderr, "A non-zero DNET requires a DADR.\r\n"); + } + } else { + dst->len = 0; + for (index = 0; index < MAX_MAC_LEN; index++) { + dst->adr[index] = 0; + } + } +} + +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; + + if ((argc > 1) && (strcmp(argv[1], "--help") == 0)) { + printf("Send BACnet What-Is-Network-Number message to a network.\r\n" + "\r\n" + "MAC:\r\n" + "Optional MAC address of router for unicast message\r\n" + "Format: xx[:xx:xx:xx:xx:xx] [dnet xx[:xx:xx:xx:xx:xx]]\r\n" + "Use hexidecimal MAC addresses.\r\n" + "\r\n" + "To send a What-Is-Network-Number request to DNET 86:\r\n" + "%s 86\r\n" + "To send a What-Is-Network-Number request to all devices:\r\n" + "%s -1\r\n", + filename_remove_path(argv[0]), filename_remove_path(argv[0])); + return 0; + } + if (argc > 1) { + address_parse(&Target_Router_Address, argc - 2, &argv[2]); + } else { + datalink_get_broadcast_address(&Target_Router_Address); + } + /* 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 */ + npdu_send_what_is_network_number(&Target_Router_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) { + My_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/ports/at91sam7s/Makefile b/ports/at91sam7s/Makefile index 152b7e71..6ad3ab44 100644 --- a/ports/at91sam7s/Makefile +++ b/ports/at91sam7s/Makefile @@ -68,6 +68,7 @@ DEMOSRC = ai.c \ device.c \ netport.c \ $(BACNET_BASIC)/tsm/tsm.c \ + $(BACNET_BASIC)/sys/debug.c \ $(BACNET_BASIC)/sys/ringbuf.c \ $(BACNET_BASIC)/npdu/h_npdu.c \ $(BACNET_BASIC)/service/h_noserv.c \ diff --git a/ports/at91sam7s/bacnet.ewp b/ports/at91sam7s/bacnet.ewp index 7574b68b..cd5ac3ae 100644 --- a/ports/at91sam7s/bacnet.ewp +++ b/ports/at91sam7s/bacnet.ewp @@ -1100,6 +1100,9 @@ $PROJ_DIR$\..\..\src\bacnet\basic\sys\ringbuf.c + + $PROJ_DIR$\..\..\src\bacnet\basic\sys\debug.c + $PROJ_DIR$\..\..\src\bacnet\basic\service\s_iam.c diff --git a/ports/atmega168/Makefile b/ports/atmega168/Makefile index 93e8b0ae..4c8425b0 100644 --- a/ports/atmega168/Makefile +++ b/ports/atmega168/Makefile @@ -51,6 +51,7 @@ CSRC = main.c \ BASICSRC = $(BACNET_BASIC)/tsm/tsm.c \ $(BACNET_BASIC)/npdu/h_npdu.c \ $(BACNET_BASIC)/sys/bigend.c \ + $(BACNET_BASIC)/sys/debug.c \ $(BACNET_BASIC)/service/s_iam.c \ $(BACNET_BASIC)/service/h_noserv.c diff --git a/ports/atmega168/bacnet.cproj b/ports/atmega168/bacnet.cproj index 343a69d2..8e3aa83c 100644 --- a/ports/atmega168/bacnet.cproj +++ b/ports/atmega168/bacnet.cproj @@ -156,6 +156,10 @@ compile BACnet Handlers\bigend.c + + compile + BACnet Handlers\debug.c + compile BACnet Handlers\h_npdu.c diff --git a/ports/atmega168/bacnet.ewp b/ports/atmega168/bacnet.ewp index 06a6a833..508ee18f 100644 --- a/ports/atmega168/bacnet.ewp +++ b/ports/atmega168/bacnet.ewp @@ -3191,6 +3191,9 @@ $PROJ_DIR$\..\..\src\bacnet\basic\sys\bigend.c + + $PROJ_DIR$\..\..\src\bacnet\basic\sys\debug.c + $PROJ_DIR$\..\..\src\bacnet\datalink\crc.c diff --git a/ports/bdk-atxx4-mstp/Makefile b/ports/bdk-atxx4-mstp/Makefile index a32a09aa..788ae0f3 100644 --- a/ports/bdk-atxx4-mstp/Makefile +++ b/ports/bdk-atxx4-mstp/Makefile @@ -110,6 +110,7 @@ BASICSRC = $(BACNET_BASIC)/service/h_dcc.c \ $(BACNET_BASIC)/service/s_iam.c \ $(BACNET_BASIC)/service/s_ihave.c \ $(BACNET_BASIC)/sys/bigend.c \ + $(BACNET_BASIC)/sys/debug.c \ $(BACNET_BASIC)/sys/fifo.c \ $(BACNET_BASIC)/sys/mstimer.c \ $(BACNET_BASIC)/sys/ringbuf.c \ diff --git a/ports/bdk-atxx4-mstp/bacnet.cproj b/ports/bdk-atxx4-mstp/bacnet.cproj index 73f9e8f9..284d6bd9 100644 --- a/ports/bdk-atxx4-mstp/bacnet.cproj +++ b/ports/bdk-atxx4-mstp/bacnet.cproj @@ -427,6 +427,10 @@ compile BACnet Core\reject.c + + compile + BACnet Core\debug.c + compile BACnet Core\mstimer.c diff --git a/ports/bdk-atxx4-mstp/bacnet.ewp b/ports/bdk-atxx4-mstp/bacnet.ewp index 7500b152..3163bb81 100644 --- a/ports/bdk-atxx4-mstp/bacnet.ewp +++ b/ports/bdk-atxx4-mstp/bacnet.ewp @@ -4309,6 +4309,9 @@ BACnet Basic Sys + + $PROJ_DIR$\..\..\src\bacnet\basic\sys\debug.c + $PROJ_DIR$\..\..\src\bacnet\basic\sys\fifo.c diff --git a/ports/stm32f10x/Makefile b/ports/stm32f10x/Makefile index 9efc6ed8..dcc2c7d0 100644 --- a/ports/stm32f10x/Makefile +++ b/ports/stm32f10x/Makefile @@ -46,6 +46,7 @@ BASIC_SRC = \ $(BACNET_BASIC)/service/s_iam.c \ $(BACNET_BASIC)/service/s_ihave.c \ $(BACNET_BASIC)/tsm/tsm.c \ + $(BACNET_BASIC)/sys/debug.c \ $(BACNET_BASIC)/sys/ringbuf.c \ $(BACNET_BASIC)/sys/fifo.c \ $(BACNET_BASIC)/sys/mstimer.c diff --git a/ports/stm32f10x/bacnet.ewp b/ports/stm32f10x/bacnet.ewp index e3ce92a6..66a40997 100644 --- a/ports/stm32f10x/bacnet.ewp +++ b/ports/stm32f10x/bacnet.ewp @@ -1183,6 +1183,9 @@ $PROJ_DIR$\..\..\src\bacnet\basic\sys\bigend.c + + $PROJ_DIR$\..\..\src\bacnet\basic\sys\debug.c + $PROJ_DIR$\..\..\src\bacnet\basic\sys\fifo.c diff --git a/ports/stm32f4xx/Makefile b/ports/stm32f4xx/Makefile index 30e5a550..054faa42 100644 --- a/ports/stm32f4xx/Makefile +++ b/ports/stm32f4xx/Makefile @@ -44,6 +44,7 @@ BASIC_SRC = \ $(BACNET_BASIC)/service/s_iam.c \ $(BACNET_BASIC)/service/s_ihave.c \ $(BACNET_BASIC)/tsm/tsm.c \ + $(BACNET_BASIC)/sys/debug.c \ $(BACNET_BASIC)/sys/ringbuf.c \ $(BACNET_BASIC)/sys/fifo.c \ $(BACNET_BASIC)/sys/mstimer.c diff --git a/ports/stm32f4xx/bacnet.ewp b/ports/stm32f4xx/bacnet.ewp index d6e3d7aa..ab1cf68b 100644 --- a/ports/stm32f4xx/bacnet.ewp +++ b/ports/stm32f4xx/bacnet.ewp @@ -1186,6 +1186,9 @@ $PROJ_DIR$\..\..\src\bacnet\basic\sys\bigend.c + + $PROJ_DIR$\..\..\src\bacnet\basic\sys\debug.c + $PROJ_DIR$\..\..\src\bacnet\basic\sys\fifo.c diff --git a/ports/xplained/bacnet.cproj b/ports/xplained/bacnet.cproj index 198152b2..61f68c02 100644 --- a/ports/xplained/bacnet.cproj +++ b/ports/xplained/bacnet.cproj @@ -840,6 +840,10 @@ compile bacnet-stack\ringbuf.c + + compile + bacnet-stack\debug.c + compile bacnet-stack\rp.c diff --git a/src/bacnet/basic/npdu/h_npdu.c b/src/bacnet/basic/npdu/h_npdu.c index 5fd6dd68..4149e2a9 100644 --- a/src/bacnet/basic/npdu/h_npdu.c +++ b/src/bacnet/basic/npdu/h_npdu.c @@ -24,6 +24,7 @@ *********************************************************************/ #include #include +#include "bacnet/bacaddr.h" #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" #include "bacnet/bacint.h" @@ -32,13 +33,176 @@ #include "bacnet/npdu.h" #include "bacnet/apdu.h" #include "bacnet/basic/services.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/tsm/tsm.h" +#include "bacnet/datalink/datalink.h" #if PRINT_ENABLED #include #endif +static uint16_t Local_Network_Number; +static uint8_t Local_Network_Number_Status = NETWORK_NUMBER_LEARNED; + +/** + * @brief get the local network number + * @return local network number + */ +uint16_t npdu_network_number(void) +{ + return Local_Network_Number; +} + +/** + * @brief set the local network number + * @param net - local network number + */ +void npdu_network_number_set(uint16_t net) +{ + Local_Network_Number = net; +} + +/** + * @brief send the local network number is message + * @param dst - the destination address for the message + * @param net - local network number + * @param status - 0=learned, 1=assigned + * @return number of bytes sent + */ +int npdu_send_network_number_is(BACNET_ADDRESS *dst, + uint16_t net, uint8_t status) +{ + uint16_t len = 0; + int pdu_len = 0; + int bytes_sent = 0; + bool data_expecting_reply = false; + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS my_address; + + /* Upon receipt of a What-Is-Network-Number message, + a device that knows the local network number shall + transmit a local broadcast Network-Number-Is message + back to the source device. */ + datalink_get_my_address(&my_address); + npdu_encode_npdu_network(&npdu_data, + NETWORK_MESSAGE_NETWORK_NUMBER_IS, + data_expecting_reply, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu(&Handler_Transmit_Buffer[0], dst, + &my_address, &npdu_data); + len = encode_unsigned16( + &Handler_Transmit_Buffer[pdu_len], net); + pdu_len += len; + Handler_Transmit_Buffer[pdu_len] = status; + pdu_len++; + bytes_sent = datalink_send_pdu( + dst, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + + return bytes_sent; +} + +/** + * @brief send the local network number is message + * @param dst - the destination address for the message + * @param net - local network number + * @return number of bytes sent + */ +int npdu_send_what_is_network_number(BACNET_ADDRESS *dst) +{ + int pdu_len = 0; + bool data_expecting_reply = false; + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS daddr = { 0 }; + BACNET_ADDRESS saddr = { 0 }; + + if (dst) { + bacnet_address_copy(&daddr, dst); + } else { + datalink_get_broadcast_address(&daddr); + } + datalink_get_my_address(&saddr); + npdu_encode_npdu_network(&npdu_data, + NETWORK_MESSAGE_WHAT_IS_NETWORK_NUMBER, + data_expecting_reply, MESSAGE_PRIORITY_NORMAL); + pdu_len = npdu_encode_pdu( + &Handler_Transmit_Buffer[0], + &daddr, &saddr, &npdu_data); + + /* Now send the message */ + return datalink_send_pdu( + dst, &npdu_data, &Handler_Transmit_Buffer[0], pdu_len); + +} + /** @file h_npdu.c Handles messages at the NPDU level of the BACnet stack. */ +/** Handler to manage the Network Layer Control Messages received in a packet. + * This handler is called if the NCPI bit 7 indicates that this packet is a + * network layer message and there is no further DNET to pass it to. + * The NCPI has already been decoded into the npdu_data structure. + * @ingroup MISCHNDLR + * + * @param src [in] The routing source information, if any. + * If src->net and src->len are 0, there is no + * routing source information. + * @param npdu_data [in] Contains a filled-out structure with information + * decoded from the NCPI and other NPDU + * bytes. + * @param npdu [in] Buffer containing the rest of the NPDU, following the + * bytes that have already been decoded. + * @param npdu_len [in] The length of the remaining NPDU message in npdu[]. + */ +static void network_control_handler(BACNET_ADDRESS *src, + BACNET_NPDU_DATA *npdu_data, + uint8_t *npdu, + uint16_t npdu_len) +{ + uint16_t dnet = 0; + uint16_t len = 0; + uint8_t status = 0; + + switch (npdu_data->network_message_type) { + case NETWORK_MESSAGE_WHAT_IS_NETWORK_NUMBER: + if (src->net == 0) { + if (Local_Network_Number) { + npdu_send_network_number_is(src, Local_Network_Number, + Local_Network_Number_Status); + } else { + /* Upon receipt of a What-Is-Network-Number message, + a device that does not know the local network number + shall discard the message. */ + } + } else { + /* Devices shall ignore What-Is-Network-Number messages + that contain SNET/SADR or DNET/DADR information in + the NPCI. */ + } + break; + case NETWORK_MESSAGE_NETWORK_NUMBER_IS: + if (src->net == 0) { + /* It shall be transmitted with a local broadcast address, + and shall never be routed. */ + if (npdu_len >= 2) { + (void)decode_unsigned16(npdu, &dnet); + Local_Network_Number = dnet; + } + if (npdu_len >= 3) { + status = npdu[2]; + /* Our net number is always learned, unless we + are a router. Ignore the learned/configured + status */ + (void)status; + } + } else { + /* Devices shall ignore Network-Number-Is messages that + contain SNET/SADR or DNET/DADR information In the NPCI or + that are sent with a local unicast address. */ + } + break; + default: + break; + } +} + /** Handler for the NPDU portion of a received packet. * Aside from error-checking, if the NPDU doesn't contain routing info, * this handler doesn't do much besides stepping over the NPDU header @@ -62,10 +226,10 @@ * @param pdu [in] Buffer containing the NPDU and APDU of the received packet. * @param pdu_len [in] The size of the received message in the pdu[] buffer. */ -void npdu_handler(BACNET_ADDRESS *src, /* source address */ - uint8_t *pdu, /* PDU data */ +void npdu_handler(BACNET_ADDRESS *src, + uint8_t *pdu, uint16_t pdu_len) -{ /* length PDU */ +{ int apdu_offset = 0; BACNET_ADDRESS dest = { 0 }; BACNET_NPDU_DATA npdu_data = { 0 }; @@ -79,10 +243,12 @@ void npdu_handler(BACNET_ADDRESS *src, /* source address */ apdu_offset = bacnet_npdu_decode(&pdu[0], pdu_len, &dest, src, &npdu_data); if (npdu_data.network_layer_message) { - /*FIXME: network layer message received! Handle it! */ -#if PRINT_ENABLED - fprintf(stderr, "NPDU: Network Layer Message discarded!\n"); -#endif + if ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK)) { + network_control_handler(src, &npdu_data, + &pdu[apdu_offset], (uint16_t)(pdu_len - apdu_offset)); + } else { + debug_printf("NPDU: message for router. Discarded!\n"); + } } else if ((apdu_offset > 0) && (apdu_offset < pdu_len)) { if ((dest.net == 0) || (dest.net == BACNET_BROADCAST_NETWORK)) { /* only handle the version that we know how to handle */ diff --git a/src/bacnet/basic/npdu/h_npdu.h b/src/bacnet/basic/npdu/h_npdu.h index c84d340d..26e2270d 100644 --- a/src/bacnet/basic/npdu/h_npdu.h +++ b/src/bacnet/basic/npdu/h_npdu.h @@ -47,6 +47,20 @@ extern "C" { BACNET_ADDRESS * src, uint8_t * pdu, uint16_t pdu_len); + + BACNET_STACK_EXPORT + uint16_t npdu_network_number(void); + BACNET_STACK_EXPORT + void npdu_network_number_set(uint16_t net); + BACNET_STACK_EXPORT + int npdu_send_network_number_is( + BACNET_ADDRESS *dst, + uint16_t net, + uint8_t status); + BACNET_STACK_EXPORT + int npdu_send_what_is_network_number( + BACNET_ADDRESS *dst); + BACNET_STACK_EXPORT void npdu_handler_cleanup(void); BACNET_STACK_EXPORT diff --git a/src/bacnet/basic/npdu/h_routed_npdu.c b/src/bacnet/basic/npdu/h_routed_npdu.c index af185341..69449ce0 100644 --- a/src/bacnet/basic/npdu/h_routed_npdu.c +++ b/src/bacnet/basic/npdu/h_routed_npdu.c @@ -161,6 +161,12 @@ static void network_control_handler(BACNET_ADDRESS *src, case NETWORK_MESSAGE_DISCONNECT_CONNECTION_TO_NETWORK: /* Do nothing - don't support PTP half-router control */ break; + case NETWORK_MESSAGE_WHAT_IS_NETWORK_NUMBER: + /* FIXME: add procedure for this message */ + break; + case NETWORK_MESSAGE_NETWORK_NUMBER_IS: + /* FIXME: add procedure for this message */ + break; default: /* An unrecognized message is bad; send an error response. */ Send_Reject_Message_To_Network( diff --git a/src/bacnet/basic/npdu/s_router.c b/src/bacnet/basic/npdu/s_router.c index 1fc26f99..0d7c3c69 100644 --- a/src/bacnet/basic/npdu/s_router.c +++ b/src/bacnet/basic/npdu/s_router.c @@ -29,6 +29,7 @@ #include "bacnet/config.h" #include "bacnet/bacdef.h" #include "bacnet/bacdcode.h" +#include "bacnet/bacaddr.h" #include "bacnet/npdu.h" #include "bacnet/apdu.h" #include "bacnet/bactext.h" @@ -43,36 +44,6 @@ /** @file s_router.c Methods to send various BACnet Router Network Layer * Messages. */ -/** Initialize an npdu_data structure with given parameters and good defaults, - * and add the Network Layer Message fields. - * The name is a misnomer, as it doesn't do any actual encoding here. - * @see npdu_encode_npdu_data for a simpler version to use when sending an - * APDU instead of a Network Layer Message. - * - * @param npdu_data [out] Returns a filled-out structure with information - * provided by the other arguments and - * good defaults. - * @param network_message_type [in] The type of Network Layer Message. - * @param data_expecting_reply [in] True if message should have a reply. - * @param priority [in] One of the 4 priorities defined in section 6.2.2, - * like B'11' = Life Safety message - */ -void npdu_encode_npdu_network(BACNET_NPDU_DATA *npdu_data, - BACNET_NETWORK_MESSAGE_TYPE network_message_type, - bool data_expecting_reply, - BACNET_MESSAGE_PRIORITY priority) -{ - if (npdu_data) { - npdu_data->data_expecting_reply = data_expecting_reply; - npdu_data->protocol_version = BACNET_PROTOCOL_VERSION; - npdu_data->network_layer_message = true; /* false if APDU */ - npdu_data->network_message_type = network_message_type; /* optional */ - npdu_data->vendor_id = 0; /* optional, if net message type is > 0x80 */ - npdu_data->priority = priority; - npdu_data->hop_count = HOP_COUNT_DEFAULT; - } -} - /** Function to encode and send any supported Network Layer Message. * The payload for the message is encoded from information in the iArgs[] array. * The contents of iArgs are are, per message type: @@ -150,6 +121,18 @@ int Send_Network_Layer_Message(BACNET_NETWORK_MESSAGE_TYPE network_message_type, } break; + case NETWORK_MESSAGE_NETWORK_NUMBER_IS: + /* Encode the DNET */ + len = encode_unsigned16( + &Handler_Transmit_Buffer[pdu_len], (uint16_t)*pVal); + pdu_len += len; + pVal++; + /* Encode the Status byte */ + Handler_Transmit_Buffer[pdu_len] = (uint8_t)*pVal; + pdu_len++; + pVal++; + break; + case NETWORK_MESSAGE_REJECT_MESSAGE_TO_NETWORK: /* Encode the Reason byte, then the DNET */ Handler_Transmit_Buffer[pdu_len++] = (uint8_t)*pVal; @@ -317,3 +300,23 @@ void Send_Initialize_Routing_Table_Ack( Send_Network_Layer_Message( NETWORK_MESSAGE_INIT_RT_TABLE_ACK, dst, (int *)DNET_list); } + +/** + * @brief Sets a BACnet network number for the local network + * + * @param dst [in] If NULL, request will be broadcast to the local BACnet + * network. Optionally may designate a particular router + * destination to respond. + * @param dnet [in] Which BACnet network to request for; if -1, no DNET + * will be sent and the receiving router(s) will send + * their full list of reachable BACnet networks. + */ +void Send_Network_Number_Is(BACNET_ADDRESS *dst, int dnet, int status) +{ + int iArgs[2]; + iArgs[0] = dnet; + iArgs[1] = status; + + Send_Network_Layer_Message( + NETWORK_MESSAGE_NETWORK_NUMBER_IS, dst, iArgs); +} diff --git a/src/bacnet/basic/npdu/s_router.h b/src/bacnet/basic/npdu/s_router.h index d50beae9..762ba657 100644 --- a/src/bacnet/basic/npdu/s_router.h +++ b/src/bacnet/basic/npdu/s_router.h @@ -43,12 +43,6 @@ extern "C" { #endif /* __cplusplus */ BACNET_STACK_EXPORT - void npdu_encode_npdu_network( - BACNET_NPDU_DATA *npdu_data, - BACNET_NETWORK_MESSAGE_TYPE network_message_type, - bool data_expecting_reply, - BACNET_MESSAGE_PRIORITY priority); - BACNET_STACK_EXPORT int Send_Network_Layer_Message( BACNET_NETWORK_MESSAGE_TYPE network_message_type, BACNET_ADDRESS * dst, @@ -73,7 +67,11 @@ extern "C" { void Send_Initialize_Routing_Table_Ack( BACNET_ADDRESS * dst, const int DNET_list[]); - + BACNET_STACK_EXPORT + void Send_Network_Number_Is( + BACNET_ADDRESS *dst, + int dnet, + int status); #ifdef __cplusplus } diff --git a/src/bacnet/npdu.c b/src/bacnet/npdu.c index d02cf9a7..5244fefe 100644 --- a/src/bacnet/npdu.c +++ b/src/bacnet/npdu.c @@ -286,6 +286,36 @@ void npdu_encode_npdu_data(BACNET_NPDU_DATA *npdu_data, } } +/** Initialize an npdu_data structure with given parameters and good defaults, + * and add the Network Layer Message fields. + * The name is a misnomer, as it doesn't do any actual encoding here. + * @see npdu_encode_npdu_data for a simpler version to use when sending an + * APDU instead of a Network Layer Message. + * + * @param npdu_data [out] Returns a filled-out structure with information + * provided by the other arguments and + * good defaults. + * @param network_message_type [in] The type of Network Layer Message. + * @param data_expecting_reply [in] True if message should have a reply. + * @param priority [in] One of the 4 priorities defined in section 6.2.2, + * like B'11' = Life Safety message + */ +void npdu_encode_npdu_network(BACNET_NPDU_DATA *npdu_data, + BACNET_NETWORK_MESSAGE_TYPE network_message_type, + bool data_expecting_reply, + BACNET_MESSAGE_PRIORITY priority) +{ + if (npdu_data) { + npdu_data->data_expecting_reply = data_expecting_reply; + npdu_data->protocol_version = BACNET_PROTOCOL_VERSION; + npdu_data->network_layer_message = true; /* false if APDU */ + npdu_data->network_message_type = network_message_type; /* optional */ + npdu_data->vendor_id = 0; /* optional, if net message type is > 0x80 */ + npdu_data->priority = priority; + npdu_data->hop_count = HOP_COUNT_DEFAULT; + } +} + /** Decode the NPDU portion of a received message, particularly the NCPI byte. * The Network Layer Protocol Control Information byte is described * in section 6.2.2 of the BACnet standard. diff --git a/src/bacnet/npdu.h b/src/bacnet/npdu.h index f7dec868..b1ecaff6 100644 --- a/src/bacnet/npdu.h +++ b/src/bacnet/npdu.h @@ -61,6 +61,9 @@ typedef struct router_port_t { struct router_port_t *next; /**< Point to next in linked list */ } BACNET_ROUTER_PORT; +#define NETWORK_NUMBER_LEARNED 0 +#define NETWORK_NUMBER_CONFIGURED 1 + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -83,6 +86,13 @@ extern "C" { bool data_expecting_reply, BACNET_MESSAGE_PRIORITY priority); + BACNET_STACK_EXPORT + void npdu_encode_npdu_network( + BACNET_NPDU_DATA *npdu_data, + BACNET_NETWORK_MESSAGE_TYPE network_message_type, + bool data_expecting_reply, + BACNET_MESSAGE_PRIORITY priority); + BACNET_STACK_EXPORT void npdu_copy_data( BACNET_NPDU_DATA * dest, diff --git a/test/bacnet/npdu/src/main.c b/test/bacnet/npdu/src/main.c index 2f55ffda..d11ed620 100644 --- a/test/bacnet/npdu/src/main.c +++ b/test/bacnet/npdu/src/main.c @@ -16,6 +16,42 @@ * @{ */ +/** + * @brief Test + */ +static void test_NPDU_Network(void) +{ + uint8_t pdu[480] = { 0 }; + BACNET_ADDRESS dest = { 0 }; + BACNET_ADDRESS src = { 0 }; + BACNET_ADDRESS npdu_dest = { 0 }; + BACNET_ADDRESS npdu_src = { 0 }; + int len = 0; + bool data_expecting_reply = true; + BACNET_NETWORK_MESSAGE_TYPE network_message_type = + NETWORK_MESSAGE_NETWORK_NUMBER_IS; + BACNET_MESSAGE_PRIORITY priority = MESSAGE_PRIORITY_NORMAL; + BACNET_NPDU_DATA npdu_data = { 0 }; + int i = 0; /* counter */ + int npdu_len = 0; + bool network_layer_message = true; + uint16_t vendor_id = 0; /* optional, if net message type is > 0x80 */ + + npdu_encode_npdu_network(&npdu_data, + network_message_type, + data_expecting_reply, priority); + len = npdu_encode_pdu(&pdu[0], &dest, &src, &npdu_data); + zassert_not_equal(len, 0, NULL); + /* can we get the info back? */ + npdu_len = npdu_decode(&pdu[0], &npdu_dest, &npdu_src, &npdu_data); + zassert_not_equal(npdu_len, 0, NULL); + zassert_equal(npdu_data.data_expecting_reply, data_expecting_reply, NULL); + zassert_equal(npdu_data.network_layer_message, network_layer_message, NULL); + zassert_equal(npdu_data.network_message_type, network_message_type, NULL); + zassert_equal(npdu_data.vendor_id, vendor_id, NULL); + zassert_equal(npdu_data.priority, priority, NULL); +} + /** * @brief Test */ @@ -146,7 +182,8 @@ void test_main(void) { ztest_test_suite(npdu_tests, ztest_unit_test(testNPDU1), - ztest_unit_test(testNPDU2) + ztest_unit_test(testNPDU2), + ztest_unit_test(test_NPDU_Network) ); ztest_run_test_suite(npdu_tests);