diff --git a/CMakeLists.txt b/CMakeLists.txt index f4553482..aa8d387a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,11 @@ option( "compile with ipv6 datalink support" ON) +option( + BACDL_ZIGBEE + "compile with zigbee datalink support" + ON) + option( BACDL_BSC "compile with secure-connect support" @@ -84,6 +89,7 @@ if(NOT (BACDL_ETHERNET OR BACDL_ARCNET OR BACDL_BIP OR BACDL_BIP6 OR + BACDL_ZIGBEE OR BACDL_BSC OR BACDL_CUSTOM)) add_definitions(-DBACDL_NONE) @@ -295,6 +301,8 @@ add_library(${PROJECT_NAME} $<$:src/bacnet/basic/bbmd6/h_bbmd6.h> $<$:src/bacnet/basic/bbmd6/vmac.c> $<$:src/bacnet/basic/bbmd6/vmac.h> + $<$:src/bacnet/basic/bzll/bzllvmac.c> + $<$:src/bacnet/basic/bzll/bzllvmac.h> $<$:src/bacnet/datalink/bsc/bvlc-sc.c> $<$:src/bacnet/datalink/bsc/bvlc-sc.h> $<$:src/bacnet/datalink/bsc/bsc-socket.c> @@ -693,6 +701,7 @@ target_compile_definitions( $<$:BACDL_BIP> $<$:BACDL_BSC> $<$:BACDL_BIP6> + $<$:BACDL_ZIGBEE> $<$:BACDL_ARCNET> $<$:BACDL_MSTP> $<$:BACDL_ETHERNET> @@ -733,6 +742,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") ports/linux/datetime-init.c $<$:ports/linux/bip-init.c> $<$:ports/linux/bip6.c> + $<$:ports/linux/bzll-init.c> $<$:ports/linux/arcnet.c> $<$:ports/linux/rs485.c> $<$:ports/linux/rs485.h> @@ -761,6 +771,7 @@ elseif(WIN32) ports/win32/bacport.h $<$:ports/win32/bip6.c> $<$:ports/win32/bip-init.c> + $<$:ports/win32/bzll-init.c> ports/win32/datetime-init.c $<$:ports/win32/dlmstp.c> # ports/win32/dlmstp-mm.c @@ -808,6 +819,7 @@ elseif(APPLE) target_sources(${PROJECT_NAME} PRIVATE ports/bsd/bacport.h $<$:ports/bsd/bip-init.c> + $<$:ports/bsd/bzll-init.c> $<$:ports/bsd/bip6.c> $<$:ports/bsd/rs485.c> $<$:ports/bsd/rs485.h> @@ -848,6 +860,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") target_sources(${PROJECT_NAME} PRIVATE ports/bsd/bacport.h $<$:ports/bsd/bip-init.c> + $<$:ports/bsd/bzll-init.c> $<$:ports/bsd/bip6.c> $<$:ports/bsd/rs485.c> $<$:ports/bsd/rs485.h> @@ -1231,4 +1244,5 @@ message(STATUS "BACNET: BACDL_BIP:......................\"${BACDL_BIP}\"") message(STATUS "BACNET: BACDL_BSC:......................\"${BACDL_BSC}\"") message(STATUS "BACNET: BACDL_ARCNET:...................\"${BACDL_ARCNET}\"") message(STATUS "BACNET: BACDL_MSTP:.....................\"${BACDL_MSTP}\"") +message(STATUS "BACNET: BACDL_ZIGBEE:...................\"${BACDL_ZIGBEE}\"") message(STATUS "BACNET: BACDL_ETHERNET:.................\"${BACDL_ETHERNET}\"") diff --git a/apps/lib/Makefile b/apps/lib/Makefile index d4ac927e..5fc61f7f 100644 --- a/apps/lib/Makefile +++ b/apps/lib/Makefile @@ -47,6 +47,10 @@ PORT_MSTP_SRC = \ PORT_ETHERNET_SRC = \ $(BACNET_PORT_DIR)/ethernet.c +PORT_ZIGBEE_SRC = \ + $(BACNET_PORT_DIR)/bzll-init.c \ + $(BACNET_SRC_DIR)/bacnet/basic/bzll/bzllvmac.c + PORT_BIP_SRC = \ $(BACNET_PORT_DIR)/bip-init.c \ $(BACNET_SRC_DIR)/bacnet/basic/bbmd/h_bbmd.c @@ -81,6 +85,7 @@ PORT_ALL_SRC = \ $(PORT_ARCNET_SRC) \ $(PORT_MSTP_SRC) \ $(PORT_ETHERNET_SRC) \ + $(PORT_ZIGBEE_SRC) \ $(PORT_BIP_SRC) \ $(PORT_BIP6_SRC) \ $(PORT_BSC_SRC) @@ -103,6 +108,9 @@ endif ifeq (${BACDL_DEFINE},-DBACDL_ETHERNET=1) BACNET_PORT_SRC = ${PORT_ETHERNET_SRC} ${APPS_ENVIRONMENT_SRC} endif +ifeq (${BACDL_DEFINE},-DBACDL_ZIGBEE=1) +BACNET_PORT_SRC = ${PORT_ZIGBEE_SRC} ${APPS_ENVIRONMENT_SRC} +endif ifeq (${BACDL_DEFINE},-DBACDL_NONE=1) BACNET_PORT_SRC = ${PORT_NONE_SRC} endif diff --git a/ports/bsd/bzll-init.c b/ports/bsd/bzll-init.c new file mode 100644 index 00000000..9b417428 --- /dev/null +++ b/ports/bsd/bzll-init.c @@ -0,0 +1,118 @@ +/** + * @file + * @brief Initializes BACnet Zigbee Link Layer interface (BSD). + * @author Steve Karg + * @date July 2025 + * @copyright SPDX-License-Identifier: MIT + */ +/* BACnet specific */ +#include "bacnet/bacdef.h" +#include "bacnet/bacaddr.h" +#include "bacnet/bacint.h" +#include "bacnet/datalink/bzll.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/object/device.h" +/** + * @brief Initialize the datalink + * @param ifname - the name of the datalink + */ +bool bzll_init(char *ifname) +{ + (void)ifname; + + return true; +} + +/** + * @brief Send a protocol data unit (PDU) to the network + * @param dest - destination address + * @param npdu_data - network routing data + * @param pdu - protocol data unit (PDU) to send + * @param pdu_len - size of the protocol data unit (PDU) + */ +int bzll_send_pdu( + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + (void)dest; + (void)npdu_data; + (void)pdu; + (void)pdu_len; + + return 0; +} + +/** + * @brief Poll the datalink queue to see if a packet has arrived + * @param src - origin address of the packet + * @param pdu - protocol data unit (PDU) buffer to store received packet + * @param pdu_size - size of the protocol data unit (PDU) buffer + * @param timeout - number of milliseconds to wait for a packet + */ +uint16_t bzll_receive( + BACNET_ADDRESS *src, uint8_t *pdu, uint16_t pdu_size, unsigned timeout) +{ + (void)src; + (void)pdu; + (void)pdu_size; + (void)timeout; + + return 0; +} + +/** + * @brief cleanup the datalink data or connections + */ +void bzll_cleanup(void) +{ + /* nothing to do */ +} + +/** + * @brief Initialize the a data link broadcast address + * @param dest - address to be filled with broadcast designator + */ +void bzll_get_broadcast_address(BACNET_ADDRESS *dest) +{ + int i = 0; + + if (dest) { + dest->mac_len = 3; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->mac[i] = 0xFF; + } + dest->net = BACNET_BROADCAST_NETWORK; + /* always zero when DNET is broadcast */ + dest->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} + +/** + * @brief Set the BACnet address for my interface + * @param my_address - address to be filled with my interface address + */ +void bzll_get_my_address(BACNET_ADDRESS *my_address) +{ + uint32_t instance; + + instance = Device_Object_Instance_Number(); + bacnet_vmac_address_set(my_address, instance); + + return; +} + +/** + * @brief Set the maintenance timer for the datalink + * @param seconds - number of seconds to set the timer + */ +void bzll_maintenance_timer(uint16_t seconds) +{ + (void)seconds; +} diff --git a/ports/linux/bzll-init.c b/ports/linux/bzll-init.c new file mode 100644 index 00000000..973beffe --- /dev/null +++ b/ports/linux/bzll-init.c @@ -0,0 +1,119 @@ +/** + * @file + * @brief Initializes BACnet Zigbee Link Layer interface (Linux). + * @author Steve Karg + * @date July 2025 + * @copyright SPDX-License-Identifier: MIT + */ +/* BACnet specific */ +#include "bacnet/bacdef.h" +#include "bacnet/bacaddr.h" +#include "bacnet/bacint.h" +#include "bacnet/datalink/bzll.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/object/device.h" + +/** + * @brief Initialize the datalink + * @param ifname - the name of the datalink + */ +bool bzll_init(char *ifname) +{ + (void)ifname; + + return true; +} + +/** + * @brief Send a protocol data unit (PDU) to the network + * @param dest - destination address + * @param npdu_data - network routing data + * @param pdu - protocol data unit (PDU) to send + * @param pdu_len - size of the protocol data unit (PDU) + */ +int bzll_send_pdu( + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + (void)dest; + (void)npdu_data; + (void)pdu; + (void)pdu_len; + + return 0; +} + +/** + * @brief Poll the datalink queue to see if a packet has arrived + * @param src - origin address of the packet + * @param pdu - protocol data unit (PDU) buffer to store received packet + * @param pdu_size - size of the protocol data unit (PDU) buffer + * @param timeout - number of milliseconds to wait for a packet + */ +uint16_t bzll_receive( + BACNET_ADDRESS *src, uint8_t *pdu, uint16_t pdu_size, unsigned timeout) +{ + (void)src; + (void)pdu; + (void)pdu_size; + (void)timeout; + + return 0; +} + +/** + * @brief cleanup the datalink data or connections + */ +void bzll_cleanup(void) +{ + /* nothing to do */ +} + +/** + * @brief Initialize the a data link broadcast address + * @param dest - address to be filled with broadcast designator + */ +void bzll_get_broadcast_address(BACNET_ADDRESS *dest) +{ + int i = 0; + + if (dest) { + dest->mac_len = 3; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->mac[i] = 0xFF; + } + dest->net = BACNET_BROADCAST_NETWORK; + /* always zero when DNET is broadcast */ + dest->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} + +/** + * @brief Set the BACnet address for my interface + * @param my_address - address to be filled with my interface address + */ +void bzll_get_my_address(BACNET_ADDRESS *my_address) +{ + uint32_t instance; + + instance = Device_Object_Instance_Number(); + bacnet_vmac_address_set(my_address, instance); + + return; +} + +/** + * @brief Set the maintenance timer for the datalink + * @param seconds - number of seconds to set the timer + */ +void bzll_maintenance_timer(uint16_t seconds) +{ + (void)seconds; +} diff --git a/ports/win32/bzll-init.c b/ports/win32/bzll-init.c new file mode 100644 index 00000000..704bf253 --- /dev/null +++ b/ports/win32/bzll-init.c @@ -0,0 +1,119 @@ +/** + * @file + * @brief Initializes BACnet Zigbee Link Layer interface (Windows). + * @author Steve Karg + * @date July 2025 + * @copyright SPDX-License-Identifier: MIT + */ +/* BACnet specific */ +#include "bacnet/bacdef.h" +#include "bacnet/bacaddr.h" +#include "bacnet/bacint.h" +#include "bacnet/datalink/bzll.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/object/device.h" + +/** + * @brief Initialize the datalink + * @param ifname - the name of the datalink + */ +bool bzll_init(char *ifname) +{ + (void)ifname; + + return true; +} + +/** + * @brief Send a protocol data unit (PDU) to the network + * @param dest - destination address + * @param npdu_data - network routing data + * @param pdu - protocol data unit (PDU) to send + * @param pdu_len - size of the protocol data unit (PDU) + */ +int bzll_send_pdu( + BACNET_ADDRESS *dest, + BACNET_NPDU_DATA *npdu_data, + uint8_t *pdu, + unsigned pdu_len) +{ + (void)dest; + (void)npdu_data; + (void)pdu; + (void)pdu_len; + + return 0; +} + +/** + * @brief Poll the datalink queue to see if a packet has arrived + * @param src - origin address of the packet + * @param pdu - protocol data unit (PDU) buffer to store received packet + * @param pdu_size - size of the protocol data unit (PDU) buffer + * @param timeout - number of milliseconds to wait for a packet + */ +uint16_t bzll_receive( + BACNET_ADDRESS *src, uint8_t *pdu, uint16_t pdu_size, unsigned timeout) +{ + (void)src; + (void)pdu; + (void)pdu_size; + (void)timeout; + + return 0; +} + +/** + * @brief cleanup the datalink data or connections + */ +void bzll_cleanup(void) +{ + /* nothing to do */ +} + +/** + * @brief Initialize the a data link broadcast address + * @param dest - address to be filled with broadcast designator + */ +void bzll_get_broadcast_address(BACNET_ADDRESS *dest) +{ + int i = 0; + + if (dest) { + dest->mac_len = 3; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->mac[i] = 0xFF; + } + dest->net = BACNET_BROADCAST_NETWORK; + /* always zero when DNET is broadcast */ + dest->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} + +/** + * @brief Set the BACnet address for my interface + * @param my_address - address to be filled with my interface address + */ +void bzll_get_my_address(BACNET_ADDRESS *my_address) +{ + uint32_t instance; + + instance = Device_Object_Instance_Number(); + bacnet_vmac_address_set(my_address, instance); + + return; +} + +/** + * @brief Set the maintenance timer for the datalink + * @param seconds - number of seconds to set the timer + */ +void bzll_maintenance_timer(uint16_t seconds) +{ + (void)seconds; +} diff --git a/src/bacnet/bacaddr.c b/src/bacnet/bacaddr.c index b0788e1f..5c52d1ed 100644 --- a/src/bacnet/bacaddr.c +++ b/src/bacnet/bacaddr.c @@ -539,3 +539,29 @@ int bacnet_vmac_entry_decode( return apdu_len; } + +/** Set a BACnet VMAC Address from a Device ID + * + * @param addr - BACnet address that be set + * @param device_id - 22-bit device ID + * + * @return true if the address is set + */ +bool bacnet_vmac_address_set(BACNET_ADDRESS *addr, uint32_t device_id) +{ + bool status = false; + size_t i; + + if (addr) { + encode_unsigned24(&addr->mac[0], device_id); + addr->mac_len = 3; + addr->net = 0; + addr->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + addr->adr[i] = 0; + } + status = true; + } + + return status; +} diff --git a/src/bacnet/bacaddr.h b/src/bacnet/bacaddr.h index cb7a2e71..f025651a 100644 --- a/src/bacnet/bacaddr.h +++ b/src/bacnet/bacaddr.h @@ -87,6 +87,8 @@ int bacnet_vmac_entry_encode( BACNET_STACK_EXPORT int bacnet_vmac_entry_decode( const uint8_t *apdu, uint32_t apdu_size, BACNET_VMAC_ENTRY *value); +BACNET_STACK_EXPORT +bool bacnet_vmac_address_set(BACNET_ADDRESS *addr, uint32_t device_id); #ifdef __cplusplus } diff --git a/src/bacnet/basic/bzll/bzllvmac.c b/src/bacnet/basic/bzll/bzllvmac.c new file mode 100644 index 00000000..bcadd4ec --- /dev/null +++ b/src/bacnet/basic/bzll/bzllvmac.c @@ -0,0 +1,305 @@ +/** + * @file + * @brief Virtual MAC (VMAC) for BACnet ZigBee Link Layer + * @author Steve Karg + * @date July 2025 + * @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0 + */ +#include +#include +#include +#include +#include +#include "bacnet/bacdef.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/sys/keylist.h" +/* me! */ +#include "bacnet/basic/bzll/bzllvmac.h" + +/* enable debugging */ +static bool VMAC_Debug = false; + +/** + * @brief Enable debugging if print is enabled + */ +void BZLL_VMAC_Debug_Enable(void) +{ + VMAC_Debug = true; +} + +/** @file + Handle VMAC address binding */ + +/* This module is used to handle the virtual MAC address binding that */ +/* occurs in BACnet for ZigBee or IPv6. */ + +/* Key List for storing the object data sorted by instance number */ +static OS_Keylist VMAC_List; + +/** + * Returns the number of VMAC in the list + */ +unsigned int BZLL_VMAC_Count(void) +{ + return (unsigned int)Keylist_Count(VMAC_List); +} + +/** + * Adds a VMAC to the list + * + * @param device_id - BACnet device object instance number + * @param vmac - BACnet ZigBee Link Layer address + * + * @return true if the device ID and MAC are added + */ +bool BZLL_VMAC_Add(uint32_t device_id, const struct bzll_vmac_data *vmac) +{ + bool status = false; + struct bzll_vmac_data *list_vmac = NULL; + uint32_t list_device_id = 0; + int index = 0; + size_t i = 0; + bool found = false; + + if (BZLL_VMAC_Entry_To_Device_ID(vmac, &list_device_id)) { + if (list_device_id == device_id) { + /* valid VMAC entry exists. */ + found = true; + status = true; + } else { + /* VMAC exists, but device ID changed */ + BZLL_VMAC_Delete(list_device_id); + } + } + if (!found) { + list_vmac = Keylist_Data(VMAC_List, device_id); + if (list_vmac) { + /* device ID already exists. Update MAC. */ + memmove(list_vmac, vmac, sizeof(struct bzll_vmac_data)); + found = true; + status = true; + } + } + if (!found) { + /* new entry - add it! */ + list_vmac = calloc(1, sizeof(struct bzll_vmac_data)); + if (list_vmac) { + /* copy the MAC into the data store */ + for (i = 0; i < sizeof(list_vmac->mac); i++) { + list_vmac->mac[i] = vmac->mac[i]; + } + list_vmac->endpoint = vmac->endpoint; + index = Keylist_Data_Add(VMAC_List, device_id, list_vmac); + if (index >= 0) { + status = true; + if (VMAC_Debug) { + debug_fprintf( + stderr, "BZLL VMAC %u added.\n", + (unsigned int)device_id); + } + } + } + } + + return status; +} + +/** + * Finds a VMAC in the list by seeking the Device ID, and deletes it. + * + * @param device_id - BACnet device object instance number + * + * @return pointer to the VMAC data from the list - be sure to free() it! + */ +bool BZLL_VMAC_Delete(uint32_t device_id) +{ + bool status = false; + struct bzll_vmac_data *pVMAC; + + pVMAC = Keylist_Data_Delete(VMAC_List, device_id); + if (pVMAC) { + free(pVMAC); + status = true; + } + + return status; +} + +/** + * Finds a VMAC in the list by seeking the Device ID. + * + * @param device_id - BACnet device object instance number + * + * @return pointer to the VMAC data from the list + */ +bool BZLL_VMAC_Entry_By_Device_ID( + uint32_t device_id, struct bzll_vmac_data *vmac) +{ + struct bzll_vmac_data *data = Keylist_Data(VMAC_List, device_id); + if (data && vmac) { + memcpy(vmac, data, sizeof(struct bzll_vmac_data)); + return true; + } + return false; +} + +/** + * Finds a VMAC in the list by seeking the list index + * + * @param index - Index that shall be returned + * @param device_id - BACnet device object instance number + * + * @return true if the device_id and vmac are found + */ +bool BZLL_VMAC_Entry_By_Index( + int index, uint32_t *device_id, struct bzll_vmac_data *vmac) +{ + bool status = false; + struct bzll_vmac_data *data; + KEY key = 0; + + data = Keylist_Data_Index(VMAC_List, index); + if (data) { + /* virtual MAC is the Device ID */ + status = Keylist_Index_Key(VMAC_List, index, &key); + if (status) { + if (device_id) { + *device_id = key; + } + if (vmac) { + memcpy(vmac, data, sizeof(struct bzll_vmac_data)); + } + } + } + + return status; +} + +/** Compare the VMAC address + * + * @param vmac1 - VMAC address that will be compared to vmac2 + * @param vmac2 - VMAC address that will be compared to vmac1 + * + * @return true if the addresses are the same + */ +bool BZLL_VMAC_Same( + const struct bzll_vmac_data *vmac1, const struct bzll_vmac_data *vmac2) +{ + bool status = false; + + if (vmac1 && vmac2) { + if (memcmp(vmac1->mac, vmac2->mac, BZLL_VMAC_EUI64) == 0 && + vmac1->endpoint == vmac2->endpoint) { + status = true; + } + } + + return status; +} + +/** + * Finds a VMAC in the list by seeking a matching VMAC address + * + * @param vmac - VMAC address that will be sought + * @param device_id - BACnet device object instance number + * + * @return true if the VMAC address was found + */ +bool BZLL_VMAC_Entry_To_Device_ID( + const struct bzll_vmac_data *vmac, uint32_t *device_id) +{ + bool status = false; + struct bzll_vmac_data *list_vmac; + int count = 0; + int index = 0; + + if (!vmac) { + return false; /* invalid parameter */ + } + count = Keylist_Count(VMAC_List); + while (count) { + index = count - 1; + list_vmac = Keylist_Data_Index(VMAC_List, index); + if (list_vmac) { + if (BZLL_VMAC_Same(vmac, list_vmac)) { + status = Keylist_Index_Key(VMAC_List, index, device_id); + break; + } + } + count--; + } + + return status; +} + +/** + * Copies the VMAC address into the provided MAC and endpoint + * + * @param vmac - VMAC address that will be copied + * @param mac - pointer to the MAC array to copy into + * @param endpoint - pointer to the endpoint to copy into + * + * @return true if the VMAC address was copied + */ +bool BZLL_VMAC_Entry_Set( + struct bzll_vmac_data *vmac, const uint8_t *mac, uint8_t endpoint) +{ + bool status = false; + unsigned int i; + + if (vmac && mac) { + for (i = 0; i < BZLL_VMAC_EUI64; i++) { + vmac->mac[i] = mac[i]; /* copy the MAC */ + } + vmac->endpoint = endpoint; + } + + return status; +} + +/** + * Cleans up the memory used by the VMAC list data + */ +void BZLL_VMAC_Cleanup(void) +{ + struct bzll_vmac_data *pVMAC; + const int index = 0; + unsigned i = 0; + + if (VMAC_List) { + do { + uint32_t device_id; + if (VMAC_Debug) { + Keylist_Index_Key(VMAC_List, index, &device_id); + } + pVMAC = Keylist_Data_Delete_By_Index(VMAC_List, index); + if (pVMAC) { + if (VMAC_Debug) { + debug_fprintf( + stderr, "BZLL VMAC List: %lu [", + (unsigned long)device_id); + /* print the MAC */ + for (i = 0; i < BZLL_VMAC_EUI64; i++) { + debug_fprintf(stderr, "%02X", pVMAC->mac[i]); + } + debug_fprintf(stderr, "]\n"); + } + free(pVMAC); + } + } while (pVMAC); + Keylist_Delete(VMAC_List); + VMAC_List = NULL; + } +} + +/** + * Initializes the VMAC list data + */ +void BZLL_VMAC_Init(void) +{ + VMAC_List = Keylist_Create(); + if (VMAC_List) { + atexit(BZLL_VMAC_Cleanup); + debug_fprintf(stderr, "BZLL VMAC List initialized.\n"); + } +} diff --git a/src/bacnet/basic/bzll/bzllvmac.h b/src/bacnet/basic/bzll/bzllvmac.h new file mode 100644 index 00000000..82dbff51 --- /dev/null +++ b/src/bacnet/basic/bzll/bzllvmac.h @@ -0,0 +1,66 @@ +/** + * @file + * @brief API for Virtual MAC (VMAC) of BACnet Zigbee Link Layer (BZLL) + * @author Steve Karg + * @date July 2025 + * @copyright SPDX-License-Identifier: MIT + */ +#ifndef BACNET_BASIC_BZLL_VMAC_H +#define BACNET_BASIC_BZLL_VMAC_H + +#include +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" + +/* define the max MAC as big as EUI64 */ +#define BZLL_VMAC_EUI64 8 +/** + * VMAC data structure + * + * @{ + */ +struct bzll_vmac_data { + uint8_t mac[BZLL_VMAC_EUI64]; + uint8_t endpoint; +}; +/** @} */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +BACNET_STACK_EXPORT +bool BZLL_VMAC_Entry_By_Device_ID( + uint32_t device_id, struct bzll_vmac_data *vmac); +BACNET_STACK_EXPORT +bool BZLL_VMAC_Entry_By_Index( + int index, uint32_t *device_id, struct bzll_vmac_data *vmac); +BACNET_STACK_EXPORT +bool BZLL_VMAC_Entry_To_Device_ID( + const struct bzll_vmac_data *vmac, uint32_t *device_id); +BACNET_STACK_EXPORT +bool BZLL_VMAC_Entry_Set( + struct bzll_vmac_data *vmac, const uint8_t *mac, uint8_t endpoint); +BACNET_STACK_EXPORT +bool BZLL_VMAC_Add(uint32_t device_id, const struct bzll_vmac_data *vmac); +BACNET_STACK_EXPORT +bool BZLL_VMAC_Delete(uint32_t device_id); +BACNET_STACK_EXPORT +bool BZLL_VMAC_Same( + const struct bzll_vmac_data *vmac1, const struct bzll_vmac_data *vmac2); + +BACNET_STACK_EXPORT +unsigned int BZLL_VMAC_Count(void); +BACNET_STACK_EXPORT +void BZLL_VMAC_Cleanup(void); +BACNET_STACK_EXPORT +void BZLL_VMAC_Init(void); + +BACNET_STACK_EXPORT +void BZLL_VMAC_Debug_Enable(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/src/bacnet/datalink/datalink.c b/src/bacnet/datalink/datalink.c index c402b363..b08d5745 100644 --- a/src/bacnet/datalink/datalink.c +++ b/src/bacnet/datalink/datalink.c @@ -276,7 +276,7 @@ void datalink_cleanup(void) #endif #if defined(BACDL_ZIGBEE) case DATALINK_ZIGBEE: - bytes = bzll_cleanup(); + bzll_cleanup(); break; #endif #if defined(BACDL_BSC) @@ -321,7 +321,7 @@ void datalink_get_broadcast_address(BACNET_ADDRESS *dest) #endif #if defined(BACDL_ZIGBEE) case DATALINK_ZIGBEE: - bytes = bzll_get_broadcast_address(dest); + bzll_get_broadcast_address(dest); break; #endif #if defined(BACDL_BSC) @@ -366,7 +366,7 @@ void datalink_get_my_address(BACNET_ADDRESS *my_address) #endif #if defined(BACDL_ZIGBEE) case DATALINK_ZIGBEE: - bytes = bzll_get_my_address(my_address); + bzll_get_my_address(my_address); break; #endif #if defined(BACDL_BSC) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 57986297..5e0bd444 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -133,6 +133,7 @@ list(APPEND testdirs bacnet/basic/binding/address bacnet/basic/bbmd bacnet/basic/bbmd6 + bacnet/basic/bzll # basic/object bacnet/basic/object/acc bacnet/basic/object/access_credential diff --git a/test/bacnet/basic/bzll/CMakeLists.txt b/test/bacnet/basic/bzll/CMakeLists.txt new file mode 100644 index 00000000..5b6d457f --- /dev/null +++ b/test/bacnet/basic/bzll/CMakeLists.txt @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.10 FATAL_ERROR) + +get_filename_component(basename ${CMAKE_CURRENT_SOURCE_DIR} NAME) +project(test_${basename} + VERSION 1.0.0 + LANGUAGES C) + +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z0-9_/-]*$" + "/src" + SRC_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE + "/test/bacnet/[a-zA-Z0-9_/-]*$" + "/test" + TST_DIR + ${CMAKE_CURRENT_SOURCE_DIR}) + +add_compile_definitions( + BIG_ENDIAN=0 + ) + +include_directories( + ${SRC_DIR} + ) + +add_executable(${PROJECT_NAME} + # File(s) under test + ${SRC_DIR}/bacnet/basic/bzll/bzllvmac.c + # Support files and stubs (pathname alphabetical) + ${SRC_DIR}/bacnet/bacaddr.c + ${SRC_DIR}/bacnet/bacdcode.c + ${SRC_DIR}/bacnet/bacint.c + ${SRC_DIR}/bacnet/bacstr.c + ${SRC_DIR}/bacnet/bacreal.c + ${SRC_DIR}/bacnet/iam.c + ${SRC_DIR}/bacnet/npdu.c + ${SRC_DIR}/bacnet/basic/sys/bigend.c + ${SRC_DIR}/bacnet/basic/sys/debug.c + ${SRC_DIR}/bacnet/basic/sys/keylist.c + ${SRC_DIR}/bacnet/hostnport.c + # Test and test library files + ./src/main.c + ) diff --git a/test/bacnet/basic/bzll/src/main.c b/test/bacnet/basic/bzll/src/main.c new file mode 100644 index 00000000..0c4567d4 --- /dev/null +++ b/test/bacnet/basic/bzll/src/main.c @@ -0,0 +1,176 @@ +/** + * @file + * @brief Test file for a basic BACnet Zigbee Link Layer (BZLL) + * @author Steve Karg + * @date July 2025 + * @copyright SPDX-License-Identifier: MIT + */ +#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 +#include +#include "bacnet/bacaddr.h" +#include "bacnet/bacdcode.h" +#include "bacnet/iam.h" +#include "bacnet/npdu.h" +#include "bacnet/datalink/bzll.h" +#include "bacnet/basic/sys/debug.h" +#include "bacnet/basic/object/device.h" +#include "bacnet/basic/bzll/bzllvmac.h" + +struct device_info_t { + uint32_t Device_ID; + /* MAC Address shall be a ZigBee EUI64 and BACnet endpoint */ + struct bzll_vmac_data VMAC_Data; + BACNET_ADDRESS BACnet_Address; +}; +static struct device_info_t TD; +static struct device_info_t IUT; + +/* 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 bzll_receive( + BACNET_ADDRESS *src, uint8_t *npdu, uint16_t max_npdu, unsigned timeout) +{ + (void)src; + (void)npdu; + (void)max_npdu; + (void)timeout; + + return 0; +} + +/** + * The send function for BACnet/IPv6 driver layer + * + * @param dest - Points to a BACNET_IP6_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 to indicate the error. + */ +int bzll_send_mpdu( + const BACNET_IP6_ADDRESS *dest, const uint8_t *mtu, uint16_t mtu_len) +{ + (void)dest; + (void)mtu; + (void)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 IUT.Device_ID; +} + +/** + * Test setup function + */ +static void test_setup(void) +{ + uint8_t td_mac[BZLL_VMAC_EUI64] = { 0x00, 0x12, 0x34, 0x56, + 0x78, 0x9A, 0xBC, 0xDE }; + uint8_t td_endpoint = 0x01; + uint8_t iut_mac[BZLL_VMAC_EUI64] = { 0x00, 0x12, 0x34, 0x56, + 0x78, 0x9A, 0xBC, 0xDF }; + uint8_t iut_endpoint = 0x02; + + BZLL_VMAC_Init(); + TD.Device_ID = 12345; + bacnet_vmac_address_set(&TD.BACnet_Address, TD.Device_ID); + BZLL_VMAC_Entry_Set(&TD.VMAC_Data, td_mac, td_endpoint); + IUT.Device_ID = 67890; + bacnet_vmac_address_set(&IUT.BACnet_Address, IUT.Device_ID); + BZLL_VMAC_Entry_Set(&IUT.VMAC_Data, iut_mac, iut_endpoint); +} + +/** + * Cleanup function to free resources + */ +static void test_cleanup(void) +{ + BZLL_VMAC_Cleanup(); +} + +/** + * Test function to execute the virtual address resolution + * and verify the functionality of the BZLL VMAC handling. + * + * This function will test adding, retrieving, and comparing VMAC entries. + * It will also check the behavior when changing device IDs. + */ +static void test_Execute_Virtual_Address_Resolution(void) +{ + uint32_t test_vmac_src = 0; + uint32_t test_device_id = 0; + uint32_t old_device_id = 0; + struct bzll_vmac_data test_vmac_data = { 0 }; + unsigned int count = 0; + int index = 0; + bool status = false; + + test_setup(); + status = BZLL_VMAC_Add(TD.Device_ID, &TD.VMAC_Data); + assert(status == true); + status = BZLL_VMAC_Entry_By_Device_ID(TD.Device_ID, &test_vmac_data); + assert(status == true); + status = BZLL_VMAC_Same(&TD.VMAC_Data, &test_vmac_data); + assert(status == true); + /* change Device ID */ + old_device_id = TD.Device_ID; + TD.Device_ID += 42; + status = BZLL_VMAC_Add(TD.Device_ID, &TD.VMAC_Data); + assert(status == true); + count = BZLL_VMAC_Count(); + assert(count == 1); + status = BZLL_VMAC_Entry_By_Device_ID(TD.Device_ID, &test_vmac_data); + assert(status == true); + status = BZLL_VMAC_Entry_By_Device_ID(old_device_id, &test_vmac_data); + assert(status == false); + status = BZLL_VMAC_Entry_By_Index(0, &test_device_id, &test_vmac_data); + assert(status == true); + assert(test_device_id == TD.Device_ID); + status = BZLL_VMAC_Same(&TD.VMAC_Data, &test_vmac_data); + assert(status == true); + status = BZLL_VMAC_Add(IUT.Device_ID, &IUT.VMAC_Data); + assert(status == true); + count = BZLL_VMAC_Count(); + for (index = 0; index < count; index++) { + status = BZLL_VMAC_Entry_By_Index(index, &test_vmac_src, NULL); + assert(status == true); + status = BZLL_VMAC_Entry_By_Device_ID(test_vmac_src, &test_vmac_data); + assert(status == true); + } + test_cleanup(); +} + +/** + * Main function to execute the test + * + * @return 0 on success, non-zero on failure + */ +int main(void) +{ + test_Execute_Virtual_Address_Resolution(); + + return 0; +}