/** * @file * @brief BACNet secure connect node API. * @author Kirill Neznamov * @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]; }