--READ smash_read_this.txt
This commit is contained in:
George 2026-06-22 16:04:13 +05:30
commit 1ddf693260
181 changed files with 66527 additions and 0 deletions

192
.clang-format Normal file
View File

@ -0,0 +1,192 @@
---
Language: Cpp
# BasedOnStyle: Microsoft
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignArrayOfStructures: None
AlignConsecutiveMacros: None
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: All
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
AttributeMacros:
- __capability
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: Always
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: false
AfterExternBlock: true
BeforeCatch: true
BeforeElse: true
BeforeLambdaBody: false
BeforeWhile: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeConceptDeclarations: true
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120
CommentPragmas: '^ IWYU pragma:'
QualifierAlignment: Leave
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
ExperimentalAutoDetectBinPacking: false
PackConstructorInitializers: BinPack
BasedOnStyle: ''
ConstructorInitializerAllOnOneLineOrOnePerLine: false
AllowAllConstructorInitializersOnNextLine: true
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IfMacros:
- KJ_IF_MAYBE
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: NoIndent
IndentRequires: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: Signature
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 1000
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
PPIndentWidth: -1
ReferenceAlignment: Pointer
ReflowComments: true
RemoveBracesLLVM: false
SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1
SortIncludes: CaseSensitive
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeParensOptions:
AfterControlStatements: true
AfterForeachMacros: true
AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false
AfterIfMacros: true
AfterOverloadedOperator: false
BeforeNonEmptyParentheses: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: Never
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInLineCommentPrefix:
Minimum: 1
Maximum: -1
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
BitFieldColonSpacing: Both
Standard: Latest
StatementAttributeLikeMacros:
- Q_EMIT
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 4
UseCRLF: false
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

32
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,32 @@
name: SonarQube
on:
workflow_dispatch:
push:
branches:
- master
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build and analyze
runs-on: ubuntu-latest
env:
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Install Build Wrapper
uses: SonarSource/sonarqube-scan-action/install-build-wrapper@v5
- name: Run Build Wrapper
run: |
mkdir build
cmake -S lib60870-C -B build
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/
- name: SonarQube Scan
uses: SonarSource/sonarqube-scan-action@v5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
with:
args: >
--define sonar.cfamily.compile-commands="${{ env.BUILD_WRAPPER_OUT_DIR }}/compile_commands.json"

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
lib60870-C/build/

266
CHANGELOG Normal file
View File

@ -0,0 +1,266 @@
Changes to version 2.4.0
------------------------
- Added function CS104_Connection_isConnected
- TLS integration code for mbedtls 2.28 and 3.6 updated for better compliance with IEC 62351 security event requirements
- TLS (mbedtls 3.6): explicitely disabled hostname verfication to avoid problem with mbedtls 3.6.3 and later
- updated minimum required cmake version to be compatible with cmake 4.0
- added integration code for secure authentication (IEC 60870-5-7:2013) module (the actual secure authentication code is available as commercial add-on)
- CS104 slave: added functions to set callback handlers for reset process and delay acquisition commands (#137)
- CS101/CS104 slave: only allow known CAA for internally handled commands -> added callback CS101_IsCAAllowedHandler (LIB8705-126)
- CS101/CS104 slave: reject commands with broadcast CA when not allowed for command type (LIB8705-130)
- CS104 slave: fixed potentical deadlock - lowPrioQueue doesn't have to be locked during sendASDU call
- CS104 slave: fixed detection of unconfirmed messages when connection is closed
- CS104 client: added threadless operation mode (CS104_Connection_startThreadless/stopThreadless/run) and example cs104_client_no_threads (LIB8705-134)
- CS104 client: implemented T1 timeout when waiting for STARTDT/STOPDT confirmation messages
- Added IMasterConnection_sendASDUEx to allow bypassing queues for correct message ordering in GI responses (LIB8705-149)
- updated CS 104 server example to show standard compliant interrogation handling
- fix: CS104 client is sending same S message multiple times when sending STOPDT_ACT (LIB8705-150)
- fix: slave is sending STOP-DT CON when there are unconfirmed sent I messages from the high prio queue (LIB8705-148)
- added some warning/alarms to mbedtls 2.28 integration to comply with IEC 62351-100-3 test cases
- CS101/CS104 slave: added additional options for interrogation handling to allow better compliance with priority rules
Changes to version 2.3.6
------------------------
- fixed wrong size calculation when decoding FileDirectory(F_DR_TA_1) type (#179)
- CS104 Slave/CS101 Slave: fixed handling of test commands to be compliant with conformance test case (LIB8705-128)
- CS104_Connection: fixed possible application crash when application is trying to send a message after connection is closed (LIB8705-127)
- fixed all unchecked results from calls to CS101_ASDU_getElement/CS101_ASDU_getElementEx (#178) -> fixes vulnerability GHSA-75pr-rr3v-j6px (possible NULL pointer dereference when invalid test command is received by CS 104 server)
- code format updates
Changes to version 2.3.5
------------------------
- fixed MessageQueue_isAsduAvailable to solve CPU usage problem in CS104 server (LIB8705-123)
- fixed problem in normalized value handling (LIB8705-122)
Changes to version 2.3.4
------------------------
- removed internal calls to CS101_ASDU_createFromBuffer to avoid dynamic memory allocation while handling received ASDUs
- added CS101_ASDU_createFromBuffer to API
- added ability to change config defines from make command (#169)
- Time(Linux): always use clock_gettime; cleanup cmake files to remove checks for clock_gettime (#162)
- Time(windows): fixed - Hal_getMonotonicTimeInMs() doesn't call GetTickCount64 correctly (#163)
Changes to version 2.3.3
------------------------
- TLS: support for TLS 1.3 (when using mbedtls 3.6)
- TLS: support to select TLS cipher suites
- CS104 slave: check IOA of received commands where IOA is fixed to 0 (LIB8705-101)
- CS 104 slave: only send S message after receiving STOPDT-ACT when there are unconfirmed outstanding I messages to be compliant with test case IEC 60870-5-604:2016-5.3.2.70 (LIB8705-97)
- fixed bug in test pattern for TestCommand (I651CL-25)
- fixed initialization problem in StepCommand_create (I651CL-22)
- disable support for C_TS_NA_1 for CS104 by default
- use monotonic time for timers when supported by platform (LIB8705-87)
- CS101 slave: updated behavior in unbalanced mode when FCB bit did not change (LIB8705-104)
- fixed - k parameter from user configuration is not used
- added handling of pending unconfirmed stopped state (LIB8705-83)
- CS104_Connection: call connection handler only outside of connection lock to avoid deadlocks in user code (LIB8705-62)
- CS104 connection: fixed potential race condition when connection is closed by peer (#147)
- CS104 slave: fixed - lock not released in error case (#138)
- fixed - CS 104 server: invalid read - when MasterConnection_deactivate is called after MasterConnection_deinit (LIB8705-54)
- fixed - CS 104 server: connection is closed by server when receiving S message in inactive state (LIB8705-55)
- fixed - CS104_Connection deadlock when sending commands/ASDUs (#134)(LIB8705-52)
Changes to version 2.3.2
------------------------
- TLS support now requires mbedtls 2.28
- updated HAL layer
- Added new IEC 62351-3 related TLS features (session resumption, alarms, ...)
- CS101 balanced link layer: Send request-status-of-link before calling reset-of-remote-link
- CS101 unbalanced master: Send request-status-of-link before calling reset-of-remote-link, added delay before repeating request-status-of-link
- added function CS101_ASDU_clone
- fixed - TLS: configured CRL are ignored (#117)
- integrated code to serve files
- other small bug fixes and improvements
- CS104 client: added connection event when connect failed
Changes to version 2.3.1
------------------------
- TLS support now requires mbedtls 2.16.12
- CS104 client/master: add support to specify local interface/IP address
- Macos HAL layer: replaced semaphores by mutex
- updated HAL layer
- fix - Possible buffer overflow when formatting IPv6 addresses (#96)
- fixed - write to queueEntry is not protected (#99)
- fixed bug in CS101_ASDU_setNumberOfElements (#91)
- CS104 slave: fixed problem in TEST-FR con timeout handling
- CS101 master: fixed problem not sending broadcast messages (#88)
- CS101 slave: fixed potential crash when application layer parameters don't match
- CS104 server: fixed - receiving I or S frame while in STOPDT should close connection
Changes to version 2.3.0
------------------------
- CS104 slave: send time sync response only to requesting client (#87)
- fixed a bug in StatusAndStatusChangeDetection type decoding
- fixed problem in TLS integration layer
- fixed wrong type in return value of TestCommandWithCP56Time2a_getCounter
- CS104 slave: close all open connections when server is stopped in non-treaded mode
- added QueryLog (F_SC_NB_1) information object type
- CS 101 slave: release plugin list in destroy function (#80)
- CS104 client: Added function CS104_Connection_sendTestCommandWithTimestamp to send C_TS_TA_1 test command
- CS104 slave: added support to handle test command C_TS_TA_1
- added missing value for IEC60870_QCC_FRZ_COUNTER_RESET (#78)
- CS104 slave: remove message from queue when confirmation received (see #77)
- CS104 master: confirm all received I messages before sending STOPDT ACT or closing the connection
- CS 104 slave: fixed bugs when TLS connection initilization fails (#75)
- fixed potential memory leak in CS104_Connection_connectAsync
- fixed wrong argument type of StepCommandWithCP56Time2a_destroy (#74)
- single point and double point information objects ignore invalid quality flags (#72)
Changes to version 2.2.1
------------------------
- CS104 slave: fixed problems in queue handling (#67)
- added missing return statement in CS101_ASDU_addPayload(#68)
- CS101_ASDU_addInformationObject now checks for correct object type
- windows socket driver: fixed - fail to detect peer close when read from socket
- fixed bug in BitString32 encoding (see #65)
- fixed - null pointer access when calling CS104_Slave_destroy without server running before (issue #64)
- added Bitstring32X_createEx functions to keep API backward compatible and allow setting of quality (issue #63)
Changes to version 2.2.0
------------------------
- CS 101/104 slave: improved stability when receiving corrupted messages
- CS 101/104 slave: added plugin interface
- CS101 master: fixed setting of own address in balanced mode (#51)
- CS101 slave: fixed bug in queue size initialization
- Socket HAL (linux/bsd): set default backlog to 2 (see #50)
- Socket HAL (linux/bsd): Socket_read detects closed socket
- fixed bugs in file handling messages
- fixed bug in OA decoding
- add support to create and handle private or not supported ASDU types
- CS104 master/slave: improved socket handling
- CS104 slave: improved memory handling and memory consumption
- removed CONFIG_CS104_SLAVE_POOL (and related) configuration option
Changes to version 2.1.1
------------------------
- CS 104 slave: added functions IMasterConnection_getPeerAddress and IMasterConnection_close
- CS 101/CS 104 slave: set P/N=1 (negative) when sending COT=44 or COT=45
- improved compatibility with VxWorks
- CS 104 slave: fixed bug in counter interrogation command handling - free stack allocated memory
- fixed some include file problems
Changes to version 2.1.0
------------------------
- CS104 slave: added support for multiple redundancy groups
- added non-threaded moded for CS 104 slave
- separated thread and semaphore support for CS 104 slave
- CS101 unbalanced link layer (master): automatically send request UD 1 when ACD bit is set in received frame
- added CS101_ASDU_getElementEx function to avoid dynamic memory allocation
- added support for static ASDU object allocation
- fixed length check when parsing M_EP_TD_1
- CS101 unbalanced master: fixed state machine problem with multiple slaves (some responses don't change state and master keeps locked on the slave)
- optionally compile library without HAL to simply using together with libiec61850
Changes to version 2.0.2
------------------------
- CS104 slave: added new CS104_ConnectionEventHandler to track connection events
- CS104 master/slave: added callback handlers to log raw messages
- CS101 master/slave: added callback handlers to log raw messages
Changes to version 2.0.1
------------------------
- fixed problems to compile code with C++
- CS101: added functions to better control link layer addresses
- CS101 master: added function to modify default link layer parameters
- link layer: fixed problem in test function handling
- application layer: allow configuration of maximum size of ASDU
- CS104 master: added new function CS104_Master_sendProcessCommandEx
- CS104: fixed timeout T2 problem
- and other small fixes
Changes to version 2.0.0
------------------------
- added support for CS 101 balanced and unbalanced serial link layer
- added serial port abstraction layer and implementations for linux and windows
- seperated lib60870-C and lib60870.NET into separate projects. Removed all .NET code from the lib60870 main branch
- added TLS support for CS 104
- API redesign. Not compatible with old API.
- updated and extended user guide
- added doxygen based API reference documentation
- added more examples
Changes to version 0.9.5
------------------------
- lib60870.NET: add support for server allowing multiple clients using the application layer (ServerMode.CONNECTION_IS_REDUNDANCY_GROUP)
- lib60870-C: add support for server allowing multiple clients using the application layer (new mode where each client connection is a redundancy group)
- lib60870-C: limit the number of allowed client connection (T104Slave_setMaxOpenConnections)
- lib60870-C: only accept single active connection in ServerMode == SINGLE_REDUNDANCY_GROUP
- lib60870-C: enhanced big endian platform detection
- lib60870-C: CS104 slave/server: added support for ConnectionRequestHandler
- lib60870-C: Socket_getPeerAddress replaced by Socket_getPeerAddressStatic to avoid dynamic memory allocation
- lib60870.NET: server support for multiple clients with separate ASDU queues (added ServerMode property)
- lib60870.NET: server - added MaxOpenConnections property
- lib60870.NET: server - added ConnectionRequestHandler
- lib60870.NET: Added RawValue properties to SetpointCommandNormalized and MeasuredValueNormalized, additional constructors with short values
- lib60870.NET: ScaledValue adds proporty to get/set short value
- lib60870.NET: fixed tests project dependencies
- lib60870.NET: fixed bug in BitString32
- lib60870-C: fixed month bug in CP56Time2a conversion functions
Changes to version 0.9.4
------------------------
- added length check when adding information objects to ASDUs --> changed signature of ASDU_addInformationObject
- lib60870-C: added TARGET=UCLINUX-XPORT to compile for Lantronix XPORT PRO
- lib60870-C: added CauseOfTransmission_toString function
- lib60870.NET: fixed bug in parsing of MeasuredValueNormalizedWithoutQuality when SEQ = 1
- added all missing timeout handling for master and slave side
- lib60870.NET: added client side ConnectionStatistics
- lib60870.NET: client socket uses Poll instead of read timeout
- lib60870.NET: added TestCommand ASDU
- lib60870.NET: client - some refactoring - added ASDU queue for congestion handling
- lib60870.NET: fixed constructors for StepPosition information objects (added quality)
- lib60870.NET: Added missing GetEncodedSize in SinglePointWithCPxxx objects
- lib60870.NET: added constructors and set properties for CP24Time2a
- lib60870-C: implemented BufferFrame to enable ASDU and IO encoding to arbitrary memory buffers
- lib60870-C: slave - replaced ASDU queue by queue consisiting of buffers of encoded ASDUs
- lib60870.NET: fixed bug in encoding of sequence of information objects
- lib60870.NET: fixed bug in EventOfProtectionEquipment
- lib60870.NET: added public constructor for CP16Time2a
- lib60870.NET: added encoding tests (with maximum number of information objects in ASDU w/wo sequence of information objects)
- lib60870.NET: fixed bug in StatusAndStatusChangeDetection
- lib60870.NET: ASDU parser - added header length check
Changes to version 0.9.3
------------------------
- lib60870.NET: changed visibility of InformationObject.Encode method to internal
- lib60870.NET: added public Constructor Encoding functions for PackedOutputCircuitInfo, PackedStartEventsOfProtectionEquipment, EventOfProtectionEquipment
- lib60870.NET/lib60870-C: Added parsing support for sequence for information objects for all data messages in monitoring direction (despite the fact that this should not happen according to IEC 60870-5-101)
Changes to version 0.9.2
------------------------
- lib60870-C: changed to select based read at client side
- lib60870.NET: extended STARTDT/STOPDT handling
- lib60870.NET: changed signature of ConnectionHandler
- lib60870-C: added functions to bind to local IP address and TCP port
- lib60870-C: default bind to address "0.0.0.0"
- lib60780-C: fixed StopDT bug
- lib60870-C: fixed C99 incompatibilities in cpXXtime2a.c
- lib60870-C: added support for M_EI_NA_1 message (end of initialization)
- lib60870-C: DoublePointWithCP56Time2a_getFromBuffer corrected parsing of time stamp
- lib60870-C: SingleCommandWithCP56Time2a_getFromBuffer corrected parsing of time stamp
- lib60870-C: SinglePointWithCP56Time2a_getFromBuffer corrected parsing of time stamp
- lib60870-C: StepPositionWithCP56Time2a_getFromBuffer corrected parsing of time stamp
- lib60870-C: DoublePointWithCP24Time2a_getFromBuffer corrected parsing of time stamp
- lib60870-C: SinglePointWithCP24Time2a_getFromBuffer corrected parsing of time stamp
- lib60870-C: corrected parsing of double point information
- lib60870-C: corrected parsing of some data types when sent as sequence of information objects
- lib60870-C: client/master: extended STARTDT/STOPDT handling; changed connection handler signature to indicate STARTDT_CON/STOPDT_CON messages

674
COPYING Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

111
README.md Normal file
View File

@ -0,0 +1,111 @@
# README lib60870-C {#mainpage}
lib60870 library for IEC 60870-5 based protocols in C
The current implementation contains code for the IEC 60870-5-101 (application layer and serial link layer) and IEC 60870-5-104 (protocool over TCP/IP) specifications.
Features:
- support for all application layer message types
- master and slave
- balanced and unbalanced link layers (for CS 101 serial communication)
- client/server for CS 104 TCP/IP communication
- CS 104 redundancy group support
- CS101 slave/CS104 server: file service support
- Supports most TLS features required by IEC 62351-3 (third party code mbedtls required)
- Support for secure authentication (IEC 60870-5-7:2013) is available as commercially available add-on
- portable C99 code
Please also consider the User Guide and the API reference documentation (https://support.mz-automation.de/doc/lib60870/latest/)
## Compiling and running the examples:
In the lib60870-C folder build the library with
`make`
Go to the examples folder and build the examples with
`make`
in each examples' directory.
The library and examples can also be build with _CMake_.
To build the library in a separate folder create a new folder as subdirectory of
the project folder and run cmake to create the build files:
`mkdir build`
`cd build`
`cmake ..`
## Building without common code and HAL
The library contains some common code and a platform abstraction layer (HAL) that is shared with
other protocol libraries of MZ Automation (e.g. libiec61850). In order to simplify using these
protocol libraries together it is possible to compile the library without the common parts.
This can be done by using the *WITHOUT_HAL* and *WITHOUT_COMMON* defines when calling make:
`make WITHOUT_HAL=1 WITHOUT_COMMON=1`
## Building with TLS support
The library can be build with support for TLS. In order to do so you have to download mbedtls version 2.28.x.
Unpack the mbedtls tarball in the dependencies folder so that a folder
dependencies/mbedtls-2.28
exists.
The cmake build system will automatically detect the mbedtls source and build the library with TLS support and mbedtls included
When using make you have to call make with WITH_MBEDTLS=1
`make WITH_MBEDTLS=1`
## Library configuration
There are different runtime and compile-time configuration options.
Compile time configuration options can be used to shrink down the library for small embedded systems. Compile time configuration can be changed by modifying the file _config/lib60870_config.h_.
## Memory allocation
The library uses dynamic memory allocation (malloc/calloc wrapped by own functions that can be replaced when required).
The CS104 slave uses dynamic memory allocation only at setup time (when calling the function _CS104_Slave_create_ and
_CS104_Slave_start_/_CS104_Slave_startThreadless_.
For details please have a look at the _User Guide_.
## Contact:
The library is developed by Michael Zillgith and supported by MZ Automation GmbH.
For bug reports, hints or support please contact info@mz-automation.de
## Licensing
This software can be dual licensed under the GPLv3 (https://www.gnu.org/licenses/gpl-3.0.en.html) and a commercial license agreement.
When using the library in commercial and non-GPL applications you should buy a commercial license.
## Commercial licenses and support
Support and commercial license options are provided by MZ Automation GmbH. Please contact info@mz-automation.de for more details.
## Contributing
If you want to contribute to the improvement and development of the library please send me comments, feature requests, bug reports, or patches.
Please do not create pull requests for the github repository. The github repository is a read-only copy. Pull requests may be silently ignored.
For more than trivial contributions I require you to sign a Contributor License Agreement. Please contact info@mz-automation.de

159
lib60870-C/CMakeLists.txt Normal file
View File

@ -0,0 +1,159 @@
cmake_minimum_required(VERSION 3.0...3.31)
# automagically detect if we should cross-compile
if(DEFINED ENV{TOOLCHAIN})
set(CMAKE_C_COMPILER $ENV{TOOLCHAIN}gcc)
set(CMAKE_CXX_COMPILER $ENV{TOOLCHAIN}g++)
set(CMAKE_AR "$ENV{TOOLCHAIN}ar" CACHE FILEPATH "CW archiver" FORCE)
endif()
project(lib60870-C)
ENABLE_TESTING()
set(LIB_VERSION_MAJOR "2")
set(LIB_VERSION_MINOR "4")
set(LIB_VERSION_PATCH "0")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/")
macro(ADD_C_FLAGS flags)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flags}")
endmacro()
# feature checks
include(CheckLibraryExists)
# check if we are on a little or a big endian
include (TestBigEndian)
test_big_endian(PLATFORM_IS_BIGENDIAN)
option(BUILD_HAL "Build the platform abstraction layer (HAL)" ON)
option(BUILD_COMMON "Build common code (shared with other libraries - e.g. libiec61850)" ON)
option(BUILD_EXAMPLES "Build the examples" ON)
option(BUILD_TESTS "Build the tests" ON)
if(BUILD_HAL)
## Detect mbedtls dependency folders. Accept folder names with patch/minor suffixes
set(MBEDTLS_DIR "")
file(GLOB MBEDTLS_2_28_CANDIDATES "${CMAKE_CURRENT_LIST_DIR}/dependencies/mbedtls-2.28*")
file(GLOB MBEDTLS_3_6_CANDIDATES "${CMAKE_CURRENT_LIST_DIR}/dependencies/mbedtls-3.6*")
if(MBEDTLS_2_28_CANDIDATES)
list(GET MBEDTLS_2_28_CANDIDATES 0 MBEDTLS_DIR)
set(WITH_MBEDTLS 1)
set(WITH_SEC_AUTH 1)
add_definitions(-DWITH_MBEDTLS=1)
message("mbedtls 2.28 found: ${MBEDTLS_DIR}")
elseif(MBEDTLS_3_6_CANDIDATES)
list(GET MBEDTLS_3_6_CANDIDATES 0 MBEDTLS_DIR)
set(WITH_MBEDTLS3 1)
set(WITH_SEC_AUTH 1)
add_definitions(-DWITH_MBEDTLS3=1)
message("mbedtls 3.6 found: ${MBEDTLS_DIR}")
else()
message("NOTE: mbedtls 2.28 or 3.6 is required for TLS support and secure authentication!")
endif()
endif(BUILD_HAL)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/config
${CMAKE_CURRENT_LIST_DIR}/src/file-service
${CMAKE_CURRENT_LIST_DIR}/src/inc/api
${CMAKE_CURRENT_LIST_DIR}/src/inc/internal
${CMAKE_CURRENT_LIST_DIR}/src/common/inc
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc
)
if(MBEDTLS_DIR)
# choose appropriate HAL adapter (mbedtls vs mbedtls3)
if(WITH_MBEDTLS3)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/src/hal/tls/mbedtls3
${MBEDTLS_DIR}/include
)
file(GLOB tls_SRCS ${MBEDTLS_DIR}/library/*.c)
else()
include_directories(
${CMAKE_CURRENT_LIST_DIR}/src/hal/tls/mbedtls
${MBEDTLS_DIR}/include
)
file(GLOB tls_SRCS ${MBEDTLS_DIR}/library/*.c)
endif()
add_definitions(-DCONFIG_CS104_SUPPORT_TLS=1)
add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h")
endif()
set(API_HEADERS
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_time.h
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_thread.h
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_socket.h
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_serial.h
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/hal_base.h
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/tls_config.h
${CMAKE_CURRENT_LIST_DIR}/src/hal/inc/tls_ciphers.h
${CMAKE_CURRENT_LIST_DIR}/src/common/inc/linked_list.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs101_master.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs101_slave.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs104_slave.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/iec60870_master.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/iec60870_slave.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/iec60870_common.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs101_information_objects.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/cs104_connection.h
${CMAKE_CURRENT_LIST_DIR}/src/inc/api/link_layer_parameters.h
${CMAKE_CURRENT_LIST_DIR}/src/file-service/cs101_file_service.h
)
include(CheckCCompilerFlag)
check_c_compiler_flag("-Wredundant-decls" SUPPORT_REDUNDANT_DECLS)
if (SUPPORT_REDUNDANT_DECLS)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wredundant-decls")
endif(SUPPORT_REDUNDANT_DECLS)
# write the detected stuff to this file
# configure_file(config/lib60870_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config/lib60870_config.h)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/src)
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/modules/sec-auth)
add_compile_definitions(SEC_AUTH_60870_5_7)
include(${CMAKE_CURRENT_LIST_DIR}/modules/sec-auth/CMakeLists.txt)
endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/modules/sec-auth)
if(BUILD_EXAMPLES)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/examples)
endif(BUILD_EXAMPLES)
if(BUILD_TESTS)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/tests)
endif(BUILD_TESTS)
INSTALL(FILES ${API_HEADERS} DESTINATION include/lib60870 COMPONENT Development)
IF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake")
INCLUDE(InstallRequiredSystemLibraries)
SET(CPACK_PACKAGE_DESCRIPTION "IEC 60870-5-101/104 master/slave library")
SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "IEC 60870-5-101/104 master/slave library")
SET(CPACK_PACKAGE_VENDOR "MZ Automation GmbH")
SET(CPACK_PACKAGE_CONTACT "info@mz-automation.de")
SET(CPACK_PACKAGE_VERSION_MAJOR "${LIB_VERSION_MAJOR}")
SET(CPACK_PACKAGE_VERSION_MINOR "${LIB_VERSION_MINOR}")
SET(CPACK_PACKAGE_VERSION_PATCH "${LIB_VERSION_PATCH}")
SET(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}_${CMAKE_SYSTEM_PROCESSOR}")
SET(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}_${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
SET(CPACK_COMPONENTS_ALL Libraries Development Applications)
#set(CPACK_PACKAGE_INSTALL_DIRECTORY "${CMAKE_PROJECT_NAME}")
INCLUDE(CPack)
ENDIF(EXISTS "${CMAKE_ROOT}/Modules/CPack.cmake")

2441
lib60870-C/Doxyfile Normal file

File diff suppressed because it is too large Load Diff

185
lib60870-C/Makefile Normal file
View File

@ -0,0 +1,185 @@
LIB60870_HOME=.
include make/target_system.mk
ifndef WITHOUT_COMMON
LIB_SOURCE_DIRS = src/common
endif
LIB_SOURCE_DIRS += src/iec60870
LIB_SOURCE_DIRS += src/iec60870/cs101
LIB_SOURCE_DIRS += src/iec60870/cs104
LIB_SOURCE_DIRS += src/iec60870/link_layer
LIB_SOURCE_DIRS += src/iec60870/apl
ifndef WITHOUT_HAL
ifeq ($(HAL_IMPL), WIN32)
LIB_SOURCE_DIRS += src/hal/socket/win32
LIB_SOURCE_DIRS += src/hal/thread/win32
LIB_SOURCE_DIRS += src/hal/time/win32
LIB_SOURCE_DIRS += src/hal/serial/win32
LIB_SOURCE_DIRS += src/hal/memory
else ifeq ($(HAL_IMPL), POSIX)
LIB_SOURCE_DIRS += src/hal/socket/linux
LIB_SOURCE_DIRS += src/hal/thread/linux
LIB_SOURCE_DIRS += src/hal/time/unix
LIB_SOURCE_DIRS += src/hal/serial/linux
LIB_SOURCE_DIRS += src/hal/memory
else ifeq ($(HAL_IMPL), BSD)
LIB_SOURCE_DIRS += src/hal/socket/bsd
LIB_SOURCE_DIRS += src/hal/thread/bsd
LIB_SOURCE_DIRS += src/hal/time/unix
LIB_SOURCE_DIRS += src/hal/memory
endif
# Auto-detect mbedtls dependency folder if not explicitly provided.
# Prefer mbedtls-3.6* over mbedtls-2.28*, and pick the highest patch version when multiple exist.
# If the user sets WITH_MBEDTLS or WITH_MBEDTLS3 externally, that will be respected.
ifeq ($(strip $(WITH_MBEDTLS)),)
ifeq ($(strip $(WITH_MBEDTLS3)),)
# try mbedtls-3.6* first (only directories, exclude archives)
MBEDTLS_DIR := $(shell for d in $(LIB60870_HOME)/dependencies/mbedtls-3.6*; do [ -d "$$d" ] && echo "$$d"; done | sort -V | tail -n1)
ifeq ($(strip $(MBEDTLS_DIR)),)
# fallback to mbedtls-2.28* (only directories)
MBEDTLS_DIR := $(shell for d in $(LIB60870_HOME)/dependencies/mbedtls-2.28*; do [ -d "$$d" ] && echo "$$d"; done | sort -V | tail -n1)
ifneq ($(strip $(MBEDTLS_DIR)),)
WITH_MBEDTLS := 1
endif
else
WITH_MBEDTLS3 := 1
endif
endif
endif
ifdef WITH_MBEDTLS
LIB_SOURCE_DIRS += $(MBEDTLS_DIR)/library
LIB_SOURCE_DIRS += src/hal/tls/mbedtls
LIB_INCLUDE_DIRS += src/hal/tls/mbedtls
LIB_INCLUDE_DIRS += $(MBEDTLS_DIR)/include
CFLAGS += -D'MBEDTLS_CONFIG_FILE="mbedtls_config.h"'
CFLAGS += -D'CONFIG_CS104_SUPPORT_TLS=1'
endif
ifdef WITH_MBEDTLS3
LIB_SOURCE_DIRS += $(MBEDTLS_DIR)/library
LIB_SOURCE_DIRS += src/hal/tls/mbedtls3
LIB_INCLUDE_DIRS += src/hal/tls/mbedtls3
LIB_INCLUDE_DIRS += $(MBEDTLS_DIR)/include
CFLAGS += -D'MBEDTLS_CONFIG_FILE="mbedtls_config.h"'
CFLAGS += -D'CONFIG_CS104_SUPPORT_TLS=1'
endif
endif
LIB_INCLUDE_DIRS += config
LIB_INCLUDE_DIRS += src/inc/api
LIB_INCLUDE_DIRS += src/inc/internal
LIB_INCLUDE_DIRS += src/hal/inc
LIB_INCLUDE_DIRS += src/common/inc
LIB_INCLUDES = $(addprefix -I,$(LIB_INCLUDE_DIRS))
ifndef INSTALL_PREFIX
INSTALL_PREFIX = ./.install
endif
LIB_API_HEADER_FILES = src/hal/inc/hal_time.h
LIB_API_HEADER_FILES += src/hal/inc/hal_thread.h
LIB_API_HEADER_FILES += src/hal/inc/hal_socket.h
LIB_API_HEADER_FILES += src/hal/inc/hal_serial.h
LIB_API_HEADER_FILES += src/hal/inc/hal_base.h
LIB_API_HEADER_FILES += src/common/inc/linked_list.h
LIB_API_HEADER_FILES += src/inc/api/cs101_information_objects.h
LIB_API_HEADER_FILES += src/inc/api/cs101_master.h
LIB_API_HEADER_FILES += src/inc/api/cs101_slave.h
LIB_API_HEADER_FILES += src/inc/api/cs104_connection.h
LIB_API_HEADER_FILES += src/inc/api/cs104_slave.h
LIB_API_HEADER_FILES += src/inc/api/iec60870_common.h
LIB_API_HEADER_FILES += src/inc/api/iec60870_master.h
LIB_API_HEADER_FILES += src/inc/api/iec60870_slave.h
LIB_API_HEADER_FILES += src/inc/api/link_layer_parameters.h
LIB_API_HEADER_FILES += src/hal/inc/tls_config.h
LIB_API_HEADER_FILES += src/hal/inc/tls_ciphers.h
LIB_API_HEADER_FILES += src/file-service/cs101_file_service.h
LIB_TEST_SOURCES = tests/all_tests.c
LIB_TEST_SOURCES += tests/unity/unity.c
LIB_TEST_INCLUDE_DIRS = tests/unity
TEST_INCLUDES = $(addprefix -I,$(LIB_TEST_INCLUDE_DIRS))
get_sources_from_directory = $(wildcard $1/*.c)
get_sources = $(foreach dir, $1, $(call get_sources_from_directory,$(dir)))
src_to = $(addprefix $(LIB_OBJS_DIR)/,$(subst .c,$1,$2))
LIB_SOURCES = $(call get_sources,$(LIB_SOURCE_DIRS))
LIB_OBJS = $(call src_to,.o,$(LIB_SOURCES))
TEST_OBJS = $(call src_to,.o,$(LIB_TEST_SOURCES))
CFLAGS += -std=gnu99
#CFLAGS += -Wno-error=format
CFLAGS += -Wstrict-prototypes -Wall -Wextra
ifneq ($(HAL_IMPL), WIN32)
CFLAGS += -Wuninitialized
endif
CFLAGS += -Wsign-compare
CFLAGS += -Wpointer-arith
CFLAGS += -Wnested-externs
CFLAGS += -Wmissing-declarations
CFLAGS += -Wshadow
CFLAGS += -Wall
#CFLAGS += -Werror
all: lib
static_checks: lib
splint -preproc +posixlib +skip-sys-headers +gnuextensions $(LIB_INCLUDES) $(LIB_SOURCES)
cppcheck: lib
cppcheck --xml --force --std=c99 --enable=all $(LIB_INCLUDES) $(LIB_SOURCES) 2> cppcheck-output.xml
lib: $(LIB_NAME)
tests: $(TEST_NAME)
dynlib: CFLAGS += -fPIC
dynlib: $(DYN_LIB_NAME)
.PHONY: examples
examples:
cd examples; $(MAKE)
$(TEST_NAME): $(LIB_OBJS) $(TEST_OBJS)
$(CC) -o $(TEST_NAME) $(LIB_OBJS) $(TEST_OBJS) -lpthread
$(LIB_NAME): $(LIB_OBJS)
$(AR) r $(LIB_NAME) $(LIB_OBJS)
$(RANLIB) $(LIB_NAME)
$(DYN_LIB_NAME): $(LIB_OBJS)
$(CC) $(LDFLAGS) $(DYNLIB_LDFLAGS) -shared -o $(DYN_LIB_NAME) $(LIB_OBJS) $(LDLIBS)
$(LIB_OBJS_DIR)/%.o: %.c config
@echo compiling $(notdir $<)
$(SILENCE)mkdir -p $(dir $@)
$(CC) $(CFLAGS) -c $(LIB_INCLUDES) $(TEST_INCLUDES) $(OUTPUT_OPTION) $<
install: $(LIB_NAME)
mkdir -p $(INSTALL_PREFIX)/include
mkdir -p $(INSTALL_PREFIX)/lib
cp $(LIB_API_HEADER_FILES) $(INSTALL_PREFIX)/include
cp $(LIB_NAME) $(INSTALL_PREFIX)/lib
clean:
rm -f $(EXAMPLES)
rm -rf $(LIB_OBJS_DIR)

View File

@ -0,0 +1,113 @@
/*
* lib60870_config.h
*/
#ifndef CONFIG_LIB60870_CONFIG_H_
#define CONFIG_LIB60870_CONFIG_H_
/* print debugging information with printf if set to 1 */
#ifndef CONFIG_DEBUG_OUTPUT
#define CONFIG_DEBUG_OUTPUT 0
#endif
/**
* Define the maximum slave message queue size (for CS 101)
*
* When set to -1 the message queue size is not limited can be set by the application
*/
#ifndef CONFIG_SLAVE_MESSAGE_QUEUE_SIZE
#define CONFIG_SLAVE_MESSAGE_QUEUE_SIZE -1
#endif
/**
* Define the default size for the slave (outstation) message queue. This is used also
* to buffer ASDUs in the case when the connection is lost.
*
* For each queued message a maximum of 256 bytes of memory are required. Usually messages are
* smaller so more then thus number of messages can be stored in the message queue
*/
#ifndef CONFIG_CS104_MESSAGE_QUEUE_SIZE
#define CONFIG_CS104_MESSAGE_QUEUE_SIZE 100
#endif
/**
* This is a connection specific ASDU queue for the slave (outstation). It is used for connection
* specific ASDUs like those that are automatically generated by the stack or created in
* the slave side callback. The messages in the queue are removed when the connection is lost.
*
* For each queued message about 256 bytes of memory are required.
*/
#ifndef CONFIG_CS104_MESSAGE_QUEUE_HIGH_PRIO_SIZE
#define CONFIG_CS104_MESSAGE_QUEUE_HIGH_PRIO_SIZE 50
#endif
/**
* Compile the library to use threads. This will require semaphore support
*/
#ifndef CONFIG_USE_THREADS
#define CONFIG_USE_THREADS 1
#endif
/**
* Compile the library using semaphore to protect critical objects.
* Required when CONFIG_USE_THREADS = 1.
*/
#ifndef CONFIG_USE_SEMAPHORES
#define CONFIG_USE_SEMAPHORES 1
#endif
/**
* Compile library with support for SINGLE_REDUNDANCY_GROUP server mode (only CS104 server)
*/
#ifndef CONFIG_CS104_SUPPORT_SERVER_MODE_SINGLE_REDUNDANCY_GROUP
#define CONFIG_CS104_SUPPORT_SERVER_MODE_SINGLE_REDUNDANCY_GROUP 1
#endif
/**
* Compile library with support for MULTIPLE_REDUNDANCY_GROUPS server mode (only CS104 server)
*/
#ifndef CONFIG_CS104_SUPPORT_SERVER_MODE_MULTIPLE_REDUNDANCY_GROUPS
#define CONFIG_CS104_SUPPORT_SERVER_MODE_MULTIPLE_REDUNDANCY_GROUPS 1
#endif
/**
* Compile library with support for CONNECTION_IS_REDUNDANCY_GROUP server mode (only CS104 server)
*/
#ifndef CONFIG_CS104_SUPPORT_SERVER_MODE_CONNECTION_IS_REDUNDANCY_GROUP
#define CONFIG_CS104_SUPPORT_SERVER_MODE_CONNECTION_IS_REDUNDANCY_GROUP 1
#endif
/**
* Set the maximum number of client connections
*/
#ifndef CONFIG_CS104_MAX_CLIENT_CONNECTIONS
#define CONFIG_CS104_MAX_CLIENT_CONNECTIONS 100
#endif
/* activate TCP keep alive mechanism. 1 -> activate */
#ifndef CONFIG_ACTIVATE_TCP_KEEPALIVE
#define CONFIG_ACTIVATE_TCP_KEEPALIVE 0
#endif
/* time (in s) between last message and first keepalive message */
#ifndef CONFIG_TCP_KEEPALIVE_IDLE
#define CONFIG_TCP_KEEPALIVE_IDLE 5
#endif
/* time between subsequent keepalive messages if no ack received */
#ifndef CONFIG_TCP_KEEPALIVE_INTERVAL
#define CONFIG_TCP_KEEPALIVE_INTERVAL 2
#endif
/* number of not missing keepalive responses until socket is considered dead */
#ifndef CONFIG_TCP_KEEPALIVE_CNT
#define CONFIG_TCP_KEEPALIVE_CNT 2
#endif
/* test command without timestamp is not allowed for CS104. Set to 1 to enable it anyway. */
#ifndef CONFIG_ALLOW_C_TS_NA_1_FOR_CS104
#define CONFIG_ALLOW_C_TS_NA_1_FOR_CS104 0
#endif
#endif /* CONFIG_LIB60870_CONFIG_H_ */

View File

@ -0,0 +1,45 @@
# README
Please add optional dependencies in this folder
## TLS Support
At the moment there are two different options for TLS support.
* mbedtls 2.28 supports TLS version 1.2, 1.1 and older versions of TLS
* mbedtls 3.6 supports TLS versions 1.2 and 1.3
### mbedtls 2.28
For TLS support with mbedtls 2.28 download the source tarball of version 2.28.x and extract here in the subfolder (Version 2.28.10 https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-2.28.10/mbedtls-2.28.10.tar.bz2)
On a Linux command line enter the following commands in this directory:
wget https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-2.28.10/mbedtls-2.28.10.tar.bz2
tar xfj mbedtls-2.28.10.tar.bz2
To allow session resumption within an ongoing TLS connection (required by IEC 62351-3:2023) a patch (mbedtls_2.28_ssl_srv.c.patch) has to be applied to the mbedtls source code:
patch mbedtls-2.28.10/library/ssl_srv.c mbedtls_2.28_ssl_srv.c.patch
When running cmake the build system will automatically find the mbedtls source code and includes it into the library build.
When using make the make command has to be invoked with the WITH_MBEDTLS=1 parameter
make WITH_MBEDTLS=1
### mbedtls 3.6
For TLS support with mbedtls 3.6 download the source tarball of version 3.6.x and extract here in the subfolder (Version 3.6.2 https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.2/mbedtls-3.6.2.tar.bz2)
On a Linux command line enter the following commands in this directory:
wget https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-3.6.2/mbedtls-3.6.2.tar.bz2
tar xfj mbedtls-3.6.2.tar.bz2
When running cmake the build system will automatically find the mbedtls source code and includes it into the library build.
When using make the make command has to be invoked with the WITH_MBEDTLS3=1 parameter
make WITH_MBEDTLS3=1

View File

@ -0,0 +1,42 @@
--- orig/mbedtls-2.28.10/library/ssl_srv.c 2025-03-24 11:49:00.000000000 +0000
+++ mbedtls-2.28.10/library/ssl_srv.c 2026-01-30 18:15:47.656423746 +0000
@@ -2691,15 +2691,36 @@
if (session->id_len == 0) {
return;
}
- if (ssl->conf->f_get_cache == NULL) {
- return;
- }
#if defined(MBEDTLS_SSL_RENEGOTIATION)
if (ssl->renego_status != MBEDTLS_SSL_INITIAL_HANDSHAKE) {
+ if (ssl->session != NULL &&
+ ssl->session->id_len == session->id_len &&
+ memcmp(ssl->session->id, session->id, session->id_len) == 0 &&
+ mbedtls_ssl_session_copy(session, ssl->session) == 0) {
+ MBEDTLS_SSL_DEBUG_MSG(3, ("renegotiation: resuming existing session"));
+ ssl->handshake->resume = 1;
+ ssl->secure_renegotiation = MBEDTLS_SSL_SECURE_RENEGOTIATION;
+
+ /* call session cache just to inform application about session resumption */
+ if (ssl->conf->f_get_cache != NULL) {
+ mbedtls_ssl_session_init(&session_tmp);
+
+ session_tmp.id_len = session->id_len;
+ memcpy(session_tmp.id, session->id, session->id_len);
+
+ ret = ssl->conf->f_get_cache(ssl->conf->p_cache,
+ &session_tmp);
+ }
+ return;
+ }
return;
}
#endif
+ if (ssl->conf->f_get_cache == NULL) {
+ return;
+ }
+
mbedtls_ssl_session_init(&session_tmp);
session_tmp.id_len = session->id_len;

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

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)

View File

@ -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
)

View File

@ -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)

View File

@ -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);
}

View File

@ -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
)

View File

@ -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)

View File

@ -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;
}

View File

@ -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
)

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)

View File

@ -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;
}

View File

@ -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
)

View File

@ -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)

View File

@ -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;
}

View File

@ -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
)

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)

View File

@ -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;
}

View File

@ -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
)

View File

@ -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)

View File

@ -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");
}

View File

@ -0,0 +1,4 @@
add_executable(cs104_client_no_threads cs104_client_no_threads.c)
target_link_libraries(cs104_client_no_threads PRIVATE lib60870)

View File

@ -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;
}

View File

@ -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
)

View File

@ -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)

View File

@ -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);
}

View File

@ -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
)

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)

View File

@ -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;
}

View File

@ -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
)

