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>
This commit is contained in:
Steve Karg
2024-11-04 07:05:26 -06:00
committed by GitHub
parent 90cdc4920b
commit cf77abac9b
161 changed files with 82982 additions and 23 deletions
+81
View File
@@ -0,0 +1,81 @@
name: BACNet/SC linux tests
on:
push:
branches:
- master
pull_request:
branches:
- '*'
jobs:
job_bsc_tests_linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Create Build Environment
run: |
sudo apt-get update -qq
sudo apt-get install -qq libconfig-dev
sudo apt-get install -qq libcap-dev
sudo apt-get install -qq libssl-dev
sudo apt-get install -qq libuv1-dev
git clone --branch v4.3-stable https://github.com/warmcat/libwebsockets.git
bash -c 'cd libwebsockets;mkdir build;cd build;cmake .. -DLWS_WITH_LIBUV=ON -DLWS_WITH_MINIMAL_EXAMPLES=0 -DLWS_MAX_SMP=32;make'
sudo bash -c 'cd libwebsockets;cd build;make install'
- name: Build and run bsc_event test
run: |
cd ./test/ports/linux/bsc_event
mkdir build
cd build
cmake ..
make
./test_bsc_event
- name: Build and run bvlc-sc test
run: |
cd ./test/bacnet/datalink/bvlc-sc
mkdir build
cd build
cmake ..
make
./test_bvlc-sc
- name: Build and run websockets test
run: |
cd ./test/bacnet/datalink/websockets
mkdir build
cd build
cmake ..
make
./test_websockets
- name: Build and run bsc-socket test
run: |
cd ./test/bacnet/datalink/bsc-socket
mkdir build
cd build
cmake ..
make
./test_bsc-socket
- name: Build and run hub-sc test
run: |
cd ./test/bacnet/datalink/hub-sc
mkdir build
cd build
cmake ..
make
./test_hub-sc
- name: Build and run bsc-node test
run: |
cd ./test/bacnet/datalink/bsc-node
mkdir build
cd build
cmake ..
make
./test_bsc-node
- name: Build and run bsc-datalink test
run: |
cd ./test/bacnet/datalink/bsc-datalink
mkdir build
cd build
cmake ..
make
./test_bsc-datalink
+75
View File
@@ -0,0 +1,75 @@
name: BACNet/SC macos/bsd tests
on:
push:
branches:
- master
pull_request:
branches:
- '*'
jobs:
job_bsc_tests_macos:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v3
- name: Create Build Environment
run: |
brew install openssl
brew install libwebsockets
- name: Build and run bsc_event test
run: |
cd ./test/ports/bsd/bsc_event
mkdir build
cd build
cmake ..
make
./test_bsc_event
- name: Build and run bvlc-sc test
run: |
cd ./test/bacnet/datalink/bvlc-sc
mkdir build
cd build
cmake ..
make
./test_bvlc-sc
- name: Build and run websockets test
run: |
cd ./test/bacnet/datalink/websockets
mkdir build
cd build
cmake .. -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@3.0
make
./test_websockets
- name: Build and run bsc-socket test
run: |
cd ./test/bacnet/datalink/bsc-socket
mkdir build
cd build
cmake ..
make
./test_bsc-socket
- name: Build and run hub-sc test
run: |
cd ./test/bacnet/datalink/hub-sc
mkdir build
cd build
cmake .. -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@3.0
make
./test_hub-sc
- name: Build and run bsc-node test
run: |
cd ./test/bacnet/datalink/bsc-node
mkdir build
cd build
cmake .. -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@3.0
make
./test_bsc-node
- name: Build and run bsc-datalink test
run: |
cd ./test/bacnet/datalink/bsc-datalink
mkdir build
cd build
cmake .. -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@3.0
make
./test_bsc-datalink
+85
View File
@@ -0,0 +1,85 @@
name: BACNet/SC windows tests
on:
push:
branches:
- master
pull_request:
branches:
- '*'
jobs:
job_bsc_tests_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Create Build Environment
run: |
rmdir 'C:\Program Files\OpenSSL' -Recurse -Force
rmdir 'C:\Strawberry' -Recurse -Force
copy c:\vcpkg\triplets\x64-windows.cmake c:\vcpkg\triplets\x64-windows-custom.cmake
"set(VCPKG_BUILD_TYPE release)`n" >> c:\vcpkg\triplets\x64-windows-custom.cmake
c:\vcpkg\vcpkg.exe install pthreads --triplet x64-windows-custom
c:\vcpkg\vcpkg.exe install libuv --triplet x64-windows-custom
c:\vcpkg\vcpkg.exe install openssl --triplet x64-windows-custom
c:\vcpkg\vcpkg.exe install libwebsockets --triplet x64-windows-custom
git clone --branch v4.3-stable https://github.com/warmcat/libwebsockets.git
cd libwebsockets
mkdir build
cd build
cmake .. -DOPENSSL_ROOT_DIR=c:\vcpkg\installed\x64-windows-custom -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-custom -DCMAKE_INSTALL_PREFIX="./pkg" -DLWS_WITH_MINIMAL_EXAMPLES=0 -DLWS_MAX_SMP=32 -DLWS_WITH_LIBUV=1 -DLWS_HAVE_PTHREAD_H=1 -DLWS_EXT_PTHREAD_INCLUDE_DIR="C:\vcpkg\installed\x64-windows-custom\include" -DLWS_EXT_PTHREAD_LIBRARIES="C:\vcpkg\installed\x64-windows-custom\lib\pthreadVC3.lib"
cmake --build . --config RELEASE
cmake --install . --config RELEASE
Copy-Item -Path .\pkg\bin -Destination c:\vcpkg\installed\x64-windows-custom -Recurse -Force
Copy-Item -Path .\pkg\include -Destination c:\vcpkg\installed\x64-windows-custom -Recurse -Force
Copy-Item -Path .\pkg\lib -Destination c:\vcpkg\installed\x64-windows-custom -Recurse -Force
Copy-Item -Path .\pkg\share -Destination c:\vcpkg\installed\x64-windows-custom -Recurse -Force
cd ../../
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v1.1
with:
msbuild-architecture: x64
- name: Build and run bsc_event test
run: |
cd ./test/ports/win32/bsc_event
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-custom
msbuild build/test_bsc_event.sln /Property:Configuration=Release
./build/Release/test_bsc_event.exe
- name: Build and run bvlc-sc test
run: |
cd ./test/bacnet/datalink/bvlc-sc
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-custom
msbuild build/test_bvlc-sc.sln /Property:Configuration=Release
./build/Release/test_bvlc-sc.exe
- name: Build and run websockets test
run: |
cd ./test/bacnet/datalink/websockets
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-custom
msbuild build/test_websockets.sln /Property:Configuration=Release
./build/Release/test_websockets.exe
- name: Build and run bsc-socket test
run: |
cd ./test/bacnet/datalink/bsc-socket
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-custom
msbuild build/test_bsc-socket.sln /Property:Configuration=Release
./build/Release/test_bsc-socket.exe
# (Link target) ->
# bacreal.obj : error LNK2019: unresolved external symbol big_endian referenced in function decode_double [D:\a\bacnet-stack-upstream\bacnet-stack-upstream\test\bacnet\datalink\hub-sc\build\test_hub-sc.vcxproj]
# - name: Build and run hub-sc test
# run: |
# cd ./test/bacnet/datalink/hub-sc
# cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-custom
# msbuild build/test_hub-sc.sln /Property:Configuration=Release
# ./build/Release/test_hub-sc.exe
- name: Build and run bsc-node test
run: |
cd ./test/bacnet/datalink/bsc-node
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-custom
msbuild build/test_bsc-node.sln /Property:Configuration=Release
./build/Release/test_bsc-node.exe
- name: Build and run bsc-datalink test
run: |
cd ./test/bacnet/datalink/bsc-datalink
cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=c:\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-custom
msbuild build/test_bsc-datalink.sln /Property:Configuration=Release
./build/Release/test_bsc-datalink.exe
+26
View File
@@ -77,6 +77,26 @@ jobs:
make clean
make LEGACY=true gateway
bacnet-sc-hub:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Create BACnet/SC Build Environment
run: |
sudo apt-get update -qq
sudo apt-get install -qq libconfig-dev
sudo apt-get install -qq libcap-dev
sudo apt-get install -qq libssl-dev
sudo apt-get install -qq libuv1-dev
git clone --branch v4.3-stable https://github.com/warmcat/libwebsockets.git
bash -c 'cd libwebsockets;mkdir build;cd build;cmake .. -DLWS_WITH_LIBUV=ON -DLWS_WITH_MINIMAL_EXAMPLES=0 -DLWS_MAX_SMP=32;make'
sudo bash -c 'cd libwebsockets;cd build;make install'
- name: Build BACnet/SC Hub Demo
run: |
gcc --version
make clean
make sc-hub
router:
runs-on: ubuntu-latest
steps:
@@ -85,6 +105,12 @@ jobs:
run: |
sudo apt-get update -qq
sudo apt-get install -qq libconfig-dev
sudo apt-get install -qq libcap-dev
sudo apt-get install -qq libssl-dev
sudo apt-get install -qq libuv1-dev
git clone --branch v4.3-stable https://github.com/warmcat/libwebsockets.git
bash -c 'cd libwebsockets;mkdir build;cd build;cmake .. -DLWS_WITH_LIBUV=ON -DLWS_WITH_MINIMAL_EXAMPLES=0 -DLWS_MAX_SMP=32;make'
sudo bash -c 'cd libwebsockets;cd build;make install'
- name: Build Router Demo
run: |
gcc --version
+6
View File
@@ -88,5 +88,11 @@ jobs:
run: |
sudo apt-get update -qq
sudo apt-get install -qq lcov
sudo apt-get install -qq libssl-dev
sudo apt-get install -qq libcap-dev
sudo apt-get install -qq libuv1-dev
git clone --branch v4.3-stable https://github.com/warmcat/libwebsockets.git
bash -c 'cd libwebsockets;mkdir build;cd build;cmake .. -DLWS_WITH_LIBUV=ON -DLWS_WITH_MINIMAL_EXAMPLES=0 -DLWS_MAX_SMP=32;make'
sudo bash -c 'cd libwebsockets;cd build;make install'
- name: Run Unit Test with Code Coverage
run: make test
+69 -5
View File
@@ -64,13 +64,19 @@ option(
"compile with ipv6 datalink support"
ON)
option(
BACDL_BSC
"compile with secure-connect support"
OFF)
if(NOT (BACDL_ETHERNET OR
BACDL_MSTP OR
BACDL_ARCNET OR
BACDL_BIP OR
BACDL_BIP6 OR
BACDL_BSC OR
BACDL_CUSTOM))
add_definitions(-DBACDL_NONE)
add_definitions(-DBACDL_NONE)
endif()
option(
@@ -174,6 +180,9 @@ elseif(MSVC)
# Might be slower if builded with /Qspectre
add_compile_options(/wd5045)
# format string expected in argument 3 is not a string literal. secure_connect.c:1414
add_compile_options(/wd4774)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "AppleClang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
@@ -186,6 +195,15 @@ endif()
find_package(Threads)
if(BACDL_BSC)
find_package(libwebsockets CONFIG REQUIRED)
find_package(PkgConfig)
pkg_check_modules(LIB_WEBSOCKETS REQUIRED libwebsockets)
#libwebsocket need C99 with variadic-macros
add_compile_options(-Wno-variadic-macros)
find_package(OpenSSL)
endif()
if(UCI)
FIND_PATH(uci_include_dir uci.h)
FIND_LIBRARY(uci_lib NAMES uci)
@@ -257,12 +275,30 @@ add_library(${PROJECT_NAME}
src/bacnet/weeklyschedule.h
src/bacnet/special_event.c
src/bacnet/special_event.h
src/bacnet/secure_connect.c
src/bacnet/secure_connect.h
$<$<BOOL:${BACDL_BIP}>:src/bacnet/basic/bbmd/h_bbmd.c>
$<$<BOOL:${BACDL_BIP}>:src/bacnet/basic/bbmd/h_bbmd.h>
$<$<BOOL:${BACDL_BIP6}>:src/bacnet/basic/bbmd6/h_bbmd6.c>
$<$<BOOL:${BACDL_BIP6}>:src/bacnet/basic/bbmd6/h_bbmd6.h>
$<$<BOOL:${BACDL_BIP6}>:src/bacnet/basic/bbmd6/vmac.c>
$<$<BOOL:${BACDL_BIP6}>:src/bacnet/basic/bbmd6/vmac.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bvlc-sc.c>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bvlc-sc.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-socket.c>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-socket.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-util.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-util.c>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-hub-connector.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-hub-connector.c>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-hub-function.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-hub-function.c>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-node-switch.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-node-switch.c>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-node.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-node.c>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-datalink.h>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/datalink/bsc/bsc-datalink.c>
src/bacnet/basic/binding/address.c
src/bacnet/basic/binding/address.h
src/bacnet/basic/npdu/h_npdu.c
@@ -340,6 +376,8 @@ add_library(${PROJECT_NAME}
src/bacnet/basic/object/nc.h
src/bacnet/basic/object/netport.c
src/bacnet/basic/object/netport.h
$<$<BOOL:${BACDL_BSC}>:src/bacnet/basic/object/sc_netport.c>
$<$<BOOL:${BACDL_BSC}>:src/bacnet/basic/object/sc_netport.h>
src/bacnet/basic/object/objects.c
src/bacnet/basic/object/objects.h
src/bacnet/basic/object/osv.c
@@ -623,6 +661,7 @@ target_compile_definitions(
PUBLIC
BACNET_PROTOCOL_REVISION=${BACNET_PROTOCOL_REVISION}
$<$<BOOL:${BACDL_BIP}>:BACDL_BIP>
$<$<BOOL:${BACDL_BSC}>:BACDL_BSC>
$<$<BOOL:${BACDL_BIP6}>:BACDL_BIP6>
$<$<BOOL:${BACDL_ARCNET}>:BACDL_ARCNET>
$<$<BOOL:${BACDL_MSTP}>:BACDL_MSTP>
@@ -633,7 +672,12 @@ target_compile_definitions(
$<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:BACNET_STACK_STATIC_DEFINE>
PRIVATE
PRINT_ENABLED=1)
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
if(BACDL_BSC)
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads ${LIB_WEBSOCKETS_LIBRARIES} )
else()
target_link_libraries(${PROJECT_NAME} PUBLIC Threads::Threads)
endif()
add_library(
${PROJECT_NAME}::${PROJECT_NAME}
@@ -652,6 +696,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message(STATUS "BACNET: building for linux")
set(BACNET_PORT_DIRECTORY_PATH ${CMAKE_CURRENT_LIST_DIR}/ports/linux)
target_link_libraries(${PROJECT_NAME} PUBLIC m)
add_compile_definitions(BACNET_PORT=linux)
target_sources(${PROJECT_NAME} PRIVATE
ports/linux/bacport.h
@@ -663,11 +708,16 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
$<$<BOOL:${BACDL_MSTP}>:ports/linux/rs485.h>
$<$<BOOL:${BACDL_MSTP}>:ports/linux/dlmstp.c>
$<$<BOOL:${BACDL_ETHERNET}>:ports/linux/ethernet.c>
$<$<BOOL:${BACDL_BSC}>:ports/linux/bsc-event.c>
$<$<BOOL:${BACDL_BSC}>:ports/linux/websocket-cli.c>
$<$<BOOL:${BACDL_BSC}>:ports/linux/websocket-srv.c>
$<$<BOOL:${BACDL_BSC}>:ports/linux/websocket-global.c>
ports/linux/mstimer-init.c)
elseif(WIN32)
message(STATUS "BACNET: building for win32")
set(BACNET_PORT_DIRECTORY_PATH ${CMAKE_CURRENT_LIST_DIR}/ports/win32)
add_compile_definitions(BACNET_PORT=win32)
target_link_libraries(${PROJECT_NAME} PUBLIC wsock32)
@@ -685,7 +735,11 @@ elseif(WIN32)
# ports/win32/dlmstp-mm.c
ports/win32/mstimer-init.c
$<$<BOOL:${BACDL_MSTP}>:ports/win32/rs485.c>
$<$<BOOL:${BACDL_MSTP}>:ports/win32/rs485.h>)
$<$<BOOL:${BACDL_MSTP}>:ports/win32/rs485.h>
$<$<BOOL:${BACDL_BSC}>:ports/win32/bsc-event.c>
$<$<BOOL:${BACDL_BSC}>:ports/win32/websocket-cli.c>
$<$<BOOL:${BACDL_BSC}>:ports/win32/websocket-srv.c>
$<$<BOOL:${BACDL_BSC}>:ports/win32/websocket-global.c>)
if(BACDL_ETHERNET)
include(ExternalProject)
@@ -729,6 +783,10 @@ elseif(APPLE)
$<$<BOOL:${BACDL_MSTP}>:ports/bsd/dlmstp.c>
ports/bsd/datetime-init.c
ports/bsd/mstimer-init.c
$<$<BOOL:${BACDL_BSC}>:ports/bsd/bsc-event.c>
$<$<BOOL:${BACDL_BSC}>:ports/bsd/websocket-cli.c>
$<$<BOOL:${BACDL_BSC}>:ports/bsd/websocket-srv.c>
$<$<BOOL:${BACDL_BSC}>:ports/bsd/websocket-global.c>
ports/bsd/stdbool.h)
if(BACDL_ETHERNET)
@@ -766,7 +824,8 @@ endif()
target_include_directories(${PROJECT_NAME} PUBLIC
$<BUILD_INTERFACE:${BACNET_PORT_DIRECTORY_PATH}>
)
$<$<BOOL:${BACDL_BSC}>:${OPEN_SSL_DIR}/include>
)
#
# examples
@@ -874,7 +933,7 @@ if(BACNET_STACK_BUILD_APPS)
)
endif(BACNET_BUILD_BACDISCOVER_APP)
if(BACDL_BIP)
if(BACDL_BIP AND (NOT BACDL_BSC))
add_executable(readbdt apps/readbdt/main.c)
target_link_libraries(readbdt PRIVATE ${PROJECT_NAME})
@@ -1017,6 +1076,10 @@ if(BACNET_STACK_BUILD_APPS)
add_executable(writepropm apps/writepropm/main.c)
target_link_libraries(writepropm PRIVATE ${PROJECT_NAME})
if(BACDL_BSC)
add_executable(sc-hub apps/sc-hub/main.c)
target_link_libraries(sc-hub PRIVATE ${PROJECT_NAME})
endif()
endif()
#
@@ -1077,6 +1140,7 @@ message(STATUS "BACNET: BACNET_PROTOCOL_REVISION:.......\"${BACNET_PROTOCOL_REVI
message(STATUS "BACNET: Selected datalinks:")
message(STATUS "BACNET: BACDL_BIP6:.....................\"${BACDL_BIP6}\"")
message(STATUS "BACNET: BACDL_BIP:......................\"${BACDL_BIP}\"")
message(STATUS "BACNET: BACDL_BSC:......................\"${BACDL_BSC}\"")
message(STATUS "BACNET: BACDL_ARCNET:...................\"${BACDL_ARCNET}\"")
message(STATUS "BACNET: BACDL_MSTP:.....................\"${BACDL_MSTP}\"")
message(STATUS "BACNET: BACDL_ETHERNET:.................\"${BACDL_ETHERNET}\"")
+11
View File
@@ -56,6 +56,12 @@ bip-client:
ethernet:
$(MAKE) BACDL=ethernet -s -C apps all
# note: requires additional libraries to be installed
# see .github/workflows/gcc.yml
.PHONY: bsc
bsc:
$(MAKE) BACDL=bsc -s -C apps all
.PHONY: apps
apps:
$(MAKE) -s -C apps all
@@ -200,6 +206,10 @@ server-client:
server-discover:
$(MAKE) LEGACY=true -s -C apps $@
.PHONY: sc-hub
sc-hub:
$(MAKE) BACDL=bsc -s -C apps $@
.PHONY: mstpcap
mstpcap:
$(MAKE) -s -C apps $@
@@ -456,6 +466,7 @@ clean: ports-clean
$(MAKE) -s -C apps/router-ipv6 clean
$(MAKE) -s -C apps/router-mstp clean
$(MAKE) -s -C apps/gateway clean
$(MAKE) -s -C apps/sc-hub clean
$(MAKE) -s -C apps/fuzz-afl clean
$(MAKE) -s -C apps/fuzz-libfuzzer clean
$(MAKE) -s -C ports/lwip clean
+16 -1
View File
@@ -35,6 +35,10 @@ endif
ifeq (${BACDL},bip6)
BACDL_DEFINE=-DBACDL_BIP6=1
endif
ifeq (${BACDL},bsc)
BACDL_DEFINE=-DBACDL_BSC=1
BACNET_DEFINE=-DBACFILE=1
endif
ifeq (${BACDL},none)
BACDL_DEFINE=-DBACDL_NONE=1
endif
@@ -108,11 +112,19 @@ ifeq (${BACNET_PORT},linux)
PFLAGS = -pthread
TARGET_EXT =
SYSTEM_LIB=-lc,-lgcc,-lrt,-lm
ifeq (${BACDL},bsc)
# note: install libwebsockets libssl libcrypto and lcap to build for BACnet/SC
SYSTEM_LIB += -lwebsockets -lssl -lcrypto -lcap
endif
endif
ifeq (${BACNET_PORT},bsd)
PFLAGS = -pthread
TARGET_EXT =
SYSTEM_LIB=-lc,-lm
ifeq (${BACDL},bsc)
# note: install libwebsockets libssl and libcrypto to build for BACnet/SC
SYSTEM_LIB += -lwebsockets -lssl -lcrypto
endif
CSTANDARD = -std=c99
endif
ifeq (${BACNET_PORT},win32)
@@ -174,7 +186,6 @@ endif
BACNET_DEFINES += -DPRINT_ENABLED=1
BACNET_DEFINES += -DBACAPP_ALL
BACNET_DEFINES += -DBACFILE
BACNET_DEFINES += -DINTRINSIC_REPORTING
BACNET_DEFINES += -DBACNET_TIME_MASTER
BACNET_DEFINES += -DBACNET_PROPERTY_LISTS=1
@@ -462,3 +473,7 @@ writepropm: $(BACNET_LIB_TARGET)
.PHONY: writegroup
writegroup: $(BACNET_LIB_TARGET)
$(MAKE) -B -C $@
.PHONY: sc-hub
sc-hub: $(BACNET_LIB_TARGET)
$(MAKE) -B -C $@
+26 -1
View File
@@ -6,6 +6,7 @@ BACNET_LIB_DIR ?= $(realpath .)
BACNET_LIB_TARGET ?= $(BACNET_LIB_DIR)/lib$(BACNET_LIB_NAME).a
BACNET_SRC_DIR ?= $(realpath ../../src)
BACNET_PORT_DIR ?= $(realpath ../../ports/linux)
BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object
BACNET_DEFINES ?=
#build for release (default) or debug
@@ -61,13 +62,34 @@ PORT_BIP6_SRC = \
$(BACNET_SRC_DIR)/bacnet/basic/bbmd6/vmac.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bvlc6.c
PORT_BSC_SRC = \
$(BACNET_PORT_DIR)/bip-init.c \
$(BACNET_PORT_DIR)/websocket-global.c \
$(BACNET_PORT_DIR)/websocket-srv.c \
$(BACNET_PORT_DIR)/websocket-cli.c \
$(BACNET_PORT_DIR)/bsc-event.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bvlc.c \
$(BACNET_SRC_DIR)/bacnet/basic/bbmd/h_bbmd.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bsc/bsc-datalink.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bsc/bsc-hub-connector.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bsc/bsc-hub-function.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bsc/bsc-node-switch.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bsc/bsc-node.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bsc/bsc-socket.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bsc/bsc-util.c \
$(BACNET_SRC_DIR)/bacnet/datalink/bsc/bvlc-sc.c \
$(BACNET_OBJECT_DIR)/sc_netport.c \
$(BACNET_OBJECT_DIR)/bacfile.c \
$(BACNET_SRC_DIR)/bacnet/secure_connect.c
PORT_ALL_SRC = \
$(BACNET_SRC_DIR)/bacnet/datalink/datalink.c \
$(PORT_ARCNET_SRC) \
$(PORT_MSTP_SRC) \
$(PORT_ETHERNET_SRC) \
$(PORT_BIP_SRC) \
$(PORT_BIP6_SRC)
$(PORT_BIP6_SRC) \
$(PORT_BSC_SRC)
PORT_NONE_SRC = \
$(BACNET_SRC_DIR)/bacnet/datalink/datalink.c
@@ -90,6 +112,9 @@ endif
ifeq (${BACDL_DEFINE},-DBACDL_NONE=1)
BACNET_PORT_SRC = ${PORT_NONE_SRC}
endif
ifeq (${BACDL_DEFINE},-DBACDL_BSC=1)
BACNET_PORT_SRC = ${PORT_BSC_SRC} ${APPS_ENVIRONMENT_SRC}
endif
ifeq (${BACDL_DEFINE},-DBACDL_ALL=1)
BACNET_PORT_SRC = ${PORT_ALL_SRC}
endif
+45
View File
@@ -0,0 +1,45 @@
#Makefile to build BACnet Application using GCC compiler
# Executable file name
TARGET = bacschub
# BACnet objects that are used with this app
BACNET_OBJECT_DIR = $(BACNET_SRC_DIR)/bacnet/basic/object
SRC = main.c \
$(BACNET_OBJECT_DIR)/client/device-client.c \
$(BACNET_OBJECT_DIR)/netport.c \
$(BACNET_SRC_DIR)/bacnet/secure_connect.c \
$(BACNET_OBJECT_DIR)/sc_netport.c
CFLAGS += -DBACNET_SECURE_CONNECT_ROUTING_TABLE=1
CFLAGS += -DBSC_CONF_HUB_FUNCTIONS_NUM=1
CFLAGS += -DBSC_CONF_HUB_CONNECTORS_NUM=1
# TARGET_EXT is defined in apps/Makefile as .exe or nothing
TARGET_BIN = ${TARGET}$(TARGET_EXT)
OBJS += ${SRC:.c=.o}
all: ${BACNET_LIB_TARGET} Makefile ${TARGET_BIN}
${TARGET_BIN}: ${OBJS} Makefile ${BACNET_LIB_TARGET}
${CC} ${PFLAGS} ${OBJS} ${LFLAGS} -o $@
size $@
cp $@ ../../bin
${BACNET_LIB_TARGET}:
( cd ${BACNET_LIB_DIR} ; $(MAKE) clean ; $(MAKE) -s )
-.c.o:
${CC} -c ${CFLAGS} $*.c -o $@
.PHONY: depend
depend:
rm -f .depend
${CC} -MM ${CFLAGS} *.c >> .depend
.PHONY: clean
clean:
rm -f core ${TARGET_BIN} ${OBJS} $(TARGET).map ${BACNET_LIB_TARGET}
.PHONY: include
include: .depend
+27
View File
@@ -0,0 +1,27 @@
# BACnet/SC Hub
Test BACnet/SC using the following steps:
* Build apps for bsc datalink:
make bsc
* Run hub app:
cd bin
./bsc-server.sh
./bacschub 1 Hubster
* Run server app:
cd bin
./bsc-client.sh
./bacserv 123 Francine
* Run any client:
cd bin
./bsc-client.sh
./bacwi
./bacepics 1
./bacepics 123
+240
View File
@@ -0,0 +1,240 @@
/**
* @file
* @brief Sample BACnet/SC hub.
* @author Mikhail Antropov <michail.antropov@dsr-corporation.com>
* @date December 2022
* @copyright SPDX-License-Identifier: MIT
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "bacnet/config.h"
#include "bacnet/apdu.h"
#include "bacnet/iam.h"
#include "bacnet/version.h"
#include "bacnet/basic/services.h"
#include "bacnet/datalink/dlenv.h"
#include "bacnet/basic/sys/filename.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/datalink/datalink.h"
/* include the device object */
#include "bacnet/basic/object/device.h"
#if defined(BAC_UCI)
#include "bacnet/basic/ucix/ucix.h"
#endif /* defined(BAC_UCI) */
#include "bacnet/datalink/bsc/bsc-datalink.h"
#include "bacnet/datalink/bsc/bsc-event.h"
/* (Doxygen note: The next two lines pull all the following Javadoc
* into the SCServerDemo module.) */
/** @addtogroup SCServerDemo */
/*@{*/
#ifndef BACDL_BSC
#error "BACDL_BSC must be defined"
#endif
/* current version of the BACnet stack */
static const char *BACnet_Version = BACNET_VERSION_TEXT;
/** Buffer used for receiving */
static uint8_t Rx_Buf[MAX_MPDU] = { 0 };
/** Initialize the handlers we will utilize.
* @see Device_Init, apdu_set_unconfirmed_handler, apdu_set_confirmed_handler
*/
static void Init_Service_Handlers(void)
{
Device_Init(NULL);
/* we need to handle who-is to support dynamic device binding */
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_IS, handler_who_is);
apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_WHO_HAS, handler_who_has);
/* set the handler for all the services we don't implement */
/* It is required to send the proper reject message... */
apdu_set_unrecognized_service_handler_handler(handler_unrecognized_service);
/* Set the handlers for any confirmed services that we support. */
/* We must implement read property - it's required! */
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_READ_PROPERTY, handler_read_property);
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_READ_PROP_MULTIPLE, handler_read_property_multiple);
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_WRITE_PROPERTY, handler_write_property);
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_WRITE_PROP_MULTIPLE, handler_write_property_multiple);
apdu_set_confirmed_handler(
SERVICE_CONFIRMED_REINITIALIZE_DEVICE, handler_reinitialize_device);
apdu_set_unconfirmed_handler(
SERVICE_UNCONFIRMED_UTC_TIME_SYNCHRONIZATION, handler_timesync_utc);
apdu_set_unconfirmed_handler(
SERVICE_UNCONFIRMED_TIME_SYNCHRONIZATION, handler_timesync);
#if defined(BACNET_TIME_MASTER)
handler_timesync_init();
#endif
}
static void print_usage(const char *filename)
{
printf("Usage: %s [device-instance [device-name]]\n", filename);
printf(" [--version][--help]\n");
}
static void print_help(const char *filename)
{
printf(
"Simulate a BACnet/SC HUB device\n"
"device-instance: BACnet Device Object Instance number that you are\n"
"trying simulate.\n"
"device-name: The Device name in ASCII for this device.\n"
"\n");
printf("Other parameters are passing over environment variables:\n"
"- BACNET_SC_ISSUER_1_CERTIFICATE_FILE: Filename of CA certificate\n"
"- BACNET_SC_OPERATIONAL_CERTIFICATE_FILE: Filename of device "
"certificate\n"
"- BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE: Filename of "
"device certificate key\n"
"- BACNET_SC_HUB_FUNCTION_BINDING: Local port or pair \"interface "
"name:port number\"\n"
"For additional information see file bin/bsc-server.sh\n");
printf(
"\nExample:\n"
"To simulate Device 111, use following command:\n"
"%s 111\n",
filename);
printf(
"To simulate Device 111 named NoFred, use following command:\n"
"%s 111 NoFred\n",
filename);
}
/** Main function of server demo.
*
* @see Device_Set_Object_Instance_Number, dlenv_init, Send_I_Am,
* datalink_receive, npdu_handler,
* dcc_timer_seconds, datalink_maintenance_timer,
* Load_Control_State_Machine_Handler, handler_cov_task,
* tsm_timer_milliseconds
*
* @param argc [in] Arg count.
* @param argv [in] Takes one argument: the Device Instance #.
* @return 0 on success.
*/
int main(int argc, char *argv[])
{
#if defined(BAC_UCI)
int uciId = 0;
struct uci_context *ctx;
#endif
#if defined(BACNET_TIME_MASTER)
BACNET_DATE_TIME bdatetime = { 0 };
#endif
BACNET_ADDRESS src = { 0 }; /* address where message came from */
int argi = 0;
const char *filename = NULL;
BACNET_CHARACTER_STRING DeviceName;
uint16_t pdu_len = 0;
unsigned delay_milliseconds = 1;
unsigned elapsed_seconds = 0;
struct mstimer datalink_timer = { 0 };
filename = filename_remove_path(argv[0]);
argi = 0;
if ((argc >= 2) && (strcmp(argv[1], "--help") == 0)) {
print_usage(filename);
print_help(filename);
return 0;
}
if ((argc >= 2) && (strcmp(argv[1], "--version") == 0)) {
printf("%s %s\n", filename, BACNET_VERSION_TEXT);
printf("Copyright (C) 2022 by Steve Karg and others.\n"
"This is free software; see the source for copying "
"conditions.\n"
"There is NO warranty; not even for MERCHANTABILITY or\n"
"FITNESS FOR A PARTICULAR PURPOSE.\n");
return 0;
}
#if defined(BAC_UCI)
ctx = ucix_init("bacnet_dev");
if (!ctx) {
fprintf(stderr, "Failed to load config file bacnet_dev\n");
}
uciId = ucix_get_option_int(ctx, "bacnet_dev", "0", "Id", 0);
if (uciId != 0) {
Device_Set_Object_Instance_Number(uciId);
} else {
#endif /* defined(BAC_UCI) */
/* allow the device ID to be set */
if (++argi < argc) {
Device_Set_Object_Instance_Number(strtol(argv[argi], NULL, 0));
}
#if defined(BAC_UCI)
}
ucix_cleanup(ctx);
#endif /* defined(BAC_UCI) */
printf(
"BACnet SC Hub Demo\n"
"BACnet Stack Version %s\n"
"BACnet Device ID: %u\n"
"Max APDU: %d\n",
BACnet_Version, Device_Object_Instance_Number(), MAX_APDU);
/* load any static address bindings to show up
in our device bindings list */
address_init();
Init_Service_Handlers();
#if defined(BAC_UCI)
const char *uciname;
ctx = ucix_init("bacnet_dev");
if (!ctx) {
fprintf(stderr, "Failed to load config file bacnet_dev\n");
}
uciname = ucix_get_option(ctx, "bacnet_dev", "0", "Name");
if (uciname != 0) {
Device_Object_Name_ANSI_Init(uciname);
} else {
#endif /* defined(BAC_UCI) */
if (++argi < argc) {
Device_Object_Name_ANSI_Init(argv[argi]);
}
#if defined(BAC_UCI)
}
ucix_cleanup(ctx);
#endif /* defined(BAC_UCI) */
if (Device_Object_Name(Device_Object_Instance_Number(), &DeviceName)) {
printf("BACnet Device Name: %s\n", DeviceName.value);
}
dlenv_init();
atexit(datalink_cleanup);
mstimer_set(&datalink_timer, 1000);
/* loop forever */
for (;;) {
/* input */
pdu_len =
datalink_receive(&src, &Rx_Buf[0], MAX_MPDU, delay_milliseconds);
/* process */
if (pdu_len) {
npdu_handler(&src, &Rx_Buf[0], pdu_len);
}
if (mstimer_expired(&datalink_timer)) {
elapsed_seconds = mstimer_interval(&datalink_timer) / 1000;
mstimer_reset(&datalink_timer);
datalink_maintenance_timer(elapsed_seconds);
#if defined(BACNET_TIME_MASTER)
Device_getCurrentDateTime(&bdatetime);
handler_timesync_task(&bdatetime);
#endif
}
}
return 0;
}
/* @} */
/* End group SCServerDemo */
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
echo "Example of parameters for BACnet/SC clients"
BACNET_SC_PRIMARY_HUB_URI="wss://127.0.0.1:50050"
export BACNET_SC_PRIMARY_HUB_URI
BACNET_SC_FAILOVER_HUB_URI="wss://127.0.0.1:5555"
export BACNET_SC_FAILOVER_HUB_URI
BACNET_SC_ISSUER_1_CERTIFICATE_FILE="certs/ca_cert.pem"
export BACNET_SC_ISSUER_1_CERTIFICATE_FILE
# Second CA certificate is not used yet
#BACNET_SC_ISSUER_2_CERTIFICATE_FILE="certs/ca_cert2.pem"
#export BACNET_SC_ISSUER_2_CERTIFICATE_FILE
BACNET_SC_OPERATIONAL_CERTIFICATE_FILE="certs/client_cert.pem"
export BACNET_SC_OPERATIONAL_CERTIFICATE_FILE
BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE="certs/client_key.pem"
export BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE
# Need if the device allows the direct connect.
# Possible values: or port number, like "5000" or pair interface name and
# port number, like "eth0:50050"
#BACNET_SC_DIRECT_CONNECT_BINDING="eth0:1234"
#export BACNET_SC_DIRECT_CONNECT_BINDING
# Set if the device supports the direct connect initiate
# Value: if allow then "1", "y", "Y", otherwise forbidden
#BACNET_SC_DIRECT_CONNECT_INITIATE="y"
#export BACNET_SC_DIRECT_CONNECT_INITIATE
# List of direct connect accept URLs separated by a space character
#BACNET_SC_DIRECT_CONNECT_ACCEPT_URLS="wss://192.0.0.1:40000 wss://192.0.0.2:6666"
#export BACNET_SC_DIRECT_CONNECT_ACCEPT_URLS
echo "Launching new shell using the BACnet/SC client environment..."
/bin/bash
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
echo "Example of parameters for BACnet/SC server"
BACNET_SC_PRIMARY_HUB_URI="wss://127.0.0.1:50050"
export BACNET_SC_PRIMARY_HUB_URI
BACNET_SC_FAILOVER_HUB_URI="wss://127.0.0.1:50050"
export BACNET_SC_FAILOVER_HUB_URI
BACNET_SC_ISSUER_1_CERTIFICATE_FILE="certs/ca_cert.pem"
export BACNET_SC_ISSUER_1_CERTIFICATE_FILE
# Second CA certificate is not used yet
#BACNET_SC_ISSUER_2_CERTIFICATE_FILE="certs/ca_cert2.pem"
#export BACNET_SC_ISSUER_2_CERTIFICATE_FILE
BACNET_SC_OPERATIONAL_CERTIFICATE_FILE="certs/server_cert.pem"
export BACNET_SC_OPERATIONAL_CERTIFICATE_FILE
BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE="certs/server_key.pem"
export BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE
# Need if the device is HUB.
# Possible values: or port number, like "9999" or pair interface name and
# port number, like "eth0:50050"
BACNET_SC_HUB_FUNCTION_BINDING="50050"
export BACNET_SC_HUB_FUNCTION_BINDING
# Need if the device allows the direct connect.
# Possible values: or port number, like "5000" or pair interface name and
# port number, like "eth0:50050"
#BACNET_SC_DIRECT_CONNECT_BINDING="eth0:1234"
#export BACNET_SC_DIRECT_CONNECT_BINDING
# Set if the device supports the direct connect initiate
# Value: if allow then "1", "y", "Y", otherwise forbidden
#BACNET_SC_DIRECT_CONNECT_INITIATE="y"
#export BACNET_SC_DIRECT_CONNECT_INITIATE
# List of direct connect accept URLs separated by a space character
#BACNET_SC_DIRECT_CONNECT_ACCEPT_URLS="wss://192.0.0.1:40000 wss://192.0.0.2:6666"
#export BACNET_SC_DIRECT_CONNECT_ACCEPT_URLS
echo "Launching new shell using the BACnet/SC server environment..."
/bin/bash
+19
View File
@@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDHDCCAgQCCQDCD53YZJJ7ljANBgkqhkiG9w0BAQsFADBPMRswGQYDVQQKExJs
aWJ3ZWJzb2NrZXRzLXRlc3QxEjAQBgNVBAcTCVhpYW9iaXRhbjEPMA0GA1UECBMG
VGFpcGVpMQswCQYDVQQGEwJUVzAgFw0yMjA3MDYxMTI0MjBaGA8yMDUwMDcxOTEx
MjQyMFowTzEbMBkGA1UEChMSbGlid2Vic29ja2V0cy10ZXN0MRIwEAYDVQQHEwlY
aWFvYml0YW4xDzANBgNVBAgTBlRhaXBlaTELMAkGA1UEBhMCVFcwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDV/Oz5VsX3V4R8vaANyPIMaFsygMeWpKan
HMT4kfigUYUHUYeLkbPewZAycCfYsCdBJmsXCXCsfiELpuYnepRetrZ29wTj/b+m
2TjUb2G4Q67CKSHNlh6CQqFvEWnBX+8fYehQT3QwJXN9hVBuPxD+xA+bI6Z28W0k
A0ZL9FfWCT6fODxM7egkSSmKeDgTUsn5wd8R8kQhk4yI2KPUys+nYxzowjNOi8uh
DXzr+Fxg/ps68ViZjGFgXSFHw5ZIvRQTGI2h0T9qmHP0BVFybJu9s6XDYuoM8EJs
WSs+4vOacaTEVYAQF5HxALfCtaOePjSp+ffE+U0CxYF3NH93YKZxAgMBAAEwDQYJ
KoZIhvcNAQELBQADggEBAImJvqnpPdSuFOZT6vtH1pJCEvJ9bSx1CAv6F4FDowKw
CqKSlYEjrILdlB0921OT0vah3lUv/kGNLvXUqTiBakwfRG09aIEnShmyo0hceOh3
O1KKYvJ2jJGk6PlRxeSg7d5Mi7XgnAdeaxwhuvZZmaIzbhAWP8qgI06P2RBS5BJv
rDD3Dhw8N8GwBD1RY2yKryFQ+4U21Ersw/38cY8UAFTkg3rWr4DWx6tnIfdrN1II
pRqSxHQW4kZag1uZdFcSiaYT+ebLWVXAzbfJLOV8EFF3RDC/A3U3VcORDBDWDhmh
8r4T+6FggYL2ejMmfwbewzI+Hm4SidgOxLf1Fx1d2v4=
-----END CERTIFICATE-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA1fzs+VbF91eEfL2gDcjyDGhbMoDHlqSmpxzE+JH4oFGFB1GH
i5Gz3sGQMnAn2LAnQSZrFwlwrH4hC6bmJ3qUXra2dvcE4/2/ptk41G9huEOuwikh
zZYegkKhbxFpwV/vH2HoUE90MCVzfYVQbj8Q/sQPmyOmdvFtJANGS/RX1gk+nzg8
TO3oJEkping4E1LJ+cHfEfJEIZOMiNij1MrPp2Mc6MIzTovLoQ186/hcYP6bOvFY
mYxhYF0hR8OWSL0UExiNodE/aphz9AVRcmybvbOlw2LqDPBCbFkrPuLzmnGkxFWA
EBeR8QC3wrWjnj40qfn3xPlNAsWBdzR/d2CmcQIDAQABAoIBACXIxz8vpz0JYwdz
pDNK7BJsys2cF6Ht6M9RMSaC/9evDUJBjyGB1LTckM2XKDIGyKekVPx4WkDa9JLO
IY2PA2vF72oKK/7l61VWvcYk9KhIqy71fFaEz1ZI1aB6/qV6fwqXiyHDJczqj/31
8EHHOQUDMY4/OUF/V7ooKd13g5rzLAPGNtC7n+VkjA11x8Ol2WbH3dj1xOG/fPRW
rciWFayJr3qVlZ+By1fDCY81MsUVAeUW/T3k9BnKg6hqa3JG67NA6BrczNKSKGy7
ppVVAeaGDIs7bsCzRpEBCgNhRBZE/AyewMVnBJbKArnryii8/ZbVQUMLVH2ph4Fi
k+0VfVECgYEA/ZE95qKFmqQeO6mvB7kVkpVKSXGu/Y5kFcpLWOc5EVHL6FmfzbqB
Axoq9HhfLy9a7NzsaCw0f4VyZrsOasgB1CBZP602CGi8hbHajJMwJg7B9d8Rqbg5
vakEudpB1h9jm9NJ5acR7/vcdVpEgpn2nP84Rn/N6reuwRVa8/u+YecCgYEA2Ap6
O6xM22pNDd3p5yPe868fyf5V5/2Kov4vF8wfisaEcfb/WbJRgwc6/kUUk1lJnxnG
PK98Hmjid5Y39dzilXgtC66XVAu0OJ04lqilsM4BSCdklimphuFszJ3POt6U7hgB
1qsKMKautj3/8v2Swcq6FIx6l6Fo7q+sptVrWecCgYB+6c1TvJCmf/Jp5lomwWqc
vYA7Fl2Bp11M0r23t7OGiaxHlWQ4slqUVOqfBgiOM2ONHl5tHYMBOKez53gl1gmk
RLSm/GkI+HMzb3t11M3KnqRSDd5nVkAAP7KP20AMjhVrDuvzuBVwSl1jl1SnEayv
k8Z08s775gffuHKXn68AjQKBgFXsLNso1tR5PbYjKVD9ikGex+TdW6tNwmK6913e
mDjoZWqyErLI4fRb3tGcBefonghCBv7ByHqLum5Xd2A4foF172xy/sq1cPMHTKdW
4bck54ub5zx1y2/SniPvK6k9NzxOngSTuBTLZjcjB3XL9h9PEpzzlhpSXtp3UhJ0
VSLHAoGADNykRP2U84xpPm6w1i6rGHqP8ge+OoowiGGKHTEHrV9Syfl/cB9UMeyC
EIhF7Nqk5v+Mh+P1RIRq59fMA4rNzmaxek5rK6EPg1b/gxzEsLVEEBXIQ5WWnXjR
u+ZVI73hb6yVVpuoNhRCgYgEpHknGCEvbURKOhyDMNilIj1mqDM=
-----END RSA PRIVATE KEY-----
+24
View File
@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEGDCCAwCgAwIBAgIDEAAEMA0GCSqGSIb3DQEBCwUAME8xGzAZBgNVBAoTEmxp
YndlYnNvY2tldHMtdGVzdDESMBAGA1UEBxMJWGlhb2JpdGFuMQ8wDQYDVQQIEwZU
YWlwZWkxCzAJBgNVBAYTAlRXMCAXDTIyMDcwNjExMjUxOFoYDzIwNTAwNzE5MTEy
NTE4WjBMMQswCQYDVQQGEwJUVzEPMA0GA1UECBMGVGFpcGVpMRswGQYDVQQKExJs
aWJ3ZWJzb2NrZXRzLXRlc3QxDzANBgNVBAMTBmNsaWVudDCCAiIwDQYJKoZIhvcN
AQEBBQADggIPADCCAgoCggIBAK8EsjZ4X7w4nBiTgX1ECxhKSgtvzduU6Nin+us5
A295uCD8C3HMRlh1rnsYqZrwMBfl9uhRs+XyXavBYcJavp6o+D4Zf0PsFAZUPlzI
6iKcMs6WZuB5vjjgin7Ml3Ziy6RclZOK69hkOChUkF0F8mccWzRXYJwq75rZCAUG
75pwcj/sJ3s4VECi5JkD9ko+3kcy6Ml9qgCLghWG1gOg1kvTy1boKkry3kKs9bad
n2aYQXY3yJXgwbs2IAdGnPcgx9ZnnkDgjT2s0QHFlsLvqFVphARJDwyNaYbpw/d9
xtUXFnZkFK+5B3jzD8Oadwc/qo6mh/uqAk4WQqPmFPYUP+Ph8DDcvELMo2pn+9c+
YOAoXtuED33J9N1sv2UWrkoLEJ8s4a2c/ViefvMFKo+OEPBOqWES9IeMXiLBDr/i
MDW1WJvFtWx2W3VtUnjMKbRG+xj4FGjv8ic/4SqnK6SCe/yKGgEyvo/afNm90Itv
1E6aiAyIMsmQC1JOoVh/e0qDGqpIVI7A/Osz9tShxDeRuZF9+gMMn0hAJkb5R3Mx
o8X6kDwaszHHvRUsfTfkfW8P1QbHwnMxeSypu0aIxw+c8Dmzt+ls7bj2m26H89Jh
Ylcrhd3Dm/7ahlVfd5yKwAW8vw2wWyECVy7LLZ6cKZWW2mZGKj7zuxkVe9B/MDTu
Tn9zAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAMLpo+SXCF+5sM5hWbJm8wRdN6Ec
MHjT3ta+zytm3oyqYcS/bSCVt0j8eqrZT3scCwwiwSy2s7Zcet5umkFUHb6Jen0Q
lXKBoaSGTWBOZbkiU6O6m6RLMrMmbp2khNag6Yq9p3sfsYz8iE18z6C1vpuKc9QT
PQLQezlhwZZWRNwkCyNRBji9N7U/uOROI8dk6KHnMZR2iR4eCSB2gtkvhPeLUXwX
8Jym2QHX5CY7/YMfs4D0I8fpjSOeeRY3cnJv37LuQL5rJpuvIZxFJ3nVr7V+DTMr
NqNE4iWHMVXqcEbMS/YJj84icGe1pCU01eI/a34rySgUu+3SCUUaXZs/+QA=
-----END CERTIFICATE-----
+51
View File
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEArwSyNnhfvDicGJOBfUQLGEpKC2/N25To2Kf66zkDb3m4IPwL
ccxGWHWuexipmvAwF+X26FGz5fJdq8Fhwlq+nqj4Phl/Q+wUBlQ+XMjqIpwyzpZm
4Hm+OOCKfsyXdmLLpFyVk4rr2GQ4KFSQXQXyZxxbNFdgnCrvmtkIBQbvmnByP+wn
ezhUQKLkmQP2Sj7eRzLoyX2qAIuCFYbWA6DWS9PLVugqSvLeQqz1tp2fZphBdjfI
leDBuzYgB0ac9yDH1meeQOCNPazRAcWWwu+oVWmEBEkPDI1phunD933G1RcWdmQU
r7kHePMPw5p3Bz+qjqaH+6oCThZCo+YU9hQ/4+HwMNy8Qsyjamf71z5g4Che24QP
fcn03Wy/ZRauSgsQnyzhrZz9WJ5+8wUqj44Q8E6pYRL0h4xeIsEOv+IwNbVYm8W1
bHZbdW1SeMwptEb7GPgUaO/yJz/hKqcrpIJ7/IoaATK+j9p82b3Qi2/UTpqIDIgy
yZALUk6hWH97SoMaqkhUjsD86zP21KHEN5G5kX36AwyfSEAmRvlHczGjxfqQPBqz
Mce9FSx9N+R9bw/VBsfCczF5LKm7RojHD5zwObO36WztuPabbofz0mFiVyuF3cOb
/tqGVV93nIrABby/DbBbIQJXLsstnpwplZbaZkYqPvO7GRV70H8wNO5Of3MCAwEA
AQKCAgBlx3Vb9+S0sLcWE7HaBxfsqEcnH323lIFUfVuOLzmwOji59dkx9Hl0N/uf
2lfHoqZVP4a208yqjKesmmkffWYdHmY+ttUryr5ab+NUUgLW3b8uJIjvQd09dlcU
JiZu0kVa7/yMMN22mZGO4p6RekPdcsAXUDj+mHl+s3fWdJIiXgIS6mKLZdaZQCFw
Wr1/r8/T1+dIRav3ZSnhGuiac44ryp8Viy4NJ+/ZFnxOFpv8Lc7j0Mz1XB9Nn8xA
bvAZRxbvuJJvaCa7FyT0wtNJydU61oHPfC0kP5h+vLMZ2isnAKYvc0QUbKXL0I36
Unml3YByNKYf1F5Cyu82ITdPY7dC99Uon/G5fRUVnamX4A+NbfGEhlJ2XXUQXTih
AwFuZHrTHAJX9dow4LQQuIhDPOYUNzZo4W04DDJ+buf4gzpNh19YReBDuzg3CTbY
K8REnXvMY4KD6ciMOraxJS1CImgSphVpP7TwOOlbuxj9v1EcTAx4Ih18qrql622V
4Ep4lb/JxWnXNHw/z2dpdMUkJJf3lmn+/4ZV+gNBtByqLRIPTG2l7O4UnR/2iuqx
0wKpY7Th4IfXjSVW6sOc+Jw7d5hsV/z7AUREEJ4WX6NNpBwQZN2VxH07Ixyvk3ZQ
XgVQXNhryNCIWpt/fWivuPCzwt2UtOrp/5WC57w1eeMsXchrAQKCAQEA5KHzL4vW
BXefo6/KhwIJA7y5TSgAbDhYpQi42Llt37NMnJcyKTVmyxpfYG8enp1FqzTVYtce
ZIw3e9T5HGNqUjpoxCp53KcDu8iGF6CQqJMFrk8GkVQU81qXuWoAP2yJfQSmBuhg
PdHKUK1hG1o62DH2gW3en7j3pxCp4Nhp9Ip/59g0LsPDBg6nYi8x5aDNoYmvEVEA
5FRz7u/wwgymGIPgWpYG3c5ks1nQAZGSa7dLFtDMEYOx476wBuHAVSWSApdUM8Vw
k/kcf/vao3Ozqe3os77CoFRwZMNaz/F/z5SwcGwBoe4QgiBo39KXR3ZcW3Hchvzz
F8T6m80xazgTjQKCAQEAw/fUbSxik3x8ZpYPqSJZob0XvKktg7R5DN2W/eLnF4cU
E3p+8Z38LWv7l6rrNGMANHjYDGSZ/gy+H1ou8irT+UdBllXKpqD59ZVUs+ovNszR
hP7wlLM0BgEDX3kn6xRAcZ+tkYSdWyco+ZKOHcfquT/rJ4K5BCeu31HoxtQlxJDQ
R6KjBN+ac7c3l2WRGz/pKj0G952qBCrXLq220TNqDKDDLPT2ZFAkeHwzPX2eeqSu
b4kHEIDzrfMUD5cmTsBPq0LYulguBYxZWsPcm+fWcSnw9s2L+EL27R99IgzCvdGI
qREu/ieZfym1ngeX3VpsPNT6wCUXLacpyJFmYWqe/wKCAQBDuISjzLO0It6ySVuf
6cZpyPjKFdMqOvZmy9KUvzgATseeilpdQmgUONePZNqY+NSuBZQ/FqD1/2Jf15GC
CyBvAsYNdNdruDyu3pJ5ZSH0DLheDSJQ4ari+i5+yRsRrkB7O2lCGoqHRSC8DN76
jxtm9kWhyL1saglQ/uqSowefW3bPYYbzYyDdJnIF7xzEe4DtHC8y3d95wlokqAWU
NNLt6AIIUu/tuziwtyWZkVjhdwVS2L3ZYjzKzKyvHScvEVXSVqimPRE/g0Yh0qPM
TnUlHEcVFqfnfZtc/VBkz4+gGNa6NgO1xyI0qQznXyVYEYMBwxQjF8GZhMsGYx0E
OTPxAoIBAQC6g1rL2Y2sv7OK09Ht8QKL/HPmHMJz8sWvaAt4coftjaOe8lx1zB6i
gH7BmGwpvPwJ9OXzzZ/++tPM9UFvPTHotFgJJHgu5VFh2pH2drIfHwte4GHLtU+T
U02Jhb9nbyvyuL4ynJxDkF7gQgs+7vJJBDvkq/c5rCc05zl8W5fioM2LEJDFZ6gR
8FRZpNJZtBdQGytRat7Q/pE1HSK9s4iAolWAqVkEmn5LqjH+mVvHcIuRHkL6AzFp
4su6Furf0wtAfevWV2zd3PmCeJ20SRCBPiDYVUOdsuZfQzQOv0Rt80p1VyR5wujF
7cWstZ9ljtUrhK1vSR3pVotfGHvvxdGzAoIBAQC6YpZdqM8zh2EA0uiNUOU/ZcmT
1QlKg+iXKfWZr+adPVPk1dT9yUx/nNwnfCbKcTEyVt7eAziiLsZw6eXOPK9U8zvC
JPUqggDZ3+kbk4jPDRAs0JtIkC0buHzkXF0rgA4bJzuUG0DEsXJXYqHoOm1OVJfs
xoLPthqUD0D2sCY7FbJXgNMa3vYb19kxeJ7v7piBrihLk5LWdAx2lSV0laFE9O8C
h6HaZeR9JIiBycZ9qHIXPPCyZWQOllCIVi1qSQkpQN4vYNhc/IE7MvQ1JBaOblJH
cscILC8aiJVFOSZnNjK/florxtTODD9IG3gHfxTR1sfaLv5zCQf9wOI5RI/x
-----END RSA PRIVATE KEY-----
+24
View File
@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEGzCCAwOgAwIBAgIDEAADMA0GCSqGSIb3DQEBCwUAME8xGzAZBgNVBAoTEmxp
YndlYnNvY2tldHMtdGVzdDESMBAGA1UEBxMJWGlhb2JpdGFuMQ8wDQYDVQQIEwZU
YWlwZWkxCzAJBgNVBAYTAlRXMCAXDTIyMDcwNjExMjUwM1oYDzIwNTAwNzE5MTEy
NTAzWjBPMQswCQYDVQQGEwJUVzEPMA0GA1UECBMGVGFpcGVpMRswGQYDVQQKExJs
aWJ3ZWJzb2NrZXRzLXRlc3QxEjAQBgNVBAMTCWxvY2FsaG9zdDCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBAMtXqhiYhGjVLwVuayvMMIrjK7S4LbRHlNUD
tJzFo2AjuLumteF+KLs4VjYzxudaCQryvMal0d/828un3x4gWjYTOC55cQ9MDNd+
pmMEE4+d1unTfo9882jM8ejlNFTcDoTPhoN7wqfxO15FniTwTCW90bmz72t6zLOm
3CpZuudHSnXHqtgOz6nSSbTjO1QR3LN+DvN04bMXBfe/0NVE/QpeOOGPZSGCOtKv
DZ4OuyKhm2RorGJnLmXxDFHmBMp00dAcerBERLDkLRpdW48K2i5tBQvAcJZzlL8Q
+H2fguSbB22eUytBbbNngv6hyRy0bAIzaYHwapRIxrLTWndMiB7yOvdislU+1SoF
WAbfV5yuhwOnwTMSodR09OxgUauA2yrAL2Q7BM5GnYLpUo5q5FgAwksJl8wySDpY
cQr7FrzSjqc1o9Us7hEElkvNBZk1+VPTaP5xEYypcbg+u+YCzctJWnJKbCBz2es6
KkpfC4aWc8UILlUK/W4jRFsNG3UhA1vnIZwK86iqm3k+PEbahcExA4qB/LBhogvs
4z9b2MQ35tiIoQDj7R5xN8c5AywDyb8xGv2Ymk4jGtDkdqrbtIU42+HM1yOk8u8Z
EixLQzowmqUqreBChoAosc3FOROmTCDlY634/acIIP31dedHNYZ51RrukAdA62Db
GDwgwz6TAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAKVv/SCPOgwPAY0G47ycvUHg
k3DQgmbxVTFFWQyrP6XvKHOl3eZU2ReJxeTxCMUKcNlW4+eM2oCZ7W1vitaupnO0
zLW6GBFSVXU4T+YZciIHnxkqlH3XxckgzNVLyWDAAJoaw2c+X1E9BzYbIT+zMYjZ
1bYAavyQOoMPd/1z0dYmI9FrwxZ2OBnv+cXAri0/JMGTczX6fVgSEPGfHHco6SKJ
RZW8VI9gIORuWhy9vxUWACJo0OkJDuU7+WTa9eDfjF+bbh+/Is86frL2gv8y9iMU
t267uX9RRd0FpiKp+pKMHmPwG3pukxWd8vkOrgjJDF+TVHggL1JYXZ7EJ9yjPdw=
-----END CERTIFICATE-----
+51
View File
@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKAIBAAKCAgEAy1eqGJiEaNUvBW5rK8wwiuMrtLgttEeU1QO0nMWjYCO4u6a1
4X4ouzhWNjPG51oJCvK8xqXR3/zby6ffHiBaNhM4LnlxD0wM136mYwQTj53W6dN+
j3zzaMzx6OU0VNwOhM+Gg3vCp/E7XkWeJPBMJb3RubPva3rMs6bcKlm650dKdceq
2A7PqdJJtOM7VBHcs34O83ThsxcF97/Q1UT9Cl444Y9lIYI60q8Nng67IqGbZGis
YmcuZfEMUeYEynTR0Bx6sEREsOQtGl1bjwraLm0FC8BwlnOUvxD4fZ+C5JsHbZ5T
K0Fts2eC/qHJHLRsAjNpgfBqlEjGstNad0yIHvI692KyVT7VKgVYBt9XnK6HA6fB
MxKh1HT07GBRq4DbKsAvZDsEzkadgulSjmrkWADCSwmXzDJIOlhxCvsWvNKOpzWj
1SzuEQSWS80FmTX5U9No/nERjKlxuD675gLNy0lackpsIHPZ6zoqSl8LhpZzxQgu
VQr9biNEWw0bdSEDW+chnArzqKqbeT48RtqFwTEDioH8sGGiC+zjP1vYxDfm2Iih
AOPtHnE3xzkDLAPJvzEa/ZiaTiMa0OR2qtu0hTjb4czXI6Ty7xkSLEtDOjCapSqt
4EKGgCixzcU5E6ZMIOVjrfj9pwgg/fV150c1hnnVGu6QB0DrYNsYPCDDPpMCAwEA
AQKCAgEAti3Ie1jJ6JJ3GkqfQhIiu4xkL/mDGS24rlAvlPWNNilK7PSjp288VuBW
fSFOZyBHUcPAVclii2ckFQZF49vgduopP5oWLTpZlwglUjjovZcQYVXW4f90pMRZ
R+tyQ0UUzPCQ92P+NjDum7uLwFR6JKY4LESFkKnolFw+jMLvrR42G+fTrNc4PlHl
L2cWVG/caAPcqUpim+1Yr2rvUuQIQS3BkHTEioz+3ptve9h+2LwpWIBfUZpX+4VH
IGkQicK359hcI2hGkqUbs69IxJNYfpe2GZ1EQccz0SsHNqWew+oRcaiTowFvo3ht
6GxSnqEW07H0KTKYilnZi4XIW3H50QauahbwH0uVsd2U9o4l7T8ZMe4RPmf9wFn/
Ez4FthbnQOWBo1DxaBmx9Bfn5nC5I6Ryr6XMnnJJ+eAtSjXSAV7/63CrpcB+sBtF
izF3yEQ8rtWACqxjMUPC3Jq9l9398/jzCpZZLvz4NUjjtfNh3i6DP6IhAK13TV7I
4G40brZhmXSVKBnxG7D5iY6PbgVWHFDEjPDwoJktK1BPad3qWf94fmt5ykZd/teq
eRIEvUzJG5PFGwOzxVRHDN8QQmNJcCJuytw4DnsxoC83izfh+nEqEPPQiDFoTLyD
+4VRgFBb6airVcyXWdr20CdOGgXoBMrNBl+E62MYAe2qc67cQYECggEBAO92XNAF
RAwZydzYch5E5ejz9TZbHfef4zNBHU/mV1vh89d9KjSF/OiL3EnoYxPkHgUXyn9A
4C4u1rldZ6dwCVAbZJnT8UF2nLRFMXhaqyJEmT7jCYRy7erd7VL95gVhUOsJYwLm
tYfcik07QmDSPVuGhV2KeknzTa1NSTU8vwQF+wKHi864dmzkslYnQqbFoTVRZtNe
rLb6g6ncw6JWx9yeB07VdpJp6x9TYT+HGj3kb5acuInP+jg7GZzkrHJMnHLweFvb
NWZflPfiANmwM8wMR+kfP2vogGamKCieojVaTi8vgqqjx4UGPd596FNFeiZRvXNk
0dB0AK2Q/126SlECggEBANlitdxHttxh/WqwFK35bcOTXah6PapPdg/72PuOl3Wt
7Wn8At6FIGQ3XZOOPf9v5HfqPpSlNV93AXXaO6ITzwIEb8IG3iEOtTLw292KU8lJ
Ll8XphOkVUrPkzGG6dfVNp6I2hTpYJISwAU5HvKpc9oB0YKhVLfC+idakVC5fZz6
zFAo27FeoIEdHt92Ze6Q34NJgpVbgmT0+wtuurqoZ/zRtkx45wuqI3E+TDa1Suq3
4OQse2WcgjzSfdLJtTfyeL1p4VhGycxg+JE5H4ji3z53FxrMnA0mSElyEji88xTG
HCYGInJpZr+QzgPTzWf5R46k08GKsmVgj1P2z9Oe3aMCggEAHBkdZuNXSrwz7ZAQ
Q/D9sUn++fPTHl1KgZcgY2G52nQ28pAjRap7NKZEoP99sLXRt/NEtY3dQE4KsBF/
uivxS88LDOnLo0zRsm10EpVBA3JdMP3e/gWmWSrVUCmute7nWczuK0b7Aggkyk8r
yMSiony0ZXd8RfUgpjctYevQ1h4FiBRzmTwX2Us0idttfLgv5Fu63w564bWfgW0A
H6pjJUX9wYhWs3NuPWzBOn0V3TFSzq9xD+qxh6uXo8tJWNgYe3wwA0dDitGoXzXL
t79vNIA0Z+xcusK7XlAc4jdK/UutZOgEQxWYsck7tUG84xiB7sEko8euvvD9q2Aa
NttHIQKCAQBDw0ZnVR+GUTFaqWq41JDHr085rmk8hqqJzvrTZ56E+2OPbxRB4wO2
2Z9c2LEwx+NTEfzR/zHFAWa+IvoFyuDzgwiHVfa2HUyUhYoz4+O8UgiXcdq+MrK3
sD706FZKhlcRUh2Q+TK2NgwPVL/16qaHZZu2ea3b3nZ2AABeRoQGhBNeRW4+Ov4T
YU13YQPjOtvBpA+FmVHrQIZlCO5CMy6/G2wVKHb1JuMHcOD3CMpConEDD26jvQbI
COFZ7LCFsRq8CJ5LEz3ibLPvKPxQglAOu7bDDtbyLmXIJzvQ3e10I1Ew9SaVvY9e
1beyb7qO16DbsTulECQN/+yNwtkD4Xi7AoIBAAcsoOzuPm75JO2B3v2Ed+7GK1Sc
lvVNU3Cz0NfLPJVysVSHegaSZ83ZoNPhmTBGbrVEr1b3BUY0lkFIOo3mf02l7WHY
xMQ8Hc2anU8qdOXvoXbQ4/YYK1WBvKu/ClBSEcaO90bqyDs73miFW/C1TfxPXnkS
T+rOMmwRQgLFPl5FAbHOk1AXZE4FFGPuUbRtkOErdw4tZhSwfXt/FA6+2LnHy788
XOFbq0FKncnh2ja+d7eNlHwLKcSC4CFNFKQmlEhZZJHcen/B0mzrHl5vSlYy7P7s
c2R43TlEAMwr2J0L7Xe4+3E61OdCO0F7NF0v3f/4ZoJImLtNzMO1hiaQ4ig=
-----END RSA PRIVATE KEY-----
Vendored
+56
View File
@@ -1,2 +1,58 @@
#!/bin/sh
# Nothing to do
sudo apt-get update -qq
# install core development packages
sudo apt-get install -qq build-essential
sudo apt-get install -qq cmake
sudo apt-get install -qq lcov
sudo apt-get install -qq clang
sudo apt-get install -qq gcc
sudo apt-get install -qq libconfig-dev
sudo apt-get install -qq liblwip-dev
sudo apt-get install -qq mingw-w64
# install git and useful tools packages
sudo apt-get install -qq git
sudo apt-get install -qq gitk
sudo apt-get install -qq gitg
# install source code format packages
sudo apt-get install -qq tofrodos
sudo apt-get install -qq clang-format
sudo apt-get install -qq sloccount
sudo apt-get install -qq complexity
sudo apt-get install -qq pmccabe
# install ARM compiler and debugging tools
sudo apt-get install -qq gcc-arm-none-eabi
sudo apt-get install -qq binutils-arm-none-eabi
sudo apt-get install -qq picolibc-arm-none-eabi
sudo apt-get install -qq gdb-multiarch
# install AVR compiler and debugging tools
sudo apt-get install -qq gcc-avr
sudo apt-get install -qq avarice
sudo apt-get install -qq avr-libc
sudo apt-get install -qq avrdude
sudo apt-get install -qq gdb-avr
sudo apt-get install -qq avrdude-doc
# install static analysis tools
sudo apt-get install -qq flawfinder
sudo apt-get install -qq cppcheck
sudo apt-get install -qq splint
sudo apt-get install -qq clang-tools
sudo apt-get install -qq clang-tidy
sudo apt-get install -qq codespell
# install BACnet Secure Connect (BACnet/SC) dependencies
sudo apt-get install -qq libconfig-dev
sudo apt-get install -qq libcap-dev
sudo apt-get install -qq libssl-dev
sudo apt-get install -qq libuv1-dev
git clone --branch v4.3-stable https://github.com/warmcat/libwebsockets.git
bash -c 'cd libwebsockets;mkdir build;cd build;cmake .. -DLWS_WITH_LIBUV=ON -DLWS_WITH_MINIMAL_EXAMPLES=0 -DLWS_MAX_SMP=32;make'
sudo bash -c 'cd libwebsockets;cd build;make install'
# all complete!
echo "🚀 ALL GOOD TO GO"
+183
View File
@@ -0,0 +1,183 @@
/**
* @file
* @brief Implementation of mutex abstraction used in BACNet secure connect.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date August 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 <pthread.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/bsc/bsc-event.h"
#define DEBUG_BSC_EVENT 0
#if DEBUG_BSC_EVENT == 1
#define DEBUG_PRINTF printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF debug_printf_disabled
#endif
struct BSC_Event {
pthread_mutex_t mutex;
pthread_cond_t cond;
bool v;
size_t counter;
};
BSC_EVENT *bsc_event_init(void)
{
BSC_EVENT *ev;
pthread_mutexattr_t attr;
if (pthread_mutexattr_init(&attr) != 0) {
return NULL;
}
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) {
pthread_mutexattr_destroy(&attr);
return NULL;
}
ev = malloc(sizeof(BSC_EVENT));
if (!ev) {
pthread_mutexattr_destroy(&attr);
return NULL;
}
if (pthread_mutex_init(&ev->mutex, &attr) != 0) {
pthread_mutexattr_destroy(&attr);
free(ev);
return NULL;
}
pthread_mutexattr_destroy(&attr);
if (pthread_cond_init(&ev->cond, NULL) != 0) {
pthread_mutex_destroy(&ev->mutex);
free(ev);
return NULL;
}
ev->v = false;
ev->counter = 0;
return ev;
}
void bsc_event_deinit(BSC_EVENT *ev)
{
pthread_mutex_destroy(&ev->mutex);
pthread_cond_destroy(&ev->cond);
free(ev);
}
void bsc_event_wait(BSC_EVENT *ev)
{
pthread_mutex_lock(&ev->mutex);
DEBUG_PRINTF("bsc_event_wait() >>> ev = %p\n", ev);
DEBUG_PRINTF("bsc_event_wait() counter before %zu\n", ev->counter);
ev->counter++;
DEBUG_PRINTF("bsc_event_wait() counter %zu\n", ev->counter);
while (!ev->v) {
pthread_cond_wait(&ev->cond, &ev->mutex);
}
DEBUG_PRINTF("bsc_event_wait() ev = %p\n", ev);
DEBUG_PRINTF("bsc_event_wait() before counter %zu\n", ev->counter);
ev->counter--;
DEBUG_PRINTF("bsc_event_wait() counter %zu\n", ev->counter);
if (!ev->counter) {
ev->v = false;
DEBUG_PRINTF("bsc_event_wait() reset ev\n");
}
else {
DEBUG_PRINTF("bsc_event_wait() wake up other waiting threads\n");
pthread_cond_broadcast(&ev->cond);
}
DEBUG_PRINTF("bsc_event_wait() <<< ev = %p\n", ev);
pthread_mutex_unlock(&ev->mutex);
}
bool bsc_event_timedwait(BSC_EVENT *ev, unsigned int ms_timeout)
{
struct timespec to;
int r = 0;
clock_gettime(CLOCK_REALTIME, &to);
to.tv_sec = to.tv_sec + ms_timeout / 1000;
to.tv_nsec = to.tv_nsec + (ms_timeout % 1000) * 1000000;
to.tv_sec += to.tv_nsec / 1000000000;
to.tv_nsec %= 1000000000;
DEBUG_PRINTF("bsc_event_timedwait() >>> before lock ev = %p ev->v = %d\n",
ev, ev->v);
pthread_mutex_lock(&ev->mutex);
DEBUG_PRINTF(
"bsc_event_timedwait() >>> after lock ev = %p ev->v = %d\n", ev, ev->v);
ev->counter++;
DEBUG_PRINTF("bsc_event_timedwait() counter %zu\n", ev->counter);
while (!ev->v) {
r = pthread_cond_timedwait(&ev->cond, &ev->mutex, &to);
if (r != 0) {
break;
}
}
if(ev->v) {
if(r!=0) {
DEBUG_PRINTF("Fired!!! r = %d\n", r);
}
r = 0;
}
ev->counter--;
DEBUG_PRINTF("bsc_event_timedwait() counter %zu\n", ev->counter);
if (!ev->counter) {
DEBUG_PRINTF(
"bsc_event_timedwait() event is reset, err = %d\n", r);
ev->v = false;
}
else {
DEBUG_PRINTF("bsc_event_timedwait() wake up other waiting threads\n");
pthread_cond_broadcast(&ev->cond);
}
DEBUG_PRINTF(
"bsc_event_timedwait() <<< ret = %d, ev = %p ev->v = %d r = %d\n",
r == 0, ev, ev->v, r);
pthread_mutex_unlock(&ev->mutex);
return r == 0;
}
void bsc_event_signal(BSC_EVENT *ev)
{
DEBUG_PRINTF("bsc_event_signal() >>> ev = %p\n", ev);
pthread_mutex_lock(&ev->mutex);
DEBUG_PRINTF("bsc_event_signal() >>> ev = %p\n", ev);
ev->v = true;
pthread_cond_broadcast(&ev->cond);
DEBUG_PRINTF("bsc_event_signal() <<< ev = %p\n", ev);
pthread_mutex_unlock(&ev->mutex);
DEBUG_PRINTF("bsc_event_signal() <<< ev = %p\n", ev);
}
void bsc_wait(int seconds)
{
sleep(seconds);
}
void bsc_wait_ms(int mseconds)
{
usleep(mseconds * 1000);
}
+762
View File
@@ -0,0 +1,762 @@
/**
* @file
* @brief Implementation of websocket client interface.
* @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 <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "bacnet/datalink/bsc/websocket.h"
#include "bacnet/basic/sys/debug.h"
#include "websocket-global.h"
#define DEBUG_WEBSOCKET_CLIENT 0
#if DEBUG_WEBSOCKET_CLIENT == 1
#define DEBUG_PRINTF debug_printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF(...)
#endif
#ifndef LWS_PROTOCOL_LIST_TERM
#define LWS_PROTOCOL_LIST_TERM \
{ \
NULL, NULL, 0, 0, 0, NULL, 0 \
}
#endif
#define BSC_RX_BUFFER_LEN BSC_WEBSOCKET_RX_BUFFER_LEN
typedef enum {
BSC_WEBSOCKET_STATE_IDLE = 0,
BSC_WEBSOCKET_STATE_CONNECTING = 1,
BSC_WEBSOCKET_STATE_CONNECTED = 2,
BSC_WEBSOCKET_STATE_DISCONNECTING = 3
} BSC_WEBSOCKET_STATE;
typedef struct {
struct lws_context *ctx;
struct lws *ws;
BSC_WEBSOCKET_STATE state;
bool want_send_data;
bool can_send_data;
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
uint8_t *fragment_buffer;
size_t fragment_buffer_size;
size_t fragment_buffer_len;
char err_desc[BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN];
BACNET_ERROR_CODE err_code;
} BSC_WEBSOCKET_CONNECTION;
/* Some forward function declarations */
static int bws_cli_websocket_event(struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len);
static const char *bws_hub_protocol = BSC_WEBSOCKET_HUB_PROTOCOL_STR;
static const char *bws_direct_protocol = BSC_WEBSOCKET_DIRECT_PROTOCOL_STR;
static pthread_mutex_t bws_cli_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
/* Websockets protocol defined in BACnet/SC \S AB.7.1. */
static struct lws_protocols bws_cli_direct_protocol[] = {
{ BSC_WEBSOCKET_DIRECT_PROTOCOL_STR, bws_cli_websocket_event, 0, 0, 0, NULL,
0 },
LWS_PROTOCOL_LIST_TERM
};
static struct lws_protocols bws_cli_hub_protocol[] = {
{ BSC_WEBSOCKET_HUB_PROTOCOL_STR, bws_cli_websocket_event, 0, 0, 0, NULL,
0 },
LWS_PROTOCOL_LIST_TERM
};
static lws_retry_bo_t bws_retry;
static BSC_WEBSOCKET_CONNECTION bws_cli_conn[BSC_CLIENT_WEBSOCKETS_MAX_NUM] = {
0
};
static BSC_WEBSOCKET_HANDLE bws_cli_alloc_connection(void)
{
int i;
for (i = 0; i < BSC_CLIENT_WEBSOCKETS_MAX_NUM; i++) {
if (bws_cli_conn[i].state == BSC_WEBSOCKET_STATE_IDLE) {
memset(&bws_cli_conn[i], 0, sizeof(bws_cli_conn[i]));
return i;
}
}
return BSC_WEBSOCKET_INVALID_HANDLE;
}
static void bws_cli_free_connection(BSC_WEBSOCKET_HANDLE h)
{
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
if (bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_IDLE) {
if (bws_cli_conn[h].fragment_buffer) {
free(bws_cli_conn[h].fragment_buffer);
bws_cli_conn[h].fragment_buffer = NULL;
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = 0;
}
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_IDLE;
bws_cli_conn[h].ws = NULL;
bws_cli_conn[h].ctx = NULL;
}
}
}
static BSC_WEBSOCKET_HANDLE bws_cli_find_connnection(struct lws *ws)
{
int i;
for (i = 0; i < BSC_CLIENT_WEBSOCKETS_MAX_NUM; i++) {
if (bws_cli_conn[i].ws == ws &&
bws_cli_conn[i].state != BSC_WEBSOCKET_STATE_IDLE) {
return i;
}
}
return BSC_WEBSOCKET_INVALID_HANDLE;
}
static void bws_set_err_desc(BSC_WEBSOCKET_HANDLE h, char *err_desc)
{
size_t len;
if (bws_cli_conn[h].err_code == ERROR_CODE_SUCCESS) {
len = strlen(err_desc) >= sizeof(bws_cli_conn[h].err_desc)
? sizeof(bws_cli_conn[h].err_desc) - 1
: strlen(err_desc);
memcpy(bws_cli_conn[h].err_desc, err_desc, len);
bws_cli_conn[h].err_desc[len] = 0;
if (strstr(err_desc, "tls:")) {
bws_cli_conn[h].err_code = ERROR_CODE_TLS_ERROR;
} else {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
}
}
}
static void bws_set_disconnect_reason(BSC_WEBSOCKET_HANDLE h, uint16_t err_code)
{
bws_cli_conn[h].err_desc[0] = 0;
switch (err_code) {
case LWS_CLOSE_STATUS_NORMAL: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER;
break;
}
case LWS_CLOSE_STATUS_GOINGAWAY: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ENDPOINT_LEAVES;
break;
}
case LWS_CLOSE_STATUS_PROTOCOL_ERR: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_PROTOCOL_ERROR;
break;
}
case LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED;
break;
}
case LWS_CLOSE_STATUS_NO_STATUS:
case LWS_CLOSE_STATUS_RESERVED: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
break;
}
case LWS_CLOSE_STATUS_ABNORMAL_CLOSE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED;
break;
}
case LWS_CLOSE_STATUS_INVALID_PAYLOAD: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_INCONSISTENT;
break;
}
case LWS_CLOSE_STATUS_POLICY_VIOLATION: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_AGAINST_POLICY;
break;
}
case LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_FRAME_TOO_LONG;
break;
}
case LWS_CLOSE_STATUS_EXTENSION_REQUIRED: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_EXTENSION_MISSING;
break;
}
case LWS_CLOSE_STATUS_UNEXPECTED_CONDITION: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_REQUEST_UNAVAILABLE;
break;
}
default: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
break;
}
}
}
static int bws_cli_websocket_event(struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len)
{
BSC_WEBSOCKET_HANDLE h;
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
uint8_t err_code[2];
(void)user;
DEBUG_PRINTF(
"bws_cli_websocket_event() >>> reason = %d, user = %p, in = %p\n",
reason, user, in);
switch (reason) {
case LWS_CALLBACK_CLIENT_ESTABLISHED: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF("bws_cli_websocket_event() connection established\n");
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_CONNECTED;
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
pthread_mutex_unlock(&bws_cli_mutex);
dispatch_func(
h, BSC_WEBSOCKET_CONNECTED, 0, NULL, NULL, 0, user_param);
break;
}
case LWS_CALLBACK_CLIENT_RECEIVE: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF(
"bws_cli_websocket_event() received %d bytes of data\n", len);
if (!lws_frame_is_binary(wsi)) {
/* According AB.7.5.3 BACnet/SC BVLC Message Exchange,
if a received data frame is not binary,
the WebSocket connection shall be closed with a
status code of 1003 -WEBSOCKET_DATA_NOT_ACCEPTED.
*/
DEBUG_PRINTF("bws_cli_websocket_event() got non-binary frame, "
"close connection for socket %d\n",
h);
lws_close_reason(
wsi, LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE, NULL, 0);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = -1\n");
return -1;
}
if (bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_CONNECTED) {
pthread_mutex_unlock(&bws_cli_mutex);
} else {
if (!bws_cli_conn[h].fragment_buffer) {
DEBUG_PRINTF("bws_cli_websocket_event() alloc %d bytes for "
"socket %d\n",
len, h);
bws_cli_conn[h].fragment_buffer = malloc(BSC_RX_BUFFER_LEN);
if (!bws_cli_conn[h].fragment_buffer) {
lws_close_reason(
wsi, LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE, NULL, 0);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = -1, "
"allocation of %d bytes failed\n",
len <= BSC_RX_BUFFER_LEN ? BSC_RX_BUFFER_LEN : len);
return -1;
}
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = BSC_RX_BUFFER_LEN;
}
if (bws_cli_conn[h].fragment_buffer_len + len >
bws_cli_conn[h].fragment_buffer_size) {
DEBUG_PRINTF(
"bws_cli_websocket_event() realloc buf of %d bytes"
"for socket %d to %d bytes\n",
bws_cli_conn[h].fragment_buffer_len, h,
bws_cli_conn[h].fragment_buffer_len + len);
bws_cli_conn[h].fragment_buffer =
realloc(bws_cli_conn[h].fragment_buffer,
bws_cli_conn[h].fragment_buffer_len + len);
if (!bws_cli_conn[h].fragment_buffer) {
lws_close_reason(
wsi, LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE, NULL, 0);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = -1, "
"re-allocation of %d bytes failed\n",
bws_cli_conn[h].fragment_buffer_len + len);
return -1;
}
bws_cli_conn[h].fragment_buffer_size =
bws_cli_conn[h].fragment_buffer_len + len;
}
DEBUG_PRINTF("bws_cli_websocket_event() got next %d bytes for "
"socket %d\n",
len, h);
memcpy(&bws_cli_conn[h].fragment_buffer[
bws_cli_conn[h].fragment_buffer_len],
in, len);
bws_cli_conn[h].fragment_buffer_len += len;
if (lws_is_final_fragment(wsi)) {
DEBUG_PRINTF(
"bws_cli_websocket_event() last fragment received\n");
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
pthread_mutex_unlock(&bws_cli_mutex);
dispatch_func(h, BSC_WEBSOCKET_RECEIVED, 0, NULL,
bws_cli_conn[h].fragment_buffer,
bws_cli_conn[h].fragment_buffer_len, user_param);
pthread_mutex_lock(&bws_cli_mutex);
bws_cli_conn[h].fragment_buffer_len = 0;
pthread_mutex_unlock(&bws_cli_mutex);
} else {
pthread_mutex_unlock(&bws_cli_mutex);
}
}
break;
}
case LWS_CALLBACK_CLIENT_WRITEABLE: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF("bws_cli_websocket_event() can write, state = %d\n",
bws_cli_conn[h].state);
DEBUG_PRINTF("bws_cli_websocket_event() ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data, bws_cli_conn[h].can_send_data);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED &&
bws_cli_conn[h].want_send_data) {
bws_cli_conn[h].can_send_data = true;
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
pthread_mutex_unlock(&bws_cli_mutex);
dispatch_func(
h, BSC_WEBSOCKET_SENDABLE, 0, NULL, NULL, 0, user_param);
pthread_mutex_lock(&bws_cli_mutex);
bws_cli_conn[h].want_send_data = false;
bws_cli_conn[h].can_send_data = false;
DEBUG_PRINTF(
"bws_cli_websocket_event() was send , ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
pthread_mutex_unlock(&bws_cli_mutex);
/* wakeup worker to process internal state */
lws_cancel_service(bws_cli_conn[h].ctx);
} else {
bws_cli_conn[h].want_send_data = false;
DEBUG_PRINTF(
"bws_cli_websocket_event() no send, ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
pthread_mutex_unlock(&bws_cli_mutex);
}
break;
}
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h != BSC_WEBSOCKET_INVALID_HANDLE && len >= 2) {
err_code[0] = ((uint8_t *)in)[1];
err_code[1] = ((uint8_t *)in)[0];
bws_set_disconnect_reason(h, *((uint16_t *)&err_code));
}
pthread_mutex_unlock(&bws_cli_mutex);
break;
}
case LWS_CALLBACK_CLIENT_CLOSED:
case LWS_CALLBACK_CLOSED:
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h != BSC_WEBSOCKET_INVALID_HANDLE) {
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
if (reason == LWS_CALLBACK_CLIENT_CONNECTION_ERROR && in) {
bws_set_err_desc(h, (char *)in);
}
pthread_mutex_unlock(&bws_cli_mutex);
/* wakeup worker to process pending event */
lws_cancel_service(bws_cli_conn[h].ctx);
} else {
pthread_mutex_unlock(&bws_cli_mutex);
}
break;
}
default: {
break;
}
}
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
static void *bws_cli_worker(void *arg)
{
BSC_WEBSOCKET_CONNECTION *conn = (BSC_WEBSOCKET_CONNECTION *)arg;
BSC_WEBSOCKET_HANDLE h = conn - &bws_cli_conn[0];
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
char err_desc[BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN];
uint16_t err_code;
while (1) {
DEBUG_PRINTF("bws_cli_worker() try mutex h = %d\n", h);
pthread_mutex_lock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() mutex locked h = %d\n", h);
if (conn->state == BSC_WEBSOCKET_STATE_CONNECTED) {
if (conn->want_send_data) {
DEBUG_PRINTF(
"bws_cli_worker() process request for sending data\n");
lws_callback_on_writable(conn->ws);
}
} else if (conn->state == BSC_WEBSOCKET_STATE_DISCONNECTING) {
DEBUG_PRINTF("bws_cli_worker() process disconnecting event\n");
DEBUG_PRINTF("bws_cli_worker() destroy ctx %p\n", conn->ctx);
/* TRICKY: Libwebsockets API is not designed to be used from
multipe service threads, as a result lws_context_destroy()
is not thread safe. More over, on different platforms the
function behaves in different ways. Call of
lws_context_destroy() leads to several calls of
bws_cli_websocket_event() callback (LWS_CALLBACK_CLOSED,
etc..). But under some OS (MacOSx) that callback is
called from context of the bws_cli_worker() thread and
under some other OS (linux) the callback is called from
internal libwebsockets lib thread. That's why
bws_cli_mutex must be unlocked before
lws_context_destroy() call. To ensure that nobody calls
lws_context_destroy() from some parallel thread it is
protected by global websocket mutex.
*/
pthread_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
lws_context_destroy(conn->ctx);
bsc_websocket_global_unlock();
pthread_mutex_lock(&bws_cli_mutex);
dispatch_func = conn->dispatch_func;
user_param = conn->user_param;
err_code = conn->err_code;
if (err_code != ERROR_CODE_SUCCESS) {
memcpy(err_desc, conn->err_desc, sizeof(err_desc));
}
bws_cli_free_connection(h);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() unlock mutex\n");
dispatch_func(h, BSC_WEBSOCKET_DISCONNECTED, err_code,
err_code != ERROR_CODE_SUCCESS ? err_desc : NULL, NULL, 0,
user_param);
return NULL;
}
DEBUG_PRINTF("bws_cli_worker() unlock mutex\n");
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() going to block on lws_service() call\n");
lws_service(conn->ctx, 0);
}
return NULL;
}
BSC_WEBSOCKET_RET bws_cli_connect(BSC_WEBSOCKET_PROTOCOL proto,
char *url,
uint8_t *ca_cert,
size_t ca_cert_size,
uint8_t *cert,
size_t cert_size,
uint8_t *key,
size_t key_size,
size_t timeout_s,
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func,
void *dispatch_func_user_param,
BSC_WEBSOCKET_HANDLE *out_handle)
{
struct lws_context_creation_info info;
char tmp_url[BSC_WSURL_MAX_LEN];
const char *prot = NULL, *addr = NULL, *path = NULL;
int port = -1;
BSC_WEBSOCKET_HANDLE h;
struct lws_client_connect_info cinfo;
pthread_t thread_id;
size_t len;
pthread_attr_t attr;
int r;
DEBUG_PRINTF("bws_cli_connect() >>> proto = %d, url = %s\n", proto, url);
if (!ca_cert || !ca_cert_size || !cert || !cert_size || !key || !key_size ||
!url || !out_handle || !timeout_s || !dispatch_func) {
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
*out_handle = BSC_WEBSOCKET_INVALID_HANDLE;
if (proto != BSC_WEBSOCKET_HUB_PROTOCOL &&
proto != BSC_WEBSOCKET_DIRECT_PROTOCOL) {
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
memset(&info, 0, sizeof(info));
memset(&cinfo, 0, sizeof(cinfo));
len = strlen(url) >= sizeof(tmp_url) ? sizeof(tmp_url) - 1 : strlen(url);
memcpy(tmp_url, url, len);
tmp_url[len] = 0;
bsc_websocket_init_log();
pthread_mutex_lock(&bws_cli_mutex);
if (lws_parse_uri(tmp_url, &prot, &addr, &port, &path) != 0 ||
port == -1 || !prot || !addr || !path) {
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
if (strcmp(prot, "wss") != 0) {
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
h = bws_cli_alloc_connection();
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_CONNECTING;
bws_cli_conn[h].fragment_buffer = NULL;
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = 0;
bws_cli_conn[h].dispatch_func = dispatch_func;
bws_cli_conn[h].user_param = dispatch_func_user_param;
info.port = CONTEXT_PORT_NO_LISTEN;
if (proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
info.protocols = bws_cli_hub_protocol;
} else {
info.protocols = bws_cli_direct_protocol;
}
info.gid = -1;
info.uid = -1;
info.client_ssl_cert_mem = cert;
info.client_ssl_cert_mem_len = cert_size;
info.client_ssl_ca_mem = ca_cert;
info.client_ssl_ca_mem_len = ca_cert_size;
info.client_ssl_key_mem = key;
info.client_ssl_key_mem_len = key_size;
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.options |= LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND;
info.timeout_secs = timeout_s;
info.connect_timeout_secs = timeout_s;
/* TRICKY: check comments related to lws_context_destroy() call */
pthread_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
bws_cli_conn[h].ctx = lws_create_context(&info);
bsc_websocket_global_unlock();
pthread_mutex_lock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() created ctx %p\n", bws_cli_conn[h].ctx);
if (!bws_cli_conn[h].ctx) {
bws_cli_free_connection(h);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
bws_cli_conn[h].ws = NULL;
cinfo.context = bws_cli_conn[h].ctx;
cinfo.address = addr;
cinfo.origin = cinfo.address;
cinfo.host = cinfo.address;
cinfo.port = port;
cinfo.path = path;
cinfo.pwsi = &bws_cli_conn[h].ws;
cinfo.alpn = "h2;http/1.1";
bws_retry.secs_since_valid_ping = 3;
bws_retry.secs_since_valid_hangup = 10;
cinfo.retry_and_idle_policy = &bws_retry;
cinfo.ssl_connection = LCCSCF_USE_SSL |
LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_SELFSIGNED;
if (proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
cinfo.protocol = bws_hub_protocol;
} else {
cinfo.protocol = bws_direct_protocol;
}
bws_cli_conn[h].err_code = ERROR_CODE_SUCCESS;
*out_handle = h;
pthread_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
lws_client_connect_via_info(&cinfo);
bsc_websocket_global_unlock();
r = pthread_attr_init(&attr);
if(!r) {
r = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
}
if(!r) {
r = pthread_create(&thread_id, &attr, &bws_cli_worker, &bws_cli_conn[h]);
}
if(r) {
/* TRICKY: Libwebsockets API is not designed to be used from
multipe service threads, as a result lws_context_destroy()
is not thread safe. More over, on different platforms the
function behaves in different ways. Call of
lws_context_destroy() leads to several calls of
bws_cli_websocket_event() callback (LWS_CALLBACK_CLOSED,
etc..). But under some OS (MacOSx) that callback is
called from context of the bws_cli_worker() thread and
under some other OS (linux) the callback is called from
internal libwebsockets lib thread. That's why
bws_cli_mutex must be unlocked before
lws_context_destroy() call. To ensure that nobody calls
lws_context_destroy() from some parallel thread it is
protected by global websocket mutex.
*/
bsc_websocket_global_lock();
lws_context_destroy(bws_cli_conn[h].ctx);
bsc_websocket_global_unlock();
pthread_mutex_lock(&bws_cli_mutex);
bws_cli_free_connection(h);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
pthread_attr_destroy(&attr);
DEBUG_PRINTF("bws_cli_connect() <<< ret = %d\n", BSC_WEBSOCKET_SUCCESS);
return BSC_WEBSOCKET_SUCCESS;
}
void bws_cli_disconnect(BSC_WEBSOCKET_HANDLE h)
{
DEBUG_PRINTF("bws_cli_disconnect() >>> h = %d\n", h);
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
pthread_mutex_lock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_disconnect() state = %d\n", bws_cli_conn[h].state);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTING ||
bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED) {
/* tell worker to process change of connection state */
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
lws_cancel_service(bws_cli_conn[h].ctx);
}
pthread_mutex_unlock(&bws_cli_mutex);
}
DEBUG_PRINTF("bws_cli_disconnect() <<<\n");
}
void bws_cli_send(BSC_WEBSOCKET_HANDLE h)
{
DEBUG_PRINTF("bws_cli_send() >>> h = %d\n", h);
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
pthread_mutex_lock(&bws_cli_mutex);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED) {
/* tell worker to process send request */
bws_cli_conn[h].want_send_data = true;
DEBUG_PRINTF("bws_cli_send() cs = 1\n");
lws_cancel_service(bws_cli_conn[h].ctx);
}
pthread_mutex_unlock(&bws_cli_mutex);
}
DEBUG_PRINTF("bws_cli_send() <<<\n");
}
BSC_WEBSOCKET_RET bws_cli_dispatch_send(
BSC_WEBSOCKET_HANDLE h, uint8_t *payload, size_t payload_size)
{
int written;
BSC_WEBSOCKET_RET ret;
DEBUG_PRINTF(
"bws_cli_dispatch_send() >>> h = %d, payload = %p, payload_size = %d\n",
h, payload, payload_size);
if (h < 0 || h >= BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
DEBUG_PRINTF(
"bws_cli_dispatch_send() <<< ret = BACNET_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
pthread_mutex_lock(&bws_cli_mutex);
if ((bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_CONNECTED) ||
!bws_cli_conn[h].want_send_data || !bws_cli_conn[h].can_send_data) {
DEBUG_PRINTF("bws_cli_dispatch_send() state = %d, ws = %d, cs = %d\n",
bws_cli_conn[h].state, bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
DEBUG_PRINTF("bws_cli_dispatch_send() <<< ret = "
"BSC_WEBSOCKET_INVALID_OPERATION\n");
pthread_mutex_unlock(&bws_cli_mutex);
return BSC_WEBSOCKET_INVALID_OPERATION;
}
written =
lws_write(bws_cli_conn[h].ws, payload, payload_size, LWS_WRITE_BINARY);
DEBUG_PRINTF("bws_cli_dispatch_send() %d bytes is sent\n", written);
if (written < (int)payload_size) {
DEBUG_PRINTF(
"bws_cli_dispatch_send() websocket connection is broken(closed)\n");
/* tell worker to process change of connection state */
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
lws_cancel_service(bws_cli_conn[h].ctx);
ret = BSC_WEBSOCKET_INVALID_OPERATION;
} else {
ret = BSC_WEBSOCKET_SUCCESS;
}
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_dispatch_send() <<< ret = %d\n", ret);
return ret;
}
+105
View File
@@ -0,0 +1,105 @@
/**
* @file
* @brief Implementation of global websocket functions.
* @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 <pthread.h>
#include <libwebsockets.h>
#include "websocket-global.h"
#include "bacnet/basic/sys/debug.h"
static pthread_mutex_t websocket_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
static pthread_mutex_t websocket_dispatch_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
#if (BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED != 1)
void bsc_websocket_global_lock(void)
{
pthread_mutex_lock(&websocket_mutex);
}
void bsc_websocket_global_unlock(void)
{
pthread_mutex_unlock(&websocket_mutex);
}
void bws_dispatch_lock(void)
{
pthread_mutex_lock(&websocket_dispatch_mutex);
}
void bws_dispatch_unlock(void)
{
pthread_mutex_unlock(&websocket_dispatch_mutex);
}
#else
static int volatile websocket_mutex_cnt = 0;
static int volatile websocket_dispatch_mutex_cnt = 0;
void bsc_websocket_global_lock_dbg(char *f, int line)
{
printf("bsc_websocket_global_lock_dbg() >>> %s:%d lock_cnt %d tid = %ld\n", f, line, websocket_mutex_cnt, pthread_self());
websocket_mutex_cnt++;
fflush(stdout);
pthread_mutex_lock(&websocket_mutex);
printf("bsc_websocket_global_lock_dbg() <<< lock_cnt %d tid = %ld\n", websocket_mutex_cnt, pthread_self());
fflush(stdout);
}
void bsc_websocket_global_unlock_dbg(char *f, int line)
{
printf("bsc_websocket_global_unlock_dbg() >>> %s:%d lock_cnt %d tid = %ld\n", f, line, websocket_mutex_cnt, pthread_self());
websocket_mutex_cnt--;
fflush(stdout);
pthread_mutex_unlock(&websocket_mutex);
printf("bsc_websocket_global_unlock_dbg() <<< lock_cnt %d tid = %ld\n", websocket_mutex_cnt, pthread_self());
fflush(stdout);
}
void bws_dispatch_lock_dbg(char *f, int line)
{
printf("bws_dispatch_lock_dbg() >>> %s:%d lock_cnt %d tid = %ld\n", f, line, websocket_dispatch_mutex_cnt, pthread_self());
websocket_dispatch_mutex_cnt++;
fflush(stdout);
pthread_mutex_lock(&websocket_dispatch_mutex);
printf("bws_dispatch_lock_dbg() <<< lock_cnt %d tid = %ld\n", websocket_dispatch_mutex_cnt, pthread_self());
fflush(stdout);
}
void bws_dispatch_unlock_dbg(char *f, int line)
{
printf("bws_dispatch_unlock_dbg() >>> %s:%d lock_cnt %d tid = %ld\n", f, line, websocket_dispatch_mutex_cnt, pthread_self());
websocket_dispatch_mutex_cnt--;
fflush(stdout);
pthread_mutex_unlock(&websocket_dispatch_mutex);
printf("bws_dispatch_unlock_dbg() <<< lock_cnt %d tid = %ld\n", websocket_dispatch_mutex_cnt, pthread_self());
fflush(stdout);
}
#endif
static bool bsc_websocket_log_initialized = false;
void bsc_websocket_init_log(void)
{
bsc_websocket_global_lock();
if(!bsc_websocket_log_initialized) {
bsc_websocket_log_initialized = true;
#if DEBUG_LIBWEBSOCKETS_ENABLED == 1
printf("LWS_MAX_SMP = %d", LWS_MAX_SMP);
lws_set_log_level(LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG |
LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY |
LLL_USER | LLL_THREAD,
NULL);
#else
lws_set_log_level(0, NULL);
#endif
}
bsc_websocket_global_unlock();
}
+27
View File
@@ -0,0 +1,27 @@
/**
* @file
* @brief Global websocket functions
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date May 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef __BSC_WEBSOCKET_MUTEX_INCLUDED__
#define __BSC_WEBSOCKET_MUTEX_INCLUDED__
#ifndef BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED
#define BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED 0
#endif
#if (BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED != 1)
void bsc_websocket_global_lock(void);
void bsc_websocket_global_unlock(void);
#else
void bsc_websocket_global_lock_dbg(char *f, int line);
void bsc_websocket_global_unlock_dbg(char *f, int line);
#define bsc_websocket_global_lock() bsc_websocket_global_lock_dbg(__FILE__, __LINE__);
#define bsc_websocket_global_unlock() bsc_websocket_global_unlock_dbg(__FILE__, __LINE__);
#endif
void bsc_websocket_init_log(void);
#endif
+999
View File
@@ -0,0 +1,999 @@
/**
* @file
* @brief Implementation of server websocket interface.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date June 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "bacnet/datalink/bsc/websocket.h"
#include "bacnet/basic/sys/debug.h"
#include "websocket-global.h"
#include <arpa/inet.h>
#define DEBUG_WEBSOCKET_SERVER 0
#if DEBUG_WEBSOCKET_SERVER == 1
#define DEBUG_PRINTF debug_printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF(...)
#endif
#define BSC_RX_BUFFER_LEN BSC_WEBSOCKET_RX_BUFFER_LEN
#ifndef LWS_PROTOCOL_LIST_TERM
#define LWS_PROTOCOL_LIST_TERM \
{ \
NULL, NULL, 0, 0, 0, NULL, 0 \
}
#endif
typedef enum {
BSC_WEBSOCKET_STATE_IDLE = 0,
BSC_WEBSOCKET_STATE_CONNECTED = 1,
BSC_WEBSOCKET_STATE_DISCONNECTING = 2
} BSC_WEBSOCKET_STATE;
/* Some forward function declarations */
static int bws_srv_websocket_event(struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len);
typedef struct {
struct lws_context *ctx;
struct lws *ws;
BSC_WEBSOCKET_STATE state;
bool want_send_data;
bool can_send_data;
uint8_t *fragment_buffer;
size_t fragment_buffer_size;
size_t fragment_buffer_len;
BACNET_ERROR_CODE err_code;
} BSC_WEBSOCKET_CONNECTION;
#if BSC_CONF_WEBSOCKET_SERVERS_NUM < 1
#error "BSC_CONF_WEBSOCKET_SERVERS_NUM must be >= 1"
#endif
static pthread_mutex_t bws_global_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
static pthread_mutex_t bws_srv_direct_mutex[BSC_CONF_WEBSOCKET_SERVERS_NUM];
static pthread_mutex_t bws_srv_hub_mutex[BSC_CONF_WEBSOCKET_SERVERS_NUM];
#if BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM > 0
static BSC_WEBSOCKET_CONNECTION
bws_hub_conn[BSC_CONF_WEBSOCKET_SERVERS_NUM]
[BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM] = { 0 };
#else
static BSC_WEBSOCKET_CONNECTION bws_hub_conn[1][1] = { 0 };
#endif
#if BSC_SERVER_DIRECT_WEBSOCKETS_MAX_NUM > 0
static BSC_WEBSOCKET_CONNECTION
bws_direct_conn[BSC_CONF_WEBSOCKET_SERVERS_NUM]
[BSC_SERVER_DIRECT_WEBSOCKETS_MAX_NUM] = { 0 };
#else
static BSC_WEBSOCKET_CONNECTION bws_direct_conn[1][1] = { 0 };
#endif
typedef struct BACNetWebsocketServerContext {
bool used;
struct lws_context *wsctx;
BSC_WEBSOCKET_PROTOCOL proto;
BSC_WEBSOCKET_CONNECTION *conn;
pthread_mutex_t *mutex;
BSC_WEBSOCKET_SRV_DISPATCH dispatch_func;
void *user_param;
bool stop_worker;
} BSC_WEBSOCKET_CONTEXT;
static BSC_WEBSOCKET_CONTEXT bws_hub_ctx[BSC_CONF_WEBSOCKET_SERVERS_NUM] = {
0
};
static BSC_WEBSOCKET_CONTEXT bws_direct_ctx[BSC_CONF_WEBSOCKET_SERVERS_NUM] = {
0
};
static bool bws_mutex_init(pthread_mutex_t *mutex)
{
pthread_mutexattr_t attr;
if (pthread_mutexattr_init(&attr) != 0) {
return false;
}
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) {
pthread_mutexattr_destroy(&attr);
return false;
}
if (pthread_mutex_init(mutex, &attr) != 0) {
pthread_mutexattr_destroy(&attr);
return false;
}
pthread_mutexattr_destroy(&attr);
return true;
}
static BSC_WEBSOCKET_CONTEXT *bws_alloc_server_ctx(BSC_WEBSOCKET_PROTOCOL proto)
{
int i;
BSC_WEBSOCKET_CONTEXT *ctx = (proto == BSC_WEBSOCKET_HUB_PROTOCOL)
? &bws_hub_ctx[0]
: &bws_direct_ctx[0];
pthread_mutex_lock(&bws_global_mutex);
DEBUG_PRINTF("bws_alloc_server_ctx() >>> proto = %d\n", proto);
for (i = 0; i < BSC_CONF_WEBSOCKET_SERVERS_NUM; i++) {
if (!ctx[i].used) {
if (proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
ctx[i].mutex = &bws_srv_hub_mutex[i];
ctx[i].conn = &bws_hub_conn[i][0];
} else {
ctx[i].mutex = &bws_srv_direct_mutex[i];
ctx[i].conn = &bws_direct_conn[i][0];
}
if (!bws_mutex_init(ctx[i].mutex)) {
DEBUG_PRINTF("bws_alloc_server_ctx() <<< ret = %p\n", &ctx[i]);
pthread_mutex_unlock(&bws_global_mutex);
return NULL;
}
ctx[i].used = true;
DEBUG_PRINTF("bws_alloc_server_ctx() <<< ret = %p\n", &ctx[i]);
pthread_mutex_unlock(&bws_global_mutex);
return &ctx[i];
}
}
DEBUG_PRINTF("bws_alloc_server_ctx() <<< ret = %p\n", &ctx[i]);
pthread_mutex_unlock(&bws_global_mutex);
return NULL;
}
static void bws_set_disconnect_reason(
BSC_WEBSOCKET_CONTEXT *ctx, BSC_WEBSOCKET_HANDLE h, uint16_t err_code)
{
switch (err_code) {
case LWS_CLOSE_STATUS_NORMAL: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER;
break;
}
case LWS_CLOSE_STATUS_GOINGAWAY: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_ENDPOINT_LEAVES;
break;
}
case LWS_CLOSE_STATUS_PROTOCOL_ERR: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_PROTOCOL_ERROR;
break;
}
case LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED;
break;
}
case LWS_CLOSE_STATUS_NO_STATUS:
case LWS_CLOSE_STATUS_RESERVED: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
break;
}
case LWS_CLOSE_STATUS_ABNORMAL_CLOSE: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED;
break;
}
case LWS_CLOSE_STATUS_INVALID_PAYLOAD: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_INCONSISTENT;
break;
}
case LWS_CLOSE_STATUS_POLICY_VIOLATION: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_AGAINST_POLICY;
break;
}
case LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_FRAME_TOO_LONG;
break;
}
case LWS_CLOSE_STATUS_EXTENSION_REQUIRED: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_EXTENSION_MISSING;
break;
}
case LWS_CLOSE_STATUS_UNEXPECTED_CONDITION: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_REQUEST_UNAVAILABLE;
break;
}
default: {
ctx->conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
break;
}
}
}
static void bws_free_server_ctx(BSC_WEBSOCKET_CONTEXT *ctx)
{
pthread_mutex_lock(&bws_global_mutex);
DEBUG_PRINTF("bws_free_server_ctx() >>> ctx = %p\n", ctx);
ctx->used = false;
ctx->wsctx = NULL;
ctx->conn = NULL;
pthread_mutex_destroy(ctx->mutex);
ctx->mutex = NULL;
ctx->dispatch_func = NULL;
ctx->user_param = NULL;
DEBUG_PRINTF("bws_free_server_ctx() <<< \n");
pthread_mutex_unlock(&bws_global_mutex);
}
#if DEBUG_ENABLED == 1
static bool bws_validate_ctx_pointer(BSC_WEBSOCKET_CONTEXT *ctx)
{
bool is_validated = false;
int i;
pthread_mutex_lock(&bws_global_mutex);
for (i = 0; i < BSC_CONF_WEBSOCKET_SERVERS_NUM; i++) {
if (ctx == &bws_hub_ctx[i]) {
is_validated = true;
break;
}
}
if (!is_validated) {
for (i = 0; i < BSC_CONF_WEBSOCKET_SERVERS_NUM; i++) {
if (ctx == &bws_direct_ctx[i]) {
is_validated = true;
break;
}
}
}
pthread_mutex_unlock(&bws_global_mutex);
return is_validated;
}
#endif
static int bws_srv_get_max_sockets(BSC_WEBSOCKET_PROTOCOL proto)
{
int max = 0;
if (proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
max = BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM;
} else if (proto == BSC_WEBSOCKET_DIRECT_PROTOCOL) {
max = BSC_SERVER_DIRECT_WEBSOCKETS_MAX_NUM;
}
return max;
}
static BSC_WEBSOCKET_HANDLE bws_srv_alloc_connection(BSC_WEBSOCKET_CONTEXT *ctx)
{
int i;
DEBUG_PRINTF("bws_srv_alloc_connection() >>> ctx = %p\n", ctx);
for (i = 0; i < bws_srv_get_max_sockets(ctx->proto); i++) {
if (ctx->conn[i].state == BSC_WEBSOCKET_STATE_IDLE) {
memset(&ctx->conn[i], 0, sizeof(ctx->conn[i]));
DEBUG_PRINTF("bws_srv_alloc_connection() <<< ret = %d\n", i);
return i;
}
}
DEBUG_PRINTF("bws_srv_alloc_connection() <<< ret = "
"BSC_WEBSOCKET_INVALID_HANDLE\n");
return BSC_WEBSOCKET_INVALID_HANDLE;
}
static void bws_srv_free_connection(
BSC_WEBSOCKET_CONTEXT *ctx, BSC_WEBSOCKET_HANDLE h)
{
DEBUG_PRINTF("bws_srv_free_connection() >>> ctx = %p, h = %d\n", ctx, h);
if (h >= 0 && h < bws_srv_get_max_sockets(ctx->proto)) {
if (ctx->conn[h].state != BSC_WEBSOCKET_STATE_IDLE) {
if (ctx->conn[h].fragment_buffer) {
free(ctx->conn[h].fragment_buffer);
ctx->conn[h].fragment_buffer = NULL;
ctx->conn[h].fragment_buffer_len = 0;
ctx->conn[h].fragment_buffer_size = 0;
}
ctx->conn[h].state = BSC_WEBSOCKET_STATE_IDLE;
ctx->conn[h].ws = NULL;
}
}
DEBUG_PRINTF("bws_srv_free_connection() <<<\n");
}
static BSC_WEBSOCKET_HANDLE bws_find_connnection(
BSC_WEBSOCKET_CONTEXT *ctx, struct lws *ws)
{
int i;
for (i = 0; i < bws_srv_get_max_sockets(ctx->proto); i++) {
if (ctx->conn[i].ws == ws &&
ctx->conn[i].state != BSC_WEBSOCKET_STATE_IDLE) {
return i;
}
}
return BSC_WEBSOCKET_INVALID_HANDLE;
}
static int bws_srv_websocket_event(struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len)
{
BSC_WEBSOCKET_HANDLE h;
BSC_WEBSOCKET_CONTEXT *ctx =
(BSC_WEBSOCKET_CONTEXT *)lws_context_user(lws_get_context(wsi));
int ret = 0;
BSC_WEBSOCKET_SRV_DISPATCH dispatch_func;
void *user_param;
bool stop_worker;
uint8_t err_code[2];
uint16_t err;
(void)user;
DEBUG_PRINTF("bws_srv_websocket_event() >>> ctx = %p, user_param = %p, "
"proto = %d, wsi = %p, "
"reason = %d, in = %p, len = %d\n",
ctx, ctx->user_param, ctx->proto, wsi, reason, in, len);
switch (reason) {
case LWS_CALLBACK_ESTABLISHED: {
pthread_mutex_lock(ctx->mutex);
DEBUG_PRINTF("bws_srv_websocket_event() established connection\n");
h = bws_srv_alloc_connection(ctx);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF("bws_srv_websocket_event() no free sockets, "
"dropping incoming connection\n");
pthread_mutex_unlock(ctx->mutex);
return -1;
}
DEBUG_PRINTF(
"bws_srv_websocket_event() ctx %p proto %d set state of"
" socket %d to BACNET_WEBSOCKET_STATE_CONNECTING\n",
ctx, ctx->proto, h);
ctx->conn[h].ws = wsi;
ctx->conn[h].state = BSC_WEBSOCKET_STATE_CONNECTED;
ctx->conn[h].err_code = ERROR_CODE_SUCCESS;
dispatch_func = ctx->dispatch_func;
user_param = ctx->user_param;
pthread_mutex_unlock(ctx->mutex);
dispatch_func((BSC_WEBSOCKET_SRV_HANDLE)ctx, h,
BSC_WEBSOCKET_CONNECTED, 0, NULL, NULL, 0, user_param);
/* wakeup worker to process pending event */
lws_cancel_service(ctx->wsctx);
break;
}
case LWS_CALLBACK_CLOSED: {
DEBUG_PRINTF("bws_srv_websocket_event() closed connection\n");
pthread_mutex_lock(ctx->mutex);
h = bws_find_connnection(ctx, wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
pthread_mutex_unlock(ctx->mutex);
} else {
DEBUG_PRINTF("bws_srv_websocket_event() ctx %p user_param = %p "
"proto %d state of "
"socket %d is %d\n",
ctx, ctx->user_param, ctx->proto, h, ctx->conn[h].state);
dispatch_func = ctx->dispatch_func;
user_param = ctx->user_param;
stop_worker = ctx->stop_worker;
err = ctx->conn[h].err_code;
bws_srv_free_connection(ctx, h);
pthread_mutex_unlock(ctx->mutex);
if (!stop_worker) {
dispatch_func((BSC_WEBSOCKET_SRV_HANDLE)ctx, h,
BSC_WEBSOCKET_DISCONNECTED, err, NULL, NULL, 0,
user_param);
}
}
break;
}
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: {
pthread_mutex_lock(ctx->mutex);
h = bws_find_connnection(ctx, wsi);
if (h != BSC_WEBSOCKET_INVALID_HANDLE && len >= 2) {
err_code[0] = ((uint8_t *)in)[1];
err_code[1] = ((uint8_t *)in)[0];
bws_set_disconnect_reason(ctx, h, *((uint16_t *)&err_code));
}
pthread_mutex_unlock(ctx->mutex);
break;
}
case LWS_CALLBACK_RECEIVE: {
pthread_mutex_lock(ctx->mutex);
h = bws_find_connnection(ctx, wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
pthread_mutex_unlock(ctx->mutex);
} else {
DEBUG_PRINTF("bws_srv_websocket_event() ctx %p proto %d "
"received %d bytes of "
"data for websocket %d\n",
ctx, ctx->proto, len, h);
if (!lws_frame_is_binary(wsi)) {
/* According AB.7.5.3 BACnet/SC BVLC Message Exchange,
if a received data frame is not binary,
the WebSocket connection shall be closed with a
status code of 1003 -WEBSOCKET_DATA_NOT_ACCEPTED.
*/
DEBUG_PRINTF(
"bws_srv_websocket_event() ctx %p proto %d got "
"non-binary frame, "
"close websocket %d\n",
ctx, ctx->proto, h);
lws_close_reason(
wsi, LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE, NULL, 0);
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF("bws_srv_websocket_event() <<< ret = -1\n");
return -1;
}
if (ctx->conn[h].state != BSC_WEBSOCKET_STATE_CONNECTED) {
pthread_mutex_unlock(ctx->mutex);
} else {
if (!ctx->conn[h].fragment_buffer) {
ctx->conn[h].fragment_buffer =
malloc(BSC_RX_BUFFER_LEN);
if (!ctx->conn[h].fragment_buffer) {
lws_close_reason(wsi,
LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE, NULL, 0);
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF("bws_srv_websocket_event() <<< ret = "
"-1, allocation of %d bytes failed\n",
len <= BSC_RX_BUFFER_LEN ? BSC_RX_BUFFER_LEN
: len);
return -1;
}
ctx->conn[h].fragment_buffer_len = 0;
ctx->conn[h].fragment_buffer_size = BSC_RX_BUFFER_LEN;
}
if (ctx->conn[h].fragment_buffer_len + len >
ctx->conn[h].fragment_buffer_size) {
DEBUG_PRINTF(
"bws_srv_websocket_event() realloc buf of %d bytes"
"for socket %d to %d bytes\n",
ctx->conn[h].fragment_buffer_len, h,
ctx->conn[h].fragment_buffer_len + len);
ctx->conn[h].fragment_buffer =
realloc(ctx->conn[h].fragment_buffer,
ctx->conn[h].fragment_buffer_len + len);
if (!ctx->conn[h].fragment_buffer) {
lws_close_reason(wsi,
LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE, NULL, 0);
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF(
"bws_srv_websocket_event() <<< ret = -1, "
"re-allocation of %d bytes failed\n",
ctx->conn[h].fragment_buffer_len + len);
return -1;
}
ctx->conn[h].fragment_buffer_size =
ctx->conn[h].fragment_buffer_len + len;
}
DEBUG_PRINTF(
"bws_srv_websocket_event() got next %d bytes for "
"socket %d total_len %d\n",
len, h, ctx->conn[h].fragment_buffer_len);
memcpy(&ctx->conn[h].fragment_buffer[
ctx->conn[h].fragment_buffer_len],
in, len);
ctx->conn[h].fragment_buffer_len += len;
if (lws_is_final_fragment(wsi) && !ctx->stop_worker) {
dispatch_func = ctx->dispatch_func;
user_param = ctx->user_param;
pthread_mutex_unlock(ctx->mutex);
dispatch_func((BSC_WEBSOCKET_SRV_HANDLE)ctx, h,
BSC_WEBSOCKET_RECEIVED, 0, NULL,
ctx->conn[h].fragment_buffer,
ctx->conn[h].fragment_buffer_len, user_param);
pthread_mutex_lock(ctx->mutex);
ctx->conn[h].fragment_buffer_len = 0;
pthread_mutex_unlock(ctx->mutex);
} else {
pthread_mutex_unlock(ctx->mutex);
}
}
}
break;
}
case LWS_CALLBACK_SERVER_WRITEABLE: {
pthread_mutex_lock(ctx->mutex);
DEBUG_PRINTF(
"bws_srv_websocket_event() ctx %p proto %d can write\n", ctx,
ctx->proto);
h = bws_find_connnection(ctx, wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
pthread_mutex_unlock(ctx->mutex);
} else {
DEBUG_PRINTF("bws_srv_websocket_event() ctx %p proto %d socket "
"%d state = %d\n",
ctx, ctx->proto, h, ctx->conn[h].state);
if (ctx->conn[h].state == BSC_WEBSOCKET_STATE_DISCONNECTING) {
/* if -1 returned from this callback libwebsocket closes
+ websocket related to wsi */
ret = -1;
}
if ((ctx->conn[h].state != BSC_WEBSOCKET_STATE_IDLE) &&
!ctx->stop_worker && ctx->conn[h].want_send_data) {
ctx->conn[h].can_send_data = true;
dispatch_func = ctx->dispatch_func;
user_param = ctx->user_param;
stop_worker = ctx->stop_worker;
pthread_mutex_unlock(ctx->mutex);
if (!stop_worker) {
dispatch_func((BSC_WEBSOCKET_SRV_HANDLE)ctx, h,
BSC_WEBSOCKET_SENDABLE, 0, NULL, NULL, 0,
user_param);
}
pthread_mutex_lock(ctx->mutex);
ctx->conn[h].want_send_data = false;
ctx->conn[h].can_send_data = false;
pthread_mutex_unlock(ctx->mutex);
/* wakeup worker to process internal state */
lws_cancel_service(ctx->wsctx);
} else {
ctx->conn[h].want_send_data = false;
pthread_mutex_unlock(ctx->mutex);
}
}
break;
}
default: {
break;
}
}
DEBUG_PRINTF("bws_srv_websocket_event() <<< ret = %d\n", ret);
return ret;
}
static void *bws_srv_worker(void *arg)
{
BSC_WEBSOCKET_CONTEXT *ctx = (BSC_WEBSOCKET_CONTEXT *)arg;
int i;
BSC_WEBSOCKET_SRV_DISPATCH dispatch_func;
void *user_param;
DEBUG_PRINTF("bws_srv_worker() started for ctx %p proto %d user_param %p\n",
ctx, ctx->proto, ctx->user_param);
pthread_mutex_lock(ctx->mutex);
dispatch_func = ctx->dispatch_func;
user_param = ctx->user_param;
pthread_mutex_unlock(ctx->mutex);
dispatch_func((BSC_WEBSOCKET_SRV_HANDLE)ctx, 0,
BSC_WEBSOCKET_SERVER_STARTED, 0, NULL, NULL, 0, user_param);
while (1) {
DEBUG_PRINTF("bws_srv_worker() ctx %p proto %d blocked user_param %p\n",
ctx, ctx->proto, ctx->user_param);
pthread_mutex_lock(ctx->mutex);
if (ctx->stop_worker) {
DEBUG_PRINTF("bws_srv_worker() ctx %p user_param %p proto %d going "
"to stop\n",
ctx, ctx->user_param, ctx->proto);
DEBUG_PRINTF("bws_srv_worker() destroy wsctx %p, ctx = %p, "
"user_param = %p\n",
ctx->wsctx, ctx, ctx->user_param);
/* TRICKY: Libwebsockets API is not designed to be used from
multipe service threads, as a result lws_context_destroy()
is not thread safe. More over, on different platforms the
function behaves in different ways. Call of
lws_context_destroy() leads to several calls of
bws_srv_websocket_event() callback (LWS_CALLBACK_CLOSED,
etc..). But under some OS (MacOSx) that callback is
called from context of the bws_cli_worker() thread and
under some other OS (linux) the callback is called from
internal libwebsockets lib thread. That's why
bws_cli_mutex must be unlocked before
lws_context_destroy() call. To ensure that nobody calls
lws_context_destroy() from some parallel thread it is
protected by global websocket mutex.
*/
pthread_mutex_unlock(ctx->mutex);
bsc_websocket_global_lock();
lws_context_destroy(ctx->wsctx);
bsc_websocket_global_unlock();
pthread_mutex_lock(ctx->mutex);
ctx->wsctx = NULL;
DEBUG_PRINTF("bws_srv_worker() set wsctx %p\n", ctx->wsctx);
ctx->stop_worker = false;
DEBUG_PRINTF(
"bws_srv_worker() ctx %p proto %d emitting stop event\n", ctx,
ctx->proto);
dispatch_func = ctx->dispatch_func;
user_param = ctx->user_param;
DEBUG_PRINTF(
"bws_srv_worker() ctx %p user_param = %p\n", ctx, user_param);
pthread_mutex_unlock(ctx->mutex);
dispatch_func(ctx, 0, BSC_WEBSOCKET_SERVER_STOPPED, 0, NULL, NULL,
0, user_param);
bws_free_server_ctx(ctx);
DEBUG_PRINTF(
"bws_srv_worker() ctx %p proto %d stopped\n", ctx, ctx->proto);
return NULL;
}
for (i = 0; i < bws_srv_get_max_sockets(ctx->proto); i++) {
DEBUG_PRINTF("bws_srv_worker() ctx %p user_param %p proto %d "
"socket %d(%p) state = %d\n",
ctx, ctx->user_param, ctx->proto, i, &ctx->conn[i],
ctx->conn[i].state);
if (ctx->conn[i].state == BSC_WEBSOCKET_STATE_CONNECTED) {
if (ctx->conn[i].want_send_data) {
DEBUG_PRINTF("bws_srv_worker() process request for sending "
"data on socket %d\n",
i);
lws_callback_on_writable(ctx->conn[i].ws);
}
} else if (ctx->conn[i].state ==
BSC_WEBSOCKET_STATE_DISCONNECTING) {
DEBUG_PRINTF("bws_srv_worker() process disconnecting event on "
"socket %d\n",
i);
lws_callback_on_writable(ctx->conn[i].ws);
}
}
DEBUG_PRINTF(
"bws_srv_worker() ctx %p proto %d unblocked\n", ctx, ctx->proto);
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF(
"bws_srv_worker() ctx %p user_param %p proto %d going to block on "
"lws_service() call\n",
ctx, ctx->user_param, ctx->proto);
lws_service(ctx->wsctx, 0);
}
return NULL;
}
BSC_WEBSOCKET_RET bws_srv_start(BSC_WEBSOCKET_PROTOCOL proto,
int port,
char *iface,
uint8_t *ca_cert,
size_t ca_cert_size,
uint8_t *cert,
size_t cert_size,
uint8_t *key,
size_t key_size,
size_t timeout_s,
BSC_WEBSOCKET_SRV_DISPATCH dispatch_func,
void *dispatch_func_user_param,
BSC_WEBSOCKET_SRV_HANDLE *sh)
{
pthread_t thread_id;
struct lws_context_creation_info info = { 0 };
BSC_WEBSOCKET_CONTEXT *ctx;
pthread_attr_t attr;
int r;
struct lws_protocols protos[] = {
{ (proto == BSC_WEBSOCKET_HUB_PROTOCOL)
? BSC_WEBSOCKET_HUB_PROTOCOL_STR
: BSC_WEBSOCKET_DIRECT_PROTOCOL_STR,
bws_srv_websocket_event, 0, 0, 0, NULL, 0 },
LWS_PROTOCOL_LIST_TERM
};
DEBUG_PRINTF("bws_srv_start() >>> proto = %d port = %d "
"dispatch_func_user_param = %p\n",
proto, port, dispatch_func_user_param);
if (proto != BSC_WEBSOCKET_HUB_PROTOCOL &&
proto != BSC_WEBSOCKET_DIRECT_PROTOCOL) {
DEBUG_PRINTF("bws_srv_start() <<< bad protocol, ret = "
"BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
if (!ca_cert || !ca_cert_size || !cert || !cert_size || !key || !key_size ||
!dispatch_func || !timeout_s || !sh) {
DEBUG_PRINTF("bws_srv_start() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
if (port < 0 || port > 65535) {
DEBUG_PRINTF("bws_srv_start() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
ctx = bws_alloc_server_ctx(proto);
if (!ctx) {
DEBUG_PRINTF(
"bws_srv_start() <<< maximum amount of servers for "
"server proto %d is to small, ret = BSC_WEBSOCKET_NO_RESOURCES\n",
proto);
return BSC_WEBSOCKET_NO_RESOURCES;
}
bsc_websocket_init_log();
pthread_mutex_lock(ctx->mutex);
info.port = port;
info.iface = iface;
info.protocols = protos;
info.gid = -1;
info.uid = -1;
info.server_ssl_cert_mem = cert;
info.server_ssl_cert_mem_len = cert_size;
info.server_ssl_ca_mem = ca_cert;
info.server_ssl_ca_mem_len = ca_cert_size;
info.server_ssl_private_key_mem = key;
info.server_ssl_private_key_mem_len = key_size;
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.options |= LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND;
info.timeout_secs = timeout_s;
info.connect_timeout_secs = timeout_s;
info.user = ctx;
/* TRICKY: check comments related to lws_context_destroy() call */
pthread_mutex_unlock(ctx->mutex);
bsc_websocket_global_lock();
ctx->wsctx = lws_create_context(&info);
bsc_websocket_global_unlock();
pthread_mutex_lock(ctx->mutex);
if (!ctx->wsctx) {
pthread_mutex_unlock(ctx->mutex);
bws_free_server_ctx(ctx);
DEBUG_PRINTF("bws_srv_start() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
ctx->dispatch_func = dispatch_func;
ctx->user_param = dispatch_func_user_param;
ctx->proto = proto;
r = pthread_attr_init(&attr);
if(!r) {
r = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
}
if(!r) {
r = pthread_create(&thread_id, &attr, &bws_srv_worker, ctx);
}
if (r) {
/* TRICKY: Libwebsockets API is not designed to be used from
multipe service threads, as a result lws_context_destroy()
is not thread safe. More over, on different platforms the
function behaves in different ways. Call of
lws_context_destroy() leads to several calls of
bws_srv_websocket_event() callback (LWS_CALLBACK_CLOSED,
etc..). But under some OS (MacOSx) that callback is
called from context of the bws_cli_worker() thread and
under some other OS (linux) the callback is called from
internal libwebsockets lib thread. That's why
bws_cli_mutex must be unlocked before
lws_context_destroy() call. To ensure that nobody calls
lws_context_destroy() from some parallel thread it is
protected by global websocket mutex.
*/
pthread_mutex_unlock(ctx->mutex);
bsc_websocket_global_lock();
lws_context_destroy(ctx->wsctx);
bsc_websocket_global_unlock();
pthread_mutex_lock(ctx->mutex);
ctx->wsctx = NULL;
pthread_mutex_unlock(ctx->mutex);
bws_free_server_ctx(ctx);
DEBUG_PRINTF(
"bws_srv_start() <<< ret = BACNET_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
pthread_attr_destroy(&attr);
pthread_mutex_unlock(ctx->mutex);
*sh = (BSC_WEBSOCKET_SRV_HANDLE)ctx;
DEBUG_PRINTF("bws_srv_start() <<< ret = BSC_WEBSOCKET_SUCCESS\n");
return BSC_WEBSOCKET_SUCCESS;
}
BSC_WEBSOCKET_RET bws_srv_stop(BSC_WEBSOCKET_SRV_HANDLE sh)
{
BSC_WEBSOCKET_CONTEXT *ctx = (BSC_WEBSOCKET_CONTEXT *)sh;
DEBUG_PRINTF(
"bws_srv_stop() >>> ctx = %p user_param = %p\n", ctx, ctx->user_param);
#if DEBUG_ENABLED == 1
if (!bws_validate_ctx_pointer(ctx)) {
DEBUG_PRINTF("bws_srv_stop() <<< bad websocket handle, ret = "
"BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
#endif
pthread_mutex_lock(ctx->mutex);
if (ctx->stop_worker) {
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF(
"bws_srv_stop() <<< ret = BSC_WEBSOCKET_INVALID_OPERATION\n");
return BSC_WEBSOCKET_INVALID_OPERATION;
}
ctx->stop_worker = true;
/* wake up libwebsockets runloop */
lws_cancel_service(ctx->wsctx);
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF("bws_srv_stop() <<< ret = BSC_WEBSOCKET_SUCCESS\n");
return BSC_WEBSOCKET_SUCCESS;
}
void bws_srv_disconnect(BSC_WEBSOCKET_SRV_HANDLE sh, BSC_WEBSOCKET_HANDLE h)
{
BSC_WEBSOCKET_CONTEXT *ctx = (BSC_WEBSOCKET_CONTEXT *)sh;
DEBUG_PRINTF("bws_srv_disconnect() >>> sh = %p h = %d\n", sh, h);
#if DEBUG_ENABLED == 1
if (!bws_validate_ctx_pointer(ctx)) {
DEBUG_PRINTF("bws_srv_disconnect() <<< bad websocket handle\n");
return;
}
#endif
pthread_mutex_lock(ctx->mutex);
if (h >= 0 && h < bws_srv_get_max_sockets(ctx->proto) &&
!ctx->stop_worker) {
if (ctx->conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED) {
/* tell worker to process change of connection state */
ctx->conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
lws_cancel_service(ctx->wsctx);
}
}
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF("bws_srv_disconnect() <<<\n");
}
void bws_srv_send(BSC_WEBSOCKET_SRV_HANDLE sh, BSC_WEBSOCKET_HANDLE h)
{
BSC_WEBSOCKET_CONTEXT *ctx = (BSC_WEBSOCKET_CONTEXT *)sh;
DEBUG_PRINTF("bws_srv_send() >>> ctx = %p h = %d\n", ctx, h);
#if DEBUG_ENABLED == 1
if (!bws_validate_ctx_pointer(ctx)) {
DEBUG_PRINTF("bws_srv_send() <<< bad websocket handle\n");
return;
}
#endif
pthread_mutex_lock(ctx->mutex);
if (ctx->conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED) {
/* tell worker to process send request */
ctx->conn[h].want_send_data = true;
lws_cancel_service(ctx->wsctx);
}
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF("bws_srv_send() <<<\n");
}
BSC_WEBSOCKET_RET bws_srv_dispatch_send(BSC_WEBSOCKET_SRV_HANDLE sh,
BSC_WEBSOCKET_HANDLE h,
uint8_t *payload,
size_t payload_size)
{
int written;
BSC_WEBSOCKET_RET ret;
BSC_WEBSOCKET_CONTEXT *ctx = (BSC_WEBSOCKET_CONTEXT *)sh;
DEBUG_PRINTF("bws_srv_dispatch_send() >>> ctx = %p h = %d payload %p "
"payload_size %d\n",
ctx, h, payload, payload_size);
#if DEBUG_ENABLED == 1
if (!bws_validate_ctx_pointer(ctx)) {
DEBUG_PRINTF("bws_srv_dispatch_send() <<< bad websocket handle, ret = "
"BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
#endif
if (h < 0 || h >= bws_srv_get_max_sockets(ctx->proto)) {
DEBUG_PRINTF("bws_srv_dispatch_send() <<< BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
if (!payload || !payload_size) {
DEBUG_PRINTF("bws_srv_dispatch_send() <<< BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->stop_worker) {
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF("bws_srv_dispatch_send() <<< ret = "
"BSC_WEBSOCKET_INVALID_OPERATION\n");
return BSC_WEBSOCKET_INVALID_OPERATION;
}
if ((ctx->conn[h].state != BSC_WEBSOCKET_STATE_CONNECTED) ||
!ctx->conn[h].want_send_data || !ctx->conn[h].can_send_data) {
DEBUG_PRINTF(
"bws_srv_dispatch_send() <<< ret = BACNET_WEBSOCKET_BAD_PARAM\n");
pthread_mutex_unlock(ctx->mutex);
return BSC_WEBSOCKET_INVALID_OPERATION;
}
written =
lws_write(ctx->conn[h].ws, payload, payload_size, LWS_WRITE_BINARY);
DEBUG_PRINTF("bws_srv_dispatch_send() %d bytes is sent\n", written);
if (written < (int)payload_size) {
DEBUG_PRINTF(
"bws_srv_dispatch_send() websocket connection is broken(closed)\n");
/* tell worker to process change of connection state */
ctx->conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
lws_cancel_service(ctx->wsctx);
ret = BSC_WEBSOCKET_INVALID_OPERATION;
} else {
ret = BSC_WEBSOCKET_SUCCESS;
}
pthread_mutex_unlock(ctx->mutex);
DEBUG_PRINTF("bws_srv_dispatch_send() <<< ret = %d\n", ret);
return ret;
}
bool bws_srv_get_peer_ip_addr(BSC_WEBSOCKET_SRV_HANDLE sh,
BSC_WEBSOCKET_HANDLE h,
uint8_t *ip_str,
size_t ip_str_len,
uint16_t *port)
{
BSC_WEBSOCKET_CONTEXT *ctx = (BSC_WEBSOCKET_CONTEXT *)sh;
int fd;
struct sockaddr_storage addr;
socklen_t len;
const char *ret = NULL;
if (!ctx || h < 0 || !ip_str || !ip_str_len || !port ||
h >= bws_srv_get_max_sockets(ctx->proto)) {
return false;
}
pthread_mutex_lock(ctx->mutex);
if (ctx->conn[h].state != BSC_WEBSOCKET_STATE_IDLE &&
ctx->conn[h].ws != NULL && !ctx->stop_worker) {
len = sizeof(addr);
fd = lws_get_socket_fd(ctx->conn[h].ws);
if (fd != -1) {
getpeername(fd, (struct sockaddr *)&addr, &len);
if (addr.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
*port = ntohs(s->sin_port);
ret = inet_ntop(
AF_INET, &s->sin_addr, (char *)ip_str, ip_str_len);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
*port = ntohs(s->sin6_port);
ret = inet_ntop(
AF_INET6, &s->sin6_addr, (char *)ip_str, ip_str_len);
}
}
}
pthread_mutex_unlock(ctx->mutex);
if (ret) {
return true;
}
return false;
}
+181
View File
@@ -0,0 +1,181 @@
/**
* @file
* @brief Implementation of mutex abstraction used in BACNet secure connect.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date August 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 <pthread.h>
#include <stdbool.h>
#include <unistd.h>
#include <errno.h>
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/bsc/bsc-event.h"
#define DEBUG_BSC_EVENT 0
#if DEBUG_BSC_EVENT == 1
#define DEBUG_PRINTF printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF debug_printf_disabled
#endif
struct BSC_Event {
pthread_mutex_t mutex;
pthread_cond_t cond;
bool v;
size_t counter;
};
BSC_EVENT *bsc_event_init(void)
{
BSC_EVENT *ev;
pthread_mutexattr_t attr;
if (pthread_mutexattr_init(&attr) != 0) {
return NULL;
}
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) {
pthread_mutexattr_destroy(&attr);
return NULL;
}
ev = malloc(sizeof(BSC_EVENT));
if (!ev) {
pthread_mutexattr_destroy(&attr);
return NULL;
}
if (pthread_mutex_init(&ev->mutex, &attr) != 0) {
pthread_mutexattr_destroy(&attr);
free(ev);
return NULL;
}
pthread_mutexattr_destroy(&attr);
if (pthread_cond_init(&ev->cond, NULL) != 0) {
pthread_mutex_destroy(&ev->mutex);
free(ev);
return NULL;
}
ev->v = false;
ev->counter = 0;
return ev;
}
void bsc_event_deinit(BSC_EVENT *ev)
{
pthread_mutex_destroy(&ev->mutex);
pthread_cond_destroy(&ev->cond);
free(ev);
}
void bsc_event_wait(BSC_EVENT *ev)
{
pthread_mutex_lock(&ev->mutex);
DEBUG_PRINTF("bsc_event_wait() >>> ev = %p\n", ev);
DEBUG_PRINTF("bsc_event_wait() counter before %zu\n", ev->counter);
ev->counter++;
DEBUG_PRINTF("bsc_event_wait() counter %zu\n", ev->counter);
while (!ev->v) {
pthread_cond_wait(&ev->cond, &ev->mutex);
}
DEBUG_PRINTF("bsc_event_wait() ev = %p\n", ev);
DEBUG_PRINTF("bsc_event_wait() before counter %zu\n", ev->counter);
ev->counter--;
DEBUG_PRINTF("bsc_event_wait() counter %zu\n", ev->counter);
if (!ev->counter) {
ev->v = false;
DEBUG_PRINTF("bsc_event_wait() reset ev\n");
} else {
DEBUG_PRINTF("bsc_event_wait() wake up other waiting threads\n");
pthread_cond_broadcast(&ev->cond);
}
DEBUG_PRINTF("bsc_event_wait() <<< ev = %p\n", ev);
pthread_mutex_unlock(&ev->mutex);
}
bool bsc_event_timedwait(BSC_EVENT *ev, unsigned int ms_timeout)
{
struct timespec to;
int r = 0;
clock_gettime(CLOCK_REALTIME, &to);
to.tv_sec = to.tv_sec + ms_timeout / 1000;
to.tv_nsec = to.tv_nsec + (ms_timeout % 1000) * 1000000;
to.tv_sec += to.tv_nsec / 1000000000;
to.tv_nsec %= 1000000000;
DEBUG_PRINTF(
"bsc_event_timedwait() >>> before lock ev = %p ev->v = %d\n", ev,
ev->v);
pthread_mutex_lock(&ev->mutex);
DEBUG_PRINTF(
"bsc_event_timedwait() >>> after lock ev = %p ev->v = %d\n", ev, ev->v);
ev->counter++;
DEBUG_PRINTF("bsc_event_timedwait() counter %zu\n", ev->counter);
while (!ev->v) {
r = pthread_cond_timedwait(&ev->cond, &ev->mutex, &to);
if (r != 0) {
break;
}
}
if (ev->v) {
if (r != 0) {
DEBUG_PRINTF("Fired!!! r = %d\n", r);
}
r = 0;
}
ev->counter--;
DEBUG_PRINTF("bsc_event_timedwait() counter %zu\n", ev->counter);
if (!ev->counter) {
DEBUG_PRINTF("bsc_event_timedwait() event is reset, err = %d\n", r);
ev->v = false;
} else {
DEBUG_PRINTF("bsc_event_timedwait() wake up other waiting threads\n");
pthread_cond_broadcast(&ev->cond);
}
DEBUG_PRINTF(
"bsc_event_timedwait() <<< ret = %d, ev = %p ev->v = %d r = %d\n",
r == 0, ev, ev->v, r);
pthread_mutex_unlock(&ev->mutex);
return r == 0;
}
void bsc_event_signal(BSC_EVENT *ev)
{
DEBUG_PRINTF("bsc_event_signal() >>> ev = %p\n", ev);
pthread_mutex_lock(&ev->mutex);
DEBUG_PRINTF("bsc_event_signal() >>> ev = %p\n", ev);
ev->v = true;
pthread_cond_broadcast(&ev->cond);
DEBUG_PRINTF("bsc_event_signal() <<< ev = %p\n", ev);
pthread_mutex_unlock(&ev->mutex);
DEBUG_PRINTF("bsc_event_signal() <<< ev = %p\n", ev);
}
void bsc_wait(int seconds)
{
sleep(seconds);
}
void bsc_wait_ms(int mseconds)
{
usleep(mseconds * 1000);
}
+781
View File
@@ -0,0 +1,781 @@
/**
* @file
* @brief Implementation of websocket client interface.
* @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
*/
#define _GNU_SOURCE
#include <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "bacnet/datalink/bsc/websocket.h"
#include "bacnet/basic/sys/debug.h"
#include "websocket-global.h"
#define DEBUG_WEBSOCKET_CLIENT 0
#if DEBUG_WEBSOCKET_CLIENT == 1
#define DEBUG_PRINTF debug_printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF debug_printf_disabled
#endif
#ifndef LWS_PROTOCOL_LIST_TERM
#define LWS_PROTOCOL_LIST_TERM \
{ \
NULL, NULL, 0, 0, 0, NULL, 0 \
}
#endif
#define BSC_RX_BUFFER_LEN BSC_WEBSOCKET_RX_BUFFER_LEN
typedef enum {
BSC_WEBSOCKET_STATE_IDLE = 0,
BSC_WEBSOCKET_STATE_CONNECTING = 1,
BSC_WEBSOCKET_STATE_CONNECTED = 2,
BSC_WEBSOCKET_STATE_DISCONNECTING = 3
} BSC_WEBSOCKET_STATE;
typedef struct {
struct lws_context *ctx;
struct lws *ws;
BSC_WEBSOCKET_STATE state;
bool want_send_data;
bool can_send_data;
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
uint8_t *fragment_buffer;
size_t fragment_buffer_size;
size_t fragment_buffer_len;
char err_desc[BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN];
BACNET_ERROR_CODE err_code;
} BSC_WEBSOCKET_CONNECTION;
/* Some forward function declarations */
static int bws_cli_websocket_event(
struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len);
static const char *bws_hub_protocol = BSC_WEBSOCKET_HUB_PROTOCOL_STR;
static const char *bws_direct_protocol = BSC_WEBSOCKET_DIRECT_PROTOCOL_STR;
static pthread_mutex_t bws_cli_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
/* Websockets protocol defined in BACnet/SC \S AB.7.1. */
static struct lws_protocols bws_cli_direct_protocol[] = {
{ BSC_WEBSOCKET_DIRECT_PROTOCOL_STR, bws_cli_websocket_event, 0, 0, 0, NULL,
0 },
LWS_PROTOCOL_LIST_TERM
};
static struct lws_protocols bws_cli_hub_protocol[] = {
{ BSC_WEBSOCKET_HUB_PROTOCOL_STR, bws_cli_websocket_event, 0, 0, 0, NULL,
0 },
LWS_PROTOCOL_LIST_TERM
};
static lws_retry_bo_t bws_retry;
static BSC_WEBSOCKET_CONNECTION bws_cli_conn[BSC_CLIENT_WEBSOCKETS_MAX_NUM] = {
0
};
static BSC_WEBSOCKET_HANDLE bws_cli_alloc_connection(void)
{
int i;
for (i = 0; i < BSC_CLIENT_WEBSOCKETS_MAX_NUM; i++) {
if (bws_cli_conn[i].state == BSC_WEBSOCKET_STATE_IDLE) {
memset(&bws_cli_conn[i], 0, sizeof(bws_cli_conn[i]));
return i;
}
}
return BSC_WEBSOCKET_INVALID_HANDLE;
}
static void bws_cli_free_connection(BSC_WEBSOCKET_HANDLE h)
{
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
if (bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_IDLE) {
if (bws_cli_conn[h].fragment_buffer) {
free(bws_cli_conn[h].fragment_buffer);
bws_cli_conn[h].fragment_buffer = NULL;
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = 0;
}
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_IDLE;
bws_cli_conn[h].ws = NULL;
bws_cli_conn[h].ctx = NULL;
}
}
}
static BSC_WEBSOCKET_HANDLE bws_cli_find_connnection(struct lws *ws)
{
int i;
for (i = 0; i < BSC_CLIENT_WEBSOCKETS_MAX_NUM; i++) {
if (bws_cli_conn[i].ws == ws &&
bws_cli_conn[i].state != BSC_WEBSOCKET_STATE_IDLE) {
return i;
}
}
return BSC_WEBSOCKET_INVALID_HANDLE;
}
static void bws_set_err_desc(BSC_WEBSOCKET_HANDLE h, char *err_desc)
{
size_t len;
if (bws_cli_conn[h].err_code == ERROR_CODE_SUCCESS) {
len = strlen(err_desc) >= sizeof(bws_cli_conn[h].err_desc)
? sizeof(bws_cli_conn[h].err_desc) - 1
: strlen(err_desc);
memcpy(bws_cli_conn[h].err_desc, err_desc, len);
bws_cli_conn[h].err_desc[len] = 0;
if (strstr(err_desc, "tls:")) {
bws_cli_conn[h].err_code = ERROR_CODE_TLS_ERROR;
} else {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
}
}
}
static void bws_set_disconnect_reason(BSC_WEBSOCKET_HANDLE h, uint16_t err_code)
{
bws_cli_conn[h].err_desc[0] = 0;
switch (err_code) {
case LWS_CLOSE_STATUS_NORMAL: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER;
break;
}
case LWS_CLOSE_STATUS_GOINGAWAY: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ENDPOINT_LEAVES;
break;
}
case LWS_CLOSE_STATUS_PROTOCOL_ERR: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_PROTOCOL_ERROR;
break;
}
case LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED;
break;
}
case LWS_CLOSE_STATUS_NO_STATUS:
case LWS_CLOSE_STATUS_RESERVED: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
break;
}
case LWS_CLOSE_STATUS_ABNORMAL_CLOSE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED;
break;
}
case LWS_CLOSE_STATUS_INVALID_PAYLOAD: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_INCONSISTENT;
break;
}
case LWS_CLOSE_STATUS_POLICY_VIOLATION: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_AGAINST_POLICY;
break;
}
case LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_FRAME_TOO_LONG;
break;
}
case LWS_CLOSE_STATUS_EXTENSION_REQUIRED: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_EXTENSION_MISSING;
break;
}
case LWS_CLOSE_STATUS_UNEXPECTED_CONDITION: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_REQUEST_UNAVAILABLE;
break;
}
default: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
break;
}
}
}
static int bws_cli_websocket_event(
struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len)
{
BSC_WEBSOCKET_HANDLE h;
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
uint8_t err_code[2];
(void)user;
DEBUG_PRINTF(
"bws_cli_websocket_event() >>> reason = %d, user = %p, in = %p\n",
reason, user, in);
switch (reason) {
case LWS_CALLBACK_CLIENT_ESTABLISHED: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF("bws_cli_websocket_event() connection established\n");
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_CONNECTED;
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
pthread_mutex_unlock(&bws_cli_mutex);
dispatch_func(
h, BSC_WEBSOCKET_CONNECTED, 0, NULL, NULL, 0, user_param);
break;
}
case LWS_CALLBACK_CLIENT_RECEIVE: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF(
"bws_cli_websocket_event() received %d bytes of data\n", len);
if (!lws_frame_is_binary(wsi)) {
/* According AB.7.5.3 BACnet/SC BVLC Message Exchange,
if a received data frame is not binary,
the WebSocket connection shall be closed with a
status code of 1003 -WEBSOCKET_DATA_NOT_ACCEPTED.
*/
DEBUG_PRINTF(
"bws_cli_websocket_event() got non-binary frame, "
"close connection for socket %d\n",
h);
lws_close_reason(
wsi, LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE, NULL, 0);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = -1\n");
return -1;
}
if (bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_CONNECTED) {
pthread_mutex_unlock(&bws_cli_mutex);
} else {
if (!bws_cli_conn[h].fragment_buffer) {
DEBUG_PRINTF(
"bws_cli_websocket_event() alloc %d bytes for "
"socket %d\n",
len, h);
bws_cli_conn[h].fragment_buffer = malloc(BSC_RX_BUFFER_LEN);
if (!bws_cli_conn[h].fragment_buffer) {
lws_close_reason(
wsi, LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE, NULL, 0);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_websocket_event() <<< ret = -1, "
"allocation of %d bytes failed\n",
len <= BSC_RX_BUFFER_LEN ? BSC_RX_BUFFER_LEN : len);
return -1;
}
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = BSC_RX_BUFFER_LEN;
}
if (bws_cli_conn[h].fragment_buffer_len + len >
bws_cli_conn[h].fragment_buffer_size) {
DEBUG_PRINTF(
"bws_cli_websocket_event() realloc buf of %d bytes"
"for socket %d to %d bytes\n",
bws_cli_conn[h].fragment_buffer_len, h,
bws_cli_conn[h].fragment_buffer_len + len);
bws_cli_conn[h].fragment_buffer = realloc(
bws_cli_conn[h].fragment_buffer,
bws_cli_conn[h].fragment_buffer_len + len);
if (!bws_cli_conn[h].fragment_buffer) {
lws_close_reason(
wsi, LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE, NULL, 0);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_websocket_event() <<< ret = -1, "
"re-allocation of %d bytes failed\n",
bws_cli_conn[h].fragment_buffer_len + len);
return -1;
}
bws_cli_conn[h].fragment_buffer_size =
bws_cli_conn[h].fragment_buffer_len + len;
}
DEBUG_PRINTF(
"bws_cli_websocket_event() got next %d bytes for "
"socket %d\n",
len, h);
memcpy(
&bws_cli_conn[h]
.fragment_buffer[bws_cli_conn[h].fragment_buffer_len],
in, len);
bws_cli_conn[h].fragment_buffer_len += len;
if (lws_is_final_fragment(wsi)) {
DEBUG_PRINTF(
"bws_cli_websocket_event() last fragment received\n");
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
pthread_mutex_unlock(&bws_cli_mutex);
dispatch_func(
h, BSC_WEBSOCKET_RECEIVED, 0, NULL,
bws_cli_conn[h].fragment_buffer,
bws_cli_conn[h].fragment_buffer_len, user_param);
pthread_mutex_lock(&bws_cli_mutex);
bws_cli_conn[h].fragment_buffer_len = 0;
pthread_mutex_unlock(&bws_cli_mutex);
} else {
pthread_mutex_unlock(&bws_cli_mutex);
}
}
break;
}
case LWS_CALLBACK_CLIENT_WRITEABLE: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF(
"bws_cli_websocket_event() can write, state = %d\n",
bws_cli_conn[h].state);
DEBUG_PRINTF(
"bws_cli_websocket_event() ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data, bws_cli_conn[h].can_send_data);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED &&
bws_cli_conn[h].want_send_data) {
bws_cli_conn[h].can_send_data = true;
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
pthread_mutex_unlock(&bws_cli_mutex);
dispatch_func(
h, BSC_WEBSOCKET_SENDABLE, 0, NULL, NULL, 0, user_param);
pthread_mutex_lock(&bws_cli_mutex);
bws_cli_conn[h].want_send_data = false;
bws_cli_conn[h].can_send_data = false;
DEBUG_PRINTF(
"bws_cli_websocket_event() was send , ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
pthread_mutex_unlock(&bws_cli_mutex);
/* wakeup worker to process internal state */
lws_cancel_service(bws_cli_conn[h].ctx);
} else {
bws_cli_conn[h].want_send_data = false;
DEBUG_PRINTF(
"bws_cli_websocket_event() no send, ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
pthread_mutex_unlock(&bws_cli_mutex);
}
break;
}
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h != BSC_WEBSOCKET_INVALID_HANDLE && len >= 2) {
err_code[0] = ((uint8_t *)in)[1];
err_code[1] = ((uint8_t *)in)[0];
bws_set_disconnect_reason(h, *((uint16_t *)&err_code));
}
pthread_mutex_unlock(&bws_cli_mutex);
break;
}
case LWS_CALLBACK_CLIENT_CLOSED:
case LWS_CALLBACK_CLOSED:
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: {
pthread_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h != BSC_WEBSOCKET_INVALID_HANDLE) {
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
if (reason == LWS_CALLBACK_CLIENT_CONNECTION_ERROR && in) {
bws_set_err_desc(h, (char *)in);
}
pthread_mutex_unlock(&bws_cli_mutex);
/* wakeup worker to process pending event */
lws_cancel_service(bws_cli_conn[h].ctx);
} else {
pthread_mutex_unlock(&bws_cli_mutex);
}
break;
}
default: {
break;
}
}
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
static void *bws_cli_worker(void *arg)
{
BSC_WEBSOCKET_CONNECTION *conn = (BSC_WEBSOCKET_CONNECTION *)arg;
BSC_WEBSOCKET_HANDLE h = conn - &bws_cli_conn[0];
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
char err_desc[BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN];
uint16_t err_code;
while (1) {
DEBUG_PRINTF("bws_cli_worker() try mutex h = %d\n", h);
pthread_mutex_lock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() mutex locked h = %d\n", h);
if (conn->state == BSC_WEBSOCKET_STATE_CONNECTED) {
if (conn->want_send_data) {
DEBUG_PRINTF(
"bws_cli_worker() process request for sending data\n");
lws_callback_on_writable(conn->ws);
}
} else if (conn->state == BSC_WEBSOCKET_STATE_DISCONNECTING) {
DEBUG_PRINTF("bws_cli_worker() process disconnecting event\n");
DEBUG_PRINTF("bws_cli_worker() destroy ctx %p\n", conn->ctx);
/* TRICKY: Libwebsockets API is not designed to be used from
multipe service threads, as a result
lws_context_destroy() is not thread safe.More over, on different
platforms the function behaves in different ways. Call of
lws_context_destroy() leads to several calls of
bws_cli_websocket_event() callback (LWS_CALLBACK_CLOSED,
etc..). But under some OS (MacOSx) that callback is
called from context of the bws_cli_worker() thread and
under some other OS (linux) the callback is called from
internal libwebsockets lib thread. That's why
bws_cli_mutex must be unlocked before
lws_context_destroy() call. To ensure that nobody calls
lws_context_destroy() from some parallel thread it is
protected by global websocket mutex.
*/
pthread_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
lws_context_destroy(conn->ctx);
bsc_websocket_global_unlock();
pthread_mutex_lock(&bws_cli_mutex);
dispatch_func = conn->dispatch_func;
user_param = conn->user_param;
err_code = conn->err_code;
if (err_code != ERROR_CODE_SUCCESS) {
memcpy(err_desc, conn->err_desc, sizeof(err_desc));
}
bws_cli_free_connection(h);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() unlock mutex\n");
dispatch_func(
h, BSC_WEBSOCKET_DISCONNECTED, err_code,
err_code != ERROR_CODE_SUCCESS ? err_desc : NULL, NULL, 0,
user_param);
return NULL;
}
DEBUG_PRINTF("bws_cli_worker() unlock mutex\n");
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() going to block on lws_service() call\n");
lws_service(conn->ctx, 0);
}
return NULL;
}
BSC_WEBSOCKET_RET bws_cli_connect(
BSC_WEBSOCKET_PROTOCOL proto,
char *url,
uint8_t *ca_cert,
size_t ca_cert_size,
uint8_t *cert,
size_t cert_size,
uint8_t *key,
size_t key_size,
size_t timeout_s,
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func,
void *dispatch_func_user_param,
BSC_WEBSOCKET_HANDLE *out_handle)
{
struct lws_context_creation_info info;
char tmp_url[BSC_WSURL_MAX_LEN];
const char *prot = NULL, *addr = NULL, *path = NULL;
int port = -1;
BSC_WEBSOCKET_HANDLE h;
struct lws_client_connect_info cinfo;
pthread_t thread_id;
size_t len;
pthread_attr_t attr;
int r;
DEBUG_PRINTF("bws_cli_connect() >>> proto = %d, url = %s\n", proto, url);
if (!ca_cert || !ca_cert_size || !cert || !cert_size || !key || !key_size ||
!url || !out_handle || !timeout_s || !dispatch_func) {
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
*out_handle = BSC_WEBSOCKET_INVALID_HANDLE;
if (proto != BSC_WEBSOCKET_HUB_PROTOCOL &&
proto != BSC_WEBSOCKET_DIRECT_PROTOCOL) {
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
memset(&info, 0, sizeof(info));
memset(&cinfo, 0, sizeof(cinfo));
len = strlen(url) >= sizeof(tmp_url) ? sizeof(tmp_url) - 1 : strlen(url);
memcpy(tmp_url, url, len);
tmp_url[len] = 0;
bsc_websocket_init_log();
pthread_mutex_lock(&bws_cli_mutex);
if (lws_parse_uri(tmp_url, &prot, &addr, &port, &path) != 0 || port == -1 ||
!prot || !addr || !path) {
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
if (strcmp(prot, "wss") != 0) {
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
h = bws_cli_alloc_connection();
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_CONNECTING;
bws_cli_conn[h].fragment_buffer = NULL;
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = 0;
bws_cli_conn[h].dispatch_func = dispatch_func;
bws_cli_conn[h].user_param = dispatch_func_user_param;
info.port = CONTEXT_PORT_NO_LISTEN;
if (proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
info.protocols = bws_cli_hub_protocol;
} else {
info.protocols = bws_cli_direct_protocol;
}
info.gid = -1;
info.uid = -1;
info.client_ssl_cert_mem = cert;
info.client_ssl_cert_mem_len = cert_size;
info.client_ssl_ca_mem = ca_cert;
info.client_ssl_ca_mem_len = ca_cert_size;
info.client_ssl_key_mem = key;
info.client_ssl_key_mem_len = key_size;
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.options |= LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND;
info.timeout_secs = timeout_s;
info.connect_timeout_secs = timeout_s;
/* TRICKY: check comments related to lws_context_destroy() call */
pthread_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
bws_cli_conn[h].ctx = lws_create_context(&info);
bsc_websocket_global_unlock();
pthread_mutex_lock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() created ctx %p\n", bws_cli_conn[h].ctx);
if (!bws_cli_conn[h].ctx) {
bws_cli_free_connection(h);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
bws_cli_conn[h].ws = NULL;
cinfo.context = bws_cli_conn[h].ctx;
cinfo.address = addr;
cinfo.origin = cinfo.address;
cinfo.host = cinfo.address;
cinfo.port = port;
cinfo.path = path;
cinfo.pwsi = &bws_cli_conn[h].ws;
cinfo.alpn = "h2;http/1.1";
bws_retry.secs_since_valid_ping = 3;
bws_retry.secs_since_valid_hangup = 10;
cinfo.retry_and_idle_policy = &bws_retry;
cinfo.ssl_connection = LCCSCF_USE_SSL |
LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_SELFSIGNED;
if (proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
cinfo.protocol = bws_hub_protocol;
} else {
cinfo.protocol = bws_direct_protocol;
}
bws_cli_conn[h].err_code = ERROR_CODE_SUCCESS;
*out_handle = h;
pthread_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
lws_client_connect_via_info(&cinfo);
bsc_websocket_global_unlock();
r = pthread_attr_init(&attr);
if (!r) {
r = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
}
if (!r) {
r = pthread_create(
&thread_id, &attr, &bws_cli_worker, &bws_cli_conn[h]);
}
if (r) {
/* TRICKY: Libwebsockets API is not designed to be used from
multipe service threads, as a result lws_context_destroy()
is not thread safe. More over, on different platforms the
function behaves in different ways. Call of
lws_context_destroy() leads to several calls of
bws_cli_websocket_event() callback (LWS_CALLBACK_CLOSED,
etc..). But under some OS (MacOSx) that callback is
called from context of the bws_cli_worker() thread and
under some other OS (linux) the callback is called from
internal libwebsockets lib thread. That's why
bws_cli_mutex must be unlocked before
lws_context_destroy() call. To ensure that nobody calls
lws_context_destroy() from some parallel thread it is
protected by global websocket mutex.
*/
/* pthread_mutex_unlock(&bws_cli_mutex); */
bsc_websocket_global_lock();
lws_context_destroy(bws_cli_conn[h].ctx);
bsc_websocket_global_unlock();
pthread_mutex_lock(&bws_cli_mutex);
bws_cli_free_connection(h);
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
pthread_attr_destroy(&attr);
DEBUG_PRINTF("bws_cli_connect() <<< ret = %d\n", BSC_WEBSOCKET_SUCCESS);
return BSC_WEBSOCKET_SUCCESS;
}
void bws_cli_disconnect(BSC_WEBSOCKET_HANDLE h)
{
DEBUG_PRINTF("bws_cli_disconnect() >>> h = %d\n", h);
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
pthread_mutex_lock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_disconnect() state = %d\n", bws_cli_conn[h].state);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTING ||
bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED) {
/* tell worker to process change of connection state */
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
lws_cancel_service(bws_cli_conn[h].ctx);
}
pthread_mutex_unlock(&bws_cli_mutex);
}
DEBUG_PRINTF("bws_cli_disconnect() <<<\n");
}
void bws_cli_send(BSC_WEBSOCKET_HANDLE h)
{
DEBUG_PRINTF("bws_cli_send() >>> h = %d\n", h);
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
pthread_mutex_lock(&bws_cli_mutex);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED) {
/* tell worker to process send request */
bws_cli_conn[h].want_send_data = true;
DEBUG_PRINTF("bws_cli_send() cs = 1\n");
lws_cancel_service(bws_cli_conn[h].ctx);
}
pthread_mutex_unlock(&bws_cli_mutex);
}
DEBUG_PRINTF("bws_cli_send() <<<\n");
}
BSC_WEBSOCKET_RET bws_cli_dispatch_send(
BSC_WEBSOCKET_HANDLE h, uint8_t *payload, size_t payload_size)
{
int written;
BSC_WEBSOCKET_RET ret;
DEBUG_PRINTF(
"bws_cli_dispatch_send() >>> h = %d, payload = %p, payload_size = %d\n",
h, payload, payload_size);
if (h < 0 || h >= BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
DEBUG_PRINTF(
"bws_cli_dispatch_send() <<< ret = BACNET_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
pthread_mutex_lock(&bws_cli_mutex);
if ((bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_CONNECTED) ||
!bws_cli_conn[h].want_send_data || !bws_cli_conn[h].can_send_data) {
DEBUG_PRINTF(
"bws_cli_dispatch_send() state = %d, ws = %d, cs = %d\n",
bws_cli_conn[h].state, bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
DEBUG_PRINTF("bws_cli_dispatch_send() <<< ret = "
"BSC_WEBSOCKET_INVALID_OPERATION\n");
pthread_mutex_unlock(&bws_cli_mutex);
return BSC_WEBSOCKET_INVALID_OPERATION;
}
written =
lws_write(bws_cli_conn[h].ws, payload, payload_size, LWS_WRITE_BINARY);
DEBUG_PRINTF("bws_cli_dispatch_send() %d bytes is sent\n", written);
if (written < (int)payload_size) {
DEBUG_PRINTF(
"bws_cli_dispatch_send() websocket connection is broken(closed)\n");
/* tell worker to process change of connection state */
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
lws_cancel_service(bws_cli_conn[h].ctx);
ret = BSC_WEBSOCKET_INVALID_OPERATION;
} else {
ret = BSC_WEBSOCKET_SUCCESS;
}
pthread_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_dispatch_send() <<< ret = %d\n", ret);
return ret;
}
+129
View File
@@ -0,0 +1,129 @@
/**
* @file
* @brief Implementation of global websocket functions.
* @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
*/
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <libwebsockets.h>
#include "websocket-global.h"
#include "bacnet/basic/sys/debug.h"
#if LWS_MAX_SMP <= 1
#warning \
"Libwebsockets must be built with LWS_MAX_SMP > 1 (otherwise it does not support thread synchronization in correct way)"
#endif
static pthread_mutex_t websocket_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
static pthread_mutex_t websocket_dispatch_mutex =
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
#if (BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED != 1)
void bsc_websocket_global_lock(void)
{
pthread_mutex_lock(&websocket_mutex);
}
void bsc_websocket_global_unlock(void)
{
pthread_mutex_unlock(&websocket_mutex);
}
void bws_dispatch_lock(void)
{
pthread_mutex_lock(&websocket_dispatch_mutex);
}
void bws_dispatch_unlock(void)
{
pthread_mutex_unlock(&websocket_dispatch_mutex);
}
#else
static volatile int websocket_mutex_cnt = 0;
static volatile int websocket_dispatch_mutex_cnt = 0;
void bsc_websocket_global_lock_dbg(char *f, int line)
{
printf(
"bsc_websocket_global_lock_dbg() >>> %s:%d lock_cnt %d tid = %ld\n", f,
line, websocket_mutex_cnt, pthread_self());
websocket_mutex_cnt++;
fflush(stdout);
pthread_mutex_lock(&websocket_mutex);
printf(
"bsc_websocket_global_lock_dbg() <<< lock_cnt %d tid = %ld\n",
websocket_mutex_cnt, pthread_self());
fflush(stdout);
}
void bsc_websocket_global_unlock_dbg(char *f, int line)
{
printf(
"bsc_websocket_global_unlock_dbg() >>> %s:%d lock_cnt %d tid = %ld\n",
f, line, websocket_mutex_cnt, pthread_self());
websocket_mutex_cnt--;
fflush(stdout);
pthread_mutex_unlock(&websocket_mutex);
printf(
"bsc_websocket_global_unlock_dbg() <<< lock_cnt %d tid = %ld\n",
websocket_mutex_cnt, pthread_self());
fflush(stdout);
}
void bws_dispatch_lock_dbg(char *f, int line)
{
printf(
"bws_dispatch_lock_dbg() >>> %s:%d lock_cnt %d tid = %ld\n", f, line,
websocket_dispatch_mutex_cnt, pthread_self());
websocket_dispatch_mutex_cnt++;
fflush(stdout);
pthread_mutex_lock(&websocket_dispatch_mutex);
printf(
"bws_dispatch_lock_dbg() <<< lock_cnt %d tid = %ld\n",
websocket_dispatch_mutex_cnt, pthread_self());
fflush(stdout);
}
void bws_dispatch_unlock_dbg(char *f, int line)
{
printf(
"bws_dispatch_unlock_dbg() >>> %s:%d lock_cnt %d tid = %ld\n", f, line,
websocket_dispatch_mutex_cnt, pthread_self());
websocket_dispatch_mutex_cnt--;
fflush(stdout);
pthread_mutex_unlock(&websocket_dispatch_mutex);
printf(
"bws_dispatch_unlock_dbg() <<< lock_cnt %d tid = %ld\n",
websocket_dispatch_mutex_cnt, pthread_self());
fflush(stdout);
}
#endif
static bool bsc_websocket_log_initialized = false;
void bsc_websocket_init_log(void)
{
bsc_websocket_global_lock();
if (!bsc_websocket_log_initialized) {
bsc_websocket_log_initialized = true;
#if DEBUG_LIBWEBSOCKETS_ENABLED == 1
printf("LWS_MAX_SMP = %d", LWS_MAX_SMP);
lws_set_log_level(
LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG |
LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY |
LLL_USER | LLL_THREAD,
NULL);
#else
lws_set_log_level(0, NULL);
#endif
}
bsc_websocket_global_unlock();
}
+29
View File
@@ -0,0 +1,29 @@
/**
* @file
* @brief Global websocket functions
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date May 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef __BSC_WEBSOCKET_MUTEX_INCLUDED__
#define __BSC_WEBSOCKET_MUTEX_INCLUDED__
#ifndef BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED
#define BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED 0
#endif
#if (BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED != 1)
void bsc_websocket_global_lock(void);
void bsc_websocket_global_unlock(void);
#else
void bsc_websocket_global_lock_dbg(char *f, int line);
void bsc_websocket_global_unlock_dbg(char *f, int line);
#define bsc_websocket_global_lock() \
bsc_websocket_global_lock_dbg(__FILE__, __LINE__);
#define bsc_websocket_global_unlock() \
bsc_websocket_global_unlock_dbg(__FILE__, __LINE__);
#endif
void bsc_websocket_init_log(void);
#endif
File diff suppressed because it is too large Load Diff
+145
View File
@@ -0,0 +1,145 @@
/**
* @file
* @brief Implementation of mutex abstraction used in BACNet secure connect.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date August 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include "bacnet/datalink/bsc/bsc-event.h"
#define DEBUG_BSC_EVENT 0
#if DEBUG_BSC_EVENT == 1
#define DEBUG_PRINTF printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF(...)
#endif
struct BSC_Event {
HANDLE mutex;
HANDLE event;
size_t counter;
};
BSC_EVENT *bsc_event_init(void)
{
BSC_EVENT *ret;
ret = (BSC_EVENT *)malloc(sizeof(BSC_EVENT));
if (!ret) {
return NULL;
}
ret->mutex = CreateMutex(
NULL, // default security attributes
FALSE, // initially not owned
NULL); // unnamed mutex
if (ret->mutex == NULL) {
free(ret);
return NULL;
}
ret->event = CreateEvent(
NULL, // default security attributes
TRUE, // manual-reset event
FALSE, // initial state is nonsignaled
NULL // unnamed event
);
if (ret->event == NULL) {
CloseHandle(ret->mutex);
free(ret);
return NULL;
}
ret->counter = 0;
return ret;
}
void bsc_event_deinit(BSC_EVENT *ev)
{
CloseHandle(ev->mutex);
CloseHandle(ev->event);
free(ev);
}
void bsc_event_wait(BSC_EVENT *ev)
{
DEBUG_PRINTF("bsc_event_wait() >>> ev = %p\n", ev);
WaitForSingleObject(ev->mutex, INFINITE);
DEBUG_PRINTF("bsc_event_wait() counter before %zu\n", ev->counter);
ev->counter++;
ReleaseMutex(ev->mutex);
WaitForSingleObject(ev->event, INFINITE);
WaitForSingleObject(ev->mutex, INFINITE);
DEBUG_PRINTF("bsc_event_wait() before counter %zu\n", ev->counter);
ev->counter--;
DEBUG_PRINTF("bsc_event_wait() counter %zu\n", ev->counter);
if (!ev->counter) {
DEBUG_PRINTF("bsc_event_wait() reset event\n");
ReleaseMutex(ev->mutex);
ResetEvent(ev->event);
} else {
DEBUG_PRINTF("bsc_event_wait() set event\n");
ReleaseMutex(ev->mutex);
}
DEBUG_PRINTF("bsc_event_wait() <<< ev = %p\n", ev);
}
bool bsc_event_timedwait(BSC_EVENT *ev, unsigned int ms_timeout)
{
bool timedout = false;
DWORD ret;
DEBUG_PRINTF("bsc_event_timedwait() >>> ev = %p\n", ev);
WaitForSingleObject(ev->mutex, INFINITE);
DEBUG_PRINTF("bsc_event_timedwait() counter before %zu\n", ev->counter);
ev->counter++;
DEBUG_PRINTF("bsc_event_timedwait() counter %zu\n", ev->counter);
ReleaseMutex(ev->mutex);
ret = WaitForSingleObject(ev->event, ms_timeout);
WaitForSingleObject(ev->mutex, INFINITE);
DEBUG_PRINTF("bsc_event_timedwait() before counter %zu\n", ev->counter);
ev->counter--;
DEBUG_PRINTF("bsc_event_timedwait() counter %zu\n", ev->counter);
if (!ev->counter) {
ReleaseMutex(ev->mutex);
if (ret == WAIT_OBJECT_0) {
ResetEvent(ev->event);
}
} else {
ReleaseMutex(ev->mutex);
}
DEBUG_PRINTF(
"bsc_event_timedwait() <<< ret = %d\n",
ret == WAIT_OBJECT_0 ? true : false);
return ret == WAIT_OBJECT_0 ? true : false;
}
void bsc_event_signal(BSC_EVENT *ev)
{
DEBUG_PRINTF("bsc_event_signal() >>> ev = %p\n", ev);
SetEvent(ev->event);
DEBUG_PRINTF("bsc_event_signal() <<< ev = %p\n", ev);
}
void bsc_wait(int seconds)
{
Sleep(seconds * 1000);
}
void bsc_wait_ms(int mseconds)
{
Sleep(mseconds);
}
+769
View File
@@ -0,0 +1,769 @@
/**
* @file
* @brief Implementation of websocket client interface.
* @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 <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "bacnet/datalink/bsc/websocket.h"
#include "bacnet/basic/sys/debug.h"
#include "websocket-global.h"
#define DEBUG_WEBSOCKET_CLIENT 0
#if DEBUG_WEBSOCKET_CLIENT == 1
#define DEBUG_PRINTF debug_printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF(...)
#endif
#ifndef LWS_PROTOCOL_LIST_TERM
#define LWS_PROTOCOL_LIST_TERM \
{ \
NULL, NULL, 0, 0, 0, NULL, 0 \
}
#endif
#define BSC_RX_BUFFER_LEN BSC_WEBSOCKET_RX_BUFFER_LEN
typedef enum {
BSC_WEBSOCKET_STATE_IDLE = 0,
BSC_WEBSOCKET_STATE_CONNECTING = 1,
BSC_WEBSOCKET_STATE_CONNECTED = 2,
BSC_WEBSOCKET_STATE_DISCONNECTING = 3
} BSC_WEBSOCKET_STATE;
typedef struct {
struct lws_context *ctx;
struct lws *ws;
BSC_WEBSOCKET_STATE state;
bool want_send_data;
bool can_send_data;
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
uint8_t *fragment_buffer;
size_t fragment_buffer_size;
size_t fragment_buffer_len;
char err_desc[BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN];
BACNET_ERROR_CODE err_code;
} BSC_WEBSOCKET_CONNECTION;
/* Some forward function declarations */
static int bws_cli_websocket_event(
struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len);
static const char *bws_hub_protocol = BSC_WEBSOCKET_HUB_PROTOCOL_STR;
static const char *bws_direct_protocol = BSC_WEBSOCKET_DIRECT_PROTOCOL_STR;
static HANDLE bws_cli_mutex = NULL;
/* Websockets protocol defined in BACnet/SC \S AB.7.1. */
static struct lws_protocols bws_cli_direct_protocol[] = {
{ BSC_WEBSOCKET_DIRECT_PROTOCOL_STR, bws_cli_websocket_event, 0, 0, 0, NULL,
0 },
LWS_PROTOCOL_LIST_TERM
};
static struct lws_protocols bws_cli_hub_protocol[] = {
{ BSC_WEBSOCKET_HUB_PROTOCOL_STR, bws_cli_websocket_event, 0, 0, 0, NULL,
0 },
LWS_PROTOCOL_LIST_TERM
};
static lws_retry_bo_t bws_retry;
static BSC_WEBSOCKET_CONNECTION bws_cli_conn[BSC_CLIENT_WEBSOCKETS_MAX_NUM] = {
0
};
static BSC_WEBSOCKET_HANDLE bws_cli_alloc_connection(void)
{
int i;
for (i = 0; i < BSC_CLIENT_WEBSOCKETS_MAX_NUM; i++) {
if (bws_cli_conn[i].state == BSC_WEBSOCKET_STATE_IDLE) {
memset(&bws_cli_conn[i], 0, sizeof(bws_cli_conn[i]));
return i;
}
}
return BSC_WEBSOCKET_INVALID_HANDLE;
}
static void bws_cli_free_connection(BSC_WEBSOCKET_HANDLE h)
{
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
if (bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_IDLE) {
if (bws_cli_conn[h].fragment_buffer) {
free(bws_cli_conn[h].fragment_buffer);
bws_cli_conn[h].fragment_buffer = NULL;
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = 0;
}
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_IDLE;
bws_cli_conn[h].ws = NULL;
bws_cli_conn[h].ctx = NULL;
}
}
}
static BSC_WEBSOCKET_HANDLE bws_cli_find_connnection(struct lws *ws)
{
int i;
for (i = 0; i < BSC_CLIENT_WEBSOCKETS_MAX_NUM; i++) {
if (bws_cli_conn[i].ws == ws &&
bws_cli_conn[i].state != BSC_WEBSOCKET_STATE_IDLE) {
return i;
}
}
return BSC_WEBSOCKET_INVALID_HANDLE;
}
static void bws_set_err_desc(BSC_WEBSOCKET_HANDLE h, char *err_desc)
{
size_t len;
if (bws_cli_conn[h].err_code == ERROR_CODE_SUCCESS) {
len = strlen(err_desc) >= sizeof(bws_cli_conn[h].err_desc)
? sizeof(bws_cli_conn[h].err_desc) - 1
: strlen(err_desc);
memcpy(bws_cli_conn[h].err_desc, err_desc, len);
bws_cli_conn[h].err_desc[len] = 0;
if (strstr(err_desc, "tls:")) {
bws_cli_conn[h].err_code = ERROR_CODE_TLS_ERROR;
} else {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
}
}
}
static void bws_set_disconnect_reason(BSC_WEBSOCKET_HANDLE h, uint16_t err_code)
{
bws_cli_conn[h].err_desc[0] = 0;
switch (err_code) {
case LWS_CLOSE_STATUS_NORMAL: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER;
break;
}
case LWS_CLOSE_STATUS_GOINGAWAY: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ENDPOINT_LEAVES;
break;
}
case LWS_CLOSE_STATUS_PROTOCOL_ERR: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_PROTOCOL_ERROR;
break;
}
case LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED;
break;
}
case LWS_CLOSE_STATUS_NO_STATUS:
case LWS_CLOSE_STATUS_RESERVED: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
break;
}
case LWS_CLOSE_STATUS_ABNORMAL_CLOSE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_NOT_ACCEPTED;
break;
}
case LWS_CLOSE_STATUS_INVALID_PAYLOAD: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_INCONSISTENT;
break;
}
case LWS_CLOSE_STATUS_POLICY_VIOLATION: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_DATA_AGAINST_POLICY;
break;
}
case LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_FRAME_TOO_LONG;
break;
}
case LWS_CLOSE_STATUS_EXTENSION_REQUIRED: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_EXTENSION_MISSING;
break;
}
case LWS_CLOSE_STATUS_UNEXPECTED_CONDITION: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_REQUEST_UNAVAILABLE;
break;
}
default: {
bws_cli_conn[h].err_code = ERROR_CODE_WEBSOCKET_ERROR;
break;
}
}
}
static int bws_cli_websocket_event(
struct lws *wsi,
enum lws_callback_reasons reason,
void *user,
void *in,
size_t len)
{
BSC_WEBSOCKET_HANDLE h;
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
uint8_t err_code[2];
(void)user;
DEBUG_PRINTF(
"bws_cli_websocket_event() >>> reason = %d, user = %p, in = %p\n",
reason, user, in);
switch (reason) {
case LWS_CALLBACK_CLIENT_ESTABLISHED: {
bsc_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF("bws_cli_websocket_event() connection established\n");
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_CONNECTED;
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
bsc_mutex_unlock(&bws_cli_mutex);
dispatch_func(
h, BSC_WEBSOCKET_CONNECTED, 0, NULL, NULL, 0, user_param);
break;
}
case LWS_CALLBACK_CLIENT_RECEIVE: {
bsc_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF(
"bws_cli_websocket_event() received %d bytes of data\n", len);
if (!lws_frame_is_binary(wsi)) {
/* According AB.7.5.3 BACnet/SC BVLC Message Exchange,
if a received data frame is not binary,
the WebSocket connection shall be closed with a
status code of 1003 -WEBSOCKET_DATA_NOT_ACCEPTED.
*/
DEBUG_PRINTF(
"bws_cli_websocket_event() got non-binary frame, "
"close connection for socket %d\n",
h);
lws_close_reason(
wsi, LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE, NULL, 0);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = -1\n");
return -1;
}
if (bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_CONNECTED) {
bsc_mutex_unlock(&bws_cli_mutex);
} else {
if (!bws_cli_conn[h].fragment_buffer) {
DEBUG_PRINTF(
"bws_cli_websocket_event() alloc %d bytes for "
"socket %d\n",
len, h);
bws_cli_conn[h].fragment_buffer = malloc(BSC_RX_BUFFER_LEN);
if (!bws_cli_conn[h].fragment_buffer) {
lws_close_reason(
wsi, LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE, NULL, 0);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_websocket_event() <<< ret = -1, "
"allocation of %d bytes failed\n",
len <= BSC_RX_BUFFER_LEN ? BSC_RX_BUFFER_LEN : len);
return -1;
}
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = BSC_RX_BUFFER_LEN;
}
if (bws_cli_conn[h].fragment_buffer_len + len >
bws_cli_conn[h].fragment_buffer_size) {
DEBUG_PRINTF(
"bws_cli_websocket_event() realloc buf of %d bytes"
"for socket %d to %d bytes\n",
bws_cli_conn[h].fragment_buffer_len, h,
bws_cli_conn[h].fragment_buffer_len + len);
bws_cli_conn[h].fragment_buffer = realloc(
bws_cli_conn[h].fragment_buffer,
bws_cli_conn[h].fragment_buffer_len + len);
if (!bws_cli_conn[h].fragment_buffer) {
lws_close_reason(
wsi, LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE, NULL, 0);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_websocket_event() <<< ret = -1, "
"re-allocation of %d bytes failed\n",
bws_cli_conn[h].fragment_buffer_len + len);
return -1;
}
bws_cli_conn[h].fragment_buffer_size =
bws_cli_conn[h].fragment_buffer_len + len;
}
DEBUG_PRINTF(
"bws_cli_websocket_event() got next %d bytes for "
"socket %d\n",
len, h);
memcpy(
&bws_cli_conn[h]
.fragment_buffer[bws_cli_conn[h].fragment_buffer_len],
in, len);
bws_cli_conn[h].fragment_buffer_len += len;
if (lws_is_final_fragment(wsi)) {
DEBUG_PRINTF(
"bws_cli_websocket_event() last fragment received\n");
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
bsc_mutex_unlock(&bws_cli_mutex);
dispatch_func(
h, BSC_WEBSOCKET_RECEIVED, 0, NULL,
&bws_cli_conn[h].fragment_buffer[0],
bws_cli_conn[h].fragment_buffer_len, user_param);
bsc_mutex_lock(&bws_cli_mutex);
bws_cli_conn[h].fragment_buffer_len = 0;
bsc_mutex_unlock(&bws_cli_mutex);
} else {
bsc_mutex_unlock(&bws_cli_mutex);
}
}
break;
}
case LWS_CALLBACK_CLIENT_WRITEABLE: {
bsc_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
DEBUG_PRINTF(
"bws_cli_websocket_event() can not find websocket handle "
"for wsi %p\n",
wsi);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
DEBUG_PRINTF(
"bws_cli_websocket_event() can write, state = %d\n",
bws_cli_conn[h].state);
DEBUG_PRINTF(
"bws_cli_websocket_event() ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data, bws_cli_conn[h].can_send_data);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED &&
bws_cli_conn[h].want_send_data) {
bws_cli_conn[h].can_send_data = true;
dispatch_func = bws_cli_conn[h].dispatch_func;
user_param = bws_cli_conn[h].user_param;
bsc_mutex_unlock(&bws_cli_mutex);
dispatch_func(
h, BSC_WEBSOCKET_SENDABLE, 0, NULL, NULL, 0, user_param);
bsc_mutex_lock(&bws_cli_mutex);
bws_cli_conn[h].want_send_data = false;
bws_cli_conn[h].can_send_data = false;
DEBUG_PRINTF(
"bws_cli_websocket_event() was send , ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
bsc_mutex_unlock(&bws_cli_mutex);
/* wakeup worker to process internal state */
lws_cancel_service(bws_cli_conn[h].ctx);
} else {
bws_cli_conn[h].want_send_data = false;
DEBUG_PRINTF(
"bws_cli_websocket_event() no send, ws = %d, cs = %d\n",
bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
bsc_mutex_unlock(&bws_cli_mutex);
}
break;
}
case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: {
bsc_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h != BSC_WEBSOCKET_INVALID_HANDLE && len >= 2) {
err_code[0] = ((uint8_t *)in)[1];
err_code[1] = ((uint8_t *)in)[0];
bws_set_disconnect_reason(h, *((uint16_t *)&err_code));
}
bsc_mutex_unlock(&bws_cli_mutex);
break;
}
case LWS_CALLBACK_CLIENT_CLOSED:
case LWS_CALLBACK_CLOSED:
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: {
bsc_mutex_lock(&bws_cli_mutex);
h = bws_cli_find_connnection(wsi);
if (h != BSC_WEBSOCKET_INVALID_HANDLE) {
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
if (reason == LWS_CALLBACK_CLIENT_CONNECTION_ERROR && in) {
bws_set_err_desc(h, (char *)in);
}
bsc_mutex_unlock(&bws_cli_mutex);
/* wakeup worker to process pending event */
lws_cancel_service(bws_cli_conn[h].ctx);
} else {
bsc_mutex_unlock(&bws_cli_mutex);
}
break;
}
default: {
break;
}
}
DEBUG_PRINTF("bws_cli_websocket_event() <<< ret = 0\n");
return 0;
}
static DWORD WINAPI bws_cli_worker(LPVOID arg)
{
BSC_WEBSOCKET_CONNECTION *conn = (BSC_WEBSOCKET_CONNECTION *)arg;
BSC_WEBSOCKET_HANDLE h = (BSC_WEBSOCKET_HANDLE)(conn - &bws_cli_conn[0]);
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func;
void *user_param;
char err_desc[BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN];
uint16_t err_code;
srand((unsigned)GetCurrentThreadId());
while (1) {
DEBUG_PRINTF("bws_cli_worker() try mutex h = %d\n", h);
bsc_mutex_lock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() mutex locked h = %d\n", h);
if (conn->state == BSC_WEBSOCKET_STATE_CONNECTED) {
if (conn->want_send_data) {
DEBUG_PRINTF(
"bws_cli_worker() process request for sending data\n");
lws_callback_on_writable(conn->ws);
}
} else if (conn->state == BSC_WEBSOCKET_STATE_DISCONNECTING) {
DEBUG_PRINTF("bws_cli_worker() process disconnecting event\n");
DEBUG_PRINTF("bws_cli_worker() destroy ctx %p\n", conn->ctx);
/* TRICKY: Libwebsockets API is not designed to be used from
multipe service threads, as a result
lws_context_destroy() is not thread safe. More over, on different
platforms the function behaves in different ways. Call of
lws_context_destroy() leads to several calls of
bws_cli_websocket_event() callback (LWS_CALLBACK_CLOSED,
etc..). But under some OS (MacOSx) that callback is
called from context of the bws_cli_worker() thread and
under some other OS (linux) the callback is called from
internal libwebsockets lib thread. That's why
bws_cli_mutex must be unlocked before
lws_context_destroy() call. To ensure that nobody calls
lws_context_destroy() from some parallel thread it is
protected by global websocket mutex.
*/
bsc_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
lws_context_destroy(conn->ctx);
bsc_websocket_global_unlock();
bsc_mutex_lock(&bws_cli_mutex);
dispatch_func = conn->dispatch_func;
user_param = conn->user_param;
err_code = conn->err_code;
if (err_code != ERROR_CODE_SUCCESS) {
memcpy(err_desc, conn->err_desc, sizeof(err_desc));
}
bws_cli_free_connection(h);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() unlock mutex\n");
dispatch_func(
h, BSC_WEBSOCKET_DISCONNECTED, err_code,
err_code != ERROR_CODE_SUCCESS ? err_desc : NULL, NULL, 0,
user_param);
return 0;
}
DEBUG_PRINTF("bws_cli_worker() unlock mutex\n");
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_worker() going to block on lws_service() call\n");
lws_service(conn->ctx, 0);
}
return 0;
}
BSC_WEBSOCKET_RET bws_cli_connect(
BSC_WEBSOCKET_PROTOCOL proto,
char *url,
uint8_t *ca_cert,
size_t ca_cert_size,
uint8_t *cert,
size_t cert_size,
uint8_t *key,
size_t key_size,
size_t timeout_s,
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func,
void *dispatch_func_user_param,
BSC_WEBSOCKET_HANDLE *out_handle)
{
struct lws_context_creation_info info;
char tmp_url[BSC_WSURL_MAX_LEN];
const char *prot = NULL, *addr = NULL, *path = NULL;
int port = -1;
BSC_WEBSOCKET_HANDLE h;
struct lws_client_connect_info cinfo;
HANDLE thread;
size_t len;
DEBUG_PRINTF("bws_cli_connect() >>> proto = %d, url = %s\n", proto, url);
if (!ca_cert || !ca_cert_size || !cert || !cert_size || !key || !key_size ||
!url || !out_handle || !timeout_s || !dispatch_func) {
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
*out_handle = BSC_WEBSOCKET_INVALID_HANDLE;
if (proto != BSC_WEBSOCKET_HUB_PROTOCOL &&
proto != BSC_WEBSOCKET_DIRECT_PROTOCOL) {
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
memset(&info, 0, sizeof(info));
memset(&cinfo, 0, sizeof(cinfo));
len = strlen(url) >= sizeof(tmp_url) ? sizeof(tmp_url) - 1 : strlen(url);
memcpy(tmp_url, url, len);
tmp_url[len] = 0;
bsc_websocket_init_log();
bsc_mutex_lock(&bws_cli_mutex);
if (lws_parse_uri(tmp_url, &prot, &addr, &port, &path) != 0 || port == -1 ||
!prot || !addr || !path) {
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
if (strcmp(prot, "wss") != 0) {
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() <<< ret = BSC_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
h = bws_cli_alloc_connection();
if (h == BSC_WEBSOCKET_INVALID_HANDLE) {
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_CONNECTING;
bws_cli_conn[h].fragment_buffer = NULL;
bws_cli_conn[h].fragment_buffer_len = 0;
bws_cli_conn[h].fragment_buffer_size = 0;
bws_cli_conn[h].dispatch_func = dispatch_func;
bws_cli_conn[h].user_param = dispatch_func_user_param;
info.port = CONTEXT_PORT_NO_LISTEN;
if (proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
info.protocols = bws_cli_hub_protocol;
} else {
info.protocols = bws_cli_direct_protocol;
}
info.gid = -1;
info.uid = -1;
info.client_ssl_cert_mem = cert;
info.client_ssl_cert_mem_len = (unsigned int)cert_size;
info.client_ssl_ca_mem = ca_cert;
info.client_ssl_ca_mem_len = (unsigned int)ca_cert_size;
info.client_ssl_key_mem = key;
info.client_ssl_key_mem_len = (unsigned int)key_size;
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.options |= LWS_SERVER_OPTION_FAIL_UPON_UNABLE_TO_BIND;
info.timeout_secs = (unsigned int)timeout_s;
info.connect_timeout_secs = (unsigned int)timeout_s;
/* TRICKY: check comments related to lws_context_destroy() call */
bsc_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
bws_cli_conn[h].ctx = lws_create_context(&info);
bsc_websocket_global_unlock();
bsc_mutex_lock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_connect() created ctx %p\n", bws_cli_conn[h].ctx);
if (!bws_cli_conn[h].ctx) {
bws_cli_free_connection(h);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
bws_cli_conn[h].ws = NULL;
cinfo.context = bws_cli_conn[h].ctx;
cinfo.address = addr;
cinfo.origin = cinfo.address;
cinfo.host = cinfo.address;
cinfo.port = port;
cinfo.path = path;
cinfo.pwsi = &bws_cli_conn[h].ws;
cinfo.alpn = "h2;http/1.1";
bws_retry.secs_since_valid_ping = 3;
bws_retry.secs_since_valid_hangup = 10;
cinfo.retry_and_idle_policy = &bws_retry;
cinfo.ssl_connection = LCCSCF_USE_SSL |
LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_SELFSIGNED;
if (proto == BSC_WEBSOCKET_HUB_PROTOCOL) {
cinfo.protocol = bws_hub_protocol;
} else {
cinfo.protocol = bws_direct_protocol;
}
bws_cli_conn[h].err_code = ERROR_CODE_SUCCESS;
*out_handle = h;
bsc_mutex_unlock(&bws_cli_mutex);
bsc_websocket_global_lock();
lws_client_connect_via_info(&cinfo);
bsc_websocket_global_unlock();
thread = CreateThread(NULL, 0, &bws_cli_worker, &bws_cli_conn[h], 0, NULL);
if (thread == NULL) {
/* TRICKY: Libwebsockets API is not designed to be used from
multipe service threads, as a result lws_context_destroy()
is not thread safe. More over, on different platforms the
function behaves in different ways. Call of
lws_context_destroy() leads to several calls of
bws_cli_websocket_event() callback (LWS_CALLBACK_CLOSED,
etc..). But under some OS (MacOSx) that callback is
called from context of the bws_cli_worker() thread and
under some other OS (linux) the callback is called from
internal libwebsockets lib thread. That's why
bws_cli_mutex must be unlocked before
lws_context_destroy() call. To ensure that nobody calls
lws_context_destroy() from some parallel thread it is
protected by global websocket mutex.
*/
bsc_websocket_global_lock();
lws_context_destroy(bws_cli_conn[h].ctx);
bsc_websocket_global_unlock();
bsc_mutex_lock(&bws_cli_mutex);
bws_cli_free_connection(h);
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF(
"bws_cli_connect() <<< ret = BSC_WEBSOCKET_NO_RESOURCES\n");
return BSC_WEBSOCKET_NO_RESOURCES;
}
DEBUG_PRINTF("bws_cli_connect() <<< ret = %d\n", BSC_WEBSOCKET_SUCCESS);
return BSC_WEBSOCKET_SUCCESS;
}
void bws_cli_disconnect(BSC_WEBSOCKET_HANDLE h)
{
DEBUG_PRINTF("bws_cli_disconnect() >>> h = %d\n", h);
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
bsc_mutex_lock(&bws_cli_mutex);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTING ||
bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED) {
/* tell worker to process change of connection state */
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
lws_cancel_service(bws_cli_conn[h].ctx);
}
bsc_mutex_unlock(&bws_cli_mutex);
}
DEBUG_PRINTF("bws_cli_disconnect() <<<\n");
}
void bws_cli_send(BSC_WEBSOCKET_HANDLE h)
{
DEBUG_PRINTF("bws_cli_send() >>> h = %d\n", h);
if (h >= 0 && h < BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
bsc_mutex_lock(&bws_cli_mutex);
if (bws_cli_conn[h].state == BSC_WEBSOCKET_STATE_CONNECTED) {
/* tell worker to process send request */
bws_cli_conn[h].want_send_data = true;
DEBUG_PRINTF("bws_cli_send() cs = 1\n");
lws_cancel_service(bws_cli_conn[h].ctx);
}
bsc_mutex_unlock(&bws_cli_mutex);
}
DEBUG_PRINTF("bws_cli_send() <<<\n");
}
BSC_WEBSOCKET_RET bws_cli_dispatch_send(
BSC_WEBSOCKET_HANDLE h, uint8_t *payload, size_t payload_size)
{
int written;
BSC_WEBSOCKET_RET ret;
DEBUG_PRINTF(
"bws_cli_dispatch_send() >>> h = %d, payload = %p, payload_size = %d\n",
h, payload, payload_size);
if (h < 0 || h >= BSC_CLIENT_WEBSOCKETS_MAX_NUM) {
DEBUG_PRINTF(
"bws_cli_dispatch_send() <<< ret = BACNET_WEBSOCKET_BAD_PARAM\n");
return BSC_WEBSOCKET_BAD_PARAM;
}
bsc_mutex_lock(&bws_cli_mutex);
if ((bws_cli_conn[h].state != BSC_WEBSOCKET_STATE_CONNECTED) ||
!bws_cli_conn[h].want_send_data || !bws_cli_conn[h].can_send_data) {
DEBUG_PRINTF(
"bws_cli_dispatch_send() state = %d, ws = %d, cs = %d\n",
bws_cli_conn[h].state, bws_cli_conn[h].want_send_data,
bws_cli_conn[h].can_send_data);
DEBUG_PRINTF("bws_cli_dispatch_send() <<< ret = "
"BSC_WEBSOCKET_INVALID_OPERATION\n");
bsc_mutex_unlock(&bws_cli_mutex);
return BSC_WEBSOCKET_INVALID_OPERATION;
}
written =
lws_write(bws_cli_conn[h].ws, payload, payload_size, LWS_WRITE_BINARY);
DEBUG_PRINTF("bws_cli_dispatch_send() %d bytes is sent\n", written);
if (written < (int)payload_size) {
DEBUG_PRINTF(
"bws_cli_dispatch_send() websocket connection is broken(closed)\n");
/* tell worker to process change of connection state */
bws_cli_conn[h].state = BSC_WEBSOCKET_STATE_DISCONNECTING;
lws_cancel_service(bws_cli_conn[h].ctx);
ret = BSC_WEBSOCKET_INVALID_OPERATION;
} else {
ret = BSC_WEBSOCKET_SUCCESS;
}
bsc_mutex_unlock(&bws_cli_mutex);
DEBUG_PRINTF("bws_cli_dispatch_send() <<< ret = %d\n", ret);
return ret;
}
+80
View File
@@ -0,0 +1,80 @@
/**
* @file
* @brief Implementation of global websocket functions.
* @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 <libwebsockets.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "websocket-global.h"
static HANDLE websocket_mutex = NULL;
static HANDLE websocket_dispatch_mutex = NULL;
static void bsc_init_mutex(volatile HANDLE *m)
{
HANDLE p;
if (*m == NULL) {
p = CreateMutex(NULL, FALSE, NULL);
if (InterlockedCompareExchangePointer((PVOID *)m, (PVOID)p, NULL) !=
NULL) {
CloseHandle(p);
}
}
}
void bsc_mutex_lock(volatile HANDLE *m)
{
bsc_init_mutex(m);
WaitForSingleObject(*m, INFINITE);
}
void bsc_mutex_unlock(volatile HANDLE *m)
{
ReleaseMutex(*m);
}
void bsc_websocket_global_lock(void)
{
bsc_mutex_lock(&websocket_mutex);
}
void bsc_websocket_global_unlock(void)
{
bsc_mutex_unlock(&websocket_mutex);
}
void bws_dispatch_lock(void)
{
bsc_mutex_lock(&websocket_dispatch_mutex);
}
void bws_dispatch_unlock(void)
{
bsc_mutex_unlock(&websocket_dispatch_mutex);
}
static BOOL bsc_websocket_log_initialized = FALSE;
void bsc_websocket_init_log(void)
{
bsc_websocket_global_lock();
if (!bsc_websocket_log_initialized) {
bsc_websocket_log_initialized = TRUE;
#if DEBUG_LIBWEBSOCKETS_ENABLED == 1
printf("LWS_MAX_SMP = %d", LWS_MAX_SMP);
lws_set_log_level(
LLL_ERR | LLL_WARN | LLL_NOTICE | LLL_INFO | LLL_DEBUG |
LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT | LLL_LATENCY |
LLL_USER | LLL_THREAD,
NULL);
#else
lws_set_log_level(0, NULL);
#endif
}
bsc_websocket_global_unlock();
}
+18
View File
@@ -0,0 +1,18 @@
/**
* @file
* @brief Global websocket functions.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date May 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef __BSC_WEBSOCKET_MUTEX_INCLUDED__
#define __BSC_WEBSOCKET_MUTEX_INCLUDED__
#include <windows.h>
void bsc_mutex_lock(volatile HANDLE *m);
void bsc_mutex_unlock(volatile HANDLE *m);
void bsc_websocket_global_lock(void);
void bsc_websocket_global_unlock(void);
void bsc_websocket_init_log(void);
#endif
File diff suppressed because it is too large Load Diff
+80 -2
View File
@@ -17,12 +17,13 @@
#include <wchar.h>
#include <wctype.h>
#endif
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/access_rule.h"
#include "bacnet/bacenum.h"
#include "bacnet/bacdcode.h"
#include "bacnet/bacint.h"
#include "bacnet/bacreal.h"
#include "bacnet/bacdef.h"
#include "bacnet/bacapp.h"
#include "bacnet/bactext.h"
#include "bacnet/datetime.h"
@@ -30,6 +31,7 @@
#include "bacnet/bacaction.h"
#include "bacnet/lighting.h"
#include "bacnet/hostnport.h"
#include "bacnet/secure_connect.h"
#include "bacnet/weeklyschedule.h"
#include "bacnet/calendar_entry.h"
#include "bacnet/special_event.h"
@@ -518,6 +520,24 @@ int bacapp_encode_application_data(
apdu_len = bacnet_channel_value_type_encode(
apdu, &value->type.Channel_Value);
break;
#endif
#if defined(BACAPP_SECURE_CONNECT)
case BACNET_APPLICATION_TAG_SC_FAILED_CONNECTION_REQUEST:
apdu_len = bacapp_encode_SCFailedConnectionRequest(
apdu, &value->type.SC_Failed_Req);
break;
case BACNET_APPLICATION_TAG_SC_HUB_FUNCTION_CONNECTION_STATUS:
apdu_len = bacapp_encode_SCHubFunctionConnection(
apdu, &value->type.SC_Hub_Function_Status);
break;
case BACNET_APPLICATION_TAG_SC_DIRECT_CONNECTION_STATUS:
apdu_len = bacapp_encode_SCDirectConnection(
apdu, &value->type.SC_Direct_Status);
break;
case BACNET_APPLICATION_TAG_SC_HUB_CONNECTION_STATUS:
apdu_len = bacapp_encode_SCHubConnection(
apdu, &value->type.SC_Hub_Status);
break;
#endif
default:
break;
@@ -535,6 +555,7 @@ int bacapp_encode_application_data(
* @param len_value_type Count of bytes of given tag
* @param value Pointer to the application value structure,
* used to store the decoded value to.
* @note Decodes only the 13 primitive application data types!
*
* @return Number of octets consumed (could be zero).
* Parameter value->tag set to MAX_BACNET_APPLICATION_TAG when
@@ -936,6 +957,10 @@ int bacapp_encode_context_data_value(
case BACNET_APPLICATION_TAG_SCALE:
case BACNET_APPLICATION_TAG_SHED_LEVEL:
case BACNET_APPLICATION_TAG_ACCESS_RULE:
case BACNET_APPLICATION_TAG_SC_FAILED_CONNECTION_REQUEST:
case BACNET_APPLICATION_TAG_SC_HUB_FUNCTION_CONNECTION_STATUS:
case BACNET_APPLICATION_TAG_SC_DIRECT_CONNECTION_STATUS:
case BACNET_APPLICATION_TAG_SC_HUB_CONNECTION_STATUS:
/* complex data is enclosed in open/close tags */
len = encode_opening_tag(apdu, context_tag_number);
apdu_len += len;
@@ -1316,6 +1341,19 @@ int bacapp_known_property_tag(
/* BACnetAccessRule */
return BACNET_APPLICATION_TAG_ACCESS_RULE;
case PROP_SC_FAILED_CONNECTION_REQUESTS:
return BACNET_APPLICATION_TAG_SC_FAILED_CONNECTION_REQUEST;
case PROP_SC_HUB_FUNCTION_CONNECTION_STATUS:
return BACNET_APPLICATION_TAG_SC_HUB_FUNCTION_CONNECTION_STATUS;
case PROP_SC_DIRECT_CONNECT_CONNECTION_STATUS:
return BACNET_APPLICATION_TAG_SC_DIRECT_CONNECTION_STATUS;
case PROP_SC_PRIMARY_HUB_CONNECTION_STATUS:
case PROP_SC_FAILOVER_HUB_CONNECTION_STATUS:
return BACNET_APPLICATION_TAG_SC_HUB_CONNECTION_STATUS;
default:
return -1;
}
@@ -1625,6 +1663,24 @@ int bacapp_decode_application_tag_value(
apdu_len = bacnet_channel_value_decode(
apdu, apdu_size, &value->type.Channel_Value);
break;
#endif
#if defined(BACAPP_SECURE_CONNECT)
case BACNET_APPLICATION_TAG_SC_FAILED_CONNECTION_REQUEST:
apdu_len = bacapp_decode_SCFailedConnectionRequest(
apdu, apdu_size, &value->type.SC_Failed_Req);
break;
case BACNET_APPLICATION_TAG_SC_HUB_FUNCTION_CONNECTION_STATUS:
apdu_len = bacapp_decode_SCHubFunctionConnection(
apdu, apdu_size, &value->type.SC_Hub_Function_Status);
break;
case BACNET_APPLICATION_TAG_SC_DIRECT_CONNECTION_STATUS:
apdu_len = bacapp_decode_SCDirectConnection(
apdu, apdu_size, &value->type.SC_Direct_Status);
break;
case BACNET_APPLICATION_TAG_SC_HUB_CONNECTION_STATUS:
apdu_len = bacapp_decode_SCHubConnection(
apdu, apdu_size, &value->type.SC_Hub_Status);
break;
#endif
default:
break;
@@ -3539,6 +3595,28 @@ int bacapp_snprintf_value(
str, str_len, &value->type.Object_Property_Reference);
break;
#endif
#if defined(BACAPP_SECURE_CONNECT)
case BACNET_APPLICATION_TAG_SC_FAILED_CONNECTION_REQUEST:
ret_val = bacapp_snprintf_SCFailedConnectionRequest(
str, str_len, &value->type.SC_Failed_Req);
break;
case BACNET_APPLICATION_TAG_SC_HUB_FUNCTION_CONNECTION_STATUS:
ret_val = bacapp_snprintf_SCHubFunctionConnection(
str, str_len, &value->type.SC_Hub_Function_Status);
break;
case BACNET_APPLICATION_TAG_SC_DIRECT_CONNECTION_STATUS:
ret_val = bacapp_snprintf_SCDirectConnection(
str, str_len, &value->type.SC_Direct_Status);
break;
case BACNET_APPLICATION_TAG_SC_HUB_CONNECTION_STATUS:
ret_val = bacapp_snprintf_SCHubConnection(
str, str_len, &value->type.SC_Hub_Status);
break;
#endif
#if defined(BACAPP_DESTINATION)
case BACNET_APPLICATION_TAG_DESTINATION:
ret_val = bacnet_destination_to_ascii(
+7
View File
@@ -29,6 +29,7 @@
#include "bacnet/calendar_entry.h"
#include "bacnet/special_event.h"
#include "bacnet/channel_value.h"
#include "bacnet/secure_connect.h"
#ifndef BACAPP_PRINT_ENABLED
#if PRINT_ENABLED
@@ -167,6 +168,12 @@ typedef struct BACnet_Application_Data_Value {
#endif
#if defined(BACAPP_CHANNEL_VALUE)
BACNET_CHANNEL_VALUE Channel_Value;
#endif
#if defined(BACAPP_SECURE_CONNECT)
BACNET_SC_FAILED_CONNECTION_REQUEST SC_Failed_Req;
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS SC_Hub_Function_Status;
BACNET_SC_DIRECT_CONNECTION_STATUS SC_Direct_Status;
BACNET_SC_HUB_CONNECTION_STATUS SC_Hub_Status;
#endif
} type;
/* simple linked list if needed */
+3 -1
View File
@@ -148,7 +148,9 @@ typedef uint32_t BACNET_ARRAY_INDEX;
Ethernet = 6 bytes
BACnet/IPv4 = 6 bytes
LonTalk = 7 bytes
BACnet/IPv6 = 3 bytes (VMAC) */
BACnet/IPv6 = 3 bytes (VMAC)
BACnet/SC = 6 bytes (VMAC)
*/
#define MAX_MAC_LEN 7
struct BACnet_Device_Address {
+32
View File
@@ -1599,6 +1599,14 @@ typedef enum {
BACNET_APPLICATION_TAG_XY_COLOR,
/* BACnetColorCommand */
BACNET_APPLICATION_TAG_COLOR_COMMAND,
/* BACNET_SC_FAILED_CONNECTION_REQUEST */
BACNET_APPLICATION_TAG_SC_FAILED_CONNECTION_REQUEST,
/* BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS */
BACNET_APPLICATION_TAG_SC_HUB_FUNCTION_CONNECTION_STATUS,
/* BACNET_SC_DIRECT_CONNECTION_STATUS */
BACNET_APPLICATION_TAG_SC_DIRECT_CONNECTION_STATUS,
/* BACNET_SC_HUB_CONNECTION_STATUS */
BACNET_APPLICATION_TAG_SC_HUB_CONNECTION_STATUS,
/* BACnetBDTEntry */
BACNET_APPLICATION_TAG_BDT_ENTRY,
/* BACnetFDTEntry */
@@ -2172,6 +2180,10 @@ typedef enum {
compilers will allocate adequate sized datatype for enum
which is used to store decoding */
ERROR_CODE_PROPRIETARY_FIRST = 256,
/* some error codes for internal stack usage */
ERROR_CODE_ = 65535,
ERROR_CODE_DISCARD = 65534,
ERROR_CODE_DEFAULT = 65535,
ERROR_CODE_PROPRIETARY_LAST = 65535
} BACNET_ERROR_CODE;
@@ -2592,6 +2604,10 @@ typedef enum {
PORT_TYPE_NON_BACNET = 8,
PORT_TYPE_BIP6 = 9,
PORT_TYPE_SERIAL = 10,
/* For BACnet/SC network port implementations with
a Protocol_Revision 24 and higher, BACnet/SC network ports shall be
represented by a Network Port object at the BACNET_APPLICATION
protocol level with network type of SECURE_CONNECT. */
PORT_TYPE_BSC = 11,
/* Enumerated values 0-63 are reserved for definition by ASHRAE.
Enumerated values 64-255 may be used by others subject to the
@@ -2993,4 +3009,20 @@ typedef enum BACnetAuditOperation {
AUDIT_OPERATION_PROPRIETARY_MAX = 63
} BACNET_AUDIT_OPERATION;
typedef enum BACnetSCHubConnectorState {
/* FIXME: prefix with typedef name as much as possible */
BACNET_SC_HUB_CONNECTOR_STATE_NO_HUB_CONNECTION = 0,
BACNET_SC_HUB_CONNECTOR_STATE_CONNECTED_TO_PRIMARY = 1,
BACNET_SC_HUB_CONNECTOR_STATE_CONNECTED_TO_FAILOVER = 2,
BACNET_SC_HUB_CONNECTOR_STATE_MAX = 3
} BACNET_SC_HUB_CONNECTOR_STATE;
typedef enum BACnetSCConnectionState {
BACNET_SC_CONNECTION_STATE_NOT_CONNECTED = 0,
BACNET_SC_CONNECTION_STATE_CONNECTED = 1,
BACNET_SC_CONNECTION_STATE_DISCONNECTED_WITH_ERRORS = 2,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT = 3,
BACNET_SC_CONNECTION_STATE_MAX = 4
} BACNET_SC_CONNECTION_STATE;
#endif /* end of BACENUM_H */
@@ -133,6 +133,28 @@ static object_functions_t Object_Table[] = {
NULL /* Create */,
NULL /* Delete */,
NULL /* Timer */ },
#endif
#if defined(BACFILE)
{ OBJECT_FILE,
bacfile_init,
bacfile_count,
bacfile_index_to_instance,
bacfile_valid_instance,
bacfile_object_name,
bacfile_read_property,
bacfile_write_property,
BACfile_Property_Lists,
NULL /* ReadRangeInfo */,
NULL /* Iterator */,
NULL /* Value_Lists */,
NULL /* COV */,
NULL /* COV Clear */,
NULL /* Intrinsic Reporting */,
NULL /* Add_List_Element */,
NULL /* Remove_List_Element */,
NULL /* Create */,
NULL /* Delete */,
NULL /* Timer */ },
#endif
{ MAX_BACNET_OBJECT_TYPE,
NULL /* Init */,
+329 -2
View File
@@ -25,6 +25,7 @@
#include "bacnet/basic/object/device.h"
/* me */
#include "bacnet/basic/object/netport.h"
#include <bacnet/basic/object/netport_internal.h>
#if defined(BACDL_BIP6)
#include "bacnet/datalink/bvlc6.h"
@@ -92,6 +93,11 @@ struct mstp_port {
uint8_t Max_Info_Frames;
};
struct bsc_port {
uint8_t MAC_Address[6];
BACNET_SC_PARAMS Parameters;
};
struct object_data {
uint32_t Instance_Number;
const char *Object_Name;
@@ -111,11 +117,14 @@ struct object_data {
struct bacnet_ipv6_port IPv6;
struct ethernet_port Ethernet;
struct mstp_port MSTP;
struct bsc_port BSC;
} Network;
};
#ifndef BACNET_NETWORK_PORTS_MAX
#define BACNET_NETWORK_PORTS_MAX 1
#endif
static struct object_data Object_List[BACNET_NETWORK_PORTS_MAX];
/* These three arrays are used by the ReadPropertyMultiple handler */
@@ -194,6 +203,53 @@ static const int BIP6_Port_Properties_Optional[] = {
-1
};
static const int BSC_Port_Properties_Optional[] = {
PROP_NETWORK_NUMBER,
PROP_NETWORK_NUMBER_QUALITY,
PROP_APDU_LENGTH,
PROP_MAC_ADDRESS,
PROP_BACNET_IP_MODE,
PROP_IP_ADDRESS,
PROP_BACNET_IP_UDP_PORT,
PROP_IP_SUBNET_MASK,
PROP_IP_DEFAULT_GATEWAY,
PROP_IP_DNS_SERVER,
PROP_MAX_BVLC_LENGTH_ACCEPTED,
PROP_MAX_NPDU_LENGTH_ACCEPTED,
PROP_SC_PRIMARY_HUB_URI,
PROP_SC_FAILOVER_HUB_URI,
PROP_SC_MINIMUM_RECONNECT_TIME,
PROP_SC_MAXIMUM_RECONNECT_TIME,
PROP_SC_CONNECT_WAIT_TIMEOUT,
PROP_SC_DISCONNECT_WAIT_TIMEOUT,
PROP_SC_HEARTBEAT_TIMEOUT,
PROP_SC_HUB_CONNECTOR_STATE,
PROP_OPERATIONAL_CERTIFICATE_FILE,
PROP_ISSUER_CERTIFICATE_FILES,
PROP_CERTIFICATE_SIGNING_REQUEST_FILE,
/*SC optional*/
#ifdef BACNET_SECURE_CONNECT_ROUTING_TABLE
PROP_ROUTING_TABLE,
#endif /* BACNET_SECURE_CONNECT_ROUTING_TABLE */
#if BSC_CONF_HUB_FUNCTIONS_NUM != 0
PROP_SC_PRIMARY_HUB_CONNECTION_STATUS,
PROP_SC_FAILOVER_HUB_CONNECTION_STATUS,
PROP_SC_HUB_FUNCTION_ENABLE,
PROP_SC_HUB_FUNCTION_ACCEPT_URIS,
PROP_SC_HUB_FUNCTION_BINDING,
PROP_SC_HUB_FUNCTION_CONNECTION_STATUS,
#endif /* BSC_CONF_HUB_FUNCTIONS_NUM!=0 */
#if BSC_CONF_HUB_CONNECTORS_NUM != 0
PROP_SC_DIRECT_CONNECT_INITIATE_ENABLE,
PROP_SC_DIRECT_CONNECT_ACCEPT_ENABLE,
PROP_SC_DIRECT_CONNECT_ACCEPT_URIS,
PROP_SC_DIRECT_CONNECT_BINDING,
PROP_SC_DIRECT_CONNECT_CONNECTION_STATUS,
#endif /* BSC_CONF_HUB_CONNECTORS_NUM!=0 */
PROP_SC_FAILED_CONNECTION_REQUESTS,
-1
};
static const int Network_Port_Properties_Proprietary[] = { -1 };
/**
@@ -229,6 +285,9 @@ void Network_Port_Property_List(
case PORT_TYPE_BIP:
*pOptional = BIP_Port_Properties_Optional;
break;
case PORT_TYPE_BSC:
*pOptional = BSC_Port_Properties_Optional;
break;
case PORT_TYPE_BIP6:
*pOptional = BIP6_Port_Properties_Optional;
break;
@@ -590,6 +649,16 @@ uint8_t Network_Port_Type(uint32_t object_instance)
index = Network_Port_Instance_To_Index(object_instance);
if (index < BACNET_NETWORK_PORTS_MAX) {
port_type = Object_List[index].Network_Type;
#if (BACNET_PROTOCOL_REVISION >= 17) && (BACNET_PROTOCOL_REVISION <= 23)
/* For BACnet/SC network port implementations with
a protocol revision Protocol_Revision 17 and higher through 23,
BACnet/SC network ports shall be represented by a Network Port
object at the BACNET_APPLICATION protocol level with
a proprietary network type value. */
if (port_type == PORT_TYPE_BSC) {
port_type = PORT_TYPE_BSC_INTERIM;
}
#endif
}
return port_type;
@@ -612,7 +681,18 @@ bool Network_Port_Type_Set(uint32_t object_instance, uint8_t value)
index = Network_Port_Instance_To_Index(object_instance);
if (index < BACNET_NETWORK_PORTS_MAX) {
#if (BACNET_PROTOCOL_REVISION >= 17) && (BACNET_PROTOCOL_REVISION <= 23)
/* For BACnet/SC network port implementations with
a protocol revision Protocol_Revision 17 and higher through 23,
BACnet/SC network ports shall be represented by a Network Port
object at the BACNET_APPLICATION protocol level with
a proprietary network type value. */
if (value == PORT_TYPE_BSC_INTERIM) {
value = PORT_TYPE_BSC;
}
#endif
Object_List[index].Network_Type = value;
status = true;
}
@@ -748,6 +828,10 @@ uint8_t Network_Port_MAC_Address_Value(
mac = &Object_List[index].Network.IPv6.MAC_Address[0];
mac_len = sizeof(Object_List[index].Network.IPv6.MAC_Address);
break;
case PORT_TYPE_BSC:
mac = &Object_List[index].Network.BSC.MAC_Address[0];
mac_len = sizeof(Object_List[index].Network.BSC.MAC_Address);
break;
default:
break;
}
@@ -824,6 +908,10 @@ bool Network_Port_MAC_Address_Set(
mac_dest = &Object_List[index].Network.IPv6.MAC_Address[0];
mac_size = sizeof(Object_List[index].Network.IPv6.MAC_Address);
break;
case PORT_TYPE_BSC:
mac_dest = &Object_List[index].Network.BSC.MAC_Address[0];
mac_size = sizeof(Object_List[index].Network.BSC.MAC_Address);
break;
default:
break;
}
@@ -3540,6 +3628,178 @@ int Network_Port_Read_Property(BACNET_READ_PROPERTY_DATA *rpdata)
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
#ifdef BACDL_BSC
case PROP_MAX_BVLC_LENGTH_ACCEPTED:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_Max_BVLC_Length_Accepted(rpdata->object_instance));
break;
case PROP_MAX_NPDU_LENGTH_ACCEPTED:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_Max_NPDU_Length_Accepted(rpdata->object_instance));
break;
case PROP_SC_PRIMARY_HUB_URI:
Network_Port_SC_Primary_Hub_URI(
rpdata->object_instance, &char_string);
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
case PROP_SC_FAILOVER_HUB_URI:
Network_Port_SC_Failover_Hub_URI(
rpdata->object_instance, &char_string);
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
case PROP_SC_MINIMUM_RECONNECT_TIME:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_SC_Minimum_Reconnect_Time(
rpdata->object_instance));
break;
case PROP_SC_MAXIMUM_RECONNECT_TIME:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_SC_Maximum_Reconnect_Time(
rpdata->object_instance));
break;
case PROP_SC_CONNECT_WAIT_TIMEOUT:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_SC_Connect_Wait_Timeout(rpdata->object_instance));
break;
case PROP_SC_DISCONNECT_WAIT_TIMEOUT:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_SC_Disconnect_Wait_Timeout(
rpdata->object_instance));
break;
case PROP_SC_HEARTBEAT_TIMEOUT:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_SC_Heartbeat_Timeout(rpdata->object_instance));
break;
case PROP_SC_HUB_CONNECTOR_STATE:
apdu_len = encode_application_enumerated(
&apdu[0],
Network_Port_SC_Hub_Connector_State(rpdata->object_instance));
break;
case PROP_OPERATIONAL_CERTIFICATE_FILE:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_Operational_Certificate_File(
rpdata->object_instance));
break;
case PROP_ISSUER_CERTIFICATE_FILES:
apdu_len = bacnet_array_encode(
rpdata->object_instance, rpdata->array_index,
Network_Port_Issuer_Certificate_File_Encode,
BACNET_ISSUER_CERT_FILE_MAX, apdu, apdu_size);
if (apdu_len == BACNET_STATUS_ABORT) {
rpdata->error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
} else if (apdu_len == BACNET_STATUS_ERROR) {
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
}
break;
case PROP_CERTIFICATE_SIGNING_REQUEST_FILE:
apdu_len = encode_application_unsigned(
&apdu[0],
Network_Port_Certificate_Signing_Request_File(
rpdata->object_instance));
break;
/* SC optionals */
#if BACNET_SECURE_CONNECT_ROUTING_TABLE
case PROP_ROUTING_TABLE:
apdu_len = Network_Port_Routing_Table_Encode(
rpdata->object_instance, apdu, apdu_size);
break;
#endif /* BACNET_SECURE_CONNECT_ROUTING_TABLE */
#if BSC_CONF_HUB_FUNCTIONS_NUM != 0
case PROP_SC_PRIMARY_HUB_CONNECTION_STATUS:
apdu_len = bacapp_encode_SCHubConnection(
&apdu[0],
Network_Port_SC_Primary_Hub_Connection_Status(
rpdata->object_instance));
break;
case PROP_SC_FAILOVER_HUB_CONNECTION_STATUS:
apdu_len = bacapp_encode_SCHubConnection(
&apdu[0],
Network_Port_SC_Failover_Hub_Connection_Status(
rpdata->object_instance));
break;
case PROP_SC_HUB_FUNCTION_ENABLE:
apdu_len = encode_application_boolean(
&apdu[0],
Network_Port_SC_Hub_Function_Enable(rpdata->object_instance));
break;
case PROP_SC_HUB_FUNCTION_ACCEPT_URIS:
apdu_len = bacnet_array_encode(
rpdata->object_instance, rpdata->array_index,
Network_Port_SC_Hub_Function_Accept_URI_Encode,
BACNET_SC_DIRECT_ACCEPT_URI_MAX, apdu, apdu_size);
if (apdu_len == BACNET_STATUS_ABORT) {
rpdata->error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
} else if (apdu_len == BACNET_STATUS_ERROR) {
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
}
break;
case PROP_SC_HUB_FUNCTION_BINDING:
Network_Port_SC_Hub_Function_Binding(
rpdata->object_instance, &char_string);
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
case PROP_SC_HUB_FUNCTION_CONNECTION_STATUS:
apdu_len = Network_Port_SC_Hub_Function_Connection_Status_Encode(
rpdata->object_instance, apdu, apdu_size);
break;
#endif /* BSC_CONF_HUB_FUNCTIONS_NUM!=0 */
#if BSC_CONF_HUB_CONNECTORS_NUM != 0
case PROP_SC_DIRECT_CONNECT_INITIATE_ENABLE:
apdu_len = encode_application_boolean(
&apdu[0],
Network_Port_SC_Direct_Connect_Initiate_Enable(
rpdata->object_instance));
break;
case PROP_SC_DIRECT_CONNECT_ACCEPT_ENABLE:
apdu_len = encode_application_boolean(
&apdu[0],
Network_Port_SC_Direct_Connect_Accept_Enable(
rpdata->object_instance));
break;
case PROP_SC_DIRECT_CONNECT_ACCEPT_URIS:
apdu_len = bacnet_array_encode(
rpdata->object_instance, rpdata->array_index,
Network_Port_SC_Direct_Connect_Accept_URI_Encode,
BACNET_SC_DIRECT_ACCEPT_URI_MAX, apdu, apdu_size);
if (apdu_len == BACNET_STATUS_ABORT) {
rpdata->error_code =
ERROR_CODE_ABORT_SEGMENTATION_NOT_SUPPORTED;
} else if (apdu_len == BACNET_STATUS_ERROR) {
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_INVALID_ARRAY_INDEX;
}
break;
case PROP_SC_DIRECT_CONNECT_BINDING:
Network_Port_SC_Direct_Connect_Binding(
rpdata->object_instance, &char_string);
apdu_len =
encode_application_character_string(&apdu[0], &char_string);
break;
case PROP_SC_DIRECT_CONNECT_CONNECTION_STATUS:
apdu_len = Network_Port_SC_Direct_Connect_Connection_Status_Encode(
rpdata->object_instance, apdu, apdu_size);
break;
#endif /* BSC_CONF_HUB_CONNECTORS_NUM!=0 */
case PROP_SC_FAILED_CONNECTION_REQUESTS:
apdu_len = Network_Port_SC_Failed_Connection_Requests_Encode(
rpdata->object_instance, apdu, apdu_size);
break;
#endif /* BACDL_BSC */
default:
rpdata->error_class = ERROR_CLASS_PROPERTY;
rpdata->error_code = ERROR_CODE_UNKNOWN_PROPERTY;
@@ -3693,6 +3953,11 @@ bool Network_Port_Write_Property(BACNET_WRITE_PROPERTY_DATA *wp_data)
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
if (!status && (wp_data->error_code == ERROR_CODE_OTHER)) {
wp_data->error_class = ERROR_CLASS_PROPERTY;
wp_data->error_code = ERROR_CODE_INVALID_DATA_TYPE;
}
return status;
}
@@ -3715,7 +3980,7 @@ int Network_Port_Read_Range_BDT(uint8_t *apdu, BACNET_READ_RANGE_DATA *pRequest)
* ReadRange service handler for the BACnet/IP FDT.
*
* @param apdu - place to encode the data
* @param apdu - BACNET_READ_RANGE_DATA data
* @param pRequest - BACNET_READ_RANGE_DATA data
*
* @return number of bytes encoded
*/
@@ -3845,7 +4110,16 @@ void Network_Port_Changes_Discard(void)
*/
void Network_Port_Cleanup(void)
{
/* do something interesting */
#if defined(BACDL_BSC) && defined(BACNET_SECURE_CONNECT_ROUTING_TABLE)
unsigned index = 0;
for (index = 0; index < BACNET_NETWORK_PORTS_MAX; index++) {
BACNET_SC_PARAMS *sc = &Object_List[index].Network.BSC.Parameters;
if (sc->Routing_Table) {
Keylist_Delete(sc->Routing_Table);
sc->Routing_Table = NULL;
}
}
#endif
}
/**
@@ -3853,5 +4127,58 @@ void Network_Port_Cleanup(void)
*/
void Network_Port_Init(void)
{
unsigned index = 0;
#ifdef BACDL_BSC
BACNET_SC_PARAMS *sc;
#endif /* BACDL_BSC */
/* do something interesting */
for (index = 0; index < BACNET_NETWORK_PORTS_MAX; index++) {
memset(&Object_List[index], 0, sizeof(Object_List[index]));
#ifdef BACDL_BSC
Object_List[index].Network_Type = PORT_TYPE_BSC;
sc = &Object_List[index].Network.BSC.Parameters;
Object_List[index].Activate_Changes =
Network_Port_SC_Pending_Params_Apply;
Object_List[index].Discard_Changes =
Network_Port_SC_Pending_Params_Discard;
#ifdef BACNET_SECURE_CONNECT_ROUTING_TABLE
sc->Routing_Table = Keylist_Create();
#endif
sc->SC_Failed_Connection_Requests_Count = 0;
#if BSC_CONF_HUB_FUNCTIONS_NUM != 0
sc->SC_Hub_Function_Connection_Status_Count = 0;
#endif
#if BSC_CONF_HUB_CONNECTORS_NUM != 0
sc->SC_Direct_Connect_Connection_Status_Count = 0;
#endif
(void)sc;
#endif /* BACDL_BSC */
}
}
#ifdef BACDL_BSC
/**
* For a given object instance-number, gets SC parameters structure
*
* @param object_instance - object-instance number of the object
*
* @return SC params structure
*/
BACNET_SC_PARAMS *Network_Port_SC_Params(uint32_t object_instance)
{
BACNET_SC_PARAMS *param = NULL;
unsigned index = 0;
index = Network_Port_Instance_To_Index(object_instance);
if (index < BACNET_NETWORK_PORTS_MAX) {
if (Object_List[index].Network_Type == PORT_TYPE_BSC) {
param = &Object_List[index].Network.BSC.Parameters;
}
}
return param;
}
#endif
+107
View File
@@ -0,0 +1,107 @@
/**
* @file
* @brief Helper Network port objects to implement secure connect attributes
* @author Mikhail Antropov <michail.antropov@dsr-corporation.com>
* @date December 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_NETPORT_INTERNAL_H
#define BACNET_NETPORT_INTERNAL_H
#include <stdbool.h>
#include <stdint.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/basic/sys/keylist.h"
#include "bacnet/apdu.h"
#include "bacnet/datetime.h"
#include "bacnet/hostnport.h"
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/basic/object/sc_netport.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Support
- SC_Hub_Function_Connection_Status
- SC_Direct_Connect_Connection_Status
- SC_FailedConnectionRequests
*/
typedef struct BACnetSCAttributes_T {
BACNET_UNSIGNED_INTEGER Max_BVLC_Length_Accepted;
BACNET_UNSIGNED_INTEGER Max_BVLC_Length_Accepted_dirty;
BACNET_UNSIGNED_INTEGER Max_NPDU_Length_Accepted;
BACNET_UNSIGNED_INTEGER Max_NPDU_Length_Accepted_dirty;
char SC_Primary_Hub_URI[BACNET_URI_LENGTH];
char SC_Primary_Hub_URI_dirty[BACNET_URI_LENGTH];
char SC_Failover_Hub_URI[BACNET_URI_LENGTH];
char SC_Failover_Hub_URI_dirty[BACNET_URI_LENGTH];
BACNET_UNSIGNED_INTEGER SC_Minimum_Reconnect_Time;
BACNET_UNSIGNED_INTEGER SC_Minimum_Reconnect_Time_dirty;
BACNET_UNSIGNED_INTEGER SC_Maximum_Reconnect_Time;
BACNET_UNSIGNED_INTEGER SC_Maximum_Reconnect_Time_dirty;
BACNET_UNSIGNED_INTEGER SC_Connect_Wait_Timeout;
BACNET_UNSIGNED_INTEGER SC_Connect_Wait_Timeout_dirty;
BACNET_UNSIGNED_INTEGER SC_Disconnect_Wait_Timeout;
BACNET_UNSIGNED_INTEGER SC_Disconnect_Wait_Timeout_dirty;
BACNET_UNSIGNED_INTEGER SC_Heartbeat_Timeout;
BACNET_UNSIGNED_INTEGER SC_Heartbeat_Timeout_dirty;
BACNET_SC_HUB_CONNECTOR_STATE SC_Hub_Connector_State;
uint32_t Operational_Certificate_File;
uint32_t Issuer_Certificate_Files[BACNET_ISSUER_CERT_FILE_MAX];
uint32_t Certificate_Signing_Request_File;
/* Optional params */
#ifdef BACNET_SECURE_CONNECT_ROUTING_TABLE
OS_Keylist Routing_Table;
#endif /* BACNET_SECURE_CONNECT_ROUTING_TABLE */
#if BSC_CONF_HUB_FUNCTIONS_NUM != 0
BACNET_SC_HUB_CONNECTION_STATUS SC_Primary_Hub_Connection_Status;
BACNET_SC_HUB_CONNECTION_STATUS SC_Failover_Hub_Connection_Status;
bool SC_Hub_Function_Enable;
bool SC_Hub_Function_Enable_dirty;
char SC_Hub_Function_Accept_URIs
[BACNET_SC_HUB_URI_MAX * (BACNET_URI_LENGTH + 1)];
char SC_Hub_Function_Accept_URIs_dirty
[BACNET_SC_HUB_URI_MAX * (BACNET_URI_LENGTH + 1)];
char SC_Hub_Function_Binding[BACNET_BINDING_STRING_LENGTH];
char SC_Hub_Function_Binding_dirty[BACNET_BINDING_STRING_LENGTH];
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS
SC_Hub_Function_Connection_Status
[BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM];
uint8_t SC_Hub_Function_Connection_Status_Count;
uint16_t Hub_Server_Port;
#endif /* BSC_CONF_HUB_FUNCTIONS_NUM!=0 */
#if BSC_CONF_HUB_CONNECTORS_NUM != 0
bool SC_Direct_Connect_Initiate_Enable;
bool SC_Direct_Connect_Initiate_Enable_dirty;
bool SC_Direct_Connect_Accept_Enable;
bool SC_Direct_Connect_Accept_Enable_dirty;
char SC_Direct_Connect_Accept_URIs
[BACNET_SC_DIRECT_ACCEPT_URI_MAX * (BACNET_URI_LENGTH + 1)];
char SC_Direct_Connect_Accept_URIs_dirty
[BACNET_SC_DIRECT_ACCEPT_URI_MAX * (BACNET_URI_LENGTH + 1)];
char SC_Direct_Connect_Binding[BACNET_BINDING_STRING_LENGTH];
char SC_Direct_Connect_Binding_dirty[BACNET_BINDING_STRING_LENGTH];
BACNET_SC_DIRECT_CONNECTION_STATUS SC_Direct_Connect_Connection_Status
[BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM];
uint8_t SC_Direct_Connect_Connection_Status_Count;
uint16_t Direct_Server_Port;
#endif /* BSC_CONF_HUB_CONNECTORS_NUM!=0 */
BACNET_SC_FAILED_CONNECTION_REQUEST SC_Failed_Connection_Requests
[BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM];
uint8_t SC_Failed_Connection_Requests_Count;
uint32_t Certificate_Key_File;
BACNET_UUID Local_UUID;
} BACNET_SC_PARAMS;
BACNET_SC_PARAMS *Network_Port_SC_Params(uint32_t object_instance);
void Network_Port_SC_Pending_Params_Apply(uint32_t object_instance);
void Network_Port_SC_Pending_Params_Discard(uint32_t object_instance);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* NETPORT_INTERNAL_H */
File diff suppressed because it is too large Load Diff
+415
View File
@@ -0,0 +1,415 @@
/**
* @file
* @brief Helper Network port objects to implement secure connect attributes
* @author Mikhail Antropov <michail.antropov@dsr-corporation.com>
* @date June 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_SC_NETPORT_H
#define BACNET_SC_NETPORT_H
#include <stdbool.h>
#include <stdint.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/basic/sys/keylist.h"
#include "bacnet/bacapp.h"
#include "bacnet/apdu.h"
#include "bacnet/secure_connect.h"
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
/* BACnet file instance numbers */
#ifndef BSC_ISSUER_CERTIFICATE_FILE_1_INSTANCE
#define BSC_ISSUER_CERTIFICATE_FILE_1_INSTANCE \
BSC_CONF_ISSUER_CERTIFICATE_FILE_1_INSTANCE
#endif
#ifndef BSC_ISSUER_CERTIFICATE_FILE_2_INSTANCE
#define BSC_ISSUER_CERTIFICATE_FILE_2_INSTANCE \
BSC_CONF_ISSUER_CERTIFICATE_FILE_2_INSTANCE
#endif
#ifndef BSC_OPERATIONAL_CERTIFICATE_FILE_INSTANCE
#define BSC_OPERATIONAL_CERTIFICATE_FILE_INSTANCE \
BSC_CONF_OPERATIONAL_CERTIFICATE_FILE_INSTANCE
#endif
#ifndef BSC_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE
#define BSC_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE \
BSC_CONF_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE
#endif
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define BACNET_SC_HUB_URI_MAX 2
#ifndef BACNET_SC_DIRECT_ACCEPT_URI_MAX
#define BACNET_SC_DIRECT_ACCEPT_URI_MAX 2
#endif
#define BACNET_ISSUER_CERT_FILE_MAX 2
#define BACNET_BINDING_STRING_LENGTH 80
#define BACNET_SC_BINDING_SEPARATOR ':'
/* */
/* getter / setter */
/* */
BACNET_STACK_EXPORT
BACNET_UNSIGNED_INTEGER
Network_Port_Max_BVLC_Length_Accepted(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_Max_BVLC_Length_Accepted_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
bool Network_Port_Max_BVLC_Length_Accepted_Dirty_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
BACNET_STACK_EXPORT
BACNET_UNSIGNED_INTEGER
Network_Port_Max_NPDU_Length_Accepted(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_Max_NPDU_Length_Accepted_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
bool Network_Port_Max_NPDU_Length_Accepted_Dirty_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
BACNET_STACK_EXPORT
bool Network_Port_SC_Primary_Hub_URI(
uint32_t object_instance, BACNET_CHARACTER_STRING *str);
const char *Network_Port_SC_Primary_Hub_URI_char(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Primary_Hub_URI_Set(
uint32_t object_instance, const char *str);
bool Network_Port_SC_Primary_Hub_URI_Dirty_Set(
uint32_t object_instance, const char *str);
BACNET_STACK_EXPORT
bool Network_Port_SC_Failover_Hub_URI(
uint32_t object_instance, BACNET_CHARACTER_STRING *str);
const char *Network_Port_SC_Failover_Hub_URI_char(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Failover_Hub_URI_Set(
uint32_t object_instance, const char *str);
bool Network_Port_SC_Failover_Hub_URI_Dirty_Set(
uint32_t object_instance, const char *str);
BACNET_STACK_EXPORT
BACNET_UNSIGNED_INTEGER
Network_Port_SC_Minimum_Reconnect_Time(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Minimum_Reconnect_Time_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
bool Network_Port_SC_Minimum_Reconnect_Time_Dirty_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
BACNET_STACK_EXPORT
BACNET_UNSIGNED_INTEGER
Network_Port_SC_Maximum_Reconnect_Time(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Maximum_Reconnect_Time_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
bool Network_Port_SC_Maximum_Reconnect_Time_Dirty_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
BACNET_STACK_EXPORT
BACNET_UNSIGNED_INTEGER
Network_Port_SC_Connect_Wait_Timeout(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Connect_Wait_Timeout_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
bool Network_Port_SC_Connect_Wait_Timeout_Dirty_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
BACNET_STACK_EXPORT
BACNET_UNSIGNED_INTEGER
Network_Port_SC_Disconnect_Wait_Timeout(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Disconnect_Wait_Timeout_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
bool Network_Port_SC_Disconnect_Wait_Timeout_Dirty_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
BACNET_STACK_EXPORT
BACNET_UNSIGNED_INTEGER
Network_Port_SC_Heartbeat_Timeout(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Heartbeat_Timeout_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
bool Network_Port_SC_Heartbeat_Timeout_Dirty_Set(
uint32_t object_instance, BACNET_UNSIGNED_INTEGER value);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTOR_STATE
Network_Port_SC_Hub_Connector_State(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Connector_State_Set(
uint32_t object_instance, BACNET_SC_HUB_CONNECTOR_STATE value);
BACNET_STACK_EXPORT
uint32_t Network_Port_Operational_Certificate_File(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_Operational_Certificate_File_Set(
uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
uint32_t
Network_Port_Issuer_Certificate_File(uint32_t object_instance, uint8_t index);
BACNET_STACK_EXPORT
bool Network_Port_Issuer_Certificate_File_Set(
uint32_t object_instance, uint8_t index, uint32_t value);
BACNET_STACK_EXPORT
int Network_Port_Issuer_Certificate_File_Encode(
uint32_t object_instance, BACNET_ARRAY_INDEX index, uint8_t *apdu);
BACNET_STACK_EXPORT
uint32_t
Network_Port_Certificate_Signing_Request_File(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_Certificate_Signing_Request_File_Set(
uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
BACNET_ROUTER_ENTRY *Network_Port_Routing_Table_Find(
uint32_t object_instance, uint16_t Network_Number);
BACNET_STACK_EXPORT
BACNET_ROUTER_ENTRY *
Network_Port_Routing_Table_Get(uint32_t object_instance, uint8_t index);
BACNET_STACK_EXPORT
bool Network_Port_Routing_Table_Add(
uint32_t object_instance,
uint16_t network_number,
uint8_t *mac,
uint8_t mac_len,
uint8_t status,
uint8_t performance_index);
BACNET_STACK_EXPORT
bool Network_Port_Routing_Table_Delete(
uint32_t object_instance, uint16_t Network_Number);
BACNET_STACK_EXPORT
bool Network_Port_Routing_Table_Delete_All(uint32_t object_instance);
BACNET_STACK_EXPORT
uint8_t Network_Port_Routing_Table_Count(uint32_t object_instance);
BACNET_STACK_EXPORT
int Network_Port_Routing_Table_Encode(
uint32_t object_instance, uint8_t *apdu, int max_apdu);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTION_STATUS *
Network_Port_SC_Primary_Hub_Connection_Status(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Primary_Hub_Connection_Status_Set(
uint32_t object_instance,
BACNET_SC_CONNECTION_STATE state,
BACNET_DATE_TIME *connect_ts,
BACNET_DATE_TIME *disconnect_ts,
BACNET_ERROR_CODE error,
const char *error_details);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTION_STATUS *
Network_Port_SC_Failover_Hub_Connection_Status(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Failover_Hub_Connection_Status_Set(
uint32_t object_instance,
BACNET_SC_CONNECTION_STATE state,
BACNET_DATE_TIME *connect_ts,
BACNET_DATE_TIME *disconnect_ts,
BACNET_ERROR_CODE error,
const char *error_details);
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Function_Enable(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Function_Enable_Set(
uint32_t object_instance, bool value);
bool Network_Port_SC_Hub_Function_Enable_Dirty_Set(
uint32_t object_instance, bool value);
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Function_Accept_URI(
uint32_t object_instance, uint8_t index, BACNET_CHARACTER_STRING *str);
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Function_Accept_URI_Set(
uint32_t object_instance, uint8_t index, const char *str);
BACNET_STACK_EXPORT
int Network_Port_SC_Hub_Function_Accept_URI_Encode(
uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu);
bool Network_Port_SC_Hub_Function_Accept_URI_Dirty_Set(
uint32_t object_instance, uint8_t index, const char *str);
const char *
Network_Port_SC_Hub_Function_Accept_URIs_char(uint32_t object_instance);
bool Network_Port_SC_Hub_Function_Accept_URIs_Set(
uint32_t object_instance, const char *str);
/*
Internal representation of the SC_Hub_Function_Binding value is a
struct {
uint16 port;
char interface_name[BACNET_BINDING_STRING_LENGTH - sizeof(uint16)];
}
External representation is string e.q "interface_name:port_number".
The Network_Port_SC_Hub_Function_Binding() and
the Network_Port_SC_Hub_Function_Binding_Set() do transform from/to.
The Network_Port_SC_Hub_Function_Binding_Dirty sets value like
external representation.
*/
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Function_Binding(
uint32_t object_instance, BACNET_CHARACTER_STRING *str);
void Network_Port_SC_Hub_Function_Binding_get(
uint32_t object_instance, uint16_t *port, char **ifname);
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Function_Binding_Set(
uint32_t object_instance, const char *str);
bool Network_Port_SC_Hub_Function_Binding_Dirty_Set(
uint32_t object_instance, const char *str);
BACNET_STACK_EXPORT
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *
Network_Port_SC_Hub_Function_Connection_Status_Get(
uint32_t object_instance, uint8_t index);
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Function_Connection_Status_Add(
uint32_t object_instance,
BACNET_SC_CONNECTION_STATE state,
BACNET_DATE_TIME *connect_ts,
BACNET_DATE_TIME *disconnect_ts,
BACNET_HOST_N_PORT_DATA *peer_address,
uint8_t *peer_VMAC,
uint8_t *peer_UUID,
BACNET_ERROR_CODE error,
const char *error_details);
BACNET_STACK_EXPORT
bool Network_Port_SC_Hub_Function_Connection_Status_Delete_All(
uint32_t object_instance);
BACNET_STACK_EXPORT
int Network_Port_SC_Hub_Function_Connection_Status_Count(
uint32_t object_instance);
BACNET_STACK_EXPORT
int Network_Port_SC_Hub_Function_Connection_Status_Encode(
uint32_t object_instance, uint8_t *apdu, int max_apdu);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Initiate_Enable(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Initiate_Enable_Set(
uint32_t object_instance, bool value);
bool Network_Port_SC_Direct_Connect_Initiate_Enable_Dirty_Set(
uint32_t object_instance, bool value);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Accept_Enable(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Accept_Enable_Set(
uint32_t object_instance, bool value);
bool Network_Port_SC_Direct_Connect_Accept_Enable_Dirty_Set(
uint32_t object_instance, bool value);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Accept_URI(
uint32_t object_instance, uint8_t index, BACNET_CHARACTER_STRING *str);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Accept_URI_Set(
uint32_t object_instance, uint8_t index, const char *str);
BACNET_STACK_EXPORT
int Network_Port_SC_Direct_Connect_Accept_URI_Encode(
uint32_t object_instance, BACNET_ARRAY_INDEX array_index, uint8_t *apdu);
bool Network_Port_SC_Direct_Connect_Accept_URI_Dirty_Set(
uint32_t object_instance, uint8_t index, const char *str);
char *Network_Port_SC_Direct_Connect_Accept_URIs_char(uint32_t object_instance);
bool Network_Port_SC_Direct_Connect_Accept_URIs_Set(
uint32_t object_instance, const char *str);
/*
Internal representation of the SC_Direct_Connect_Binding value is a
struct {
uint16 port;
char interface_name[BACNET_BINDING_STRING_LENGTH - sizeof(uint16)];
}
External representation is string e.q "interface_name:port_number".
The Network_Port_SC_Direct_Connect_Binding() and
the Network_Port_SC_Direct_Connect_Binding_Set() do transform from/to.
The Network_Port_SC_Direct_Connect_Binding_Dirty sets value like
external representation.
*/
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Binding(
uint32_t object_instance, BACNET_CHARACTER_STRING *str);
void Network_Port_SC_Direct_Connect_Binding_get(
uint32_t object_instance, uint16_t *port, char **ifname);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Binding_Set(
uint32_t object_instance, const char *str);
bool Network_Port_SC_Direct_Connect_Binding_Dirty_Set(
uint32_t object_instance, const char *str);
BACNET_STACK_EXPORT
BACNET_SC_DIRECT_CONNECTION_STATUS *
Network_Port_SC_Direct_Connect_Connection_Status_Get(
uint32_t object_instance, uint8_t index);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Connection_Status_Add(
uint32_t object_instance,
const char *uri,
BACNET_SC_CONNECTION_STATE state,
BACNET_DATE_TIME *connect_ts,
BACNET_DATE_TIME *disconnect_ts,
BACNET_HOST_N_PORT_DATA *peer_address,
uint8_t *peer_VMAC,
uint8_t *peer_UUID,
BACNET_ERROR_CODE error,
const char *error_details);
BACNET_STACK_EXPORT
bool Network_Port_SC_Direct_Connect_Connection_Status_Delete_All(
uint32_t object_instance);
BACNET_STACK_EXPORT
int Network_Port_SC_Direct_Connect_Connection_Status_Count(
uint32_t object_instance);
BACNET_STACK_EXPORT
int Network_Port_SC_Direct_Connect_Connection_Status_Encode(
uint32_t object_instance, uint8_t *apdu, int max_apdu);
BACNET_STACK_EXPORT
BACNET_SC_FAILED_CONNECTION_REQUEST *
Network_Port_SC_Failed_Connection_Requests_Get(
uint32_t object_instance, uint8_t index);
BACNET_STACK_EXPORT
bool Network_Port_SC_Failed_Connection_Requests_Add(
uint32_t object_instance,
BACNET_DATE_TIME *ts,
BACNET_HOST_N_PORT_DATA *peer_address,
uint8_t *peer_VMAC,
uint8_t *peer_UUID,
BACNET_ERROR_CODE error,
const char *error_details);
BACNET_STACK_EXPORT
bool Network_Port_SC_Failed_Connection_Requests_Delete_All(
uint32_t object_instance);
BACNET_STACK_EXPORT
int Network_Port_SC_Failed_Connection_Requests_Count(uint32_t object_instance);
BACNET_STACK_EXPORT
int Network_Port_SC_Failed_Connection_Requests_Encode(
uint32_t object_instance, uint8_t *apdu, int max_apdu);
BACNET_STACK_EXPORT
uint32_t Network_Port_Certificate_Key_File(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_Certificate_Key_File_Set(
uint32_t object_instance, uint32_t value);
BACNET_STACK_EXPORT
const BACNET_UUID *Network_Port_SC_Local_UUID(uint32_t object_instance);
BACNET_STACK_EXPORT
bool Network_Port_SC_Local_UUID_Set(
uint32_t object_instance, BACNET_UUID *value);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+32
View File
@@ -31,6 +31,7 @@
#define BACDL_MSTP
#define BACDL_BIP
#define BACDL_BIP6
#define BACDL_BSC
#endif
#if defined(BACDL_ETHERNET)
@@ -65,6 +66,13 @@
#define BACDL_SOME_DATALINK_ENABLED 1
#endif
#if defined(BACDL_BSC)
#if defined(BACDL_SOME_DATALINK_ENABLED)
#define BACDL_MULTIPLE 1
#endif
#define BACDL_SOME_DATALINK_ENABLED 1
#endif
#if defined(BACDL_CUSTOM)
#if defined(BACDL_SOME_DATALINK_ENABLED)
#define BACDL_MULTIPLE 1
@@ -144,6 +152,27 @@
#endif
#endif
#if defined(BACDL_BSC)
#ifndef SC_NETPORT_BVLC_MAX
#define SC_NETPORT_BVLC_MAX 1500
#endif
#ifndef SC_NETPORT_NPDU_MAX
#define SC_NETPORT_NPDU_MAX 1500
#endif
#ifndef SC_NETPORT_CONNECT_TIMEOUT
#define SC_NETPORT_CONNECT_TIMEOUT 5
#endif
#ifndef SC_NETPORT_HEARTBEAT_TIMEOUT
#define SC_NETPORT_HEARTBEAT_TIMEOUT 60
#endif
#ifndef SC_NETPORT_DISCONNECT_TIMEOUT
#define SC_NETPORT_DISCONNECT_TIMEOUT 150
#endif
#ifndef SC_NETPORT_RECONNECT_TIME
#define SC_NETPORT_RECONNECT_TIME 2
#endif
#endif
/* for confirmed messages, this is the number of transactions */
/* that we hold in a queue waiting for timeout. */
/* Configure to zero if you don't want any confirmed messages */
@@ -205,6 +234,7 @@
defined(BACAPP_SHED_LEVEL) || \
defined(BACAPP_ACCESS_RULE) || \
defined(BACAPP_CHANNEL_VALUE) || \
defined(BACAPP_SECURE_CONNECT) || \
defined(BACAPP_TYPES_EXTRA))
#define BACAPP_ALL
#endif
@@ -253,6 +283,7 @@
#define BACAPP_SHED_LEVEL
#define BACAPP_ACCESS_RULE
#define BACAPP_CHANNEL_VALUE
#define BACAPP_SECURE_CONNECT
#endif
/* clang-format off */
@@ -270,6 +301,7 @@
defined(BACAPP_DEVICE_OBJECT_REFERENCE) || \
defined(BACAPP_OBJECT_PROPERTY_REFERENCE) || \
defined(BACAPP_DESTINATION) || \
defined(BACAPP_SECURE_CONNECT) || \
defined(BACAPP_BDT_ENTRY) || \
defined(BACAPP_FDT_ENTRY) || \
defined(BACAPP_ACTION_COMMAND) || \
+54
View File
@@ -0,0 +1,54 @@
This readme explains how to enable support of BACNet Secure Connect datalink
(ANNEX AB BACnet Secure Connect in standard), how to set up the building
environment and clarifies some important moments regarding implementation
of that functionality.
By default the support of that functionality is turned off, to enable it
you need to set option BACDL_BSC=ON in CMakeLists.txt if cmake build system
is used or set BACDL_BSC=1 define in a case if using makefiles.
BACNet/SC standard uses websockets as a transport layer, so Windows/Linux/BSD
implementations use libwebsocket library. Implementation for Zephyr uses
native websocket API for the client side and mongoose library for the
server side. Websocket layer which is built on the top of libwebsockets
uses 1 service thread per websocket server instance and 1 service thread
per 1 websocket client instance. As a result, libwebsocket must be built
with LWS_MAX_SMP > 1, otherwise rarely crashes may ocure in the application
which uses bacnet stack library. Recommended value for that define is 32.
You should note, that by default libwebsocket is built with LWS_MAX_SMP=1,
so a packet manager like vcpkg and apt can have that lib built with
LWS_MAX_SMP=1, which can lead to unstable work. It's better to build
libwebsockets from recent stable sources for your platform, corresponded
examples of how to do that you can find in .github/workflows/bsc-tests-platform.yml,
(for example bsc-tests-linux.yml).
In order to build bacnet stack library for linux, user needs to install
libconfig-dev, libcap-dev and libssl-dev to the system. Most easiest way to do
that is to use Advanced Packaging Tool (APT), check bsc-test-linux.yml.
For MacOSX build user must install brew packet manager (https://brew.sh),
then install openssl using brew then build libwebsocket, check
.github/workflows/bsc-tests-macosx.yml.
Windows build may be a challenge because libwebsocket depends on libpthreads
which not so easy to find and build on windows, refer to the libwebsocket
build instruction https://libwebsockets.org/lws-api-doc-master/html/md_README_8build.html.
Using of vcpkg packet manager can save a lot of time, but you must ensure that
installed libwebsocket library is compiled with LWS_MAX_SMP > 1.
A good example how to use vcpkg packet manager with custom build of
libwebsocket can be found in .github/workflows/bsc-tests-windows.yml.
One moment is still needed to be improved. The current implementation processes
dataflow between BACNET/SC nodes ignoring max_bvlc_size and max_npdu_size
params in connect request and connect accept packets as that is not
explicitly explained in standard. As a result if a node which has max_npdu_size
less than remote peer can drop received packet if it does not fit into it's internal
buffer which depends on BVLC_SC_NPDU_SIZE_CONF parameter. In opposite, a node
can send a PDU more than max_npdu_size which may also lead to the drop of PDU
on remote peer side.
The current implementation does not support Certificate_Signing_Request_File
property of BACNET/SC netport and properties Operational_Certificate_File
and Issuer_Certificate_Files related to certificates are readonly and can't
be changed remotely. So, management of device certificates is out of the scope
of current BACNet/SC implementation.
+169
View File
@@ -0,0 +1,169 @@
/**
* @file
* @brief Configuration file of BACNet/SC datalink.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date August 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_CONF_H
#define BACNET_DATALINK_BSC_CONF_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bvlc-sc.h"
#if !defined(BACDL_BSC)
#define BSC_CONF_TX_PRE 0
#else
#ifndef bsd
#define bsd 1
#endif
#ifndef linux
#define linux 2
#endif
#ifndef win32
#define win32 3
#endif
#if BACNET_PORT == bsd || BACNET_PORT == linux || BACNET_PORT == win32
#include <libwebsockets.h>
#define BSC_CONF_TX_PRE LWS_PRE
#else
#define BSC_CONF_TX_PRE 0
#endif
#endif
#ifndef BSC_CONF_HUB_CONNECTORS_NUM
#define BSC_CONF_HUB_CONNECTORS_NUM 1
#endif
#ifndef BSC_CONF_HUB_FUNCTIONS_NUM
#define BSC_CONF_HUB_FUNCTIONS_NUM 1
#endif
#ifndef BSC_CONF_NODE_SWITCHES_NUM
#define BSC_CONF_NODE_SWITCHES_NUM 1
#endif
#ifndef BSC_CONF_NODES_NUM
#define BSC_CONF_NODES_NUM 1
#endif
#ifndef BVLC_SC_NPDU_SIZE_CONF
/* 16 bytes is sum of all sizes of all static (non variable)
fields of header of BVLC message */
#define BVLC_SC_NPDU_SIZE_CONF ((MAX_PDU) + 16)
#endif
#ifndef BSC_CONF_WEBSOCKET_RX_BUFFER_LEN
#define BSC_CONF_WEBSOCKET_RX_BUFFER_LEN BVLC_SC_NPDU_SIZE_CONF
#endif
/* THIS should not be changed, most of BACNet/SC devices must have */
/* hub connector, it uses 2 connections */
#ifndef BSC_CONF_HUB_CONNECTOR_CONNECTIONS_NUM
#define BSC_CONF_HUB_CONNECTOR_CONNECTIONS_NUM (BSC_CONF_HUB_CONNECTORS_NUM * 2)
#endif
#ifndef BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM
#define BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM (BSC_CONF_HUB_FUNCTIONS_NUM * 10)
#endif
#ifndef BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM
#define BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM 10
#endif
/* Total amount of client(initiator) webosocket connections */
#ifndef BSC_CONF_CLIENT_CONNECTIONS_NUM
#define BSC_CONF_CLIENT_CONNECTIONS_NUM \
(BSC_CONF_HUB_CONNECTOR_CONNECTIONS_NUM + \
BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM * BSC_CONF_NODE_SWITCHES_NUM)
#endif
#ifndef BSC_CONF_SERVER_HUB_CONNECTIONS_MAX_NUM
#define BSC_CONF_SERVER_HUB_CONNECTIONS_MAX_NUM \
(BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM)
#endif
#ifndef BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM
#define BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM \
(BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM * BSC_CONF_NODE_SWITCHES_NUM)
#endif
#define BSC_CONF_SOCKET_TX_BUFFERED_PACKET_NUM 2
#define BSC_CONF_DATALINK_BUFFERED_PACKET_NUM 10
#define BSC_CONF_SOCK_RX_BUFFER_SIZE BVLC_SC_NPDU_SIZE_CONF
/* 2 bytes is a prefix containing BVLC message length.
BSC_CONF_TX_PRE - some reserved bytes before actual payload.
Some libs like libwebsocket requires some bytes to be reserved
before actual payload for sending, so BSC_CONF_TX_PRE is used for
that purpose (it allows to avoid copying of payload and
buffer reallocation)
*/
#define BSC_CONF_SOCK_TX_BUFFER_SIZE \
((BVLC_SC_NPDU_SIZE_CONF + 2 + BSC_CONF_TX_PRE) * \
BSC_CONF_SOCKET_TX_BUFFERED_PACKET_NUM)
/* datalink RX buffer size is always rounded to next power of two */
/* so if final buffer size is 1628 it will be rounded to 2048 */
#define BSC_CONF_DATALINK_RX_BUFFER_SIZE \
(BVLC_SC_NPDU_SIZE_CONF * BSC_CONF_DATALINK_BUFFERED_PACKET_NUM)
#ifndef BSC_CONF_WSURL_MAX_LEN
#define BSC_CONF_WSURL_MAX_LEN 128
#endif
#ifndef BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN
#define BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN 128
#endif
#ifndef BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK
#define BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK \
BSC_CONF_WSURL_MAX_LEN
#endif
#ifndef BSC_CONF_NODE_MAX_URIS_NUM_IN_ADDRESS_RESOLUTION_ACK
#define BSC_CONF_NODE_MAX_URIS_NUM_IN_ADDRESS_RESOLUTION_ACK \
(BSC_CONF_SOCK_RX_BUFFER_SIZE / \
BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK - \
1)
#endif
#ifndef BSC_CONF_OPERATIONAL_CERTIFICATE_FILE_INSTANCE
#define BSC_CONF_OPERATIONAL_CERTIFICATE_FILE_INSTANCE 5
#endif
#ifndef BSC_CONF_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE
#define BSC_CONF_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE 6
#endif
#ifndef BSC_CONF_ISSUER_CERTIFICATE_FILE_1_INSTANCE
#define BSC_CONF_ISSUER_CERTIFICATE_FILE_1_INSTANCE 7
#endif
#ifndef BSC_CONF_ISSUER_CERTIFICATE_FILE_2_INSTANCE
#define BSC_CONF_ISSUER_CERTIFICATE_FILE_2_INSTANCE 8
#endif
#ifndef BSC_CONF_ISSUER_CERTIFICATE_FILE_3_INSTANCE
#define BSC_CONF_ISSUER_CERTIFICATE_FILE_3_INSTANCE 9
#endif
#ifndef BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM
#define BSC_CONF_HUB_FUNCTION_CONNECTION_STATUS_MAX_NUM \
BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM
#endif
#ifndef BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM
#define BSC_CONF_NODE_SWITCH_CONNECTION_STATUS_MAX_NUM \
BSC_CONF_NODE_SWITCH_CONNECTIONS_NUM
#endif
#ifndef BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM
#define BSC_CONF_FAILED_CONNECTION_STATUS_MAX_NUM 4
#endif
#endif
+653
View File
@@ -0,0 +1,653 @@
/**
* @file
* @brief BACnet secure connect hub function API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date October 2022
* @copyright SPDX-License-Identifier: MIT
*/
#include "bacnet/basic/sys/debug.h"
#include <bacnet/basic/sys/fifo.h>
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-datalink.h"
#include "bacnet/datalink/bsc/bsc-socket.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/datalink/bsc/bsc-event.h"
#include "bacnet/datalink/bsc/bsc-hub-function.h"
#include "bacnet/datalink/bsc/bsc-node.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/bacenum.h"
#include "bacnet/basic/object/netport.h"
#include "bacnet/basic/object/sc_netport.h"
#include "bacnet/basic/object/bacfile.h"
#define PRINTF debug_perror
#define DEBUG_BSC_DATALINK 0
#if DEBUG_BSC_DATALINK == 1
#define DEBUG_PRINTF debug_printf
#else
#define DEBUG_PRINTF debug_printf_disabled
#endif
#define BSC_NEXT_POWER_OF_TWO1(v) \
((((unsigned int)v) - 1) | ((((unsigned int)v) - 1) >> 1))
#define BSC_NEXT_POWER_OF_TWO2(v) \
(BSC_NEXT_POWER_OF_TWO1(v) | BSC_NEXT_POWER_OF_TWO1(v) >> 2)
#define BSC_NEXT_POWER_OF_TWO3(v) \
(BSC_NEXT_POWER_OF_TWO2(v) | BSC_NEXT_POWER_OF_TWO2(v) >> 4)
#define BSC_NEXT_POWER_OF_TWO4(v) \
(BSC_NEXT_POWER_OF_TWO3(v) | BSC_NEXT_POWER_OF_TWO3(v) >> 8)
#define BSC_NEXT_POWER_OF_TWO(v) \
((BSC_NEXT_POWER_OF_TWO4(v) | BSC_NEXT_POWER_OF_TWO4(v) >> 16)) + 1
typedef enum {
BSC_DATALINK_STATE_IDLE = 0,
BSC_DATALINK_STATE_STARTING = 1,
BSC_DATALINK_STATE_STARTED = 2,
BSC_DATALINK_STATE_STOPPING = 3
} BSC_DATALINK_STATE;
static FIFO_BUFFER bsc_fifo = { 0 };
static uint8_t
bsc_fifo_buf[BSC_NEXT_POWER_OF_TWO(BSC_CONF_DATALINK_RX_BUFFER_SIZE)];
static BSC_NODE *bsc_node = NULL;
static BSC_NODE_CONF bsc_conf;
static BSC_DATALINK_STATE bsc_datalink_state = BSC_DATALINK_STATE_IDLE;
static BSC_EVENT *bsc_event = NULL;
static BSC_EVENT *bsc_data_event = NULL;
/**
* @brief bsc_node_event() is a callback function which is called by
* BACnet/SC datalink when some event occurs.
* The function is used to notify the upper layer about the events.
* @param node - pointer to the BACnet/SC node
* @param ev - event type
* @param dest - pointer to the destination address
* @param pdu - pointer to the received PDU
* @param pdu_len - length of the received PDU
*/
static void bsc_node_event(
BSC_NODE *node,
BSC_NODE_EVENT ev,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *pdu,
size_t pdu_len)
{
uint16_t pdu16_len;
DEBUG_PRINTF("bsc_node_event() >>> ev = %d\n", ev);
bws_dispatch_lock();
(void)node;
(void)dest;
if (ev == BSC_NODE_EVENT_STARTED || ev == BSC_NODE_EVENT_STOPPED) {
if (bsc_datalink_state != BSC_DATALINK_STATE_IDLE) {
bsc_event_signal(bsc_event);
}
} else if (ev == BSC_NODE_EVENT_RECEIVED_NPDU) {
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
if (pdu_len <= USHRT_MAX &&
FIFO_Available(
&bsc_fifo, (unsigned)(pdu_len + sizeof(pdu16_len)))) {
pdu16_len = (uint16_t)pdu_len;
FIFO_Add(&bsc_fifo, (uint8_t *)&pdu16_len, sizeof(pdu16_len));
FIFO_Add(&bsc_fifo, pdu, pdu16_len);
bsc_event_signal(bsc_data_event);
}
#if DEBUG_ENABLED == 1
else {
PRINTF("pdu of size %d\n is dropped\n", pdu_len);
}
#endif
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_node_event() <<<\n");
}
/**
* @brief bsc_deinit_resources() is a function which is used to
* deinitialize all resources allocated by BACnet/SC datalink.
*/
static void bsc_deinit_resources(void)
{
if (bsc_event) {
bsc_event_deinit(bsc_event);
bsc_event = NULL;
}
if (bsc_data_event) {
bsc_event_deinit(bsc_data_event);
bsc_data_event = NULL;
}
}
/**
* @brief Initialize the BACnet/SC datalink layer
* @param ifname - name of the network interface
* @return true if the initialization was successful, otherwise false
*/
bool bsc_init(char *ifname)
{
BSC_SC_RET r;
bool ret = false;
DEBUG_PRINTF("bsc_init() >>>\n");
(void)ifname;
bws_dispatch_lock();
if (bsc_datalink_state != BSC_DATALINK_STATE_IDLE) {
bws_dispatch_unlock();
PRINTF("bsc_init() <<< ret = %d\n", ret);
return ret;
}
bsc_event = bsc_event_init();
bsc_data_event = bsc_event_init();
if (!bsc_event || !bsc_data_event) {
bsc_deinit_resources();
bws_dispatch_unlock();
PRINTF("bsc_init() <<< ret = %d\n", false);
return false;
}
DEBUG_PRINTF(
"bsc_init() BACNET/SC datalink configured to use input fifo "
"of size %d\n",
sizeof(bsc_fifo_buf));
FIFO_Init(&bsc_fifo, bsc_fifo_buf, sizeof(bsc_fifo_buf));
ret = bsc_node_conf_fill_from_netport(&bsc_conf, &bsc_node_event);
if (!ret) {
bsc_deinit_resources();
bws_dispatch_unlock();
PRINTF("bsc_init() <<< configuration of BACNET/SC datalink "
"failed, ret = false\n");
return ret;
}
bsc_datalink_state = BSC_DATALINK_STATE_STARTING;
r = bsc_node_init(&bsc_conf, &bsc_node);
if (r == BSC_SC_SUCCESS) {
r = bsc_node_start(bsc_node);
if (r == BSC_SC_SUCCESS) {
bws_dispatch_unlock();
bsc_event_wait(bsc_event);
bws_dispatch_lock();
bsc_datalink_state = BSC_DATALINK_STATE_STARTED;
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_init() <<< ret = %d\n", true);
return true;
}
}
bsc_deinit_resources();
bsc_node_conf_cleanup(&bsc_conf);
bsc_datalink_state = BSC_DATALINK_STATE_IDLE;
bws_dispatch_unlock();
PRINTF("bsc_init() <<< ret = %d\n", false);
return false;
}
/**
* @brief Blocking thread-safe bsc_cleanup() function
* de-initializes BACNet/SC datalink.
*/
void bsc_cleanup(void)
{
DEBUG_PRINTF("bsc_cleanup() >>>\n");
bws_dispatch_lock();
if (bsc_datalink_state != BSC_DATALINK_STATE_IDLE &&
bsc_datalink_state != BSC_DATALINK_STATE_STOPPING) {
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTING) {
bws_dispatch_unlock();
bsc_event_wait(bsc_event);
bws_dispatch_lock();
}
if (bsc_datalink_state != BSC_DATALINK_STATE_STOPPING) {
bsc_datalink_state = BSC_DATALINK_STATE_STOPPING;
bsc_event_signal(bsc_data_event);
bsc_node_stop(bsc_node);
bws_dispatch_unlock();
bsc_event_wait(bsc_event);
bsc_event_wait(bsc_data_event);
bws_dispatch_lock();
bsc_deinit_resources();
(void)bsc_node_deinit(bsc_node);
bsc_node_conf_cleanup(&bsc_conf);
bsc_node = NULL;
bsc_datalink_state = BSC_DATALINK_STATE_IDLE;
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_cleanup() <<<\n");
}
/**
* @brief Send a BACnet/SC PDU to a remote node
* @param dest - destination address
* @param npdu_data - network layer data
* @param pdu - PDU to send
* @param pdu_len - length of the PDU
* @return number of bytes sent on success, negative number on failure
*/
int bsc_send_pdu(
BACNET_ADDRESS *dest,
BACNET_NPDU_DATA *npdu_data,
uint8_t *pdu,
unsigned pdu_len)
{
BSC_SC_RET ret;
BACNET_SC_VMAC_ADDRESS dest_vmac;
int len = -1;
static uint8_t buf[BVLC_SC_NPDU_SIZE_CONF];
/* this datalink doesn't need to know the npdu data */
(void)npdu_data;
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
if (dest->net == BACNET_BROADCAST_NETWORK || dest->mac_len == 0) {
/* broadcast message */
memset(&dest_vmac.address[0], 0xFF, BVLC_SC_VMAC_SIZE);
} else if (dest->mac_len == BVLC_SC_VMAC_SIZE) {
/* unicast */
memcpy(&dest_vmac.address[0], &dest->mac[0], BVLC_SC_VMAC_SIZE);
} else {
bws_dispatch_unlock();
PRINTF("bsc_send_pdu() <<< ret = -1, incorrect dest mac address\n");
return len;
}
len = (int)bvlc_sc_encode_encapsulated_npdu(
buf, sizeof(buf), bsc_get_next_message_id(), NULL, &dest_vmac, pdu,
pdu_len);
ret = bsc_node_send(bsc_node, buf, len);
len = pdu_len;
if (ret != BSC_SC_SUCCESS) {
len = -1;
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_send_pdu() <<< ret = %d\n", len);
return len;
}
/**
* @brief Remove a BACnet/SC packet from the FIFO buffer
* @param packet_size - size of the packet to remove
*/
static void bsc_remove_packet(size_t packet_size)
{
size_t i;
for (i = 0; i < packet_size; i++) {
(void)FIFO_Get(&bsc_fifo);
}
}
/**
* @brief Blocking thread-safe bsc_receive() function
* receives NPDUs transferred over BACNet/SC
* from a node specified by it's virtual MAC address as
* defined in Clause AB.1.5.2.
* @param src - source VMAC address
* @param pdu - a buffer to hold the PDU portion of the received packet,
* after the BVLC portion has been stripped off.
* @param max_pdu - size of the pdu[] buffer
* @param timeout_ms - the number of milliseconds to wait for a packet
* @return the number of octets (remaining) in the PDU, or zero if no packet
*/
uint16_t bsc_receive(
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout_ms)
{
uint16_t pdu_len = 0;
uint16_t npdu16_len = 0;
BVLC_SC_DECODED_MESSAGE dm;
BACNET_ERROR_CODE error;
BACNET_ERROR_CLASS class;
const char *err_desc = NULL;
static uint8_t buf[BVLC_SC_NPDU_SIZE_CONF];
DEBUG_PRINTF("bsc_receive() >>>\n");
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
if (FIFO_Count(&bsc_fifo) <= sizeof(npdu16_len)) {
bws_dispatch_unlock();
bsc_event_timedwait(bsc_data_event, timeout_ms);
bws_dispatch_lock();
}
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED &&
FIFO_Count(&bsc_fifo) > sizeof(npdu16_len)) {
DEBUG_PRINTF("bsc_receive() processing data...\n");
FIFO_Pull(&bsc_fifo, (uint8_t *)&npdu16_len, sizeof(npdu16_len));
if (sizeof(buf) < npdu16_len) {
PRINTF("bsc_receive() pdu of size %d is dropped\n", npdu16_len);
bsc_remove_packet(npdu16_len);
} else {
FIFO_Pull(&bsc_fifo, buf, npdu16_len);
if (!bvlc_sc_decode_message(
buf, npdu16_len, &dm, &error, &class, &err_desc)) {
PRINTF(
"bsc_receive() pdu of size %d is dropped because "
"of err = %d, class %d, desc = %s\n",
npdu16_len, error, class, err_desc);
bsc_remove_packet(npdu16_len);
} else {
if (dm.hdr.origin &&
max_pdu >= dm.payload.encapsulated_npdu.npdu_len) {
src->mac_len = BVLC_SC_VMAC_SIZE;
memcpy(
&src->mac[0], &dm.hdr.origin->address[0],
BVLC_SC_VMAC_SIZE);
memcpy(
pdu, dm.payload.encapsulated_npdu.npdu,
dm.payload.encapsulated_npdu.npdu_len);
pdu_len =
(uint16_t)dm.payload.encapsulated_npdu.npdu_len;
}
#if DEBUG_ENABLED == 1
else {
PRINTF(
"bsc_receive() pdu of size %d is dropped "
"because origin addr is absent or output "
"buf of size %d is to small\n",
npdu16_len, max_pdu);
}
#endif
}
}
DEBUG_PRINTF("bsc_receive() pdu_len = %d\n", pdu_len);
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_receive() <<< ret = %d\n", pdu_len);
return pdu_len;
}
/**
* @brief Function can be used to retrieve broadcast
* VMAC address for BACNet/SC node.
* @param dest - value of broadcast VMAC address
*/
void bsc_get_broadcast_address(BACNET_ADDRESS *dest)
{
if (dest) {
dest->net = BACNET_BROADCAST_NETWORK;
dest->mac_len = BVLC_SC_VMAC_SIZE;
memset(&dest->mac[0], 0xFF, BVLC_SC_VMAC_SIZE);
/* no SADR */
dest->len = 0;
memset(dest->adr, 0, sizeof(dest->adr));
}
}
/**
* @brief Function can be used to retrieve local
* VMAC address of initialized BACNet/SC datalink.
* @param my_address - value of local VMAC address
*/
void bsc_get_my_address(BACNET_ADDRESS *my_address)
{
if (my_address) {
memset(my_address, 0, sizeof(*my_address));
}
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
my_address->mac_len = BVLC_SC_VMAC_SIZE;
memcpy(
&my_address->mac[0], &bsc_conf.local_vmac.address[0],
BVLC_SC_VMAC_SIZE);
}
bws_dispatch_unlock();
}
/**
* @brief Determine if the BACnet/SC direct connection is established
* with a remote BACnet/SC node.
* @param dest - BACnet/SC VMAC address of the remote node to check direct
* connection status
* @param urls - array representing the possible URIs of a remote node for
* acceptance of direct connections. Can contain 1 element
* @param urls_cnt - number of elements in the urls array
* @return true if the connection is established, otherwise false
*/
bool bsc_direct_connection_established(
BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt)
{
bool ret = false;
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
ret = bsc_node_direct_connection_established(
bsc_node, dest, urls, urls_cnt);
}
bws_dispatch_unlock();
return ret;
}
/**
* @brief Start the process of establishing a direct BACnet/SC connection
* to a node identified by either urls or dest parameter. The user should
* note that if the dest parameter is used, the local node tries to resolve
* it (e.g. to get URIs related to dest VMAC from all existing BACnet/SC
* nodes in the network). As a result, the process of establishing a BACnet/SC
* connection by dest may take an unpredictable amount of time depending on
* the current network configuration.
* @param dest - BACnet/SC VMAC address of the remote node to check direct
* connection status
* @param urls - array representing the possible URIs of a remote node for
* acceptance of direct connections. Can contain 1 element
* @param urls_cnt - number of elements in the urls array
* @return BSC_SC_SUCCESS if the process of establishing a BACnet/SC
* connection was started successfully, otherwise an error code
*/
BSC_SC_RET
bsc_connect_direct(BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt)
{
BSC_SC_RET ret = BSC_SC_INVALID_OPERATION;
DEBUG_PRINTF(
"bsc_connect_direct() >>> dest = %p, urls = %p, urls_cnt = %d\n", dest,
urls, urls_cnt);
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
ret = bsc_node_connect_direct(bsc_node, dest, urls, urls_cnt);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_connect_direct() ret = %d\n", ret);
return ret;
}
/**
* @brief Disconnect a direct BACnet/SC connection with a remote node
* identified by its VMAC address.
* @param dest - BACnet/SC VMAC address of the remote node to disconnect
* the direct connection with
*/
void bsc_disconnect_direct(BACNET_SC_VMAC_ADDRESS *dest)
{
bws_dispatch_lock();
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
bsc_node_disconnect_direct(bsc_node, dest);
}
bws_dispatch_unlock();
}
/**
* @brief Process the hub connector state
*/
static void bsc_update_hub_connector_state(void)
{
BACNET_SC_HUB_CONNECTOR_STATE state;
uint32_t instance;
instance = Network_Port_Index_To_Instance(0);
state = bsc_node_hub_connector_state(bsc_node);
Network_Port_SC_Hub_Connector_State_Set(instance, state);
}
/**
* @brief Process the hub connector status
*/
static void bsc_update_hub_connector_status(void)
{
BACNET_SC_HUB_CONNECTION_STATUS *status;
uint32_t instance;
instance = Network_Port_Index_To_Instance(0);
status = bsc_node_hub_connector_status(bsc_node, true);
if (status) {
Network_Port_SC_Primary_Hub_Connection_Status_Set(
instance, status->State, &status->Connect_Timestamp,
&status->Disconnect_Timestamp, status->Error,
status->Error_Details[0] ? status->Error_Details : NULL);
}
status = bsc_node_hub_connector_status(bsc_node, false);
if (status) {
Network_Port_SC_Failover_Hub_Connection_Status_Set(
instance, status->State, &status->Connect_Timestamp,
&status->Disconnect_Timestamp, status->Error,
status->Error_Details[0] ? status->Error_Details : NULL);
}
}
/**
* @brief Process the hub function status
*/
static void bsc_update_hub_function_status(void)
{
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *s;
size_t cnt;
int i;
uint32_t instance = Network_Port_Index_To_Instance(0);
BACNET_SC_VMAC_ADDRESS uninitialized = { 0 };
s = bsc_node_hub_function_status(bsc_node, &cnt);
if (s) {
Network_Port_SC_Hub_Function_Connection_Status_Delete_All(instance);
for (i = 0; i < cnt; i++) {
if (memcmp(
&uninitialized.address[0], &s[i].Peer_VMAC[0],
BVLC_SC_VMAC_SIZE) != 0) {
Network_Port_SC_Hub_Function_Connection_Status_Add(
instance, s[i].State, &s[i].Connect_Timestamp,
&s[i].Disconnect_Timestamp, &s[i].Peer_Address,
s[i].Peer_VMAC, s[i].Peer_UUID.uuid.uuid128, s[i].Error,
s[i].Error_Details[0] == 0 ? NULL : s[i].Error_Details);
}
}
}
}
/**
* @brief Add direct connection status to the network port
* @param s - direct connection status
* @param cnt - number of direct connection statuses
*/
static void bsc_add_direct_status_to_netport(
BACNET_SC_DIRECT_CONNECTION_STATUS *s, size_t cnt)
{
int i;
uint32_t instance = Network_Port_Index_To_Instance(0);
BACNET_SC_VMAC_ADDRESS uninitialized = { 0 };
for (i = 0; i < cnt; i++) {
if (memcmp(
&uninitialized.address[0], &s[i].Peer_VMAC[0],
BVLC_SC_VMAC_SIZE) != 0) {
Network_Port_SC_Direct_Connect_Connection_Status_Add(
instance, s[i].URI[0] == 0 ? NULL : s[i].URI, s[i].State,
&s[i].Connect_Timestamp, &s[i].Disconnect_Timestamp,
&s[i].Peer_Address, s[i].Peer_VMAC, s[i].Peer_UUID.uuid.uuid128,
s[i].Error,
s[i].Error_Details[0] == 0 ? NULL : s[i].Error_Details);
}
}
}
/**
* @brief Process the direct connection status
*/
static void bsc_update_direct_connection_status(void)
{
BACNET_SC_DIRECT_CONNECTION_STATUS *s = NULL;
size_t cnt = 0;
uint32_t instance = Network_Port_Index_To_Instance(0);
s = bsc_node_direct_connection_status(bsc_node, &cnt);
if (s) {
Network_Port_SC_Direct_Connect_Connection_Status_Delete_All(instance);
if (s && cnt) {
bsc_add_direct_status_to_netport(s, cnt);
}
}
}
/**
* @brief Process the failed requests
*/
static void bsc_update_failed_requests(void)
{
BACNET_SC_FAILED_CONNECTION_REQUEST *r;
size_t cnt;
int i;
uint32_t instance = Network_Port_Index_To_Instance(0);
r = bsc_node_failed_requests_status(bsc_node, &cnt);
if (r) {
Network_Port_SC_Failed_Connection_Requests_Delete_All(instance);
for (i = 0; i < cnt; i++) {
if (r[i].Peer_Address.host[0] != 0) {
#if DEBUG_ENABLED == 1
DEBUG_PRINTF(
"failed request record %d, host %s, vmac %s, uuid "
"%s, error %d, details = %s\n",
i, r[i].Peer_Address.host,
bsc_vmac_to_string(
(BACNET_SC_VMAC_ADDRESS *)r[i].Peer_VMAC),
bsc_uuid_to_string(
(BACNET_SC_UUID *)r[i].Peer_UUID.uuid.uuid128),
r[i].Error, r[i].Error_Details);
#endif
Network_Port_SC_Failed_Connection_Requests_Add(
instance, &r[i].Timestamp, &r[i].Peer_Address,
r[i].Peer_VMAC, r[i].Peer_UUID.uuid.uuid128, r[i].Error,
r[i].Error_Details[0] != 0 ? r[i].Error_Details : NULL);
}
}
}
}
/**
* @brief Update the network port properties
*/
static void bsc_update_netport_properties(void)
{
if (bsc_datalink_state == BSC_DATALINK_STATE_STARTED) {
bsc_update_hub_connector_state();
bsc_update_hub_connector_status();
bsc_update_hub_function_status();
bsc_update_direct_connection_status();
bsc_update_failed_requests();
}
}
/**
* @brief Manage the BACnet/SC datalink timer
* @param seconds - number of elapsed seconds
*/
void bsc_maintenance_timer(uint16_t seconds)
{
bws_dispatch_lock();
bsc_node_maintenance_timer(seconds);
bsc_update_netport_properties();
bws_dispatch_unlock();
}
+176
View File
@@ -0,0 +1,176 @@
/**
* @file
* @brief BACNet/SC datalink public interface API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date October 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#ifndef BACNET_DATALINK_BSC_DATALINK_H
#define BACNET_DATALINK_BSC_DATALINK_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/npdu.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-retcodes.h"
/**
* @brief Blocking thread-safe bsc_init() function
* initializes BACNet/SC datalink in accordence with value of properties
* from BACNet/SC Network Port Object (sc_netport.h). That means that
* user must initialize and set corresponded properties required for
* configuration of BACNet/SC datalink before calling of that function.
* According to "Addendum cc" to ANSI/ASHRAE Standard 135-2020
* https://bacnet.org/wp-content/uploads/sites/4/2022/08/Add-135-2020cc.pdf
* most important properties are:
* SC_Primary_Hub_URI
* SC_Failover_Hub_URI
* SC_Maximum_Reconnect_Time
* SC_Connect_Wait_Timeout
* SC_Disconnect_Wait_Timeout
* SC_Heartbeat_Timeout
* Operational_Certificate_File
* Issuer_Certificate_Files
* SC_Hub_Function_Enable
* SC_Hub_Function_Binding
* SC_Direct_Connect_Initiate_Enable
* SC_Direct_Connect_Binding
* SC_Direct_Connect_Accept_Enable
* @param ifname - ignored and unused it was added for backward compatibility.
* @return true if datalink was initiated and started, otherwise returns false.
*/
BACNET_STACK_EXPORT
bool bsc_init(char *ifname);
/**
* @brief Blocking thread-safe bsc_cleanup() function
* de-initializes BACNet/SC datalink.
*/
BACNET_STACK_EXPORT
void bsc_cleanup(void);
/**
* @brief Function checks if all needed certificate file are present.
* @return true if all needed certificate file are present otherwise returns
* false.
*/
BACNET_STACK_EXPORT
bool bsc_cert_files_check(void);
/**
* @brief Blocking thread-safe bsc_send_pdu() function
* sends pdu over BACNet/SC to node specified by
* destination address param.
*
* @param dest [in] BACNet/SC node's virtual MAC address as
* defined in Clause AB.1.5.2.
* Can be broadcast.
* @param npdu_data [in] BACNet/SC datalink does not use that
* parameter. Added for backward
* compatibility.
* @param pdu [in] protocol data unit to be sent.
* @param pdu_len [in] - number of bytes to send.
* @return Number of bytes sent on success, negative number on failure.
*/
BACNET_STACK_EXPORT
int bsc_send_pdu(
BACNET_ADDRESS *dest,
BACNET_NPDU_DATA *npdu_data,
uint8_t *pdu,
unsigned pdu_len);
/**
* @brief Blocking thread-safe bsc_receive() function
* receives NPDUs transferred over BACNet/SC
* from a node specified by it's virtual MAC address as
* defined in Clause AB.1.5.2.
*
* @param src [out] Source VMAC address
* @param pdu [out] A buffer to hold the PDU portion of the received packet,
* after the BVLC portion has been stripped off.
* @param max_pdu [in] Size of the pdu[] buffer.
* @param timeout [in] The number of milliseconds to wait for a packet.
* @return The number of octets (remaining) in the PDU, or zero on failure.
*/
BACNET_STACK_EXPORT
uint16_t bsc_receive(
BACNET_ADDRESS *src, uint8_t *pdu, uint16_t max_pdu, unsigned timeout_ms);
/**
* @brief Function can be used to retrieve broadcast
* VMAC address for BACNet/SC node.
*
* @param addr [out] Value of broadcast VMAC address.
*/
BACNET_STACK_EXPORT
void bsc_get_broadcast_address(BACNET_ADDRESS *addr);
/**
* @brief Function can be used to retrieve local
* VMAC address of initialized BACNet/SC datalink.
* If function called when datalink is not started,
* my_address filled by empty vmac address
* X'000000000000' as it defined in clause AB.1.5.2
*
* @param my_address [out] Value of local VMAC address.
*/
BACNET_STACK_EXPORT
void bsc_get_my_address(BACNET_ADDRESS *my_address);
/**
* @brief Function checks if BACNet/SC direct connection is
* established with remote BACNet/SC node.
* User can check the status of connection using either
* destination vmac or list of destination urls.
* @param dest BACNet/SC vmac of remote node to check direct
* connection status.
* @param urls this array represents the possible URIs of a
* remote node for acceptance of direct connections.
* Can contain 1 elem.
* @param urls_cnt - size of urls array.
* @return true if connection is established otherwise returns
* false.
*/
BACNET_STACK_EXPORT
bool bsc_direct_connection_established(
BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt);
/**
* @brief Function starts process of establishing of a
* direct BACNet/SC connection to node identified by
* either urls or dest parameter. User should note that
* if dest parameter is used, local node tries to resolve
* it (e.g.to get URIs related to dest vmac from all existent
* BACNet/SC nodes in network). As a result the process of
* establishing of a BACNet/SC connection by dest may
* take unpredictable amount of time depending on a current
* network configuration.
* @param dest BACNet/SC vmac of remote node to check direct
* connection status.
* @param urls this array represents the possible URIs of a
* remote node for acceptance of direct connections.
* Can contain 1 elem.
* @param urls_cnt - size of urls array.
*
* @return BSC_SC_SUCCESS if process of a establishing of a BACNet/SC
* connection was started successfully, otherwise returns
* any retcode from BSC_SC_RET enum.
*/
BACNET_STACK_EXPORT
BSC_SC_RET
bsc_connect_direct(BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt);
BACNET_STACK_EXPORT
void bsc_disconnect_direct(BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
void bsc_maintenance_timer(uint16_t seconds);
#endif
+101
View File
@@ -0,0 +1,101 @@
/**
* @file
* @brief crossplatform event abstraction used in BACnet secure connect.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date August 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_EVENT_H
#define BACNET_DATALINK_BSC_EVENT_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
struct BSC_Event;
typedef struct BSC_Event BSC_EVENT;
/**
* @brief bsc_event_init() allocates and initializes auto-reset
* event object to non-signalled state. An event object
* can be set to signalled state by the call of
* bsc_event_signal(). When the state of the event object
* is signaled, it remains signaled until last thread is
* released which was blocked on bsc_event_wait() or
* bsc_event_timed_wait() calls. When writing the code user
* must always remember the following statements:
* 1. It is guaranteed that all currently waiting threads will be
* unblocked by the call of bsc_event_signal().
* 2. If user has called bsc_event_wait() or bsc_event_timedwait()
* from thread N after the call of bsc_event_signal() but
* when the state of the corresponded event object is still
* signalled, it is not guaranteed that thread N will be
* unblocked. Thread N may be stay blocked or may unblock
* depending on multitasking context of the OS.
*
* @return handle of event object if function succeeded, otherwise
* returns NULL.
*/
BSC_EVENT *bsc_event_init(void);
/**
* @brief bsc_event_deinit() deinitialize auto-reset
* event object. If a user calls that function while
* some threads are waiting the specified event object ev
* the behaviour is undefined.
*
* @param ev - handle to previously initialized event object.
*/
void bsc_event_deinit(BSC_EVENT *ev);
/**
* @brief bsc_wait() suspends the execution of the current thread
* for the specified amount of seconds.
*
* @param seconds - time to sleep in seconds.
*/
void bsc_wait(int seconds);
/**
* @brief bsc_wait() suspends the execution of the current thread
* for the specified amount of seconds.
*
* @param mseconds - time to sleep in milliseconds.
*/
void bsc_wait_ms(int mseconds);
/**
* @brief bsc_event_wait() suspends the execution of the current thread
* until corresponded event object becomes signalled.
*
* @param ev - handle to previously initialized event object.
*/
void bsc_event_wait(BSC_EVENT *ev);
/**
* @brief bsc_event_timedwait() suspends the execution of the current thread
* until corresponded event object becomes signalled
* or ms_timeout becomes elapsed.
*
* @param ev - handle to previously initialized event object.
* @param ms_timeout - timeout in milliseconds
* @return true if the corresponded event was signalled.
* false if ms_timeout was elapsed but event ev is still in
* non signalled state.
*/
bool bsc_event_timedwait(BSC_EVENT *ev, unsigned int ms_timeout);
/**
* @brief bsc_event_signal() function sets state of corresponded event object
* to signalled state.
*
* @param ev - handle to previously initialized event object.
*/
void bsc_event_signal(BSC_EVENT *ev);
#endif
+651
View File
@@ -0,0 +1,651 @@
/**
* @file
* @brief BACnet hub connector API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date July 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-socket.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/datalink/bsc/bsc-hub-connector.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/bacenum.h"
#include "bacnet/basic/object/sc_netport.h"
#include "bacnet/bactext.h"
#define DEBUG_BSC_HUB_CONNECTOR 0
#if DEBUG_BSC_HUB_CONNECTOR == 1
#define DEBUG_PRINTF debug_printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF debug_printf_disabled
#endif
typedef enum {
BSC_HUB_CONN_PRIMARY = 0,
BSC_HUB_CONN_FAILOVER = 1
} BSC_HUB_CONN_TYPE;
static void hub_connector_socket_event(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE reason,
const char *reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
static void hub_connector_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev);
typedef enum {
BSC_HUB_CONNECTOR_STATE_IDLE = 0,
BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY = 1,
BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER = 2,
BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY = 3,
BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER = 4,
BSC_HUB_CONNECTOR_STATE_WAIT_FOR_RECONNECT = 5,
BSC_HUB_CONNECTOR_STATE_WAIT_FOR_CTX_DEINIT = 6,
BSC_HUB_CONNECTOR_STATE_DUPLICATED_VMAC = 7
} BSC_HUB_CONNECTOR_STATE;
typedef struct BSC_Hub_Connector {
bool used;
BSC_SOCKET_CTX ctx;
BSC_CONTEXT_CFG cfg;
BSC_SOCKET sock[2];
BSC_HUB_CONNECTOR_STATE state;
unsigned int reconnect_timeout_s;
uint8_t primary_url[BSC_WSURL_MAX_LEN + 1];
uint8_t failover_url[BSC_WSURL_MAX_LEN + 1];
struct mstimer t;
BSC_HUB_CONNECTOR_EVENT_FUNC event_func;
void *user_arg;
BACNET_SC_HUB_CONNECTION_STATUS primary_status;
BACNET_SC_HUB_CONNECTION_STATUS failover_status;
} BSC_HUB_CONNECTOR;
#if BSC_CONF_HUB_CONNECTORS_NUM > 0
static BSC_HUB_CONNECTOR bsc_hub_connector[BSC_CONF_HUB_CONNECTORS_NUM] = { 0 };
#else
static BSC_HUB_CONNECTOR *bsc_hub_connector = NULL;
#endif
static BSC_SOCKET_CTX_FUNCS bsc_hub_connector_ctx_funcs = {
NULL, NULL, hub_connector_socket_event, hub_connector_context_event, NULL
};
/**
* @brief Initialize the BACnet hub connector status
* @param s - pointer to the status structure
*/
static void hub_connector_reset_status(BACNET_SC_HUB_CONNECTION_STATUS *s)
{
/* set timestamps to unspecified values */
memset(&s->Connect_Timestamp, 0xFF, sizeof(s->Connect_Timestamp));
memset(&s->Disconnect_Timestamp, 0xFF, sizeof(s->Disconnect_Timestamp));
s->Error = ERROR_CODE_DEFAULT;
s->Error_Details[0] = 0;
}
/**
* @brief Allocate a hub connector
* @return pointer to the allocated hub connector
*/
static BSC_HUB_CONNECTOR *hub_connector_alloc(void)
{
int i;
for (i = 0; i < BSC_CONF_HUB_CONNECTORS_NUM; i++) {
if (!bsc_hub_connector[i].used) {
memset(&bsc_hub_connector[i], 0, sizeof(bsc_hub_connector[i]));
bsc_hub_connector[i].used = true;
hub_connector_reset_status(&bsc_hub_connector[i].primary_status);
hub_connector_reset_status(&bsc_hub_connector[i].failover_status);
DEBUG_PRINTF(
"hub_connector_alloc() ret = %p\n", &bsc_hub_connector[i]);
return &bsc_hub_connector[i];
}
}
DEBUG_PRINTF("hub_connector_alloc() ret = %p\n", &bsc_hub_connector[i]);
return NULL;
}
/**
* @brief Free a hub connector
* @param c - pointer to the hub connector
*/
static void hub_connector_free(BSC_HUB_CONNECTOR *c)
{
DEBUG_PRINTF("hub_connector_free() c = %p\n", c);
c->used = false;
}
/**
* @brief Update the BACnet hub connector status
* @param s - pointer to the status structure
* @param state - new state
* @param err - error code
* @param err_desc - error description
*/
static void hub_conector_update_status(
BACNET_SC_HUB_CONNECTION_STATUS *s,
BACNET_SC_CONNECTION_STATE state,
BACNET_ERROR_CODE err,
const char *err_desc)
{
s->State = state;
if (state == BACNET_SC_CONNECTION_STATE_NOT_CONNECTED ||
state == BACNET_SC_CONNECTION_STATE_DISCONNECTED_WITH_ERRORS) {
bsc_set_timestamp(&s->Disconnect_Timestamp);
} else if (
state == BACNET_SC_CONNECTION_STATE_CONNECTED ||
state == BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT) {
bsc_set_timestamp(&s->Connect_Timestamp);
}
s->Error = err;
s->Error_Details[0] = 0;
if (err_desc) {
bsc_copy_str(&s->Error_Details[0], err_desc, sizeof(s->Error_Details));
}
}
/**
* @brief Connect to a BACnet hub
* @param p - pointer to the hub connector
* @param type - connection type
*/
static void hub_connector_connect(BSC_HUB_CONNECTOR *p, BSC_HUB_CONN_TYPE type)
{
BSC_SC_RET ret;
char *url = (type == BSC_HUB_CONN_PRIMARY) ? (char *)p->primary_url
: (char *)p->failover_url;
p->state = (type == BSC_HUB_CONN_PRIMARY)
? BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY
: BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER;
DEBUG_PRINTF(
"hub_connector_connect() hub = %p connecting to url %s\n", p, url);
if (url[0] == 0) {
mstimer_set(&p->t, p->reconnect_timeout_s * 1000);
p->state = BSC_HUB_CONNECTOR_STATE_WAIT_FOR_RECONNECT;
return;
}
ret = bsc_connect(&p->ctx, &p->sock[type], url);
(void)ret;
#if DEBUG_ENABLED == 1
if (ret != BSC_SC_SUCCESS) {
DEBUG_PRINTF(
"hub_connector_connect() got error while "
"connecting to hub type %d, err = %d\n",
type, ret);
}
#endif
}
/**
* @brief Process the hub connector state
* @param c - pointer to the hub connector
*/
static void hub_connector_process_state(BSC_HUB_CONNECTOR *c)
{
if (c->state == BSC_HUB_CONNECTOR_STATE_WAIT_FOR_RECONNECT) {
if (mstimer_expired(&c->t)) {
hub_connector_connect(c, BSC_HUB_CONN_PRIMARY);
}
}
}
/**
* @brief Hub connector maintenance timer
* @param seconds - number of elapsed seconds
*/
void bsc_hub_connector_maintenance_timer(uint16_t seconds)
{
int i;
(void)seconds;
bws_dispatch_lock();
for (i = 0; i < BSC_CONF_HUB_CONNECTORS_NUM; i++) {
if (bsc_hub_connector[i].used) {
hub_connector_process_state(&bsc_hub_connector[i]);
}
}
bws_dispatch_unlock();
}
/**
* @brief Hub connector socket event
* @param c - pointer to the socket
* @param ev - event
* @param disconnect_reason - disconnect reason
* @param disconnect_reason_desc - disconnect reason description
* @param pdu - pointer to the PDU
* @param pdu_len - PDU length
* @param decoded_pdu - decoded PDU
*/
static void hub_connector_socket_event(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE disconnect_reason,
const char *disconnect_reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu)
{
BSC_HUB_CONNECTOR *hc;
BACNET_SC_CONNECTION_STATE st =
BACNET_SC_CONNECTION_STATE_DISCONNECTED_WITH_ERRORS;
bws_dispatch_lock();
hc = (BSC_HUB_CONNECTOR *)c->ctx->user_arg;
DEBUG_PRINTF(
"hub_connector_socket_event() >>> hub_connector = %p, socket "
"= %p, ev = %d, reason = %d, reason_desc = %p,"
"pdu = %p, pdu_len = %d\n",
hc, c, ev, disconnect_reason, disconnect_reason_desc, pdu, pdu_len);
DEBUG_PRINTF("hub_connector_socket_event() state = %d\n", hc->state);
if (ev == BSC_SOCKET_EVENT_CONNECTED) {
if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY) {
DEBUG_PRINTF(
"hub_connector_socket_event() hub_connector = %p "
"connected primary\n",
hc);
hc->state = BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY;
hub_conector_update_status(
&hc->primary_status, BACNET_SC_CONNECTION_STATE_CONNECTED,
ERROR_CODE_DEFAULT, NULL);
hc->event_func(
BSC_HUBC_EVENT_CONNECTED_PRIMARY, hc, hc->user_arg, NULL, 0,
NULL);
} else if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER) {
DEBUG_PRINTF(
"hub_connector_socket_event() hub_connector = %p "
"connected failover\n",
hc);
hc->state = BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER;
hub_conector_update_status(
&hc->failover_status, BACNET_SC_CONNECTION_STATE_CONNECTED,
ERROR_CODE_DEFAULT, NULL);
hc->event_func(
BSC_HUBC_EVENT_CONNECTED_FAILOVER, hc, hc->user_arg, NULL, 0,
NULL);
}
} else if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
if (disconnect_reason == ERROR_CODE_NODE_DUPLICATE_VMAC &&
hc->state != BSC_HUB_CONNECTOR_STATE_WAIT_FOR_CTX_DEINIT) {
DEBUG_PRINTF("hub_connector_socket_event() "
"got ERROR_CODE_NODE_DUPLICATE_VMAC error\n");
if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY) {
hub_conector_update_status(
&hc->primary_status,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT,
disconnect_reason, disconnect_reason_desc);
} else if (
hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER) {
hub_conector_update_status(
&hc->failover_status,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT,
disconnect_reason, disconnect_reason_desc);
}
hc->state = BSC_HUB_CONNECTOR_STATE_DUPLICATED_VMAC;
hc->event_func(
BSC_HUBC_EVENT_ERROR_DUPLICATED_VMAC, hc, hc->user_arg, NULL, 0,
NULL);
} else if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY) {
DEBUG_PRINTF("hub_connector_socket_event() try to connect to "
"failover hub\n");
hub_conector_update_status(
&hc->primary_status,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT, disconnect_reason,
disconnect_reason_desc);
hub_connector_connect(hc, BSC_HUB_CONN_FAILOVER);
} else if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER) {
DEBUG_PRINTF(
"hub_connector_socket_event() wait for %d seconds\n",
hc->reconnect_timeout_s);
hub_conector_update_status(
&hc->failover_status,
BACNET_SC_CONNECTION_STATE_FAILED_TO_CONNECT, disconnect_reason,
disconnect_reason_desc);
hc->state = BSC_HUB_CONNECTOR_STATE_WAIT_FOR_RECONNECT;
mstimer_set(&hc->t, hc->reconnect_timeout_s * 1000);
} else if (
hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY ||
hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER) {
if (disconnect_reason == ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER ||
disconnect_reason == ERROR_CODE_SUCCESS) {
st = BACNET_SC_CONNECTION_STATE_NOT_CONNECTED;
}
if (hc->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY) {
hub_conector_update_status(
&hc->primary_status, st, ERROR_CODE_DEFAULT, NULL);
} else {
hub_conector_update_status(
&hc->failover_status, st, ERROR_CODE_DEFAULT, NULL);
}
DEBUG_PRINTF(
"hub_connector_socket_event() try to connect to primary hub\n");
hub_connector_connect(hc, BSC_HUB_CONN_PRIMARY);
}
} else if (ev == BSC_SOCKET_EVENT_RECEIVED) {
DEBUG_PRINTF(
"hub_connector_socket_event() hub_connector = %p pdu of "
"%d len is received\n",
hc, pdu_len);
hc->event_func(
BSC_HUBC_EVENT_RECEIVED, hc, hc->user_arg, pdu, pdu_len,
decoded_pdu);
}
bws_dispatch_unlock();
DEBUG_PRINTF("hub_connector_socket_event() <<<\n");
}
/**
* @brief Hub connector context event
* @param ctx - pointer to the context
* @param ev - event
*/
static void hub_connector_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev)
{
BSC_HUB_CONNECTOR *c;
DEBUG_PRINTF(
"hub_connector_context_event() >>> ctx = %p, ev = %d\n", ctx, ev);
if (ev == BSC_CTX_DEINITIALIZED) {
bws_dispatch_lock();
c = (BSC_HUB_CONNECTOR *)ctx->user_arg;
if (c->state != BSC_HUB_CONNECTOR_STATE_IDLE) {
c->state = BSC_HUB_CONNECTOR_STATE_IDLE;
hub_connector_free(c);
c->event_func(
BSC_HUBC_EVENT_STOPPED, c, c->user_arg, NULL, 0, NULL);
}
bws_dispatch_unlock();
}
DEBUG_PRINTF("hub_connector_context_event() <<<\n");
}
/**
* @brief Start a BACnet hub connector
* @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_npdu_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
* @param primaryURL - primary hub URL
* @param failoverURL - failover hub URL
* @param reconnect_timeout_s - reconnect timeout in seconds
* @param event_func - event function
* @param user_arg - user argument
* @param h - pointer to the hub connector handle
* @return status
*/
BSC_SC_RET bsc_hub_connector_start(
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_npdu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s,
char *primaryURL,
char *failoverURL,
unsigned int reconnect_timeout_s,
BSC_HUB_CONNECTOR_EVENT_FUNC event_func,
void *user_arg,
BSC_HUB_CONNECTOR_HANDLE *h)
{
BSC_SC_RET ret = BSC_SC_SUCCESS;
BSC_HUB_CONNECTOR *c;
DEBUG_PRINTF("bsc_hub_connector_start() >>>\n");
if (!ca_cert_chain || !ca_cert_chain_size || !cert_chain ||
!cert_chain_size || !key || !key_size || !local_uuid || !local_vmac ||
!max_local_npdu_len || !max_local_bvlc_len || !connect_timeout_s ||
!heartbeat_timeout_s || !disconnect_timeout_s || !primaryURL ||
!reconnect_timeout_s || !event_func || !h) {
DEBUG_PRINTF("bsc_hub_connector_start() <<< ret = BSC_SC_BAD_PARAM\n");
return BSC_SC_BAD_PARAM;
}
if (strlen(primaryURL) > BSC_WSURL_MAX_LEN ||
(failoverURL && (strlen(failoverURL) > BSC_WSURL_MAX_LEN))) {
DEBUG_PRINTF("bsc_hub_connector_start() <<< ret = BSC_SC_BAD_PARAM\n");
return BSC_SC_BAD_PARAM;
}
bws_dispatch_lock();
c = hub_connector_alloc();
if (!c) {
bws_dispatch_unlock();
DEBUG_PRINTF(
"bsc_hub_connector_start() <<< ret = BSC_SC_NO_RESOURCES\n");
return BSC_SC_NO_RESOURCES;
}
c->reconnect_timeout_s = reconnect_timeout_s;
c->primary_url[0] = 0;
c->failover_url[0] = 0;
c->user_arg = user_arg;
strcpy((char *)c->primary_url, primaryURL);
if (failoverURL) {
strcpy((char *)c->failover_url, failoverURL);
}
c->event_func = event_func;
bsc_init_ctx_cfg(
BSC_SOCKET_CTX_INITIATOR, &c->cfg, BSC_WEBSOCKET_HUB_PROTOCOL, 0, NULL,
ca_cert_chain, ca_cert_chain_size, cert_chain, cert_chain_size, key,
key_size, local_uuid, local_vmac, max_local_bvlc_len,
max_local_npdu_len, connect_timeout_s, heartbeat_timeout_s,
disconnect_timeout_s);
DEBUG_PRINTF(
"bsc_hub_connector_start() uuid = %s, vmac = %s\n",
bsc_uuid_to_string(&c->cfg.local_uuid),
bsc_vmac_to_string(&c->cfg.local_vmac));
ret = bsc_init_ctx(
&c->ctx, &c->cfg, &bsc_hub_connector_ctx_funcs, c->sock,
sizeof(c->sock) / sizeof(BSC_SOCKET), (void *)c);
if (ret == BSC_SC_SUCCESS) {
*h = (BSC_HUB_CONNECTOR_HANDLE)c;
DEBUG_PRINTF(
"bsc_hub_connector_start() hub = %p connecting to url %s\n", c,
c->primary_url);
ret = bsc_connect(
&c->ctx, &c->sock[BSC_HUB_CONN_PRIMARY], (char *)c->primary_url);
if (ret == BSC_SC_SUCCESS) {
c->state = BSC_HUB_CONNECTOR_STATE_CONNECTING_PRIMARY;
} else {
if (c->failover_url[0] == 0) {
c->state = BSC_HUB_CONNECTOR_STATE_IDLE;
bsc_deinit_ctx(&c->ctx);
hub_connector_free(c);
*h = NULL;
} else {
c->state = BSC_HUB_CONNECTOR_STATE_CONNECTING_FAILOVER;
DEBUG_PRINTF(
"bsc_hub_connector_start() hub = %p connecting to url %s\n",
c, c->primary_url);
ret = bsc_connect(
&c->ctx, &c->sock[BSC_HUB_CONN_FAILOVER],
(char *)c->failover_url);
if (ret != BSC_SC_SUCCESS) {
c->state = BSC_HUB_CONNECTOR_STATE_IDLE;
bsc_deinit_ctx(&c->ctx);
hub_connector_free(c);
*h = NULL;
}
}
}
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_connector_start() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Stop a BACnet hub connector
* @param h - pointer to the hub connector
*/
void bsc_hub_connector_stop(BSC_HUB_CONNECTOR_HANDLE h)
{
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
DEBUG_PRINTF("bsc_hub_connector_stop() >>> h = %p\n", h);
bws_dispatch_lock();
#if DEBUG_ENABLED == 1
if (c) {
DEBUG_PRINTF("bsc_hub_connector_stop() state = %d\n", c->state);
}
#endif
if (c && c->state != BSC_HUB_CONNECTOR_STATE_WAIT_FOR_CTX_DEINIT &&
c->state != BSC_HUB_CONNECTOR_STATE_IDLE) {
c->state = BSC_HUB_CONNECTOR_STATE_WAIT_FOR_CTX_DEINIT;
bsc_deinit_ctx(&c->ctx);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_connector_stop() <<<\n");
}
/**
* @brief Send a BACnet hub connector PDU
* @param h - pointer to the hub connector
* @param pdu - pointer to the PDU
* @param pdu_len - PDU length
* @return status
*/
BSC_SC_RET
bsc_hub_connector_send(BSC_HUB_CONNECTOR_HANDLE h, uint8_t *pdu, size_t pdu_len)
{
BSC_SC_RET ret;
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
DEBUG_PRINTF(
"bsc_hub_connector_send() >>> h = %p, pdu = %p, pdu_len = %d\n", h,
pdu, pdu_len);
bws_dispatch_lock();
if (!c) {
DEBUG_PRINTF("bsc_hub_connector_send() <<< ret = BSC_SC_BAD_PARAM\n");
bws_dispatch_unlock();
return BSC_SC_BAD_PARAM;
}
if (c->state == BSC_HUB_CONNECTOR_STATE_IDLE ||
(c->state != BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY &&
c->state != BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER)) {
DEBUG_PRINTF(
"bsc_hub_connector_send() pdu is dropped, state of "
"hub_connector %p is %d\n",
c, c->state);
DEBUG_PRINTF(
"bsc_hub_connector_send() <<< ret = BSC_SC_INVALID_OPERATION\n");
bws_dispatch_unlock();
return BSC_SC_INVALID_OPERATION;
}
if (c->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY) {
ret = bsc_send(&c->sock[BSC_HUB_CONN_PRIMARY], pdu, pdu_len);
} else {
ret = bsc_send(&c->sock[BSC_HUB_CONN_FAILOVER], pdu, pdu_len);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_connector_send() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Check if the hub connector is stopped
* @param h - pointer to the hub connector
* @return status
*/
bool bsc_hub_connector_stopped(BSC_HUB_CONNECTOR_HANDLE h)
{
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
bool ret = false;
DEBUG_PRINTF("bsc_hub_connector_stopped() >>> h = %p\n", h);
bws_dispatch_lock();
if (c && c->state == BSC_HUB_CONNECTOR_STATE_IDLE) {
ret = true;
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_connector_stopped() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Get the hub connector status
* @param h - pointer to the hub connector
* @param primary - primary or failover status
* @return pointer to the status
*/
BACNET_SC_HUB_CONNECTION_STATUS *
bsc_hub_connector_status(BSC_HUB_CONNECTOR_HANDLE h, bool primary)
{
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
BACNET_SC_HUB_CONNECTION_STATUS *ret = NULL;
bws_dispatch_lock();
if (c) {
if (primary) {
ret = &c->primary_status;
} else {
ret = &c->failover_status;
}
}
bws_dispatch_unlock();
return ret;
}
/**
* @brief Get the hub connector state
* @param h - pointer to the hub connector
* @return state
*/
BACNET_SC_HUB_CONNECTOR_STATE
bsc_hub_connector_state(BSC_HUB_CONNECTOR_HANDLE h)
{
BSC_HUB_CONNECTOR *c = (BSC_HUB_CONNECTOR *)h;
BACNET_SC_HUB_CONNECTOR_STATE ret =
BACNET_SC_HUB_CONNECTOR_STATE_NO_HUB_CONNECTION;
bws_dispatch_lock();
if (c) {
if (c->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_PRIMARY) {
ret = BACNET_SC_HUB_CONNECTOR_STATE_CONNECTED_TO_PRIMARY;
} else if (c->state == BSC_HUB_CONNECTOR_STATE_CONNECTED_FAILOVER) {
ret = BACNET_SC_HUB_CONNECTOR_STATE_CONNECTED_TO_FAILOVER;
}
}
bws_dispatch_unlock();
return ret;
}
@@ -0,0 +1,81 @@
/**
* @file
* @brief BACNet secure connect hub connector API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date July 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_HUB_CONNECTOR_H
#define BACNET_DATALINK_BSC_HUB_CONNECTOR_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-retcodes.h"
typedef void *BSC_HUB_CONNECTOR_HANDLE;
typedef enum {
BSC_HUBC_EVENT_CONNECTED_PRIMARY = 1,
BSC_HUBC_EVENT_CONNECTED_FAILOVER = 2,
BSC_HUBC_EVENT_ERROR_DUPLICATED_VMAC = 3,
BSC_HUBC_EVENT_RECEIVED = 4,
BSC_HUBC_EVENT_STOPPED = 5
} BSC_HUB_CONNECTOR_EVENT;
typedef void (*BSC_HUB_CONNECTOR_EVENT_FUNC)(
BSC_HUB_CONNECTOR_EVENT ev,
BSC_HUB_CONNECTOR_HANDLE h,
void *user_arg,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_hub_connector_start(
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_npdu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s,
char *primaryURL,
char *failoverURL,
unsigned int reconnnect_timeout_s,
BSC_HUB_CONNECTOR_EVENT_FUNC event_func,
void *user_arg,
BSC_HUB_CONNECTOR_HANDLE *h);
BACNET_STACK_EXPORT
void bsc_hub_connector_stop(BSC_HUB_CONNECTOR_HANDLE h);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_hub_connector_send(
BSC_HUB_CONNECTOR_HANDLE h, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
bool bsc_hub_connector_stopped(BSC_HUB_CONNECTOR_HANDLE h);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTOR_STATE
bsc_hub_connector_state(BSC_HUB_CONNECTOR_HANDLE h);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTION_STATUS *
bsc_hub_connector_status(BSC_HUB_CONNECTOR_HANDLE h, bool primary);
BACNET_STACK_EXPORT
void bsc_hub_connector_maintenance_timer(uint16_t seconds);
#endif
+535
View File
@@ -0,0 +1,535 @@
/**
* @file
* @brief BACNet hub function API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date July 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-socket.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/datalink/bsc/bsc-hub-function.h"
#include "bacnet/bacdef.h"
#include "bacnet/npdu.h"
#include "bacnet/bacenum.h"
#define DEBUG_BSC_HUB_FUNCTION 0
#if DEBUG_BSC_HUB_FUNCTION == 1
#define DEBUG_PRINTF debug_printf
#else
#undef DEBUG_ENABLED
#define DEBUG_PRINTF debug_printf_disabled
#endif
static BSC_SOCKET *hub_function_find_connection_for_vmac(
BACNET_SC_VMAC_ADDRESS *vmac, void *user_arg);
static BSC_SOCKET *
hub_function_find_connection_for_uuid(BACNET_SC_UUID *uuid, void *user_arg);
static void hub_function_failed_request(
BSC_SOCKET_CTX *ctx,
BSC_SOCKET *c,
BACNET_SC_VMAC_ADDRESS *vmac,
BACNET_SC_UUID *uuid,
BACNET_ERROR_CODE error,
const char *error_desc);
static void hub_function_socket_event(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE reason,
const char *reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
static void hub_function_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev);
typedef enum {
BSC_HUB_FUNCTION_STATE_IDLE = 0,
BSC_HUB_FUNCTION_STATE_STARTING = 1,
BSC_HUB_FUNCTION_STATE_STARTED = 2,
BSC_HUB_FUNCTION_STATE_STOPPING = 3
} BSC_HUB_FUNCTION_STATE;
typedef struct BSC_Hub_Connector {
bool used;
BSC_SOCKET_CTX ctx;
BSC_CONTEXT_CFG cfg;
BSC_SOCKET sock[BSC_CONF_HUB_FUNCTION_CONNECTIONS_NUM];
BSC_HUB_FUNCTION_STATE state;
BSC_HUB_EVENT_FUNC event_func;
void *user_arg;
} BSC_HUB_FUNCTION;
#if BSC_CONF_HUB_FUNCTIONS_NUM > 0
static BSC_HUB_FUNCTION bsc_hub_function[BSC_CONF_HUB_FUNCTIONS_NUM] = { 0 };
#else
static BSC_HUB_FUNCTION *bsc_hub_function = NULL;
#endif
static BSC_SOCKET_CTX_FUNCS bsc_hub_function_ctx_funcs = {
hub_function_find_connection_for_vmac,
hub_function_find_connection_for_uuid, hub_function_socket_event,
hub_function_context_event, hub_function_failed_request
};
/**
* @brief Allocate a hub function
* @return pointer to the hub function
*/
static BSC_HUB_FUNCTION *hub_function_alloc(void)
{
int i;
for (i = 0; i < BSC_CONF_HUB_FUNCTIONS_NUM; i++) {
if (!bsc_hub_function[i].used) {
bsc_hub_function[i].used = true;
return &bsc_hub_function[i];
}
}
return NULL;
}
/**
* @brief Free a hub function
* @param p - pointer to the hub function
*/
static void hub_function_free(BSC_HUB_FUNCTION *p)
{
p->used = false;
}
/**
* @brief find a hub function connection for a specific VMAC address
* @param vmac - pointer to the VMAC address
* @param user_arg - pointer to the user argument
* @return pointer to the socket, or NULL if not found
*/
static BSC_SOCKET *hub_function_find_connection_for_vmac(
BACNET_SC_VMAC_ADDRESS *vmac, void *user_arg)
{
int i;
BSC_HUB_FUNCTION *f;
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)user_arg;
DEBUG_PRINTF(
"hubf = %p local_vmac = %s\n", f,
bsc_vmac_to_string(&f->cfg.local_vmac));
for (i = 0; i < sizeof(f->sock) / sizeof(BSC_SOCKET); i++) {
DEBUG_PRINTF(
"hubf = %p, sock %p, state = %d, vmac = %s\n", f, &f->sock[i],
f->sock[i].state, bsc_vmac_to_string(&f->sock[i].vmac));
if (f->sock[i].state != BSC_SOCK_STATE_IDLE &&
!memcmp(
&vmac->address[0], &f->sock[i].vmac.address[0],
sizeof(vmac->address))) {
bws_dispatch_unlock();
return &f->sock[i];
}
}
bws_dispatch_unlock();
return NULL;
}
/**
* @brief find a hub function connection for a specific UUID
* @param uuid - pointer to the UUID
* @param user_arg - pointer to the user argument
* @return pointer to the socket, or NULL if not found
*/
static BSC_SOCKET *
hub_function_find_connection_for_uuid(BACNET_SC_UUID *uuid, void *user_arg)
{
int i;
BSC_HUB_FUNCTION *f;
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)user_arg;
for (i = 0; i < sizeof(f->sock) / sizeof(BSC_SOCKET); i++) {
DEBUG_PRINTF(
"hubf = %p, sock %p, state = %d, uuid = %s\n", f, &f->sock[i],
f->sock[i].state, bsc_uuid_to_string(&f->sock[i].uuid));
if (f->sock[i].state != BSC_SOCK_STATE_IDLE &&
!memcmp(
&uuid->uuid[0], &f->sock[i].uuid.uuid[0], sizeof(uuid->uuid))) {
bws_dispatch_unlock();
DEBUG_PRINTF("found socket\n");
return &f->sock[i];
}
}
bws_dispatch_unlock();
return NULL;
}
/**
* @brief update the status of the hub function
* @param f - pointer to the hub function
* @param c - pointer to the socket
* @param ev - event
* @param disconnect_reason - disconnect reason
* @param disconnect_reason_desc - disconnect reason description
*/
static void hub_function_update_status(
BSC_HUB_FUNCTION *f,
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE disconnect_reason,
const char *disconnect_reason_desc)
{
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *s;
if (f->user_arg) {
s = bsc_node_find_hub_status_for_vmac(f->user_arg, &c->vmac);
if (s) {
memcpy(s->Peer_VMAC, &c->vmac.address[0], BVLC_SC_VMAC_SIZE);
memcpy(
&s->Peer_UUID.uuid.uuid128[0], &c->uuid.uuid[0],
BVLC_SC_UUID_SIZE);
if (!bsc_socket_get_peer_addr(c, &s->Peer_Address)) {
memset(&s->Peer_Address, 0, sizeof(s->Peer_Address));
}
if (disconnect_reason_desc) {
bsc_copy_str(
s->Error_Details, disconnect_reason_desc,
sizeof(s->Error_Details));
} else {
s->Error_Details[0] = 0;
}
s->Error = ERROR_CODE_DEFAULT;
if (ev == BSC_SOCKET_EVENT_CONNECTED) {
s->State = BACNET_SC_CONNECTION_STATE_CONNECTED;
bsc_set_timestamp(&s->Connect_Timestamp);
memset(
&s->Disconnect_Timestamp, 0xff,
sizeof(s->Disconnect_Timestamp));
} else if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
bsc_set_timestamp(&s->Disconnect_Timestamp);
if (disconnect_reason == ERROR_CODE_WEBSOCKET_CLOSED_BY_PEER ||
disconnect_reason == ERROR_CODE_SUCCESS) {
s->State = BACNET_SC_CONNECTION_STATE_NOT_CONNECTED;
} else {
s->State =
BACNET_SC_CONNECTION_STATE_DISCONNECTED_WITH_ERRORS;
s->Error = disconnect_reason;
}
}
}
}
}
/**
* @brief Handle a hub function failed request
* @param ctx - pointer to the socket context
* @param c - pointer to the socket
* @param vmac - pointer to the VMAC address
* @param uuid - pointer to the UUID
* @param error - error code
* @param error_desc - pointer to the error description
*/
static void hub_function_failed_request(
BSC_SOCKET_CTX *ctx,
BSC_SOCKET *c,
BACNET_SC_VMAC_ADDRESS *vmac,
BACNET_SC_UUID *uuid,
BACNET_ERROR_CODE error,
const char *error_desc)
{
BSC_HUB_FUNCTION *f;
BACNET_HOST_N_PORT_DATA peer;
(void)ctx;
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)c->ctx->user_arg;
if (f->user_arg) {
if (bsc_socket_get_peer_addr(c, &peer)) {
bsc_node_store_failed_request_info(
(BSC_NODE *)f->user_arg, &peer, vmac, uuid, error, error_desc);
}
}
bws_dispatch_unlock();
}
/**
* @brief Handle a hub function socket event
* @param c - pointer to the socket
* @param ev - event
* @param reason - disconnect reason
* @param reason_desc - disconnect reason description
* @param pdu - pointer to the PDU
* @param pdu_len - PDU length
* @param decoded_pdu - decoded PDU
*/
static void hub_function_socket_event(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE reason,
const char *reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu)
{
BSC_SOCKET *dst;
BSC_SC_RET ret;
int i;
uint8_t *p_pdu;
BSC_HUB_FUNCTION *f;
size_t len;
DEBUG_PRINTF(
"hub_function_socket_event() >>> c = %p, ev = %d, reason = "
"%d, desc = %p, pdu = %p, pdu_len = %d, decoded_pdu = %p\n",
c, ev, reason, reason_desc, pdu, pdu_len, decoded_pdu);
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)c->ctx->user_arg;
if (ev == BSC_SOCKET_EVENT_RECEIVED) {
/* double check that received message does not contain */
/* originating virtual address and contains dest vaddr */
/* although such kind of check is already in bsc-socket.c */
if (!decoded_pdu->hdr.origin && decoded_pdu->hdr.dest) {
if (bvlc_sc_is_vmac_broadcast(decoded_pdu->hdr.dest)) {
if (bsc_socket_get_global_buf_size() >= pdu_len) {
p_pdu = bsc_socket_get_global_buf();
len = pdu_len;
memcpy(p_pdu, pdu, len);
for (i = 0; i < sizeof(f->sock) / sizeof(BSC_SOCKET); i++) {
if (&f->sock[i] != c &&
f->sock[i].state == BSC_SOCK_STATE_CONNECTED) {
/* change origin address if presented or add origin
*/
/* address into pdu by extending of it's header */
len = (uint16_t)bvlc_sc_set_orig(
&p_pdu, len, &c->vmac);
ret = bsc_send(&f->sock[i], p_pdu, len);
(void)ret;
#if DEBUG_ENABLED == 1
if (ret != BSC_SC_SUCCESS) {
DEBUG_PRINTF(
"sending of reconstructed pdu failed, "
"err = %d\n",
ret);
}
#endif
}
}
}
#if DEBUG_ENABLED == 1
else {
DEBUG_PRINTF("pdu with len = %d is dropped\n", pdu_len);
}
#endif
} else {
dst = hub_function_find_connection_for_vmac(
decoded_pdu->hdr.dest, (void *)f);
if (!dst) {
DEBUG_PRINTF(
"can not find socket, hub dropped pdu of size "
"%d for dest vmac %s\n",
pdu_len, bsc_vmac_to_string(decoded_pdu->hdr.dest));
} else {
bvlc_sc_remove_dest_set_orig(pdu, pdu_len, &c->vmac);
ret = bsc_send(dst, pdu, pdu_len);
(void)ret;
#if DEBUG_ENABLED == 1
if (ret != BSC_SC_SUCCESS) {
DEBUG_PRINTF(
"sending of pdu of %d bytes failed, err = %d\n",
pdu_len, ret);
}
#endif
}
}
}
} else if (ev == BSC_SOCKET_EVENT_DISCONNECTED) {
hub_function_update_status(f, c, ev, reason, reason_desc);
if (reason == ERROR_CODE_NODE_DUPLICATE_VMAC) {
f->event_func(
BSC_HUBF_EVENT_ERROR_DUPLICATED_VMAC,
(BSC_HUB_FUNCTION_HANDLE)f, f->user_arg);
}
} else if (ev == BSC_SOCKET_EVENT_CONNECTED) {
hub_function_update_status(f, c, ev, reason, reason_desc);
}
bws_dispatch_unlock();
DEBUG_PRINTF("hub_function_socket_event() <<<\n");
}
/**
* @brief Handle a hub function context event
* @param ctx - pointer to the socket context
* @param ev - event
*/
static void hub_function_context_event(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev)
{
BSC_HUB_FUNCTION *f;
bws_dispatch_lock();
f = (BSC_HUB_FUNCTION *)ctx->user_arg;
if (ev == BSC_CTX_INITIALIZED) {
f->state = BSC_HUB_FUNCTION_STATE_STARTED;
f->event_func(
BSC_HUBF_EVENT_STARTED, (BSC_HUB_FUNCTION_HANDLE)f, f->user_arg);
} else if (ev == BSC_CTX_DEINITIALIZED) {
f->state = BSC_HUB_FUNCTION_STATE_IDLE;
hub_function_free(f);
f->event_func(
BSC_HUBF_EVENT_STOPPED, (BSC_HUB_FUNCTION_HANDLE)f, f->user_arg);
}
bws_dispatch_unlock();
}
/**
* @brief Start a BACnet hub function
* @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 port - port number
* @param iface - pointer to the interface
* @param local_uuid - pointer to the local UUID
* @param local_vmac - pointer to the local VMAC address
* @param max_local_bvlc_len - maximum local BVLC length
* @param max_local_npdu_len - maximum local NPDU length
* @param connect_timeout_s - connection timeout in seconds
* @param heartbeat_timeout_s - heartbeat timeout in seconds
* @param disconnect_timeout_s - disconnect timeout in seconds
* @param event_func - pointer to the event function
* @param user_arg - pointer to the user argument
* @param h - pointer to the hub function handle
* @return BACnet/SC status
*/
BSC_SC_RET bsc_hub_function_start(
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,
int port,
char *iface,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_npdu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s,
BSC_HUB_EVENT_FUNC event_func,
void *user_arg,
BSC_HUB_FUNCTION_HANDLE *h)
{
BSC_SC_RET ret;
BSC_HUB_FUNCTION *f;
DEBUG_PRINTF("bsc_hub_function_start() >>>\n");
if (!ca_cert_chain || !ca_cert_chain_size || !cert_chain ||
!cert_chain_size || !key || !key_size || !local_uuid || !local_vmac ||
!max_local_npdu_len || !max_local_bvlc_len || !connect_timeout_s ||
!heartbeat_timeout_s || !disconnect_timeout_s || !event_func || !h) {
DEBUG_PRINTF("bsc_hub_function_start() <<< ret = BSC_SC_BAD_PARAM\n");
return BSC_SC_BAD_PARAM;
}
*h = NULL;
bws_dispatch_lock();
f = hub_function_alloc();
if (!f) {
bws_dispatch_unlock();
DEBUG_PRINTF(
"bsc_hub_function_start() <<< ret = BSC_SC_NO_RESOURCES\n");
return BSC_SC_NO_RESOURCES;
}
f->user_arg = user_arg;
f->event_func = event_func;
bsc_init_ctx_cfg(
BSC_SOCKET_CTX_ACCEPTOR, &f->cfg, BSC_WEBSOCKET_HUB_PROTOCOL, port,
iface, ca_cert_chain, ca_cert_chain_size, cert_chain, cert_chain_size,
key, key_size, local_uuid, local_vmac, max_local_bvlc_len,
max_local_npdu_len, connect_timeout_s, heartbeat_timeout_s,
disconnect_timeout_s);
ret = bsc_init_ctx(
&f->ctx, &f->cfg, &bsc_hub_function_ctx_funcs, f->sock,
sizeof(f->sock) / sizeof(BSC_SOCKET), f);
if (ret == BSC_SC_SUCCESS) {
f->state = BSC_HUB_FUNCTION_STATE_STARTING;
*h = (BSC_HUB_FUNCTION_HANDLE)f;
} else {
hub_function_free(f);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_function_start() << ret = %d\n", ret);
return ret;
}
/**
* @brief Stop a BACnet hub function
* @param h - pointer to the hub function handle
*/
void bsc_hub_function_stop(BSC_HUB_FUNCTION_HANDLE h)
{
BSC_HUB_FUNCTION *f = (BSC_HUB_FUNCTION *)h;
DEBUG_PRINTF("bsc_hub_function_stop() >>> h = %p\n", h);
bws_dispatch_lock();
if (f && f->state != BSC_HUB_FUNCTION_STATE_IDLE &&
f->state != BSC_HUB_FUNCTION_STATE_STOPPING) {
f->state = BSC_HUB_FUNCTION_STATE_STOPPING;
bsc_deinit_ctx(&f->ctx);
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_function_stop() <<<\n");
}
/**
* @brief Check if the hub function is stopped
* @param h - pointer to the hub function handle
* @return true if the hub function is stopped, false otherwise
*/
bool bsc_hub_function_stopped(BSC_HUB_FUNCTION_HANDLE h)
{
BSC_HUB_FUNCTION *f = (BSC_HUB_FUNCTION *)h;
bool ret = false;
DEBUG_PRINTF("bsc_hub_function_stopped() >>> h = %p\n", h);
bws_dispatch_lock();
if (f && f->state == BSC_HUB_FUNCTION_STATE_IDLE) {
ret = true;
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_function_stopped() <<< ret = %d\n", ret);
return ret;
}
/**
* @brief Check if the hub function is started
* @param h - pointer to the hub function handle
* @return true if the hub function is started, false otherwise
*/
bool bsc_hub_function_started(BSC_HUB_FUNCTION_HANDLE h)
{
BSC_HUB_FUNCTION *f = (BSC_HUB_FUNCTION *)h;
bool ret = false;
DEBUG_PRINTF("bsc_hub_function_started() >>> h = %p\n", h);
bws_dispatch_lock();
if (f && f->state == BSC_HUB_FUNCTION_STATE_STARTED) {
ret = true;
}
bws_dispatch_unlock();
DEBUG_PRINTF("bsc_hub_function_started() <<< ret = %d\n", ret);
return ret;
}
@@ -0,0 +1,62 @@
/**
* @file
* @brief BACNet secure connect hub function API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date July 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_HUB_FUNCTION_H
#define BACNET_DATALINK_BSC_HUB_FUNCTION_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/basic/object/sc_netport.h"
typedef void *BSC_HUB_FUNCTION_HANDLE;
typedef enum {
BSC_HUBF_EVENT_STARTED = 1,
BSC_HUBF_EVENT_STOPPED = 2,
BSC_HUBF_EVENT_ERROR_DUPLICATED_VMAC = 3
} BSC_HUB_FUNCTION_EVENT;
typedef void (*BSC_HUB_EVENT_FUNC)(
BSC_HUB_FUNCTION_EVENT ev, BSC_HUB_FUNCTION_HANDLE h, void *user_arg);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_hub_function_start(
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,
int port,
char *iface,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_npdu_len,
unsigned int connect_timeout_s,
unsigned int heartbeat_timeout_s,
unsigned int disconnect_timeout_s,
BSC_HUB_EVENT_FUNC event_func,
void *user_arg,
BSC_HUB_FUNCTION_HANDLE *h);
BACNET_STACK_EXPORT
void bsc_hub_function_stop(BSC_HUB_FUNCTION_HANDLE h);
BACNET_STACK_EXPORT
bool bsc_hub_function_stopped(BSC_HUB_FUNCTION_HANDLE h);
BACNET_STACK_EXPORT
bool bsc_hub_function_started(BSC_HUB_FUNCTION_HANDLE h);
#endif
File diff suppressed because it is too large Load Diff
+126
View File
@@ -0,0 +1,126 @@
/**
* @file
* @brief BACNet secure connect node switch function API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date October 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_NODE_SWITCH_H
#define BACNET_DATALINK_BSC_NODE_SWITCH_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/datalink/bsc/bsc-node.h"
typedef void *BSC_NODE_SWITCH_HANDLE;
typedef enum {
BSC_NODE_SWITCH_EVENT_STARTED = 1,
BSC_NODE_SWITCH_EVENT_STOPPED = 2,
BSC_NODE_SWITCH_EVENT_RECEIVED = 3,
BSC_NODE_SWITCH_EVENT_DUPLICATED_VMAC = 4,
/* BSC_NODE_SWITCH_EVENT_CONNECTED event is emitted every */
/* time remote peer connects only if bsc_node_switch_connect() */
/* was called after start for corresponded mac or url/urls. */
/* Events indication are stopped only */
/* if node switch was stopped or bsc_node_switch_disconnect() */
/* was called. For a connection initiated from a remote peer that */
/* event won't be ever emitted. */
BSC_NODE_SWITCH_EVENT_CONNECTED = 5,
/* The BSC_NODE_SWITCH_EVENT_DISCONNECTED event is emitted */
/* every time remote peer disconnects only if */
/* bsc_node_switch_connect() was called after start. */
/* If user called bsc_node_switch_disconnect() or stopped */
/* node switch, after last event indication next event indications */
/* are stopped for corresponded peer with corresponded vmac. */
BSC_NODE_SWITCH_EVENT_DISCONNECTED = 6
} BSC_NODE_SWITCH_EVENT;
/* dest parameter is actual only for */
/* BSC_NODE_SWITCH_EVENT_CONNECTED and BSC_NODE_SWITCH_EVENT_DISCONNECTED */
/* events. It is the parameter which was specified in */
/* bsc_node_switch_connect() or bsc_node_switch_disconnect() calls. */
typedef void (*BSC_NODE_SWITCH_EVENT_FUNC)(
BSC_NODE_SWITCH_EVENT ev,
BSC_NODE_SWITCH_HANDLE h,
void *user_arg,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_switch_start(
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,
int port,
char *iface,
BACNET_SC_UUID *local_uuid,
BACNET_SC_VMAC_ADDRESS *local_vmac,
uint16_t max_local_bvlc_len,
uint16_t max_local_npdu_len,
uint16_t connect_timeout_s,
uint16_t heartbeat_timeout_s,
uint16_t disconnect_timeout_s,
uint16_t reconnnect_timeout_s,
uint16_t address_resolution_timeout_s,
bool direct_connect_accept_enable,
bool direct_connect_initiate_enable,
BSC_NODE_SWITCH_EVENT_FUNC event_func,
void *user_arg,
BSC_NODE_SWITCH_HANDLE *h);
BACNET_STACK_EXPORT
void bsc_node_switch_stop(BSC_NODE_SWITCH_HANDLE h);
BACNET_STACK_EXPORT
bool bsc_node_switch_stopped(BSC_NODE_SWITCH_HANDLE h);
BACNET_STACK_EXPORT
bool bsc_node_switch_started(BSC_NODE_SWITCH_HANDLE h);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_switch_connect(
BSC_NODE_SWITCH_HANDLE h,
BACNET_SC_VMAC_ADDRESS *dest,
char **urls,
size_t urls_cnt);
BACNET_STACK_EXPORT
bool bsc_node_switch_connected(
BSC_NODE_SWITCH_HANDLE h,
BACNET_SC_VMAC_ADDRESS *dest,
char **urls,
size_t urls_cnt);
BACNET_STACK_EXPORT
void bsc_node_switch_disconnect(
BSC_NODE_SWITCH_HANDLE h, BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
void bsc_node_switch_process_address_resolution(
BSC_NODE_SWITCH_HANDLE h, BSC_ADDRESS_RESOLUTION *r);
BACNET_STACK_EXPORT
BSC_SC_RET
bsc_node_switch_send(BSC_NODE_SWITCH_HANDLE h, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
void bsc_node_switch_maintenance_timer(uint16_t seconds);
#endif
File diff suppressed because it is too large Load Diff
+162
View File
@@ -0,0 +1,162 @@
/**
* @file
* @brief BACNet secure connect node API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date October 2022
* @copyright SPDX-License-Identifier: GPL-2.0-or-later WITH GCC-exception-2.0
*/
#ifndef BACNET_DATALINK_BSC_NODE_H
#define BACNET_DATALINK_BSC_NODE_H
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/object/sc_netport.h"
typedef struct BSC_Node BSC_NODE;
typedef enum {
BSC_NODE_EVENT_STARTED = 1,
BSC_NODE_EVENT_STOPPED = 2,
BSC_NODE_EVENT_RESTARTED = 3,
BSC_NODE_EVENT_RECEIVED_NPDU = 4,
BSC_NODE_EVENT_RECEIVED_RESULT = 5,
BSC_NODE_EVENT_RECEIVED_ADVERTISIMENT = 6,
BSC_NODE_EVENT_DIRECT_CONNECTED = 7,
BSC_NODE_EVENT_DIRECT_DISCONNECTED = 8
} BSC_NODE_EVENT;
typedef void (*BSC_NODE_EVENT_FUNC)(
BSC_NODE *node,
BSC_NODE_EVENT ev,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *pdu,
size_t pdu_len);
typedef struct {
bool used;
BACNET_SC_VMAC_ADDRESS vmac;
uint8_t utf8_urls[BSC_CONF_NODE_MAX_URIS_NUM_IN_ADDRESS_RESOLUTION_ACK]
[BSC_CONF_NODE_MAX_URI_SIZE_IN_ADDRESS_RESOLUTION_ACK + 1];
size_t urls_num;
struct mstimer fresh_timer;
} BSC_ADDRESS_RESOLUTION;
typedef struct {
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_npdu_len;
uint16_t connect_timeout_s;
uint16_t heartbeat_timeout_s;
uint16_t disconnect_timeout_s;
uint16_t reconnnect_timeout_s;
uint16_t address_resolution_timeout_s;
uint16_t address_resolution_freshness_timeout_s;
char *primaryURL;
char *failoverURL;
uint16_t hub_server_port;
uint16_t direct_server_port;
char *hub_iface;
char *direct_iface;
bool direct_connect_accept_enable;
bool direct_connect_initiate_enable;
bool hub_function_enabled;
char *direct_connection_accept_uris; /* URIs joined ' 'space */
size_t direct_connection_accept_uris_len;
BSC_NODE_EVENT_FUNC event_func;
} BSC_NODE_CONF;
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_init(BSC_NODE_CONF *conf, BSC_NODE **node);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_deinit(BSC_NODE *node);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_start(BSC_NODE *node);
BACNET_STACK_EXPORT
void bsc_node_stop(BSC_NODE *node);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_send(BSC_NODE *node, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
BSC_ADDRESS_RESOLUTION *
bsc_node_get_address_resolution(void *node, BACNET_SC_VMAC_ADDRESS *vmac);
BACNET_STACK_EXPORT
BSC_SC_RET
bsc_node_send_address_resolution(void *node, BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
BSC_SC_RET
bsc_node_hub_connector_send(void *user_arg, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
BSC_SC_RET bsc_node_connect_direct(
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt);
BACNET_STACK_EXPORT
void bsc_node_disconnect_direct(BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTOR_STATE
bsc_node_hub_connector_state(BSC_NODE *node);
BACNET_STACK_EXPORT
BACNET_SC_HUB_CONNECTION_STATUS *
bsc_node_hub_connector_status(BSC_NODE *node, bool primary);
BACNET_STACK_EXPORT
bool bsc_node_direct_connection_established(
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *dest, char **urls, size_t urls_cnt);
BACNET_STACK_EXPORT
void bsc_node_maintenance_timer(uint16_t seconds);
BACNET_STACK_EXPORT
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *
bsc_node_hub_function_status(BSC_NODE *node, size_t *cnt);
BACNET_STACK_EXPORT
BACNET_SC_DIRECT_CONNECTION_STATUS *
bsc_node_direct_connection_status(BSC_NODE *node, size_t *cnt);
BACNET_STACK_EXPORT
void bsc_node_store_failed_request_info(
BSC_NODE *node,
BACNET_HOST_N_PORT_DATA *peer,
BACNET_SC_VMAC_ADDRESS *vmac,
BACNET_SC_UUID *uuid,
BACNET_ERROR_CODE error,
const char *error_desc);
BACNET_STACK_EXPORT
BACNET_SC_FAILED_CONNECTION_REQUEST *
bsc_node_failed_requests_status(BSC_NODE *node, size_t *cnt);
BACNET_STACK_EXPORT
BACNET_SC_DIRECT_CONNECTION_STATUS *bsc_node_find_direct_status_for_vmac(
BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *vmac);
BACNET_STACK_EXPORT
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *
bsc_node_find_hub_status_for_vmac(BSC_NODE *node, BACNET_SC_VMAC_ADDRESS *vmac);
#endif
+20
View File
@@ -0,0 +1,20 @@
/**
* @file
* @brief BACNet secure connect main include header.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date August 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_RETCODES_H
#define BACNET_DATALINK_BSC_RETCODES_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
typedef enum {
BSC_SC_SUCCESS = 0,
BSC_SC_NO_RESOURCES = 1,
BSC_SC_BAD_PARAM = 2,
BSC_SC_INVALID_OPERATION = 3
} BSC_SC_RET;
#endif
File diff suppressed because it is too large Load Diff
+308
View File
@@ -0,0 +1,308 @@
/**
* @file
* @brief BACNet secure connect socket API.
* In general, user should not use that API directly,
* BACNet/SC datalink API should be used.
* @author Kirill Neznamov <kirill\.neznamov@dsr-corporation\.com>
* @date December 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_SOCKET_H
#define BACNET_DATALINK_BSC_SOCKET_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <limits.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/npdu.h"
#include "bacnet/datalink/bsc/websocket.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/object/sc_netport.h"
#define BSC_RX_BUFFER_SIZE BSC_CONF_SOCK_RX_BUFFER_SIZE
#define BSC_TX_BUFFER_SIZE BSC_CONF_SOCK_TX_BUFFER_SIZE
#define BSC_SOCKET_CTX_NUM \
(BSC_CONF_NODES_NUM * \
(BSC_CONF_HUB_CONNECTORS_NUM + 2 * BSC_CONF_NODE_SWITCHES_NUM + \
BSC_CONF_HUB_FUNCTIONS_NUM))
struct BSC_Socket;
typedef struct BSC_Socket BSC_SOCKET;
struct BSC_SocketContext;
typedef struct BSC_SocketContext BSC_SOCKET_CTX;
struct BSC_SocketContextFuncs;
typedef struct BSC_SocketContextFuncs BSC_SOCKET_CTX_FUNCS;
struct BSC_ContextCFG;
typedef struct BSC_ContextCFG BSC_CONTEXT_CFG;
typedef enum {
BSC_SOCKET_CTX_INITIATOR = 1,
BSC_SOCKET_CTX_ACCEPTOR = 2
} BSC_SOCKET_CTX_TYPE;
typedef enum {
BSC_SOCKET_EVENT_CONNECTED = 0,
BSC_SOCKET_EVENT_DISCONNECTED = 1,
BSC_SOCKET_EVENT_RECEIVED = 2
} BSC_SOCKET_EVENT;
typedef enum {
BSC_CTX_INITIALIZED = 0,
BSC_CTX_DEINITIALIZED = 1
} BSC_CTX_EVENT;
typedef enum {
BSC_CTX_STATE_IDLE = 0,
BSC_CTX_STATE_INITIALIZING = 1,
BSC_CTX_STATE_INITIALIZED = 2,
BSC_CTX_STATE_DEINITIALIZING = 3
} BSC_CTX_STATE;
typedef enum {
BSC_SOCK_STATE_IDLE = 0,
BSC_SOCK_STATE_AWAITING_WEBSOCKET = 1,
BSC_SOCK_STATE_AWAITING_REQUEST = 2,
BSC_SOCK_STATE_AWAITING_ACCEPT = 3,
BSC_SOCK_STATE_CONNECTED = 4,
BSC_SOCK_STATE_DISCONNECTING = 5,
BSC_SOCK_STATE_ERROR = 6,
BSC_SOCK_STATE_ERROR_FLUSH_TX = 7
} BSC_SOCKET_STATE;
struct BSC_Socket {
BSC_SOCKET_CTX *ctx;
BSC_WEBSOCKET_HANDLE wh;
BSC_SOCKET_STATE state;
BACNET_ERROR_CODE reason;
struct mstimer t;
struct mstimer heartbeat;
BACNET_SC_VMAC_ADDRESS vmac; /* VMAC address of the requesting node. */
BACNET_SC_UUID uuid;
/* Regarding max_bvlc_len and max_npdu_len: */
/* These are the datalink limits and are passed up the stack to let */
/* the application layer know one of the several numbers that go into
* computing */
/* how big an NPDU/APDU can be. */
uint16_t max_bvlc_len; /* remote peer max bvlc len */
uint16_t max_npdu_len; /* remote peer max npdu len */
uint16_t expected_connect_accept_message_id;
uint16_t expected_disconnect_message_id;
uint16_t expected_heartbeat_message_id;
uint8_t tx_buf[BSC_TX_BUFFER_SIZE];
size_t tx_buf_size;
};
struct BSC_ContextCFG {
BSC_SOCKET_CTX_TYPE type;
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 *priv_key;
size_t priv_key_size;
BACNET_SC_VMAC_ADDRESS local_vmac;
BACNET_SC_UUID local_uuid;
uint16_t max_bvlc_len; /* local peer max bvlc len */
uint16_t max_ndpu_len; /* local peer max npdu len */
/* According AB.6.2 BACnet/SC Connection Establishment and Termination */
/* recommended default value for establishing of connection 10 seconds */
uint16_t connect_timeout_s;
uint16_t disconnect_timeout_s;
/* According 12.56.Y10 SC_Heartbeat_Timeout */
/* (http://www.bacnet.org/Addenda/Add-135-2020cc.pdf) the recommended
* default */
/* value is 300 seconds. */
uint16_t heartbeat_timeout_s;
};
struct BSC_SocketContextFuncs {
BSC_SOCKET *(*find_connection_for_vmac)(
BACNET_SC_VMAC_ADDRESS *vmac, void *user_arg);
BSC_SOCKET *(*find_connection_for_uuid)(
BACNET_SC_UUID *uuid, void *user_arg);
/* We always reserve BSC_PRE bytes before BVLC message header */
/* to avoid copying of packet payload during manipulation with */
/* origin and dest addresses (e.g. adding them to received PDU) */
/* That's why pdu pointer has always reserved BSC_PRE bytes behind */
/* The params disconnect_reason and disconnect_reason_desc are meanfull */
/* only for disconnect events, e.g. when ev == BSC_SOCKET_EVENT_DISCONNECTED
*/
void (*socket_event)(
BSC_SOCKET *c,
BSC_SOCKET_EVENT ev,
BACNET_ERROR_CODE disconnect_reason,
const char *disconnect_reason_desc,
uint8_t *pdu,
size_t pdu_len,
BVLC_SC_DECODED_MESSAGE *decoded_pdu);
void (*context_event)(BSC_SOCKET_CTX *ctx, BSC_CTX_EVENT ev);
void (*failed_request)(
BSC_SOCKET_CTX *ctx,
BSC_SOCKET *c,
BACNET_SC_VMAC_ADDRESS *vmac,
BACNET_SC_UUID *uuid,
BACNET_ERROR_CODE error,
const char *error_desc);
};
struct BSC_SocketContext {
BSC_CTX_STATE state;
BSC_WEBSOCKET_SRV_HANDLE sh;
BSC_SOCKET *sock;
size_t sock_num;
BSC_SOCKET_CTX_FUNCS *funcs;
BSC_CONTEXT_CFG *cfg;
bool deinit_in_progress;
void *user_arg;
};
/* max_local_bvlc_len - The maximum BVLC message size int bytes that can be */
/* received and processed by BSC/SC datalink. */
/* max_local_ndpu_len - The maximum NPDU message size in bytes hat can be */
/* handled by BSC/SC datalink. */
BACNET_STACK_EXPORT
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);
BACNET_STACK_EXPORT
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);
BACNET_STACK_EXPORT
void bsc_deinit_ctx(BSC_SOCKET_CTX *ctx);
/**
* @brief bsc_connect() function starts connect operation for a
* specified BACNet socket. The function call be called only
* for initiator context otherwise BSC_SC_INVALID_OPERATION
* error is returned. As a result if bsc_connect() was
* succeeded for given param c, that leads to emitting of
* BSC_SOCKET_EVENT_CONNECTED or BSC_SOCKET_EVENT_DISCONNECTED
* events depending on the result of connect operation.
* If connect operation is failed, BSC_SOCKET_EVENT_DISCONNECTED
* event is emitted and user can determine the reason why it
* happened using disconnect_reason and disconnect_reason_desc
* parameters in callback function.
* If connect operation succeeded, BSC_SOCKET_EVENT_CONNECTED
* event is emitted.
*
* @param ctx - socket context.
* @param c - BACNet socket descriptor .
* @param url - url to connect to. For example: wss://legrand.com:8080.
*
* @return error code from BSC_SC_RET enum.
* The following error codes can be returned:
* BSC_SC_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_SC_INVALID_OPERATION - if socket is not in opened state,
or disconnect operation is in progress using
bsc_disconnect() or bsc_deinit_ctx().
* BSC_SC_SUCCESS - operation has succeeded.
* BSC_SC_NO_RESOURCES - there are not resources (memory, etc.. )
* to send data
*/
BACNET_STACK_EXPORT
BSC_SC_RET bsc_connect(BSC_SOCKET_CTX *ctx, BSC_SOCKET *c, char *url);
BACNET_STACK_EXPORT
void bsc_disconnect(BSC_SOCKET *c);
/**
* @brief bsc_send() function schedules transmitting of pdu to
* another BACNet socket. The function may be used only
* when the socket is in a connected state
* otherwise BSC_SC_INVALID_OPERATION error is returned.
*
* @param c - BACNet socket descriptor initialized by bsc_accept() or
* bsc_connect() calls.
* @param pdu - pointer to a data to send.
* @param pdu_len - size in bytes of data to send.
*
* @return error code from BSC_SC_RET enum.
* The following error codes can be returned:
* BSC_SC_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_SC_INVALID_OPERATION - if socket is not in opened state,
or disconnect operation is in progress using
bsc_disconnect() or bsc_deinit_ctx().
* BSC_SC_SUCCESS - operation has succeeded.
* BSC_SC_NO_RESOURCES - there are not resources (memory, etc.. )
* to send data
*/
BACNET_STACK_EXPORT
BSC_SC_RET bsc_send(BSC_SOCKET *c, uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
uint16_t bsc_get_next_message_id(void);
BACNET_STACK_EXPORT
void bsc_socket_maintenance_timer(uint16_t seconds);
/**
* @brief bsc_socket_get_peer_addr() function gets information
* about remote peer address only for socket with acceptor cotext.
*
* @param c - BACNet socket descriptor initialized by bsc_accept() call.
* @param data - pointer to a struct holding address information.
* @param pdu_len - size in bytes of data to send.
*
* @return false if socket is not acceptor or bad parameter was passed or
* if getting of address information failed.
* true if function succeeds.
*/
BACNET_STACK_EXPORT
bool bsc_socket_get_peer_addr(BSC_SOCKET *c, BACNET_HOST_N_PORT_DATA *data);
BACNET_STACK_EXPORT
uint8_t *bsc_socket_get_global_buf(void);
BACNET_STACK_EXPORT
size_t bsc_socket_get_global_buf_size(void);
#endif
+343
View File
@@ -0,0 +1,343 @@
/**
* @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;
}
+36
View File
@@ -0,0 +1,36 @@
/**
* @file
* @brief module for common function for BACnet/SC implementation
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date Jule 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_UTIL_H
#define BACNET_DATALINK_BSC_UTIL_H
#include <stdbool.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/basic/sys/mstimer.h"
#include "bacnet/basic/sys/debug.h"
#include "bacnet/datalink/bsc/bsc-node.h"
#include "bacnet/datalink/bsc/bsc-retcodes.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/websocket.h"
#include "bacnet/datetime.h"
BSC_SC_RET bsc_map_websocket_retcode(BSC_WEBSOCKET_RET ret);
void bsc_copy_vmac(BACNET_SC_VMAC_ADDRESS *dst, BACNET_SC_VMAC_ADDRESS *src);
void bsc_copy_uuid(BACNET_SC_UUID *dst, BACNET_SC_UUID *src);
char *bsc_vmac_to_string(BACNET_SC_VMAC_ADDRESS *vmac);
char *bsc_uuid_to_string(BACNET_SC_UUID *uuid);
void bsc_generate_random_vmac(BACNET_SC_VMAC_ADDRESS *p);
void bsc_generate_random_uuid(BACNET_SC_UUID *p);
bool bsc_node_conf_fill_from_netport(
BSC_NODE_CONF *bsc_conf, BSC_NODE_EVENT_FUNC event_func);
void bsc_node_conf_cleanup(BSC_NODE_CONF *bsc_conf);
void bsc_copy_str(char *dst, const char *src, size_t dst_len);
void bsc_set_timestamp(BACNET_DATE_TIME *timestamp);
#endif
File diff suppressed because it is too large Load Diff
+425
View File
@@ -0,0 +1,425 @@
/**
* @file
* @brief API for encoding/decoding of BACnet/SC BVLC messages
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date May 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_BVLC_SC_H
#define BACNET_DATALINK_BSC_BVLC_SC_H
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <limits.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/npdu.h"
#ifndef BVLC_SC_NPDU_SIZE_CONF
#define BVLC_SC_NPDU_SIZE 1440
#else
#define BVLC_SC_NPDU_SIZE BVLC_SC_NPDU_SIZE_CONF
#endif
#define BVLC_SC_NPDU_MAX_SIZE \
61327 /* Table 6-1. NPDU Lengths of BACnet Data Link Layers */
#define BVLC_SC_VMAC_SIZE 6
#define BVLC_SC_UUID_SIZE 16
#define BSC_PRE (2 * BVLC_SC_VMAC_SIZE)
#if !defined(USER_DEFINED_BVLC_SC_HEADER_OPTION_MAX)
#define BVLC_SC_HEADER_OPTION_MAX \
4 /* though BACNet standard does not limit number of option headers \
the implementation defines max value */
#else
#define BVLC_SC_HEADER_OPTION_MAX USER_DEFINED_BVLC_SC_HEADER_OPTION_MAX
#endif
#if BVLC_SC_NPDU_SIZE > BVLC_SC_NPDU_MAX_SIZE
#error \
"Maximum NPDU Length on BACNet/SC Data Link must be <= BVLC_SC_NPDU_MAX_SIZE"
#endif
/*
* BACnet/SC BVLC Messages (functions) (AB.2 BACnet/SC Virtual Link Layer
* Messages)
*/
typedef enum BVLC_SC_Message_Type {
BVLC_SC_RESULT = 0x00,
BVLC_SC_ENCAPSULATED_NPDU = 0x01,
BVLC_SC_ADDRESS_RESOLUTION = 0x02,
BVLC_SC_ADDRESS_RESOLUTION_ACK = 0x03,
BVLC_SC_ADVERTISIMENT = 0x04,
BVLC_SC_ADVERTISIMENT_SOLICITATION = 0x05,
BVLC_SC_CONNECT_REQUEST = 0x06,
BVLC_SC_CONNECT_ACCEPT = 0x07,
BVLC_SC_DISCONNECT_REQUEST = 0x08,
BVLC_SC_DISCONNECT_ACK = 0x09,
BVLC_SC_HEARTBEAT_REQUEST = 0x0a,
BVLC_SC_HEARTBEAT_ACK = 0x0b,
BVLC_SC_PROPRIETARY_MESSAGE = 0x0c
} BVLC_SC_MESSAGE_TYPE;
/*
* AB.2.2 Control Flags
*/
#define BVLC_SC_CONTROL_DATA_OPTIONS (1 << 0)
#define BVLC_SC_CONTROL_DEST_OPTIONS (1 << 1)
#define BVLC_SC_CONTROL_DEST_VADDR (1 << 2)
#define BVLC_SC_CONTROL_ORIG_VADDR (1 << 3)
/*
* AB.2.3 Header Options
*/
#define BVLC_SC_HEADER_DATA (1 << 5)
#define BVLC_SC_HEADER_MUST_UNDERSTAND (1 << 6)
#define BVLC_SC_HEADER_MORE (1 << 7)
#define BVLC_SC_HEADER_OPTION_TYPE_MASK (0x1F)
/**
* BACnet SC VMAC Address
*
* B.1.5.2 VMAC Addressing of Nodes
* For the BVLC message exchange, BACnet/SC nodes are identified by their
* 6-octet virtual MAC address as defined in Clause H.7.3.
* For broadcast BVLC messages that need to reach all nodes of the BACnet/SC
* network, the destination VMAC address shall be the non-EUI-48 value
* X'FFFFFFFFFFFF', referred to as the Local Broadcast VMAC address.
* The reserved EUI-48 value X'000000000000' is not used by this data link
* and therefore can be used internally to indicate that a VMAC is unknown
* or uninitialized.
* @{
*/
typedef struct BACnet_SC_VMAC_Address {
uint8_t address[BVLC_SC_VMAC_SIZE];
} BACNET_SC_VMAC_ADDRESS;
/** @} */
/**
* BACnet SC UUID
* AB.1.5.3 Device UUID
* Every BACnet device that supports one or more BACnet/SC network ports shall
* have a Universally Unique ID (UUID) as defined in RFC 4122. This UUID
* identifies the device regardless of its current VMAC address or device
* instance number and is referred to as the device UUID.
* This device UUID shall be generated before first deployment of the device in
* an installation, shall be persistently stored across device restarts, and
* shall not change over the entire lifetime of a device.
* If a device is replaced in an installation, the new device is not required
* to reuse the UUID of the replaced device. For BACnet/SC, it is assumed
* that existing connections to the device being replaced are all terminated
* before the new device comes into operation.
* @{
*/
typedef struct BACnet_SC_Uuid {
uint8_t uuid[BVLC_SC_UUID_SIZE];
} BACNET_SC_UUID;
/** @} */
/*
* AB.2.3.1 Secure Path Header Option
*/
typedef enum BVLC_SC_Option_Type {
BVLC_SC_OPTION_TYPE_SECURE_PATH = 1,
BVLC_SC_OPTION_TYPE_PROPRIETARY = 31
} BVLC_SC_OPTION_TYPE;
typedef enum BVLC_SC_Direct_Connection_Support {
BVLC_SC_DIRECT_CONNECTION_ACCEPT_UNSUPPORTED = 0,
BVLC_SC_DIRECT_CONNECTION_ACCEPT_SUPPORTED = 1,
BVLC_SC_DIRECT_CONNECTION_SUPPORT_MAX = 1
} BVLC_SC_DIRECT_CONNECTION_SUPPORT;
typedef struct BVLC_SC_Decoded_Hdr {
uint8_t bvlc_function;
uint16_t message_id;
BACNET_SC_VMAC_ADDRESS *origin;
BACNET_SC_VMAC_ADDRESS *dest;
uint8_t *dest_options; /* pointer to packed dest options list in message */
size_t dest_options_len;
size_t dest_options_num; /* number of filled items in dest_options */
uint8_t *data_options; /* pointer to packed data options list in message */
size_t data_options_len;
size_t data_options_num; /* number of filled items in data_options */
uint8_t *payload; /* packed payload, points to data in message */
size_t payload_len;
} BVLC_SC_DECODED_HDR;
typedef struct BVLC_SC_Decoded_Result {
uint8_t bvlc_function;
uint8_t result;
uint8_t error_header_marker;
uint16_t error_class;
uint16_t error_code;
uint8_t *utf8_details_string; /* NOTE!: this is utf 8 string without
trailing zero */
size_t utf8_details_string_len;
} BVLC_SC_DECODED_RESULT;
typedef struct BVLC_SC_Decoded_Address_Resolution_Ack {
uint8_t *utf8_websocket_uri_string; /* NOTE!: this is utf 8 string without
trailing zero */
size_t utf8_websocket_uri_string_len;
} BVLC_SC_DECODED_ADDRESS_RESOLUTION_ACK;
typedef struct BVLC_SC_Decoded_Ecapsulated_NPDU {
uint8_t *npdu;
size_t npdu_len;
} BVLC_SC_DECODED_ENCAPSULATED_NPDU;
typedef struct BVLC_SC_Decoded_Advertisiment {
BACNET_SC_HUB_CONNECTOR_STATE hub_status;
BVLC_SC_DIRECT_CONNECTION_SUPPORT support;
uint16_t max_bvlc_len;
uint16_t max_npdu_len;
} BVLC_SC_DECODED_ADVERTISIMENT;
typedef struct BVLC_SC_Decoded_Connect_Request {
BACNET_SC_VMAC_ADDRESS *vmac;
BACNET_SC_UUID *uuid;
uint16_t max_bvlc_len;
uint16_t max_npdu_len;
} BVLC_SC_DECODED_CONNECT_REQUEST;
typedef struct BVLC_SC_Decoded_Connect_Accept {
BACNET_SC_VMAC_ADDRESS *vmac;
BACNET_SC_UUID *uuid;
uint16_t max_bvlc_len;
uint16_t max_npdu_len;
} BVLC_SC_DECODED_CONNECT_ACCEPT;
typedef struct BVLC_SC_Decoded_Proprietary {
uint16_t vendor_id;
uint8_t function;
uint8_t *data;
size_t data_len;
} BVLC_SC_DECODED_PROPRIETARY;
typedef union BVLC_SC_Decoded_Data {
BVLC_SC_DECODED_RESULT result;
BVLC_SC_DECODED_ENCAPSULATED_NPDU encapsulated_npdu;
BVLC_SC_DECODED_ADDRESS_RESOLUTION_ACK address_resolution_ack;
BVLC_SC_DECODED_ADVERTISIMENT advertisiment;
BVLC_SC_DECODED_CONNECT_REQUEST connect_request;
BVLC_SC_DECODED_CONNECT_ACCEPT connect_accept;
BVLC_SC_DECODED_PROPRIETARY proprietary;
} BVLC_SC_DECODED_DATA;
typedef struct BVLC_SC_Decoded_Hdr_Proprietary_Option {
uint16_t vendor_id;
uint8_t option_type;
uint8_t *data;
size_t data_len;
} BVLC_SC_DECODED_HDR_PROPRIETARY_OPTION;
typedef union BVLC_SC_Decoded_Specific_Option_Data {
BVLC_SC_DECODED_HDR_PROPRIETARY_OPTION proprietary;
} BVLC_SC_DECODED_SPECIFIC_OPTION_DATA;
typedef struct BVLC_SC_Decoded_Hdr_Option {
uint8_t packed_header_marker;
BVLC_SC_OPTION_TYPE type;
bool must_understand;
BVLC_SC_DECODED_SPECIFIC_OPTION_DATA specific;
} BVLC_SC_DECODED_HDR_OPTION;
typedef struct BVLC_SC_Decoded_Message {
BVLC_SC_DECODED_HDR hdr;
BVLC_SC_DECODED_HDR_OPTION data_options[BVLC_SC_HEADER_OPTION_MAX];
BVLC_SC_DECODED_HDR_OPTION dest_options[BVLC_SC_HEADER_OPTION_MAX];
BVLC_SC_DECODED_DATA payload;
} BVLC_SC_DECODED_MESSAGE;
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
BACNET_STACK_EXPORT
size_t bvlc_sc_add_option_to_destination_options(
uint8_t *out_pdu,
size_t out_pdu_size,
uint8_t *pdu,
size_t pdu_size,
uint8_t *sc_option,
size_t sc_option_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_add_option_to_data_options(
uint8_t *out_pdu,
size_t out_pdu_size,
uint8_t *pdu,
size_t pdu_size,
uint8_t *sc_option,
size_t sc_option_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_proprietary_option(
uint8_t *pdu,
size_t pdu_size,
bool must_understand,
uint16_t vendor_id,
uint8_t proprietary_option_type,
uint8_t *proprietary_data,
size_t proprietary_data_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_secure_path_option(
uint8_t *pdu, size_t pdu_size, bool must_understand);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_result(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t bvlc_function,
uint8_t result_code,
uint8_t *error_header_marker,
uint16_t *error_class,
uint16_t *error_code,
uint8_t *utf8_details_string);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_encapsulated_npdu(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *npdu,
size_t npdu_size);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_address_resolution(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_address_resolution_ack(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint8_t *web_socket_uris,
size_t web_socket_uris_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_advertisiment(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
BACNET_SC_HUB_CONNECTOR_STATE hub_status,
BVLC_SC_DIRECT_CONNECTION_SUPPORT support,
uint16_t max_bvlc_len,
uint16_t max_npdu_size);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_advertisiment_solicitation(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_connect_request(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *local_vmac,
BACNET_SC_UUID *local_uuid,
uint16_t max_bvlc_len,
uint16_t max_npdu_size);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_connect_accept(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *local_vmac,
BACNET_SC_UUID *local_uuid,
uint16_t max_bvlc_len,
uint16_t max_npdu_len);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_disconnect_request(
uint8_t *pdu, size_t pdu_len, uint16_t message_id);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_disconnect_ack(
uint8_t *pdu, size_t pdu_len, uint16_t message_id);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_heartbeat_request(
uint8_t *out_buf, size_t out_buf_len, uint16_t message_id);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_heartbeat_ack(
uint8_t *out_buf, size_t out_buf_len, uint16_t message_id);
BACNET_STACK_EXPORT
size_t bvlc_sc_encode_proprietary_message(
uint8_t *pdu,
size_t pdu_len,
uint16_t message_id,
BACNET_SC_VMAC_ADDRESS *origin,
BACNET_SC_VMAC_ADDRESS *dest,
uint16_t vendor_id,
uint8_t proprietary_function,
uint8_t *proprietary_data,
size_t proprietary_data_len);
BACNET_STACK_EXPORT
bool bvlc_sc_decode_message(
uint8_t *buf,
size_t buf_len,
BVLC_SC_DECODED_MESSAGE *message,
BACNET_ERROR_CODE *error,
BACNET_ERROR_CLASS *class,
const char **err_desc);
BACNET_STACK_EXPORT
void bvlc_sc_remove_dest_set_orig(
uint8_t *pdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *orig);
BACNET_STACK_EXPORT
size_t
bvlc_sc_set_orig(uint8_t **ppdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *orig);
BACNET_STACK_EXPORT
bool bvlc_sc_is_vmac_broadcast(BACNET_SC_VMAC_ADDRESS *vmac);
BACNET_STACK_EXPORT
bool bvlc_sc_need_send_bvlc_result(BVLC_SC_DECODED_MESSAGE *dm);
BACNET_STACK_EXPORT
bool bvlc_sc_pdu_has_dest_broadcast(uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
bool bvlc_sc_pdu_has_no_dest(uint8_t *pdu, size_t pdu_len);
BACNET_STACK_EXPORT
bool bvlc_sc_pdu_get_dest(
uint8_t *pdu, size_t pdu_len, BACNET_SC_VMAC_ADDRESS *vmac);
BACNET_STACK_EXPORT
size_t bvlc_sc_remove_orig_and_dest(uint8_t **ppdu, size_t pdu_len);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+450
View File
@@ -0,0 +1,450 @@
/**
* @file
* @brief Client/Server thread-safe websocket interface API.
* @author Kirill Neznamov <kirill.neznamov@dsr-corporation.com>
* @date May 2022
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_DATALINK_BSC_WEBSOCKET_H
#define BACNET_DATALINK_BSC_WEBSOCKET_H
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datalink/bsc/bsc-conf.h"
/**
* Maximum number of sockets that can be opened on client's side.
* @{
*/
#ifndef BSC_CONF_CLIENT_CONNECTIONS_NUM
#define BSC_CLIENT_WEBSOCKETS_MAX_NUM 4
#else
#define BSC_CLIENT_WEBSOCKETS_MAX_NUM BSC_CONF_CLIENT_CONNECTIONS_NUM
#endif
/** @} */
/**
* Maximum number of server instances. Value 10 means that
* 10 hub servers and 10 direct servers can be started.
* @{
*/
#ifndef BSC_CONF_WEBSOCKET_SERVERS_NUM
#define BSC_CONF_WEBSOCKET_SERVERS_NUM 1
#endif
/** @} */
/** @} */
/**
* Maximum number of sockets supported for hub websocket server
* @{
*/
#ifndef BSC_CONF_SERVER_HUB_CONNECTIONS_MAX_NUM
#define BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM 4
#else
#define BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM \
BSC_CONF_SERVER_HUB_CONNECTIONS_MAX_NUM
#endif
/** @} */
/**
* Initial size of websocket buffer used on receive. It can be increased
* dynamically depending on received packet size.
* @{
*/
#ifndef BSC_CONF_WEBSOCKET_RX_BUFFER_LEN
#define BSC_WEBSOCKET_RX_BUFFER_LEN 512
#else
#define BSC_WEBSOCKET_RX_BUFFER_LEN BSC_CONF_WEBSOCKET_RX_BUFFER_LEN
#endif
/** @} */
/**
* Maximum number of sockets supported for direct websocket server
* @{
*/
#ifndef BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM
#define BSC_SERVER_DIRECT_WEBSOCKETS_MAX_NUM 4
#else
#define BSC_SERVER_DIRECT_WEBSOCKETS_MAX_NUM \
BSC_CONF_SERVER_DIRECT_CONNECTIONS_MAX_NUM
#endif
#ifndef BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN
#define BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN 128
#else
#define BSC_WEBSOCKET_ERR_DESC_STR_MAX_LEN \
BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN
#endif
#define BSC_WSURL_MAX_LEN BSC_CONF_WSURL_MAX_LEN
typedef int BSC_WEBSOCKET_HANDLE;
typedef void *BSC_WEBSOCKET_SRV_HANDLE;
#define BSC_WEBSOCKET_INVALID_HANDLE (-1)
/* Websockets protocol defined in BACnet/SC \S AB.7.1. */
#define BSC_WEBSOCKET_HUB_PROTOCOL_STR "hub.bsc.bacnet.org"
#define BSC_WEBSOCKET_DIRECT_PROTOCOL_STR "dc.bsc.bacnet.org"
typedef enum {
BSC_WEBSOCKET_HUB_PROTOCOL = 0,
BSC_WEBSOCKET_DIRECT_PROTOCOL = 1,
BSC_WEBSOCKET_PROTOCOLS_AMOUNT = 2 /* must be always last */
} BSC_WEBSOCKET_PROTOCOL;
typedef enum {
BSC_WEBSOCKET_SUCCESS = 0,
BSC_WEBSOCKET_NO_RESOURCES = 1,
BSC_WEBSOCKET_BAD_PARAM = 2,
BSC_WEBSOCKET_INVALID_OPERATION = 3
} BSC_WEBSOCKET_RET;
typedef enum {
BSC_WEBSOCKET_CONNECTED = 0,
BSC_WEBSOCKET_DISCONNECTED = 1,
BSC_WEBSOCKET_RECEIVED = 2,
BSC_WEBSOCKET_SENDABLE = 3,
BSC_WEBSOCKET_SERVER_STARTED = 4,
BSC_WEBSOCKET_SERVER_STOPPED = 5
} BSC_WEBSOCKET_EVENT;
/*
values of ws_reason and ws_reason_desc parameters are actual
only for BSC_WEBSOCKET_DISCONNECTED event.
*/
typedef void (*BSC_WEBSOCKET_CLI_DISPATCH)(
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);
typedef void (*BSC_WEBSOCKET_SRV_DISPATCH)(
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);
/**
* @brief Asynchronous bws_cli_сonnect() function starts establishing
* of a new connection to a websocket server specified by url parameter.
* Result of completion of operation is call of dispatch_func() with
* BSC_WEBSOCKET_CONNECTED in a case if connection established successfully or
* BSC_WEBSOCKET_DISCONNECTED if connection attempt failed.
*
* @param type - type of BACNet/SC connection, check
* BSC_WEBSOCKET_CONNECTION_TYPE enum. According BACNet standard
* different type of connections require different websocket protocols.
* @param url - BACNet/SC server URL. For example: wss://legrand.com:8080.
* @param ca_cert - pointer to certificate authority (CA) cert in PEM or DER
* format.
* @param ca_cert_size - size in bytes of CA cert.
* @param cert - pointer to client certificate in PEM or DER format.
* @param cert_size - size in bytes of client certificate.
* @param key - pointer to client private key in PEM or DER format.
* @param key_size - size of private key in bytes of of client certificate.
* @param timeout_s - timeout for socket operations (connect, etc..) in seconds.
* Must be > 0.
* @param dispatch_func - pointer to dispatch callback function to handle
* events from websocket specified by *out_handle.
* @param dispatch_func_user_param - parameter which is passed into
dispatch_func call.
* @param out_handle - pointer to a websocket handle.
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_WEBSOCKET_NO_RESOURCES - if a user has already opened
* more sockets than the limit defined by BSC_CLIENT_WEBSOCKETS_MAX_NUM,
* or if some mem allocation has failed or some allocation of system
* resources like mutex, thread, etc .., failed.
* BSC_WEBSOCKET_SUCCESS - connect operation was successfully started.
*/
BSC_WEBSOCKET_RET bws_cli_connect(
BSC_WEBSOCKET_PROTOCOL proto,
char *url,
uint8_t *ca_cert,
size_t ca_cert_size,
uint8_t *cert,
size_t cert_size,
uint8_t *key,
size_t key_size,
size_t timeout_s,
BSC_WEBSOCKET_CLI_DISPATCH dispatch_func,
void *dispatch_func_user_param,
BSC_WEBSOCKET_HANDLE *out_handle);
/**
* @brief Asynchronous bws_cli_disconnnect() function starts process of
* disconnection for specified websocket handle. When the process completes,
* dispatch_func() with event BSC_WEBSOCKET_DISCONNECTED is called.
* connection to some websocket server.
*
* @param h - websocket handle.
*
*/
void bws_cli_disconnect(BSC_WEBSOCKET_HANDLE h);
/**
* @brief Non-blocked bws_cli_send() function signals to the websocket
* specified by websocket handle h that some data is needed to be sent.
* When websocket becomes sendable, dispatch_func() is called with
* event BSC_WEBSOCKET_SENDABLE and data can be sent from dispatch_func()
* call using bws_cli_dispatch_send() call.
*
* @param h - websocket handle.
*
*/
void bws_cli_send(BSC_WEBSOCKET_HANDLE h);
/**
* @brief bws_cli_dispatch_send() function sends data to a websocket server
* in a case if websocket handle is sendable (e.g. ready to send data).
* In as case if data was not sent for some reasons thic could result
* dispatch_func() cal with event BSC_WEBSOCKET_DISCONNECTED
* @param h - websocket handle.
* @param payload - pointer to a data to send. It is assumed that
* BSC_CONF_TX_PRE bytes are available before payload.
* So for example you need this kind of code to use
* bws_srv_dispatch_send with a 128-byte payload:
*
* char buf[BSC_CONF_TX_PRE + 128];
*
* // fill your part of the buffer... for example here
* // it's all zeros
*
* memset(&buf[BSC_CONF_TX_PRE], 0, 128);
* bws_cli_dispatch_send(sh, h, &buf[BSC_CONF_TX_PRE], 128);
*
* @param payload_size - size in bytes of data to send.
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_WEBSOCKET_NO_RESOURCES - if some mem allocation has failed o
* some allocation of system resources like mutex, thread,
* etc .., has failed.
* BSC_WEBSOCKET_INVALID_OPERATION - if the function was called not from
* dispatch_func() callback context or websocket is not in connected
* state.
* BSC_WEBSOCKET_SUCCESS - data is sent successfully.
*/
BSC_WEBSOCKET_RET bws_cli_dispatch_send(
BSC_WEBSOCKET_HANDLE h, uint8_t *payload, size_t payload_size);
/**
* @brief Asynchronous bws_srv_start() function triggers process of
* starting of a websocket server on a specified port for specified
* BACNet websocket protocol. At present time peer can have only 2
* instances of server: onerelates to BSC_WEBSOCKET_HUB_PROTOCOL and
* the other to BSC_WEBSOCKET_HUB_PROTOCOL. When process completes,
* dispatch_func() is called with BSC_WEBSOCKET_SERVER_STARTED
* event.
*
* @param proto - type of BACNet websocket protocol defined in
* BSC_WEBSOCKET_PROTOCOL enum.
* @param port - port number.
* @param iface - name of interface to bind to. If the parameter is NULL
* that means that websocket server binds to all interfaces.
* @param ca_cert - pointer to certificate authority (CA) cert in PEM or DER
* format.
* @param ca_cert_size - size in bytes of CA cert.
* @param cert - pointer to server certificate in PEM or DER format.
* @param cert_size - size in bytes of server certificate.
* @param key - pointer to server private key in PEM or DER format.
* @param key_size - size of private key in bytes of of client certificate.
* @param timeout_s - timeout for socket operations (connect, etc..) in seconds.
* Must be > 0.
* @param dispatch_func - pointer to dispatch callback function to handle
* events from a websocket which is corresponded to
* server specified by proto param.
* @param dispatch_func_user_param - parameter which is passed into
* dispatch_func call.
* @param sh - pointer to receive websocket server handle
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_WEBSOCKET_NO_RESOURCES - if a user has already opened
* more sockets than the limit defined to corresponded protocol
* (BSC_SERVER_HUB_WEBSOCKETS_MAX_NUM or
* BSC_CLIENT_WEBSOCKETS_MAX_NUM), or if some mem allocation
* has failed or some allocation of system resources like
* mutex, thread, condition variable etc .., failed.
* BSC_WEBSOCKET_SUCCESS - the start operation is in progress.
* BSC_WEBSOCKET_INVALID_OPERATION - operation is not started because
* server in a process of shutdown of server is already started.
*/
BSC_WEBSOCKET_RET bws_srv_start(
BSC_WEBSOCKET_PROTOCOL proto,
int port,
char *iface,
uint8_t *ca_cert,
size_t ca_cert_size,
uint8_t *cert,
size_t cert_size,
uint8_t *key,
size_t key_size,
size_t timeout_s,
BSC_WEBSOCKET_SRV_DISPATCH dispatch_func,
void *dispatch_func_user_param,
BSC_WEBSOCKET_SRV_HANDLE *sh);
/**
* @brief Asynchronous bws_srv_stop() function starts process of a shutdowns
* of a websocket server specified by proto param.
* opened websocket connections are closed.
*
* @param sh - websocket server handle
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_SUCCESS - the operation is started or server was
* already stopped before. BSC_WEBSOCKET_INVALID_OPERATION - if server was not
* started or server shutdown is already in progress.
*/
BSC_WEBSOCKET_RET bws_srv_stop(BSC_WEBSOCKET_SRV_HANDLE sh);
/**
* @brief Asynchronous bws_srv_disconnnect() function starts process of
* disconnection for specified websocket handle h for specified server type
* by proto parameter. When the process completes, dispatch_func() with event
* BSC_WEBSOCKET_DISCONNECTED is called.
*
* @param sh - websocket server handle.
* @param h - websocket handle.
*
*/
void bws_srv_disconnect(BSC_WEBSOCKET_SRV_HANDLE sh, BSC_WEBSOCKET_HANDLE h);
/**
* @brief Asynchronous bws_srv_send() function signals to a websocket
* specified by handle h for specified server type by proto param that
* some data is needed to be sent.
*
* When websocket becomes sendable, dispatch_func() is called with
* event BSC_WEBSOCKET_SENDABLE and data can be sent from dispatch_func()
* call using bws_srv_dispatch_send() call.
*
* @param sh - websocket server handle.
* @param h - websocket handle.
*
*/
void bws_srv_send(BSC_WEBSOCKET_SRV_HANDLE sh, BSC_WEBSOCKET_HANDLE h);
/**
* @brief bws_srv_dispatch_send() function sends data to a websocket server
* in a case if websocket handle is sendable (e.g. ready to send data).
* In as case if data was not sent for some reasons thic could result
* dispatch_func() call with event BSC_WEBSOCKET_DISCONNECTED
*
* @param sh - websocket server handle.
* @param h - websocket handle.
* @param payload - pointer to a data to send. It is assumed that
* BSC_CONF_TX_PRE bytes are available before payload.
* So for example you need this kind of code to use
* bws_srv_dispatch_send with a 128-byte payload:
*
* char buf[BSC_CONF_TX_PRE + 128];
*
* // fill your part of the buffer... for example here
* // it's all zeros
*
* memset(&buf[BSC_CONF_TX_PRE], 0, 128);
* bws_srv_dispatch_send(sh, h, &buf[BSC_CONF_TX_PRE], 128);
*
* @param payload_size - size in bytes of data to send.
*
* @return error code from BSC_WEBSOCKET_RET enum.
* The following error codes can be returned:
* BSC_WEBSOCKET_BAD_PARAM - In a case if some input parameter is
* incorrect.
* BSC_WEBSOCKET_NO_RESOURCES - if some mem allocation has failed o
* some allocation of system resources like mutex, thread,
* etc .., has failed.
* BSC_WEBSOCKET_INVALID_OPERATION - if the function was called not from
* dispatch_func() callback context or websocket is not in connected
* state or server in a process of a shutdown or server is not started.
* BSC_WEBSOCKET_SUCCESS - data is sent successfully.
*/
BSC_WEBSOCKET_RET bws_srv_dispatch_send(
BSC_WEBSOCKET_SRV_HANDLE sh,
BSC_WEBSOCKET_HANDLE h,
uint8_t *payload,
size_t payload_size);
/**
* @brief bws_dispatch_lock() function acquires internal websocket
* global mutex. As a result dispatch_func() callback for all
* websocket server and client instances won't be called until user
* calls bws_dispatch_unlock().
*/
#ifndef BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED
#define BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED 0
#endif
#if (BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED != 1)
void bws_dispatch_lock(void);
#else
extern void bws_dispatch_lock_dbg(char *f, int line);
#define bws_dispatch_lock() bws_dispatch_lock_dbg(__FILE__, __LINE__)
#endif
/**
* @brief bws_dispatch_unlock() function releases internal websocket
* global mutex.
*/
#if (BSC_DEBUG_WEBSOCKET_MUTEX_ENABLED != 1)
void bws_dispatch_unlock(void);
#else
extern void bws_dispatch_unlock_dbg(char *f, int line);
#define bws_dispatch_unlock() bws_dispatch_unlock_dbg(__FILE__, __LINE__)
#endif
/**
* @brief bws_srv_get_peer_ip_addr() gets ipv4 or ipv6 address as ANSI string
* and port of remote peer.
*
* @param sh - websocket server handle.
* @param h - websocket handle.
* @param ip_str - buffer to store null terminated string of ip address.
* @param ip_str_len - size of ip_str buffer
* @param port- pointer to store port of a remote node.
*
* @return true if function succeeded otherwise returns false
* if peer's address information can't be retrieved from
* underlying websocket library.
*/
bool bws_srv_get_peer_ip_addr(
BSC_WEBSOCKET_SRV_HANDLE sh,
BSC_WEBSOCKET_HANDLE h,
uint8_t *ip_str,
size_t ip_str_len,
uint16_t *port);
#endif
+50 -1
View File
@@ -29,6 +29,9 @@
#if defined(BACDL_MSTP)
#include "bacnet/datalink/dlmstp.h"
#endif
#if defined(BACDL_BSC)
#include "bacnet/datalink/bsc/bsc-datalink.h"
#endif
#ifdef HAVE_STRINGS_H
#include <strings.h> /* for strcasecmp() */
#endif
@@ -39,7 +42,8 @@ static enum {
DATALINK_ETHERNET,
DATALINK_BIP,
DATALINK_BIP6,
DATALINK_MSTP
DATALINK_MSTP,
DATALINK_BSC
} Datalink_Transport;
void datalink_set(char *datalink_string)
@@ -72,6 +76,11 @@ void datalink_set(char *datalink_string)
Datalink_Transport = DATALINK_MSTP;
}
#endif
#if defined(BACDL_BSC)
else if (strcasecmp("bsc", datalink_string) == 0) {
Datalink_Transport = DATALINK_BSC;
}
#endif
}
bool datalink_init(char *ifname)
@@ -106,6 +115,11 @@ bool datalink_init(char *ifname)
case DATALINK_MSTP:
status = dlmstp_init(ifname);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
status = bsc_init(ifname);
break;
#endif
default:
break;
@@ -150,6 +164,11 @@ int datalink_send_pdu(
case DATALINK_MSTP:
bytes = dlmstp_send_pdu(dest, npdu_data, pdu, pdu_len);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bytes = bsc_send_pdu(dest, npdu_data, pdu, pdu_len);
break;
#endif
default:
break;
@@ -190,6 +209,11 @@ uint16_t datalink_receive(
case DATALINK_MSTP:
bytes = dlmstp_receive(src, pdu, max_pdu, timeout);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bytes = bsc_receive(src, pdu, max_pdu, timeout);
break;
#endif
default:
break;
@@ -227,6 +251,11 @@ void datalink_cleanup(void)
case DATALINK_MSTP:
dlmstp_cleanup();
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bsc_cleanup();
break;
#endif
default:
break;
@@ -262,6 +291,11 @@ void datalink_get_broadcast_address(BACNET_ADDRESS *dest)
case DATALINK_MSTP:
dlmstp_get_broadcast_address(dest);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bsc_get_broadcast_address(dest);
break;
#endif
default:
break;
@@ -297,6 +331,11 @@ void datalink_get_my_address(BACNET_ADDRESS *my_address)
case DATALINK_MSTP:
dlmstp_get_my_address(my_address);
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bsc_get_my_address(my_address);
break;
#endif
default:
break;
@@ -333,6 +372,11 @@ void datalink_set_interface(char *ifname)
case DATALINK_MSTP:
(void)ifname;
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
(void)ifname;
break;
#endif
default:
break;
@@ -365,6 +409,11 @@ void datalink_maintenance_timer(uint16_t seconds)
#if defined(BACDL_MSTP)
case DATALINK_MSTP:
break;
#endif
#if defined(BACDL_BSC)
case DATALINK_BSC:
bsc_maintenance_timer(seconds);
break;
#endif
default:
break;
+16
View File
@@ -32,6 +32,11 @@
#include "bacnet/basic/bbmd6/h_bbmd6.h"
#endif
#if defined(BACDL_BSC)
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bsc-datalink.h"
#endif
#if defined(BACDL_ETHERNET) && !defined(BACDL_MULTIPLE)
#define MAX_MPDU ETHERNET_MPDU_MAX
@@ -99,6 +104,17 @@ void routed_get_my_address(BACNET_ADDRESS *my_address);
#define datalink_get_my_address bip6_get_my_address
#define datalink_maintenance_timer(s) bvlc6_maintenance_timer(s)
#elif defined(BACDL_BSC) && !defined(BACDL_MULTIPLE)
#define MAX_MPDU BVLC_SC_NPDU_SIZE_CONF
#define datalink_init bsc_init
#define datalink_send_pdu bsc_send_pdu
#define datalink_receive bsc_receive
#define datalink_cleanup bsc_cleanup
#define datalink_get_broadcast_address bsc_get_broadcast_address
#define datalink_get_my_address bsc_get_my_address
#define datalink_maintenance_timer(s) bsc_maintenance_timer(s)
#elif !defined(BACDL_TEST) /* Multiple, none or custom datalink */
#include "bacnet/npdu.h"
+230
View File
@@ -26,6 +26,14 @@
#if (BACNET_PROTOCOL_REVISION >= 17)
#include "bacnet/basic/object/netport.h"
#endif
#if defined(BACDL_BSC)
#include "bacnet/basic/object/bacfile.h"
#include "bacnet/basic/object/sc_netport.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#include "bacnet/datalink/bsc/bsc-util.h"
#include "bacnet/datalink/bsc/bsc-datalink.h"
#include "bacnet/datalink/bsc/bsc-event.h"
#endif
/** @file dlenv.c Initialize the DataLink configuration. */
/* timer used to renew Foreign Device Registration */
@@ -459,6 +467,179 @@ void dlenv_network_port_init(void)
since they are already set */
Network_Port_Changes_Pending_Set(instance, false);
}
#elif defined(BACDL_BSC)
/**
* @brief Datalink network port object settings
* @param primary_hub_uri
* @param failover_hub_uri
* @param filename_ca_1_cert
* @param filename_ca_2_cert
* @param filename_cert
* @param filename_key
* @param direct_connect_port
* @param hub_function_port
* @param direct_connect_initiate
* @param direct_connect_accept_urls
*/
static void bacnet_secure_connect_network_port_init(
char *primary_hub_uri,
char *failover_hub_uri,
char *filename_ca_1_cert,
char *filename_ca_2_cert,
char *filename_cert,
char *filename_key,
char *direct_binding,
char *hub_binding,
char *direct_connect_initiate,
char *direct_connect_accept_urls)
{
const uint32_t instance = 1;
BACNET_SC_UUID uuid = { 0 };
BACNET_SC_VMAC_ADDRESS vmac = { 0 };
long seed;
char c;
seed = (long)&instance;
srand((int)seed);
Network_Port_Object_Instance_Number_Set(0, instance);
Network_Port_Name_Set(instance, "BACnet/BSC Port");
Network_Port_Type_Set(instance, PORT_TYPE_BSC);
bsc_generate_random_uuid(&uuid);
Network_Port_SC_Local_UUID_Set(instance, (BACNET_UUID *)&uuid);
bsc_generate_random_vmac(&vmac);
Network_Port_MAC_Address_Set(instance, vmac.address, sizeof(vmac));
/* common NP data */
Network_Port_Reliability_Set(instance, RELIABILITY_NO_FAULT_DETECTED);
Network_Port_Out_Of_Service_Set(instance, false);
Network_Port_Quality_Set(instance, PORT_QUALITY_UNKNOWN);
Network_Port_APDU_Length_Set(instance, MAX_APDU);
Network_Port_Network_Number_Set(instance, 0);
/* SC parameters */
Network_Port_Max_BVLC_Length_Accepted_Set(instance, SC_NETPORT_BVLC_MAX);
Network_Port_Max_NPDU_Length_Accepted_Set(instance, SC_NETPORT_NPDU_MAX);
Network_Port_SC_Connect_Wait_Timeout_Set(
instance, SC_NETPORT_CONNECT_TIMEOUT);
Network_Port_SC_Heartbeat_Timeout_Set(
instance, SC_NETPORT_HEARTBEAT_TIMEOUT);
Network_Port_SC_Disconnect_Wait_Timeout_Set(
instance, SC_NETPORT_DISCONNECT_TIMEOUT);
Network_Port_SC_Maximum_Reconnect_Time_Set(
instance, SC_NETPORT_RECONNECT_TIME);
if (filename_ca_1_cert == NULL) {
fprintf(stderr, "BACNET_SC_ISSUER_1_CERTIFICATE_FILE must be set\n");
return;
}
bacfile_create(BSC_ISSUER_CERTIFICATE_FILE_1_INSTANCE);
bacfile_pathname_set(
BSC_ISSUER_CERTIFICATE_FILE_1_INSTANCE, filename_ca_1_cert);
Network_Port_Issuer_Certificate_File_Set(
instance, 0, BSC_ISSUER_CERTIFICATE_FILE_1_INSTANCE);
if (filename_ca_2_cert) {
bacfile_create(BSC_ISSUER_CERTIFICATE_FILE_2_INSTANCE);
bacfile_pathname_set(
BSC_ISSUER_CERTIFICATE_FILE_2_INSTANCE, filename_ca_2_cert);
Network_Port_Issuer_Certificate_File_Set(
instance, 1, BSC_ISSUER_CERTIFICATE_FILE_2_INSTANCE);
}
if (filename_cert == NULL) {
fprintf(stderr, "BACNET_SC_OPERATIONAL_CERTIFICATE_FILE must be set\n");
return;
}
bacfile_create(BSC_OPERATIONAL_CERTIFICATE_FILE_INSTANCE);
bacfile_pathname_set(
BSC_OPERATIONAL_CERTIFICATE_FILE_INSTANCE, filename_cert);
Network_Port_Operational_Certificate_File_Set(
instance, BSC_OPERATIONAL_CERTIFICATE_FILE_INSTANCE);
if (filename_key == NULL) {
fprintf(
stderr,
"BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE must be set\n");
return;
}
bacfile_create(BSC_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE);
bacfile_pathname_set(
BSC_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE, filename_key);
Network_Port_Certificate_Key_File_Set(
instance, BSC_CERTIFICATE_SIGNING_REQUEST_FILE_INSTANCE);
if ((primary_hub_uri == NULL) && (failover_hub_uri == NULL) &&
(direct_binding == NULL) && (hub_binding == NULL)) {
fprintf(
stderr,
"At least must be set:\n"
"BACNET_SC_HUB_FUNCTION_BINDING for HUB or\n"
"BACNET_SC_PRIMARY_HUB_URI and BACNET_SC_FAILOVER_HUB_URI for node "
"or\n"
"BACNET_SC_DIRECT_CONNECT_BINDING for direct connect.\n");
return;
}
Network_Port_SC_Primary_Hub_URI_Set(instance, primary_hub_uri);
Network_Port_SC_Failover_Hub_URI_Set(instance, failover_hub_uri);
Network_Port_SC_Direct_Connect_Binding_Set(instance, direct_binding);
Network_Port_SC_Direct_Connect_Accept_Enable_Set(
instance, direct_binding != NULL);
c = direct_connect_initiate ? direct_connect_initiate[0] : '0';
if ((c != '0') && (c != 'n') && (c != 'N')) {
Network_Port_SC_Direct_Connect_Initiate_Enable_Set(instance, true);
} else {
Network_Port_SC_Direct_Connect_Initiate_Enable_Set(instance, false);
}
Network_Port_SC_Direct_Connect_Accept_URIs_Set(
instance, direct_connect_accept_urls);
/* HUB parameters */
Network_Port_SC_Hub_Function_Binding_Set(instance, hub_binding);
Network_Port_SC_Hub_Function_Enable_Set(instance, hub_binding != NULL);
/* last thing - clear pending changes - we don't want to set these
since they are already set */
Network_Port_Changes_Pending_Set(instance, false);
}
static bool dlenv_hub_connection_status_check(void)
{
uint32_t instance = Network_Port_Index_To_Instance(0);
BACNET_SC_HUB_CONNECTION_STATUS *status;
status = Network_Port_SC_Primary_Hub_Connection_Status(instance);
if (status && status->State == BACNET_SC_CONNECTION_STATE_CONNECTED) {
return true;
}
status = Network_Port_SC_Failover_Hub_Connection_Status(instance);
if (status && status->State == BACNET_SC_CONNECTION_STATE_CONNECTED) {
return true;
}
return false;
}
/**
* Datalink network port object settings for BACnet/SC
*/
void dlenv_network_port_init(void)
{
/* if a user has configured BACnet/SC port with primary hub URI, */
/* wait for a establishin of a connection to BACnet/SC hub at first */
/* to reduce possibility of packet losses. */
if (Network_Port_SC_Primary_Hub_URI_char(1)) {
while (!dlenv_hub_connection_status_check()) {
bsc_wait(1);
bsc_maintenance_timer(1);
}
}
}
#else
/**
* Datalink network port object settings
@@ -548,6 +729,24 @@ void dlenv_maintenance_timer(uint16_t elapsed_seconds)
* - BACNET_BIP6_PORT - UDP/IP port number (0..65534) used for BACnet/IPv6
* communications. Default is 47808 (0xBAC0).
* - BACNET_BIP6_BROADCAST - FF05::BAC0 or FF02::BAC0 or ...
* - BACDL_BSC: (BACnet Secure Connect)
* - BACNET_SC_PRIMARY_HUB_URI
* - BACNET_SC_FAILOVER_HUB_URI
* - BACNET_SC_ISSUER_1_CERTIFICATE_FILE
* - BACNET_SC_ISSUER_2_CERTIFICATE_FILE
* - BACNET_SC_OPERATIONAL_CERTIFICATE_FILE
* - BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE
* - BACNET_SC_DIRECT_CONNECT_BINDING - pair: interface name (optional) and
* TCP/IP port number (0..65534), like "50000" (port only) or
* "eth0:50000"(both)
* - BACNET_SC_HUB_FUNCTION_BINDING - pair: interface name (optional) and
* TCP/IP port number (0..65534), like "50000" (port only) or
* "eth0:50000"(both)
* - BACNET_SC_DIRECT_CONNECT_INITIATE - if true equal "1", "y", "Y",
* otherwise false
* - BACNET_SC_DIRECT_CONNECT_ACCEPT_URLS - list of direct connect accept URLs
* separated by a space character, e.g.
* "wss://192.0.0.1:40000 wss://192.0.0.2:6666"
*/
void dlenv_init(void)
{
@@ -574,6 +773,8 @@ void dlenv_init(void)
datalink_set("ethernet");
#elif defined(BACDL_ARCNET)
datalink_set("arcnet");
#elif defined(BACDL_BSC)
datalink_set("bsc");
#else
datalink_set("none");
#endif
@@ -665,6 +866,35 @@ void dlenv_init(void)
} else {
dlmstp_set_mac_address(127);
}
#elif defined(BACDL_BSC)
char *primary_hub_uri;
char *failover_hub_uri;
char *filename_ca_1_cert;
char *filename_ca_2_cert;
char *filename_cert;
char *filename_key;
char *direct_binding;
char *hub_binding;
char *direct_connect_initiate;
char *direct_connect_accept_urls;
primary_hub_uri = getenv("BACNET_SC_PRIMARY_HUB_URI");
failover_hub_uri = getenv("BACNET_SC_FAILOVER_HUB_URI");
filename_ca_1_cert = getenv("BACNET_SC_ISSUER_1_CERTIFICATE_FILE");
filename_ca_2_cert = getenv("BACNET_SC_ISSUER_2_CERTIFICATE_FILE");
filename_cert = getenv("BACNET_SC_OPERATIONAL_CERTIFICATE_FILE");
filename_key = getenv("BACNET_SC_OPERATIONAL_CERTIFICATE_PRIVATE_KEY_FILE");
direct_binding = getenv("BACNET_SC_DIRECT_CONNECT_BINDING");
hub_binding = getenv("BACNET_SC_HUB_FUNCTION_BINDING");
direct_connect_initiate = getenv("BACNET_SC_DIRECT_CONNECT_INITIATE");
direct_connect_accept_urls = getenv("BACNET_SC_DIRECT_CONNECT_ACCEPT_URLS");
bacnet_secure_connect_network_port_init(
primary_hub_uri, failover_hub_uri, filename_ca_1_cert,
filename_ca_2_cert, filename_cert, filename_key, direct_binding,
hub_binding, direct_connect_initiate, direct_connect_accept_urls);
if (!bsc_cert_files_check()) {
exit(1);
}
#endif
pEnv = getenv("BACNET_APDU_TIMEOUT");
if (pEnv) {
+2
View File
@@ -391,6 +391,8 @@ static const int Properties_BACnetARRAY[] = {
PROP_WEEKLY_SCHEDULE,
PROP_EXCEPTION_SCHEDULE,
PROP_TAGS,
PROP_ISSUER_CERTIFICATE_FILES,
PROP_SC_HUB_FUNCTION_ACCEPT_URIS,
-1
};
File diff suppressed because it is too large Load Diff
+175
View File
@@ -0,0 +1,175 @@
/**
* @file
* @brief BACnet Secure Connect encode and decode functions.
* @author Mikhail Antropov <michail.antropov@dsr-corporation.com>
* @date 2023
* @copyright SPDX-License-Identifier: MIT
*/
#ifndef BACNET_SECURE_CONNECT_H
#define BACNET_SECURE_CONNECT_H
#include <stdbool.h>
#include <stdint.h>
/* BACnet Stack defines - first */
#include "bacnet/bacdef.h"
/* BACnet Stack API */
#include "bacnet/datetime.h"
#include "bacnet/datalink/bsc/bsc-conf.h"
#include "bacnet/datalink/bsc/bvlc-sc.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Support
- SC_Hub_Function_Connection_Status
- SC_Direct_Connect_Connection_Status
- SC_FailedConnectionRequests
*/
typedef struct BACnetUUID_T_ {
union {
struct guid {
uint32_t time_low;
uint16_t time_mid;
uint16_t time_hi_and_version;
uint8_t clock_seq_and_node[8];
} guid;
uint8_t uuid128[16];
} uuid;
} BACNET_UUID;
#define BACNET_ERROR_STRING_LENGTH BSC_CONF_WEBSOCKET_ERR_DESC_STR_MAX_LEN
#define BACNET_URI_LENGTH BSC_CONF_WSURL_MAX_LEN
#define BACNET_PEER_VMAC_LENGTH BVLC_SC_VMAC_SIZE
enum BACNET_HOST_N_PORT_TYPE {
BACNET_HOST_N_PORT_IP = 1,
BACNET_HOST_N_PORT_HOST = 2
};
typedef struct BACnetHostNPort_data_T {
uint8_t type;
char host[BACNET_URI_LENGTH];
uint16_t port;
} BACNET_HOST_N_PORT_DATA;
typedef struct BACnetSCHubConnectionStatus_T {
BACNET_SC_CONNECTION_STATE State; /*index = 0 */
BACNET_DATE_TIME Connect_Timestamp; /*index = 1 */
BACNET_DATE_TIME Disconnect_Timestamp; /*index = 2 */
/* optionals - use ERROR_CODE_DEFAULT for default value */
BACNET_ERROR_CODE Error; /* index = 3 */
char Error_Details[BACNET_ERROR_STRING_LENGTH]; /* index = 4 */
} BACNET_SC_HUB_CONNECTION_STATUS;
typedef struct BACnetSCHubFunctionConnectionStatus_T {
BACNET_SC_CONNECTION_STATE State;
BACNET_DATE_TIME Connect_Timestamp;
BACNET_DATE_TIME Disconnect_Timestamp;
BACNET_HOST_N_PORT_DATA Peer_Address;
uint8_t Peer_VMAC[BACNET_PEER_VMAC_LENGTH];
BACNET_UUID Peer_UUID;
BACNET_ERROR_CODE Error;
char Error_Details[BACNET_ERROR_STRING_LENGTH];
} BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS;
typedef struct BACnetSCFailedConnectionRequest_T {
BACNET_DATE_TIME Timestamp;
BACNET_HOST_N_PORT_DATA Peer_Address;
uint8_t Peer_VMAC[BACNET_PEER_VMAC_LENGTH];
BACNET_UUID Peer_UUID;
BACNET_ERROR_CODE Error;
char Error_Details[BACNET_ERROR_STRING_LENGTH];
} BACNET_SC_FAILED_CONNECTION_REQUEST;
typedef enum BACnetRouterStatus {
BACNET_ROUTER_STATUS_AVAILABLE = 0,
BACNET_ROUTER_STATUS_BUSY = 1,
BACNET_ROUTER_STATUS_DISCONNECTED = 2,
BACNET_ROUTER_STATUS_MAX = 2
} BACNET_ROUTER_STATUS;
typedef struct BACnetRouterEntry_T {
uint16_t Network_Number;
uint8_t Mac_Address[6];
BACNET_ROUTER_STATUS Status;
uint8_t Performance_Index;
} BACNET_ROUTER_ENTRY;
typedef struct BACnetSCDirectConnectionStatus_T {
char URI[BACNET_URI_LENGTH];
BACNET_SC_CONNECTION_STATE State;
BACNET_DATE_TIME Connect_Timestamp;
BACNET_DATE_TIME Disconnect_Timestamp;
BACNET_HOST_N_PORT_DATA Peer_Address;
uint8_t Peer_VMAC[BACNET_PEER_VMAC_LENGTH];
BACNET_UUID Peer_UUID;
BACNET_ERROR_CODE Error;
char Error_Details[BACNET_ERROR_STRING_LENGTH];
} BACNET_SC_DIRECT_CONNECTION_STATUS;
/* */
/* Encode / decode */
/* */
BACNET_STACK_EXPORT
int bacapp_encode_SCHubConnection(
uint8_t *apdu, const BACNET_SC_HUB_CONNECTION_STATUS *value);
BACNET_STACK_EXPORT
int bacapp_decode_SCHubConnection(
const uint8_t *apdu,
size_t apdu_size,
BACNET_SC_HUB_CONNECTION_STATUS *value);
BACNET_STACK_EXPORT
int bacapp_encode_SCHubFunctionConnection(
uint8_t *apdu, const BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *value);
BACNET_STACK_EXPORT
int bacapp_decode_SCHubFunctionConnection(
const uint8_t *apdu,
size_t apdu_size,
BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *value);
BACNET_STACK_EXPORT
int bacapp_encode_SCFailedConnectionRequest(
uint8_t *apdu, const BACNET_SC_FAILED_CONNECTION_REQUEST *value);
BACNET_STACK_EXPORT
int bacapp_decode_SCFailedConnectionRequest(
const uint8_t *apdu,
size_t apdu_size,
BACNET_SC_FAILED_CONNECTION_REQUEST *value);
BACNET_STACK_EXPORT
int bacapp_encode_RouterEntry(uint8_t *apdu, const BACNET_ROUTER_ENTRY *value);
BACNET_STACK_EXPORT
int bacapp_decode_RouterEntry(
const uint8_t *apdu, size_t apdu_size, BACNET_ROUTER_ENTRY *value);
BACNET_STACK_EXPORT
int bacapp_encode_SCDirectConnection(
uint8_t *apdu, const BACNET_SC_DIRECT_CONNECTION_STATUS *value);
BACNET_STACK_EXPORT
int bacapp_decode_SCDirectConnection(
const uint8_t *apdu,
size_t apdu_size,
BACNET_SC_DIRECT_CONNECTION_STATUS *value);
BACNET_STACK_EXPORT
int bacapp_snprintf_SCFailedConnectionRequest(
char *str, size_t str_len, const BACNET_SC_FAILED_CONNECTION_REQUEST *req);
BACNET_STACK_EXPORT
int bacapp_snprintf_SCHubFunctionConnection(
char *str,
size_t str_len,
const BACNET_SC_HUB_FUNCTION_CONNECTION_STATUS *st);
BACNET_STACK_EXPORT
int bacapp_snprintf_SCDirectConnection(
char *str, size_t str_len, const BACNET_SC_DIRECT_CONNECTION_STATUS *st);
BACNET_STACK_EXPORT
int bacapp_snprintf_SCHubConnection(
char *str, size_t str_len, const BACNET_SC_HUB_CONNECTION_STATUS *st);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+25
View File
@@ -109,6 +109,7 @@ list(APPEND testdirs
bacnet/reject
bacnet/rp
bacnet/rpm
bacnet/secure_connect
bacnet/specialevent
bacnet/timestamp
bacnet/timesync
@@ -187,6 +188,30 @@ list(APPEND testdirs
bacnet/datalink/mstp
)
# ports tests
if(ZEPHYR_BASE)
message(FATAL_ERROR "ZEPHYR_BASE env variable defined. Use zephyr/CMakeLists.txt for Zephyr build")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message(STATUS "Added ports specific tests for linux")
list(APPEND testdirs
ports/linux/bsc_event
)
elseif(WIN32)
message(STATUS "Added ports specific tests for win32")
list(APPEND testdirs
ports/win32/bsc_event
)
elseif(APPLE)
message(STATUS "Added ports specific tests for APPLE")
list(APPEND testdirs
ports/bsd/bsc_event
)
endif()
enable_testing()
foreach(testdir IN ITEMS ${testdirs})
get_filename_component(basename ${testdir} NAME)
+1
View File
@@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
+1
View File
@@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
+1
View File
@@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -58,6 +58,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${TST_DIR}/bacnet/basic/object/test/property_test.c
@@ -59,6 +59,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${ZTST_DIR}/ztest_mock.c
@@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/basic/sys/debug.c
@@ -60,6 +60,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/basic/sys/keylist.c
@@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/basic/sys/debug.c
@@ -66,6 +66,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${TST_DIR}/bacnet/basic/object/test/apdu_mock.c
@@ -65,6 +65,8 @@ static void test_BACnet_File_Object(void)
required_property++;
}
bacfile_cleanup();
return;
}
/**
@@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/basic/sys/debug.c
@@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${TST_DIR}/bacnet/basic/object/test/property_test.c
@@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${TST_DIR}/bacnet/basic/object/test/device_mock.c
@@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/basic/sys/debug.c
@@ -62,6 +62,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/basic/sys/bigend.c
${SRC_DIR}/bacnet/basic/sys/days.c
${SRC_DIR}/bacnet/basic/sys/debug.c
@@ -61,6 +61,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
${SRC_DIR}/bacnet/bactimevalue.c
${SRC_DIR}/bacnet/dailyschedule.c
# Test and test library files
@@ -64,6 +64,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${TST_DIR}/bacnet/basic/object/test/property_test.c
@@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${TST_DIR}/bacnet/basic/object/test/device_mock.c
@@ -63,6 +63,7 @@ add_executable(${PROJECT_NAME}
${SRC_DIR}/bacnet/calendar_entry.c
${SRC_DIR}/bacnet/special_event.c
${SRC_DIR}/bacnet/channel_value.c
${SRC_DIR}/bacnet/secure_connect.c
# Test and test library files
./src/main.c
${TST_DIR}/bacnet/basic/object/test/device_mock.c

Some files were not shown because too many files have changed in this diff Show More