cf77abac9b
* Added BACnet Secure Connect datalink. * Added BACnet/SC hub application --------- Co-authored-by: Kirill Neznamov <kirill.neznamov@dsr-corporation.com> Co-authored-by: Mikhail Antropov <michail.antropov@dsr-corporation.com> Co-authored-by: Ondřej Hruška <ondra@ondrovo.com> Co-authored-by: Patrick Grimm <patrick@lunatiki.de>
344 lines
10 KiB
C
344 lines
10 KiB
C
/**
|
|
* @file
|
|
* @brief module for common function for BACnet/SC implementation
|
|
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
|
|
* @date Jule 2022
|
|
* SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
|
|
*/
|
|
#include "bacnet/datalink/bsc/bsc-util.h"
|
|
#include "bacnet/basic/object/bacfile.h"
|
|
#include "bacnet/basic/object/netport.h"
|
|
#include "bacnet/basic/object/sc_netport.h"
|
|
#include "bacnet/basic/object/bacfile.h"
|
|
#include "bacnet/basic/sys/debug.h"
|
|
#include <stdlib.h>
|
|
|
|
#define PRINTF debug_aprintf
|
|
#define PRINTF_ERR debug_perror
|
|
|
|
/**
|
|
* @brief Map websocket return code to BACnet/SC return code
|
|
* @param ret - websocket return code
|
|
* @return BACnet/SC return code
|
|
*/
|
|
BSC_SC_RET bsc_map_websocket_retcode(BSC_WEBSOCKET_RET ret)
|
|
{
|
|
switch (ret) {
|
|
case BSC_WEBSOCKET_SUCCESS:
|
|
return BSC_SC_SUCCESS;
|
|
case BSC_WEBSOCKET_NO_RESOURCES:
|
|
return BSC_SC_NO_RESOURCES;
|
|
case BSC_WEBSOCKET_BAD_PARAM:
|
|
return BSC_SC_BAD_PARAM;
|
|
case BSC_WEBSOCKET_INVALID_OPERATION:
|
|
default:
|
|
return BSC_SC_INVALID_OPERATION;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Copy BACnet Secure Connect VMAC address
|
|
* @param dst - destination VMAC address
|
|
* @param src - source VMAC address
|
|
*/
|
|
void bsc_copy_vmac(BACNET_SC_VMAC_ADDRESS *dst, BACNET_SC_VMAC_ADDRESS *src)
|
|
{
|
|
memcpy(dst->address, src->address, sizeof(src->address));
|
|
}
|
|
|
|
/**
|
|
* @brief Copy the BACnet Secure Connect UUID
|
|
* @param dst - destination UUID
|
|
* @param src - source UUID
|
|
*/
|
|
void bsc_copy_uuid(BACNET_SC_UUID *dst, BACNET_SC_UUID *src)
|
|
{
|
|
memcpy(dst->uuid, src->uuid, sizeof(src->uuid));
|
|
}
|
|
|
|
/**
|
|
* @brief Convert BACnet Secure Connect VMAC address to string
|
|
* @param vmac - VMAC address
|
|
* @return string representation of VMAC address
|
|
*/
|
|
char *bsc_vmac_to_string(BACNET_SC_VMAC_ADDRESS *vmac)
|
|
{
|
|
static char buf[128];
|
|
sprintf(
|
|
buf, "%02x%02x%02x%02x%02x%02x", vmac->address[0], vmac->address[1],
|
|
vmac->address[2], vmac->address[3], vmac->address[4], vmac->address[5]);
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @brief Convert BACnet Secure Connect UUID to string
|
|
* @param uuid - UUID
|
|
* @return string representation of UUID
|
|
*/
|
|
char *bsc_uuid_to_string(BACNET_SC_UUID *uuid)
|
|
{
|
|
static char buf[128];
|
|
sprintf(
|
|
buf,
|
|
"%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x",
|
|
uuid->uuid[0], uuid->uuid[1], uuid->uuid[2], uuid->uuid[3],
|
|
uuid->uuid[4], uuid->uuid[5], uuid->uuid[6], uuid->uuid[7],
|
|
uuid->uuid[8], uuid->uuid[9], uuid->uuid[10], uuid->uuid[11],
|
|
uuid->uuid[12], uuid->uuid[13], uuid->uuid[14], uuid->uuid[15]);
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @brief Generate random BACnet Secure Connect VMAC address
|
|
* @param p - pointer to the VMAC address
|
|
*/
|
|
void bsc_generate_random_vmac(BACNET_SC_VMAC_ADDRESS *p)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < BVLC_SC_VMAC_SIZE; i++) {
|
|
p->address[i] = rand() % 255;
|
|
if (i == 0) {
|
|
/* According H.7.3 EUI-48 and Random-48 VMAC Address:
|
|
The Random-48 VMAC is a 6-octet VMAC address in which the least
|
|
significant 4 bits (Bit 3 to Bit 0) in the first octet shall be
|
|
B'0010' (X'2'), and all other 44 bits are randomly selected to be
|
|
0 or 1. */
|
|
p->address[i] = (p->address[i] & 0xF0) | 0x02;
|
|
}
|
|
}
|
|
debug_printf_hex(
|
|
0, p->address, BVLC_SC_VMAC_SIZE, "bsc_generate_random_vmac");
|
|
}
|
|
|
|
/**
|
|
* @brief Generate random BACnet Secure Connect UUID
|
|
* @param p - pointer to the UUID
|
|
*/
|
|
void bsc_generate_random_uuid(BACNET_SC_UUID *p)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < BVLC_SC_UUID_SIZE; i++) {
|
|
p->uuid[i] = rand() % 255;
|
|
}
|
|
debug_printf_hex(0, p->uuid, BVLC_SC_UUID_SIZE, "bsc_generate_random_uuid");
|
|
}
|
|
|
|
/*
|
|
* bsc_node_load_cert_bacfile loads one credentional file from bacfile object
|
|
* Note: the function adds null-terminated byte to loaded file
|
|
* (certificate, certificate key).
|
|
* The MbedTLS PEM parser requires data to be null-terminated.
|
|
*/
|
|
#ifdef CONFIG_MBEDTLS
|
|
#define ZERO_BYTE 1
|
|
#else
|
|
#define ZERO_BYTE 0
|
|
#endif
|
|
|
|
/**
|
|
* @brief Load certificate from BACnet file object
|
|
* @param file_instance - file instance
|
|
* @param pbuf - pointer to the buffer
|
|
* @param psize - pointer to the size
|
|
* @return true if successful, else false
|
|
*/
|
|
static bool bsc_node_load_cert_bacfile(
|
|
uint32_t file_instance, uint8_t **pbuf, size_t *psize)
|
|
{
|
|
uint32_t file_length;
|
|
uint8_t *buf;
|
|
|
|
*psize = bacfile_file_size(file_instance) + ZERO_BYTE;
|
|
if (*psize == 0) {
|
|
return false;
|
|
}
|
|
|
|
buf = calloc(1, *psize);
|
|
if (buf == NULL) {
|
|
return false;
|
|
}
|
|
|
|
file_length =
|
|
bacfile_read(file_instance, buf, (uint32_t)(*psize - ZERO_BYTE));
|
|
#ifdef CONFIG_MBEDTLS
|
|
buf[*psize - 1] = 0;
|
|
#endif
|
|
if (file_length == 0) {
|
|
PRINTF_ERR("Can't read %s file\n", bacfile_pathname(file_instance));
|
|
free(buf);
|
|
return false;
|
|
}
|
|
*pbuf = buf;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Fill BACnet/SC node configuration from network port object
|
|
* @param bsc_conf - pointer to the BACnet/SC node configuration
|
|
* @param event_func - event function
|
|
* @return true if successful, else false
|
|
*/
|
|
bool bsc_node_conf_fill_from_netport(
|
|
BSC_NODE_CONF *bsc_conf, BSC_NODE_EVENT_FUNC event_func)
|
|
{
|
|
uint32_t instance;
|
|
uint32_t file_instance;
|
|
|
|
instance = Network_Port_Index_To_Instance(0);
|
|
bsc_conf->ca_cert_chain = NULL;
|
|
bsc_conf->cert_chain = NULL;
|
|
bsc_conf->key = NULL;
|
|
|
|
file_instance = Network_Port_Issuer_Certificate_File(instance, 0);
|
|
if (!bsc_node_load_cert_bacfile(
|
|
file_instance, &bsc_conf->ca_cert_chain,
|
|
&bsc_conf->ca_cert_chain_size)) {
|
|
bsc_node_conf_cleanup(bsc_conf);
|
|
return false;
|
|
}
|
|
|
|
file_instance = Network_Port_Operational_Certificate_File(instance);
|
|
if (!bsc_node_load_cert_bacfile(
|
|
file_instance, &bsc_conf->cert_chain, &bsc_conf->cert_chain_size)) {
|
|
bsc_node_conf_cleanup(bsc_conf);
|
|
return false;
|
|
}
|
|
|
|
file_instance = Network_Port_Certificate_Key_File(instance);
|
|
bsc_conf->key_size = bacfile_file_size(file_instance) + 1;
|
|
if (!bsc_node_load_cert_bacfile(
|
|
file_instance, &bsc_conf->key, &bsc_conf->key_size)) {
|
|
bsc_node_conf_cleanup(bsc_conf);
|
|
return false;
|
|
}
|
|
bsc_conf->local_uuid =
|
|
(BACNET_SC_UUID *)Network_Port_SC_Local_UUID(instance);
|
|
Network_Port_MAC_Address_Value(
|
|
instance, bsc_conf->local_vmac.address,
|
|
sizeof(bsc_conf->local_vmac.address));
|
|
bsc_conf->max_local_bvlc_len =
|
|
(uint16_t)Network_Port_Max_BVLC_Length_Accepted(instance);
|
|
bsc_conf->max_local_npdu_len =
|
|
(uint16_t)Network_Port_Max_NPDU_Length_Accepted(instance);
|
|
bsc_conf->connect_timeout_s =
|
|
(uint16_t)Network_Port_SC_Connect_Wait_Timeout(instance);
|
|
bsc_conf->heartbeat_timeout_s =
|
|
(uint16_t)Network_Port_SC_Heartbeat_Timeout(instance);
|
|
bsc_conf->disconnect_timeout_s =
|
|
(uint16_t)Network_Port_SC_Disconnect_Wait_Timeout(instance);
|
|
bsc_conf->reconnnect_timeout_s =
|
|
(uint16_t)Network_Port_SC_Maximum_Reconnect_Time(instance);
|
|
bsc_conf->address_resolution_timeout_s = bsc_conf->connect_timeout_s;
|
|
bsc_conf->address_resolution_freshness_timeout_s =
|
|
bsc_conf->connect_timeout_s;
|
|
bsc_conf->primaryURL =
|
|
(char *)Network_Port_SC_Primary_Hub_URI_char(instance);
|
|
bsc_conf->failoverURL =
|
|
(char *)Network_Port_SC_Failover_Hub_URI_char(instance);
|
|
#if BSC_CONF_HUB_CONNECTORS_NUM != 0
|
|
bsc_conf->direct_connect_initiate_enable =
|
|
Network_Port_SC_Direct_Connect_Initiate_Enable(instance);
|
|
bsc_conf->direct_connect_accept_enable =
|
|
Network_Port_SC_Direct_Connect_Accept_Enable(instance);
|
|
Network_Port_SC_Direct_Connect_Binding_get(
|
|
instance, &bsc_conf->direct_server_port, &bsc_conf->direct_iface);
|
|
#endif
|
|
#if BSC_CONF_HUB_FUNCTIONS_NUM != 0
|
|
Network_Port_SC_Hub_Function_Binding_get(
|
|
instance, &bsc_conf->hub_server_port, &bsc_conf->hub_iface);
|
|
bsc_conf->hub_function_enabled =
|
|
Network_Port_SC_Hub_Function_Enable(instance);
|
|
#endif
|
|
|
|
#if BSC_CONF_HUB_CONNECTORS_NUM != 0
|
|
bsc_conf->direct_connection_accept_uris =
|
|
Network_Port_SC_Direct_Connect_Accept_URIs_char(instance);
|
|
bsc_conf->direct_connection_accept_uris_len =
|
|
strlen(bsc_conf->direct_connection_accept_uris);
|
|
#endif
|
|
bsc_conf->event_func = event_func;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief Cleanup BACnet/SC node configuration
|
|
* @param bsc_conf - pointer to the BACnet/SC node configuration
|
|
*/
|
|
void bsc_node_conf_cleanup(BSC_NODE_CONF *bsc_conf)
|
|
{
|
|
bsc_conf->ca_cert_chain_size = 0;
|
|
if (bsc_conf->ca_cert_chain) {
|
|
free(bsc_conf->ca_cert_chain);
|
|
}
|
|
bsc_conf->cert_chain_size = 0;
|
|
if (bsc_conf->cert_chain) {
|
|
free(bsc_conf->cert_chain);
|
|
}
|
|
bsc_conf->key_size = 0;
|
|
if (bsc_conf->key) {
|
|
free(bsc_conf->key);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Copy string
|
|
* @param dst - destination string
|
|
* @param src - source string
|
|
* @param dst_len - destination string length
|
|
*/
|
|
void bsc_copy_str(char *dst, const char *src, size_t dst_len)
|
|
{
|
|
size_t len;
|
|
if (dst_len > 0) {
|
|
len = strlen(src) >= dst_len ? dst_len - 1 : strlen(src);
|
|
memcpy(dst, src, len);
|
|
dst[len] = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Set timestamp
|
|
* @param timestamp - pointer to the timestamp
|
|
*/
|
|
void bsc_set_timestamp(BACNET_DATE_TIME *timestamp)
|
|
{
|
|
int16_t utc_offset_minutes;
|
|
bool dst_active;
|
|
datetime_local(
|
|
×tamp->date, ×tamp->time, &utc_offset_minutes, &dst_active);
|
|
}
|
|
|
|
/**
|
|
* @brief Check if BACnet/SC certificate files exist
|
|
* @return true if all files exist, else false
|
|
*/
|
|
bool bsc_cert_files_check(void)
|
|
{
|
|
uint32_t instance;
|
|
uint32_t file_instance;
|
|
|
|
instance = Network_Port_Index_To_Instance(0);
|
|
|
|
file_instance = Network_Port_Issuer_Certificate_File(instance, 0);
|
|
if (bacfile_file_size(file_instance) == 0) {
|
|
PRINTF_ERR("CA certificate file not exist\n");
|
|
return false;
|
|
}
|
|
|
|
file_instance = Network_Port_Operational_Certificate_File(instance);
|
|
if (bacfile_file_size(file_instance) == 0) {
|
|
PRINTF_ERR("Certificate file not exist\n");
|
|
return false;
|
|
}
|
|
|
|
file_instance = Network_Port_Certificate_Key_File(instance);
|
|
if (bacfile_file_size(file_instance) == 0) {
|
|
PRINTF_ERR("Certificate key file not exist\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|