Files
bacnet_stack/src/bacnet/datalink/bsc/bsc-util.c
T
Steve Karg cf77abac9b Feature/bacnet secure connect hub (#818)
* 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>
2024-11-04 07:05:26 -06:00

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(
&timestamp->date, &timestamp->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;
}