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,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);
|
||||
}
|
||||
Reference in new issue
Block a user