Feature/bacnet secure connect hub (#818)

* Added BACnet Secure Connect datalink.

* Added BACnet/SC hub application
---------

Co-authored-by: Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
Co-authored-by: Mikhail Antropov <michail.antropov@dsr-corporation.com>
Co-authored-by: Ondřej Hruška <ondra@ondrovo.com>
Co-authored-by: Patrick Grimm <patrick@lunatiki.de>
This commit is contained in:
Steve Karg
2024-11-04 07:05:26 -06:00
committed by GitHub
parent 90cdc4920b
commit cf77abac9b
161 changed files with 82982 additions and 23 deletions
+54
View File
@@ -0,0 +1,54 @@
This readme explains how to enable support of BACNet Secure Connect datalink
(ANNEX AB BACnet Secure Connect in standard), how to set up the building
environment and clarifies some important moments regarding implementation
of that functionality.
By default the support of that functionality is turned off, to enable it
you need to set option BACDL_BSC=ON in CMakeLists.txt if cmake build system
is used or set BACDL_BSC=1 define in a case if using makefiles.
BACNet/SC standard uses websockets as a transport layer, so Windows/Linux/BSD
implementations use libwebsocket library. Implementation for Zephyr uses
native websocket API for the client side and mongoose library for the
server side. Websocket layer which is built on the top of libwebsockets
uses 1 service thread per websocket server instance and 1 service thread
per 1 websocket client instance. As a result, libwebsocket must be built
with LWS_MAX_SMP > 1, otherwise rarely crashes may ocure in the application
which uses bacnet stack library. Recommended value for that define is 32.
You should note, that by default libwebsocket is built with LWS_MAX_SMP=1,
so a packet manager like vcpkg and apt can have that lib built with
LWS_MAX_SMP=1, which can lead to unstable work. It's better to build
libwebsockets from recent stable sources for your platform, corresponded
examples of how to do that you can find in .github/workflows/bsc-tests-platform.yml,
(for example bsc-tests-linux.yml).
In order to build bacnet stack library for linux, user needs to install
libconfig-dev, libcap-dev and libssl-dev to the system. Most easiest way to do
that is to use Advanced Packaging Tool (APT), check bsc-test-linux.yml.
For MacOSX build user must install brew packet manager (https://brew.sh),
then install openssl using brew then build libwebsocket, check
.github/workflows/bsc-tests-macosx.yml.
Windows build may be a challenge because libwebsocket depends on libpthreads
which not so easy to find and build on windows, refer to the libwebsocket
build instruction https://libwebsockets.org/lws-api-doc-master/html/md_README_8build.html.
Using of vcpkg packet manager can save a lot of time, but you must ensure that
installed libwebsocket library is compiled with LWS_MAX_SMP > 1.
A good example how to use vcpkg packet manager with custom build of
libwebsocket can be found in .github/workflows/bsc-tests-windows.yml.
One moment is still needed to be improved. The current implementation processes
dataflow between BACNET/SC nodes ignoring max_bvlc_size and max_npdu_size
params in connect request and connect accept packets as that is not
explicitly explained in standard. As a result if a node which has max_npdu_size
less than remote peer can drop received packet if it does not fit into it's internal
buffer which depends on BVLC_SC_NPDU_SIZE_CONF parameter. In opposite, a node
can send a PDU more than max_npdu_size which may also lead to the drop of PDU
on remote peer side.
The current implementation does not support Certificate_Signing_Request_File
property of BACNET/SC netport and properties Operational_Certificate_File
and Issuer_Certificate_Files related to certificates are readonly and can't
be changed remotely. So, management of device certificates is out of the scope
of current BACNet/SC implementation.
+169
View File
@@ -0,0 +1,169 @@
/**
* @file
* @brief Configuration file of BACNet/SC datalink.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date August 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_CONF_H
#define BACNET_DATALINK_BSC_CONF_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bvlc-sc.h"
#if !defined(BACDL_BSC)
#define BSC_CONF_TX_PRE 0
#else
#ifndef bsd
#define bsd 1
#endif
#ifndef linux
#define linux 2
#endif
#ifndef win32
#define win32 3
#endif
#if BACNET_PORT == bsd || BACNET_PORT == linux || BACNET_PORT == win32
#include <libwebsockets.h>
#define BSC_CONF_TX_PRE LWS_PRE
#else
#define BSC_CONF_TX_PRE 0
#endif
#endif
#ifndef BSC_CONF_HUB_CONNECTORS_NUM
#define BSC_CONF_HUB_CONNECTORS_NUM 1
#endif
#ifndef BSC_CONF_HUB_FUNCTIONS_NUM
#define BSC_CONF_HUB_FUNCTIONS_NUM 1
#endif
#ifndef BSC_CONF_NODE_SWITCHES_NUM
#define BSC_CONF_NODE_SWITCHES_NUM 1
#endif
#ifndef BSC_CONF_NODES_NUM
#define BSC_CONF_NODES_NUM 1
#endif
#ifndef BVLC_SC_NPDU_SIZE_CONF
/* 16 bytes is sum of all sizes of all static (non variable)
fields of header of BVLC message */
#define BVLC_SC_NPDU_SIZE_CONF ((MAX_PDU) + 16)
#endif
#ifndef BSC_CONF_WEBSOCKET_RX_BUFFER_LEN
#define BSC_CONF_WEBSOCKET_RX_BUFFER_LEN BVLC_SC_NPDU_SIZE_CONF
#endif
/* THIS should not be changed, most of BACNet/SC devices must have */
/* hub connector, it uses 2 connections */
#ifndef BSC_CONF_HUB_CONNECTOR_CONNECTIONS_NUM
#define BSC_CONF_HUB_CONNECTOR_CONNECTIONS_NUM (BSC_CONF_HUB_CONNECTORS_NUM * 2)
#endif
#ifndef BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM
#define BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM (BSC_CONF_HUB_FUNCTIONS_NUM * 10)
#endif
#ifndef BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM
#define BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM 10
#endif
/* Total amount of client(initiator) webosocket connections */
#ifndef BSC_CONF_CLIENT_CONNECTIONS_NUM
#define BSC_CONF_CLIENT_CONNECTIONS_NUM \
(BSC_CONF_HUB_CONNECTOR_CONNECTIONS_NUM + \
BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM * BSC_CONF_NODE_SWITCHES_NUM)
#endif
#ifndef BSC_CONF_SERVER_HUB_CONNECTIONS_MAX_NUM
#define BSC_CONF_SERVER_HUB_CONNECTIONS_MAX_NUM \
(BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM)
#endif
#ifndef BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM
#define BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM \
(BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM * BSC_CONF_NODE_SWITCHES_NUM)
#endif
#define BSC_CONF_SOCKET_TX_BUFFERED_PACKET_NUM 2
#define BSC_CONF_DATALINK_BUFFERED_PACKET_NUM 10
#define BSC_CONF_SOCK_RX_BUFFER_SIZE BVLC_SC_NPDU_SIZE_CONF
/* 2 bytes is a prefix containing BVLC message length.
BSC_CONF_TX_PRE - some reserved bytes before actual payload.
Some libs like libwebsocket requires some bytes to be reserved
before actual payload for sending, so BSC_CONF_TX_PRE is used for
that purpose (it allows to avoid copying of payload and
buffer reallocation)
*/
#define BSC_CONF_SOCK_TX_BUFFER_SIZE \
((BVLC_SC_NPDU_SIZE_CONF + 2 + BSC_CONF_TX_PRE) * \
BSC_CONF_SOCKET_TX_BUFFERED_PACKET_NUM)
/* datalink RX buffer size is always rounded to next power of two */
/* so if final buffer size is 1628 it will be rounded to 2048 */
#define BSC_CONF_DATALINK_RX_BUFFER_SIZE \
(BVLC_SC_NPDU_SIZE_CONF * BSC_CONF_DATALINK_BUFFERED_PACKET_NUM)
#ifndef BSC_CONF_WSURL_MAX_LEN
#define BSC_CONF_WSURL_MAX_LEN 128
#endif
#ifndef BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN
#define BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN 128
#endif
#ifndef BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK
#define BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK \
BSC_CONF_WSURL_MAX_LEN
#endif
#ifndef BSC_CONF_NODE_MAX_URIS_NUM_IN_ADDRESS_RESOLUTION_ACK
#define BSC_CONF_NODE_MAX_URIS_NUM_IN_ADDRESS_RESOLUTION_ACK \
(BSC_CONF_SOCK_RX_BUFFER_SIZE / \
BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK - \
1)
#endif
#ifndef BSC_CONF_OPERATIONAL_CERTIFICATE_FILE_INSTANCE
#define BSC_CONF_OPERATIONAL_CERTIFICATE_FILE_INSTANCE 5
#endif
#ifndef BSC_CONF_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE
#define BSC_CONF_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE 6
#endif
#ifndef BSC_CONF_ISSUER_CERTIFICATE_FILE_1_INSTANCE
#define BSC_CONF_ISSUER_CERTIFICATE_FILE_1_INSTANCE 7
#endif
#ifndef BSC_CONF_ISSUER_CERTIFICATE_FILE_2_INSTANCE
#define BSC_CONF_ISSUER_CERTIFICATE_FILE_2_INSTANCE 8
#endif
#ifndef BSC_CONF_ISSUER_CERTIFICATE_FILE_3_INSTANCE
#define BSC_CONF_ISSUER_CERTIFICATE_FILE_3_INSTANCE 9
#endif
#ifndef BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM
#define BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM \
BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM
#endif
#ifndef BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM
#define BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM \
BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM
#endif
#ifndef BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM
#define BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM 4
#endif
#endif
+653
View File
@@ -0,0 +1,653 @@
/**
* @file
* @brief BACnet secure connect hub function API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date October 2022
* @copyright SPDX-License-Identifier: MIT
*/
#include "bacnet/basic/sys/debug.h"
#include <bacnet/basic/sys/fifo.h>
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-datalink.h"
#include "bacnet/datalink/bsc/bsc-socket.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/datalink/bsc/bsc-event.h"
#include "bacnet/datalink/bsc/bsc-hub-function.h"
#include "bacnet/datalink/bsc/bsc-node.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/bacenum.h"
#include "bacnet/basic/object/netport.h"
#include "bacnet/basic/object/sc_netport.h"
#include "bacnet/basic/object/bacfile.h"
#define PRINTF debug_perror
#define DEBUG_BSC_DATALINK 0
#if DEBUG_BSC_DATALINK == 1
#define DEBUG_PRINTF debug_printf
#else
#define DEBUG_PRINTF debug_printf_disabled
#endif
#define BSC_NEXT_POWER_OF_TWO1(v) \
((((unsigned int)v) - 1) | ((((unsigned int)v) - 1) >> 1))
#define BSC_NEXT_POWER_OF_TWO2(v) \
(BSC_NEXT_POWER_OF_TWO1(v) | BSC_NEXT_POWER_OF_TWO1(v) >> 2)
#define BSC_NEXT_POWER_OF_TWO3(v) \
(BSC_NEXT_POWER_OF_TWO2(v) | BSC_NEXT_POWER_OF_TWO2(v) >> 4)
#define BSC_NEXT_POWER_OF_TWO4(v) \
(BSC_NEXT_POWER_OF_TWO3(v) | BSC_NEXT_POWER_OF_TWO3(v) >> 8)
#define BSC_NEXT_POWER_OF_TWO(v) \
((BSC_NEXT_POWER_OF_TWO4(v) | BSC_NEXT_POWER_OF_TWO4(v) >> 16)) + 1
typedef enum {
BSC_DATALINK_STATE_IDLE = 0,
BSC_DATALINK_STATE_STARTING = 1,
BSC_DATALINK_STATE_STARTED = 2,
BSC_DATALINK_STATE_STOPPING = 3
} BSC_DATALINK_STATE;
static FIFO_BUFFER bsc_fifo = { 0 };
static uint8_t
bsc_fifo_buf[BSC_NEXT_POWER_OF_TWO(BSC_CONF_DATALINK_RX_BUFFER_SIZE)];
static BSC_NODE *bsc_node = NULL;
static BSC_NODE_CONF bsc_conf;
static BSC_DATALINK_STATE bsc_datalink_state = BSC_DATALINK_STATE_IDLE;
static BSC_EVENT *bsc_event = NULL;
static BSC_EVENT *bsc_data_event = NULL;
/**
* @brief bsc_node_event() is a callback function which is called by
* BACnet/SC datalink when some event occurs.
* The function is used to notify the upper layer about the events.
* @param node - pointer to the BACnet/SC node
* @param ev - event type
* @param dest - pointer to the destination address
* @param pdu - pointer to the received PDU
* @param pdu_len - length of the received PDU
*/
static void bsc_node_event(
BSC_NODE *node,
BSC_NODE_EVENT ev,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *pdu,
size_t pdu_len)
{
uint16_t pdu16_len;
DEBUG_PRINTF("bsc_node_event() >>> ev = %d\n", ev);
bws_dispatch_lock();
(void)node;
(void)dest;
if (ev == BSC_NODE_EVENT_STARTED || ev == BSC_NODE_EVENT_STOPPED) {
if (bsc_datalink_state != BSC_DATALINK_STATE_IDLE) {
bsc_event_signal(bsc_event);
}
} else if (ev == BSC_NODE_EVENT_RECEIVED_NPDU) {
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
if (pdu_len <= USHRT_MAX &&
FIFO_Available(
&bsc_fifo, (unsigned)(pdu_len + sizeof(pdu16_len)))) {
pdu16_len = (uint16_t)pdu_len;
FIFO_Add(&bsc_fifo, (uint8_t *)&pdu16_len, sizeof(pdu16_len));
FIFO_Add(&bsc_fifo, pdu, pdu16_len);
bsc_event_signal(bsc_data_event);
}
#if DEBUG_ENABLED == 1
else {
PRINTF("pdu of size %d\n is dropped\n", pdu_len);
}
#endif
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_node_event() <<<\n");
}
/**
* @brief bsc_deinit_resources() is a function which is used to
* deinitialize all resources allocated by BACnet/SC datalink.
*/
static void bsc_deinit_resources(void)
{
if (bsc_event) {
bsc_event_deinit(bsc_event);
bsc_event = NULL;
}
if (bsc_data_event) {
bsc_event_deinit(bsc_data_event);
bsc_data_event = NULL;
}
}
/**
* @brief Initialize the BACnet/SC datalink layer
* @param ifname - name of the network interface
* @return true if the initialization was successful, otherwise false
*/
bool bsc_init(char *ifname)
{
BSC_SC_RET r;
bool ret = false;
DEBUG_PRINTF("bsc_init() >>>\n");
(void)ifname;
bws_dispatch_lock();
if (bsc_datalink_state != BSC_DATALINK_STATE_IDLE) {
bws_dispatch_unlock();
PRINTF("bsc_init() <<< ret = %d\n", ret);
return ret;
}
bsc_event = bsc_event_init();
bsc_data_event = bsc_event_init();
if (!bsc_event || !bsc_data_event) {
bsc_deinit_resources();
bws_dispatch_unlock();
PRINTF("bsc_init() <<< ret = %d\n", false);
return false;
}
DEBUG_PRINTF(
"bsc_init() BACNET/SC datalink configured to use input fifo "
"of size %d\n",
sizeof(bsc_fifo_buf));
FIFO_Init(&bsc_fifo, bsc_fifo_buf, sizeof(bsc_fifo_buf));
ret = bsc_node_conf_fill_from_netport(&bsc_conf, &bsc_node_event);
if (!ret) {
bsc_deinit_resources();
bws_dispatch_unlock();
PRINTF("bsc_init() <<< configuration of BACNET/SC datalink "
"failed, ret = false\n");
return ret;
}
bsc_datalink_state = BSC_DATALINK_STATE_STARTING;
r = bsc_node_init(&bsc_conf, &bsc_node);
if (r == BSC_SC_SUCCESS) {
r = bsc_node_start(bsc_node);
if (r == BSC_SC_SUCCESS) {
bws_dispatch_unlock();
bsc_event_wait(bsc_event);
bws_dispatch_lock();
bsc_datalink_state = BSC_DATALINK_STATE_STARTED;
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_init() <<< ret = %d\n", true);
return true;
}
}
bsc_deinit_resources();
bsc_node_conf_cleanup(&bsc_conf);
bsc_datalink_state = BSC_DATALINK_STATE_IDLE;
bws_dispatch_unlock();
PRINTF("bsc_init() <<< ret = %d\n", false);
return false;
}
/**
* @brief Blocking thread-safe bsc_cleanup() function
* de-initializes BACNet/SC datalink.
*/
void bsc_cleanup(void)
{
DEBUG_PRINTF("bsc_cleanup() >>>\n");
bws_dispatch_lock();
if (bsc_datalink_state != BSC_DATALINK_STATE_IDLE &&
bsc_datalink_state != BSC_DATALINK_STATE_STOPPING) {
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTING) {
bws_dispatch_unlock();
bsc_event_wait(bsc_event);
bws_dispatch_lock();
}
if (bsc_datalink_state != BSC_DATALINK_STATE_STOPPING) {
bsc_datalink_state = BSC_DATALINK_STATE_STOPPING;
bsc_event_signal(bsc_data_event);
bsc_node_stop(bsc_node);
bws_dispatch_unlock();
bsc_event_wait(bsc_event);
bsc_event_wait(bsc_data_event);
bws_dispatch_lock();
bsc_deinit_resources();
(void)bsc_node_deinit(bsc_node);
bsc_node_conf_cleanup(&bsc_conf);
bsc_node = NULL;
bsc_datalink_state = BSC_DATALINK_STATE_IDLE;
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_cleanup() <<<\n");
}
/**
* @brief Send a BACnet/SC PDU to a remote node
* @param dest - destination address
* @param npdu_data - network layer data
* @param pdu - PDU to send
* @param pdu_len - length of the PDU
* @return number of bytes sent on success, negative number on failure
*/
int bsc_send_pdu(
BACNET_ADDRESS *dest,
BACNET_NPDU_DATA *npdu_data,
uint8_t *pdu,
unsigned pdu_len)
{
BSC_SC_RET ret;
BACNET_SC_VMAC_ADDRESS dest_vmac;
int len = -1;
static uint8_t buf[BVLC_SC_NPDU_SIZE_CONF];
/* this datalink doesn't need to know the npdu data */
(void)npdu_data;
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
if (dest->net == BACNET_BROADCAST_NETWORK || dest->mac_len == 0) {
/* broadcast message */
memset(&dest_vmac.address[0], 0xFF, BVLC_SC_VMAC_SIZE);
} else if (dest->mac_len == BVLC_SC_VMAC_SIZE) {
/* unicast */
memcpy(&dest_vmac.address[0], &dest->mac[0], BVLC_SC_VMAC_SIZE);
} else {
bws_dispatch_unlock();
PRINTF("bsc_send_pdu() <<< ret = -1, incorrect dest mac address\n");
return len;
}
len = (int)bvlc_sc_encode_encapsulated_npdu(
buf, sizeof(buf), bsc_get_next_message_id(), NULL, &dest_vmac, pdu,
pdu_len);
ret = bsc_node_send(bsc_node, buf, len);
len = pdu_len;
if (ret != BSC_SC_SUCCESS) {
len = -1;
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_send_pdu() <<< ret = %d\n", len);
return len;
}
/**
* @brief Remove a BACnet/SC packet from the FIFO buffer
* @param packet_size - size of the packet to remove
*/
static void bsc_remove_packet(size_t packet_size)
{
size_t i;
for (i = 0; i < packet_size; i++) {
(void)FIFO_Get(&bsc_fifo);
}
}
/**
* @brief Blocking thread-safe bsc_receive() function
* receives NPDUs transferred over BACNet/SC
* from a node specified by it's virtual MAC address as
* defined in Clause AB.1.5.2.
* @param src - source VMAC address
* @param pdu - a buffer to hold the PDU portion of the received packet,
* after the BVLC portion has been stripped off.
* @param max_pdu - size of the pdu[] buffer
* @param timeout_ms - the number of milliseconds to wait for a packet
* @return the number of octets (remaining) in the PDU, or zero if no packet
*/
uint16_t bsc_receive(
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout_ms)
{
uint16_t pdu_len = 0;
uint16_t npdu16_len = 0;
BVLC_SC_DECODED_MESSAGE dm;
BACNET_ERROR_CODE error;
BACNET_ERROR_CLASS class;
const char *err_desc = NULL;
static uint8_t buf[BVLC_SC_NPDU_SIZE_CONF];
DEBUG_PRINTF("bsc_receive() >>>\n");
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
if (FIFO_Count(&bsc_fifo) <= sizeof(npdu16_len)) {
bws_dispatch_unlock();
bsc_event_timedwait(bsc_data_event, timeout_ms);
bws_dispatch_lock();
}
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED &&
FIFO_Count(&bsc_fifo) > sizeof(npdu16_len)) {
DEBUG_PRINTF("bsc_receive() processing data...\n");
FIFO_Pull(&bsc_fifo, (uint8_t *)&npdu16_len, sizeof(npdu16_len));
if (sizeof(buf) < npdu16_len) {
PRINTF("bsc_receive() pdu of size %d is dropped\n", npdu16_len);
bsc_remove_packet(npdu16_len);
} else {
FIFO_Pull(&bsc_fifo, buf, npdu16_len);
if (!bvlc_sc_decode_message(
buf, npdu16_len, &dm, &error, &class, &err_desc)) {
PRINTF(
"bsc_receive() pdu of size %d is dropped because "
"of err = %d, class %d, desc = %s\n",
npdu16_len, error, class, err_desc);
bsc_remove_packet(npdu16_len);
} else {
if (dm.hdr.origin &&
max_pdu >= dm.payload.encapsulated_npdu.npdu_len) {
src->mac_len = BVLC_SC_VMAC_SIZE;
memcpy(
&src->mac[0], &dm.hdr.origin->address[0],
BVLC_SC_VMAC_SIZE);
memcpy(
pdu, dm.payload.encapsulated_npdu.npdu,
dm.payload.encapsulated_npdu.npdu_len);
pdu_len =
(uint16_t)dm.payload.encapsulated_npdu.npdu_len;
}
#if DEBUG_ENABLED == 1
else {
PRINTF(
"bsc_receive() pdu of size %d is dropped "
"because origin addr is absent or output "
"buf of size %d is to small\n",
npdu16_len, max_pdu);
}
#endif
}
}
DEBUG_PRINTF("bsc_receive() pdu_len = %d\n", pdu_len);
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_receive() <<< ret = %d\n", pdu_len);
return pdu_len;
}
/**
* @brief Function can be used to retrieve broadcast
* VMAC address for BACNet/SC node.
* @param dest - value of broadcast VMAC address
*/
void bsc_get_broadcast_address(BACNET_ADDRESS *dest)
{
if (dest) {
dest->net = BACNET_BROADCAST_NETWORK;
dest->mac_len = BVLC_SC_VMAC_SIZE;
memset(&dest->mac[0], 0xFF, BVLC_SC_VMAC_SIZE);
/* no SADR */
dest->len = 0;
memset(dest->adr, 0, sizeof(dest->adr));
}
}
/**
* @brief Function can be used to retrieve local
* VMAC address of initialized BACNet/SC datalink.
* @param my_address - value of local VMAC address
*/
void bsc_get_my_address(BACNET_ADDRESS *my_address)
{
if (my_address) {
memset(my_address, 0, sizeof(*my_address));
}
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
my_address->mac_len = BVLC_SC_VMAC_SIZE;
memcpy(
&my_address->mac[0], &bsc_conf.local_vmac.address[0],
BVLC_SC_VMAC_SIZE);
}
bws_dispatch_unlock();
}
/**
* @brief Determine if the BACnet/SC direct connection is established
* with a remote BACnet/SC node.
* @param dest - BACnet/SC VMAC address of the remote node to check direct
* connection status
* @param urls - array representing the possible URIs of a remote node for
* acceptance of direct connections. Can contain 1 element
* @param urls_cnt - number of elements in the urls array
* @return true if the connection is established, otherwise false
*/
bool bsc_direct_connection_established(
BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt)
{
bool ret = false;
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
ret = bsc_node_direct_connection_established(
bsc_node, dest, urls, urls_cnt);
}
bws_dispatch_unlock();
return ret;
}
/**
* @brief Start the process of establishing a direct BACnet/SC connection
* to a node identified by either urls or dest parameter. The user should
* note that if the dest parameter is used, the local node tries to resolve
* it (e.g. to get URIs related to dest VMAC from all existing BACnet/SC
* nodes in the network). As a result, the process of establishing a BACnet/SC
* connection by dest may take an unpredictable amount of time depending on
* the current network configuration.
* @param dest - BACnet/SC VMAC address of the remote node to check direct
* connection status
* @param urls - array representing the possible URIs of a remote node for
* acceptance of direct connections. Can contain 1 element
* @param urls_cnt - number of elements in the urls array
* @return BSC_SC_SUCCESS if the process of establishing a BACnet/SC
* connection was started successfully, otherwise an error code
*/
BSC_SC_RET
bsc_connect_direct(BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt)
{
BSC_SC_RET ret = BSC_SC_INVALID_OPERATION;
DEBUG_PRINTF(
"bsc_connect_direct() >>> dest = %p, urls = %p, urls_cnt = %d\n", dest,
urls, urls_cnt);
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
ret = bsc_node_connect_direct(bsc_node, dest, urls, urls_cnt);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_connect_direct() ret = %d\n", ret);
return ret;
}
/**
* @brief Disconnect a direct BACnet/SC connection with a remote node
* identified by its VMAC address.
* @param dest - BACnet/SC VMAC address of the remote node to disconnect
* the direct connection with
*/
void bsc_disconnect_direct(BACNET_SC_VMAC_ADDRESS *dest)
{
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
bsc_node_disconnect_direct(bsc_node, dest);
}
bws_dispatch_unlock();
}
/**
* @brief Process the hub connector state
*/
static void bsc_update_hub_connector_state(void)
{
BACNET_SC_HUB_CONNECTOR_STATE state;
uint32_t instance;
instance = Network_Port_Index_To_Instance(0);
state = bsc_node_hub_connector_state(bsc_node);
Network_Port_SC_Hub_Connector_State_Set(instance, state);
}
/**
* @brief Process the hub connector status
*/
static void bsc_update_hub_connector_status(void)
{
BACNET_SC_HUB_CONNECTION_STATUS *status;
uint32_t instance;
instance = Network_Port_Index_To_Instance(0);
status = bsc_node_hub_connector_status(bsc_node, true);
if (status) {
Network_Port_SC_Primary_Hub_Connection_Status_Set(
instance, status->State, &status->Connect_Timestamp,
&status->Disconnect_Timestamp, status->Error,
status->Error_Details[0] ? status->Error_Details : NULL);
}
status = bsc_node_hub_connector_status(bsc_node, false);
if (status) {
Network_Port_SC_Failover_Hub_Connection_Status_Set(
instance, status->State, &status->Connect_Timestamp,
&status->Disconnect_Timestamp, status->Error,
status->Error_Details[0] ? status->Error_Details : NULL);
}
}
/**
* @brief Process the hub function status
*/
static void bsc_update_hub_function_status(void)
{
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *s;
size_t cnt;
int i;
uint32_t instance = Network_Port_Index_To_Instance(0);
BACNET_SC_VMAC_ADDRESS uninitialized = { 0 };
s = bsc_node_hub_function_status(bsc_node, &cnt);
if (s) {
Network_Port_SC_Hub_Function_Connection_Status_Delete_All(instance);
for (i = 0; i < cnt; i++) {
if (memcmp(
&uninitialized.address[0], &s[i].Peer_VMAC[0],
BVLC_SC_VMAC_SIZE) != 0) {
Network_Port_SC_Hub_Function_Connection_Status_Add(
instance, s[i].State, &s[i].Connect_Timestamp,
&s[i].Disconnect_Timestamp, &s[i].Peer_Address,
s[i].Peer_VMAC, s[i].Peer_UUID.uuid.uuid128, s[i].Error,
s[i].Error_Details[0] == 0 ? NULL : s[i].Error_Details);
}
}
}
}
/**
* @brief Add direct connection status to the network port
* @param s - direct connection status
* @param cnt - number of direct connection statuses
*/
static void bsc_add_direct_status_to_netport(
BACNET_SC_DIRECT_CONNECTION_STATUS *s, size_t cnt)
{
int i;
uint32_t instance = Network_Port_Index_To_Instance(0);
BACNET_SC_VMAC_ADDRESS uninitialized = { 0 };
for (i = 0; i < cnt; i++) {
if (memcmp(
&uninitialized.address[0], &s[i].Peer_VMAC[0],
BVLC_SC_VMAC_SIZE) != 0) {
Network_Port_SC_Direct_Connect_Connection_Status_Add(
instance, s[i].URI[0] == 0 ? NULL : s[i].URI, s[i].State,
&s[i].Connect_Timestamp, &s[i].Disconnect_Timestamp,
&s[i].Peer_Address, s[i].Peer_VMAC, s[i].Peer_UUID.uuid.uuid128,
s[i].Error,
s[i].Error_Details[0] == 0 ? NULL : s[i].Error_Details);
}
}
}
/**
* @brief Process the direct connection status
*/
static void bsc_update_direct_connection_status(void)
{
BACNET_SC_DIRECT_CONNECTION_STATUS *s = NULL;
size_t cnt = 0;
uint32_t instance = Network_Port_Index_To_Instance(0);
s = bsc_node_direct_connection_status(bsc_node, &cnt);
if (s) {
Network_Port_SC_Direct_Connect_Connection_Status_Delete_All(instance);
if (s && cnt) {
bsc_add_direct_status_to_netport(s, cnt);
}
}
}
/**
* @brief Process the failed requests
*/
static void bsc_update_failed_requests(void)
{
BACNET_SC_FAILED_CONNECTION_REQUEST *r;
size_t cnt;
int i;
uint32_t instance = Network_Port_Index_To_Instance(0);
r = bsc_node_failed_requests_status(bsc_node, &cnt);
if (r) {
Network_Port_SC_Failed_Connection_Requests_Delete_All(instance);
for (i = 0; i < cnt; i++) {
if (r[i].Peer_Address.host[0] != 0) {
#if DEBUG_ENABLED == 1
DEBUG_PRINTF(
"failed request record %d, host %s, vmac %s, uuid "
"%s, error %d, details = %s\n",
i, r[i].Peer_Address.host,
bsc_vmac_to_string(
(BACNET_SC_VMAC_ADDRESS *)r[i].Peer_VMAC),
bsc_uuid_to_string(
(BACNET_SC_UUID *)r[i].Peer_UUID.uuid.uuid128),
r[i].Error, r[i].Error_Details);
#endif
Network_Port_SC_Failed_Connection_Requests_Add(
instance, &r[i].Timestamp, &r[i].Peer_Address,
r[i].Peer_VMAC, r[i].Peer_UUID.uuid.uuid128, r[i].Error,
r[i].Error_Details[0] != 0 ? r[i].Error_Details : NULL);
}
}
}
}
/**
* @brief Update the network port properties
*/
static void bsc_update_netport_properties(void)
{
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
bsc_update_hub_connector_state();
bsc_update_hub_connector_status();
bsc_update_hub_function_status();
bsc_update_direct_connection_status();
bsc_update_failed_requests();
}
}
/**
* @brief Manage the BACnet/SC datalink timer
* @param seconds - number of elapsed seconds
*/
void bsc_maintenance_timer(uint16_t seconds)
{
bws_dispatch_lock();
bsc_node_maintenance_timer(seconds);
bsc_update_netport_properties();
bws_dispatch_unlock();
}
+176
View File
@@ -0,0 +1,176 @@
/**
* @file
* @brief BACNet/SC datalink public interface API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date October 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#ifndef BACNET_DATALINK_BSC_DATALINK_H
#define BACNET_DATALINK_BSC_DATALINK_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/npdu.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-retcodes.h"
/**
* @brief Blocking thread-safe bsc_init() function
* initializes BACNet/SC datalink in accordence with value of properties
* from BACNet/SC Network Port Object (sc_netport.h). That means that
* user must initialize and set corresponded properties required for
* configuration of BACNet/SC datalink before calling of that function.
* According to "Addendum cc" to ANSI/ASHRAE Standard 135-2020
* https://bacnet.org/wp-content/uploads/sites/4/2022/08/Add-135-2020cc.pdf
* most important properties are:
* SC_Primary_Hub_URI
* SC_Failover_Hub_URI
* SC_Maximum_Reconnect_Time
* SC_Connect_Wait_Timeout
* SC_Disconnect_Wait_Timeout
* SC_Heartbeat_Timeout
* Operational_Certificate_File
* Issuer_Certificate_Files
* SC_Hub_Function_Enable
* SC_Hub_Function_Binding
* SC_Direct_Connect_Initiate_Enable
* SC_Direct_Connect_Binding
* SC_Direct_Connect_Accept_Enable
* @param ifname - ignored and unused it was added for backward compatibility.
* @return true if datalink was initiated and started, otherwise returns false.
*/
BACNET_STACK_EXPORT
bool bsc_init(char *ifname);
/**
* @brief Blocking thread-safe bsc_cleanup() function
* de-initializes BACNet/SC datalink.
*/
BACNET_STACK_EXPORT
void bsc_cleanup(void);
/**
* @brief Function checks if all needed certificate file are present.
* @return true if all needed certificate file are present otherwise returns
* false.
*/
BACNET_STACK_EXPORT
bool bsc_cert_files_check(void);
/**
* @brief Blocking thread-safe bsc_send_pdu() function
* sends pdu over BACNet/SC to node specified by
* destination address param.
*
* @param dest [in] BACNet/SC node's virtual MAC address as
* defined in Clause AB.1.5.2.
* Can be broadcast.
* @param npdu_data [in] BACNet/SC datalink does not use that
* parameter. Added for backward
* compatibility.
* @param pdu [in] protocol data unit to be sent.
* @param pdu_len [in] - number of bytes to send.
* @return Number of bytes sent on success, negative number on failure.
*/
BACNET_STACK_EXPORT
int bsc_send_pdu(
BACNET_ADDRESS *dest,
BACNET_NPDU_DATA *npdu_data,
uint8_t *pdu,
unsigned pdu_len);
/**
* @brief Blocking thread-safe bsc_receive() function
* receives NPDUs transferred over BACNet/SC
* from a node specified by it's virtual MAC address as
* defined in Clause AB.1.5.2.
*
* @param src [out] Source VMAC address
* @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.
*/
BACNET_STACK_EXPORT
uint16_t bsc_receive(
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout_ms);
/**
* @brief Function can be used to retrieve broadcast
* VMAC address for BACNet/SC node.
*
* @param addr [out] Value of broadcast VMAC address.
*/
BACNET_STACK_EXPORT
void bsc_get_broadcast_address(BACNET_ADDRESS *addr);
/**
* @brief Function can be used to retrieve local
* VMAC address of initialized BACNet/SC datalink.
* If function called when datalink is not started,
* my_address filled by empty vmac address
* X'000000000000' as it defined in clause AB.1.5.2
*
* @param my_address [out] Value of local VMAC address.
*/
BACNET_STACK_EXPORT
void bsc_get_my_address(BACNET_ADDRESS *my_address);
/**
* @brief Function checks if BACNet/SC direct connection is
* established with remote BACNet/SC node.
* User can check the status of connection using either
* destination vmac or list of destination urls.
* @param dest BACNet/SC vmac of remote node to check direct
* connection status.
* @param urls this array represents the possible URIs of a
* remote node for acceptance of direct connections.
* Can contain 1 elem.
* @param urls_cnt - size of urls array.
* @return true if connection is established otherwise returns
* false.
*/
BACNET_STACK_EXPORT
bool bsc_direct_connection_established(
BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt);
/**
* @brief Function starts process of establishing of a
* direct BACNet/SC connection to node identified by
* either urls or dest parameter. User should note that
* if dest parameter is used, local node tries to resolve
* it (e.g.to get URIs related to dest vmac from all existent
* BACNet/SC nodes in network). As a result the process of
* establishing of a BACNet/SC connection by dest may
* take unpredictable amount of time depending on a current
* network configuration.
* @param dest BACNet/SC vmac of remote node to check direct
* connection status.
* @param urls this array represents the possible URIs of a
* remote node for acceptance of direct connections.
* Can contain 1 elem.
* @param urls_cnt - size of urls array.
*
* @return BSC_SC_SUCCESS if process of a establishing of a BACNet/SC
* connection was started successfully, otherwise returns
* any retcode from BSC_SC_RET enum.
*/
BACNET_STACK_EXPORT
BSC_SC_RET
bsc_connect_direct(BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt);
BACNET_STACK_EXPORT
void bsc_disconnect_direct(BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
void bsc_maintenance_timer(uint16_t seconds);
#endif
+101
View File
@@ -0,0 +1,101 @@
/**
* @file
* @brief crossplatform event abstraction used in BACnet secure connect.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date August 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_EVENT_H
#define BACNET_DATALINK_BSC_EVENT_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
struct BSC_Event;
typedef struct BSC_Event BSC_EVENT;
/**
* @brief bsc_event_init() allocates and initializes auto-reset
* event object to non-signalled state. An event object
* can be set to signalled state by the call of
* bsc_event_signal(). When the state of the event object
* is signaled, it remains signaled until last thread is
* released which was blocked on bsc_event_wait() or
* bsc_event_timed_wait() calls. When writing the code user
* must always remember the following statements:
* 1. It is guaranteed that all currently waiting threads will be
* unblocked by the call of bsc_event_signal().
* 2. If user has called bsc_event_wait() or bsc_event_timedwait()
* from thread N after the call of bsc_event_signal() but
* when the state of the corresponded event object is still
* signalled, it is not guaranteed that thread N will be
* unblocked. Thread N may be stay blocked or may unblock
* depending on multitasking context of the OS.
*
* @return handle of event object if function succeeded, otherwise
* returns NULL.
*/
BSC_EVENT *bsc_event_init(void);
/**
* @brief bsc_event_deinit() deinitialize auto-reset
* event object. If a user calls that function while
* some threads are waiting the specified event object ev
* the behaviour is undefined.
*
* @param ev - handle to previously initialized event object.
*/
void bsc_event_deinit(BSC_EVENT *ev);
/**
* @brief bsc_wait() suspends the execution of the current thread
* for the specified amount of seconds.
*
* @param seconds - time to sleep in seconds.
*/
void bsc_wait(int seconds);
/**
* @brief bsc_wait() suspends the execution of the current thread
* for the specified amount of seconds.
*
* @param mseconds - time to sleep in milliseconds.
*/
void bsc_wait_ms(int mseconds);
/**
* @brief bsc_event_wait() suspends the execution of the current thread
* until corresponded event object becomes signalled.
*
* @param ev - handle to previously initialized event object.
*/
void bsc_event_wait(BSC_EVENT *ev);
/**
* @brief bsc_event_timedwait() suspends the execution of the current thread
* until corresponded event object becomes signalled
* or ms_timeout becomes elapsed.
*
* @param ev - handle to previously initialized event object.
* @param ms_timeout - timeout in milliseconds
* @return true if the corresponded event was signalled.
* false if ms_timeout was elapsed but event ev is still in
* non signalled state.
*/
bool bsc_event_timedwait(BSC_EVENT *ev, unsigned int ms_timeout);
/**
* @brief bsc_event_signal() function sets state of corresponded event object
* to signalled state.
*
* @param ev - handle to previously initialized event object.
*/
void bsc_event_signal(BSC_EVENT *ev);
#endif
+651
View File
@@ -0,0 +1,651 @@
/**
* @file
* @brief BACnet hub connector API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date July 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-socket.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/datalink/bsc/bsc-hub-connector.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/bacenum.h"
#include "bacnet/basic/object/sc_netport.h"
#include "bacnet/bactext.h"
#define DEBUG_BSC_HUB_CONNECTOR 0
#if DEBUG_BSC_HUB_CONNECTOR == 1
#define DEBUG_PRINTF debug_printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF debug_printf_disabled
#endif
typedef enum {
BSC_HUB_CONN_PRIMARY = 0,
BSC_HUB_CONN_FAILOVER = 1
} BSC_HUB_CONN_TYPE;
static void hub_connector_socket_event(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE reason,
const char *reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
static void hub_connector_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev);
typedef enum {
BSC_HUB_CONNECTOR_STATE_IDLE = 0,
BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY = 1,
BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER = 2,
BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY = 3,
BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER = 4,
BSC_HUB_CONNECTOR_STATE_WAIT_FOR_RECONNECT = 5,
BSC_HUB_CONNECTOR_STATE_WAIT_FOR_CTX_DEINIT = 6,
BSC_HUB_CONNECTOR_STATE_DUPLICATED_VMAC = 7
} BSC_HUB_CONNECTOR_STATE;
typedef struct BSC_Hub_Connector {
bool used;
BSC_SOCKET_CTX ctx;
BSC_CONTEXT_CFG cfg;
BSC_SOCKET sock[2];
BSC_HUB_CONNECTOR_STATE state;
unsigned int reconnect_timeout_s;
uint8_t primary_url[BSC_WSURL_MAX_LEN + 1];
uint8_t failover_url[BSC_WSURL_MAX_LEN + 1];
struct mstimer t;
BSC_HUB_CONNECTOR_EVENT_FUNC event_func;
void *user_arg;
BACNET_SC_HUB_CONNECTION_STATUS primary_status;
BACNET_SC_HUB_CONNECTION_STATUS failover_status;
} BSC_HUB_CONNECTOR;
#if BSC_CONF_HUB_CONNECTORS_NUM > 0
static BSC_HUB_CONNECTOR bsc_hub_connector[BSC_CONF_HUB_CONNECTORS_NUM] = { 0 };
#else
static BSC_HUB_CONNECTOR *bsc_hub_connector = NULL;
#endif
static BSC_SOCKET_CTX_FUNCS bsc_hub_connector_ctx_funcs = {
NULL, NULL, hub_connector_socket_event, hub_connector_context_event, NULL
};
/**
* @brief Initialize the BACnet hub connector status
* @param s - pointer to the status structure
*/
static void hub_connector_reset_status(BACNET_SC_HUB_CONNECTION_STATUS *s)
{
/* set timestamps to unspecified values */
memset(&s->Connect_Timestamp, 0xFF, sizeof(s->Connect_Timestamp));
memset(&s->Disconnect_Timestamp, 0xFF, sizeof(s->Disconnect_Timestamp));
s->Error = ERROR_CODE_DEFAULT;
s->Error_Details[0] = 0;
}
/**
* @brief Allocate a hub connector
* @return pointer to the allocated hub connector
*/
static BSC_HUB_CONNECTOR *hub_connector_alloc(void)
{
int i;
for (i = 0; i < BSC_CONF_HUB_CONNECTORS_NUM; i++) {
if (!bsc_hub_connector[i].used) {
memset(&bsc_hub_connector[i], 0, sizeof(bsc_hub_connector[i]));
bsc_hub_connector[i].used = true;
hub_connector_reset_status(&bsc_hub_connector[i].primary_status);
hub_connector_reset_status(&bsc_hub_connector[i].failover_status);
DEBUG_PRINTF(
"hub_connector_alloc() ret = %p\n", &bsc_hub_connector[i]);
return &bsc_hub_connector[i];
}
}
DEBUG_PRINTF("hub_connector_alloc() ret = %p\n", &bsc_hub_connector[i]);
return NULL;
}
/**
* @brief Free a hub connector
* @param c - pointer to the hub connector
*/
static void hub_connector_free(BSC_HUB_CONNECTOR *c)
{
DEBUG_PRINTF("hub_connector_free() c = %p\n", c);
c->used = false;
}
/**
* @brief Update the BACnet hub connector status
* @param s - pointer to the status structure
* @param state - new state
* @param err - error code
* @param err_desc - error description
*/
static void hub_conector_update_status(
BACNET_SC_HUB_CONNECTION_STATUS *s,
BACNET_SC_CONNECTION_STATE state,
BACNET_ERROR_CODE err,
const char *err_desc)
{
s->State = state;
if (state == BACNET_SC_CONNECTION_STATE_NOT_CONNECTED ||
state == BACNET_SC_CONNECTION_STATE_DISCONNECTED_WITH_ERRORS) {
bsc_set_timestamp(&s->Disconnect_Timestamp);
} else if (
state == BACNET_SC_CONNECTION_STATE_CONNECTED ||
state == BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT) {
bsc_set_timestamp(&s->Connect_Timestamp);
}
s->Error = err;
s->Error_Details[0] = 0;
if (err_desc) {
bsc_copy_str(&s->Error_Details[0], err_desc, sizeof(s->Error_Details));
}
}
/**
* @brief Connect to a BACnet hub
* @param p - pointer to the hub connector
* @param type - connection type
*/
static void hub_connector_connect(BSC_HUB_CONNECTOR *p, BSC_HUB_CONN_TYPE type)
{
BSC_SC_RET ret;
char *url = (type == BSC_HUB_CONN_PRIMARY) ? (char *)p->primary_url
: (char *)p->failover_url;
p->state = (type == BSC_HUB_CONN_PRIMARY)
? BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY
: BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER;
DEBUG_PRINTF(
"hub_connector_connect() hub = %p connecting to url %s\n", p, url);
if (url[0] == 0) {
mstimer_set(&p->t, p->reconnect_timeout_s * 1000);
p->state = BSC_HUB_CONNECTOR_STATE_WAIT_FOR_RECONNECT;
return;
}
ret = bsc_connect(&p->ctx, &p->sock[type], url);
(void)ret;
#if DEBUG_ENABLED == 1
if (ret != BSC_SC_SUCCESS) {
DEBUG_PRINTF(
"hub_connector_connect() got error while "
"connecting to hub type %d, err = %d\n",
type, ret);
}
#endif
}
/**
* @brief Process the hub connector state
* @param c - pointer to the hub connector
*/
static void hub_connector_process_state(BSC_HUB_CONNECTOR *c)
{
if (c->state == BSC_HUB_CONNECTOR_STATE_WAIT_FOR_RECONNECT) {
if (mstimer_expired(&c->t)) {
hub_connector_connect(c, BSC_HUB_CONN_PRIMARY);
}
}
}
/**
* @brief Hub connector maintenance timer
* @param seconds - number of elapsed seconds
*/
void bsc_hub_connector_maintenance_timer(uint16_t seconds)
{
int i;
(void)seconds;
bws_dispatch_lock();
for (i = 0; i < BSC_CONF_HUB_CONNECTORS_NUM; i++) {
if (bsc_hub_connector[i].used) {
hub_connector_process_state(&bsc_hub_connector[i]);
}
}
bws_dispatch_unlock();
}
/**
* @brief Hub connector socket event
* @param c - pointer to the socket
* @param ev - event
* @param disconnect_reason - disconnect reason
* @param disconnect_reason_desc - disconnect reason description
* @param pdu - pointer to the PDU
* @param pdu_len - PDU length
* @param decoded_pdu - decoded PDU
*/
static void hub_connector_socket_event(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE disconnect_reason,
const char *disconnect_reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu)
{
BSC_HUB_CONNECTOR *hc;
BACNET_SC_CONNECTION_STATE st =
BACNET_SC_CONNECTION_STATE_DISCONNECTED_WITH_ERRORS;
bws_dispatch_lock();
hc = (BSC_HUB_CONNECTOR *)c->ctx->user_arg;
DEBUG_PRINTF(
"hub_connector_socket_event() >>> hub_connector = %p, socket "
"= %p, ev = %d, reason = %d, reason_desc = %p,"
"pdu = %p, pdu_len = %d\n",
hc, c, ev, disconnect_reason, disconnect_reason_desc, pdu, pdu_len);
DEBUG_PRINTF("hub_connector_socket_event() state = %d\n", hc->state);
if (ev == BSC_SOCKET_EVENT_CONNECTED) {
if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY) {
DEBUG_PRINTF(
"hub_connector_socket_event() hub_connector = %p "
"connected primary\n",
hc);
hc->state = BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY;
hub_conector_update_status(
&hc->primary_status, BACNET_SC_CONNECTION_STATE_CONNECTED,
ERROR_CODE_DEFAULT, NULL);
hc->event_func(
BSC_HUBC_EVENT_CONNECTED_PRIMARY, hc, hc->user_arg, NULL, 0,
NULL);
} else if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER) {
DEBUG_PRINTF(
"hub_connector_socket_event() hub_connector = %p "
"connected failover\n",
hc);
hc->state = BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER;
hub_conector_update_status(
&hc->failover_status, BACNET_SC_CONNECTION_STATE_CONNECTED,
ERROR_CODE_DEFAULT, NULL);
hc->event_func(
BSC_HUBC_EVENT_CONNECTED_FAILOVER, hc, hc->user_arg, NULL, 0,
NULL);
}
} else if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
if (disconnect_reason == ERROR_CODE_NODE_DUPLICATE_VMAC &&
hc->state != BSC_HUB_CONNECTOR_STATE_WAIT_FOR_CTX_DEINIT) {
DEBUG_PRINTF("hub_connector_socket_event() "
"got ERROR_CODE_NODE_DUPLICATE_VMAC error\n");
if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY) {
hub_conector_update_status(
&hc->primary_status,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT,
disconnect_reason, disconnect_reason_desc);
} else if (
hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER) {
hub_conector_update_status(
&hc->failover_status,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT,
disconnect_reason, disconnect_reason_desc);
}
hc->state = BSC_HUB_CONNECTOR_STATE_DUPLICATED_VMAC;
hc->event_func(
BSC_HUBC_EVENT_ERROR_DUPLICATED_VMAC, hc, hc->user_arg, NULL, 0,
NULL);
} else if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY) {
DEBUG_PRINTF("hub_connector_socket_event() try to connect to "
"failover hub\n");
hub_conector_update_status(
&hc->primary_status,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT, disconnect_reason,
disconnect_reason_desc);
hub_connector_connect(hc, BSC_HUB_CONN_FAILOVER);
} else if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER) {
DEBUG_PRINTF(
"hub_connector_socket_event() wait for %d seconds\n",
hc->reconnect_timeout_s);
hub_conector_update_status(
&hc->failover_status,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT, disconnect_reason,
disconnect_reason_desc);
hc->state = BSC_HUB_CONNECTOR_STATE_WAIT_FOR_RECONNECT;
mstimer_set(&hc->t, hc->reconnect_timeout_s * 1000);
} else if (
hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY ||
hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER) {
if (disconnect_reason == ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER ||
disconnect_reason == ERROR_CODE_SUCCESS) {
st = BACNET_SC_CONNECTION_STATE_NOT_CONNECTED;
}
if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY) {
hub_conector_update_status(
&hc->primary_status, st, ERROR_CODE_DEFAULT, NULL);
} else {
hub_conector_update_status(
&hc->failover_status, st, ERROR_CODE_DEFAULT, NULL);
}
DEBUG_PRINTF(
"hub_connector_socket_event() try to connect to primary hub\n");
hub_connector_connect(hc, BSC_HUB_CONN_PRIMARY);
}
} else if (ev == BSC_SOCKET_EVENT_RECEIVED) {
DEBUG_PRINTF(
"hub_connector_socket_event() hub_connector = %p pdu of "
"%d len is received\n",
hc, pdu_len);
hc->event_func(
BSC_HUBC_EVENT_RECEIVED, hc, hc->user_arg, pdu, pdu_len,
decoded_pdu);
}
bws_dispatch_unlock();
DEBUG_PRINTF("hub_connector_socket_event() <<<\n");
}
/**
* @brief Hub connector context event
* @param ctx - pointer to the context
* @param ev - event
*/
static void hub_connector_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev)
{
BSC_HUB_CONNECTOR *c;
DEBUG_PRINTF(
"hub_connector_context_event() >>> ctx = %p, ev = %d\n", ctx, ev);
if (ev == BSC_CTX_DEINITIALIZED) {
bws_dispatch_lock();
c = (BSC_HUB_CONNECTOR *)ctx->user_arg;
if (c->state != BSC_HUB_CONNECTOR_STATE_IDLE) {
c->state = BSC_HUB_CONNECTOR_STATE_IDLE;
hub_connector_free(c);
c->event_func(
BSC_HUBC_EVENT_STOPPED, c, c->user_arg, NULL, 0, NULL);
}
bws_dispatch_unlock();
}
DEBUG_PRINTF("hub_connector_context_event() <<<\n");
}
/**
* @brief Start a BACnet hub connector
* @param ca_cert_chain - pointer to the CA certificate chain
* @param ca_cert_chain_size - size of the CA certificate chain
* @param cert_chain - pointer to the certificate chain
* @param cert_chain_size - size of the certificate chain
* @param key - pointer to the private key
* @param key_size - size of the private key
* @param local_uuid - pointer to the local UUID
* @param local_vmac - pointer to the local VMAC address
* @param max_local_bvlc_len - maximum BVLC message size
* @param max_local_npdu_len - maximum NPDU message size
* @param connect_timeout_s - connection timeout in seconds
* @param heartbeat_timeout_s - heartbeat timeout in seconds
* @param disconnect_timeout_s - disconnect timeout in seconds
* @param primaryURL - primary hub URL
* @param failoverURL - failover hub URL
* @param reconnect_timeout_s - reconnect timeout in seconds
* @param event_func - event function
* @param user_arg - user argument
* @param h - pointer to the hub connector handle
* @return status
*/
BSC_SC_RET bsc_hub_connector_start(
uint8_t *ca_cert_chain,
size_t ca_cert_chain_size,
uint8_t *cert_chain,
size_t cert_chain_size,
uint8_t *key,
size_t key_size,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_npdu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s,
char *primaryURL,
char *failoverURL,
unsigned int reconnect_timeout_s,
BSC_HUB_CONNECTOR_EVENT_FUNC event_func,
void *user_arg,
BSC_HUB_CONNECTOR_HANDLE *h)
{
BSC_SC_RET ret = BSC_SC_SUCCESS;
BSC_HUB_CONNECTOR *c;
DEBUG_PRINTF("bsc_hub_connector_start() >>>\n");
if (!ca_cert_chain || !ca_cert_chain_size || !cert_chain ||
!cert_chain_size || !key || !key_size || !local_uuid || !local_vmac ||
!max_local_npdu_len || !max_local_bvlc_len || !connect_timeout_s ||
!heartbeat_timeout_s || !disconnect_timeout_s || !primaryURL ||
!reconnect_timeout_s || !event_func || !h) {
DEBUG_PRINTF("bsc_hub_connector_start() <<< ret = BSC_SC_BAD_PARAM\n");
return BSC_SC_BAD_PARAM;
}
if (strlen(primaryURL) > BSC_WSURL_MAX_LEN ||
(failoverURL && (strlen(failoverURL) > BSC_WSURL_MAX_LEN))) {
DEBUG_PRINTF("bsc_hub_connector_start() <<< ret = BSC_SC_BAD_PARAM\n");
return BSC_SC_BAD_PARAM;
}
bws_dispatch_lock();
c = hub_connector_alloc();
if (!c) {
bws_dispatch_unlock();
DEBUG_PRINTF(
"bsc_hub_connector_start() <<< ret = BSC_SC_NO_RESOURCES\n");
return BSC_SC_NO_RESOURCES;
}
c->reconnect_timeout_s = reconnect_timeout_s;
c->primary_url[0] = 0;
c->failover_url[0] = 0;
c->user_arg = user_arg;
strcpy((char *)c->primary_url, primaryURL);
if (failoverURL) {
strcpy((char *)c->failover_url, failoverURL);
}
c->event_func = event_func;
bsc_init_ctx_cfg(
BSC_SOCKET_CTX_INITIATOR, &c->cfg, BSC_WEBSOCKET_HUB_PROTOCOL, 0, NULL,
ca_cert_chain, ca_cert_chain_size, cert_chain, cert_chain_size, key,
key_size, local_uuid, local_vmac, max_local_bvlc_len,
max_local_npdu_len, connect_timeout_s, heartbeat_timeout_s,
disconnect_timeout_s);
DEBUG_PRINTF(
"bsc_hub_connector_start() uuid = %s, vmac = %s\n",
bsc_uuid_to_string(&c->cfg.local_uuid),
bsc_vmac_to_string(&c->cfg.local_vmac));
ret = bsc_init_ctx(
&c->ctx, &c->cfg, &bsc_hub_connector_ctx_funcs, c->sock,
sizeof(c->sock) / sizeof(BSC_SOCKET), (void *)c);
if (ret == BSC_SC_SUCCESS) {
*h = (BSC_HUB_CONNECTOR_HANDLE)c;
DEBUG_PRINTF(
"bsc_hub_connector_start() hub = %p connecting to url %s\n", c,
c->primary_url);
ret = bsc_connect(
&c->ctx, &c->sock[BSC_HUB_CONN_PRIMARY], (char *)c->primary_url);
if (ret == BSC_SC_SUCCESS) {
c->state = BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY;
} else {
if (c->failover_url[0] == 0) {
c->state = BSC_HUB_CONNECTOR_STATE_IDLE;
bsc_deinit_ctx(&c->ctx);
hub_connector_free(c);
*h = NULL;
} else {
c->state = BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER;
DEBUG_PRINTF(
"bsc_hub_connector_start() hub = %p connecting to url %s\n",
c, c->primary_url);
ret = bsc_connect(
&c->ctx, &c->sock[BSC_HUB_CONN_FAILOVER],
(char *)c->failover_url);
if (ret != BSC_SC_SUCCESS) {
c->state = BSC_HUB_CONNECTOR_STATE_IDLE;
bsc_deinit_ctx(&c->ctx);
hub_connector_free(c);
*h = NULL;
}
}
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_connector_start() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Stop a BACnet hub connector
* @param h - pointer to the hub connector
*/
void bsc_hub_connector_stop(BSC_HUB_CONNECTOR_HANDLE h)
{
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
DEBUG_PRINTF("bsc_hub_connector_stop() >>> h = %p\n", h);
bws_dispatch_lock();
#if DEBUG_ENABLED == 1
if (c) {
DEBUG_PRINTF("bsc_hub_connector_stop() state = %d\n", c->state);
}
#endif
if (c && c->state != BSC_HUB_CONNECTOR_STATE_WAIT_FOR_CTX_DEINIT &&
c->state != BSC_HUB_CONNECTOR_STATE_IDLE) {
c->state = BSC_HUB_CONNECTOR_STATE_WAIT_FOR_CTX_DEINIT;
bsc_deinit_ctx(&c->ctx);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_connector_stop() <<<\n");
}
/**
* @brief Send a BACnet hub connector PDU
* @param h - pointer to the hub connector
* @param pdu - pointer to the PDU
* @param pdu_len - PDU length
* @return status
*/
BSC_SC_RET
bsc_hub_connector_send(BSC_HUB_CONNECTOR_HANDLE h, uint8_t *pdu, size_t pdu_len)
{
BSC_SC_RET ret;
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
DEBUG_PRINTF(
"bsc_hub_connector_send() >>> h = %p, pdu = %p, pdu_len = %d\n", h,
pdu, pdu_len);
bws_dispatch_lock();
if (!c) {
DEBUG_PRINTF("bsc_hub_connector_send() <<< ret = BSC_SC_BAD_PARAM\n");
bws_dispatch_unlock();
return BSC_SC_BAD_PARAM;
}
if (c->state == BSC_HUB_CONNECTOR_STATE_IDLE ||
(c->state != BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY &&
c->state != BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER)) {
DEBUG_PRINTF(
"bsc_hub_connector_send() pdu is dropped, state of "
"hub_connector %p is %d\n",
c, c->state);
DEBUG_PRINTF(
"bsc_hub_connector_send() <<< ret = BSC_SC_INVALID_OPERATION\n");
bws_dispatch_unlock();
return BSC_SC_INVALID_OPERATION;
}
if (c->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY) {
ret = bsc_send(&c->sock[BSC_HUB_CONN_PRIMARY], pdu, pdu_len);
} else {
ret = bsc_send(&c->sock[BSC_HUB_CONN_FAILOVER], pdu, pdu_len);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_connector_send() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Check if the hub connector is stopped
* @param h - pointer to the hub connector
* @return status
*/
bool bsc_hub_connector_stopped(BSC_HUB_CONNECTOR_HANDLE h)
{
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
bool ret = false;
DEBUG_PRINTF("bsc_hub_connector_stopped() >>> h = %p\n", h);
bws_dispatch_lock();
if (c && c->state == BSC_HUB_CONNECTOR_STATE_IDLE) {
ret = true;
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_connector_stopped() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Get the hub connector status
* @param h - pointer to the hub connector
* @param primary - primary or failover status
* @return pointer to the status
*/
BACNET_SC_HUB_CONNECTION_STATUS *
bsc_hub_connector_status(BSC_HUB_CONNECTOR_HANDLE h, bool primary)
{
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
BACNET_SC_HUB_CONNECTION_STATUS *ret = NULL;
bws_dispatch_lock();
if (c) {
if (primary) {
ret = &c->primary_status;
} else {
ret = &c->failover_status;
}
}
bws_dispatch_unlock();
return ret;
}
/**
* @brief Get the hub connector state
* @param h - pointer to the hub connector
* @return state
*/
BACNET_SC_HUB_CONNECTOR_STATE
bsc_hub_connector_state(BSC_HUB_CONNECTOR_HANDLE h)
{
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
BACNET_SC_HUB_CONNECTOR_STATE ret =
BACNET_SC_HUB_CONNECTOR_STATE_NO_HUB_CONNECTION;
bws_dispatch_lock();
if (c) {
if (c->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY) {
ret = BACNET_SC_HUB_CONNECTOR_STATE_CONNECTED_TO_PRIMARY;
} else if (c->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER) {
ret = BACNET_SC_HUB_CONNECTOR_STATE_CONNECTED_TO_FAILOVER;
}
}
bws_dispatch_unlock();
return ret;
}
@@ -0,0 +1,81 @@
/**
* @file
* @brief BACNet secure connect hub connector API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date July 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_HUB_CONNECTOR_H
#define BACNET_DATALINK_BSC_HUB_CONNECTOR_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-retcodes.h"
typedef void *BSC_HUB_CONNECTOR_HANDLE;
typedef enum {
BSC_HUBC_EVENT_CONNECTED_PRIMARY = 1,
BSC_HUBC_EVENT_CONNECTED_FAILOVER = 2,
BSC_HUBC_EVENT_ERROR_DUPLICATED_VMAC = 3,
BSC_HUBC_EVENT_RECEIVED = 4,
BSC_HUBC_EVENT_STOPPED = 5
} BSC_HUB_CONNECTOR_EVENT;
typedef void (*BSC_HUB_CONNECTOR_EVENT_FUNC)(
BSC_HUB_CONNECTOR_EVENT ev,
BSC_HUB_CONNECTOR_HANDLE h,
void *user_arg,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_hub_connector_start(
uint8_t *ca_cert_chain,
size_t ca_cert_chain_size,
uint8_t *cert_chain,
size_t cert_chain_size,
uint8_t *key,
size_t key_size,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_npdu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s,
char *primaryURL,
char *failoverURL,
unsigned int reconnnect_timeout_s,
BSC_HUB_CONNECTOR_EVENT_FUNC event_func,
void *user_arg,
BSC_HUB_CONNECTOR_HANDLE *h);
BACNET_STACK_EXPORT
void bsc_hub_connector_stop(BSC_HUB_CONNECTOR_HANDLE h);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_hub_connector_send(
BSC_HUB_CONNECTOR_HANDLE h, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
bool bsc_hub_connector_stopped(BSC_HUB_CONNECTOR_HANDLE h);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTOR_STATE
bsc_hub_connector_state(BSC_HUB_CONNECTOR_HANDLE h);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTION_STATUS *
bsc_hub_connector_status(BSC_HUB_CONNECTOR_HANDLE h, bool primary);
BACNET_STACK_EXPORT
void bsc_hub_connector_maintenance_timer(uint16_t seconds);
#endif
+535
View File
@@ -0,0 +1,535 @@
/**
* @file
* @brief BACNet hub function API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date July 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-socket.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/datalink/bsc/bsc-hub-function.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/bacenum.h"
#define DEBUG_BSC_HUB_FUNCTION 0
#if DEBUG_BSC_HUB_FUNCTION == 1
#define DEBUG_PRINTF debug_printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF debug_printf_disabled
#endif
static BSC_SOCKET *hub_function_find_connection_for_vmac(
BACNET_SC_VMAC_ADDRESS *vmac, void *user_arg);
static BSC_SOCKET *
hub_function_find_connection_for_uuid(BACNET_SC_UUID *uuid, void *user_arg);
static void hub_function_failed_request(
BSC_SOCKET_CTX *ctx,
BSC_SOCKET *c,
BACNET_SC_VMAC_ADDRESS *vmac,
BACNET_SC_UUID *uuid,
BACNET_ERROR_CODE error,
const char *error_desc);
static void hub_function_socket_event(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE reason,
const char *reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
static void hub_function_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev);
typedef enum {
BSC_HUB_FUNCTION_STATE_IDLE = 0,
BSC_HUB_FUNCTION_STATE_STARTING = 1,
BSC_HUB_FUNCTION_STATE_STARTED = 2,
BSC_HUB_FUNCTION_STATE_STOPPING = 3
} BSC_HUB_FUNCTION_STATE;
typedef struct BSC_Hub_Connector {
bool used;
BSC_SOCKET_CTX ctx;
BSC_CONTEXT_CFG cfg;
BSC_SOCKET sock[BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM];
BSC_HUB_FUNCTION_STATE state;
BSC_HUB_EVENT_FUNC event_func;
void *user_arg;
} BSC_HUB_FUNCTION;
#if BSC_CONF_HUB_FUNCTIONS_NUM > 0
static BSC_HUB_FUNCTION bsc_hub_function[BSC_CONF_HUB_FUNCTIONS_NUM] = { 0 };
#else
static BSC_HUB_FUNCTION *bsc_hub_function = NULL;
#endif
static BSC_SOCKET_CTX_FUNCS bsc_hub_function_ctx_funcs = {
hub_function_find_connection_for_vmac,
hub_function_find_connection_for_uuid, hub_function_socket_event,
hub_function_context_event, hub_function_failed_request
};
/**
* @brief Allocate a hub function
* @return pointer to the hub function
*/
static BSC_HUB_FUNCTION *hub_function_alloc(void)
{
int i;
for (i = 0; i < BSC_CONF_HUB_FUNCTIONS_NUM; i++) {
if (!bsc_hub_function[i].used) {
bsc_hub_function[i].used = true;
return &bsc_hub_function[i];
}
}
return NULL;
}
/**
* @brief Free a hub function
* @param p - pointer to the hub function
*/
static void hub_function_free(BSC_HUB_FUNCTION *p)
{
p->used = false;
}
/**
* @brief find a hub function connection for a specific VMAC address
* @param vmac - pointer to the VMAC address
* @param user_arg - pointer to the user argument
* @return pointer to the socket, or NULL if not found
*/
static BSC_SOCKET *hub_function_find_connection_for_vmac(
BACNET_SC_VMAC_ADDRESS *vmac, void *user_arg)
{
int i;
BSC_HUB_FUNCTION *f;
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)user_arg;
DEBUG_PRINTF(
"hubf = %p local_vmac = %s\n", f,
bsc_vmac_to_string(&f->cfg.local_vmac));
for (i = 0; i < sizeof(f->sock) / sizeof(BSC_SOCKET); i++) {
DEBUG_PRINTF(
"hubf = %p, sock %p, state = %d, vmac = %s\n", f, &f->sock[i],
f->sock[i].state, bsc_vmac_to_string(&f->sock[i].vmac));
if (f->sock[i].state != BSC_SOCK_STATE_IDLE &&
!memcmp(
&vmac->address[0], &f->sock[i].vmac.address[0],
sizeof(vmac->address))) {
bws_dispatch_unlock();
return &f->sock[i];
}
}
bws_dispatch_unlock();
return NULL;
}
/**
* @brief find a hub function connection for a specific UUID
* @param uuid - pointer to the UUID
* @param user_arg - pointer to the user argument
* @return pointer to the socket, or NULL if not found
*/
static BSC_SOCKET *
hub_function_find_connection_for_uuid(BACNET_SC_UUID *uuid, void *user_arg)
{
int i;
BSC_HUB_FUNCTION *f;
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)user_arg;
for (i = 0; i < sizeof(f->sock) / sizeof(BSC_SOCKET); i++) {
DEBUG_PRINTF(
"hubf = %p, sock %p, state = %d, uuid = %s\n", f, &f->sock[i],
f->sock[i].state, bsc_uuid_to_string(&f->sock[i].uuid));
if (f->sock[i].state != BSC_SOCK_STATE_IDLE &&
!memcmp(
&uuid->uuid[0], &f->sock[i].uuid.uuid[0], sizeof(uuid->uuid))) {
bws_dispatch_unlock();
DEBUG_PRINTF("found socket\n");
return &f->sock[i];
}
}
bws_dispatch_unlock();
return NULL;
}
/**
* @brief update the status of the hub function
* @param f - pointer to the hub function
* @param c - pointer to the socket
* @param ev - event
* @param disconnect_reason - disconnect reason
* @param disconnect_reason_desc - disconnect reason description
*/
static void hub_function_update_status(
BSC_HUB_FUNCTION *f,
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE disconnect_reason,
const char *disconnect_reason_desc)
{
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *s;
if (f->user_arg) {
s = bsc_node_find_hub_status_for_vmac(f->user_arg, &c->vmac);
if (s) {
memcpy(s->Peer_VMAC, &c->vmac.address[0], BVLC_SC_VMAC_SIZE);
memcpy(
&s->Peer_UUID.uuid.uuid128[0], &c->uuid.uuid[0],
BVLC_SC_UUID_SIZE);
if (!bsc_socket_get_peer_addr(c, &s->Peer_Address)) {
memset(&s->Peer_Address, 0, sizeof(s->Peer_Address));
}
if (disconnect_reason_desc) {
bsc_copy_str(
s->Error_Details, disconnect_reason_desc,
sizeof(s->Error_Details));
} else {
s->Error_Details[0] = 0;
}
s->Error = ERROR_CODE_DEFAULT;
if (ev == BSC_SOCKET_EVENT_CONNECTED) {
s->State = BACNET_SC_CONNECTION_STATE_CONNECTED;
bsc_set_timestamp(&s->Connect_Timestamp);
memset(
&s->Disconnect_Timestamp, 0xff,
sizeof(s->Disconnect_Timestamp));
} else if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
bsc_set_timestamp(&s->Disconnect_Timestamp);
if (disconnect_reason == ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER ||
disconnect_reason == ERROR_CODE_SUCCESS) {
s->State = BACNET_SC_CONNECTION_STATE_NOT_CONNECTED;
} else {
s->State =
BACNET_SC_CONNECTION_STATE_DISCONNECTED_WITH_ERRORS;
s->Error = disconnect_reason;
}
}
}
}
}
/**
* @brief Handle a hub function failed request
* @param ctx - pointer to the socket context
* @param c - pointer to the socket
* @param vmac - pointer to the VMAC address
* @param uuid - pointer to the UUID
* @param error - error code
* @param error_desc - pointer to the error description
*/
static void hub_function_failed_request(
BSC_SOCKET_CTX *ctx,
BSC_SOCKET *c,
BACNET_SC_VMAC_ADDRESS *vmac,
BACNET_SC_UUID *uuid,
BACNET_ERROR_CODE error,
const char *error_desc)
{
BSC_HUB_FUNCTION *f;
BACNET_HOST_N_PORT_DATA peer;
(void)ctx;
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)c->ctx->user_arg;
if (f->user_arg) {
if (bsc_socket_get_peer_addr(c, &peer)) {
bsc_node_store_failed_request_info(
(BSC_NODE *)f->user_arg, &peer, vmac, uuid, error, error_desc);
}
}
bws_dispatch_unlock();
}
/**
* @brief Handle a hub function socket event
* @param c - pointer to the socket
* @param ev - event
* @param reason - disconnect reason
* @param reason_desc - disconnect reason description
* @param pdu - pointer to the PDU
* @param pdu_len - PDU length
* @param decoded_pdu - decoded PDU
*/
static void hub_function_socket_event(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE reason,
const char *reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu)
{
BSC_SOCKET *dst;
BSC_SC_RET ret;
int i;
uint8_t *p_pdu;
BSC_HUB_FUNCTION *f;
size_t len;
DEBUG_PRINTF(
"hub_function_socket_event() >>> c = %p, ev = %d, reason = "
"%d, desc = %p, pdu = %p, pdu_len = %d, decoded_pdu = %p\n",
c, ev, reason, reason_desc, pdu, pdu_len, decoded_pdu);
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)c->ctx->user_arg;
if (ev == BSC_SOCKET_EVENT_RECEIVED) {
/* double check that received message does not contain */
/* originating virtual address and contains dest vaddr */
/* although such kind of check is already in bsc-socket.c */
if (!decoded_pdu->hdr.origin && decoded_pdu->hdr.dest) {
if (bvlc_sc_is_vmac_broadcast(decoded_pdu->hdr.dest)) {
if (bsc_socket_get_global_buf_size() >= pdu_len) {
p_pdu = bsc_socket_get_global_buf();
len = pdu_len;
memcpy(p_pdu, pdu, len);
for (i = 0; i < sizeof(f->sock) / sizeof(BSC_SOCKET); i++) {
if (&f->sock[i] != c &&
f->sock[i].state == BSC_SOCK_STATE_CONNECTED) {
/* change origin address if presented or add origin
*/
/* address into pdu by extending of it's header */
len = (uint16_t)bvlc_sc_set_orig(
&p_pdu, len, &c->vmac);
ret = bsc_send(&f->sock[i], p_pdu, len);
(void)ret;
#if DEBUG_ENABLED == 1
if (ret != BSC_SC_SUCCESS) {
DEBUG_PRINTF(
"sending of reconstructed pdu failed, "
"err = %d\n",
ret);
}
#endif
}
}
}
#if DEBUG_ENABLED == 1
else {
DEBUG_PRINTF("pdu with len = %d is dropped\n", pdu_len);
}
#endif
} else {
dst = hub_function_find_connection_for_vmac(
decoded_pdu->hdr.dest, (void *)f);
if (!dst) {
DEBUG_PRINTF(
"can not find socket, hub dropped pdu of size "
"%d for dest vmac %s\n",
pdu_len, bsc_vmac_to_string(decoded_pdu->hdr.dest));
} else {
bvlc_sc_remove_dest_set_orig(pdu, pdu_len, &c->vmac);
ret = bsc_send(dst, pdu, pdu_len);
(void)ret;
#if DEBUG_ENABLED == 1
if (ret != BSC_SC_SUCCESS) {
DEBUG_PRINTF(
"sending of pdu of %d bytes failed, err = %d\n",
pdu_len, ret);
}
#endif
}
}
}
} else if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
hub_function_update_status(f, c, ev, reason, reason_desc);
if (reason == ERROR_CODE_NODE_DUPLICATE_VMAC) {
f->event_func(
BSC_HUBF_EVENT_ERROR_DUPLICATED_VMAC,
(BSC_HUB_FUNCTION_HANDLE)f, f->user_arg);
}
} else if (ev == BSC_SOCKET_EVENT_CONNECTED) {
hub_function_update_status(f, c, ev, reason, reason_desc);
}
bws_dispatch_unlock();
DEBUG_PRINTF("hub_function_socket_event() <<<\n");
}
/**
* @brief Handle a hub function context event
* @param ctx - pointer to the socket context
* @param ev - event
*/
static void hub_function_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev)
{
BSC_HUB_FUNCTION *f;
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)ctx->user_arg;
if (ev == BSC_CTX_INITIALIZED) {
f->state = BSC_HUB_FUNCTION_STATE_STARTED;
f->event_func(
BSC_HUBF_EVENT_STARTED, (BSC_HUB_FUNCTION_HANDLE)f, f->user_arg);
} else if (ev == BSC_CTX_DEINITIALIZED) {
f->state = BSC_HUB_FUNCTION_STATE_IDLE;
hub_function_free(f);
f->event_func(
BSC_HUBF_EVENT_STOPPED, (BSC_HUB_FUNCTION_HANDLE)f, f->user_arg);
}
bws_dispatch_unlock();
}
/**
* @brief Start a BACnet hub function
* @param ca_cert_chain - pointer to the CA certificate chain
* @param ca_cert_chain_size - size of the CA certificate chain
* @param cert_chain - pointer to the certificate chain
* @param cert_chain_size - size of the certificate chain
* @param key - pointer to the private key
* @param key_size - size of the private key
* @param port - port number
* @param iface - pointer to the interface
* @param local_uuid - pointer to the local UUID
* @param local_vmac - pointer to the local VMAC address
* @param max_local_bvlc_len - maximum local BVLC length
* @param max_local_npdu_len - maximum local NPDU length
* @param connect_timeout_s - connection timeout in seconds
* @param heartbeat_timeout_s - heartbeat timeout in seconds
* @param disconnect_timeout_s - disconnect timeout in seconds
* @param event_func - pointer to the event function
* @param user_arg - pointer to the user argument
* @param h - pointer to the hub function handle
* @return BACnet/SC status
*/
BSC_SC_RET bsc_hub_function_start(
uint8_t *ca_cert_chain,
size_t ca_cert_chain_size,
uint8_t *cert_chain,
size_t cert_chain_size,
uint8_t *key,
size_t key_size,
int port,
char *iface,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_npdu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s,
BSC_HUB_EVENT_FUNC event_func,
void *user_arg,
BSC_HUB_FUNCTION_HANDLE *h)
{
BSC_SC_RET ret;
BSC_HUB_FUNCTION *f;
DEBUG_PRINTF("bsc_hub_function_start() >>>\n");
if (!ca_cert_chain || !ca_cert_chain_size || !cert_chain ||
!cert_chain_size || !key || !key_size || !local_uuid || !local_vmac ||
!max_local_npdu_len || !max_local_bvlc_len || !connect_timeout_s ||
!heartbeat_timeout_s || !disconnect_timeout_s || !event_func || !h) {
DEBUG_PRINTF("bsc_hub_function_start() <<< ret = BSC_SC_BAD_PARAM\n");
return BSC_SC_BAD_PARAM;
}
*h = NULL;
bws_dispatch_lock();
f = hub_function_alloc();
if (!f) {
bws_dispatch_unlock();
DEBUG_PRINTF(
"bsc_hub_function_start() <<< ret = BSC_SC_NO_RESOURCES\n");
return BSC_SC_NO_RESOURCES;
}
f->user_arg = user_arg;
f->event_func = event_func;
bsc_init_ctx_cfg(
BSC_SOCKET_CTX_ACCEPTOR, &f->cfg, BSC_WEBSOCKET_HUB_PROTOCOL, port,
iface, ca_cert_chain, ca_cert_chain_size, cert_chain, cert_chain_size,
key, key_size, local_uuid, local_vmac, max_local_bvlc_len,
max_local_npdu_len, connect_timeout_s, heartbeat_timeout_s,
disconnect_timeout_s);
ret = bsc_init_ctx(
&f->ctx, &f->cfg, &bsc_hub_function_ctx_funcs, f->sock,
sizeof(f->sock) / sizeof(BSC_SOCKET), f);
if (ret == BSC_SC_SUCCESS) {
f->state = BSC_HUB_FUNCTION_STATE_STARTING;
*h = (BSC_HUB_FUNCTION_HANDLE)f;
} else {
hub_function_free(f);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_function_start() << ret = %d\n", ret);
return ret;
}
/**
* @brief Stop a BACnet hub function
* @param h - pointer to the hub function handle
*/
void bsc_hub_function_stop(BSC_HUB_FUNCTION_HANDLE h)
{
BSC_HUB_FUNCTION *f = (BSC_HUB_FUNCTION *)h;
DEBUG_PRINTF("bsc_hub_function_stop() >>> h = %p\n", h);
bws_dispatch_lock();
if (f && f->state != BSC_HUB_FUNCTION_STATE_IDLE &&
f->state != BSC_HUB_FUNCTION_STATE_STOPPING) {
f->state = BSC_HUB_FUNCTION_STATE_STOPPING;
bsc_deinit_ctx(&f->ctx);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_function_stop() <<<\n");
}
/**
* @brief Check if the hub function is stopped
* @param h - pointer to the hub function handle
* @return true if the hub function is stopped, false otherwise
*/
bool bsc_hub_function_stopped(BSC_HUB_FUNCTION_HANDLE h)
{
BSC_HUB_FUNCTION *f = (BSC_HUB_FUNCTION *)h;
bool ret = false;
DEBUG_PRINTF("bsc_hub_function_stopped() >>> h = %p\n", h);
bws_dispatch_lock();
if (f && f->state == BSC_HUB_FUNCTION_STATE_IDLE) {
ret = true;
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_function_stopped() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Check if the hub function is started
* @param h - pointer to the hub function handle
* @return true if the hub function is started, false otherwise
*/
bool bsc_hub_function_started(BSC_HUB_FUNCTION_HANDLE h)
{
BSC_HUB_FUNCTION *f = (BSC_HUB_FUNCTION *)h;
bool ret = false;
DEBUG_PRINTF("bsc_hub_function_started() >>> h = %p\n", h);
bws_dispatch_lock();
if (f && f->state == BSC_HUB_FUNCTION_STATE_STARTED) {
ret = true;
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_function_started() <<< ret = %d\n", ret);
return ret;
}
@@ -0,0 +1,62 @@
/**
* @file
* @brief BACNet secure connect hub function API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date July 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_HUB_FUNCTION_H
#define BACNET_DATALINK_BSC_HUB_FUNCTION_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/basic/object/sc_netport.h"
typedef void *BSC_HUB_FUNCTION_HANDLE;
typedef enum {
BSC_HUBF_EVENT_STARTED = 1,
BSC_HUBF_EVENT_STOPPED = 2,
BSC_HUBF_EVENT_ERROR_DUPLICATED_VMAC = 3
} BSC_HUB_FUNCTION_EVENT;
typedef void (*BSC_HUB_EVENT_FUNC)(
BSC_HUB_FUNCTION_EVENT ev, BSC_HUB_FUNCTION_HANDLE h, void *user_arg);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_hub_function_start(
uint8_t *ca_cert_chain,
size_t ca_cert_chain_size,
uint8_t *cert_chain,
size_t cert_chain_size,
uint8_t *key,
size_t key_size,
int port,
char *iface,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_npdu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s,
BSC_HUB_EVENT_FUNC event_func,
void *user_arg,
BSC_HUB_FUNCTION_HANDLE *h);
BACNET_STACK_EXPORT
void bsc_hub_function_stop(BSC_HUB_FUNCTION_HANDLE h);
BACNET_STACK_EXPORT
bool bsc_hub_function_stopped(BSC_HUB_FUNCTION_HANDLE h);
BACNET_STACK_EXPORT
bool bsc_hub_function_started(BSC_HUB_FUNCTION_HANDLE h);
#endif
File diff suppressed because it is too large Load Diff
+126
View File
@@ -0,0 +1,126 @@
/**
* @file
* @brief BACNet secure connect node switch function API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date October 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_NODE_SWITCH_H
#define BACNET_DATALINK_BSC_NODE_SWITCH_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/datalink/bsc/bsc-node.h"
typedef void *BSC_NODE_SWITCH_HANDLE;
typedef enum {
BSC_NODE_SWITCH_EVENT_STARTED = 1,
BSC_NODE_SWITCH_EVENT_STOPPED = 2,
BSC_NODE_SWITCH_EVENT_RECEIVED = 3,
BSC_NODE_SWITCH_EVENT_DUPLICATED_VMAC = 4,
/* BSC_NODE_SWITCH_EVENT_CONNECTED event is emitted every */
/* time remote peer connects only if bsc_node_switch_connect() */
/* was called after start for corresponded mac or url/urls. */
/* Events indication are stopped only */
/* if node switch was stopped or bsc_node_switch_disconnect() */
/* was called. For a connection initiated from a remote peer that */
/* event won't be ever emitted. */
BSC_NODE_SWITCH_EVENT_CONNECTED = 5,
/* The BSC_NODE_SWITCH_EVENT_DISCONNECTED event is emitted */
/* every time remote peer disconnects only if */
/* bsc_node_switch_connect() was called after start. */
/* If user called bsc_node_switch_disconnect() or stopped */
/* node switch, after last event indication next event indications */
/* are stopped for corresponded peer with corresponded vmac. */
BSC_NODE_SWITCH_EVENT_DISCONNECTED = 6
} BSC_NODE_SWITCH_EVENT;
/* dest parameter is actual only for */
/* BSC_NODE_SWITCH_EVENT_CONNECTED and BSC_NODE_SWITCH_EVENT_DISCONNECTED */
/* events. It is the parameter which was specified in */
/* bsc_node_switch_connect() or bsc_node_switch_disconnect() calls. */
typedef void (*BSC_NODE_SWITCH_EVENT_FUNC)(
BSC_NODE_SWITCH_EVENT ev,
BSC_NODE_SWITCH_HANDLE h,
void *user_arg,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_switch_start(
uint8_t *ca_cert_chain,
size_t ca_cert_chain_size,
uint8_t *cert_chain,
size_t cert_chain_size,
uint8_t *key,
size_t key_size,
int port,
char *iface,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_npdu_len,
uint16_t connect_timeout_s,
uint16_t heartbeat_timeout_s,
uint16_t disconnect_timeout_s,
uint16_t reconnnect_timeout_s,
uint16_t address_resolution_timeout_s,
bool direct_connect_accept_enable,
bool direct_connect_initiate_enable,
BSC_NODE_SWITCH_EVENT_FUNC event_func,
void *user_arg,
BSC_NODE_SWITCH_HANDLE *h);
BACNET_STACK_EXPORT
void bsc_node_switch_stop(BSC_NODE_SWITCH_HANDLE h);
BACNET_STACK_EXPORT
bool bsc_node_switch_stopped(BSC_NODE_SWITCH_HANDLE h);
BACNET_STACK_EXPORT
bool bsc_node_switch_started(BSC_NODE_SWITCH_HANDLE h);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_switch_connect(
BSC_NODE_SWITCH_HANDLE h,
BACNET_SC_VMAC_ADDRESS *dest,
char **urls,
size_t urls_cnt);
BACNET_STACK_EXPORT
bool bsc_node_switch_connected(
BSC_NODE_SWITCH_HANDLE h,
BACNET_SC_VMAC_ADDRESS *dest,
char **urls,
size_t urls_cnt);
BACNET_STACK_EXPORT
void bsc_node_switch_disconnect(
BSC_NODE_SWITCH_HANDLE h, BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
void bsc_node_switch_process_address_resolution(
BSC_NODE_SWITCH_HANDLE h, BSC_ADDRESS_RESOLUTION *r);
BACNET_STACK_EXPORT
BSC_SC_RET
bsc_node_switch_send(BSC_NODE_SWITCH_HANDLE h, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
void bsc_node_switch_maintenance_timer(uint16_t seconds);
#endif
File diff suppressed because it is too large Load Diff
+162
View File
@@ -0,0 +1,162 @@
/**
* @file
* @brief BACNet secure connect node API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date October 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#ifndef BACNET_DATALINK_BSC_NODE_H
#define BACNET_DATALINK_BSC_NODE_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/object/sc_netport.h"
typedef struct BSC_Node BSC_NODE;
typedef enum {
BSC_NODE_EVENT_STARTED = 1,
BSC_NODE_EVENT_STOPPED = 2,
BSC_NODE_EVENT_RESTARTED = 3,
BSC_NODE_EVENT_RECEIVED_NPDU = 4,
BSC_NODE_EVENT_RECEIVED_RESULT = 5,
BSC_NODE_EVENT_RECEIVED_ADVERTISIMENT = 6,
BSC_NODE_EVENT_DIRECT_CONNECTED = 7,
BSC_NODE_EVENT_DIRECT_DISCONNECTED = 8
} BSC_NODE_EVENT;
typedef void (*BSC_NODE_EVENT_FUNC)(
BSC_NODE *node,
BSC_NODE_EVENT ev,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *pdu,
size_t pdu_len);
typedef struct {
bool used;
BACNET_SC_VMAC_ADDRESS vmac;
uint8_t utf8_urls[BSC_CONF_NODE_MAX_URIS_NUM_IN_ADDRESS_RESOLUTION_ACK]
[BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK + 1];
size_t urls_num;
struct mstimer fresh_timer;
} BSC_ADDRESS_RESOLUTION;
typedef struct {
uint8_t *ca_cert_chain;
size_t ca_cert_chain_size;
uint8_t *cert_chain;
size_t cert_chain_size;
uint8_t *key;
size_t key_size;
BACNET_SC_UUID *local_uuid;
BACNET_SC_VMAC_ADDRESS local_vmac;
uint16_t max_local_bvlc_len;
uint16_t max_local_npdu_len;
uint16_t connect_timeout_s;
uint16_t heartbeat_timeout_s;
uint16_t disconnect_timeout_s;
uint16_t reconnnect_timeout_s;
uint16_t address_resolution_timeout_s;
uint16_t address_resolution_freshness_timeout_s;
char *primaryURL;
char *failoverURL;
uint16_t hub_server_port;
uint16_t direct_server_port;
char *hub_iface;
char *direct_iface;
bool direct_connect_accept_enable;
bool direct_connect_initiate_enable;
bool hub_function_enabled;
char *direct_connection_accept_uris; /* URIs joined ' 'space */
size_t direct_connection_accept_uris_len;
BSC_NODE_EVENT_FUNC event_func;
} BSC_NODE_CONF;
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_init(BSC_NODE_CONF *conf, BSC_NODE **node);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_deinit(BSC_NODE *node);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_start(BSC_NODE *node);
BACNET_STACK_EXPORT
void bsc_node_stop(BSC_NODE *node);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_send(BSC_NODE *node, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
BSC_ADDRESS_RESOLUTION *
bsc_node_get_address_resolution(void *node, BACNET_SC_VMAC_ADDRESS *vmac);
BACNET_STACK_EXPORT
BSC_SC_RET
bsc_node_send_address_resolution(void *node, BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
BSC_SC_RET
bsc_node_hub_connector_send(void *user_arg, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_connect_direct(
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt);
BACNET_STACK_EXPORT
void bsc_node_disconnect_direct(BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTOR_STATE
bsc_node_hub_connector_state(BSC_NODE *node);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTION_STATUS *
bsc_node_hub_connector_status(BSC_NODE *node, bool primary);
BACNET_STACK_EXPORT
bool bsc_node_direct_connection_established(
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt);
BACNET_STACK_EXPORT
void bsc_node_maintenance_timer(uint16_t seconds);
BACNET_STACK_EXPORT
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *
bsc_node_hub_function_status(BSC_NODE *node, size_t *cnt);
BACNET_STACK_EXPORT
BACNET_SC_DIRECT_CONNECTION_STATUS *
bsc_node_direct_connection_status(BSC_NODE *node, size_t *cnt);
BACNET_STACK_EXPORT
void bsc_node_store_failed_request_info(
BSC_NODE *node,
BACNET_HOST_N_PORT_DATA *peer,
BACNET_SC_VMAC_ADDRESS *vmac,
BACNET_SC_UUID *uuid,
BACNET_ERROR_CODE error,
const char *error_desc);
BACNET_STACK_EXPORT
BACNET_SC_FAILED_CONNECTION_REQUEST *
bsc_node_failed_requests_status(BSC_NODE *node, size_t *cnt);
BACNET_STACK_EXPORT
BACNET_SC_DIRECT_CONNECTION_STATUS *bsc_node_find_direct_status_for_vmac(
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *vmac);
BACNET_STACK_EXPORT
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *
bsc_node_find_hub_status_for_vmac(BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *vmac);
#endif
+20
View File
@@ -0,0 +1,20 @@
/**
* @file
* @brief BACNet secure connect main include header.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date August 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_RETCODES_H
#define BACNET_DATALINK_BSC_RETCODES_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
typedef enum {
BSC_SC_SUCCESS = 0,
BSC_SC_NO_RESOURCES = 1,
BSC_SC_BAD_PARAM = 2,
BSC_SC_INVALID_OPERATION = 3
} BSC_SC_RET;
#endif
File diff suppressed because it is too large Load Diff
+308
View File
@@ -0,0 +1,308 @@
/**
* @file
* @brief BACNet secure connect socket API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date December 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_SOCKET_H
#define BACNET_DATALINK_BSC_SOCKET_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <limits.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/npdu.h"
#include "bacnet/datalink/bsc/websocket.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/object/sc_netport.h"
#define BSC_RX_BUFFER_SIZE BSC_CONF_SOCK_RX_BUFFER_SIZE
#define BSC_TX_BUFFER_SIZE BSC_CONF_SOCK_TX_BUFFER_SIZE
#define BSC_SOCKET_CTX_NUM \
(BSC_CONF_NODES_NUM * \
(BSC_CONF_HUB_CONNECTORS_NUM + 2 * BSC_CONF_NODE_SWITCHES_NUM + \
BSC_CONF_HUB_FUNCTIONS_NUM))
struct BSC_Socket;
typedef struct BSC_Socket BSC_SOCKET;
struct BSC_SocketContext;
typedef struct BSC_SocketContext BSC_SOCKET_CTX;
struct BSC_SocketContextFuncs;
typedef struct BSC_SocketContextFuncs BSC_SOCKET_CTX_FUNCS;
struct BSC_ContextCFG;
typedef struct BSC_ContextCFG BSC_CONTEXT_CFG;
typedef enum {
BSC_SOCKET_CTX_INITIATOR = 1,
BSC_SOCKET_CTX_ACCEPTOR = 2
} BSC_SOCKET_CTX_TYPE;
typedef enum {
BSC_SOCKET_EVENT_CONNECTED = 0,
BSC_SOCKET_EVENT_DISCONNECTED = 1,
BSC_SOCKET_EVENT_RECEIVED = 2
} BSC_SOCKET_EVENT;
typedef enum {
BSC_CTX_INITIALIZED = 0,
BSC_CTX_DEINITIALIZED = 1
} BSC_CTX_EVENT;
typedef enum {
BSC_CTX_STATE_IDLE = 0,
BSC_CTX_STATE_INITIALIZING = 1,
BSC_CTX_STATE_INITIALIZED = 2,
BSC_CTX_STATE_DEINITIALIZING = 3
} BSC_CTX_STATE;
typedef enum {
BSC_SOCK_STATE_IDLE = 0,
BSC_SOCK_STATE_AWAITING_WEBSOCKET = 1,
BSC_SOCK_STATE_AWAITING_REQUEST = 2,
BSC_SOCK_STATE_AWAITING_ACCEPT = 3,
BSC_SOCK_STATE_CONNECTED = 4,
BSC_SOCK_STATE_DISCONNECTING = 5,
BSC_SOCK_STATE_ERROR = 6,
BSC_SOCK_STATE_ERROR_FLUSH_TX = 7
} BSC_SOCKET_STATE;
struct BSC_Socket {
BSC_SOCKET_CTX *ctx;
BSC_WEBSOCKET_HANDLE wh;
BSC_SOCKET_STATE state;
BACNET_ERROR_CODE reason;
struct mstimer t;
struct mstimer heartbeat;
BACNET_SC_VMAC_ADDRESS vmac; /* VMAC address of the requesting node. */
BACNET_SC_UUID uuid;
/* Regarding max_bvlc_len and max_npdu_len: */
/* These are the datalink limits and are passed up the stack to let */
/* the application layer know one of the several numbers that go into
* computing */
/* how big an NPDU/APDU can be. */
uint16_t max_bvlc_len; /* remote peer max bvlc len */
uint16_t max_npdu_len; /* remote peer max npdu len */
uint16_t expected_connect_accept_message_id;
uint16_t expected_disconnect_message_id;
uint16_t expected_heartbeat_message_id;
uint8_t tx_buf[BSC_TX_BUFFER_SIZE];
size_t tx_buf_size;
};
struct BSC_ContextCFG {
BSC_SOCKET_CTX_TYPE type;
BSC_WEBSOCKET_PROTOCOL proto;
uint16_t port;
char *iface;
uint8_t *ca_cert_chain;
size_t ca_cert_chain_size;
uint8_t *cert_chain;
size_t cert_chain_size;
uint8_t *priv_key;
size_t priv_key_size;
BACNET_SC_VMAC_ADDRESS local_vmac;
BACNET_SC_UUID local_uuid;
uint16_t max_bvlc_len; /* local peer max bvlc len */
uint16_t max_ndpu_len; /* local peer max npdu len */
/* According AB.6.2 BACnet/SC Connection Establishment and Termination */
/* recommended default value for establishing of connection 10 seconds */
uint16_t connect_timeout_s;
uint16_t disconnect_timeout_s;
/* According 12.56.Y10 SC_Heartbeat_Timeout */
/* (http://www.bacnet.org/Addenda/Add-135-2020cc.pdf) the recommended
* default */
/* value is 300 seconds. */
uint16_t heartbeat_timeout_s;
};
struct BSC_SocketContextFuncs {
BSC_SOCKET *(*find_connection_for_vmac)(
BACNET_SC_VMAC_ADDRESS *vmac, void *user_arg);
BSC_SOCKET *(*find_connection_for_uuid)(
BACNET_SC_UUID *uuid, void *user_arg);
/* We always reserve BSC_PRE bytes before BVLC message header */
/* to avoid copying of packet payload during manipulation with */
/* origin and dest addresses (e.g. adding them to received PDU) */
/* That's why pdu pointer has always reserved BSC_PRE bytes behind */
/* The params disconnect_reason and disconnect_reason_desc are meanfull */
/* only for disconnect events, e.g. when ev == BSC_SOCKET_EVENT_DISCONNECTED
*/
void (*socket_event)(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE disconnect_reason,
const char *disconnect_reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
void (*context_event)(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev);
void (*failed_request)(
BSC_SOCKET_CTX *ctx,
BSC_SOCKET *c,
BACNET_SC_VMAC_ADDRESS *vmac,
BACNET_SC_UUID *uuid,
BACNET_ERROR_CODE error,
const char *error_desc);
};
struct BSC_SocketContext {
BSC_CTX_STATE state;
BSC_WEBSOCKET_SRV_HANDLE sh;
BSC_SOCKET *sock;
size_t sock_num;
BSC_SOCKET_CTX_FUNCS *funcs;
BSC_CONTEXT_CFG *cfg;
bool deinit_in_progress;
void *user_arg;
};
/* max_local_bvlc_len - The maximum BVLC message size int bytes that can be */
/* received and processed by BSC/SC datalink. */
/* max_local_ndpu_len - The maximum NPDU message size in bytes hat can be */
/* handled by BSC/SC datalink. */
BACNET_STACK_EXPORT
void bsc_init_ctx_cfg(
BSC_SOCKET_CTX_TYPE type,
BSC_CONTEXT_CFG *cfg,
BSC_WEBSOCKET_PROTOCOL proto,
uint16_t port,
char *iface,
uint8_t *ca_cert_chain,
size_t ca_cert_chain_size,
uint8_t *cert_chain,
size_t cert_chain_size,
uint8_t *key,
size_t key_size,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_ndpu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_init_ctx(
BSC_SOCKET_CTX *ctx,
BSC_CONTEXT_CFG *cfg,
BSC_SOCKET_CTX_FUNCS *funcs,
BSC_SOCKET *sockets,
size_t sockets_num,
void *user_arg);
BACNET_STACK_EXPORT
void bsc_deinit_ctx(BSC_SOCKET_CTX *ctx);
/**
* @brief bsc_connect() function starts connect operation for a
* specified BACNet socket. The function call be called only
* for initiator context otherwise BSC_SC_INVALID_OPERATION
* error is returned. As a result if bsc_connect() was
* succeeded for given param c, that leads to emitting of
* BSC_SOCKET_EVENT_CONNECTED or BSC_SOCKET_EVENT_DISCONNECTED
* events depending on the result of connect operation.
* If connect operation is failed, BSC_SOCKET_EVENT_DISCONNECTED
* event is emitted and user can determine the reason why it
* happened using disconnect_reason and disconnect_reason_desc
* parameters in callback function.
* If connect operation succeeded, BSC_SOCKET_EVENT_CONNECTED
* event is emitted.
*
* @param ctx - socket context.
* @param c - BACNet socket descriptor .
* @param url - url to connect to. For example: wss://legrand.com:8080.
*
* @return error code from BSC_SC_RET enum.
* The following error codes can be returned:
* BSC_SC_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_SC_INVALID_OPERATION - if socket is not in opened state,
or disconnect operation is in progress using
bsc_disconnect() or bsc_deinit_ctx().
* BSC_SC_SUCCESS - operation has succeeded.
* BSC_SC_NO_RESOURCES - there are not resources (memory, etc.. )
* to send data
*/
BACNET_STACK_EXPORT
BSC_SC_RET bsc_connect(BSC_SOCKET_CTX *ctx, BSC_SOCKET *c, char *url);
BACNET_STACK_EXPORT
void bsc_disconnect(BSC_SOCKET *c);
/**
* @brief bsc_send() function schedules transmitting of pdu to
* another BACNet socket. The function may be used only
* when the socket is in a connected state
* otherwise BSC_SC_INVALID_OPERATION error is returned.
*
* @param c - BACNet socket descriptor initialized by bsc_accept() or
* bsc_connect() calls.
* @param pdu - pointer to a data to send.
* @param pdu_len - size in bytes of data to send.
*
* @return error code from BSC_SC_RET enum.
* The following error codes can be returned:
* BSC_SC_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_SC_INVALID_OPERATION - if socket is not in opened state,
or disconnect operation is in progress using
bsc_disconnect() or bsc_deinit_ctx().
* BSC_SC_SUCCESS - operation has succeeded.
* BSC_SC_NO_RESOURCES - there are not resources (memory, etc.. )
* to send data
*/
BACNET_STACK_EXPORT
BSC_SC_RET bsc_send(BSC_SOCKET *c, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
uint16_t bsc_get_next_message_id(void);
BACNET_STACK_EXPORT
void bsc_socket_maintenance_timer(uint16_t seconds);
/**
* @brief bsc_socket_get_peer_addr() function gets information
* about remote peer address only for socket with acceptor cotext.
*
* @param c - BACNet socket descriptor initialized by bsc_accept() call.
* @param data - pointer to a struct holding address information.
* @param pdu_len - size in bytes of data to send.
*
* @return false if socket is not acceptor or bad parameter was passed or
* if getting of address information failed.
* true if function succeeds.
*/
BACNET_STACK_EXPORT
bool bsc_socket_get_peer_addr(BSC_SOCKET *c, BACNET_HOST_N_PORT_DATA *data);
BACNET_STACK_EXPORT
uint8_t *bsc_socket_get_global_buf(void);
BACNET_STACK_EXPORT
size_t bsc_socket_get_global_buf_size(void);
#endif
+343
View File
@@ -0,0 +1,343 @@
/**
* @file
* @brief module for common function for BACnet/SC implementation
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date Jule 2022
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/basic/object/bacfile.h"
#include "bacnet/basic/object/netport.h"
#include "bacnet/basic/object/sc_netport.h"
#include "bacnet/basic/object/bacfile.h"
#include "bacnet/basic/sys/debug.h"
#include <stdlib.h>
#define PRINTF debug_aprintf
#define PRINTF_ERR debug_perror
/**
* @brief Map websocket return code to BACnet/SC return code
* @param ret - websocket return code
* @return BACnet/SC return code
*/
BSC_SC_RET bsc_map_websocket_retcode(BSC_WEBSOCKET_RET ret)
{
switch (ret) {
case BSC_WEBSOCKET_SUCCESS:
return BSC_SC_SUCCESS;
case BSC_WEBSOCKET_NO_RESOURCES:
return BSC_SC_NO_RESOURCES;
case BSC_WEBSOCKET_BAD_PARAM:
return BSC_SC_BAD_PARAM;
case BSC_WEBSOCKET_INVALID_OPERATION:
default:
return BSC_SC_INVALID_OPERATION;
}
}
/**
* @brief Copy BACnet Secure Connect VMAC address
* @param dst - destination VMAC address
* @param src - source VMAC address
*/
void bsc_copy_vmac(BACNET_SC_VMAC_ADDRESS *dst, BACNET_SC_VMAC_ADDRESS *src)
{
memcpy(dst->address, src->address, sizeof(src->address));
}
/**
* @brief Copy the BACnet Secure Connect UUID
* @param dst - destination UUID
* @param src - source UUID
*/
void bsc_copy_uuid(BACNET_SC_UUID *dst, BACNET_SC_UUID *src)
{
memcpy(dst->uuid, src->uuid, sizeof(src->uuid));
}
/**
* @brief Convert BACnet Secure Connect VMAC address to string
* @param vmac - VMAC address
* @return string representation of VMAC address
*/
char *bsc_vmac_to_string(BACNET_SC_VMAC_ADDRESS *vmac)
{
static char buf[128];
sprintf(
buf, "%02x%02x%02x%02x%02x%02x", vmac->address[0], vmac->address[1],
vmac->address[2], vmac->address[3], vmac->address[4], vmac->address[5]);
return buf;
}
/**
* @brief Convert BACnet Secure Connect UUID to string
* @param uuid - UUID
* @return string representation of UUID
*/
char *bsc_uuid_to_string(BACNET_SC_UUID *uuid)
{
static char buf[128];
sprintf(
buf,
"%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x",
uuid->uuid[0], uuid->uuid[1], uuid->uuid[2], uuid->uuid[3],
uuid->uuid[4], uuid->uuid[5], uuid->uuid[6], uuid->uuid[7],
uuid->uuid[8], uuid->uuid[9], uuid->uuid[10], uuid->uuid[11],
uuid->uuid[12], uuid->uuid[13], uuid->uuid[14], uuid->uuid[15]);
return buf;
}
/**
* @brief Generate random BACnet Secure Connect VMAC address
* @param p - pointer to the VMAC address
*/
void bsc_generate_random_vmac(BACNET_SC_VMAC_ADDRESS *p)
{
int i;
for (i = 0; i < BVLC_SC_VMAC_SIZE; i++) {
p->address[i] = rand() % 255;
if (i == 0) {
/* According H.7.3 EUI-48 and Random-48 VMAC Address:
The Random-48 VMAC is a 6-octet VMAC address in which the least
significant 4 bits (Bit 3 to Bit 0) in the first octet shall be
B'0010' (X'2'), and all other 44 bits are randomly selected to be
0 or 1. */
p->address[i] = (p->address[i] & 0xF0) | 0x02;
}
}
debug_printf_hex(
0, p->address, BVLC_SC_VMAC_SIZE, "bsc_generate_random_vmac");
}
/**
* @brief Generate random BACnet Secure Connect UUID
* @param p - pointer to the UUID
*/
void bsc_generate_random_uuid(BACNET_SC_UUID *p)
{
int i;
for (i = 0; i < BVLC_SC_UUID_SIZE; i++) {
p->uuid[i] = rand() % 255;
}
debug_printf_hex(0, p->uuid, BVLC_SC_UUID_SIZE, "bsc_generate_random_uuid");
}
/*
* bsc_node_load_cert_bacfile loads one credentional file from bacfile object
* Note: the function adds null-terminated byte to loaded file
* (certificate, certificate key).
* The MbedTLS PEM parser requires data to be null-terminated.
*/
#ifdef CONFIG_MBEDTLS
#define ZERO_BYTE 1
#else
#define ZERO_BYTE 0
#endif
/**
* @brief Load certificate from BACnet file object
* @param file_instance - file instance
* @param pbuf - pointer to the buffer
* @param psize - pointer to the size
* @return true if successful, else false
*/
static bool bsc_node_load_cert_bacfile(
uint32_t file_instance, uint8_t **pbuf, size_t *psize)
{
uint32_t file_length;
uint8_t *buf;
*psize = bacfile_file_size(file_instance) + ZERO_BYTE;
if (*psize == 0) {
return false;
}
buf = calloc(1, *psize);
if (buf == NULL) {
return false;
}
file_length =
bacfile_read(file_instance, buf, (uint32_t)(*psize - ZERO_BYTE));
#ifdef CONFIG_MBEDTLS
buf[*psize - 1] = 0;
#endif
if (file_length == 0) {
PRINTF_ERR("Can't read %s file\n", bacfile_pathname(file_instance));
free(buf);
return false;
}
*pbuf = buf;
return true;
}
/**
* @brief Fill BACnet/SC node configuration from network port object
* @param bsc_conf - pointer to the BACnet/SC node configuration
* @param event_func - event function
* @return true if successful, else false
*/
bool bsc_node_conf_fill_from_netport(
BSC_NODE_CONF *bsc_conf, BSC_NODE_EVENT_FUNC event_func)
{
uint32_t instance;
uint32_t file_instance;
instance = Network_Port_Index_To_Instance(0);
bsc_conf->ca_cert_chain = NULL;
bsc_conf->cert_chain = NULL;
bsc_conf->key = NULL;
file_instance = Network_Port_Issuer_Certificate_File(instance, 0);
if (!bsc_node_load_cert_bacfile(
file_instance, &bsc_conf->ca_cert_chain,
&bsc_conf->ca_cert_chain_size)) {
bsc_node_conf_cleanup(bsc_conf);
return false;
}
file_instance = Network_Port_Operational_Certificate_File(instance);
if (!bsc_node_load_cert_bacfile(
file_instance, &bsc_conf->cert_chain, &bsc_conf->cert_chain_size)) {
bsc_node_conf_cleanup(bsc_conf);
return false;
}
file_instance = Network_Port_Certificate_Key_File(instance);
bsc_conf->key_size = bacfile_file_size(file_instance) + 1;
if (!bsc_node_load_cert_bacfile(
file_instance, &bsc_conf->key, &bsc_conf->key_size)) {
bsc_node_conf_cleanup(bsc_conf);
return false;
}
bsc_conf->local_uuid =
(BACNET_SC_UUID *)Network_Port_SC_Local_UUID(instance);
Network_Port_MAC_Address_Value(
instance, bsc_conf->local_vmac.address,
sizeof(bsc_conf->local_vmac.address));
bsc_conf->max_local_bvlc_len =
(uint16_t)Network_Port_Max_BVLC_Length_Accepted(instance);
bsc_conf->max_local_npdu_len =
(uint16_t)Network_Port_Max_NPDU_Length_Accepted(instance);
bsc_conf->connect_timeout_s =
(uint16_t)Network_Port_SC_Connect_Wait_Timeout(instance);
bsc_conf->heartbeat_timeout_s =
(uint16_t)Network_Port_SC_Heartbeat_Timeout(instance);
bsc_conf->disconnect_timeout_s =
(uint16_t)Network_Port_SC_Disconnect_Wait_Timeout(instance);
bsc_conf->reconnnect_timeout_s =
(uint16_t)Network_Port_SC_Maximum_Reconnect_Time(instance);
bsc_conf->address_resolution_timeout_s = bsc_conf->connect_timeout_s;
bsc_conf->address_resolution_freshness_timeout_s =
bsc_conf->connect_timeout_s;
bsc_conf->primaryURL =
(char *)Network_Port_SC_Primary_Hub_URI_char(instance);
bsc_conf->failoverURL =
(char *)Network_Port_SC_Failover_Hub_URI_char(instance);
#if BSC_CONF_HUB_CONNECTORS_NUM != 0
bsc_conf->direct_connect_initiate_enable =
Network_Port_SC_Direct_Connect_Initiate_Enable(instance);
bsc_conf->direct_connect_accept_enable =
Network_Port_SC_Direct_Connect_Accept_Enable(instance);
Network_Port_SC_Direct_Connect_Binding_get(
instance, &bsc_conf->direct_server_port, &bsc_conf->direct_iface);
#endif
#if BSC_CONF_HUB_FUNCTIONS_NUM != 0
Network_Port_SC_Hub_Function_Binding_get(
instance, &bsc_conf->hub_server_port, &bsc_conf->hub_iface);
bsc_conf->hub_function_enabled =
Network_Port_SC_Hub_Function_Enable(instance);
#endif
#if BSC_CONF_HUB_CONNECTORS_NUM != 0
bsc_conf->direct_connection_accept_uris =
Network_Port_SC_Direct_Connect_Accept_URIs_char(instance);
bsc_conf->direct_connection_accept_uris_len =
strlen(bsc_conf->direct_connection_accept_uris);
#endif
bsc_conf->event_func = event_func;
return true;
}
/**
* @brief Cleanup BACnet/SC node configuration
* @param bsc_conf - pointer to the BACnet/SC node configuration
*/
void bsc_node_conf_cleanup(BSC_NODE_CONF *bsc_conf)
{
bsc_conf->ca_cert_chain_size = 0;
if (bsc_conf->ca_cert_chain) {
free(bsc_conf->ca_cert_chain);
}
bsc_conf->cert_chain_size = 0;
if (bsc_conf->cert_chain) {
free(bsc_conf->cert_chain);
}
bsc_conf->key_size = 0;
if (bsc_conf->key) {
free(bsc_conf->key);
}
}
/**
* @brief Copy string
* @param dst - destination string
* @param src - source string
* @param dst_len - destination string length
*/
void bsc_copy_str(char *dst, const char *src, size_t dst_len)
{
size_t len;
if (dst_len > 0) {
len = strlen(src) >= dst_len ? dst_len - 1 : strlen(src);
memcpy(dst, src, len);
dst[len] = 0;
}
}
/**
* @brief Set timestamp
* @param timestamp - pointer to the timestamp
*/
void bsc_set_timestamp(BACNET_DATE_TIME *timestamp)
{
int16_t utc_offset_minutes;
bool dst_active;
datetime_local(
&timestamp->date, &timestamp->time, &utc_offset_minutes, &dst_active);
}
/**
* @brief Check if BACnet/SC certificate files exist
* @return true if all files exist, else false
*/
bool bsc_cert_files_check(void)
{
uint32_t instance;
uint32_t file_instance;
instance = Network_Port_Index_To_Instance(0);
file_instance = Network_Port_Issuer_Certificate_File(instance, 0);
if (bacfile_file_size(file_instance) == 0) {
PRINTF_ERR("CA certificate file not exist\n");
return false;
}
file_instance = Network_Port_Operational_Certificate_File(instance);
if (bacfile_file_size(file_instance) == 0) {
PRINTF_ERR("Certificate file not exist\n");
return false;
}
file_instance = Network_Port_Certificate_Key_File(instance);
if (bacfile_file_size(file_instance) == 0) {
PRINTF_ERR("Certificate key file not exist\n");
return false;
}
return true;
}
+36
View File
@@ -0,0 +1,36 @@
/**
* @file
* @brief module for common function for BACnet/SC implementation
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date Jule 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_UTIL_H
#define BACNET_DATALINK_BSC_UTIL_H
#include <stdbool.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/bsc/bsc-node.h"
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/websocket.h"
#include "bacnet/datetime.h"
BSC_SC_RET bsc_map_websocket_retcode(BSC_WEBSOCKET_RET ret);
void bsc_copy_vmac(BACNET_SC_VMAC_ADDRESS *dst, BACNET_SC_VMAC_ADDRESS *src);
void bsc_copy_uuid(BACNET_SC_UUID *dst, BACNET_SC_UUID *src);
char *bsc_vmac_to_string(BACNET_SC_VMAC_ADDRESS *vmac);
char *bsc_uuid_to_string(BACNET_SC_UUID *uuid);
void bsc_generate_random_vmac(BACNET_SC_VMAC_ADDRESS *p);
void bsc_generate_random_uuid(BACNET_SC_UUID *p);
bool bsc_node_conf_fill_from_netport(
BSC_NODE_CONF *bsc_conf, BSC_NODE_EVENT_FUNC event_func);
void bsc_node_conf_cleanup(BSC_NODE_CONF *bsc_conf);
void bsc_copy_str(char *dst, const char *src, size_t dst_len);
void bsc_set_timestamp(BACNET_DATE_TIME *timestamp);
#endif
File diff suppressed because it is too large Load Diff
+425
View File
@@ -0,0 +1,425 @@
/**
* @file
* @brief API for encoding/decoding of BACnet/SC BVLC messages
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date May 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_BVLC_SC_H
#define BACNET_DATALINK_BSC_BVLC_SC_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <limits.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/npdu.h"
#ifndef BVLC_SC_NPDU_SIZE_CONF
#define BVLC_SC_NPDU_SIZE 1440
#else
#define BVLC_SC_NPDU_SIZE BVLC_SC_NPDU_SIZE_CONF
#endif
#define BVLC_SC_NPDU_MAX_SIZE \
61327 /* Table 6-1. NPDU Lengths of BACnet Data Link Layers */
#define BVLC_SC_VMAC_SIZE 6
#define BVLC_SC_UUID_SIZE 16
#define BSC_PRE (2 * BVLC_SC_VMAC_SIZE)
#if !defined(USER_DEFINED_BVLC_SC_HEADER_OPTION_MAX)
#define BVLC_SC_HEADER_OPTION_MAX \
4 /* though BACNet standard does not limit number of option headers \
the implementation defines max value */
#else
#define BVLC_SC_HEADER_OPTION_MAX USER_DEFINED_BVLC_SC_HEADER_OPTION_MAX
#endif
#if BVLC_SC_NPDU_SIZE > BVLC_SC_NPDU_MAX_SIZE
#error \
"Maximum NPDU Length on BACNet/SC Data Link must be <= BVLC_SC_NPDU_MAX_SIZE"
#endif
/*
* BACnet/SC BVLC Messages (functions) (AB.2 BACnet/SC Virtual Link Layer
* Messages)
*/
typedef enum BVLC_SC_Message_Type {
BVLC_SC_RESULT = 0x00,
BVLC_SC_ENCAPSULATED_NPDU = 0x01,
BVLC_SC_ADDRESS_RESOLUTION = 0x02,
BVLC_SC_ADDRESS_RESOLUTION_ACK = 0x03,
BVLC_SC_ADVERTISIMENT = 0x04,
BVLC_SC_ADVERTISIMENT_SOLICITATION = 0x05,
BVLC_SC_CONNECT_REQUEST = 0x06,
BVLC_SC_CONNECT_ACCEPT = 0x07,
BVLC_SC_DISCONNECT_REQUEST = 0x08,
BVLC_SC_DISCONNECT_ACK = 0x09,
BVLC_SC_HEARTBEAT_REQUEST = 0x0a,
BVLC_SC_HEARTBEAT_ACK = 0x0b,
BVLC_SC_PROPRIETARY_MESSAGE = 0x0c
} BVLC_SC_MESSAGE_TYPE;
/*
* AB.2.2 Control Flags
*/
#define BVLC_SC_CONTROL_DATA_OPTIONS (1 << 0)
#define BVLC_SC_CONTROL_DEST_OPTIONS (1 << 1)
#define BVLC_SC_CONTROL_DEST_VADDR (1 << 2)
#define BVLC_SC_CONTROL_ORIG_VADDR (1 << 3)
/*
* AB.2.3 Header Options
*/
#define BVLC_SC_HEADER_DATA (1 << 5)
#define BVLC_SC_HEADER_MUST_UNDERSTAND (1 << 6)
#define BVLC_SC_HEADER_MORE (1 << 7)
#define BVLC_SC_HEADER_OPTION_TYPE_MASK (0x1F)
/**
* BACnet SC VMAC Address
*
* B.1.5.2 VMAC Addressing of Nodes
* For the BVLC message exchange, BACnet/SC nodes are identified by their
* 6-octet virtual MAC address as defined in Clause H.7.3.
* For broadcast BVLC messages that need to reach all nodes of the BACnet/SC
* network, the destination VMAC address shall be the non-EUI-48 value
* X'FFFFFFFFFFFF', referred to as the Local Broadcast VMAC address.
* The reserved EUI-48 value X'000000000000' is not used by this data link
* and therefore can be used internally to indicate that a VMAC is unknown
* or uninitialized.
* @{
*/
typedef struct BACnet_SC_VMAC_Address {
uint8_t address[BVLC_SC_VMAC_SIZE];
} BACNET_SC_VMAC_ADDRESS;
/** @} */
/**
* BACnet SC UUID
* AB.1.5.3 Device UUID
* Every BACnet device that supports one or more BACnet/SC network ports shall
* have a Universally Unique ID (UUID) as defined in RFC 4122. This UUID
* identifies the device regardless of its current VMAC address or device
* instance number and is referred to as the device UUID.
* This device UUID shall be generated before first deployment of the device in
* an installation, shall be persistently stored across device restarts, and
* shall not change over the entire lifetime of a device.
* If a device is replaced in an installation, the new device is not required
* to reuse the UUID of the replaced device. For BACnet/SC, it is assumed
* that existing connections to the device being replaced are all terminated
* before the new device comes into operation.
* @{
*/
typedef struct BACnet_SC_Uuid {
uint8_t uuid[BVLC_SC_UUID_SIZE];
} BACNET_SC_UUID;
/** @} */
/*
* AB.2.3.1 Secure Path Header Option
*/
typedef enum BVLC_SC_Option_Type {
BVLC_SC_OPTION_TYPE_SECURE_PATH = 1,
BVLC_SC_OPTION_TYPE_PROPRIETARY = 31
} BVLC_SC_OPTION_TYPE;
typedef enum BVLC_SC_Direct_Connection_Support {
BVLC_SC_DIRECT_CONNECTION_ACCEPT_UNSUPPORTED = 0,
BVLC_SC_DIRECT_CONNECTION_ACCEPT_SUPPORTED = 1,
BVLC_SC_DIRECT_CONNECTION_SUPPORT_MAX = 1
} BVLC_SC_DIRECT_CONNECTION_SUPPORT;
typedef struct BVLC_SC_Decoded_Hdr {
uint8_t bvlc_function;
uint16_t message_id;
BACNET_SC_VMAC_ADDRESS *origin;
BACNET_SC_VMAC_ADDRESS *dest;
uint8_t *dest_options; /* pointer to packed dest options list in message */
size_t dest_options_len;
size_t dest_options_num; /* number of filled items in dest_options */
uint8_t *data_options; /* pointer to packed data options list in message */
size_t data_options_len;
size_t data_options_num; /* number of filled items in data_options */
uint8_t *payload; /* packed payload, points to data in message */
size_t payload_len;
} BVLC_SC_DECODED_HDR;
typedef struct BVLC_SC_Decoded_Result {
uint8_t bvlc_function;
uint8_t result;
uint8_t error_header_marker;
uint16_t error_class;
uint16_t error_code;
uint8_t *utf8_details_string; /* NOTE!: this is utf 8 string without
trailing zero */
size_t utf8_details_string_len;
} BVLC_SC_DECODED_RESULT;
typedef struct BVLC_SC_Decoded_Address_Resolution_Ack {
uint8_t *utf8_websocket_uri_string; /* NOTE!: this is utf 8 string without
trailing zero */
size_t utf8_websocket_uri_string_len;
} BVLC_SC_DECODED_ADDRESS_RESOLUTION_ACK;
typedef struct BVLC_SC_Decoded_Ecapsulated_NPDU {
uint8_t *npdu;
size_t npdu_len;
} BVLC_SC_DECODED_ENCAPSULATED_NPDU;
typedef struct BVLC_SC_Decoded_Advertisiment {
BACNET_SC_HUB_CONNECTOR_STATE hub_status;
BVLC_SC_DIRECT_CONNECTION_SUPPORT support;
uint16_t max_bvlc_len;
uint16_t max_npdu_len;
} BVLC_SC_DECODED_ADVERTISIMENT;
typedef struct BVLC_SC_Decoded_Connect_Request {
BACNET_SC_VMAC_ADDRESS *vmac;
BACNET_SC_UUID *uuid;
uint16_t max_bvlc_len;
uint16_t max_npdu_len;
} BVLC_SC_DECODED_CONNECT_REQUEST;
typedef struct BVLC_SC_Decoded_Connect_Accept {
BACNET_SC_VMAC_ADDRESS *vmac;
BACNET_SC_UUID *uuid;
uint16_t max_bvlc_len;
uint16_t max_npdu_len;
} BVLC_SC_DECODED_CONNECT_ACCEPT;
typedef struct BVLC_SC_Decoded_Proprietary {
uint16_t vendor_id;
uint8_t function;
uint8_t *data;
size_t data_len;
} BVLC_SC_DECODED_PROPRIETARY;
typedef union BVLC_SC_Decoded_Data {
BVLC_SC_DECODED_RESULT result;
BVLC_SC_DECODED_ENCAPSULATED_NPDU encapsulated_npdu;
BVLC_SC_DECODED_ADDRESS_RESOLUTION_ACK address_resolution_ack;
BVLC_SC_DECODED_ADVERTISIMENT advertisiment;
BVLC_SC_DECODED_CONNECT_REQUEST connect_request;
BVLC_SC_DECODED_CONNECT_ACCEPT connect_accept;
BVLC_SC_DECODED_PROPRIETARY proprietary;
} BVLC_SC_DECODED_DATA;
typedef struct BVLC_SC_Decoded_Hdr_Proprietary_Option {
uint16_t vendor_id;
uint8_t option_type;
uint8_t *data;
size_t data_len;
} BVLC_SC_DECODED_HDR_PROPRIETARY_OPTION;
typedef union BVLC_SC_Decoded_Specific_Option_Data {
BVLC_SC_DECODED_HDR_PROPRIETARY_OPTION proprietary;
} BVLC_SC_DECODED_SPECIFIC_OPTION_DATA;
typedef struct BVLC_SC_Decoded_Hdr_Option {
uint8_t packed_header_marker;
BVLC_SC_OPTION_TYPE type;
bool must_understand;
BVLC_SC_DECODED_SPECIFIC_OPTION_DATA specific;
} BVLC_SC_DECODED_HDR_OPTION;
typedef struct BVLC_SC_Decoded_Message {
BVLC_SC_DECODED_HDR hdr;
BVLC_SC_DECODED_HDR_OPTION data_options[BVLC_SC_HEADER_OPTION_MAX];
BVLC_SC_DECODED_HDR_OPTION dest_options[BVLC_SC_HEADER_OPTION_MAX];
BVLC_SC_DECODED_DATA payload;
} BVLC_SC_DECODED_MESSAGE;
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
size_t bvlc_sc_add_option_to_destination_options(
uint8_t *out_pdu,
size_t out_pdu_size,
uint8_t *pdu,
size_t pdu_size,
uint8_t *sc_option,
size_t sc_option_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_add_option_to_data_options(
uint8_t *out_pdu,
size_t out_pdu_size,
uint8_t *pdu,
size_t pdu_size,
uint8_t *sc_option,
size_t sc_option_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_proprietary_option(
uint8_t *pdu,
size_t pdu_size,
bool must_understand,
uint16_t vendor_id,
uint8_t proprietary_option_type,
uint8_t *proprietary_data,
size_t proprietary_data_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_secure_path_option(
uint8_t *pdu, size_t pdu_size, bool must_understand);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_result(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t bvlc_function,
uint8_t result_code,
uint8_t *error_header_marker,
uint16_t *error_class,
uint16_t *error_code,
uint8_t *utf8_details_string);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_encapsulated_npdu(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *npdu,
size_t npdu_size);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_address_resolution(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_address_resolution_ack(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *web_socket_uris,
size_t web_socket_uris_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_advertisiment(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
BACNET_SC_HUB_CONNECTOR_STATE hub_status,
BVLC_SC_DIRECT_CONNECTION_SUPPORT support,
uint16_t max_bvlc_len,
uint16_t max_npdu_size);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_advertisiment_solicitation(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_connect_request(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *local_vmac,
BACNET_SC_UUID *local_uuid,
uint16_t max_bvlc_len,
uint16_t max_npdu_size);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_connect_accept(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *local_vmac,
BACNET_SC_UUID *local_uuid,
uint16_t max_bvlc_len,
uint16_t max_npdu_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_disconnect_request(
uint8_t *pdu, size_t pdu_len, uint16_t message_id);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_disconnect_ack(
uint8_t *pdu, size_t pdu_len, uint16_t message_id);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_heartbeat_request(
uint8_t *out_buf, size_t out_buf_len, uint16_t message_id);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_heartbeat_ack(
uint8_t *out_buf, size_t out_buf_len, uint16_t message_id);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_proprietary_message(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint16_t vendor_id,
uint8_t proprietary_function,
uint8_t *proprietary_data,
size_t proprietary_data_len);
BACNET_STACK_EXPORT
bool bvlc_sc_decode_message(
uint8_t *buf,
size_t buf_len,
BVLC_SC_DECODED_MESSAGE *message,
BACNET_ERROR_CODE *error,
BACNET_ERROR_CLASS *class,
const char **err_desc);
BACNET_STACK_EXPORT
void bvlc_sc_remove_dest_set_orig(
uint8_t *pdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *orig);
BACNET_STACK_EXPORT
size_t
bvlc_sc_set_orig(uint8_t **ppdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *orig);
BACNET_STACK_EXPORT
bool bvlc_sc_is_vmac_broadcast(BACNET_SC_VMAC_ADDRESS *vmac);
BACNET_STACK_EXPORT
bool bvlc_sc_need_send_bvlc_result(BVLC_SC_DECODED_MESSAGE *dm);
BACNET_STACK_EXPORT
bool bvlc_sc_pdu_has_dest_broadcast(uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
bool bvlc_sc_pdu_has_no_dest(uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
bool bvlc_sc_pdu_get_dest(
uint8_t *pdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *vmac);
BACNET_STACK_EXPORT
size_t bvlc_sc_remove_orig_and_dest(uint8_t **ppdu, size_t pdu_len);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+450
View File
@@ -0,0 +1,450 @@
/**
* @file
* @brief Client/Server thread-safe websocket interface API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date May 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_WEBSOCKET_H
#define BACNET_DATALINK_BSC_WEBSOCKET_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-conf.h"
/**
* Maximum number of sockets that can be opened on client's side.
* @{
*/
#ifndef BSC_CONF_CLIENT_CONNECTIONS_NUM
#define BSC_CLIENT_WEBSOCKETS_MAX_NUM 4
#else
#define BSC_CLIENT_WEBSOCKETS_MAX_NUM BSC_CONF_CLIENT_CONNECTIONS_NUM
#endif
/** @} */
/**
* Maximum number of server instances. Value 10 means that
* 10 hub servers and 10 direct servers can be started.
* @{
*/
#ifndef BSC_CONF_WEBSOCKET_SERVERS_NUM
#define BSC_CONF_WEBSOCKET_SERVERS_NUM 1
#endif
/** @} */
/** @} */
/**
* Maximum number of sockets supported for hub websocket server
* @{
*/
#ifndef BSC_CONF_SERVER_HUB_CONNECTIONS_MAX_NUM
#define BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM 4
#else
#define BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM \
BSC_CONF_SERVER_HUB_CONNECTIONS_MAX_NUM
#endif
/** @} */
/**
* Initial size of websocket buffer used on receive. It can be increased
* dynamically depending on received packet size.
* @{
*/
#ifndef BSC_CONF_WEBSOCKET_RX_BUFFER_LEN
#define BSC_WEBSOCKET_RX_BUFFER_LEN 512
#else
#define BSC_WEBSOCKET_RX_BUFFER_LEN BSC_CONF_WEBSOCKET_RX_BUFFER_LEN
#endif
/** @} */
/**
* Maximum number of sockets supported for direct websocket server
* @{
*/
#ifndef BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM
#define BSC_SERVER_DIRECT_WEBSOCKETS_MAX_NUM 4
#else
#define BSC_SERVER_DIRECT_WEBSOCKETS_MAX_NUM \
BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM
#endif
#ifndef BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN
#define BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN 128
#else
#define BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN \
BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN
#endif
#define BSC_WSURL_MAX_LEN BSC_CONF_WSURL_MAX_LEN
typedef int BSC_WEBSOCKET_HANDLE;
typedef void *BSC_WEBSOCKET_SRV_HANDLE;
#define BSC_WEBSOCKET_INVALID_HANDLE (-1)
/* Websockets protocol defined in BACnet/SC \S AB.7.1. */
#define BSC_WEBSOCKET_HUB_PROTOCOL_STR "hub.bsc.bacnet.org"
#define BSC_WEBSOCKET_DIRECT_PROTOCOL_STR "dc.bsc.bacnet.org"
typedef enum {
BSC_WEBSOCKET_HUB_PROTOCOL = 0,
BSC_WEBSOCKET_DIRECT_PROTOCOL = 1,
BSC_WEBSOCKET_PROTOCOLS_AMOUNT = 2 /* must be always last */
} BSC_WEBSOCKET_PROTOCOL;
typedef enum {
BSC_WEBSOCKET_SUCCESS = 0,
BSC_WEBSOCKET_NO_RESOURCES = 1,
BSC_WEBSOCKET_BAD_PARAM = 2,
BSC_WEBSOCKET_INVALID_OPERATION = 3
} BSC_WEBSOCKET_RET;
typedef enum {
BSC_WEBSOCKET_CONNECTED = 0,
BSC_WEBSOCKET_DISCONNECTED = 1,
BSC_WEBSOCKET_RECEIVED = 2,
BSC_WEBSOCKET_SENDABLE = 3,
BSC_WEBSOCKET_SERVER_STARTED = 4,
BSC_WEBSOCKET_SERVER_STOPPED = 5
} BSC_WEBSOCKET_EVENT;
/*
values of ws_reason and ws_reason_desc parameters are actual
only for BSC_WEBSOCKET_DISCONNECTED event.
*/
typedef void (*BSC_WEBSOCKET_CLI_DISPATCH)(
BSC_WEBSOCKET_HANDLE h,
BSC_WEBSOCKET_EVENT ev,
BACNET_ERROR_CODE ws_reason,
char *ws_reason_desc,
uint8_t *buf,
size_t bufsize,
void *dispatch_func_user_param);
typedef void (*BSC_WEBSOCKET_SRV_DISPATCH)(
BSC_WEBSOCKET_SRV_HANDLE sh,
BSC_WEBSOCKET_HANDLE h,
BSC_WEBSOCKET_EVENT ev,
BACNET_ERROR_CODE ws_reason,
char *ws_reason_desc,
uint8_t *buf,
size_t bufsize,
void *dispatch_func_user_param);
/**
* @brief Asynchronous bws_cli_сonnect() function starts establishing
* of a new connection to a websocket server specified by url parameter.
* Result of completion of operation is call of dispatch_func() with
* BSC_WEBSOCKET_CONNECTED in a case if connection established successfully or
* BSC_WEBSOCKET_DISCONNECTED if connection attempt failed.
*
* @param type - type of BACNet/SC connection, check
* BSC_WEBSOCKET_CONNECTION_TYPE enum. According BACNet standard
* different type of connections require different websocket protocols.
* @param url - BACNet/SC server URL. For example: wss://legrand.com:8080.
* @param ca_cert - pointer to certificate authority (CA) cert in PEM or DER
* format.
* @param ca_cert_size - size in bytes of CA cert.
* @param cert - pointer to client certificate in PEM or DER format.
* @param cert_size - size in bytes of client certificate.
* @param key - pointer to client private key in PEM or DER format.
* @param key_size - size of private key in bytes of of client certificate.
* @param timeout_s - timeout for socket operations (connect, etc..) in seconds.
* Must be > 0.
* @param dispatch_func - pointer to dispatch callback function to handle
* events from websocket specified by *out_handle.
* @param dispatch_func_user_param - parameter which is passed into
dispatch_func call.
* @param out_handle - pointer to a websocket handle.
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_WEBSOCKET_NO_RESOURCES - if a user has already opened
* more sockets than the limit defined by BSC_CLIENT_WEBSOCKETS_MAX_NUM,
* or if some mem allocation has failed or some allocation of system
* resources like mutex, thread, etc .., failed.
* BSC_WEBSOCKET_SUCCESS - connect operation was successfully started.
*/
BSC_WEBSOCKET_RET bws_cli_connect(
BSC_WEBSOCKET_PROTOCOL proto,
char *url,
uint8_t *ca_cert,
size_t ca_cert_size,
uint8_t *cert,
size_t cert_size,
uint8_t *key,
size_t key_size,
size_t timeout_s,
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func,
void *dispatch_func_user_param,
BSC_WEBSOCKET_HANDLE *out_handle);
/**
* @brief Asynchronous bws_cli_disconnnect() function starts process of
* disconnection for specified websocket handle. When the process completes,
* dispatch_func() with event BSC_WEBSOCKET_DISCONNECTED is called.
* connection to some websocket server.
*
* @param h - websocket handle.
*
*/
void bws_cli_disconnect(BSC_WEBSOCKET_HANDLE h);
/**
* @brief Non-blocked bws_cli_send() function signals to the websocket
* specified by websocket handle h that some data is needed to be sent.
* When websocket becomes sendable, dispatch_func() is called with
* event BSC_WEBSOCKET_SENDABLE and data can be sent from dispatch_func()
* call using bws_cli_dispatch_send() call.
*
* @param h - websocket handle.
*
*/
void bws_cli_send(BSC_WEBSOCKET_HANDLE h);
/**
* @brief bws_cli_dispatch_send() function sends data to a websocket server
* in a case if websocket handle is sendable (e.g. ready to send data).
* In as case if data was not sent for some reasons thic could result
* dispatch_func() cal with event BSC_WEBSOCKET_DISCONNECTED
* @param h - websocket handle.
* @param payload - pointer to a data to send. It is assumed that
* BSC_CONF_TX_PRE bytes are available before payload.
* So for example you need this kind of code to use
* bws_srv_dispatch_send with a 128-byte payload:
*
* char buf[BSC_CONF_TX_PRE + 128];
*
* // fill your part of the buffer... for example here
* // it's all zeros
*
* memset(&buf[BSC_CONF_TX_PRE], 0, 128);
* bws_cli_dispatch_send(sh, h, &buf[BSC_CONF_TX_PRE], 128);
*
* @param payload_size - size in bytes of data to send.
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_WEBSOCKET_NO_RESOURCES - if some mem allocation has failed o
* some allocation of system resources like mutex, thread,
* etc .., has failed.
* BSC_WEBSOCKET_INVALID_OPERATION - if the function was called not from
* dispatch_func() callback context or websocket is not in connected
* state.
* BSC_WEBSOCKET_SUCCESS - data is sent successfully.
*/
BSC_WEBSOCKET_RET bws_cli_dispatch_send(
BSC_WEBSOCKET_HANDLE h, uint8_t *payload, size_t payload_size);
/**
* @brief Asynchronous bws_srv_start() function triggers process of
* starting of a websocket server on a specified port for specified
* BACNet websocket protocol. At present time peer can have only 2
* instances of server: onerelates to BSC_WEBSOCKET_HUB_PROTOCOL and
* the other to BSC_WEBSOCKET_HUB_PROTOCOL. When process completes,
* dispatch_func() is called with BSC_WEBSOCKET_SERVER_STARTED
* event.
*
* @param proto - type of BACNet websocket protocol defined in
* BSC_WEBSOCKET_PROTOCOL enum.
* @param port - port number.
* @param iface - name of interface to bind to. If the parameter is NULL
* that means that websocket server binds to all interfaces.
* @param ca_cert - pointer to certificate authority (CA) cert in PEM or DER
* format.
* @param ca_cert_size - size in bytes of CA cert.
* @param cert - pointer to server certificate in PEM or DER format.
* @param cert_size - size in bytes of server certificate.
* @param key - pointer to server private key in PEM or DER format.
* @param key_size - size of private key in bytes of of client certificate.
* @param timeout_s - timeout for socket operations (connect, etc..) in seconds.
* Must be > 0.
* @param dispatch_func - pointer to dispatch callback function to handle
* events from a websocket which is corresponded to
* server specified by proto param.
* @param dispatch_func_user_param - parameter which is passed into
* dispatch_func call.
* @param sh - pointer to receive websocket server handle
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_WEBSOCKET_NO_RESOURCES - if a user has already opened
* more sockets than the limit defined to corresponded protocol
* (BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM or
* BSC_CLIENT_WEBSOCKETS_MAX_NUM), or if some mem allocation
* has failed or some allocation of system resources like
* mutex, thread, condition variable etc .., failed.
* BSC_WEBSOCKET_SUCCESS - the start operation is in progress.
* BSC_WEBSOCKET_INVALID_OPERATION - operation is not started because
* server in a process of shutdown of server is already started.
*/
BSC_WEBSOCKET_RET bws_srv_start(
BSC_WEBSOCKET_PROTOCOL proto,
int port,
char *iface,
uint8_t *ca_cert,
size_t ca_cert_size,
uint8_t *cert,
size_t cert_size,
uint8_t *key,
size_t key_size,
size_t timeout_s,
BSC_WEBSOCKET_SRV_DISPATCH dispatch_func,
void *dispatch_func_user_param,
BSC_WEBSOCKET_SRV_HANDLE *sh);
/**
* @brief Asynchronous bws_srv_stop() function starts process of a shutdowns
* of a websocket server specified by proto param.
* opened websocket connections are closed.
*
* @param sh - websocket server handle
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_SUCCESS - the operation is started or server was
* already stopped before. BSC_WEBSOCKET_INVALID_OPERATION - if server was not
* started or server shutdown is already in progress.
*/
BSC_WEBSOCKET_RET bws_srv_stop(BSC_WEBSOCKET_SRV_HANDLE sh);
/**
* @brief Asynchronous bws_srv_disconnnect() function starts process of
* disconnection for specified websocket handle h for specified server type
* by proto parameter. When the process completes, dispatch_func() with event
* BSC_WEBSOCKET_DISCONNECTED is called.
*
* @param sh - websocket server handle.
* @param h - websocket handle.
*
*/
void bws_srv_disconnect(BSC_WEBSOCKET_SRV_HANDLE sh, BSC_WEBSOCKET_HANDLE h);
/**
* @brief Asynchronous bws_srv_send() function signals to a websocket
* specified by handle h for specified server type by proto param that
* some data is needed to be sent.
*
* When websocket becomes sendable, dispatch_func() is called with
* event BSC_WEBSOCKET_SENDABLE and data can be sent from dispatch_func()
* call using bws_srv_dispatch_send() call.
*
* @param sh - websocket server handle.
* @param h - websocket handle.
*
*/
void bws_srv_send(BSC_WEBSOCKET_SRV_HANDLE sh, BSC_WEBSOCKET_HANDLE h);
/**
* @brief bws_srv_dispatch_send() function sends data to a websocket server
* in a case if websocket handle is sendable (e.g. ready to send data).
* In as case if data was not sent for some reasons thic could result
* dispatch_func() call with event BSC_WEBSOCKET_DISCONNECTED
*
* @param sh - websocket server handle.
* @param h - websocket handle.
* @param payload - pointer to a data to send. It is assumed that
* BSC_CONF_TX_PRE bytes are available before payload.
* So for example you need this kind of code to use
* bws_srv_dispatch_send with a 128-byte payload:
*
* char buf[BSC_CONF_TX_PRE + 128];
*
* // fill your part of the buffer... for example here
* // it's all zeros
*
* memset(&buf[BSC_CONF_TX_PRE], 0, 128);
* bws_srv_dispatch_send(sh, h, &buf[BSC_CONF_TX_PRE], 128);
*
* @param payload_size - size in bytes of data to send.
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_WEBSOCKET_NO_RESOURCES - if some mem allocation has failed o
* some allocation of system resources like mutex, thread,
* etc .., has failed.
* BSC_WEBSOCKET_INVALID_OPERATION - if the function was called not from
* dispatch_func() callback context or websocket is not in connected
* state or server in a process of a shutdown or server is not started.
* BSC_WEBSOCKET_SUCCESS - data is sent successfully.
*/
BSC_WEBSOCKET_RET bws_srv_dispatch_send(
BSC_WEBSOCKET_SRV_HANDLE sh,
BSC_WEBSOCKET_HANDLE h,
uint8_t *payload,
size_t payload_size);
/**
* @brief bws_dispatch_lock() function acquires internal websocket
* global mutex. As a result dispatch_func() callback for all
* websocket server and client instances won't be called until user
* calls bws_dispatch_unlock().
*/
#ifndef BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED
#define BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED 0
#endif
#if (BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED != 1)
void bws_dispatch_lock(void);
#else
extern void bws_dispatch_lock_dbg(char *f, int line);
#define bws_dispatch_lock() bws_dispatch_lock_dbg(__FILE__, __LINE__)
#endif
/**
* @brief bws_dispatch_unlock() function releases internal websocket
* global mutex.
*/
#if (BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED != 1)
void bws_dispatch_unlock(void);
#else
extern void bws_dispatch_unlock_dbg(char *f, int line);
#define bws_dispatch_unlock() bws_dispatch_unlock_dbg(__FILE__, __LINE__)
#endif
/**
* @brief bws_srv_get_peer_ip_addr() gets ipv4 or ipv6 address as ANSI string
* and port of remote peer.
*
* @param sh - websocket server handle.
* @param h - websocket handle.
* @param ip_str - buffer to store null terminated string of ip address.
* @param ip_str_len - size of ip_str buffer
* @param port- pointer to store port of a remote node.
*
* @return true if function succeeded otherwise returns false
* if peer's address information can't be retrieved from
* underlying websocket library.
*/
bool bws_srv_get_peer_ip_addr(
BSC_WEBSOCKET_SRV_HANDLE sh,
BSC_WEBSOCKET_HANDLE h,
uint8_t *ip_str,
size_t ip_str_len,
uint16_t *port);
#endif
+50 -1
View File
@@ -29,6 +29,9 @@
#if defined(BACDL_MSTP)
#include "bacnet/datalink/dlmstp.h"
#endif
#if defined(BACDL_BSC)
#include "bacnet/datalink/bsc/bsc-datalink.h"
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h> /* for strcasecmp() */
#endif
@@ -39,7 +42,8 @@ static enum {
DATALINK_ETHERNET,
DATALINK_BIP,
DATALINK_BIP6,
DATALINK_MSTP
DATALINK_MSTP,
DATALINK_BSC
} Datalink_Transport;
void datalink_set(char *datalink_string)
@@ -72,6 +76,11 @@ void datalink_set(char *datalink_string)
Datalink_Transport = DATALINK_MSTP;
}
#endif
#if defined(BACDL_BSC)
else if (strcasecmp("bsc", datalink_string) == 0) {
Datalink_Transport = DATALINK_BSC;
}
#endif
}
bool datalink_init(char *ifname)
@@ -106,6 +115,11 @@ bool datalink_init(char *ifname)
case DATALINK_MSTP:
status = dlmstp_init(ifname);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
status = bsc_init(ifname);
break;
#endif
default:
break;
@@ -150,6 +164,11 @@ int datalink_send_pdu(
case DATALINK_MSTP:
bytes = dlmstp_send_pdu(dest, npdu_data, pdu, pdu_len);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bytes = bsc_send_pdu(dest, npdu_data, pdu, pdu_len);
break;
#endif
default:
break;
@@ -190,6 +209,11 @@ uint16_t datalink_receive(
case DATALINK_MSTP:
bytes = dlmstp_receive(src, pdu, max_pdu, timeout);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bytes = bsc_receive(src, pdu, max_pdu, timeout);
break;
#endif
default:
break;
@@ -227,6 +251,11 @@ void datalink_cleanup(void)
case DATALINK_MSTP:
dlmstp_cleanup();
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bsc_cleanup();
break;
#endif
default:
break;
@@ -262,6 +291,11 @@ void datalink_get_broadcast_address(BACNET_ADDRESS *dest)
case DATALINK_MSTP:
dlmstp_get_broadcast_address(dest);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bsc_get_broadcast_address(dest);
break;
#endif
default:
break;
@@ -297,6 +331,11 @@ void datalink_get_my_address(BACNET_ADDRESS *my_address)
case DATALINK_MSTP:
dlmstp_get_my_address(my_address);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bsc_get_my_address(my_address);
break;
#endif
default:
break;
@@ -333,6 +372,11 @@ void datalink_set_interface(char *ifname)
case DATALINK_MSTP:
(void)ifname;
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
(void)ifname;
break;
#endif
default:
break;
@@ -365,6 +409,11 @@ void datalink_maintenance_timer(uint16_t seconds)
#if defined(BACDL_MSTP)
case DATALINK_MSTP:
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bsc_maintenance_timer(seconds);
break;
#endif
default:
break;
+16
View File
@@ -32,6 +32,11 @@
#include "bacnet/basic/bbmd6/h_bbmd6.h"
#endif
#if defined(BACDL_BSC)
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bsc-datalink.h"
#endif
#if defined(BACDL_ETHERNET) && !defined(BACDL_MULTIPLE)
#define MAX_MPDU ETHERNET_MPDU_MAX
@@ -99,6 +104,17 @@ void routed_get_my_address(BACNET_ADDRESS *my_address);
#define datalink_get_my_address bip6_get_my_address
#define datalink_maintenance_timer(s) bvlc6_maintenance_timer(s)
#elif defined(BACDL_BSC) && !defined(BACDL_MULTIPLE)
#define MAX_MPDU BVLC_SC_NPDU_SIZE_CONF
#define datalink_init bsc_init
#define datalink_send_pdu bsc_send_pdu
#define datalink_receive bsc_receive
#define datalink_cleanup bsc_cleanup
#define datalink_get_broadcast_address bsc_get_broadcast_address
#define datalink_get_my_address bsc_get_my_address
#define datalink_maintenance_timer(s) bsc_maintenance_timer(s)
#elif !defined(BACDL_TEST) /* Multiple, none or custom datalink */
#include "bacnet/npdu.h"
+230
View File
@@ -26,6 +26,14 @@
#if (BACNET_PROTOCOL_REVISION >= 17)
#include "bacnet/basic/object/netport.h"
#endif
#if defined(BACDL_BSC)
#include "bacnet/basic/object/bacfile.h"
#include "bacnet/basic/object/sc_netport.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/datalink/bsc/bsc-datalink.h"
#include "bacnet/datalink/bsc/bsc-event.h"
#endif
/** @file dlenv.c Initialize the DataLink configuration. */
/* timer used to renew Foreign Device Registration */
@@ -459,6 +467,179 @@ void dlenv_network_port_init(void)
since they are already set */
Network_Port_Changes_Pending_Set(instance, false);
}
#elif defined(BACDL_BSC)
/**
* @brief Datalink network port object settings
* @param primary_hub_uri
* @param failover_hub_uri
* @param filename_ca_1_cert
* @param filename_ca_2_cert
* @param filename_cert
* @param filename_key
* @param direct_connect_port
* @param hub_function_port
* @param direct_connect_initiate
* @param direct_connect_accept_urls
*/
static void bacnet_secure_connect_network_port_init(
char *primary_hub_uri,
char *failover_hub_uri,
char *filename_ca_1_cert,
char *filename_ca_2_cert,
char *filename_cert,
char *filename_key,
char *direct_binding,
char *hub_binding,
char *direct_connect_initiate,
char *direct_connect_accept_urls)
{
const uint32_t instance = 1;
BACNET_SC_UUID uuid = { 0 };
BACNET_SC_VMAC_ADDRESS vmac = { 0 };
long seed;
char c;
seed = (long)&instance;
srand((int)seed);
Network_Port_Object_Instance_Number_Set(0, instance);
Network_Port_Name_Set(instance, "BACnet/BSC Port");
Network_Port_Type_Set(instance, PORT_TYPE_BSC);
bsc_generate_random_uuid(&uuid);
Network_Port_SC_Local_UUID_Set(instance, (BACNET_UUID *)&uuid);
bsc_generate_random_vmac(&vmac);
Network_Port_MAC_Address_Set(instance, vmac.address, sizeof(vmac));
/* common NP data */
Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED);
Network_Port_Out_Of_Service_Set(instance, false);
Network_Port_Quality_Set(instance, PORT_QUALITY_UNKNOWN);
Network_Port_APDU_Length_Set(instance, MAX_APDU);
Network_Port_Network_Number_Set(instance, 0);
/* SC parameters */
Network_Port_Max_BVLC_Length_Accepted_Set(instance, SC_NETPORT_BVLC_MAX);
Network_Port_Max_NPDU_Length_Accepted_Set(instance, SC_NETPORT_NPDU_MAX);
Network_Port_SC_Connect_Wait_Timeout_Set(
instance, SC_NETPORT_CONNECT_TIMEOUT);
Network_Port_SC_Heartbeat_Timeout_Set(
instance, SC_NETPORT_HEARTBEAT_TIMEOUT);
Network_Port_SC_Disconnect_Wait_Timeout_Set(
instance, SC_NETPORT_DISCONNECT_TIMEOUT);
Network_Port_SC_Maximum_Reconnect_Time_Set(
instance, SC_NETPORT_RECONNECT_TIME);
if (filename_ca_1_cert == NULL) {
fprintf(stderr, "BACNET_SC_ISSUER_1_CERTIFICATE_FILE must be set\n");
return;
}
bacfile_create(BSC_ISSUER_CERTIFICATE_FILE_1_INSTANCE);
bacfile_pathname_set(
BSC_ISSUER_CERTIFICATE_FILE_1_INSTANCE, filename_ca_1_cert);
Network_Port_Issuer_Certificate_File_Set(
instance, 0, BSC_ISSUER_CERTIFICATE_FILE_1_INSTANCE);
if (filename_ca_2_cert) {
bacfile_create(BSC_ISSUER_CERTIFICATE_FILE_2_INSTANCE);
bacfile_pathname_set(
BSC_ISSUER_CERTIFICATE_FILE_2_INSTANCE, filename_ca_2_cert);
Network_Port_Issuer_Certificate_File_Set(
instance, 1, BSC_ISSUER_CERTIFICATE_FILE_2_INSTANCE);
}
if (filename_cert == NULL) {
fprintf(stderr, "BACNET_SC_OPERATIONAL_CERTIFICATE_FILE must be set\n");
return;
}
bacfile_create(BSC_OPERATIONAL_CERTIFICATE_FILE_INSTANCE);
bacfile_pathname_set(
BSC_OPERATIONAL_CERTIFICATE_FILE_INSTANCE, filename_cert);
Network_Port_Operational_Certificate_File_Set(
instance, BSC_OPERATIONAL_CERTIFICATE_FILE_INSTANCE);
if (filename_key == NULL) {
fprintf(
stderr,
"BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE must be set\n");
return;
}
bacfile_create(BSC_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE);
bacfile_pathname_set(
BSC_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE, filename_key);
Network_Port_Certificate_Key_File_Set(
instance, BSC_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE);
if ((primary_hub_uri == NULL) && (failover_hub_uri == NULL) &&
(direct_binding == NULL) && (hub_binding == NULL)) {
fprintf(
stderr,
"At least must be set:\n"
"BACNET_SC_HUB_FUNCTION_BINDING for HUB or\n"
"BACNET_SC_PRIMARY_HUB_URI and BACNET_SC_FAILOVER_HUB_URI for node "
"or\n"
"BACNET_SC_DIRECT_CONNECT_BINDING for direct connect.\n");
return;
}
Network_Port_SC_Primary_Hub_URI_Set(instance, primary_hub_uri);
Network_Port_SC_Failover_Hub_URI_Set(instance, failover_hub_uri);
Network_Port_SC_Direct_Connect_Binding_Set(instance, direct_binding);
Network_Port_SC_Direct_Connect_Accept_Enable_Set(
instance, direct_binding != NULL);
c = direct_connect_initiate ? direct_connect_initiate[0] : '0';
if ((c != '0') && (c != 'n') && (c != 'N')) {
Network_Port_SC_Direct_Connect_Initiate_Enable_Set(instance, true);
} else {
Network_Port_SC_Direct_Connect_Initiate_Enable_Set(instance, false);
}
Network_Port_SC_Direct_Connect_Accept_URIs_Set(
instance, direct_connect_accept_urls);
/* HUB parameters */
Network_Port_SC_Hub_Function_Binding_Set(instance, hub_binding);
Network_Port_SC_Hub_Function_Enable_Set(instance, hub_binding != NULL);
/* last thing - clear pending changes - we don't want to set these
since they are already set */
Network_Port_Changes_Pending_Set(instance, false);
}
static bool dlenv_hub_connection_status_check(void)
{
uint32_t instance = Network_Port_Index_To_Instance(0);
BACNET_SC_HUB_CONNECTION_STATUS *status;
status = Network_Port_SC_Primary_Hub_Connection_Status(instance);
if (status && status->State == BACNET_SC_CONNECTION_STATE_CONNECTED) {
return true;
}
status = Network_Port_SC_Failover_Hub_Connection_Status(instance);
if (status && status->State == BACNET_SC_CONNECTION_STATE_CONNECTED) {
return true;
}
return false;
}
/**
* Datalink network port object settings for BACnet/SC
*/
void dlenv_network_port_init(void)
{
/* if a user has configured BACnet/SC port with primary hub URI, */
/* wait for a establishin of a connection to BACnet/SC hub at first */
/* to reduce possibility of packet losses. */
if (Network_Port_SC_Primary_Hub_URI_char(1)) {
while (!dlenv_hub_connection_status_check()) {
bsc_wait(1);
bsc_maintenance_timer(1);
}
}
}
#else
/**
* Datalink network port object settings
@@ -548,6 +729,24 @@ void dlenv_maintenance_timer(uint16_t elapsed_seconds)
* - BACNET_BIP6_PORT - UDP/IP port number (0..65534) used for BACnet/IPv6
* communications. Default is 47808 (0xBAC0).
* - BACNET_BIP6_BROADCAST - FF05::BAC0 or FF02::BAC0 or ...
* - BACDL_BSC: (BACnet Secure Connect)
* - BACNET_SC_PRIMARY_HUB_URI
* - BACNET_SC_FAILOVER_HUB_URI
* - BACNET_SC_ISSUER_1_CERTIFICATE_FILE
* - BACNET_SC_ISSUER_2_CERTIFICATE_FILE
* - BACNET_SC_OPERATIONAL_CERTIFICATE_FILE
* - BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE
* - BACNET_SC_DIRECT_CONNECT_BINDING - pair: interface name (optional) and
* TCP/IP port number (0..65534), like "50000" (port only) or
* "eth0:50000"(both)
* - BACNET_SC_HUB_FUNCTION_BINDING - pair: interface name (optional) and
* TCP/IP port number (0..65534), like "50000" (port only) or
* "eth0:50000"(both)
* - BACNET_SC_DIRECT_CONNECT_INITIATE - if true equal "1", "y", "Y",
* otherwise false
* - BACNET_SC_DIRECT_CONNECT_ACCEPT_URLS - list of direct connect accept URLs
* separated by a space character, e.g.
* "wss://192.0.0.1:40000 wss://192.0.0.2:6666"
*/
void dlenv_init(void)
{
@@ -574,6 +773,8 @@ void dlenv_init(void)
datalink_set("ethernet");
#elif defined(BACDL_ARCNET)
datalink_set("arcnet");
#elif defined(BACDL_BSC)
datalink_set("bsc");
#else
datalink_set("none");
#endif
@@ -665,6 +866,35 @@ void dlenv_init(void)
} else {
dlmstp_set_mac_address(127);
}
#elif defined(BACDL_BSC)
char *primary_hub_uri;
char *failover_hub_uri;
char *filename_ca_1_cert;
char *filename_ca_2_cert;
char *filename_cert;
char *filename_key;
char *direct_binding;
char *hub_binding;
char *direct_connect_initiate;
char *direct_connect_accept_urls;
primary_hub_uri = getenv("BACNET_SC_PRIMARY_HUB_URI");
failover_hub_uri = getenv("BACNET_SC_FAILOVER_HUB_URI");
filename_ca_1_cert = getenv("BACNET_SC_ISSUER_1_CERTIFICATE_FILE");
filename_ca_2_cert = getenv("BACNET_SC_ISSUER_2_CERTIFICATE_FILE");
filename_cert = getenv("BACNET_SC_OPERATIONAL_CERTIFICATE_FILE");
filename_key = getenv("BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE");
direct_binding = getenv("BACNET_SC_DIRECT_CONNECT_BINDING");
hub_binding = getenv("BACNET_SC_HUB_FUNCTION_BINDING");
direct_connect_initiate = getenv("BACNET_SC_DIRECT_CONNECT_INITIATE");
direct_connect_accept_urls = getenv("BACNET_SC_DIRECT_CONNECT_ACCEPT_URLS");
bacnet_secure_connect_network_port_init(
primary_hub_uri, failover_hub_uri, filename_ca_1_cert,
filename_ca_2_cert, filename_cert, filename_key, direct_binding,
hub_binding, direct_connect_initiate, direct_connect_accept_urls);
if (!bsc_cert_files_check()) {
exit(1);
}
#endif
pEnv = getenv("BACNET_APDU_TIMEOUT");
if (pEnv) {