View File

@ -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);
}

View File

@ -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
)

View File

@ -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)

View File

@ -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);
}

View File

@ -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
)

View File

@ -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)

View File

@ -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);
}

View File

@ -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
)

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)

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

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);
}

View File

@ -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
)

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)

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

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);
}

View File

@ -0,0 +1,7 @@
$(LIB_NAME):
cd $(LIB60870_HOME); $(MAKE) -f Makefile
lib: $(LIB_NAME)
libclean: clean
cd $(LIB60870_HOME); $(MAKE) -f Makefile clean

View File

@ -0,0 +1,3 @@
INCLUDES += -I$(LIB60870_HOME)/src/inc/api
INCLUDES += -I$(LIB60870_HOME)/src/hal/inc
INCLUDES += -I$(LIB60870_HOME)/src/tls

View File

@ -0,0 +1,185 @@
UNAME := $(shell uname)
MIPSEL_TOOLCHAIN_PREFIX=mipsel-openwrt-linux-
#ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabihf-
#ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabi-
#ARM_TOOLCHAIN_PREFIX=arm-poky-linux-gnueabi-
ARM_TOOLCHAIN_PREFIX=arm-linux-gnueabihf-
UCLINUX_ARM_TOOLCHAIN_PREFIX=arm-uclinux-elf-
UCLINUX_XPORT_TOOLCHAIN_PREFIX=m68k-uclinux-
#MINGW_TOOLCHAIN_PREFIX=i586-mingw32msvc-
MINGW_TOOLCHAIN_PREFIX=i686-w64-mingw32-
MINGW64_TOOLCHAIN_PREFIX=x86_64-w64-mingw32-
#POWERPC_TOOLCHAIN_PREFIX=powerpc-poky-linux-
POWERPC_TOOLCHAIN_PREFIX=powerpc-linux-gnu-
ifndef TARGET
ifeq ($(UNAME), Linux)
TARGET=POSIX
else ifeq ($(findstring MINGW,$(UNAME)), MINGW)
TARGET=WIN32
else ifeq ($(UNAME), Darwin)
TARGET=BSD
else ifeq ($(UNAME), FreeBSD)
TARGET=BSD
endif
endif
ifeq ($(TARGET), WIN32)
WINDOWS=1
endif
ifeq ($(TARGET), WIN64)
WINDOWS=1
endif
ifdef WINDOWS
ifeq ($(UNAME), Linux)
ifeq ($(TARGET), WIN32)
TOOLCHAIN_PREFIX=$(MINGW_TOOLCHAIN_PREFIX)
endif
ifeq ($(TARGET), WIN64)
TOOLCHAIN_PREFIX=$(MINGW64_TOOLCHAIN_PREFIX)
endif
else
TOOLCHAIN_PREFIX=
endif
endif
ifeq ($(TARGET), LINUX-POWERPC)
TOOLCHAIN_PREFIX=$(POWERPC_TOOLCHAIN_PREFIX)
endif
ifeq ($(TARGET), LINUX-MIPSEL)
TOOLCHAIN_PREFIX=$(MIPSEL_TOOLCHAIN_PREFIX)
endif
ifeq ($(TARGET), LINUX-ARM)
TOOLCHAIN_PREFIX=$(ARM_TOOLCHAIN_PREFIX)
CFLAGS += -mno-unaligned-access
# CFLAGS += -mcpu=arm926ej-s
endif
ifeq ($(TARGET), UCLINUX-WAGO)
TOOLCHAIN_PREFIX=$(UCLINUX_ARM_TOOLCHAIN_PREFIX)
CFLAGS += -msoft-float
CFLAGS += -Wall
CFLAGS += -DEMBED
CFLAGS += -Dlinux -D__linux__ -Dunix
CFLAGS += -D__uClinux__
CFLAGS += -DTARGET=UCLINUX-WAGO
LDFLAGS += -Wl,-move-rodata -Wl,-elf2flt
endif
ifeq ($(TARGET), UCLINUX-XPORT)
TOOLCHAIN_PREFIX=$(UCLINUX_XPORT_TOOLCHAIN_PREFIX)
CFLAGS += -DPLATFORM_BYTE_ORDER
CFLAGS += -mcpu=5208
CFLAGS += -fno-builtin -fno-common
CFLAGS += -fno-dwarf2-cfi-asm -msep-data -DCONFIG_COLDFIRE -D__linux__ -Dunix -D__uClinux__
endif
ifdef WINDOWS
HAL_IMPL = WIN32
LIB_OBJS_DIR = $(LIB60870_HOME)/build_win32
CFLAGS=-g -DWIN32
ifeq ($(TARGET), WIN32)
CFLAGS+=-m32
endif
ifeq ($(TARGET), WIN64)
CFLAGS+=-m64
endif
LDLIBS=-lws2_32
DYNLIB_LDFLAGS=-Wl,-no-undefined -Wl,--enable-runtime-pseudo-reloc -Wl,--output-def,libiec61850.def,--out-implib,libiec61850.a
# on Windows: only compile with ethernet support if winpcap files are in third_party/winpcap!
ifneq (, $(wildcard $(LIB60870_HOME)/third_party/winpcap/Include/.))
ifeq ($(TARGET), WIN64)
LDFLAGS += -L$(LIB60870_HOME)/third_party/winpcap/Lib/x64
else
LDFLAGS += -L$(LIB60870_HOME)/third_party/winpcap/Lib
endif
LDLIBS+=-liphlpapi -lwpcap
else
$(warning winpcap not found - will build without GOOSE support!)
CFLAGS += -DEXCLUDE_ETHERNET_WINDOWS
EXCLUDE_ETHERNET_WINDOWS = 1
endif
else
ifeq ($(TARGET), BSD)
HAL_IMPL = BSD
else
HAL_IMPL = POSIX
endif
LDLIBS = -lpthread
ifeq ($(TARGET), LINUX-MIPSEL)
LIB_OBJS_DIR = $(LIB60870_HOME)/build-mipsel
else ifeq ($(TARGET), LINUX-ARM)
LIB_OBJS_DIR = $(LIB60870_HOME)/build-arm
else ifeq ($(TARGET), UCLINUX-WAGO)
LIB_OBJS_DIR = $(LIB60870_HOME)/build-wago
CFLAGS += -DTARGET_SYSTEM_UCLINUX_WAGO
else ifeq ($(TARGET), LINUX-POWERPC)
LIB_OBJS_DIR = $(LIB60870_HOME)/build-powerpc
else
LIB_OBJS_DIR = $(LIB60870_HOME)/build
endif
CFLAGS += -g
#CFLAGS += -Os
DYNLIB_LDFLAGS=-lpthread
endif
ifneq ($(TARGET), CLANG-CHECK)
CC=$(TOOLCHAIN_PREFIX)gcc
CPP=$(TOOLCHAIN_PREFIX)g++
endif
ifeq ($(TARGET), BSD)
CC=cc
CPP=c++
endif
AR=$(TOOLCHAIN_PREFIX)ar
RANLIB=$(TOOLCHAIN_PREFIX)ranlib
ifeq ($(TARGET), WIN32)
PROJECT_BINARY_NAME := $(PROJECT_BINARY_NAME).exe
endif
LIB_NAME = $(LIB_OBJS_DIR)/liblib60870.a
TEST_NAME = $(LIB_OBJS_DIR)/tests.exe
ifeq ($(TARGET), BSD)
CFLAGS += -arch i386
LDFLAGS += -arch i386
endif
ifeq ($(TARGET), WIN32)
DYN_LIB_NAME = $(LIB_OBJS_DIR)/lib60870.dll
else
ifeq ($(TARGET), BSD)
DYN_LIB_NAME = $(LIB_OBJS_DIR)/liblib60870.dylib
else
DYN_LIB_NAME = $(LIB_OBJS_DIR)/liblib60870.so
endif
endif

View File

@ -0,0 +1 @@
This folder contains optional library add-ons (e.g. for secure authentication).

View File

@ -0,0 +1,185 @@
set (lib_common_SRCS
./file-service/file_server.c
./iec60870/apl/cpXXtime2a.c
./iec60870/cs101/cs101_asdu.c
./iec60870/cs101/cs101_bcr.c
./iec60870/cs101/cs101_information_objects.c
./iec60870/cs101/cs101_master_connection.c
./iec60870/cs101/cs101_master.c
./iec60870/cs101/cs101_queue.c
./iec60870/cs101/cs101_slave.c
./iec60870/cs104/cs104_connection.c
./iec60870/cs104/cs104_frame.c
./iec60870/cs104/cs104_slave.c
./iec60870/link_layer/buffer_frame.c
./iec60870/link_layer/link_layer.c
./iec60870/link_layer/serial_transceiver_ft_1_2.c
./iec60870/frame.c
./iec60870/lib60870_common.c
)
if (BUILD_COMMON)
list (APPEND lib_common_SRCS ./common/linked_list.c)
endif (BUILD_COMMON)
if (BUILD_HAL)
set (lib_linux_SRCS
./hal/serial/linux/serial_port_linux.c
./hal/socket/linux/socket_linux.c
./hal/thread/linux/thread_linux.c
./hal/time/unix/time.c
./hal/memory/lib_memory.c
)
set (lib_windows_SRCS
./hal/serial/win32/serial_port_win32.c
./hal/socket/win32/socket_win32.c
./hal/thread/win32/thread_win32.c
./hal/time/win32/time.c
./hal/memory/lib_memory.c
)
set (lib_bsd_SRCS
./hal/serial/linux/serial_port_linux.c
./hal/socket/bsd/socket_bsd.c
./hal/thread/bsd/thread_bsd.c
./hal/time/unix/time.c
./hal/memory/lib_memory.c
)
set (lib_macos_SRCS
./hal/serial/linux/serial_port_linux.c
./hal/socket/bsd/socket_bsd.c
./hal/thread/macos/thread_macos.c
./hal/time/unix/time.c
./hal/memory/lib_memory.c
)
endif (BUILD_HAL)
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/../modules/sec-auth)
include(${CMAKE_CURRENT_LIST_DIR}/../modules/sec-auth/CMakeLists_src.txt)
add_compile_definitions(SEC_AUTH_60870_5_7)
endif(EXISTS ${CMAKE_CURRENT_LIST_DIR}/../modules/sec-auth)
IF(WIN32)
IF(MSVC)
set_source_files_properties(${lib_common_SRCS} ${lib_windows_SRCS}
PROPERTIES LANGUAGE CXX)
ENDIF()
set (library_SRCS
${lib_common_SRCS}
${lib_windows_SRCS}
)
set (CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}\"/DEF:${CMAKE_CURRENT_SOURCE_DIR}/vs/lib60870.def\"")
ELSEIF(UNIX)
IF(APPLE)
set (library_SRCS
${lib_common_SRCS}
${lib_macos_SRCS}
)
ELSEIF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
set (library_SRCS
${lib_common_SRCS}
${lib_bsd_SRCS}
)
ELSE()
set (library_SRCS
${lib_common_SRCS}
${lib_linux_SRCS}
)
ENDIF(APPLE)
ENDIF(WIN32)
IF(WITH_MBEDTLS)
list (APPEND library_SRCS ${tls_SRCS})
list (APPEND library_SRCS ./hal/tls/mbedtls/tls_mbedtls.c)
add_definitions(-DLIB60870_HAS_TLS_SUPPORT=1)
ENDIF(WITH_MBEDTLS)
IF(WITH_MBEDTLS3)
list (APPEND library_SRCS ${tls_SRCS})
list (APPEND library_SRCS ./hal/tls/mbedtls3/tls_mbedtls.c)
add_definitions(-DLIB60870_HAS_TLS_SUPPORT=1)
ENDIF(WITH_MBEDTLS3)
include (GenerateExportHeader)
set(RES_FILES "")
if ( WIN32 )
# Adding RC resource file for adding information to the archive
set(RES_FILES "${CMAKE_CURRENT_BINARY_DIR}/version.rc")
message(STATUS "Generating RC file : ${RES_FILES}")
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in
${RES_FILES}
@ONLY)
if( MINGW )
set(CMAKE_RC_COMPILER_INIT windres)
ENABLE_LANGUAGE(RC)
SET(CMAKE_RC_COMPILE_OBJECT
"<CMAKE_RC_COMPILER> <FLAGS> -O coff <DEFINES> -i <SOURCE> -o <OBJECT>")
endif(MINGW)
set(library_SRCS ${library_SRCS} ${RES_FILES})
endif( WIN32 )
add_library (lib60870-shared SHARED ${library_SRCS} )
set_target_properties(lib60870-shared PROPERTIES
OUTPUT_NAME lib60870
SOVERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}"
WINDOWS_EXPORT_ALL_SYMBOLS true
)
GENERATE_EXPORT_HEADER(lib60870-shared
BASE_NAME lib60870-shared
EXPORT_MACRO_NAME lib60870-shared_EXPORT
EXPORT_FILE_NAME lib60870-shared_export.h
STATIC_DEFINE lib60870-shared_BUILT_AS_STATIC
)
add_library (lib60870 STATIC ${library_SRCS})
IF(UNIX)
target_link_libraries (lib60870
-lpthread
-lm
-lrt
)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/lib60870.pc.in
${CMAKE_CURRENT_BINARY_DIR}/lib60870.pc @ONLY
)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/lib60870.pc" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/pkgconfig")
ENDIF(UNIX)
IF(MINGW)
target_link_libraries(lib60870-shared ws2_32 iphlpapi bcrypt)
target_link_libraries(lib60870 ws2_32 iphlpapi bcrypt)
ENDIF(MINGW)
IF(MSVC)
target_link_libraries(lib60870-shared ws2_32 iphlpapi bcrypt)
target_link_libraries(lib60870 ws2_32 iphlpapi bcrypt)
ENDIF(MSVC)
install (TARGETS lib60870 lib60870-shared
RUNTIME DESTINATION bin COMPONENT Applications
ARCHIVE DESTINATION lib COMPONENT Libraries
LIBRARY DESTINATION lib COMPONENT Libraries
)

View File

