--READ smash_read_this.txt
This commit is contained in:
george committed 2026-06-22 16:04:13 +05:30
commit 1ddf693260
181 files changed
+66527

No files matched your search

+18
View File
@@ -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
)
+20
View File
@@ -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
)
+20
View File
@@ -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
)
+20
View File
@@ -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
)
+20
View File
@@ -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-----
+210
View File
@@ -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
)
+20
View File
@@ -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-----
+363
View File
@@ -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);
}