/** * @file * @brief module for common function for BACnet/SC implementation * @author Kirill Neznamov * @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 #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; }