d4cbec91e2
* Enabled style and CERT-C addon for CPPCHECK with some suppressions and fixed the defects found..
1363 lines
46 KiB
C
1363 lines
46 KiB
C
/**
|
|
* @file
|
|
* @brief BACNet secure connect node switch function 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
|
|
*/
|
|
#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-node-switch.h"
|
|
#include "bacnet/datalink/bsc/bsc-hub-connector.h"
|
|
#include "bacnet/datalink/bsc/bsc-node.h"
|
|
#include "bacnet/bacdef.h"
|
|
#include "bacnet/npdu.h"
|
|
#include "bacnet/bacenum.h"
|
|
|
|
#define DEBUG_BSC_NODE_SWITCH 0
|
|
|
|
#if DEBUG_BSC_NODE_SWITCH == 1
|
|
#define DEBUG_PRINTF debug_printf
|
|
#else
|
|
#undef DEBUG_ENABLED
|
|
#define DEBUG_PRINTF debug_printf_disabled
|
|
#endif
|
|
|
|
typedef enum {
|
|
BSC_NODE_SWITCH_STATE_IDLE = 0,
|
|
BSC_NODE_SWITCH_STATE_STARTING = 1,
|
|
BSC_NODE_SWITCH_STATE_STARTED = 2,
|
|
BSC_NODE_SWITCH_STATE_STOPPING = 3
|
|
} BSC_NODE_SWITCH_STATE;
|
|
|
|
typedef enum {
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_IDLE = 0,
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_CONNECTION = 1,
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_RESOLUTION = 2,
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_CONNECTED = 3,
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_DELAYING = 4,
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_LOCAL_DISCONNECT = 5
|
|
} BSC_NODE_SWITCH_CONNECTION_STATE;
|
|
|
|
static BSC_SOCKET *node_switch_acceptor_find_connection_for_vmac(
|
|
BACNET_SC_VMAC_ADDRESS *vmac, void *user_arg);
|
|
|
|
static BSC_SOCKET *node_switch_acceptor_find_connection_for_uuid(
|
|
BACNET_SC_UUID *uuid, void *user_arg);
|
|
|
|
static void node_switch_acceptor_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 node_switch_acceptor_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
|
|
node_switch_acceptor_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev);
|
|
|
|
static void node_switch_initiator_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
|
|
node_switch_initiator_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev);
|
|
|
|
typedef struct BSC_Node_Switch_Acceptor {
|
|
BSC_SOCKET_CTX ctx;
|
|
BSC_CONTEXT_CFG cfg;
|
|
BSC_SOCKET sock[BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM];
|
|
BSC_NODE_SWITCH_STATE state;
|
|
BACNET_SC_DIRECT_CONNECTION_STATUS *status;
|
|
} BSC_NODE_SWITCH_ACCEPTOR;
|
|
|
|
typedef struct {
|
|
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_cnt;
|
|
int url_elem;
|
|
} BSC_NODE_SWITCH_URLS;
|
|
|
|
typedef struct BSC_Node_Switch_Initiator {
|
|
BSC_SOCKET_CTX ctx;
|
|
BSC_CONTEXT_CFG cfg;
|
|
BSC_SOCKET sock[BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM];
|
|
BSC_NODE_SWITCH_CONNECTION_STATE
|
|
sock_state[BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM];
|
|
BACNET_SC_VMAC_ADDRESS dest_vmac[BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM];
|
|
struct mstimer t[BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM];
|
|
BSC_NODE_SWITCH_URLS urls[BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM];
|
|
BSC_NODE_SWITCH_STATE state;
|
|
} BSC_NODE_SWITCH_INITIATOR;
|
|
|
|
typedef struct {
|
|
bool used;
|
|
BSC_NODE_SWITCH_ACCEPTOR acceptor;
|
|
BSC_NODE_SWITCH_INITIATOR initiator;
|
|
BSC_NODE_SWITCH_EVENT_FUNC event_func;
|
|
unsigned int reconnect_timeout_s;
|
|
unsigned int address_resolution_timeout_s;
|
|
bool direct_connect_accept_enable;
|
|
bool direct_connect_initiate_enable;
|
|
void *user_arg;
|
|
} BSC_NODE_SWITCH_CTX;
|
|
|
|
#if BSC_CONF_NODE_SWITCHES_NUM > 0
|
|
static BSC_NODE_SWITCH_CTX bsc_node_switch[BSC_CONF_NODE_SWITCHES_NUM] = { 0 };
|
|
#else
|
|
static BSC_NODE_SWITCH_CTX *bsc_node_switch = NULL;
|
|
#endif
|
|
|
|
static BSC_SOCKET_CTX_FUNCS bsc_node_switch_acceptor_ctx_funcs = {
|
|
node_switch_acceptor_find_connection_for_vmac,
|
|
node_switch_acceptor_find_connection_for_uuid,
|
|
node_switch_acceptor_socket_event, node_switch_acceptor_context_event,
|
|
node_switch_acceptor_failed_request
|
|
};
|
|
|
|
static BSC_SOCKET_CTX_FUNCS bsc_node_switch_initiator_ctx_funcs = {
|
|
NULL, NULL, node_switch_initiator_socket_event,
|
|
node_switch_initiator_context_event, NULL
|
|
};
|
|
|
|
static int node_switch_acceptor_find_connection_index_for_vmac(
|
|
BACNET_SC_VMAC_ADDRESS *vmac, BSC_NODE_SWITCH_CTX *ctx);
|
|
|
|
/**
|
|
* @brief Allocate a node switch context
|
|
* @return pointer to the allocated node switch context
|
|
*/
|
|
static BSC_NODE_SWITCH_CTX *node_switch_alloc(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < BSC_CONF_NODE_SWITCHES_NUM; i++) {
|
|
if (!bsc_node_switch[i].used) {
|
|
bsc_node_switch[i].used = true;
|
|
memset(
|
|
&bsc_node_switch[i].initiator, 0,
|
|
sizeof(bsc_node_switch[i].initiator));
|
|
memset(
|
|
&bsc_node_switch[i].acceptor, 0,
|
|
sizeof(bsc_node_switch[i].acceptor));
|
|
return &bsc_node_switch[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Free the node switch context
|
|
*/
|
|
static void node_switch_free(BSC_NODE_SWITCH_CTX *ctx)
|
|
{
|
|
ctx->used = false;
|
|
}
|
|
|
|
/**
|
|
* @brief Update the status of the node switch
|
|
* @param ctx - pointer to the node switch context
|
|
* @param initiator - true if the initiator, otherwise false
|
|
* @param failed_to_connect - true if failed to connect, otherwise false
|
|
* @param URI - pointer to the URI
|
|
* @param c - pointer to the socket
|
|
* @param ev - socket event
|
|
* @param reason - error code
|
|
* @param reason_desc - pointer to the reason description
|
|
*/
|
|
static void node_switch_update_status(
|
|
BSC_NODE_SWITCH_CTX *ctx,
|
|
bool initiator,
|
|
bool failed_to_connect,
|
|
const char *URI,
|
|
BSC_SOCKET *c,
|
|
BSC_SOCKET_EVENT ev,
|
|
BACNET_ERROR_CODE reason,
|
|
const char *reason_desc)
|
|
{
|
|
BACNET_SC_DIRECT_CONNECTION_STATUS *s;
|
|
|
|
DEBUG_PRINTF(
|
|
"node_switch_update_status() >>> ctx = %p, initiator = %d, "
|
|
"failed_to_connect = %d, URI = %p, c = %p, ev = %d, reason = "
|
|
"%d, reason_desc = %p\n",
|
|
ctx, initiator, failed_to_connect, URI, c, ev, reason, reason_desc);
|
|
|
|
if (ctx->user_arg) {
|
|
s = bsc_node_find_direct_status_for_vmac(ctx->user_arg, &c->vmac);
|
|
|
|
if (s) {
|
|
DEBUG_PRINTF(
|
|
"found existent status entry %p for vmac = %s\n", s,
|
|
bsc_vmac_to_string(&c->vmac));
|
|
if (!initiator) {
|
|
s->URI[0] = 0;
|
|
} else if (URI) {
|
|
bsc_copy_str(s->URI, URI, sizeof(s->URI));
|
|
}
|
|
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 (reason_desc) {
|
|
bsc_copy_str(
|
|
s->Error_Details, reason_desc, sizeof(s->Error_Details));
|
|
} else {
|
|
s->Error_Details[0] = 0;
|
|
}
|
|
s->Error = reason;
|
|
if (ev == BSC_SOCKET_EVENT_CONNECTED) {
|
|
DEBUG_PRINTF("set status state to CONNECTED\n");
|
|
if (!bsc_socket_get_peer_addr(c, &s->Peer_Address)) {
|
|
memset(&s->Peer_Address, 0, sizeof(s->Peer_Address));
|
|
}
|
|
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 (reason == ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER ||
|
|
reason == ERROR_CODE_SUCCESS) {
|
|
s->State = BACNET_SC_CONNECTION_STATE_NOT_CONNECTED;
|
|
DEBUG_PRINTF("set status state to NOT-CONNECTED\n");
|
|
} else {
|
|
s->State =
|
|
BACNET_SC_CONNECTION_STATE_DISCONNECTED_WITH_ERRORS;
|
|
DEBUG_PRINTF(
|
|
"set status state to DISCONNECTED-WITH-ERRORS\n");
|
|
}
|
|
if (failed_to_connect) {
|
|
s->State = BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT;
|
|
DEBUG_PRINTF("set status state to FAILED-TO-CONNECT\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_PRINTF("node_switch_update_status() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Copy URLs from the address resolution to the node switch context
|
|
* @param ctx - pointer to the node switch context
|
|
* @param index - index of the URL
|
|
* @param r - pointer to the address resolution
|
|
*/
|
|
static void
|
|
copy_urls(BSC_NODE_SWITCH_CTX *ctx, int index, BSC_ADDRESS_RESOLUTION *r)
|
|
{
|
|
int i;
|
|
for (i = 0; i < r->urls_num; i++) {
|
|
ctx->initiator.urls[index].utf8_urls[i][0] = 0;
|
|
strncpy(
|
|
(char *)&ctx->initiator.urls[index].utf8_urls[i][0],
|
|
(char *)&r->utf8_urls[i][0],
|
|
sizeof(ctx->initiator.urls[index].utf8_urls[i]) - 1);
|
|
}
|
|
ctx->initiator.urls[index].urls_cnt = r->urls_num;
|
|
}
|
|
|
|
/**
|
|
* @brief Copy URLs from the array to the node switch context
|
|
* @param ctx - pointer to the node switch context
|
|
* @param index - index of the URL
|
|
* @param urls - pointer to the array of URLs
|
|
* @param urls_cnt - number of URLs
|
|
*/
|
|
static void
|
|
copy_urls2(BSC_NODE_SWITCH_CTX *ctx, int index, char **urls, size_t urls_cnt)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < urls_cnt; i++) {
|
|
ctx->initiator.urls[index].utf8_urls[i][0] = 0;
|
|
strncpy(
|
|
(char *)&ctx->initiator.urls[index].utf8_urls[i][0], urls[i],
|
|
sizeof(ctx->initiator.urls[index].utf8_urls[i]) - 1);
|
|
}
|
|
ctx->initiator.urls[index].urls_cnt = urls_cnt;
|
|
}
|
|
|
|
/**
|
|
* @brief Find the connection socket 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 *node_switch_acceptor_find_connection_for_vmac(
|
|
BACNET_SC_VMAC_ADDRESS *vmac, void *user_arg)
|
|
{
|
|
int i;
|
|
BSC_NODE_SWITCH_CTX *c;
|
|
bws_dispatch_lock();
|
|
c = (BSC_NODE_SWITCH_CTX *)user_arg;
|
|
for (i = 0; i < sizeof(c->acceptor.sock) / sizeof(BSC_SOCKET); i++) {
|
|
DEBUG_PRINTF(
|
|
"ns = %p, sock %p, state = %d, vmac = %s\n", c,
|
|
&c->acceptor.sock[i], c->acceptor.sock[i].state,
|
|
bsc_vmac_to_string(&c->acceptor.sock[i].vmac));
|
|
if (c->acceptor.sock[i].state != BSC_SOCK_STATE_IDLE &&
|
|
!memcmp(
|
|
&vmac->address[0], &c->acceptor.sock[i].vmac.address[0],
|
|
sizeof(vmac->address))) {
|
|
bws_dispatch_unlock();
|
|
return &c->acceptor.sock[i];
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Find the connection socket 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 *node_switch_acceptor_find_connection_for_uuid(
|
|
BACNET_SC_UUID *uuid, void *user_arg)
|
|
{
|
|
int i;
|
|
BSC_NODE_SWITCH_CTX *c;
|
|
bws_dispatch_lock();
|
|
c = (BSC_NODE_SWITCH_CTX *)user_arg;
|
|
for (i = 0; i < sizeof(c->acceptor.sock) / sizeof(BSC_SOCKET); i++) {
|
|
DEBUG_PRINTF(
|
|
"ns = %p, sock %p, state = %d, uuid = %s\n", c,
|
|
&c->acceptor.sock[i], c->acceptor.sock[i].state,
|
|
bsc_uuid_to_string(&c->acceptor.sock[i].uuid));
|
|
if (c->acceptor.sock[i].state != BSC_SOCK_STATE_IDLE &&
|
|
!memcmp(
|
|
&uuid->uuid[0], &c->acceptor.sock[i].uuid.uuid[0],
|
|
sizeof(uuid->uuid))) {
|
|
DEBUG_PRINTF("found\n");
|
|
bws_dispatch_unlock();
|
|
return &c->acceptor.sock[i];
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Handle a node switch acceptor 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 node_switch_acceptor_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_NODE_SWITCH_CTX *ns;
|
|
BACNET_HOST_N_PORT_DATA peer;
|
|
(void)ctx;
|
|
|
|
bws_dispatch_lock();
|
|
ns = (BSC_NODE_SWITCH_CTX *)c->ctx->user_arg;
|
|
if (ns->user_arg) {
|
|
if (bsc_socket_get_peer_addr(c, &peer)) {
|
|
bsc_node_store_failed_request_info(
|
|
(BSC_NODE *)ns->user_arg, &peer, vmac, uuid, error, error_desc);
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
/**
|
|
* @brief Handle a node switch acceptor 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 node_switch_acceptor_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)
|
|
{
|
|
uint8_t *p_pdu;
|
|
BSC_NODE_SWITCH_CTX *ctx;
|
|
BACNET_ERROR_CODE error;
|
|
BACNET_ERROR_CLASS class;
|
|
const char *err_desc;
|
|
bool res = true;
|
|
|
|
DEBUG_PRINTF(
|
|
"node_switch_acceptor_socket_event() >>> c %p, ev = %d\n", c, ev);
|
|
|
|
bws_dispatch_lock();
|
|
ctx = (BSC_NODE_SWITCH_CTX *)c->ctx->user_arg;
|
|
|
|
/* Node switch does not take care about incoming connection status, */
|
|
/* so just route PDUs to upper layer */
|
|
|
|
if (ctx->acceptor.state == BSC_NODE_SWITCH_STATE_STARTED) {
|
|
if (ev == BSC_SOCKET_EVENT_RECEIVED) {
|
|
if (bsc_socket_get_global_buf_size() >= pdu_len) {
|
|
p_pdu = bsc_socket_get_global_buf();
|
|
memcpy(p_pdu, pdu, pdu_len);
|
|
pdu_len = bvlc_sc_set_orig(&p_pdu, pdu_len, &c->vmac);
|
|
res = bvlc_sc_decode_message(
|
|
p_pdu, pdu_len, decoded_pdu, &error, &class, &err_desc);
|
|
if (res) {
|
|
ctx->event_func(
|
|
BSC_NODE_SWITCH_EVENT_RECEIVED, ctx, ctx->user_arg,
|
|
NULL, p_pdu, pdu_len, decoded_pdu);
|
|
}
|
|
}
|
|
} else if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
|
|
node_switch_update_status(
|
|
ctx, false, false, NULL, c, ev, disconnect_reason,
|
|
disconnect_reason_desc);
|
|
if (disconnect_reason == ERROR_CODE_NODE_DUPLICATE_VMAC) {
|
|
ctx->event_func(
|
|
BSC_NODE_SWITCH_EVENT_DUPLICATED_VMAC, ctx, ctx->user_arg,
|
|
NULL, NULL, 0, NULL);
|
|
}
|
|
} else if (ev == BSC_SOCKET_EVENT_CONNECTED) {
|
|
node_switch_update_status(
|
|
ctx, false, false, NULL, c, ev, ERROR_CODE_DEFAULT, NULL);
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("node_switch_acceptor_socket_event() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Deinialize the node switch context
|
|
* @param ns - pointer to the node switch context
|
|
*/
|
|
static void node_switch_context_deinitialized(BSC_NODE_SWITCH_CTX *ns)
|
|
{
|
|
if (ns->initiator.state == BSC_NODE_SWITCH_STATE_IDLE &&
|
|
ns->acceptor.state == BSC_NODE_SWITCH_STATE_IDLE) {
|
|
node_switch_free(ns);
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_STOPPED, ns, ns->user_arg, NULL, NULL, 0,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle a node switch acceptor context event
|
|
* @param ctx - pointer to the socket context
|
|
* @param ev - event
|
|
*/
|
|
static void
|
|
node_switch_acceptor_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
|
|
bws_dispatch_lock();
|
|
DEBUG_PRINTF(
|
|
"node_switch_acceptor_context_event() >>> ctx = %p, ev = %d, "
|
|
"user_arg = %p\n",
|
|
ctx, ev, ctx->user_arg);
|
|
ns = (BSC_NODE_SWITCH_CTX *)ctx->user_arg;
|
|
if (ev == BSC_CTX_INITIALIZED) {
|
|
if (ns->acceptor.state == BSC_NODE_SWITCH_STATE_STARTING) {
|
|
ns->acceptor.state = BSC_NODE_SWITCH_STATE_STARTED;
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_STARTED, ns, ns->user_arg, NULL, NULL, 0,
|
|
NULL);
|
|
}
|
|
} else if (ev == BSC_CTX_DEINITIALIZED) {
|
|
ns->acceptor.state = BSC_NODE_SWITCH_STATE_IDLE;
|
|
node_switch_context_deinitialized(ns);
|
|
}
|
|
DEBUG_PRINTF("node_switch_acceptor_context_event() <<<\n");
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
/**
|
|
* @brief find a node switch initiator connection index for a specific
|
|
* VMAC address
|
|
* @param vmac - pointer to the VMAC address
|
|
* @param ctx - pointer to the node switch context
|
|
* @return connection index, or -1 if not found
|
|
*/
|
|
static int node_switch_initiator_find_connection_index_for_vmac(
|
|
BACNET_SC_VMAC_ADDRESS *vmac, BSC_NODE_SWITCH_CTX *ctx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(ctx->initiator.sock) / sizeof(BSC_SOCKET); i++) {
|
|
if (ctx->initiator.sock_state[i] !=
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_IDLE &&
|
|
!memcmp(
|
|
&ctx->initiator.dest_vmac[i].address[0], &vmac->address[0],
|
|
sizeof(vmac->address))) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @brief find a node switch acceptor connection index for a specific VMAC
|
|
* address
|
|
* @param vmac - pointer to the VMAC address
|
|
* @param ctx - pointer to the node switch context
|
|
* @return connection index, or -1 if not found
|
|
*/
|
|
static int node_switch_acceptor_find_connection_index_for_vmac(
|
|
BACNET_SC_VMAC_ADDRESS *vmac, BSC_NODE_SWITCH_CTX *ctx)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(ctx->acceptor.sock) / sizeof(BSC_SOCKET); i++) {
|
|
if (ctx->acceptor.sock[i].state == BSC_SOCK_STATE_CONNECTED &&
|
|
!memcmp(
|
|
&ctx->acceptor.sock[i].vmac.address[0], &vmac->address[0],
|
|
sizeof(vmac->address))) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @brief find a node switch initiator connection index
|
|
* @param ctx - pointer to the node switch context
|
|
* @param c - pointer to the socket
|
|
* @return node switch initiator index, or -1 if not found
|
|
*/
|
|
static int
|
|
node_switch_initiator_get_index(BSC_NODE_SWITCH_CTX *ctx, BSC_SOCKET *c)
|
|
{
|
|
return ((c - &ctx->initiator.sock[0]) >= 0)
|
|
? (int)(c - &ctx->initiator.sock[0])
|
|
: -1;
|
|
}
|
|
|
|
/**
|
|
* @brief allocate a socket for the node switch initiator
|
|
* @param ctx - pointer to the node switch context
|
|
* @return socket index, or -1 if not found
|
|
*/
|
|
static int node_switch_initiator_alloc_sock(BSC_NODE_SWITCH_CTX *ctx)
|
|
{
|
|
int i;
|
|
for (i = 0; i < sizeof(ctx->initiator.sock) / sizeof(BSC_SOCKET); i++) {
|
|
if (ctx->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_IDLE) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @brief Connect to the next URL
|
|
* @param ctx - pointer to the node switch context
|
|
* @param index - index of the URL
|
|
*/
|
|
static void connect_next_url(BSC_NODE_SWITCH_CTX *ctx, int index)
|
|
{
|
|
BSC_SC_RET ret = BSC_SC_BAD_PARAM;
|
|
|
|
while (ret != BSC_SC_SUCCESS) {
|
|
if (ctx->initiator.urls[index].url_elem >=
|
|
ctx->initiator.urls[index].urls_cnt) {
|
|
ctx->initiator.sock_state[index] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_DELAYING;
|
|
mstimer_set(
|
|
&ctx->initiator.t[index], ctx->reconnect_timeout_s * 1000);
|
|
ctx->initiator.urls[index].url_elem = 0;
|
|
break;
|
|
} else {
|
|
ctx->initiator.sock_state[index] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_CONNECTION;
|
|
ret = bsc_connect(
|
|
&ctx->initiator.ctx, &ctx->initiator.sock[index],
|
|
(char *)&ctx->initiator.urls[index]
|
|
.utf8_urls[ctx->initiator.urls[index].url_elem][0]);
|
|
ctx->initiator.urls[index].url_elem++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Connect to the next URL or delay
|
|
* @param ns - pointer to the node switch context
|
|
* @param dest - pointer to the VMAC address
|
|
* @param sock_index - socket index
|
|
*/
|
|
static void node_switch_connect_or_delay(
|
|
BSC_NODE_SWITCH_CTX *ns, BACNET_SC_VMAC_ADDRESS *dest, int sock_index)
|
|
{
|
|
BSC_ADDRESS_RESOLUTION *r;
|
|
|
|
if (ns->initiator.urls[sock_index].urls_cnt > 0) {
|
|
connect_next_url(ns, sock_index);
|
|
} else if (dest) {
|
|
r = bsc_node_get_address_resolution(ns->user_arg, dest);
|
|
if (r && r->urls_num) {
|
|
copy_urls(ns, sock_index, r);
|
|
ns->initiator.urls[sock_index].url_elem = 0;
|
|
connect_next_url(ns, sock_index);
|
|
} else {
|
|
ns->initiator.sock_state[sock_index] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_RESOLUTION;
|
|
ns->initiator.urls[sock_index].urls_cnt = 0;
|
|
memcpy(
|
|
&ns->initiator.dest_vmac[sock_index].address[0],
|
|
&dest->address[0], BVLC_SC_VMAC_SIZE);
|
|
mstimer_set(
|
|
&ns->initiator.t[sock_index],
|
|
ns->address_resolution_timeout_s * 1000);
|
|
(void)bsc_node_send_address_resolution(
|
|
ns->user_arg, &ns->initiator.dest_vmac[sock_index]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Process a node switch initiator runloop
|
|
* @param ns - pointer to the node switch context
|
|
*/
|
|
static void node_switch_initiator_runloop(BSC_NODE_SWITCH_CTX *ns)
|
|
{
|
|
int i;
|
|
|
|
/* DEBUG_PRINTF("node_switch_initiator_runloop() >>> ctx = %p\n", ns); */
|
|
|
|
for (i = 0; i < sizeof(ns->initiator.sock) / sizeof(BSC_SOCKET); i++) {
|
|
/* DEBUG_PRINTF("node_switch_initiator_runloop() socket %d state %d\n",
|
|
*/
|
|
/* i, ns->initiator.sock_state[i]); */
|
|
if (ns->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_DELAYING) {
|
|
if (mstimer_expired(&ns->initiator.t[i])) {
|
|
ns->initiator.urls[i].url_elem = 0;
|
|
ns->initiator.sock_state[i] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_CONNECTION;
|
|
node_switch_connect_or_delay(
|
|
ns, &ns->initiator.dest_vmac[i], i);
|
|
}
|
|
} else if (
|
|
ns->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_RESOLUTION) {
|
|
if (mstimer_expired(&ns->initiator.t[i])) {
|
|
ns->initiator.sock_state[i] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_DELAYING;
|
|
mstimer_set(
|
|
&ns->initiator.t[i], ns->reconnect_timeout_s * 1000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Run the node switch maintenance timer
|
|
* @param seconds - number of seconds elapsed from the previous call
|
|
*/
|
|
void bsc_node_switch_maintenance_timer(uint16_t seconds)
|
|
{
|
|
int i;
|
|
(void)seconds;
|
|
|
|
bws_dispatch_lock();
|
|
for (i = 0; i < BSC_CONF_NODE_SWITCHES_NUM; i++) {
|
|
if (bsc_node_switch[i].used) {
|
|
node_switch_initiator_runloop(&bsc_node_switch[i]);
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
/**
|
|
* @brief Handle a node switch initiator 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 node_switch_initiator_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)
|
|
{
|
|
int index;
|
|
uint8_t *p_pdu = NULL;
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
int elem;
|
|
BACNET_ERROR_CODE error;
|
|
BACNET_ERROR_CLASS class;
|
|
const char *err_desc;
|
|
bool res;
|
|
|
|
DEBUG_PRINTF(
|
|
"node_switch_initiator_socket_event() >>> c %p, ev = %d\n", c, ev);
|
|
|
|
bws_dispatch_lock();
|
|
|
|
ns = (BSC_NODE_SWITCH_CTX *)c->ctx->user_arg;
|
|
|
|
if (ns->initiator.state == BSC_NODE_SWITCH_STATE_STARTED) {
|
|
if (ev == BSC_SOCKET_EVENT_DISCONNECTED &&
|
|
disconnect_reason == ERROR_CODE_NODE_DUPLICATE_VMAC) {
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_DUPLICATED_VMAC, ns, ns->user_arg, NULL,
|
|
NULL, 0, NULL);
|
|
} else if (ev == BSC_SOCKET_EVENT_RECEIVED) {
|
|
if (bsc_socket_get_global_buf_size() >= pdu_len) {
|
|
p_pdu = bsc_socket_get_global_buf();
|
|
memcpy(p_pdu, pdu, pdu_len);
|
|
pdu_len = bvlc_sc_set_orig(&p_pdu, pdu_len, &c->vmac);
|
|
res = bvlc_sc_decode_message(
|
|
p_pdu, pdu_len, decoded_pdu, &error, &class, &err_desc);
|
|
if (res) {
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_RECEIVED, ns, ns->user_arg, NULL,
|
|
p_pdu, pdu_len, decoded_pdu);
|
|
}
|
|
}
|
|
}
|
|
|
|
index = node_switch_initiator_get_index(ns, c);
|
|
|
|
if (index > -1) {
|
|
elem = ns->initiator.urls[index].url_elem - 1;
|
|
if (elem < 0 || elem >= ns->initiator.urls[index].urls_cnt) {
|
|
elem = -1;
|
|
}
|
|
if (ns->initiator.sock_state[index] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_CONNECTION) {
|
|
if (ev == BSC_SOCKET_EVENT_CONNECTED) {
|
|
ns->initiator.sock_state[index] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_CONNECTED;
|
|
/* if user provides url instead of dest in */
|
|
/* bsc_node_switch_connect() ns->initiator.dest_vmac[index]
|
|
*/
|
|
/* is unset. that's why we always set it from socket */
|
|
/* descriptor */
|
|
memcpy(
|
|
&ns->initiator.dest_vmac[index].address[0],
|
|
&c->vmac.address[0], sizeof(c->vmac.address));
|
|
node_switch_update_status(
|
|
ns, true, false,
|
|
elem == -1 ? NULL
|
|
: (const char *)&ns->initiator.urls[index]
|
|
.utf8_urls[elem][0],
|
|
c, ev, ERROR_CODE_DEFAULT, NULL);
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_CONNECTED, ns, ns->user_arg,
|
|
&ns->initiator.dest_vmac[index], NULL, 0, NULL);
|
|
} else if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
|
|
node_switch_update_status(
|
|
ns, true, true,
|
|
elem == -1 ? NULL
|
|
: (const char *)&ns->initiator.urls[index]
|
|
.utf8_urls[elem][0],
|
|
c, ev, disconnect_reason, disconnect_reason_desc);
|
|
connect_next_url(ns, index);
|
|
}
|
|
} else if (
|
|
ns->initiator.sock_state[index] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_CONNECTED) {
|
|
if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
|
|
node_switch_update_status(
|
|
ns, true, false,
|
|
elem == -1 ? NULL
|
|
: (const char *)&ns->initiator.urls[index]
|
|
.utf8_urls[elem][0],
|
|
c, ev, disconnect_reason, disconnect_reason_desc);
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_DISCONNECTED, ns, ns->user_arg,
|
|
&ns->initiator.dest_vmac[index], NULL, 0, NULL);
|
|
ns->initiator.urls[index].url_elem = 0;
|
|
connect_next_url(ns, index);
|
|
}
|
|
} else if (
|
|
ns->initiator.sock_state[index] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_LOCAL_DISCONNECT) {
|
|
if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
|
|
node_switch_update_status(
|
|
ns, true, false,
|
|
elem == -1 ? NULL
|
|
: (const char *)&ns->initiator.urls[index]
|
|
.utf8_urls[elem][0],
|
|
c, ev, ERROR_CODE_SUCCESS, NULL);
|
|
ns->initiator.sock_state[index] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_IDLE;
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_DISCONNECTED, ns, ns->user_arg,
|
|
&ns->initiator.dest_vmac[index], NULL, 0, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("node_switch_initiator_socket_event() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Handle a node switch initiator context event
|
|
* @param ctx - pointer to the socket context
|
|
* @param ev - event
|
|
*/
|
|
static void
|
|
node_switch_initiator_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
int i;
|
|
|
|
bws_dispatch_lock();
|
|
DEBUG_PRINTF(
|
|
"node_switch_initiator_context_event () >>> ctx = %p, ev = "
|
|
"%d, user_arg = %p\n",
|
|
ctx, ev, ctx->user_arg);
|
|
ns = (BSC_NODE_SWITCH_CTX *)ctx->user_arg;
|
|
if (ev == BSC_CTX_DEINITIALIZED) {
|
|
for (i = 0; i < sizeof(ns->initiator.sock) / sizeof(BSC_SOCKET); i++) {
|
|
if (ns->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_CONNECTED) {
|
|
ns->initiator.sock_state[i] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_IDLE;
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_DISCONNECTED, ns, ns->user_arg,
|
|
&ns->initiator.dest_vmac[i], NULL, 0, NULL);
|
|
}
|
|
}
|
|
ns->initiator.state = BSC_NODE_SWITCH_STATE_IDLE;
|
|
node_switch_context_deinitialized(ns);
|
|
}
|
|
DEBUG_PRINTF("node_switch_initiator_context_event() <<<\n");
|
|
bws_dispatch_unlock();
|
|
}
|
|
|
|
/**
|
|
* @brief Start the node switch
|
|
* @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 key
|
|
* @param key_size - size of the 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 reconnnect_timeout_s - reconnect timeout in seconds
|
|
* @param address_resolution_timeout_s - address resolution timeout in seconds
|
|
* @param direct_connect_accept_enable - true if direct connect accept is
|
|
* enabled
|
|
* @param direct_connect_initiate_enable - true if direct connect initiate is
|
|
* enabled
|
|
* @param event_func - pointer to the event function
|
|
* @param user_arg - pointer to the user argument
|
|
* @param h - pointer to the node switch handle
|
|
* @return BACnet/SC status
|
|
*/
|
|
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)
|
|
{
|
|
BSC_SC_RET ret = BSC_SC_SUCCESS;
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
|
|
DEBUG_PRINTF("bsc_node_switch_start() >>>\n");
|
|
|
|
if (!address_resolution_timeout_s || !event_func ||
|
|
(direct_connect_accept_enable && !port) || !h) {
|
|
DEBUG_PRINTF("bsc_node_switch_start() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
|
|
if (!direct_connect_accept_enable && !direct_connect_initiate_enable) {
|
|
DEBUG_PRINTF("bsc_node_switch_start() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
|
|
*h = NULL;
|
|
bws_dispatch_lock();
|
|
ns = node_switch_alloc();
|
|
|
|
if (!ns) {
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_start() <<< ret = BSC_SC_NO_RESOURCES\n");
|
|
return BSC_SC_NO_RESOURCES;
|
|
}
|
|
|
|
ns->event_func = event_func;
|
|
ns->user_arg = user_arg;
|
|
ns->reconnect_timeout_s = reconnnect_timeout_s;
|
|
ns->address_resolution_timeout_s = address_resolution_timeout_s;
|
|
ns->direct_connect_initiate_enable = direct_connect_initiate_enable;
|
|
ns->direct_connect_accept_enable = direct_connect_accept_enable;
|
|
ns->initiator.state = BSC_NODE_SWITCH_STATE_IDLE;
|
|
ns->acceptor.state = BSC_NODE_SWITCH_STATE_IDLE;
|
|
|
|
if (direct_connect_initiate_enable) {
|
|
bsc_init_ctx_cfg(
|
|
BSC_SOCKET_CTX_INITIATOR, &ns->initiator.cfg,
|
|
BSC_WEBSOCKET_DIRECT_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);
|
|
ret = bsc_init_ctx(
|
|
&ns->initiator.ctx, &ns->initiator.cfg,
|
|
&bsc_node_switch_initiator_ctx_funcs, ns->initiator.sock,
|
|
sizeof(ns->initiator.sock) / sizeof(BSC_SOCKET), ns);
|
|
if (ret == BSC_SC_SUCCESS) {
|
|
ns->initiator.state = BSC_NODE_SWITCH_STATE_STARTED;
|
|
}
|
|
}
|
|
|
|
if (ret == BSC_SC_SUCCESS && direct_connect_accept_enable) {
|
|
bsc_init_ctx_cfg(
|
|
BSC_SOCKET_CTX_ACCEPTOR, &ns->acceptor.cfg,
|
|
BSC_WEBSOCKET_DIRECT_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(
|
|
&ns->acceptor.ctx, &ns->acceptor.cfg,
|
|
&bsc_node_switch_acceptor_ctx_funcs, ns->acceptor.sock,
|
|
sizeof(ns->acceptor.sock) / sizeof(BSC_SOCKET), ns);
|
|
if (ret == BSC_SC_SUCCESS) {
|
|
ns->acceptor.state = BSC_NODE_SWITCH_STATE_STARTING;
|
|
}
|
|
}
|
|
|
|
if (ret != BSC_SC_SUCCESS) {
|
|
if (ns->initiator.state == BSC_NODE_SWITCH_STATE_STARTED) {
|
|
bsc_deinit_ctx(&ns->initiator.ctx);
|
|
ns->initiator.state = BSC_NODE_SWITCH_STATE_IDLE;
|
|
}
|
|
node_switch_free(ns);
|
|
} else {
|
|
*h = ns;
|
|
if (direct_connect_initiate_enable && !direct_connect_accept_enable) {
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_STARTED, ns, ns->user_arg, NULL, NULL, 0,
|
|
NULL);
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_start() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Stop the node switch
|
|
* @param h - pointer to the node switch handle
|
|
*/
|
|
void bsc_node_switch_stop(BSC_NODE_SWITCH_HANDLE h)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
DEBUG_PRINTF("bsc_node_switch_stop() >>> h = %p \n", h);
|
|
bws_dispatch_lock();
|
|
ns = (BSC_NODE_SWITCH_CTX *)h;
|
|
if (ns) {
|
|
DEBUG_PRINTF("bsc_node_switch_stop() unreg loop\n");
|
|
if (ns->direct_connect_accept_enable &&
|
|
ns->acceptor.state != BSC_NODE_SWITCH_STATE_IDLE) {
|
|
ns->acceptor.state = BSC_NODE_SWITCH_STATE_STOPPING;
|
|
bsc_deinit_ctx(&ns->acceptor.ctx);
|
|
}
|
|
if (ns->direct_connect_initiate_enable &&
|
|
ns->initiator.state != BSC_NODE_SWITCH_STATE_IDLE) {
|
|
ns->initiator.state = BSC_NODE_SWITCH_STATE_STOPPING;
|
|
bsc_deinit_ctx(&ns->initiator.ctx);
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_stop() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Check if the node switch is stopped
|
|
* @param h - pointer to the node switch handle
|
|
* @return true if the node switch is stopped, false otherwise
|
|
*/
|
|
bool bsc_node_switch_stopped(BSC_NODE_SWITCH_HANDLE h)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns = (BSC_NODE_SWITCH_CTX *)h;
|
|
bool ret = false;
|
|
|
|
DEBUG_PRINTF("bsc_node_switch_stopped() h = %p >>>\n", h);
|
|
bws_dispatch_lock();
|
|
if (ns && ns->acceptor.state == BSC_NODE_SWITCH_STATE_IDLE &&
|
|
ns->initiator.state == BSC_NODE_SWITCH_STATE_IDLE) {
|
|
ret = true;
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_stoped() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if the node switch is started
|
|
* @param h - pointer to the node switch handle
|
|
* @return true if the node switch is started, false otherwise
|
|
*/
|
|
bool bsc_node_switch_started(BSC_NODE_SWITCH_HANDLE h)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns = (BSC_NODE_SWITCH_CTX *)h;
|
|
bool ret = false;
|
|
|
|
DEBUG_PRINTF("bsc_node_switch_started() h = %p >>>\n", h);
|
|
bws_dispatch_lock();
|
|
if (ns) {
|
|
ret = true;
|
|
if (ns->direct_connect_initiate_enable &&
|
|
ns->initiator.state != BSC_NODE_SWITCH_STATE_STARTED) {
|
|
ret = false;
|
|
}
|
|
if (ns->direct_connect_accept_enable &&
|
|
ns->acceptor.state != BSC_NODE_SWITCH_STATE_STARTED) {
|
|
ret = false;
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_started() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Connect to a node switch
|
|
* @param h - pointer to the node switch handle
|
|
* @param dest - pointer to the VMAC address
|
|
* @param urls - pointer to the URLs
|
|
* @param urls_cnt - number of URLs
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET bsc_node_switch_connect(
|
|
BSC_NODE_SWITCH_HANDLE h,
|
|
BACNET_SC_VMAC_ADDRESS *dest,
|
|
char **urls,
|
|
size_t urls_cnt)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
BSC_SC_RET ret = BSC_SC_INVALID_OPERATION;
|
|
int i;
|
|
|
|
DEBUG_PRINTF(
|
|
"bsc_node_switch_connect() >>> h = %p, dest = %p, urls = %p, "
|
|
"urls_cnt = %u\n",
|
|
h, dest, urls, urls_cnt);
|
|
|
|
for (i = 0; i < urls_cnt; i++) {
|
|
if (strlen(urls[i]) >
|
|
BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK) {
|
|
DEBUG_PRINTF(
|
|
"bsc_node_switch_connect() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
}
|
|
|
|
if (!dest && (!urls || !urls_cnt)) {
|
|
DEBUG_PRINTF("bsc_node_switch_connect() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
|
|
if (dest && (urls || urls_cnt)) {
|
|
DEBUG_PRINTF("bsc_node_switch_connect() <<< ret = BSC_SC_BAD_PARAM\n");
|
|
return BSC_SC_BAD_PARAM;
|
|
}
|
|
|
|
bws_dispatch_lock();
|
|
ns = (BSC_NODE_SWITCH_CTX *)h;
|
|
if (ns->direct_connect_initiate_enable) {
|
|
if (urls && urls_cnt) {
|
|
i = node_switch_initiator_alloc_sock(ns);
|
|
if (i == -1) {
|
|
ret = BSC_SC_NO_RESOURCES;
|
|
} else {
|
|
copy_urls2(ns, i, urls, urls_cnt);
|
|
ns->initiator.urls[i].url_elem = 0;
|
|
node_switch_connect_or_delay(ns, NULL, i);
|
|
ret = BSC_SC_SUCCESS;
|
|
}
|
|
} else {
|
|
i = node_switch_initiator_find_connection_index_for_vmac(dest, ns);
|
|
if (i != -1) {
|
|
ret = BSC_SC_SUCCESS;
|
|
} else {
|
|
i = node_switch_initiator_alloc_sock(ns);
|
|
if (i == -1) {
|
|
ret = BSC_SC_NO_RESOURCES;
|
|
} else {
|
|
ns->initiator.urls[i].urls_cnt = 0;
|
|
node_switch_connect_or_delay(ns, dest, i);
|
|
ret = BSC_SC_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_connect() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Process an address resolution
|
|
* @param h - pointer to the node switch handle
|
|
* @param r - pointer to the address resolution
|
|
*/
|
|
void bsc_node_switch_process_address_resolution(
|
|
BSC_NODE_SWITCH_HANDLE h, BSC_ADDRESS_RESOLUTION *r)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
int i;
|
|
|
|
DEBUG_PRINTF(
|
|
"bsc_node_switch_process_address_resolution() >>> h = %p, r = "
|
|
"%p (%s)\n",
|
|
h, r, bsc_vmac_to_string(&r->vmac));
|
|
bws_dispatch_lock();
|
|
|
|
ns = (BSC_NODE_SWITCH_CTX *)h;
|
|
if (r && r->urls_num) {
|
|
i = node_switch_initiator_find_connection_index_for_vmac(&r->vmac, ns);
|
|
if (i != -1 &&
|
|
ns->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_RESOLUTION) {
|
|
copy_urls(ns, i, r);
|
|
ns->initiator.urls[i].url_elem = 0;
|
|
node_switch_connect_or_delay(ns, NULL, i);
|
|
}
|
|
}
|
|
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_process_address_resolution() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Disconnect from a node switch
|
|
* @param h - pointer to the node switch handle
|
|
* @param dest - pointer to the VMAC address
|
|
*/
|
|
void bsc_node_switch_disconnect(
|
|
BSC_NODE_SWITCH_HANDLE h, BACNET_SC_VMAC_ADDRESS *dest)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
BSC_SOCKET *c;
|
|
int i;
|
|
|
|
DEBUG_PRINTF(
|
|
"bsc_node_switch_disconnect() >>> h = %p, dest = %p (%s)\n", h, dest,
|
|
bsc_vmac_to_string(dest));
|
|
bws_dispatch_lock();
|
|
ns = (BSC_NODE_SWITCH_CTX *)h;
|
|
if (ns->direct_connect_initiate_enable) {
|
|
i = node_switch_initiator_find_connection_index_for_vmac(dest, ns);
|
|
|
|
if (i != -1) {
|
|
if (ns->initiator.sock_state[i] !=
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_LOCAL_DISCONNECT) {
|
|
if (ns->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_CONNECTED ||
|
|
ns->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_WAIT_CONNECTION) {
|
|
c = &ns->initiator.sock[i];
|
|
bsc_disconnect(c);
|
|
ns->initiator.sock_state[i] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_LOCAL_DISCONNECT;
|
|
} else {
|
|
ns->initiator.sock_state[i] =
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_IDLE;
|
|
ns->event_func(
|
|
BSC_NODE_SWITCH_EVENT_DISCONNECTED, ns, ns->user_arg,
|
|
&ns->initiator.dest_vmac[i], NULL, 0, NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_disconnect() <<<\n");
|
|
}
|
|
|
|
/**
|
|
* @brief Send a PDU to a node switch
|
|
* @param h - pointer to the node switch handle
|
|
* @param pdu - pointer to the PDU
|
|
* @param pdu_len - PDU length
|
|
* @return BACnet/SC status
|
|
*/
|
|
BSC_SC_RET
|
|
bsc_node_switch_send(BSC_NODE_SWITCH_HANDLE h, uint8_t *pdu, size_t pdu_len)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
BSC_SC_RET ret = BSC_SC_SUCCESS;
|
|
BSC_SOCKET *c = NULL;
|
|
BACNET_SC_VMAC_ADDRESS dest;
|
|
uint8_t **ppdu = &pdu;
|
|
int i;
|
|
|
|
DEBUG_PRINTF(
|
|
"bsc_node_switch_send() >>> pdu = %p, pdu_len = %u\n", pdu, pdu_len);
|
|
bws_dispatch_lock();
|
|
ns = (BSC_NODE_SWITCH_CTX *)h;
|
|
if (bvlc_sc_pdu_has_no_dest(pdu, pdu_len) ||
|
|
bvlc_sc_pdu_has_dest_broadcast(pdu, pdu_len)) {
|
|
ret = bsc_node_hub_connector_send(ns->user_arg, pdu, pdu_len);
|
|
} else {
|
|
if (bvlc_sc_pdu_get_dest(pdu, pdu_len, &dest)) {
|
|
i = node_switch_initiator_find_connection_index_for_vmac(&dest, ns);
|
|
if (i != -1 &&
|
|
ns->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_CONNECTED) {
|
|
c = &ns->initiator.sock[i];
|
|
}
|
|
if (!c) {
|
|
c = node_switch_acceptor_find_connection_for_vmac(&dest, ns);
|
|
}
|
|
if (c && c->state == BSC_SOCK_STATE_CONNECTED) {
|
|
pdu_len = bvlc_sc_remove_orig_and_dest(ppdu, pdu_len);
|
|
if (pdu_len > 0) {
|
|
ret = bsc_send(c, *ppdu, pdu_len);
|
|
}
|
|
} else {
|
|
ret = bsc_node_hub_connector_send(ns->user_arg, pdu, pdu_len);
|
|
}
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
DEBUG_PRINTF("bsc_node_switch_send() <<< ret = %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if a node switch is connected
|
|
* @param h - pointer to the node switch handle
|
|
* @param dest - pointer to the VMAC address
|
|
* @param urls - pointer to the URLs
|
|
* @param urls_cnt - number of URLs
|
|
*/
|
|
BACNET_STACK_EXPORT
|
|
bool bsc_node_switch_connected(
|
|
BSC_NODE_SWITCH_HANDLE h,
|
|
BACNET_SC_VMAC_ADDRESS *dest,
|
|
char **urls,
|
|
size_t urls_cnt)
|
|
{
|
|
BSC_NODE_SWITCH_CTX *ns;
|
|
bool ret = false;
|
|
int i, j, k;
|
|
|
|
if (!dest && (!urls || urls_cnt == 0)) {
|
|
return false;
|
|
}
|
|
|
|
bws_dispatch_lock();
|
|
ns = (BSC_NODE_SWITCH_CTX *)h;
|
|
if (ns->direct_connect_initiate_enable) {
|
|
if (dest) {
|
|
i = node_switch_initiator_find_connection_index_for_vmac(dest, ns);
|
|
if (i != -1) {
|
|
if (ns->initiator.sock_state[i] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_CONNECTED) {
|
|
ret = true;
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0; i < urls_cnt; i++) {
|
|
for (j = 0; j < sizeof(ns->initiator.sock) / sizeof(BSC_SOCKET);
|
|
j++) {
|
|
if (ns->initiator.sock_state[j] ==
|
|
BSC_NODE_SWITCH_CONNECTION_STATE_CONNECTED) {
|
|
if (ns->initiator.urls[j].urls_cnt > 0) {
|
|
for (k = 0; k < ns->initiator.urls[j].urls_cnt;
|
|
k++) {
|
|
if (strlen((char *)&ns->initiator.urls[j]
|
|
.utf8_urls[k][0]) ==
|
|
strlen(urls[i])) {
|
|
if (!memcmp(
|
|
(char *)&ns->initiator.urls[j]
|
|
.utf8_urls[k][0],
|
|
urls[i], strlen(urls[i]))) {
|
|
ret = true;
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (ns->direct_connect_accept_enable) {
|
|
if (dest) {
|
|
i = node_switch_acceptor_find_connection_index_for_vmac(dest, ns);
|
|
if (i != -1) {
|
|
ret = true;
|
|
}
|
|
}
|
|
}
|
|
bws_dispatch_unlock();
|
|
return ret;
|
|
}
|