/**
 * @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, &timestamp) <
                 0)) {
                index = i;
                memcpy(
                    &timestamp, &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(
        &timestamp, &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, &timestamp) < 0) {
            index = i;
            memcpy(
                &timestamp, &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, &timestamp) <
                 0)) {
                index = i;
                memcpy(
                    &timestamp, &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(
        &timestamp, &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, &timestamp) < 0) {
            index = i;
            memcpy(
                &timestamp, &node->hub_status[i].Connect_Timestamp,
                sizeof(timestamp));
        }
    }

    return &node->hub_status[index];
}