@ -0,0 +1,175 @@
/*
* Copyright 2013-2022 Michael Zillgith
*
* This file is part of lib60870-C
*
* lib60870-C is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* lib60870-C is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#ifndef LINKED_LIST_H_
#define LINKED_LIST_H_
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup common_api_group
*/
/**@{*/
/**
* \defgroup LINKED_LIST LinkedList data type definition and handling functions
*/
/**@{*/
struct sLinkedList {
void* data;
struct sLinkedList* next;
};
/**
* \brief Reference to a linked list or to a linked list element.
*/
typedef struct sLinkedList* LinkedList;
/**
* \brief Create a new LinkedList object
*
* \return the newly created LinkedList instance
*/
LinkedList
LinkedList_create(void);
/**
* \brief Delete a LinkedList object
*
* This function destroy the LinkedList object. It will free all data structures used by the LinkedList
* instance. It will call free for all elements of the linked list. This function should only be used if
* simple objects (like dynamically allocated strings) are stored in the linked list.
*
* \param self the LinkedList instance
*/
void
LinkedList_destroy(LinkedList self);
typedef void (*LinkedListValueDeleteFunction) (void*);
/**
* \brief Delete a LinkedList object
*
* This function destroy the LinkedList object. It will free all data structures used by the LinkedList
* instance. It will call a user provided function for each data element. This user provided function is
* responsible to properly free the data element.
*
* \param self the LinkedList instance
* \param valueDeleteFunction a function that is called for each data element of the LinkedList with the pointer
* to the linked list data element.
*/
void
LinkedList_destroyDeep(LinkedList self, LinkedListValueDeleteFunction valueDeleteFunction);
/**
* \brief Delete a LinkedList object without freeing the element data
*
* This function should be used statically allocated data objects are stored in the LinkedList instance.
* Other use cases would be if the data elements in the list should not be deleted.
*
* \param self the LinkedList instance
*/
void
LinkedList_destroyStatic(LinkedList self);
/**
* \brief Add a new element to the list
*
* This function will add a new data element to the list. The new element will the last element in the
* list.
*
* \param self the LinkedList instance
* \param data data to append to the LinkedList instance
*/
void
LinkedList_add(LinkedList self, void* data);
/**
* \brief Removed the specified element from the list
*
* \param self the LinkedList instance
* \param data data to remove from the LinkedList instance
*/
bool
LinkedList_remove(LinkedList self, const void* data);
/**
* \brief Get the list element specified by index (starting with 0).
*
* \param self the LinkedList instance
* \param index index of the requested element.
*/
LinkedList
LinkedList_get(LinkedList self, int index);
/**
* \brief Get the next element in the list (iterator).
*
* \param self the LinkedList instance
*/
LinkedList
LinkedList_getNext(LinkedList self);
/**
* \brief Get the last element in the list.
*
* \param listElement the LinkedList instance
*/
LinkedList
LinkedList_getLastElement(LinkedList self);
/**
* \brief Insert a new element int the list
*
* \param listElement the LinkedList instance
*/
LinkedList
LinkedList_insertAfter(LinkedList listElement, void* data);
/**
* \brief Get the size of the list
*
* \param self the LinkedList instance
*
* \return number of data elements stored in the list
*/
int
LinkedList_size(LinkedList self);
void*
LinkedList_getData(LinkedList self);
/**@}*/
/**@}*/
#ifdef __cplusplus
}
#endif
#endif /* LINKED_LIST_H_ */

View File

@ -0,0 +1,186 @@
/*
* Copyright 2013-2022 Michael Zillgith
*
* This file is part of lib60870-C
*
* lib60870-C is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* lib60870-C is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "linked_list.h"
#include "lib_memory.h"
#ifndef CONFIG_COMPILE_WITHOUT_COMMON
LinkedList
LinkedList_getLastElement(LinkedList list)
{
while (list->next != NULL) {
list = list->next;
}
return list;
}
LinkedList
LinkedList_create()
{
LinkedList newList;
newList = (LinkedList) GLOBAL_MALLOC(sizeof(struct sLinkedList));
newList->data = NULL;
newList->next = NULL;
return newList;
}
/**
* Destroy list (free). Also frees element data with helper function.
*/
void
LinkedList_destroyDeep(LinkedList list, LinkedListValueDeleteFunction valueDeleteFunction)
{
LinkedList nextElement = list;
LinkedList currentElement;
do {
currentElement = nextElement;
nextElement = currentElement->next;
if (currentElement->data != NULL)
valueDeleteFunction(currentElement->data);
GLOBAL_FREEMEM(currentElement);
}
while (nextElement != NULL);
}
void
LinkedList_destroy(LinkedList list)
{
LinkedList_destroyDeep(list, Memory_free);
}
/**
* Destroy list (free) without freeing the element data
*/
void
LinkedList_destroyStatic(LinkedList list)
{
LinkedList nextElement = list;
LinkedList currentElement;
do {
currentElement = nextElement;
nextElement = currentElement->next;
GLOBAL_FREEMEM(currentElement);
}
while (nextElement != NULL);
}
int
LinkedList_size(LinkedList list)
{
LinkedList nextElement = list;
int size = 0;
while (nextElement->next != NULL) {
nextElement = nextElement->next;
size++;
}
return size;
}
void
LinkedList_add(LinkedList list, void* data)
{
LinkedList newElement = LinkedList_create();
newElement->data = data;
LinkedList listEnd = LinkedList_getLastElement(list);
listEnd->next = newElement;
}
bool
LinkedList_remove(LinkedList list, const void* data)
{
LinkedList lastElement = list;
LinkedList currentElement = list->next;
while (currentElement != NULL) {
if (currentElement->data == data) {
lastElement->next = currentElement->next;
GLOBAL_FREEMEM(currentElement);
return true;
}
lastElement = currentElement;
currentElement = currentElement->next;
}
return false;
}
LinkedList
LinkedList_insertAfter(LinkedList list, void* data)
{
LinkedList originalNextElement = LinkedList_getNext(list);
LinkedList newElement = LinkedList_create();
newElement->data = data;
newElement->next = originalNextElement;
list->next = newElement;
return newElement;
}
LinkedList
LinkedList_getNext(LinkedList list)
{
return list->next;
}
LinkedList
LinkedList_get(LinkedList list, int index)
{
LinkedList element = LinkedList_getNext(list);
int i = 0;
while (i < index) {
element = LinkedList_getNext(element);
if (element == NULL)
return NULL;
i++;
}
return element;
}
void*
LinkedList_getData(LinkedList self)
{
return self->data;
}
#endif

View File

@ -0,0 +1,143 @@
/*
* Copyright 2016-2022 Michael Zillgith
*
* This file is part of lib60870-C
*
* lib60870-C is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* lib60870-C is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#ifndef LIB60870_C_FILE_SERVICE_FILE_SERVICE_H_
#define LIB60870_C_FILE_SERVICE_FILE_SERVICE_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include "iec60870_common.h"
#include "iec60870_slave.h"
typedef enum
{
CS101_FILE_ERROR_SUCCESS,
CS101_FILE_ERROR_TIMEOUT,
CS101_FILE_ERROR_FILE_NOT_READY,
CS101_FILE_ERROR_SECTION_NOT_READY,
CS101_FILE_ERROR_UNKNOWN_CA,
CS101_FILE_ERROR_UNKNOWN_IOA,
CS101_FILE_ERROR_UNKNOWN_SERVICE,
CS101_FILE_ERROR_PROTOCOL_ERROR,
CS101_FILE_ERROR_ABORTED_BY_REMOTE
} CS101_FileErrorCode;
typedef struct sCS101_FileServer* CS101_FileServer;
typedef struct sCS101_IFileReceiver* CS101_IFileReceiver;
struct sCS101_IFileReceiver
{
void* object; /* user provided context object */
void (*finished) (CS101_IFileReceiver self, CS101_FileErrorCode result);
void (*segmentReceived) (CS101_IFileReceiver self, uint8_t sectionName, int offset, int size, uint8_t* data);
};
typedef struct sCS101_IFileProvider* CS101_IFileProvider;
struct sCS101_IFileProvider {
int ca;
int ioa;
uint8_t nof;
void* object; /* user provided context object */
uint64_t (*getFileDate) (CS101_IFileProvider self);
int (*getFileSize) (CS101_IFileProvider self);
int (*getSectionSize) (CS101_IFileProvider self, int sectionNumber);
/**
* \brief Get section data (will be called by the file service implementation)
*
* \param offset byte offset of segment in the section
* \param size of segment (has to be at most the size of the provided data buffer)
* \param data buffer to store the segment data
*/
bool (*getSegmentData) (CS101_IFileProvider self, int sectionNumber, int offset, int size, uint8_t* data);
void (*transferComplete) (CS101_IFileProvider self, bool success);
};
/**
* \brief Will be called by the \ŕef CS101_FileServer when the master sends a FILE READY (file download announcement) message to the slave
*
* \param parameter user provided context parameter
* \param ca CA of the file
* \param ioa IOA of the file
* \param nof the name of file (file type) of the file
* \param lengthOfFile the length of the file to be sent
* \param[out] errCode error code of file service (0 - ok, 1 - unknown CA, 2 - unknown IOA, 3-unknown name of file, 4 - file not ready )
*
* \return file handle (interface to access the file)
*/
typedef CS101_IFileReceiver (*CS101_FileReadyHandler) (void* parameter, int ca, int ioa, uint16_t nof, int lengthOfFile, int* errCode);
typedef struct sCS101_FilesAvailable* CS101_FilesAvailable;
struct sCS101_FilesAvailable
{
/**
* Used to handle request directory call
*
* \return The next file or NULL if no more file available
*/
CS101_IFileProvider (*getNextFile) (void* parameter, CS101_IFileProvider continueAfter);
CS101_IFileProvider (*getFile) (void* parameter, int ca, int ioa, uint16_t nof, int* errCode);
void* parameter;
};
/**
* \brief Handle transparent files (implement CS101_IFileProvider interface)
*/
typedef struct sCS101_TransparentFile* CS101_TransparentFile;
CS101_IFileProvider
CS101_TransparentFile_create(int ca, int ioa, uint8_t nof);
CS101_FileServer
CS101_FileServer_create(CS101_AppLayerParameters alParams);
void
CS101_FileServer_destroy(CS101_FileServer self);
void
CS101_FileServer_setFilesAvailableIfc(CS101_FileServer self, CS101_FilesAvailable ifc);
void
CS101_FileServer_setFileReadyHandler(CS101_FileServer self, CS101_FileReadyHandler handler, void* parameter);
CS101_SlavePlugin
CS101_FileServer_getSlavePlugin(CS101_FileServer self);
#ifdef __cplusplus
}
#endif
#endif /* LIB60870_C_FILE_SERVICE_FILE_SERVICE_H_ */

View File

@ -0,0 +1,965 @@
/*
* Copyright 2016-2024 Michael Zillgith
*
* This file is part of lib60870-C
*
* lib60870-C is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* lib60870-C is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include "cs101_file_service.h"
#include "lib_memory.h"
#include "iec60870_slave.h"
#include "lib60870_internal.h"
#include "hal_time.h"
typedef enum
{
UNSELECTED_IDLE,
WAITING_FOR_FILE_CALL,
WAITING_FOR_SECTION_CALL,
TRANSMIT_SECTION,
WAITING_FOR_SECTION_ACK,
WAITING_FOR_FILE_ACK,
SEND_ABORT,
TRANSFER_COMPLETED,
WAITING_FOR_SECTION_READY,
RECEIVE_SECTION,
} FileServerState;
struct sCS101_FileServer
{
struct sCS101_SlavePlugin plugin;
CS101_IFileReceiver fileReceiver;
CS101_FileReadyHandler fileReadyHandler; /* handler that is called when the master wants to send a file */
void* fileReadyHandlerParameter;
CS101_FilesAvailable filesAvailable;
int ca;
int ioa;
uint8_t oa;
uint16_t nof;
uint64_t lastSendTime;
FileServerState state;
uint8_t currentSectionNumber;
int currentSectionOffset;
int currentSectionSize;
uint8_t sectionChecksum;
uint8_t fileChecksum;
int maxSegmentSize; /* max. size of segment payload data */
uint64_t timeout;
CS101_IFileProvider selectedFile;
IMasterConnection selectedConnection;
};
static void
sendCallFile(CS101_FileServer self, IMasterConnection connection, int oa)
{
sCS101_StaticASDU _asdu;
uint8_t ioBuf[64];
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
/* send call file */
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
oa, self->ca, false, false);
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
FileCallOrSelect_create((FileCallOrSelect) &ioBuf, self->ioa, self->nof, 0, 2/*=request-file*/));
IMasterConnection_sendASDU(connection, newAsdu);
}
static void
sendCallSection(CS101_FileServer self, IMasterConnection connection, int oa)
{
sCS101_StaticASDU _asdu;
uint8_t ioBuf[64];
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
/* send call file */
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
oa, self->ca, false, false);
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
FileCallOrSelect_create((FileCallOrSelect) &ioBuf, self->ioa, self->nof, self->currentSectionNumber, 6/*=request-section*/));
DEBUG_PRINT("Send CALL SECTION number: %i\n", self->currentSectionNumber);
IMasterConnection_sendASDU(connection, newAsdu);
}
static void
sendFileAck(CS101_FileServer self, IMasterConnection connection, int oa, uint8_t nos, uint8_t afq)
{
sCS101_StaticASDU _asdu;
uint8_t ioBuf[64];
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
/* send call file */
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
oa, self->ca, false, false);
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
FileACK_create((FileACK) &ioBuf, self->ioa, self->nof, nos, afq));
IMasterConnection_sendASDU(connection, newAsdu);
}
static void
sendSectionReady(CS101_FileServer self, IMasterConnection connection, int oa)
{
sCS101_StaticASDU _asdu;
uint8_t ioBuf[64];
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
/* send call file */
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
oa, self->ca, false, false);
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
SectionReady_create((SectionReady) &ioBuf, self->ioa, self->nof, self->currentSectionNumber, self->currentSectionSize, false));
IMasterConnection_sendASDU(connection, newAsdu);
}
static void
sendLastSection(CS101_FileServer self, IMasterConnection connection, int oa)
{
sCS101_StaticASDU _asdu;
uint8_t ioBuf[64];
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
/* send call file */
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
oa, self->ca, false, false);
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
FileLastSegmentOrSection_create((FileLastSegmentOrSection) &ioBuf, self->ioa, self->nof, self->currentSectionNumber, 1 /* file-transfer-without-deact */, self->fileChecksum));
IMasterConnection_sendASDU(connection, newAsdu);
}
static void
sendFileReady(CS101_FileServer self, IMasterConnection connection, int oa, uint32_t lof, bool positive)
{
sCS101_StaticASDU _asdu;
uint8_t ioBuf[64];
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
/* send call file */
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
oa, self->ca, false, false);
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
FileReady_create((FileReady) &ioBuf, self->ioa, self->nof, lof, positive));
IMasterConnection_sendASDU(connection, newAsdu);
}
static uint8_t
calculateChecksum(uint8_t* data, int size)
{
uint8_t checksum = 0;
int i;
for (i = 0; i < size; i++) {
checksum += data[i];
}
return checksum;
}
static bool
sendSegment(CS101_FileServer self, IMasterConnection connection, int oa)
{
int currentSegmentSize = self->currentSectionSize - self->currentSectionOffset;
DEBUG_PRINT("sendSegment(%i/%i)\n", self->currentSectionOffset, self->currentSectionSize);
if (currentSegmentSize > 0)
{
if (currentSegmentSize > self->maxSegmentSize)
currentSegmentSize = self->maxSegmentSize;
sCS101_StaticASDU _asdu;
uint8_t ioBuf[64];
uint8_t segmentData[255];
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
/* send call file */
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
oa, self->ca, false, false);
self->selectedFile->getSegmentData(self->selectedFile, self->currentSectionNumber - 1, self->currentSectionOffset, currentSegmentSize, segmentData);
CS101_ASDU_addInformationObject(newAsdu, (InformationObject)
FileSegment_create((FileSegment) &ioBuf, self->ioa, self->nof, self->currentSectionNumber, segmentData, currentSegmentSize));
IMasterConnection_sendASDU(connection, newAsdu);
self->currentSectionOffset += currentSegmentSize;
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->sectionChecksum += calculateChecksum(segmentData, currentSegmentSize);
return true;
}
else
{
return false;
}
}
static void
sendLastSegment(CS101_FileServer self, IMasterConnection connection, int oa)
{
sCS101_StaticASDU _asdu;
uint8_t ioBuf[64];
CS101_AppLayerParameters alParams = IMasterConnection_getApplicationLayerParameters(connection);
/* send call file */
CS101_ASDU newAsdu = CS101_ASDU_initializeStatic(&_asdu, alParams, false, CS101_COT_FILE_TRANSFER,
oa, self->ca, false, false);
CS101_ASDU_addInformationObject(newAsdu,
(InformationObject)
FileLastSegmentOrSection_create((FileLastSegmentOrSection) &ioBuf, self->ioa, self->nof, self->currentSectionNumber,
3 /* section-transfer-without-deact */, self->sectionChecksum));
DEBUG_PRINT("Send LAST SEGMENT (NoS=%i, CHS=%i)\n", self->currentSectionNumber, self->sectionChecksum);
self->fileChecksum += self->sectionChecksum;
self->sectionChecksum = 0;
IMasterConnection_sendASDU(connection, newAsdu);
}
/**
*
* Call inside asdu handler
*/
static CS101_SlavePlugin_Result
CS101_FileServer_handleAsdu(void* parameter, IMasterConnection connection, CS101_ASDU asdu)
{
CS101_FileServer self = (CS101_FileServer) parameter;
CS101_SlavePlugin_Result result = CS101_PLUGIN_RESULT_NOT_HANDLED;
IEC60870_5_TypeID typeId = CS101_ASDU_getTypeID(asdu);
/* check if type if is in range of file services */
if ((typeId >= F_FR_NA_1) && (typeId <= F_SC_NB_1))
{
/* check for timeout */
if (self->state != UNSELECTED_IDLE)
{
if (Hal_getMonotonicTimeInMs() > self->lastSendTime + self->timeout)
{
DEBUG_PRINT ("Abort file transfer due to timeout\n");
self->state = UNSELECTED_IDLE;
}
}
int oa = CS101_ASDU_getOA(asdu);
uint8_t ioBuf[250];
switch (typeId) {
case F_FR_NA_1: /* File Ready */
DEBUG_PRINT("Received file ready F_FR_NA_1\n");
if (self->fileReadyHandler)
{
FileReady fileReady = (FileReady) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
if (fileReady)
{
int ioa = InformationObject_getObjectAddress((InformationObject) fileReady);
self->fileReceiver = NULL;
int errCode = 0;
if (self->fileReadyHandler) {
self->fileReceiver = self->fileReadyHandler(self->fileReadyHandlerParameter, CS101_ASDU_getCA(asdu), ioa, FileReady_getNOF(fileReady), FileReady_getLengthOfFile(fileReady), &errCode);
}
if (self->fileReceiver)
{
self->ca = CS101_ASDU_getCA(asdu);
self->ioa = ioa;
self->oa = oa;
self->nof = FileReady_getNOF(fileReady);
self->fileChecksum = 0;
sendCallFile(self, connection, oa);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_SECTION_READY;
}
else
{
if (errCode == 1)
{
CS101_ASDU_setNegative(asdu, true);
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
IMasterConnection_sendASDU(connection, asdu);
}
else if (errCode == 2)
{
CS101_ASDU_setNegative(asdu, true);
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
IMasterConnection_sendASDU(connection, asdu);
}
else
{
self->ca = CS101_ASDU_getCA(asdu);
self->ioa = ioa;
self->nof = FileReady_getNOF(fileReady);
sendFileReady(self, connection, oa, 0, false);
}
}
}
else
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
}
else
{
CS101_ASDU_setNegative(asdu, true);
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
IMasterConnection_sendASDU(connection, asdu);
}
break;
case F_SR_NA_1: /* Section Ready */
if (self->state == WAITING_FOR_SECTION_READY)
{
SectionReady sectionReady = (SectionReady) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
if (sectionReady)
{
self->currentSectionNumber = SectionReady_getNameOfSection(sectionReady);
self->currentSectionOffset = 0;
self->currentSectionSize = SectionReady_getLengthOfSection(sectionReady);
/* send call section */
sendCallSection(self, connection, oa);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = RECEIVE_SECTION;
}
else
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
}
break;
case F_SG_NA_1: /* Segment */
if (self->state == RECEIVE_SECTION)
{
FileSegment segment = (FileSegment) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
if (segment)
{
uint8_t nos = FileSegment_getNameOfSection(segment);
uint8_t los = FileSegment_getLengthOfSegment(segment);
DEBUG_PRINT("Received F_SG_NA_1(segment) (NoS=%i, LoS=%i)\n", nos, los);
if (self->fileReceiver)
{
self->fileReceiver->segmentReceived(self->fileReceiver, nos, self->currentSectionOffset, los, FileSegment_getSegmentData(segment));
}
self->currentSectionOffset += los;
self->lastSendTime = Hal_getMonotonicTimeInMs();
}
else
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
}
else {
DEBUG_PRINT ("Unexpected F_SG_NA_1(file segment)\n");
}
break;
case F_LS_NA_1: /* Last Segment/Section */
{
DEBUG_PRINT ("Received F_LS_NA_1 (last segment/section)\n");
FileLastSegmentOrSection lastSection = (FileLastSegmentOrSection) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
if (lastSection)
{
uint8_t lsq = FileLastSegmentOrSection_getLSQ(lastSection);
if (self->state == RECEIVE_SECTION)
{
if (lsq == 3 /* SECTION_TRANSFER_WITHOUT_DEACT */)
{
DEBUG_PRINT("Send segment ACK for NoS=%i\n", FileLastSegmentOrSection_getNameOfSection(lastSection));
sendFileAck(self, connection, oa, FileLastSegmentOrSection_getNameOfSection(lastSection), 3 /* POS_ACK_SECTION */);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_SECTION_READY;
}
else if (lsq == 2 /* FILE_TRANSFER_WITH_DEACT */)
{
/* master aborted transfer */
if (self->fileReceiver) {
self->fileReceiver->finished(self->fileReceiver, CS101_FILE_ERROR_ABORTED_BY_REMOTE);
}
self->state = UNSELECTED_IDLE;
}
else {
DEBUG_PRINT("Unexpected LSQ\n");
}
}
else if (self->state == WAITING_FOR_SECTION_READY)
{
if (lsq == 1 /* FILE_TRANSFER_WITHOUT_DEACT */)
{
DEBUG_PRINT("Send file ACK\n");
sendFileAck(self, connection, oa, FileLastSegmentOrSection_getNameOfSection(lastSection), 1 /* POS_ACK_FILE */);
self->lastSendTime = Hal_getMonotonicTimeInMs();
if (self->fileReceiver)
{
self->fileReceiver->finished(self->fileReceiver, CS101_FILE_ERROR_SUCCESS);
}
self->state = UNSELECTED_IDLE;
}
else if (lsq == 2 /* FILE_TRANSFER_WITH_DEACT */)
{
/* master aborted transfer */
if (self->fileReceiver)
{
self->fileReceiver->finished(self->fileReceiver, CS101_FILE_ERROR_ABORTED_BY_REMOTE);
}
self->state = UNSELECTED_IDLE;
}
else
{
DEBUG_PRINT("Unexpected LSQ\n");
}
}
}
else
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
}
break;
case F_AF_NA_1: /* 124 - ACK file, ACK section */
DEBUG_PRINT("Received file/section ACK F_AF_NA_1\n");
if (self->state != UNSELECTED_IDLE)
{
FileACK ack = (FileACK) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
if (ack)
{
uint8_t afq = FileACK_getAFQ(ack);
if (afq == 1 /* POS_ACK_FILE */)
{
DEBUG_PRINT("Received positive file ACK\n");
if (self->state == WAITING_FOR_FILE_ACK)
{
if (self->selectedFile)
self->selectedFile->transferComplete(self->selectedFile, true);
/* TODO remove file from list of available files */
self->selectedFile = NULL;
self->selectedConnection = NULL;
self->state = UNSELECTED_IDLE;
}
else
{
DEBUG_PRINT("Unexpected file transfer state --> abort file transfer\n");
self->state = SEND_ABORT;
}
}
else if ((afq & 0x0f) == 2 /* NEG_ACK_FILE */)
{
DEBUG_PRINT("Received negative file ACK - stop transfer\n");
if (self->state == WAITING_FOR_FILE_ACK)
{
if (self->selectedFile)
self->selectedFile->transferComplete(self->selectedFile, false);
self->selectedFile = NULL;
self->selectedConnection = NULL;
self->state = UNSELECTED_IDLE;
}
else
{
DEBUG_PRINT("Unexpected file transfer state --> abort file transfer\n");
self->state = SEND_ABORT;
}
}
else if ((afq & 0x0f) == 4 /* NEG_ACK_SECTION */)
{
DEBUG_PRINT("Received negative file section ACK - repeat section\n");
if (self->state == WAITING_FOR_SECTION_ACK)
{
self->currentSectionOffset = 0;
self->sectionChecksum = 0;
sendSectionReady(self, connection, oa);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = TRANSMIT_SECTION;
}
else
{
DEBUG_PRINT("Unexpected file transfer state --> abort file transfer\n");
self->state = SEND_ABORT;
}
}
else if ((afq & 0x0f) == 3 /* POS_ACK_SECTION */)
{
DEBUG_PRINT("Received positive section ACK\n");
if (self->state == WAITING_FOR_SECTION_ACK)
{
self->currentSectionNumber++;
int nextSectionSize = self->selectedFile->getSectionSize(self->selectedFile, self->currentSectionNumber - 1);
self->currentSectionOffset = 0;
if (nextSectionSize <= 0)
{
DEBUG_PRINT("Received positive file section ACK - send last section indication\n");
sendLastSection(self, connection, oa);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_FILE_ACK;
}
else
{
DEBUG_PRINT("Received positive file section ACK - send next section ready indication");
self->currentSectionSize = nextSectionSize;
sendSectionReady(self, connection, oa);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_SECTION_CALL;
}
self->sectionChecksum = 0;
}
else
{
DEBUG_PRINT("Unexpected file transfer state --> abort file transfer\n");
self->state = SEND_ABORT;
}
}
}
else
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
}
else
{
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
IMasterConnection_sendASDU(connection, asdu);
}
break;
case F_SC_NA_1: /* 122 - Call/Select directory/file/section */
DEBUG_PRINT("Received call/select F_SC_NA_1\n");
if (CS101_ASDU_getCOT(asdu) == CS101_COT_FILE_TRANSFER)
{
FileCallOrSelect sc = (FileCallOrSelect) CS101_ASDU_getElementEx(asdu, (InformationObject) ioBuf, 0);
if (sc)
{
uint8_t scq = FileCallOrSelect_getSCQ(sc);
int ioa = InformationObject_getObjectAddress((InformationObject) sc);
uint16_t nof = FileCallOrSelect_getNOF(sc);
if (scq == 1 /* SELECT_FILE */)
{
DEBUG_PRINT("Received SELECT FILE\n");
if (self->state == UNSELECTED_IDLE)
{
CS101_IFileProvider file = NULL;
int errCode = 0;
if (self->filesAvailable)
file = self->filesAvailable->getFile(self->filesAvailable->parameter, CS101_ASDU_getCA(asdu), ioa, nof, &errCode);
if (file == NULL)
{
if (errCode == 1)
{
CS101_ASDU_setNegative(asdu, true);
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
IMasterConnection_sendASDU(connection, asdu);
}
else if (errCode == 2)
{
CS101_ASDU_setNegative(asdu, true);
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
IMasterConnection_sendASDU(connection, asdu);
}
else
{
self->ca = CS101_ASDU_getCA(asdu);
self->ioa = ioa;
self->nof = nof;
sendFileReady(self, connection, oa, 0, false);
}
}
else
{
/* TODO check if file is already selected by other client */
self->selectedFile = file;
self->selectedConnection = connection;
self->ioa = ioa;
self->ca = CS101_ASDU_getCA(asdu);
self->nof = nof;
int fileSize = file->getFileSize(file);
DEBUG_PRINT("Send FILE READY\n");
sendFileReady(self, connection, oa, fileSize, true);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_FILE_CALL;
}
}
else
{
DEBUG_PRINT("Unexpected select file message\n");
}
}
else if (scq == 3 /* DEACTIVATE_FILE */)
{
DEBUG_PRINT("Received DEACTIVATE FILE\n");
if (self->state == UNSELECTED_IDLE)
{
self->selectedFile = NULL;
self->selectedConnection = NULL;
self->state = UNSELECTED_IDLE;
}
else
{
DEBUG_PRINT("Unexpected DEACTIVATE FILE message\n");
}
}
else if (scq == 2 /* REQUEST_FILE */)
{
DEBUG_PRINT("Received CALL FILE\n");
if (self->state == WAITING_FOR_FILE_CALL)
{
/* TODO check if NoF matches */
if ((ioa != self->ioa) || (CS101_ASDU_getCA(asdu) != self->ca))
{
DEBUG_PRINT("Call for file that is not selected!\n");
if (CS101_ASDU_getCA(asdu) != self->ca)
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
else
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
CS101_ASDU_setNegative(asdu, true);
IMasterConnection_sendASDU(connection, asdu);
}
else
{
self->currentSectionNumber = 1;
self->currentSectionOffset = 0;
self->fileChecksum = 0;
self->currentSectionSize = self->selectedFile->getSectionSize(self->selectedFile, 0);
DEBUG_PRINT("Send SECTION READY\n");
sendSectionReady(self, connection, oa);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_SECTION_CALL;
}
}
else
{
DEBUG_PRINT("Unexpected CALL FILE message\n");
}
}
else if (scq == 6 /* REQUEST_SECTION */ )
{
uint8_t nos = FileCallOrSelect_getNameOfSection(sc);
DEBUG_PRINT("Received CALL SECTION (NoS = %i)\n", nos);
if (self->state == WAITING_FOR_SECTION_CALL)
{
/* TODO check if NoF matches */
if ((ioa != self->ioa) || (CS101_ASDU_getCA(asdu) != self->ca))
{
DEBUG_PRINT("Call for file that is not selected!\n");
if (CS101_ASDU_getCA(asdu) != self->ca)
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_CA);
else
CS101_ASDU_setCOT(asdu, CS101_COT_UNKNOWN_IOA);
CS101_ASDU_setNegative(asdu, true);
IMasterConnection_sendASDU(connection, asdu);
}
else
{
if (CS101_ASDU_isNegative(asdu))
{
self->currentSectionNumber++;
self->currentSectionOffset = 0;
self->currentSectionSize = self->selectedFile->getSectionSize(self->selectedFile, self->currentSectionNumber - 1);
if (self->currentSectionSize > 0)
{
DEBUG_PRINT("Send F_SR_NA_1 (section ready) (NoS = %i)\n", self->currentSectionNumber);
sendSectionReady(self, connection, oa);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_SECTION_CALL;
}
else
{
DEBUG_PRINT("Send F_LS_NA_1 (last section))\n");
sendLastSection(self, connection, oa);
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_FILE_ACK;
}
}
else
{
/* positive */
self->currentSectionSize = self->selectedFile->getSectionSize(self->selectedFile, nos - 1);
if (self->currentSectionSize > 0)
{
self->currentSectionNumber = nos;
self->currentSectionOffset = 0;
self->state = TRANSMIT_SECTION;
}
else
{
DEBUG_PRINT("Unexpected number of section -> send negative confirm\n");
CS101_ASDU_setNegative(asdu, true);
IMasterConnection_sendASDU(connection, asdu);
self->lastSendTime = Hal_getMonotonicTimeInMs();
}
}
}
}
else
{
DEBUG_PRINT("Unexpected CALL SECTION message\n");
}
}
}
else
result = CS101_PLUGIN_RESULT_INVALID_ASDU;
}
else if (CS101_ASDU_getCOT(asdu) == CS101_COT_REQUEST)
{
/* call directory */
/* TODO send directory */
}
else
{
DEBUG_PRINT("Received unexpected COT = %i\n", CS101_ASDU_getCOT(asdu));
}
break;
default:
DEBUG_PRINT("Received unexpected type ID %i in file service\n", typeId);
break;
}
result = CS101_PLUGIN_RESULT_HANDLED;
}
return result;
}
static void
CS101_FileServer_runTask(void* parameter, IMasterConnection connection)
{
CS101_FileServer self = (CS101_FileServer) parameter;
if (self->state != UNSELECTED_IDLE)
{
if (self->state == TRANSMIT_SECTION)
{
if (self->selectedConnection == connection)
{
if (self->selectedFile)
{
if (sendSegment(self, connection, self->oa) == false)
{
sendLastSegment(self, connection, self->oa);
self->fileChecksum += self->sectionChecksum;
self->sectionChecksum = 0;
self->lastSendTime = Hal_getMonotonicTimeInMs();
self->state = WAITING_FOR_SECTION_ACK;
}
}
}
}
/* check for timeout */
if (Hal_getMonotonicTimeInMs() > self->lastSendTime + self->timeout)
{
DEBUG_PRINT ("Abort file transfer due to timeout\n");
self->state = UNSELECTED_IDLE;
}
}
}
bool (*handleAsdu) (void* parameter, IMasterConnection connection, CS101_ASDU asdu);
void (*runTask) (void* parameter, IMasterConnection connection);
void* parameter;
CS101_FileServer
CS101_FileServer_create(CS101_AppLayerParameters alParams)
{
CS101_FileServer self = (CS101_FileServer) GLOBAL_CALLOC(1, sizeof(struct sCS101_FileServer));
if (self)
{
self->timeout = 3000;
self->state = UNSELECTED_IDLE;
self->maxSegmentSize = FileSegment_GetMaxDataSize(alParams);
self->plugin.parameter = self;
self->plugin.handleAsdu = CS101_FileServer_handleAsdu;
self->plugin.runTask = CS101_FileServer_runTask;
self->plugin.sendAsdu = NULL;
self->plugin.setEnqueueFunction = NULL;
self->plugin.hasAsduToSend = NULL;
self->plugin.getNextAsduToSend = NULL;
self->plugin.setForwardAsduFunction = NULL;
}
return self;
}
void
CS101_FileServer_destroy(CS101_FileServer self)
{
GLOBAL_FREEMEM(self);
}
void
CS101_FileServer_setFilesAvailableIfc(CS101_FileServer self, CS101_FilesAvailable ifc)
{
self->filesAvailable = ifc;
}
void
CS101_FileServer_setFileReadyHandler(CS101_FileServer self, CS101_FileReadyHandler handler, void* parameter)
{
self->fileReadyHandler = handler;
self->fileReadyHandlerParameter = parameter;
}
CS101_SlavePlugin
CS101_FileServer_getSlavePlugin(CS101_FileServer self)
{
return &(self->plugin);
}

