25d14c7c15
* 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
1849 lines
67 KiB
C
1849 lines
67 KiB
C
|
||
/**
|
||
* @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;
|
||
}
|