Files
bacnet_stack/src/bacnet/datalink/bsc/bsc-socket.c
T
Steve Karg 25d14c7c15 Fixed BACnet/SC secure connect VMAC, UUID, and initialization sequence (#1142)
* Refactored the BACnet/SC datalink initialization order by moving certificate file checks and hub connection registration to occur after datalink initialization
* Replaced stdlib rand() with platform-specific cryptographically secure random functions (RtlGenRandom for Windows, getrandom for Linux, arc4random_buf for BSD) to generate UUID and VMAC addresses, preventing duplicates that broke connections
* Enabled conditional debug output in BACnet/SC components when BUILD=debug is specified
2025-11-14 15:41:17 -06:00

1849 lines
67 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file
* @brief BACNet secure connect API.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date May 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "bacnet/bactext.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-socket.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/sys/debug.h"
#undef DEBUG_PRINTF
#if DEBUG_BSC_SOCKET
#define DEBUG_PRINTF debug_printf
#if (DEBUG_BSC_SOCKET == 2)
#define DEBUG_PRINTF_VERBOSE debug_printf
#else
#define DEBUG_PRINTF_VERBOSE debug_printf_disabled
#endif
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF debug_printf_disabled
#define DEBUG_PRINTF_VERBOSE debug_printf_disabled
#endif
static const char *s_error_no_origin =
"'Originating Virtual Address' field must be present";
static const char *s_error_dest_presented =
"'Destination Virtual Address' field must be absent";
static const char *s_error_origin_presented =
"'Originating Virtual Address' field must be absent";
static const char *s_error_no_dest =
"'Destination Virtual Address' field must be present";
static BSC_SOCKET_CTX *bsc_socket_ctx[BSC_SOCKET_CTX_NUM] = { 0 };
static BVLC_SC_DECODED_MESSAGE bsc_dm = { 0 };
#define TX_BUF_PTR(c) \
&c->tx_buf[c->tx_buf_size + sizeof(uint16_t) + BSC_CONF_TX_PRE]
#define TX_BUF_UPDATE(c, len) \
memcpy(&c->tx_buf[c->tx_buf_size], &len, sizeof(uint16_t)); \
c->tx_buf_size += sizeof(uint16_t) + BSC_CONF_TX_PRE + len
#define TX_BUF_BYTES_AVAIL(c) \
(((sizeof(c->tx_buf) - c->tx_buf_size) >= \
(sizeof(uint16_t) + BSC_CONF_TX_PRE)) \
? (sizeof(c->tx_buf) - c->tx_buf_size - sizeof(uint16_t) - \
BSC_CONF_TX_PRE) \
: 0)
/**
* @brief Add the socket context to the list
* @param ctx - pointer to the socket context
* @return true if the context was added, otherwise false
*/
static bool bsc_ctx_add(BSC_SOCKET_CTX *ctx)
{
int i;
for (i = 0; i < BSC_SOCKET_CTX_NUM; i++) {
if (bsc_socket_ctx[i] == NULL) {
bsc_socket_ctx[i] = ctx;
return true;
}
}
return false;
}
/**
* @brief Remove the socket context from the list
* @param ctx - pointer to the socket context
*/
static void bsc_ctx_remove(BSC_SOCKET_CTX *ctx)
{
int i;
for (i = 0; i < BSC_SOCKET_CTX_NUM; i++) {
if (bsc_socket_ctx[i] == ctx) {
bsc_socket_ctx[i] = NULL;
break;
}
}
}
/**
* @brief Reset the BACnet Secure Connect
* @param c - pointer to the socket
*/
static void bsc_reset_socket(BSC_SOCKET *c)
{
memset(&c->vmac, 0, sizeof(c->vmac));
memset(&c->uuid, 0, sizeof(c->uuid));
c->tx_buf_size = 0;
}
/**
* @brief Initialize the BACnet Secure Connect context
* @param type - type of the context
* @param cfg - pointer to the configuration
* @param proto - WebSocket protocol
* @param port - port number
* @param iface - interface name
* @param ca_cert_chain - pointer to the CA certificate chain
* @param ca_cert_chain_size - size of the CA certificate chain
* @param cert_chain - pointer to the certificate chain
* @param cert_chain_size - size of the certificate chain
* @param key - pointer to the private key
* @param key_size - size of the private key
* @param local_uuid - pointer to the local UUID
* @param local_vmac - pointer to the local VMAC address
* @param max_local_bvlc_len - maximum BVLC message size
* @param max_local_ndpu_len - maximum NPDU message size
* @param connect_timeout_s - connection timeout in seconds
* @param heartbeat_timeout_s - heartbeat timeout in seconds
* @param disconnect_timeout_s - disconnect timeout in seconds
*/
void bsc_init_ctx_cfg(
BSC_SOCKET_CTX_TYPE type,
BSC_CONTEXT_CFG *cfg,
BSC_WEBSOCKET_PROTOCOL proto,
uint16_t port,
char *iface,
uint8_t *ca_cert_chain,
size_t ca_cert_chain_size,
uint8_t *cert_chain,
size_t cert_chain_size,
uint8_t *key,
size_t key_size,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_ndpu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s)
{
DEBUG_PRINTF_VERBOSE("bsc_init_ctx_cfg() >>> cfg = %p\n");
if (cfg) {
cfg->proto = proto;
cfg->port = port;
cfg->type = type;
cfg->iface = iface;
cfg->ca_cert_chain = ca_cert_chain;
cfg->ca_cert_chain_size = ca_cert_chain_size;
cfg->cert_chain = cert_chain;
cfg->cert_chain_size = cert_chain_size;
cfg->priv_key = key;
cfg->priv_key_size = key_size;
bsc_copy_uuid(&cfg->local_uuid, local_uuid);
bsc_copy_vmac(&cfg->local_vmac, local_vmac);
cfg->max_bvlc_len = max_local_bvlc_len;
cfg->max_ndpu_len = max_local_ndpu_len;
cfg->connect_timeout_s = connect_timeout_s;
cfg->heartbeat_timeout_s = heartbeat_timeout_s;
cfg->disconnect_timeout_s = disconnect_timeout_s;
}
DEBUG_PRINTF_VERBOSE("bsc_init_ctx_cfg() <<<\n");
}
/**
* @brief Find the socket context by the WebSocket handle
* @param ctx - pointer to the socket context
* @param h - pointer to the socket
*/
static BSC_SOCKET *
bsc_find_conn_by_websocket(BSC_SOCKET_CTX *ctx, BSC_WEBSOCKET_HANDLE h)
{
size_t i;
for (i = 0; i < ctx->sock_num; i++) {
if (ctx->sock[i].state != BSC_SOCK_STATE_IDLE && ctx->sock[i].wh == h) {
return &ctx->sock[i];
}
}
return NULL;
}
/**
* @brief Find the free socket
* @param ctx - pointer to the socket context
* @return pointer to the free socket
*/
static BSC_SOCKET *bsc_find_free_socket(BSC_SOCKET_CTX *ctx)
{
size_t i;
for (i = 0; i < ctx->sock_num; i++) {
if (ctx->sock[i].state == BSC_SOCK_STATE_IDLE) {
bsc_reset_socket(&ctx->sock[i]);
return &ctx->sock[i];
}
}
return NULL;
}
/**
* @brief Process the socket error
* @param c - pointer to the socket
* @param reason - error code
*/
static void bsc_srv_process_error(BSC_SOCKET *c, BACNET_ERROR_CODE reason)
{
DEBUG_PRINTF(
"bsc_srv_process_error() >>> c = %p, reason = %s\n", c,
bactext_error_code_name(reason));
c->state = BSC_SOCK_STATE_ERROR;
c->reason = reason;
bws_srv_disconnect(c->ctx->sh, c->wh);
DEBUG_PRINTF_VERBOSE("bsc_srv_process_error() <<<\n");
}
/**
* @brief Process the socket error
* @param c - pointer to the socket
* @param reason - error code
*/
static void bsc_cli_process_error(BSC_SOCKET *c, BACNET_ERROR_CODE reason)
{
DEBUG_PRINTF(
"bsc_cli_process_error() >>> c = %p, reason = %s\n", c,
bactext_error_code_name(reason));
c->state = BSC_SOCK_STATE_ERROR;
c->reason = reason;
bws_cli_disconnect(c->wh);
DEBUG_PRINTF_VERBOSE("bsc_cli_process_error() <<<\n");
}
/**
* @brief Prepare the error message
* @param c - pointer to the socket
* @param origin - pointer to the origin VMAC address
* @param dest - pointer to the destination VMAC address
* @param bvlc_function - BVLC function
* @param error_header_marker - pointer to the error header marker
* @param error_class - error class
* @param error_code - error code
* @param utf8_details_string - UTF-8 reason text
* @return true if the error was prepared, otherwise false
*/
static bool bsc_prepare_error_extended(
BSC_SOCKET *c,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t bvlc_function,
uint8_t *error_header_marker,
BACNET_ERROR_CLASS error_class,
BACNET_ERROR_CODE error_code,
const char *utf8_details_string)
{
uint16_t eclass = (uint16_t)error_class;
uint16_t ecode = (uint16_t)error_code;
size_t len;
uint16_t message_id;
DEBUG_PRINTF(
"bsc_prepare_error_extended() >>> bvlc_function = %d\n", bvlc_function);
#if DEBUG_BSC_SOCKET == 1
if (error_header_marker) {
DEBUG_PRINTF(
" error_header_marker = %d\n",
*error_header_marker);
}
if (error_class) {
DEBUG_PRINTF(
" error_class = %d\n", error_class);
}
if (error_code) {
DEBUG_PRINTF(
" error_code = %d\n", error_code);
}
if (utf8_details_string) {
DEBUG_PRINTF(
" utf8_details_string = %s\n",
utf8_details_string);
}
if (origin) {
DEBUG_PRINTF(
" origin = %s\n",
bsc_vmac_to_string(origin));
}
if (dest) {
DEBUG_PRINTF(
" dest = %s\n",
bsc_vmac_to_string(dest));
}
#endif
message_id = bsc_get_next_message_id();
DEBUG_PRINTF(
" message_id = %04x\n", message_id);
len = bvlc_sc_encode_result(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c), message_id, origin, dest,
bvlc_function, 1, error_header_marker, &eclass, &ecode,
utf8_details_string);
if (len) {
TX_BUF_UPDATE(c, len);
DEBUG_PRINTF(
"bsc_prepare_error_extended() <<< ret = true, pdu_len = %d\n", len);
return true;
}
DEBUG_PRINTF_VERBOSE("bsc_prepare_error_extended() <<< ret = false\n");
return false;
}
/**
* @brief Prepare the protocol error extended message
* @param c - pointer to the socket
* @param dm - pointer to the decoded message
* @param origin - pointer to the origin VMAC address
* @param dest - pointer to the destination VMAC address
* @param error_header_marker - pointer to the error header marker
* @param error_class - error class
* @param error_code - error code
* @param utf8_details_string - UTF-8 reason text
* @return true if the error was prepared, otherwise false
*/
static bool bsc_prepare_protocol_error_extended(
BSC_SOCKET *c,
BVLC_SC_DECODED_MESSAGE *dm,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *error_header_marker,
BACNET_ERROR_CLASS error_class,
BACNET_ERROR_CODE error_code,
const char *utf8_details_string)
{
if (bvlc_sc_need_send_bvlc_result(dm)) {
return bsc_prepare_error_extended(
c, origin, dest, BVLC_SC_RESULT, error_header_marker, error_class,
error_code, utf8_details_string);
}
return false;
}
/**
* @brief Prepare the protocol error message
* @param c - pointer to the socket
* @param dm - pointer to the decoded message
* @param origin - pointer to the origin VMAC address
* @param dest - pointer to the destination VMAC address
* @param error_class - error class
* @param error_code - error code
* @param utf8_details_string - UTF-8 reason text
* @return true if the error was prepared, otherwise false
*/
static bool bsc_prepare_protocol_error(
BSC_SOCKET *c,
BVLC_SC_DECODED_MESSAGE *dm,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
BACNET_ERROR_CLASS error_class,
BACNET_ERROR_CODE error_code,
const char *utf8_details_string)
{
DEBUG_PRINTF(
"Socket %p Error: %s %s\n", c, bactext_error_class_name(error_class),
bactext_error_code_name(error_code));
return bsc_prepare_protocol_error_extended(
c, dm, origin, dest, NULL, error_class, error_code,
utf8_details_string);
}
/**
* @brief Clear the VMAC and UUID for BACnet Secure Connect socket
* @param c - pointer to the socket
*/
static void bsc_clear_vmac_and_uuid(BSC_SOCKET *c)
{
memset(&c->vmac, 0, sizeof(c->vmac));
memset(&c->uuid, 0, sizeof(c->uuid));
}
/**
* @brief Set the BACnet Secure Connect socket to the idle state
* @param c - pointer to the socket
*/
static void bsc_set_socket_idle(BSC_SOCKET *c)
{
c->state = BSC_SOCK_STATE_IDLE;
c->wh = BSC_WEBSOCKET_INVALID_HANDLE;
}
/**
* @brief Set the BACnet Secure Connect socket to the disconnecting state
* @param c - pointer to the socket
* @param dm - pointer to the decoded message
* @param buf - pointer to the buffer
* @param buflen - buffer length
* @param need_disconnect - pointer to the flag
*/
static void bsc_process_socket_disconnecting(
BSC_SOCKET *c,
BVLC_SC_DECODED_MESSAGE *dm,
uint8_t *buf,
size_t buflen,
bool *need_disconnect)
{
DEBUG_PRINTF_VERBOSE("bsc_process_socket_disconnecting() >>> c = %p\n", c);
if (dm->hdr.bvlc_function == BVLC_SC_DISCONNECT_ACK) {
#if DEBUG_BSC_SOCKET == 1
if (dm->hdr.message_id != c->expected_disconnect_message_id) {
DEBUG_PRINTF(
"bsc_process_socket_disconnecting() got disconnect ack with "
"unexpected message id %04x for socket %p\n",
dm->hdr.message_id, c);
} else {
DEBUG_PRINTF(
"bsc_process_socket_disconnecting() got disconnect ack for "
"socket %p\n",
c);
}
#endif
*need_disconnect = true;
} else if (dm->hdr.bvlc_function == BVLC_SC_RESULT) {
if (dm->payload.result.bvlc_function == BVLC_SC_DISCONNECT_REQUEST &&
dm->payload.result.result != 0) {
DEBUG_PRINTF(
"bsc_process_socket_disconnecting() got BVLC_SC_RESULT "
"NAK on BVLC_SC_DISCONNECT_REQUEST\n");
*need_disconnect = true;
}
} else if (
dm->hdr.bvlc_function == BVLC_SC_ENCAPSULATED_NPDU ||
dm->hdr.bvlc_function == BVLC_SC_ADDRESS_RESOLUTION ||
dm->hdr.bvlc_function == BVLC_SC_ADDRESS_RESOLUTION_ACK ||
dm->hdr.bvlc_function == BVLC_SC_ADVERTISIMENT ||
dm->hdr.bvlc_function == BVLC_SC_ADVERTISIMENT_SOLICITATION ||
dm->hdr.bvlc_function == BVLC_SC_PROPRIETARY_MESSAGE) {
DEBUG_PRINTF(
"bsc_process_socket_disconnecting() emit received event "
"buf = %p, size = %d\n",
buf, buflen);
c->ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_RECEIVED, 0, NULL, buf, buflen, dm);
}
DEBUG_PRINTF_VERBOSE("bsc_process_socket_disconnecting() <<<\n");
}
/**
* @brief Process the socket connected state
* @param c - pointer to the socket
* @param dm - pointer to the decoded message
* @param buf - pointer to the buffer
* @param buflen - buffer length
* @param need_disconnect - pointer to the flag
* @param need_send - pointer to the flag
*/
static void bsc_process_socket_connected_state(
BSC_SOCKET *c,
BVLC_SC_DECODED_MESSAGE *dm,
uint8_t *buf,
size_t buflen,
bool *need_disconnect,
bool *need_send)
{
uint16_t message_id;
size_t len;
DEBUG_PRINTF_VERBOSE(
"bsc_process_socket_connected_state() >>> c = %p, dm = %p, buf = %p, "
"buflen = %d\n",
c, dm, buf, buflen);
if (dm->hdr.bvlc_function == BVLC_SC_HEARTBEAT_ACK) {
if (dm->hdr.message_id != c->expected_heartbeat_message_id) {
DEBUG_PRINTF_VERBOSE(
"bsc_process_socket_connected_state() got heartbeat ack with "
"unexpected message id %04x for socket %p\n",
dm->hdr.message_id, c);
} else {
DEBUG_PRINTF_VERBOSE(
"bsc_process_socket_connected_state() got heartbeat ack for "
"socket %p\n",
c);
}
} else if (dm->hdr.bvlc_function == BVLC_SC_HEARTBEAT_REQUEST) {
DEBUG_PRINTF(
"bsc_process_socket_connected_state() got heartbeat "
"request with message id %04x\n",
dm->hdr.message_id);
message_id = dm->hdr.message_id;
len = bvlc_sc_encode_heartbeat_ack(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c), message_id);
if (len) {
TX_BUF_UPDATE(c, len);
*need_send = true;
} else {
DEBUG_PRINTF(
"bsc_process_socket_connected_state() no resources to "
"answer on heartbeat request "
"socket %p\n",
c);
}
} else if (dm->hdr.bvlc_function == BVLC_SC_DISCONNECT_REQUEST) {
DEBUG_PRINTF(
"bsc_process_socket_connected_state() got disconnect "
"request with message id %04x\n",
dm->hdr.message_id);
message_id = dm->hdr.message_id;
len = bvlc_sc_encode_disconnect_ack(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c), message_id);
if (len) {
TX_BUF_UPDATE(c, len);
c->reason = ERROR_CODE_SUCCESS;
c->state = BSC_SOCK_STATE_ERROR_FLUSH_TX;
*need_send = true;
} else {
DEBUG_PRINTF(
"bsc_process_socket_connected_state() no resources to answer "
"on disconnect request, just disconnecting without ack\n");
c->state = BSC_SOCK_STATE_DISCONNECTING;
*need_disconnect = true;
}
} else if (dm->hdr.bvlc_function == BVLC_SC_DISCONNECT_ACK) {
/* This is unexpected! We assume that the remote peer is confused and */
/* thought we sent a Disconnect-Request so we'll close the socket */
/* and hope that remote peer clears itself up. */
DEBUG_PRINTF(
"bsc_process_socket_connected_state() got unexpected "
"disconnect ack with message id %04x\n",
dm->hdr.message_id);
c->state = BSC_SOCK_STATE_DISCONNECTING;
*need_disconnect = true;
} else if (dm->hdr.bvlc_function == BVLC_SC_RESULT) {
DEBUG_PRINTF(
"bsc_process_socket_connected_state() emit received event "
"buf = %p, size = %d\n",
buf, buflen);
c->ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_RECEIVED, 0, NULL, buf, buflen, dm);
} else if (
dm->hdr.bvlc_function == BVLC_SC_ENCAPSULATED_NPDU ||
dm->hdr.bvlc_function == BVLC_SC_ADDRESS_RESOLUTION ||
dm->hdr.bvlc_function == BVLC_SC_ADDRESS_RESOLUTION_ACK ||
dm->hdr.bvlc_function == BVLC_SC_ADVERTISIMENT ||
dm->hdr.bvlc_function == BVLC_SC_ADVERTISIMENT_SOLICITATION ||
dm->hdr.bvlc_function == BVLC_SC_PROPRIETARY_MESSAGE) {
DEBUG_PRINTF(
"bsc_process_socket_connected_state() emit received event "
"buf = %p, size = %d\n",
buf, buflen);
c->ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_RECEIVED, 0, NULL, buf, buflen, dm);
}
DEBUG_PRINTF_VERBOSE("bsc_process_socket_connected_state() <<<\n");
}
/**
* @brief Process the socket state
* @param c - pointer to the socket
* @param dm - pointer to the decoded message
* @param rx_buf - pointer to the buffer
* @param rx_buf_size - buffer size
* @param need_disconnect - pointer to the flag
* @param need_send - pointer to the flag
*/
static void bsc_process_socket_state(
BSC_SOCKET *c,
BVLC_SC_DECODED_MESSAGE *dm,
uint8_t *rx_buf,
size_t rx_buf_size,
bool *need_disconnect,
bool *need_send)
{
bool expired;
uint16_t error_class;
uint16_t error_code;
const char *err_desc = NULL;
bool valid = true;
size_t len;
DEBUG_PRINTF_VERBOSE(
"bsc_process_socket_state() >>> ctx = %p, c = %p, state = %d, "
"rx_buf = %p, rx_buf_size = %d\n",
c->ctx, c, c->state, rx_buf, rx_buf_size);
if (rx_buf) {
if (!bvlc_sc_decode_message(
rx_buf, rx_buf_size, dm, &error_code, &error_class,
&err_desc)) {
/* we use this error code+class to indicate that the received bvlc
message has length less than 4 octets.
According EA-001-4 'Clarifying BVLC-Result in BACnet/SC
'If a BVLC message is received that has fewer than four octets,
a BVLC-Result NAK shall not be returned. The message shall be
discarded and not be processed.' */
if ((error_code != ERROR_CODE_DISCARD) &&
(error_class != ERROR_CLASS_COMMUNICATION)) {
*need_send = bsc_prepare_protocol_error(
c, dm, dm->hdr.origin, dm->hdr.dest, error_class,
error_code, err_desc);
} else {
DEBUG_PRINTF(
"bsc_process_socket_state() decoding failed, message "
"is silently dropped cause it's length < 4 bytes\n");
}
} else {
DEBUG_PRINTF_VERBOSE(
"bsc_process_socket_state() "
"bvlc_function %s, message id %04x\n",
bsc_bvlc_message_type_to_string(dm->hdr.bvlc_function),
dm->hdr.message_id);
if (dm->hdr.bvlc_function == BVLC_SC_ENCAPSULATED_NPDU ||
dm->hdr.bvlc_function == BVLC_SC_ADVERTISIMENT ||
dm->hdr.bvlc_function == BVLC_SC_ADDRESS_RESOLUTION_ACK ||
dm->hdr.bvlc_function == BVLC_SC_ADDRESS_RESOLUTION ||
dm->hdr.bvlc_function == BVLC_SC_ADVERTISIMENT_SOLICITATION ||
dm->hdr.bvlc_function == BVLC_SC_RESULT) {
if (c->ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR &&
c->ctx->cfg->proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
/* this is a case when socket is a hub connector
receiving from hub */
if (dm->hdr.origin == NULL &&
dm->hdr.bvlc_function != BVLC_SC_RESULT) {
error_class = ERROR_CLASS_COMMUNICATION;
error_code = ERROR_CODE_HEADER_ENCODING_ERROR;
*need_send = bsc_prepare_protocol_error(
c, dm, NULL, &c->vmac, error_class, error_code,
s_error_no_origin);
valid = false;
} else if (
dm->hdr.dest != NULL &&
!bvlc_sc_is_vmac_broadcast(dm->hdr.dest)) {
error_class = ERROR_CLASS_COMMUNICATION;
error_code = ERROR_CODE_HEADER_ENCODING_ERROR;
*need_send = bsc_prepare_protocol_error(
c, dm, NULL, &c->vmac, error_class, error_code,
s_error_dest_presented);
valid = false;
}
} else if (
c->ctx->cfg->type == BSC_SOCKET_CTX_ACCEPTOR &&
c->ctx->cfg->proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
/* this is a case when socket is hub function
receiving from node */
if (dm->hdr.dest == NULL) {
error_class = ERROR_CLASS_COMMUNICATION;
error_code = ERROR_CODE_HEADER_ENCODING_ERROR;
*need_send = bsc_prepare_protocol_error(
c, dm, NULL, NULL, error_class, error_code,
s_error_no_dest);
valid = false;
} else if (dm->hdr.origin != NULL) {
error_class = ERROR_CLASS_COMMUNICATION;
error_code = ERROR_CODE_HEADER_ENCODING_ERROR;
*need_send = bsc_prepare_protocol_error(
c, dm, NULL, NULL, error_class, error_code,
s_error_origin_presented);
valid = false;
}
}
}
/* every valid message restarts heartbeat timeout */
/* and only valid messages are processed */
if (valid) {
if (c->ctx->cfg->type == BSC_SOCKET_CTX_ACCEPTOR) {
mstimer_set(
&c->heartbeat,
2 * c->ctx->cfg->heartbeat_timeout_s * 1000);
} else {
mstimer_set(
&c->heartbeat, c->ctx->cfg->heartbeat_timeout_s * 1000);
}
if (c->state == BSC_SOCK_STATE_CONNECTED) {
bsc_process_socket_connected_state(
c, dm, rx_buf, rx_buf_size, need_disconnect, need_send);
} else if (c->state == BSC_SOCK_STATE_DISCONNECTING) {
bsc_process_socket_disconnecting(
c, dm, rx_buf, rx_buf_size, need_disconnect);
}
}
}
}
expired = mstimer_expired(&c->t);
DEBUG_PRINTF_VERBOSE(
"BSC-Socket: connection mstimer_expired() = %d\n", expired);
if (c->state == BSC_SOCK_STATE_AWAITING_ACCEPT && expired) {
c->state = BSC_SOCK_STATE_ERROR;
c->reason = ERROR_CODE_TIMEOUT;
*need_disconnect = true;
DEBUG_PRINTF("BSC-Socket: connection timeout AWAITING_ACCEPT.\n");
} else if (c->state == BSC_SOCK_STATE_AWAITING_REQUEST && expired) {
c->state = BSC_SOCK_STATE_ERROR;
c->reason = ERROR_CODE_TIMEOUT;
*need_disconnect = true;
DEBUG_PRINTF("BSC-Socket: connection timeout AWAITING_REQUEST.\n");
} else if (c->state == BSC_SOCK_STATE_DISCONNECTING && expired) {
c->state = BSC_SOCK_STATE_ERROR;
c->reason = ERROR_CODE_TIMEOUT;
*need_disconnect = true;
DEBUG_PRINTF("BSC-Socket: connection timeout DISCONNECTING.\n");
} else if (c->state == BSC_SOCK_STATE_CONNECTED) {
expired = mstimer_expired(&c->heartbeat);
DEBUG_PRINTF_VERBOSE(
"BSC-Socket: heartbeat mstimer_expired() = %d\n", expired);
if (expired) {
DEBUG_PRINTF_VERBOSE(
"BSC-Socket: heartbeat timeout elapsed "
"for socket %p\n",
c);
if (c->ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
DEBUG_PRINTF(
"BSC-Socket: sending heartbeat request on connection %p\n",
c);
c->expected_heartbeat_message_id = bsc_get_next_message_id();
DEBUG_PRINTF_VERBOSE(
"BSC-Socket: heartbeat message id %04x\n",
c->expected_heartbeat_message_id);
len = bvlc_sc_encode_heartbeat_request(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c),
c->expected_heartbeat_message_id);
if (len) {
TX_BUF_UPDATE(c, len);
mstimer_set(
&c->heartbeat, c->ctx->cfg->heartbeat_timeout_s * 1000);
*need_send = true;
} else {
DEBUG_PRINTF(
"BSC-Socket: sending of "
"heartbeat request failed on connection %p\n",
c);
}
} else if (c->ctx->cfg->type == BSC_SOCKET_CTX_ACCEPTOR) {
DEBUG_PRINTF(
"BSC-Socket: zombie socket %p is "
"disconnecting by timeout.\n",
c);
c->state = BSC_SOCK_STATE_ERROR;
c->reason = ERROR_CODE_TIMEOUT;
*need_disconnect = true;
}
}
}
DEBUG_PRINTF_VERBOSE("bsc_process_socket_state() <<<\n");
}
/**
* @brief Run the socket loop
* @param s - pointer to the socket
* @param dm - pointer to the decoded message
* @param rx_buf - pointer to the buffer
* @param rx_buf_size - buffer size
*/
static void bsc_runloop_socket(
BSC_SOCKET *s,
BVLC_SC_DECODED_MESSAGE *dm,
uint8_t *rx_buf,
size_t rx_buf_size)
{
bool need_disconnect = false;
bool need_send = false;
if (s->state != BSC_SOCK_STATE_IDLE) {
bsc_process_socket_state(
s, dm, rx_buf, rx_buf_size, &need_disconnect, &need_send);
if (need_disconnect) {
if (s->ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
bws_cli_disconnect(s->wh);
} else {
bws_srv_disconnect(s->ctx->sh, s->wh);
}
}
if (need_send) {
if (s->ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
bws_cli_send(s->wh);
} else {
bws_srv_send(s->ctx->sh, s->wh);
}
}
}
}
/**
* @brief Run the socket maintenance timer process
* @param seconds - time in seconds
*/
void bsc_socket_maintenance_timer(uint16_t seconds)
{
int i, j, count = 0;
DEBUG_PRINTF_VERBOSE(
"bsc_socket_maintenance_timer(%us) >>>\n", (unsigned)seconds);
bws_dispatch_lock();
for (i = 0; i < BSC_SOCKET_CTX_NUM; i++) {
if (bsc_socket_ctx[i] != NULL) {
if (bsc_socket_ctx[i]->state == BSC_CTX_STATE_INITIALIZED) {
for (j = 0; j < bsc_socket_ctx[i]->sock_num; j++) {
count++;
bsc_runloop_socket(
&bsc_socket_ctx[i]->sock[j], NULL, NULL, 0);
}
}
}
}
bws_dispatch_unlock();
DEBUG_PRINTF_VERBOSE(
"bsc_socket_maintenance_timer() <<< %d sockets processed\n", count);
}
/**
* @brief Process the server awaiting request state
* @param c - pointer to the socket
* @param dm - pointer to the decoded message
* @param buf - pointer to the buffer
* @param bufsize - buffer size
*/
static void bsc_process_srv_awaiting_request(
BSC_SOCKET *c, BVLC_SC_DECODED_MESSAGE *dm, uint8_t *buf, size_t bufsize)
{
uint16_t error_class;
uint16_t error_code;
BSC_SOCKET *existing = NULL;
uint16_t message_id;
size_t len;
uint16_t ucode;
uint16_t uclass;
const char *err_desc = NULL;
DEBUG_PRINTF_VERBOSE(
"bsc_process_srv_awaiting_request() >>> c = %p, dm = %p, buf = %p, "
"bufsize = %d\n",
c, dm, buf, bufsize);
if (!bvlc_sc_decode_message(
buf, bufsize, dm, &error_code, &error_class, &err_desc)) {
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() decoding of received message "
"failed, error code = %d, class = %d\n",
error_code, error_class);
if (c->ctx->funcs->failed_request) {
c->ctx->funcs->failed_request(
c->ctx, c, NULL, NULL, error_code, err_desc);
}
} else if (dm->hdr.bvlc_function == BVLC_SC_CONNECT_REQUEST) {
existing = c->ctx->funcs->find_connection_for_uuid(
dm->payload.connect_request.uuid, c->ctx->user_arg);
if (existing) {
/* Regarding AB.6.2.3 BACnet/SC Connection Accepting Peer */
/* State Machine: On receipt of a Connect-Request message */
/* from the initiating peer whose 'Device UUID' is equal to the */
/* initiating peer device UUID of an existing connection, */
/* then return a Connect-Accept message, disconnect and */
/* close the existing connection to the connection peer node */
/* with matching Device UUID, and enter the CONNECTED state. */
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() accepting connection from "
"known uuid %s\n and vmac %s\n",
bsc_uuid_to_string(dm->payload.connect_request.uuid),
bsc_vmac_to_string(dm->payload.connect_request.vmac));
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() existing = %p, "
"existing->state = %s, c = %p\n",
existing, bsc_socket_state_to_string(existing->state), c);
bsc_copy_vmac(&c->vmac, dm->payload.connect_request.vmac);
bsc_copy_uuid(&c->uuid, dm->payload.connect_request.uuid);
c->max_npdu_len = dm->payload.connect_request.max_npdu_len;
c->max_bvlc_len = dm->payload.connect_request.max_bvlc_len;
message_id = dm->hdr.message_id;
len = bvlc_sc_encode_connect_accept(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c), message_id,
&c->ctx->cfg->local_vmac, &c->ctx->cfg->local_uuid,
c->ctx->cfg->max_bvlc_len, c->ctx->cfg->max_ndpu_len);
if (!len) {
if (c->ctx->funcs->failed_request) {
c->ctx->funcs->failed_request(
c->ctx, c, dm->payload.connect_request.vmac,
dm->payload.connect_request.uuid,
ERROR_CODE_ABORT_OUT_OF_RESOURCES, NULL);
}
bsc_srv_process_error(c, ERROR_CODE_ABORT_OUT_OF_RESOURCES);
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() connect_accept "
"encoding failed, err = ABORT_OUT_OF_RESOURCES\n");
return;
} else {
TX_BUF_UPDATE(c, len);
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() request to "
"send connect accept to socket %d(%p)\n",
c->wh, c);
bws_srv_send(c->ctx->sh, c->wh);
}
existing->expected_disconnect_message_id =
bsc_get_next_message_id();
len = bvlc_sc_encode_disconnect_request(
TX_BUF_PTR(existing), TX_BUF_BYTES_AVAIL(existing),
existing->expected_disconnect_message_id);
if (len) {
TX_BUF_UPDATE(existing, len);
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() request to "
"send disconnect request with message id %04x "
" to existing socket %d(%p)\n",
existing->expected_disconnect_message_id, existing->wh,
existing);
bws_srv_send(existing->ctx->sh, existing->wh);
} else {
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() sending of "
"disconnect request to existing socket (%p) failed. "
"err = BSC_SC_NO_RESOURCES\n",
c);
}
DEBUG_PRINTF_VERBOSE(
"bsc_process_srv_awaiting_request() set socket %p to "
"connected state\n",
c);
mstimer_set(
&c->heartbeat, 2 * c->ctx->cfg->heartbeat_timeout_s * 1000);
c->state = BSC_SOCK_STATE_CONNECTED;
c->ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_CONNECTED, 0, NULL, NULL, 0, NULL);
DEBUG_PRINTF_VERBOSE("bsc_process_srv_awaiting_request() <<<\n");
return;
}
existing = c->ctx->funcs->find_connection_for_vmac(
dm->payload.connect_request.vmac, c->ctx->user_arg);
if (existing) {
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() rejected connection for "
"duplicated vmac %s from uuid %s,"
" vmac is used by uuid %s\n",
bsc_vmac_to_string(dm->payload.connect_request.vmac),
bsc_uuid_to_string(dm->payload.connect_request.uuid),
bsc_uuid_to_string(&existing->uuid));
uclass = ERROR_CLASS_COMMUNICATION;
ucode = ERROR_CODE_NODE_DUPLICATE_VMAC;
message_id = dm->hdr.message_id;
if (c->ctx->funcs->failed_request) {
c->ctx->funcs->failed_request(
c->ctx, c, dm->payload.connect_request.vmac,
dm->payload.connect_request.uuid,
ERROR_CODE_NODE_DUPLICATE_VMAC, NULL);
}
len = bvlc_sc_encode_result(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c), message_id, NULL, NULL,
BVLC_SC_CONNECT_REQUEST, 1, NULL, &uclass, &ucode, NULL);
if (len) {
TX_BUF_UPDATE(c, len);
c->state = BSC_SOCK_STATE_ERROR_FLUSH_TX;
c->reason = ERROR_CODE_NODE_DUPLICATE_VMAC;
bws_srv_send(c->ctx->sh, c->wh);
} else {
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() sending of nack result "
"message failed, err = BSC_SC_NO_RESOURCES\n");
bsc_srv_process_error(c, ERROR_CODE_NODE_DUPLICATE_VMAC);
}
DEBUG_PRINTF_VERBOSE("bsc_process_srv_awaiting_request() <<<\n");
return;
}
bsc_copy_vmac(&c->vmac, dm->payload.connect_request.vmac);
bsc_copy_uuid(&c->uuid, dm->payload.connect_request.uuid);
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() local vmac = %s, "
"local uuid = %s\n",
bsc_vmac_to_string(&c->ctx->cfg->local_vmac),
bsc_uuid_to_string(&c->ctx->cfg->local_uuid));
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() remote vmac = %s, "
"remote uuid = %s\n",
bsc_vmac_to_string(&c->vmac), bsc_uuid_to_string(&c->uuid));
if (memcmp(
&c->vmac.address, &c->ctx->cfg->local_vmac.address,
sizeof(c->ctx->cfg->local_vmac.address)) == 0 &&
memcmp(
&c->uuid.uuid, &c->ctx->cfg->local_uuid.uuid,
sizeof(c->ctx->cfg->local_uuid.uuid)) != 0) {
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() rejected "
"connection of a duplicate "
"of this port's vmac %s from uuid %s\n",
bsc_vmac_to_string(&c->vmac), bsc_uuid_to_string(&c->uuid));
uclass = ERROR_CLASS_COMMUNICATION;
ucode = ERROR_CODE_NODE_DUPLICATE_VMAC;
message_id = dm->hdr.message_id;
if (c->ctx->funcs->failed_request) {
c->ctx->funcs->failed_request(
c->ctx, c, &c->vmac, &c->uuid,
ERROR_CODE_NODE_DUPLICATE_VMAC, NULL);
}
len = bvlc_sc_encode_result(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c), message_id, NULL, NULL,
BVLC_SC_CONNECT_REQUEST, 1, NULL, &uclass, &ucode, NULL);
if (len) {
TX_BUF_UPDATE(c, len);
c->state = BSC_SOCK_STATE_ERROR_FLUSH_TX;
c->reason = ERROR_CODE_NODE_DUPLICATE_VMAC;
bws_srv_send(c->ctx->sh, c->wh);
} else {
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() sending of nack result "
"message failed, err = BSC_SC_NO_RESOURCES\n");
bsc_srv_process_error(c, ERROR_CODE_NODE_DUPLICATE_VMAC);
}
DEBUG_PRINTF_VERBOSE("bsc_process_srv_awaiting_request() <<<\n");
return;
}
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() accepted connection from new "
"uuid %s with vmac %s\n",
bsc_uuid_to_string(&c->uuid), bsc_vmac_to_string(&c->vmac));
message_id = dm->hdr.message_id;
len = bvlc_sc_encode_connect_accept(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c), message_id,
&c->ctx->cfg->local_vmac, &c->ctx->cfg->local_uuid,
c->ctx->cfg->max_bvlc_len, c->ctx->cfg->max_ndpu_len);
if (len) {
TX_BUF_UPDATE(c, len);
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() set socket %p to "
"connected state\n",
c);
mstimer_set(
&c->heartbeat, 2 * c->ctx->cfg->heartbeat_timeout_s * 1000);
c->state = BSC_SOCK_STATE_CONNECTED;
c->ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_CONNECTED, 0, NULL, NULL, 0, NULL);
bws_srv_send(c->ctx->sh, c->wh);
} else {
DEBUG_PRINTF("bsc_process_srv_awaiting_request() sending of "
"connect accept failed, err = BSC_SC_NO_RESOURCES\n");
if (c->ctx->funcs->failed_request) {
c->ctx->funcs->failed_request(
c->ctx, c, &c->vmac, &c->uuid,
ERROR_CODE_ABORT_OUT_OF_RESOURCES, NULL);
}
bsc_srv_process_error(c, ERROR_CODE_ABORT_OUT_OF_RESOURCES);
}
}
#if DEBUG_BSC_SOCKET == 1
else {
DEBUG_PRINTF(
"bsc_process_srv_awaiting_request() unexpected message "
"with bvlc function "
"%d is discarded in awaiting request state\n",
dm->hdr.bvlc_function);
}
#endif
DEBUG_PRINTF_VERBOSE("bsc_process_srv_awaiting_request() <<<\n");
}
/**
* @brief Dispatch the server function
* @param sh - pointer to the server handle
* @param h - handle
* @param ev - event
* @param ws_reason - reason
* @param ws_reason_desc - reason description
* @param buf - pointer to the buffer
* @param bufsize - buffer size
* @param dispatch_func_user_param - pointer to the user parameter
*/
static void bsc_dispatch_srv_func(
BSC_WEBSOCKET_SRV_HANDLE sh,
BSC_WEBSOCKET_HANDLE h,
BSC_WEBSOCKET_EVENT ev,
BACNET_ERROR_CODE ws_reason,
char *ws_reason_desc,
uint8_t *buf,
size_t bufsize,
void *dispatch_func_user_param)
{
BSC_SOCKET_CTX *ctx = (BSC_SOCKET_CTX *)dispatch_func_user_param;
BSC_SOCKET *c = NULL;
BSC_WEBSOCKET_RET wret;
uint8_t *p;
bool failed = false;
uint16_t len;
size_t i;
(void)sh;
bws_dispatch_lock();
DEBUG_PRINTF_VERBOSE(
"bsc_dispatch_srv_func() >>> sh = %p, h = %d, ev = %d, "
"reason = %d, desc = %p, buf "
"= %p, bufsize = %ld, ctx = %p\n",
sh, h, ev, ws_reason, ws_reason_desc, buf, bufsize, ctx);
if (ev == BSC_WEBSOCKET_SERVER_STOPPED) {
for (i = 0; i < ctx->sock_num; i++) {
ctx->sock[i].state = BSC_SOCK_STATE_IDLE;
}
DEBUG_PRINTF("bsc_dispatch_srv_func() ctx %p is deinitialized\n", ctx);
bsc_ctx_remove(ctx);
ctx->state = BSC_CTX_STATE_IDLE;
ctx->funcs->context_event(ctx, BSC_CTX_DEINITIALIZED);
bsc_socket_maintenance_timer(0);
DEBUG_PRINTF("bsc_dispatch_srv_func() <<<\n");
bws_dispatch_unlock();
return;
} else if (ev == BSC_WEBSOCKET_SERVER_STARTED) {
ctx->state = BSC_CTX_STATE_INITIALIZED;
DEBUG_PRINTF("bsc_dispatch_srv_func() ctx %p is initialized\n", ctx);
ctx->funcs->context_event(ctx, BSC_CTX_INITIALIZED);
bsc_socket_maintenance_timer(0);
DEBUG_PRINTF("bsc_dispatch_srv_func() <<<\n");
bws_dispatch_unlock();
return;
}
if (ev != BSC_WEBSOCKET_CONNECTED) {
c = bsc_find_conn_by_websocket(ctx, h);
if (!c) {
DEBUG_PRINTF(
"bsc_dispatch_srv_func() can not find socket "
"descriptor for websocket %d\n",
h);
DEBUG_PRINTF("bsc_dispatch_srv_func() <<<\n");
bws_dispatch_unlock();
return;
}
DEBUG_PRINTF_VERBOSE(
"bsc_dispatch_srv_func() socket %p, state = %d\n", c, c->state);
}
if (ev == BSC_WEBSOCKET_DISCONNECTED) {
if (c->state == BSC_SOCK_STATE_ERROR) {
bsc_set_socket_idle(c);
ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_DISCONNECTED, c->reason, NULL, NULL, 0,
NULL);
bsc_clear_vmac_and_uuid(c);
} else {
bsc_set_socket_idle(c);
ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_DISCONNECTED, ws_reason, ws_reason_desc,
NULL, 0, NULL);
bsc_clear_vmac_and_uuid(c);
}
} else if (ev == BSC_WEBSOCKET_CONNECTED) {
c = bsc_find_free_socket(ctx);
if (!c) {
DEBUG_PRINTF("bsc_dispatch_srv_func() no free socket, connection "
"is dropped\n");
bws_srv_disconnect(ctx->sh, h);
} else {
bsc_reset_socket(c);
c->wh = h;
c->ctx = ctx;
c->state = BSC_SOCK_STATE_AWAITING_REQUEST;
mstimer_set(&c->t, c->ctx->cfg->connect_timeout_s * 1000);
}
} else if (ev == BSC_WEBSOCKET_RECEIVED) {
DEBUG_PRINTF(
"bsc_dispatch_srv_func() BSC_WEBSOCKET_RECEIVED event "
"socket %p, state = %s\n",
c, bsc_socket_state_to_string(c->state));
if (c->state == BSC_SOCK_STATE_AWAITING_REQUEST) {
bsc_process_srv_awaiting_request(c, &bsc_dm, buf, bufsize);
} else if (
c->state == BSC_SOCK_STATE_DISCONNECTING ||
c->state == BSC_SOCK_STATE_CONNECTED) {
bsc_runloop_socket(c, &bsc_dm, buf, bufsize);
} else {
DEBUG_PRINTF(
"bsc_dispatch_srv_func() data was dropped for socket "
"%p, state %s, data_size %d\n",
c, bsc_socket_state_to_string(c->state), bufsize);
}
} else if (ev == BSC_WEBSOCKET_SENDABLE) {
p = c->tx_buf;
while (c->tx_buf_size > 0) {
memcpy(&len, p, sizeof(len));
wret = bws_srv_dispatch_send(
c->ctx->sh, c->wh, &p[sizeof(len) + BSC_CONF_TX_PRE], len);
if (wret != BSC_WEBSOCKET_SUCCESS) {
DEBUG_PRINTF(
"bsc_dispatch_srv_func() send data failed. "
"Error=%s, start disconnect operation on socket %p\n",
bsc_websocket_return_to_string(wret), c);
bsc_srv_process_error(
c,
c->state != BSC_SOCK_STATE_ERROR_FLUSH_TX
? ERROR_CODE_ABORT_OUT_OF_RESOURCES
: c->reason);
failed = true;
break;
} else {
c->tx_buf_size -= len + sizeof(len) + BSC_CONF_TX_PRE;
p += len + sizeof(len) + BSC_CONF_TX_PRE;
}
}
if (!failed) {
if (c->state == BSC_SOCK_STATE_ERROR_FLUSH_TX) {
bsc_srv_process_error(c, c->reason);
}
}
}
bsc_socket_maintenance_timer(0);
DEBUG_PRINTF_VERBOSE("bsc_dispatch_srv_func() <<<\n");
bws_dispatch_unlock();
}
/**
* @brief Process the client awaiting accept state
* @param c - pointer to the socket
* @param dm - pointer to the decoded message
* @param buf - pointer to the buffer
* @param bufsize - buffer size
*/
static void bsc_process_cli_awaiting_accept(
BSC_SOCKET *c, BVLC_SC_DECODED_MESSAGE *dm, uint8_t *buf, size_t bufsize)
{
uint16_t error_class;
uint16_t error_code;
const char *err_desc = NULL;
DEBUG_PRINTF_VERBOSE(
"bsc_process_cli_awaiting_accept() >>> c = %p, dm = %p, buf = "
"%p, bufsize = %d\n",
c, dm, buf, bufsize);
if (!bvlc_sc_decode_message(
buf, bufsize, dm, &error_code, &error_class, &err_desc)) {
DEBUG_PRINTF(
"bsc_process_cli_awaiting_accept() <<< decoding failed "
"code = %d, class = %d\n",
error_code, error_class);
return;
}
if (dm->hdr.bvlc_function == BVLC_SC_CONNECT_ACCEPT) {
if (dm->hdr.message_id != c->expected_connect_accept_message_id) {
DEBUG_PRINTF(
"bsc_process_cli_awaiting_accept() got bvlc result packet "
"with unexpected message id %04x\n",
dm->hdr.message_id);
} else {
DEBUG_PRINTF(
"bsc_process_cli_awaiting_accept() set state of "
"socket %p to BSC_SOCKET_EVENT_CONNECTED\n",
c);
bsc_copy_vmac(&c->vmac, dm->payload.connect_accept.vmac);
bsc_copy_uuid(&c->uuid, dm->payload.connect_accept.uuid);
c->max_bvlc_len = dm->payload.connect_accept.max_bvlc_len;
c->max_npdu_len = dm->payload.connect_accept.max_npdu_len;
mstimer_set(&c->heartbeat, c->ctx->cfg->heartbeat_timeout_s * 1000);
c->state = BSC_SOCK_STATE_CONNECTED;
c->ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_CONNECTED, 0, NULL, NULL, 0, NULL);
}
} else if (dm->hdr.bvlc_function == BVLC_SC_RESULT) {
if (dm->payload.result.bvlc_function != BVLC_SC_CONNECT_REQUEST) {
DEBUG_PRINTF(
"bsc_process_cli_awaiting_accept() got unexpected "
"bvlc function %d in "
"BVLC-Result message in awaiting accept state\n",
dm->payload.result.bvlc_function);
} else if (
dm->hdr.message_id != c->expected_connect_accept_message_id) {
DEBUG_PRINTF(
"bsc_process_cli_awaiting_accept() got bvlc result packet "
"with unexpected message id %04x\n",
dm->hdr.message_id);
} else if (
dm->payload.result.error_code == ERROR_CODE_NODE_DUPLICATE_VMAC) {
/* According AB.6.2.2 BACnet/SC Connection Initiating */
/* Peer State Machine: on receipt of a BVLC-Result NAK */
/* message with an 'Error Code' of NODE_DUPLICATE_VMAC, */
/* the initiating peer's node shall choose a new */
/* Random-48 VMAC, close the WebSocket connection, and */
/* enter the IDLE state. */
/* Signal upper layer about that error */
bsc_cli_process_error(c, ERROR_CODE_NODE_DUPLICATE_VMAC);
}
#if DEBUG_BSC_SOCKET == 1
else {
DEBUG_PRINTF(
"bsc_process_cli_awaiting_accept() got unexpected "
"BVLC_RESULT error code "
"%d in BVLC-Result message in awaiting accept state\n",
dm->payload.result.error_code);
}
#endif
} else if (dm->hdr.bvlc_function == BVLC_SC_DISCONNECT_REQUEST) {
/* AB.6.2.2 BACnet/SC Connection Initiating Peer State */
/* Machine does not say anything about situation when */
/* disconnect request is received from remote peer after */
/* connect request. Handle this situation as an error, log */
/* it and close connection. */
DEBUG_PRINTF("bsc_process_cli_awaiting_accept() got unexpected "
"disconnect request\n");
bsc_cli_process_error(c, ERROR_CODE_OTHER);
} else if (dm->hdr.bvlc_function == BVLC_SC_DISCONNECT_ACK) {
/* AB.6.2.2 BACnet/SC Connection Initiating Peer State */
/* Machine does not say anything about situation when */
/* disconnect ack is received from remote peer after connect */
/* request. Handle this situation as an error, log it and */
/* close connection. */
DEBUG_PRINTF(
"bsc_process_cli_awaiting_accept() got unexpected disconnect ack "
"request\n");
bsc_cli_process_error(c, ERROR_CODE_OTHER);
}
#if DEBUG_BSC_SOCKET == 1
else {
DEBUG_PRINTF(
"bsc_process_cli_awaiting_accept() unexpected message "
"with bvlc function "
"%d is discarded in awaiting accept state\n",
dm->hdr.bvlc_function);
}
#endif
DEBUG_PRINTF_VERBOSE("bsc_process_cli_awaiting_accept() <<<\n");
}
/**
* @brief Dispatch the client function
* @param h - handle
* @param ev - event
* @param ws_reason - reason
* @param ws_reason_desc - reason description
* @param buf - pointer to the buffer
* @param bufsize - buffer size
* @param dispatch_func_user_param - pointer to the user parameter
*/
static void bsc_dispatch_cli_func(
BSC_WEBSOCKET_HANDLE h,
BSC_WEBSOCKET_EVENT ev,
BACNET_ERROR_CODE ws_reason,
char *ws_reason_desc,
uint8_t *buf,
size_t bufsize,
void *dispatch_func_user_param)
{
BSC_SOCKET_CTX *ctx = (BSC_SOCKET_CTX *)dispatch_func_user_param;
BSC_SOCKET *c;
size_t len;
uint16_t pdu_len;
BSC_WEBSOCKET_RET wret;
uint8_t *p;
size_t i;
bool all_socket_disconnected = true;
bool failed = false;
bws_dispatch_lock();
DEBUG_PRINTF_VERBOSE(
"bsc_dispatch_cli_func() >>> h = %d, ev = %d, reason = %d, "
"reason_desc = %p, buf = %p, "
"bufsize = %ld, ctx = %p\n",
h, ev, ws_reason, ws_reason_desc, buf, bufsize, ctx);
c = bsc_find_conn_by_websocket(ctx, h);
if (!c) {
DEBUG_PRINTF(
"bsc_dispatch_cli_func() <<< warning, can not find "
"connection object for websocket %d\n",
h);
bws_dispatch_unlock();
return;
}
DEBUG_PRINTF_VERBOSE(
"bsc_dispatch_cli_func() ev = %d, state = %d\n", ev, c->state);
if (ev == BSC_WEBSOCKET_DISCONNECTED) {
DEBUG_PRINTF(
"bsc_dispatch_cli_func() websocket %s ctx->state = %s\n",
bsc_websocket_event_to_string(ev),
bsc_context_state_to_string(ctx->state));
if (ctx->state == BSC_CTX_STATE_DEINITIALIZING) {
bsc_set_socket_idle(c);
bsc_clear_vmac_and_uuid(c);
for (i = 0; i < ctx->sock_num; i++) {
if (ctx->sock[i].state != BSC_SOCK_STATE_IDLE) {
all_socket_disconnected = false;
break;
}
}
if (all_socket_disconnected) {
ctx->state = BSC_CTX_STATE_IDLE;
bsc_ctx_remove(ctx);
ctx->funcs->context_event(ctx, BSC_CTX_DEINITIALIZED);
}
} else if (c->state == BSC_SOCK_STATE_ERROR) {
bsc_set_socket_idle(c);
ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_DISCONNECTED, c->reason, NULL, NULL, 0,
NULL);
bsc_clear_vmac_and_uuid(c);
} else {
bsc_set_socket_idle(c);
ctx->funcs->socket_event(
c, BSC_SOCKET_EVENT_DISCONNECTED, ws_reason, ws_reason_desc,
NULL, 0, NULL);
bsc_clear_vmac_and_uuid(c);
}
} else if (ev == BSC_WEBSOCKET_CONNECTED) {
DEBUG_PRINTF(
"bsc_dispatch_cli_func() websocket %s c->state = %s\n",
bsc_websocket_event_to_string(ev),
bsc_socket_state_to_string(c->state));
if (c->state == BSC_SOCK_STATE_AWAITING_WEBSOCKET) {
DEBUG_PRINTF(
"bsc_dispatch_cli_func() conn %p, websocket %d, state "
"changed to BSC_SOCK_STATE_AWAITING_ACCEPT\n",
c, h);
c->state = BSC_SOCK_STATE_AWAITING_ACCEPT;
mstimer_set(&c->t, c->ctx->cfg->connect_timeout_s * 1000);
c->expected_connect_accept_message_id = bsc_get_next_message_id();
DEBUG_PRINTF(
"bsc_dispatch_cli_func() expected connect accept "
"message id = %04x\n",
c->expected_connect_accept_message_id);
DEBUG_PRINTF(
"bsc_dispatch_cli_func() going to send connect request "
"with uuid %s and vmac %s\n",
bsc_uuid_to_string(&ctx->cfg->local_uuid),
bsc_vmac_to_string(&ctx->cfg->local_vmac));
len = bvlc_sc_encode_connect_request(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c),
c->expected_connect_accept_message_id, &ctx->cfg->local_vmac,
&ctx->cfg->local_uuid, ctx->cfg->max_bvlc_len,
ctx->cfg->max_ndpu_len);
if (!len) {
bsc_cli_process_error(c, ERROR_CODE_ABORT_OUT_OF_RESOURCES);
} else {
TX_BUF_UPDATE(c, len);
}
bws_cli_send(c->wh);
}
} else if (ev == BSC_WEBSOCKET_SENDABLE) {
p = c->tx_buf;
while (c->tx_buf_size > 0) {
memcpy(&pdu_len, p, sizeof(pdu_len));
DEBUG_PRINTF(
"bsc_dispatch_cli_func() sending pdu of %d bytes\n", pdu_len);
wret = bws_cli_dispatch_send(
c->wh, &p[sizeof(pdu_len) + BSC_CONF_TX_PRE], pdu_len);
if (wret != BSC_WEBSOCKET_SUCCESS) {
DEBUG_PRINTF(
"bsc_dispatch_cli_func() pdu send failed, err = %d, start "
"disconnect operation on socket %p\n",
wret, c);
bsc_cli_process_error(
c,
c->state != BSC_SOCK_STATE_ERROR_FLUSH_TX
? ERROR_CODE_ABORT_OUT_OF_RESOURCES
: c->reason);
failed = true;
break;
} else {
c->tx_buf_size -= pdu_len + sizeof(pdu_len) + BSC_CONF_TX_PRE;
p += pdu_len + sizeof(pdu_len) + BSC_CONF_TX_PRE;
}
}
if (!failed) {
if (c->state == BSC_SOCK_STATE_ERROR_FLUSH_TX) {
bsc_cli_process_error(c, c->reason);
}
}
} else if (ev == BSC_WEBSOCKET_RECEIVED) {
if (c->state == BSC_SOCK_STATE_AWAITING_ACCEPT) {
bsc_process_cli_awaiting_accept(c, &bsc_dm, buf, bufsize);
} else if (
c->state == BSC_SOCK_STATE_DISCONNECTING ||
c->state == BSC_SOCK_STATE_CONNECTED) {
bsc_runloop_socket(c, &bsc_dm, buf, bufsize);
}
#if DEBUG_BSC_SOCKET == 1
else {
DEBUG_PRINTF(
"bsc_dispatch_cli_func() data was dropped for socket "
"%p, state %d, data_size %d\n",
c, c->state, bufsize);
}
#endif
}
bsc_socket_maintenance_timer(0);
DEBUG_PRINTF_VERBOSE("bsc_dispatch_cli_func() <<<\n");
bws_dispatch_unlock();
}
/**
* @brief Initialize the context
* @param ctx - pointer to the context
* @param cfg - pointer to the configuration
* @param funcs - pointer to the functions
* @param sockets - pointer to the sockets
* @param sockets_num - number of sockets
* @param user_arg - pointer to the user argument
* @return BSC_SC_RET - status
*/
BSC_SC_RET bsc_init_ctx(
BSC_SOCKET_CTX *ctx,
BSC_CONTEXT_CFG *cfg,
BSC_SOCKET_CTX_FUNCS *funcs,
BSC_SOCKET *sockets,
size_t sockets_num,
void *user_arg)
{
BSC_WEBSOCKET_RET ret;
BSC_SC_RET sc_ret = BSC_SC_SUCCESS;
size_t i;
DEBUG_PRINTF(
"bsc_init_сtx() >>> ctx = %p, cfg = %p, funcs = %p, user_arg = %p\n",
ctx, cfg, funcs, user_arg);
if (!ctx || !cfg || !funcs || !funcs->socket_event ||
!funcs->context_event || !sockets || !sockets_num) {
DEBUG_PRINTF("bsc_init_ctx() <<< ret = BSC_SC_BAD_PARAM\n");
return BSC_SC_BAD_PARAM;
}
if (cfg->type == BSC_SOCKET_CTX_ACCEPTOR) {
if (!funcs->find_connection_for_vmac ||
!funcs->find_connection_for_uuid) {
DEBUG_PRINTF("bsc_init_ctx() <<< ret = BSC_SC_BAD_PARAM\n");
return BSC_SC_BAD_PARAM;
}
}
bws_dispatch_lock();
if (ctx->state != BSC_CTX_STATE_IDLE) {
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_init_ctx() <<< ret = BSC_SC_INVALID_OPERATION\n");
return BSC_SC_INVALID_OPERATION;
}
memset(ctx, 0, sizeof(*ctx));
ctx->user_arg = user_arg;
ctx->cfg = cfg;
ctx->funcs = funcs;
ctx->sock = sockets;
ctx->sock_num = sockets_num;
for (i = 0; i < sockets_num; i++) {
bsc_set_socket_idle(&ctx->sock[i]);
}
ctx->state = BSC_CTX_STATE_INITIALIZING;
if (!bsc_ctx_add(ctx)) {
sc_ret = BSC_SC_NO_RESOURCES;
} else {
if (cfg->type == BSC_SOCKET_CTX_ACCEPTOR) {
ret = bws_srv_start(
cfg->proto, cfg->port, cfg->iface, cfg->ca_cert_chain,
cfg->ca_cert_chain_size, cfg->cert_chain, cfg->cert_chain_size,
cfg->priv_key, cfg->priv_key_size, cfg->connect_timeout_s,
bsc_dispatch_srv_func, ctx, &ctx->sh);
sc_ret = bsc_map_websocket_retcode(ret);
if (sc_ret != BSC_SC_SUCCESS) {
bsc_ctx_remove(ctx);
}
} else {
ctx->state = BSC_CTX_STATE_INITIALIZED;
ctx->funcs->context_event(ctx, BSC_CTX_INITIALIZED);
}
}
bws_dispatch_unlock();
DEBUG_PRINTF_VERBOSE("bsc_init_ctx() <<< ret = %d \n", sc_ret);
return sc_ret;
}
/**
* @brief Deinitialize the context
* @param ctx - pointer to the context
*/
void bsc_deinit_ctx(BSC_SOCKET_CTX *ctx)
{
size_t i;
bool active_socket = false;
DEBUG_PRINTF("bsc_deinit_ctx() >>> ctx = %p\n", ctx);
bws_dispatch_lock();
if (!ctx || ctx->state == BSC_CTX_STATE_IDLE ||
ctx->state == BSC_CTX_STATE_DEINITIALIZING) {
DEBUG_PRINTF("bsc_deinit_ctx() no action required\n");
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_deinit_ctx() <<<\n");
return;
}
if (ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
ctx->state = BSC_CTX_STATE_DEINITIALIZING;
for (i = 0; i < ctx->sock_num; i++) {
if (ctx->sock[i].state != BSC_SOCK_STATE_IDLE) {
active_socket = true;
DEBUG_PRINTF(
"bsc_deinit_ctx() disconnect socket %ld(%p) with wh = %d\n",
i, &ctx->sock[i], ctx->sock[i].wh);
bws_cli_disconnect(ctx->sock[i].wh);
}
}
if (!active_socket) {
DEBUG_PRINTF(
"bsc_deinit_ctx() no active sockets, ctx de-initialized\n");
ctx->state = BSC_CTX_STATE_IDLE;
bsc_ctx_remove(ctx);
ctx->funcs->context_event(ctx, BSC_CTX_DEINITIALIZED);
}
} else {
ctx->state = BSC_CTX_STATE_DEINITIALIZING;
(void)bws_srv_stop(ctx->sh);
}
bws_dispatch_unlock();
DEBUG_PRINTF_VERBOSE("bsc_deinit_ctx() <<<\n");
}
/**
* @brief Connect the socket
* @param ctx - pointer to the context
* @param c - pointer to the socket
* @param url - pointer to the URL
* @return BSC_SC_RET - status
*/
BSC_SC_RET bsc_connect(BSC_SOCKET_CTX *ctx, BSC_SOCKET *c, char *url)
{
BSC_SC_RET ret = BSC_SC_INVALID_OPERATION;
BSC_WEBSOCKET_RET wret;
DEBUG_PRINTF_VERBOSE(
"bsc_connect() >>> ctx = %p, c = %p, url = %s\n", ctx, c, url);
if (!ctx || !c || !url) {
ret = BSC_SC_BAD_PARAM;
} else {
bws_dispatch_lock();
if (ctx->state == BSC_CTX_STATE_INITIALIZED &&
ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
c->ctx = ctx;
c->state = BSC_SOCK_STATE_AWAITING_WEBSOCKET;
c->tx_buf_size = 0;
wret = bws_cli_connect(
ctx->cfg->proto, url, ctx->cfg->ca_cert_chain,
ctx->cfg->ca_cert_chain_size, ctx->cfg->cert_chain,
ctx->cfg->cert_chain_size, ctx->cfg->priv_key,
ctx->cfg->priv_key_size, ctx->cfg->connect_timeout_s,
bsc_dispatch_cli_func, ctx, &c->wh);
ret = bsc_map_websocket_retcode(wret);
if (wret != BSC_WEBSOCKET_SUCCESS) {
DEBUG_PRINTF(
"bsc_connect() failed. %s\n",
bsc_websocket_return_to_string(wret));
bsc_set_socket_idle(c);
bsc_clear_vmac_and_uuid(c);
}
}
bws_dispatch_unlock();
}
DEBUG_PRINTF_VERBOSE("bsc_connect() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Disconnect the socket
* @param c - pointer to the socket
*/
void bsc_disconnect(BSC_SOCKET *c)
{
size_t len;
DEBUG_PRINTF("bsc_disconnect() >>> c = %p\n", c);
bws_dispatch_lock();
if (c->ctx->state == BSC_CTX_STATE_INITIALIZED) {
if (c->state == BSC_SOCK_STATE_CONNECTED) {
c->expected_disconnect_message_id = bsc_get_next_message_id();
c->state = BSC_SOCK_STATE_DISCONNECTING;
mstimer_set(&c->t, c->ctx->cfg->disconnect_timeout_s * 1000);
len = bvlc_sc_encode_disconnect_request(
TX_BUF_PTR(c), TX_BUF_BYTES_AVAIL(c),
c->expected_disconnect_message_id);
if (!len) {
DEBUG_PRINTF(
"bsc_disconnect() disconnect request not sent, err = "
"BSC_SC_NO_RESOURCES\n");
if (c->ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
bsc_cli_process_error(c, ERROR_CODE_ABORT_OUT_OF_RESOURCES);
} else {
bsc_srv_process_error(c, ERROR_CODE_ABORT_OUT_OF_RESOURCES);
}
} else {
TX_BUF_UPDATE(c, len);
if (c->ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
bws_cli_send(c->wh);
} else {
bws_srv_send(c->ctx->sh, c->wh);
}
}
} else if (c->ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
if (c->state != BSC_SOCK_STATE_IDLE) {
bws_cli_disconnect(c->wh);
}
} else if (c->ctx->cfg->type == BSC_SOCKET_CTX_ACCEPTOR) {
if (c->state != BSC_SOCK_STATE_IDLE) {
bws_srv_disconnect(c->ctx->sh, c->wh);
}
}
}
bws_dispatch_unlock();
DEBUG_PRINTF_VERBOSE("bsc_disconnect() <<<\n");
}
/**
* @brief Send the BACnet Secure Connect PDU
* @param c - pointer to the socket
* @param pdu - pointer to the PDU
* @param pdu_len - PDU length
* @return BSC_SC_RET - status
*/
BSC_SC_RET bsc_send(BSC_SOCKET *c, uint8_t *pdu, size_t pdu_len)
{
BSC_SC_RET ret = BSC_SC_SUCCESS;
DEBUG_PRINTF(
"bsc_send() >>> c = %p, pdu = %p, pdu_len = %d\n", c, pdu, pdu_len);
if (!c || !pdu || !pdu_len) {
ret = BSC_SC_BAD_PARAM;
} else {
bws_dispatch_lock();
if (c->ctx->state != BSC_CTX_STATE_INITIALIZED ||
c->state != BSC_SOCK_STATE_CONNECTED) {
ret = BSC_SC_INVALID_OPERATION;
} else {
if (TX_BUF_BYTES_AVAIL(c) < pdu_len) {
ret = BSC_SC_NO_RESOURCES;
} else {
memcpy(TX_BUF_PTR(c), pdu, pdu_len);
TX_BUF_UPDATE(c, pdu_len);
if (c->ctx->cfg->type == BSC_SOCKET_CTX_INITIATOR) {
bws_cli_send(c->wh);
} else {
bws_srv_send(c->ctx->sh, c->wh);
}
}
}
bws_dispatch_unlock();
}
DEBUG_PRINTF_VERBOSE("bsc_send() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Get the next message ID
* @return uint16_t - message ID
*/
uint16_t bsc_get_next_message_id(void)
{
static uint16_t message_id;
static bool initialized = false;
uint16_t ret;
bws_dispatch_lock();
if (!initialized) {
message_id = (uint16_t)(rand() % USHRT_MAX);
initialized = true;
} else {
message_id++;
}
ret = message_id;
DEBUG_PRINTF_VERBOSE("next message id = %u(%04x)\n", ret, ret);
bws_dispatch_unlock();
return ret;
}
/**
* @brief Get the socket peer address
* @param c - pointer to the socket
* @param data - pointer to the address data
* @return true on success, false on failure
*/
bool bsc_socket_get_peer_addr(BSC_SOCKET *c, BACNET_HOST_N_PORT_DATA *data)
{
bool ret = false;
if (!c || !data) {
return false;
}
bws_dispatch_lock();
if (c->ctx->cfg->type == BSC_SOCKET_CTX_ACCEPTOR) {
data->type = BACNET_HOST_N_PORT_IP;
ret = bws_srv_get_peer_ip_addr(
c->ctx->sh, c->wh, (uint8_t *)data->host, sizeof(data->host),
&data->port);
}
bws_dispatch_unlock();
return ret;
}
/**
* @brief Get the socket global buffer
* @return pointer to the buffer
*/
BACNET_STACK_EXPORT
uint8_t *bsc_socket_get_global_buf(void)
{
static uint8_t buf[BSC_PRE + BVLC_SC_NPDU_SIZE_CONF];
return &buf[BSC_PRE];
}
/**
* @brief Get the socket global buffer size
* @return buffer size
*/
BACNET_STACK_EXPORT
size_t bsc_socket_get_global_buf_size(void)
{
return BVLC_SC_NPDU_SIZE_CONF;
}