View File

@ -0,0 +1,231 @@
cmake_minimum_required(VERSION 3.5.1)
# automagically detect if we should cross-compile
if(DEFINED ENV{TOOLCHAIN})
set(CMAKE_C_COMPILER $ENV{TOOLCHAIN}gcc)
set(CMAKE_CXX_COMPILER $ENV{TOOLCHAIN}g++)
set(CMAKE_AR "$ENV{TOOLCHAIN}ar" CACHE FILEPATH "CW archiver" FORCE)
endif()
project(hal)
set(LIBHAL_VERSION_MAJOR "2")
set(LIBHAL_VERSION_MINOR "2")
set(LIBHAL_VERSION_PATCH "0")
# feature checks
include(CheckLibraryExists)
# check if we are on a little or a big endian
include (TestBigEndian)
test_big_endian(PLATFORM_IS_BIGENDIAN)
if(WIN32)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib")
message("Found winpcap -> compile ethernet HAL layer (required for GOOSE/SV support)")
set(WITH_WPCAP 1)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Include")
else()
message("winpcap not found -> skip ethernet HAL layer (no L2 GOOSE/SV support)")
endif()
endif(WIN32)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/inc
)
set (libhal_linux_SRCS
${CMAKE_CURRENT_LIST_DIR}/socket/linux/socket_linux.c
${CMAKE_CURRENT_LIST_DIR}/ethernet/linux/ethernet_linux.c
${CMAKE_CURRENT_LIST_DIR}/thread/linux/thread_linux.c
${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c
${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c
${CMAKE_CURRENT_LIST_DIR}/serial/linux/serial_port_linux.c
${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c
)
set (libhal_windows_SRCS
${CMAKE_CURRENT_LIST_DIR}/socket/win32/socket_win32.c
${CMAKE_CURRENT_LIST_DIR}/thread/win32/thread_win32.c
${CMAKE_CURRENT_LIST_DIR}/filesystem/win32/file_provider_win32.c
${CMAKE_CURRENT_LIST_DIR}/time/win32/time.c
${CMAKE_CURRENT_LIST_DIR}/serial/win32/serial_port_win32.c
${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c
)
if(WITH_WPCAP)
set (libhal_windows_SRCS ${libhal_windows_SRCS}
${CMAKE_CURRENT_LIST_DIR}/ethernet/win32/ethernet_win32.c
)
endif(WITH_WPCAP)
set (libhal_bsd_SRCS
${CMAKE_CURRENT_LIST_DIR}/socket/bsd/socket_bsd.c
${CMAKE_CURRENT_LIST_DIR}/ethernet/bsd/ethernet_bsd.c
${CMAKE_CURRENT_LIST_DIR}/thread/bsd/thread_bsd.c
${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c
${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c
${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c
)
set (libhal_macos_SRCS
${CMAKE_CURRENT_LIST_DIR}/socket/bsd/socket_bsd.c
${CMAKE_CURRENT_LIST_DIR}/ethernet/bsd/ethernet_bsd.c
${CMAKE_CURRENT_LIST_DIR}/thread/macos/thread_macos.c
${CMAKE_CURRENT_LIST_DIR}/filesystem/linux/file_provider_linux.c
${CMAKE_CURRENT_LIST_DIR}/time/unix/time.c
${CMAKE_CURRENT_LIST_DIR}/memory/lib_memory.c
)
IF(WIN32)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib")
message("Found winpcap -> can compile with GOOSE support")
set(WITH_WPCAP 1)
endif()
set (libhal_SRCS
${libhal_windows_SRCS}
)
IF(MSVC)
set_source_files_properties(${libhal_SRCS}
PROPERTIES LANGUAGE CXX)
ENDIF()
ELSEIF(UNIX)
IF(APPLE)
set (libhal_SRCS
${libhal_macos_SRCS}
)
ELSEIF(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
set (libhal_SRCS
${libhal_bsd_SRCS}
)
ELSE()
set (libhal_SRCS
${libhal_linux_SRCS}
)
ENDIF(APPLE)
ENDIF(WIN32)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC" )
#set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC" )
if(WITH_MBEDTLS)
message("Found mbedtls 2.28 -> can compile HAL with TLS 1.2 support")
set(WITH_MBEDTLS 1)
endif(WITH_MBEDTLS)
if (WITH_MBEDTLS3)
message("Found mbedtls 3.6 -> can compile HAL with TLS 1.3 support")
set(WITH_MBEDTLS3 1)
endif(WITH_MBEDTLS3)
if(WITH_MBEDTLS)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls
${MBEDTLS_INCLUDE_DIR}
)
if(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
link_directories(${CONFIG_EXTERNAL_MBEDTLS_DYNLIB_PATH})
else()
file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-2.28/library/*.c)
endif(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h")
set (libhal_SRCS ${libhal_SRCS}
${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls/tls_mbedtls.c
)
IF(MSVC)
set_source_files_properties(${libhal_SRCS}
PROPERTIES LANGUAGE CXX)
ENDIF()
list (APPEND libhal_SRCS ${tls_SRCS})
endif(WITH_MBEDTLS)
if(WITH_MBEDTLS3)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls3
${MBEDTLS_INCLUDE_DIR}
)
if(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
link_directories(${CONFIG_EXTERNAL_MBEDTLS_DYNLIB_PATH})
else()
file(GLOB tls_SRCS ${CMAKE_CURRENT_LIST_DIR}/../third_party/mbedtls/mbedtls-3.6.0/library/*.c)
endif(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
add_definitions(-DMBEDTLS_CONFIG_FILE="mbedtls_config.h")
set (libhal_SRCS ${libhal_SRCS}
${CMAKE_CURRENT_LIST_DIR}/tls/mbedtls3/tls_mbedtls.c
)
IF(MSVC)
set_source_files_properties(${libhal_SRCS}
PROPERTIES LANGUAGE CXX)
ENDIF()
list (APPEND libhal_SRCS ${tls_SRCS})
endif(WITH_MBEDTLS3)
add_library (hal STATIC ${libhal_SRCS})
add_library (hal-shared STATIC ${libhal_SRCS})
target_compile_definitions(hal-shared PRIVATE EXPORT_FUNCTIONS_FOR_DLL)
SET_TARGET_PROPERTIES(hal-shared PROPERTIES
COMPILE_FLAGS "-fPIC"
)
IF(UNIX)
target_link_libraries (hal
-lpthread
-lrt
)
ENDIF(UNIX)
IF(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
target_link_libraries(hal mbedcrypto mbedx509 mbedtls)
ENDIF(CONFIG_USE_EXTERNAL_MBEDTLS_DYNLIB)
IF(MINGW)
target_link_libraries(hal ws2_32 iphlpapi bcrypt)
message("Building with MinGW")
ENDIF(MINGW)
IF (MSVC)
target_link_libraries(hal bcrypt)
target_link_libraries(hal-shared bcrypt)
ENDIF()
iF(WITH_WPCAP)
target_link_libraries(hal
${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib
${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/packet.lib
)
ENDIF(WITH_WPCAP)
set(BINDIR "bin")
set(LIBDIR "lib")
if(UNIX)
# GNUInstallDirs is required for Debian multiarch
include(GNUInstallDirs)
set(LIBDIR ${CMAKE_INSTALL_LIBDIR})
set(BINDIR ${CMAKE_INSTALL_BINDIR})
endif()
install (TARGETS hal hal-shared
RUNTIME DESTINATION ${BINDIR} COMPONENT Applications
ARCHIVE DESTINATION ${LIBDIR} COMPONENT Libraries
LIBRARY DESTINATION ${LIBDIR} COMPONENT Libraries
)

View File

@ -0,0 +1,51 @@
/*
* hal_base.h
*
* Copyright 2013-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#ifndef HAL_INC_HAL_BASE_H_
#define HAL_INC_HAL_BASE_H_
#include <stdlib.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#ifdef __GNUC__
#define ATTRIBUTE_PACKED __attribute__ ((__packed__))
#else
#define ATTRIBUTE_PACKED
#endif
#ifndef DEPRECATED
#if defined(__GNUC__) || defined(__clang__)
#define DEPRECATED __attribute__((deprecated))
#else
#define DEPRECATED
#endif
#endif
#if defined _WIN32 || defined __CYGWIN__
#ifdef EXPORT_FUNCTIONS_FOR_DLL
#define PAL_API __declspec(dllexport)
#else
#define PAL_API
#endif
#define PAL_INTERNAL
#else
#if __GNUC__ >= 4
#define PAL_API __attribute__ ((visibility ("default")))
#define PAL_INTERNAL __attribute__ ((visibility ("hidden")))
#else
#define PAL_API
#define PAL_INTERNAL
#endif
#endif
#endif /* HAL_INC_HAL_BASE_H_ */

View File

@ -0,0 +1,140 @@
/*
* hal_serial.h
*
* Copyright 2013-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#ifndef SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_
#define SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_
#include "hal_base.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file hal_serial.h
* \brief Abstraction layer for serial ports.
* Has to be implemented for the serial link layer of CS 101.
*/
/*! \addtogroup hal Platform (Hardware/OS) abstraction layer
*
* @{
*/
/**
* @defgroup HAL_SERIAL Access to serial interfaces
*
* Serial interface abstraction layer. This functions have to be implemented to
* port lib60870 to new platforms when the serial link layers are required.
*
* @{
*/
typedef struct sSerialPort* SerialPort;
typedef enum {
SERIAL_PORT_ERROR_NONE = 0,
SERIAL_PORT_ERROR_INVALID_ARGUMENT = 1,
SERIAL_PORT_ERROR_INVALID_BAUDRATE = 2,
SERIAL_PORT_ERROR_OPEN_FAILED = 3,
SERIAL_PORT_ERROR_UNKNOWN = 99
} SerialPortError;
/**
* \brief Create a new SerialPort instance
*
* \param interfaceName identifier or name of the serial interface (e.g. "/dev/ttyS1" or "COM4")
* \param baudRate the baud rate in baud (e.g. 9600)
* \param dataBits the number of data bits (usually 8)
* \param parity defines what kind of parity to use ('E' - even parity, 'O' - odd parity, 'N' - no parity)
* \param stopBits the number of stop buts (usually 1)
*
* \return the new SerialPort instance
*/
PAL_API SerialPort
SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits);
/**
* \brief Destroy the SerialPort instance and release all resources
*/
PAL_API void
SerialPort_destroy(SerialPort self);
/**
* \brief Open the serial interface
*
* \return true in case of success, false otherwise (use \ref SerialPort_getLastError for a detailed error code)
*/
PAL_API bool
SerialPort_open(SerialPort self);
/**
* \brief Close (release) the serial interface
*/
PAL_API void
SerialPort_close(SerialPort self);
/**
* \brief Get the baudrate used by the serial interface
*
* \return the baud rate in baud
*/
PAL_API int
SerialPort_getBaudRate(SerialPort self);
/**
* \brief Set the timeout used for message reception
*
* \param timeout the timeout value in ms.
*/
PAL_API void
SerialPort_setTimeout(SerialPort self, int timeout);
/**
* \brief Discard all data in the input buffer of the serial interface
*/
PAL_API void
SerialPort_discardInBuffer(SerialPort self);
/**
* \brief Read a byte from the interface
*
* \return number of read bytes of -1 in case of an error
*/
PAL_API int
SerialPort_readByte(SerialPort self);
/**
* \brief Write the number of bytes from the buffer to the serial interface
*
* \param buffer the buffer containing the data to write
* \param startPos start position in the buffer of the data to write
* \param numberOfBytes number of bytes to write
*
* \return number of bytes written, or -1 in case of an error
*/
PAL_API int
SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int numberOfBytes);
/**
* \brief Get the error code of the last operation
*/
PAL_API SerialPortError
SerialPort_getLastError(SerialPort self);
/*! @} */
/*! @} */
#ifdef __cplusplus
}
#endif
#endif /* SRC_IEC60870_LINK_LAYER_SERIAL_PORT_H_ */

View File

@ -0,0 +1,393 @@
/*
* socket_hal.h
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#ifndef SOCKET_HAL_H_
#define SOCKET_HAL_H_
#include "hal_base.h"
/**
* \file hal_socket.h
* \brief Abstraction layer TCP/IP sockets
* Has to be implemented for CS 104 TCP/IP.
*/
#ifdef __cplusplus
extern "C" {
#endif
/*! \defgroup hal Platform (Hardware/OS) abstraction layer
*
* Platform abstraction layer. These functions have to be implemented when the library is
* to be ported to new platforms. It might not be required to implement all interfaces
* depending on the required library features.
*
* @{
*/
/**
* @defgroup HAL_SOCKET Interface to the TCP/IP stack (abstract socket layer)
*
* Thread and Socket abstraction layer. This functions have to be implemented to
* port lib60870 to a new hardware/OS platform when TCP/IP is required.
*
* @{
*/
/** Opaque reference for a server socket instance */
typedef struct sServerSocket* ServerSocket;
typedef struct sUdpSocket* UdpSocket;
/** Opaque reference for a client or connection socket instance */
typedef struct sSocket* Socket;
/** Opaque reference for a set of server and socket handles */
typedef struct sHandleSet* HandleSet;
/** State of an asynchronous connect */
typedef enum
{
SOCKET_STATE_CONNECTING = 0,
SOCKET_STATE_FAILED = 1,
SOCKET_STATE_CONNECTED = 2
} SocketState;
/**
* \brief Create a new connection handle set (HandleSet)
*
* \return new HandleSet instance
*/
PAL_API HandleSet
Handleset_new(void);
/**
* \brief Reset the handle set for reuse
*/
PAL_API void
Handleset_reset(HandleSet self);
/**
* \brief add a socket to an existing handle set
*
* \param self the HandleSet instance
* \param sock the socket to add
*/
PAL_API void
Handleset_addSocket(HandleSet self, const Socket sock);
/**
* \brief remove a socket from an existing handle set
*/
void
Handleset_removeSocket(HandleSet self, const Socket sock);
/**
* \brief wait for a socket to become ready
*
* This function is corresponding to the BSD socket select function.
* It returns the number of sockets on which data is pending or 0 if no data is pending
* on any of the monitored connections. The function will return after "timeout" ms if no
* data is pending.
* The function shall return -1 if a socket error occures.
*
* \param self the HandleSet instance
* \param timeout in milliseconds (ms)
* \return It returns the number of sockets on which data is pending
* or 0 if no data is pending on any of the monitored connections.
* The function shall return -1 if a socket error occures.
*/
PAL_API int
Handleset_waitReady(HandleSet self, unsigned int timeoutMs);
/**
* \brief destroy the HandleSet instance
*
* \param self the HandleSet instance to destroy
*/
PAL_API void
Handleset_destroy(HandleSet self);
/**
* \brief Create a new TcpServerSocket instance
*
* Implementation of this function is MANDATORY if server functionality is required.
*
* \param address ip address or hostname to listen on
* \param port the TCP port to listen on
*
* \return the newly create TcpServerSocket instance
*/
PAL_API ServerSocket
TcpServerSocket_create(const char* address, int port);
/**
* \brief Create an IPv4 UDP socket instance
*
* \return new UDP socket instance
*/
PAL_API UdpSocket
UdpSocket_create(void);
/**
* \brief Create an IPv6 UDP socket instance
*
* \return new UDP socket instance
*/
PAL_API UdpSocket
UdpSocket_createIpV6(void);
/**
* \brief Add the socket to an IPv4 or IPv6 multicast group
*
* \param self UDP socket instance
* \param multicastAddress IPv4 or IPv6 multicast address
*
* \return true on success, false otherwise
*/
PAL_API bool
UdpSocket_addGroupMembership(UdpSocket self, const char* multicastAddress);
/**
* \brief Sets the multicast TTL (number of hops) for this UDP socket
*
* \param self UDP socket instance
* \param ttl number of hops for multicast messages. Default is 1 (not routable!)
*
* \return true on success, false otherwise
*/
PAL_API bool
UdpSocket_setMulticastTtl(UdpSocket self, int ttl);
PAL_API bool
UdpSocket_bind(UdpSocket self, const char* address, int port);
PAL_API bool
UdpSocket_sendTo(UdpSocket self, const char* address, int port, uint8_t* msg, int msgSize);
/**
* \brief Receive data from UDP socket (store data and (optionally) the IP address of the sender
*
* \param self UDP socket instance
* \param address (optional) buffer to store the IP address as string
* \param maxAddrSize (optional) size of the provided buffer to store the IP address
* \param msg buffer to store the UDP message data
* \param msgSize the maximum size of the message to receive
*
* \return number of received bytes or -1 in case of an error
*/
PAL_API int
UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize);
PAL_API void
ServerSocket_listen(ServerSocket self);
/**
* \brief accept a new incoming connection (non-blocking)
*
* This function shall accept a new incoming connection. It is non-blocking and has to
* return NULL if no new connection is pending.
*
* Implementation of this function is MANDATORY if server functionality is required.
*
* NOTE: The behaviour of this function changed with version 0.8!
*
* \param self server socket instance
*
* \return handle of the new connection socket or NULL if no new connection is available
*/
PAL_API Socket
ServerSocket_accept(ServerSocket self);
/**
* \brief active TCP keep alive for socket and set keep alive parameters
*
* NOTE: implementation is mandatory for IEC 61850 MMS
*
* \param self server socket instance
* \param idleTime time (in s) between last received message and first keep alive message
* \param interval time (in s) between subsequent keep alive messages if no ACK received
* \param count number of not missing keep alive ACKs until socket is considered dead
*/
PAL_API void
Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count);
/**
* \brief set the maximum number of pending connections in the queue
*
* Implementation of this function is OPTIONAL.
*
* \param self the server socket instance
* \param backlog the number of pending connections in the queue
*
*/
PAL_API void
ServerSocket_setBacklog(ServerSocket self, int backlog);
/**
* \brief destroy a server socket instance
*
* Free all resources allocated by this server socket instance.
*
* Implementation of this function is MANDATORY if server functionality is required.
*
* \param self server socket instance
*/
PAL_API void
ServerSocket_destroy(ServerSocket self);
/**
* \brief create a TCP client socket
*
* Implementation of this function is MANDATORY if client functionality is required.
*
* \return a new client socket instance.
*/
PAL_API Socket
TcpSocket_create(void);
/**
* \brief set the timeout to establish a new connection
*
* \param self the client socket instance
* \param timeoutInMs the timeout in ms
*/
PAL_API void
Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs);
/**
* \brief bind a socket to a particular IP address and port (for TcpSocket)
*
* NOTE: Don't use the socket when this functions returns false!
*
* \param self the client socket instance
* \param srcAddress the local IP address or hostname as C string
* \param srcPort the local TCP port to use. When < 1 the OS will chose the TCP port to use.
*
* \return true in case of success, false otherwise
*/
PAL_API bool
Socket_bind(Socket self, const char* srcAddress, int srcPort);
/**
* \brief connect to a server
*
* Connect to a server application identified by the address and port parameter.
*
* The "address" parameter may either be a hostname or an IP address. The IP address
* has to be provided as a C string (e.g. "10.0.2.1").
*
* Implementation of this function is MANDATORY if client functionality is required.
*
* NOTE: return type changed from int to bool with version 0.8
*
* \param self the client socket instance
* \param address the IP address or hostname as C string
* \param port the TCP port of the application to connect to
*
* \return true if the connection was established successfully, false otherwise
*/
PAL_API bool
Socket_connect(Socket self, const char* address, int port);
PAL_API bool
Socket_connectAsync(Socket self, const char* address, int port);
PAL_API SocketState
Socket_checkAsyncConnectState(Socket self);
/**
* \brief read from socket to local buffer (non-blocking)
*
* The function shall return immediately if no data is available. In this case
* the function returns 0. If an error happens the function shall return -1.
*
* Implementation of this function is MANDATORY
*
* NOTE: The behaviour of this function changed with version 0.8!
*
* \param self the client, connection or server socket instance
* \param buf the buffer where the read bytes are copied to
* \param size the maximum number of bytes to read (size of the provided buffer)
*
* \return the number of bytes read or -1 if an error occurred
*/
PAL_API int
Socket_read(Socket self, uint8_t* buf, int size);
/**
* \brief send a message through the socket
*
* Implementation of this function is MANDATORY
*
* \param self client, connection or server socket instance
*
* \return number of bytes transmitted of -1 in case of an error
*/
PAL_API int
Socket_write(Socket self, uint8_t* buf, int size);
PAL_API char*
Socket_getLocalAddress(Socket self);
/**
* \brief Get the address of the peer application (IP address and port number)
*
* The peer address has to be returned as null terminated string
*
* Implementation of this function is MANDATORY (libiec61850)
*
* \param self the client, connection or server socket instance
*
* \return the IP address and port number as strings separated by the ':' character.
*/
PAL_API char*
Socket_getPeerAddress(Socket self);
/**
* \brief Get the address of the peer application (IP address and port number)
*
* The peer address has to be returned as null terminated string
*
* Implementation of this function is MANDATORY (lib60870 and libiec61850)
*
* \param self the client, connection or server socket instance
* \param peerAddressString a string to store the peer address (the string should have space
* for at least 60 characters)
*
* \return the IP address and port number as strings separated by the ':' character. If the
* address is an IPv6 address the IP part is encapsulated in square brackets.
*/
PAL_API char*
Socket_getPeerAddressStatic(Socket self, char* peerAddressString);
/**
* \brief destroy a socket (close the socket if a connection is established)
*
* This function shall close the connection (if one is established) and free all
* resources allocated by the socket.
*
* Implementation of this function is MANDATORY
*
* \param self the client, connection or server socket instance
*/
PAL_API void
Socket_destroy(Socket self);
/*! @} */
/*! @} */
#ifdef __cplusplus
}
#endif
#endif /* SOCKET_HAL_H_ */

View File

@ -0,0 +1,105 @@
/*
* thread_hal.h
*
* Multi-threading abstraction layer
*
* Copyright 2013-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#ifndef THREAD_HAL_H_
#define THREAD_HAL_H_
#include "hal_base.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file hal_thread.h
* \brief Abstraction layer for threading and synchronization
*/
/*! \addtogroup hal
*
* @{
*/
/**
* @defgroup HAL_THREAD Threading and synchronization API
*
* @{
*/
/** Opaque reference of a Thread instance */
typedef struct sThread* Thread;
/** Qpaque reference of a Semaphore instance */
typedef void* Semaphore;
/** Reference to a function that is called when starting the thread */
typedef void* (*ThreadExecutionFunction) (void*);
/**
* \brief Create a new Thread instance
*
* \param function the entry point of the thread
* \param parameter a parameter that is passed to the threads start function
* \param autodestroy the thread is automatically destroyed if the ThreadExecutionFunction has finished.
*
* \return the newly created Thread instance
*/
PAL_API Thread
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy);
/**
* \brief Start a Thread.
*
* This function invokes the start function of the thread. The thread terminates when
* the start function returns.
*
* \param thread the Thread instance to start
*/
PAL_API void
Thread_start(Thread thread);
/**
* \brief Destroy a Thread and free all related resources.
*
* \param thread the Thread instance to destroy
*/
PAL_API void
Thread_destroy(Thread thread);
/**
* \brief Suspend execution of the Thread for the specified number of milliseconds
*/
PAL_API void
Thread_sleep(int millies);
PAL_API Semaphore
Semaphore_create(int initialValue);
/* Wait until semaphore value is greater than zero. Then decrease the semaphore value. */
PAL_API void
Semaphore_wait(Semaphore self);
PAL_API void
Semaphore_post(Semaphore self);
PAL_API void
Semaphore_destroy(Semaphore self);
/*! @} */
/*! @} */
#ifdef __cplusplus
}
#endif
#endif /* THREAD_HAL_H_ */

View File

