1526 lines
51 KiB
C
1526 lines
51 KiB
C
/**
|
|
* @file
|
|
* @brief BACNet secure connect node 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/datalink/bsc/bsc-conf.h"
|
|
#include "bacnet/datalink/bsc/bsc-node.h"
|
|
#include "bacnet/datalink/bsc/bsc-event.h"
|
|
#include "bacnet/datalink/bsc/bsc-util.h"
|
|
#include "bacnet/datalink/bsc/bsc-hub-function.h"
|
|
#include "bacnet/datalink/bsc/bsc-hub-connector.h"
|
|
#include "bacnet/datalink/bsc/bsc-node-switch.h"
|
|
#include "bacnet/datalink/bsc/bsc-socket.h"
|
|
#include "bacnet/basic/object/sc_netport.h"
|
|
#include "bacnet/bacdef.h"
|
|
#include "bacnet/npdu.h"
|
|
#include "bacnet/bacenum.h"
|
|
|
|
#define DEBUG_BSC_NODE 0
|
|
|
|
#undef DEBUG_PRINTF
|
|
#if DEBUG_BSC_NODE == 1
|
|
#define DEBUG_PRINTF debug_printf
|
|
#else
|
|
#undef DEBUG_ENABLED
|
|
#define DEBUG_PRINTF debug_printf_disabled
|
|
#endif
|
|
|
|
typedef enum {
|
|
BSC_NODE_STATE_IDLE = 0,
|
|
BSC_NODE_STATE_STARTING = 1,
|
|
BSC_NODE_STATE_STARTED = 2,
|
|
BSC_NODE_STATE_RESTARTING = 3,
|
|
BSC_NODE_STATE_STOPPING = 4
|
|
} BSC_NODE_STATE;
|
|
|
|
struct BSC_Node {
|
|
bool used;
|
|
BSC_NODE_STATE state;
|
|
BSC_NODE_CONF *conf;
|
|
BSC_ADDRESS_RESOLUTION *resolution;
|
|
BSC_HUB_CONNECTOR_HANDLE hub_connector;
|
|
BSC_HUB_FUNCTION_HANDLE hub_function;
|
|
BSC_NODE_SWITCH_HANDLE node_switch;
|
|
BACNET_SC_FAILED_CONNECTION_REQUEST *failed;
|
|
BACNET_SC_DIRECT_CONNECTION_STATUS *direct_status;
|
|
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *hub_status;
|
|
};
|
|
|
|
#if defined(BSC_CONF_NODES_NUM) && (BSC_CONF_NODES_NUM < 1)
|
|
#error "BSC_CONF_NODES_NUM must be >= 1"
|
|
#endif
|
|
|
|
static struct BSC_Node bsc_node[BSC_CONF_NODES_NUM] = { 0 };
|
|
|
|
static BACNET_SC_FAILED_CONNECTION_REQUEST
|
|
bsc_failed_request[BSC_CONF_NODES_NUM]
|
|
[BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM];
|
|
static bool bsc_failed_request_initialized[BSC_CONF_NODES_NUM] = { 0 };
|
|
|
|
static BACNET_SC_DIRECT_CONNECTION_STATUS
|
|
bsc_direct_status[BSC_CONF_NODES_NUM]
|
|
[BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM];
|
|
static bool bsc_direct_status_initialized[BSC_CONF_NODES_NUM] = { 0 };
|
|
|
|
static BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS
|
|
bsc_hub_status[BSC_CONF_NODES_NUM]
|
|
[BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM];
|
|
static bool bsc_hub_status_initialized[BSC_CONF_NODES_NUM] = { 0 };
|
|
|
|
static BSC_ADDRESS_RESOLUTION
|
|
bsc_address_resolution[BSC_CONF_NODES_NUM]
|
|
[BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM];
|
|
|
|
static BSC_NODE_CONF bsc_conf[BSC_CONF_NODES_NUM];
|
|
|
|
static BSC_SC_RET bsc_node_start_state(BSC_NODE *node, BSC_NODE_STATE state);
|
|
|
|
/**
|
|
* @brief Initialize direct connection status
|
|
* @param s - pointer to the direct connection status
|
|
*/
|
|
static void bsc_node_init_direct_status(BACNET_SC_DIRECT_CONNECTION_STATUS *s)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM; j++) {
|
|
memset(&s[j], 0, sizeof(*s));
|
|
memset(&s[j].Connect_Timestamp, 0xFF, sizeof(s[j].Connect_Timestamp));
|
|
memset(
|
|
&s[j].Disconnect_Timestamp, 0xFF,
|
|
sizeof(s[j].Disconnect_Timestamp));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize hub connection status
|
|
* @param s - pointer to the hub connection status
|
|
*/
|
|
static void
|
|
bsc_node_init_hub_status(BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *s)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM; j++) {
|
|
memset(&s[j], 0, sizeof(*s));
|
|
memset(&s[j].Connect_Timestamp, 0xFF, sizeof(s[j].Connect_Timestamp));
|
|
memset(
|
|
&s[j].Disconnect_Timestamp, 0xFF,
|
|
sizeof(s[j].Disconnect_Timestamp));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Allocate a node
|
|
* @return pointer to the allocated node
|
|
*/
|
|
static BSC_NODE *bsc_alloc_node(void)
|
|
{
|
|
int i, j;
|
|
DEBUG_PRINTF("bsc_alloc_node() >>> \n");
|
|
|
|
for (i = 0; i < BSC_CONF_NODES_NUM; i++) {
|
|
if (bsc_node[i].used == false) {
|
|
memset(&bsc_node[i], 0, sizeof(bsc_node[i]));
|
|
bsc_node[i].used = true;
|
|
bsc_node[i].hub_status = (BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS
|
|
*)&bsc_hub_status[i][0];
|
|
bsc_node[i].direct_status =
|
|
(BACNET_SC_DIRECT_CONNECTION_STATUS *)&bsc_direct_status[i][0];
|
|
|
|
/* Start/stop cycles of a node must not make an influence to history
|
|
That's why hub and direct status arrays are initialized
|
|
only once */
|
|
|
|
if (!bsc_hub_status_initialized[i]) {
|
|
bsc_node_init_hub_status(bsc_node[i].hub_status);
|
|
}
|
|
|
|
if (!bsc_direct_status_initialized[i]) {
|
|
bsc_node_init_direct_status(bsc_node[i].direct_status);
|
|
}
|
|
|
|
bsc_node[i].conf = &bsc_conf[i];
|
|
bsc_node[i].resolution = &bsc_address_resolution[i][0];
|
|
bsc_node[i].failed = &bsc_failed_request[i][0];
|
|
memset(
|
|
bsc_node[i].resolution, 0,
|
|
sizeof(BSC_ADDRESS_RESOLUTION) *
|
|
BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM);
|
|
|
|
/* Start/stop cycles of a node must not make an influence to history
|
|
* about failed requests */
|
|
/* That's why bsc_failed_request[] array is initialized only once */
|
|
|
|
if (!bsc_failed_request_initialized[i]) {
|
|
for (j = 0; j < BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM;
|
|
j++) {
|
|
memset(
|
|
&bsc_failed_request[i][j], 0,
|
|
sizeof(bsc_failed_request[i][j]));
|
|
memset(
|
|
&bsc_failed_request[i][j].Timestamp, 0xff,
|
|
sizeof(bsc_failed_request[i][j].Timestamp));
|
|
}
|
|
bsc_failed_request_initialized[i] = true;
|
|
}
|
|
DEBUG_PRINTF(
|
|
"bsc_alloc_node() <<< i = %d, node = %p, conf = %p\n", i,
|
|
&bsc_node[i], bsc_node[i].conf);
|
|
return &bsc_node[i];
|
|
}
|
|
}
|
|
DEBUG_PRINTF("bsc_alloc_node() <<< ret = NULL\n");
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if the node is enabled or not
|
|
* @param conf - pointer to the node configuration
|
|
* @return true if the node is enabled, otherwise false
|
|
*/
|
|
static bool node_switch_enabled(BSC_NODE_CONF *conf)
|
|
{
|
|
if (conf->direct_connect_initiate_enable ||
|
|
conf->direct_connect_accept_enable) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the address resolution for the BACnet/SC node
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param vmac - pointer to the VMAC address
|
|
* @return pointer to the address resolution
|
|
*/
|
|
static BSC_ADDRESS_RESOLUTION *
|
|
node_get_address_resolution(BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *vmac)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM; i++) {
|
|
if (node->resolution[i].used &&
|
|
!memcmp(
|
|
&vmac->address[0], &node->resolution[i].vmac.address[0],
|
|
BVLC_SC_VMAC_SIZE)) {
|
|
return &node->resolution[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Free the address resolution
|
|
* @param r - pointer to the address resolution
|
|
*/
|
|
static void node_free_address_resolution(BSC_ADDRESS_RESOLUTION *r)
|
|
{
|
|
r->used = false;
|
|
r->urls_num = false;
|
|
}
|
|
|
|
/**
|
|
* @brief Allocate the address resolution
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param vmac - pointer to the VMAC address
|
|
* @return pointer to the address resolution
|
|
*/
|
|
static BSC_ADDRESS_RESOLUTION *
|
|
node_alloc_address_resolution(BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *vmac)
|
|
{
|
|
int i;
|
|
unsigned long max = 0;
|
|
int max_index = 0;
|
|
|
|
for (i = 0; i < BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM; i++) {
|
|
if (!node->resolution[i].used) {
|
|
node->resolution[i].used = true;
|
|
mstimer_set(
|
|
&node->resolution[i].fresh_timer,
|
|
node->conf->address_resolution_freshness_timeout_s * 1000);
|
|
memcpy(
|
|
&node->resolution[i].vmac.address[0], &vmac->address[0],
|
|
BVLC_SC_VMAC_SIZE);
|
|
return &node->resolution[i];
|
|
}
|
|
}
|
|
|
|
/* find and remove oldest resolution */
|
|
|
|
for (i = 0; i < BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM; i++) {
|
|
if (mstimer_elapsed(&node->resolution[i].fresh_timer) > max) {
|
|
max = mstimer_elapsed(&node->resolution[i].fresh_timer);
|
|
max_index = i;
|
|
}
|
|
}
|
|
|
|
node->resolution[max_index].urls_num = 0;
|
|
return &node->resolution[max_index];
|
|
}
|
|
|
|
/**
|
|
* @brief Free the BACnet/SC node
|
|
* @param node - pointer to the BACnet/SC node
|
|
*/
|
|
static void bsc_free_node(BSC_NODE *node)
|
|
{
|
|
DEBUG_PRINTF(
|
|
"bsc_free_node() >>> node = %p, state = %d\n", node, node->state);
|
|
node->used = false;
|
|
DEBUG_PRINTF("bsc_free_node() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Process a BACnet/SC node stop event
|
|
* @param node - pointer to the BACnet/SC node
|
|
*/
|
|
static void bsc_node_process_stop_event(BSC_NODE *node)
|
|
{
|
|
bool stopped = true;
|
|
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_stop_event() >>> node = %p, state = %d\n", node,
|
|
node->state);
|
|
|
|
if (node->conf->hub_function_enabled) {
|
|
if (node->hub_function &&
|
|
!bsc_hub_function_stopped(node->hub_function)) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_stop_event() hub_function %p is not "
|
|
"stopped\n",
|
|
node->hub_function);
|
|
stopped = false;
|
|
}
|
|
}
|
|
if (node->node_switch && node_switch_enabled(node->conf)) {
|
|
if (!bsc_node_switch_stopped(node->node_switch)) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_stop_event() node_switch %p is not stopped\n",
|
|
node->node_switch);
|
|
stopped = false;
|
|
}
|
|
}
|
|
if (node->hub_connector &&
|
|
!bsc_hub_connector_stopped(node->hub_connector)) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_stop_event() hub_connector %p is not stopped\n",
|
|
node->hub_connector);
|
|
stopped = false;
|
|
}
|
|
|
|
DEBUG_PRINTF("bsc_node_process_stop_event() stopped = %d\n", stopped);
|
|
|
|
if (node->state == BSC_NODE_STATE_STOPPING) {
|
|
if (stopped) {
|
|
node->state = BSC_NODE_STATE_IDLE;
|
|
DEBUG_PRINTF("bsc_node_process_stop_event() emit stop event\n");
|
|
node->conf->event_func(node, BSC_NODE_EVENT_STOPPED, NULL, NULL, 0);
|
|
}
|
|
} else if (node->state == BSC_NODE_STATE_RESTARTING) {
|
|
if (stopped) {
|
|
DEBUG_PRINTF("bsc_node_process_stop_event() emit restart event\n");
|
|
(void)bsc_node_start_state(node, BSC_NODE_STATE_RESTARTING);
|
|
}
|
|
}
|
|
DEBUG_PRINTF("bsc_node_process_stop_event() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Process a BACnet/SC node start event
|
|
* @param node - pointer to the BACnet/SC node
|
|
*/
|
|
static void bsc_node_process_start_event(BSC_NODE *node)
|
|
{
|
|
bool started = true;
|
|
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_start_event() >>> node = %p, state = %d\n", node,
|
|
node->state);
|
|
if (node->hub_function && node->conf->hub_function_enabled) {
|
|
if (!bsc_hub_function_started(node->hub_function)) {
|
|
started = false;
|
|
}
|
|
}
|
|
if (node->node_switch && node_switch_enabled(node->conf)) {
|
|
if (!bsc_node_switch_started(node->node_switch)) {
|
|
started = false;
|
|
}
|
|
}
|
|
DEBUG_PRINTF("bsc_node_process_start_event() started = %d\n", started);
|
|
if (started) {
|
|
if (node->state == BSC_NODE_STATE_STARTING) {
|
|
node->state = BSC_NODE_STATE_STARTED;
|
|
node->conf->event_func(node, BSC_NODE_EVENT_STARTED, NULL, NULL, 0);
|
|
} else if (node->state == BSC_NODE_STATE_RESTARTING) {
|
|
node->state = BSC_NODE_STATE_STARTED;
|
|
node->conf->event_func(
|
|
node, BSC_NODE_EVENT_RESTARTED, NULL, NULL, 0);
|
|
}
|
|
}
|
|
DEBUG_PRINTF("bsc_node_process_start_event() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Process a BACnet/SC node restart event
|
|
* @param node - pointer to the BACnet/SC node
|
|
*/
|
|
static void bsc_node_restart(BSC_NODE *node)
|
|
{
|
|
DEBUG_PRINTF(
|
|
"bsc_node_restart() >>> node = %p hub_function %p "
|
|
"hub_connector %p node_switch %p\n",
|
|
node, node->hub_function, node->hub_connector, node->node_switch);
|
|
node->state = BSC_NODE_STATE_RESTARTING;
|
|
if (node->conf->primaryURL) {
|
|
bsc_hub_connector_stop(node->hub_connector);
|
|
}
|
|
if (node->hub_function && node->conf->hub_function_enabled) {
|
|
bsc_hub_function_stop(node->hub_function);
|
|
}
|
|
if (node_switch_enabled(node->conf)) {
|
|
bsc_node_switch_stop(node->node_switch);
|
|
}
|
|
DEBUG_PRINTF("bsc_node_restart() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Parse URLs for the BACnet/SC node
|
|
* @param r - pointer to the address resolution
|
|
* @param decoded_pdu - pointer to the decoded PDU
|
|
*/
|
|
static void bsc_node_parse_urls(
|
|
BSC_ADDRESS_RESOLUTION *r, BVLC_SC_DECODED_MESSAGE *decoded_pdu)
|
|
{
|
|
int i, j;
|
|
int start;
|
|
uint8_t *url = NULL;
|
|
|
|
url = decoded_pdu->payload.address_resolution_ack.utf8_websocket_uri_string;
|
|
r->urls_num = 0;
|
|
for (i = 0, j = 0, start = 0;
|
|
i < decoded_pdu->payload.address_resolution_ack
|
|
.utf8_websocket_uri_string_len;
|
|
i++) {
|
|
if (url[i] == 0x20) {
|
|
if (i > BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK ||
|
|
(i - start) == 0) {
|
|
start = i + 1;
|
|
continue;
|
|
} else {
|
|
memcpy(&r->utf8_urls[j][0], &url[start], i - start);
|
|
r->utf8_urls[j][i] = 0;
|
|
j++;
|
|
start = i + 1;
|
|
}
|
|
}
|
|
}
|
|
if (i - start > 0 &&
|
|
i <= BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK) {
|
|
memcpy(&r->utf8_urls[j][0], &url[start], i - start);
|
|
r->utf8_urls[j][i] = 0;
|
|
j++;
|
|
}
|
|
r->urls_num = j;
|
|
}
|
|
|
|
/**
|
|
* @brief Process a BACnet/SC node received event
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param pdu - pointer to the PDU
|
|
* @param pdu_len - PDU length
|
|
* @param decoded_pdu - pointer to the decoded PDU
|
|
*/
|
|
static void bsc_node_process_received(
|
|
BSC_NODE *node,
|
|
uint8_t *pdu,
|
|
size_t pdu_len,
|
|
BVLC_SC_DECODED_MESSAGE *decoded_pdu)
|
|
{
|
|
int i;
|
|
static uint8_t buf[BVLC_SC_NPDU_SIZE_CONF];
|
|
size_t bufsize;
|
|
BSC_SC_RET ret;
|
|
uint16_t error_class;
|
|
uint16_t error_code;
|
|
BSC_ADDRESS_RESOLUTION *r;
|
|
const char *error_must_understand_option_string =
|
|
"'must understand' option not understood ";
|
|
const char *error_direct_connections_unsupported_string =
|
|
"direct connections are not supported";
|
|
|
|
(void)ret;
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_received() >>> node = %p, pdu = %p, pdu_len "
|
|
"= %d, decoded_pdu = %p\n",
|
|
node, pdu, pdu_len, decoded_pdu);
|
|
|
|
for (i = 0; i < decoded_pdu->hdr.dest_options_num; i++) {
|
|
if (decoded_pdu->dest_options[i].must_understand) {
|
|
DEBUG_PRINTF("bsc_node_process_received() pdu with "
|
|
"'must-understand' is dropped\n");
|
|
if (bvlc_sc_need_send_bvlc_result(decoded_pdu)) {
|
|
error_code = ERROR_CODE_HEADER_NOT_UNDERSTOOD;
|
|
error_class = ERROR_CLASS_COMMUNICATION;
|
|
bufsize = bvlc_sc_encode_result(
|
|
buf, sizeof(buf), decoded_pdu->hdr.message_id, NULL,
|
|
decoded_pdu->hdr.origin, decoded_pdu->hdr.bvlc_function, 1,
|
|
&decoded_pdu->dest_options[i].packed_header_marker,
|
|
&error_class, &error_code,
|
|
error_must_understand_option_string);
|
|
if (bufsize) {
|
|
ret = bsc_node_send(node, buf, bufsize);
|
|
#if DEBUG_ENABLED == 1
|
|
if (ret != BSC_SC_SUCCESS) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_received() warning "
|
|
"bvlc-result pdu is not sent, error %d\n",
|
|
ret);
|
|
}
|
|
#endif
|
|
}
|
|
DEBUG_PRINTF("bsc_node_process_received() <<<\n");
|
|
return;
|
|
} else {
|
|
DEBUG_PRINTF("bsc_node_process_received() <<<\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (decoded_pdu->hdr.bvlc_function) {
|
|
case BVLC_SC_RESULT: {
|
|
if (decoded_pdu->payload.result.bvlc_function ==
|
|
BVLC_SC_ADDRESS_RESOLUTION) {
|
|
DEBUG_PRINTF(
|
|
"received a NAK for address resolution from %s\n",
|
|
bsc_vmac_to_string(decoded_pdu->hdr.origin));
|
|
r = node_get_address_resolution(node, decoded_pdu->hdr.origin);
|
|
if (r) {
|
|
r->urls_num = 0;
|
|
mstimer_restart(&r->fresh_timer);
|
|
} else {
|
|
r = node_alloc_address_resolution(
|
|
node, decoded_pdu->hdr.origin);
|
|
if (r) {
|
|
r->urls_num = 0;
|
|
mstimer_restart(&r->fresh_timer);
|
|
} else {
|
|
DEBUG_PRINTF(
|
|
"can't allocate address resolution for "
|
|
"node with address %s\n",
|
|
bsc_vmac_to_string(decoded_pdu->hdr.origin));
|
|
}
|
|
}
|
|
}
|
|
DEBUG_PRINTF(
|
|
"node %p get pdu with bvlc "
|
|
"function %d error_class %d error_code %d from node %s\n",
|
|
node, decoded_pdu->payload.result.bvlc_function,
|
|
decoded_pdu->payload.result.error_class,
|
|
decoded_pdu->payload.result.error_code,
|
|
bsc_vmac_to_string(decoded_pdu->hdr.origin));
|
|
node->conf->event_func(
|
|
node, BSC_NODE_EVENT_RECEIVED_RESULT, NULL, pdu, pdu_len);
|
|
break;
|
|
}
|
|
case BVLC_SC_ADVERTISIMENT: {
|
|
node->conf->event_func(
|
|
node, BSC_NODE_EVENT_RECEIVED_ADVERTISIMENT, NULL, pdu,
|
|
pdu_len);
|
|
break;
|
|
}
|
|
case BVLC_SC_ADVERTISIMENT_SOLICITATION: {
|
|
bufsize = bvlc_sc_encode_advertisiment(
|
|
buf, sizeof(buf), bsc_get_next_message_id(), NULL,
|
|
decoded_pdu->hdr.origin,
|
|
bsc_hub_connector_state(node->hub_connector),
|
|
node_switch_enabled(node->conf)
|
|
? BVLC_SC_DIRECT_CONNECTION_ACCEPT_SUPPORTED
|
|
: BVLC_SC_DIRECT_CONNECTION_ACCEPT_UNSUPPORTED,
|
|
node->conf->max_local_bvlc_len, node->conf->max_local_npdu_len);
|
|
if (bufsize) {
|
|
ret = bsc_node_send(node, buf, bufsize);
|
|
#if DEBUG_ENABLED == 1
|
|
if (ret != BSC_SC_SUCCESS) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_received() warning "
|
|
"advertisement pdu is not sent to node %s, error %d\n",
|
|
ret, bsc_vmac_to_string(decoded_pdu->hdr.origin));
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
case BVLC_SC_ADDRESS_RESOLUTION: {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_received() got BVLC_SC_ADDRESS_RESOLUTION\n");
|
|
if (node_switch_enabled(node->conf)) {
|
|
bufsize = bvlc_sc_encode_address_resolution_ack(
|
|
buf, sizeof(buf), decoded_pdu->hdr.message_id, NULL,
|
|
decoded_pdu->hdr.origin,
|
|
(uint8_t *)node->conf->direct_connection_accept_uris,
|
|
node->conf->direct_connection_accept_uris_len);
|
|
if (bufsize) {
|
|
ret = bsc_node_send(node, buf, bufsize);
|
|
#if DEBUG_ENABLED == 1
|
|
if (ret != BSC_SC_SUCCESS) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_received() warning "
|
|
"address resolution ack is not sent, error %d\n",
|
|
ret);
|
|
}
|
|
#endif
|
|
}
|
|
} else {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_received() node switch is "
|
|
"disabled, send error to node %s\n",
|
|
bsc_vmac_to_string(decoded_pdu->hdr.origin));
|
|
error_code = ERROR_CODE_OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED;
|
|
error_class = ERROR_CLASS_COMMUNICATION;
|
|
bufsize = bvlc_sc_encode_result(
|
|
buf, sizeof(buf), decoded_pdu->hdr.message_id, NULL,
|
|
decoded_pdu->hdr.origin, decoded_pdu->hdr.bvlc_function, 1,
|
|
NULL, &error_class, &error_code,
|
|
error_direct_connections_unsupported_string);
|
|
if (bufsize) {
|
|
ret = bsc_node_send(node, buf, bufsize);
|
|
#if DEBUG_ENABLED == 1
|
|
if (ret != BSC_SC_SUCCESS) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_process_received() warning "
|
|
"bvlc-result pdu is not sent, error %d\n",
|
|
ret);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case BVLC_SC_ADDRESS_RESOLUTION_ACK: {
|
|
DEBUG_PRINTF("bsc_node_process_received() got "
|
|
"BVLC_SC_ADDRESS_RESOLUTION_ACK\n");
|
|
r = node_get_address_resolution(node, decoded_pdu->hdr.origin);
|
|
if (!r) {
|
|
r = node_alloc_address_resolution(
|
|
node, decoded_pdu->hdr.origin);
|
|
#if DEBUG_ENABLED == 1
|
|
if (!r) {
|
|
DEBUG_PRINTF(
|
|
"can't allocate address resolution for node "
|
|
"with address %s\n",
|
|
bsc_vmac_to_string(decoded_pdu->hdr.origin));
|
|
}
|
|
#endif
|
|
}
|
|
if (r) {
|
|
bsc_node_parse_urls(r, decoded_pdu);
|
|
mstimer_restart(&r->fresh_timer);
|
|
bsc_node_switch_process_address_resolution(
|
|
node->node_switch, r);
|
|
}
|
|
break;
|
|
}
|
|
case BVLC_SC_ENCAPSULATED_NPDU: {
|
|
node->conf->event_func(
|
|
node, BSC_NODE_EVENT_RECEIVED_NPDU, NULL, pdu, pdu_len);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
DEBUG_PRINTF("bsc_node_process_received() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief handle a BACnet/SC node connection event
|
|
* @param ev - connection event
|
|
* @param h - connection handle
|
|
* @param user_arg - user argument
|
|
* @param pdu - pointer to the PDU
|
|
* @param pdu_len - PDU length
|
|
* @param decoded_pdu - pointer to the decoded PDU
|
|
*/
|
|
static void bsc_hub_connector_event(
|
|
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)
|
|
{
|
|
BSC_NODE *node = (BSC_NODE *)user_arg;
|
|
|
|
(void)h;
|
|
bws_dispatch_lock();
|
|
DEBUG_PRINTF(
|
|
"bsc_hub_connector_event() >>> ev = %d, h = %p, node = %p\n", ev, h,
|
|
user_arg);
|
|
if (ev == BSC_HUBC_EVENT_STOPPED) {
|
|
node->hub_connector = NULL;
|
|
bsc_node_process_stop_event(node);
|
|
} else if (ev == BSC_HUBC_EVENT_ERROR_DUPLICATED_VMAC) {
|
|
if (node->state != BSC_NODE_STATE_STOPPING &&
|
|
node->state != BSC_NODE_STATE_RESTARTING) {
|
|
bsc_node_restart(node);
|
|
}
|
|
} else if (ev == BSC_HUBC_EVENT_RECEIVED) {
|
|
bsc_node_process_received(node, pdu, pdu_len, decoded_pdu);
|
|
}
|
|
DEBUG_PRINTF("bsc_hub_connector_event() <<<\n");
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
/**
|
|
* @brief handle a BACnet/SC hub function event
|
|
* @param ev - hub function event
|
|
* @param h - hub function handle
|
|
* @param user_arg - user argument
|
|
*/
|
|
static void bsc_hub_function_event(
|
|
BSC_HUB_FUNCTION_EVENT ev, BSC_HUB_FUNCTION_HANDLE h, void *user_arg)
|
|
{
|
|
BSC_NODE *node = (BSC_NODE *)user_arg;
|
|
|
|
(void)h;
|
|
bws_dispatch_lock();
|
|
DEBUG_PRINTF(
|
|
"bsc_hub_function_event() >>> ev = %d, h = %p, node = %p\n", ev, h,
|
|
user_arg);
|
|
if (ev == BSC_HUBF_EVENT_STARTED) {
|
|
bsc_node_process_start_event(node);
|
|
} else if (ev == BSC_HUBF_EVENT_STOPPED) {
|
|
node->hub_function = NULL;
|
|
bsc_node_process_stop_event(node);
|
|
} else if (ev == BSC_HUBF_EVENT_ERROR_DUPLICATED_VMAC) {
|
|
if (node->state != BSC_NODE_STATE_STOPPING &&
|
|
node->state != BSC_NODE_STATE_RESTARTING &&
|
|
node->state != BSC_NODE_STATE_IDLE) {
|
|
bsc_node_restart(node);
|
|
}
|
|
}
|
|
DEBUG_PRINTF("bsc_hub_function_event() <<<\n");
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
/**
|
|
* @brief handle a BACnet/SC node switch event
|
|
* @param ev - node switch event
|
|
* @param h - node switch handle
|
|
* @param user_arg - user argument
|
|
* @param dest - pointer to the destination VMAC address
|
|
* @param pdu - pointer to the PDU
|
|
* @param pdu_len - PDU length
|
|
* @param decoded_pdu - pointer to the decoded PDU
|
|
*/
|
|
static void bsc_node_switch_event(
|
|
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)
|
|
{
|
|
BSC_NODE *node = (BSC_NODE *)user_arg;
|
|
|
|
(void)h;
|
|
bws_dispatch_lock();
|
|
DEBUG_PRINTF(
|
|
"bsc_node_switch_event() >>> ev = %d, h = %p, node = %p\n", ev, h,
|
|
user_arg);
|
|
if (ev == BSC_NODE_SWITCH_EVENT_STARTED) {
|
|
bsc_node_process_start_event(node);
|
|
} else if (ev == BSC_NODE_SWITCH_EVENT_STOPPED) {
|
|
node->node_switch = NULL;
|
|
bsc_node_process_stop_event(node);
|
|
} else if (ev == BSC_NODE_SWITCH_EVENT_DUPLICATED_VMAC) {
|
|
if (node->state != BSC_NODE_STATE_STOPPING &&
|
|
node->state != BSC_NODE_STATE_RESTARTING &&
|
|
node->state != BSC_NODE_STATE_IDLE) {
|
|
bsc_node_restart(node);
|
|
}
|
|
} else if (ev == BSC_NODE_SWITCH_EVENT_RECEIVED) {
|
|
bsc_node_process_received(node, pdu, pdu_len, decoded_pdu);
|
|
} else if (ev == BSC_NODE_SWITCH_EVENT_CONNECTED) {
|
|
node->conf->event_func(
|
|
node, BSC_NODE_EVENT_DIRECT_CONNECTED, dest, NULL, 0);
|
|
} else if (ev == BSC_NODE_SWITCH_EVENT_DISCONNECTED) {
|
|
node->conf->event_func(
|
|
node, BSC_NODE_EVENT_DIRECT_DISCONNECTED, dest, NULL, 0);
|
|
}
|
|
DEBUG_PRINTF("bsc_node_switch_event() <<<\n");
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize the BACnet/SC node
|
|
* @param conf - pointer to the node configuration
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET bsc_node_init(BSC_NODE_CONF *conf, BSC_NODE **node)
|
|
{
|
|
DEBUG_PRINTF("bsc_node_init() >>> conf = %p, node = %p\n", conf, node);
|
|
|
|
if (!conf || !node) {
|
|
DEBUG_PRINTF("bsc_node_init() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
|
|
if (!conf->ca_cert_chain || !conf->ca_cert_chain_size ||
|
|
!conf->cert_chain || !conf->cert_chain_size || !conf->key ||
|
|
!conf->key_size || !conf->local_uuid || conf->connect_timeout_s <= 0 ||
|
|
conf->heartbeat_timeout_s <= 0 || conf->disconnect_timeout_s <= 0 ||
|
|
conf->reconnnect_timeout_s <= 0 ||
|
|
conf->address_resolution_timeout_s <= 0 ||
|
|
conf->address_resolution_freshness_timeout_s <= 0 ||
|
|
!conf->event_func) {
|
|
DEBUG_PRINTF("bsc_node_init() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
bws_dispatch_lock();
|
|
*node = bsc_alloc_node();
|
|
|
|
if (!(*node)) {
|
|
DEBUG_PRINTF("bsc_node_init() <<< ret = BSC_SC_NO_RESOURCE\n");
|
|
bws_dispatch_unlock();
|
|
return BSC_SC_NO_RESOURCES;
|
|
}
|
|
|
|
memcpy((*node)->conf, conf, sizeof(*conf));
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_init() <<< ret = BSC_SC_SUCCESS\n");
|
|
return BSC_SC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief Deinitialize the BACnet/SC node
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET bsc_node_deinit(BSC_NODE *node)
|
|
{
|
|
DEBUG_PRINTF("bsc_node_deinit() >>> node = %p\n", node);
|
|
bws_dispatch_lock();
|
|
if (node->state != BSC_NODE_STATE_IDLE) {
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_deinit() <<< ret = BSC_SC_INVALID_OPERATION\n");
|
|
return BSC_SC_INVALID_OPERATION;
|
|
}
|
|
bsc_free_node(node);
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_deinit() <<< ret = BSC_SC_SUCCESS\n");
|
|
return BSC_SC_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief Handle the BACnet/SC node start state
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param state - node state
|
|
* @return BACnet/SC status
|
|
*/
|
|
static BSC_SC_RET bsc_node_start_state(BSC_NODE *node, BSC_NODE_STATE state)
|
|
{
|
|
BSC_SC_RET ret = BSC_SC_BAD_PARAM;
|
|
bws_dispatch_lock();
|
|
DEBUG_PRINTF(
|
|
"bsc_node_start_state() >>> node = %p state = %d\n", node, state);
|
|
|
|
node->state = state;
|
|
node->hub_connector = NULL;
|
|
node->hub_function = NULL;
|
|
node->node_switch = NULL;
|
|
|
|
if (node->state != BSC_NODE_STATE_RESTARTING) {
|
|
memset(
|
|
node->resolution, 0,
|
|
sizeof(BSC_ADDRESS_RESOLUTION) *
|
|
BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM);
|
|
} else {
|
|
bsc_generate_random_vmac(&node->conf->local_vmac);
|
|
DEBUG_PRINTF(
|
|
"bsc_node_start_state() generated random vmac %s for node %p\n",
|
|
bsc_vmac_to_string(&node->conf->local_vmac), node);
|
|
}
|
|
|
|
if (node->conf->primaryURL) {
|
|
ret = bsc_hub_connector_start(
|
|
node->conf->ca_cert_chain, node->conf->ca_cert_chain_size,
|
|
node->conf->cert_chain, node->conf->cert_chain_size,
|
|
node->conf->key, node->conf->key_size, node->conf->local_uuid,
|
|
&node->conf->local_vmac, node->conf->max_local_bvlc_len,
|
|
node->conf->max_local_npdu_len, node->conf->connect_timeout_s,
|
|
node->conf->heartbeat_timeout_s, node->conf->disconnect_timeout_s,
|
|
node->conf->primaryURL, node->conf->failoverURL,
|
|
node->conf->reconnnect_timeout_s, bsc_hub_connector_event, node,
|
|
&node->hub_connector);
|
|
|
|
if (ret != BSC_SC_SUCCESS) {
|
|
node->state = BSC_NODE_STATE_IDLE;
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_start_state() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (node->conf->hub_function_enabled) {
|
|
ret = bsc_hub_function_start(
|
|
node->conf->ca_cert_chain, node->conf->ca_cert_chain_size,
|
|
node->conf->cert_chain, node->conf->cert_chain_size,
|
|
node->conf->key, node->conf->key_size, node->conf->hub_server_port,
|
|
node->conf->hub_iface, node->conf->local_uuid,
|
|
&node->conf->local_vmac, node->conf->max_local_bvlc_len,
|
|
node->conf->max_local_npdu_len, node->conf->connect_timeout_s,
|
|
node->conf->heartbeat_timeout_s, node->conf->disconnect_timeout_s,
|
|
bsc_hub_function_event, node, &node->hub_function);
|
|
if (ret != BSC_SC_SUCCESS) {
|
|
node->state = BSC_NODE_STATE_IDLE;
|
|
bsc_hub_connector_stop(node->hub_connector);
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_start_state() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (node_switch_enabled(node->conf)) {
|
|
ret = bsc_node_switch_start(
|
|
node->conf->ca_cert_chain, node->conf->ca_cert_chain_size,
|
|
node->conf->cert_chain, node->conf->cert_chain_size,
|
|
node->conf->key, node->conf->key_size,
|
|
node->conf->direct_server_port, node->conf->direct_iface,
|
|
node->conf->local_uuid, &node->conf->local_vmac,
|
|
node->conf->max_local_bvlc_len, node->conf->max_local_npdu_len,
|
|
node->conf->connect_timeout_s, node->conf->heartbeat_timeout_s,
|
|
node->conf->disconnect_timeout_s, node->conf->reconnnect_timeout_s,
|
|
node->conf->address_resolution_timeout_s,
|
|
node->conf->direct_connect_accept_enable,
|
|
node->conf->direct_connect_initiate_enable, bsc_node_switch_event,
|
|
node, &node->node_switch);
|
|
if (ret != BSC_SC_SUCCESS) {
|
|
node->state = BSC_NODE_STATE_IDLE;
|
|
bsc_hub_connector_stop(node->hub_connector);
|
|
bsc_hub_function_stop(node->hub_function);
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_start_state() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
if (!node->conf->hub_function_enabled && !node_switch_enabled(node->conf)) {
|
|
if (!node->conf->primaryURL) {
|
|
node->state = BSC_NODE_STATE_IDLE;
|
|
} else {
|
|
node->state = BSC_NODE_STATE_STARTED;
|
|
node->conf->event_func(node, BSC_NODE_EVENT_STARTED, NULL, NULL, 0);
|
|
}
|
|
}
|
|
DEBUG_PRINTF(
|
|
"bsc_node_start_state() hub_function %p hub_connector %p node_switch "
|
|
"%p\n",
|
|
node->hub_function, node->hub_connector, node->node_switch);
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_start_state() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Start the BACnet/SC node
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET bsc_node_start(BSC_NODE *node)
|
|
{
|
|
BSC_SC_RET ret;
|
|
|
|
DEBUG_PRINTF("bsc_node_start() >>> node = %p\n", node);
|
|
|
|
if (!node) {
|
|
DEBUG_PRINTF("bsc_node_start() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
|
|
bws_dispatch_lock();
|
|
|
|
if (node->state != BSC_NODE_STATE_IDLE) {
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_start() <<< ret = BSC_SC_INVALID_OPERATION\n");
|
|
return BSC_SC_INVALID_OPERATION;
|
|
}
|
|
ret = bsc_node_start_state(node, BSC_NODE_STATE_STARTING);
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_start() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Stop the BACnet/SC node
|
|
* @param node - pointer to the BACnet/SC node
|
|
*/
|
|
void bsc_node_stop(BSC_NODE *node)
|
|
{
|
|
DEBUG_PRINTF("bsc_node_stop() >>> node = %p\n", node);
|
|
|
|
if (node) {
|
|
bws_dispatch_lock();
|
|
|
|
if (node->state != BSC_NODE_STATE_IDLE &&
|
|
node->state != BSC_NODE_STATE_STOPPING) {
|
|
node->state = BSC_NODE_STATE_STOPPING;
|
|
bsc_hub_connector_stop(node->hub_connector);
|
|
if (node->conf->hub_function_enabled) {
|
|
bsc_hub_function_stop(node->hub_function);
|
|
}
|
|
if (node_switch_enabled(node->conf)) {
|
|
bsc_node_switch_stop(node->node_switch);
|
|
}
|
|
}
|
|
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
DEBUG_PRINTF("bsc_node_stop() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Send a PDU to the BACnet/SC connected node
|
|
* @param p_node - pointer to the BACnet/SC node
|
|
* @param pdu - pointer to the PDU
|
|
* @param pdu_len - PDU length
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET
|
|
bsc_node_hub_connector_send(void *p_node, uint8_t *pdu, size_t pdu_len)
|
|
{
|
|
BSC_NODE *node = (BSC_NODE *)p_node;
|
|
BSC_SC_RET ret;
|
|
|
|
DEBUG_PRINTF(
|
|
"bsc_node_hub_connector_send() >>> p_node = %p, pdu = %p, "
|
|
"pdu_len = %d\n",
|
|
p_node, pdu, pdu_len);
|
|
|
|
if (!node) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_hub_connector_send() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
|
|
bws_dispatch_lock();
|
|
|
|
if (node->state != BSC_NODE_STATE_STARTED) {
|
|
DEBUG_PRINTF("bsc_node_hub_connector_send() <<< ret = "
|
|
"BSC_SC_INVALID_OPERATION\n");
|
|
bws_dispatch_unlock();
|
|
return BSC_SC_INVALID_OPERATION;
|
|
}
|
|
|
|
ret = bsc_hub_connector_send(node->hub_connector, pdu, pdu_len);
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_hub_connector_send() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Send a PDU to the BACnet/SC connected node
|
|
* @param p_node - pointer to the BACnet/SC node
|
|
* @param pdu - pointer to the PDU
|
|
* @param pdu_len - PDU length
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET bsc_node_send(BSC_NODE *p_node, uint8_t *pdu, size_t pdu_len)
|
|
{
|
|
BSC_NODE *node = (BSC_NODE *)p_node;
|
|
BSC_SC_RET ret;
|
|
|
|
DEBUG_PRINTF(
|
|
"bsc_node_send() >>> p_node = %p(%s), pdu = %p, "
|
|
"pdu_len = %d\n",
|
|
p_node, p_node ? bsc_vmac_to_string(&p_node->conf->local_vmac) : NULL,
|
|
pdu, pdu_len);
|
|
|
|
if (!node) {
|
|
DEBUG_PRINTF("bsc_node_send() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
|
|
bws_dispatch_lock();
|
|
|
|
if (node->state != BSC_NODE_STATE_STARTED) {
|
|
DEBUG_PRINTF("bsc_node_send() <<< ret = "
|
|
"BSC_SC_INVALID_OPERATION\n");
|
|
bws_dispatch_unlock();
|
|
return BSC_SC_INVALID_OPERATION;
|
|
}
|
|
|
|
if (node_switch_enabled(node->conf)) {
|
|
ret = bsc_node_switch_send(node->node_switch, pdu, pdu_len);
|
|
} else {
|
|
ret = bsc_hub_connector_send(node->hub_connector, pdu, pdu_len);
|
|
}
|
|
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_send() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the BACnet/SC node address resolution
|
|
* @param p_node - pointer to the BACnet/SC node
|
|
* @param vmac - pointer to the VMAC address
|
|
* @return pointer to the address resolution, or NULL if not found
|
|
*/
|
|
BSC_ADDRESS_RESOLUTION *
|
|
bsc_node_get_address_resolution(void *p_node, BACNET_SC_VMAC_ADDRESS *vmac)
|
|
{
|
|
int i;
|
|
BSC_NODE *node = (BSC_NODE *)p_node;
|
|
bws_dispatch_lock();
|
|
|
|
if (!node || node->state != BSC_NODE_STATE_STARTED || !vmac) {
|
|
bws_dispatch_unlock();
|
|
return NULL;
|
|
}
|
|
for (i = 0; i < BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM; i++) {
|
|
if (node->resolution[i].used &&
|
|
!memcmp(
|
|
&vmac->address[0], &node->resolution[i].vmac.address[0],
|
|
BVLC_SC_VMAC_SIZE)) {
|
|
if (!mstimer_expired(&node->resolution[i].fresh_timer)) {
|
|
bws_dispatch_unlock();
|
|
return &node->resolution[i];
|
|
} else {
|
|
node_free_address_resolution(&node->resolution[i]);
|
|
bws_dispatch_unlock();
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Send an address resolution to the BACnet/SC node
|
|
* @param p_node - pointer to the BACnet/SC node
|
|
* @param dest - pointer to the destination VMAC address
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET
|
|
bsc_node_send_address_resolution(void *p_node, BACNET_SC_VMAC_ADDRESS *dest)
|
|
{
|
|
BSC_NODE *node = (BSC_NODE *)p_node;
|
|
uint8_t pdu[32];
|
|
size_t pdu_len;
|
|
BSC_SC_RET ret;
|
|
DEBUG_PRINTF(
|
|
"bsc_node_send_address_resolution() >>> node = %p, dest = %p\n", node,
|
|
dest);
|
|
pdu_len = bvlc_sc_encode_address_resolution(
|
|
pdu, sizeof(pdu), bsc_get_next_message_id(), NULL, dest);
|
|
ret = bsc_node_send(node, pdu, pdu_len);
|
|
DEBUG_PRINTF("bsc_node_send_address_resolution() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Connect to the BACnet/SC node directly
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param dest - pointer to the destination VMAC address
|
|
* @param urls - pointer to the URLs
|
|
* @param urls_cnt - URLs count
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET bsc_node_connect_direct(
|
|
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt)
|
|
{
|
|
BSC_SC_RET ret = BSC_SC_INVALID_OPERATION;
|
|
DEBUG_PRINTF(
|
|
"bsc_node_connect_direct() >>> node = %p, dest = %p, urls = "
|
|
"%p, urls_cnt = %d\n",
|
|
node, dest, urls, urls_cnt);
|
|
bws_dispatch_lock();
|
|
if (node->state == BSC_NODE_STATE_STARTED &&
|
|
node->conf->direct_connect_initiate_enable) {
|
|
ret = bsc_node_switch_connect(node->node_switch, dest, urls, urls_cnt);
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_connect_direct() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Disconnect from the BACnet/SC node direct connection
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param dest - pointer to the destination VMAC address
|
|
*/
|
|
void bsc_node_disconnect_direct(BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest)
|
|
{
|
|
DEBUG_PRINTF(
|
|
"bsc_node_disconnect_direct() >>> node = %p, dest = %p\n", node, dest);
|
|
bws_dispatch_lock();
|
|
if (node->state == BSC_NODE_STATE_STARTED &&
|
|
node->conf->direct_connect_initiate_enable) {
|
|
bsc_node_switch_disconnect(node->node_switch, dest);
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_disconnect_direct() <<< \n");
|
|
}
|
|
|
|
/**
|
|
* @brief Determine if the direct connection to the BACnet/SC node is
|
|
* established
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param dest - pointer to the destination VMAC address
|
|
* @param urls - pointer to the URLs
|
|
* @param urls_cnt - URLs count
|
|
* @return true if the direct connection is established, false otherwise
|
|
*/
|
|
bool bsc_node_direct_connection_established(
|
|
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt)
|
|
{
|
|
bool ret = false;
|
|
bws_dispatch_lock();
|
|
if (node->state == BSC_NODE_STATE_STARTED &&
|
|
(node->conf->direct_connect_initiate_enable ||
|
|
node->conf->direct_connect_accept_enable)) {
|
|
ret =
|
|
bsc_node_switch_connected(node->node_switch, dest, urls, urls_cnt);
|
|
}
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the BACnet/SC node state
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @return BACnet/SC hub connector state
|
|
*/
|
|
BACNET_SC_HUB_CONNECTOR_STATE
|
|
bsc_node_hub_connector_state(BSC_NODE *node)
|
|
{
|
|
BACNET_SC_HUB_CONNECTOR_STATE ret =
|
|
BACNET_SC_HUB_CONNECTOR_STATE_NO_HUB_CONNECTION;
|
|
bws_dispatch_lock();
|
|
if (node->state == BSC_NODE_STATE_STARTED) {
|
|
ret = bsc_hub_connector_state(node->hub_connector);
|
|
}
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the BACnet/SC node hub connector status
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param primary - true if the primary hub connector status is requested
|
|
* @return pointer to the hub connector status
|
|
*/
|
|
BACNET_SC_HUB_CONNECTION_STATUS *
|
|
bsc_node_hub_connector_status(BSC_NODE *node, bool primary)
|
|
{
|
|
BACNET_SC_HUB_CONNECTION_STATUS *ret = NULL;
|
|
bws_dispatch_lock();
|
|
if (node->state == BSC_NODE_STATE_STARTED) {
|
|
ret = bsc_hub_connector_status(node->hub_connector, primary);
|
|
}
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the BACnet/SC node hub function status
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param cnt - pointer to the status count
|
|
* @return pointer to the hub function status
|
|
*/
|
|
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *
|
|
bsc_node_hub_function_status(BSC_NODE *node, size_t *cnt)
|
|
{
|
|
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *ret = NULL;
|
|
bws_dispatch_lock();
|
|
if (node->state == BSC_NODE_STATE_STARTED &&
|
|
node->conf->hub_function_enabled) {
|
|
*cnt = BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM;
|
|
ret = node->hub_status;
|
|
}
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the BACnet/SC node direct connection status
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param cnt - pointer to the status count
|
|
* @return pointer to the direct connection status
|
|
*/
|
|
BACNET_SC_DIRECT_CONNECTION_STATUS *
|
|
bsc_node_direct_connection_status(BSC_NODE *node, size_t *cnt)
|
|
{
|
|
BACNET_SC_DIRECT_CONNECTION_STATUS *ret = NULL;
|
|
bws_dispatch_lock();
|
|
if (node->state == BSC_NODE_STATE_STARTED &&
|
|
(node->conf->direct_connect_accept_enable ||
|
|
node->conf->direct_connect_initiate_enable)) {
|
|
*cnt = BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM;
|
|
ret = node->direct_status;
|
|
}
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Maintenance timer for the BACnet/SC node
|
|
* @param seconds - number of seconds since the last call
|
|
*/
|
|
void bsc_node_maintenance_timer(uint16_t seconds)
|
|
{
|
|
(void)seconds;
|
|
|
|
bsc_socket_maintenance_timer(seconds);
|
|
bsc_hub_connector_maintenance_timer(seconds);
|
|
bsc_node_switch_maintenance_timer(seconds);
|
|
}
|
|
|
|
/**
|
|
* @brief Add failed request information to the BACnet/SC node
|
|
* @param r - pointer to the failed request information
|
|
* @param peer - pointer to the peer address
|
|
* @param vmac - pointer to the VMAC address
|
|
* @param uuid - pointer to the UUID
|
|
* @param error - error code
|
|
* @param error_desc - error description
|
|
*/
|
|
static void bsc_node_add_failed_request_info(
|
|
BACNET_SC_FAILED_CONNECTION_REQUEST *r,
|
|
BACNET_HOST_N_PORT_DATA *peer,
|
|
BACNET_SC_VMAC_ADDRESS *vmac,
|
|
BACNET_SC_UUID *uuid,
|
|
BACNET_ERROR_CODE error,
|
|
const char *error_desc)
|
|
{
|
|
bsc_set_timestamp(&r->Timestamp);
|
|
memcpy(&r->Peer_Address, peer, sizeof(*peer));
|
|
memcpy(r->Peer_VMAC, &vmac->address[0], BVLC_SC_VMAC_SIZE);
|
|
memcpy(&r->Peer_UUID.uuid.uuid128[0], &uuid->uuid[0], BVLC_SC_UUID_SIZE);
|
|
r->Error = error;
|
|
if (!error_desc) {
|
|
r->Error_Details[0] = 0;
|
|
} else {
|
|
bsc_copy_str(r->Error_Details, error_desc, sizeof(r->Error_Details));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Store failed request information to the BACnet/SC node
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param peer - pointer to the peer address
|
|
* @param vmac - pointer to the VMAC address
|
|
* @param uuid - pointer to the UUID
|
|
* @param error - error code
|
|
* @param error_desc - error description
|
|
*/
|
|
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)
|
|
{
|
|
size_t i, j = 0;
|
|
bool found = false;
|
|
BACNET_DATE_TIME t;
|
|
|
|
bws_dispatch_lock();
|
|
|
|
for (i = 0; i < BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM; i++) {
|
|
if (node->failed[i].Peer_Address.host[0] == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) {
|
|
bsc_node_add_failed_request_info(
|
|
&node->failed[i], peer, vmac, uuid, error, error_desc);
|
|
} else {
|
|
bsc_set_timestamp(&t);
|
|
for (i = 0; i < BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM; i++) {
|
|
if (datetime_compare(&node->failed[i].Timestamp, &t) < 0) {
|
|
j = i;
|
|
memcpy(&t, &node->failed[i].Timestamp, sizeof(t));
|
|
}
|
|
}
|
|
bsc_node_add_failed_request_info(
|
|
&node->failed[j], peer, vmac, uuid, error, error_desc);
|
|
}
|
|
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
/**
|
|
* @brief Get the failed request status from the BACnet/SC node
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param cnt - pointer to the status count
|
|
* @return pointer to the failed request status
|
|
*/
|
|
BACNET_SC_FAILED_CONNECTION_REQUEST *
|
|
bsc_node_failed_requests_status(BSC_NODE *node, size_t *cnt)
|
|
{
|
|
BACNET_SC_FAILED_CONNECTION_REQUEST *ret = NULL;
|
|
bws_dispatch_lock();
|
|
if (node->state == BSC_NODE_STATE_STARTED &&
|
|
(node->conf->direct_connect_accept_enable ||
|
|
node->conf->hub_function_enabled)) {
|
|
ret = node->failed;
|
|
*cnt = BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM;
|
|
}
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Find the BACnet/SC node direct connection status for the VMAC address
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param vmac - pointer to the VMAC address
|
|
* @return pointer to the direct connection status
|
|
*/
|
|
BACNET_SC_DIRECT_CONNECTION_STATUS *bsc_node_find_direct_status_for_vmac(
|
|
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *vmac)
|
|
{
|
|
int i;
|
|
int index = -1;
|
|
BACNET_DATE_TIME timestamp;
|
|
|
|
for (i = 0; i < BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM; i++) {
|
|
if (!datetime_is_valid(
|
|
&node->direct_status[i].Connect_Timestamp.date,
|
|
&node->direct_status[i].Connect_Timestamp.time)) {
|
|
return &node->direct_status[i];
|
|
}
|
|
if (!memcmp(
|
|
&node->direct_status[i].Peer_VMAC[0], &vmac->address[0],
|
|
BVLC_SC_VMAC_SIZE)) {
|
|
return &node->direct_status[i];
|
|
}
|
|
}
|
|
|
|
/* ok, all entries are already filled, try to found oldest entry with
|
|
non connected status */
|
|
|
|
for (i = 0; i < BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM; i++) {
|
|
if (node->direct_status[i].State !=
|
|
BACNET_SC_CONNECTION_STATE_CONNECTED &&
|
|
datetime_is_valid(
|
|
&node->direct_status[i].Disconnect_Timestamp.date,
|
|
&node->direct_status[i].Disconnect_Timestamp.time)) {
|
|
if (index == -1 ||
|
|
(datetime_compare(
|
|
&node->direct_status[i].Disconnect_Timestamp, ×tamp) <
|
|
0)) {
|
|
index = i;
|
|
memcpy(
|
|
×tamp, &node->direct_status[i].Disconnect_Timestamp,
|
|
sizeof(timestamp));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (index != -1) {
|
|
return &node->direct_status[index];
|
|
}
|
|
|
|
/* ok, all entries are already filled and all are in connected state,
|
|
so reuse oldest entry which is in connected state */
|
|
|
|
memcpy(
|
|
×tamp, &node->direct_status[0].Connect_Timestamp,
|
|
sizeof(timestamp));
|
|
index = 0;
|
|
|
|
for (i = 0; i < BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM; i++) {
|
|
if (datetime_compare(
|
|
&node->direct_status[i].Connect_Timestamp, ×tamp) < 0) {
|
|
index = i;
|
|
memcpy(
|
|
×tamp, &node->direct_status[i].Connect_Timestamp,
|
|
sizeof(timestamp));
|
|
}
|
|
}
|
|
|
|
return &node->direct_status[index];
|
|
}
|
|
|
|
/**
|
|
* @brief Find the BACnet/SC node hub function status for the VMAC address
|
|
* @param node - pointer to the BACnet/SC node
|
|
* @param vmac - pointer to the VMAC address
|
|
* @return pointer to the hub function status
|
|
*/
|
|
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *
|
|
bsc_node_find_hub_status_for_vmac(BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *vmac)
|
|
{
|
|
int i;
|
|
int index = -1;
|
|
BACNET_DATE_TIME timestamp;
|
|
|
|
for (i = 0; i < BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM; i++) {
|
|
if (!datetime_is_valid(
|
|
&node->hub_status[i].Connect_Timestamp.date,
|
|
&node->hub_status[i].Connect_Timestamp.time)) {
|
|
return &node->hub_status[i];
|
|
}
|
|
if (!memcmp(
|
|
&node->hub_status[i].Peer_VMAC[0], &vmac->address[0],
|
|
BVLC_SC_VMAC_SIZE)) {
|
|
return &node->hub_status[i];
|
|
}
|
|
}
|
|
|
|
/* ok, all entries are already filled, try to found oldest entry with
|
|
non connected status */
|
|
|
|
for (i = 0; i < BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM; i++) {
|
|
if (node->hub_status[i].State != BACNET_SC_CONNECTION_STATE_CONNECTED &&
|
|
datetime_is_valid(
|
|
&node->hub_status[i].Disconnect_Timestamp.date,
|
|
&node->hub_status[i].Disconnect_Timestamp.time)) {
|
|
if (index == -1 ||
|
|
(datetime_compare(
|
|
&node->hub_status[i].Disconnect_Timestamp, ×tamp) <
|
|
0)) {
|
|
index = i;
|
|
memcpy(
|
|
×tamp, &node->hub_status[i].Disconnect_Timestamp,
|
|
sizeof(timestamp));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (index != -1) {
|
|
return &node->hub_status[index];
|
|
}
|
|
|
|
/* ok, all entries are already filled and all are in connected state,
|
|
so reuse oldest entry which is in connected state */
|
|
|
|
memcpy(
|
|
×tamp, &node->hub_status[0].Connect_Timestamp, sizeof(timestamp));
|
|
index = 0;
|
|
|
|
for (i = 0; i < BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM; i++) {
|
|
if (datetime_compare(
|
|
&node->hub_status[i].Connect_Timestamp, ×tamp) < 0) {
|
|
index = i;
|
|
memcpy(
|
|
×tamp, &node->hub_status[i].Connect_Timestamp,
|
|
sizeof(timestamp));
|
|
}
|
|
}
|
|
|
|
return &node->hub_status[index];
|
|
}
|