feat:
--READ smash_read_this.txt
This commit is contained in:
commit
1ddf693260
181 files changed
+66527
No files matched your search
@@ -0,0 +1,159 @@
|
||||
cmake_minimum_required(VERSION 3.0...3.31)
|
||||
|
||||
# automagically detect if we should cross-compile
|
||||
if(DEFINED ENV{TOOLCHAIN})
|
||||
set(CMAKE_C_COMPILER $ENV{TOOLCHAIN}gcc)
|
||||
set(CMAKE_CXX_COMPILER $ENV{TOOLCHAIN}g++)
|
||||
set(CMAKE_AR "$ENV{TOOLCHAIN}ar" CACHE FILEPATH "CW archiver" FORCE)
|
||||
endif()
|
||||
|
||||
project(lib60870-C)
|
||||
ENABLE_TESTING()
|
||||
|
||||
set(LIB_VERSION_MAJOR "2")
|
||||
set(LIB_VERSION_MINOR "4")
|
||||
set(LIB_VERSION_PATCH "0")
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/")
|
||||
|
||||
macro(ADD_C_FLAGS flags)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flags}")
|
||||
endmacro()
|
||||
|
||||
# feature checks
|
||||
include(CheckLibraryExists)
|
||||
|
||||
# check if we are on a little or a big endian
|
||||
include (TestBigEndian)
|
||||
test_big_endian(PLATFORM_IS_BIGENDIAN)
|
||||
|
||||
option(BUILD_HAL "Build the platform abstraction layer (HAL)" ON)
|
||||
option(BUILD_COMMON "Build common code (shared with other libraries - e.g. libiec61850)" ON)
|
||||
|
||||
option(BUILD_EXAMPLES "Build the examples" ON)
|
||||
option(BUILD_TESTS "Build the tests" ON)
|
||||
|
||||
if(BUILD_HAL)
|
||||
|
||||
## Detect mbedtls dependency folders. Accept folder names with patch/minor suffixes
|
||||
set(MBEDTLS_DIR "")
|
||||
file(GLOB MBEDTLS_2_28_CANDIDATES "${CMAKE_CURRENT_LIST_DIR}/dependencies/mbedtls-2.28*")
|
||||
file(GLOB MBEDTLS_3_6_CANDIDATES "${CMAKE_CURRENT_LIST_DIR}/dependencies/mbedtls-3.6*")
|
||||
|
||||
if(MBEDTLS_2_28_CANDIDATES)
|
||||
list(GET MBEDTLS_2_28_CANDIDATES 0 MBEDTLS_DIR)
|
||||
set(WITH_MBEDTLS 1)
|
||||
set(WITH_SEC_AUTH 1)
|
||||
add_definitions(-DWITH_MBEDTLS=1)
|
||||
message("mbedtls 2.28 found: ${MBEDTLS_DIR}")
|
||||
elseif(MBEDTLS_3_6_CANDIDATES)
|
||||
list(GET MBEDTLS_3_6_CANDIDATES 0 MBEDTLS_DIR)
|
||||
set(WITH_MBEDTLS3 1)
|
||||
set(WITH_SEC_AUTH 1)
|
||||
add_definitions(-DWITH_MBEDTLS3=1)
|
||||
message("mbedtls 3.6 found: ${MBEDTLS_DIR}")
|
||||
else()
|
||||
message("NOTE: mbedtls 2.28 or 3.6 is required for TLS support and secure authentication!")
|
||||
endif()
|
||||
|
||||
endif(BUILD_HAL)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/config
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/file-service
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/internal
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/common/inc
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc
|
||||
)
|
||||
|
||||
if(MBEDTLS_DIR)
|
||||
# choose appropriate HAL adapter (mbedtls vs mbedtls3)
|
||||
if(WITH_MBEDTLS3)
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/tls/mbedtls3
|
||||
${MBEDTLS_DIR}/include
|
||||
)
|
||||
file(GLOB tls_SRCS ${MBEDTLS_DIR}/library/*.c)
|
||||
else()
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/tls/mbedtls
|
||||
${MBEDTLS_DIR}/include
|
||||
)
|
||||
file(GLOB tls_SRCS ${MBEDTLS_DIR}/library/*.c)
|
||||
endif()
|
||||
|
||||
add_definitions(-DCONFIG_CS104_SUPPORT_TLS=1)
|
||||
add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h")
|
||||
endif()
|
||||
|
||||
|
||||
set(API_HEADERS
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_time.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_thread.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_socket.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_serial.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_base.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/tls_config.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/tls_ciphers.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/common/inc/linked_list.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs101_master.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs101_slave.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs104_slave.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/iec60870_master.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/iec60870_slave.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/iec60870_common.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs101_information_objects.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs104_connection.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/link_layer_parameters.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/file-service/cs101_file_service.h
|
||||
)
|
||||
|
||||
include(CheckCCompilerFlag)
|
||||
|
||||
check_c_compiler_flag("-Wredundant-decls" SUPPORT_REDUNDANT_DECLS)
|
||||
if (SUPPORT_REDUNDANT_DECLS)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wredundant-decls")
|
||||
endif(SUPPORT_REDUNDANT_DECLS)
|
||||
|
||||
# write the detected stuff to this file
|
||||
# configure_file(config/lib60870_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config/lib60870_config.h)
|
||||
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/src)
|
||||
|
||||
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/modules/sec-auth)
|
||||
|
||||
add_compile_definitions(SEC_AUTH_60870_5_7)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/modules/sec-auth/CMakeLists.txt)
|
||||
|
||||
endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/modules/sec-auth)
|
||||
|
||||
if(BUILD_EXAMPLES)
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/examples)
|
||||
endif(BUILD_EXAMPLES)
|
||||
|
||||
if(BUILD_TESTS)
|
||||
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/tests)
|
||||
endif(BUILD_TESTS)
|
||||
|
||||
INSTALL(FILES ${API_HEADERS} DESTINATION include/lib60870 COMPONENT Development)
|
||||
|
||||
IF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake")
|
||||
INCLUDE(InstallRequiredSystemLibraries)
|
||||
|
||||
SET(CPACK_PACKAGE_DESCRIPTION "IEC 60870-5-101/104 master/slave library")
|
||||
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IEC 60870-5-101/104 master/slave library")
|
||||
SET(CPACK_PACKAGE_VENDOR "MZ Automation GmbH")
|
||||
SET(CPACK_PACKAGE_CONTACT "info@mz-automation.de")
|
||||
SET(CPACK_PACKAGE_VERSION_MAJOR "${LIB_VERSION_MAJOR}")
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "${LIB_VERSION_MINOR}")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "${LIB_VERSION_PATCH}")
|
||||
SET(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CMAKE_SYSTEM_PROCESSOR}")
|
||||
SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
|
||||
|
||||
SET(CPACK_COMPONENTS_ALL Libraries Development Applications)
|
||||
#set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CMAKE_PROJECT_NAME}")
|
||||
INCLUDE(CPack)
|
||||
|
||||
ENDIF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake")
|
||||
+2441
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,185 @@
|
||||
LIB60870_HOME=.
|
||||
|
||||
include make/target_system.mk
|
||||
|
||||
ifndef WITHOUT_COMMON
|
||||
|
||||
LIB_SOURCE_DIRS = src/common
|
||||
|
||||
endif
|
||||
|
||||
LIB_SOURCE_DIRS += src/iec60870
|
||||
LIB_SOURCE_DIRS += src/iec60870/cs101
|
||||
LIB_SOURCE_DIRS += src/iec60870/cs104
|
||||
LIB_SOURCE_DIRS += src/iec60870/link_layer
|
||||
LIB_SOURCE_DIRS += src/iec60870/apl
|
||||
|
||||
ifndef WITHOUT_HAL
|
||||
|
||||
ifeq ($(HAL_IMPL), WIN32)
|
||||
LIB_SOURCE_DIRS += src/hal/socket/win32
|
||||
LIB_SOURCE_DIRS += src/hal/thread/win32
|
||||
LIB_SOURCE_DIRS += src/hal/time/win32
|
||||
LIB_SOURCE_DIRS += src/hal/serial/win32
|
||||
LIB_SOURCE_DIRS += src/hal/memory
|
||||
else ifeq ($(HAL_IMPL), POSIX)
|
||||
LIB_SOURCE_DIRS += src/hal/socket/linux
|
||||
LIB_SOURCE_DIRS += src/hal/thread/linux
|
||||
LIB_SOURCE_DIRS += src/hal/time/unix
|
||||
LIB_SOURCE_DIRS += src/hal/serial/linux
|
||||
LIB_SOURCE_DIRS += src/hal/memory
|
||||
else ifeq ($(HAL_IMPL), BSD)
|
||||
LIB_SOURCE_DIRS += src/hal/socket/bsd
|
||||
LIB_SOURCE_DIRS += src/hal/thread/bsd
|
||||
LIB_SOURCE_DIRS += src/hal/time/unix
|
||||
LIB_SOURCE_DIRS += src/hal/memory
|
||||
endif
|
||||
|
||||
# Auto-detect mbedtls dependency folder if not explicitly provided.
|
||||
# Prefer mbedtls-3.6* over mbedtls-2.28*, and pick the highest patch version when multiple exist.
|
||||
# If the user sets WITH_MBEDTLS or WITH_MBEDTLS3 externally, that will be respected.
|
||||
ifeq ($(strip $(WITH_MBEDTLS)),)
|
||||
ifeq ($(strip $(WITH_MBEDTLS3)),)
|
||||
# try mbedtls-3.6* first (only directories, exclude archives)
|
||||
MBEDTLS_DIR := $(shell for d in $(LIB60870_HOME)/dependencies/mbedtls-3.6*; do [ -d "$$d" ] && echo "$$d"; done | sort -V | tail -n1)
|
||||
ifeq ($(strip $(MBEDTLS_DIR)),)
|
||||
# fallback to mbedtls-2.28* (only directories)
|
||||
MBEDTLS_DIR := $(shell for d in $(LIB60870_HOME)/dependencies/mbedtls-2.28*; do [ -d "$$d" ] && echo "$$d"; done | sort -V | tail -n1)
|
||||
ifneq ($(strip $(MBEDTLS_DIR)),)
|
||||
WITH_MBEDTLS := 1
|
||||
endif
|
||||
else
|
||||
WITH_MBEDTLS3 := 1
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef WITH_MBEDTLS
|
||||
LIB_SOURCE_DIRS += $(MBEDTLS_DIR)/library
|
||||
LIB_SOURCE_DIRS += src/hal/tls/mbedtls
|
||||
LIB_INCLUDE_DIRS += src/hal/tls/mbedtls
|
||||
LIB_INCLUDE_DIRS += $(MBEDTLS_DIR)/include
|
||||
CFLAGS += -D'MBEDTLS_CONFIG_FILE="mbedtls_config.h"'
|
||||
CFLAGS += -D'CONFIG_CS104_SUPPORT_TLS=1'
|
||||
endif
|
||||
|
||||
ifdef WITH_MBEDTLS3
|
||||
LIB_SOURCE_DIRS += $(MBEDTLS_DIR)/library
|
||||
LIB_SOURCE_DIRS += src/hal/tls/mbedtls3
|
||||
LIB_INCLUDE_DIRS += src/hal/tls/mbedtls3
|
||||
LIB_INCLUDE_DIRS += $(MBEDTLS_DIR)/include
|
||||
CFLAGS += -D'MBEDTLS_CONFIG_FILE="mbedtls_config.h"'
|
||||
CFLAGS += -D'CONFIG_CS104_SUPPORT_TLS=1'
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
LIB_INCLUDE_DIRS += config
|
||||
LIB_INCLUDE_DIRS += src/inc/api
|
||||
LIB_INCLUDE_DIRS += src/inc/internal
|
||||
LIB_INCLUDE_DIRS += src/hal/inc
|
||||
LIB_INCLUDE_DIRS += src/common/inc
|
||||
|
||||
|
||||
LIB_INCLUDES = $(addprefix -I,$(LIB_INCLUDE_DIRS))
|
||||
|
||||
ifndef INSTALL_PREFIX
|
||||
INSTALL_PREFIX = ./.install
|
||||
endif
|
||||
|
||||
LIB_API_HEADER_FILES = src/hal/inc/hal_time.h
|
||||
LIB_API_HEADER_FILES += src/hal/inc/hal_thread.h
|
||||
LIB_API_HEADER_FILES += src/hal/inc/hal_socket.h
|
||||
LIB_API_HEADER_FILES += src/hal/inc/hal_serial.h
|
||||
LIB_API_HEADER_FILES += src/hal/inc/hal_base.h
|
||||
LIB_API_HEADER_FILES += src/common/inc/linked_list.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/cs101_information_objects.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/cs101_master.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/cs101_slave.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/cs104_connection.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/cs104_slave.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/iec60870_common.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/iec60870_master.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/iec60870_slave.h
|
||||
LIB_API_HEADER_FILES += src/inc/api/link_layer_parameters.h
|
||||
LIB_API_HEADER_FILES += src/hal/inc/tls_config.h
|
||||
LIB_API_HEADER_FILES += src/hal/inc/tls_ciphers.h
|
||||
LIB_API_HEADER_FILES += src/file-service/cs101_file_service.h
|
||||
|
||||
LIB_TEST_SOURCES = tests/all_tests.c
|
||||
LIB_TEST_SOURCES += tests/unity/unity.c
|
||||
|
||||
LIB_TEST_INCLUDE_DIRS = tests/unity
|
||||
|
||||
TEST_INCLUDES = $(addprefix -I,$(LIB_TEST_INCLUDE_DIRS))
|
||||
|
||||
get_sources_from_directory = $(wildcard $1/*.c)
|
||||
get_sources = $(foreach dir, $1, $(call get_sources_from_directory,$(dir)))
|
||||
src_to = $(addprefix $(LIB_OBJS_DIR)/,$(subst .c,$1,$2))
|
||||
|
||||
LIB_SOURCES = $(call get_sources,$(LIB_SOURCE_DIRS))
|
||||
|
||||
LIB_OBJS = $(call src_to,.o,$(LIB_SOURCES))
|
||||
TEST_OBJS = $(call src_to,.o,$(LIB_TEST_SOURCES))
|
||||
CFLAGS += -std=gnu99
|
||||
#CFLAGS += -Wno-error=format
|
||||
CFLAGS += -Wstrict-prototypes -Wall -Wextra
|
||||
|
||||
ifneq ($(HAL_IMPL), WIN32)
|
||||
CFLAGS += -Wuninitialized
|
||||
endif
|
||||
|
||||
CFLAGS += -Wsign-compare
|
||||
CFLAGS += -Wpointer-arith
|
||||
CFLAGS += -Wnested-externs
|
||||
CFLAGS += -Wmissing-declarations
|
||||
CFLAGS += -Wshadow
|
||||
CFLAGS += -Wall
|
||||
#CFLAGS += -Werror
|
||||
|
||||
all: lib
|
||||
|
||||
static_checks: lib
|
||||
splint -preproc +posixlib +skip-sys-headers +gnuextensions $(LIB_INCLUDES) $(LIB_SOURCES)
|
||||
|
||||
cppcheck: lib
|
||||
cppcheck --xml --force --std=c99 --enable=all $(LIB_INCLUDES) $(LIB_SOURCES) 2> cppcheck-output.xml
|
||||
|
||||
lib: $(LIB_NAME)
|
||||
|
||||
tests: $(TEST_NAME)
|
||||
|
||||
dynlib: CFLAGS += -fPIC
|
||||
|
||||
dynlib: $(DYN_LIB_NAME)
|
||||
|
||||
.PHONY: examples
|
||||
|
||||
examples:
|
||||
cd examples; $(MAKE)
|
||||
|
||||
$(TEST_NAME): $(LIB_OBJS) $(TEST_OBJS)
|
||||
$(CC) -o $(TEST_NAME) $(LIB_OBJS) $(TEST_OBJS) -lpthread
|
||||
|
||||
$(LIB_NAME): $(LIB_OBJS)
|
||||
$(AR) r $(LIB_NAME) $(LIB_OBJS)
|
||||
$(RANLIB) $(LIB_NAME)
|
||||
|
||||
$(DYN_LIB_NAME): $(LIB_OBJS)
|
||||
$(CC) $(LDFLAGS) $(DYNLIB_LDFLAGS) -shared -o $(DYN_LIB_NAME) $(LIB_OBJS) $(LDLIBS)
|
||||
|
||||
$(LIB_OBJS_DIR)/%.o: %.c config
|
||||
@echo compiling $(notdir $<)
|
||||
$(SILENCE)mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) -c $(LIB_INCLUDES) $(TEST_INCLUDES) $(OUTPUT_OPTION) $<
|
||||
|
||||
install: $(LIB_NAME)
|
||||
mkdir -p $(INSTALL_PREFIX)/include
|
||||
mkdir -p $(INSTALL_PREFIX)/lib
|
||||
cp $(LIB_API_HEADER_FILES) $(INSTALL_PREFIX)/include
|
||||
cp $(LIB_NAME) $(INSTALL_PREFIX)/lib
|
||||
|
||||
clean:
|
||||
rm -f $(EXAMPLES)
|
||||
rm -rf $(LIB_OBJS_DIR)
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* lib60870_config.h
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_LIB60870_CONFIG_H_
|
||||
#define CONFIG_LIB60870_CONFIG_H_
|
||||
|
||||
|
||||
/* print debugging information with printf if set to 1 */
|
||||
#ifndef CONFIG_DEBUG_OUTPUT
|
||||
#define CONFIG_DEBUG_OUTPUT 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Define the maximum slave message queue size (for CS 101)
|
||||
*
|
||||
* When set to -1 the message queue size is not limited can be set by the application
|
||||
*/
|
||||
#ifndef CONFIG_SLAVE_MESSAGE_QUEUE_SIZE
|
||||
#define CONFIG_SLAVE_MESSAGE_QUEUE_SIZE -1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Define the default size for the slave (outstation) message queue. This is used also
|
||||
* to buffer ASDUs in the case when the connection is lost.
|
||||
*
|
||||
* For each queued message a maximum of 256 bytes of memory are required. Usually messages are
|
||||
* smaller so more then thus number of messages can be stored in the message queue
|
||||
*/
|
||||
#ifndef CONFIG_CS104_MESSAGE_QUEUE_SIZE
|
||||
#define CONFIG_CS104_MESSAGE_QUEUE_SIZE 100
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is a connection specific ASDU queue for the slave (outstation). It is used for connection
|
||||
* specific ASDUs like those that are automatically generated by the stack or created in
|
||||
* the slave side callback. The messages in the queue are removed when the connection is lost.
|
||||
*
|
||||
* For each queued message about 256 bytes of memory are required.
|
||||
*/
|
||||
#ifndef CONFIG_CS104_MESSAGE_QUEUE_HIGH_PRIO_SIZE
|
||||
#define CONFIG_CS104_MESSAGE_QUEUE_HIGH_PRIO_SIZE 50
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Compile the library to use threads. This will require semaphore support
|
||||
*/
|
||||
#ifndef CONFIG_USE_THREADS
|
||||
#define CONFIG_USE_THREADS 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Compile the library using semaphore to protect critical objects.
|
||||
* Required when CONFIG_USE_THREADS = 1.
|
||||
*/
|
||||
#ifndef CONFIG_USE_SEMAPHORES
|
||||
#define CONFIG_USE_SEMAPHORES 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Compile library with support for SINGLE_REDUNDANCY_GROUP server mode (only CS104 server)
|
||||
*/
|
||||
#ifndef CONFIG_CS104_SUPPORT_SERVER_MODE_SINGLE_REDUNDANCY_GROUP
|
||||
#define CONFIG_CS104_SUPPORT_SERVER_MODE_SINGLE_REDUNDANCY_GROUP 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Compile library with support for MULTIPLE_REDUNDANCY_GROUPS server mode (only CS104 server)
|
||||
*/
|
||||
#ifndef CONFIG_CS104_SUPPORT_SERVER_MODE_MULTIPLE_REDUNDANCY_GROUPS
|
||||
#define CONFIG_CS104_SUPPORT_SERVER_MODE_MULTIPLE_REDUNDANCY_GROUPS 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Compile library with support for CONNECTION_IS_REDUNDANCY_GROUP server mode (only CS104 server)
|
||||
*/
|
||||
#ifndef CONFIG_CS104_SUPPORT_SERVER_MODE_CONNECTION_IS_REDUNDANCY_GROUP
|
||||
#define CONFIG_CS104_SUPPORT_SERVER_MODE_CONNECTION_IS_REDUNDANCY_GROUP 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Set the maximum number of client connections
|
||||
*/
|
||||
#ifndef CONFIG_CS104_MAX_CLIENT_CONNECTIONS
|
||||
#define CONFIG_CS104_MAX_CLIENT_CONNECTIONS 100
|
||||
#endif
|
||||
|
||||
/* activate TCP keep alive mechanism. 1 -> activate */
|
||||
#ifndef CONFIG_ACTIVATE_TCP_KEEPALIVE
|
||||
#define CONFIG_ACTIVATE_TCP_KEEPALIVE 0
|
||||
#endif
|
||||
|
||||
/* time (in s) between last message and first keepalive message */
|
||||
#ifndef CONFIG_TCP_KEEPALIVE_IDLE
|
||||
#define CONFIG_TCP_KEEPALIVE_IDLE 5
|
||||
#endif
|
||||
|
||||
/* time between subsequent keepalive messages if no ack received */
|
||||
#ifndef CONFIG_TCP_KEEPALIVE_INTERVAL
|
||||
#define CONFIG_TCP_KEEPALIVE_INTERVAL 2
|
||||
#endif
|
||||
|
||||
/* number of not missing keepalive responses until socket is considered dead */
|
||||
#ifndef CONFIG_TCP_KEEPALIVE_CNT
|
||||
#define CONFIG_TCP_KEEPALIVE_CNT 2
|
||||
#endif
|
||||
|
||||
/* test command without timestamp is not allowed for CS104. Set to 1 to enable it anyway. */
|
||||
#ifndef CONFIG_ALLOW_C_TS_NA_1_FOR_CS104
|
||||
#define CONFIG_ALLOW_C_TS_NA_1_FOR_CS104 0
|
||||
#endif
|
||||
|
||||
#endif /* CONFIG_LIB60870_CONFIG_H_ */
|
||||
@@ -0,0 +1,45 @@
|
||||
# README
|
||||
|
||||
Please add optional dependencies in this folder
|
||||
|
||||
## TLS Support
|
||||
|
||||
At the moment there are two different options for TLS support.
|
||||
|
||||
* mbedtls 2.28 supports TLS version 1.2, 1.1 and older versions of TLS
|
||||
* mbedtls 3.6 supports TLS versions 1.2 and 1.3
|
||||
|
||||
### mbedtls 2.28
|
||||
|
||||
For TLS support with mbedtls 2.28 download the source tarball of version 2.28.x and extract here in the subfolder (Version 2.28.10 https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-2.28.10/mbedtls-2.28.10.tar.bz2)
|
||||
|
||||
On a Linux command line enter the following commands in this directory:
|
||||
|
||||
wget https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-2.28.10/mbedtls-2.28.10.tar.bz2
|
||||
tar xfj mbedtls-2.28.10.tar.bz2
|
||||
|
||||
To allow session resumption within an ongoing TLS connection (required by IEC 62351-3:2023) a patch (mbedtls_2.28_ssl_srv.c.patch) has to be applied to the mbedtls source code:
|
||||
|
||||
patch mbedtls-2.28.10/library/ssl_srv.c mbedtls_2.28_ssl_srv.c.patch
|
||||
|
||||
When running cmake the build system will automatically find the mbedtls source code and includes it into the library build.
|
||||
|
||||
When using make the make command has to be invoked with the WITH_MBEDTLS=1 parameter
|
||||
|
||||
make WITH_MBEDTLS=1
|
||||
|
||||
### mbedtls 3.6
|
||||
|
||||
For TLS support with mbedtls 3.6 download the source tarball of version 3.6.x and extract here in the subfolder (Version 3.6.2 https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.2/mbedtls-3.6.2.tar.bz2)
|
||||
|
||||
On a Linux command line enter the following commands in this directory:
|
||||
|
||||
wget https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.2/mbedtls-3.6.2.tar.bz2
|
||||
tar xfj mbedtls-3.6.2.tar.bz2
|
||||
|
||||
When running cmake the build system will automatically find the mbedtls source code and includes it into the library build.
|
||||
|
||||
When using make the make command has to be invoked with the WITH_MBEDTLS3=1 parameter
|
||||
|
||||
make WITH_MBEDTLS3=1
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
--- orig/mbedtls-2.28.10/library/ssl_srv.c 2025-03-24 11:49:00.000000000 +0000
|
||||
+++ mbedtls-2.28.10/library/ssl_srv.c 2026-01-30 18:15:47.656423746 +0000
|
||||
@@ -2691,15 +2691,36 @@
|
||||
if (session->id_len == 0) {
|
||||
return;
|
||||
}
|
||||
- if (ssl->conf->f_get_cache == NULL) {
|
||||
- return;
|
||||
- }
|
||||
#if defined(MBEDTLS_SSL_RENEGOTIATION)
|
||||
if (ssl->renego_status != MBEDTLS_SSL_INITIAL_HANDSHAKE) {
|
||||
+ if (ssl->session != NULL &&
|
||||
+ ssl->session->id_len == session->id_len &&
|
||||
+ memcmp(ssl->session->id, session->id, session->id_len) == 0 &&
|
||||
+ mbedtls_ssl_session_copy(session, ssl->session) == 0) {
|
||||
+ MBEDTLS_SSL_DEBUG_MSG(3, ("renegotiation: resuming existing session"));
|
||||
+ ssl->handshake->resume = 1;
|
||||
+ ssl->secure_renegotiation = MBEDTLS_SSL_SECURE_RENEGOTIATION;
|
||||
+
|
||||
+ /* call session cache just to inform application about session resumption */
|
||||
+ if (ssl->conf->f_get_cache != NULL) {
|
||||
+ mbedtls_ssl_session_init(&session_tmp);
|
||||
+
|
||||
+ session_tmp.id_len = session->id_len;
|
||||
+ memcpy(session_tmp.id, session->id, session->id_len);
|
||||
+
|
||||
+ ret = ssl->conf->f_get_cache(ssl->conf->p_cache,
|
||||
+ &session_tmp);
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
+ if (ssl->conf->f_get_cache == NULL) {
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
mbedtls_ssl_session_init(&session_tmp);
|
||||
|
||||
session_tmp.id_len = session->id_len;
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,18 @@
|
||||
add_subdirectory(cs101_master_balanced)
|
||||
add_subdirectory(cs101_master_unbalanced)
|
||||
add_subdirectory(cs101_slave)
|
||||
add_subdirectory(cs101_slave_files)
|
||||
add_subdirectory(cs104_client)
|
||||
add_subdirectory(cs104_client_async)
|
||||
add_subdirectory(cs104_client_no_threads)
|
||||
add_subdirectory(cs104_server)
|
||||
add_subdirectory(cs104_server_no_threads)
|
||||
add_subdirectory(cs104_server_files)
|
||||
add_subdirectory(cs104_redundancy_server)
|
||||
add_subdirectory(multi_client_server)
|
||||
|
||||
if (WITH_MBEDTLS OR WITH_MBEDTLS3)
|
||||
add_subdirectory(tls_client)
|
||||
add_subdirectory(tls_server)
|
||||
endif (WITH_MBEDTLS OR WITH_MBEDTLS3)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
master_example.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs101_master_balanced
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs101_master_balanced
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = cs101_master
|
||||
PROJECT_SOURCES = master_example.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* master_example.c
|
||||
*/
|
||||
|
||||
#include "cs101_master.h"
|
||||
#include "hal_serial.h"
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static bool running = false;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
|
||||
/* Callback handler to log sent or received messages (optional) */
|
||||
static void
|
||||
rawMessageHandler(void* parameter, uint8_t* msg, int msgSize, bool sent)
|
||||
{
|
||||
if (sent)
|
||||
printf("SEND: ");
|
||||
else
|
||||
printf("RCVD: ");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < msgSize; i++)
|
||||
{
|
||||
printf("%02x ", msg[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool
|
||||
asduReceivedHandler(void* parameter, int address, CS101_ASDU asdu)
|
||||
{
|
||||
printf("RECVD ASDU type: %s(%i) elements: %i\n", TypeID_toString(CS101_ASDU_getTypeID(asdu)),
|
||||
CS101_ASDU_getTypeID(asdu), CS101_ASDU_getNumberOfElements(asdu));
|
||||
|
||||
if (CS101_ASDU_getTypeID(asdu) == M_ME_TE_1)
|
||||
{
|
||||
printf(" measured scaled values with CP56Time2a timestamp:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
MeasuredValueScaledWithCP56Time2a io = (MeasuredValueScaledWithCP56Time2a)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
MeasuredValueScaled_getValue((MeasuredValueScaled)io));
|
||||
|
||||
MeasuredValueScaledWithCP56Time2a_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == M_SP_NA_1)
|
||||
{
|
||||
printf(" single point information:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
SinglePointInformation io = (SinglePointInformation)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
SinglePointInformation_getValue((SinglePointInformation)io));
|
||||
|
||||
SinglePointInformation_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
linkLayerStateChanged(void* parameter, int address, LinkLayerState state)
|
||||
{
|
||||
printf("Link layer state: ");
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case LL_STATE_IDLE:
|
||||
printf("IDLE\n");
|
||||
break;
|
||||
case LL_STATE_ERROR:
|
||||
printf("ERROR\n");
|
||||
break;
|
||||
case LL_STATE_BUSY:
|
||||
printf("BUSY\n");
|
||||
break;
|
||||
case LL_STATE_AVAILABLE:
|
||||
printf("AVAILABLE\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
const char* serialPort = "/dev/ttyUSB0";
|
||||
|
||||
if (argc > 1)
|
||||
serialPort = argv[1];
|
||||
|
||||
SerialPort port = SerialPort_create(serialPort, 9600, 8, 'E', 1);
|
||||
|
||||
CS101_Master master = CS101_Master_create(port, NULL, NULL, IEC60870_LINK_LAYER_BALANCED);
|
||||
|
||||
CS101_Master_setOwnAddress(master, 2);
|
||||
|
||||
/* Set the address of the slave (optional for balanced master */
|
||||
CS101_Master_useSlaveAddress(master, 3);
|
||||
|
||||
/* set handler for received ASDUs (application layer data) */
|
||||
CS101_Master_setASDUReceivedHandler(master, asduReceivedHandler, NULL);
|
||||
|
||||
/* modify some of the default parameters */
|
||||
LinkLayerParameters llParams = CS101_Master_getLinkLayerParameters(master);
|
||||
llParams->useSingleCharACK = false;
|
||||
llParams->addressLength = 1;
|
||||
|
||||
/* set handler for link layer state changes */
|
||||
CS101_Master_setLinkLayerStateChanged(master, linkLayerStateChanged, NULL);
|
||||
|
||||
/* log messages */
|
||||
CS101_Master_setRawMessageHandler(master, rawMessageHandler, NULL);
|
||||
|
||||
SerialPort_open(port);
|
||||
|
||||
running = true;
|
||||
|
||||
int cycleCounter = 0;
|
||||
|
||||
while (running)
|
||||
{
|
||||
CS101_Master_run(master);
|
||||
|
||||
if (cycleCounter == 10)
|
||||
CS101_Master_sendInterrogationCommand(master, CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
||||
|
||||
if (cycleCounter == 50)
|
||||
{
|
||||
InformationObject sc = (InformationObject)SingleCommand_create(NULL, 5000, true, false, 0);
|
||||
|
||||
printf("Send control command C_SC_NA_1\n");
|
||||
CS101_Master_sendProcessCommand(master, CS101_COT_ACTIVATION, 1, sc);
|
||||
|
||||
InformationObject_destroy(sc);
|
||||
}
|
||||
|
||||
if (cycleCounter == 80)
|
||||
{
|
||||
/* Send clock synchronization command */
|
||||
struct sCP56Time2a newTime;
|
||||
|
||||
CP56Time2a_createFromMsTimestamp(&newTime, Hal_getTimeInMs());
|
||||
|
||||
printf("Send time sync command\n");
|
||||
CS101_Master_sendClockSyncCommand(master, 1, &newTime);
|
||||
}
|
||||
|
||||
Thread_sleep(1);
|
||||
|
||||
cycleCounter++;
|
||||
}
|
||||
|
||||
CS101_Master_destroy(master);
|
||||
|
||||
SerialPort_close(port);
|
||||
SerialPort_destroy(port);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
master_example.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs101_master_unbalanced
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs101_master_unbalanced
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = cs101_master
|
||||
PROJECT_SOURCES = master_example.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* master_example.c
|
||||
*/
|
||||
|
||||
#include "cs101_master.h"
|
||||
#include "hal_serial.h"
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static bool running = false;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
|
||||
/* Callback handler to log sent or received messages (optional) */
|
||||
static void
|
||||
rawMessageHandler(void* parameter, uint8_t* msg, int msgSize, bool sent)
|
||||
{
|
||||
if (sent)
|
||||
printf("SEND: ");
|
||||
else
|
||||
printf("RCVD: ");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < msgSize; i++)
|
||||
{
|
||||
printf("%02x ", msg[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool
|
||||
asduReceivedHandler(void* parameter, int address, CS101_ASDU asdu)
|
||||
{
|
||||
printf("SLAVE %i: RECVD ASDU type: %s(%i) elements: %i\n", address, TypeID_toString(CS101_ASDU_getTypeID(asdu)),
|
||||
CS101_ASDU_getTypeID(asdu), CS101_ASDU_getNumberOfElements(asdu));
|
||||
|
||||
if (CS101_ASDU_getTypeID(asdu) == M_ME_TE_1)
|
||||
{
|
||||
printf(" measured scaled values with CP56Time2a timestamp (M_ME_TE_1):\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
MeasuredValueScaledWithCP56Time2a io = (MeasuredValueScaledWithCP56Time2a)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io != NULL)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
MeasuredValueScaled_getValue((MeasuredValueScaled)io));
|
||||
|
||||
MeasuredValueScaledWithCP56Time2a_destroy(io);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" invalid object!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == M_SP_NA_1)
|
||||
{
|
||||
printf(" single point information (M_SP_NA_1):\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
SinglePointInformation io = (SinglePointInformation)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io != NULL)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
SinglePointInformation_getValue((SinglePointInformation)io));
|
||||
|
||||
SinglePointInformation_destroy(io);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" invalid object!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == M_EP_TD_1)
|
||||
{
|
||||
printf(" event of protection equipment (M_EP_TD_1):\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
EventOfProtectionEquipmentWithCP56Time2a epe =
|
||||
(EventOfProtectionEquipmentWithCP56Time2a)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (epe != NULL)
|
||||
{
|
||||
SingleEvent singleEvent = EventOfProtectionEquipmentWithCP56Time2a_getEvent(epe);
|
||||
|
||||
printf(" IOA: %i state: %i QDQ: %i\n", InformationObject_getObjectAddress((InformationObject)epe),
|
||||
SingleEvent_getEventState(singleEvent), SingleEvent_getQDP(singleEvent));
|
||||
|
||||
EventOfProtectionEquipmentWithCP56Time2a_destroy(epe);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" invalid object!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
linkLayerStateChanged(void* parameter, int address, LinkLayerState state)
|
||||
{
|
||||
printf("Link layer state changed for slave %i: ", address);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case LL_STATE_IDLE:
|
||||
printf("IDLE\n");
|
||||
break;
|
||||
case LL_STATE_ERROR:
|
||||
printf("ERROR\n");
|
||||
break;
|
||||
case LL_STATE_BUSY:
|
||||
printf("BUSY\n");
|
||||
break;
|
||||
case LL_STATE_AVAILABLE:
|
||||
printf("AVAILABLE\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
const char* serialPort = "/dev/ttyUSB0";
|
||||
|
||||
if (argc > 1)
|
||||
serialPort = argv[1];
|
||||
|
||||
SerialPort port = SerialPort_create(serialPort, 9600, 8, 'E', 1);
|
||||
|
||||
CS101_Master master = CS101_Master_create(port, NULL, NULL, IEC60870_LINK_LAYER_UNBALANCED);
|
||||
|
||||
LinkLayerParameters llParams = CS101_Master_getLinkLayerParameters(master);
|
||||
|
||||
llParams->addressLength = 1;
|
||||
|
||||
/* Setting the callback handler for generic ASDUs */
|
||||
CS101_Master_setASDUReceivedHandler(master, asduReceivedHandler, NULL);
|
||||
|
||||
/* set callback handler for link layer state changes */
|
||||
CS101_Master_setLinkLayerStateChanged(master, linkLayerStateChanged, NULL);
|
||||
|
||||
/* log messages */
|
||||
CS101_Master_setRawMessageHandler(master, rawMessageHandler, NULL);
|
||||
|
||||
CS101_Master_addSlave(master, 1);
|
||||
CS101_Master_addSlave(master, 2);
|
||||
|
||||
if (!SerialPort_open(port))
|
||||
{
|
||||
fprintf(stderr, "Failed to open serial port\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
running = true;
|
||||
|
||||
int cycleCounter = 0;
|
||||
|
||||
while (running)
|
||||
{
|
||||
CS101_Master_pollSingleSlave(master, 1);
|
||||
CS101_Master_run(master);
|
||||
|
||||
CS101_Master_pollSingleSlave(master, 2);
|
||||
CS101_Master_run(master);
|
||||
|
||||
if (cycleCounter == 10)
|
||||
{
|
||||
/* Send a general interrogation to a specific slave */
|
||||
if (CS101_Master_isChannelReady(master, 1))
|
||||
{
|
||||
CS101_Master_useSlaveAddress(master, 1);
|
||||
CS101_Master_sendInterrogationCommand(master, CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
||||
CS101_Master_run(master);
|
||||
}
|
||||
else
|
||||
cycleCounter--;
|
||||
}
|
||||
|
||||
if (cycleCounter == 30)
|
||||
{
|
||||
/* Send a read request */
|
||||
if (CS101_Master_isChannelReady(master, 1))
|
||||
{
|
||||
CS101_Master_useSlaveAddress(master, 1);
|
||||
CS101_Master_sendReadCommand(master, 1, 102);
|
||||
CS101_Master_run(master);
|
||||
}
|
||||
else
|
||||
cycleCounter--;
|
||||
}
|
||||
|
||||
if (cycleCounter == 50)
|
||||
{
|
||||
if (CS101_Master_isChannelReady(master, 1))
|
||||
{
|
||||
printf("Send control command C_SC_NA_1\n");
|
||||
|
||||
InformationObject sc = (InformationObject)SingleCommand_create(NULL, 5000, true, false, 0);
|
||||
|
||||
CS101_Master_useSlaveAddress(master, 2);
|
||||
CS101_Master_sendProcessCommand(master, CS101_COT_ACTIVATION, 1, sc);
|
||||
CS101_Master_run(master);
|
||||
|
||||
InformationObject_destroy(sc);
|
||||
}
|
||||
else
|
||||
cycleCounter--;
|
||||
}
|
||||
|
||||
if (cycleCounter == 80)
|
||||
{
|
||||
/* Send clock synchronization command */
|
||||
|
||||
if (CS101_Master_isChannelReady(master, 1))
|
||||
{
|
||||
struct sCP56Time2a newTime;
|
||||
|
||||
CP56Time2a_createFromMsTimestamp(&newTime, Hal_getTimeInMs());
|
||||
|
||||
printf("Send time sync command\n");
|
||||
/* Use broadcast address */
|
||||
CS101_Master_useSlaveAddress(master, 255);
|
||||
CS101_Master_sendClockSyncCommand(master, 1, &newTime);
|
||||
CS101_Master_run(master);
|
||||
}
|
||||
else
|
||||
cycleCounter--;
|
||||
}
|
||||
|
||||
Thread_sleep(100);
|
||||
|
||||
cycleCounter++;
|
||||
}
|
||||
|
||||
CS101_Master_destroy(master);
|
||||
|
||||
SerialPort_close(port);
|
||||
SerialPort_destroy(port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
slave_example.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs101_slave
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs101_slave
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = cs101_slave
|
||||
PROJECT_SOURCES = slave_example.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* slave_example.c
|
||||
*
|
||||
* Example CS101 slave
|
||||
*
|
||||
*/
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs101_slave.h"
|
||||
|
||||
#include "hal_serial.h"
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
|
||||
void
|
||||
printCP56Time2a(CP56Time2a time)
|
||||
{
|
||||
printf("%02i:%02i:%02i %02i/%02i/%04i", CP56Time2a_getHour(time), CP56Time2a_getMinute(time),
|
||||
CP56Time2a_getSecond(time), CP56Time2a_getDayOfMonth(time), CP56Time2a_getMonth(time),
|
||||
CP56Time2a_getYear(time) + 2000);
|
||||
}
|
||||
|
||||
/* Callback handler to log sent or received messages (optional) */
|
||||
static void
|
||||
rawMessageHandler(void* parameter, uint8_t* msg, int msgSize, bool sent)
|
||||
{
|
||||
if (sent)
|
||||
{
|
||||
printf("SEND: ");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("RCVD: ");
|
||||
}
|
||||
|
||||
int i;
|
||||
for (i = 0; i < msgSize; i++)
|
||||
{
|
||||
printf("%02x ", msg[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* Callback handler that is called when a clock synchronization command is received */
|
||||
static bool
|
||||
clockSyncHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, CP56Time2a newTime)
|
||||
{
|
||||
printf("Process time sync command with time ");
|
||||
printCP56Time2a(newTime);
|
||||
printf("\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Semaphore gi_stateLock = NULL;
|
||||
static int gi_state = 0; /* 0 = IDLE, 1 = IN_PROGRESS */
|
||||
static IMasterConnection gi_connection = NULL;
|
||||
static int gi_progress = 0;
|
||||
static sCS101_StaticASDU gi_asdu;
|
||||
static uint8_t gi_oa = 0;
|
||||
|
||||
static CS101_ASDU
|
||||
getNextInterrogationASDUHandler(void* parameter, IMasterConnection connection)
|
||||
{
|
||||
CS101_ASDU retVal = NULL;
|
||||
|
||||
Semaphore_wait(gi_stateLock);
|
||||
|
||||
if (gi_state == 1)
|
||||
{
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
if (gi_progress == 0)
|
||||
{
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&gi_asdu, alParams, false, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject) MeasuredValueScaled_create(NULL, 100, -1, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
MeasuredValueScaled_create((MeasuredValueScaled) io, 101, 23, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
MeasuredValueScaled_create((MeasuredValueScaled) io, 102, 2300, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
gi_progress++;
|
||||
|
||||
retVal = (CS101_ASDU)&gi_asdu;
|
||||
}
|
||||
else if (gi_progress == 1)
|
||||
{
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&gi_asdu, alParams, false, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject) SinglePointInformation_create(NULL, 104, true, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
SinglePointInformation_create((SinglePointInformation) io, 105, false, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
gi_progress++;
|
||||
|
||||
retVal = (CS101_ASDU)&gi_asdu;
|
||||
}
|
||||
else if (gi_progress == 2)
|
||||
{
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&gi_asdu, alParams, true, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
InformationObject io = NULL;
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io = (InformationObject) SinglePointInformation_create(NULL, 300, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject) SinglePointInformation_create((SinglePointInformation) io, 301, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject) SinglePointInformation_create((SinglePointInformation) io, 302, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject) SinglePointInformation_create((SinglePointInformation) io, 303, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject) SinglePointInformation_create((SinglePointInformation) io, 304, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject) SinglePointInformation_create((SinglePointInformation) io, 305, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject) SinglePointInformation_create((SinglePointInformation) io, 306, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject) SinglePointInformation_create((SinglePointInformation) io, 307, false, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
gi_progress++;
|
||||
|
||||
retVal = (CS101_ASDU)&gi_asdu;
|
||||
}
|
||||
else if (gi_progress == 3)
|
||||
{
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&gi_asdu, alParams, true, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject) BitString32_create(NULL, 500, 0xaaaa);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
gi_progress++;
|
||||
|
||||
retVal = (CS101_ASDU)&gi_asdu;
|
||||
}
|
||||
else if (gi_progress == 4)
|
||||
{
|
||||
/* send ACT_TERM */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&gi_asdu, alParams, false, CS101_COT_ACTIVATION_TERMINATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject) InterrogationCommand_create(NULL, 0, 20);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
/* this one we do not return but use the sendASDU function to put it into the class 1 queue */
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
|
||||
/* finished */
|
||||
gi_state = 0;
|
||||
gi_connection = NULL;
|
||||
gi_progress = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore_post(gi_stateLock);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/* Callback handler that is called when an interrogation command is received */
|
||||
static bool
|
||||
interrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
|
||||
{
|
||||
printf("Received interrogation for group %i\n", qoi);
|
||||
|
||||
int ca = CS101_ASDU_getCA(asdu);
|
||||
|
||||
if (ca == 1) /* only handle interrogation for CA(SDU) 1 */
|
||||
{
|
||||
if (qoi == 20) /* only handle station interrogation */
|
||||
{
|
||||
Semaphore_wait(gi_stateLock);
|
||||
|
||||
gi_state = 1;
|
||||
gi_connection = connection;
|
||||
gi_progress = 0;
|
||||
gi_oa = (uint8_t)CS101_ASDU_getOA(asdu);
|
||||
|
||||
/* this will put the ACT-CON ASDU into the class 1 queue */
|
||||
IMasterConnection_sendACT_CON(connection, asdu, false);
|
||||
|
||||
/* the remaining GI data has to be sent as class 2 data in the getNextInterrogationASDUHandler callback */
|
||||
/* the final ACT-TERM message has to sent as class 1 data when the GI process is finished */
|
||||
|
||||
Semaphore_post(gi_stateLock);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* send error response */
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
asduHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
|
||||
{
|
||||
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1)
|
||||
{
|
||||
printf("received single command\n");
|
||||
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION)
|
||||
{
|
||||
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
||||
|
||||
if (io)
|
||||
{
|
||||
if (InformationObject_getObjectAddress(io) == 5000)
|
||||
{
|
||||
SingleCommand sc = (SingleCommand)io;
|
||||
|
||||
printf("IOA: %i switch to %i\n", InformationObject_getObjectAddress(io),
|
||||
SingleCommand_getState(sc));
|
||||
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR: ASDU contains no information object!\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
resetCUHandler(void* parameter)
|
||||
{
|
||||
printf("Received reset CU\n");
|
||||
|
||||
CS101_Slave_flushQueues((CS101_Slave)parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
linkLayerStateChanged(void* parameter, int address, LinkLayerState state)
|
||||
{
|
||||
printf("Link layer state: ");
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case LL_STATE_IDLE:
|
||||
printf("IDLE\n");
|
||||
break;
|
||||
case LL_STATE_ERROR:
|
||||
printf("ERROR\n");
|
||||
break;
|
||||
case LL_STATE_BUSY:
|
||||
printf("BUSY\n");
|
||||
break;
|
||||
case LL_STATE_AVAILABLE:
|
||||
printf("AVAILABLE\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
/* Add Ctrl-C handler */
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
const char* serialPort = "/dev/ttyUSB0";
|
||||
|
||||
if (argc > 1)
|
||||
serialPort = argv[1];
|
||||
|
||||
SerialPort port = SerialPort_create(serialPort, 9600, 8, 'E', 1);
|
||||
|
||||
if (!port)
|
||||
{
|
||||
fprintf(stderr, "Failed to create serial port\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
gi_stateLock = Semaphore_create(1);
|
||||
|
||||
/* create a new slave/server instance with default link layer and application layer parameters */
|
||||
//CS101_Slave slave = CS101_Slave_create(port, NULL, NULL, IEC60870_LINK_LAYER_BALANCED);
|
||||
CS101_Slave slave = CS101_Slave_create(port, NULL, NULL, IEC60870_LINK_LAYER_UNBALANCED);
|
||||
|
||||
CS101_Slave_setLinkLayerAddress(slave, 1);
|
||||
CS101_Slave_setLinkLayerAddressOtherStation(slave, 1);
|
||||
|
||||
/* get the application layer parameters - we need them to create correct ASDUs */
|
||||
CS101_AppLayerParameters alParameters = CS101_Slave_getAppLayerParameters(slave);
|
||||
|
||||
/* change default application layer parameters (optional) */
|
||||
alParameters->sizeOfCA = 2;
|
||||
alParameters->sizeOfIOA = 3;
|
||||
alParameters->sizeOfCOT = 2;
|
||||
|
||||
LinkLayerParameters llParameters = CS101_Slave_getLinkLayerParameters(slave);
|
||||
llParameters->timeoutForAck = 500;
|
||||
llParameters->addressLength = 1;
|
||||
|
||||
/* set the callback handler for the clock synchronization command */
|
||||
CS101_Slave_setClockSyncHandler(slave, clockSyncHandler, NULL);
|
||||
|
||||
/* set the callback handler for the interrogation command */
|
||||
CS101_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
||||
|
||||
/* set handler for other message types */
|
||||
CS101_Slave_setASDUHandler(slave, asduHandler, NULL);
|
||||
|
||||
/* set handler for reset CU (reset communication unit) message */
|
||||
CS101_Slave_setResetCUHandler(slave, resetCUHandler, (void*)slave);
|
||||
|
||||
CS101_Slave_setGetNextInterrogationASDUHandler(slave, getNextInterrogationASDUHandler, slave);
|
||||
|
||||
/* set timeout for detecting connection loss */
|
||||
CS101_Slave_setIdleTimeout(slave, 1500);
|
||||
|
||||
/* set handler for link layer state changes */
|
||||
CS101_Slave_setLinkLayerStateChanged(slave, linkLayerStateChanged, NULL);
|
||||
|
||||
/* log messages */
|
||||
CS101_Slave_setRawMessageHandler(slave, rawMessageHandler, NULL);
|
||||
|
||||
int16_t scaledValue = 0;
|
||||
|
||||
uint64_t lastMessageSent = 0;
|
||||
|
||||
if (!SerialPort_open(port))
|
||||
{
|
||||
fprintf(stderr, "Failed to open serial port\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
while (running)
|
||||
{
|
||||
/* has to be called periodically */
|
||||
CS101_Slave_run(slave);
|
||||
|
||||
/* Enqueue a measurement every second */
|
||||
if (Hal_getTimeInMs() > (lastMessageSent + 1000))
|
||||
{
|
||||
CS101_ASDU newAsdu = CS101_ASDU_create(alParameters, false, CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
|
||||
InformationObject io =
|
||||
(InformationObject)MeasuredValueScaled_create(NULL, 110, scaledValue, IEC60870_QUALITY_GOOD);
|
||||
|
||||
scaledValue++;
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
CS101_Slave_enqueueUserDataClass2(slave, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
lastMessageSent = Hal_getTimeInMs();
|
||||
}
|
||||
|
||||
Thread_sleep(1);
|
||||
}
|
||||
|
||||
goto exit_program;
|
||||
|
||||
exit_program:
|
||||
CS101_Slave_destroy(slave);
|
||||
|
||||
SerialPort_close(port);
|
||||
SerialPort_destroy(port);
|
||||
|
||||
Semaphore_destroy(gi_stateLock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
cs101_slave_files.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs101_slave_files
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs101_slave_files
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = cs101_slave_files
|
||||
PROJECT_SOURCES = cs101_slave_files.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* slave_example.c
|
||||
*
|
||||
* Example CS101 slave
|
||||
*
|
||||
*/
|
||||
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs101_file_service.h"
|
||||
#include "cs101_slave.h"
|
||||
|
||||
#include "hal_serial.h"
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
|
||||
static int fileSize = 800;
|
||||
static uint8_t sectionData1[500];
|
||||
static uint8_t sectionData2[500];
|
||||
|
||||
static uint64_t
|
||||
getFileDate(CS101_IFileProvider self)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
getFileSize(CS101_IFileProvider self)
|
||||
{
|
||||
printf("getFileSize --> %i\n", fileSize);
|
||||
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
static int
|
||||
getSectionSize(CS101_IFileProvider self, int sectionNumber)
|
||||
{
|
||||
printf("getSectionSize(%i)\n", sectionNumber);
|
||||
|
||||
if (sectionNumber == 0)
|
||||
return fileSize / 2;
|
||||
if (sectionNumber == 1)
|
||||
return fileSize / 2;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
getSegmentData(CS101_IFileProvider self, int sectionNumber, int offset, int size, uint8_t* data)
|
||||
{
|
||||
printf("getSegmentData(NoS=%i, offset=%i, size=%i)\n", sectionNumber, offset, size);
|
||||
if (sectionNumber == 0)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
data[i] = sectionData1[i + offset];
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (sectionNumber == 1)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
data[i] = sectionData2[i + offset];
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
transferComplete(CS101_IFileProvider self, bool success)
|
||||
{
|
||||
printf("FILE TRANSFER COMPLETE\n");
|
||||
}
|
||||
|
||||
static int numberOfFiles = 1;
|
||||
static struct sCS101_IFileProvider fileProvider[1];
|
||||
|
||||
static void
|
||||
initializeFiles()
|
||||
{
|
||||
fileProvider[0].ca = 1;
|
||||
fileProvider[0].ioa = 30000;
|
||||
fileProvider[0].nof = CS101_NOF_TRANSPARENT_FILE;
|
||||
fileProvider[0].object = NULL;
|
||||
fileProvider[0].getFileSize = getFileSize;
|
||||
fileProvider[0].getFileDate = getFileDate;
|
||||
fileProvider[0].getSectionSize = getSectionSize;
|
||||
fileProvider[0].getSegmentData = getSegmentData;
|
||||
fileProvider[0].transferComplete = transferComplete;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < fileSize / 2; i++)
|
||||
{
|
||||
sectionData1[i] = (uint8_t)(i % 0x100);
|
||||
sectionData2[i] = (uint8_t)((i + 1) % 0x100);
|
||||
}
|
||||
}
|
||||
|
||||
static CS101_IFileProvider
|
||||
getNextFile(void* parameter, CS101_IFileProvider continueAfter)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static CS101_IFileProvider
|
||||
getFile(void* parameter, int ca, int ioa, uint16_t nof, int* errCode)
|
||||
{
|
||||
printf("getFile %i:%i (type:%i)\n", ca, ioa, nof);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < numberOfFiles; i++)
|
||||
{
|
||||
if ((ca == fileProvider[i].ca) && (ioa == fileProvider[i].ioa))
|
||||
{
|
||||
return &(fileProvider[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
IFileReceiver_finished(CS101_IFileReceiver self, CS101_FileErrorCode result)
|
||||
{
|
||||
FILE* fp = (FILE*)self->object;
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (result != CS101_FILE_ERROR_SUCCESS)
|
||||
{
|
||||
remove("upload.dat");
|
||||
}
|
||||
|
||||
printf("File upload finished: %i\n", result);
|
||||
}
|
||||
|
||||
static void
|
||||
IFileReceiver_segmentReceived(CS101_IFileReceiver self, uint8_t sectionName, int offset, int size, uint8_t* data)
|
||||
{
|
||||
FILE* fp = (FILE*)self->object;
|
||||
|
||||
printf("File upload - section %i - offset: %i - size: %i\n", sectionName, offset, size);
|
||||
|
||||
fwrite(data, size, 1, fp);
|
||||
}
|
||||
|
||||
struct sCS101_IFileReceiver myFileReceiver;
|
||||
|
||||
static CS101_IFileReceiver
|
||||
fileReadyHandler(void* parameter, int ca, int ioa, uint16_t nof, int lengthOfFile, int* err)
|
||||
{
|
||||
if ((ca == 1) && (ioa == 30001))
|
||||
{
|
||||
myFileReceiver.object = fopen("upload.dat", "wb");
|
||||
|
||||
if (myFileReceiver.object == NULL)
|
||||
{
|
||||
printf("Rejected file upload - cannot create local file\n");
|
||||
|
||||
*err = 1;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
myFileReceiver.finished = IFileReceiver_finished;
|
||||
myFileReceiver.segmentReceived = IFileReceiver_segmentReceived;
|
||||
|
||||
printf("Accepted file upload\n");
|
||||
|
||||
*err = 0;
|
||||
|
||||
return &myFileReceiver;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Rejected file upload - unknown file\n");
|
||||
|
||||
*err = 1;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
printCP56Time2a(CP56Time2a time)
|
||||
{
|
||||
printf("%02i:%02i:%02i %02i/%02i/%04i", CP56Time2a_getHour(time), CP56Time2a_getMinute(time),
|
||||
CP56Time2a_getSecond(time), CP56Time2a_getDayOfMonth(time), CP56Time2a_getMonth(time),
|
||||
CP56Time2a_getYear(time) + 2000);
|
||||
}
|
||||
|
||||
/* Callback handler to log sent or received messages (optional) */
|
||||
static void
|
||||
rawMessageHandler(void* parameter, uint8_t* msg, int msgSize, bool sent)
|
||||
{
|
||||
if (sent)
|
||||
printf("SEND: ");
|
||||
else
|
||||
printf("RCVD: ");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < msgSize; i++)
|
||||
{
|
||||
printf("%02x ", msg[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* Callback handler that is called when a clock synchronization command is received */
|
||||
static bool
|
||||
clockSyncHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, CP56Time2a newTime)
|
||||
{
|
||||
printf("Process time sync command with time ");
|
||||
printCP56Time2a(newTime);
|
||||
printf("\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Callback handler that is called when an interrogation command is received */
|
||||
static bool
|
||||
interrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
|
||||
{
|
||||
printf("Received interrogation for group %i\n", qoi);
|
||||
|
||||
if (qoi == 20)
|
||||
{ /* only handle station interrogation */
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
IMasterConnection_sendACT_CON(connection, asdu, false);
|
||||
|
||||
/* The CS101 specification only allows information objects without timestamp in GI responses */
|
||||
|
||||
CS101_ASDU newAsdu = CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject)MeasuredValueScaled_create(NULL, 100, -1, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)io, 101, 23, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)io, 102, 2300, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
newAsdu = CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
||||
|
||||
io = (InformationObject)SinglePointInformation_create(NULL, 104, true, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)SinglePointInformation_create(
|
||||
(SinglePointInformation)io, 105, false, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
newAsdu = CS101_ASDU_create(alParams, true, CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, io = (InformationObject)SinglePointInformation_create(NULL, 300, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)SinglePointInformation_create(
|
||||
(SinglePointInformation)io, 301, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)SinglePointInformation_create(
|
||||
(SinglePointInformation)io, 302, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)SinglePointInformation_create(
|
||||
(SinglePointInformation)io, 303, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)SinglePointInformation_create(
|
||||
(SinglePointInformation)io, 304, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)SinglePointInformation_create(
|
||||
(SinglePointInformation)io, 305, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)SinglePointInformation_create(
|
||||
(SinglePointInformation)io, 306, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)SinglePointInformation_create(
|
||||
(SinglePointInformation)io, 307, false, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
IMasterConnection_sendACT_TERM(connection, asdu);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
asduHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
|
||||
{
|
||||
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1)
|
||||
{
|
||||
printf("received single command\n");
|
||||
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION)
|
||||
{
|
||||
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
||||
|
||||
if (io)
|
||||
{
|
||||
if (InformationObject_getObjectAddress(io) == 5000)
|
||||
{
|
||||
SingleCommand sc = (SingleCommand)io;
|
||||
|
||||
printf("IOA: %i switch to %i\n", InformationObject_getObjectAddress(io),
|
||||
SingleCommand_getState(sc));
|
||||
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
}
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
resetCUHandler(void* parameter)
|
||||
{
|
||||
printf("Received reset CU\n");
|
||||
|
||||
CS101_Slave_flushQueues((CS101_Slave)parameter);
|
||||
}
|
||||
|
||||
static void
|
||||
linkLayerStateChanged(void* parameter, int address, LinkLayerState state)
|
||||
{
|
||||
printf("Link layer state: ");
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case LL_STATE_IDLE:
|
||||
printf("IDLE\n");
|
||||
break;
|
||||
case LL_STATE_ERROR:
|
||||
printf("ERROR\n");
|
||||
break;
|
||||
case LL_STATE_BUSY:
|
||||
printf("BUSY\n");
|
||||
break;
|
||||
case LL_STATE_AVAILABLE:
|
||||
printf("AVAILABLE\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
/* Add Ctrl-C handler */
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
const char* serialPort = "/dev/ttyUSB1";
|
||||
|
||||
if (argc > 1)
|
||||
serialPort = argv[1];
|
||||
|
||||
SerialPort port = SerialPort_create(serialPort, 9600, 8, 'E', 1);
|
||||
|
||||
if (!port)
|
||||
{
|
||||
printf("Cannot open serial port %s\n", serialPort);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* create a new slave/server instance with default link layer and application layer parameters */
|
||||
// CS101_Slave slave = CS101_Slave_create(port, NULL, NULL, IEC60870_LINK_LAYER_UNBALANCED);
|
||||
CS101_Slave slave = CS101_Slave_create(port, NULL, NULL, IEC60870_LINK_LAYER_BALANCED);
|
||||
|
||||
CS101_Slave_setLinkLayerAddress(slave, 3);
|
||||
CS101_Slave_setLinkLayerAddressOtherStation(slave, 3);
|
||||
|
||||
/* get the application layer parameters - we need them to create correct ASDUs */
|
||||
CS101_AppLayerParameters alParameters = CS101_Slave_getAppLayerParameters(slave);
|
||||
|
||||
LinkLayerParameters llParameters = CS101_Slave_getLinkLayerParameters(slave);
|
||||
llParameters->timeoutForAck = 500;
|
||||
|
||||
/* set the callback handler for the clock synchronization command */
|
||||
CS101_Slave_setClockSyncHandler(slave, clockSyncHandler, NULL);
|
||||
|
||||
/* set the callback handler for the interrogation command */
|
||||
CS101_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
||||
|
||||
/* set handler for other message types */
|
||||
CS101_Slave_setASDUHandler(slave, asduHandler, NULL);
|
||||
|
||||
/* set handler for reset CU (reset communication unit) message */
|
||||
CS101_Slave_setResetCUHandler(slave, resetCUHandler, (void*)slave);
|
||||
|
||||
/* set timeout for detecting connection loss */
|
||||
CS101_Slave_setIdleTimeout(slave, 1500);
|
||||
|
||||
/* set handler for link layer state changes */
|
||||
CS101_Slave_setLinkLayerStateChanged(slave, linkLayerStateChanged, NULL);
|
||||
|
||||
CS101_FileServer fileServer = CS101_FileServer_create(alParameters);
|
||||
|
||||
initializeFiles();
|
||||
|
||||
struct sCS101_FilesAvailable fileAvailable;
|
||||
fileAvailable.getFile = getFile;
|
||||
fileAvailable.getNextFile = getNextFile;
|
||||
fileAvailable.parameter = NULL;
|
||||
|
||||
CS101_FileServer_setFilesAvailableIfc(fileServer, &fileAvailable);
|
||||
CS101_FileServer_setFileReadyHandler(fileServer, fileReadyHandler, NULL);
|
||||
|
||||
CS101_Slave_addPlugin(slave, CS101_FileServer_getSlavePlugin(fileServer));
|
||||
|
||||
/* uncomment to log messages */
|
||||
// CS101_Slave_setRawMessageHandler(slave, rawMessageHandler, NULL);
|
||||
|
||||
int16_t scaledValue = 0;
|
||||
|
||||
uint64_t lastMessageSent = 0;
|
||||
|
||||
SerialPort_open(port);
|
||||
|
||||
while (running)
|
||||
{
|
||||
/* has to be called periodically */
|
||||
CS101_Slave_run(slave);
|
||||
|
||||
/* Enqueue a measurement every second */
|
||||
if (Hal_getTimeInMs() > (lastMessageSent + 1000))
|
||||
{
|
||||
CS101_ASDU newAsdu = CS101_ASDU_create(alParameters, false, CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
|
||||
InformationObject io =
|
||||
(InformationObject)MeasuredValueScaled_create(NULL, 110, scaledValue, IEC60870_QUALITY_GOOD);
|
||||
|
||||
scaledValue++;
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
CS101_Slave_enqueueUserDataClass2(slave, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
lastMessageSent = Hal_getTimeInMs();
|
||||
}
|
||||
|
||||
Thread_sleep(1);
|
||||
}
|
||||
|
||||
CS101_Slave_destroy(slave);
|
||||
|
||||
SerialPort_close(port);
|
||||
SerialPort_destroy(port);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
simple_client.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(simple_client
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(simple_client
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = simple_client
|
||||
PROJECT_SOURCES = simple_client.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/* scada.c — simulates a SCADA control center */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs104_connection.h"
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
void sigint_handler(int s) { running = false; }
|
||||
|
||||
/* ── called for every ASDU received from RTU ───────────────── */
|
||||
static bool asduReceivedHandler(void* p, int address, CS101_ASDU asdu) {
|
||||
int typeId = CS101_ASDU_getTypeID(asdu);
|
||||
int cot = CS101_ASDU_getCOT(asdu);
|
||||
|
||||
/* measured float value M_ME_NC_1 */
|
||||
if (typeId == M_ME_NC_1) {
|
||||
int i;
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++) {
|
||||
MeasuredValueShort mv = (MeasuredValueShort)
|
||||
CS101_ASDU_getElement(asdu, i);
|
||||
int ioa = InformationObject_getObjectAddress((InformationObject)mv);
|
||||
float value = MeasuredValueShort_getValue(mv);
|
||||
InformationObject_destroy((InformationObject)mv);
|
||||
|
||||
if (ioa == 100)
|
||||
printf("[SCADA ←] Voltage IOA=100 %.2f kV\n", value);
|
||||
else if (ioa == 101)
|
||||
printf("[SCADA ←] Current IOA=101 %.1f A\n", value);
|
||||
else
|
||||
printf("[SCADA ←] Float IOA=%d %.2f\n", ioa, value);
|
||||
}
|
||||
}
|
||||
|
||||
/* single point M_SP_NA_1 */
|
||||
else if (typeId == M_SP_NA_1) {
|
||||
int i;
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++) {
|
||||
SinglePointInformation sp = (SinglePointInformation)
|
||||
CS101_ASDU_getElement(asdu, i);
|
||||
int ioa = InformationObject_getObjectAddress((InformationObject)sp);
|
||||
bool state = SinglePointInformation_getValue(sp);
|
||||
InformationObject_destroy((InformationObject)sp);
|
||||
|
||||
if (ioa == 200)
|
||||
printf("[SCADA ←] Breaker1 IOA=200 %s\n", state ? "ON" : "OFF");
|
||||
else if (ioa == 201)
|
||||
printf("[SCADA ←] Breaker2 IOA=201 %s\n", state ? "ON" : "OFF");
|
||||
else
|
||||
printf("[SCADA ←] Point IOA=%d %s\n", ioa, state ? "ON" : "OFF");
|
||||
}
|
||||
}
|
||||
|
||||
/* command confirmation */
|
||||
else if (typeId == C_SC_NA_1) {
|
||||
if (cot == CS101_COT_ACTIVATION_CON)
|
||||
printf("[SCADA ←] Command confirmed by RTU\n");
|
||||
else if (cot == CS101_COT_UNKNOWN_IOA)
|
||||
printf("[SCADA ←] Command rejected — unknown IOA\n");
|
||||
}
|
||||
|
||||
else {
|
||||
printf("[SCADA ←] TypeID=%d COT=%d\n", typeId, cot);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ── connection state ───────────────────────────────────────── */
|
||||
static void connectionHandler(void* p, CS104_Connection con, CS104_ConnectionEvent event) {
|
||||
if (event == CS104_CONNECTION_OPENED)
|
||||
printf("[SCADA] Connected to RTU\n");
|
||||
else if (event == CS104_CONNECTION_CLOSED)
|
||||
printf("[SCADA] Disconnected from RTU\n");
|
||||
else if (event == CS104_CONNECTION_STARTDT_CON_RECEIVED)
|
||||
printf("[SCADA] STARTDT confirmed — data transfer active\n");
|
||||
else if (event == CS104_CONNECTION_STOPDT_CON_RECEIVED)
|
||||
printf("[SCADA] STOPDT confirmed\n");
|
||||
}
|
||||
|
||||
/* ── send a single command to RTU ───────────────────────────── */
|
||||
void send_command(CS104_Connection con, int ioa, bool state) {
|
||||
CS101_AppLayerParameters alParams =
|
||||
CS104_Connection_getAppLayerParameters(con);
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_create(alParams, false,
|
||||
CS101_COT_ACTIVATION, 0, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject)
|
||||
SingleCommand_create(NULL, ioa, state, false, 0);
|
||||
|
||||
CS101_ASDU_addInformationObject(asdu, io);
|
||||
InformationObject_destroy(io);
|
||||
|
||||
CS104_Connection_sendASDU(con, asdu);
|
||||
CS101_ASDU_destroy(asdu);
|
||||
|
||||
printf("[SCADA →] Command sent — IOA=%d state=%s\n",
|
||||
ioa, state ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
/* ── send general interrogation ─────────────────────────────── */
|
||||
void send_gi(CS104_Connection con) {
|
||||
CS104_Connection_sendInterrogationCommand(con,
|
||||
CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
||||
printf("[SCADA →] General Interrogation sent\n");
|
||||
}
|
||||
|
||||
/* ── main ───────────────────────────────────────────────────── */
|
||||
int main(int argc, char** argv) {
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
const char* ip = (argc > 1) ? argv[1] : "127.0.0.1";
|
||||
|
||||
printf("[SCADA] Connecting to RTU at %s:2404\n\n", ip);
|
||||
|
||||
CS104_Connection con = CS104_Connection_create(ip, 2404);
|
||||
|
||||
CS104_Connection_setConnectionHandler(con, connectionHandler, NULL);
|
||||
CS104_Connection_setASDUReceivedHandler(con, asduReceivedHandler, NULL);
|
||||
|
||||
CS104_Connection_connect(con);
|
||||
Thread_sleep(1000);
|
||||
|
||||
/* start data transfer */
|
||||
CS104_Connection_sendStartDT(con);
|
||||
Thread_sleep(500);
|
||||
|
||||
/* request all current values */
|
||||
send_gi(con);
|
||||
Thread_sleep(2000);
|
||||
|
||||
/* main loop — send commands periodically */
|
||||
uint64_t lastCmd = 0;
|
||||
bool b1_state = false;
|
||||
bool b2_state = false;
|
||||
|
||||
printf("\n[SCADA] Running — commands every 5s, Ctrl+C to stop\n\n");
|
||||
|
||||
while (running) {
|
||||
uint64_t now = Hal_getMonotonicTimeInMs();
|
||||
|
||||
/* every 5s — toggle breaker1 then breaker2 alternately */
|
||||
if (now >= lastCmd + 5000) {
|
||||
lastCmd = now;
|
||||
|
||||
static int cmd_count = 0;
|
||||
cmd_count++;
|
||||
|
||||
if (cmd_count % 2 == 0) {
|
||||
b1_state = !b1_state;
|
||||
send_command(con, 200, b1_state); /* toggle breaker1 */
|
||||
} else {
|
||||
b2_state = !b2_state;
|
||||
send_command(con, 201, b2_state); /* toggle breaker2 */
|
||||
}
|
||||
}
|
||||
|
||||
Thread_sleep(10);
|
||||
}
|
||||
|
||||
printf("\n[SCADA] Shutting down\n");
|
||||
CS104_Connection_sendStopDT(con);
|
||||
Thread_sleep(500);
|
||||
CS104_Connection_destroy(con);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
cs104_client_async.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs104_client_async
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs104_client_async
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = cs104_client_async
|
||||
PROJECT_SOURCES = cs104_client_async.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
#include "cs104_connection.h"
|
||||
#include "hal_time.h"
|
||||
#include "hal_thread.h"
|
||||
#include <signal.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static bool running = true;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
running = false;
|
||||
}
|
||||
|
||||
static Semaphore lastEventLock = NULL;
|
||||
static CS104_ConnectionEvent lastEvent = (CS104_ConnectionEvent)-1;
|
||||
|
||||
/* Callback handler to log sent or received messages (optional) */
|
||||
static void
|
||||
rawMessageHandler (void* parameter, uint8_t* msg, int msgSize, bool sent)
|
||||
{
|
||||
if (sent)
|
||||
printf("SEND: ");
|
||||
else
|
||||
printf("RCVD: ");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < msgSize; i++) {
|
||||
printf("%02x ", msg[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
/* Connection event handler */
|
||||
static void
|
||||
connectionHandler (void* parameter, CS104_Connection connection, CS104_ConnectionEvent event)
|
||||
{
|
||||
switch (event) {
|
||||
case CS104_CONNECTION_OPENED:
|
||||
printf("Connection established\n");
|
||||
break;
|
||||
case CS104_CONNECTION_CLOSED:
|
||||
printf("Connection closed\n");
|
||||
break;
|
||||
case CS104_CONNECTION_STARTDT_CON_RECEIVED:
|
||||
printf("Received STARTDT_CON\n");
|
||||
break;
|
||||
case CS104_CONNECTION_STOPDT_CON_RECEIVED:
|
||||
printf("Received STOPDT_CON\n");
|
||||
break;
|
||||
case CS104_CONNECTION_FAILED:
|
||||
printf("Connection failed\n");
|
||||
}
|
||||
|
||||
Semaphore_wait(lastEventLock);
|
||||
lastEvent = event;
|
||||
Semaphore_post(lastEventLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* CS101_ASDUReceivedHandler implementation
|
||||
*
|
||||
* For CS104 the address parameter has to be ignored
|
||||
*/
|
||||
static bool
|
||||
asduReceivedHandler (void* parameter, int address, CS101_ASDU asdu)
|
||||
{
|
||||
printf("RECVD ASDU type: %s(%i) elements: %i\n",
|
||||
TypeID_toString(CS101_ASDU_getTypeID(asdu)),
|
||||
CS101_ASDU_getTypeID(asdu),
|
||||
CS101_ASDU_getNumberOfElements(asdu));
|
||||
|
||||
if (CS101_ASDU_getTypeID(asdu) == M_ME_TE_1)
|
||||
{
|
||||
printf(" measured scaled values with CP56Time2a timestamp:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
MeasuredValueScaledWithCP56Time2a io =
|
||||
(MeasuredValueScaledWithCP56Time2a) CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n",
|
||||
InformationObject_getObjectAddress((InformationObject) io),
|
||||
MeasuredValueScaled_getValue((MeasuredValueScaled) io)
|
||||
);
|
||||
|
||||
MeasuredValueScaledWithCP56Time2a_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == M_SP_NA_1)
|
||||
{
|
||||
printf(" single point information:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
SinglePointInformation io =
|
||||
(SinglePointInformation) CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n",
|
||||
InformationObject_getObjectAddress((InformationObject) io),
|
||||
SinglePointInformation_getValue((SinglePointInformation) io)
|
||||
);
|
||||
|
||||
SinglePointInformation_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == C_TS_TA_1) {
|
||||
printf(" test command with timestamp\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
lastEventLock = Semaphore_create(1);
|
||||
|
||||
const char* ip = "localhost";
|
||||
uint16_t port = IEC_60870_5_104_DEFAULT_PORT;
|
||||
const char* localIp = NULL;
|
||||
int localPort = -1;
|
||||
|
||||
if (argc > 1)
|
||||
ip = argv[1];
|
||||
|
||||
if (argc > 2)
|
||||
port = atoi(argv[2]);
|
||||
|
||||
if (argc > 3)
|
||||
localIp = argv[3];
|
||||
|
||||
if (argc > 4)
|
||||
port = atoi(argv[4]);
|
||||
|
||||
printf("Connecting to: %s:%i\n", ip, port);
|
||||
CS104_Connection con = CS104_Connection_create(ip, port);
|
||||
|
||||
CS104_APCIParameters apciParameters = CS104_Connection_getAPCIParameters(con);
|
||||
apciParameters->t0 = 2;
|
||||
|
||||
CS101_AppLayerParameters alParams = CS104_Connection_getAppLayerParameters(con);
|
||||
alParams->originatorAddress = 3;
|
||||
|
||||
CS104_Connection_setConnectionHandler(con, connectionHandler, NULL);
|
||||
CS104_Connection_setASDUReceivedHandler(con, asduReceivedHandler, NULL);
|
||||
|
||||
/* optional bind to local IP address/interface */
|
||||
if (localIp)
|
||||
CS104_Connection_setLocalAddress(con, localIp, localPort);
|
||||
|
||||
/* uncomment to log messages */
|
||||
//CS104_Connection_setRawMessageHandler(con, rawMessageHandler, NULL);
|
||||
|
||||
CS104_Connection_connectAsync(con);
|
||||
|
||||
bool startDTSent = false;
|
||||
|
||||
uint64_t lastGiSent = 0;
|
||||
|
||||
while (running)
|
||||
{
|
||||
Semaphore_wait(lastEventLock);
|
||||
|
||||
if (lastEvent == CS104_CONNECTION_OPENED) {
|
||||
CS104_Connection_sendStartDT(con);
|
||||
startDTSent = true;
|
||||
}
|
||||
else if (lastEvent == CS104_CONNECTION_CLOSED || lastEvent == CS104_CONNECTION_FAILED) {
|
||||
running = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (startDTSent)
|
||||
{
|
||||
uint64_t currentTime = Hal_getTimeInMs();
|
||||
|
||||
if (currentTime < lastGiSent)
|
||||
lastGiSent = currentTime;
|
||||
|
||||
if (currentTime > lastGiSent + 10000) {
|
||||
lastGiSent = currentTime;
|
||||
|
||||
CS104_Connection_sendInterrogationCommand(con, CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore_post(lastEventLock);
|
||||
|
||||
Thread_sleep(100);
|
||||
}
|
||||
|
||||
CS104_Connection_destroy(con);
|
||||
|
||||
printf("exit\n");
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
add_executable(cs104_client_no_threads cs104_client_no_threads.c)
|
||||
|
||||
target_link_libraries(cs104_client_no_threads PRIVATE lib60870)
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
#include "cs104_connection.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static bool
|
||||
asduReceived(void* parameter, int connectionIndex, CS101_ASDU asdu)
|
||||
{
|
||||
(void)parameter;
|
||||
(void)connectionIndex;
|
||||
|
||||
printf("RECVD ASDU type: %s(%i) elements: %i\n", TypeID_toString(CS101_ASDU_getTypeID(asdu)),
|
||||
CS101_ASDU_getTypeID(asdu), CS101_ASDU_getNumberOfElements(asdu));
|
||||
|
||||
if (CS101_ASDU_getTypeID(asdu) == M_ME_TE_1)
|
||||
{
|
||||
printf(" measured scaled values with CP56Time2a timestamp:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
MeasuredValueScaledWithCP56Time2a io = (MeasuredValueScaledWithCP56Time2a)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
MeasuredValueScaled_getValue((MeasuredValueScaled)io));
|
||||
|
||||
MeasuredValueScaledWithCP56Time2a_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == M_SP_NA_1)
|
||||
{
|
||||
printf(" single point information:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
SinglePointInformation io = (SinglePointInformation)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
SinglePointInformation_getValue((SinglePointInformation)io));
|
||||
|
||||
SinglePointInformation_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == C_TS_TA_1)
|
||||
{
|
||||
printf(" test command with timestamp\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
connectionHandler(void* parameter, CS104_Connection connection, CS104_ConnectionEvent event)
|
||||
{
|
||||
(void)parameter;
|
||||
(void)connection;
|
||||
|
||||
printf("[EVENT] %d\n", event);
|
||||
if (event == CS104_CONNECTION_STARTDT_CON_RECEIVED)
|
||||
{
|
||||
printf("Sending interrogation command...\n");
|
||||
// CS104_Connection_sendInterrogationCommand(connection, CS101_COT_ACTIVATION, 1,
|
||||
// (QualifierOfInterrogation)20); /* station interrogation */
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
const char* host = (argc > 1) ? argv[1] : "127.0.0.1";
|
||||
int port = (argc > 2) ? atoi(argv[2]) : -1;
|
||||
|
||||
CS104_Connection con = CS104_Connection_create(host, port);
|
||||
|
||||
if (!con)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate connection\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
CS104_Connection_setASDUReceivedHandler(con, asduReceived, NULL);
|
||||
CS104_Connection_setConnectionHandler(con, connectionHandler, NULL);
|
||||
|
||||
CS104_APCIParameters apciParams = CS104_Connection_getAPCIParameters(con);
|
||||
|
||||
apciParams->t3 = 2;
|
||||
|
||||
printf("APCI parameters:\n");
|
||||
printf(" t0: %i\n", apciParams->t0);
|
||||
printf(" t1: %i\n", apciParams->t1);
|
||||
printf(" t2: %i\n", apciParams->t2);
|
||||
printf(" t3: %i\n", apciParams->t3);
|
||||
printf(" k: %i\n", apciParams->k);
|
||||
printf(" w: %i\n", apciParams->w);
|
||||
|
||||
|
||||
if (!CS104_Connection_startThreadless(con))
|
||||
{
|
||||
fprintf(stderr, "Failed to start threadless connection\n");
|
||||
CS104_Connection_destroy(con);
|
||||
return 2;
|
||||
}
|
||||
|
||||
CS104_Connection_sendStartDT(con);
|
||||
|
||||
/* Simple loop: run for up to 30 seconds */
|
||||
int iterations = 0;
|
||||
|
||||
while (CS104_Connection_isThreadless(con))
|
||||
{
|
||||
if (!CS104_Connection_run(con, 100))
|
||||
{
|
||||
break; /* connection closed or failed */
|
||||
}
|
||||
|
||||
if (iterations == 100)
|
||||
{
|
||||
// printf("Sending interrogation command...\n");
|
||||
// CS104_Connection_sendInterrogationCommand(con, CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
||||
}
|
||||
|
||||
iterations++;
|
||||
}
|
||||
|
||||
CS104_Connection_stopThreadless(con);
|
||||
CS104_Connection_destroy(con);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
cs104_redundancy_server.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs104_redundancy_server
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs104_redundancy_server
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = cs104_redundancy_server
|
||||
PROJECT_SOURCES = cs104_redundancy_server.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs104_slave.h"
|
||||
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
#define MAX_GI_SESSIONS 10
|
||||
|
||||
typedef struct {
|
||||
int state; /* 0 - idle, 1 - GI running */
|
||||
IMasterConnection connection;
|
||||
int progress;
|
||||
int oa; /* originator address */
|
||||
} GISession;
|
||||
|
||||
static GISession giSessions[MAX_GI_SESSIONS];
|
||||
static Semaphore gi_lock;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
(void)signalId;
|
||||
running = false;
|
||||
}
|
||||
|
||||
void
|
||||
printCP56Time2a(CP56Time2a time)
|
||||
{
|
||||
printf("%02i:%02i:%02i %02i/%02i/%04i", CP56Time2a_getHour(time), CP56Time2a_getMinute(time),
|
||||
CP56Time2a_getSecond(time), CP56Time2a_getDayOfMonth(time), CP56Time2a_getMonth(time),
|
||||
CP56Time2a_getYear(time) + 2000);
|
||||
}
|
||||
|
||||
static void
|
||||
handleGISession(GISession* session)
|
||||
{
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(session->connection);
|
||||
|
||||
if (session->progress == 0)
|
||||
{
|
||||
/* send scaled values */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, session->oa, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject)MeasuredValueScaled_create(NULL, 100, -1, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)io, 101, 23, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)io, 102, 2300, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(session->connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
session->progress = 1;
|
||||
}
|
||||
else if (session->progress == 1)
|
||||
{
|
||||
/* send single points */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, session->oa, 1, false, false);
|
||||
|
||||
InformationObject io =
|
||||
(InformationObject)SinglePointInformation_create(NULL, 104, true, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)io, 105, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(session->connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
session->progress = 2;
|
||||
}
|
||||
else if (session->progress == 2)
|
||||
{
|
||||
/* send more single points */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, true, CS101_COT_INTERROGATED_BY_STATION, session->oa, 1, false, false);
|
||||
|
||||
SinglePointInformation io = SinglePointInformation_create(NULL, 300, true, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)io);
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 301, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 302, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 303, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 304, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 305, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 306, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 307, false, IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(session->connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
session->progress = 3;
|
||||
}
|
||||
else if (session->progress == 3)
|
||||
{
|
||||
/* send termination message */
|
||||
CS101_ASDU tempAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, session->oa, 1, false, false);
|
||||
|
||||
IMasterConnection_sendACT_TERM(session->connection, tempAsdu);
|
||||
|
||||
CS101_ASDU_destroy(tempAsdu);
|
||||
|
||||
session->state = 0;
|
||||
session->connection = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handleGeneralInterrogation()
|
||||
{
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
for (int i = 0; i < MAX_GI_SESSIONS; i++)
|
||||
{
|
||||
if (giSessions[i].state == 1)
|
||||
{
|
||||
handleGISession(&giSessions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
|
||||
/* Callback handler to log sent or received messages (optional) */
|
||||
static void
|
||||
rawMessageHandler(void* parameter, IMasterConnection conneciton, uint8_t* msg, int msgSize, bool sent)
|
||||
{
|
||||
if (sent)
|
||||
printf("SEND: ");
|
||||
else
|
||||
printf("RCVD: ");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < msgSize; i++)
|
||||
{
|
||||
printf("%02x ", msg[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool
|
||||
clockSyncHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, CP56Time2a newTime)
|
||||
{
|
||||
printf("Process time sync command with time ");
|
||||
printCP56Time2a(newTime);
|
||||
printf("\n");
|
||||
|
||||
uint64_t newSystemTimeInMs = CP56Time2a_toMsTimestamp(newTime);
|
||||
|
||||
/* Set time for ACT_CON message */
|
||||
CP56Time2a_setFromMsTimestamp(newTime, Hal_getTimeInMs());
|
||||
|
||||
/* TODO update system time here */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
interrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
|
||||
{
|
||||
(void)parameter;
|
||||
|
||||
int ca = CS101_ASDU_getCA(asdu);
|
||||
|
||||
printf("Received interrogation for CASDU %i and group %i\n", ca, qoi);
|
||||
|
||||
if (ca == 1) /* only handle interrogation for CA 1 */
|
||||
{
|
||||
if (qoi == 20) /* only handle station interrogation */
|
||||
{
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
/* find existing session for this connection or a free slot */
|
||||
GISession* session = NULL;
|
||||
|
||||
for (int i = 0; i < MAX_GI_SESSIONS; i++)
|
||||
{
|
||||
if (giSessions[i].state == 1 && giSessions[i].connection == connection)
|
||||
{
|
||||
/* GI already running for this connection - reject */
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
Semaphore_post(gi_lock);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_GI_SESSIONS; i++)
|
||||
{
|
||||
if (giSessions[i].state == 0)
|
||||
{
|
||||
session = &giSessions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (session)
|
||||
{
|
||||
session->state = 1;
|
||||
session->connection = connection;
|
||||
session->progress = 0;
|
||||
session->oa = (uint8_t)CS101_ASDU_getOA(asdu);
|
||||
IMasterConnection_sendACT_CON(connection, asdu, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no free slot - reject */
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* send error response */
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
asduHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
|
||||
{
|
||||
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1)
|
||||
{
|
||||
printf("received single command\n");
|
||||
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION)
|
||||
{
|
||||
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
||||
|
||||
if (io)
|
||||
{
|
||||
if (InformationObject_getObjectAddress(io) == 5000)
|
||||
{
|
||||
SingleCommand sc = (SingleCommand)io;
|
||||
|
||||
printf("IOA: %i switch to %i\n", InformationObject_getObjectAddress(io),
|
||||
SingleCommand_getState(sc));
|
||||
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR: message has no valid information object\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
connectionRequestHandler(void* parameter, const char* ipAddress)
|
||||
{
|
||||
printf("New connection request from %s\n", ipAddress);
|
||||
|
||||
#if 0
|
||||
if (strcmp(ipAddress, "127.0.0.1") == 0) {
|
||||
printf("Accept connection\n");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
printf("Deny connection\n");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
connectionEventHandler(void* parameter, IMasterConnection con, CS104_PeerConnectionEvent event)
|
||||
{
|
||||
if (event == CS104_CON_EVENT_CONNECTION_OPENED)
|
||||
{
|
||||
printf("Connection opened (%p)\n", con);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_CONNECTION_CLOSED)
|
||||
{
|
||||
printf("Connection closed (%p)\n", con);
|
||||
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
for (int i = 0; i < MAX_GI_SESSIONS; i++)
|
||||
{
|
||||
if (giSessions[i].connection == con)
|
||||
{
|
||||
giSessions[i].state = 0;
|
||||
giSessions[i].connection = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_ACTIVATED)
|
||||
{
|
||||
printf("Connection activated (%p)\n", con);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_DEACTIVATED)
|
||||
{
|
||||
printf("Connection deactivated (%p)\n", con);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
/* Add Ctrl-C handler */
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
gi_lock = Semaphore_create(1);
|
||||
|
||||
memset(giSessions, 0, sizeof(giSessions));
|
||||
|
||||
/* create a new slave/server instance with default connection parameters and
|
||||
* default message queue size */
|
||||
CS104_Slave slave = CS104_Slave_create(100, 100);
|
||||
|
||||
CS104_Slave_setLocalAddress(slave, "0.0.0.0");
|
||||
|
||||
/* Set mode to a multiple redundancy groups
|
||||
* NOTE: library has to be compiled with CONFIG_CS104_SUPPORT_SERVER_MODE_SINGLE_REDUNDANCY_GROUP enabled (=1)
|
||||
*/
|
||||
CS104_Slave_setServerMode(slave, CS104_MODE_MULTIPLE_REDUNDANCY_GROUPS);
|
||||
|
||||
CS104_RedundancyGroup redGroup1 = CS104_RedundancyGroup_create("red-group-1");
|
||||
CS104_RedundancyGroup_addAllowedClient(redGroup1, "192.168.2.9");
|
||||
|
||||
CS104_RedundancyGroup redGroup2 = CS104_RedundancyGroup_create("red-group-2");
|
||||
CS104_RedundancyGroup_addAllowedClient(redGroup2, "192.168.2.223");
|
||||
CS104_RedundancyGroup_addAllowedClient(redGroup2, "192.168.2.222");
|
||||
|
||||
CS104_RedundancyGroup redGroup3 = CS104_RedundancyGroup_create("catch-all");
|
||||
|
||||
CS104_Slave_addRedundancyGroup(slave, redGroup1);
|
||||
CS104_Slave_addRedundancyGroup(slave, redGroup2);
|
||||
CS104_Slave_addRedundancyGroup(slave, redGroup3);
|
||||
|
||||
/* get the connection parameters - we need them to create correct ASDUs */
|
||||
CS101_AppLayerParameters alParams = CS104_Slave_getAppLayerParameters(slave);
|
||||
|
||||
/* set the callback handler for the clock synchronization command */
|
||||
CS104_Slave_setClockSyncHandler(slave, clockSyncHandler, NULL);
|
||||
|
||||
/* set the callback handler for the interrogation command */
|
||||
CS104_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
||||
|
||||
/* set handler for other message types */
|
||||
CS104_Slave_setASDUHandler(slave, asduHandler, NULL);
|
||||
|
||||
/* set handler to handle connection requests (optional) */
|
||||
CS104_Slave_setConnectionRequestHandler(slave, connectionRequestHandler, NULL);
|
||||
|
||||
/* set handler to track connection events (optional) */
|
||||
CS104_Slave_setConnectionEventHandler(slave, connectionEventHandler, NULL);
|
||||
|
||||
/* uncomment to log messages */
|
||||
// CS104_Slave_setRawMessageHandler(slave, rawMessageHandler, NULL);
|
||||
|
||||
CS104_Slave_start(slave);
|
||||
|
||||
if (CS104_Slave_isRunning(slave) == false)
|
||||
{
|
||||
printf("Starting server failed!\n");
|
||||
goto exit_program;
|
||||
}
|
||||
|
||||
int16_t scaledValue = 0;
|
||||
|
||||
uint64_t lastPeriodicTransmission = 0;
|
||||
|
||||
while (running)
|
||||
{
|
||||
handleGeneralInterrogation();
|
||||
|
||||
if (Hal_getMonotonicTimeInMs() - lastPeriodicTransmission >= 1000)
|
||||
{
|
||||
lastPeriodicTransmission = Hal_getMonotonicTimeInMs();
|
||||
|
||||
CS101_ASDU newAsdu = CS101_ASDU_create(alParams, false, CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
|
||||
InformationObject io =
|
||||
(InformationObject)MeasuredValueScaled_create(NULL, 110, scaledValue, IEC60870_QUALITY_GOOD);
|
||||
|
||||
scaledValue++;
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
/* Add ASDU to slave event queue */
|
||||
CS104_Slave_enqueueASDU(slave, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
}
|
||||
|
||||
Thread_sleep(10);
|
||||
}
|
||||
|
||||
CS104_Slave_stop(slave);
|
||||
|
||||
exit_program:
|
||||
CS104_Slave_destroy(slave);
|
||||
|
||||
Semaphore_destroy(gi_lock);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
simple_server.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs104_server
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs104_server
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = simple_server
|
||||
PROJECT_SOURCES = simple_server.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
/* rtu.c — simulates a substation RTU */
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "cs104_slave.h"
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
static float voltage = 11.2; /* kV */
|
||||
static float current = 450.0; /* A */
|
||||
static bool breaker1 = false; /* OFF */
|
||||
static bool breaker2 = false; /* OFF */
|
||||
|
||||
void sigint_handler(int s) { running = false; }
|
||||
|
||||
/* ── connection events ──────────────────────────────────────── */
|
||||
static void connectionEventHandler(void* p, IMasterConnection con, CS104_PeerConnectionEvent event) {
|
||||
if (event == CS104_CON_EVENT_CONNECTION_OPENED)
|
||||
printf("[RTU] SCADA connected\n");
|
||||
else if (event == CS104_CON_EVENT_CONNECTION_CLOSED)
|
||||
printf("[RTU] SCADA disconnected\n");
|
||||
else if (event == CS104_CON_EVENT_ACTIVATED)
|
||||
printf("[RTU] Data transfer started\n");
|
||||
}
|
||||
|
||||
/* ── accept all connections ─────────────────────────────────── */
|
||||
static bool connectionRequestHandler(void* p, const char* ip) {
|
||||
printf("[RTU] Connection request from %s — accepted\n", ip);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ── send all current values (General Interrogation) ────────── */
|
||||
static bool interrogationHandler(void* p, IMasterConnection con, CS101_ASDU asdu, uint8_t qoi) {
|
||||
printf("[RTU] General Interrogation received\n");
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(con);
|
||||
|
||||
/* confirm GI received */
|
||||
IMasterConnection_sendACT_CON(con, asdu, false);
|
||||
|
||||
/* send voltage IOA 100 */
|
||||
CS101_ASDU a1 = CS101_ASDU_create(alParams, false,
|
||||
CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
||||
InformationObject io1 = (InformationObject)
|
||||
MeasuredValueShort_create(NULL, 100, voltage, IEC60870_QUALITY_GOOD);
|
||||
CS101_ASDU_addInformationObject(a1, io1);
|
||||
InformationObject_destroy(io1);
|
||||
IMasterConnection_sendASDU(con, a1);
|
||||
CS101_ASDU_destroy(a1);
|
||||
|
||||
/* send current IOA 101 */
|
||||
CS101_ASDU a2 = CS101_ASDU_create(alParams, false,
|
||||
CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
||||
InformationObject io2 = (InformationObject)
|
||||
MeasuredValueShort_create(NULL, 101, current, IEC60870_QUALITY_GOOD);
|
||||
CS101_ASDU_addInformationObject(a2, io2);
|
||||
InformationObject_destroy(io2);
|
||||
IMasterConnection_sendASDU(con, a2);
|
||||
CS101_ASDU_destroy(a2);
|
||||
|
||||
/* send breaker1 IOA 200 */
|
||||
CS101_ASDU a3 = CS101_ASDU_create(alParams, false,
|
||||
CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
||||
InformationObject io3 = (InformationObject)
|
||||
SinglePointInformation_create(NULL, 200, breaker1, IEC60870_QUALITY_GOOD);
|
||||
CS101_ASDU_addInformationObject(a3, io3);
|
||||
InformationObject_destroy(io3);
|
||||
IMasterConnection_sendASDU(con, a3);
|
||||
CS101_ASDU_destroy(a3);
|
||||
|
||||
/* send breaker2 IOA 201 */
|
||||
CS101_ASDU a4 = CS101_ASDU_create(alParams, false,
|
||||
CS101_COT_INTERROGATED_BY_STATION, 0, 1, false, false);
|
||||
InformationObject io4 = (InformationObject)
|
||||
SinglePointInformation_create(NULL, 201, breaker2, IEC60870_QUALITY_GOOD);
|
||||
CS101_ASDU_addInformationObject(a4, io4);
|
||||
InformationObject_destroy(io4);
|
||||
IMasterConnection_sendASDU(con, a4);
|
||||
CS101_ASDU_destroy(a4);
|
||||
|
||||
/* GI complete */
|
||||
IMasterConnection_sendACT_TERM(con, asdu);
|
||||
printf("[RTU] GI complete — sent voltage=%.1f current=%.1f breaker1=%d breaker2=%d\n",
|
||||
voltage, current, breaker1, breaker2);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ── handle commands from SCADA ─────────────────────────────── */
|
||||
static bool asduHandler(void* p, IMasterConnection con, CS101_ASDU asdu) {
|
||||
|
||||
/* single command C_SC_NA_1 */
|
||||
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1) {
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION) {
|
||||
|
||||
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
||||
if (io) {
|
||||
int ioa = InformationObject_getObjectAddress(io);
|
||||
bool state = SingleCommand_getState((SingleCommand)io);
|
||||
InformationObject_destroy(io);
|
||||
|
||||
printf("[RTU] Command received — IOA=%d state=%s\n",
|
||||
ioa, state ? "ON" : "OFF");
|
||||
|
||||
if (ioa == 200) {
|
||||
breaker1 = state;
|
||||
printf("[RTU] Breaker 1 → %s\n", breaker1 ? "ON" : "OFF");
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else if (ioa == 201) {
|
||||
breaker2 = state;
|
||||
printf("[RTU] Breaker 2 → %s\n", breaker2 ? "ON" : "OFF");
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else {
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
}
|
||||
|
||||
IMasterConnection_sendASDU(con, asdu);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ── main ───────────────────────────────────────────────────── */
|
||||
int main() {
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
CS104_Slave slave = CS104_Slave_create(10, 10);
|
||||
CS104_Slave_setLocalAddress(slave, "0.0.0.0");
|
||||
CS104_Slave_setLocalPort(slave, 2404);
|
||||
|
||||
CS101_AppLayerParameters alParams = CS104_Slave_getAppLayerParameters(slave);
|
||||
|
||||
CS104_Slave_setConnectionEventHandler(slave, connectionEventHandler, NULL);
|
||||
CS104_Slave_setConnectionRequestHandler(slave, connectionRequestHandler, NULL);
|
||||
CS104_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
||||
CS104_Slave_setASDUHandler(slave, asduHandler, NULL);
|
||||
|
||||
CS104_Slave_start(slave);
|
||||
|
||||
if (!CS104_Slave_isRunning(slave)) {
|
||||
printf("[RTU] Failed to start!\n");
|
||||
CS104_Slave_destroy(slave);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("[RTU] Running on port 2404\n");
|
||||
printf("[RTU] IOA 100=voltage 101=current 200=breaker1 201=breaker2\n\n");
|
||||
|
||||
uint64_t lastSent = 0;
|
||||
|
||||
while (running) {
|
||||
uint64_t now = Hal_getMonotonicTimeInMs();
|
||||
|
||||
/* every 3s — send spontaneous measurements */
|
||||
if (now >= lastSent + 3000) {
|
||||
lastSent = now;
|
||||
|
||||
/* simulate small fluctuations */
|
||||
voltage += ((rand() % 10) - 5) * 0.1;
|
||||
current += ((rand() % 20) - 10) * 1.0;
|
||||
|
||||
/* voltage — IOA 100 */
|
||||
CS101_ASDU a1 = CS101_ASDU_create(alParams, false,
|
||||
CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
InformationObject io1 = (InformationObject)
|
||||
MeasuredValueShort_create(NULL, 100, voltage, IEC60870_QUALITY_GOOD);
|
||||
CS101_ASDU_addInformationObject(a1, io1);
|
||||
InformationObject_destroy(io1);
|
||||
CS104_Slave_enqueueASDU(slave, a1);
|
||||
CS101_ASDU_destroy(a1);
|
||||
|
||||
/* current — IOA 101 */
|
||||
CS101_ASDU a2 = CS101_ASDU_create(alParams, false,
|
||||
CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
InformationObject io2 = (InformationObject)
|
||||
MeasuredValueShort_create(NULL, 101, current, IEC60870_QUALITY_GOOD);
|
||||
CS101_ASDU_addInformationObject(a2, io2);
|
||||
InformationObject_destroy(io2);
|
||||
CS104_Slave_enqueueASDU(slave, a2);
|
||||
CS101_ASDU_destroy(a2);
|
||||
|
||||
printf("[RTU →] voltage=%.2fkV current=%.1fA breaker1=%s breaker2=%s\n",
|
||||
voltage, current,
|
||||
breaker1 ? "ON" : "OFF",
|
||||
breaker2 ? "ON" : "OFF");
|
||||
}
|
||||
|
||||
Thread_sleep(10);
|
||||
}
|
||||
|
||||
printf("[RTU] Shutting down\n");
|
||||
CS104_Slave_stop(slave);
|
||||
CS104_Slave_destroy(slave);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
cs104_server_files.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs104_server_files
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs104_server_files
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,523 @@
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs101_file_service.h"
|
||||
#include "cs104_slave.h"
|
||||
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
static int gi_state = 0; /* 0 - no GI running, 1 - GI is running */
|
||||
static IMasterConnection gi_connection = NULL;
|
||||
static int gi_progress = 0;
|
||||
static int gi_oa = 0; /* originator address */
|
||||
static Semaphore gi_lock;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
(void)signalId;
|
||||
running = false;
|
||||
}
|
||||
|
||||
static sCS101_StaticASDU _asdu;
|
||||
static uint8_t ioBuf[250];
|
||||
|
||||
static void
|
||||
handleGeneralInterrogation()
|
||||
{
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
if (gi_state == 1)
|
||||
{
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(gi_connection);
|
||||
|
||||
if (gi_progress == 0)
|
||||
{
|
||||
/* send scaled values */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)&ioBuf, 100, -1, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)&ioBuf, 101, 23, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)MeasuredValueScaled_create((MeasuredValueScaled)&ioBuf, 102, 2300,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
gi_progress = 1;
|
||||
}
|
||||
else if (gi_progress == 1)
|
||||
{
|
||||
/* send single points */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 104, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 105, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
gi_progress = 2;
|
||||
}
|
||||
else if (gi_progress == 2)
|
||||
{
|
||||
/* send more single points */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, true, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 300, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 301, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 302, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 303, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 304, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 305, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 306, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 307, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
gi_progress = 3;
|
||||
}
|
||||
else if (gi_progress == 3)
|
||||
{
|
||||
/* send even more single points */
|
||||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(
|
||||
&_asdu, alParams, true, CS101_COT_INTERROGATED_BY_STATION, gi_oa, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 400 + i,
|
||||
true, IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
}
|
||||
|
||||
gi_progress = 4;
|
||||
}
|
||||
else if (gi_progress == 4)
|
||||
{
|
||||
/* send termination message */
|
||||
CS101_ASDU tempAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, gi_oa, 1, false, false);
|
||||
|
||||
IMasterConnection_sendACT_TERM(gi_connection, tempAsdu);
|
||||
|
||||
CS101_ASDU_destroy(tempAsdu);
|
||||
|
||||
gi_state = 0;
|
||||
gi_connection = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
|
||||
static bool
|
||||
interrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
|
||||
{
|
||||
printf("Received interrogation for group %i\n", qoi);
|
||||
|
||||
if (CS101_ASDU_getCA(asdu) != 1)
|
||||
{
|
||||
printf("Unknown CA: %i\n", CS101_ASDU_getCA(asdu));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (qoi == 20) /* only handle station interrogation */
|
||||
{
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
if (gi_state == 0)
|
||||
{
|
||||
gi_state = 1;
|
||||
gi_connection = connection;
|
||||
gi_progress = 0;
|
||||
gi_oa = CS101_ASDU_getOA(asdu);
|
||||
|
||||
IMasterConnection_sendACT_CON(connection, asdu, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
asduHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
|
||||
{
|
||||
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1)
|
||||
{
|
||||
printf("received single command\n");
|
||||
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION)
|
||||
{
|
||||
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
||||
|
||||
if (io)
|
||||
{
|
||||
if (InformationObject_getObjectAddress(io) == 5000)
|
||||
{
|
||||
SingleCommand sc = (SingleCommand)io;
|
||||
|
||||
printf("IOA: %i switch to %i\n", InformationObject_getObjectAddress(io),
|
||||
SingleCommand_getState(sc));
|
||||
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
}
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
connectionRequestHandler(void* parameter, const char* ipAddress)
|
||||
{
|
||||
printf("New connection request from %s\n", ipAddress);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
connectionEventHandler(void* parameter, IMasterConnection con, CS104_PeerConnectionEvent event)
|
||||
{
|
||||
if (event == CS104_CON_EVENT_CONNECTION_OPENED)
|
||||
{
|
||||
printf("Connection opened (%p)\n", con);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_CONNECTION_CLOSED)
|
||||
{
|
||||
printf("Connection closed (%p)\n", con);
|
||||
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
if (gi_connection == con)
|
||||
{
|
||||
gi_state = 0;
|
||||
gi_connection = NULL;
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_ACTIVATED)
|
||||
{
|
||||
printf("Connection activated (%p)\n", con);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_DEACTIVATED)
|
||||
{
|
||||
printf("Connection deactivated (%p)\n", con);
|
||||
}
|
||||
}
|
||||
|
||||
static int fileSize = 800;
|
||||
static uint8_t sectionData1[500];
|
||||
static uint8_t sectionData2[500];
|
||||
|
||||
static uint64_t
|
||||
getFileDate(CS101_IFileProvider self)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
getFileSize(CS101_IFileProvider self)
|
||||
{
|
||||
printf("getFileSize --> %i\n", fileSize);
|
||||
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
static int
|
||||
getSectionSize(CS101_IFileProvider self, int sectionNumber)
|
||||
{
|
||||
printf("getSectionSize(%i)\n", sectionNumber);
|
||||
|
||||
if (sectionNumber == 0)
|
||||
return fileSize / 2;
|
||||
if (sectionNumber == 1)
|
||||
return fileSize / 2;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool
|
||||
getSegmentData(CS101_IFileProvider self, int sectionNumber, int offset, int size, uint8_t* data)
|
||||
{
|
||||
printf("getSegmentData(NoS=%i, offset=%i, size=%i)\n", sectionNumber, offset, size);
|
||||
if (sectionNumber == 0)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
data[i] = sectionData1[i + offset];
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (sectionNumber == 1)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
data[i] = sectionData2[i + offset];
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
transferComplete(CS101_IFileProvider self, bool success)
|
||||
{
|
||||
printf("FILE TRANSFER COMPLETE\n");
|
||||
}
|
||||
|
||||
static int numberOfFiles = 1;
|
||||
static struct sCS101_IFileProvider fileProvider[1];
|
||||
|
||||
static void
|
||||
initializeFiles()
|
||||
{
|
||||
fileProvider[0].ca = 1;
|
||||
fileProvider[0].ioa = 30000;
|
||||
fileProvider[0].nof = CS101_NOF_TRANSPARENT_FILE;
|
||||
fileProvider[0].object = NULL;
|
||||
fileProvider[0].getFileSize = getFileSize;
|
||||
fileProvider[0].getFileDate = getFileDate;
|
||||
fileProvider[0].getSectionSize = getSectionSize;
|
||||
fileProvider[0].getSegmentData = getSegmentData;
|
||||
fileProvider[0].transferComplete = transferComplete;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < fileSize / 2; i++)
|
||||
{
|
||||
sectionData1[i] = (uint8_t)(i % 0x100);
|
||||
sectionData2[i] = (uint8_t)((i + 1) % 0x100);
|
||||
}
|
||||
}
|
||||
|
||||
static CS101_IFileProvider
|
||||
getNextFile(void* parameter, CS101_IFileProvider continueAfter)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static CS101_IFileProvider
|
||||
getFile(void* parameter, int ca, int ioa, uint16_t nof, int* errCode)
|
||||
{
|
||||
printf("getFile %i:%i (type:%i)\n", ca, ioa, nof);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < numberOfFiles; i++)
|
||||
{
|
||||
if ((ca == fileProvider[i].ca) && (ioa == fileProvider[i].ioa))
|
||||
{
|
||||
return &(fileProvider[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
IFileReceiver_finished(CS101_IFileReceiver self, CS101_FileErrorCode result)
|
||||
{
|
||||
FILE* fp = (FILE*)self->object;
|
||||
|
||||
fclose(fp);
|
||||
|
||||
if (result != CS101_FILE_ERROR_SUCCESS)
|
||||
{
|
||||
remove("upload.dat");
|
||||
}
|
||||
|
||||
printf("File upload finished: %i\n", result);
|
||||
}
|
||||
|
||||
static void
|
||||
IFileReceiver_segmentReceived(CS101_IFileReceiver self, uint8_t sectionName, int offset, int size, uint8_t* data)
|
||||
{
|
||||
FILE* fp = (FILE*)self->object;
|
||||
|
||||
printf("File upload - section %i - offset: %i - size: %i\n", sectionName, offset, size);
|
||||
|
||||
fwrite(data, size, 1, fp);
|
||||
}
|
||||
|
||||
struct sCS101_IFileReceiver myFileReceiver;
|
||||
|
||||
static CS101_IFileReceiver
|
||||
fileReadyHandler(void* parameter, int ca, int ioa, uint16_t nof, int lengthOfFile, int* err)
|
||||
{
|
||||
if ((ca == 1) && (ioa == 30001))
|
||||
{
|
||||
myFileReceiver.object = fopen("upload.dat", "wb");
|
||||
|
||||
myFileReceiver.finished = IFileReceiver_finished;
|
||||
myFileReceiver.segmentReceived = IFileReceiver_segmentReceived;
|
||||
|
||||
printf("Accepted file upload\n");
|
||||
|
||||
*err = 0;
|
||||
|
||||
return &myFileReceiver;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Rejected file upload - unknown file\n");
|
||||
|
||||
*err = 1;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
/* Add Ctrl-C handler */
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
gi_lock = Semaphore_create(1);
|
||||
|
||||
/* create a new slave/server instance with default connection parameters and
|
||||
* default message queue size (will provide space for 100 messages of the maximum
|
||||
* message size or more messages for smaller messages */
|
||||
CS104_Slave slave = CS104_Slave_create(100, 100);
|
||||
|
||||
CS104_Slave_setLocalAddress(slave, "0.0.0.0");
|
||||
|
||||
/* Set mode to a single redundancy group
|
||||
* NOTE: library has to be compiled with CONFIG_CS104_SUPPORT_SERVER_MODE_SINGLE_REDUNDANCY_GROUP enabled (=1)
|
||||
*/
|
||||
CS104_Slave_setServerMode(slave, CS104_MODE_SINGLE_REDUNDANCY_GROUP);
|
||||
|
||||
/* get the connection parameters - we need them to create correct ASDUs */
|
||||
CS101_AppLayerParameters alParams = CS104_Slave_getAppLayerParameters(slave);
|
||||
|
||||
/* set the callback handler for the interrogation command */
|
||||
CS104_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
||||
|
||||
/* set handler for other message types */
|
||||
CS104_Slave_setASDUHandler(slave, asduHandler, NULL);
|
||||
|
||||
/* set handler to handle connection requests (optional) */
|
||||
CS104_Slave_setConnectionRequestHandler(slave, connectionRequestHandler, NULL);
|
||||
|
||||
/* set handler to track connection events (optional) */
|
||||
CS104_Slave_setConnectionEventHandler(slave, connectionEventHandler, NULL);
|
||||
|
||||
CS101_FileServer fileServer = CS101_FileServer_create(alParams);
|
||||
|
||||
initializeFiles();
|
||||
|
||||
struct sCS101_FilesAvailable fileAvailable;
|
||||
fileAvailable.getFile = getFile;
|
||||
fileAvailable.getNextFile = getNextFile;
|
||||
fileAvailable.parameter = NULL;
|
||||
|
||||
CS101_FileServer_setFilesAvailableIfc(fileServer, &fileAvailable);
|
||||
CS101_FileServer_setFileReadyHandler(fileServer, fileReadyHandler, NULL);
|
||||
|
||||
CS104_Slave_addPlugin(slave, CS101_FileServer_getSlavePlugin(fileServer));
|
||||
|
||||
CS104_Slave_start(slave);
|
||||
|
||||
if (CS104_Slave_isRunning(slave) == false)
|
||||
{
|
||||
printf("Starting server failed!\n");
|
||||
goto exit_program;
|
||||
}
|
||||
|
||||
int16_t scaledValue = 0;
|
||||
|
||||
uint64_t nextSendTime = Hal_getTimeInMs() + 1000;
|
||||
|
||||
while (running)
|
||||
{
|
||||
handleGeneralInterrogation();
|
||||
|
||||
if (Hal_getTimeInMs() >= nextSendTime)
|
||||
{
|
||||
nextSendTime += 1000;
|
||||
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
|
||||
scaledValue++;
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)MeasuredValueScaled_create((MeasuredValueScaled)&ioBuf, 110, scaledValue,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
/* Add ASDU to slave event queue - don't release the ASDU afterwards!
|
||||
* The ASDU will be released by the Slave instance when the ASDU
|
||||
* has been sent.
|
||||
*/
|
||||
CS104_Slave_enqueueASDU(slave, newAsdu);
|
||||
}
|
||||
|
||||
Thread_sleep(1);
|
||||
}
|
||||
|
||||
CS104_Slave_stop(slave);
|
||||
|
||||
exit_program:
|
||||
CS104_Slave_destroy(slave);
|
||||
CS101_FileServer_destroy(fileServer);
|
||||
|
||||
Semaphore_destroy(gi_lock);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
cs104_server_no_threads.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(cs104_server_no_threads
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(cs104_server_no_threads
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = cs104_server_no_threads
|
||||
PROJECT_SOURCES = cs104_server_no_threads.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs104_slave.h"
|
||||
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
static int gi_state = 0; /* 0 - no GI running, 1 - GI is running */
|
||||
static IMasterConnection gi_connection = NULL;
|
||||
static int gi_progress = 0;
|
||||
static int gi_oa = 0; /* originator address */
|
||||
|
||||
static void
|
||||
handleGeneralInterrogation();
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
(void)signalId;
|
||||
running = false;
|
||||
}
|
||||
|
||||
static sCS101_StaticASDU _asdu;
|
||||
static uint8_t ioBuf[250];
|
||||
|
||||
static void
|
||||
handleGeneralInterrogation()
|
||||
{
|
||||
if (gi_state == 1)
|
||||
{
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(gi_connection);
|
||||
|
||||
if (gi_progress == 0)
|
||||
{
|
||||
/* send scaled values */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)&ioBuf, 100, -1, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)&ioBuf, 101, 23, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)MeasuredValueScaled_create((MeasuredValueScaled)&ioBuf, 102, 2300,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
gi_progress = 1;
|
||||
}
|
||||
else if (gi_progress == 1)
|
||||
{
|
||||
/* send single points */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 104, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 105, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
gi_progress = 2;
|
||||
}
|
||||
else if (gi_progress == 2)
|
||||
{
|
||||
/* send more single points */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, true, CS101_COT_INTERROGATED_BY_STATION,
|
||||
gi_oa, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 300, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 301, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 302, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 303, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 304, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 305, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 306, true,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)&ioBuf, 307, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
gi_progress = 3;
|
||||
}
|
||||
else if (gi_progress == 3)
|
||||
{
|
||||
/* send termination message */
|
||||
CS101_ASDU tempAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, gi_oa, 1, false, false);
|
||||
|
||||
IMasterConnection_sendACT_TERM(gi_connection, tempAsdu);
|
||||
|
||||
CS101_ASDU_destroy(tempAsdu);
|
||||
|
||||
gi_state = 0;
|
||||
gi_connection = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
printCP56Time2a(CP56Time2a time)
|
||||
{
|
||||
printf("%02i:%02i:%02i %02i/%02i/%04i", CP56Time2a_getHour(time), CP56Time2a_getMinute(time),
|
||||
CP56Time2a_getSecond(time), CP56Time2a_getDayOfMonth(time), CP56Time2a_getMonth(time),
|
||||
CP56Time2a_getYear(time) + 2000);
|
||||
}
|
||||
|
||||
/* Callback handler to log sent or received messages (optional) */
|
||||
static void
|
||||
rawMessageHandler(void* parameter, IMasterConnection conneciton, uint8_t* msg, int msgSize, bool sent)
|
||||
{
|
||||
if (sent)
|
||||
printf("SEND: ");
|
||||
else
|
||||
printf("RCVD: ");
|
||||
|
||||
int i;
|
||||
for (i = 0; i < msgSize; i++)
|
||||
{
|
||||
printf("%02x ", msg[i]);
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool
|
||||
clockSyncHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, CP56Time2a newTime)
|
||||
{
|
||||
printf("Process time sync command with time ");
|
||||
printCP56Time2a(newTime);
|
||||
printf("\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
interrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
|
||||
{
|
||||
(void)parameter;
|
||||
|
||||
int ca = CS101_ASDU_getCA(asdu);
|
||||
|
||||
printf("Received interrogation for CASDU %i and group %i\n", ca,qoi);
|
||||
|
||||
if (ca == 1) /* only handle interrogation for CA 1 */
|
||||
{
|
||||
if (qoi == 20) /* only handle station interrogation */
|
||||
{
|
||||
gi_state = 1;
|
||||
gi_connection = connection;
|
||||
gi_progress = 0;
|
||||
gi_oa = (uint8_t)CS101_ASDU_getOA(asdu);
|
||||
IMasterConnection_sendACT_CON(connection, asdu, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* send error response */
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
asduHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
|
||||
{
|
||||
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1)
|
||||
{
|
||||
printf("received single command\n");
|
||||
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION)
|
||||
{
|
||||
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
||||
|
||||
if (io)
|
||||
{
|
||||
if (InformationObject_getObjectAddress(io) == 5000)
|
||||
{
|
||||
SingleCommand sc = (SingleCommand)io;
|
||||
|
||||
printf("IOA: %i switch to %i\n", InformationObject_getObjectAddress(io),
|
||||
SingleCommand_getState(sc));
|
||||
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR: message has no valid information object\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
connectionRequestHandler(void* parameter, const char* ipAddress)
|
||||
{
|
||||
printf("New connection request from %s\n", ipAddress);
|
||||
|
||||
#if 0
|
||||
if (strcmp(ipAddress, "127.0.0.1") == 0) {
|
||||
printf("Accept connection\n");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
printf("Deny connection\n");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
connectionEventHandler(void* parameter, IMasterConnection con, CS104_PeerConnectionEvent event)
|
||||
{
|
||||
if (event == CS104_CON_EVENT_CONNECTION_OPENED)
|
||||
{
|
||||
printf("Connection opened (%p)\n", con);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_CONNECTION_CLOSED)
|
||||
{
|
||||
printf("Connection closed (%p)\n", con);
|
||||
|
||||
if (gi_connection == con)
|
||||
{
|
||||
gi_state = 0;
|
||||
gi_connection = NULL;
|
||||
}
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_ACTIVATED)
|
||||
{
|
||||
printf("Connection activated (%p)\n", con);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_DEACTIVATED)
|
||||
{
|
||||
printf("Connection deactivated (%p)\n", con);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
/* Add Ctrl-C handler */
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
/* create a new slave/server instance with default connection parameters and
|
||||
* default message queue size (will provide space for 100 messages of the maximum
|
||||
* message size or more messages for smaller messages */
|
||||
CS104_Slave slave = CS104_Slave_create(100, 100);
|
||||
|
||||
CS104_Slave_setLocalAddress(slave, "0.0.0.0");
|
||||
|
||||
/* Set mode to a single redundancy group
|
||||
* NOTE: library has to be compiled with CONFIG_CS104_SUPPORT_SERVER_MODE_SINGLE_REDUNDANCY_GROUP enabled (=1)
|
||||
*/
|
||||
CS104_Slave_setServerMode(slave, CS104_MODE_SINGLE_REDUNDANCY_GROUP);
|
||||
|
||||
/* get the connection parameters - we need them to create correct ASDUs */
|
||||
CS101_AppLayerParameters alParams = CS104_Slave_getAppLayerParameters(slave);
|
||||
|
||||
/* set the callback handler for the clock synchronization command */
|
||||
CS104_Slave_setClockSyncHandler(slave, clockSyncHandler, NULL);
|
||||
|
||||
/* set the callback handler for the interrogation command */
|
||||
CS104_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
||||
|
||||
/* set handler for other message types */
|
||||
CS104_Slave_setASDUHandler(slave, asduHandler, NULL);
|
||||
|
||||
/* set handler to handle connection requests (optional) */
|
||||
CS104_Slave_setConnectionRequestHandler(slave, connectionRequestHandler, NULL);
|
||||
|
||||
/* set handler to track connection events (optional) */
|
||||
CS104_Slave_setConnectionEventHandler(slave, connectionEventHandler, NULL);
|
||||
|
||||
/* uncomment to log messages */
|
||||
// CS104_Slave_setRawMessageHandler(slave, rawMessageHandler, NULL);
|
||||
|
||||
CS104_Slave_startThreadless(slave);
|
||||
|
||||
if (CS104_Slave_isRunning(slave) == false)
|
||||
{
|
||||
printf("Starting server failed!\n");
|
||||
goto exit_program;
|
||||
}
|
||||
|
||||
int16_t scaledValue = 0;
|
||||
|
||||
uint64_t nextSendTime = Hal_getTimeInMs() + 1000;
|
||||
|
||||
while (running)
|
||||
{
|
||||
handleGeneralInterrogation();
|
||||
|
||||
CS104_Slave_tick(slave);
|
||||
|
||||
if (Hal_getTimeInMs() >= nextSendTime)
|
||||
{
|
||||
nextSendTime += 1000;
|
||||
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
|
||||
scaledValue++;
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)MeasuredValueScaled_create((MeasuredValueScaled)&ioBuf, 110, scaledValue,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
/* Add ASDU to slave event queue */
|
||||
CS104_Slave_enqueueASDU(slave, newAsdu);
|
||||
}
|
||||
|
||||
Thread_sleep(1);
|
||||
}
|
||||
|
||||
CS104_Slave_stopThreadless(slave);
|
||||
|
||||
exit_program:
|
||||
CS104_Slave_destroy(slave);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
multi_client_server.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
add_executable(multi_client_server
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(multi_client_server
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = multi_client_server
|
||||
PROJECT_SOURCES = multi_client_server.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs104_slave.h"
|
||||
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
#define MAX_GI_SESSIONS 10
|
||||
|
||||
typedef struct {
|
||||
int state; /* 0 - idle, 1 - GI running */
|
||||
IMasterConnection connection;
|
||||
int progress;
|
||||
int oa; /* originator address */
|
||||
} GISession;
|
||||
|
||||
static GISession giSessions[MAX_GI_SESSIONS];
|
||||
static Semaphore gi_lock;
|
||||
|
||||
static CS101_AppLayerParameters appLayerParameters;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
(void)signalId;
|
||||
running = false;
|
||||
}
|
||||
|
||||
void
|
||||
printCP56Time2a(CP56Time2a time)
|
||||
{
|
||||
printf("%02i:%02i:%02i %02i/%02i/%04i", CP56Time2a_getHour(time), CP56Time2a_getMinute(time),
|
||||
CP56Time2a_getSecond(time), CP56Time2a_getDayOfMonth(time), CP56Time2a_getMonth(time) + 1,
|
||||
CP56Time2a_getYear(time) + 2000);
|
||||
}
|
||||
|
||||
static void
|
||||
handleGISession(GISession* session)
|
||||
{
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(session->connection);
|
||||
|
||||
if (session->progress == 0)
|
||||
{
|
||||
/* send scaled values */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, session->oa, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject)MeasuredValueScaled_create(NULL, 100, -1, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)io, 101, 23, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)io, 102, 2300, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(session->connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
session->progress = 1;
|
||||
}
|
||||
else if (session->progress == 1)
|
||||
{
|
||||
/* send single points */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, session->oa, 1, false, false);
|
||||
|
||||
InformationObject io =
|
||||
(InformationObject)SinglePointInformation_create(NULL, 104, true, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)io, 105, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(session->connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
session->progress = 2;
|
||||
}
|
||||
else if (session->progress == 2)
|
||||
{
|
||||
/* send more single points */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, true, CS101_COT_INTERROGATED_BY_STATION, session->oa, 1, false, false);
|
||||
|
||||
SinglePointInformation io = SinglePointInformation_create(NULL, 300, true, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)io);
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 301, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 302, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 303, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 304, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 305, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 306, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(io, 307, false, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy((InformationObject)io);
|
||||
|
||||
IMasterConnection_sendASDU(session->connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
session->progress = 3;
|
||||
}
|
||||
else if (session->progress == 3)
|
||||
{
|
||||
/* send termination message */
|
||||
CS101_ASDU tempAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, session->oa, 1, false, false);
|
||||
|
||||
IMasterConnection_sendACT_TERM(session->connection, tempAsdu);
|
||||
|
||||
CS101_ASDU_destroy(tempAsdu);
|
||||
|
||||
session->state = 0;
|
||||
session->connection = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
handleGeneralInterrogation()
|
||||
{
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
for (int i = 0; i < MAX_GI_SESSIONS; i++)
|
||||
{
|
||||
if (giSessions[i].state == 1)
|
||||
{
|
||||
handleGISession(&giSessions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
|
||||
static bool
|
||||
clockSyncHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, CP56Time2a newTime)
|
||||
{
|
||||
printf("Process time sync command with time ");
|
||||
printCP56Time2a(newTime);
|
||||
printf("\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
interrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
|
||||
{
|
||||
(void)parameter;
|
||||
|
||||
int ca = CS101_ASDU_getCA(asdu);
|
||||
|
||||
printf("Received interrogation for CASDU %i and group %i\n", ca, qoi);
|
||||
|
||||
if (ca == 1) /* only handle interrogation for CA 1 */
|
||||
{
|
||||
if (qoi == 20) /* only handle station interrogation */
|
||||
{
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
/* find existing session for this connection or a free slot */
|
||||
GISession* session = NULL;
|
||||
|
||||
for (int i = 0; i < MAX_GI_SESSIONS; i++)
|
||||
{
|
||||
if (giSessions[i].state == 1 && giSessions[i].connection == connection)
|
||||
{
|
||||
/* GI already running for this connection - reject */
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
Semaphore_post(gi_lock);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_GI_SESSIONS; i++)
|
||||
{
|
||||
if (giSessions[i].state == 0)
|
||||
{
|
||||
session = &giSessions[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (session)
|
||||
{
|
||||
session->state = 1;
|
||||
session->connection = connection;
|
||||
session->progress = 0;
|
||||
session->oa = (uint8_t)CS101_ASDU_getOA(asdu);
|
||||
IMasterConnection_sendACT_CON(connection, asdu, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no free slot - reject */
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* send error response */
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
asduHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
|
||||
{
|
||||
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1)
|
||||
{
|
||||
printf("received single command\n");
|
||||
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION)
|
||||
{
|
||||
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
||||
|
||||
if (io)
|
||||
{
|
||||
if (InformationObject_getObjectAddress(io) == 5000)
|
||||
{
|
||||
SingleCommand sc = (SingleCommand)io;
|
||||
|
||||
printf("IOA: %i switch to %i\n", InformationObject_getObjectAddress(io),
|
||||
SingleCommand_getState(sc));
|
||||
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR: ASDU contains no information object!\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
connectionEventHandler(void* parameter, IMasterConnection con, CS104_PeerConnectionEvent event)
|
||||
{
|
||||
if (event == CS104_CON_EVENT_CONNECTION_OPENED)
|
||||
{
|
||||
printf("Connection opened (%p)\n", con);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_CONNECTION_CLOSED)
|
||||
{
|
||||
printf("Connection closed (%p)\n", con);
|
||||
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
for (int i = 0; i < MAX_GI_SESSIONS; i++)
|
||||
{
|
||||
if (giSessions[i].connection == con)
|
||||
{
|
||||
giSessions[i].state = 0;
|
||||
giSessions[i].connection = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_ACTIVATED)
|
||||
{
|
||||
printf("Connection activated (%p)\n", con);
|
||||
}
|
||||
else if (event == CS104_CON_EVENT_DEACTIVATED)
|
||||
{
|
||||
printf("Connection deactivated (%p)\n", con);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
int openConnections = 0;
|
||||
|
||||
/* Add Ctrl-C handler */
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
gi_lock = Semaphore_create(1);
|
||||
|
||||
memset(giSessions, 0, sizeof(giSessions));
|
||||
|
||||
/* create a new slave/server instance with default connection parameters and
|
||||
* default message queue size */
|
||||
CS104_Slave slave = CS104_Slave_create(100, 100);
|
||||
|
||||
CS104_Slave_setLocalAddress(slave, "0.0.0.0");
|
||||
|
||||
/* get the connection parameters - we need them to create correct ASDUs */
|
||||
appLayerParameters = CS104_Slave_getAppLayerParameters(slave);
|
||||
|
||||
/* set the callback handler for the clock synchronization command */
|
||||
CS104_Slave_setClockSyncHandler(slave, clockSyncHandler, NULL);
|
||||
|
||||
/* set the callback handler for the interrogation command */
|
||||
CS104_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
||||
|
||||
/* set handler for other message types */
|
||||
CS104_Slave_setASDUHandler(slave, asduHandler, NULL);
|
||||
|
||||
CS104_Slave_setConnectionEventHandler(slave, connectionEventHandler, NULL);
|
||||
|
||||
/* Set server mode to allow multiple clients using the application layer
|
||||
* NOTE: library has to be compiled with CONFIG_CS104_SUPPORT_SERVER_MODE_CONNECTION_IS_REDUNDANCY_GROUP enabled
|
||||
* (=1)
|
||||
*/
|
||||
CS104_Slave_setServerMode(slave, CS104_MODE_CONNECTION_IS_REDUNDANCY_GROUP);
|
||||
|
||||
CS104_Slave_start(slave);
|
||||
|
||||
if (CS104_Slave_isRunning(slave) == false)
|
||||
{
|
||||
printf("Starting server failed!\n");
|
||||
goto exit_program;
|
||||
}
|
||||
|
||||
int16_t scaledValue = 0;
|
||||
|
||||
uint64_t lastPeriodicTransmission = 0;
|
||||
|
||||
while (running)
|
||||
{
|
||||
handleGeneralInterrogation();
|
||||
|
||||
if (Hal_getMonotonicTimeInMs() - lastPeriodicTransmission >= 1000)
|
||||
{
|
||||
lastPeriodicTransmission = Hal_getMonotonicTimeInMs();
|
||||
|
||||
int connectionsCount = CS104_Slave_getOpenConnections(slave);
|
||||
|
||||
if (connectionsCount != openConnections)
|
||||
{
|
||||
openConnections = connectionsCount;
|
||||
|
||||
printf("Connected clients: %i\n", openConnections);
|
||||
}
|
||||
|
||||
CS101_ASDU periodicAsdu = CS101_ASDU_create(appLayerParameters, false, CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
|
||||
if (periodicAsdu)
|
||||
{
|
||||
InformationObject io =
|
||||
(InformationObject)MeasuredValueScaled_create(NULL, 110, scaledValue, IEC60870_QUALITY_GOOD);
|
||||
|
||||
if (io)
|
||||
{
|
||||
scaledValue++;
|
||||
|
||||
CS101_ASDU_addInformationObject(periodicAsdu, io);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
/* Add ASDU to slave event queue */
|
||||
CS104_Slave_enqueueASDU(slave, periodicAsdu);
|
||||
}
|
||||
|
||||
CS101_ASDU_destroy(periodicAsdu);
|
||||
}
|
||||
}
|
||||
|
||||
Thread_sleep(10);
|
||||
}
|
||||
|
||||
CS104_Slave_stop(slave);
|
||||
|
||||
exit_program:
|
||||
CS104_Slave_destroy(slave);
|
||||
|
||||
Semaphore_destroy(gi_lock);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
tls_client.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
configure_file(client_CA1_1.key client_CA1_1.key COPYONLY)
|
||||
configure_file(client_CA1_1.pem client_CA1_1.pem COPYONLY)
|
||||
configure_file(root_CA1.pem root_CA1.pem COPYONLY)
|
||||
configure_file(server_CA1_1.pem server_CA1_1.pem COPYONLY)
|
||||
|
||||
add_executable(tls_client
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(tls_client
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = tls_client
|
||||
PROJECT_SOURCES = tls_client.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEA2PuXMNUYO43NbEvBmEAru/uL1JdU6gFuhMKLuZOPaOGjGhth
|
||||
JiDO9AsnUGzKAk3m9QZ/YhAzY9CiEeYsnGaPeI0OBdkgWmpz5k9Fw+bqaqlxYQTy
|
||||
Bw69/kYbwNyMmGsb8XqXKZvhPXdLoaxkVmS+AMlxVcN/7c2ldZGTTDrhBtJnfuPK
|
||||
rGmH9cFg+XVvUskPQsUIwJtn1sN+niZ++hkuiCzmoZ4A+m73QACltdcr7aNtHJmh
|
||||
aU/p1bmLIhYfbxGmyvm2faJ8htYuaRBj6DcZq44IyDGz2LThmdWzpIcYbovCzB2X
|
||||
Pn26b0BXsXBpN+ptf2xpAEDWDdzaR6Xp4BgJwQIDAQABAoIBAQDQGLJOlgBQlVWv
|
||||
CBSaNOj8t2nKsHwylL7uujoQ95DxUH0BO8L3Mz3n1Y6V1lAC172pvtqKLOlsUBov
|
||||
OmYMdVwhjH4nY65gqHmRJvPMxviI5Qqktn58AEp8w7Y4SAza3NaGyECTGjlxnqi9
|
||||
XD06khGbZZa5Xu6hHboSwFPZJxrLU1jaopJUgFG+p9oUgiSp5cfGDwAsU9JELmkP
|
||||
MVF0GWedpypBBKi9JsniOulr1USpNZN2rzEkkxwY0QQttw3E9dgheIsut7dUYWLz
|
||||
9NLKcRWIK/Y29NzS6Urye8lUTHHBrXgk5pUcdN3vuY7mkleqIn5tYY6xf93/5/VA
|
||||
jF+HcgolAoGBAPDhS986xppbfLmrresIUUZKxk2s/Vg+vHPLX0SJFF7Uhy8nMYoJ
|
||||
JqfG2mS+/tiM/wPBglVhsrlsfnDIag7Brqx7sjH2OHO6VX8jQPYgOuCbNwp7uL1w
|
||||
bG82R5rujcxxFAtMVAM3zYz9sNGSu8u7M/U3kfTBwtntFJ6iPC60REbjAoGBAOaa
|
||||
SdtX0bOQAYDM4moEDVnRPMHp8lZAjqKphGqTDrGOqU4usNW8+ZNBhn3vF1+n2Gq5
|
||||
KY2IWSF0j71jqpOXahW0EBoXpcTLs5JBWet8J5vKzbpN8Uq8TvTABn67G1F/DZub
|
||||
FOiCDy/Kku4yWT2aUqNwS07va7gzFhyyjMl/JWoLAoGAATpEtriH9pVsx012r3H1
|
||||
aBRNemvdRqvbLgPlUmYYcntGzRi4CeoOBmDfEBBhIB1n108PKPw8evFwm4aJ89VM
|
||||
3JgsylBk7UIP2XwGgrqbUjW4TBdhU6XVB6QRLVr14grZfU1ASFvqckOAuTC0QE+N
|
||||
7jwARG0QXyf0KPLOt7Y3et0CgYEAhJcd9EJQTsB0PMyROofN7WDDYHPVZQaFfL2f
|
||||
Z2/auPjgHBX4k0yu6553aB17AQMPCn4giEJnjTbqFukhgO9EjeoUgAwswjSlsWhl
|
||||
/WJLm+ZF1+NM473WYB+xHFkU4gz9lATdRrrRZJdDWDYW3bbH4TWF94LuGuE0y5dW
|
||||
H909c/UCgYEAiYY/TTZvfEsQvCo4Rv6qg7cI2/OdGwGhMmtziYy4SIAAm0Ga2s3R
|
||||
L7Kq72In+nbaDIUD2zTSGQmwTm3B0C73vIUAXvupcl28nE5px0YNV6NZJaaFSV66
|
||||
hP1CgPBYe6KjnVufOiqhcnDdJQ6XdqK0tblj+cavkZgW+UdeqVBXCFQ=
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkMCFFTJkICEIidmnrisIFxZ99KKLhDFMA0GCSqGSIb3DQEBCwUAMGox
|
||||
CzAJBgNVBAYTAkRFMQswCQYDVQQIDAJCVzERMA8GA1UEBwwIRnJlaWJ1cmcxGzAZ
|
||||
BgNVBAoMEk1aIEF1dG9tYXRpb24gR21iSDEMMAoGA1UECwwDUiZEMRAwDgYDVQQD
|
||||
DAdyb290X0NBMB4XDTIyMDMxODA5MzMxOFoXDTIyMDQxNzA5MzMxOFowajELMAkG
|
||||
A1UEBhMCREUxCzAJBgNVBAgMAkJXMREwDwYDVQQHDAhGcmVpYnVyZzEbMBkGA1UE
|
||||
CgwSTVogQXV0b21hdGlvbiBHbWJIMQwwCgYDVQQLDANSJkQxEDAOBgNVBAMMB2Ns
|
||||
aWVudDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDY+5cw1Rg7jc1s
|
||||
S8GYQCu7+4vUl1TqAW6Ewou5k49o4aMaG2EmIM70CydQbMoCTeb1Bn9iEDNj0KIR
|
||||
5iycZo94jQ4F2SBaanPmT0XD5upqqXFhBPIHDr3+RhvA3IyYaxvxepcpm+E9d0uh
|
||||
rGRWZL4AyXFVw3/tzaV1kZNMOuEG0md+48qsaYf1wWD5dW9SyQ9CxQjAm2fWw36e
|
||||
Jn76GS6ILOahngD6bvdAAKW11yvto20cmaFpT+nVuYsiFh9vEabK+bZ9onyG1i5p
|
||||
EGPoNxmrjgjIMbPYtOGZ1bOkhxhui8LMHZc+fbpvQFexcGk36m1/bGkAQNYN3NpH
|
||||
pengGAnBAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADSnrKdPqeUr3F1MIk6P8SKo
|
||||
yR1VrPmNCljaC1i3realDlG+7jlPHfTCkwZwlEfKGa/yANJAw4hv+2tR5m4CsgMB
|
||||
x6FkKG9p6NTXyv4gXZeLa3ivqFqz7awTVMBf1C1VVeTi/H2kvHSBRmbj6Z5p7/MN
|
||||
9E1t5NsgbKKfbj4hQD+f7r6zgFdgTK8C5OYT2ijYryFl1Qqrl5CYPpswm3vL0KkM
|
||||
e3RMOBqamkFqr4OCZw5juNpGrp3bK3dLF+N6Ceb+PGnS0YU29NpUXo64lzIxdwxs
|
||||
NDqbFMYXEXGKqUDVQAuj1374M85Cvqlso0Jenc+hWN2/kfAgHGE1Ne3sD9oJg5w=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDtTCCAp2gAwIBAgIUJysTAOCqE3IaNO1QgtOPxMq6M8EwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCREUxCzAJBgNVBAgMAkJXMREwDwYDVQQHDAhGcmVpYnVy
|
||||
ZzEbMBkGA1UECgwSTVogQXV0b21hdGlvbiBHbWJIMQwwCgYDVQQLDANSJkQxEDAO
|
||||
BgNVBAMMB3Jvb3RfQ0EwHhcNMjIwMzE4MDkyNzEwWhcNMzIwMzE1MDkyNzEwWjBq
|
||||
MQswCQYDVQQGEwJERTELMAkGA1UECAwCQlcxETAPBgNVBAcMCEZyZWlidXJnMRsw
|
||||
GQYDVQQKDBJNWiBBdXRvbWF0aW9uIEdtYkgxDDAKBgNVBAsMA1ImRDEQMA4GA1UE
|
||||
AwwHcm9vdF9DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOMyaDaT
|
||||
+a4DT0s2NCrjUN8coLPfFrLRdN0Gx0hRViuLUFxd001jXruRgXKt2g8lR+YnzUeA
|
||||
PQHbcIfRQhL+jy/ZMXpmz4Nrl7vyOWFdu8nBKU6c7y9LmSGbnOJZjDXwlX6ERwui
|
||||
qFzAvRA6YXbPN8gY0B3Ou+T/mjkWN9L1x+V+7bGs9rVIoM78fVyM2FERBfsBtT76
|
||||
QVQv3KZ+a9EOLxqcZ/nGqsFFysFOSkiRC6Cy4mC5CSik9S5D7X9lz/bdga7O+hqd
|
||||
SKfir6YMlQGV37JPqmz69N6vvb9UOX/G989T4qdVB/zQOvMdcIWXkqb3vSAXYi/c
|
||||
ClVS1Pymsy/MXQ0CAwEAAaNTMFEwHQYDVR0OBBYEFGYgIECdrhTsmgCKpVM0RHeC
|
||||
kFUmMB8GA1UdIwQYMBaAFGYgIECdrhTsmgCKpVM0RHeCkFUmMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBACsiuHFQjqOglenp/fcNbU034m/vvkyV
|
||||
SZXau9amXBWdeTEpc1HaPOYO7jFFnu/QoH6AbGZkpL0yWZJA2rf102AkOdAe6E0g
|
||||
2H77/hHoHVCfxOiOl3+icsLXJ4VXqV2vmUOEVnWfHRtej4My6avT9uCNMO2bw9hm
|
||||
56RAZrs82T9Mpg/1XQ9YUO1q4/JfP/+dCzPXAdwJ/h2cJ/q6Q9g1gRns8IzVlGOZ
|
||||
0ZBQCLqLl8vUei+t6YgjyBbeNCz4CEcmXKIJeqMB1jhpsgr6BBMTNTU2Q60b9fzU
|
||||
OCGLw94EnKYtHWGy2WHMFNbwkNCR0/jwhxKkU0HXy1aNMUBWp99M7P8=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkMCFFTJkICEIidmnrisIFxZ99KKLhDIMA0GCSqGSIb3DQEBCwUAMGox
|
||||
CzAJBgNVBAYTAkRFMQswCQYDVQQIDAJCVzERMA8GA1UEBwwIRnJlaWJ1cmcxGzAZ
|
||||
BgNVBAoMEk1aIEF1dG9tYXRpb24gR21iSDEMMAoGA1UECwwDUiZEMRAwDgYDVQQD
|
||||
DAdyb290X0NBMB4XDTIyMDUyNjEwNDc0NFoXDTMwMDgxMjEwNDc0NFowajELMAkG
|
||||
A1UEBhMCREUxCzAJBgNVBAgMAkJXMREwDwYDVQQHDAhGcmVpYnVyZzEbMBkGA1UE
|
||||
CgwSTVogQXV0b21hdGlvbiBHbWJIMQwwCgYDVQQLDANSJkQxEDAOBgNVBAMMB3Nl
|
||||
cnZlcjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYUV+nESoiZ3TU
|
||||
D1D5xxNEOuhWnPzjR6S1evjlXRFCSnPzNK4i/kZc+oFfBKApeyiOguQJgr5WaVfg
|
||||
ZDrFduUAoB39+VRL5ZWxSr2Qv1y53fR3BgqHZXj9Ob829SJmxhXdvn/xF9SljBU0
|
||||
Fb95Jxs1etUc2SIGtwgm2LGu4qzIf52tyQ/sriP4BmHjcsawro1j6ml/nAcFy0ZY
|
||||
U4IV0lKtptyTih6bzTsI4pf0+FN8Pzs6eMFByp0eS6zxZk/9IadlX7pzWTRI3g7Q
|
||||
TgxqGlaSvwPZmOxuEYUhbwBTg12huyiEKVYBnQwNHU3iijSyPgeBjaEZHYBtH/lj
|
||||
YsfiRFylAgMBAAEwDQYJKoZIhvcNAQELBQADggEBANPRnvfByVoKwfMcQYUnYT6l
|
||||
5OhYt8f2tQfoa0EXirP0O2xG052ZBl3Z5ZzBCcsq1zveaPoeqXFl6HjqIqURB5NS
|
||||
imJIi7kB7o6C2z19yxOndPm3urKGyfvxtSy2iyzTMZ8eL8RFMJC5DVV+n5Y+1EgC
|
||||
pYIu//I0ojnFOemEJXVjfxQhiUbx6Nw8HalHOhW1i017XcOWMKji/lwHfWF2PFmn
|
||||
pIWZCFPCUtHzBUkXCRzn9ESeMDcMXN6qLb2wGJkRUDw+Ls1RGJd6dnB811vOuOd+
|
||||
QQc8lyyBZ1byARcxQ8lAtof6Mv7Yzebv1OxRr7NcrV/+ujnSFyJWKrJdcMx7+10=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,210 @@
|
||||
#include "cs104_connection.h"
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
static void
|
||||
connectionHandler(void* parameter, CS104_Connection connection, CS104_ConnectionEvent event)
|
||||
{
|
||||
switch (event)
|
||||
{
|
||||
case CS104_CONNECTION_OPENED:
|
||||
printf("Connection established\n");
|
||||
break;
|
||||
case CS104_CONNECTION_CLOSED:
|
||||
printf("Connection closed\n");
|
||||
break;
|
||||
case CS104_CONNECTION_FAILED:
|
||||
printf("Failed to connect\n");
|
||||
break;
|
||||
case CS104_CONNECTION_STARTDT_CON_RECEIVED:
|
||||
printf("Received STARTDT_CON\n");
|
||||
break;
|
||||
case CS104_CONNECTION_STOPDT_CON_RECEIVED:
|
||||
printf("Received STOPDT_CON\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* CS101_ASDUReceivedHandler implementation
|
||||
*
|
||||
* For CS104 the address parameter has to be ignored
|
||||
*/
|
||||
static bool
|
||||
asduReceivedHandler(void* parameter, int address, CS101_ASDU asdu)
|
||||
{
|
||||
printf("RECVD ASDU type: %s(%i) elements: %i\n", TypeID_toString(CS101_ASDU_getTypeID(asdu)),
|
||||
CS101_ASDU_getTypeID(asdu), CS101_ASDU_getNumberOfElements(asdu));
|
||||
|
||||
if (CS101_ASDU_getTypeID(asdu) == M_ME_TE_1)
|
||||
{
|
||||
printf(" measured scaled values with CP56Time2a timestamp:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
MeasuredValueScaledWithCP56Time2a io = (MeasuredValueScaledWithCP56Time2a)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
MeasuredValueScaled_getValue((MeasuredValueScaled)io));
|
||||
|
||||
MeasuredValueScaledWithCP56Time2a_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == M_SP_NA_1)
|
||||
{
|
||||
printf(" single point information:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
SinglePointInformation io = (SinglePointInformation)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
SinglePointInformation_getValue((SinglePointInformation)io));
|
||||
|
||||
SinglePointInformation_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
securityEventHandler(void* parameter, TLSEventLevel eventLevel, int eventCode, const char* msg, TLSConnection con)
|
||||
{
|
||||
(void)parameter;
|
||||
|
||||
char peerAddrBuf[60];
|
||||
char* peerAddr = NULL;
|
||||
const char* tlsVersion = "unknown";
|
||||
|
||||
if (con)
|
||||
{
|
||||
peerAddr = TLSConnection_getPeerAddress(con, peerAddrBuf);
|
||||
tlsVersion = TLSConfigVersion_toString(TLSConnection_getTLSVersion(con));
|
||||
}
|
||||
|
||||
printf("[SECURITY EVENT] %s (t: %i, c: %i, version: %s remote-ip: %s)\n", msg, eventLevel, eventCode, tlsVersion,
|
||||
peerAddr);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
char* hostname = "127.0.0.1";
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
hostname = argv[1];
|
||||
}
|
||||
|
||||
TLSConfiguration tlsConfig = TLSConfiguration_create();
|
||||
|
||||
TLSConfiguration_setEventHandler(tlsConfig, securityEventHandler, NULL);
|
||||
|
||||
TLSConfiguration_setChainValidation(tlsConfig, true);
|
||||
TLSConfiguration_setAllowOnlyKnownCertificates(tlsConfig, true);
|
||||
|
||||
TLSConfiguration_setOwnKeyFromFile(tlsConfig, "client_CA1_1.key", NULL);
|
||||
TLSConfiguration_setOwnCertificateFromFile(tlsConfig, "client_CA1_1.pem");
|
||||
TLSConfiguration_addCACertificateFromFile(tlsConfig, "root_CA1.pem");
|
||||
|
||||
TLSConfiguration_addAllowedCertificateFromFile(tlsConfig, "server_CA1_1.pem");
|
||||
|
||||
// TLSConfiguration_setMaxTlsVersion(tlsConfig, TLS_VERSION_TLS_1_1);
|
||||
|
||||
CS104_Connection con = CS104_Connection_createSecure(hostname, IEC_60870_5_104_DEFAULT_TLS_PORT, tlsConfig);
|
||||
|
||||
CS104_Connection_setConnectionHandler(con, connectionHandler, NULL);
|
||||
CS104_Connection_setASDUReceivedHandler(con, asduReceivedHandler, NULL);
|
||||
|
||||
if (CS104_Connection_connect(con))
|
||||
{
|
||||
printf("Connected!\n");
|
||||
|
||||
CS104_Connection_sendStartDT(con);
|
||||
|
||||
Thread_sleep(1000);
|
||||
|
||||
CS104_Connection_sendInterrogationCommand(con, CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
||||
|
||||
Thread_sleep(1000);
|
||||
|
||||
InformationObject sc = (InformationObject)SingleCommand_create(NULL, 5000, true, false, 0);
|
||||
|
||||
printf("Send control command C_SC_NA_1\n");
|
||||
CS104_Connection_sendProcessCommand(con, C_SC_NA_1, CS101_COT_ACTIVATION, 1, sc);
|
||||
|
||||
InformationObject_destroy(sc);
|
||||
|
||||
/* Send clock synchronization command */
|
||||
struct sCP56Time2a newTime;
|
||||
|
||||
CP56Time2a_createFromMsTimestamp(&newTime, Hal_getTimeInMs());
|
||||
|
||||
printf("Send time sync command\n");
|
||||
CS104_Connection_sendClockSyncCommand(con, 1, &newTime);
|
||||
|
||||
Thread_sleep(1000);
|
||||
|
||||
printf("Close connection\n");
|
||||
|
||||
CS104_Connection_close(con);
|
||||
}
|
||||
else
|
||||
printf("Connect failed!\n");
|
||||
|
||||
printf("Waiting...\n");
|
||||
Thread_sleep(5000);
|
||||
|
||||
if (CS104_Connection_connect(con))
|
||||
{
|
||||
printf("Connected!\n");
|
||||
|
||||
CS104_Connection_sendStartDT(con);
|
||||
|
||||
Thread_sleep(1000);
|
||||
|
||||
CS104_Connection_sendInterrogationCommand(con, CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
||||
|
||||
Thread_sleep(1000);
|
||||
|
||||
InformationObject sc = (InformationObject)SingleCommand_create(NULL, 5000, true, false, 0);
|
||||
|
||||
printf("Send control command C_SC_NA_1\n");
|
||||
CS104_Connection_sendProcessCommand(con, C_SC_NA_1, CS101_COT_ACTIVATION, 1, sc);
|
||||
|
||||
InformationObject_destroy(sc);
|
||||
|
||||
/* Send clock synchronization command */
|
||||
struct sCP56Time2a newTime;
|
||||
|
||||
CP56Time2a_createFromMsTimestamp(&newTime, Hal_getTimeInMs());
|
||||
|
||||
printf("Send time sync command\n");
|
||||
CS104_Connection_sendClockSyncCommand(con, 1, &newTime);
|
||||
|
||||
Thread_sleep(1000);
|
||||
|
||||
printf("Close connection\n");
|
||||
}
|
||||
else
|
||||
printf("Connect failed!\n");
|
||||
|
||||
Thread_sleep(1000);
|
||||
|
||||
CS104_Connection_destroy(con);
|
||||
|
||||
TLSConfiguration_destroy(tlsConfig);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
include_directories(
|
||||
.
|
||||
)
|
||||
|
||||
set(example_SRCS
|
||||
tls_server.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
set_source_files_properties(${example_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF(WIN32)
|
||||
|
||||
configure_file(server_CA1_1.key server_CA1_1.key COPYONLY)
|
||||
configure_file(client_CA1_1.pem client_CA1_1.pem COPYONLY)
|
||||
configure_file(root_CA1.pem root_CA1.pem COPYONLY)
|
||||
configure_file(server_CA1_1.pem server_CA1_1.pem COPYONLY)
|
||||
|
||||
add_executable(tls_server
|
||||
${example_SRCS}
|
||||
)
|
||||
|
||||
target_link_libraries(tls_server
|
||||
lib60870
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
LIB60870_HOME=../..
|
||||
|
||||
PROJECT_BINARY_NAME = tls_server
|
||||
PROJECT_SOURCES = tls_server.c
|
||||
|
||||
include $(LIB60870_HOME)/make/target_system.mk
|
||||
include $(LIB60870_HOME)/make/stack_includes.mk
|
||||
|
||||
all: $(PROJECT_BINARY_NAME)
|
||||
|
||||
include $(LIB60870_HOME)/make/common_targets.mk
|
||||
|
||||
|
||||
$(PROJECT_BINARY_NAME): $(PROJECT_SOURCES) $(LIB_NAME)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -g -o $(PROJECT_BINARY_NAME) $(PROJECT_SOURCES) $(INCLUDES) $(LIB_NAME) $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -f $(PROJECT_BINARY_NAME)
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkMCFFTJkICEIidmnrisIFxZ99KKLhDFMA0GCSqGSIb3DQEBCwUAMGox
|
||||
CzAJBgNVBAYTAkRFMQswCQYDVQQIDAJCVzERMA8GA1UEBwwIRnJlaWJ1cmcxGzAZ
|
||||
BgNVBAoMEk1aIEF1dG9tYXRpb24gR21iSDEMMAoGA1UECwwDUiZEMRAwDgYDVQQD
|
||||
DAdyb290X0NBMB4XDTIyMDMxODA5MzMxOFoXDTIyMDQxNzA5MzMxOFowajELMAkG
|
||||
A1UEBhMCREUxCzAJBgNVBAgMAkJXMREwDwYDVQQHDAhGcmVpYnVyZzEbMBkGA1UE
|
||||
CgwSTVogQXV0b21hdGlvbiBHbWJIMQwwCgYDVQQLDANSJkQxEDAOBgNVBAMMB2Ns
|
||||
aWVudDEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDY+5cw1Rg7jc1s
|
||||
S8GYQCu7+4vUl1TqAW6Ewou5k49o4aMaG2EmIM70CydQbMoCTeb1Bn9iEDNj0KIR
|
||||
5iycZo94jQ4F2SBaanPmT0XD5upqqXFhBPIHDr3+RhvA3IyYaxvxepcpm+E9d0uh
|
||||
rGRWZL4AyXFVw3/tzaV1kZNMOuEG0md+48qsaYf1wWD5dW9SyQ9CxQjAm2fWw36e
|
||||
Jn76GS6ILOahngD6bvdAAKW11yvto20cmaFpT+nVuYsiFh9vEabK+bZ9onyG1i5p
|
||||
EGPoNxmrjgjIMbPYtOGZ1bOkhxhui8LMHZc+fbpvQFexcGk36m1/bGkAQNYN3NpH
|
||||
pengGAnBAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADSnrKdPqeUr3F1MIk6P8SKo
|
||||
yR1VrPmNCljaC1i3realDlG+7jlPHfTCkwZwlEfKGa/yANJAw4hv+2tR5m4CsgMB
|
||||
x6FkKG9p6NTXyv4gXZeLa3ivqFqz7awTVMBf1C1VVeTi/H2kvHSBRmbj6Z5p7/MN
|
||||
9E1t5NsgbKKfbj4hQD+f7r6zgFdgTK8C5OYT2ijYryFl1Qqrl5CYPpswm3vL0KkM
|
||||
e3RMOBqamkFqr4OCZw5juNpGrp3bK3dLF+N6Ceb+PGnS0YU29NpUXo64lzIxdwxs
|
||||
NDqbFMYXEXGKqUDVQAuj1374M85Cvqlso0Jenc+hWN2/kfAgHGE1Ne3sD9oJg5w=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDtTCCAp2gAwIBAgIUJysTAOCqE3IaNO1QgtOPxMq6M8EwDQYJKoZIhvcNAQEL
|
||||
BQAwajELMAkGA1UEBhMCREUxCzAJBgNVBAgMAkJXMREwDwYDVQQHDAhGcmVpYnVy
|
||||
ZzEbMBkGA1UECgwSTVogQXV0b21hdGlvbiBHbWJIMQwwCgYDVQQLDANSJkQxEDAO
|
||||
BgNVBAMMB3Jvb3RfQ0EwHhcNMjIwMzE4MDkyNzEwWhcNMzIwMzE1MDkyNzEwWjBq
|
||||
MQswCQYDVQQGEwJERTELMAkGA1UECAwCQlcxETAPBgNVBAcMCEZyZWlidXJnMRsw
|
||||
GQYDVQQKDBJNWiBBdXRvbWF0aW9uIEdtYkgxDDAKBgNVBAsMA1ImRDEQMA4GA1UE
|
||||
AwwHcm9vdF9DQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOMyaDaT
|
||||
+a4DT0s2NCrjUN8coLPfFrLRdN0Gx0hRViuLUFxd001jXruRgXKt2g8lR+YnzUeA
|
||||
PQHbcIfRQhL+jy/ZMXpmz4Nrl7vyOWFdu8nBKU6c7y9LmSGbnOJZjDXwlX6ERwui
|
||||
qFzAvRA6YXbPN8gY0B3Ou+T/mjkWN9L1x+V+7bGs9rVIoM78fVyM2FERBfsBtT76
|
||||
QVQv3KZ+a9EOLxqcZ/nGqsFFysFOSkiRC6Cy4mC5CSik9S5D7X9lz/bdga7O+hqd
|
||||
SKfir6YMlQGV37JPqmz69N6vvb9UOX/G989T4qdVB/zQOvMdcIWXkqb3vSAXYi/c
|
||||
ClVS1Pymsy/MXQ0CAwEAAaNTMFEwHQYDVR0OBBYEFGYgIECdrhTsmgCKpVM0RHeC
|
||||
kFUmMB8GA1UdIwQYMBaAFGYgIECdrhTsmgCKpVM0RHeCkFUmMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wDQYJKoZIhvcNAQELBQADggEBACsiuHFQjqOglenp/fcNbU034m/vvkyV
|
||||
SZXau9amXBWdeTEpc1HaPOYO7jFFnu/QoH6AbGZkpL0yWZJA2rf102AkOdAe6E0g
|
||||
2H77/hHoHVCfxOiOl3+icsLXJ4VXqV2vmUOEVnWfHRtej4My6avT9uCNMO2bw9hm
|
||||
56RAZrs82T9Mpg/1XQ9YUO1q4/JfP/+dCzPXAdwJ/h2cJ/q6Q9g1gRns8IzVlGOZ
|
||||
0ZBQCLqLl8vUei+t6YgjyBbeNCz4CEcmXKIJeqMB1jhpsgr6BBMTNTU2Q60b9fzU
|
||||
OCGLw94EnKYtHWGy2WHMFNbwkNCR0/jwhxKkU0HXy1aNMUBWp99M7P8=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA2FFfpxEqImd01A9Q+ccTRDroVpz840ektXr45V0RQkpz8zSu
|
||||
Iv5GXPqBXwSgKXsojoLkCYK+VmlX4GQ6xXblAKAd/flUS+WVsUq9kL9cud30dwYK
|
||||
h2V4/Tm/NvUiZsYV3b5/8RfUpYwVNBW/eScbNXrVHNkiBrcIJtixruKsyH+drckP
|
||||
7K4j+AZh43LGsK6NY+ppf5wHBctGWFOCFdJSrabck4oem807COKX9PhTfD87OnjB
|
||||
QcqdHkus8WZP/SGnZV+6c1k0SN4O0E4MahpWkr8D2ZjsbhGFIW8AU4NdobsohClW
|
||||
AZ0MDR1N4oo0sj4HgY2hGR2AbR/5Y2LH4kRcpQIDAQABAoIBAEK2tf1cedYqegl8
|
||||
v8iI8RQ15rnvqL6ftdiSmHiEf3ImbCQxtxLrwN+kEoovbwXcCeIJ1DJqtDEKRCPc
|
||||
RZPo2y+aMiXF442UvNn05wnhOsPIBEFBB7ZCQVI3oRVd/MIdjVjaC7NbWiXEUjXC
|
||||
D09aFDYmL9u5y5iukkEIy6PYHNmokN0MlHwhFqqp72hGe6UvHfZ6yu46z1eF6zLN
|
||||
S4b2ULUzJ6Ov0Y7kmN7vP058yfZYHoj4TPeRBDm3Qx8g9c5d6da22vfkt61UzXEN
|
||||
ih8fIejq6BGila2wMuZKyCM/Oxv5WX0RTzVQO13+42f29BM47Mdk/a8ijBZCzXnC
|
||||
oAnHcwECgYEA7Isah3VNc4KkSNRrnV1UrInQ8BAlNLTN4cJTbyOkogCUECvfzAS+
|
||||
K/l8YZOzZ7YoJkK7RzDeUqRfrZhyJut6x7J/3Vl6qLXpnx3iEPJdxaD5L1iftnIP
|
||||
NOytbphClO+VAjSO3frhlCwZ00Z6o6meTV+CNmRT2LDFEzxAMc3GtrECgYEA6hxh
|
||||
m3S5KX7Ze/m5v9l4vi2iGDNBJkk48Cc+qfgVLGa0TSd7cY+8bjYNufr6vqThKlVu
|
||||
RByZ3Wo5C5PfrkU69YbJ9LnQ+RTZPu+IxPIsUM3xlyTin7bufyOcWhPr1820MKqP
|
||||
A/mRJ/SKel7ubrURai7KDETR0mI9XajhtwF/qjUCgYEAvW1sclwTCVTuwVAzWhM6
|
||||
0u2PACC92uaMFaYscM1nc0DpUcYA8/48WTTzUaUZwA1VO8am+Yz+DcqKwJdbmyVq
|
||||
7u9YjGey3dbIX19sAcxGIhUWWL8tL8tJuEVtYirW7zSp7NkwLD5UVfe3OsWvQs97
|
||||
8VRyD6LqrpZpTE0sz3WOFBECgYAxgOLa3mmw7pPKdVnjyXaQsFGQUHY8REt37LSB
|
||||
eGXxx53kmq6tqrkrjN6GLx4KZg7+xqXUXT/j4+xAGHq5/QWkmWXnC8u2f8QYXMpM
|
||||
6vCX/ZRSY4hQQXxZAgyzt3atYV/y0n3/VyxsiHcnvR8p5bvS+iXbRkof9IoJXgas
|
||||
jfKS6QKBgQC6ZFuvYIeqkfZ0Yyxxum3qlGxpR41wcuIpb4hBQ0gr1haTL0aaoUiQ
|
||||
qqUfzVRst/oPxf6vqeCxtWh1/3lGa1QXP9KJDA5twFMqTg5jv92vjMIgPTEZ2Oif
|
||||
+YyTs72V197KHctx2/T4RxAGhxCLJwDzk2shvLS+1voU/w40YRy9yA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDWzCCAkMCFFTJkICEIidmnrisIFxZ99KKLhDIMA0GCSqGSIb3DQEBCwUAMGox
|
||||
CzAJBgNVBAYTAkRFMQswCQYDVQQIDAJCVzERMA8GA1UEBwwIRnJlaWJ1cmcxGzAZ
|
||||
BgNVBAoMEk1aIEF1dG9tYXRpb24gR21iSDEMMAoGA1UECwwDUiZEMRAwDgYDVQQD
|
||||
DAdyb290X0NBMB4XDTIyMDUyNjEwNDc0NFoXDTMwMDgxMjEwNDc0NFowajELMAkG
|
||||
A1UEBhMCREUxCzAJBgNVBAgMAkJXMREwDwYDVQQHDAhGcmVpYnVyZzEbMBkGA1UE
|
||||
CgwSTVogQXV0b21hdGlvbiBHbWJIMQwwCgYDVQQLDANSJkQxEDAOBgNVBAMMB3Nl
|
||||
cnZlcjEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYUV+nESoiZ3TU
|
||||
D1D5xxNEOuhWnPzjR6S1evjlXRFCSnPzNK4i/kZc+oFfBKApeyiOguQJgr5WaVfg
|
||||
ZDrFduUAoB39+VRL5ZWxSr2Qv1y53fR3BgqHZXj9Ob829SJmxhXdvn/xF9SljBU0
|
||||
Fb95Jxs1etUc2SIGtwgm2LGu4qzIf52tyQ/sriP4BmHjcsawro1j6ml/nAcFy0ZY
|
||||
U4IV0lKtptyTih6bzTsI4pf0+FN8Pzs6eMFByp0eS6zxZk/9IadlX7pzWTRI3g7Q
|
||||
TgxqGlaSvwPZmOxuEYUhbwBTg12huyiEKVYBnQwNHU3iijSyPgeBjaEZHYBtH/lj
|
||||
YsfiRFylAgMBAAEwDQYJKoZIhvcNAQELBQADggEBANPRnvfByVoKwfMcQYUnYT6l
|
||||
5OhYt8f2tQfoa0EXirP0O2xG052ZBl3Z5ZzBCcsq1zveaPoeqXFl6HjqIqURB5NS
|
||||
imJIi7kB7o6C2z19yxOndPm3urKGyfvxtSy2iyzTMZ8eL8RFMJC5DVV+n5Y+1EgC
|
||||
pYIu//I0ojnFOemEJXVjfxQhiUbx6Nw8HalHOhW1i017XcOWMKji/lwHfWF2PFmn
|
||||
pIWZCFPCUtHzBUkXCRzn9ESeMDcMXN6qLb2wGJkRUDw+Ls1RGJd6dnB811vOuOd+
|
||||
QQc8lyyBZ1byARcxQ8lAtof6Mv7Yzebv1OxRr7NcrV/+ujnSFyJWKrJdcMx7+10=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,363 @@
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs104_slave.h"
|
||||
|
||||
#include "hal_thread.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
static bool running = true;
|
||||
|
||||
static int gi_state = 0; /* 0 - no GI running, 1 - GI is running */
|
||||
static IMasterConnection gi_connection = NULL;
|
||||
static int gi_progress = 0;
|
||||
static int gi_oa = 0; /* originator address */
|
||||
static Semaphore gi_lock;
|
||||
|
||||
static CS101_AppLayerParameters appLayerParameters;
|
||||
|
||||
void
|
||||
sigint_handler(int signalId)
|
||||
{
|
||||
(void)signalId;
|
||||
running = false;
|
||||
}
|
||||
|
||||
void
|
||||
printCP56Time2a(CP56Time2a time)
|
||||
{
|
||||
printf("%02i:%02i:%02i %02i/%02i/%04i", CP56Time2a_getHour(time), CP56Time2a_getMinute(time),
|
||||
CP56Time2a_getSecond(time), CP56Time2a_getDayOfMonth(time), CP56Time2a_getMonth(time) + 1,
|
||||
CP56Time2a_getYear(time) + 2000);
|
||||
}
|
||||
|
||||
static void
|
||||
handleGeneralInterrogation()
|
||||
{
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
if (gi_state == 1)
|
||||
{
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(gi_connection);
|
||||
|
||||
if (gi_progress == 0)
|
||||
{
|
||||
/* send scaled values */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, gi_oa, 1, false, false);
|
||||
|
||||
InformationObject io = (InformationObject)MeasuredValueScaled_create(NULL, 100, -1, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)io, 101, 23, IEC60870_QUALITY_GOOD));
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)MeasuredValueScaled_create(
|
||||
(MeasuredValueScaled)io, 102, 2300, IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
gi_progress = 1;
|
||||
}
|
||||
else if (gi_progress == 1)
|
||||
{
|
||||
/* send single points */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, gi_oa, 1, false, false);
|
||||
|
||||
InformationObject io =
|
||||
(InformationObject)SinglePointInformation_create(NULL, 104, true, IEC60870_QUALITY_GOOD);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create((SinglePointInformation)io, 105, false,
|
||||
IEC60870_QUALITY_GOOD));
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
gi_progress = 2;
|
||||
}
|
||||
else if (gi_progress == 2)
|
||||
{
|
||||
/* send more single points */
|
||||
CS101_ASDU newAsdu =
|
||||
CS101_ASDU_create(alParams, true, CS101_COT_INTERROGATED_BY_STATION, gi_oa, 1, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(NULL, 300, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(NULL, 301, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(NULL, 302, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(NULL, 303, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(NULL, 304, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(NULL, 305, false, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(NULL, 306, true, IEC60870_QUALITY_GOOD));
|
||||
CS101_ASDU_addInformationObject(
|
||||
newAsdu, (InformationObject)SinglePointInformation_create(NULL, 307, false, IEC60870_QUALITY_GOOD));
|
||||
|
||||
IMasterConnection_sendASDU(gi_connection, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
|
||||
gi_progress = 3;
|
||||
}
|
||||
else if (gi_progress == 3)
|
||||
{
|
||||
/* send termination message */
|
||||
CS101_ASDU tempAsdu =
|
||||
CS101_ASDU_create(alParams, false, CS101_COT_INTERROGATED_BY_STATION, gi_oa, 1, false, false);
|
||||
|
||||
IMasterConnection_sendACT_TERM(gi_connection, tempAsdu);
|
||||
|
||||
CS101_ASDU_destroy(tempAsdu);
|
||||
|
||||
gi_state = 0;
|
||||
gi_connection = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
|
||||
static bool
|
||||
clockSyncHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, CP56Time2a newTime)
|
||||
{
|
||||
printf("Process time sync command with time ");
|
||||
printCP56Time2a(newTime);
|
||||
printf("\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
interrogationHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi)
|
||||
{
|
||||
(void)parameter;
|
||||
|
||||
int ca = CS101_ASDU_getCA(asdu);
|
||||
|
||||
printf("Received interrogation for CASDU %i and group %i\n", ca, qoi);
|
||||
|
||||
if (ca == 1) /* only handle interrogation for CA 1 */
|
||||
{
|
||||
if (qoi == 20) /* only handle station interrogation */
|
||||
{
|
||||
Semaphore_wait(gi_lock);
|
||||
|
||||
gi_state = 1;
|
||||
gi_connection = connection;
|
||||
gi_progress = 0;
|
||||
gi_oa = (uint8_t)CS101_ASDU_getOA(asdu);
|
||||
IMasterConnection_sendACT_CON(connection, asdu, false);
|
||||
|
||||
Semaphore_post(gi_lock);
|
||||
}
|
||||
else
|
||||
{
|
||||
IMasterConnection_sendACT_CON(connection, asdu, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* send error response */
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
asduHandler(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
|
||||
{
|
||||
if (CS101_ASDU_getTypeID(asdu) == C_SC_NA_1)
|
||||
{
|
||||
printf("received single command\n");
|
||||
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_ACTIVATION)
|
||||
{
|
||||
InformationObject io = CS101_ASDU_getElement(asdu, 0);
|
||||
|
||||
if (io)
|
||||
{
|
||||
if (InformationObject_getObjectAddress(io) == 5000)
|
||||
{
|
||||
SingleCommand sc = (SingleCommand)io;
|
||||
|
||||
printf("IOA: %i switch to %i\n", InformationObject_getObjectAddress(io),
|
||||
SingleCommand_getState(sc));
|
||||
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_ACTIVATION_CON);
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("ERROR: ASDU contains no information object!\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_COT);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
connectionRequestHandler(void* parameter, const char* ipAddress)
|
||||
{
|
||||
printf("New connection from %s\n", ipAddress);
|
||||
|
||||
#if 0
|
||||
if (strcmp(ipAddress, "127.0.0.1") == 0) {
|
||||
printf("Accept connection\n");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
printf("Deny connection\n");
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
securityEventHandler(void* parameter, TLSEventLevel eventLevel, int eventCode, const char* msg, TLSConnection con)
|
||||
{
|
||||
(void)parameter;
|
||||
|
||||
char peerAddrBuf[60];
|
||||
char* peerAddr = NULL;
|
||||
const char* tlsVersion = "unknown";
|
||||
|
||||
if (con)
|
||||
{
|
||||
peerAddr = TLSConnection_getPeerAddress(con, peerAddrBuf);
|
||||
tlsVersion = TLSConfigVersion_toString(TLSConnection_getTLSVersion(con));
|
||||
}
|
||||
|
||||
printf("[SECURITY EVENT] %s (t: %i, c: %i, version: %s remote-ip: %s)\n", msg, eventLevel, eventCode, tlsVersion,
|
||||
peerAddr);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
/* Add Ctrl-C handler */
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
gi_lock = Semaphore_create(1);
|
||||
|
||||
TLSConfiguration tlsConfig = TLSConfiguration_create();
|
||||
|
||||
TLSConfiguration_setEventHandler(tlsConfig, securityEventHandler, NULL);
|
||||
|
||||
TLSConfiguration_setMinTlsVersion(tlsConfig, TLS_VERSION_TLS_1_2);
|
||||
|
||||
TLSConfiguration_setChainValidation(tlsConfig, false);
|
||||
TLSConfiguration_setAllowOnlyKnownCertificates(tlsConfig, true);
|
||||
|
||||
TLSConfiguration_setOwnKeyFromFile(tlsConfig, "server_CA1_1.key", NULL);
|
||||
TLSConfiguration_setOwnCertificateFromFile(tlsConfig, "server_CA1_1.pem");
|
||||
TLSConfiguration_addCACertificateFromFile(tlsConfig, "root_CA1.pem");
|
||||
|
||||
TLSConfiguration_addAllowedCertificateFromFile(tlsConfig, "client_CA1_1.pem");
|
||||
|
||||
TLSConfiguration_setRenegotiationTime(tlsConfig, 2000);
|
||||
|
||||
/* create a new slave/server instance */
|
||||
CS104_Slave slave = CS104_Slave_createSecure(100, 100, tlsConfig);
|
||||
|
||||
CS104_Slave_setLocalAddress(slave, "0.0.0.0");
|
||||
|
||||
/* get the connection parameters - we need them to create correct ASDUs */
|
||||
appLayerParameters = CS104_Slave_getAppLayerParameters(slave);
|
||||
|
||||
/* set the callback handler for the clock synchronization command */
|
||||
CS104_Slave_setClockSyncHandler(slave, clockSyncHandler, NULL);
|
||||
|
||||
/* set the callback handler for the interrogation command */
|
||||
CS104_Slave_setInterrogationHandler(slave, interrogationHandler, NULL);
|
||||
|
||||
/* set handler for other message types */
|
||||
CS104_Slave_setASDUHandler(slave, asduHandler, NULL);
|
||||
|
||||
CS104_Slave_setConnectionRequestHandler(slave, connectionRequestHandler, NULL);
|
||||
|
||||
CS104_Slave_start(slave);
|
||||
|
||||
if (CS104_Slave_isRunning(slave) == false)
|
||||
{
|
||||
printf("Starting server failed!\n");
|
||||
goto exit_program;
|
||||
}
|
||||
|
||||
int16_t scaledValue = 0;
|
||||
|
||||
uint64_t lastPeriodicTransmission = 0;
|
||||
|
||||
while (running)
|
||||
{
|
||||
handleGeneralInterrogation();
|
||||
|
||||
if (Hal_getMonotonicTimeInMs() - lastPeriodicTransmission >= 1000)
|
||||
{
|
||||
lastPeriodicTransmission = Hal_getMonotonicTimeInMs();
|
||||
|
||||
CS101_ASDU newAsdu = CS101_ASDU_create(appLayerParameters, false, CS101_COT_PERIODIC, 0, 1, false, false);
|
||||
|
||||
InformationObject io =
|
||||
(InformationObject)MeasuredValueScaled_create(NULL, 110, scaledValue, IEC60870_QUALITY_GOOD);
|
||||
|
||||
scaledValue++;
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, io);
|
||||
|
||||
InformationObject_destroy(io);
|
||||
|
||||
/* Add ASDU to slave event queue */
|
||||
CS104_Slave_enqueueASDU(slave, newAsdu);
|
||||
|
||||
CS101_ASDU_destroy(newAsdu);
|
||||
}
|
||||
|
||||
Thread_sleep(10);
|
||||
}
|
||||
|
||||
CS104_Slave_stop(slave);
|
||||
|
||||
exit_program:
|
||||
CS104_Slave_destroy(slave);
|
||||
|
||||
TLSConfiguration_destroy(tlsConfig);
|
||||
|
||||
Semaphore_destroy(gi_lock);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
$(LIB_NAME):
|
||||
cd $(LIB60870_HOME); $(MAKE) -f Makefile
|
||||
|
||||
lib: $(LIB_NAME)
|
||||
|
||||
libclean: clean
|
||||
cd $(LIB60870_HOME); $(MAKE) -f Makefile clean
|
||||
@@ -0,0 +1,3 @@
|
||||
INCLUDES += -I$(LIB60870_HOME)/src/inc/api
|
||||
INCLUDES += -I$(LIB60870_HOME)/src/hal/inc
|
||||
INCLUDES += -I$(LIB60870_HOME)/src/tls
|
||||
@@ -0,0 +1,185 @@
|
||||
UNAME := $(shell uname)
|
||||
|
||||
MIPSEL_TOOLCHAIN_PREFIX=mipsel-openwrt-linux-
|
||||
#ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabihf-
|
||||
#ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabi-
|
||||
#ARM_TOOLCHAIN_PREFIX=arm-poky-linux-gnueabi-
|
||||
ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabihf-
|
||||
UCLINUX_ARM_TOOLCHAIN_PREFIX=arm-uclinux-elf-
|
||||
UCLINUX_XPORT_TOOLCHAIN_PREFIX=m68k-uclinux-
|
||||
#MINGW_TOOLCHAIN_PREFIX=i586-mingw32msvc-
|
||||
MINGW_TOOLCHAIN_PREFIX=i686-w64-mingw32-
|
||||
MINGW64_TOOLCHAIN_PREFIX=x86_64-w64-mingw32-
|
||||
|
||||
#POWERPC_TOOLCHAIN_PREFIX=powerpc-poky-linux-
|
||||
POWERPC_TOOLCHAIN_PREFIX=powerpc-linux-gnu-
|
||||
|
||||
ifndef TARGET
|
||||
ifeq ($(UNAME), Linux)
|
||||
TARGET=POSIX
|
||||
else ifeq ($(findstring MINGW,$(UNAME)), MINGW)
|
||||
TARGET=WIN32
|
||||
else ifeq ($(UNAME), Darwin)
|
||||
TARGET=BSD
|
||||
else ifeq ($(UNAME), FreeBSD)
|
||||
TARGET=BSD
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), WIN32)
|
||||
WINDOWS=1
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), WIN64)
|
||||
WINDOWS=1
|
||||
endif
|
||||
|
||||
ifdef WINDOWS
|
||||
ifeq ($(UNAME), Linux)
|
||||
ifeq ($(TARGET), WIN32)
|
||||
TOOLCHAIN_PREFIX=$(MINGW_TOOLCHAIN_PREFIX)
|
||||
endif
|
||||
ifeq ($(TARGET), WIN64)
|
||||
TOOLCHAIN_PREFIX=$(MINGW64_TOOLCHAIN_PREFIX)
|
||||
endif
|
||||
else
|
||||
TOOLCHAIN_PREFIX=
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), LINUX-POWERPC)
|
||||
TOOLCHAIN_PREFIX=$(POWERPC_TOOLCHAIN_PREFIX)
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), LINUX-MIPSEL)
|
||||
TOOLCHAIN_PREFIX=$(MIPSEL_TOOLCHAIN_PREFIX)
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), LINUX-ARM)
|
||||
TOOLCHAIN_PREFIX=$(ARM_TOOLCHAIN_PREFIX)
|
||||
CFLAGS += -mno-unaligned-access
|
||||
# CFLAGS += -mcpu=arm926ej-s
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), UCLINUX-WAGO)
|
||||
TOOLCHAIN_PREFIX=$(UCLINUX_ARM_TOOLCHAIN_PREFIX)
|
||||
CFLAGS += -msoft-float
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += -DEMBED
|
||||
CFLAGS += -Dlinux -D__linux__ -Dunix
|
||||
CFLAGS += -D__uClinux__
|
||||
CFLAGS += -DTARGET=UCLINUX-WAGO
|
||||
LDFLAGS += -Wl,-move-rodata -Wl,-elf2flt
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), UCLINUX-XPORT)
|
||||
TOOLCHAIN_PREFIX=$(UCLINUX_XPORT_TOOLCHAIN_PREFIX)
|
||||
CFLAGS += -DPLATFORM_BYTE_ORDER
|
||||
CFLAGS += -mcpu=5208
|
||||
CFLAGS += -fno-builtin -fno-common
|
||||
CFLAGS += -fno-dwarf2-cfi-asm -msep-data -DCONFIG_COLDFIRE -D__linux__ -Dunix -D__uClinux__
|
||||
endif
|
||||
|
||||
|
||||
ifdef WINDOWS
|
||||
HAL_IMPL = WIN32
|
||||
LIB_OBJS_DIR = $(LIB60870_HOME)/build_win32
|
||||
CFLAGS=-g -DWIN32
|
||||
|
||||
ifeq ($(TARGET), WIN32)
|
||||
CFLAGS+=-m32
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), WIN64)
|
||||
CFLAGS+=-m64
|
||||
endif
|
||||
|
||||
LDLIBS=-lws2_32
|
||||
DYNLIB_LDFLAGS=-Wl,-no-undefined -Wl,--enable-runtime-pseudo-reloc -Wl,--output-def,libiec61850.def,--out-implib,libiec61850.a
|
||||
|
||||
|
||||
# on Windows: only compile with ethernet support if winpcap files are in third_party/winpcap!
|
||||
ifneq (, $(wildcard $(LIB60870_HOME)/third_party/winpcap/Include/.))
|
||||
|
||||
ifeq ($(TARGET), WIN64)
|
||||
LDFLAGS += -L$(LIB60870_HOME)/third_party/winpcap/Lib/x64
|
||||
else
|
||||
LDFLAGS += -L$(LIB60870_HOME)/third_party/winpcap/Lib
|
||||
endif
|
||||
|
||||
LDLIBS+=-liphlpapi -lwpcap
|
||||
else
|
||||
$(warning winpcap not found - will build without GOOSE support!)
|
||||
CFLAGS += -DEXCLUDE_ETHERNET_WINDOWS
|
||||
EXCLUDE_ETHERNET_WINDOWS = 1
|
||||
endif
|
||||
|
||||
|
||||
else
|
||||
ifeq ($(TARGET), BSD)
|
||||
HAL_IMPL = BSD
|
||||
else
|
||||
HAL_IMPL = POSIX
|
||||
endif
|
||||
|
||||
|
||||
LDLIBS = -lpthread
|
||||
|
||||
|
||||
ifeq ($(TARGET), LINUX-MIPSEL)
|
||||
LIB_OBJS_DIR = $(LIB60870_HOME)/build-mipsel
|
||||
else ifeq ($(TARGET), LINUX-ARM)
|
||||
LIB_OBJS_DIR = $(LIB60870_HOME)/build-arm
|
||||
else ifeq ($(TARGET), UCLINUX-WAGO)
|
||||
LIB_OBJS_DIR = $(LIB60870_HOME)/build-wago
|
||||
CFLAGS += -DTARGET_SYSTEM_UCLINUX_WAGO
|
||||
else ifeq ($(TARGET), LINUX-POWERPC)
|
||||
LIB_OBJS_DIR = $(LIB60870_HOME)/build-powerpc
|
||||
else
|
||||
LIB_OBJS_DIR = $(LIB60870_HOME)/build
|
||||
endif
|
||||
|
||||
CFLAGS += -g
|
||||
#CFLAGS += -Os
|
||||
|
||||
DYNLIB_LDFLAGS=-lpthread
|
||||
endif
|
||||
|
||||
ifneq ($(TARGET), CLANG-CHECK)
|
||||
CC=$(TOOLCHAIN_PREFIX)gcc
|
||||
CPP=$(TOOLCHAIN_PREFIX)g++
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), BSD)
|
||||
CC=cc
|
||||
CPP=c++
|
||||
endif
|
||||
|
||||
AR=$(TOOLCHAIN_PREFIX)ar
|
||||
RANLIB=$(TOOLCHAIN_PREFIX)ranlib
|
||||
|
||||
|
||||
ifeq ($(TARGET), WIN32)
|
||||
PROJECT_BINARY_NAME := $(PROJECT_BINARY_NAME).exe
|
||||
endif
|
||||
|
||||
LIB_NAME = $(LIB_OBJS_DIR)/liblib60870.a
|
||||
|
||||
TEST_NAME = $(LIB_OBJS_DIR)/tests.exe
|
||||
|
||||
ifeq ($(TARGET), BSD)
|
||||
CFLAGS += -arch i386
|
||||
LDFLAGS += -arch i386
|
||||
endif
|
||||
|
||||
ifeq ($(TARGET), WIN32)
|
||||
DYN_LIB_NAME = $(LIB_OBJS_DIR)/lib60870.dll
|
||||
else
|
||||
|
||||
ifeq ($(TARGET), BSD)
|
||||
DYN_LIB_NAME = $(LIB_OBJS_DIR)/liblib60870.dylib
|
||||
else
|
||||
DYN_LIB_NAME = $(LIB_OBJS_DIR)/liblib60870.so
|
||||
endif
|
||||
|
||||
endif
|
||||
@@ -0,0 +1 @@
|
||||
This folder contains optional library add-ons (e.g. for secure authentication).
|
||||
@@ -0,0 +1,185 @@
|
||||
|
||||
set (lib_common_SRCS
|
||||
./file-service/file_server.c
|
||||
./iec60870/apl/cpXXtime2a.c
|
||||
./iec60870/cs101/cs101_asdu.c
|
||||
./iec60870/cs101/cs101_bcr.c
|
||||
./iec60870/cs101/cs101_information_objects.c
|
||||
./iec60870/cs101/cs101_master_connection.c
|
||||
./iec60870/cs101/cs101_master.c
|
||||
./iec60870/cs101/cs101_queue.c
|
||||
./iec60870/cs101/cs101_slave.c
|
||||
./iec60870/cs104/cs104_connection.c
|
||||
./iec60870/cs104/cs104_frame.c
|
||||
./iec60870/cs104/cs104_slave.c
|
||||
./iec60870/link_layer/buffer_frame.c
|
||||
./iec60870/link_layer/link_layer.c
|
||||
./iec60870/link_layer/serial_transceiver_ft_1_2.c
|
||||
./iec60870/frame.c
|
||||
./iec60870/lib60870_common.c
|
||||
)
|
||||
|
||||
if (BUILD_COMMON)
|
||||
list (APPEND lib_common_SRCS ./common/linked_list.c)
|
||||
endif (BUILD_COMMON)
|
||||
|
||||
if (BUILD_HAL)
|
||||
|
||||
set (lib_linux_SRCS
|
||||
./hal/serial/linux/serial_port_linux.c
|
||||
./hal/socket/linux/socket_linux.c
|
||||
./hal/thread/linux/thread_linux.c
|
||||
./hal/time/unix/time.c
|
||||
./hal/memory/lib_memory.c
|
||||
)
|
||||
|
||||
set (lib_windows_SRCS
|
||||
./hal/serial/win32/serial_port_win32.c
|
||||
./hal/socket/win32/socket_win32.c
|
||||
./hal/thread/win32/thread_win32.c
|
||||
./hal/time/win32/time.c
|
||||
./hal/memory/lib_memory.c
|
||||
)
|
||||
|
||||
set (lib_bsd_SRCS
|
||||
./hal/serial/linux/serial_port_linux.c
|
||||
./hal/socket/bsd/socket_bsd.c
|
||||
./hal/thread/bsd/thread_bsd.c
|
||||
./hal/time/unix/time.c
|
||||
./hal/memory/lib_memory.c
|
||||
)
|
||||
|
||||
set (lib_macos_SRCS
|
||||
./hal/serial/linux/serial_port_linux.c
|
||||
./hal/socket/bsd/socket_bsd.c
|
||||
./hal/thread/macos/thread_macos.c
|
||||
./hal/time/unix/time.c
|
||||
./hal/memory/lib_memory.c
|
||||
)
|
||||
|
||||
endif (BUILD_HAL)
|
||||
|
||||
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/../modules/sec-auth)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../modules/sec-auth/CMakeLists_src.txt)
|
||||
|
||||
add_compile_definitions(SEC_AUTH_60870_5_7)
|
||||
|
||||
endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/../modules/sec-auth)
|
||||
|
||||
IF(WIN32)
|
||||
|
||||
IF(MSVC)
|
||||
set_source_files_properties(${lib_common_SRCS} ${lib_windows_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF()
|
||||
|
||||
set (library_SRCS
|
||||
${lib_common_SRCS}
|
||||
${lib_windows_SRCS}
|
||||
)
|
||||
|
||||
set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}\"/DEF:${CMAKE_CURRENT_SOURCE_DIR}/vs/lib60870.def\"")
|
||||
|
||||
ELSEIF(UNIX)
|
||||
IF(APPLE)
|
||||
set (library_SRCS
|
||||
${lib_common_SRCS}
|
||||
${lib_macos_SRCS}
|
||||
)
|
||||
ELSEIF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
||||
set (library_SRCS
|
||||
${lib_common_SRCS}
|
||||
${lib_bsd_SRCS}
|
||||
)
|
||||
ELSE()
|
||||
set (library_SRCS
|
||||
${lib_common_SRCS}
|
||||
${lib_linux_SRCS}
|
||||
)
|
||||
ENDIF(APPLE)
|
||||
ENDIF(WIN32)
|
||||
|
||||
IF(WITH_MBEDTLS)
|
||||
|
||||
list (APPEND library_SRCS ${tls_SRCS})
|
||||
list (APPEND library_SRCS ./hal/tls/mbedtls/tls_mbedtls.c)
|
||||
|
||||
add_definitions(-DLIB60870_HAS_TLS_SUPPORT=1)
|
||||
|
||||
ENDIF(WITH_MBEDTLS)
|
||||
|
||||
IF(WITH_MBEDTLS3)
|
||||
|
||||
list (APPEND library_SRCS ${tls_SRCS})
|
||||
list (APPEND library_SRCS ./hal/tls/mbedtls3/tls_mbedtls.c)
|
||||
|
||||
add_definitions(-DLIB60870_HAS_TLS_SUPPORT=1)
|
||||
|
||||
ENDIF(WITH_MBEDTLS3)
|
||||
|
||||
include (GenerateExportHeader)
|
||||
|
||||
set(RES_FILES "")
|
||||
if ( WIN32 )
|
||||
# Adding RC resource file for adding information to the archive
|
||||
set(RES_FILES "${CMAKE_CURRENT_BINARY_DIR}/version.rc")
|
||||
message(STATUS "Generating RC file : ${RES_FILES}")
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in
|
||||
${RES_FILES}
|
||||
@ONLY)
|
||||
if( MINGW )
|
||||
set(CMAKE_RC_COMPILER_INIT windres)
|
||||
ENABLE_LANGUAGE(RC)
|
||||
SET(CMAKE_RC_COMPILE_OBJECT
|
||||
"<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
|
||||
endif(MINGW)
|
||||
set(library_SRCS ${library_SRCS} ${RES_FILES})
|
||||
endif( WIN32 )
|
||||
|
||||
add_library (lib60870-shared SHARED ${library_SRCS} )
|
||||
|
||||
set_target_properties(lib60870-shared PROPERTIES
|
||||
OUTPUT_NAME lib60870
|
||||
SOVERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}"
|
||||
WINDOWS_EXPORT_ALL_SYMBOLS true
|
||||
)
|
||||
|
||||
|
||||
GENERATE_EXPORT_HEADER(lib60870-shared
|
||||
BASE_NAME lib60870-shared
|
||||
EXPORT_MACRO_NAME lib60870-shared_EXPORT
|
||||
EXPORT_FILE_NAME lib60870-shared_export.h
|
||||
STATIC_DEFINE lib60870-shared_BUILT_AS_STATIC
|
||||
)
|
||||
|
||||
add_library (lib60870 STATIC ${library_SRCS})
|
||||
|
||||
IF(UNIX)
|
||||
target_link_libraries (lib60870
|
||||
-lpthread
|
||||
-lm
|
||||
-lrt
|
||||
)
|
||||
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/lib60870.pc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/lib60870.pc @ONLY
|
||||
)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/lib60870.pc" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/pkgconfig")
|
||||
ENDIF(UNIX)
|
||||
IF(MINGW)
|
||||
target_link_libraries(lib60870-shared ws2_32 iphlpapi bcrypt)
|
||||
target_link_libraries(lib60870 ws2_32 iphlpapi bcrypt)
|
||||
ENDIF(MINGW)
|
||||
IF(MSVC)
|
||||
target_link_libraries(lib60870-shared ws2_32 iphlpapi bcrypt)
|
||||
target_link_libraries(lib60870 ws2_32 iphlpapi bcrypt)
|
||||
ENDIF(MSVC)
|
||||
|
||||
install (TARGETS lib60870 lib60870-shared
|
||||
RUNTIME DESTINATION bin COMPONENT Applications
|
||||
ARCHIVE DESTINATION lib COMPONENT Libraries
|
||||
LIBRARY DESTINATION lib COMPONENT Libraries
|
||||
)
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2013-2022 Michael Zillgith
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#ifndef LINKED_LIST_H_
|
||||
#define LINKED_LIST_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \addtogroup common_api_group
|
||||
*/
|
||||
/**@{*/
|
||||
|
||||
/**
|
||||
* \defgroup LINKED_LIST LinkedList data type definition and handling functions
|
||||
*/
|
||||
/**@{*/
|
||||
|
||||
|
||||
struct sLinkedList {
|
||||
void* data;
|
||||
struct sLinkedList* next;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Reference to a linked list or to a linked list element.
|
||||
*/
|
||||
typedef struct sLinkedList* LinkedList;
|
||||
|
||||
/**
|
||||
* \brief Create a new LinkedList object
|
||||
*
|
||||
* \return the newly created LinkedList instance
|
||||
*/
|
||||
LinkedList
|
||||
LinkedList_create(void);
|
||||
|
||||
/**
|
||||
* \brief Delete a LinkedList object
|
||||
*
|
||||
* This function destroy the LinkedList object. It will free all data structures used by the LinkedList
|
||||
* instance. It will call free for all elements of the linked list. This function should only be used if
|
||||
* simple objects (like dynamically allocated strings) are stored in the linked list.
|
||||
*
|
||||
* \param self the LinkedList instance
|
||||
*/
|
||||
void
|
||||
LinkedList_destroy(LinkedList self);
|
||||
|
||||
|
||||
typedef void (*LinkedListValueDeleteFunction) (void*);
|
||||
|
||||
/**
|
||||
* \brief Delete a LinkedList object
|
||||
*
|
||||
* This function destroy the LinkedList object. It will free all data structures used by the LinkedList
|
||||
* instance. It will call a user provided function for each data element. This user provided function is
|
||||
* responsible to properly free the data element.
|
||||
*
|
||||
* \param self the LinkedList instance
|
||||
* \param valueDeleteFunction a function that is called for each data element of the LinkedList with the pointer
|
||||
* to the linked list data element.
|
||||
*/
|
||||
void
|
||||
LinkedList_destroyDeep(LinkedList self, LinkedListValueDeleteFunction valueDeleteFunction);
|
||||
|
||||
/**
|
||||
* \brief Delete a LinkedList object without freeing the element data
|
||||
*
|
||||
* This function should be used statically allocated data objects are stored in the LinkedList instance.
|
||||
* Other use cases would be if the data elements in the list should not be deleted.
|
||||
*
|
||||
* \param self the LinkedList instance
|
||||
*/
|
||||
void
|
||||
LinkedList_destroyStatic(LinkedList self);
|
||||
|
||||
/**
|
||||
* \brief Add a new element to the list
|
||||
*
|
||||
* This function will add a new data element to the list. The new element will the last element in the
|
||||
* list.
|
||||
*
|
||||
* \param self the LinkedList instance
|
||||
* \param data data to append to the LinkedList instance
|
||||
*/
|
||||
void
|
||||
LinkedList_add(LinkedList self, void* data);
|
||||
|
||||
/**
|
||||
* \brief Removed the specified element from the list
|
||||
*
|
||||
* \param self the LinkedList instance
|
||||
* \param data data to remove from the LinkedList instance
|
||||
*/
|
||||
bool
|
||||
LinkedList_remove(LinkedList self, const void* data);
|
||||
|
||||
/**
|
||||
* \brief Get the list element specified by index (starting with 0).
|
||||
*
|
||||
* \param self the LinkedList instance
|
||||
* \param index index of the requested element.
|
||||
*/
|
||||
LinkedList
|
||||
LinkedList_get(LinkedList self, int index);
|
||||
|
||||
/**
|
||||
* \brief Get the next element in the list (iterator).
|
||||
*
|
||||
* \param self the LinkedList instance
|
||||
*/
|
||||
LinkedList
|
||||
LinkedList_getNext(LinkedList self);
|
||||
|
||||
/**
|
||||
* \brief Get the last element in the list.
|
||||
*
|
||||
* \param listElement the LinkedList instance
|
||||
*/
|
||||
LinkedList
|
||||
LinkedList_getLastElement(LinkedList self);
|
||||
|
||||
/**
|
||||
* \brief Insert a new element int the list
|
||||
*
|
||||
* \param listElement the LinkedList instance
|
||||
*/
|
||||
LinkedList
|
||||
LinkedList_insertAfter(LinkedList listElement, void* data);
|
||||
|
||||
/**
|
||||
* \brief Get the size of the list
|
||||
*
|
||||
* \param self the LinkedList instance
|
||||
*
|
||||
* \return number of data elements stored in the list
|
||||
*/
|
||||
int
|
||||
LinkedList_size(LinkedList self);
|
||||
|
||||
void*
|
||||
LinkedList_getData(LinkedList self);
|
||||
|
||||
/**@}*/
|
||||
|
||||
/**@}*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LINKED_LIST_H_ */
|
||||
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Copyright 2013-2022 Michael Zillgith
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#include "linked_list.h"
|
||||
#include "lib_memory.h"
|
||||
|
||||
#ifndef CONFIG_COMPILE_WITHOUT_COMMON
|
||||
|
||||
LinkedList
|
||||
LinkedList_getLastElement(LinkedList list)
|
||||
{
|
||||
while (list->next != NULL) {
|
||||
list = list->next;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
LinkedList
|
||||
LinkedList_create()
|
||||
{
|
||||
LinkedList newList;
|
||||
|
||||
newList = (LinkedList) GLOBAL_MALLOC(sizeof(struct sLinkedList));
|
||||
newList->data = NULL;
|
||||
newList->next = NULL;
|
||||
|
||||
return newList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy list (free). Also frees element data with helper function.
|
||||
*/
|
||||
void
|
||||
LinkedList_destroyDeep(LinkedList list, LinkedListValueDeleteFunction valueDeleteFunction)
|
||||
{
|
||||
LinkedList nextElement = list;
|
||||
LinkedList currentElement;
|
||||
|
||||
do {
|
||||
currentElement = nextElement;
|
||||
nextElement = currentElement->next;
|
||||
|
||||
if (currentElement->data != NULL)
|
||||
valueDeleteFunction(currentElement->data);
|
||||
|
||||
GLOBAL_FREEMEM(currentElement);
|
||||
}
|
||||
while (nextElement != NULL);
|
||||
}
|
||||
|
||||
void
|
||||
LinkedList_destroy(LinkedList list)
|
||||
{
|
||||
LinkedList_destroyDeep(list, Memory_free);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy list (free) without freeing the element data
|
||||
*/
|
||||
void
|
||||
LinkedList_destroyStatic(LinkedList list)
|
||||
{
|
||||
LinkedList nextElement = list;
|
||||
LinkedList currentElement;
|
||||
|
||||
do {
|
||||
currentElement = nextElement;
|
||||
nextElement = currentElement->next;
|
||||
GLOBAL_FREEMEM(currentElement);
|
||||
}
|
||||
while (nextElement != NULL);
|
||||
}
|
||||
|
||||
int
|
||||
LinkedList_size(LinkedList list)
|
||||
{
|
||||
LinkedList nextElement = list;
|
||||
int size = 0;
|
||||
|
||||
while (nextElement->next != NULL) {
|
||||
nextElement = nextElement->next;
|
||||
size++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void
|
||||
LinkedList_add(LinkedList list, void* data)
|
||||
{
|
||||
LinkedList newElement = LinkedList_create();
|
||||
|
||||
newElement->data = data;
|
||||
|
||||
LinkedList listEnd = LinkedList_getLastElement(list);
|
||||
|
||||
listEnd->next = newElement;
|
||||
}
|
||||
|
||||
bool
|
||||
LinkedList_remove(LinkedList list, const void* data)
|
||||
{
|
||||
LinkedList lastElement = list;
|
||||
|
||||
LinkedList currentElement = list->next;
|
||||
|
||||
while (currentElement != NULL) {
|
||||
if (currentElement->data == data) {
|
||||
lastElement->next = currentElement->next;
|
||||
GLOBAL_FREEMEM(currentElement);
|
||||
return true;
|
||||
}
|
||||
|
||||
lastElement = currentElement;
|
||||
currentElement = currentElement->next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
LinkedList
|
||||
LinkedList_insertAfter(LinkedList list, void* data)
|
||||
{
|
||||
LinkedList originalNextElement = LinkedList_getNext(list);
|
||||
|
||||
LinkedList newElement = LinkedList_create();
|
||||
|
||||
newElement->data = data;
|
||||
newElement->next = originalNextElement;
|
||||
|
||||
list->next = newElement;
|
||||
|
||||
return newElement;
|
||||
}
|
||||
|
||||
LinkedList
|
||||
LinkedList_getNext(LinkedList list)
|
||||
{
|
||||
return list->next;
|
||||
}
|
||||
|
||||
LinkedList
|
||||
LinkedList_get(LinkedList list, int index)
|
||||
{
|
||||
LinkedList element = LinkedList_getNext(list);
|
||||
|
||||
int i = 0;
|
||||
|
||||
while (i < index) {
|
||||
element = LinkedList_getNext(element);
|
||||
|
||||
if (element == NULL)
|
||||
return NULL;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
void*
|
||||
LinkedList_getData(LinkedList self)
|
||||
{
|
||||
return self->data;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2016-2022 Michael Zillgith
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#ifndef LIB60870_C_FILE_SERVICE_FILE_SERVICE_H_
|
||||
#define LIB60870_C_FILE_SERVICE_FILE_SERVICE_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "iec60870_common.h"
|
||||
#include "iec60870_slave.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
CS101_FILE_ERROR_SUCCESS,
|
||||
CS101_FILE_ERROR_TIMEOUT,
|
||||
CS101_FILE_ERROR_FILE_NOT_READY,
|
||||
CS101_FILE_ERROR_SECTION_NOT_READY,
|
||||
CS101_FILE_ERROR_UNKNOWN_CA,
|
||||
CS101_FILE_ERROR_UNKNOWN_IOA,
|
||||
CS101_FILE_ERROR_UNKNOWN_SERVICE,
|
||||
CS101_FILE_ERROR_PROTOCOL_ERROR,
|
||||
CS101_FILE_ERROR_ABORTED_BY_REMOTE
|
||||
} CS101_FileErrorCode;
|
||||
|
||||
typedef struct sCS101_FileServer* CS101_FileServer;
|
||||
|
||||
typedef struct sCS101_IFileReceiver* CS101_IFileReceiver;
|
||||
|
||||
struct sCS101_IFileReceiver
|
||||
{
|
||||
void* object; /* user provided context object */
|
||||
|
||||
void (*finished) (CS101_IFileReceiver self, CS101_FileErrorCode result);
|
||||
void (*segmentReceived) (CS101_IFileReceiver self, uint8_t sectionName, int offset, int size, uint8_t* data);
|
||||
};
|
||||
|
||||
typedef struct sCS101_IFileProvider* CS101_IFileProvider;
|
||||
|
||||
struct sCS101_IFileProvider {
|
||||
int ca;
|
||||
int ioa;
|
||||
uint8_t nof;
|
||||
|
||||
void* object; /* user provided context object */
|
||||
|
||||
uint64_t (*getFileDate) (CS101_IFileProvider self);
|
||||
int (*getFileSize) (CS101_IFileProvider self);
|
||||
int (*getSectionSize) (CS101_IFileProvider self, int sectionNumber);
|
||||
|
||||
/**
|
||||
* \brief Get section data (will be called by the file service implementation)
|
||||
*
|
||||
* \param offset byte offset of segment in the section
|
||||
* \param size of segment (has to be at most the size of the provided data buffer)
|
||||
* \param data buffer to store the segment data
|
||||
*/
|
||||
bool (*getSegmentData) (CS101_IFileProvider self, int sectionNumber, int offset, int size, uint8_t* data);
|
||||
|
||||
void (*transferComplete) (CS101_IFileProvider self, bool success);
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Will be called by the \ŕef CS101_FileServer when the master sends a FILE READY (file download announcement) message to the slave
|
||||
*
|
||||
* \param parameter user provided context parameter
|
||||
* \param ca CA of the file
|
||||
* \param ioa IOA of the file
|
||||
* \param nof the name of file (file type) of the file
|
||||
* \param lengthOfFile the length of the file to be sent
|
||||
* \param[out] errCode error code of file service (0 - ok, 1 - unknown CA, 2 - unknown IOA, 3-unknown name of file, 4 - file not ready )
|
||||
*
|
||||
* \return file handle (interface to access the file)
|
||||
*/
|
||||
typedef CS101_IFileReceiver (*CS101_FileReadyHandler) (void* parameter, int ca, int ioa, uint16_t nof, int lengthOfFile, int* errCode);
|
||||
|
||||
typedef struct sCS101_FilesAvailable* CS101_FilesAvailable;
|
||||
|
||||
struct sCS101_FilesAvailable
|
||||
{
|
||||
/**
|
||||
* Used to handle request directory call
|
||||
*
|
||||
* \return The next file or NULL if no more file available
|
||||
*/
|
||||
CS101_IFileProvider (*getNextFile) (void* parameter, CS101_IFileProvider continueAfter);
|
||||
|
||||
CS101_IFileProvider (*getFile) (void* parameter, int ca, int ioa, uint16_t nof, int* errCode);
|
||||
|
||||
void* parameter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \brief Handle transparent files (implement CS101_IFileProvider interface)
|
||||
*/
|
||||
typedef struct sCS101_TransparentFile* CS101_TransparentFile;
|
||||
|
||||
CS101_IFileProvider
|
||||
CS101_TransparentFile_create(int ca, int ioa, uint8_t nof);
|
||||
|
||||
CS101_FileServer
|
||||
CS101_FileServer_create(CS101_AppLayerParameters alParams);
|
||||
|
||||
void
|
||||
CS101_FileServer_destroy(CS101_FileServer self);
|
||||
|
||||
void
|
||||
CS101_FileServer_setFilesAvailableIfc(CS101_FileServer self, CS101_FilesAvailable ifc);
|
||||
|
||||
void
|
||||
CS101_FileServer_setFileReadyHandler(CS101_FileServer self, CS101_FileReadyHandler handler, void* parameter);
|
||||
|
||||
CS101_SlavePlugin
|
||||
CS101_FileServer_getSlavePlugin(CS101_FileServer self);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* LIB60870_C_FILE_SERVICE_FILE_SERVICE_H_ */
|
||||
@@ -0,0 +1,965 @@
|
||||
/*
|
||||
* Copyright 2016-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#include "cs101_file_service.h"
|
||||
#include "lib_memory.h"
|
||||
#include "iec60870_slave.h"
|
||||
#include "lib60870_internal.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
UNSELECTED_IDLE,
|
||||
WAITING_FOR_FILE_CALL,
|
||||
WAITING_FOR_SECTION_CALL,
|
||||
TRANSMIT_SECTION,
|
||||
WAITING_FOR_SECTION_ACK,
|
||||
WAITING_FOR_FILE_ACK,
|
||||
SEND_ABORT,
|
||||
TRANSFER_COMPLETED,
|
||||
WAITING_FOR_SECTION_READY,
|
||||
RECEIVE_SECTION,
|
||||
} FileServerState;
|
||||
|
||||
struct sCS101_FileServer
|
||||
{
|
||||
struct sCS101_SlavePlugin plugin;
|
||||
|
||||
CS101_IFileReceiver fileReceiver;
|
||||
|
||||
CS101_FileReadyHandler fileReadyHandler; /* handler that is called when the master wants to send a file */
|
||||
void* fileReadyHandlerParameter;
|
||||
|
||||
CS101_FilesAvailable filesAvailable;
|
||||
|
||||
int ca;
|
||||
int ioa;
|
||||
uint8_t oa;
|
||||
uint16_t nof;
|
||||
|
||||
uint64_t lastSendTime;
|
||||
|
||||
FileServerState state;
|
||||
|
||||
uint8_t currentSectionNumber;
|
||||
int currentSectionOffset;
|
||||
int currentSectionSize;
|
||||
uint8_t sectionChecksum;
|
||||
uint8_t fileChecksum;
|
||||
|
||||
int maxSegmentSize; /* max. size of segment payload data */
|
||||
|
||||
uint64_t timeout;
|
||||
|
||||
CS101_IFileProvider selectedFile;
|
||||
IMasterConnection selectedConnection;
|
||||
};
|
||||
|
||||
static void
|
||||
sendCallFile(CS101_FileServer self, IMasterConnection connection, int oa)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
uint8_t ioBuf[64];
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
/* send call file */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
|
||||
oa, self->ca, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
FileCallOrSelect_create((FileCallOrSelect) &ioBuf, self->ioa, self->nof, 0, 2/*=request-file*/));
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
}
|
||||
|
||||
static void
|
||||
sendCallSection(CS101_FileServer self, IMasterConnection connection, int oa)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
uint8_t ioBuf[64];
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
/* send call file */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
|
||||
oa, self->ca, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
FileCallOrSelect_create((FileCallOrSelect) &ioBuf, self->ioa, self->nof, self->currentSectionNumber, 6/*=request-section*/));
|
||||
|
||||
DEBUG_PRINT("Send CALL SECTION number: %i\n", self->currentSectionNumber);
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
}
|
||||
|
||||
static void
|
||||
sendFileAck(CS101_FileServer self, IMasterConnection connection, int oa, uint8_t nos, uint8_t afq)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
uint8_t ioBuf[64];
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
/* send call file */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
|
||||
oa, self->ca, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
FileACK_create((FileACK) &ioBuf, self->ioa, self->nof, nos, afq));
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
}
|
||||
|
||||
static void
|
||||
sendSectionReady(CS101_FileServer self, IMasterConnection connection, int oa)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
uint8_t ioBuf[64];
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
/* send call file */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
|
||||
oa, self->ca, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
SectionReady_create((SectionReady) &ioBuf, self->ioa, self->nof, self->currentSectionNumber, self->currentSectionSize, false));
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
}
|
||||
|
||||
static void
|
||||
sendLastSection(CS101_FileServer self, IMasterConnection connection, int oa)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
uint8_t ioBuf[64];
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
/* send call file */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
|
||||
oa, self->ca, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
FileLastSegmentOrSection_create((FileLastSegmentOrSection) &ioBuf, self->ioa, self->nof, self->currentSectionNumber, 1 /* file-transfer-without-deact */, self->fileChecksum));
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
}
|
||||
|
||||
static void
|
||||
sendFileReady(CS101_FileServer self, IMasterConnection connection, int oa, uint32_t lof, bool positive)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
uint8_t ioBuf[64];
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
/* send call file */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
|
||||
oa, self->ca, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
FileReady_create((FileReady) &ioBuf, self->ioa, self->nof, lof, positive));
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
}
|
||||
|
||||
static uint8_t
|
||||
calculateChecksum(uint8_t* data, int size)
|
||||
{
|
||||
uint8_t checksum = 0;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
static bool
|
||||
sendSegment(CS101_FileServer self, IMasterConnection connection, int oa)
|
||||
{
|
||||
int currentSegmentSize = self->currentSectionSize - self->currentSectionOffset;
|
||||
|
||||
DEBUG_PRINT("sendSegment(%i/%i)\n", self->currentSectionOffset, self->currentSectionSize);
|
||||
|
||||
if (currentSegmentSize > 0)
|
||||
{
|
||||
if (currentSegmentSize > self->maxSegmentSize)
|
||||
currentSegmentSize = self->maxSegmentSize;
|
||||
|
||||
sCS101_StaticASDU _asdu;
|
||||
uint8_t ioBuf[64];
|
||||
uint8_t segmentData[255];
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
/* send call file */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
|
||||
oa, self->ca, false, false);
|
||||
|
||||
self->selectedFile->getSegmentData(self->selectedFile, self->currentSectionNumber - 1, self->currentSectionOffset, currentSegmentSize, segmentData);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
|
||||
FileSegment_create((FileSegment) &ioBuf, self->ioa, self->nof, self->currentSectionNumber, segmentData, currentSegmentSize));
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
|
||||
self->currentSectionOffset += currentSegmentSize;
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->sectionChecksum += calculateChecksum(segmentData, currentSegmentSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sendLastSegment(CS101_FileServer self, IMasterConnection connection, int oa)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
uint8_t ioBuf[64];
|
||||
|
||||
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
|
||||
|
||||
/* send call file */
|
||||
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
|
||||
oa, self->ca, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(newAsdu,
|
||||
(InformationObject)
|
||||
FileLastSegmentOrSection_create((FileLastSegmentOrSection) &ioBuf, self->ioa, self->nof, self->currentSectionNumber,
|
||||
3 /* section-transfer-without-deact */, self->sectionChecksum));
|
||||
|
||||
DEBUG_PRINT("Send LAST SEGMENT (NoS=%i, CHS=%i)\n", self->currentSectionNumber, self->sectionChecksum);
|
||||
|
||||
self->fileChecksum += self->sectionChecksum;
|
||||
self->sectionChecksum = 0;
|
||||
|
||||
IMasterConnection_sendASDU(connection, newAsdu);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Call inside asdu handler
|
||||
*/
|
||||
static CS101_SlavePlugin_Result
|
||||
CS101_FileServer_handleAsdu(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
|
||||
{
|
||||
CS101_FileServer self = (CS101_FileServer) parameter;
|
||||
|
||||
CS101_SlavePlugin_Result result = CS101_PLUGIN_RESULT_NOT_HANDLED;
|
||||
|
||||
IEC60870_5_TypeID typeId = CS101_ASDU_getTypeID(asdu);
|
||||
|
||||
/* check if type if is in range of file services */
|
||||
if ((typeId >= F_FR_NA_1) && (typeId <= F_SC_NB_1))
|
||||
{
|
||||
/* check for timeout */
|
||||
if (self->state != UNSELECTED_IDLE)
|
||||
{
|
||||
if (Hal_getMonotonicTimeInMs() > self->lastSendTime + self->timeout)
|
||||
{
|
||||
DEBUG_PRINT ("Abort file transfer due to timeout\n");
|
||||
|
||||
self->state = UNSELECTED_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
int oa = CS101_ASDU_getOA(asdu);
|
||||
|
||||
uint8_t ioBuf[250];
|
||||
|
||||
switch (typeId) {
|
||||
|
||||
case F_FR_NA_1: /* File Ready */
|
||||
|
||||
DEBUG_PRINT("Received file ready F_FR_NA_1\n");
|
||||
|
||||
if (self->fileReadyHandler)
|
||||
{
|
||||
FileReady fileReady = (FileReady) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
|
||||
|
||||
if (fileReady)
|
||||
{
|
||||
int ioa = InformationObject_getObjectAddress((InformationObject) fileReady);
|
||||
|
||||
self->fileReceiver = NULL;
|
||||
|
||||
int errCode = 0;
|
||||
|
||||
if (self->fileReadyHandler) {
|
||||
self->fileReceiver = self->fileReadyHandler(self->fileReadyHandlerParameter, CS101_ASDU_getCA(asdu), ioa, FileReady_getNOF(fileReady), FileReady_getLengthOfFile(fileReady), &errCode);
|
||||
}
|
||||
|
||||
if (self->fileReceiver)
|
||||
{
|
||||
self->ca = CS101_ASDU_getCA(asdu);
|
||||
self->ioa = ioa;
|
||||
self->oa = oa;
|
||||
self->nof = FileReady_getNOF(fileReady);
|
||||
self->fileChecksum = 0;
|
||||
|
||||
sendCallFile(self, connection, oa);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_SECTION_READY;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (errCode == 1)
|
||||
{
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
else if (errCode == 2)
|
||||
{
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->ca = CS101_ASDU_getCA(asdu);
|
||||
self->ioa = ioa;
|
||||
self->nof = FileReady_getNOF(fileReady);
|
||||
sendFileReady(self, connection, oa, 0, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
|
||||
}
|
||||
else
|
||||
{
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case F_SR_NA_1: /* Section Ready */
|
||||
|
||||
if (self->state == WAITING_FOR_SECTION_READY)
|
||||
{
|
||||
SectionReady sectionReady = (SectionReady) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
|
||||
|
||||
if (sectionReady)
|
||||
{
|
||||
self->currentSectionNumber = SectionReady_getNameOfSection(sectionReady);
|
||||
self->currentSectionOffset = 0;
|
||||
self->currentSectionSize = SectionReady_getLengthOfSection(sectionReady);
|
||||
|
||||
/* send call section */
|
||||
sendCallSection(self, connection, oa);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = RECEIVE_SECTION;
|
||||
}
|
||||
else
|
||||
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case F_SG_NA_1: /* Segment */
|
||||
|
||||
if (self->state == RECEIVE_SECTION)
|
||||
{
|
||||
FileSegment segment = (FileSegment) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
|
||||
|
||||
if (segment)
|
||||
{
|
||||
uint8_t nos = FileSegment_getNameOfSection(segment);
|
||||
uint8_t los = FileSegment_getLengthOfSegment(segment);
|
||||
|
||||
DEBUG_PRINT("Received F_SG_NA_1(segment) (NoS=%i, LoS=%i)\n", nos, los);
|
||||
|
||||
if (self->fileReceiver)
|
||||
{
|
||||
self->fileReceiver->segmentReceived(self->fileReceiver, nos, self->currentSectionOffset, los, FileSegment_getSegmentData(segment));
|
||||
}
|
||||
|
||||
self->currentSectionOffset += los;
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
}
|
||||
else
|
||||
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
|
||||
}
|
||||
else {
|
||||
DEBUG_PRINT ("Unexpected F_SG_NA_1(file segment)\n");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case F_LS_NA_1: /* Last Segment/Section */
|
||||
{
|
||||
DEBUG_PRINT ("Received F_LS_NA_1 (last segment/section)\n");
|
||||
|
||||
FileLastSegmentOrSection lastSection = (FileLastSegmentOrSection) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
|
||||
|
||||
if (lastSection)
|
||||
{
|
||||
uint8_t lsq = FileLastSegmentOrSection_getLSQ(lastSection);
|
||||
|
||||
if (self->state == RECEIVE_SECTION)
|
||||
{
|
||||
if (lsq == 3 /* SECTION_TRANSFER_WITHOUT_DEACT */)
|
||||
{
|
||||
DEBUG_PRINT("Send segment ACK for NoS=%i\n", FileLastSegmentOrSection_getNameOfSection(lastSection));
|
||||
|
||||
sendFileAck(self, connection, oa, FileLastSegmentOrSection_getNameOfSection(lastSection), 3 /* POS_ACK_SECTION */);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_SECTION_READY;
|
||||
|
||||
}
|
||||
else if (lsq == 2 /* FILE_TRANSFER_WITH_DEACT */)
|
||||
{
|
||||
/* master aborted transfer */
|
||||
|
||||
if (self->fileReceiver) {
|
||||
self->fileReceiver->finished(self->fileReceiver, CS101_FILE_ERROR_ABORTED_BY_REMOTE);
|
||||
}
|
||||
|
||||
self->state = UNSELECTED_IDLE;
|
||||
}
|
||||
else {
|
||||
DEBUG_PRINT("Unexpected LSQ\n");
|
||||
}
|
||||
|
||||
}
|
||||
else if (self->state == WAITING_FOR_SECTION_READY)
|
||||
{
|
||||
if (lsq == 1 /* FILE_TRANSFER_WITHOUT_DEACT */)
|
||||
{
|
||||
DEBUG_PRINT("Send file ACK\n");
|
||||
|
||||
sendFileAck(self, connection, oa, FileLastSegmentOrSection_getNameOfSection(lastSection), 1 /* POS_ACK_FILE */);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
|
||||
if (self->fileReceiver)
|
||||
{
|
||||
self->fileReceiver->finished(self->fileReceiver, CS101_FILE_ERROR_SUCCESS);
|
||||
}
|
||||
|
||||
self->state = UNSELECTED_IDLE;
|
||||
}
|
||||
else if (lsq == 2 /* FILE_TRANSFER_WITH_DEACT */)
|
||||
{
|
||||
/* master aborted transfer */
|
||||
|
||||
if (self->fileReceiver)
|
||||
{
|
||||
self->fileReceiver->finished(self->fileReceiver, CS101_FILE_ERROR_ABORTED_BY_REMOTE);
|
||||
}
|
||||
|
||||
self->state = UNSELECTED_IDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected LSQ\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case F_AF_NA_1: /* 124 - ACK file, ACK section */
|
||||
|
||||
DEBUG_PRINT("Received file/section ACK F_AF_NA_1\n");
|
||||
|
||||
if (self->state != UNSELECTED_IDLE)
|
||||
{
|
||||
FileACK ack = (FileACK) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
|
||||
|
||||
if (ack)
|
||||
{
|
||||
uint8_t afq = FileACK_getAFQ(ack);
|
||||
|
||||
if (afq == 1 /* POS_ACK_FILE */)
|
||||
{
|
||||
DEBUG_PRINT("Received positive file ACK\n");
|
||||
|
||||
if (self->state == WAITING_FOR_FILE_ACK)
|
||||
{
|
||||
if (self->selectedFile)
|
||||
self->selectedFile->transferComplete(self->selectedFile, true);
|
||||
|
||||
/* TODO remove file from list of available files */
|
||||
|
||||
self->selectedFile = NULL;
|
||||
self->selectedConnection = NULL;
|
||||
|
||||
self->state = UNSELECTED_IDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected file transfer state --> abort file transfer\n");
|
||||
|
||||
self->state = SEND_ABORT;
|
||||
}
|
||||
}
|
||||
else if ((afq & 0x0f) == 2 /* NEG_ACK_FILE */)
|
||||
{
|
||||
DEBUG_PRINT("Received negative file ACK - stop transfer\n");
|
||||
|
||||
if (self->state == WAITING_FOR_FILE_ACK)
|
||||
{
|
||||
if (self->selectedFile)
|
||||
self->selectedFile->transferComplete(self->selectedFile, false);
|
||||
|
||||
self->selectedFile = NULL;
|
||||
self->selectedConnection = NULL;
|
||||
|
||||
self->state = UNSELECTED_IDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected file transfer state --> abort file transfer\n");
|
||||
|
||||
self->state = SEND_ABORT;
|
||||
}
|
||||
}
|
||||
else if ((afq & 0x0f) == 4 /* NEG_ACK_SECTION */)
|
||||
{
|
||||
DEBUG_PRINT("Received negative file section ACK - repeat section\n");
|
||||
|
||||
if (self->state == WAITING_FOR_SECTION_ACK)
|
||||
{
|
||||
self->currentSectionOffset = 0;
|
||||
self->sectionChecksum = 0;
|
||||
|
||||
sendSectionReady(self, connection, oa);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = TRANSMIT_SECTION;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected file transfer state --> abort file transfer\n");
|
||||
|
||||
self->state = SEND_ABORT;
|
||||
}
|
||||
}
|
||||
else if ((afq & 0x0f) == 3 /* POS_ACK_SECTION */)
|
||||
{
|
||||
DEBUG_PRINT("Received positive section ACK\n");
|
||||
|
||||
if (self->state == WAITING_FOR_SECTION_ACK)
|
||||
{
|
||||
self->currentSectionNumber++;
|
||||
|
||||
int nextSectionSize = self->selectedFile->getSectionSize(self->selectedFile, self->currentSectionNumber - 1);
|
||||
|
||||
self->currentSectionOffset = 0;
|
||||
|
||||
if (nextSectionSize <= 0)
|
||||
{
|
||||
DEBUG_PRINT("Received positive file section ACK - send last section indication\n");
|
||||
|
||||
sendLastSection(self, connection, oa);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_FILE_ACK;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Received positive file section ACK - send next section ready indication");
|
||||
|
||||
self->currentSectionSize = nextSectionSize;
|
||||
|
||||
sendSectionReady(self, connection, oa);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_SECTION_CALL;
|
||||
}
|
||||
|
||||
self->sectionChecksum = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected file transfer state --> abort file transfer\n");
|
||||
|
||||
self->state = SEND_ABORT;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
|
||||
}
|
||||
else
|
||||
{
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case F_SC_NA_1: /* 122 - Call/Select directory/file/section */
|
||||
|
||||
DEBUG_PRINT("Received call/select F_SC_NA_1\n");
|
||||
|
||||
if (CS101_ASDU_getCOT(asdu) == CS101_COT_FILE_TRANSFER)
|
||||
{
|
||||
FileCallOrSelect sc = (FileCallOrSelect) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
|
||||
|
||||
if (sc)
|
||||
{
|
||||
uint8_t scq = FileCallOrSelect_getSCQ(sc);
|
||||
int ioa = InformationObject_getObjectAddress((InformationObject) sc);
|
||||
uint16_t nof = FileCallOrSelect_getNOF(sc);
|
||||
|
||||
if (scq == 1 /* SELECT_FILE */)
|
||||
{
|
||||
DEBUG_PRINT("Received SELECT FILE\n");
|
||||
|
||||
if (self->state == UNSELECTED_IDLE)
|
||||
{
|
||||
CS101_IFileProvider file = NULL;
|
||||
|
||||
int errCode = 0;
|
||||
|
||||
if (self->filesAvailable)
|
||||
file = self->filesAvailable->getFile(self->filesAvailable->parameter, CS101_ASDU_getCA(asdu), ioa, nof, &errCode);
|
||||
|
||||
if (file == NULL)
|
||||
{
|
||||
if (errCode == 1)
|
||||
{
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
else if (errCode == 2)
|
||||
{
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->ca = CS101_ASDU_getCA(asdu);
|
||||
self->ioa = ioa;
|
||||
self->nof = nof;
|
||||
sendFileReady(self, connection, oa, 0, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* TODO check if file is already selected by other client */
|
||||
|
||||
self->selectedFile = file;
|
||||
self->selectedConnection = connection;
|
||||
self->ioa = ioa;
|
||||
self->ca = CS101_ASDU_getCA(asdu);
|
||||
self->nof = nof;
|
||||
|
||||
int fileSize = file->getFileSize(file);
|
||||
|
||||
DEBUG_PRINT("Send FILE READY\n");
|
||||
|
||||
sendFileReady(self, connection, oa, fileSize, true);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_FILE_CALL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected select file message\n");
|
||||
}
|
||||
|
||||
}
|
||||
else if (scq == 3 /* DEACTIVATE_FILE */)
|
||||
{
|
||||
DEBUG_PRINT("Received DEACTIVATE FILE\n");
|
||||
|
||||
if (self->state == UNSELECTED_IDLE)
|
||||
{
|
||||
self->selectedFile = NULL;
|
||||
self->selectedConnection = NULL;
|
||||
|
||||
self->state = UNSELECTED_IDLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected DEACTIVATE FILE message\n");
|
||||
}
|
||||
}
|
||||
else if (scq == 2 /* REQUEST_FILE */)
|
||||
{
|
||||
DEBUG_PRINT("Received CALL FILE\n");
|
||||
|
||||
if (self->state == WAITING_FOR_FILE_CALL)
|
||||
{
|
||||
/* TODO check if NoF matches */
|
||||
|
||||
if ((ioa != self->ioa) || (CS101_ASDU_getCA(asdu) != self->ca))
|
||||
{
|
||||
DEBUG_PRINT("Call for file that is not selected!\n");
|
||||
|
||||
if (CS101_ASDU_getCA(asdu) != self->ca)
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
}
|
||||
else
|
||||
{
|
||||
self->currentSectionNumber = 1;
|
||||
self->currentSectionOffset = 0;
|
||||
self->fileChecksum = 0;
|
||||
self->currentSectionSize = self->selectedFile->getSectionSize(self->selectedFile, 0);
|
||||
|
||||
DEBUG_PRINT("Send SECTION READY\n");
|
||||
|
||||
sendSectionReady(self, connection, oa);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_SECTION_CALL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected CALL FILE message\n");
|
||||
}
|
||||
|
||||
}
|
||||
else if (scq == 6 /* REQUEST_SECTION */ )
|
||||
{
|
||||
uint8_t nos = FileCallOrSelect_getNameOfSection(sc);
|
||||
|
||||
DEBUG_PRINT("Received CALL SECTION (NoS = %i)\n", nos);
|
||||
|
||||
if (self->state == WAITING_FOR_SECTION_CALL)
|
||||
{
|
||||
/* TODO check if NoF matches */
|
||||
|
||||
if ((ioa != self->ioa) || (CS101_ASDU_getCA(asdu) != self->ca))
|
||||
{
|
||||
DEBUG_PRINT("Call for file that is not selected!\n");
|
||||
|
||||
if (CS101_ASDU_getCA(asdu) != self->ca)
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
|
||||
else
|
||||
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
|
||||
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CS101_ASDU_isNegative(asdu))
|
||||
{
|
||||
self->currentSectionNumber++;
|
||||
self->currentSectionOffset = 0;
|
||||
|
||||
self->currentSectionSize = self->selectedFile->getSectionSize(self->selectedFile, self->currentSectionNumber - 1);
|
||||
|
||||
if (self->currentSectionSize > 0)
|
||||
{
|
||||
DEBUG_PRINT("Send F_SR_NA_1 (section ready) (NoS = %i)\n", self->currentSectionNumber);
|
||||
|
||||
sendSectionReady(self, connection, oa);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_SECTION_CALL;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Send F_LS_NA_1 (last section))\n");
|
||||
|
||||
sendLastSection(self, connection, oa);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_FILE_ACK;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* positive */
|
||||
self->currentSectionSize = self->selectedFile->getSectionSize(self->selectedFile, nos - 1);
|
||||
|
||||
if (self->currentSectionSize > 0)
|
||||
{
|
||||
self->currentSectionNumber = nos;
|
||||
self->currentSectionOffset = 0;
|
||||
|
||||
self->state = TRANSMIT_SECTION;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected number of section -> send negative confirm\n");
|
||||
|
||||
CS101_ASDU_setNegative(asdu, true);
|
||||
|
||||
IMasterConnection_sendASDU(connection, asdu);
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Unexpected CALL SECTION message\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
|
||||
}
|
||||
else if (CS101_ASDU_getCOT(asdu) == CS101_COT_REQUEST)
|
||||
{
|
||||
/* call directory */
|
||||
|
||||
/* TODO send directory */
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_PRINT("Received unexpected COT = %i\n", CS101_ASDU_getCOT(asdu));
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
DEBUG_PRINT("Received unexpected type ID %i in file service\n", typeId);
|
||||
break;
|
||||
}
|
||||
|
||||
result = CS101_PLUGIN_RESULT_HANDLED;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
CS101_FileServer_runTask(void* parameter, IMasterConnection connection)
|
||||
{
|
||||
CS101_FileServer self = (CS101_FileServer) parameter;
|
||||
|
||||
if (self->state != UNSELECTED_IDLE)
|
||||
{
|
||||
if (self->state == TRANSMIT_SECTION)
|
||||
{
|
||||
if (self->selectedConnection == connection)
|
||||
{
|
||||
if (self->selectedFile)
|
||||
{
|
||||
if (sendSegment(self, connection, self->oa) == false)
|
||||
{
|
||||
sendLastSegment(self, connection, self->oa);
|
||||
|
||||
self->fileChecksum += self->sectionChecksum;
|
||||
self->sectionChecksum = 0;
|
||||
|
||||
self->lastSendTime = Hal_getMonotonicTimeInMs();
|
||||
self->state = WAITING_FOR_SECTION_ACK;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check for timeout */
|
||||
if (Hal_getMonotonicTimeInMs() > self->lastSendTime + self->timeout)
|
||||
{
|
||||
DEBUG_PRINT ("Abort file transfer due to timeout\n");
|
||||
|
||||
self->state = UNSELECTED_IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool (*handleAsdu) (void* parameter, IMasterConnection connection, CS101_ASDU asdu);
|
||||
void (*runTask) (void* parameter, IMasterConnection connection);
|
||||
|
||||
void* parameter;
|
||||
|
||||
CS101_FileServer
|
||||
CS101_FileServer_create(CS101_AppLayerParameters alParams)
|
||||
{
|
||||
CS101_FileServer self = (CS101_FileServer) GLOBAL_CALLOC(1, sizeof(struct sCS101_FileServer));
|
||||
|
||||
if (self)
|
||||
{
|
||||
self->timeout = 3000;
|
||||
self->state = UNSELECTED_IDLE;
|
||||
|
||||
self->maxSegmentSize = FileSegment_GetMaxDataSize(alParams);
|
||||
|
||||
self->plugin.parameter = self;
|
||||
self->plugin.handleAsdu = CS101_FileServer_handleAsdu;
|
||||
self->plugin.runTask = CS101_FileServer_runTask;
|
||||
self->plugin.sendAsdu = NULL;
|
||||
self->plugin.setEnqueueFunction = NULL;
|
||||
self->plugin.hasAsduToSend = NULL;
|
||||
self->plugin.getNextAsduToSend = NULL;
|
||||
self->plugin.setForwardAsduFunction = NULL;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
CS101_FileServer_destroy(CS101_FileServer self)
|
||||
{
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_FileServer_setFilesAvailableIfc(CS101_FileServer self, CS101_FilesAvailable ifc)
|
||||
{
|
||||
self->filesAvailable = ifc;
|
||||
}
|
||||
|
||||
void
|
||||
CS101_FileServer_setFileReadyHandler(CS101_FileServer self, CS101_FileReadyHandler handler, void* parameter)
|
||||
{
|
||||
self->fileReadyHandler = handler;
|
||||
self->fileReadyHandlerParameter = parameter;
|
||||
}
|
||||
|
||||
CS101_SlavePlugin
|
||||
CS101_FileServer_getSlavePlugin(CS101_FileServer self)
|
||||
{
|
||||
return &(self->plugin);
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
cmake_minimum_required(VERSION 3.5.1)
|
||||
|
||||
# automagically detect if we should cross-compile
|
||||
if(DEFINED ENV{TOOLCHAIN})
|
||||
set(CMAKE_C_COMPILER $ENV{TOOLCHAIN}gcc)
|
||||
set(CMAKE_CXX_COMPILER $ENV{TOOLCHAIN}g++)
|
||||
set(CMAKE_AR "$ENV{TOOLCHAIN}ar" CACHE FILEPATH "CW archiver" FORCE)
|
||||
endif()
|
||||
|
||||
project(hal)
|
||||
|
||||
set(LIBHAL_VERSION_MAJOR "2")
|
||||
set(LIBHAL_VERSION_MINOR "2")
|
||||
set(LIBHAL_VERSION_PATCH "0")
|
||||
|
||||
# feature checks
|
||||
include(CheckLibraryExists)
|
||||
|
||||
# check if we are on a little or a big endian
|
||||
include (TestBigEndian)
|
||||
test_big_endian(PLATFORM_IS_BIGENDIAN)
|
||||
|
||||
if(WIN32)
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib")
|
||||
message("Found winpcap -> compile ethernet HAL layer (required for GOOSE/SV support)")
|
||||
set(WITH_WPCAP 1)
|
||||
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Include")
|
||||
else()
|
||||
message("winpcap not found -> skip ethernet HAL layer (no L2 GOOSE/SV support)")
|
||||
endif()
|
||||
|
||||
endif(WIN32)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/inc
|
||||
)
|
||||
|
||||
set (libhal_linux_SRCS
|
||||
${CMAKE_CURRENT_LIST_DIR}/socket/linux/socket_linux.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/ethernet/linux/ethernet_linux.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/thread/linux/thread_linux.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/serial/linux/serial_port_linux.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c
|
||||
)
|
||||
|
||||
set (libhal_windows_SRCS
|
||||
${CMAKE_CURRENT_LIST_DIR}/socket/win32/socket_win32.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/thread/win32/thread_win32.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/filesystem/win32/file_provider_win32.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/time/win32/time.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/serial/win32/serial_port_win32.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c
|
||||
)
|
||||
|
||||
if(WITH_WPCAP)
|
||||
set (libhal_windows_SRCS ${libhal_windows_SRCS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ethernet/win32/ethernet_win32.c
|
||||
)
|
||||
endif(WITH_WPCAP)
|
||||
|
||||
set (libhal_bsd_SRCS
|
||||
${CMAKE_CURRENT_LIST_DIR}/socket/bsd/socket_bsd.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/ethernet/bsd/ethernet_bsd.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/thread/bsd/thread_bsd.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c
|
||||
)
|
||||
|
||||
set (libhal_macos_SRCS
|
||||
${CMAKE_CURRENT_LIST_DIR}/socket/bsd/socket_bsd.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/ethernet/bsd/ethernet_bsd.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/thread/macos/thread_macos.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c
|
||||
)
|
||||
|
||||
IF(WIN32)
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib")
|
||||
message("Found winpcap -> can compile with GOOSE support")
|
||||
set(WITH_WPCAP 1)
|
||||
endif()
|
||||
|
||||
set (libhal_SRCS
|
||||
${libhal_windows_SRCS}
|
||||
)
|
||||
|
||||
IF(MSVC)
|
||||
set_source_files_properties(${libhal_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF()
|
||||
|
||||
ELSEIF(UNIX)
|
||||
IF(APPLE)
|
||||
set (libhal_SRCS
|
||||
${libhal_macos_SRCS}
|
||||
)
|
||||
ELSEIF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
|
||||
set (libhal_SRCS
|
||||
${libhal_bsd_SRCS}
|
||||
)
|
||||
ELSE()
|
||||
set (libhal_SRCS
|
||||
${libhal_linux_SRCS}
|
||||
)
|
||||
ENDIF(APPLE)
|
||||
ENDIF(WIN32)
|
||||
|
||||
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" )
|
||||
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC" )
|
||||
|
||||
if(WITH_MBEDTLS)
|
||||
message("Found mbedtls 2.28 -> can compile HAL with TLS 1.2 support")
|
||||
set(WITH_MBEDTLS 1)
|
||||
endif(WITH_MBEDTLS)
|
||||
|
||||
if (WITH_MBEDTLS3)
|
||||
message("Found mbedtls 3.6 -> can compile HAL with TLS 1.3 support")
|
||||
set(WITH_MBEDTLS3 1)
|
||||
endif(WITH_MBEDTLS3)
|
||||
|
||||
if(WITH_MBEDTLS)
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls
|
||||
${MBEDTLS_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
if(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
|
||||
link_directories(${CONFIG_EXTERNAL_MBEDTLS_DYNLIB_PATH})
|
||||
else()
|
||||
file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.28/library/*.c)
|
||||
endif(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
|
||||
|
||||
add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h")
|
||||
|
||||
set (libhal_SRCS ${libhal_SRCS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls/tls_mbedtls.c
|
||||
)
|
||||
|
||||
IF(MSVC)
|
||||
set_source_files_properties(${libhal_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF()
|
||||
|
||||
list (APPEND libhal_SRCS ${tls_SRCS})
|
||||
|
||||
endif(WITH_MBEDTLS)
|
||||
|
||||
if(WITH_MBEDTLS3)
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls3
|
||||
${MBEDTLS_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
if(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
|
||||
link_directories(${CONFIG_EXTERNAL_MBEDTLS_DYNLIB_PATH})
|
||||
else()
|
||||
file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-3.6.0/library/*.c)
|
||||
endif(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
|
||||
|
||||
add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h")
|
||||
|
||||
set (libhal_SRCS ${libhal_SRCS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls3/tls_mbedtls.c
|
||||
)
|
||||
|
||||
IF(MSVC)
|
||||
set_source_files_properties(${libhal_SRCS}
|
||||
PROPERTIES LANGUAGE CXX)
|
||||
ENDIF()
|
||||
|
||||
list (APPEND libhal_SRCS ${tls_SRCS})
|
||||
endif(WITH_MBEDTLS3)
|
||||
|
||||
add_library (hal STATIC ${libhal_SRCS})
|
||||
|
||||
add_library (hal-shared STATIC ${libhal_SRCS})
|
||||
|
||||
target_compile_definitions(hal-shared PRIVATE EXPORT_FUNCTIONS_FOR_DLL)
|
||||
|
||||
SET_TARGET_PROPERTIES(hal-shared PROPERTIES
|
||||
COMPILE_FLAGS "-fPIC"
|
||||
)
|
||||
|
||||
IF(UNIX)
|
||||
target_link_libraries (hal
|
||||
-lpthread
|
||||
-lrt
|
||||
)
|
||||
ENDIF(UNIX)
|
||||
|
||||
IF(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
|
||||
target_link_libraries(hal mbedcrypto mbedx509 mbedtls)
|
||||
ENDIF(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
|
||||
|
||||
IF(MINGW)
|
||||
target_link_libraries(hal ws2_32 iphlpapi bcrypt)
|
||||
message("Building with MinGW")
|
||||
ENDIF(MINGW)
|
||||
|
||||
IF (MSVC)
|
||||
target_link_libraries(hal bcrypt)
|
||||
target_link_libraries(hal-shared bcrypt)
|
||||
ENDIF()
|
||||
|
||||
iF(WITH_WPCAP)
|
||||
target_link_libraries(hal
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/packet.lib
|
||||
)
|
||||
ENDIF(WITH_WPCAP)
|
||||
|
||||
set(BINDIR "bin")
|
||||
set(LIBDIR "lib")
|
||||
if(UNIX)
|
||||
# GNUInstallDirs is required for Debian multiarch
|
||||
include(GNUInstallDirs)
|
||||
set(LIBDIR ${CMAKE_INSTALL_LIBDIR})
|
||||
set(BINDIR ${CMAKE_INSTALL_BINDIR})
|
||||
endif()
|
||||
|
||||
install (TARGETS hal hal-shared
|
||||
RUNTIME DESTINATION ${BINDIR} COMPONENT Applications
|
||||
ARCHIVE DESTINATION ${LIBDIR} COMPONENT Libraries
|
||||
LIBRARY DESTINATION ${LIBDIR} COMPONENT Libraries
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* hal_base.h
|
||||
*
|
||||
* Copyright 2013-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#ifndef HAL_INC_HAL_BASE_H_
|
||||
#define HAL_INC_HAL_BASE_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define ATTRIBUTE_PACKED __attribute__ ((__packed__))
|
||||
#else
|
||||
#define ATTRIBUTE_PACKED
|
||||
#endif
|
||||
|
||||
#ifndef DEPRECATED
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define DEPRECATED __attribute__((deprecated))
|
||||
#else
|
||||
#define DEPRECATED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
#ifdef EXPORT_FUNCTIONS_FOR_DLL
|
||||
#define PAL_API __declspec(dllexport)
|
||||
#else
|
||||
#define PAL_API
|
||||
#endif
|
||||
|
||||
#define PAL_INTERNAL
|
||||
#else
|
||||
#if __GNUC__ >= 4
|
||||
#define PAL_API __attribute__ ((visibility ("default")))
|
||||
#define PAL_INTERNAL __attribute__ ((visibility ("hidden")))
|
||||
#else
|
||||
#define PAL_API
|
||||
#define PAL_INTERNAL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* HAL_INC_HAL_BASE_H_ */
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* hal_serial.h
|
||||
*
|
||||
* Copyright 2013-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#ifndef SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_
|
||||
#define SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_
|
||||
|
||||
#include "hal_base.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \file hal_serial.h
|
||||
* \brief Abstraction layer for serial ports.
|
||||
* Has to be implemented for the serial link layer of CS 101.
|
||||
*/
|
||||
|
||||
/*! \addtogroup hal Platform (Hardware/OS) abstraction layer
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup HAL_SERIAL Access to serial interfaces
|
||||
*
|
||||
* Serial interface abstraction layer. This functions have to be implemented to
|
||||
* port lib60870 to new platforms when the serial link layers are required.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
typedef struct sSerialPort* SerialPort;
|
||||
|
||||
typedef enum {
|
||||
SERIAL_PORT_ERROR_NONE = 0,
|
||||
SERIAL_PORT_ERROR_INVALID_ARGUMENT = 1,
|
||||
SERIAL_PORT_ERROR_INVALID_BAUDRATE = 2,
|
||||
SERIAL_PORT_ERROR_OPEN_FAILED = 3,
|
||||
SERIAL_PORT_ERROR_UNKNOWN = 99
|
||||
} SerialPortError;
|
||||
|
||||
/**
|
||||
* \brief Create a new SerialPort instance
|
||||
*
|
||||
* \param interfaceName identifier or name of the serial interface (e.g. "/dev/ttyS1" or "COM4")
|
||||
* \param baudRate the baud rate in baud (e.g. 9600)
|
||||
* \param dataBits the number of data bits (usually 8)
|
||||
* \param parity defines what kind of parity to use ('E' - even parity, 'O' - odd parity, 'N' - no parity)
|
||||
* \param stopBits the number of stop buts (usually 1)
|
||||
*
|
||||
* \return the new SerialPort instance
|
||||
*/
|
||||
PAL_API SerialPort
|
||||
SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits);
|
||||
|
||||
/**
|
||||
* \brief Destroy the SerialPort instance and release all resources
|
||||
*/
|
||||
PAL_API void
|
||||
SerialPort_destroy(SerialPort self);
|
||||
|
||||
/**
|
||||
* \brief Open the serial interface
|
||||
*
|
||||
* \return true in case of success, false otherwise (use \ref SerialPort_getLastError for a detailed error code)
|
||||
*/
|
||||
PAL_API bool
|
||||
SerialPort_open(SerialPort self);
|
||||
|
||||
/**
|
||||
* \brief Close (release) the serial interface
|
||||
*/
|
||||
PAL_API void
|
||||
SerialPort_close(SerialPort self);
|
||||
|
||||
/**
|
||||
* \brief Get the baudrate used by the serial interface
|
||||
*
|
||||
* \return the baud rate in baud
|
||||
*/
|
||||
PAL_API int
|
||||
SerialPort_getBaudRate(SerialPort self);
|
||||
|
||||
/**
|
||||
* \brief Set the timeout used for message reception
|
||||
*
|
||||
* \param timeout the timeout value in ms.
|
||||
*/
|
||||
PAL_API void
|
||||
SerialPort_setTimeout(SerialPort self, int timeout);
|
||||
|
||||
/**
|
||||
* \brief Discard all data in the input buffer of the serial interface
|
||||
*/
|
||||
PAL_API void
|
||||
SerialPort_discardInBuffer(SerialPort self);
|
||||
|
||||
/**
|
||||
* \brief Read a byte from the interface
|
||||
*
|
||||
* \return number of read bytes of -1 in case of an error
|
||||
*/
|
||||
PAL_API int
|
||||
SerialPort_readByte(SerialPort self);
|
||||
|
||||
/**
|
||||
* \brief Write the number of bytes from the buffer to the serial interface
|
||||
*
|
||||
* \param buffer the buffer containing the data to write
|
||||
* \param startPos start position in the buffer of the data to write
|
||||
* \param numberOfBytes number of bytes to write
|
||||
*
|
||||
* \return number of bytes written, or -1 in case of an error
|
||||
*/
|
||||
PAL_API int
|
||||
SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int numberOfBytes);
|
||||
|
||||
/**
|
||||
* \brief Get the error code of the last operation
|
||||
*/
|
||||
PAL_API SerialPortError
|
||||
SerialPort_getLastError(SerialPort self);
|
||||
|
||||
/*! @} */
|
||||
|
||||
/*! @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_ */
|
||||
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
* socket_hal.h
|
||||
*
|
||||
* Copyright 2013-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#ifndef SOCKET_HAL_H_
|
||||
#define SOCKET_HAL_H_
|
||||
|
||||
#include "hal_base.h"
|
||||
|
||||
/**
|
||||
* \file hal_socket.h
|
||||
* \brief Abstraction layer TCP/IP sockets
|
||||
* Has to be implemented for CS 104 TCP/IP.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*! \defgroup hal Platform (Hardware/OS) abstraction layer
|
||||
*
|
||||
* Platform abstraction layer. These functions have to be implemented when the library is
|
||||
* to be ported to new platforms. It might not be required to implement all interfaces
|
||||
* depending on the required library features.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup HAL_SOCKET Interface to the TCP/IP stack (abstract socket layer)
|
||||
*
|
||||
* Thread and Socket abstraction layer. This functions have to be implemented to
|
||||
* port lib60870 to a new hardware/OS platform when TCP/IP is required.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Opaque reference for a server socket instance */
|
||||
typedef struct sServerSocket* ServerSocket;
|
||||
|
||||
typedef struct sUdpSocket* UdpSocket;
|
||||
|
||||
/** Opaque reference for a client or connection socket instance */
|
||||
typedef struct sSocket* Socket;
|
||||
|
||||
/** Opaque reference for a set of server and socket handles */
|
||||
typedef struct sHandleSet* HandleSet;
|
||||
|
||||
/** State of an asynchronous connect */
|
||||
typedef enum
|
||||
{
|
||||
SOCKET_STATE_CONNECTING = 0,
|
||||
SOCKET_STATE_FAILED = 1,
|
||||
SOCKET_STATE_CONNECTED = 2
|
||||
} SocketState;
|
||||
|
||||
|
||||
/**
|
||||
* \brief Create a new connection handle set (HandleSet)
|
||||
*
|
||||
* \return new HandleSet instance
|
||||
*/
|
||||
PAL_API HandleSet
|
||||
Handleset_new(void);
|
||||
|
||||
/**
|
||||
* \brief Reset the handle set for reuse
|
||||
*/
|
||||
PAL_API void
|
||||
Handleset_reset(HandleSet self);
|
||||
|
||||
/**
|
||||
* \brief add a socket to an existing handle set
|
||||
*
|
||||
* \param self the HandleSet instance
|
||||
* \param sock the socket to add
|
||||
*/
|
||||
PAL_API void
|
||||
Handleset_addSocket(HandleSet self, const Socket sock);
|
||||
|
||||
/**
|
||||
* \brief remove a socket from an existing handle set
|
||||
*/
|
||||
void
|
||||
Handleset_removeSocket(HandleSet self, const Socket sock);
|
||||
|
||||
/**
|
||||
* \brief wait for a socket to become ready
|
||||
*
|
||||
* This function is corresponding to the BSD socket select function.
|
||||
* It returns the number of sockets on which data is pending or 0 if no data is pending
|
||||
* on any of the monitored connections. The function will return after "timeout" ms if no
|
||||
* data is pending.
|
||||
* The function shall return -1 if a socket error occures.
|
||||
*
|
||||
* \param self the HandleSet instance
|
||||
* \param timeout in milliseconds (ms)
|
||||
* \return It returns the number of sockets on which data is pending
|
||||
* or 0 if no data is pending on any of the monitored connections.
|
||||
* The function shall return -1 if a socket error occures.
|
||||
*/
|
||||
PAL_API int
|
||||
Handleset_waitReady(HandleSet self, unsigned int timeoutMs);
|
||||
|
||||
/**
|
||||
* \brief destroy the HandleSet instance
|
||||
*
|
||||
* \param self the HandleSet instance to destroy
|
||||
*/
|
||||
PAL_API void
|
||||
Handleset_destroy(HandleSet self);
|
||||
|
||||
/**
|
||||
* \brief Create a new TcpServerSocket instance
|
||||
*
|
||||
* Implementation of this function is MANDATORY if server functionality is required.
|
||||
*
|
||||
* \param address ip address or hostname to listen on
|
||||
* \param port the TCP port to listen on
|
||||
*
|
||||
* \return the newly create TcpServerSocket instance
|
||||
*/
|
||||
PAL_API ServerSocket
|
||||
TcpServerSocket_create(const char* address, int port);
|
||||
|
||||
/**
|
||||
* \brief Create an IPv4 UDP socket instance
|
||||
*
|
||||
* \return new UDP socket instance
|
||||
*/
|
||||
PAL_API UdpSocket
|
||||
UdpSocket_create(void);
|
||||
|
||||
/**
|
||||
* \brief Create an IPv6 UDP socket instance
|
||||
*
|
||||
* \return new UDP socket instance
|
||||
*/
|
||||
PAL_API UdpSocket
|
||||
UdpSocket_createIpV6(void);
|
||||
|
||||
/**
|
||||
* \brief Add the socket to an IPv4 or IPv6 multicast group
|
||||
*
|
||||
* \param self UDP socket instance
|
||||
* \param multicastAddress IPv4 or IPv6 multicast address
|
||||
*
|
||||
* \return true on success, false otherwise
|
||||
*/
|
||||
PAL_API bool
|
||||
UdpSocket_addGroupMembership(UdpSocket self, const char* multicastAddress);
|
||||
|
||||
/**
|
||||
* \brief Sets the multicast TTL (number of hops) for this UDP socket
|
||||
*
|
||||
* \param self UDP socket instance
|
||||
* \param ttl number of hops for multicast messages. Default is 1 (not routable!)
|
||||
*
|
||||
* \return true on success, false otherwise
|
||||
*/
|
||||
PAL_API bool
|
||||
UdpSocket_setMulticastTtl(UdpSocket self, int ttl);
|
||||
|
||||
PAL_API bool
|
||||
UdpSocket_bind(UdpSocket self, const char* address, int port);
|
||||
|
||||
PAL_API bool
|
||||
UdpSocket_sendTo(UdpSocket self, const char* address, int port, uint8_t* msg, int msgSize);
|
||||
|
||||
/**
|
||||
* \brief Receive data from UDP socket (store data and (optionally) the IP address of the sender
|
||||
*
|
||||
* \param self UDP socket instance
|
||||
* \param address (optional) buffer to store the IP address as string
|
||||
* \param maxAddrSize (optional) size of the provided buffer to store the IP address
|
||||
* \param msg buffer to store the UDP message data
|
||||
* \param msgSize the maximum size of the message to receive
|
||||
*
|
||||
* \return number of received bytes or -1 in case of an error
|
||||
*/
|
||||
PAL_API int
|
||||
UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize);
|
||||
|
||||
|
||||
PAL_API void
|
||||
ServerSocket_listen(ServerSocket self);
|
||||
|
||||
/**
|
||||
* \brief accept a new incoming connection (non-blocking)
|
||||
*
|
||||
* This function shall accept a new incoming connection. It is non-blocking and has to
|
||||
* return NULL if no new connection is pending.
|
||||
*
|
||||
* Implementation of this function is MANDATORY if server functionality is required.
|
||||
*
|
||||
* NOTE: The behaviour of this function changed with version 0.8!
|
||||
*
|
||||
* \param self server socket instance
|
||||
*
|
||||
* \return handle of the new connection socket or NULL if no new connection is available
|
||||
*/
|
||||
PAL_API Socket
|
||||
ServerSocket_accept(ServerSocket self);
|
||||
|
||||
/**
|
||||
* \brief active TCP keep alive for socket and set keep alive parameters
|
||||
*
|
||||
* NOTE: implementation is mandatory for IEC 61850 MMS
|
||||
*
|
||||
* \param self server socket instance
|
||||
* \param idleTime time (in s) between last received message and first keep alive message
|
||||
* \param interval time (in s) between subsequent keep alive messages if no ACK received
|
||||
* \param count number of not missing keep alive ACKs until socket is considered dead
|
||||
*/
|
||||
PAL_API void
|
||||
Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count);
|
||||
|
||||
/**
|
||||
* \brief set the maximum number of pending connections in the queue
|
||||
*
|
||||
* Implementation of this function is OPTIONAL.
|
||||
*
|
||||
* \param self the server socket instance
|
||||
* \param backlog the number of pending connections in the queue
|
||||
*
|
||||
*/
|
||||
PAL_API void
|
||||
ServerSocket_setBacklog(ServerSocket self, int backlog);
|
||||
|
||||
/**
|
||||
* \brief destroy a server socket instance
|
||||
*
|
||||
* Free all resources allocated by this server socket instance.
|
||||
*
|
||||
* Implementation of this function is MANDATORY if server functionality is required.
|
||||
*
|
||||
* \param self server socket instance
|
||||
*/
|
||||
PAL_API void
|
||||
ServerSocket_destroy(ServerSocket self);
|
||||
|
||||
/**
|
||||
* \brief create a TCP client socket
|
||||
*
|
||||
* Implementation of this function is MANDATORY if client functionality is required.
|
||||
*
|
||||
* \return a new client socket instance.
|
||||
*/
|
||||
PAL_API Socket
|
||||
TcpSocket_create(void);
|
||||
|
||||
/**
|
||||
* \brief set the timeout to establish a new connection
|
||||
*
|
||||
* \param self the client socket instance
|
||||
* \param timeoutInMs the timeout in ms
|
||||
*/
|
||||
PAL_API void
|
||||
Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs);
|
||||
|
||||
/**
|
||||
* \brief bind a socket to a particular IP address and port (for TcpSocket)
|
||||
*
|
||||
* NOTE: Don't use the socket when this functions returns false!
|
||||
*
|
||||
* \param self the client socket instance
|
||||
* \param srcAddress the local IP address or hostname as C string
|
||||
* \param srcPort the local TCP port to use. When < 1 the OS will chose the TCP port to use.
|
||||
*
|
||||
* \return true in case of success, false otherwise
|
||||
*/
|
||||
PAL_API bool
|
||||
Socket_bind(Socket self, const char* srcAddress, int srcPort);
|
||||
|
||||
/**
|
||||
* \brief connect to a server
|
||||
*
|
||||
* Connect to a server application identified by the address and port parameter.
|
||||
*
|
||||
* The "address" parameter may either be a hostname or an IP address. The IP address
|
||||
* has to be provided as a C string (e.g. "10.0.2.1").
|
||||
*
|
||||
* Implementation of this function is MANDATORY if client functionality is required.
|
||||
*
|
||||
* NOTE: return type changed from int to bool with version 0.8
|
||||
*
|
||||
* \param self the client socket instance
|
||||
* \param address the IP address or hostname as C string
|
||||
* \param port the TCP port of the application to connect to
|
||||
*
|
||||
* \return true if the connection was established successfully, false otherwise
|
||||
*/
|
||||
PAL_API bool
|
||||
Socket_connect(Socket self, const char* address, int port);
|
||||
|
||||
PAL_API bool
|
||||
Socket_connectAsync(Socket self, const char* address, int port);
|
||||
|
||||
PAL_API SocketState
|
||||
Socket_checkAsyncConnectState(Socket self);
|
||||
|
||||
/**
|
||||
* \brief read from socket to local buffer (non-blocking)
|
||||
*
|
||||
* The function shall return immediately if no data is available. In this case
|
||||
* the function returns 0. If an error happens the function shall return -1.
|
||||
*
|
||||
* Implementation of this function is MANDATORY
|
||||
*
|
||||
* NOTE: The behaviour of this function changed with version 0.8!
|
||||
*
|
||||
* \param self the client, connection or server socket instance
|
||||
* \param buf the buffer where the read bytes are copied to
|
||||
* \param size the maximum number of bytes to read (size of the provided buffer)
|
||||
*
|
||||
* \return the number of bytes read or -1 if an error occurred
|
||||
*/
|
||||
PAL_API int
|
||||
Socket_read(Socket self, uint8_t* buf, int size);
|
||||
|
||||
/**
|
||||
* \brief send a message through the socket
|
||||
*
|
||||
* Implementation of this function is MANDATORY
|
||||
*
|
||||
* \param self client, connection or server socket instance
|
||||
*
|
||||
* \return number of bytes transmitted of -1 in case of an error
|
||||
*/
|
||||
PAL_API int
|
||||
Socket_write(Socket self, uint8_t* buf, int size);
|
||||
|
||||
PAL_API char*
|
||||
Socket_getLocalAddress(Socket self);
|
||||
|
||||
/**
|
||||
* \brief Get the address of the peer application (IP address and port number)
|
||||
*
|
||||
* The peer address has to be returned as null terminated string
|
||||
*
|
||||
* Implementation of this function is MANDATORY (libiec61850)
|
||||
*
|
||||
* \param self the client, connection or server socket instance
|
||||
*
|
||||
* \return the IP address and port number as strings separated by the ':' character.
|
||||
*/
|
||||
PAL_API char*
|
||||
Socket_getPeerAddress(Socket self);
|
||||
|
||||
/**
|
||||
* \brief Get the address of the peer application (IP address and port number)
|
||||
*
|
||||
* The peer address has to be returned as null terminated string
|
||||
*
|
||||
* Implementation of this function is MANDATORY (lib60870 and libiec61850)
|
||||
*
|
||||
* \param self the client, connection or server socket instance
|
||||
* \param peerAddressString a string to store the peer address (the string should have space
|
||||
* for at least 60 characters)
|
||||
*
|
||||
* \return the IP address and port number as strings separated by the ':' character. If the
|
||||
* address is an IPv6 address the IP part is encapsulated in square brackets.
|
||||
*/
|
||||
PAL_API char*
|
||||
Socket_getPeerAddressStatic(Socket self, char* peerAddressString);
|
||||
|
||||
/**
|
||||
* \brief destroy a socket (close the socket if a connection is established)
|
||||
*
|
||||
* This function shall close the connection (if one is established) and free all
|
||||
* resources allocated by the socket.
|
||||
*
|
||||
* Implementation of this function is MANDATORY
|
||||
*
|
||||
* \param self the client, connection or server socket instance
|
||||
*/
|
||||
PAL_API void
|
||||
Socket_destroy(Socket self);
|
||||
|
||||
/*! @} */
|
||||
|
||||
/*! @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SOCKET_HAL_H_ */
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* thread_hal.h
|
||||
*
|
||||
* Multi-threading abstraction layer
|
||||
*
|
||||
* Copyright 2013-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#ifndef THREAD_HAL_H_
|
||||
#define THREAD_HAL_H_
|
||||
|
||||
#include "hal_base.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \file hal_thread.h
|
||||
* \brief Abstraction layer for threading and synchronization
|
||||
*/
|
||||
|
||||
/*! \addtogroup hal
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup HAL_THREAD Threading and synchronization API
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Opaque reference of a Thread instance */
|
||||
typedef struct sThread* Thread;
|
||||
|
||||
/** Qpaque reference of a Semaphore instance */
|
||||
typedef void* Semaphore;
|
||||
|
||||
/** Reference to a function that is called when starting the thread */
|
||||
typedef void* (*ThreadExecutionFunction) (void*);
|
||||
|
||||
/**
|
||||
* \brief Create a new Thread instance
|
||||
*
|
||||
* \param function the entry point of the thread
|
||||
* \param parameter a parameter that is passed to the threads start function
|
||||
* \param autodestroy the thread is automatically destroyed if the ThreadExecutionFunction has finished.
|
||||
*
|
||||
* \return the newly created Thread instance
|
||||
*/
|
||||
PAL_API Thread
|
||||
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy);
|
||||
|
||||
/**
|
||||
* \brief Start a Thread.
|
||||
*
|
||||
* This function invokes the start function of the thread. The thread terminates when
|
||||
* the start function returns.
|
||||
*
|
||||
* \param thread the Thread instance to start
|
||||
*/
|
||||
PAL_API void
|
||||
Thread_start(Thread thread);
|
||||
|
||||
/**
|
||||
* \brief Destroy a Thread and free all related resources.
|
||||
*
|
||||
* \param thread the Thread instance to destroy
|
||||
*/
|
||||
PAL_API void
|
||||
Thread_destroy(Thread thread);
|
||||
|
||||
/**
|
||||
* \brief Suspend execution of the Thread for the specified number of milliseconds
|
||||
*/
|
||||
PAL_API void
|
||||
Thread_sleep(int millies);
|
||||
|
||||
PAL_API Semaphore
|
||||
Semaphore_create(int initialValue);
|
||||
|
||||
/* Wait until semaphore value is greater than zero. Then decrease the semaphore value. */
|
||||
PAL_API void
|
||||
Semaphore_wait(Semaphore self);
|
||||
|
||||
PAL_API void
|
||||
Semaphore_post(Semaphore self);
|
||||
|
||||
PAL_API void
|
||||
Semaphore_destroy(Semaphore self);
|
||||
|
||||
/*! @} */
|
||||
|
||||
/*! @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* THREAD_HAL_H_ */
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* time.c
|
||||
*
|
||||
* Copyright 2013-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#ifndef HAL_C_
|
||||
#define HAL_C_
|
||||
|
||||
#include "hal_base.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \file hal_time.h
|
||||
* \brief Abstraction layer for system time access
|
||||
*/
|
||||
|
||||
/*! \addtogroup hal
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup HAL_TIME Time related functions
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
typedef uint64_t nsSinceEpoch;
|
||||
typedef uint64_t msSinceEpoch;
|
||||
|
||||
/**
|
||||
* Get the system time in milliseconds.
|
||||
*
|
||||
* The time value returned as 64-bit unsigned integer should represent the milliseconds
|
||||
* since the UNIX epoch (1970/01/01 00:00 UTC).
|
||||
*
|
||||
* \return the system time with millisecond resolution.
|
||||
*/
|
||||
PAL_API msSinceEpoch
|
||||
Hal_getTimeInMs(void);
|
||||
|
||||
/**
|
||||
* Get the system time in nanoseconds.
|
||||
*
|
||||
* The time value returned as 64-bit unsigned integer should represent the nanoseconds
|
||||
* since the UNIX epoch (1970/01/01 00:00 UTC).
|
||||
*
|
||||
* \return the system time with nanosecond resolution.
|
||||
*/
|
||||
PAL_API nsSinceEpoch
|
||||
Hal_getTimeInNs(void);
|
||||
|
||||
/**
|
||||
* Set the system time from ns time
|
||||
*
|
||||
* The time value returned as 64-bit unsigned integer should represent the nanoseconds
|
||||
* since the UNIX epoch (1970/01/01 00:00 UTC).
|
||||
*
|
||||
* \return true on success, otherwise false
|
||||
*/
|
||||
PAL_API bool
|
||||
Hal_setTimeInNs(nsSinceEpoch nsTime);
|
||||
|
||||
/**
|
||||
* Get the monotonic time or system tick time in ms
|
||||
*
|
||||
* \return the system time with millisecond resolution.
|
||||
*/
|
||||
PAL_API msSinceEpoch
|
||||
Hal_getMonotonicTimeInMs(void);
|
||||
|
||||
/**
|
||||
* Get the monotonic time or system tick in nanoseconds.
|
||||
*
|
||||
* \return the system time with nanosecond resolution.
|
||||
*/
|
||||
PAL_API nsSinceEpoch
|
||||
Hal_getMonotonicTimeInNs(void);
|
||||
|
||||
/*! @} */
|
||||
|
||||
/*! @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* HAL_C_ */
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* lib_memory.h
|
||||
*
|
||||
* Copyright 2014-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#ifndef MEMORY_H_
|
||||
#define MEMORY_H_
|
||||
|
||||
#include "hal_base.h"
|
||||
|
||||
#define CALLOC(nmemb, size) Memory_calloc(nmemb, size)
|
||||
#define MALLOC(size) Memory_malloc(size)
|
||||
#define REALLOC(oldptr, size) Memory_realloc(oldptr, size)
|
||||
#define FREEMEM(ptr) Memory_free(ptr)
|
||||
|
||||
#define GLOBAL_CALLOC(nmemb, size) Memory_calloc(nmemb, size)
|
||||
#define GLOBAL_MALLOC(size) Memory_malloc(size)
|
||||
#define GLOBAL_REALLOC(oldptr, size) Memory_realloc(oldptr, size)
|
||||
#define GLOBAL_FREEMEM(ptr) Memory_free(ptr)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void
|
||||
(*MemoryExceptionHandler) (void* parameter);
|
||||
|
||||
PAL_API void
|
||||
Memory_installExceptionHandler(MemoryExceptionHandler handler, void* parameter);
|
||||
|
||||
PAL_API void*
|
||||
Memory_malloc(size_t size);
|
||||
|
||||
PAL_API void*
|
||||
Memory_calloc(size_t nmemb, size_t size);
|
||||
|
||||
PAL_API void *
|
||||
Memory_realloc(void *ptr, size_t size);
|
||||
|
||||
PAL_API void
|
||||
Memory_free(void* memb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* MEMORY_H_ */
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* platform_endian.h
|
||||
*
|
||||
* Copyright 2013-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#ifndef ENDIAN_H_
|
||||
#define ENDIAN_H_
|
||||
|
||||
#ifndef PLATFORM_IS_BIGENDIAN
|
||||
#ifdef __GNUC__
|
||||
#ifdef __BYTE_ORDER__
|
||||
#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
||||
#define PLATFORM_IS_BIGENDIAN 1
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
/* older versions of GCC have __BYTE_ORDER__ not defined! */
|
||||
#ifdef __PPC__
|
||||
#define PLATFORM_IS_BIGENDIAN 1
|
||||
#endif
|
||||
|
||||
#endif /* __BYTE_ORDER__ */
|
||||
#endif /* __GNUC__ */
|
||||
#endif
|
||||
|
||||
#if (PLATFORM_IS_BIGENDIAN == 1)
|
||||
# define ORDER_LITTLE_ENDIAN 0
|
||||
#else
|
||||
# define ORDER_LITTLE_ENDIAN 1
|
||||
#endif
|
||||
|
||||
#endif /* ENDIAN_H_ */
|
||||
@@ -0,0 +1,263 @@
|
||||
#ifndef IANA_TLS_CIPHER_SUITES_H_
|
||||
#define IANA_TLS_CIPHER_SUITES_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define TLS_NULL_WITH_NULL_NULL 0x0000
|
||||
#define TLS_RSA_WITH_NULL_MD5 0x0001
|
||||
#define TLS_RSA_WITH_NULL_SHA 0x0002
|
||||
#define TLS_RSA_EXPORT_WITH_RC4_40_MD5 0x0003
|
||||
#define TLS_RSA_WITH_RC4_128_MD5 0x0004
|
||||
#define TLS_RSA_WITH_RC4_128_SHA 0x0005
|
||||
#define TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 0x0006
|
||||
#define TLS_RSA_WITH_IDEA_CBC_SHA 0x0007
|
||||
#define TLS_RSA_EXPORT_WITH_DES40_CBC_SHA 0x0008
|
||||
#define TLS_RSA_WITH_DES_CBC_SHA 0x0009
|
||||
#define TLS_RSA_WITH_3DES_EDE_CBC_SHA 0x000A
|
||||
#define TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA 0x000B
|
||||
#define TLS_DH_DSS_WITH_DES_CBC_SHA 0x000C
|
||||
#define TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA 0x000D
|
||||
#define TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA 0x000E
|
||||
#define TLS_DH_RSA_WITH_DES_CBC_SHA 0x000F
|
||||
#define TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA 0x0010
|
||||
#define TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA 0x0011
|
||||
#define TLS_DHE_DSS_WITH_DES_CBC_SHA 0x0012
|
||||
#define TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA 0x0013
|
||||
#define TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA 0x0014
|
||||
#define TLS_DHE_RSA_WITH_DES_CBC_SHA 0x0015
|
||||
#define TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA 0x0016
|
||||
#define TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 0x0017
|
||||
#define TLS_DH_anon_WITH_RC4_128_MD5 0x0018
|
||||
#define TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA 0x0019
|
||||
#define TLS_DH_anon_WITH_DES_CBC_SHA 0x001A
|
||||
#define TLS_DH_anon_WITH_3DES_EDE_CBC_SHA 0x001B
|
||||
#define TLS_RSA_WITH_AES_128_CBC_SHA 0x002F
|
||||
#define TLS_DH_DSS_WITH_AES_128_CBC_SHA 0x0030
|
||||
#define TLS_DH_RSA_WITH_AES_128_CBC_SHA 0x0031
|
||||
#define TLS_DHE_DSS_WITH_AES_128_CBC_SHA 0x0032
|
||||
#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA 0x0033
|
||||
#define TLS_DH_anon_WITH_AES_128_CBC_SHA 0x0034
|
||||
#define TLS_RSA_WITH_AES_256_CBC_SHA 0x0035
|
||||
#define TLS_DH_DSS_WITH_AES_256_CBC_SHA 0x0036
|
||||
#define TLS_DH_RSA_WITH_AES_256_CBC_SHA 0x0037
|
||||
#define TLS_DHE_DSS_WITH_AES_256_CBC_SHA 0x0038
|
||||
#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA 0x0039
|
||||
#define TLS_DH_anon_WITH_AES_256_CBC_SHA 0x003A
|
||||
#define TLS_RSA_WITH_NULL_SHA256 0x003B
|
||||
#define TLS_RSA_WITH_AES_128_CBC_SHA256 0x003C
|
||||
#define TLS_RSA_WITH_AES_256_CBC_SHA256 0x003D
|
||||
#define TLS_DH_DSS_WITH_AES_128_CBC_SHA256 0x003E
|
||||
#define TLS_DH_RSA_WITH_AES_128_CBC_SHA256 0x003F
|
||||
#define TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 0x0040
|
||||
#define TLS_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0041
|
||||
#define TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA 0x0042
|
||||
#define TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0043
|
||||
#define TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA 0x0044
|
||||
#define TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0045
|
||||
#define TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA 0x0046
|
||||
#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 0x0067
|
||||
#define TLS_DH_DSS_WITH_AES_256_CBC_SHA256 0x0068
|
||||
#define TLS_DH_RSA_WITH_AES_256_CBC_SHA256 0x0069
|
||||
#define TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 0x006A
|
||||
#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 0x006B
|
||||
#define TLS_DH_anon_WITH_AES_128_CBC_SHA256 0x006C
|
||||
#define TLS_DH_anon_WITH_AES_256_CBC_SHA256 0x006D
|
||||
#define TLS_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0084
|
||||
#define TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA 0x0085
|
||||
#define TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0086
|
||||
#define TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA 0x0087
|
||||
#define TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0088
|
||||
#define TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA 0x0089
|
||||
#define TLS_RSA_WITH_SEED_CBC_SHA 0x0096
|
||||
#define TLS_DH_DSS_WITH_SEED_CBC_SHA 0x0097
|
||||
#define TLS_DH_RSA_WITH_SEED_CBC_SHA 0x0098
|
||||
#define TLS_DHE_DSS_WITH_SEED_CBC_SHA 0x0099
|
||||
#define TLS_DHE_RSA_WITH_SEED_CBC_SHA 0x009A
|
||||
#define TLS_DH_anon_WITH_SEED_CBC_SHA 0x009B
|
||||
#define TLS_RSA_WITH_AES_128_GCM_SHA256 0x009C
|
||||
#define TLS_RSA_WITH_AES_256_GCM_SHA384 0x009D
|
||||
#define TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 0x009E
|
||||
#define TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 0x009F
|
||||
#define TLS_DH_RSA_WITH_AES_128_GCM_SHA256 0x00A0
|
||||
#define TLS_DH_RSA_WITH_AES_256_GCM_SHA384 0x00A1
|
||||
#define TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 0x00A2
|
||||
#define TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 0x00A3
|
||||
#define TLS_DH_DSS_WITH_AES_128_GCM_SHA256 0x00A4
|
||||
#define TLS_DH_DSS_WITH_AES_256_GCM_SHA384 0x00A5
|
||||
#define TLS_DH_anon_WITH_AES_128_GCM_SHA256 0x00A6
|
||||
#define TLS_DH_anon_WITH_AES_256_GCM_SHA384 0x00A7
|
||||
#define TLS_PSK_WITH_AES_128_CBC_SHA 0x008C
|
||||
#define TLS_PSK_WITH_AES_256_CBC_SHA 0x008D
|
||||
#define TLS_DHE_PSK_WITH_AES_128_CBC_SHA 0x008E
|
||||
#define TLS_DHE_PSK_WITH_AES_256_CBC_SHA 0x008F
|
||||
#define TLS_RSA_PSK_WITH_AES_128_CBC_SHA 0x0090
|
||||
#define TLS_RSA_PSK_WITH_AES_256_CBC_SHA 0x0091
|
||||
#define TLS_PSK_WITH_AES_256_GCM_SHA384 0x00A9
|
||||
#define TLS_PSK_WITH_AES_128_CBC_SHA256 0x00AE
|
||||
#define TLS_PSK_WITH_AES_256_CBC_SHA384 0x00AF
|
||||
#define TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 0x00B0
|
||||
#define TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 0x00B1
|
||||
#define TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 0x00B2
|
||||
#define TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 0x00B3
|
||||
#define TLS_PSK_WITH_NULL_SHA 0x002C
|
||||
#define TLS_DHE_PSK_WITH_NULL_SHA 0x002D
|
||||
#define TLS_RSA_PSK_WITH_NULL_SHA 0x002E
|
||||
#define TLS_PSK_WITH_NULL_SHA256 0x00B4
|
||||
#define TLS_PSK_WITH_NULL_SHA384 0x00B5
|
||||
#define TLS_DHE_PSK_WITH_NULL_SHA256 0x00B6
|
||||
#define TLS_DHE_PSK_WITH_NULL_SHA384 0x00B7
|
||||
#define TLS_RSA_PSK_WITH_NULL_SHA256 0x00B8
|
||||
#define TLS_RSA_PSK_WITH_NULL_SHA384 0x00B9
|
||||
#define TLS_ECDH_ECDSA_WITH_NULL_SHA 0xC001
|
||||
#define TLS_ECDH_ECDSA_WITH_RC4_128_SHA 0xC002
|
||||
#define TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC003
|
||||
#define TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA 0xC004
|
||||
#define TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA 0xC005
|
||||
#define TLS_ECDHE_ECDSA_WITH_NULL_SHA 0xC006
|
||||
#define TLS_ECDHE_ECDSA_WITH_RC4_128_SHA 0xC007
|
||||
#define TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC008
|
||||
#define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 0xC009
|
||||
#define TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 0xC00A
|
||||
#define TLS_ECDH_RSA_WITH_NULL_SHA 0xC00B
|
||||
#define TLS_ECDH_RSA_WITH_RC4_128_SHA 0xC00C
|
||||
#define TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA 0xC00D
|
||||
#define TLS_ECDH_RSA_WITH_AES_128_CBC_SHA 0xC00E
|
||||
#define TLS_ECDH_RSA_WITH_AES_256_CBC_SHA 0xC00F
|
||||
#define TLS_ECDHE_RSA_WITH_NULL_SHA 0xC010
|
||||
#define TLS_ECDHE_RSA_WITH_RC4_128_SHA 0xC011
|
||||
#define TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 0xC012
|
||||
#define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 0xC013
|
||||
#define TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 0xC014
|
||||
#define TLS_ECDH_anon_WITH_NULL_SHA 0xC015
|
||||
#define TLS_ECDH_anon_WITH_RC4_128_SHA 0xC016
|
||||
#define TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA 0xC017
|
||||
#define TLS_ECDH_anon_WITH_AES_128_CBC_SHA 0xC018
|
||||
#define TLS_ECDH_anon_WITH_AES_256_CBC_SHA 0xC019
|
||||
#define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 0xC023
|
||||
#define TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 0xC024
|
||||
#define TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 0xC025
|
||||
#define TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 0xC026
|
||||
#define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 0xC027
|
||||
#define TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 0xC028
|
||||
#define TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 0xC029
|
||||
#define TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 0xC02A
|
||||
#define TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0xC02B
|
||||
#define TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 0xC02C
|
||||
#define TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 0xC02D
|
||||
#define TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 0xC02E
|
||||
#define TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC02F
|
||||
#define TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 0xC030
|
||||
#define TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 0xC031
|
||||
#define TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 0xC032
|
||||
#define TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA 0xC035
|
||||
#define TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA 0xC036
|
||||
#define TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 0xC037
|
||||
#define TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 0xC038
|
||||
#define TLS_ECDHE_PSK_WITH_NULL_SHA 0xC039
|
||||
#define TLS_ECDHE_PSK_WITH_NULL_SHA256 0xC03A
|
||||
#define TLS_ECDHE_PSK_WITH_NULL_SHA384 0xC03B
|
||||
#define TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 0xC072
|
||||
#define TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 0xC073
|
||||
#define TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 0xC074
|
||||
#define TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 0xC075
|
||||
#define TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 0xC076
|
||||
#define TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 0xC077
|
||||
#define TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 0xC078
|
||||
#define TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 0xC079
|
||||
#define TLS_RSA_WITH_ARIA_128_CBC_SHA256 0xC03C
|
||||
#define TLS_RSA_WITH_ARIA_256_CBC_SHA384 0xC03D
|
||||
#define TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 0xC03E
|
||||
#define TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 0xC03F
|
||||
#define TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 0xC040
|
||||
#define TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 0xC041
|
||||
#define TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 0xC042
|
||||
#define TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 0xC043
|
||||
#define TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 0xC044
|
||||
#define TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 0xC045
|
||||
#define TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 0xC046
|
||||
#define TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 0xC047
|
||||
#define TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 0xC048
|
||||
#define TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 0xC049
|
||||
#define TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 0xC04A
|
||||
#define TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 0xC04B
|
||||
#define TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 0xC04C
|
||||
#define TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 0xC04D
|
||||
#define TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 0xC04E
|
||||
#define TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 0xC04F
|
||||
#define TLS_RSA_WITH_ARIA_128_GCM_SHA256 0xC050
|
||||
#define TLS_RSA_WITH_ARIA_256_GCM_SHA384 0xC051
|
||||
#define TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 0xC052
|
||||
#define TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 0xC053
|
||||
#define TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 0xC054
|
||||
#define TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 0xC055
|
||||
#define TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 0xC056
|
||||
#define TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 0xC057
|
||||
#define TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 0xC058
|
||||
#define TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 0xC059
|
||||
#define TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 0xC05A
|
||||
#define TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 0xC05B
|
||||
#define TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 0xC05C
|
||||
#define TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 0xC05D
|
||||
#define TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 0xC05E
|
||||
#define TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 0xC05F
|
||||
#define TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 0xC060
|
||||
#define TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 0xC061
|
||||
#define TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 0xC062
|
||||
#define TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 0xC063
|
||||
#define TLS_PSK_WITH_ARIA_128_CBC_SHA256 0xC064
|
||||
#define TLS_PSK_WITH_ARIA_256_CBC_SHA384 0xC065
|
||||
#define TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 0xC066
|
||||
#define TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 0xC067
|
||||
#define TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 0xC068
|
||||
#define TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 0xC069
|
||||
#define TLS_PSK_WITH_ARIA_128_GCM_SHA256 0xC06A
|
||||
#define TLS_PSK_WITH_ARIA_256_GCM_SHA384 0xC06B
|
||||
#define TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 0xC06C
|
||||
#define TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 0xC06D
|
||||
#define TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 0xC06E
|
||||
#define TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 0xC06F
|
||||
#define TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 0xC070
|
||||
#define TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 0xC071
|
||||
#define TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 0xC076
|
||||
#define TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 0xC077
|
||||
#define TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 0xC078
|
||||
#define TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 0xC079
|
||||
#define TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 0xC07A
|
||||
#define TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 0xC07B
|
||||
#define TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 0xC07C
|
||||
#define TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 0xC07D
|
||||
#define TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 0xC07E
|
||||
#define TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 0xC07F
|
||||
#define TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 0xC080
|
||||
#define TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 0xC081
|
||||
#define TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 0xC082
|
||||
#define TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 0xC083
|
||||
#define TLS_ECDHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 0xC084
|
||||
#define TLS_ECDHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 0xC085
|
||||
#define TLS_RSA_WITH_AES_128_CCM 0xC09C
|
||||
#define TLS_RSA_WITH_AES_256_CCM 0xC09D
|
||||
#define TLS_DHE_RSA_WITH_AES_128_CCM 0xC09E
|
||||
#define TLS_DHE_RSA_WITH_AES_256_CCM 0xC09F
|
||||
#define TLS_RSA_WITH_AES_128_CCM_8 0xC0A0
|
||||
#define TLS_RSA_WITH_AES_256_CCM_8 0xC0A1
|
||||
#define TLS_DHE_RSA_WITH_AES_128_CCM_8 0xC0A2
|
||||
#define TLS_DHE_RSA_WITH_AES_256_CCM_8 0xC0A3
|
||||
#define TLS_PSK_WITH_AES_128_CCM 0xC0A4
|
||||
#define TLS_PSK_WITH_AES_256_CCM 0xC0A5
|
||||
#define TLS_DHE_PSK_WITH_AES_128_CCM 0xC0A6
|
||||
#define TLS_DHE_PSK_WITH_AES_256_CCM 0xC0A7
|
||||
#define TLS_PSK_WITH_AES_128_CCM_8 0xC0A8
|
||||
#define TLS_PSK_WITH_AES_256_CCM_8 0xC0A9
|
||||
#define TLS_PSK_DHE_WITH_AES_128_CCM_8 0xC0AA
|
||||
#define TLS_PSK_DHE_WITH_AES_256_CCM_8 0xC0AB
|
||||
#define TLS_ECDHE_ECDSA_WITH_AES_128_CCM 0xC0AC
|
||||
|
||||
// ... add more cipher suite codes here ...
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* IANA_TLS_CIPHER_SUITES_H_ */
|
||||
@@ -0,0 +1,382 @@
|
||||
/*
|
||||
* tls_config.h
|
||||
*
|
||||
* TLS Configuration API for protocol stacks using TCP/IP
|
||||
*
|
||||
* Copyright 2017-2024 Michael Zillgith
|
||||
*
|
||||
* Abstraction layer for configuration of different TLS implementations
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRC_TLS_CONFIG_H_
|
||||
#define SRC_TLS_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "hal_base.h"
|
||||
#include "tls_ciphers.h"
|
||||
|
||||
/**
|
||||
* \file tls_config.h
|
||||
* \brief TLS API functions
|
||||
*/
|
||||
|
||||
/*! \addtogroup hal Platform (Hardware/OS) abstraction layer
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup TLS_CONFIG_API TLS configuration
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
typedef struct sTLSConfiguration* TLSConfiguration;
|
||||
|
||||
/**
|
||||
* \brief Create a new \ref TLSConfiguration object to represent TLS configuration and certificates
|
||||
*
|
||||
* WARNING: Configuration cannot be changed after using for the first time.
|
||||
*
|
||||
* \return the new TLS configuration
|
||||
*/
|
||||
PAL_API TLSConfiguration
|
||||
TLSConfiguration_create(void);
|
||||
|
||||
/* will be called by stack automatically when appropriate */
|
||||
PAL_API void
|
||||
TLSConfiguration_setClientMode(TLSConfiguration self);
|
||||
|
||||
typedef enum {
|
||||
TLS_VERSION_NOT_SELECTED = 0,
|
||||
TLS_VERSION_SSL_3_0 = 3,
|
||||
TLS_VERSION_TLS_1_0 = 4,
|
||||
TLS_VERSION_TLS_1_1 = 5,
|
||||
TLS_VERSION_TLS_1_2 = 6,
|
||||
TLS_VERSION_TLS_1_3 = 7
|
||||
} TLSConfigVersion;
|
||||
|
||||
/**
|
||||
* \brief Convert TLS version number to string
|
||||
*
|
||||
* \param version TLS version number
|
||||
*
|
||||
* \return the TLS version as null terminated string
|
||||
*/
|
||||
PAL_API const char*
|
||||
TLSConfigVersion_toString(TLSConfigVersion version);
|
||||
|
||||
typedef enum {
|
||||
TLS_SEC_EVT_INFO = 0,
|
||||
TLS_SEC_EVT_WARNING = 1,
|
||||
TLS_SEC_EVT_INCIDENT = 2
|
||||
} TLSEventLevel;
|
||||
|
||||
#define TLS_EVENT_CODE_ALM_ALGO_NOT_SUPPORTED 1
|
||||
#define TLS_EVENT_CODE_ALM_UNSECURE_COMMUNICATION 2
|
||||
#define TLS_EVENT_CODE_ALM_CERT_UNAVAILABLE 3
|
||||
#define TLS_EVENT_CODE_ALM_BAD_CERT 4
|
||||
#define TLS_EVENT_CODE_ALM_CERT_SIZE_EXCEEDED 5
|
||||
#define TLS_EVENT_CODE_ALM_CERT_VALIDATION_FAILED 6
|
||||
#define TLS_EVENT_CODE_ALM_CERT_REQUIRED 7
|
||||
#define TLS_EVENT_CODE_ALM_HANDSHAKE_FAILED_UNKNOWN_REASON 8
|
||||
#define TLS_EVENT_CODE_WRN_INSECURE_TLS_VERSION 9
|
||||
#define TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION 10
|
||||
#define TLS_EVENT_CODE_ALM_CERT_EXPIRED 11
|
||||
#define TLS_EVENT_CODE_ALM_CERT_REVOKED 12
|
||||
#define TLS_EVENT_CODE_ALM_CERT_NOT_CONFIGURED 13
|
||||
#define TLS_EVENT_CODE_ALM_CERT_NOT_TRUSTED 14
|
||||
#define TLS_EVENT_CODE_ALM_NO_CIPHER 15
|
||||
#define TLS_EVENT_CODE_INF_SESSION_ESTABLISHED 16
|
||||
#define TLS_EVENT_CODE_WRN_CERT_EXPIRED 17
|
||||
#define TLS_EVENT_CODE_WRN_CERT_NOT_YET_VALID 18
|
||||
#define TLS_EVENT_CODE_WRN_CRL_EXPIRED 19
|
||||
#define TLS_EVENT_CODE_WRN_CRL_NOT_YET_VALID 20
|
||||
#define TLS_EVENT_CODE_ALM_TLS_VERSION_CHANGE 21
|
||||
#define TLS_EVENT_CODE_WRN_MIN_KEY_LENGTH 22
|
||||
#define TLS_EVENT_CODE_ALM_INSUFFICIENT_KEY_LENGTH 23
|
||||
#define TLS_EVENT_CODE_WRN_CRL_NOT_ACCESSIBLE 24
|
||||
#define TLS_EVENT_CODE_ALM_CA_CERT_NOT_AVAILABLE 25
|
||||
#define TLS_EVENT_CODE_ALM_RENEGOTIATION_TIMEOUT 26
|
||||
#define TLS_EVENT_CODE_INF_SESSION_RESUMED 27
|
||||
#define TLS_EVENT_CODE_INF_SESSION_EXPIRED 28
|
||||
|
||||
typedef struct sTLSConnection* TLSConnection;
|
||||
|
||||
/**
|
||||
* \brief Get the peer address of the TLS connection
|
||||
*
|
||||
* \param self the TLS connection instance
|
||||
* \param peerAddrBuf user provided buffer that can hold at least 60 characters, or NULL to allow the function to allocate the memory for the buffer
|
||||
*
|
||||
* \returns peer address:port as null terminated string
|
||||
*/
|
||||
PAL_API char*
|
||||
TLSConnection_getPeerAddress(TLSConnection self, char* peerAddrBuf);
|
||||
|
||||
/**
|
||||
* \brief Get the TLS certificate used by the peer
|
||||
*
|
||||
* \param self the TLS connection instance
|
||||
* \param certSize[OUT] the certificate size in bytes
|
||||
*
|
||||
* \return address of the certificate buffer
|
||||
*/
|
||||
PAL_API uint8_t*
|
||||
TLSConnection_getPeerCertificate(TLSConnection self, int* certSize);
|
||||
|
||||
/**
|
||||
* \brief Get the TLS version used by the connection
|
||||
*
|
||||
* \param self the TLS connection instance
|
||||
*
|
||||
* \return TLS version
|
||||
*/
|
||||
PAL_API TLSConfigVersion
|
||||
TLSConnection_getTLSVersion(TLSConnection self);
|
||||
|
||||
typedef void (*TLSConfiguration_EventHandler)(void* parameter, TLSEventLevel eventLevel, int eventCode, const char* message, TLSConnection con);
|
||||
|
||||
/**
|
||||
* \brief Set the security event handler
|
||||
*
|
||||
* \param handler the security event callback handler
|
||||
* \param parameter user provided parameter to be passed to the callback handler
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setEventHandler(TLSConfiguration self, TLSConfiguration_EventHandler handler, void* parameter);
|
||||
|
||||
/**
|
||||
* \brief enable or disable TLS session resumption (default: enabled)
|
||||
*
|
||||
* NOTE: Depending on the used TLS version this is implemented by
|
||||
* session IDs or by session tickets.
|
||||
*
|
||||
* \param enable true to enable session resumption, false otherwise
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_enableSessionResumption(TLSConfiguration self, bool enable);
|
||||
|
||||
/**
|
||||
* \brief Set the maximum life time of a cached TLS session for session resumption in seconds
|
||||
*
|
||||
* \param intervalInSeconds the maximum lifetime of a cached TLS session
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setSessionResumptionInterval(TLSConfiguration self, int intervalInSeconds);
|
||||
|
||||
/**
|
||||
* \brief Enables the validation of the certificate trust chain (enabled by default)
|
||||
*
|
||||
* \param value true to enable chain validation, false to disable
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setChainValidation(TLSConfiguration self, bool value);
|
||||
|
||||
/**
|
||||
* \brief Enabled or disables the verification of validity times for certificates and CRLs
|
||||
*
|
||||
* \param value true to enable time validation, false to disable (enabled by default)
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setTimeValidation(TLSConfiguration self, bool value);
|
||||
|
||||
/**
|
||||
* \brief Set if only known certificates are accepted.
|
||||
*
|
||||
* If set to true only known certificates are accepted. Connections with unknown certificates
|
||||
* are rejected even if they are signed by a trusted authority.
|
||||
*
|
||||
* \param value true to enable setting, false otherwise
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setAllowOnlyKnownCertificates(TLSConfiguration self, bool value);
|
||||
|
||||
/**
|
||||
* \brief Set own certificate (identity) from a byte buffer
|
||||
*
|
||||
* \param certificate the certificate buffer
|
||||
* \param certLen the lenght of the certificate
|
||||
*
|
||||
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_setOwnCertificate(TLSConfiguration self, uint8_t* certificate, int certLen);
|
||||
|
||||
/**
|
||||
* \brief Set own certificate (identity) from a certificate file
|
||||
*
|
||||
* \param filename of the certificate file
|
||||
*
|
||||
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_setOwnCertificateFromFile(TLSConfiguration self, const char* filename);
|
||||
|
||||
/**
|
||||
* \brief Set the own private key from a byte buffer
|
||||
*
|
||||
* \param key the private key to use
|
||||
* \param keyLen the length of the key
|
||||
* \param password the password of the key or null if the key is not password protected
|
||||
*
|
||||
* \return true, when the key was set, false otherwise (e.g. unknown key format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_setOwnKey(TLSConfiguration self, uint8_t* key, int keyLen, const char* keyPassword);
|
||||
|
||||
/**
|
||||
* \brief Set the own private key from a key file
|
||||
*
|
||||
* \param filename filename/path of the key file
|
||||
* \param password the password of the key or null if the key is not password protected
|
||||
*
|
||||
* \return true, when the key was set, false otherwise (e.g. unknown key format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_setOwnKeyFromFile(TLSConfiguration self, const char* filename, const char* keyPassword);
|
||||
|
||||
/**
|
||||
* Add a certificate to the list of allowed peer certificates from a byte buffer
|
||||
*
|
||||
* \param certificate the certificate buffer
|
||||
* \param certLen the length of the certificate buffer
|
||||
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_addAllowedCertificate(TLSConfiguration self, uint8_t* certificate, int certLen);
|
||||
|
||||
/**
|
||||
* \brief Add a certificate to the list of allowed peer certificates
|
||||
*
|
||||
* \param filename filename of the certificate file
|
||||
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_addAllowedCertificateFromFile(TLSConfiguration self, const char* filename);
|
||||
|
||||
/**
|
||||
* \brief Add a CA certificate used to validate peer certificates from a byte buffer
|
||||
*
|
||||
* \param certificate the certificate buffer
|
||||
* \param certLen the length of the certificate buffer
|
||||
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_addCACertificate(TLSConfiguration self, uint8_t* certificate, int certLen);
|
||||
|
||||
/**
|
||||
* \brief Add a CA certificate used to validate peer certificates from a file
|
||||
*
|
||||
* \param filename filename of the certificate file
|
||||
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_addCACertificateFromFile(TLSConfiguration self, const char* filename);
|
||||
|
||||
/**
|
||||
* \brief Set the renegotiation timeout.
|
||||
*
|
||||
* After the timeout elapsed a TLS session renegotiation has to occur.
|
||||
*
|
||||
* \param timeInMs session renegotiation timeout in milliseconds
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setRenegotiationTime(TLSConfiguration self, int timeInMs);
|
||||
|
||||
/**
|
||||
* \brief Set the maximum time allowed for TLS session renegotiation.
|
||||
*
|
||||
* When the renegotiation handshake does not complete within the configured
|
||||
* timeout a security event is generated and the renegotiation attempt is
|
||||
* aborted. Provide a value \<= 0 to disable the timeout enforcement.
|
||||
* The default is 10000 milliseconds.
|
||||
*
|
||||
* \param timeoutInMs timeout in milliseconds
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setRenegotiationTimeout(TLSConfiguration self, int timeoutInMs);
|
||||
|
||||
/**
|
||||
* \brief Set minimal allowed TLS version to use
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setMinTlsVersion(TLSConfiguration self, TLSConfigVersion version);
|
||||
|
||||
/**
|
||||
* \brief Set maximal allowed TLS version to use
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setMaxTlsVersion(TLSConfiguration self, TLSConfigVersion version);
|
||||
|
||||
/**
|
||||
* \brief Set the minimum allowed public key length in bits (smaller keys will cause certificate validation to fail)
|
||||
*
|
||||
* \param keyLengthInBits minimum key length in bits
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_setMinimumKeyLength(TLSConfiguration self, int keyLengthInBits);
|
||||
|
||||
/**
|
||||
* \brief Add a CRL (certificate revocation list) from buffer
|
||||
*
|
||||
* \param crl the buffer containing the CRL
|
||||
* \param crlLen the length of the CRL buffer
|
||||
* \return true, when the CRL was imported, false otherwise (e.g. unknown format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_addCRL(TLSConfiguration self, uint8_t* crl, int crlLen);
|
||||
|
||||
/**
|
||||
* \brief Add a CRL (certificate revocation list) from a file
|
||||
*
|
||||
* \param filename filename of the CRL file
|
||||
* \return true, when the CRL was imported, false otherwise (e.g. unknown format)
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSConfiguration_addCRLFromFile(TLSConfiguration self, const char* filename);
|
||||
|
||||
/**
|
||||
* \brief Removes any CRL (certificate revocation list) currently in use
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_resetCRL(TLSConfiguration self);
|
||||
|
||||
/**
|
||||
* \brief Add an allowed ciphersuite to the list of allowed ciphersuites
|
||||
*
|
||||
* \param self the TLS configuration instance
|
||||
* \param ciphersuite the ciphersuite to add (IANA cipher suite ID)
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_addCipherSuite(TLSConfiguration self, int ciphersuite);
|
||||
|
||||
/**
|
||||
* \brief Clear the list of allowed ciphersuites
|
||||
*
|
||||
* \param self the TLS configuration instance
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_clearCipherSuiteList(TLSConfiguration self);
|
||||
|
||||
/**
|
||||
* Release all resource allocated by the TLSConfiguration instance
|
||||
*
|
||||
* NOTE: Do not use the object after calling this function!
|
||||
*/
|
||||
PAL_API void
|
||||
TLSConfiguration_destroy(TLSConfiguration self);
|
||||
|
||||
/** @} */
|
||||
|
||||
/** @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SRC_TLS_CONFIG_H_ */
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* tls_socket.h
|
||||
*
|
||||
* TLS socket API for protocol libraries using TCP/IP
|
||||
*
|
||||
* Copyright 2017-2021 Michael Zillgith, MZ Automation GmbH
|
||||
*
|
||||
* Abstraction layer for different TLS implementations
|
||||
*
|
||||
* The implementation has to connect the TLS API layer with the socket API layer
|
||||
* and perform all TLS tasks like handshake, encryption/decryption.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SRC_TLS_SOCKET_API_H_
|
||||
#define SRC_TLS_SOCKET_API_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \file tls_socket.h
|
||||
* \brief Abstraction layer for different TLS implementations.
|
||||
*
|
||||
* The implementation has to connect the TLS API layer with the socket API layer
|
||||
* and perform all TLS tasks like handshake, encryption/decryption.
|
||||
*/
|
||||
|
||||
/*! \addtogroup hal Platform (Hardware/OS) abstraction layer
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup HAL_TLS_SOCKET Abstraction layer for different TLS implementations.
|
||||
*
|
||||
* The implementation has to connect the TLS API layer with the socket API layer
|
||||
* and perform all TLS tasks like handshake, encryption/decryption.
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "tls_config.h"
|
||||
#include "hal_socket.h"
|
||||
|
||||
typedef struct sTLSSocket* TLSSocket;
|
||||
|
||||
/**
|
||||
* \brief This function create a new TLSSocket instance using the given Socket instance
|
||||
*
|
||||
* NOTE: This function also has to perform the TLS handshake
|
||||
*
|
||||
* \param socket the socket instance to use for the TLS connection
|
||||
* \param configuration the TLS configuration object to use
|
||||
* \param storeClientCert if true, the client certificate will be stored
|
||||
* for later access by \ref TLSSocket_getPeerCertificate
|
||||
*
|
||||
* \return new TLS connection instance
|
||||
*/
|
||||
PAL_API TLSSocket
|
||||
TLSSocket_create(Socket socket, TLSConfiguration configuration, bool storeClientCert);
|
||||
|
||||
/**
|
||||
* \brief Perform a new TLS handshake/session renegotiation
|
||||
*/
|
||||
PAL_API bool
|
||||
TLSSocket_performHandshake(TLSSocket self);
|
||||
|
||||
/**
|
||||
* \brief Access the certificate used by the peer
|
||||
*
|
||||
* \param[out] certSize the size of the certificate in bytes
|
||||
*
|
||||
* \return the certificate byte buffer
|
||||
*/
|
||||
PAL_API uint8_t*
|
||||
TLSSocket_getPeerCertificate(TLSSocket self, int* certSize);
|
||||
|
||||
/**
|
||||
* \brief read from socket to local buffer (non-blocking)
|
||||
*
|
||||
* The function shall return immediately if no data is available. In this case
|
||||
* the function returns 0. If an error happens the function shall return -1.
|
||||
*
|
||||
* \param self the client, connection or server socket instance
|
||||
* \param buf the buffer where the read bytes are copied to
|
||||
* \param size the maximum number of bytes to read (size of the provided buffer)
|
||||
*
|
||||
* \return the number of bytes read or -1 if an error occurred
|
||||
*/
|
||||
PAL_API int
|
||||
TLSSocket_read(TLSSocket self, uint8_t* buf, int size);
|
||||
|
||||
/**
|
||||
* \brief send a message through the socket
|
||||
*
|
||||
* Implementation of this function is MANDATORY
|
||||
*
|
||||
* \param self client, connection or server socket instance
|
||||
*
|
||||
* \return number of bytes transmitted of -1 in case of an error
|
||||
*/
|
||||
PAL_API int
|
||||
TLSSocket_write(TLSSocket self, uint8_t* buf, int size);
|
||||
|
||||
/**
|
||||
* \brief Closes the TLS connection and released all resources
|
||||
*/
|
||||
PAL_API void
|
||||
TLSSocket_close(TLSSocket self);
|
||||
|
||||
/*! @} */
|
||||
|
||||
/*! @} */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SRC_TLS_SOCKET_API_H_ */
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* lib_memory.c
|
||||
*
|
||||
* Copyright 2014-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "lib_memory.h"
|
||||
|
||||
static MemoryExceptionHandler exceptionHandler = NULL;
|
||||
static void* exceptionHandlerParameter = NULL;
|
||||
|
||||
static void
|
||||
noMemoryAvailableHandler(void)
|
||||
{
|
||||
if (exceptionHandler != NULL)
|
||||
exceptionHandler(exceptionHandlerParameter);
|
||||
}
|
||||
|
||||
void
|
||||
Memory_installExceptionHandler(MemoryExceptionHandler handler, void* parameter)
|
||||
{
|
||||
exceptionHandler = handler;
|
||||
exceptionHandlerParameter = parameter;
|
||||
}
|
||||
|
||||
void*
|
||||
Memory_malloc(size_t size)
|
||||
{
|
||||
void* memory = malloc(size);
|
||||
|
||||
if (memory == NULL)
|
||||
noMemoryAvailableHandler();
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
void*
|
||||
Memory_calloc(size_t nmemb, size_t size)
|
||||
{
|
||||
void* memory = calloc(nmemb, size);
|
||||
|
||||
if (memory == NULL)
|
||||
noMemoryAvailableHandler();
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
void *
|
||||
Memory_realloc(void *ptr, size_t size)
|
||||
{
|
||||
void* memory = realloc(ptr, size);
|
||||
|
||||
if (memory == NULL)
|
||||
noMemoryAvailableHandler();
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
void
|
||||
Memory_free(void* memb)
|
||||
{
|
||||
free(memb);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* serial_port_linux.c
|
||||
*
|
||||
* Copyright 2013-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#include "lib_memory.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
#include "hal_serial.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
struct sSerialPort {
|
||||
char interfaceName[100];
|
||||
int fd;
|
||||
int baudRate;
|
||||
uint8_t dataBits;
|
||||
char parity;
|
||||
uint8_t stopBits;
|
||||
uint64_t lastSentTime;
|
||||
struct timeval timeout;
|
||||
SerialPortError lastError;
|
||||
};
|
||||
|
||||
SerialPort
|
||||
SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits)
|
||||
{
|
||||
SerialPort self = (SerialPort) GLOBAL_MALLOC(sizeof(struct sSerialPort));
|
||||
|
||||
if (self != NULL) {
|
||||
self->fd = -1;
|
||||
self->baudRate = baudRate;
|
||||
self->dataBits = dataBits;
|
||||
self->stopBits = stopBits;
|
||||
self->parity = parity;
|
||||
self->lastSentTime = 0;
|
||||
self->timeout.tv_sec = 0;
|
||||
self->timeout.tv_usec = 100000; /* 100 ms */
|
||||
strncpy(self->interfaceName, interfaceName, 99);
|
||||
self->lastError = SERIAL_PORT_ERROR_NONE;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
SerialPort_destroy(SerialPort self)
|
||||
{
|
||||
if (self != NULL) {
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SerialPort_open(SerialPort self)
|
||||
{
|
||||
self->fd = open(self->interfaceName, O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL);
|
||||
|
||||
if (self->fd == -1) {
|
||||
self->lastError = SERIAL_PORT_ERROR_OPEN_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
struct termios tios;
|
||||
speed_t baudrate;
|
||||
|
||||
tcgetattr(self->fd, &tios);
|
||||
|
||||
switch (self->baudRate) {
|
||||
case 110:
|
||||
baudrate = B110;
|
||||
break;
|
||||
case 300:
|
||||
baudrate = B300;
|
||||
break;
|
||||
case 600:
|
||||
baudrate = B600;
|
||||
break;
|
||||
case 1200:
|
||||
baudrate = B1200;
|
||||
break;
|
||||
case 2400:
|
||||
baudrate = B2400;
|
||||
break;
|
||||
case 4800:
|
||||
baudrate = B4800;
|
||||
break;
|
||||
case 9600:
|
||||
baudrate = B9600;
|
||||
break;
|
||||
case 19200:
|
||||
baudrate = B19200;
|
||||
break;
|
||||
case 38400:
|
||||
baudrate = B38400;
|
||||
break;
|
||||
case 57600:
|
||||
baudrate = B57600;
|
||||
break;
|
||||
case 115200:
|
||||
baudrate = B115200;
|
||||
break;
|
||||
default:
|
||||
baudrate = B9600;
|
||||
self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE;
|
||||
}
|
||||
|
||||
/* Set baud rate */
|
||||
if ((cfsetispeed(&tios, baudrate) < 0) || (cfsetospeed(&tios, baudrate) < 0)) {
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE;
|
||||
return false;
|
||||
}
|
||||
|
||||
tios.c_cflag |= (CREAD | CLOCAL);
|
||||
|
||||
/* Set data bits (5/6/7/8) */
|
||||
tios.c_cflag &= ~CSIZE;
|
||||
switch (self->dataBits) {
|
||||
case 5:
|
||||
tios.c_cflag |= CS5;
|
||||
break;
|
||||
case 6:
|
||||
tios.c_cflag |= CS6;
|
||||
break;
|
||||
case 7:
|
||||
tios.c_cflag |= CS7;
|
||||
break;
|
||||
case 8:
|
||||
default:
|
||||
tios.c_cflag |= CS8;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Set stop bits (1/2) */
|
||||
if (self->stopBits == 1)
|
||||
tios.c_cflag &=~ CSTOPB;
|
||||
else /* 2 */
|
||||
tios.c_cflag |= CSTOPB;
|
||||
|
||||
if (self->parity == 'N') {
|
||||
tios.c_cflag &=~ PARENB;
|
||||
} else if (self->parity == 'E') {
|
||||
tios.c_cflag |= PARENB;
|
||||
tios.c_cflag &=~ PARODD;
|
||||
} else { /* 'O' */
|
||||
tios.c_cflag |= PARENB;
|
||||
tios.c_cflag |= PARODD;
|
||||
}
|
||||
|
||||
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
|
||||
|
||||
if (self->parity == 'N') {
|
||||
tios.c_iflag &= ~INPCK;
|
||||
} else {
|
||||
tios.c_iflag |= INPCK;
|
||||
}
|
||||
|
||||
tios.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL);
|
||||
tios.c_iflag |= IGNBRK; /* Set ignore break to allow 0xff characters */
|
||||
tios.c_iflag |= IGNPAR;
|
||||
tios.c_oflag &=~ OPOST;
|
||||
|
||||
tios.c_cc[VMIN] = 0;
|
||||
tios.c_cc[VTIME] = 0;
|
||||
|
||||
if (tcsetattr(self->fd, TCSANOW, &tios) < 0) {
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
self->lastError = SERIAL_PORT_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SerialPort_close(SerialPort self)
|
||||
{
|
||||
if (self->fd != -1) {
|
||||
close(self->fd);
|
||||
self->fd = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
SerialPort_getBaudRate(SerialPort self)
|
||||
{
|
||||
return self->baudRate;
|
||||
}
|
||||
|
||||
void
|
||||
SerialPort_discardInBuffer(SerialPort self)
|
||||
{
|
||||
tcflush(self->fd, TCIOFLUSH);
|
||||
}
|
||||
|
||||
void
|
||||
SerialPort_setTimeout(SerialPort self, int timeout)
|
||||
{
|
||||
self->timeout.tv_sec = timeout / 1000;
|
||||
self->timeout.tv_usec = (timeout % 1000) * 1000;
|
||||
}
|
||||
|
||||
SerialPortError
|
||||
SerialPort_getLastError(SerialPort self)
|
||||
{
|
||||
return self->lastError;
|
||||
}
|
||||
|
||||
int
|
||||
SerialPort_readByte(SerialPort self)
|
||||
{
|
||||
uint8_t buf[1];
|
||||
fd_set set;
|
||||
|
||||
self->lastError = SERIAL_PORT_ERROR_NONE;
|
||||
|
||||
FD_ZERO(&set);
|
||||
FD_SET(self->fd, &set);
|
||||
|
||||
int ret = select(self->fd + 1, &set, NULL, NULL, &(self->timeout));
|
||||
|
||||
if (ret == -1) {
|
||||
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
|
||||
return -1;
|
||||
}
|
||||
else if (ret == 0)
|
||||
return -1;
|
||||
else {
|
||||
read(self->fd, (char*) buf, 1);
|
||||
|
||||
return (int) buf[0];
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int bufSize)
|
||||
{
|
||||
/* TODO assure minimum line idle time? */
|
||||
|
||||
self->lastError = SERIAL_PORT_ERROR_NONE;
|
||||
|
||||
ssize_t result = write(self->fd, buffer + startPos, bufSize);
|
||||
|
||||
tcdrain(self->fd);
|
||||
|
||||
self->lastSentTime = Hal_getMonotonicTimeInMs();
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* serial_port_win32.c
|
||||
*
|
||||
* Copyright 2013-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_NONSTDC_NO_DEPRECATE
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "lib_memory.h"
|
||||
|
||||
#include "hal_serial.h"
|
||||
#include "hal_time.h"
|
||||
|
||||
struct sSerialPort {
|
||||
char interfaceName[100];
|
||||
HANDLE comPort;
|
||||
int baudRate;
|
||||
uint8_t dataBits;
|
||||
char parity;
|
||||
uint8_t stopBits;
|
||||
uint64_t lastSentTime;
|
||||
int timeout;
|
||||
SerialPortError lastError;
|
||||
};
|
||||
|
||||
SerialPort
|
||||
SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits)
|
||||
{
|
||||
SerialPort self = (SerialPort)GLOBAL_MALLOC(sizeof(struct sSerialPort));
|
||||
|
||||
if (self != NULL) {
|
||||
self->comPort = NULL;
|
||||
self->baudRate = baudRate;
|
||||
self->dataBits = dataBits;
|
||||
self->stopBits = stopBits;
|
||||
self->parity = parity;
|
||||
self->lastSentTime = 0;
|
||||
self->timeout = 100; /* 100 ms */
|
||||
strncpy(self->interfaceName, interfaceName, 100);
|
||||
self->lastError = SERIAL_PORT_ERROR_NONE;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
SerialPort_destroy(SerialPort self)
|
||||
{
|
||||
if (self != NULL) {
|
||||
|
||||
if (self->comPort != INVALID_HANDLE_VALUE)
|
||||
SerialPort_close(self);
|
||||
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SerialPort_open(SerialPort self)
|
||||
{
|
||||
COMMTIMEOUTS timeouts = { 0 };
|
||||
|
||||
self->comPort = CreateFile(self->interfaceName, GENERIC_READ | GENERIC_WRITE,
|
||||
0, NULL, OPEN_EXISTING, 0, NULL);
|
||||
|
||||
if (self->comPort == INVALID_HANDLE_VALUE) {
|
||||
self->lastError = SERIAL_PORT_ERROR_OPEN_FAILED;
|
||||
return false;
|
||||
}
|
||||
|
||||
DCB _serialParams = { 0 };
|
||||
_serialParams.DCBlength = sizeof(DCB);
|
||||
|
||||
LPDCB serialParams = &_serialParams;
|
||||
|
||||
BOOL status = GetCommState(self->comPort, serialParams);
|
||||
|
||||
if (status == false) {
|
||||
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
|
||||
goto exit_error;
|
||||
}
|
||||
|
||||
/* set baud rate */
|
||||
switch (self->baudRate) {
|
||||
case 110:
|
||||
serialParams->BaudRate = CBR_110;
|
||||
break;
|
||||
case 300:
|
||||
serialParams->BaudRate = CBR_300;
|
||||
break;
|
||||
case 600:
|
||||
serialParams->BaudRate = CBR_600;
|
||||
break;
|
||||
case 1200:
|
||||
serialParams->BaudRate = CBR_1200;
|
||||
break;
|
||||
case 2400:
|
||||
serialParams->BaudRate = CBR_2400;
|
||||
break;
|
||||
case 4800:
|
||||
serialParams->BaudRate = CBR_4800;
|
||||
break;
|
||||
case 9600:
|
||||
serialParams->BaudRate = CBR_9600;
|
||||
break;
|
||||
case 19200:
|
||||
serialParams->BaudRate = CBR_19200;
|
||||
break;
|
||||
case 38400:
|
||||
serialParams->BaudRate = CBR_38400;
|
||||
break;
|
||||
case 57600:
|
||||
serialParams->BaudRate = CBR_57600;
|
||||
break;
|
||||
case 115200:
|
||||
serialParams->BaudRate = CBR_115200;
|
||||
break;
|
||||
default:
|
||||
serialParams->BaudRate = CBR_9600;
|
||||
self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE;
|
||||
}
|
||||
|
||||
/* Set data bits (5/6/7/8) */
|
||||
serialParams->ByteSize = self->dataBits;
|
||||
|
||||
/* Set stop bits (1/2) */
|
||||
if (self->stopBits == 1)
|
||||
serialParams->StopBits = ONESTOPBIT;
|
||||
else /* 2 */
|
||||
serialParams->StopBits = TWOSTOPBITS;
|
||||
|
||||
if (self->parity == 'N')
|
||||
serialParams->Parity = NOPARITY;
|
||||
else if (self->parity == 'E')
|
||||
serialParams->Parity = EVENPARITY;
|
||||
else /* 'O' */
|
||||
serialParams->Parity = ODDPARITY;
|
||||
|
||||
status = SetCommState(self->comPort, serialParams);
|
||||
|
||||
if (status == false) {
|
||||
self->lastError = SERIAL_PORT_ERROR_INVALID_ARGUMENT;
|
||||
goto exit_error;
|
||||
}
|
||||
|
||||
timeouts.ReadIntervalTimeout = 100;
|
||||
timeouts.ReadTotalTimeoutConstant = 50;
|
||||
timeouts.ReadTotalTimeoutMultiplier = 10;
|
||||
timeouts.WriteTotalTimeoutConstant = 100;
|
||||
timeouts.WriteTotalTimeoutMultiplier = 10;
|
||||
|
||||
status = SetCommTimeouts(self->comPort, &timeouts);
|
||||
|
||||
if (status == false) {
|
||||
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
|
||||
goto exit_error;
|
||||
}
|
||||
|
||||
status = SetCommMask(self->comPort, EV_RXCHAR);
|
||||
|
||||
if (status == false) {
|
||||
printf("SetCommMask failed!\n");
|
||||
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
|
||||
goto exit_error;
|
||||
}
|
||||
|
||||
self->lastError = SERIAL_PORT_ERROR_NONE;
|
||||
|
||||
return true;
|
||||
|
||||
exit_error:
|
||||
|
||||
if (self->comPort != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(self->comPort);
|
||||
self->comPort = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
SerialPort_close(SerialPort self)
|
||||
{
|
||||
if (self->comPort != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(self->comPort);
|
||||
self->comPort = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
SerialPort_getBaudRate(SerialPort self)
|
||||
{
|
||||
return self->baudRate;
|
||||
}
|
||||
|
||||
void
|
||||
SerialPort_discardInBuffer(SerialPort self)
|
||||
{
|
||||
PurgeComm(self->comPort, PURGE_RXCLEAR | PURGE_TXCLEAR);
|
||||
}
|
||||
|
||||
void
|
||||
SerialPort_setTimeout(SerialPort self, int timeout)
|
||||
{
|
||||
self->timeout = timeout;
|
||||
}
|
||||
|
||||
SerialPortError
|
||||
SerialPort_getLastError(SerialPort self)
|
||||
{
|
||||
return self->lastError;
|
||||
}
|
||||
|
||||
int
|
||||
SerialPort_readByte(SerialPort self)
|
||||
{
|
||||
uint8_t buf[1];
|
||||
|
||||
DWORD bytesRead = 0;
|
||||
|
||||
BOOL status = ReadFile(self->comPort, buf, 1, &bytesRead, NULL);
|
||||
|
||||
if (status == false) {
|
||||
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
self->lastError = SERIAL_PORT_ERROR_NONE;
|
||||
|
||||
if (bytesRead == 0)
|
||||
return -1;
|
||||
else
|
||||
return (int) buf[0];
|
||||
}
|
||||
|
||||
int
|
||||
SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int bufSize)
|
||||
{
|
||||
//TODO assure minimum line idle time
|
||||
|
||||
self->lastError = SERIAL_PORT_ERROR_NONE;
|
||||
|
||||
DWORD numberOfBytesWritten;
|
||||
|
||||
BOOL status = WriteFile(self->comPort, buffer + startPos, bufSize, &numberOfBytesWritten, NULL);
|
||||
|
||||
if (status == false) {
|
||||
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
status = FlushFileBuffers(self->comPort);
|
||||
|
||||
if (status == false) {
|
||||
printf("FlushFileBuffers failed!\n");
|
||||
}
|
||||
|
||||
self->lastSentTime = Hal_getMonotonicTimeInMs();
|
||||
|
||||
return (int) numberOfBytesWritten;
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
/*
|
||||
* socket_bsd.c
|
||||
*
|
||||
* Copyright 2013-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#include "hal_socket.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <netinet/tcp.h> // required for TCP keepalive
|
||||
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include "hal_thread.h"
|
||||
#include "lib_memory.h"
|
||||
#include "linked_list.h"
|
||||
|
||||
#ifndef DEBUG_SOCKET
|
||||
#define DEBUG_SOCKET 0
|
||||
#endif
|
||||
|
||||
#ifndef IPV6_ADD_MEMBERSHIP
|
||||
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
|
||||
#endif
|
||||
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
struct sSocket
|
||||
{
|
||||
int fd;
|
||||
uint32_t connectTimeout;
|
||||
};
|
||||
|
||||
struct sServerSocket
|
||||
{
|
||||
int fd;
|
||||
int backLog;
|
||||
};
|
||||
|
||||
struct sUdpSocket
|
||||
{
|
||||
int fd;
|
||||
int namespace; /* IPv4: AF_INET; IPv6: AF_INET6 */
|
||||
};
|
||||
|
||||
struct sHandleSet
|
||||
{
|
||||
LinkedList sockets;
|
||||
bool pollfdIsUpdated;
|
||||
struct pollfd* fds;
|
||||
int nfds;
|
||||
};
|
||||
|
||||
HandleSet
|
||||
Handleset_new(void)
|
||||
{
|
||||
HandleSet self = (HandleSet)GLOBAL_MALLOC(sizeof(struct sHandleSet));
|
||||
|
||||
if (self)
|
||||
{
|
||||
self->sockets = LinkedList_create();
|
||||
self->pollfdIsUpdated = false;
|
||||
self->fds = NULL;
|
||||
self->nfds = 0;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
Handleset_reset(HandleSet self)
|
||||
{
|
||||
if (self)
|
||||
{
|
||||
if (self->sockets)
|
||||
{
|
||||
LinkedList_destroyStatic(self->sockets);
|
||||
self->sockets = LinkedList_create();
|
||||
self->pollfdIsUpdated = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Handleset_addSocket(HandleSet self, const Socket sock)
|
||||
{
|
||||
if (self != NULL && sock != NULL && sock->fd != -1)
|
||||
{
|
||||
LinkedList_add(self->sockets, sock);
|
||||
self->pollfdIsUpdated = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Handleset_removeSocket(HandleSet self, const Socket sock)
|
||||
{
|
||||
if (self && self->sockets && sock)
|
||||
{
|
||||
LinkedList_remove(self->sockets, sock);
|
||||
self->pollfdIsUpdated = false;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
Handleset_waitReady(HandleSet self, unsigned int timeoutMs)
|
||||
{
|
||||
/* check if pollfd array is updated */
|
||||
if (self->pollfdIsUpdated == false)
|
||||
{
|
||||
if (self->fds)
|
||||
{
|
||||
GLOBAL_FREEMEM(self->fds);
|
||||
self->fds = NULL;
|
||||
}
|
||||
|
||||
self->nfds = LinkedList_size(self->sockets);
|
||||
|
||||
self->fds = GLOBAL_CALLOC(self->nfds, sizeof(struct pollfd));
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < self->nfds; i++)
|
||||
{
|
||||
LinkedList sockElem = LinkedList_get(self->sockets, i);
|
||||
|
||||
if (sockElem)
|
||||
{
|
||||
Socket sock = (Socket)LinkedList_getData(sockElem);
|
||||
|
||||
if (sock)
|
||||
{
|
||||
self->fds[i].fd = sock->fd;
|
||||
self->fds[i].events = POLL_IN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self->pollfdIsUpdated = true;
|
||||
}
|
||||
|
||||
if (self->fds && self->nfds > 0)
|
||||
{
|
||||
int result = poll(self->fds, self->nfds, timeoutMs);
|
||||
|
||||
if (result == -1)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: poll error (errno: %i)\n", errno);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* there is no socket to wait for */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Handleset_destroy(HandleSet self)
|
||||
{
|
||||
if (self)
|
||||
{
|
||||
if (self->sockets)
|
||||
LinkedList_destroyStatic(self->sockets);
|
||||
|
||||
if (self->fds)
|
||||
GLOBAL_FREEMEM(self->fds);
|
||||
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count)
|
||||
{
|
||||
#if defined SO_KEEPALIVE
|
||||
int optval;
|
||||
socklen_t optlen = sizeof(optval);
|
||||
|
||||
optval = idleTime;
|
||||
setsockopt(self->fd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);
|
||||
optval = 1;
|
||||
setsockopt(self->fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, optlen);
|
||||
|
||||
#if defined TCP_KEEPCNT
|
||||
optval = interval;
|
||||
setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen);
|
||||
|
||||
optval = count;
|
||||
setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen);
|
||||
#endif /* TCP_KEEPCNT */
|
||||
|
||||
#endif /* SO_KEEPALIVE */
|
||||
}
|
||||
|
||||
static bool
|
||||
prepareAddress(const char* address, int port, struct sockaddr_in* sockaddr)
|
||||
{
|
||||
|
||||
memset((char*)sockaddr, 0, sizeof(struct sockaddr_in));
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
struct hostent* server;
|
||||
server = gethostbyname(address);
|
||||
|
||||
if (server == NULL)
|
||||
return false;
|
||||
|
||||
memcpy((char*)&sockaddr->sin_addr.s_addr, (char*)server->h_addr, server->h_length);
|
||||
}
|
||||
else
|
||||
sockaddr->sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
sockaddr->sin_family = AF_INET;
|
||||
sockaddr->sin_port = htons(port);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
setSocketNonBlocking(Socket self)
|
||||
{
|
||||
int flags = fcntl(self->fd, F_GETFL, 0);
|
||||
fcntl(self->fd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
static void
|
||||
activateTcpNoDelay(Socket self)
|
||||
{
|
||||
/* activate TCP_NODELAY option - packets will be sent immediately */
|
||||
int flag = 1;
|
||||
setsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int));
|
||||
}
|
||||
|
||||
ServerSocket
|
||||
TcpServerSocket_create(const char* address, int port)
|
||||
{
|
||||
ServerSocket serverSocket = NULL;
|
||||
|
||||
int fd;
|
||||
|
||||
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) >= 0)
|
||||
{
|
||||
struct sockaddr_in serverAddress;
|
||||
|
||||
if (!prepareAddress(address, port, &serverAddress))
|
||||
{
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int optionReuseAddr = 1;
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&optionReuseAddr, sizeof(int));
|
||||
|
||||
if (bind(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) >= 0)
|
||||
{
|
||||
serverSocket = (ServerSocket)GLOBAL_MALLOC(sizeof(struct sServerSocket));
|
||||
serverSocket->fd = fd;
|
||||
serverSocket->backLog = 2;
|
||||
|
||||
setSocketNonBlocking((Socket)serverSocket);
|
||||
}
|
||||
else
|
||||
{
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return serverSocket;
|
||||
}
|
||||
|
||||
void
|
||||
ServerSocket_listen(ServerSocket self)
|
||||
{
|
||||
listen(self->fd, self->backLog);
|
||||
}
|
||||
|
||||
/* CHANGED TO MAKE NON-BLOCKING --> RETURNS NULL IF NO CONNECTION IS PENDING */
|
||||
Socket
|
||||
ServerSocket_accept(ServerSocket self)
|
||||
{
|
||||
int fd;
|
||||
|
||||
Socket conSocket = NULL;
|
||||
|
||||
fd = accept(self->fd, NULL, NULL);
|
||||
|
||||
if (fd >= 0)
|
||||
{
|
||||
conSocket = (Socket)GLOBAL_CALLOC(1, sizeof(struct sSocket));
|
||||
|
||||
if (conSocket)
|
||||
{
|
||||
conSocket->fd = fd;
|
||||
|
||||
setSocketNonBlocking(conSocket);
|
||||
|
||||
activateTcpNoDelay(conSocket);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* out of memory */
|
||||
close(fd);
|
||||
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: out of memory\n");
|
||||
}
|
||||
}
|
||||
|
||||
return conSocket;
|
||||
}
|
||||
|
||||
void
|
||||
ServerSocket_setBacklog(ServerSocket self, int backlog)
|
||||
{
|
||||
self->backLog = backlog;
|
||||
}
|
||||
|
||||
static void
|
||||
closeAndShutdownSocket(int socketFd)
|
||||
{
|
||||
if (socketFd != -1)
|
||||
{
|
||||
|
||||
if (DEBUG_SOCKET)
|
||||
printf("socket_linux.c: call shutdown for %i!\n", socketFd);
|
||||
|
||||
/* shutdown is required to unblock read or accept in another thread! */
|
||||
shutdown(socketFd, SHUT_RDWR);
|
||||
|
||||
close(socketFd);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ServerSocket_destroy(ServerSocket self)
|
||||
{
|
||||
int fd = self->fd;
|
||||
|
||||
self->fd = -1;
|
||||
|
||||
closeAndShutdownSocket(fd);
|
||||
|
||||
Thread_sleep(10);
|
||||
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
Socket
|
||||
TcpSocket_create()
|
||||
{
|
||||
Socket self = (Socket)NULL;
|
||||
|
||||
int sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (sock != -1)
|
||||
{
|
||||
self = (Socket)GLOBAL_MALLOC(sizeof(struct sSocket));
|
||||
|
||||
if (self)
|
||||
{
|
||||
self->fd = sock;
|
||||
self->connectTimeout = 5000;
|
||||
|
||||
#if 0
|
||||
int tcpUserTimeout = 10000;
|
||||
int result = setsockopt(sock, SOL_TCP, TCP_USER_TIMEOUT, &tcpUserTimeout, sizeof(tcpUserTimeout));
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
/* out of memory */
|
||||
close(sock);
|
||||
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: out of memory\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to create socket (errno=%i)\n", errno);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs)
|
||||
{
|
||||
self->connectTimeout = timeoutInMs;
|
||||
}
|
||||
|
||||
bool
|
||||
Socket_bind(Socket self, const char* srcAddress, int srcPort)
|
||||
{
|
||||
struct sockaddr_in localAddress;
|
||||
|
||||
if (!prepareAddress(srcAddress, srcPort, &localAddress))
|
||||
return false;
|
||||
|
||||
int result = bind(self->fd, (struct sockaddr*)&localAddress, sizeof(localAddress));
|
||||
|
||||
if (result == -1)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to bind TCP socket (errno=%i)\n", errno);
|
||||
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Socket_connectAsync(Socket self, const char* address, int port)
|
||||
{
|
||||
struct sockaddr_in serverAddress;
|
||||
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: connect: %s:%i\n", address, port);
|
||||
|
||||
if (!prepareAddress(address, port, &serverAddress))
|
||||
return false;
|
||||
|
||||
fd_set fdSet;
|
||||
FD_ZERO(&fdSet);
|
||||
FD_SET(self->fd, &fdSet);
|
||||
|
||||
activateTcpNoDelay(self);
|
||||
|
||||
fcntl(self->fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
if (connect(self->fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0)
|
||||
{
|
||||
|
||||
if (errno != EINPROGRESS)
|
||||
{
|
||||
self->fd = -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true; /* is connecting or already connected */
|
||||
}
|
||||
|
||||
SocketState
|
||||
Socket_checkAsyncConnectState(Socket self)
|
||||
{
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
fd_set fdSet;
|
||||
FD_ZERO(&fdSet);
|
||||
FD_SET(self->fd, &fdSet);
|
||||
|
||||
int selectVal = select(self->fd + 1, NULL, &fdSet, NULL, &timeout);
|
||||
|
||||
if (selectVal == 1)
|
||||
{
|
||||
|
||||
/* Check if connection is established */
|
||||
|
||||
int so_error;
|
||||
socklen_t len = sizeof so_error;
|
||||
|
||||
if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, &so_error, &len) >= 0)
|
||||
{
|
||||
|
||||
if (so_error == 0)
|
||||
return SOCKET_STATE_CONNECTED;
|
||||
}
|
||||
|
||||
return SOCKET_STATE_FAILED;
|
||||
}
|
||||
else if (selectVal == 0)
|
||||
{
|
||||
return SOCKET_STATE_CONNECTING;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SOCKET_STATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Socket_connect(Socket self, const char* address, int port)
|
||||
{
|
||||
if (Socket_connectAsync(self, address, port) == false)
|
||||
return false;
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = self->connectTimeout / 1000;
|
||||
timeout.tv_usec = (self->connectTimeout % 1000) * 1000;
|
||||
|
||||
fd_set fdSet;
|
||||
FD_ZERO(&fdSet);
|
||||
FD_SET(self->fd, &fdSet);
|
||||
|
||||
if (select(self->fd + 1, NULL, &fdSet, NULL, &timeout) == 1)
|
||||
{
|
||||
|
||||
/* Check if connection is established */
|
||||
|
||||
int so_error;
|
||||
socklen_t len = sizeof so_error;
|
||||
|
||||
if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, &so_error, &len) >= 0)
|
||||
{
|
||||
|
||||
if (so_error == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
close(self->fd);
|
||||
self->fd = -1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static char*
|
||||
convertAddressToStr(struct sockaddr_storage* addr)
|
||||
{
|
||||
char addrString[INET6_ADDRSTRLEN + 7];
|
||||
int port;
|
||||
|
||||
bool isIPv6;
|
||||
|
||||
if (addr->ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)addr;
|
||||
port = ntohs(ipv4Addr->sin_port);
|
||||
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
|
||||
isIPv6 = false;
|
||||
}
|
||||
else if (addr->ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)addr;
|
||||
port = ntohs(ipv6Addr->sin6_port);
|
||||
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
|
||||
isIPv6 = true;
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
|
||||
char* clientConnection = (char*)GLOBAL_MALLOC(strlen(addrString) + 9);
|
||||
|
||||
if (isIPv6)
|
||||
sprintf(clientConnection, "[%s]:%i", addrString, port);
|
||||
else
|
||||
sprintf(clientConnection, "%s:%i", addrString, port);
|
||||
|
||||
return clientConnection;
|
||||
}
|
||||
|
||||
char*
|
||||
Socket_getPeerAddress(Socket self)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
|
||||
if (getpeername(self->fd, (struct sockaddr*)&addr, &addrLen) == 0)
|
||||
{
|
||||
return convertAddressToStr(&addr);
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char*
|
||||
Socket_getLocalAddress(Socket self)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
|
||||
if (getsockname(self->fd, (struct sockaddr*)&addr, &addrLen) == 0)
|
||||
{
|
||||
return convertAddressToStr(&addr);
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char*
|
||||
Socket_getPeerAddressStatic(Socket self, char* peerAddressString)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
|
||||
getpeername(self->fd, (struct sockaddr*)&addr, &addrLen);
|
||||
|
||||
char addrString[INET6_ADDRSTRLEN + 7];
|
||||
int port;
|
||||
|
||||
bool isIPv6;
|
||||
|
||||
if (addr.ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&addr;
|
||||
port = ntohs(ipv4Addr->sin_port);
|
||||
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
|
||||
isIPv6 = false;
|
||||
}
|
||||
else if (addr.ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&addr;
|
||||
port = ntohs(ipv6Addr->sin6_port);
|
||||
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
|
||||
isIPv6 = true;
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
|
||||
if (isIPv6)
|
||||
sprintf(peerAddressString, "[%s]:%i", addrString, port);
|
||||
else
|
||||
sprintf(peerAddressString, "%s:%i", addrString, port);
|
||||
|
||||
return peerAddressString;
|
||||
}
|
||||
|
||||
int
|
||||
Socket_read(Socket self, uint8_t* buf, int size)
|
||||
{
|
||||
if (self->fd == -1)
|
||||
return -1;
|
||||
|
||||
int read_bytes = recv(self->fd, buf, size, MSG_DONTWAIT);
|
||||
|
||||
if (read_bytes == 0)
|
||||
return -1;
|
||||
|
||||
if (read_bytes == -1)
|
||||
{
|
||||
int error = errno;
|
||||
|
||||
switch (error)
|
||||
{
|
||||
|
||||
case EAGAIN:
|
||||
return 0;
|
||||
case EBADF:
|
||||
return -1;
|
||||
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return read_bytes;
|
||||
}
|
||||
|
||||
int
|
||||
Socket_write(Socket self, uint8_t* buf, int size)
|
||||
{
|
||||
if (self->fd == -1)
|
||||
return -1;
|
||||
|
||||
/* MSG_NOSIGNAL - prevent send to signal SIGPIPE when peer unexpectedly closed the socket */
|
||||
int retVal = send(self->fd, buf, size, MSG_NOSIGNAL | MSG_DONTWAIT);
|
||||
|
||||
if ((retVal == -1) && (errno == EAGAIN))
|
||||
return 0;
|
||||
else
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void
|
||||
Socket_destroy(Socket self)
|
||||
{
|
||||
int fd = self->fd;
|
||||
|
||||
self->fd = -1;
|
||||
|
||||
closeAndShutdownSocket(fd);
|
||||
|
||||
Thread_sleep(10);
|
||||
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
static UdpSocket
|
||||
UdpSocket_createUsingNamespace(int namespace)
|
||||
{
|
||||
UdpSocket self = NULL;
|
||||
|
||||
int sock = socket(namespace, SOCK_DGRAM, IPPROTO_UDP);
|
||||
|
||||
if (sock != -1)
|
||||
{
|
||||
self = (UdpSocket)GLOBAL_MALLOC(sizeof(struct sSocket));
|
||||
|
||||
if (self)
|
||||
{
|
||||
self->fd = sock;
|
||||
self->namespace = namespace;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to allocate memory\n");
|
||||
|
||||
close(sock);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to create UDP socket (errno=%i)\n", errno);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
UdpSocket
|
||||
UdpSocket_create()
|
||||
{
|
||||
return UdpSocket_createUsingNamespace(AF_INET);
|
||||
}
|
||||
|
||||
UdpSocket
|
||||
UdpSocket_createIpV6()
|
||||
{
|
||||
return UdpSocket_createUsingNamespace(AF_INET6);
|
||||
}
|
||||
|
||||
bool
|
||||
UdpSocket_addGroupMembership(UdpSocket self, const char* multicastAddress)
|
||||
{
|
||||
if (self->namespace == AF_INET)
|
||||
{
|
||||
struct ip_mreq mreq;
|
||||
|
||||
if (!inet_aton(multicastAddress, &(mreq.imr_multiaddr)))
|
||||
{
|
||||
printf("SOCKET: Invalid IPv4 multicast address\n");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
if (setsockopt(self->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv4 multicast group (errno: %i)\n", errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (self->namespace == AF_INET6)
|
||||
{
|
||||
struct ipv6_mreq mreq;
|
||||
|
||||
if (inet_pton(AF_INET6, multicastAddress, &(mreq.ipv6mr_multiaddr)) < 1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv6 multicast group (errno: %i)\n", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
mreq.ipv6mr_interface = 0;
|
||||
|
||||
if (setsockopt(self->fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv6 multicast group (errno: %i)\n", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
UdpSocket_setMulticastTtl(UdpSocket self, int ttl)
|
||||
{
|
||||
if (self->namespace == AF_INET)
|
||||
{
|
||||
if (setsockopt(self->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv4 multicast TTL (errno: %i)\n", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (self->namespace == AF_INET6)
|
||||
{
|
||||
if (setsockopt(self->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) == -1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv6 multicast TTL(hops) (errno: %i)\n", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
UdpSocket_bind(UdpSocket self, const char* address, int port)
|
||||
{
|
||||
// TODO add support for IPv6
|
||||
struct sockaddr_in localAddress;
|
||||
|
||||
if (!prepareAddress(address, port, &localAddress))
|
||||
{
|
||||
close(self->fd);
|
||||
self->fd = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = bind(self->fd, (struct sockaddr*)&localAddress, sizeof(localAddress));
|
||||
|
||||
if (result == -1)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to bind UDP socket (errno=%i)\n", errno);
|
||||
|
||||
close(self->fd);
|
||||
self->fd = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
UdpSocket_sendTo(UdpSocket self, const char* address, int port, uint8_t* msg, int msgSize)
|
||||
{
|
||||
// TODO add support for IPv6
|
||||
struct sockaddr_in remoteAddress;
|
||||
|
||||
if (!prepareAddress(address, port, &remoteAddress))
|
||||
{
|
||||
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to lookup remote address %s\n", address);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = sendto(self->fd, msg, msgSize, 0, (struct sockaddr*)&remoteAddress, sizeof(remoteAddress));
|
||||
|
||||
if (result == msgSize)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (result == -1)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to send UDP message (errno=%i)\n", errno);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to send UDP message (insufficient data sent)\n");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int
|
||||
UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize)
|
||||
{
|
||||
// TODO add support for IPv6
|
||||
struct sockaddr_storage remoteAddress;
|
||||
|
||||
socklen_t structSize = sizeof(struct sockaddr_storage);
|
||||
|
||||
int result = recvfrom(self->fd, msg, msgSize, MSG_DONTWAIT, (struct sockaddr*)&remoteAddress, &structSize);
|
||||
|
||||
if (result == -1)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to receive UDP message (errno=%i)\n", errno);
|
||||
}
|
||||
|
||||
if (address)
|
||||
{
|
||||
bool isIPv6;
|
||||
char addrString[INET6_ADDRSTRLEN + 7];
|
||||
int port;
|
||||
|
||||
if (remoteAddress.ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&remoteAddress;
|
||||
port = ntohs(ipv4Addr->sin_port);
|
||||
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
|
||||
isIPv6 = false;
|
||||
}
|
||||
else if (remoteAddress.ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&remoteAddress;
|
||||
port = ntohs(ipv6Addr->sin6_port);
|
||||
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
|
||||
isIPv6 = true;
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
|
||||
if (isIPv6)
|
||||
snprintf(address, maxAddrSize, "[%s]:%i", addrString, port);
|
||||
else
|
||||
snprintf(address, maxAddrSize, "%s:%i", addrString, port);
|
||||
}
|
||||
|
||||
if (address)
|
||||
{
|
||||
bool isIPv6;
|
||||
char addrString[INET6_ADDRSTRLEN + 7];
|
||||
int port;
|
||||
|
||||
if (remoteAddress.ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&remoteAddress;
|
||||
port = ntohs(ipv4Addr->sin_port);
|
||||
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
|
||||
isIPv6 = false;
|
||||
}
|
||||
else if (remoteAddress.ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&remoteAddress;
|
||||
port = ntohs(ipv6Addr->sin6_port);
|
||||
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
|
||||
isIPv6 = true;
|
||||
}
|
||||
else
|
||||
return result;
|
||||
|
||||
if (isIPv6)
|
||||
snprintf(address, maxAddrSize, "[%s]:%i", addrString, port);
|
||||
else
|
||||
snprintf(address, maxAddrSize, "%s:%i", addrString, port);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,954 @@
|
||||
/*
|
||||
* socket_win32.c
|
||||
*
|
||||
* Copyright 2013-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include <windows.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#pragma comment(lib, "Ws2_32.lib")
|
||||
|
||||
#include "hal_socket.h"
|
||||
#include "lib_memory.h"
|
||||
|
||||
#ifndef DEBUG_SOCKET
|
||||
#define DEBUG_SOCKET 0
|
||||
#endif
|
||||
|
||||
#ifndef __MINGW64_VERSION_MAJOR
|
||||
struct tcp_keepalive
|
||||
{
|
||||
u_long onoff;
|
||||
u_long keepalivetime;
|
||||
u_long keepaliveinterval;
|
||||
};
|
||||
#endif
|
||||
|
||||
#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR, 4)
|
||||
|
||||
struct sSocket
|
||||
{
|
||||
SOCKET fd;
|
||||
uint32_t connectTimeout;
|
||||
};
|
||||
|
||||
struct sServerSocket
|
||||
{
|
||||
SOCKET fd;
|
||||
int backLog;
|
||||
};
|
||||
|
||||
struct sHandleSet
|
||||
{
|
||||
fd_set handles;
|
||||
SOCKET maxHandle;
|
||||
};
|
||||
|
||||
struct sUdpSocket
|
||||
{
|
||||
SOCKET fd;
|
||||
int ns; /* IPv4: AF_INET; IPv6: AF_INET6 */
|
||||
};
|
||||
|
||||
HandleSet
|
||||
Handleset_new(void)
|
||||
{
|
||||
HandleSet result = (HandleSet)GLOBAL_MALLOC(sizeof(struct sHandleSet));
|
||||
|
||||
if (result != NULL)
|
||||
{
|
||||
FD_ZERO(&result->handles);
|
||||
result->maxHandle = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
Handleset_reset(HandleSet self)
|
||||
{
|
||||
FD_ZERO(&self->handles);
|
||||
self->maxHandle = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
void
|
||||
Handleset_addSocket(HandleSet self, const Socket sock)
|
||||
{
|
||||
if (self != NULL && sock != NULL && sock->fd != INVALID_SOCKET)
|
||||
{
|
||||
|
||||
FD_SET(sock->fd, &self->handles);
|
||||
|
||||
if ((sock->fd > self->maxHandle) || (self->maxHandle == INVALID_SOCKET))
|
||||
self->maxHandle = sock->fd;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Handleset_removeSocket(HandleSet self, const Socket sock)
|
||||
{
|
||||
if (self != NULL && sock != NULL && sock->fd != INVALID_SOCKET)
|
||||
{
|
||||
FD_CLR(sock->fd, &self->handles);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
Handleset_waitReady(HandleSet self, unsigned int timeoutMs)
|
||||
{
|
||||
int result;
|
||||
|
||||
if ((self != NULL) && (self->maxHandle != INVALID_SOCKET))
|
||||
{
|
||||
struct timeval timeout;
|
||||
|
||||
timeout.tv_sec = timeoutMs / 1000;
|
||||
timeout.tv_usec = (timeoutMs % 1000) * 1000;
|
||||
|
||||
fd_set handles;
|
||||
|
||||
memcpy((void*)&handles, &(self->handles), sizeof(fd_set));
|
||||
|
||||
result = select(0, &handles, NULL, NULL, &timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
Handleset_destroy(HandleSet self)
|
||||
{
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
static bool wsaStartupCalled = false;
|
||||
static int socketCount = 0;
|
||||
|
||||
void
|
||||
Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count)
|
||||
{
|
||||
(void)count; /* not supported in windows socket API */
|
||||
|
||||
struct tcp_keepalive keepalive;
|
||||
DWORD retVal = 0;
|
||||
|
||||
keepalive.onoff = 1;
|
||||
keepalive.keepalivetime = idleTime * 1000;
|
||||
keepalive.keepaliveinterval = interval * 1000;
|
||||
|
||||
if (WSAIoctl(self->fd, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &retVal, NULL, NULL) ==
|
||||
SOCKET_ERROR)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("WIN32_SOCKET: WSAIotcl(SIO_KEEPALIVE_VALS) failed: %d\n", WSAGetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
setSocketNonBlocking(Socket self)
|
||||
{
|
||||
unsigned long mode = 1;
|
||||
if (ioctlsocket(self->fd, FIONBIO, &mode) != 0)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("WIN32_SOCKET: failed to set socket non-blocking!\n");
|
||||
}
|
||||
|
||||
/* activate TCP_NODELAY */
|
||||
|
||||
int tcpNoDelay = 1;
|
||||
|
||||
setsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&tcpNoDelay, sizeof(int));
|
||||
}
|
||||
|
||||
static bool
|
||||
prepareAddress(const char* address, int port, struct sockaddr_in* sockaddr)
|
||||
{
|
||||
memset((char*)sockaddr, 0, sizeof(struct sockaddr_in));
|
||||
|
||||
if (address != NULL)
|
||||
{
|
||||
struct hostent* server;
|
||||
server = gethostbyname(address);
|
||||
|
||||
if (server == NULL)
|
||||
return false;
|
||||
|
||||
memcpy((char*)&sockaddr->sin_addr.s_addr, (char*)server->h_addr, server->h_length);
|
||||
}
|
||||
else
|
||||
sockaddr->sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
sockaddr->sin_family = AF_INET;
|
||||
sockaddr->sin_port = htons(port);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
wsaStartUp(void)
|
||||
{
|
||||
if (wsaStartupCalled == false)
|
||||
{
|
||||
int ec;
|
||||
WSADATA wsa;
|
||||
|
||||
if ((ec = WSAStartup(MAKEWORD(2, 0), &wsa)) != 0)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("WIN32_SOCKET: winsock error: code %i\n", ec);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
wsaStartupCalled = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
wsaShutdown(void)
|
||||
{
|
||||
if (wsaStartupCalled)
|
||||
{
|
||||
if (socketCount == 0)
|
||||
{
|
||||
WSACleanup();
|
||||
wsaStartupCalled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ServerSocket
|
||||
TcpServerSocket_create(const char* address, int port)
|
||||
{
|
||||
ServerSocket serverSocket = NULL;
|
||||
int ec;
|
||||
SOCKET listen_socket = INVALID_SOCKET;
|
||||
|
||||
if (wsaStartUp() == false)
|
||||
return NULL;
|
||||
|
||||
struct sockaddr_in server_addr;
|
||||
|
||||
if (!prepareAddress(address, port, &server_addr))
|
||||
return NULL;
|
||||
|
||||
listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
|
||||
if (listen_socket == INVALID_SOCKET)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("WIN32_SOCKET: socket failed with error: %i\n", WSAGetLastError());
|
||||
|
||||
wsaShutdown();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int optionReuseAddr = 1;
|
||||
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&optionReuseAddr, sizeof(int));
|
||||
|
||||
ec = bind(listen_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
|
||||
|
||||
if (ec == SOCKET_ERROR)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("WIN32_SOCKET: bind failed with error:%i\n", WSAGetLastError());
|
||||
closesocket(listen_socket);
|
||||
|
||||
wsaShutdown();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
serverSocket = (ServerSocket)GLOBAL_MALLOC(sizeof(struct sServerSocket));
|
||||
|
||||
if (serverSocket)
|
||||
{
|
||||
serverSocket->fd = listen_socket;
|
||||
serverSocket->backLog = 10;
|
||||
|
||||
setSocketNonBlocking((Socket)serverSocket);
|
||||
|
||||
socketCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
closesocket(listen_socket);
|
||||
wsaShutdown();
|
||||
}
|
||||
|
||||
return serverSocket;
|
||||
}
|
||||
|
||||
void
|
||||
ServerSocket_listen(ServerSocket self)
|
||||
{
|
||||
listen(self->fd, self->backLog);
|
||||
}
|
||||
|
||||
Socket
|
||||
ServerSocket_accept(ServerSocket self)
|
||||
{
|
||||
Socket conSocket = NULL;
|
||||
|
||||
SOCKET fd = accept(self->fd, NULL, NULL);
|
||||
|
||||
if (fd != INVALID_SOCKET)
|
||||
{
|
||||
conSocket = (Socket)GLOBAL_CALLOC(1, sizeof(struct sSocket));
|
||||
conSocket->fd = fd;
|
||||
|
||||
socketCount++;
|
||||
|
||||
setSocketNonBlocking(conSocket);
|
||||
|
||||
if (DEBUG_SOCKET)
|
||||
printf("WIN32_SOCKET: connection accepted\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("WIN32_SOCKET: accept failed\n");
|
||||
}
|
||||
|
||||
return conSocket;
|
||||
}
|
||||
|
||||
void
|
||||
ServerSocket_setBacklog(ServerSocket self, int backlog)
|
||||
{
|
||||
self->backLog = backlog;
|
||||
}
|
||||
|
||||
void
|
||||
ServerSocket_destroy(ServerSocket self)
|
||||
{
|
||||
if (self->fd != INVALID_SOCKET)
|
||||
{
|
||||
shutdown(self->fd, 2);
|
||||
closesocket(self->fd);
|
||||
socketCount--;
|
||||
self->fd = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
wsaShutdown();
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
Socket
|
||||
TcpSocket_create()
|
||||
{
|
||||
Socket self = NULL;
|
||||
|
||||
if (wsaStartUp() == false)
|
||||
return NULL;
|
||||
|
||||
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (sock != INVALID_SOCKET)
|
||||
{
|
||||
self = (Socket)GLOBAL_MALLOC(sizeof(struct sSocket));
|
||||
|
||||
if (self)
|
||||
{
|
||||
self->fd = sock;
|
||||
self->connectTimeout = 5000;
|
||||
|
||||
socketCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to create socket - cannot allocate memory\n");
|
||||
|
||||
closesocket(sock);
|
||||
wsaShutdown();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to create socket (error code=%i)\n", WSAGetLastError());
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs)
|
||||
{
|
||||
self->connectTimeout = timeoutInMs;
|
||||
}
|
||||
|
||||
bool
|
||||
Socket_bind(Socket self, const char* srcAddress, int srcPort)
|
||||
{
|
||||
struct sockaddr_in localAddress;
|
||||
|
||||
if (!prepareAddress(srcAddress, srcPort, &localAddress))
|
||||
return false;
|
||||
|
||||
int result = bind(self->fd, (struct sockaddr*)&localAddress, sizeof(localAddress));
|
||||
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to bind TCP socket (errno=%i)\n", WSAGetLastError());
|
||||
|
||||
closesocket(self->fd);
|
||||
self->fd = -1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
Socket_connectAsync(Socket self, const char* address, int port)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("WIN32_SOCKET: Socket_connect: %s:%i\n", address, port);
|
||||
|
||||
struct sockaddr_in serverAddress;
|
||||
|
||||
if (wsaStartUp() == false)
|
||||
return false;
|
||||
|
||||
if (!prepareAddress(address, port, &serverAddress))
|
||||
return false;
|
||||
|
||||
setSocketNonBlocking(self);
|
||||
|
||||
if (connect(self->fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR)
|
||||
{
|
||||
if (WSAGetLastError() != WSAEWOULDBLOCK)
|
||||
{
|
||||
closesocket(self->fd);
|
||||
self->fd = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true; /* is connecting or already connected */
|
||||
}
|
||||
|
||||
SocketState
|
||||
Socket_checkAsyncConnectState(Socket self)
|
||||
{
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = 0;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
fd_set fdSet;
|
||||
FD_ZERO(&fdSet);
|
||||
FD_SET(self->fd, &fdSet);
|
||||
|
||||
int selectVal = select(0, NULL, &fdSet, NULL, &timeout);
|
||||
|
||||
if (selectVal == 1)
|
||||
{
|
||||
|
||||
/* Check if connection is established */
|
||||
|
||||
int so_error;
|
||||
int len = sizeof so_error;
|
||||
|
||||
if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, (char*)(&so_error), &len) != SOCKET_ERROR)
|
||||
{
|
||||
if (so_error == 0)
|
||||
{
|
||||
|
||||
int recvRes = recv(self->fd, NULL, 0, 0);
|
||||
|
||||
if (recvRes == SOCKET_ERROR)
|
||||
{
|
||||
int wsaError = WSAGetLastError();
|
||||
|
||||
if (wsaError == WSAECONNRESET)
|
||||
return SOCKET_STATE_FAILED;
|
||||
|
||||
if (wsaError == WSAECONNABORTED)
|
||||
return SOCKET_STATE_FAILED;
|
||||
}
|
||||
|
||||
return SOCKET_STATE_CONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
return SOCKET_STATE_FAILED;
|
||||
}
|
||||
else if (selectVal == 0)
|
||||
{
|
||||
return SOCKET_STATE_CONNECTING;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SOCKET_STATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Socket_connect(Socket self, const char* address, int port)
|
||||
{
|
||||
if (Socket_connectAsync(self, address, port) == false)
|
||||
return false;
|
||||
|
||||
struct timeval timeout;
|
||||
timeout.tv_sec = self->connectTimeout / 1000;
|
||||
timeout.tv_usec = (self->connectTimeout % 1000) * 1000;
|
||||
|
||||
fd_set fdSet;
|
||||
FD_ZERO(&fdSet);
|
||||
FD_SET(self->fd, &fdSet);
|
||||
|
||||
if (select(0, NULL, &fdSet, NULL, &timeout) == 1)
|
||||
{
|
||||
/* Check if connection is established */
|
||||
|
||||
int so_error;
|
||||
socklen_t len = sizeof so_error;
|
||||
|
||||
if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, (char*)&so_error, &len) >= 0)
|
||||
{
|
||||
|
||||
if (so_error == 0)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
closesocket(self->fd);
|
||||
self->fd = INVALID_SOCKET;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static char*
|
||||
convertAddressToStr(struct sockaddr_storage* addr)
|
||||
{
|
||||
char addrString[INET6_ADDRSTRLEN + 7];
|
||||
int addrStringLen = INET6_ADDRSTRLEN + 7;
|
||||
int port;
|
||||
|
||||
bool isIPv6;
|
||||
|
||||
if (addr->ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)addr;
|
||||
port = ntohs(ipv4Addr->sin_port);
|
||||
ipv4Addr->sin_port = 0;
|
||||
WSAAddressToString((LPSOCKADDR)ipv4Addr, sizeof(struct sockaddr_storage), NULL, (LPSTR)addrString,
|
||||
(LPDWORD)&addrStringLen);
|
||||
isIPv6 = false;
|
||||
}
|
||||
else if (addr->ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)addr;
|
||||
port = ntohs(ipv6Addr->sin6_port);
|
||||
ipv6Addr->sin6_port = 0;
|
||||
WSAAddressToString((LPSOCKADDR)ipv6Addr, sizeof(struct sockaddr_storage), NULL, (LPSTR)addrString,
|
||||
(LPDWORD)&addrStringLen);
|
||||
isIPv6 = true;
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
|
||||
char* clientConnection = (char*)GLOBAL_MALLOC(strlen(addrString) + 9);
|
||||
|
||||
if (isIPv6)
|
||||
sprintf(clientConnection, "[%s]:%i", addrString, port);
|
||||
else
|
||||
sprintf(clientConnection, "%s:%i", addrString, port);
|
||||
|
||||
return clientConnection;
|
||||
}
|
||||
|
||||
char*
|
||||
Socket_getPeerAddress(Socket self)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
|
||||
if (getpeername(self->fd, (struct sockaddr*)&addr, &addrLen) == 0)
|
||||
{
|
||||
return convertAddressToStr(&addr);
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char*
|
||||
Socket_getLocalAddress(Socket self)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t addrLen = sizeof(addr);
|
||||
|
||||
if (getsockname(self->fd, (struct sockaddr*)&addr, &addrLen) == 0)
|
||||
{
|
||||
return convertAddressToStr(&addr);
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char*
|
||||
Socket_getPeerAddressStatic(Socket self, char* peerAddressString)
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
int addrLen = sizeof(addr);
|
||||
|
||||
getpeername(self->fd, (struct sockaddr*)&addr, &addrLen);
|
||||
|
||||
char addrString[INET6_ADDRSTRLEN + 7];
|
||||
int addrStringLen = INET6_ADDRSTRLEN + 7;
|
||||
int port;
|
||||
|
||||
bool isIPv6;
|
||||
|
||||
if (addr.ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&addr;
|
||||
port = ntohs(ipv4Addr->sin_port);
|
||||
ipv4Addr->sin_port = 0;
|
||||
WSAAddressToString((LPSOCKADDR)ipv4Addr, sizeof(struct sockaddr_storage), NULL, (LPSTR)addrString,
|
||||
(LPDWORD)&addrStringLen);
|
||||
isIPv6 = false;
|
||||
}
|
||||
else if (addr.ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&addr;
|
||||
port = ntohs(ipv6Addr->sin6_port);
|
||||
ipv6Addr->sin6_port = 0;
|
||||
WSAAddressToString((LPSOCKADDR)ipv6Addr, sizeof(struct sockaddr_storage), NULL, (LPSTR)addrString,
|
||||
(LPDWORD)&addrStringLen);
|
||||
isIPv6 = true;
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
|
||||
if (isIPv6)
|
||||
sprintf(peerAddressString, "[%s]:%i", addrString, port);
|
||||
else
|
||||
sprintf(peerAddressString, "%s:%i", addrString, port);
|
||||
|
||||
return peerAddressString;
|
||||
}
|
||||
|
||||
int
|
||||
Socket_read(Socket self, uint8_t* buf, int size)
|
||||
{
|
||||
int bytes_read = recv(self->fd, (char*)buf, size, 0);
|
||||
|
||||
if (bytes_read == 0) /* peer has closed socket */
|
||||
return -1;
|
||||
|
||||
if (bytes_read == SOCKET_ERROR)
|
||||
{
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
int
|
||||
Socket_write(Socket self, uint8_t* buf, int size)
|
||||
{
|
||||
int bytes_sent = send(self->fd, (char*)buf, size, 0);
|
||||
|
||||
if (bytes_sent == SOCKET_ERROR)
|
||||
{
|
||||
int errorCode = WSAGetLastError();
|
||||
|
||||
if (errorCode == WSAEWOULDBLOCK)
|
||||
bytes_sent = 0;
|
||||
else
|
||||
bytes_sent = -1;
|
||||
}
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
void
|
||||
Socket_destroy(Socket self)
|
||||
{
|
||||
if (self->fd != INVALID_SOCKET)
|
||||
{
|
||||
shutdown(self->fd, 2);
|
||||
closesocket(self->fd);
|
||||
|
||||
self->fd = INVALID_SOCKET;
|
||||
|
||||
socketCount--;
|
||||
}
|
||||
|
||||
wsaShutdown();
|
||||
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
static UdpSocket
|
||||
UdpSocket_createUsingNamespace(int ns)
|
||||
{
|
||||
if (wsaStartUp() == false)
|
||||
return NULL;
|
||||
|
||||
UdpSocket self = NULL;
|
||||
|
||||
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
|
||||
if (sock != INVALID_SOCKET)
|
||||
{
|
||||
self = (UdpSocket)GLOBAL_MALLOC(sizeof(struct sSocket));
|
||||
|
||||
if (self)
|
||||
{
|
||||
self->fd = sock;
|
||||
self->ns = ns;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to allocate memory\n");
|
||||
|
||||
closesocket(sock);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to create UDP socket (errno=%i)\n", WSAGetLastError());
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
UdpSocket
|
||||
UdpSocket_create()
|
||||
{
|
||||
return UdpSocket_createUsingNamespace(AF_INET);
|
||||
}
|
||||
|
||||
UdpSocket
|
||||
UdpSocket_createIpV6()
|
||||
{
|
||||
return UdpSocket_createUsingNamespace(AF_INET6);
|
||||
}
|
||||
|
||||
bool
|
||||
UdpSocket_addGroupMembership(UdpSocket self, const char* multicastAddress)
|
||||
{
|
||||
if (self->ns == AF_INET)
|
||||
{
|
||||
struct ip_mreq mreq;
|
||||
|
||||
if (inet_pton(AF_INET, multicastAddress, &(mreq.imr_multiaddr)) < 1)
|
||||
{
|
||||
printf("SOCKET: Invalid IPv4 multicast address\n");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
if (setsockopt(self->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&mreq, sizeof(mreq)) == -1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv4 multicast group (errno: %i)\n", WSAGetLastError());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (self->ns == AF_INET6)
|
||||
{
|
||||
struct ipv6_mreq mreq;
|
||||
|
||||
if (inet_pton(AF_INET6, multicastAddress, &(mreq.ipv6mr_multiaddr)) < 1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv6 multicast group (errno: %i)\n", WSAGetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
mreq.ipv6mr_interface = 0;
|
||||
|
||||
if (setsockopt(self->fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (const char*)&mreq, sizeof(mreq)) == -1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv6 multicast group (errno: %i)\n", WSAGetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
UdpSocket_setMulticastTtl(UdpSocket self, int ttl)
|
||||
{
|
||||
if (self->ns == AF_INET)
|
||||
{
|
||||
if (setsockopt(self->fd, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof(ttl)) == -1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv4 multicast TTL (errno: %i)\n", WSAGetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (self->ns == AF_INET6)
|
||||
{
|
||||
if (setsockopt(self->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char*)&ttl, sizeof(ttl)) == -1)
|
||||
{
|
||||
printf("SOCKET: failed to set IPv6 multicast TTL(hops) (errno: %i)\n", WSAGetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
UdpSocket_bind(UdpSocket self, const char* address, int port)
|
||||
{
|
||||
// TODO add support for IPv6
|
||||
struct sockaddr_in localAddress;
|
||||
|
||||
if (!prepareAddress(address, port, &localAddress))
|
||||
{
|
||||
closesocket(self->fd);
|
||||
self->fd = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = bind(self->fd, (struct sockaddr*)&localAddress, sizeof(localAddress));
|
||||
|
||||
if (result == -1)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to bind UDP socket (errno=%i)\n", errno);
|
||||
|
||||
closesocket(self->fd);
|
||||
self->fd = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
UdpSocket_sendTo(UdpSocket self, const char* address, int port, uint8_t* msg, int msgSize)
|
||||
{
|
||||
// TODO add support for IPv6
|
||||
struct sockaddr_in remoteAddress;
|
||||
|
||||
if (!prepareAddress(address, port, &remoteAddress))
|
||||
{
|
||||
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to lookup remote address %s\n", address);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int result =
|
||||
sendto(self->fd, (const char*)msg, msgSize, 0, (struct sockaddr*)&remoteAddress, sizeof(remoteAddress));
|
||||
|
||||
if (result == msgSize)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (result == -1)
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to send UDP message (errno=%i)\n", errno);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (DEBUG_SOCKET)
|
||||
printf("SOCKET: failed to send UDP message (insufficient data sent)\n");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int
|
||||
UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize)
|
||||
{
|
||||
// TODO add support for IPv6
|
||||
struct sockaddr_storage remoteAddress;
|
||||
memset(&remoteAddress, 0, sizeof(struct sockaddr_storage));
|
||||
socklen_t structSize = sizeof(struct sockaddr_storage);
|
||||
|
||||
if (address)
|
||||
address[0] = 0;
|
||||
|
||||
int result = recvfrom(self->fd, (char*)msg, msgSize, 0, (struct sockaddr*)&remoteAddress, &structSize);
|
||||
|
||||
if (result == 0) /* peer has closed socket */
|
||||
return -1;
|
||||
|
||||
if (result == SOCKET_ERROR)
|
||||
{
|
||||
if (WSAGetLastError() == WSAEWOULDBLOCK)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (address)
|
||||
{
|
||||
bool isIPv6;
|
||||
char addrString[INET6_ADDRSTRLEN + 7];
|
||||
int port;
|
||||
|
||||
if (remoteAddress.ss_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&remoteAddress;
|
||||
port = ntohs(ipv4Addr->sin_port);
|
||||
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
|
||||
isIPv6 = false;
|
||||
}
|
||||
else if (remoteAddress.ss_family == AF_INET6)
|
||||
{
|
||||
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&remoteAddress;
|
||||
port = ntohs(ipv6Addr->sin6_port);
|
||||
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
|
||||
isIPv6 = true;
|
||||
}
|
||||
else
|
||||
return result;
|
||||
|
||||
if (isIPv6)
|
||||
snprintf(address, maxAddrSize, "[%s]:%i", addrString, port);
|
||||
else
|
||||
snprintf(address, maxAddrSize, "%s:%i", addrString, port);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* thread_bsd.c
|
||||
*
|
||||
* Copyright 2013-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <unistd.h>
|
||||
#include "hal_thread.h"
|
||||
#include "lib_memory.h"
|
||||
|
||||
struct sThread {
|
||||
ThreadExecutionFunction function;
|
||||
void* parameter;
|
||||
pthread_t pthread;
|
||||
int state;
|
||||
bool autodestroy;
|
||||
};
|
||||
|
||||
Semaphore
|
||||
Semaphore_create(int initialValue)
|
||||
{
|
||||
Semaphore self = GLOBAL_MALLOC(sizeof(sem_t));
|
||||
|
||||
sem_init((sem_t*) self, 0, initialValue);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* Wait until semaphore value is more than zero. Then decrease the semaphore value. */
|
||||
void
|
||||
Semaphore_wait(Semaphore self)
|
||||
{
|
||||
sem_wait((sem_t*) self);
|
||||
}
|
||||
|
||||
void
|
||||
Semaphore_post(Semaphore self)
|
||||
{
|
||||
sem_post((sem_t*) self);
|
||||
}
|
||||
|
||||
void
|
||||
Semaphore_destroy(Semaphore self)
|
||||
{
|
||||
sem_destroy((sem_t*) self);
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
Thread
|
||||
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
|
||||
{
|
||||
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
|
||||
|
||||
if (thread != NULL) {
|
||||
thread->parameter = parameter;
|
||||
thread->function = function;
|
||||
thread->state = 0;
|
||||
thread->autodestroy = autodestroy;
|
||||
}
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
static void*
|
||||
destroyAutomaticThread(void* parameter)
|
||||
{
|
||||
Thread thread = (Thread) parameter;
|
||||
|
||||
thread->function(thread->parameter);
|
||||
|
||||
GLOBAL_FREEMEM(thread);
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void
|
||||
Thread_start(Thread thread)
|
||||
{
|
||||
if (thread->autodestroy == true) {
|
||||
pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread);
|
||||
pthread_detach(thread->pthread);
|
||||
}
|
||||
else
|
||||
pthread_create(&thread->pthread, NULL, thread->function, thread->parameter);
|
||||
|
||||
thread->state = 1;
|
||||
}
|
||||
|
||||
void
|
||||
Thread_destroy(Thread thread)
|
||||
{
|
||||
if (thread->state == 1) {
|
||||
pthread_join(thread->pthread, NULL);
|
||||
}
|
||||
|
||||
GLOBAL_FREEMEM(thread);
|
||||
}
|
||||
|
||||
void
|
||||
Thread_sleep(int millies)
|
||||
{
|
||||
usleep(millies * 1000);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* thread_linux.c
|
||||
*
|
||||
* Copyright 2013-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <unistd.h>
|
||||
#include "hal_thread.h"
|
||||
#include "lib_memory.h"
|
||||
|
||||
struct sThread {
|
||||
ThreadExecutionFunction function;
|
||||
void* parameter;
|
||||
pthread_t pthread;
|
||||
int state;
|
||||
bool autodestroy;
|
||||
};
|
||||
|
||||
Semaphore
|
||||
Semaphore_create(int initialValue)
|
||||
{
|
||||
Semaphore self = GLOBAL_MALLOC(sizeof(sem_t));
|
||||
|
||||
sem_init((sem_t*) self, 0, initialValue);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* Wait until semaphore value is more than zero. Then decrease the semaphore value. */
|
||||
void
|
||||
Semaphore_wait(Semaphore self)
|
||||
{
|
||||
sem_wait((sem_t*) self);
|
||||
}
|
||||
|
||||
void
|
||||
Semaphore_post(Semaphore self)
|
||||
{
|
||||
sem_post((sem_t*) self);
|
||||
}
|
||||
|
||||
void
|
||||
Semaphore_destroy(Semaphore self)
|
||||
{
|
||||
sem_destroy((sem_t*) self);
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
Thread
|
||||
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
|
||||
{
|
||||
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
|
||||
|
||||
if (thread != NULL) {
|
||||
thread->parameter = parameter;
|
||||
thread->function = function;
|
||||
thread->state = 0;
|
||||
thread->autodestroy = autodestroy;
|
||||
}
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
static void*
|
||||
destroyAutomaticThread(void* parameter)
|
||||
{
|
||||
Thread thread = (Thread) parameter;
|
||||
|
||||
thread->function(thread->parameter);
|
||||
|
||||
GLOBAL_FREEMEM(thread);
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void
|
||||
Thread_start(Thread thread)
|
||||
{
|
||||
if (thread->autodestroy == true) {
|
||||
pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread);
|
||||
pthread_detach(thread->pthread);
|
||||
}
|
||||
else
|
||||
pthread_create(&thread->pthread, NULL, thread->function, thread->parameter);
|
||||
|
||||
thread->state = 1;
|
||||
}
|
||||
|
||||
void
|
||||
Thread_destroy(Thread thread)
|
||||
{
|
||||
if (thread->state == 1) {
|
||||
pthread_join(thread->pthread, NULL);
|
||||
}
|
||||
|
||||
GLOBAL_FREEMEM(thread);
|
||||
}
|
||||
|
||||
void
|
||||
Thread_sleep(int millies)
|
||||
{
|
||||
usleep(millies * 1000);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* thread_macos.c
|
||||
*
|
||||
* Copyright 2013-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
/*
|
||||
* NOTE: MacOS needs own thread layer because it doesn't support unnamed semaphores!
|
||||
* NOTE: named semaphores were replaced by POSIX mutex
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include "hal_thread.h"
|
||||
#include "lib_memory.h"
|
||||
|
||||
struct sThread {
|
||||
ThreadExecutionFunction function;
|
||||
void* parameter;
|
||||
pthread_t pthread;
|
||||
int state;
|
||||
bool autodestroy;
|
||||
};
|
||||
|
||||
typedef struct sSemaphore* mSemaphore;
|
||||
|
||||
struct sSemaphore
|
||||
{
|
||||
pthread_mutex_t mutex;
|
||||
};
|
||||
|
||||
/*
|
||||
* NOTE: initialValue is ignored because semaphore was replaced by mutex
|
||||
*/
|
||||
Semaphore
|
||||
Semaphore_create(int initialValue)
|
||||
{
|
||||
mSemaphore self = NULL;
|
||||
|
||||
self = (mSemaphore) GLOBAL_CALLOC(1, sizeof(struct sSemaphore));
|
||||
|
||||
if (self) {
|
||||
pthread_mutex_init(&(self->mutex), NULL);
|
||||
}
|
||||
|
||||
return (Semaphore)self;
|
||||
}
|
||||
|
||||
/* lock mutex */
|
||||
void
|
||||
Semaphore_wait(Semaphore self)
|
||||
{
|
||||
mSemaphore mSelf = (mSemaphore) self;
|
||||
|
||||
int retVal = pthread_mutex_lock(&(mSelf->mutex));
|
||||
|
||||
if (retVal) {
|
||||
printf("FATAL ERROR: pthread_mutex_lock failed (err=%i)\n", retVal);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/* unlock mutex */
|
||||
void
|
||||
Semaphore_post(Semaphore self)
|
||||
{
|
||||
mSemaphore mSelf = (mSemaphore) self;
|
||||
|
||||
int retVal = pthread_mutex_unlock(&(mSelf->mutex));
|
||||
|
||||
if (retVal) {
|
||||
printf("FATAL ERROR: pthread_mutex_unlock failed (err=%i)\n", retVal);
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Semaphore_destroy(Semaphore self)
|
||||
{
|
||||
if (self) {
|
||||
mSemaphore mSelf = (mSemaphore) self;
|
||||
|
||||
pthread_mutex_destroy(&(mSelf->mutex));
|
||||
|
||||
GLOBAL_FREEMEM(mSelf);
|
||||
}
|
||||
}
|
||||
|
||||
Thread
|
||||
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
|
||||
{
|
||||
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
|
||||
|
||||
if (thread != NULL) {
|
||||
thread->parameter = parameter;
|
||||
thread->function = function;
|
||||
thread->state = 0;
|
||||
thread->autodestroy = autodestroy;
|
||||
}
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
static void*
|
||||
destroyAutomaticThread(void* parameter)
|
||||
{
|
||||
Thread thread = (Thread) parameter;
|
||||
|
||||
thread->function(thread->parameter);
|
||||
|
||||
GLOBAL_FREEMEM(thread);
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
void
|
||||
Thread_start(Thread thread)
|
||||
{
|
||||
if (thread->autodestroy == true) {
|
||||
pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread);
|
||||
pthread_detach(thread->pthread);
|
||||
}
|
||||
else
|
||||
pthread_create(&thread->pthread, NULL, thread->function, thread->parameter);
|
||||
|
||||
thread->state = 1;
|
||||
}
|
||||
|
||||
void
|
||||
Thread_destroy(Thread thread)
|
||||
{
|
||||
if (thread->state == 1) {
|
||||
pthread_join(thread->pthread, NULL);
|
||||
}
|
||||
|
||||
GLOBAL_FREEMEM(thread);
|
||||
}
|
||||
|
||||
void
|
||||
Thread_sleep(int millies)
|
||||
{
|
||||
usleep(millies * 1000);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* thread_win32.c
|
||||
*
|
||||
* Copyright 2013-2021 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
#include "lib_memory.h"
|
||||
#include "hal_thread.h"
|
||||
|
||||
struct sThread {
|
||||
ThreadExecutionFunction function;
|
||||
void* parameter;
|
||||
HANDLE handle;
|
||||
int state;
|
||||
bool autodestroy;
|
||||
};
|
||||
|
||||
static DWORD WINAPI
|
||||
destroyAutomaticThreadRunner(LPVOID parameter)
|
||||
{
|
||||
Thread thread = (Thread) parameter;
|
||||
|
||||
thread->function(thread->parameter);
|
||||
|
||||
thread->state = 0;
|
||||
|
||||
Thread_destroy(thread);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static DWORD WINAPI
|
||||
threadRunner(LPVOID parameter)
|
||||
{
|
||||
Thread thread = (Thread) parameter;
|
||||
|
||||
thread->function(thread->parameter);
|
||||
|
||||
return (DWORD)0;
|
||||
}
|
||||
|
||||
Thread
|
||||
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
|
||||
{
|
||||
DWORD threadId;
|
||||
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
|
||||
|
||||
thread->parameter = parameter;
|
||||
thread->function = function;
|
||||
thread->state = 0;
|
||||
thread->autodestroy = autodestroy;
|
||||
|
||||
if (autodestroy == true)
|
||||
thread->handle = CreateThread(0, 0, destroyAutomaticThreadRunner, thread, CREATE_SUSPENDED, &threadId);
|
||||
else
|
||||
thread->handle = CreateThread(0, 0, threadRunner, thread, CREATE_SUSPENDED, &threadId);
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
void
|
||||
Thread_start(Thread thread)
|
||||
{
|
||||
thread->state = 1;
|
||||
ResumeThread(thread->handle);
|
||||
}
|
||||
|
||||
void
|
||||
Thread_destroy(Thread thread)
|
||||
{
|
||||
if (thread->state == 1)
|
||||
WaitForSingleObject(thread->handle, INFINITE);
|
||||
|
||||
CloseHandle(thread->handle);
|
||||
|
||||
GLOBAL_FREEMEM(thread);
|
||||
}
|
||||
|
||||
void
|
||||
Thread_sleep(int millies)
|
||||
{
|
||||
Sleep(millies);
|
||||
}
|
||||
|
||||
Semaphore
|
||||
Semaphore_create(int initialValue)
|
||||
{
|
||||
HANDLE self = CreateSemaphore(NULL, 1, 1, NULL);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* Wait until semaphore value is greater than zero. Then decrease the semaphore value. */
|
||||
void
|
||||
Semaphore_wait(Semaphore self)
|
||||
{
|
||||
WaitForSingleObject((HANDLE) self, INFINITE);
|
||||
}
|
||||
|
||||
void
|
||||
Semaphore_post(Semaphore self)
|
||||
{
|
||||
ReleaseSemaphore((HANDLE) self, 1, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
Semaphore_destroy(Semaphore self)
|
||||
{
|
||||
CloseHandle((HANDLE) self);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* time.c
|
||||
*
|
||||
* Copyright 2013-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#include "hal_time.h"
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
msSinceEpoch
|
||||
Hal_getTimeInMs()
|
||||
{
|
||||
struct timeval now;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
|
||||
return ((uint64_t) now.tv_sec * 1000LL) + (now.tv_usec / 1000);
|
||||
}
|
||||
|
||||
nsSinceEpoch
|
||||
Hal_getTimeInNs()
|
||||
{
|
||||
struct timespec now;
|
||||
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
|
||||
nsSinceEpoch nsTime = (nsSinceEpoch)(now.tv_sec) * 1000000000UL;
|
||||
nsTime += (nsSinceEpoch)(now.tv_nsec);
|
||||
|
||||
return nsTime;
|
||||
}
|
||||
|
||||
bool
|
||||
Hal_setTimeInNs(nsSinceEpoch nsTime)
|
||||
{
|
||||
struct timespec tv;
|
||||
|
||||
tv.tv_sec = nsTime / 1000000000UL;
|
||||
tv.tv_nsec = nsTime % 1000000000UL;
|
||||
|
||||
if (clock_settime(CLOCK_REALTIME, &tv) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
msSinceEpoch
|
||||
Hal_getMonotonicTimeInMs()
|
||||
{
|
||||
uint64_t timeVal = 0;
|
||||
|
||||
struct timespec ts;
|
||||
|
||||
if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
|
||||
{
|
||||
timeVal = ((uint64_t)ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000);
|
||||
}
|
||||
|
||||
return timeVal;
|
||||
}
|
||||
|
||||
nsSinceEpoch
|
||||
Hal_getMonotonicTimeInNs()
|
||||
{
|
||||
uint64_t nsTime = 0;
|
||||
|
||||
struct timespec ts;
|
||||
|
||||
if (clock_gettime(CLOCK_REALTIME, &ts) == 0)
|
||||
{
|
||||
nsTime = ts.tv_sec * 1000000000UL;
|
||||
nsTime += ts.tv_nsec;
|
||||
}
|
||||
|
||||
return nsTime;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* time.c
|
||||
*
|
||||
* Copyright 2013-2024 Michael Zillgith
|
||||
*
|
||||
* This file is part of Platform Abstraction Layer (libpal)
|
||||
* for libiec61850, libmms, and lib60870.
|
||||
*/
|
||||
|
||||
#include "hal_time.h"
|
||||
#include <time.h>
|
||||
#include <windows.h>
|
||||
|
||||
uint64_t
|
||||
Hal_getTimeInMs()
|
||||
{
|
||||
FILETIME ft;
|
||||
uint64_t now;
|
||||
|
||||
static const uint64_t DIFF_TO_UNIXTIME = 11644473600000ULL;
|
||||
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
|
||||
now = (LONGLONG)ft.dwLowDateTime + ((LONGLONG)(ft.dwHighDateTime) << 32LL);
|
||||
|
||||
return (now / 10000LL) - DIFF_TO_UNIXTIME;
|
||||
}
|
||||
|
||||
nsSinceEpoch
|
||||
Hal_getTimeInNs()
|
||||
{
|
||||
FILETIME ft;
|
||||
|
||||
static const uint64_t DIFF_TO_UNIXTIME = 11644473600000000000ULL;
|
||||
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
|
||||
nsSinceEpoch nsTime = (LONGLONG)ft.dwLowDateTime + ((LONGLONG)(ft.dwHighDateTime) << 32LL);
|
||||
|
||||
nsTime = nsTime * 100LL - DIFF_TO_UNIXTIME;
|
||||
|
||||
return nsTime;
|
||||
}
|
||||
|
||||
bool
|
||||
Hal_setTimeInNs(nsSinceEpoch nsTime)
|
||||
{
|
||||
uint64_t t = (nsTime / 100ULL) + 116444736000000000ULL;
|
||||
|
||||
FILETIME ft;
|
||||
|
||||
ft.dwLowDateTime = (uint32_t)(t & 0xffffffff);
|
||||
ft.dwHighDateTime = (uint32_t)(t >> 32);
|
||||
|
||||
SYSTEMTIME st;
|
||||
|
||||
FileTimeToSystemTime(&ft, &st);
|
||||
|
||||
return SetSystemTime(&st);
|
||||
}
|
||||
|
||||
msSinceEpoch
|
||||
Hal_getMonotonicTimeInMs()
|
||||
{
|
||||
return (msSinceEpoch)GetTickCount64();
|
||||
}
|
||||
|
||||
nsSinceEpoch
|
||||
Hal_getMonotonicTimeInNs()
|
||||
{
|
||||
return (nsSinceEpoch)(GetTickCount64() * 1000000ULL);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#ifndef MBEDTLS_CONFIG_H
|
||||
#define MBEDTLS_CONFIG_H
|
||||
|
||||
/* System support */
|
||||
#define MBEDTLS_HAVE_ASM
|
||||
#define MBEDTLS_HAVE_TIME
|
||||
#define MBEDTLS_HAVE_TIME_DATE
|
||||
#define MBEDTLS_NO_UDBL_DIVISION
|
||||
#define MBEDTLS_PLATFORM_C
|
||||
#define MBEDTLS_DEBUG_C
|
||||
|
||||
/* mbed TLS feature support */
|
||||
#define MBEDTLS_CIPHER_MODE_CBC
|
||||
#define MBEDTLS_PKCS1_V15
|
||||
#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
|
||||
#define MBEDTLS_SSL_PROTO_TLS1_2
|
||||
#define MBEDTLS_SSL_PROTO_TLS1_1
|
||||
#define MBEDTLS_SSL_PROTO_TLS1
|
||||
#define MBEDTLS_SSL_RENEGOTIATION
|
||||
#define MBEDTLS_ENABLE_WEAK_CIPHERSUITES
|
||||
#define MBEDTLS_CIPHER_NULL_CIPHER
|
||||
|
||||
#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_CERTIFICATES
|
||||
|
||||
/* mbed TLS modules */
|
||||
#define MBEDTLS_GCM_C
|
||||
#define MBEDTLS_AES_C
|
||||
#define MBEDTLS_ASN1_PARSE_C
|
||||
#define MBEDTLS_ASN1_WRITE_C
|
||||
#define MBEDTLS_BIGNUM_C
|
||||
#define MBEDTLS_CIPHER_C
|
||||
#define MBEDTLS_CTR_DRBG_C
|
||||
/* #define MBEDTLS_DES_C */
|
||||
#define MBEDTLS_ENTROPY_C
|
||||
#define MBEDTLS_ERROR_C
|
||||
#define MBEDTLS_MD_C
|
||||
#define MBEDTLS_MD5_C
|
||||
#define MBEDTLS_NET_C
|
||||
#define MBEDTLS_NIST_KW_C
|
||||
#define MBEDTLS_OID_C
|
||||
#define MBEDTLS_PK_C
|
||||
#define MBEDTLS_PK_PARSE_C
|
||||
#define MBEDTLS_RSA_C
|
||||
#define MBEDTLS_SHA1_C
|
||||
#define MBEDTLS_SHA256_C
|
||||
#define MBEDTLS_SSL_CLI_C
|
||||
#define MBEDTLS_SSL_SRV_C
|
||||
#define MBEDTLS_SSL_TLS_C
|
||||
#define MBEDTLS_X509_CRT_PARSE_C
|
||||
#define MBEDTLS_X509_CRL_PARSE_C
|
||||
#define MBEDTLS_X509_USE_C
|
||||
#define MBEDTLS_SSL_CACHE_C
|
||||
|
||||
/* For test certificates */
|
||||
#define MBEDTLS_BASE64_C
|
||||
#define MBEDTLS_CERTS_C
|
||||
#define MBEDTLS_PEM_PARSE_C
|
||||
|
||||
#define MBEDTLS_PKCS12_C
|
||||
#define MBEDTLS_PKCS5_C
|
||||
|
||||
/* For testing with compat.sh */
|
||||
#define MBEDTLS_FS_IO
|
||||
|
||||
#define MBEDTLS_X509_CHECK_KEY_USAGE
|
||||
#define MBEDTLS_X509_CHECK_EXTENDED_KEY_USAGE
|
||||
|
||||
#include "mbedtls/check_config.h"
|
||||
|
||||
#endif /* MBEDTLS_CONFIG_H */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,73 @@
|
||||
// https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#introduce-a-level-of-indirection-and-versioning-in-the-config-files
|
||||
// #ifndef MBEDTLS_CONFIG_H
|
||||
// #define MBEDTLS_CONFIG_H
|
||||
|
||||
/* System support */
|
||||
#define MBEDTLS_HAVE_ASM
|
||||
#define MBEDTLS_HAVE_TIME
|
||||
#define MBEDTLS_HAVE_TIME_DATE
|
||||
#define MBEDTLS_NO_UDBL_DIVISION
|
||||
#define MBEDTLS_PLATFORM_C
|
||||
#define MBEDTLS_DEBUG_C
|
||||
|
||||
/* mbed TLS feature support */
|
||||
#define MBEDTLS_CIPHER_MODE_CBC
|
||||
#define MBEDTLS_PKCS1_V15
|
||||
#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
|
||||
#define MBEDTLS_SSL_PROTO_TLS1_2
|
||||
#define MBEDTLS_SSL_PROTO_TLS1_3
|
||||
// MIGRATE 2.28->3.x.x: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#remove-support-for-tls-10-11-and-dtls-10
|
||||
// #define MBEDTLS_SSL_PROTO_TLS1_1
|
||||
// #define MBEDTLS_SSL_PROTO_TLS1
|
||||
#define MBEDTLS_SSL_RENEGOTIATION
|
||||
|
||||
#error "MBEDTLS"
|
||||
|
||||
#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_CERTIFICATES
|
||||
|
||||
/* mbed TLS modules */
|
||||
#define MBEDTLS_GCM_C
|
||||
#define MBEDTLS_AES_C
|
||||
#define MBEDTLS_ASN1_PARSE_C
|
||||
#define MBEDTLS_ASN1_WRITE_C
|
||||
#define MBEDTLS_BIGNUM_C
|
||||
#define MBEDTLS_CIPHER_C
|
||||
#define MBEDTLS_CTR_DRBG_C
|
||||
/* #define MBEDTLS_DES_C */
|
||||
#define MBEDTLS_ENTROPY_C
|
||||
#define MBEDTLS_MD_C
|
||||
#define MBEDTLS_MD5_C
|
||||
#define MBEDTLS_NET_C
|
||||
#define MBEDTLS_OID_C
|
||||
#define MBEDTLS_PK_C
|
||||
#define MBEDTLS_PK_PARSE_C
|
||||
#define MBEDTLS_RSA_C
|
||||
#define MBEDTLS_SHA1_C
|
||||
#define MBEDTLS_SHA256_C
|
||||
#define MBEDTLS_SSL_CLI_C
|
||||
#define MBEDTLS_SSL_SRV_C
|
||||
#define MBEDTLS_SSL_TLS_C
|
||||
#define MBEDTLS_X509_CRT_PARSE_C
|
||||
#define MBEDTLS_X509_CRL_PARSE_C
|
||||
#define MBEDTLS_X509_USE_C
|
||||
#define MBEDTLS_SSL_CACHE_C
|
||||
|
||||
/* For test certificates */
|
||||
#define MBEDTLS_BASE64_C
|
||||
#define MBEDTLS_CERTS_C
|
||||
#define MBEDTLS_PEM_PARSE_C
|
||||
|
||||
#define MBEDTLS_PKCS12_C
|
||||
#define MBEDTLS_PKCS5_C
|
||||
|
||||
/* For testing with compat.sh */
|
||||
#define MBEDTLS_FS_IO
|
||||
|
||||
// MIGRATE 2.28->3.x.x: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#remove-mbedtls_x509_check__key_usage-options-from-mbedtls_configh
|
||||
// #define MBEDTLS_X509_CHECK_KEY_USAGE
|
||||
// #define MBEDTLS_X509_CHECK_EXTENDED_KEY_USAGE
|
||||
|
||||
// MIGRATE 2.28->3.x.x: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#introduce-a-level-of-indirection-and-versioning-in-the-config-files
|
||||
// #include "mbedtls/check_config.h"
|
||||
|
||||
// #endif /* MBEDTLS_CONFIG_H */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,685 @@
|
||||
/*
|
||||
* cpXXtime2a.c
|
||||
*
|
||||
* Implementation of the types CP16Time2a, CP24Time2a and CP56Time2a
|
||||
*
|
||||
* Copyright 2016-2022 Michael Zillgith
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "lib_memory.h"
|
||||
|
||||
#include "iec60870_common.h"
|
||||
#include "apl_types_internal.h"
|
||||
|
||||
/**********************************
|
||||
* CP16Time2a type
|
||||
**********************************/
|
||||
|
||||
bool
|
||||
CP16Time2a_getFromBuffer (CP16Time2a self, const uint8_t* msg, int msgSize, int startIndex)
|
||||
{
|
||||
if (msgSize < startIndex + 2)
|
||||
return false;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 2; i++)
|
||||
self->encodedValue[i] = msg[startIndex + i];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
CP16Time2a_getEplapsedTimeInMs(const CP16Time2a self)
|
||||
{
|
||||
return (self->encodedValue[0] + (self->encodedValue[1] * 0x100));
|
||||
}
|
||||
|
||||
void
|
||||
CP16Time2a_setEplapsedTimeInMs(CP16Time2a self, int value)
|
||||
{
|
||||
self->encodedValue[0] = (uint8_t) (value % 0x100);
|
||||
self->encodedValue[1] = (uint8_t) (value / 0x100);
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
CP16Time2a_getEncodedValue(CP16Time2a self)
|
||||
{
|
||||
return self->encodedValue;
|
||||
}
|
||||
|
||||
/************************************
|
||||
* CP24Time2a and CP26Time2a common
|
||||
************************************/
|
||||
|
||||
static int
|
||||
getMillisecond(const uint8_t* encodedValue)
|
||||
{
|
||||
return (encodedValue[0] + (encodedValue[1] * 0x100)) % 1000;
|
||||
}
|
||||
|
||||
static void
|
||||
setMillisecond(uint8_t* encodedValue, int value)
|
||||
{
|
||||
int millies = encodedValue[0] + (encodedValue[1] * 0x100);
|
||||
|
||||
/* erase sub-second part */
|
||||
millies = millies - (millies % 1000);
|
||||
|
||||
millies = millies + value;
|
||||
|
||||
encodedValue[0] = (uint8_t) (millies & 0xff);
|
||||
encodedValue[1] = (uint8_t) ((millies / 0x100) & 0xff);
|
||||
}
|
||||
|
||||
static int
|
||||
getSecond(const uint8_t* encodedValue)
|
||||
{
|
||||
return (encodedValue[0] + (encodedValue[1] * 0x100)) / 1000;
|
||||
}
|
||||
|
||||
static void
|
||||
setSecond(uint8_t* encodedValue, int value)
|
||||
{
|
||||
int millies = encodedValue[0] + (encodedValue[1] * 0x100);
|
||||
|
||||
int msPart = millies % 1000;
|
||||
|
||||
millies = (value * 1000) + msPart;
|
||||
|
||||
encodedValue[0] = (uint8_t) (millies & 0xff);
|
||||
encodedValue[1] = (uint8_t) ((millies / 0x100) & 0xff);
|
||||
}
|
||||
|
||||
static int
|
||||
getMinute(const uint8_t* encodedValue)
|
||||
{
|
||||
return (encodedValue[2] & 0x3f);
|
||||
}
|
||||
|
||||
static void
|
||||
setMinute(uint8_t* encodedValue, int value)
|
||||
{
|
||||
encodedValue[2] = (uint8_t) ((encodedValue[2] & 0xc0) | (value & 0x3f));
|
||||
}
|
||||
|
||||
static bool
|
||||
isInvalid(const uint8_t* encodedValue)
|
||||
{
|
||||
return ((encodedValue[2] & 0x80) != 0);
|
||||
}
|
||||
|
||||
static void
|
||||
setInvalid(uint8_t* encodedValue, bool value)
|
||||
{
|
||||
if (value)
|
||||
encodedValue[2] |= 0x80;
|
||||
else
|
||||
encodedValue[2] &= 0x7f;
|
||||
}
|
||||
|
||||
static bool
|
||||
isSubstituted(const uint8_t* encodedValue)
|
||||
{
|
||||
return ((encodedValue[2] & 0x40) == 0x40);
|
||||
}
|
||||
|
||||
static void
|
||||
setSubstituted(uint8_t* encodedValue, bool value)
|
||||
{
|
||||
if (value)
|
||||
encodedValue[2] |= 0x40;
|
||||
else
|
||||
encodedValue[2] &= 0xbf;
|
||||
}
|
||||
|
||||
/**********************************
|
||||
* CP24Time2a type
|
||||
**********************************/
|
||||
|
||||
bool
|
||||
CP24Time2a_getFromBuffer (CP24Time2a self, const uint8_t* msg, int msgSize, int startIndex)
|
||||
{
|
||||
if (msgSize < startIndex + 3)
|
||||
return false;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
self->encodedValue[i] = msg[startIndex + i];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
CP24Time2a_getMillisecond(const CP24Time2a self)
|
||||
{
|
||||
return getMillisecond(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP24Time2a_setMillisecond(CP24Time2a self, int value)
|
||||
{
|
||||
setMillisecond(self->encodedValue, value);
|
||||
}
|
||||
|
||||
int
|
||||
CP24Time2a_getSecond(const CP24Time2a self)
|
||||
{
|
||||
return getSecond(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP24Time2a_setSecond(CP24Time2a self, int value)
|
||||
{
|
||||
setSecond(self->encodedValue, value);
|
||||
}
|
||||
|
||||
int
|
||||
CP24Time2a_getMinute(const CP24Time2a self)
|
||||
{
|
||||
return getMinute(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP24Time2a_setMinute(CP24Time2a self, int value)
|
||||
{
|
||||
setMinute(self->encodedValue, value);
|
||||
}
|
||||
|
||||
bool
|
||||
CP24Time2a_isInvalid(const CP24Time2a self)
|
||||
{
|
||||
return isInvalid(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP24Time2a_setInvalid(CP24Time2a self, bool value)
|
||||
{
|
||||
setInvalid(self->encodedValue, value);
|
||||
}
|
||||
|
||||
bool
|
||||
CP24Time2a_isSubstituted(const CP24Time2a self)
|
||||
{
|
||||
return isSubstituted(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP24Time2a_setSubstituted(CP24Time2a self, bool value)
|
||||
{
|
||||
setSubstituted(self->encodedValue, value);
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* function found here: http://stackoverflow.com/questions/530519/stdmktime-and-timezone-info */
|
||||
time_t my_timegm(register struct tm * t)
|
||||
/* struct tm to seconds since Unix epoch */
|
||||
{
|
||||
register long year;
|
||||
register time_t result;
|
||||
#define MONTHSPERYEAR 12 /* months per calendar year */
|
||||
static const int cumdays[MONTHSPERYEAR] =
|
||||
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
|
||||
|
||||
/*@ +matchanyintegral @*/
|
||||
year = 1900 + t->tm_year + t->tm_mon / MONTHSPERYEAR;
|
||||
result = (year - 1970) * 365 + cumdays[t->tm_mon % MONTHSPERYEAR];
|
||||
result += (year - 1968) / 4;
|
||||
result -= (year - 1900) / 100;
|
||||
result += (year - 1600) / 400;
|
||||
if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0) &&
|
||||
(t->tm_mon % MONTHSPERYEAR) < 2)
|
||||
result--;
|
||||
result += t->tm_mday - 1;
|
||||
result *= 24;
|
||||
result += t->tm_hour;
|
||||
result *= 60;
|
||||
result += t->tm_min;
|
||||
result *= 60;
|
||||
result += t->tm_sec;
|
||||
if (t->tm_isdst == 1)
|
||||
result -= 3600;
|
||||
/*@ -matchanyintegral @*/
|
||||
return (result);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Conversion from UTC date to second, unsigned 32-bit Unix epoch version.
|
||||
* Written by François Grieu, 2015-07-21; public domain.
|
||||
*
|
||||
* my_mktime converts from struct tm UTC to non-leap seconds since
|
||||
* 00:00:00 on the first UTC day of year 1970 (fixed).
|
||||
* It works from 1970 to 2105 inclusive. It strives to be compatible
|
||||
* with C compilers supporting // comments and claiming C89 conformance.
|
||||
*
|
||||
* input: Pointer to a struct tm with field tm_year, tm_mon, tm_mday,
|
||||
* tm_hour, tm_min, tm_sec set per mktime convention; thus
|
||||
* - tm_year is year minus 1900
|
||||
* - tm_mon is [0..11] for January to December, but [-2..14]
|
||||
* works for November of previous year to February of next year
|
||||
* - tm_mday, tm_hour, tm_min, tm_sec similarly can be offset to
|
||||
* the full range [-32767 to 32768], as long as the combination
|
||||
* with tm_year gives a result within years [1970..2105], and
|
||||
* tm_year>0.
|
||||
* output: Number of non-leap seconds since beginning of the first UTC
|
||||
* day of year 1970, as an unsigned at-least-32-bit integer.
|
||||
* The input is not changed (in particular, fields tm_wday,
|
||||
* tm_yday, and tm_isdst are unchanged and ignored).
|
||||
*/
|
||||
static time_t
|
||||
my_mktime(const struct tm * ptm)
|
||||
{
|
||||
int m, y = ptm->tm_year;
|
||||
|
||||
if ((m = ptm->tm_mon) < 2) {
|
||||
m += 12;
|
||||
--y;
|
||||
}
|
||||
|
||||
return ((((time_t) (y - 69) * 365u + y / 4 - y / 100 * 3 / 4 + (m + 2) * 153 / 5 - 446 +
|
||||
ptm->tm_mday) * 24u + ptm->tm_hour) * 60u + ptm->tm_min) * 60u + ptm->tm_sec;
|
||||
}
|
||||
|
||||
/**********************************
|
||||
* CP32Time2a type
|
||||
**********************************/
|
||||
|
||||
CP32Time2a
|
||||
CP32Time2a_create(CP32Time2a self)
|
||||
{
|
||||
if (self == NULL)
|
||||
self = (CP32Time2a) GLOBAL_CALLOC(1, sizeof(struct sCP32Time2a));
|
||||
else
|
||||
memset (self, 0, sizeof(struct sCP32Time2a));
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
bool
|
||||
CP32Time2a_getFromBuffer (CP32Time2a self, const uint8_t* msg, int msgSize, int startIndex)
|
||||
{
|
||||
if (msgSize < startIndex + 4)
|
||||
return false;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 4; i++)
|
||||
self->encodedValue[i] = msg[startIndex + i];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
CP32Time2a_getMillisecond(const CP32Time2a self)
|
||||
{
|
||||
return (self->encodedValue[0] + (self->encodedValue[1] * 0x100)) % 1000;
|
||||
}
|
||||
|
||||
void
|
||||
CP32Time2a_setMillisecond(CP32Time2a self, int value)
|
||||
{
|
||||
int millies = (CP32Time2a_getSecond(self) * 1000) + value;
|
||||
|
||||
self->encodedValue[0] = (uint8_t) (millies & 0xff);
|
||||
self->encodedValue[1] = (uint8_t) ((millies / 0x100) & 0xff);
|
||||
}
|
||||
|
||||
int
|
||||
CP32Time2a_getSecond(const CP32Time2a self)
|
||||
{
|
||||
return getSecond(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP32Time2a_setSecond(CP32Time2a self, int value)
|
||||
{
|
||||
setSecond(self->encodedValue, value);
|
||||
}
|
||||
|
||||
int
|
||||
CP32Time2a_getMinute(const CP32Time2a self)
|
||||
{
|
||||
return getMinute(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP32Time2a_setMinute(CP32Time2a self, int value)
|
||||
{
|
||||
setMinute(self->encodedValue, value);
|
||||
}
|
||||
|
||||
bool
|
||||
CP32Time2a_isInvalid(const CP32Time2a self)
|
||||
{
|
||||
return isInvalid(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP32Time2a_setInvalid(CP32Time2a self, bool value)
|
||||
{
|
||||
setInvalid(self->encodedValue, value);
|
||||
}
|
||||
|
||||
bool
|
||||
CP32Time2a_isSubstituted(const CP32Time2a self)
|
||||
{
|
||||
return isSubstituted(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP32Time2a_setSubstituted(CP32Time2a self, bool value)
|
||||
{
|
||||
setSubstituted(self->encodedValue, value);
|
||||
}
|
||||
|
||||
int
|
||||
CP32Time2a_getHour(const CP32Time2a self)
|
||||
{
|
||||
return (self->encodedValue[3] & 0x1f);
|
||||
}
|
||||
|
||||
void
|
||||
CP32Time2a_setHour(CP32Time2a self, int value)
|
||||
{
|
||||
self->encodedValue[3] = (uint8_t) ((self->encodedValue[3] & 0xe0) | (value & 0x1f));
|
||||
}
|
||||
|
||||
bool
|
||||
CP32Time2a_isSummerTime(const CP32Time2a self)
|
||||
{
|
||||
return ((self->encodedValue[3] & 0x80) != 0);
|
||||
}
|
||||
|
||||
void
|
||||
CP32Time2a_setSummerTime(CP32Time2a self, bool value)
|
||||
{
|
||||
if (value)
|
||||
self->encodedValue[3] |= 0x80;
|
||||
else
|
||||
self->encodedValue[3] &= 0x7f;
|
||||
}
|
||||
|
||||
void
|
||||
CP32Time2a_setFromMsTimestamp(CP32Time2a self, uint64_t timestamp)
|
||||
{
|
||||
memset(self->encodedValue, 0, 4);
|
||||
|
||||
time_t timeVal = timestamp / 1000;
|
||||
|
||||
int msPart = timestamp % 1000;
|
||||
|
||||
struct tm tmTime;
|
||||
|
||||
#ifdef _WIN32
|
||||
gmtime_s(&tmTime, &timeVal);
|
||||
#else
|
||||
gmtime_r(&timeVal, &tmTime);
|
||||
#endif
|
||||
|
||||
CP32Time2a_setSecond(self, tmTime.tm_sec);
|
||||
|
||||
CP32Time2a_setMillisecond(self, msPart);
|
||||
|
||||
CP32Time2a_setMinute(self, tmTime.tm_min);
|
||||
|
||||
CP32Time2a_setHour(self, tmTime.tm_hour);
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
CP32Time2a_getEncodedValue(CP32Time2a self)
|
||||
{
|
||||
return self->encodedValue;
|
||||
}
|
||||
|
||||
/**********************************
|
||||
* CP56Time2a type
|
||||
**********************************/
|
||||
|
||||
CP56Time2a
|
||||
CP56Time2a_createFromMsTimestamp(CP56Time2a self, uint64_t timestamp)
|
||||
{
|
||||
if (self == NULL)
|
||||
self = (CP56Time2a) GLOBAL_CALLOC(1, sizeof(struct sCP56Time2a));
|
||||
else
|
||||
memset (self, 0, sizeof(struct sCP56Time2a));
|
||||
|
||||
if (self != NULL)
|
||||
CP56Time2a_setFromMsTimestamp(self, timestamp);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setFromMsTimestamp(CP56Time2a self, uint64_t timestamp)
|
||||
{
|
||||
memset(self->encodedValue, 0, 7);
|
||||
|
||||
time_t timeVal = timestamp / 1000;
|
||||
int msPart = timestamp % 1000;
|
||||
|
||||
struct tm tmTime;
|
||||
|
||||
/* TODO replace with portable implementation */
|
||||
#ifdef _WIN32
|
||||
gmtime_s(&tmTime, &timeVal);
|
||||
#else
|
||||
gmtime_r(&timeVal, &tmTime);
|
||||
#endif
|
||||
|
||||
CP56Time2a_setMillisecond(self, msPart);
|
||||
|
||||
CP56Time2a_setSecond(self, tmTime.tm_sec);
|
||||
|
||||
CP56Time2a_setMinute(self, tmTime.tm_min);
|
||||
|
||||
CP56Time2a_setHour(self, tmTime.tm_hour);
|
||||
|
||||
CP56Time2a_setDayOfMonth(self, tmTime.tm_mday);
|
||||
|
||||
/* set day of week to 0 = not present */
|
||||
CP56Time2a_setDayOfWeek(self, 0);
|
||||
|
||||
CP56Time2a_setMonth(self, tmTime.tm_mon + 1);
|
||||
|
||||
CP56Time2a_setYear(self, tmTime.tm_year);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
CP56Time2a_toMsTimestamp(const CP56Time2a self)
|
||||
{
|
||||
struct tm tmTime;
|
||||
|
||||
tmTime.tm_sec = CP56Time2a_getSecond(self);
|
||||
tmTime.tm_min = CP56Time2a_getMinute(self);
|
||||
tmTime.tm_hour = CP56Time2a_getHour(self);
|
||||
tmTime.tm_mday = CP56Time2a_getDayOfMonth(self);
|
||||
tmTime.tm_mon = CP56Time2a_getMonth(self) - 1;
|
||||
tmTime.tm_year = CP56Time2a_getYear(self) + 100;
|
||||
|
||||
time_t timestamp = my_mktime(&tmTime);
|
||||
|
||||
uint64_t msTimestamp = ((uint64_t) (timestamp * (uint64_t) 1000)) + CP56Time2a_getMillisecond(self);
|
||||
|
||||
return msTimestamp;
|
||||
}
|
||||
|
||||
/* private */ bool
|
||||
CP56Time2a_getFromBuffer(CP56Time2a self, const uint8_t* msg, int msgSize, int startIndex)
|
||||
{
|
||||
if (msgSize < startIndex + 7)
|
||||
return false;
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < 7; i++)
|
||||
self->encodedValue[i] = msg[startIndex + i];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
CP56Time2a_getMillisecond(const CP56Time2a self)
|
||||
{
|
||||
return getMillisecond(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setMillisecond(CP56Time2a self, int value)
|
||||
{
|
||||
setMillisecond(self->encodedValue, value);
|
||||
}
|
||||
|
||||
int
|
||||
CP56Time2a_getSecond(const CP56Time2a self)
|
||||
{
|
||||
return getSecond(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setSecond(CP56Time2a self, int value)
|
||||
{
|
||||
setSecond(self->encodedValue, value);
|
||||
}
|
||||
|
||||
int
|
||||
CP56Time2a_getMinute(const CP56Time2a self)
|
||||
{
|
||||
return getMinute(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setMinute(CP56Time2a self, int value)
|
||||
{
|
||||
setMinute(self->encodedValue, value);
|
||||
}
|
||||
|
||||
int
|
||||
CP56Time2a_getHour(const CP56Time2a self)
|
||||
{
|
||||
return (self->encodedValue[3] & 0x1f);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setHour(CP56Time2a self, int value)
|
||||
{
|
||||
self->encodedValue[3] = (uint8_t) ((self->encodedValue[3] & 0xe0) | (value & 0x1f));
|
||||
}
|
||||
|
||||
int
|
||||
CP56Time2a_getDayOfWeek(const CP56Time2a self)
|
||||
{
|
||||
return ((self->encodedValue[4] & 0xe0) >> 5);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setDayOfWeek(CP56Time2a self, int value)
|
||||
{
|
||||
self->encodedValue[4] = (uint8_t) ((self->encodedValue[4] & 0x1f) | ((value & 0x07) << 5));
|
||||
}
|
||||
|
||||
int
|
||||
CP56Time2a_getDayOfMonth(const CP56Time2a self)
|
||||
{
|
||||
return (self->encodedValue[4] & 0x1f);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setDayOfMonth(CP56Time2a self, int value)
|
||||
{
|
||||
self->encodedValue[4] = (uint8_t) ((self->encodedValue[4] & 0xe0) + (value & 0x1f));
|
||||
}
|
||||
|
||||
int
|
||||
CP56Time2a_getMonth(const CP56Time2a self)
|
||||
{
|
||||
return (self->encodedValue[5] & 0x0f);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setMonth(CP56Time2a self, int value)
|
||||
{
|
||||
self->encodedValue[5] = (uint8_t) ((self->encodedValue[5] & 0xf0) + (value & 0x0f));
|
||||
}
|
||||
|
||||
int
|
||||
CP56Time2a_getYear(const CP56Time2a self)
|
||||
{
|
||||
return (self->encodedValue[6] & 0x7f);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setYear(CP56Time2a self, int value)
|
||||
{
|
||||
value = value % 100;
|
||||
|
||||
self->encodedValue[6] = (uint8_t) ((self->encodedValue[6] & 0x80) + (value & 0x7f));
|
||||
}
|
||||
|
||||
bool
|
||||
CP56Time2a_isSummerTime(const CP56Time2a self)
|
||||
{
|
||||
return ((self->encodedValue[3] & 0x80) != 0);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setSummerTime(CP56Time2a self, bool value)
|
||||
{
|
||||
if (value)
|
||||
self->encodedValue[3] |= 0x80;
|
||||
else
|
||||
self->encodedValue[3] &= 0x7f;
|
||||
}
|
||||
|
||||
bool
|
||||
CP56Time2a_isInvalid(const CP56Time2a self)
|
||||
{
|
||||
return isInvalid(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setInvalid(CP56Time2a self, bool value)
|
||||
{
|
||||
setInvalid(self->encodedValue, value);
|
||||
}
|
||||
|
||||
bool
|
||||
CP56Time2a_isSubstituted(const CP56Time2a self)
|
||||
{
|
||||
return isSubstituted(self->encodedValue);
|
||||
}
|
||||
|
||||
void
|
||||
CP56Time2a_setSubstituted(CP56Time2a self, bool value)
|
||||
{
|
||||
setSubstituted(self->encodedValue, value);
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
CP56Time2a_getEncodedValue(CP56Time2a self)
|
||||
{
|
||||
return self->encodedValue;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* bcr.c
|
||||
*
|
||||
* Implementation of Binary Counter Reading (BCR) data type.
|
||||
*
|
||||
* Copyright 2016 MZ Automation GmbH
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#include "iec60870_common.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "platform_endian.h"
|
||||
#include "apl_types_internal.h"
|
||||
#include "lib_memory.h"
|
||||
|
||||
BinaryCounterReading
|
||||
BinaryCounterReading_create(BinaryCounterReading self, int32_t value, int seqNumber,
|
||||
bool hasCarry, bool isAdjusted, bool isInvalid)
|
||||
{
|
||||
if (self == NULL)
|
||||
self = (BinaryCounterReading) GLOBAL_MALLOC(sizeof(struct sBinaryCounterReading));
|
||||
|
||||
if (self != NULL) {
|
||||
BinaryCounterReading_setValue(self, value);
|
||||
BinaryCounterReading_setSequenceNumber(self, seqNumber);
|
||||
BinaryCounterReading_setCarry(self, hasCarry);
|
||||
BinaryCounterReading_setAdjusted(self, isAdjusted);
|
||||
BinaryCounterReading_setInvalid(self, isInvalid);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
void
|
||||
BinaryCounterReading_destroy(BinaryCounterReading self)
|
||||
{
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
|
||||
int32_t
|
||||
BinaryCounterReading_getValue(BinaryCounterReading self)
|
||||
{
|
||||
int32_t value;
|
||||
|
||||
uint8_t* valueBytes = (uint8_t*) &(value);
|
||||
uint8_t* encodedValue = self->encodedValue;
|
||||
|
||||
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||
valueBytes[0] = encodedValue[0];
|
||||
valueBytes[1] = encodedValue[1];
|
||||
valueBytes[2] = encodedValue[2];
|
||||
valueBytes[3] = encodedValue[3];
|
||||
#else
|
||||
valueBytes[0] = encodedValue[3];
|
||||
valueBytes[1] = encodedValue[2];
|
||||
valueBytes[2] = encodedValue[1];
|
||||
valueBytes[3] = encodedValue[0];
|
||||
#endif
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void
|
||||
BinaryCounterReading_setValue(BinaryCounterReading self, int32_t value)
|
||||
{
|
||||
uint8_t* valueBytes = (uint8_t*) &(value);
|
||||
uint8_t* encodedValue = self->encodedValue;
|
||||
|
||||
#if (ORDER_LITTLE_ENDIAN == 1)
|
||||
encodedValue[0] = valueBytes[0];
|
||||
encodedValue[1] = valueBytes[1];
|
||||
encodedValue[2] = valueBytes[2];
|
||||
encodedValue[3] = valueBytes[3];
|
||||
#else
|
||||
encodedValue[0] = valueBytes[3];
|
||||
encodedValue[1] = valueBytes[2];
|
||||
encodedValue[2] = valueBytes[1];
|
||||
encodedValue[3] = valueBytes[0];
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
BinaryCounterReading_getSequenceNumber(BinaryCounterReading self)
|
||||
{
|
||||
return (self->encodedValue[4] & 0x1f);
|
||||
}
|
||||
|
||||
void
|
||||
BinaryCounterReading_setSequenceNumber(BinaryCounterReading self, int value)
|
||||
{
|
||||
int seqNumber = value & 0x1f;
|
||||
int flags = self->encodedValue[4] & 0xe0;
|
||||
|
||||
self->encodedValue[4] = flags | seqNumber;
|
||||
}
|
||||
|
||||
bool
|
||||
BinaryCounterReading_hasCarry(BinaryCounterReading self)
|
||||
{
|
||||
return ((self->encodedValue[4] & 0x20) == 0x20);
|
||||
}
|
||||
|
||||
void
|
||||
BinaryCounterReading_setCarry(BinaryCounterReading self, bool value)
|
||||
{
|
||||
if (value)
|
||||
self->encodedValue[4] |= 0x20;
|
||||
else
|
||||
self->encodedValue[4] &= 0xdf;
|
||||
}
|
||||
|
||||
bool
|
||||
BinaryCounterReading_isAdjusted(BinaryCounterReading self)
|
||||
{
|
||||
return ((self->encodedValue[4] & 0x40) == 0x40);
|
||||
}
|
||||
|
||||
void
|
||||
BinaryCounterReading_setAdjusted(BinaryCounterReading self, bool value)
|
||||
{
|
||||
if (value)
|
||||
self->encodedValue[4] |= 0x40;
|
||||
else
|
||||
self->encodedValue[4] &= 0xbf;
|
||||
}
|
||||
|
||||
bool
|
||||
BinaryCounterReading_isInvalid(BinaryCounterReading self)
|
||||
{
|
||||
return ((self->encodedValue[4] & 0x80) == 0x80);
|
||||
}
|
||||
|
||||
void
|
||||
BinaryCounterReading_setInvalid(BinaryCounterReading self, bool value)
|
||||
{
|
||||
if (value)
|
||||
self->encodedValue[4] |= 0x80;
|
||||
else
|
||||
self->encodedValue[4] &= 0x7f;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,933 @@
|
||||
/*
|
||||
* cs101_master.c
|
||||
*
|
||||
* Copyright 2017-2025 Michael Zillgith
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "cs101_master.h"
|
||||
#include "apl_types_internal.h"
|
||||
#include "buffer_frame.h"
|
||||
#include "cs101_asdu_internal.h"
|
||||
#include "cs101_queue.h"
|
||||
#include "lib60870_config.h"
|
||||
#include "lib60870_internal.h"
|
||||
#include "lib_memory.h"
|
||||
#include "linked_list.h"
|
||||
#include "link_layer.h"
|
||||
#include "serial_transceiver_ft_1_2.h"
|
||||
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
#include "sec_endpoint_int.h"
|
||||
#endif
|
||||
|
||||
struct sCS101_Master
|
||||
{
|
||||
SerialPort serialPort;
|
||||
|
||||
struct sLinkLayerParameters linkLayerParameters;
|
||||
struct sCS101_AppLayerParameters alParameters;
|
||||
|
||||
SerialTransceiverFT12 transceiver;
|
||||
|
||||
IEC60870_LinkLayerMode linkLayerMode;
|
||||
|
||||
LinkLayerBalanced balancedLinkLayer;
|
||||
|
||||
LinkLayerPrimaryUnbalanced unbalancedLinkLayer;
|
||||
int slaveAddress; /* address of the currently selected slave (unbalanced link layer only) */
|
||||
|
||||
CS101_ASDUReceivedHandler asduReceivedHandler;
|
||||
void* asduReceivedHandlerParameter;
|
||||
|
||||
struct sCS101_Queue userDataQueue;
|
||||
|
||||
#if (CONFIG_USE_THREADS == 1)
|
||||
bool isRunning;
|
||||
Thread workerThread;
|
||||
#endif
|
||||
|
||||
IEC60870_LinkLayerStateChangedHandler stateChangedHandler;
|
||||
void* stateChangedHandlerParameter;
|
||||
|
||||
LinkedList plugins; /* list of CS101_MasterPlugin references */
|
||||
|
||||
/* TODO for unbalanced mode - multiple peerConnection objects would be required?! */
|
||||
struct sIPeerConnection peerConnection;
|
||||
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
SecureEndpoint secureEndpoint;
|
||||
#endif
|
||||
};
|
||||
|
||||
static struct sCS101_AppLayerParameters defaultAppLayerParameters = {
|
||||
/* .sizeOfTypeId = */ 1,
|
||||
/* .sizeOfVSQ = */ 1,
|
||||
/* .sizeOfCOT = */ 2,
|
||||
/* .originatorAddress = */ 0,
|
||||
/* .sizeOfCA = */ 2,
|
||||
/* .sizeOfIOA = */ 3,
|
||||
/* .maxSizeOfASDU = */ 249
|
||||
};
|
||||
|
||||
/* returns true when the ASDU was handled by the plugin and can be discarded */
|
||||
static bool
|
||||
callPluginsToHandleAsdu(CS101_Master self, CS101_ASDU asdu)
|
||||
{
|
||||
bool retVal = false;
|
||||
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
if (self->secureEndpoint)
|
||||
{
|
||||
if (SecureEndpoint_asduReceived(self->secureEndpoint, &(self->peerConnection), asdu, NULL, 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif /* SEC_AUTH_60870_5_7 */
|
||||
|
||||
if (self->plugins)
|
||||
{
|
||||
LinkedList pluginElem = LinkedList_getNext(self->plugins);
|
||||
|
||||
while (pluginElem)
|
||||
{
|
||||
CS101_MasterPlugin plugin = (CS101_MasterPlugin)LinkedList_getData(pluginElem);
|
||||
|
||||
CS101_MasterPlugin_Result result = plugin->handleAsdu(plugin->parameter, &(self->peerConnection), asdu);
|
||||
|
||||
if (result == CS101_MASTER_PLUGIN_RESULT_HANDLED)
|
||||
{
|
||||
retVal = true;
|
||||
break;
|
||||
}
|
||||
|
||||
pluginElem = LinkedList_getNext(pluginElem);
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/********************************************
|
||||
* IBalancedApplicationLayer
|
||||
********************************************/
|
||||
static Frame
|
||||
IBalancedApplicationLayer_GetUserData(void* parameter, Frame frame)
|
||||
{
|
||||
CS101_Master self = (CS101_Master)parameter;
|
||||
|
||||
Frame ret = NULL;
|
||||
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
if (self->secureEndpoint)
|
||||
{
|
||||
ret = SecureEndpoint_getNextWaitingAsdu(self->secureEndpoint, frame);
|
||||
}
|
||||
#endif /* SEC_AUTH_60870_5_7 */
|
||||
|
||||
/* get ASDU from plugin when available */
|
||||
if (self->plugins && ret == NULL)
|
||||
{
|
||||
LinkedList pluginElem = LinkedList_getNext(self->plugins);
|
||||
|
||||
while (pluginElem)
|
||||
{
|
||||
CS101_MasterPlugin plugin = (CS101_MasterPlugin)LinkedList_getData(pluginElem);
|
||||
|
||||
ret = plugin->getNextAsduToSend(plugin->parameter, frame);
|
||||
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
pluginElem = LinkedList_getNext(pluginElem);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == NULL)
|
||||
{
|
||||
CS101_Queue_lock(&(self->userDataQueue));
|
||||
|
||||
ret = CS101_Queue_dequeue(&(self->userDataQueue), frame);
|
||||
|
||||
CS101_Queue_unlock(&(self->userDataQueue));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
IBalancedApplicationLayer_HandleReceivedData(void* parameter, uint8_t* msg, bool isBroadcast, int userDataStart,
|
||||
int userDataLength)
|
||||
{
|
||||
UNUSED_PARAMETER(isBroadcast);
|
||||
|
||||
CS101_Master self = (CS101_Master)parameter;
|
||||
|
||||
struct sCS101_ASDU _asdu;
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_createFromBufferEx(&_asdu, &(self->alParameters), msg + userDataStart, userDataLength);
|
||||
|
||||
if (asdu)
|
||||
{
|
||||
if (callPluginsToHandleAsdu(self, asdu) == false)
|
||||
{
|
||||
if (self->asduReceivedHandler)
|
||||
self->asduReceivedHandler(self->asduReceivedHandlerParameter, 0, asdu);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct sIBalancedApplicationLayer cs101BalancedAppLayerInterface = {
|
||||
IBalancedApplicationLayer_GetUserData,
|
||||
IBalancedApplicationLayer_HandleReceivedData
|
||||
};
|
||||
|
||||
/********************************************
|
||||
* END IBalancedApplicationLayer
|
||||
********************************************/
|
||||
|
||||
/********************************************
|
||||
* IPrimaryApplicationLayer
|
||||
********************************************/
|
||||
|
||||
static void
|
||||
IPrimaryApplicationLayer_AccessDemand(void* parameter, int slaveAddress)
|
||||
{
|
||||
CS101_Master self = (CS101_Master)parameter;
|
||||
|
||||
DEBUG_PRINT("MASTER: Access demand for slave %i\n", slaveAddress);
|
||||
|
||||
LinkLayerPrimaryUnbalanced_requestClass1Data(self->unbalancedLinkLayer, slaveAddress);
|
||||
}
|
||||
|
||||
static void
|
||||
IPrimaryApplicationLayer_UserData(void* parameter, int slaveAddress, uint8_t* msg, int start, int length)
|
||||
{
|
||||
CS101_Master self = (CS101_Master)parameter;
|
||||
|
||||
struct sCS101_ASDU _asdu;
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_createFromBufferEx(&_asdu, &(self->alParameters), msg + start, length);
|
||||
|
||||
if (asdu)
|
||||
{
|
||||
if (callPluginsToHandleAsdu(self, asdu) == false)
|
||||
{
|
||||
if (self->asduReceivedHandler)
|
||||
self->asduReceivedHandler(self->asduReceivedHandlerParameter, slaveAddress, asdu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
IPrimaryApplicationLayer_Timeout(void* parameter, int slaveAddress)
|
||||
{
|
||||
UNUSED_PARAMETER(parameter);
|
||||
UNUSED_PARAMETER(slaveAddress);
|
||||
}
|
||||
|
||||
static struct sIPrimaryApplicationLayer cs101UnbalancedAppLayerInterface = {
|
||||
IPrimaryApplicationLayer_AccessDemand,
|
||||
IPrimaryApplicationLayer_UserData,
|
||||
IPrimaryApplicationLayer_Timeout
|
||||
};
|
||||
|
||||
/********************************************
|
||||
* END IPrimaryApplicationLayer
|
||||
********************************************/
|
||||
|
||||
static const char*
|
||||
stateToString(LinkLayerState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case LL_STATE_IDLE:
|
||||
return ("IDLE");
|
||||
break;
|
||||
case LL_STATE_ERROR:
|
||||
return ("ERROR");
|
||||
break;
|
||||
case LL_STATE_BUSY:
|
||||
return ("BUSY");
|
||||
break;
|
||||
case LL_STATE_AVAILABLE:
|
||||
return ("AVAILABLE");
|
||||
break;
|
||||
}
|
||||
|
||||
return ("UNKNOWN");
|
||||
}
|
||||
|
||||
static void
|
||||
invokeConnectionHandler(CS101_Master self, int event)
|
||||
{
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
if (self->secureEndpoint)
|
||||
SecureEndpoint_handleCS104ConnectionEvent(self->secureEndpoint, &(self->peerConnection), event);
|
||||
#endif /* SEC_AUTH_60870_5_7 */
|
||||
|
||||
/* call events for plugins */
|
||||
if (self->plugins)
|
||||
{
|
||||
LinkedList pluginElem = LinkedList_getNext(self->plugins);
|
||||
|
||||
while (pluginElem)
|
||||
{
|
||||
CS101_MasterPlugin plugin = (CS101_MasterPlugin)LinkedList_getData(pluginElem);
|
||||
|
||||
if (plugin->eventHandler)
|
||||
plugin->eventHandler(plugin->parameter, NULL, (int)event);
|
||||
|
||||
pluginElem = LinkedList_getNext(pluginElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
CS101_Master_forwardASDU(CS101_MasterPlugin callingPlugin, void* ctx, CS101_ASDU asdu)
|
||||
{
|
||||
CS101_Master self = (CS101_Master)ctx;
|
||||
|
||||
/* call other plugins first */
|
||||
if (self->plugins)
|
||||
{
|
||||
LinkedList pluginElem = LinkedList_getNext(self->plugins);
|
||||
|
||||
while (pluginElem)
|
||||
{
|
||||
CS101_MasterPlugin plugin = (CS101_MasterPlugin) LinkedList_getData(pluginElem);
|
||||
|
||||
if (plugin != callingPlugin)
|
||||
{
|
||||
if (plugin->handleAsdu)
|
||||
{
|
||||
CS101_MasterPlugin_Result result = plugin->handleAsdu(plugin->parameter, NULL, asdu);
|
||||
|
||||
if (result == CS101_MASTER_PLUGIN_RESULT_HANDLED) {
|
||||
return;
|
||||
}
|
||||
else if (result == CS101_MASTER_PLUGIN_RESULT_INVALID_ASDU)
|
||||
{
|
||||
DEBUG_PRINT("Invalid message");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pluginElem = LinkedList_getNext(pluginElem);
|
||||
}
|
||||
}
|
||||
|
||||
if (self->asduReceivedHandler)
|
||||
self->asduReceivedHandler(self->asduReceivedHandlerParameter, 0, asdu);
|
||||
}
|
||||
|
||||
static void
|
||||
internalLinkLayerStateHandler(void* parameter, int address, LinkLayerState newState)
|
||||
{
|
||||
CS101_Master self = (CS101_Master)parameter;
|
||||
|
||||
printf("Link layer state changed to %s(%i)\n", stateToString(newState), newState);
|
||||
|
||||
if (newState == LL_STATE_AVAILABLE)
|
||||
{
|
||||
invokeConnectionHandler(self, 2);
|
||||
}
|
||||
else if (newState == LL_STATE_ERROR)
|
||||
{
|
||||
invokeConnectionHandler(self, 0);
|
||||
}
|
||||
|
||||
if (self->stateChangedHandler)
|
||||
self->stateChangedHandler(self->stateChangedHandlerParameter, address, newState);
|
||||
}
|
||||
|
||||
static CS101_AppLayerParameters
|
||||
_IPeerConnection_getApplicationLayerParameters(IPeerConnection self)
|
||||
{
|
||||
CS101_Master master = (CS101_Master)self->object;
|
||||
|
||||
return &(master->alParameters);
|
||||
}
|
||||
|
||||
static bool
|
||||
_IPeerConnnection_isReady(IPeerConnection self)
|
||||
{
|
||||
CS101_Master master = (CS101_Master)self->object;
|
||||
|
||||
//TODO implement link layer state check
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
isBroadcastAddress(CS101_Master self, int address)
|
||||
{
|
||||
if (self->linkLayerParameters.addressLength == 1)
|
||||
{
|
||||
return (address == 255);
|
||||
}
|
||||
else if (self->linkLayerParameters.addressLength == 2)
|
||||
{
|
||||
return (address == 65535);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
sendAsduInternal(CS101_Master self, CS101_ASDU asdu)
|
||||
{
|
||||
if (self->unbalancedLinkLayer)
|
||||
{
|
||||
struct sBufferFrame bufferFrame;
|
||||
uint8_t buffer[256];
|
||||
BufferFrame_initialize(&bufferFrame, buffer, 0);
|
||||
|
||||
CS101_ASDU_encode(asdu, (Frame)&bufferFrame);
|
||||
|
||||
if (isBroadcastAddress(self, self->slaveAddress))
|
||||
LinkLayerPrimaryUnbalanced_sendNoReply(self->unbalancedLinkLayer, self->slaveAddress, &bufferFrame);
|
||||
else
|
||||
LinkLayerPrimaryUnbalanced_sendConfirmed(self->unbalancedLinkLayer, self->slaveAddress, &bufferFrame);
|
||||
}
|
||||
else
|
||||
CS101_Queue_enqueue(&(self->userDataQueue), asdu);
|
||||
}
|
||||
|
||||
static bool
|
||||
_IPeerConnection_sendASDU(IPeerConnection self, CS101_ASDU asdu)
|
||||
{
|
||||
CS101_Master master = (CS101_Master)(self->object);
|
||||
|
||||
//TODO can be done any check if the link is ready to send?
|
||||
//CS101_Master_sendASDU(master, asdu);
|
||||
|
||||
sendAsduInternal(master, asdu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CS101_Master
|
||||
CS101_Master_createEx(SerialPort serialPort, const LinkLayerParameters llParameters,
|
||||
const CS101_AppLayerParameters alParameters, IEC60870_LinkLayerMode linkLayerMode, int queueSize)
|
||||
{
|
||||
CS101_Master self = (CS101_Master)GLOBAL_MALLOC(sizeof(struct sCS101_Master));
|
||||
|
||||
if (self)
|
||||
{
|
||||
if (llParameters)
|
||||
self->linkLayerParameters = *llParameters;
|
||||
else
|
||||
{
|
||||
self->linkLayerParameters.addressLength = 1;
|
||||
self->linkLayerParameters.timeoutForAck = 200;
|
||||
self->linkLayerParameters.timeoutRepeat = 1000;
|
||||
self->linkLayerParameters.timeoutLinkState = 5000;
|
||||
self->linkLayerParameters.useSingleCharACK = true;
|
||||
}
|
||||
|
||||
if (alParameters)
|
||||
self->alParameters = *alParameters;
|
||||
else
|
||||
self->alParameters = defaultAppLayerParameters;
|
||||
|
||||
self->transceiver = SerialTransceiverFT12_create(serialPort, &(self->linkLayerParameters));
|
||||
|
||||
self->linkLayerMode = linkLayerMode;
|
||||
|
||||
if (linkLayerMode == IEC60870_LINK_LAYER_UNBALANCED)
|
||||
{
|
||||
self->balancedLinkLayer = NULL;
|
||||
|
||||
self->unbalancedLinkLayer = LinkLayerPrimaryUnbalanced_create(
|
||||
self->transceiver, &(self->linkLayerParameters), &cs101UnbalancedAppLayerInterface, self);
|
||||
|
||||
LinkLayerPrimaryUnbalanced_setStateChangeHandler(self->unbalancedLinkLayer, internalLinkLayerStateHandler, self);
|
||||
}
|
||||
else
|
||||
{
|
||||
CS101_Queue_initialize(&(self->userDataQueue), queueSize);
|
||||
|
||||
self->unbalancedLinkLayer = NULL;
|
||||
|
||||
self->balancedLinkLayer = LinkLayerBalanced_create(0, self->transceiver, &(self->linkLayerParameters),
|
||||
&cs101BalancedAppLayerInterface, self);
|
||||
|
||||
LinkLayerBalanced_setDIR(self->balancedLinkLayer, true);
|
||||
|
||||
LinkLayerBalanced_setStateChangeHandler(self->balancedLinkLayer, internalLinkLayerStateHandler, self);
|
||||
}
|
||||
|
||||
self->asduReceivedHandler = NULL;
|
||||
|
||||
self->stateChangedHandler = NULL;
|
||||
self->stateChangedHandlerParameter = NULL;
|
||||
|
||||
#if (CONFIG_USE_THREADS == 1)
|
||||
self->isRunning = false;
|
||||
self->workerThread = NULL;
|
||||
#endif
|
||||
|
||||
self->plugins = NULL;
|
||||
|
||||
self->peerConnection.object = self;
|
||||
self->peerConnection.close = NULL;
|
||||
self->peerConnection.getApplicationLayerParameters = _IPeerConnection_getApplicationLayerParameters;
|
||||
self->peerConnection.getPeerAddress = NULL;
|
||||
self->peerConnection.isReady = _IPeerConnnection_isReady;
|
||||
self->peerConnection.sendACT_CON = NULL;
|
||||
self->peerConnection.sendACT_TERM = NULL;
|
||||
self->peerConnection.sendASDU = _IPeerConnection_sendASDU;
|
||||
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
self->secureEndpoint = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
CS101_Master
|
||||
CS101_Master_create(SerialPort serialPort, const LinkLayerParameters llParameters,
|
||||
const CS101_AppLayerParameters alParameters, IEC60870_LinkLayerMode linkLayerMode)
|
||||
{
|
||||
return CS101_Master_createEx(serialPort, llParameters, alParameters, linkLayerMode, CS101_MAX_QUEUE_SIZE);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_addPlugin(CS101_Master self, CS101_MasterPlugin plugin)
|
||||
{
|
||||
if (self->plugins == NULL)
|
||||
self->plugins = LinkedList_create();
|
||||
|
||||
if (self->plugins)
|
||||
{
|
||||
if (plugin->setForwardAsduFunction)
|
||||
plugin->setForwardAsduFunction(plugin->parameter, (CS101_MasterPluginForwardAsduFunc)CS101_Master_forwardASDU, self);
|
||||
|
||||
LinkedList_add(self->plugins, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
void
|
||||
CS101_Master_setSecureEndpoint(CS101_Master self, SecureEndpoint secureEndpoint)
|
||||
{
|
||||
self->secureEndpoint = secureEndpoint;
|
||||
|
||||
SecureEndpoint_setCS101(secureEndpoint);
|
||||
|
||||
SecureEndpoint_addForwardASDUFunctionForMaster(secureEndpoint, CS101_Master_forwardASDU, self);
|
||||
}
|
||||
#endif /* SEC_AUTH_60870_5_7 */
|
||||
|
||||
void
|
||||
CS101_Master_run(CS101_Master self)
|
||||
{
|
||||
if (self->unbalancedLinkLayer)
|
||||
{
|
||||
LinkLayerPrimaryUnbalanced_run(self->unbalancedLinkLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
LinkLayerBalanced_run(self->balancedLinkLayer);
|
||||
}
|
||||
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
if (self->secureEndpoint)
|
||||
{
|
||||
SecureEndpoint_runTask(self->secureEndpoint, &(self->peerConnection));
|
||||
|
||||
//TODO check if there is an ASDU to send when in unbalanced mode!?
|
||||
}
|
||||
#endif /* SEC_AUTH_60870_5_7 */
|
||||
|
||||
if (self->plugins)
|
||||
{
|
||||
LinkedList pluginElem = LinkedList_getNext(self->plugins);
|
||||
|
||||
while (pluginElem)
|
||||
{
|
||||
CS101_MasterPlugin plugin = (CS101_MasterPlugin)LinkedList_getData(pluginElem);
|
||||
|
||||
if (plugin->runTask)
|
||||
{
|
||||
// TODO is peer connection required? -> only for unbalanced mode?
|
||||
plugin->runTask(plugin->parameter, NULL);
|
||||
}
|
||||
|
||||
pluginElem = LinkedList_getNext(pluginElem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if (CONFIG_USE_THREADS == 1)
|
||||
static void*
|
||||
masterMainThread(void* parameter)
|
||||
{
|
||||
CS101_Master self = (CS101_Master)parameter;
|
||||
|
||||
self->isRunning = true;
|
||||
|
||||
while (self->isRunning)
|
||||
{
|
||||
CS101_Master_run(self);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif /* (CONFIG_USE_THREADS == 1) */
|
||||
|
||||
void
|
||||
CS101_Master_start(CS101_Master self)
|
||||
{
|
||||
#if (CONFIG_USE_THREADS == 1)
|
||||
if (self->workerThread == NULL)
|
||||
{
|
||||
self->workerThread = Thread_create(masterMainThread, self, false);
|
||||
Thread_start(self->workerThread);
|
||||
}
|
||||
#endif /* (CONFIG_USE_THREADS == 1) */
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_stop(CS101_Master self)
|
||||
{
|
||||
#if (CONFIG_USE_THREADS == 1)
|
||||
if (self->isRunning)
|
||||
{
|
||||
self->isRunning = false;
|
||||
Thread_destroy(self->workerThread);
|
||||
self->workerThread = NULL;
|
||||
}
|
||||
#endif /* (CONFIG_USE_THREADS == 1) */
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_destroy(CS101_Master self)
|
||||
{
|
||||
if (self)
|
||||
{
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
if (self->secureEndpoint)
|
||||
SecureEndpoint_removeForwardASDUFunction(self->secureEndpoint, self);
|
||||
#endif /* SEC_AUTH_60870_5_7 */
|
||||
|
||||
if (self->balancedLinkLayer)
|
||||
{
|
||||
LinkLayerBalanced_destroy(self->balancedLinkLayer);
|
||||
CS101_Queue_dispose(&(self->userDataQueue));
|
||||
}
|
||||
|
||||
if (self->unbalancedLinkLayer)
|
||||
{
|
||||
LinkLayerPrimaryUnbalanced_destroy(self->unbalancedLinkLayer);
|
||||
}
|
||||
|
||||
SerialTransceiverFT12_destroy(self->transceiver);
|
||||
|
||||
if (self->plugins)
|
||||
{
|
||||
LinkedList_destroyStatic(self->plugins);
|
||||
self->plugins = NULL;
|
||||
}
|
||||
|
||||
GLOBAL_FREEMEM(self);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_setDIR(CS101_Master self, bool dir)
|
||||
{
|
||||
if (self->balancedLinkLayer)
|
||||
LinkLayerBalanced_setDIR(self->balancedLinkLayer, dir);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_setOwnAddress(CS101_Master self, int address)
|
||||
{
|
||||
if (self->balancedLinkLayer)
|
||||
LinkLayerBalanced_setAddress(self->balancedLinkLayer, address);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_useSlaveAddress(CS101_Master self, int address)
|
||||
{
|
||||
self->slaveAddress = address;
|
||||
|
||||
if (self->balancedLinkLayer)
|
||||
{
|
||||
LinkLayerBalanced_setOtherStationAddress(self->balancedLinkLayer, address);
|
||||
}
|
||||
}
|
||||
|
||||
LinkLayerParameters
|
||||
CS101_Master_getLinkLayerParameters(CS101_Master self)
|
||||
{
|
||||
return &(self->linkLayerParameters);
|
||||
}
|
||||
|
||||
CS101_AppLayerParameters
|
||||
CS101_Master_getAppLayerParameters(CS101_Master self)
|
||||
{
|
||||
return &(self->alParameters);
|
||||
}
|
||||
|
||||
bool
|
||||
CS101_Master_isChannelReady(CS101_Master self, int address)
|
||||
{
|
||||
if (self->unbalancedLinkLayer)
|
||||
return LinkLayerPrimaryUnbalanced_isChannelAvailable(self->unbalancedLinkLayer, address);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_sendLinkLayerTestFunction(CS101_Master self)
|
||||
{
|
||||
if (self->unbalancedLinkLayer)
|
||||
LinkLayerPrimaryUnbalanced_sendLinkLayerTestFunction(self->unbalancedLinkLayer,
|
||||
self->slaveAddress);
|
||||
else if (self->balancedLinkLayer)
|
||||
LinkLayerBalanced_sendLinkLayerTestFunction(self->balancedLinkLayer);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_sendInterrogationCommand(CS101_Master self, CS101_CauseOfTransmission cot, int ca,
|
||||
QualifierOfInterrogation qoi)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_initializeStatic(&_asdu, &(self->alParameters), false, cot,
|
||||
self->alParameters.originatorAddress, ca, false, false);
|
||||
|
||||
struct sInterrogationCommand _io;
|
||||
|
||||
InformationObject io = (InformationObject)InterrogationCommand_create(&_io, 0, qoi);
|
||||
|
||||
CS101_ASDU_addInformationObject(asdu, io);
|
||||
|
||||
CS101_Master_sendASDU(self, asdu);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_sendCounterInterrogationCommand(CS101_Master self, CS101_CauseOfTransmission cot, int ca, uint8_t qcc)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_initializeStatic(&_asdu, &(self->alParameters), false, cot,
|
||||
self->alParameters.originatorAddress, ca, false, false);
|
||||
|
||||
struct sCounterInterrogationCommand _io;
|
||||
|
||||
InformationObject io = (InformationObject)CounterInterrogationCommand_create(&_io, 0, qcc);
|
||||
|
||||
CS101_ASDU_addInformationObject(asdu, io);
|
||||
|
||||
CS101_Master_sendASDU(self, asdu);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_sendReadCommand(CS101_Master self, int ca, int ioa)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_initializeStatic(&_asdu, &(self->alParameters), false, CS101_COT_REQUEST,
|
||||
self->alParameters.originatorAddress, ca, false, false);
|
||||
|
||||
struct sReadCommand _io;
|
||||
|
||||
InformationObject io = (InformationObject)ReadCommand_create(&_io, ioa);
|
||||
|
||||
CS101_ASDU_addInformationObject(asdu, io);
|
||||
|
||||
CS101_Master_sendASDU(self, asdu);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_sendClockSyncCommand(CS101_Master self, int ca, CP56Time2a time)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_initializeStatic(&_asdu, &(self->alParameters), false, CS101_COT_ACTIVATION,
|
||||
self->alParameters.originatorAddress, ca, false, false);
|
||||
|
||||
struct sClockSynchronizationCommand _io;
|
||||
|
||||
InformationObject io = (InformationObject)ClockSynchronizationCommand_create(&_io, 0, time);
|
||||
|
||||
CS101_ASDU_addInformationObject(asdu, io);
|
||||
|
||||
CS101_Master_sendASDU(self, asdu);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_sendTestCommand(CS101_Master self, int ca)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_initializeStatic(&_asdu, &(self->alParameters), false, CS101_COT_ACTIVATION,
|
||||
self->alParameters.originatorAddress, ca, false, false);
|
||||
|
||||
struct sTestCommand _io;
|
||||
|
||||
InformationObject io = (InformationObject)TestCommand_create(&_io);
|
||||
|
||||
CS101_ASDU_addInformationObject(asdu, io);
|
||||
|
||||
CS101_Master_sendASDU(self, asdu);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_sendProcessCommand(CS101_Master self, CS101_CauseOfTransmission cot, int ca, InformationObject command)
|
||||
{
|
||||
sCS101_StaticASDU _asdu;
|
||||
|
||||
CS101_ASDU asdu = CS101_ASDU_initializeStatic(&_asdu, &(self->alParameters), false, cot,
|
||||
self->alParameters.originatorAddress, ca, false, false);
|
||||
|
||||
CS101_ASDU_addInformationObject(asdu, command);
|
||||
|
||||
CS101_Master_sendASDU(self, asdu);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CS101_Master_sendASDU(CS101_Master self, CS101_ASDU asdu)
|
||||
{
|
||||
CS101_MasterPlugin_Result result = CS101_MASTER_PLUGIN_RESULT_NOT_HANDLED;
|
||||
|
||||
#ifdef SEC_AUTH_60870_5_7
|
||||
if (self->secureEndpoint)
|
||||
{
|
||||
if (SecureEndpoint_sendAsdu(self->secureEndpoint, &(self->peerConnection), asdu))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif /* SEC_AUTH_60870_5_7 */
|
||||
|
||||
/* call plugins */
|
||||
if (self->plugins)
|
||||
{
|
||||
LinkedList pluginElem = LinkedList_getNext(self->plugins);
|
||||
|
||||
while (pluginElem)
|
||||
{
|
||||
CS101_MasterPlugin plugin = (CS101_MasterPlugin)LinkedList_getData(pluginElem);
|
||||
|
||||
result = plugin->sendAsdu(plugin->parameter, NULL, asdu);
|
||||
|
||||
if (result == CS101_MASTER_PLUGIN_RESULT_HANDLED)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
pluginElem = LinkedList_getNext(pluginElem);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == CS101_MASTER_PLUGIN_RESULT_NOT_HANDLED)
|
||||
{
|
||||
if (self->unbalancedLinkLayer)
|
||||
{
|
||||
struct sBufferFrame bufferFrame;
|
||||
uint8_t buffer[256];
|
||||
BufferFrame_initialize(&bufferFrame, buffer, 0);
|
||||
|
||||
CS101_ASDU_encode(asdu, (Frame)&bufferFrame);
|
||||
|
||||
if (isBroadcastAddress(self, self->slaveAddress))
|
||||
LinkLayerPrimaryUnbalanced_sendNoReply(self->unbalancedLinkLayer, self->slaveAddress, &bufferFrame);
|
||||
else
|
||||
LinkLayerPrimaryUnbalanced_sendConfirmed(self->unbalancedLinkLayer, self->slaveAddress, &bufferFrame);
|
||||
}
|
||||
else
|
||||
CS101_Queue_enqueue(&(self->userDataQueue), asdu);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_addSlave(CS101_Master self, int address)
|
||||
{
|
||||
if (self->unbalancedLinkLayer)
|
||||
LinkLayerPrimaryUnbalanced_addSlaveConnection(self->unbalancedLinkLayer, address);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_pollSingleSlave(CS101_Master self, int address)
|
||||
{
|
||||
if (self->unbalancedLinkLayer)
|
||||
{
|
||||
LinkLayerPrimaryUnbalanced_requestClass2Data(self->unbalancedLinkLayer, address);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_pollSingleSlaveClass1(CS101_Master self, int address)
|
||||
{
|
||||
if (self->unbalancedLinkLayer)
|
||||
{
|
||||
LinkLayerPrimaryUnbalanced_requestClass1Data(self->unbalancedLinkLayer, address);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_setASDUReceivedHandler(CS101_Master self, CS101_ASDUReceivedHandler handler, void* parameter)
|
||||
{
|
||||
self->asduReceivedHandler = handler;
|
||||
self->asduReceivedHandlerParameter = parameter;
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_setLinkLayerStateChanged(CS101_Master self, IEC60870_LinkLayerStateChangedHandler handler, void* parameter)
|
||||
{
|
||||
if (self->linkLayerMode == IEC60870_LINK_LAYER_BALANCED)
|
||||
{
|
||||
LinkLayerBalanced_setStateChangeHandler(self->balancedLinkLayer, handler, parameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
LinkLayerPrimaryUnbalanced_setStateChangeHandler(self->unbalancedLinkLayer, handler, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_setRawMessageHandler(CS101_Master self, IEC60870_RawMessageHandler handler, void* parameter)
|
||||
{
|
||||
SerialTransceiverFT12_setRawMessageHandler(self->transceiver, handler, parameter);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Master_setIdleTimeout(CS101_Master self, int timeoutInMs)
|
||||
{
|
||||
if (self->linkLayerMode == IEC60870_LINK_LAYER_BALANCED)
|
||||
LinkLayerBalanced_setIdleTimeout(self->balancedLinkLayer, timeoutInMs);
|
||||
|
||||
/* unbalanced primary layer does not support automatic idle detection */
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* cs101_master_connection.c
|
||||
*
|
||||
* Copyright 2017 MZ Automation GmbH
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#include "iec60870_slave.h"
|
||||
|
||||
bool
|
||||
IMasterConnection_isReady(IMasterConnection self)
|
||||
{
|
||||
return self->isReady(self);
|
||||
}
|
||||
|
||||
bool
|
||||
IMasterConnection_sendASDU(IMasterConnection self, CS101_ASDU asdu)
|
||||
{
|
||||
return self->sendASDU(self, asdu);
|
||||
}
|
||||
|
||||
bool
|
||||
IMasterConnection_sendASDUEx(IMasterConnection self, CS101_ASDU asdu, bool bypassQueue)
|
||||
{
|
||||
if (self->sendASDUEx)
|
||||
return self->sendASDUEx(self, asdu, bypassQueue);
|
||||
else
|
||||
return self->sendASDU(self, asdu);
|
||||
}
|
||||
|
||||
bool
|
||||
IMasterConnection_sendACT_CON(IMasterConnection self, CS101_ASDU asdu, bool negative)
|
||||
{
|
||||
return self->sendACT_CON(self, asdu, negative);
|
||||
}
|
||||
|
||||
bool
|
||||
IMasterConnection_sendACT_TERM(IMasterConnection self, CS101_ASDU asdu)
|
||||
{
|
||||
return self->sendACT_TERM(self, asdu);
|
||||
}
|
||||
|
||||
CS101_AppLayerParameters
|
||||
IMasterConnection_getApplicationLayerParameters(IMasterConnection self)
|
||||
{
|
||||
return self->getApplicationLayerParameters(self);
|
||||
}
|
||||
|
||||
void
|
||||
IMasterConnection_close(IMasterConnection self)
|
||||
{
|
||||
if (self->close)
|
||||
self->close(self);
|
||||
}
|
||||
|
||||
int
|
||||
IMasterConnection_getPeerAddress(IMasterConnection self, char* addrBuf, int addrBufSize)
|
||||
{
|
||||
if (self->getPeerAddress)
|
||||
return self->getPeerAddress(self, addrBuf, addrBufSize);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
IPeerConnection_isReady(IPeerConnection self)
|
||||
{
|
||||
if (self->isReady)
|
||||
return self->isReady(self);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
IPeerConnection_sendASDU(IPeerConnection self, CS101_ASDU asdu)
|
||||
{
|
||||
if (self->sendASDU)
|
||||
return self->sendASDU(self, asdu);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
IPeerConnection_sendACT_CON(IPeerConnection self, CS101_ASDU asdu, bool negative)
|
||||
{
|
||||
if (self->sendACT_CON)
|
||||
return self->sendACT_CON(self, asdu, negative);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
IPeerConnection_sendACT_TERM(IPeerConnection self, CS101_ASDU asdu)
|
||||
{
|
||||
if (self->sendACT_TERM)
|
||||
return self->sendACT_TERM(self, asdu);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
CS101_AppLayerParameters
|
||||
IPeerConnection_getApplicationLayerParameters(IPeerConnection self)
|
||||
{
|
||||
return self->getApplicationLayerParameters(self);
|
||||
}
|
||||
|
||||
void
|
||||
IPeerConnection_close(IPeerConnection self)
|
||||
{
|
||||
if (self->close)
|
||||
self->close(self);
|
||||
}
|
||||
|
||||
int
|
||||
IPeerConnection_getPeerAddress(IPeerConnection self, char* addrBuf, int addrBufSize)
|
||||
{
|
||||
if (self->getPeerAddress)
|
||||
return self->getPeerAddress(self, addrBuf, addrBufSize);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* cs101_queue.c
|
||||
*
|
||||
* Copyright 2017-2025 MZ Automation GmbH
|
||||
*
|
||||
* This file is part of lib60870-C
|
||||
*
|
||||
* lib60870-C is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* lib60870-C is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* See COPYING file for the complete license text.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "buffer_frame.h"
|
||||
#include "lib_memory.h"
|
||||
#include "lib60870_config.h"
|
||||
#include "lib60870_internal.h"
|
||||
#include "apl_types_internal.h"
|
||||
#include "cs101_queue.h"
|
||||
|
||||
/********************************************
|
||||
* CS101_Queue
|
||||
********************************************/
|
||||
|
||||
void
|
||||
CS101_Queue_initialize(CS101_Queue self, int maxQueueSize)
|
||||
{
|
||||
self->entryCounter = 0;
|
||||
self->firstMsgIndex = 0;
|
||||
self->lastMsgIndex = 0;
|
||||
self->size = maxQueueSize;
|
||||
|
||||
BufferFrame_initialize(&(self->encodeFrame), NULL, 0);
|
||||
|
||||
#if (CS101_MAX_QUEUE_SIZE == -1)
|
||||
int queueSize = maxQueueSize;
|
||||
|
||||
if (maxQueueSize == -1)
|
||||
queueSize = 100;
|
||||
|
||||
self->elements = (CS101_QueueElement) GLOBAL_CALLOC(queueSize, sizeof(struct sCS101_QueueElement));
|
||||
|
||||
self->size = queueSize;
|
||||
#else
|
||||
self->size = CS101_MAX_QUEUE_SIZE;
|
||||
#endif
|
||||
|
||||
|
||||
#if (CONFIG_USE_SEMAPHORES == 1)
|
||||
self->queueLock = Semaphore_create(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Queue_dispose(CS101_Queue self)
|
||||
{
|
||||
#if (CONFIG_USE_SEMAPHORES == 1)
|
||||
Semaphore_destroy(self->queueLock);
|
||||
#endif
|
||||
|
||||
#if (CS101_MAX_QUEUE_SIZE == -1)
|
||||
GLOBAL_FREEMEM(self->elements);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Queue_lock(CS101_Queue self)
|
||||
{
|
||||
#if (CONFIG_USE_SEMAPHORES == 1)
|
||||
Semaphore_wait(self->queueLock);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Queue_unlock(CS101_Queue self)
|
||||
{
|
||||
#if (CONFIG_USE_SEMAPHORES == 1)
|
||||
Semaphore_post(self->queueLock);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Queue_enqueue(CS101_Queue self, CS101_ASDU asdu)
|
||||
{
|
||||
CS101_Queue_lock(self);
|
||||
|
||||
int nextIndex;
|
||||
bool removeEntry = false;
|
||||
|
||||
if (self->entryCounter == 0) {
|
||||
self->firstMsgIndex = 0;
|
||||
nextIndex = 0;
|
||||
}
|
||||
else
|
||||
nextIndex = self->lastMsgIndex + 1;
|
||||
|
||||
if (nextIndex == self->size)
|
||||
nextIndex = 0;
|
||||
|
||||
if (self->entryCounter == self->size)
|
||||
removeEntry = true;
|
||||
|
||||
if (removeEntry == false) {
|
||||
DEBUG_PRINT("add entry (nextIndex:%i)\n", nextIndex);
|
||||
self->lastMsgIndex = nextIndex;
|
||||
self->entryCounter++;
|
||||
}
|
||||
else {
|
||||
DEBUG_PRINT("add entry (nextIndex:%i) -> remove oldest\n", nextIndex);
|
||||
|
||||
/* remove oldest entry */
|
||||
self->lastMsgIndex = nextIndex;
|
||||
|
||||
int firstIndex = nextIndex + 1;
|
||||
|
||||
if (firstIndex == self->size)
|
||||
firstIndex = 0;
|
||||
|
||||
self->firstMsgIndex = firstIndex;
|
||||
}
|
||||
|
||||
self->encodeFrame.buffer = self->elements[nextIndex].buffer;
|
||||
self->encodeFrame.startSize = 0;
|
||||
self->encodeFrame.msgSize = 0;
|
||||
|
||||
CS101_ASDU_encode(asdu, (Frame)&(self->encodeFrame));
|
||||
|
||||
int srcSize = self->encodeFrame.msgSize;
|
||||
self->elements[nextIndex].size = srcSize;
|
||||
|
||||
DEBUG_PRINT("Events in FIFO: %i (first: %i, last: %i)\n", self->entryCounter,
|
||||
self->firstMsgIndex, self->lastMsgIndex);
|
||||
|
||||
CS101_Queue_unlock(self);
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Locking has to be done by caller!
|
||||
*/
|
||||
Frame
|
||||
CS101_Queue_dequeue(CS101_Queue self, Frame resultStorage)
|
||||
{
|
||||
Frame frame = NULL;
|
||||
|
||||
if (self->entryCounter != 0)
|
||||
{
|
||||
if (resultStorage)
|
||||
{
|
||||
frame = resultStorage;
|
||||
|
||||
int currentIndex = self->firstMsgIndex;
|
||||
|
||||
Frame_appendBytes(frame, self->elements[currentIndex].buffer, self->elements[currentIndex].size);
|
||||
|
||||
self->firstMsgIndex = (currentIndex + 1) % self->size;
|
||||
self->entryCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Locking has to be done by caller!
|
||||
*/
|
||||
bool
|
||||
CS101_Queue_hasWaitingAsdu(CS101_Queue self, TypeID* typeId)
|
||||
{
|
||||
if (self->entryCounter != 0)
|
||||
{
|
||||
if (typeId)
|
||||
{
|
||||
int currentIndex = self->firstMsgIndex;
|
||||
|
||||
*typeId = (TypeID)(self->elements[currentIndex].buffer[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
CS101_Queue_isFull(CS101_Queue self)
|
||||
{
|
||||
return (self->entryCounter == self->size);
|
||||
}
|
||||
|
||||
bool
|
||||
CS101_Queue_isEmpty(CS101_Queue self)
|
||||
{
|
||||
return (self->entryCounter == 0);
|
||||
}
|
||||
|
||||
void
|
||||
CS101_Queue_flush(CS101_Queue self)
|
||||
{
|
||||
CS101_Queue_lock(self);
|
||||
self->entryCounter = 0;
|
||||
self->firstMsgIndex = 0;
|
||||
self->lastMsgIndex = 0;
|
||||
CS101_Queue_unlock(self);
|
||||
}
|
||||
|
||||
/********************************************
|
||||
* END CS101_Queue
|
||||
********************************************/
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 181 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user