@ -0,0 +1,96 @@
/*
* time.c
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#ifndef HAL_C_
#define HAL_C_
#include "hal_base.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file hal_time.h
* \brief Abstraction layer for system time access
*/
/*! \addtogroup hal
*
* @{
*/
/**
* @defgroup HAL_TIME Time related functions
*
* @{
*/
typedef uint64_t nsSinceEpoch;
typedef uint64_t msSinceEpoch;
/**
* Get the system time in milliseconds.
*
* The time value returned as 64-bit unsigned integer should represent the milliseconds
* since the UNIX epoch (1970/01/01 00:00 UTC).
*
* \return the system time with millisecond resolution.
*/
PAL_API msSinceEpoch
Hal_getTimeInMs(void);
/**
* Get the system time in nanoseconds.
*
* The time value returned as 64-bit unsigned integer should represent the nanoseconds
* since the UNIX epoch (1970/01/01 00:00 UTC).
*
* \return the system time with nanosecond resolution.
*/
PAL_API nsSinceEpoch
Hal_getTimeInNs(void);
/**
* Set the system time from ns time
*
* The time value returned as 64-bit unsigned integer should represent the nanoseconds
* since the UNIX epoch (1970/01/01 00:00 UTC).
*
* \return true on success, otherwise false
*/
PAL_API bool
Hal_setTimeInNs(nsSinceEpoch nsTime);
/**
* Get the monotonic time or system tick time in ms
*
* \return the system time with millisecond resolution.
*/
PAL_API msSinceEpoch
Hal_getMonotonicTimeInMs(void);
/**
* Get the monotonic time or system tick in nanoseconds.
*
* \return the system time with nanosecond resolution.
*/
PAL_API nsSinceEpoch
Hal_getMonotonicTimeInNs(void);
/*! @} */
/*! @} */
#ifdef __cplusplus
}
#endif
#endif /* HAL_C_ */

View File

@ -0,0 +1,53 @@
/*
* lib_memory.h
*
* Copyright 2014-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#ifndef MEMORY_H_
#define MEMORY_H_
#include "hal_base.h"
#define CALLOC(nmemb, size) Memory_calloc(nmemb, size)
#define MALLOC(size) Memory_malloc(size)
#define REALLOC(oldptr, size) Memory_realloc(oldptr, size)
#define FREEMEM(ptr) Memory_free(ptr)
#define GLOBAL_CALLOC(nmemb, size) Memory_calloc(nmemb, size)
#define GLOBAL_MALLOC(size) Memory_malloc(size)
#define GLOBAL_REALLOC(oldptr, size) Memory_realloc(oldptr, size)
#define GLOBAL_FREEMEM(ptr) Memory_free(ptr)
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
typedef void
(*MemoryExceptionHandler) (void* parameter);
PAL_API void
Memory_installExceptionHandler(MemoryExceptionHandler handler, void* parameter);
PAL_API void*
Memory_malloc(size_t size);
PAL_API void*
Memory_calloc(size_t nmemb, size_t size);
PAL_API void *
Memory_realloc(void *ptr, size_t size);
PAL_API void
Memory_free(void* memb);
#ifdef __cplusplus
}
#endif
#endif /* MEMORY_H_ */

View File

@ -0,0 +1,37 @@
/*
* platform_endian.h
*
* Copyright 2013-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#ifndef ENDIAN_H_
#define ENDIAN_H_
#ifndef PLATFORM_IS_BIGENDIAN
#ifdef __GNUC__
#ifdef __BYTE_ORDER__
#if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#define PLATFORM_IS_BIGENDIAN 1
#endif
#else
/* older versions of GCC have __BYTE_ORDER__ not defined! */
#ifdef __PPC__
#define PLATFORM_IS_BIGENDIAN 1
#endif
#endif /* __BYTE_ORDER__ */
#endif /* __GNUC__ */
#endif
#if (PLATFORM_IS_BIGENDIAN == 1)
# define ORDER_LITTLE_ENDIAN 0
#else
# define ORDER_LITTLE_ENDIAN 1
#endif
#endif /* ENDIAN_H_ */

View File

@ -0,0 +1,263 @@
#ifndef IANA_TLS_CIPHER_SUITES_H_
#define IANA_TLS_CIPHER_SUITES_H_
#ifdef __cplusplus
extern "C" {
#endif
#define TLS_NULL_WITH_NULL_NULL 0x0000
#define TLS_RSA_WITH_NULL_MD5 0x0001
#define TLS_RSA_WITH_NULL_SHA 0x0002
#define TLS_RSA_EXPORT_WITH_RC4_40_MD5 0x0003
#define TLS_RSA_WITH_RC4_128_MD5 0x0004
#define TLS_RSA_WITH_RC4_128_SHA 0x0005
#define TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 0x0006
#define TLS_RSA_WITH_IDEA_CBC_SHA 0x0007
#define TLS_RSA_EXPORT_WITH_DES40_CBC_SHA 0x0008
#define TLS_RSA_WITH_DES_CBC_SHA 0x0009
#define TLS_RSA_WITH_3DES_EDE_CBC_SHA 0x000A
#define TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA 0x000B
#define TLS_DH_DSS_WITH_DES_CBC_SHA 0x000C
#define TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA 0x000D
#define TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA 0x000E
#define TLS_DH_RSA_WITH_DES_CBC_SHA 0x000F
#define TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA 0x0010
#define TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA 0x0011
#define TLS_DHE_DSS_WITH_DES_CBC_SHA 0x0012
#define TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA 0x0013
#define TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA 0x0014
#define TLS_DHE_RSA_WITH_DES_CBC_SHA 0x0015
#define TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA 0x0016
#define TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 0x0017
#define TLS_DH_anon_WITH_RC4_128_MD5 0x0018
#define TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA 0x0019
#define TLS_DH_anon_WITH_DES_CBC_SHA 0x001A
#define TLS_DH_anon_WITH_3DES_EDE_CBC_SHA 0x001B
#define TLS_RSA_WITH_AES_128_CBC_SHA 0x002F
#define TLS_DH_DSS_WITH_AES_128_CBC_SHA 0x0030
#define TLS_DH_RSA_WITH_AES_128_CBC_SHA 0x0031
#define TLS_DHE_DSS_WITH_AES_128_CBC_SHA 0x0032
#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA 0x0033
#define TLS_DH_anon_WITH_AES_128_CBC_SHA 0x0034
#define TLS_RSA_WITH_AES_256_CBC_SHA 0x0035
#define TLS_DH_DSS_WITH_AES_256_CBC_SHA 0x0036
#define TLS_DH_RSA_WITH_AES_256_CBC_SHA 0x0037
#define TLS_DHE_DSS_WITH_AES_256_CBC_SHA 0x0038
#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA 0x0039
#define TLS_DH_anon_WITH_AES_256_CBC_SHA 0x003A
#define TLS_RSA_WITH_NULL_SHA256 0x003B
#define TLS_RSA_WITH_AES_128_CBC_SHA256 0x003C
#define TLS_RSA_WITH_AES_256_CBC_SHA256 0x003D
#define TLS_DH_DSS_WITH_AES_128_CBC_SHA256 0x003E
#define TLS_DH_RSA_WITH_AES_128_CBC_SHA256 0x003F
#define TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 0x0040
#define TLS_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0041
#define TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA 0x0042
#define TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0043
#define TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA 0x0044
#define TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA 0x0045
#define TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA 0x0046
#define TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 0x0067
#define TLS_DH_DSS_WITH_AES_256_CBC_SHA256 0x0068
#define TLS_DH_RSA_WITH_AES_256_CBC_SHA256 0x0069
#define TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 0x006A
#define TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 0x006B
#define TLS_DH_anon_WITH_AES_128_CBC_SHA256 0x006C
#define TLS_DH_anon_WITH_AES_256_CBC_SHA256 0x006D
#define TLS_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0084
#define TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA 0x0085
#define TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0086
#define TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA 0x0087
#define TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA 0x0088
#define TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA 0x0089
#define TLS_RSA_WITH_SEED_CBC_SHA 0x0096
#define TLS_DH_DSS_WITH_SEED_CBC_SHA 0x0097
#define TLS_DH_RSA_WITH_SEED_CBC_SHA 0x0098
#define TLS_DHE_DSS_WITH_SEED_CBC_SHA 0x0099
#define TLS_DHE_RSA_WITH_SEED_CBC_SHA 0x009A
#define TLS_DH_anon_WITH_SEED_CBC_SHA 0x009B
#define TLS_RSA_WITH_AES_128_GCM_SHA256 0x009C
#define TLS_RSA_WITH_AES_256_GCM_SHA384 0x009D
#define TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 0x009E
#define TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 0x009F
#define TLS_DH_RSA_WITH_AES_128_GCM_SHA256 0x00A0
#define TLS_DH_RSA_WITH_AES_256_GCM_SHA384 0x00A1
#define TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 0x00A2
#define TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 0x00A3
#define TLS_DH_DSS_WITH_AES_128_GCM_SHA256 0x00A4
#define TLS_DH_DSS_WITH_AES_256_GCM_SHA384 0x00A5
#define TLS_DH_anon_WITH_AES_128_GCM_SHA256 0x00A6
#define TLS_DH_anon_WITH_AES_256_GCM_SHA384 0x00A7
#define TLS_PSK_WITH_AES_128_CBC_SHA 0x008C
#define TLS_PSK_WITH_AES_256_CBC_SHA 0x008D
#define TLS_DHE_PSK_WITH_AES_128_CBC_SHA 0x008E
#define TLS_DHE_PSK_WITH_AES_256_CBC_SHA 0x008F
#define TLS_RSA_PSK_WITH_AES_128_CBC_SHA 0x0090
#define TLS_RSA_PSK_WITH_AES_256_CBC_SHA 0x0091
#define TLS_PSK_WITH_AES_256_GCM_SHA384 0x00A9
#define TLS_PSK_WITH_AES_128_CBC_SHA256 0x00AE
#define TLS_PSK_WITH_AES_256_CBC_SHA384 0x00AF
#define TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 0x00B0
#define TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 0x00B1
#define TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 0x00B2
#define TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 0x00B3
#define TLS_PSK_WITH_NULL_SHA 0x002C
#define TLS_DHE_PSK_WITH_NULL_SHA 0x002D
#define TLS_RSA_PSK_WITH_NULL_SHA 0x002E
#define TLS_PSK_WITH_NULL_SHA256 0x00B4
#define TLS_PSK_WITH_NULL_SHA384 0x00B5
#define TLS_DHE_PSK_WITH_NULL_SHA256 0x00B6
#define TLS_DHE_PSK_WITH_NULL_SHA384 0x00B7
#define TLS_RSA_PSK_WITH_NULL_SHA256 0x00B8
#define TLS_RSA_PSK_WITH_NULL_SHA384 0x00B9
#define TLS_ECDH_ECDSA_WITH_NULL_SHA 0xC001
#define TLS_ECDH_ECDSA_WITH_RC4_128_SHA 0xC002
#define TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC003
#define TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA 0xC004
#define TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA 0xC005
#define TLS_ECDHE_ECDSA_WITH_NULL_SHA 0xC006
#define TLS_ECDHE_ECDSA_WITH_RC4_128_SHA 0xC007
#define TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA 0xC008
#define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA 0xC009
#define TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 0xC00A
#define TLS_ECDH_RSA_WITH_NULL_SHA 0xC00B
#define TLS_ECDH_RSA_WITH_RC4_128_SHA 0xC00C
#define TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA 0xC00D
#define TLS_ECDH_RSA_WITH_AES_128_CBC_SHA 0xC00E
#define TLS_ECDH_RSA_WITH_AES_256_CBC_SHA 0xC00F
#define TLS_ECDHE_RSA_WITH_NULL_SHA 0xC010
#define TLS_ECDHE_RSA_WITH_RC4_128_SHA 0xC011
#define TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA 0xC012
#define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 0xC013
#define TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 0xC014
#define TLS_ECDH_anon_WITH_NULL_SHA 0xC015
#define TLS_ECDH_anon_WITH_RC4_128_SHA 0xC016
#define TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA 0xC017
#define TLS_ECDH_anon_WITH_AES_128_CBC_SHA 0xC018
#define TLS_ECDH_anon_WITH_AES_256_CBC_SHA 0xC019
#define TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 0xC023
#define TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 0xC024
#define TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 0xC025
#define TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 0xC026
#define TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 0xC027
#define TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 0xC028
#define TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 0xC029
#define TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 0xC02A
#define TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 0xC02B
#define TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 0xC02C
#define TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 0xC02D
#define TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 0xC02E
#define TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 0xC02F
#define TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 0xC030
#define TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 0xC031
#define TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 0xC032
#define TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA 0xC035
#define TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA 0xC036
#define TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 0xC037
#define TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 0xC038
#define TLS_ECDHE_PSK_WITH_NULL_SHA 0xC039
#define TLS_ECDHE_PSK_WITH_NULL_SHA256 0xC03A
#define TLS_ECDHE_PSK_WITH_NULL_SHA384 0xC03B
#define TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 0xC072
#define TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 0xC073
#define TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 0xC074
#define TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 0xC075
#define TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 0xC076
#define TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 0xC077
#define TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 0xC078
#define TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 0xC079
#define TLS_RSA_WITH_ARIA_128_CBC_SHA256 0xC03C
#define TLS_RSA_WITH_ARIA_256_CBC_SHA384 0xC03D
#define TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 0xC03E
#define TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 0xC03F
#define TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 0xC040
#define TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 0xC041
#define TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 0xC042
#define TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 0xC043
#define TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 0xC044
#define TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 0xC045
#define TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 0xC046
#define TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 0xC047
#define TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 0xC048
#define TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 0xC049
#define TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 0xC04A
#define TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 0xC04B
#define TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 0xC04C
#define TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 0xC04D
#define TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 0xC04E
#define TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 0xC04F
#define TLS_RSA_WITH_ARIA_128_GCM_SHA256 0xC050
#define TLS_RSA_WITH_ARIA_256_GCM_SHA384 0xC051
#define TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 0xC052
#define TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 0xC053
#define TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 0xC054
#define TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 0xC055
#define TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 0xC056
#define TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 0xC057
#define TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 0xC058
#define TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 0xC059
#define TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 0xC05A
#define TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 0xC05B
#define TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 0xC05C
#define TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 0xC05D
#define TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 0xC05E
#define TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 0xC05F
#define TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 0xC060
#define TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 0xC061
#define TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 0xC062
#define TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 0xC063
#define TLS_PSK_WITH_ARIA_128_CBC_SHA256 0xC064
#define TLS_PSK_WITH_ARIA_256_CBC_SHA384 0xC065
#define TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 0xC066
#define TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 0xC067
#define TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 0xC068
#define TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 0xC069
#define TLS_PSK_WITH_ARIA_128_GCM_SHA256 0xC06A
#define TLS_PSK_WITH_ARIA_256_GCM_SHA384 0xC06B
#define TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 0xC06C
#define TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 0xC06D
#define TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 0xC06E
#define TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 0xC06F
#define TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 0xC070
#define TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 0xC071
#define TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 0xC076
#define TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 0xC077
#define TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 0xC078
#define TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 0xC079
#define TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 0xC07A
#define TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 0xC07B
#define TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 0xC07C
#define TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 0xC07D
#define TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 0xC07E
#define TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 0xC07F
#define TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 0xC080
#define TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 0xC081
#define TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 0xC082
#define TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 0xC083
#define TLS_ECDHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 0xC084
#define TLS_ECDHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 0xC085
#define TLS_RSA_WITH_AES_128_CCM 0xC09C
#define TLS_RSA_WITH_AES_256_CCM 0xC09D
#define TLS_DHE_RSA_WITH_AES_128_CCM 0xC09E
#define TLS_DHE_RSA_WITH_AES_256_CCM 0xC09F
#define TLS_RSA_WITH_AES_128_CCM_8 0xC0A0
#define TLS_RSA_WITH_AES_256_CCM_8 0xC0A1
#define TLS_DHE_RSA_WITH_AES_128_CCM_8 0xC0A2
#define TLS_DHE_RSA_WITH_AES_256_CCM_8 0xC0A3
#define TLS_PSK_WITH_AES_128_CCM 0xC0A4
#define TLS_PSK_WITH_AES_256_CCM 0xC0A5
#define TLS_DHE_PSK_WITH_AES_128_CCM 0xC0A6
#define TLS_DHE_PSK_WITH_AES_256_CCM 0xC0A7
#define TLS_PSK_WITH_AES_128_CCM_8 0xC0A8
#define TLS_PSK_WITH_AES_256_CCM_8 0xC0A9
#define TLS_PSK_DHE_WITH_AES_128_CCM_8 0xC0AA
#define TLS_PSK_DHE_WITH_AES_256_CCM_8 0xC0AB
#define TLS_ECDHE_ECDSA_WITH_AES_128_CCM 0xC0AC
// ... add more cipher suite codes here ...
#ifdef __cplusplus
}
#endif
#endif /* IANA_TLS_CIPHER_SUITES_H_ */

View File

@ -0,0 +1,382 @@
/*
* tls_config.h
*
* TLS Configuration API for protocol stacks using TCP/IP
*
* Copyright 2017-2024 Michael Zillgith
*
* Abstraction layer for configuration of different TLS implementations
*
*/
#ifndef SRC_TLS_CONFIG_H_
#define SRC_TLS_CONFIG_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "hal_base.h"
#include "tls_ciphers.h"
/**
* \file tls_config.h
* \brief TLS API functions
*/
/*! \addtogroup hal Platform (Hardware/OS) abstraction layer
*
* @{
*/
/**
* @defgroup TLS_CONFIG_API TLS configuration
*
* @{
*/
typedef struct sTLSConfiguration* TLSConfiguration;
/**
* \brief Create a new \ref TLSConfiguration object to represent TLS configuration and certificates
*
* WARNING: Configuration cannot be changed after using for the first time.
*
* \return the new TLS configuration
*/
PAL_API TLSConfiguration
TLSConfiguration_create(void);
/* will be called by stack automatically when appropriate */
PAL_API void
TLSConfiguration_setClientMode(TLSConfiguration self);
typedef enum {
TLS_VERSION_NOT_SELECTED = 0,
TLS_VERSION_SSL_3_0 = 3,
TLS_VERSION_TLS_1_0 = 4,
TLS_VERSION_TLS_1_1 = 5,
TLS_VERSION_TLS_1_2 = 6,
TLS_VERSION_TLS_1_3 = 7
} TLSConfigVersion;
/**
* \brief Convert TLS version number to string
*
* \param version TLS version number
*
* \return the TLS version as null terminated string
*/
PAL_API const char*
TLSConfigVersion_toString(TLSConfigVersion version);
typedef enum {
TLS_SEC_EVT_INFO = 0,
TLS_SEC_EVT_WARNING = 1,
TLS_SEC_EVT_INCIDENT = 2
} TLSEventLevel;
#define TLS_EVENT_CODE_ALM_ALGO_NOT_SUPPORTED 1
#define TLS_EVENT_CODE_ALM_UNSECURE_COMMUNICATION 2
#define TLS_EVENT_CODE_ALM_CERT_UNAVAILABLE 3
#define TLS_EVENT_CODE_ALM_BAD_CERT 4
#define TLS_EVENT_CODE_ALM_CERT_SIZE_EXCEEDED 5
#define TLS_EVENT_CODE_ALM_CERT_VALIDATION_FAILED 6
#define TLS_EVENT_CODE_ALM_CERT_REQUIRED 7
#define TLS_EVENT_CODE_ALM_HANDSHAKE_FAILED_UNKNOWN_REASON 8
#define TLS_EVENT_CODE_WRN_INSECURE_TLS_VERSION 9
#define TLS_EVENT_CODE_INF_SESSION_RENEGOTIATION 10
#define TLS_EVENT_CODE_ALM_CERT_EXPIRED 11
#define TLS_EVENT_CODE_ALM_CERT_REVOKED 12
#define TLS_EVENT_CODE_ALM_CERT_NOT_CONFIGURED 13
#define TLS_EVENT_CODE_ALM_CERT_NOT_TRUSTED 14
#define TLS_EVENT_CODE_ALM_NO_CIPHER 15
#define TLS_EVENT_CODE_INF_SESSION_ESTABLISHED 16
#define TLS_EVENT_CODE_WRN_CERT_EXPIRED 17
#define TLS_EVENT_CODE_WRN_CERT_NOT_YET_VALID 18
#define TLS_EVENT_CODE_WRN_CRL_EXPIRED 19
#define TLS_EVENT_CODE_WRN_CRL_NOT_YET_VALID 20
#define TLS_EVENT_CODE_ALM_TLS_VERSION_CHANGE 21
#define TLS_EVENT_CODE_WRN_MIN_KEY_LENGTH 22
#define TLS_EVENT_CODE_ALM_INSUFFICIENT_KEY_LENGTH 23
#define TLS_EVENT_CODE_WRN_CRL_NOT_ACCESSIBLE 24
#define TLS_EVENT_CODE_ALM_CA_CERT_NOT_AVAILABLE 25
#define TLS_EVENT_CODE_ALM_RENEGOTIATION_TIMEOUT 26
#define TLS_EVENT_CODE_INF_SESSION_RESUMED 27
#define TLS_EVENT_CODE_INF_SESSION_EXPIRED 28
typedef struct sTLSConnection* TLSConnection;
/**
* \brief Get the peer address of the TLS connection
*
* \param self the TLS connection instance
* \param peerAddrBuf user provided buffer that can hold at least 60 characters, or NULL to allow the function to allocate the memory for the buffer
*
* \returns peer address:port as null terminated string
*/
PAL_API char*
TLSConnection_getPeerAddress(TLSConnection self, char* peerAddrBuf);
/**
* \brief Get the TLS certificate used by the peer
*
* \param self the TLS connection instance
* \param certSize[OUT] the certificate size in bytes
*
* \return address of the certificate buffer
*/
PAL_API uint8_t*
TLSConnection_getPeerCertificate(TLSConnection self, int* certSize);
/**
* \brief Get the TLS version used by the connection
*
* \param self the TLS connection instance
*
* \return TLS version
*/
PAL_API TLSConfigVersion
TLSConnection_getTLSVersion(TLSConnection self);
typedef void (*TLSConfiguration_EventHandler)(void* parameter, TLSEventLevel eventLevel, int eventCode, const char* message, TLSConnection con);
/**
* \brief Set the security event handler
*
* \param handler the security event callback handler
* \param parameter user provided parameter to be passed to the callback handler
*/
PAL_API void
TLSConfiguration_setEventHandler(TLSConfiguration self, TLSConfiguration_EventHandler handler, void* parameter);
/**
* \brief enable or disable TLS session resumption (default: enabled)
*
* NOTE: Depending on the used TLS version this is implemented by
* session IDs or by session tickets.
*
* \param enable true to enable session resumption, false otherwise
*/
PAL_API void
TLSConfiguration_enableSessionResumption(TLSConfiguration self, bool enable);
/**
* \brief Set the maximum life time of a cached TLS session for session resumption in seconds
*
* \param intervalInSeconds the maximum lifetime of a cached TLS session
*/
PAL_API void
TLSConfiguration_setSessionResumptionInterval(TLSConfiguration self, int intervalInSeconds);
/**
* \brief Enables the validation of the certificate trust chain (enabled by default)
*
* \param value true to enable chain validation, false to disable
*/
PAL_API void
TLSConfiguration_setChainValidation(TLSConfiguration self, bool value);
/**
* \brief Enabled or disables the verification of validity times for certificates and CRLs
*
* \param value true to enable time validation, false to disable (enabled by default)
*/
PAL_API void
TLSConfiguration_setTimeValidation(TLSConfiguration self, bool value);
/**
* \brief Set if only known certificates are accepted.
*
* If set to true only known certificates are accepted. Connections with unknown certificates
* are rejected even if they are signed by a trusted authority.
*
* \param value true to enable setting, false otherwise
*/
PAL_API void
TLSConfiguration_setAllowOnlyKnownCertificates(TLSConfiguration self, bool value);
/**
* \brief Set own certificate (identity) from a byte buffer
*
* \param certificate the certificate buffer
* \param certLen the lenght of the certificate
*
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
*/
PAL_API bool
TLSConfiguration_setOwnCertificate(TLSConfiguration self, uint8_t* certificate, int certLen);
/**
* \brief Set own certificate (identity) from a certificate file
*
* \param filename of the certificate file
*
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
*/
PAL_API bool
TLSConfiguration_setOwnCertificateFromFile(TLSConfiguration self, const char* filename);
/**
* \brief Set the own private key from a byte buffer
*
* \param key the private key to use
* \param keyLen the length of the key
* \param password the password of the key or null if the key is not password protected
*
* \return true, when the key was set, false otherwise (e.g. unknown key format)
*/
PAL_API bool
TLSConfiguration_setOwnKey(TLSConfiguration self, uint8_t* key, int keyLen, const char* keyPassword);
/**
* \brief Set the own private key from a key file
*
* \param filename filename/path of the key file
* \param password the password of the key or null if the key is not password protected
*
* \return true, when the key was set, false otherwise (e.g. unknown key format)
*/
PAL_API bool
TLSConfiguration_setOwnKeyFromFile(TLSConfiguration self, const char* filename, const char* keyPassword);
/**
* Add a certificate to the list of allowed peer certificates from a byte buffer
*
* \param certificate the certificate buffer
* \param certLen the length of the certificate buffer
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
*/
PAL_API bool
TLSConfiguration_addAllowedCertificate(TLSConfiguration self, uint8_t* certificate, int certLen);
/**
* \brief Add a certificate to the list of allowed peer certificates
*
* \param filename filename of the certificate file
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
*/
PAL_API bool
TLSConfiguration_addAllowedCertificateFromFile(TLSConfiguration self, const char* filename);
/**
* \brief Add a CA certificate used to validate peer certificates from a byte buffer
*
* \param certificate the certificate buffer
* \param certLen the length of the certificate buffer
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
*/
PAL_API bool
TLSConfiguration_addCACertificate(TLSConfiguration self, uint8_t* certificate, int certLen);
/**
* \brief Add a CA certificate used to validate peer certificates from a file
*
* \param filename filename of the certificate file
* \return true, when the certificate was set, false otherwise (e.g. unknown certificate format)
*/
PAL_API bool
TLSConfiguration_addCACertificateFromFile(TLSConfiguration self, const char* filename);
/**
* \brief Set the renegotiation timeout.
*
* After the timeout elapsed a TLS session renegotiation has to occur.
*
* \param timeInMs session renegotiation timeout in milliseconds
*/
PAL_API void
TLSConfiguration_setRenegotiationTime(TLSConfiguration self, int timeInMs);
/**
* \brief Set the maximum time allowed for TLS session renegotiation.
*
* When the renegotiation handshake does not complete within the configured
* timeout a security event is generated and the renegotiation attempt is
* aborted. Provide a value \<= 0 to disable the timeout enforcement.
* The default is 10000 milliseconds.
*
* \param timeoutInMs timeout in milliseconds
*/
PAL_API void
TLSConfiguration_setRenegotiationTimeout(TLSConfiguration self, int timeoutInMs);
/**
* \brief Set minimal allowed TLS version to use
*/
PAL_API void
TLSConfiguration_setMinTlsVersion(TLSConfiguration self, TLSConfigVersion version);
/**
* \brief Set maximal allowed TLS version to use
*/
PAL_API void
TLSConfiguration_setMaxTlsVersion(TLSConfiguration self, TLSConfigVersion version);
/**
* \brief Set the minimum allowed public key length in bits (smaller keys will cause certificate validation to fail)
*
* \param keyLengthInBits minimum key length in bits
*/
PAL_API void
TLSConfiguration_setMinimumKeyLength(TLSConfiguration self, int keyLengthInBits);
/**
* \brief Add a CRL (certificate revocation list) from buffer
*
* \param crl the buffer containing the CRL
* \param crlLen the length of the CRL buffer
* \return true, when the CRL was imported, false otherwise (e.g. unknown format)
*/
PAL_API bool
TLSConfiguration_addCRL(TLSConfiguration self, uint8_t* crl, int crlLen);
/**
* \brief Add a CRL (certificate revocation list) from a file
*
* \param filename filename of the CRL file
* \return true, when the CRL was imported, false otherwise (e.g. unknown format)
*/
PAL_API bool
TLSConfiguration_addCRLFromFile(TLSConfiguration self, const char* filename);
/**
* \brief Removes any CRL (certificate revocation list) currently in use
*/
PAL_API void
TLSConfiguration_resetCRL(TLSConfiguration self);
/**
* \brief Add an allowed ciphersuite to the list of allowed ciphersuites
*
* \param self the TLS configuration instance
* \param ciphersuite the ciphersuite to add (IANA cipher suite ID)
*/
PAL_API void
TLSConfiguration_addCipherSuite(TLSConfiguration self, int ciphersuite);
/**
* \brief Clear the list of allowed ciphersuites
*
* \param self the TLS configuration instance
*/
PAL_API void
TLSConfiguration_clearCipherSuiteList(TLSConfiguration self);
/**
* Release all resource allocated by the TLSConfiguration instance
*
* NOTE: Do not use the object after calling this function!
*/
PAL_API void
TLSConfiguration_destroy(TLSConfiguration self);
/** @} */
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* SRC_TLS_CONFIG_H_ */

View File

@ -0,0 +1,122 @@
/*
* tls_socket.h
*
* TLS socket API for protocol libraries using TCP/IP
*
* Copyright 2017-2021 Michael Zillgith, MZ Automation GmbH
*
* Abstraction layer for different TLS implementations
*
* The implementation has to connect the TLS API layer with the socket API layer
* and perform all TLS tasks like handshake, encryption/decryption.
*
*/
#ifndef SRC_TLS_SOCKET_API_H_
#define SRC_TLS_SOCKET_API_H_
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file tls_socket.h
* \brief Abstraction layer for different TLS implementations.
*
* The implementation has to connect the TLS API layer with the socket API layer
* and perform all TLS tasks like handshake, encryption/decryption.
*/
/*! \addtogroup hal Platform (Hardware/OS) abstraction layer
*
* @{
*/
/**
* @defgroup HAL_TLS_SOCKET Abstraction layer for different TLS implementations.
*
* The implementation has to connect the TLS API layer with the socket API layer
* and perform all TLS tasks like handshake, encryption/decryption.
*
* @{
*/
#include <stdint.h>
#include "tls_config.h"
#include "hal_socket.h"
typedef struct sTLSSocket* TLSSocket;
/**
* \brief This function create a new TLSSocket instance using the given Socket instance
*
* NOTE: This function also has to perform the TLS handshake
*
* \param socket the socket instance to use for the TLS connection
* \param configuration the TLS configuration object to use
* \param storeClientCert if true, the client certificate will be stored
* for later access by \ref TLSSocket_getPeerCertificate
*
* \return new TLS connection instance
*/
PAL_API TLSSocket
TLSSocket_create(Socket socket, TLSConfiguration configuration, bool storeClientCert);
/**
* \brief Perform a new TLS handshake/session renegotiation
*/
PAL_API bool
TLSSocket_performHandshake(TLSSocket self);
/**
* \brief Access the certificate used by the peer
*
* \param[out] certSize the size of the certificate in bytes
*
* \return the certificate byte buffer
*/
PAL_API uint8_t*
TLSSocket_getPeerCertificate(TLSSocket self, int* certSize);
/**
* \brief read from socket to local buffer (non-blocking)
*
* The function shall return immediately if no data is available. In this case
* the function returns 0. If an error happens the function shall return -1.
*
* \param self the client, connection or server socket instance
* \param buf the buffer where the read bytes are copied to
* \param size the maximum number of bytes to read (size of the provided buffer)
*
* \return the number of bytes read or -1 if an error occurred
*/
PAL_API int
TLSSocket_read(TLSSocket self, uint8_t* buf, int size);
/**
* \brief send a message through the socket
*
* Implementation of this function is MANDATORY
*
* \param self client, connection or server socket instance
*
* \return number of bytes transmitted of -1 in case of an error
*/
PAL_API int
TLSSocket_write(TLSSocket self, uint8_t* buf, int size);
/**
* \brief Closes the TLS connection and released all resources
*/
PAL_API void
TLSSocket_close(TLSSocket self);
/*! @} */
/*! @} */
#ifdef __cplusplus
}
#endif
#endif /* SRC_TLS_SOCKET_API_H_ */

View File

@ -0,0 +1,68 @@
/*
* lib_memory.c
*
* Copyright 2014-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#include <stdlib.h>
#include "lib_memory.h"
static MemoryExceptionHandler exceptionHandler = NULL;
static void* exceptionHandlerParameter = NULL;
static void
noMemoryAvailableHandler(void)
{
if (exceptionHandler != NULL)
exceptionHandler(exceptionHandlerParameter);
}
void
Memory_installExceptionHandler(MemoryExceptionHandler handler, void* parameter)
{
exceptionHandler = handler;
exceptionHandlerParameter = parameter;
}
void*
Memory_malloc(size_t size)
{
void* memory = malloc(size);
if (memory == NULL)
noMemoryAvailableHandler();
return memory;
}
void*
Memory_calloc(size_t nmemb, size_t size)
{
void* memory = calloc(nmemb, size);
if (memory == NULL)
noMemoryAvailableHandler();
return memory;
}
void *
Memory_realloc(void *ptr, size_t size)
{
void* memory = realloc(ptr, size);
if (memory == NULL)
noMemoryAvailableHandler();
return memory;
}
void
Memory_free(void* memb)
{
free(memb);
}

View File

@ -0,0 +1,264 @@
/*
* serial_port_linux.c
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#include "lib_memory.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#include "hal_serial.h"
#include "hal_time.h"
struct sSerialPort {
char interfaceName[100];
int fd;
int baudRate;
uint8_t dataBits;
char parity;
uint8_t stopBits;
uint64_t lastSentTime;
struct timeval timeout;
SerialPortError lastError;
};
SerialPort
SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits)
{
SerialPort self = (SerialPort) GLOBAL_MALLOC(sizeof(struct sSerialPort));
if (self != NULL) {
self->fd = -1;
self->baudRate = baudRate;
self->dataBits = dataBits;
self->stopBits = stopBits;
self->parity = parity;
self->lastSentTime = 0;
self->timeout.tv_sec = 0;
self->timeout.tv_usec = 100000; /* 100 ms */
strncpy(self->interfaceName, interfaceName, 99);
self->lastError = SERIAL_PORT_ERROR_NONE;
}
return self;
}
void
SerialPort_destroy(SerialPort self)
{
if (self != NULL) {
GLOBAL_FREEMEM(self);
}
}
bool
SerialPort_open(SerialPort self)
{
self->fd = open(self->interfaceName, O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL);
if (self->fd == -1) {
self->lastError = SERIAL_PORT_ERROR_OPEN_FAILED;
return false;
}
struct termios tios;
speed_t baudrate;
tcgetattr(self->fd, &tios);
switch (self->baudRate) {
case 110:
baudrate = B110;
break;
case 300:
baudrate = B300;
break;
case 600:
baudrate = B600;
break;
case 1200:
baudrate = B1200;
break;
case 2400:
baudrate = B2400;
break;
case 4800:
baudrate = B4800;
break;
case 9600:
baudrate = B9600;
break;
case 19200:
baudrate = B19200;
break;
case 38400:
baudrate = B38400;
break;
case 57600:
baudrate = B57600;
break;
case 115200:
baudrate = B115200;
break;
default:
baudrate = B9600;
self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE;
}
/* Set baud rate */
if ((cfsetispeed(&tios, baudrate) < 0) || (cfsetospeed(&tios, baudrate) < 0)) {
close(self->fd);
self->fd = -1;
self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE;
return false;
}
tios.c_cflag |= (CREAD | CLOCAL);
/* Set data bits (5/6/7/8) */
tios.c_cflag &= ~CSIZE;
switch (self->dataBits) {
case 5:
tios.c_cflag |= CS5;
break;
case 6:
tios.c_cflag |= CS6;
break;
case 7:
tios.c_cflag |= CS7;
break;
case 8:
default:
tios.c_cflag |= CS8;
break;
}
/* Set stop bits (1/2) */
if (self->stopBits == 1)
tios.c_cflag &=~ CSTOPB;
else /* 2 */
tios.c_cflag |= CSTOPB;
if (self->parity == 'N') {
tios.c_cflag &=~ PARENB;
} else if (self->parity == 'E') {
tios.c_cflag |= PARENB;
tios.c_cflag &=~ PARODD;
} else { /* 'O' */
tios.c_cflag |= PARENB;
tios.c_cflag |= PARODD;
}
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
if (self->parity == 'N') {
tios.c_iflag &= ~INPCK;
} else {
tios.c_iflag |= INPCK;
}
tios.c_iflag &= ~(IXON | IXOFF | IXANY | ICRNL);
tios.c_iflag |= IGNBRK; /* Set ignore break to allow 0xff characters */
tios.c_iflag |= IGNPAR;
tios.c_oflag &=~ OPOST;
tios.c_cc[VMIN] = 0;
tios.c_cc[VTIME] = 0;
if (tcsetattr(self->fd, TCSANOW, &tios) < 0) {
close(self->fd);
self->fd = -1;
self->lastError = SERIAL_PORT_ERROR_INVALID_ARGUMENT;
return false;
}
return true;
}
void
SerialPort_close(SerialPort self)
{
if (self->fd != -1) {
close(self->fd);
self->fd = 0;
}
}
int
SerialPort_getBaudRate(SerialPort self)
{
return self->baudRate;
}
void
SerialPort_discardInBuffer(SerialPort self)
{
tcflush(self->fd, TCIOFLUSH);
}
void
SerialPort_setTimeout(SerialPort self, int timeout)
{
self->timeout.tv_sec = timeout / 1000;
self->timeout.tv_usec = (timeout % 1000) * 1000;
}
SerialPortError
SerialPort_getLastError(SerialPort self)
{
return self->lastError;
}
int
SerialPort_readByte(SerialPort self)
{
uint8_t buf[1];
fd_set set;
self->lastError = SERIAL_PORT_ERROR_NONE;
FD_ZERO(&set);
FD_SET(self->fd, &set);
int ret = select(self->fd + 1, &set, NULL, NULL, &(self->timeout));
if (ret == -1) {
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
return -1;
}
else if (ret == 0)
return -1;
else {
read(self->fd, (char*) buf, 1);
return (int) buf[0];
}
}
int
SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int bufSize)
{
/* TODO assure minimum line idle time? */
self->lastError = SERIAL_PORT_ERROR_NONE;
ssize_t result = write(self->fd, buffer + startPos, bufSize);
tcdrain(self->fd);
self->lastSentTime = Hal_getMonotonicTimeInMs();
return result;
}

View File

@ -0,0 +1,273 @@
/*
* serial_port_win32.c
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_NONSTDC_NO_DEPRECATE
#endif
#include <stdint.h>
#include <stdio.h>
#include <windows.h>
#include "lib_memory.h"
#include "hal_serial.h"
#include "hal_time.h"
struct sSerialPort {
char interfaceName[100];
HANDLE comPort;
int baudRate;
uint8_t dataBits;
char parity;
uint8_t stopBits;
uint64_t lastSentTime;
int timeout;
SerialPortError lastError;
};
SerialPort
SerialPort_create(const char* interfaceName, int baudRate, uint8_t dataBits, char parity, uint8_t stopBits)
{
SerialPort self = (SerialPort)GLOBAL_MALLOC(sizeof(struct sSerialPort));
if (self != NULL) {
self->comPort = NULL;
self->baudRate = baudRate;
self->dataBits = dataBits;
self->stopBits = stopBits;
self->parity = parity;
self->lastSentTime = 0;
self->timeout = 100; /* 100 ms */
strncpy(self->interfaceName, interfaceName, 100);
self->lastError = SERIAL_PORT_ERROR_NONE;
}
return self;
}
void
SerialPort_destroy(SerialPort self)
{
if (self != NULL) {
if (self->comPort != INVALID_HANDLE_VALUE)
SerialPort_close(self);
GLOBAL_FREEMEM(self);
}
}
bool
SerialPort_open(SerialPort self)
{
COMMTIMEOUTS timeouts = { 0 };
self->comPort = CreateFile(self->interfaceName, GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, 0, NULL);
if (self->comPort == INVALID_HANDLE_VALUE) {
self->lastError = SERIAL_PORT_ERROR_OPEN_FAILED;
return false;
}
DCB _serialParams = { 0 };
_serialParams.DCBlength = sizeof(DCB);
LPDCB serialParams = &_serialParams;
BOOL status = GetCommState(self->comPort, serialParams);
if (status == false) {
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
goto exit_error;
}
/* set baud rate */
switch (self->baudRate) {
case 110:
serialParams->BaudRate = CBR_110;
break;
case 300:
serialParams->BaudRate = CBR_300;
break;
case 600:
serialParams->BaudRate = CBR_600;
break;
case 1200:
serialParams->BaudRate = CBR_1200;
break;
case 2400:
serialParams->BaudRate = CBR_2400;
break;
case 4800:
serialParams->BaudRate = CBR_4800;
break;
case 9600:
serialParams->BaudRate = CBR_9600;
break;
case 19200:
serialParams->BaudRate = CBR_19200;
break;
case 38400:
serialParams->BaudRate = CBR_38400;
break;
case 57600:
serialParams->BaudRate = CBR_57600;
break;
case 115200:
serialParams->BaudRate = CBR_115200;
break;
default:
serialParams->BaudRate = CBR_9600;
self->lastError = SERIAL_PORT_ERROR_INVALID_BAUDRATE;
}
/* Set data bits (5/6/7/8) */
serialParams->ByteSize = self->dataBits;
/* Set stop bits (1/2) */
if (self->stopBits == 1)
serialParams->StopBits = ONESTOPBIT;
else /* 2 */
serialParams->StopBits = TWOSTOPBITS;
if (self->parity == 'N')
serialParams->Parity = NOPARITY;
else if (self->parity == 'E')
serialParams->Parity = EVENPARITY;
else /* 'O' */
serialParams->Parity = ODDPARITY;
status = SetCommState(self->comPort, serialParams);
if (status == false) {
self->lastError = SERIAL_PORT_ERROR_INVALID_ARGUMENT;
goto exit_error;
}
timeouts.ReadIntervalTimeout = 100;
timeouts.ReadTotalTimeoutConstant = 50;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 100;
timeouts.WriteTotalTimeoutMultiplier = 10;
status = SetCommTimeouts(self->comPort, &timeouts);
if (status == false) {
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
goto exit_error;
}
status = SetCommMask(self->comPort, EV_RXCHAR);
if (status == false) {
printf("SetCommMask failed!\n");
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
goto exit_error;
}
self->lastError = SERIAL_PORT_ERROR_NONE;
return true;
exit_error:
if (self->comPort != INVALID_HANDLE_VALUE) {
CloseHandle(self->comPort);
self->comPort = INVALID_HANDLE_VALUE;
}
return false;
}
void
SerialPort_close(SerialPort self)
{
if (self->comPort != INVALID_HANDLE_VALUE) {
CloseHandle(self->comPort);
self->comPort = INVALID_HANDLE_VALUE;
}
}
int
SerialPort_getBaudRate(SerialPort self)
{
return self->baudRate;
}
void
SerialPort_discardInBuffer(SerialPort self)
{
PurgeComm(self->comPort, PURGE_RXCLEAR | PURGE_TXCLEAR);
}
void
SerialPort_setTimeout(SerialPort self, int timeout)
{
self->timeout = timeout;
}
SerialPortError
SerialPort_getLastError(SerialPort self)
{
return self->lastError;
}
int
SerialPort_readByte(SerialPort self)
{
uint8_t buf[1];
DWORD bytesRead = 0;
BOOL status = ReadFile(self->comPort, buf, 1, &bytesRead, NULL);
if (status == false) {
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
return -1;
}
self->lastError = SERIAL_PORT_ERROR_NONE;
if (bytesRead == 0)
return -1;
else
return (int) buf[0];
}
int
SerialPort_write(SerialPort self, uint8_t* buffer, int startPos, int bufSize)
{
//TODO assure minimum line idle time
self->lastError = SERIAL_PORT_ERROR_NONE;
DWORD numberOfBytesWritten;
BOOL status = WriteFile(self->comPort, buffer + startPos, bufSize, &numberOfBytesWritten, NULL);
if (status == false) {
self->lastError = SERIAL_PORT_ERROR_UNKNOWN;
return -1;
}
status = FlushFileBuffers(self->comPort);
if (status == false) {
printf("FlushFileBuffers failed!\n");
}
self->lastSentTime = Hal_getMonotonicTimeInMs();
return (int) numberOfBytesWritten;
}

View File

@ -0,0 +1,967 @@
/*
* socket_bsd.c
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#include "hal_socket.h"
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/tcp.h> // required for TCP keepalive
#include <poll.h>
#include <signal.h>
#include "hal_thread.h"
#include "lib_memory.h"
#include "linked_list.h"
#ifndef DEBUG_SOCKET
#define DEBUG_SOCKET 0
#endif
#ifndef IPV6_ADD_MEMBERSHIP
#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
#endif
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
struct sSocket
{
int fd;
uint32_t connectTimeout;
};
struct sServerSocket
{
int fd;
int backLog;
};
struct sUdpSocket
{
int fd;
int namespace; /* IPv4: AF_INET; IPv6: AF_INET6 */
};
struct sHandleSet
{
LinkedList sockets;
bool pollfdIsUpdated;
struct pollfd* fds;
int nfds;
};
HandleSet
Handleset_new(void)
{
HandleSet self = (HandleSet)GLOBAL_MALLOC(sizeof(struct sHandleSet));
if (self)
{
self->sockets = LinkedList_create();
self->pollfdIsUpdated = false;
self->fds = NULL;
self->nfds = 0;
}
return self;
}
void
Handleset_reset(HandleSet self)
{
if (self)
{
if (self->sockets)
{
LinkedList_destroyStatic(self->sockets);
self->sockets = LinkedList_create();
self->pollfdIsUpdated = false;
}
}
}
void
Handleset_addSocket(HandleSet self, const Socket sock)
{
if (self != NULL && sock != NULL && sock->fd != -1)
{
LinkedList_add(self->sockets, sock);
self->pollfdIsUpdated = false;
}
}
void
Handleset_removeSocket(HandleSet self, const Socket sock)
{
if (self && self->sockets && sock)
{
LinkedList_remove(self->sockets, sock);
self->pollfdIsUpdated = false;
}
}
int
Handleset_waitReady(HandleSet self, unsigned int timeoutMs)
{
/* check if pollfd array is updated */
if (self->pollfdIsUpdated == false)
{
if (self->fds)
{
GLOBAL_FREEMEM(self->fds);
self->fds = NULL;
}
self->nfds = LinkedList_size(self->sockets);
self->fds = GLOBAL_CALLOC(self->nfds, sizeof(struct pollfd));
int i;
for (i = 0; i < self->nfds; i++)
{
LinkedList sockElem = LinkedList_get(self->sockets, i);
if (sockElem)
{
Socket sock = (Socket)LinkedList_getData(sockElem);
if (sock)
{
self->fds[i].fd = sock->fd;
self->fds[i].events = POLL_IN;
}
}
}
self->pollfdIsUpdated = true;
}
if (self->fds && self->nfds > 0)
{
int result = poll(self->fds, self->nfds, timeoutMs);
if (result == -1)
{
if (DEBUG_SOCKET)
printf("SOCKET: poll error (errno: %i)\n", errno);
}
return result;
}
else
{
/* there is no socket to wait for */
return 0;
}
}
void
Handleset_destroy(HandleSet self)
{
if (self)
{
if (self->sockets)
LinkedList_destroyStatic(self->sockets);
if (self->fds)
GLOBAL_FREEMEM(self->fds);
GLOBAL_FREEMEM(self);
}
}
void
Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count)
{
#if defined SO_KEEPALIVE
int optval;
socklen_t optlen = sizeof(optval);
optval = idleTime;
setsockopt(self->fd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);
optval = 1;
setsockopt(self->fd, SOL_SOCKET, SO_NOSIGPIPE, &optval, optlen);
#if defined TCP_KEEPCNT
optval = interval;
setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen);
optval = count;
setsockopt(self->fd, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen);
#endif /* TCP_KEEPCNT */
#endif /* SO_KEEPALIVE */
}
static bool
prepareAddress(const char* address, int port, struct sockaddr_in* sockaddr)
{
memset((char*)sockaddr, 0, sizeof(struct sockaddr_in));
if (address != NULL)
{
struct hostent* server;
server = gethostbyname(address);
if (server == NULL)
return false;
memcpy((char*)&sockaddr->sin_addr.s_addr, (char*)server->h_addr, server->h_length);
}
else
sockaddr->sin_addr.s_addr = htonl(INADDR_ANY);
sockaddr->sin_family = AF_INET;
sockaddr->sin_port = htons(port);
return true;
}
static void
setSocketNonBlocking(Socket self)
{
int flags = fcntl(self->fd, F_GETFL, 0);
fcntl(self->fd, F_SETFL, flags | O_NONBLOCK);
}
static void
activateTcpNoDelay(Socket self)
{
/* activate TCP_NODELAY option - packets will be sent immediately */
int flag = 1;
setsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(int));
}
ServerSocket
TcpServerSocket_create(const char* address, int port)
{
ServerSocket serverSocket = NULL;
int fd;
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) >= 0)
{
struct sockaddr_in serverAddress;
if (!prepareAddress(address, port, &serverAddress))
{
close(fd);
return NULL;
}
int optionReuseAddr = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&optionReuseAddr, sizeof(int));
if (bind(fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) >= 0)
{
serverSocket = (ServerSocket)GLOBAL_MALLOC(sizeof(struct sServerSocket));
serverSocket->fd = fd;
serverSocket->backLog = 2;
setSocketNonBlocking((Socket)serverSocket);
}
else
{
close(fd);
return NULL;
}
}
return serverSocket;
}
void
ServerSocket_listen(ServerSocket self)
{
listen(self->fd, self->backLog);
}
/* CHANGED TO MAKE NON-BLOCKING --> RETURNS NULL IF NO CONNECTION IS PENDING */
Socket
ServerSocket_accept(ServerSocket self)
{
int fd;
Socket conSocket = NULL;
fd = accept(self->fd, NULL, NULL);
if (fd >= 0)
{
conSocket = (Socket)GLOBAL_CALLOC(1, sizeof(struct sSocket));
if (conSocket)
{
conSocket->fd = fd;
setSocketNonBlocking(conSocket);
activateTcpNoDelay(conSocket);
}
else
{
/* out of memory */
close(fd);
if (DEBUG_SOCKET)
printf("SOCKET: out of memory\n");
}
}
return conSocket;
}
void
ServerSocket_setBacklog(ServerSocket self, int backlog)
{
self->backLog = backlog;
}
static void
closeAndShutdownSocket(int socketFd)
{
if (socketFd != -1)
{
if (DEBUG_SOCKET)
printf("socket_linux.c: call shutdown for %i!\n", socketFd);
/* shutdown is required to unblock read or accept in another thread! */
shutdown(socketFd, SHUT_RDWR);
close(socketFd);
}
}
void
ServerSocket_destroy(ServerSocket self)
{
int fd = self->fd;
self->fd = -1;
closeAndShutdownSocket(fd);
Thread_sleep(10);
GLOBAL_FREEMEM(self);
}
Socket
TcpSocket_create()
{
Socket self = (Socket)NULL;
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock != -1)
{
self = (Socket)GLOBAL_MALLOC(sizeof(struct sSocket));
if (self)
{
self->fd = sock;
self->connectTimeout = 5000;
#if 0
int tcpUserTimeout = 10000;
int result = setsockopt(sock, SOL_TCP, TCP_USER_TIMEOUT, &tcpUserTimeout, sizeof(tcpUserTimeout));
#endif
}
else
{
/* out of memory */
close(sock);
if (DEBUG_SOCKET)
printf("SOCKET: out of memory\n");
}
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to create socket (errno=%i)\n", errno);
}
return self;
}
void
Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs)
{
self->connectTimeout = timeoutInMs;
}
bool
Socket_bind(Socket self, const char* srcAddress, int srcPort)
{
struct sockaddr_in localAddress;
if (!prepareAddress(srcAddress, srcPort, &localAddress))
return false;
int result = bind(self->fd, (struct sockaddr*)&localAddress, sizeof(localAddress));
if (result == -1)
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to bind TCP socket (errno=%i)\n", errno);
close(self->fd);
self->fd = -1;
return false;
}
return true;
}
bool
Socket_connectAsync(Socket self, const char* address, int port)
{
struct sockaddr_in serverAddress;
if (DEBUG_SOCKET)
printf("SOCKET: connect: %s:%i\n", address, port);
if (!prepareAddress(address, port, &serverAddress))
return false;
fd_set fdSet;
FD_ZERO(&fdSet);
FD_SET(self->fd, &fdSet);
activateTcpNoDelay(self);
fcntl(self->fd, F_SETFL, O_NONBLOCK);
if (connect(self->fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0)
{
if (errno != EINPROGRESS)
{
self->fd = -1;
return false;
}
}
return true; /* is connecting or already connected */
}
SocketState
Socket_checkAsyncConnectState(Socket self)
{
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
fd_set fdSet;
FD_ZERO(&fdSet);
FD_SET(self->fd, &fdSet);
int selectVal = select(self->fd + 1, NULL, &fdSet, NULL, &timeout);
if (selectVal == 1)
{
/* Check if connection is established */
int so_error;
socklen_t len = sizeof so_error;
if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, &so_error, &len) >= 0)
{
if (so_error == 0)
return SOCKET_STATE_CONNECTED;
}
return SOCKET_STATE_FAILED;
}
else if (selectVal == 0)
{
return SOCKET_STATE_CONNECTING;
}
else
{
return SOCKET_STATE_FAILED;
}
}
bool
Socket_connect(Socket self, const char* address, int port)
{
if (Socket_connectAsync(self, address, port) == false)
return false;
struct timeval timeout;
timeout.tv_sec = self->connectTimeout / 1000;
timeout.tv_usec = (self->connectTimeout % 1000) * 1000;
fd_set fdSet;
FD_ZERO(&fdSet);
FD_SET(self->fd, &fdSet);
if (select(self->fd + 1, NULL, &fdSet, NULL, &timeout) == 1)
{
/* Check if connection is established */
int so_error;
socklen_t len = sizeof so_error;
if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, &so_error, &len) >= 0)
{
if (so_error == 0)
return true;
}
}
close(self->fd);
self->fd = -1;
return false;
}
static char*
convertAddressToStr(struct sockaddr_storage* addr)
{
char addrString[INET6_ADDRSTRLEN + 7];
int port;
bool isIPv6;
if (addr->ss_family == AF_INET)
{
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)addr;
port = ntohs(ipv4Addr->sin_port);
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
isIPv6 = false;
}
else if (addr->ss_family == AF_INET6)
{
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)addr;
port = ntohs(ipv6Addr->sin6_port);
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
isIPv6 = true;
}
else
return NULL;
char* clientConnection = (char*)GLOBAL_MALLOC(strlen(addrString) + 9);
if (isIPv6)
sprintf(clientConnection, "[%s]:%i", addrString, port);
else
sprintf(clientConnection, "%s:%i", addrString, port);
return clientConnection;
}
char*
Socket_getPeerAddress(Socket self)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
if (getpeername(self->fd, (struct sockaddr*)&addr, &addrLen) == 0)
{
return convertAddressToStr(&addr);
}
else
return NULL;
}
char*
Socket_getLocalAddress(Socket self)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
if (getsockname(self->fd, (struct sockaddr*)&addr, &addrLen) == 0)
{
return convertAddressToStr(&addr);
}
else
return NULL;
}
char*
Socket_getPeerAddressStatic(Socket self, char* peerAddressString)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
getpeername(self->fd, (struct sockaddr*)&addr, &addrLen);
char addrString[INET6_ADDRSTRLEN + 7];
int port;
bool isIPv6;
if (addr.ss_family == AF_INET)
{
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&addr;
port = ntohs(ipv4Addr->sin_port);
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
isIPv6 = false;
}
else if (addr.ss_family == AF_INET6)
{
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&addr;
port = ntohs(ipv6Addr->sin6_port);
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
isIPv6 = true;
}
else
return NULL;
if (isIPv6)
sprintf(peerAddressString, "[%s]:%i", addrString, port);
else
sprintf(peerAddressString, "%s:%i", addrString, port);
return peerAddressString;
}
int
Socket_read(Socket self, uint8_t* buf, int size)
{
if (self->fd == -1)
return -1;
int read_bytes = recv(self->fd, buf, size, MSG_DONTWAIT);
if (read_bytes == 0)
return -1;
if (read_bytes == -1)
{
int error = errno;
switch (error)
{
case EAGAIN:
return 0;
case EBADF:
return -1;
default:
return -1;
}
}
return read_bytes;
}
int
Socket_write(Socket self, uint8_t* buf, int size)
{
if (self->fd == -1)
return -1;
/* MSG_NOSIGNAL - prevent send to signal SIGPIPE when peer unexpectedly closed the socket */
int retVal = send(self->fd, buf, size, MSG_NOSIGNAL | MSG_DONTWAIT);
if ((retVal == -1) && (errno == EAGAIN))
return 0;
else
return retVal;
}
void
Socket_destroy(Socket self)
{
int fd = self->fd;
self->fd = -1;
closeAndShutdownSocket(fd);
Thread_sleep(10);
GLOBAL_FREEMEM(self);
}
static UdpSocket
UdpSocket_createUsingNamespace(int namespace)
{
UdpSocket self = NULL;
int sock = socket(namespace, SOCK_DGRAM, IPPROTO_UDP);
if (sock != -1)
{
self = (UdpSocket)GLOBAL_MALLOC(sizeof(struct sSocket));
if (self)
{
self->fd = sock;
self->namespace = namespace;
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to allocate memory\n");
close(sock);
}
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to create UDP socket (errno=%i)\n", errno);
}
return self;
}
UdpSocket
UdpSocket_create()
{
return UdpSocket_createUsingNamespace(AF_INET);
}
UdpSocket
UdpSocket_createIpV6()
{
return UdpSocket_createUsingNamespace(AF_INET6);
}
bool
UdpSocket_addGroupMembership(UdpSocket self, const char* multicastAddress)
{
if (self->namespace == AF_INET)
{
struct ip_mreq mreq;
if (!inet_aton(multicastAddress, &(mreq.imr_multiaddr)))
{
printf("SOCKET: Invalid IPv4 multicast address\n");
return false;
}
else
{
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(self->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
{
printf("SOCKET: failed to set IPv4 multicast group (errno: %i)\n", errno);
return false;
}
}
return true;
}
else if (self->namespace == AF_INET6)
{
struct ipv6_mreq mreq;
if (inet_pton(AF_INET6, multicastAddress, &(mreq.ipv6mr_multiaddr)) < 1)
{
printf("SOCKET: failed to set IPv6 multicast group (errno: %i)\n", errno);
return false;
}
mreq.ipv6mr_interface = 0;
if (setsockopt(self->fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
{
printf("SOCKET: failed to set IPv6 multicast group (errno: %i)\n", errno);
return false;
}
return true;
}
return false;
}
bool
UdpSocket_setMulticastTtl(UdpSocket self, int ttl)
{
if (self->namespace == AF_INET)
{
if (setsockopt(self->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1)
{
printf("SOCKET: failed to set IPv4 multicast TTL (errno: %i)\n", errno);
return false;
}
return true;
}
else if (self->namespace == AF_INET6)
{
if (setsockopt(self->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) == -1)
{
printf("SOCKET: failed to set IPv6 multicast TTL(hops) (errno: %i)\n", errno);
return false;
}
return true;
}
return false;
}
bool
UdpSocket_bind(UdpSocket self, const char* address, int port)
{
// TODO add support for IPv6
struct sockaddr_in localAddress;
if (!prepareAddress(address, port, &localAddress))
{
close(self->fd);
self->fd = 0;
return false;
}
int result = bind(self->fd, (struct sockaddr*)&localAddress, sizeof(localAddress));
if (result == -1)
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to bind UDP socket (errno=%i)\n", errno);
close(self->fd);
self->fd = 0;
return false;
}
return true;
}
bool
UdpSocket_sendTo(UdpSocket self, const char* address, int port, uint8_t* msg, int msgSize)
{
// TODO add support for IPv6
struct sockaddr_in remoteAddress;
if (!prepareAddress(address, port, &remoteAddress))
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to lookup remote address %s\n", address);
return false;
}
int result = sendto(self->fd, msg, msgSize, 0, (struct sockaddr*)&remoteAddress, sizeof(remoteAddress));
if (result == msgSize)
{
return true;
}
else if (result == -1)
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to send UDP message (errno=%i)\n", errno);
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to send UDP message (insufficient data sent)\n");
}
return false;
}
int
UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize)
{
// TODO add support for IPv6
struct sockaddr_storage remoteAddress;
socklen_t structSize = sizeof(struct sockaddr_storage);
int result = recvfrom(self->fd, msg, msgSize, MSG_DONTWAIT, (struct sockaddr*)&remoteAddress, &structSize);
if (result == -1)
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to receive UDP message (errno=%i)\n", errno);
}
if (address)
{
bool isIPv6;
char addrString[INET6_ADDRSTRLEN + 7];
int port;
if (remoteAddress.ss_family == AF_INET)
{
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&remoteAddress;
port = ntohs(ipv4Addr->sin_port);
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
isIPv6 = false;
}
else if (remoteAddress.ss_family == AF_INET6)
{
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&remoteAddress;
port = ntohs(ipv6Addr->sin6_port);
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
isIPv6 = true;
}
else
return -1;
if (isIPv6)
snprintf(address, maxAddrSize, "[%s]:%i", addrString, port);
else
snprintf(address, maxAddrSize, "%s:%i", addrString, port);
}
if (address)
{
bool isIPv6;
char addrString[INET6_ADDRSTRLEN + 7];
int port;
if (remoteAddress.ss_family == AF_INET)
{
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&remoteAddress;
port = ntohs(ipv4Addr->sin_port);
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
isIPv6 = false;
}
else if (remoteAddress.ss_family == AF_INET6)
{
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&remoteAddress;
port = ntohs(ipv6Addr->sin6_port);
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
isIPv6 = true;
}
else
return result;
if (isIPv6)
snprintf(address, maxAddrSize, "[%s]:%i", addrString, port);
else
snprintf(address, maxAddrSize, "%s:%i", addrString, port);
}
return result;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,954 @@
/*
* socket_win32.c
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdbool.h>
#include <stdio.h>
#pragma comment(lib, "Ws2_32.lib")
#include "hal_socket.h"
#include "lib_memory.h"
#ifndef DEBUG_SOCKET
#define DEBUG_SOCKET 0
#endif
#ifndef __MINGW64_VERSION_MAJOR
struct tcp_keepalive
{
u_long onoff;
u_long keepalivetime;
u_long keepaliveinterval;
};
#endif
#define SIO_KEEPALIVE_VALS _WSAIOW(IOC_VENDOR, 4)
struct sSocket
{
SOCKET fd;
uint32_t connectTimeout;
};
struct sServerSocket
{
SOCKET fd;
int backLog;
};
struct sHandleSet
{
fd_set handles;
SOCKET maxHandle;
};
struct sUdpSocket
{
SOCKET fd;
int ns; /* IPv4: AF_INET; IPv6: AF_INET6 */
};
HandleSet
Handleset_new(void)
{
HandleSet result = (HandleSet)GLOBAL_MALLOC(sizeof(struct sHandleSet));
if (result != NULL)
{
FD_ZERO(&result->handles);
result->maxHandle = INVALID_SOCKET;
}
return result;
}
void
Handleset_reset(HandleSet self)
{
FD_ZERO(&self->handles);
self->maxHandle = INVALID_SOCKET;
}
void
Handleset_addSocket(HandleSet self, const Socket sock)
{
if (self != NULL && sock != NULL && sock->fd != INVALID_SOCKET)
{
FD_SET(sock->fd, &self->handles);
if ((sock->fd > self->maxHandle) || (self->maxHandle == INVALID_SOCKET))
self->maxHandle = sock->fd;
}
}
void
Handleset_removeSocket(HandleSet self, const Socket sock)
{
if (self != NULL && sock != NULL && sock->fd != INVALID_SOCKET)
{
FD_CLR(sock->fd, &self->handles);
}
}
int
Handleset_waitReady(HandleSet self, unsigned int timeoutMs)
{
int result;
if ((self != NULL) && (self->maxHandle != INVALID_SOCKET))
{
struct timeval timeout;
timeout.tv_sec = timeoutMs / 1000;
timeout.tv_usec = (timeoutMs % 1000) * 1000;
fd_set handles;
memcpy((void*)&handles, &(self->handles), sizeof(fd_set));
result = select(0, &handles, NULL, NULL, &timeout);
}
else
{
result = -1;
}
return result;
}
void
Handleset_destroy(HandleSet self)
{
GLOBAL_FREEMEM(self);
}
static bool wsaStartupCalled = false;
static int socketCount = 0;
void
Socket_activateTcpKeepAlive(Socket self, int idleTime, int interval, int count)
{
(void)count; /* not supported in windows socket API */
struct tcp_keepalive keepalive;
DWORD retVal = 0;
keepalive.onoff = 1;
keepalive.keepalivetime = idleTime * 1000;
keepalive.keepaliveinterval = interval * 1000;
if (WSAIoctl(self->fd, SIO_KEEPALIVE_VALS, &keepalive, sizeof(keepalive), NULL, 0, &retVal, NULL, NULL) ==
SOCKET_ERROR)
{
if (DEBUG_SOCKET)
printf("WIN32_SOCKET: WSAIotcl(SIO_KEEPALIVE_VALS) failed: %d\n", WSAGetLastError());
}
}
static void
setSocketNonBlocking(Socket self)
{
unsigned long mode = 1;
if (ioctlsocket(self->fd, FIONBIO, &mode) != 0)
{
if (DEBUG_SOCKET)
printf("WIN32_SOCKET: failed to set socket non-blocking!\n");
}
/* activate TCP_NODELAY */
int tcpNoDelay = 1;
setsockopt(self->fd, IPPROTO_TCP, TCP_NODELAY, (const char*)&tcpNoDelay, sizeof(int));
}
static bool
prepareAddress(const char* address, int port, struct sockaddr_in* sockaddr)
{
memset((char*)sockaddr, 0, sizeof(struct sockaddr_in));
if (address != NULL)
{
struct hostent* server;
server = gethostbyname(address);
if (server == NULL)
return false;
memcpy((char*)&sockaddr->sin_addr.s_addr, (char*)server->h_addr, server->h_length);
}
else
sockaddr->sin_addr.s_addr = htonl(INADDR_ANY);
sockaddr->sin_family = AF_INET;
sockaddr->sin_port = htons(port);
return true;
}
static bool
wsaStartUp(void)
{
if (wsaStartupCalled == false)
{
int ec;
WSADATA wsa;
if ((ec = WSAStartup(MAKEWORD(2, 0), &wsa)) != 0)
{
if (DEBUG_SOCKET)
printf("WIN32_SOCKET: winsock error: code %i\n", ec);
return false;
}
else
{
wsaStartupCalled = true;
return true;
}
}
else
return true;
}
static void
wsaShutdown(void)
{
if (wsaStartupCalled)
{
if (socketCount == 0)
{
WSACleanup();
wsaStartupCalled = false;
}
}
}
ServerSocket
TcpServerSocket_create(const char* address, int port)
{
ServerSocket serverSocket = NULL;
int ec;
SOCKET listen_socket = INVALID_SOCKET;
if (wsaStartUp() == false)
return NULL;
struct sockaddr_in server_addr;
if (!prepareAddress(address, port, &server_addr))
return NULL;
listen_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listen_socket == INVALID_SOCKET)
{
if (DEBUG_SOCKET)
printf("WIN32_SOCKET: socket failed with error: %i\n", WSAGetLastError());
wsaShutdown();
return NULL;
}
int optionReuseAddr = 1;
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&optionReuseAddr, sizeof(int));
ec = bind(listen_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ec == SOCKET_ERROR)
{
if (DEBUG_SOCKET)
printf("WIN32_SOCKET: bind failed with error:%i\n", WSAGetLastError());
closesocket(listen_socket);
wsaShutdown();
return NULL;
}
serverSocket = (ServerSocket)GLOBAL_MALLOC(sizeof(struct sServerSocket));
if (serverSocket)
{
serverSocket->fd = listen_socket;
serverSocket->backLog = 10;
setSocketNonBlocking((Socket)serverSocket);
socketCount++;
}
else
{
closesocket(listen_socket);
wsaShutdown();
}
return serverSocket;
}
void
ServerSocket_listen(ServerSocket self)
{
listen(self->fd, self->backLog);
}
Socket
ServerSocket_accept(ServerSocket self)
{
Socket conSocket = NULL;
SOCKET fd = accept(self->fd, NULL, NULL);
if (fd != INVALID_SOCKET)
{
conSocket = (Socket)GLOBAL_CALLOC(1, sizeof(struct sSocket));
conSocket->fd = fd;
socketCount++;
setSocketNonBlocking(conSocket);
if (DEBUG_SOCKET)
printf("WIN32_SOCKET: connection accepted\n");
}
else
{
if (DEBUG_SOCKET)
printf("WIN32_SOCKET: accept failed\n");
}
return conSocket;
}
void
ServerSocket_setBacklog(ServerSocket self, int backlog)
{
self->backLog = backlog;
}
void
ServerSocket_destroy(ServerSocket self)
{
if (self->fd != INVALID_SOCKET)
{
shutdown(self->fd, 2);
closesocket(self->fd);
socketCount--;
self->fd = INVALID_SOCKET;
}
wsaShutdown();
GLOBAL_FREEMEM(self);
}
Socket
TcpSocket_create()
{
Socket self = NULL;
if (wsaStartUp() == false)
return NULL;
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock != INVALID_SOCKET)
{
self = (Socket)GLOBAL_MALLOC(sizeof(struct sSocket));
if (self)
{
self->fd = sock;
self->connectTimeout = 5000;
socketCount++;
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to create socket - cannot allocate memory\n");
closesocket(sock);
wsaShutdown();
}
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to create socket (error code=%i)\n", WSAGetLastError());
}
return self;
}
void
Socket_setConnectTimeout(Socket self, uint32_t timeoutInMs)
{
self->connectTimeout = timeoutInMs;
}
bool
Socket_bind(Socket self, const char* srcAddress, int srcPort)
{
struct sockaddr_in localAddress;
if (!prepareAddress(srcAddress, srcPort, &localAddress))
return false;
int result = bind(self->fd, (struct sockaddr*)&localAddress, sizeof(localAddress));
if (result == SOCKET_ERROR)
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to bind TCP socket (errno=%i)\n", WSAGetLastError());
closesocket(self->fd);
self->fd = -1;
return false;
}
return true;
}
bool
Socket_connectAsync(Socket self, const char* address, int port)
{
if (DEBUG_SOCKET)
printf("WIN32_SOCKET: Socket_connect: %s:%i\n", address, port);
struct sockaddr_in serverAddress;
if (wsaStartUp() == false)
return false;
if (!prepareAddress(address, port, &serverAddress))
return false;
setSocketNonBlocking(self);
if (connect(self->fd, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR)
{
if (WSAGetLastError() != WSAEWOULDBLOCK)
{
closesocket(self->fd);
self->fd = INVALID_SOCKET;
return false;
}
}
return true; /* is connecting or already connected */
}
SocketState
Socket_checkAsyncConnectState(Socket self)
{
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 0;
fd_set fdSet;
FD_ZERO(&fdSet);
FD_SET(self->fd, &fdSet);
int selectVal = select(0, NULL, &fdSet, NULL, &timeout);
if (selectVal == 1)
{
/* Check if connection is established */
int so_error;
int len = sizeof so_error;
if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, (char*)(&so_error), &len) != SOCKET_ERROR)
{
if (so_error == 0)
{
int recvRes = recv(self->fd, NULL, 0, 0);
if (recvRes == SOCKET_ERROR)
{
int wsaError = WSAGetLastError();
if (wsaError == WSAECONNRESET)
return SOCKET_STATE_FAILED;
if (wsaError == WSAECONNABORTED)
return SOCKET_STATE_FAILED;
}
return SOCKET_STATE_CONNECTED;
}
}
return SOCKET_STATE_FAILED;
}
else if (selectVal == 0)
{
return SOCKET_STATE_CONNECTING;
}
else
{
return SOCKET_STATE_FAILED;
}
}
bool
Socket_connect(Socket self, const char* address, int port)
{
if (Socket_connectAsync(self, address, port) == false)
return false;
struct timeval timeout;
timeout.tv_sec = self->connectTimeout / 1000;
timeout.tv_usec = (self->connectTimeout % 1000) * 1000;
fd_set fdSet;
FD_ZERO(&fdSet);
FD_SET(self->fd, &fdSet);
if (select(0, NULL, &fdSet, NULL, &timeout) == 1)
{
/* Check if connection is established */
int so_error;
socklen_t len = sizeof so_error;
if (getsockopt(self->fd, SOL_SOCKET, SO_ERROR, (char*)&so_error, &len) >= 0)
{
if (so_error == 0)
return true;
}
}
closesocket(self->fd);
self->fd = INVALID_SOCKET;
return false;
}
static char*
convertAddressToStr(struct sockaddr_storage* addr)
{
char addrString[INET6_ADDRSTRLEN + 7];
int addrStringLen = INET6_ADDRSTRLEN + 7;
int port;
bool isIPv6;
if (addr->ss_family == AF_INET)
{
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)addr;
port = ntohs(ipv4Addr->sin_port);
ipv4Addr->sin_port = 0;
WSAAddressToString((LPSOCKADDR)ipv4Addr, sizeof(struct sockaddr_storage), NULL, (LPSTR)addrString,
(LPDWORD)&addrStringLen);
isIPv6 = false;
}
else if (addr->ss_family == AF_INET6)
{
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)addr;
port = ntohs(ipv6Addr->sin6_port);
ipv6Addr->sin6_port = 0;
WSAAddressToString((LPSOCKADDR)ipv6Addr, sizeof(struct sockaddr_storage), NULL, (LPSTR)addrString,
(LPDWORD)&addrStringLen);
isIPv6 = true;
}
else
return NULL;
char* clientConnection = (char*)GLOBAL_MALLOC(strlen(addrString) + 9);
if (isIPv6)
sprintf(clientConnection, "[%s]:%i", addrString, port);
else
sprintf(clientConnection, "%s:%i", addrString, port);
return clientConnection;
}
char*
Socket_getPeerAddress(Socket self)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
if (getpeername(self->fd, (struct sockaddr*)&addr, &addrLen) == 0)
{
return convertAddressToStr(&addr);
}
else
return NULL;
}
char*
Socket_getLocalAddress(Socket self)
{
struct sockaddr_storage addr;
socklen_t addrLen = sizeof(addr);
if (getsockname(self->fd, (struct sockaddr*)&addr, &addrLen) == 0)
{
return convertAddressToStr(&addr);
}
else
return NULL;
}
char*
Socket_getPeerAddressStatic(Socket self, char* peerAddressString)
{
struct sockaddr_storage addr;
int addrLen = sizeof(addr);
getpeername(self->fd, (struct sockaddr*)&addr, &addrLen);
char addrString[INET6_ADDRSTRLEN + 7];
int addrStringLen = INET6_ADDRSTRLEN + 7;
int port;
bool isIPv6;
if (addr.ss_family == AF_INET)
{
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&addr;
port = ntohs(ipv4Addr->sin_port);
ipv4Addr->sin_port = 0;
WSAAddressToString((LPSOCKADDR)ipv4Addr, sizeof(struct sockaddr_storage), NULL, (LPSTR)addrString,
(LPDWORD)&addrStringLen);
isIPv6 = false;
}
else if (addr.ss_family == AF_INET6)
{
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&addr;
port = ntohs(ipv6Addr->sin6_port);
ipv6Addr->sin6_port = 0;
WSAAddressToString((LPSOCKADDR)ipv6Addr, sizeof(struct sockaddr_storage), NULL, (LPSTR)addrString,
(LPDWORD)&addrStringLen);
isIPv6 = true;
}
else
return NULL;
if (isIPv6)
sprintf(peerAddressString, "[%s]:%i", addrString, port);
else
sprintf(peerAddressString, "%s:%i", addrString, port);
return peerAddressString;
}
int
Socket_read(Socket self, uint8_t* buf, int size)
{
int bytes_read = recv(self->fd, (char*)buf, size, 0);
if (bytes_read == 0) /* peer has closed socket */
return -1;
if (bytes_read == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAEWOULDBLOCK)
return 0;
else
return -1;
}
return bytes_read;
}
int
Socket_write(Socket self, uint8_t* buf, int size)
{
int bytes_sent = send(self->fd, (char*)buf, size, 0);
if (bytes_sent == SOCKET_ERROR)
{
int errorCode = WSAGetLastError();
if (errorCode == WSAEWOULDBLOCK)
bytes_sent = 0;
else
bytes_sent = -1;
}
return bytes_sent;
}
void
Socket_destroy(Socket self)
{
if (self->fd != INVALID_SOCKET)
{
shutdown(self->fd, 2);
closesocket(self->fd);
self->fd = INVALID_SOCKET;
socketCount--;
}
wsaShutdown();
GLOBAL_FREEMEM(self);
}
static UdpSocket
UdpSocket_createUsingNamespace(int ns)
{
if (wsaStartUp() == false)
return NULL;
UdpSocket self = NULL;
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock != INVALID_SOCKET)
{
self = (UdpSocket)GLOBAL_MALLOC(sizeof(struct sSocket));
if (self)
{
self->fd = sock;
self->ns = ns;
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to allocate memory\n");
closesocket(sock);
}
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to create UDP socket (errno=%i)\n", WSAGetLastError());
}
return self;
}
UdpSocket
UdpSocket_create()
{
return UdpSocket_createUsingNamespace(AF_INET);
}
UdpSocket
UdpSocket_createIpV6()
{
return UdpSocket_createUsingNamespace(AF_INET6);
}
bool
UdpSocket_addGroupMembership(UdpSocket self, const char* multicastAddress)
{
if (self->ns == AF_INET)
{
struct ip_mreq mreq;
if (inet_pton(AF_INET, multicastAddress, &(mreq.imr_multiaddr)) < 1)
{
printf("SOCKET: Invalid IPv4 multicast address\n");
return false;
}
else
{
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(self->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&mreq, sizeof(mreq)) == -1)
{
printf("SOCKET: failed to set IPv4 multicast group (errno: %i)\n", WSAGetLastError());
return false;
}
}
return true;
}
else if (self->ns == AF_INET6)
{
struct ipv6_mreq mreq;
if (inet_pton(AF_INET6, multicastAddress, &(mreq.ipv6mr_multiaddr)) < 1)
{
printf("SOCKET: failed to set IPv6 multicast group (errno: %i)\n", WSAGetLastError());
return false;
}
mreq.ipv6mr_interface = 0;
if (setsockopt(self->fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (const char*)&mreq, sizeof(mreq)) == -1)
{
printf("SOCKET: failed to set IPv6 multicast group (errno: %i)\n", WSAGetLastError());
return false;
}
return true;
}
return false;
}
bool
UdpSocket_setMulticastTtl(UdpSocket self, int ttl)
{
if (self->ns == AF_INET)
{
if (setsockopt(self->fd, IPPROTO_IP, IP_MULTICAST_TTL, (const char*)&ttl, sizeof(ttl)) == -1)
{
printf("SOCKET: failed to set IPv4 multicast TTL (errno: %i)\n", WSAGetLastError());
return false;
}
return true;
}
else if (self->ns == AF_INET6)
{
if (setsockopt(self->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char*)&ttl, sizeof(ttl)) == -1)
{
printf("SOCKET: failed to set IPv6 multicast TTL(hops) (errno: %i)\n", WSAGetLastError());
return false;
}
return true;
}
return false;
}
bool
UdpSocket_bind(UdpSocket self, const char* address, int port)
{
// TODO add support for IPv6
struct sockaddr_in localAddress;
if (!prepareAddress(address, port, &localAddress))
{
closesocket(self->fd);
self->fd = 0;
return false;
}
int result = bind(self->fd, (struct sockaddr*)&localAddress, sizeof(localAddress));
if (result == -1)
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to bind UDP socket (errno=%i)\n", errno);
closesocket(self->fd);
self->fd = 0;
return false;
}
return true;
}
bool
UdpSocket_sendTo(UdpSocket self, const char* address, int port, uint8_t* msg, int msgSize)
{
// TODO add support for IPv6
struct sockaddr_in remoteAddress;
if (!prepareAddress(address, port, &remoteAddress))
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to lookup remote address %s\n", address);
return false;
}
int result =
sendto(self->fd, (const char*)msg, msgSize, 0, (struct sockaddr*)&remoteAddress, sizeof(remoteAddress));
if (result == msgSize)
{
return true;
}
else if (result == -1)
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to send UDP message (errno=%i)\n", errno);
}
else
{
if (DEBUG_SOCKET)
printf("SOCKET: failed to send UDP message (insufficient data sent)\n");
}
return false;
}
int
UdpSocket_receiveFrom(UdpSocket self, char* address, int maxAddrSize, uint8_t* msg, int msgSize)
{
// TODO add support for IPv6
struct sockaddr_storage remoteAddress;
memset(&remoteAddress, 0, sizeof(struct sockaddr_storage));
socklen_t structSize = sizeof(struct sockaddr_storage);
if (address)
address[0] = 0;
int result = recvfrom(self->fd, (char*)msg, msgSize, 0, (struct sockaddr*)&remoteAddress, &structSize);
if (result == 0) /* peer has closed socket */
return -1;
if (result == SOCKET_ERROR)
{
if (WSAGetLastError() == WSAEWOULDBLOCK)
return 0;
else
return -1;
}
if (address)
{
bool isIPv6;
char addrString[INET6_ADDRSTRLEN + 7];
int port;
if (remoteAddress.ss_family == AF_INET)
{
struct sockaddr_in* ipv4Addr = (struct sockaddr_in*)&remoteAddress;
port = ntohs(ipv4Addr->sin_port);
inet_ntop(AF_INET, &(ipv4Addr->sin_addr), addrString, INET_ADDRSTRLEN);
isIPv6 = false;
}
else if (remoteAddress.ss_family == AF_INET6)
{
struct sockaddr_in6* ipv6Addr = (struct sockaddr_in6*)&remoteAddress;
port = ntohs(ipv6Addr->sin6_port);
inet_ntop(AF_INET6, &(ipv6Addr->sin6_addr), addrString, INET6_ADDRSTRLEN);
isIPv6 = true;
}
else
return result;
if (isIPv6)
snprintf(address, maxAddrSize, "[%s]:%i", addrString, port);
else
snprintf(address, maxAddrSize, "%s:%i", addrString, port);
}
return result;
}

View File

@ -0,0 +1,109 @@
/**
* thread_bsd.c
*
* Copyright 2013-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include "hal_thread.h"
#include "lib_memory.h"
struct sThread {
ThreadExecutionFunction function;
void* parameter;
pthread_t pthread;
int state;
bool autodestroy;
};
Semaphore
Semaphore_create(int initialValue)
{
Semaphore self = GLOBAL_MALLOC(sizeof(sem_t));
sem_init((sem_t*) self, 0, initialValue);
return self;
}
/* Wait until semaphore value is more than zero. Then decrease the semaphore value. */
void
Semaphore_wait(Semaphore self)
{
sem_wait((sem_t*) self);
}
void
Semaphore_post(Semaphore self)
{
sem_post((sem_t*) self);
}
void
Semaphore_destroy(Semaphore self)
{
sem_destroy((sem_t*) self);
GLOBAL_FREEMEM(self);
}
Thread
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
{
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
if (thread != NULL) {
thread->parameter = parameter;
thread->function = function;
thread->state = 0;
thread->autodestroy = autodestroy;
}
return thread;
}
static void*
destroyAutomaticThread(void* parameter)
{
Thread thread = (Thread) parameter;
thread->function(thread->parameter);
GLOBAL_FREEMEM(thread);
pthread_exit(NULL);
}
void
Thread_start(Thread thread)
{
if (thread->autodestroy == true) {
pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread);
pthread_detach(thread->pthread);
}
else
pthread_create(&thread->pthread, NULL, thread->function, thread->parameter);
thread->state = 1;
}
void
Thread_destroy(Thread thread)
{
if (thread->state == 1) {
pthread_join(thread->pthread, NULL);
}
GLOBAL_FREEMEM(thread);
}
void
Thread_sleep(int millies)
{
usleep(millies * 1000);
}

View File

@ -0,0 +1,109 @@
/*
* thread_linux.c
*
* Copyright 2013-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#include "hal_thread.h"
#include "lib_memory.h"
struct sThread {
ThreadExecutionFunction function;
void* parameter;
pthread_t pthread;
int state;
bool autodestroy;
};
Semaphore
Semaphore_create(int initialValue)
{
Semaphore self = GLOBAL_MALLOC(sizeof(sem_t));
sem_init((sem_t*) self, 0, initialValue);
return self;
}
/* Wait until semaphore value is more than zero. Then decrease the semaphore value. */
void
Semaphore_wait(Semaphore self)
{
sem_wait((sem_t*) self);
}
void
Semaphore_post(Semaphore self)
{
sem_post((sem_t*) self);
}
void
Semaphore_destroy(Semaphore self)
{
sem_destroy((sem_t*) self);
GLOBAL_FREEMEM(self);
}
Thread
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
{
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
if (thread != NULL) {
thread->parameter = parameter;
thread->function = function;
thread->state = 0;
thread->autodestroy = autodestroy;
}
return thread;
}
static void*
destroyAutomaticThread(void* parameter)
{
Thread thread = (Thread) parameter;
thread->function(thread->parameter);
GLOBAL_FREEMEM(thread);
pthread_exit(NULL);
}
void
Thread_start(Thread thread)
{
if (thread->autodestroy == true) {
pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread);
pthread_detach(thread->pthread);
}
else
pthread_create(&thread->pthread, NULL, thread->function, thread->parameter);
thread->state = 1;
}
void
Thread_destroy(Thread thread)
{
if (thread->state == 1) {
pthread_join(thread->pthread, NULL);
}
GLOBAL_FREEMEM(thread);
}
void
Thread_sleep(int millies)
{
usleep(millies * 1000);
}

View File

@ -0,0 +1,149 @@
/**
* thread_macos.c
*
* Copyright 2013-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
/*
* NOTE: MacOS needs own thread layer because it doesn't support unnamed semaphores!
* NOTE: named semaphores were replaced by POSIX mutex
*/
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include "hal_thread.h"
#include "lib_memory.h"
struct sThread {
ThreadExecutionFunction function;
void* parameter;
pthread_t pthread;
int state;
bool autodestroy;
};
typedef struct sSemaphore* mSemaphore;
struct sSemaphore
{
pthread_mutex_t mutex;
};
/*
* NOTE: initialValue is ignored because semaphore was replaced by mutex
*/
Semaphore
Semaphore_create(int initialValue)
{
mSemaphore self = NULL;
self = (mSemaphore) GLOBAL_CALLOC(1, sizeof(struct sSemaphore));
if (self) {
pthread_mutex_init(&(self->mutex), NULL);
}
return (Semaphore)self;
}
/* lock mutex */
void
Semaphore_wait(Semaphore self)
{
mSemaphore mSelf = (mSemaphore) self;
int retVal = pthread_mutex_lock(&(mSelf->mutex));
if (retVal) {
printf("FATAL ERROR: pthread_mutex_lock failed (err=%i)\n", retVal);
exit(-1);
}
}
/* unlock mutex */
void
Semaphore_post(Semaphore self)
{
mSemaphore mSelf = (mSemaphore) self;
int retVal = pthread_mutex_unlock(&(mSelf->mutex));
if (retVal) {
printf("FATAL ERROR: pthread_mutex_unlock failed (err=%i)\n", retVal);
exit(-1);
}
}
void
Semaphore_destroy(Semaphore self)
{
if (self) {
mSemaphore mSelf = (mSemaphore) self;
pthread_mutex_destroy(&(mSelf->mutex));
GLOBAL_FREEMEM(mSelf);
}
}
Thread
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
{
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
if (thread != NULL) {
thread->parameter = parameter;
thread->function = function;
thread->state = 0;
thread->autodestroy = autodestroy;
}
return thread;
}
static void*
destroyAutomaticThread(void* parameter)
{
Thread thread = (Thread) parameter;
thread->function(thread->parameter);
GLOBAL_FREEMEM(thread);
pthread_exit(NULL);
}
void
Thread_start(Thread thread)
{
if (thread->autodestroy == true) {
pthread_create(&thread->pthread, NULL, destroyAutomaticThread, thread);
pthread_detach(thread->pthread);
}
else
pthread_create(&thread->pthread, NULL, thread->function, thread->parameter);
thread->state = 1;
}
void
Thread_destroy(Thread thread)
{
if (thread->state == 1) {
pthread_join(thread->pthread, NULL);
}
GLOBAL_FREEMEM(thread);
}
void
Thread_sleep(int millies)
{
usleep(millies * 1000);
}

View File

@ -0,0 +1,114 @@
/*
* thread_win32.c
*
* Copyright 2013-2021 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#include <windows.h>
#include "lib_memory.h"
#include "hal_thread.h"
struct sThread {
ThreadExecutionFunction function;
void* parameter;
HANDLE handle;
int state;
bool autodestroy;
};
static DWORD WINAPI
destroyAutomaticThreadRunner(LPVOID parameter)
{
Thread thread = (Thread) parameter;
thread->function(thread->parameter);
thread->state = 0;
Thread_destroy(thread);
return 0;
}
static DWORD WINAPI
threadRunner(LPVOID parameter)
{
Thread thread = (Thread) parameter;
thread->function(thread->parameter);
return (DWORD)0;
}
Thread
Thread_create(ThreadExecutionFunction function, void* parameter, bool autodestroy)
{
DWORD threadId;
Thread thread = (Thread) GLOBAL_MALLOC(sizeof(struct sThread));
thread->parameter = parameter;
thread->function = function;
thread->state = 0;
thread->autodestroy = autodestroy;
if (autodestroy == true)
thread->handle = CreateThread(0, 0, destroyAutomaticThreadRunner, thread, CREATE_SUSPENDED, &threadId);
else
thread->handle = CreateThread(0, 0, threadRunner, thread, CREATE_SUSPENDED, &threadId);
return thread;
}
void
Thread_start(Thread thread)
{
thread->state = 1;
ResumeThread(thread->handle);
}
void
Thread_destroy(Thread thread)
{
if (thread->state == 1)
WaitForSingleObject(thread->handle, INFINITE);
CloseHandle(thread->handle);
GLOBAL_FREEMEM(thread);
}
void
Thread_sleep(int millies)
{
Sleep(millies);
}
Semaphore
Semaphore_create(int initialValue)
{
HANDLE self = CreateSemaphore(NULL, 1, 1, NULL);
return self;
}
/* Wait until semaphore value is greater than zero. Then decrease the semaphore value. */
void
Semaphore_wait(Semaphore self)
{
WaitForSingleObject((HANDLE) self, INFINITE);
}
void
Semaphore_post(Semaphore self)
{
ReleaseSemaphore((HANDLE) self, 1, NULL);
}
void
Semaphore_destroy(Semaphore self)
{
CloseHandle((HANDLE) self);
}

View File

@ -0,0 +1,81 @@
/*
* time.c
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#include "hal_time.h"
#include <time.h>
#include <sys/time.h>
msSinceEpoch
Hal_getTimeInMs()
{
struct timeval now;
gettimeofday(&now, NULL);
return ((uint64_t) now.tv_sec * 1000LL) + (now.tv_usec / 1000);
}
nsSinceEpoch
Hal_getTimeInNs()
{
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
nsSinceEpoch nsTime = (nsSinceEpoch)(now.tv_sec) * 1000000000UL;
nsTime += (nsSinceEpoch)(now.tv_nsec);
return nsTime;
}
bool
Hal_setTimeInNs(nsSinceEpoch nsTime)
{
struct timespec tv;
tv.tv_sec = nsTime / 1000000000UL;
tv.tv_nsec = nsTime % 1000000000UL;
if (clock_settime(CLOCK_REALTIME, &tv) < 0) {
return false;
}
return true;
}
msSinceEpoch
Hal_getMonotonicTimeInMs()
{
uint64_t timeVal = 0;
struct timespec ts;
if (clock_gettime (CLOCK_MONOTONIC, &ts) == 0)
{
timeVal = ((uint64_t)ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000);
}
return timeVal;
}
nsSinceEpoch
Hal_getMonotonicTimeInNs()
{
uint64_t nsTime = 0;
struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == 0)
{
nsTime = ts.tv_sec * 1000000000UL;
nsTime += ts.tv_nsec;
}
return nsTime;
}

View File

@ -0,0 +1,72 @@
/*
* time.c
*
* Copyright 2013-2024 Michael Zillgith
*
* This file is part of Platform Abstraction Layer (libpal)
* for libiec61850, libmms, and lib60870.
*/
#include "hal_time.h"
#include <time.h>
#include <windows.h>
uint64_t
Hal_getTimeInMs()
{
FILETIME ft;
uint64_t now;
static const uint64_t DIFF_TO_UNIXTIME = 11644473600000ULL;
GetSystemTimeAsFileTime(&ft);
now = (LONGLONG)ft.dwLowDateTime + ((LONGLONG)(ft.dwHighDateTime) << 32LL);
return (now / 10000LL) - DIFF_TO_UNIXTIME;
}
nsSinceEpoch
Hal_getTimeInNs()
{
FILETIME ft;
static const uint64_t DIFF_TO_UNIXTIME = 11644473600000000000ULL;
GetSystemTimeAsFileTime(&ft);
nsSinceEpoch nsTime = (LONGLONG)ft.dwLowDateTime + ((LONGLONG)(ft.dwHighDateTime) << 32LL);
nsTime = nsTime * 100LL - DIFF_TO_UNIXTIME;
return nsTime;
}
bool
Hal_setTimeInNs(nsSinceEpoch nsTime)
{
uint64_t t = (nsTime / 100ULL) + 116444736000000000ULL;
FILETIME ft;
ft.dwLowDateTime = (uint32_t)(t & 0xffffffff);
ft.dwHighDateTime = (uint32_t)(t >> 32);
SYSTEMTIME st;
FileTimeToSystemTime(&ft, &st);
return SetSystemTime(&st);
}
msSinceEpoch
Hal_getMonotonicTimeInMs()
{
return (msSinceEpoch)GetTickCount64();
}
nsSinceEpoch
Hal_getMonotonicTimeInNs()
{
return (nsSinceEpoch)(GetTickCount64() * 1000000ULL);
}

View File

@ -0,0 +1,70 @@
#ifndef MBEDTLS_CONFIG_H
#define MBEDTLS_CONFIG_H
/* System support */
#define MBEDTLS_HAVE_ASM
#define MBEDTLS_HAVE_TIME
#define MBEDTLS_HAVE_TIME_DATE
#define MBEDTLS_NO_UDBL_DIVISION
#define MBEDTLS_PLATFORM_C
#define MBEDTLS_DEBUG_C
/* mbed TLS feature support */
#define MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_PKCS1_V15
#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
#define MBEDTLS_SSL_PROTO_TLS1_2
#define MBEDTLS_SSL_PROTO_TLS1_1
#define MBEDTLS_SSL_PROTO_TLS1
#define MBEDTLS_SSL_RENEGOTIATION
#define MBEDTLS_ENABLE_WEAK_CIPHERSUITES
#define MBEDTLS_CIPHER_NULL_CIPHER
#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_CERTIFICATES
/* mbed TLS modules */
#define MBEDTLS_GCM_C
#define MBEDTLS_AES_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_ASN1_WRITE_C
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_CIPHER_C
#define MBEDTLS_CTR_DRBG_C
/* #define MBEDTLS_DES_C */
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_ERROR_C
#define MBEDTLS_MD_C
#define MBEDTLS_MD5_C
#define MBEDTLS_NET_C
#define MBEDTLS_NIST_KW_C
#define MBEDTLS_OID_C
#define MBEDTLS_PK_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_RSA_C
#define MBEDTLS_SHA1_C
#define MBEDTLS_SHA256_C
#define MBEDTLS_SSL_CLI_C
#define MBEDTLS_SSL_SRV_C
#define MBEDTLS_SSL_TLS_C
#define MBEDTLS_X509_CRT_PARSE_C
#define MBEDTLS_X509_CRL_PARSE_C
#define MBEDTLS_X509_USE_C
#define MBEDTLS_SSL_CACHE_C
/* For test certificates */
#define MBEDTLS_BASE64_C
#define MBEDTLS_CERTS_C
#define MBEDTLS_PEM_PARSE_C
#define MBEDTLS_PKCS12_C
#define MBEDTLS_PKCS5_C
/* For testing with compat.sh */
#define MBEDTLS_FS_IO
#define MBEDTLS_X509_CHECK_KEY_USAGE
#define MBEDTLS_X509_CHECK_EXTENDED_KEY_USAGE
#include "mbedtls/check_config.h"
#endif /* MBEDTLS_CONFIG_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,73 @@
// https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#introduce-a-level-of-indirection-and-versioning-in-the-config-files
// #ifndef MBEDTLS_CONFIG_H
// #define MBEDTLS_CONFIG_H
/* System support */
#define MBEDTLS_HAVE_ASM
#define MBEDTLS_HAVE_TIME
#define MBEDTLS_HAVE_TIME_DATE
#define MBEDTLS_NO_UDBL_DIVISION
#define MBEDTLS_PLATFORM_C
#define MBEDTLS_DEBUG_C
/* mbed TLS feature support */
#define MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_PKCS1_V15
#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED
#define MBEDTLS_SSL_PROTO_TLS1_2
#define MBEDTLS_SSL_PROTO_TLS1_3
// MIGRATE 2.28->3.x.x: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#remove-support-for-tls-10-11-and-dtls-10
// #define MBEDTLS_SSL_PROTO_TLS1_1
// #define MBEDTLS_SSL_PROTO_TLS1
#define MBEDTLS_SSL_RENEGOTIATION
#error "MBEDTLS"
#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_CERTIFICATES
/* mbed TLS modules */
#define MBEDTLS_GCM_C
#define MBEDTLS_AES_C
#define MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_ASN1_WRITE_C
#define MBEDTLS_BIGNUM_C
#define MBEDTLS_CIPHER_C
#define MBEDTLS_CTR_DRBG_C
/* #define MBEDTLS_DES_C */
#define MBEDTLS_ENTROPY_C
#define MBEDTLS_MD_C
#define MBEDTLS_MD5_C
#define MBEDTLS_NET_C
#define MBEDTLS_OID_C
#define MBEDTLS_PK_C
#define MBEDTLS_PK_PARSE_C
#define MBEDTLS_RSA_C
#define MBEDTLS_SHA1_C
#define MBEDTLS_SHA256_C
#define MBEDTLS_SSL_CLI_C
#define MBEDTLS_SSL_SRV_C
#define MBEDTLS_SSL_TLS_C
#define MBEDTLS_X509_CRT_PARSE_C
#define MBEDTLS_X509_CRL_PARSE_C
#define MBEDTLS_X509_USE_C
#define MBEDTLS_SSL_CACHE_C
/* For test certificates */
#define MBEDTLS_BASE64_C
#define MBEDTLS_CERTS_C
#define MBEDTLS_PEM_PARSE_C
#define MBEDTLS_PKCS12_C
#define MBEDTLS_PKCS5_C
/* For testing with compat.sh */
#define MBEDTLS_FS_IO
// MIGRATE 2.28->3.x.x: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#remove-mbedtls_x509_check__key_usage-options-from-mbedtls_configh
// #define MBEDTLS_X509_CHECK_KEY_USAGE
// #define MBEDTLS_X509_CHECK_EXTENDED_KEY_USAGE
// MIGRATE 2.28->3.x.x: https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#introduce-a-level-of-indirection-and-versioning-in-the-config-files
// #include "mbedtls/check_config.h"
// #endif /* MBEDTLS_CONFIG_H */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,685 @@
/*
* cpXXtime2a.c
*
* Implementation of the types CP16Time2a, CP24Time2a and CP56Time2a
*
* Copyright 2016-2022 Michael Zillgith
*
* This file is part of lib60870-C
*
* lib60870-C is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* lib60870-C is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with lib60870-C. If not, see <http://www.gnu.org/licenses/>.
*
* See COPYING file for the complete license text.
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <time.h>
#include "lib_memory.h"
#include "iec60870_common.h"
#include "apl_types_internal.h"
/**********************************
* CP16Time2a type
**********************************/
bool
CP16Time2a_getFromBuffer (CP16Time2a self, const uint8_t* msg, int msgSize, int startIndex)
{
if (msgSize < startIndex + 2)
return false;
int i;
for (i = 0; i < 2; i++)
self->encodedValue[i] = msg[startIndex + i];
return true;
}
int
CP16Time2a_getEplapsedTimeInMs(const CP16Time2a self)
{
return (self->encodedValue[0] + (self->encodedValue[1] * 0x100));
}
void
CP16Time2a_setEplapsedTimeInMs(CP16Time2a self, int value)
{
self->encodedValue[0] = (uint8_t) (value % 0x100);
self->encodedValue[1] = (uint8_t) (value / 0x100);
}
uint8_t*
CP16Time2a_getEncodedValue(CP16Time2a self)
{
return self->encodedValue;
}
/************************************
* CP24Time2a and CP26Time2a common
************************************/
static int
getMillisecond(const uint8_t* encodedValue)
{
return (encodedValue[0] + (encodedValue[1] * 0x100)) % 1000;
}
static void
setMillisecond(uint8_t* encodedValue, int value)
{
int millies = encodedValue[0] + (encodedValue[1] * 0x100);
/* erase sub-second part */
millies = millies - (millies % 1000);
millies = millies + value;
encodedValue[0] = (uint8_t) (millies & 0xff);
encodedValue[1] = (uint8_t) ((millies / 0x100) & 0xff);
}
static int
getSecond(const uint8_t* encodedValue)
{
return (encodedValue[0] + (encodedValue[1] * 0x100)) / 1000;
}
static void
setSecond(uint8_t* encodedValue, int value)
{
int millies = encodedValue[0] + (encodedValue[1] * 0x100);
int msPart = millies % 1000;
millies = (value * 1000) + msPart;
encodedValue[0] = (uint8_t) (millies & 0xff);
encodedValue[1] = (uint8_t) ((millies / 0x100) & 0xff);
}
static int
getMinute(const uint8_t* encodedValue)
{
return (encodedValue[2] & 0x3f);
}
static void
setMinute(uint8_t* encodedValue, int value)
{
encodedValue[2] = (uint8_t) ((encodedValue[2] & 0xc0) | (value & 0x3f));
}
static bool
isInvalid(const uint8_t* encodedValue)
{
return ((encodedValue[2] & 0x80) != 0);
}
static void
setInvalid(uint8_t* encodedValue, bool value)
{
if (value)
encodedValue[2] |= 0x80;
else
encodedValue[2] &= 0x7f;
}
static bool
isSubstituted(const uint8_t* encodedValue)
{
return ((encodedValue[2] & 0x40) == 0x40);
}
static void
setSubstituted(uint8_t* encodedValue, bool value)
{
if (value)
encodedValue[2] |= 0x40;
else
encodedValue[2] &= 0xbf;
}
/**********************************
* CP24Time2a type
**********************************/
bool
CP24Time2a_getFromBuffer (CP24Time2a self, const uint8_t* msg, int msgSize, int startIndex)
{
if (msgSize < startIndex + 3)
return false;
int i;
for (i = 0; i < 3; i++)
self->encodedValue[i] = msg[startIndex + i];
return true;
}
int
CP24Time2a_getMillisecond(const CP24Time2a self)
{
return getMillisecond(self->encodedValue);
}
void
CP24Time2a_setMillisecond(CP24Time2a self, int value)
{
setMillisecond(self->encodedValue, value);
}
int
CP24Time2a_getSecond(const CP24Time2a self)
{
return getSecond(self->encodedValue);
}
void
CP24Time2a_setSecond(CP24Time2a self, int value)
{
setSecond(self->encodedValue, value);
}
int
CP24Time2a_getMinute(const CP24Time2a self)
{
return getMinute(self->encodedValue);
}
void
CP24Time2a_setMinute(CP24Time2a self, int value)
{
setMinute(self->encodedValue, value);
}
bool
CP24Time2a_isInvalid(const CP24Time2a self)
{
return isInvalid(self->encodedValue);
}
void
CP24Time2a_setInvalid(CP24Time2a self, bool value)
{
setInvalid(self->encodedValue, value);
}
bool
CP24Time2a_isSubstituted(const CP24Time2a self)
{
return isSubstituted(self->encodedValue);
}
void
CP24Time2a_setSubstituted(CP24Time2a self, bool value)
{
setSubstituted(self->encodedValue, value);
}
#if 0
/* function found here: http://stackoverflow.com/questions/530519/stdmktime-and-timezone-info */
time_t my_timegm(register struct tm * t)
/* struct tm to seconds since Unix epoch */
{
register long year;
register time_t result;
#define MONTHSPERYEAR 12 /* months per calendar year */
static const int cumdays[MONTHSPERYEAR] =
{ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
/*@ +matchanyintegral @*/
year = 1900 + t->tm_year + t->tm_mon / MONTHSPERYEAR;
result = (year - 1970) * 365 + cumdays[t->tm_mon % MONTHSPERYEAR];
result += (year - 1968) / 4;
result -= (year - 1900) / 100;
result += (year - 1600) / 400;
if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0) &&
(t->tm_mon % MONTHSPERYEAR) < 2)
result--;
result += t->tm_mday - 1;
result *= 24;
result += t->tm_hour;
result *= 60;
result += t->tm_min;
result *= 60;
result += t->tm_sec;
if (t->tm_isdst == 1)
result -= 3600;
/*@ -matchanyintegral @*/
return (result);
}
#endif
/* Conversion from UTC date to second, unsigned 32-bit Unix epoch version.
* Written by François Grieu, 2015-07-21; public domain.
*
* my_mktime converts from struct tm UTC to non-leap seconds since
* 00:00:00 on the first UTC day of year 1970 (fixed).
* It works from 1970 to 2105 inclusive. It strives to be compatible
* with C compilers supporting // comments and claiming C89 conformance.
*
* input: Pointer to a struct tm with field tm_year, tm_mon, tm_mday,
* tm_hour, tm_min, tm_sec set per mktime convention; thus
* - tm_year is year minus 1900
* - tm_mon is [0..11] for January to December, but [-2..14]
* works for November of previous year to February of next year
* - tm_mday, tm_hour, tm_min, tm_sec similarly can be offset to
* the full range [-32767 to 32768], as long as the combination
* with tm_year gives a result within years [1970..2105], and
* tm_year>0.
* output: Number of non-leap seconds since beginning of the first UTC
* day of year 1970, as an unsigned at-least-32-bit integer.
* The input is not changed (in particular, fields tm_wday,
* tm_yday, and tm_isdst are unchanged and ignored).
*/
static time_t
my_mktime(const struct tm * ptm)
{
int m, y = ptm->tm_year;
if ((m = ptm->tm_mon) < 2) {
m += 12;
--y;
}
return ((((time_t) (y - 69) * 365u + y / 4 - y / 100 * 3 / 4 + (m + 2) * 153 / 5 - 446 +
ptm->tm_mday) * 24u + ptm->tm_hour) * 60u + ptm->tm_min) * 60u + ptm->tm_sec;
}
/**********************************
* CP32Time2a type
**********************************/
CP32Time2a
CP32Time2a_create(CP32Time2a self)
{
if (self == NULL)
self = (CP32Time2a) GLOBAL_CALLOC(1, sizeof(struct sCP32Time2a));
else
memset (self, 0, sizeof(struct sCP32Time2a));
return self;
}
bool
CP32Time2a_getFromBuffer (CP32Time2a self, const uint8_t* msg, int msgSize, int startIndex)
{
if (msgSize < startIndex + 4)
return false;
int i;
for (i = 0; i < 4; i++)
self->encodedValue[i] = msg[startIndex + i];
return true;
}
int
CP32Time2a_getMillisecond(const CP32Time2a self)
{
return (self->encodedValue[0] + (self->encodedValue[1] * 0x100)) % 1000;
}
void
CP32Time2a_setMillisecond(CP32Time2a self, int value)
{
int millies = (CP32Time2a_getSecond(self) * 1000) + value;
self->encodedValue[0] = (uint8_t) (millies & 0xff);
self->encodedValue[1] = (uint8_t) ((millies / 0x100) & 0xff);
}
int
CP32Time2a_getSecond(const CP32Time2a self)
{
return getSecond(self->encodedValue);
}
void
CP32Time2a_setSecond(CP32Time2a self, int value)
{
setSecond(self->encodedValue, value);
}
int
CP32Time2a_getMinute(const CP32Time2a self)
{
return getMinute(self->encodedValue);
}
void
CP32Time2a_setMinute(CP32Time2a self, int value)
{
setMinute(self->encodedValue, value);
}
bool
CP32Time2a_isInvalid(const CP32Time2a self)
{
return isInvalid(self->encodedValue);
}
void
CP32Time2a_setInvalid(CP32Time2a self, bool value)
{
setInvalid(self->encodedValue, value);
}
bool
CP32Time2a_isSubstituted(const CP32Time2a self)
{
return isSubstituted(self->encodedValue);
}
void
CP32Time2a_setSubstituted(CP32Time2a self, bool value)
{
setSubstituted(self->encodedValue, value);
}
int
CP32Time2a_getHour(const CP32Time2a self)
{
return (self->encodedValue[3] & 0x1f);
}
void
CP32Time2a_setHour(CP32Time2a self, int value)
{
self->encodedValue[3] = (uint8_t) ((self->encodedValue[3] & 0xe0) | (value & 0x1f));
}
bool
CP32Time2a_isSummerTime(const CP32Time2a self)
{
return ((self->encodedValue[3] & 0x80) != 0);
}
void
CP32Time2a_setSummerTime(CP32Time2a self, bool value)
{
if (value)
self->encodedValue[3] |= 0x80;
else
self->encodedValue[3] &= 0x7f;
}
void
CP32Time2a_setFromMsTimestamp(CP32Time2a self, uint64_t timestamp)
{
memset(self->encodedValue, 0, 4);
time_t timeVal = timestamp / 1000;
int msPart = timestamp % 1000;
struct tm tmTime;
#ifdef _WIN32
gmtime_s(&tmTime, &timeVal);
#else
gmtime_r(&timeVal, &tmTime);
#endif
CP32Time2a_setSecond(self, tmTime.tm_sec);
CP32Time2a_setMillisecond(self, msPart);
CP32Time2a_setMinute(self, tmTime.tm_min);
CP32Time2a_setHour(self, tmTime.tm_hour);
}
uint8_t*
CP32Time2a_getEncodedValue(CP32Time2a self)
{
return self->encodedValue;
}
/**********************************
* CP56Time2a type
**********************************/
CP56Time2a
CP56Time2a_createFromMsTimestamp(CP56Time2a self, uint64_t timestamp)
{
if (self == NULL)
self = (CP56Time2a) GLOBAL_CALLOC(1, sizeof(struct sCP56Time2a));
else
memset (self, 0, sizeof(struct sCP56Time2a));
if (self != NULL)
CP56Time2a_setFromMsTimestamp(self, timestamp);
return self;
}
void
CP56Time2a_setFromMsTimestamp(CP56Time2a self, uint64_t timestamp)
{
memset(self->encodedValue, 0, 7);
time_t timeVal = timestamp / 1000;
int msPart = timestamp % 1000;
struct tm tmTime;
/* TODO replace with portable implementation */
#ifdef _WIN32
gmtime_s(&tmTime, &timeVal);
#else
gmtime_r(&timeVal, &tmTime);
#endif
CP56Time2a_setMillisecond(self, msPart);
CP56Time2a_setSecond(self, tmTime.tm_sec);
CP56Time2a_setMinute(self, tmTime.tm_min);
CP56Time2a_setHour(self, tmTime.tm_hour);
CP56Time2a_setDayOfMonth(self, tmTime.tm_mday);
/* set day of week to 0 = not present */
CP56Time2a_setDayOfWeek(self, 0);
CP56Time2a_setMonth(self, tmTime.tm_mon + 1);
CP56Time2a_setYear(self, tmTime.tm_year);
}
uint64_t
CP56Time2a_toMsTimestamp(const CP56Time2a self)
{
struct tm tmTime;
tmTime.tm_sec = CP56Time2a_getSecond(self);
tmTime.tm_min = CP56Time2a_getMinute(self);
tmTime.tm_hour = CP56Time2a_getHour(self);
tmTime.tm_mday = CP56Time2a_getDayOfMonth(self);
tmTime.tm_mon = CP56Time2a_getMonth(self) - 1;
tmTime.tm_year = CP56Time2a_getYear(self) + 100;
time_t timestamp = my_mktime(&tmTime);
uint64_t msTimestamp = ((uint64_t) (timestamp * (uint64_t) 1000)) + CP56Time2a_getMillisecond(self);
return msTimestamp;
}
/* private */ bool
CP56Time2a_getFromBuffer(CP56Time2a self, const uint8_t* msg, int msgSize, int startIndex)
{
if (msgSize < startIndex + 7)
return false;
int i;
for (i = 0; i < 7; i++)
self->encodedValue[i] = msg[startIndex + i];
return true;
}
int
CP56Time2a_getMillisecond(const CP56Time2a self)
{
return getMillisecond(self->encodedValue);
}
void
CP56Time2a_setMillisecond(CP56Time2a self, int value)
{
setMillisecond(self->encodedValue, value);
}
int
CP56Time2a_getSecond(const CP56Time2a self)
{
return getSecond(self->encodedValue);
}
void
CP56Time2a_setSecond(CP56Time2a self, int value)
{
setSecond(self->encodedValue, value);
}
int
CP56Time2a_getMinute(const CP56Time2a self)
{
return getMinute(self->encodedValue);
}
void
CP56Time2a_setMinute(CP56Time2a self, int value)
{
setMinute(self->encodedValue, value);
}
int
CP56Time2a_getHour(const CP56Time2a self)
{
return (self->encodedValue[3] & 0x1f);
}
void
CP56Time2a_setHour(CP56Time2a self, int value)
{
self->encodedValue[3] = (uint8_t) ((self->encodedValue[3] & 0xe0) | (value & 0x1f));
}
int
CP56Time2a_getDayOfWeek(const CP56Time2a self)
{
return ((self->encodedValue[4] & 0xe0) >> 5);
}
void
CP56Time2a_setDayOfWeek(CP56Time2a self, int value)
{
self->encodedValue[4] = (uint8_t) ((self->encodedValue[4] & 0x1f) | ((value & 0x07) << 5));
}
int
CP56Time2a_getDayOfMonth(const CP56Time2a self)
{
return (self->encodedValue[4] & 0x1f);
}
void
CP56Time2a_setDayOfMonth(CP56Time2a self, int value)
{
self->encodedValue[4] = (uint8_t) ((self->encodedValue[4] & 0xe0) + (value & 0x1f));
}
int
CP56Time2a_getMonth(const CP56Time2a self)
{
return (self->encodedValue[5] & 0x0f);
}
void
CP56Time2a_setMonth(CP56Time2a self, int value)
{
self->encodedValue[5] = (uint8_t) ((self->encodedValue[5] & 0xf0) + (value & 0x0f));
}
int
CP56Time2a_getYear(const CP56Time2a self)
{
return (self->encodedValue[6] & 0x7f);
}
void
CP56Time2a_setYear(CP56Time2a self, int value)
{
value = value % 100;
self->encodedValue[6] = (uint8_t) ((self->encodedValue[6] & 0x80) + (value & 0x7f));
}
bool
CP56Time2a_isSummerTime(const CP56Time2a self)
{
return ((self->encodedValue[3] & 0x80) != 0);
}
void
CP56Time2a_setSummerTime(CP56Time2a self, bool value)
{
if (value)
self->encodedValue[3] |= 0x80;
else
self->encodedValue[3] &= 0x7f;
}
bool
CP56Time2a_isInvalid(const CP56Time2a self)
{
return isInvalid(self->encodedValue);
}
void
CP56Time2a_setInvalid(CP56Time2a self, bool value)
{
setInvalid(self->encodedValue, value);
}
bool
CP56Time2a_isSubstituted(const CP56Time2a self)
{
return isSubstituted(self->encodedValue);
}
void
CP56Time2a_setSubstituted(CP56Time2a self, bool value)
{
setSubstituted(self->encodedValue, value);
}
uint8_t*
CP56Time2a_getEncodedValue(CP56Time2a self)
{
return self->encodedValue;
}

File diff suppressed because it is too large Load Diff

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