feat:
--READ smash_read_this.txt
This commit is contained in:
commit
1ddf693260
192
.clang-format
Normal file
192
.clang-format
Normal 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
32
.github/workflows/main.yml
vendored
Normal 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
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
lib60870-C/build/
|
||||
266
CHANGELOG
Normal file
266
CHANGELOG
Normal 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
674
COPYING
Normal 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
111
README.md
Normal 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
159
lib60870-C/CMakeLists.txt
Normal 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
2441
lib60870-C/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
185
lib60870-C/Makefile
Normal file
185
lib60870-C/Makefile
Normal 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)
|
||||
|
||||
113
lib60870-C/config/lib60870_config.h
Normal file
113
lib60870-C/config/lib60870_config.h
Normal 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_ */
|
||||
45
lib60870-C/dependencies/README.md
Normal file
45
lib60870-C/dependencies/README.md
Normal 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
|
||||
|
||||
42
lib60870-C/dependencies/mbedtls_2.28_ssl_srv.c.patch
Normal file
42
lib60870-C/dependencies/mbedtls_2.28_ssl_srv.c.patch
Normal 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;
|
||||
BIN
lib60870-C/doxydoc/mz-automation.ico
Normal file
BIN
lib60870-C/doxydoc/mz-automation.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
18
lib60870-C/examples/CMakeLists.txt
Normal file
18
lib60870-C/examples/CMakeLists.txt
Normal 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)
|
||||
|
||||
20
lib60870-C/examples/cs101_master_balanced/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs101_master_balanced/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs101_master_balanced/Makefile
Normal file
20
lib60870-C/examples/cs101_master_balanced/Makefile
Normal 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)
|
||||
|
||||
|
||||
186
lib60870-C/examples/cs101_master_balanced/master_example.c
Normal file
186
lib60870-C/examples/cs101_master_balanced/master_example.c
Normal 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);
|
||||
}
|
||||
20
lib60870-C/examples/cs101_master_unbalanced/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs101_master_unbalanced/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs101_master_unbalanced/Makefile
Normal file
20
lib60870-C/examples/cs101_master_unbalanced/Makefile
Normal 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)
|
||||
|
||||
|
||||
267
lib60870-C/examples/cs101_master_unbalanced/master_example.c
Normal file
267
lib60870-C/examples/cs101_master_unbalanced/master_example.c
Normal 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;
|
||||
}
|
||||
20
lib60870-C/examples/cs101_slave/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs101_slave/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs101_slave/Makefile
Normal file
20
lib60870-C/examples/cs101_slave/Makefile
Normal 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)
|
||||
|
||||
|
||||
420
lib60870-C/examples/cs101_slave/slave_example.c
Normal file
420
lib60870-C/examples/cs101_slave/slave_example.c
Normal 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;
|
||||
}
|
||||
20
lib60870-C/examples/cs101_slave_files/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs101_slave_files/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs101_slave_files/Makefile
Normal file
20
lib60870-C/examples/cs101_slave_files/Makefile
Normal 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)
|
||||
|
||||
|
||||
502
lib60870-C/examples/cs101_slave_files/cs101_slave_files.c
Normal file
502
lib60870-C/examples/cs101_slave_files/cs101_slave_files.c
Normal 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;
|
||||
}
|
||||
20
lib60870-C/examples/cs104_client/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs104_client/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs104_client/Makefile
Normal file
20
lib60870-C/examples/cs104_client/Makefile
Normal 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)
|
||||
|
||||
|
||||
173
lib60870-C/examples/cs104_client/simple_client.c
Normal file
173
lib60870-C/examples/cs104_client/simple_client.c
Normal 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;
|
||||
}
|
||||
20
lib60870-C/examples/cs104_client_async/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs104_client_async/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs104_client_async/Makefile
Normal file
20
lib60870-C/examples/cs104_client_async/Makefile
Normal 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)
|
||||
|
||||
|
||||
210
lib60870-C/examples/cs104_client_async/cs104_client_async.c
Normal file
210
lib60870-C/examples/cs104_client_async/cs104_client_async.c
Normal 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");
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
add_executable(cs104_client_no_threads cs104_client_no_threads.c)
|
||||
|
||||
target_link_libraries(cs104_client_no_threads PRIVATE lib60870)
|
||||
|
||||
@ -0,0 +1,138 @@
|
||||
#include "cs104_connection.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static bool
|
||||
asduReceived(void* parameter, int connectionIndex, CS101_ASDU asdu)
|
||||
{
|
||||
(void)parameter;
|
||||
(void)connectionIndex;
|
||||
|
||||
printf("RECVD ASDU type: %s(%i) elements: %i\n", TypeID_toString(CS101_ASDU_getTypeID(asdu)),
|
||||
CS101_ASDU_getTypeID(asdu), CS101_ASDU_getNumberOfElements(asdu));
|
||||
|
||||
if (CS101_ASDU_getTypeID(asdu) == M_ME_TE_1)
|
||||
{
|
||||
printf(" measured scaled values with CP56Time2a timestamp:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
MeasuredValueScaledWithCP56Time2a io = (MeasuredValueScaledWithCP56Time2a)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
MeasuredValueScaled_getValue((MeasuredValueScaled)io));
|
||||
|
||||
MeasuredValueScaledWithCP56Time2a_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == M_SP_NA_1)
|
||||
{
|
||||
printf(" single point information:\n");
|
||||
|
||||
int i;
|
||||
|
||||
for (i = 0; i < CS101_ASDU_getNumberOfElements(asdu); i++)
|
||||
{
|
||||
SinglePointInformation io = (SinglePointInformation)CS101_ASDU_getElement(asdu, i);
|
||||
|
||||
if (io)
|
||||
{
|
||||
printf(" IOA: %i value: %i\n", InformationObject_getObjectAddress((InformationObject)io),
|
||||
SinglePointInformation_getValue((SinglePointInformation)io));
|
||||
|
||||
SinglePointInformation_destroy(io);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (CS101_ASDU_getTypeID(asdu) == C_TS_TA_1)
|
||||
{
|
||||
printf(" test command with timestamp\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
connectionHandler(void* parameter, CS104_Connection connection, CS104_ConnectionEvent event)
|
||||
{
|
||||
(void)parameter;
|
||||
(void)connection;
|
||||
|
||||
printf("[EVENT] %d\n", event);
|
||||
if (event == CS104_CONNECTION_STARTDT_CON_RECEIVED)
|
||||
{
|
||||
printf("Sending interrogation command...\n");
|
||||
// CS104_Connection_sendInterrogationCommand(connection, CS101_COT_ACTIVATION, 1,
|
||||
// (QualifierOfInterrogation)20); /* station interrogation */
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char** argv)
|
||||
{
|
||||
const char* host = (argc > 1) ? argv[1] : "127.0.0.1";
|
||||
int port = (argc > 2) ? atoi(argv[2]) : -1;
|
||||
|
||||
CS104_Connection con = CS104_Connection_create(host, port);
|
||||
|
||||
if (!con)
|
||||
{
|
||||
fprintf(stderr, "Failed to allocate connection\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
CS104_Connection_setASDUReceivedHandler(con, asduReceived, NULL);
|
||||
CS104_Connection_setConnectionHandler(con, connectionHandler, NULL);
|
||||
|
||||
CS104_APCIParameters apciParams = CS104_Connection_getAPCIParameters(con);
|
||||
|
||||
apciParams->t3 = 2;
|
||||
|
||||
printf("APCI parameters:\n");
|
||||
printf(" t0: %i\n", apciParams->t0);
|
||||
printf(" t1: %i\n", apciParams->t1);
|
||||
printf(" t2: %i\n", apciParams->t2);
|
||||
printf(" t3: %i\n", apciParams->t3);
|
||||
printf(" k: %i\n", apciParams->k);
|
||||
printf(" w: %i\n", apciParams->w);
|
||||
|
||||
|
||||
if (!CS104_Connection_startThreadless(con))
|
||||
{
|
||||
fprintf(stderr, "Failed to start threadless connection\n");
|
||||
CS104_Connection_destroy(con);
|
||||
return 2;
|
||||
}
|
||||
|
||||
CS104_Connection_sendStartDT(con);
|
||||
|
||||
/* Simple loop: run for up to 30 seconds */
|
||||
int iterations = 0;
|
||||
|
||||
while (CS104_Connection_isThreadless(con))
|
||||
{
|
||||
if (!CS104_Connection_run(con, 100))
|
||||
{
|
||||
break; /* connection closed or failed */
|
||||
}
|
||||
|
||||
if (iterations == 100)
|
||||
{
|
||||
// printf("Sending interrogation command...\n");
|
||||
// CS104_Connection_sendInterrogationCommand(con, CS101_COT_ACTIVATION, 1, IEC60870_QOI_STATION);
|
||||
}
|
||||
|
||||
iterations++;
|
||||
}
|
||||
|
||||
CS104_Connection_stopThreadless(con);
|
||||
CS104_Connection_destroy(con);
|
||||
|
||||
return 0;
|
||||
}
|
||||
20
lib60870-C/examples/cs104_redundancy_server/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs104_redundancy_server/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs104_redundancy_server/Makefile
Normal file
20
lib60870-C/examples/cs104_redundancy_server/Makefile
Normal 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)
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
20
lib60870-C/examples/cs104_server/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs104_server/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs104_server/Makefile
Normal file
20
lib60870-C/examples/cs104_server/Makefile
Normal 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)
|
||||
|
||||
|
||||
204
lib60870-C/examples/cs104_server/simple_server.c
Normal file
204
lib60870-C/examples/cs104_server/simple_server.c
Normal 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;
|
||||
}
|
||||
20
lib60870-C/examples/cs104_server_files/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs104_server_files/CMakeLists.txt
Normal 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
|
||||
)
|
||||
523
lib60870-C/examples/cs104_server_files/cs104_server_files.c
Normal file
523
lib60870-C/examples/cs104_server_files/cs104_server_files.c
Normal 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);
|
||||
}
|
||||
20
lib60870-C/examples/cs104_server_no_threads/CMakeLists.txt
Normal file
20
lib60870-C/examples/cs104_server_no_threads/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/cs104_server_no_threads/Makefile
Normal file
20
lib60870-C/examples/cs104_server_no_threads/Makefile
Normal 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)
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
20
lib60870-C/examples/multi_client_server/CMakeLists.txt
Normal file
20
lib60870-C/examples/multi_client_server/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/multi_client_server/Makefile
Normal file
20
lib60870-C/examples/multi_client_server/Makefile
Normal 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)
|
||||
|
||||
|
||||
415
lib60870-C/examples/multi_client_server/multi_client_server.c
Normal file
415
lib60870-C/examples/multi_client_server/multi_client_server.c
Normal 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);
|
||||
}
|
||||
25
lib60870-C/examples/tls_client/CMakeLists.txt
Normal file
25
lib60870-C/examples/tls_client/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/tls_client/Makefile
Normal file
20
lib60870-C/examples/tls_client/Makefile
Normal 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)
|
||||
|
||||
|
||||
27
lib60870-C/examples/tls_client/client_CA1_1.key
Normal file
27
lib60870-C/examples/tls_client/client_CA1_1.key
Normal 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-----
|
||||
20
lib60870-C/examples/tls_client/client_CA1_1.pem
Normal file
20
lib60870-C/examples/tls_client/client_CA1_1.pem
Normal 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-----
|
||||
22
lib60870-C/examples/tls_client/root_CA1.pem
Normal file
22
lib60870-C/examples/tls_client/root_CA1.pem
Normal 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-----
|
||||
20
lib60870-C/examples/tls_client/server_CA1_1.pem
Normal file
20
lib60870-C/examples/tls_client/server_CA1_1.pem
Normal 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-----
|
||||
210
lib60870-C/examples/tls_client/tls_client.c
Normal file
210
lib60870-C/examples/tls_client/tls_client.c
Normal 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);
|
||||
}
|
||||
25
lib60870-C/examples/tls_server/CMakeLists.txt
Normal file
25
lib60870-C/examples/tls_server/CMakeLists.txt
Normal 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
|
||||
)
|
||||
20
lib60870-C/examples/tls_server/Makefile
Normal file
20
lib60870-C/examples/tls_server/Makefile
Normal 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)
|
||||
|
||||
|
||||
20
lib60870-C/examples/tls_server/client_CA1_1.pem
Normal file
20
lib60870-C/examples/tls_server/client_CA1_1.pem
Normal 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-----
|
||||
22
lib60870-C/examples/tls_server/root_CA1.pem
Normal file
22
lib60870-C/examples/tls_server/root_CA1.pem
Normal 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-----
|
||||
27
lib60870-C/examples/tls_server/server_CA1_1.key
Normal file
27
lib60870-C/examples/tls_server/server_CA1_1.key
Normal 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-----
|
||||
20
lib60870-C/examples/tls_server/server_CA1_1.pem
Normal file
20
lib60870-C/examples/tls_server/server_CA1_1.pem
Normal 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-----
|
||||
363
lib60870-C/examples/tls_server/tls_server.c
Normal file
363
lib60870-C/examples/tls_server/tls_server.c
Normal 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);
|
||||
}
|
||||
7
lib60870-C/make/common_targets.mk
Normal file
7
lib60870-C/make/common_targets.mk
Normal 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
|
||||
3
lib60870-C/make/stack_includes.mk
Normal file
3
lib60870-C/make/stack_includes.mk
Normal 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
|
||||
185
lib60870-C/make/target_system.mk
Normal file
185
lib60870-C/make/target_system.mk
Normal 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
|
||||
1
lib60870-C/modules/README.md
Normal file
1
lib60870-C/modules/README.md
Normal file
@ -0,0 +1 @@
|
||||
This folder contains optional library add-ons (e.g. for secure authentication).
|
||||
185
lib60870-C/src/CMakeLists.txt
Normal file
185
lib60870-C/src/CMakeLists.txt
Normal 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
|
||||
)
|
||||
175
lib60870-C/src/common/inc/linked_list.h
Normal file
175
lib60870-C/src/common/inc/linked_list.h
Normal 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_ */
|
||||
186
lib60870-C/src/common/linked_list.c
Normal file
186
lib60870-C/src/common/linked_list.c
Normal 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
|
||||
|
||||
143
lib60870-C/src/file-service/cs101_file_service.h
Normal file
143
lib60870-C/src/file-service/cs101_file_service.h
Normal 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_ */
|
||||
965
lib60870-C/src/file-service/file_server.c
Normal file
965
lib60870-C/src/file-service/file_server.c
Normal 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);
|
||||
}
|
||||
231
lib60870-C/src/hal/CMakeLists.txt
Normal file
231
lib60870-C/src/hal/CMakeLists.txt
Normal 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
|
||||
)
|
||||
51
lib60870-C/src/hal/inc/hal_base.h
Normal file
51
lib60870-C/src/hal/inc/hal_base.h
Normal 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_ */
|
||||
140
lib60870-C/src/hal/inc/hal_serial.h
Normal file
140
lib60870-C/src/hal/inc/hal_serial.h
Normal 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_ */
|
||||
393
lib60870-C/src/hal/inc/hal_socket.h
Normal file
393
lib60870-C/src/hal/inc/hal_socket.h
Normal 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_ */
|
||||
105
lib60870-C/src/hal/inc/hal_thread.h
Normal file
105
lib60870-C/src/hal/inc/hal_thread.h
Normal 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_ */
|
||||
96
lib60870-C/src/hal/inc/hal_time.h
Normal file
96
lib60870-C/src/hal/inc/hal_time.h
Normal 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_ */
|
||||
53
lib60870-C/src/hal/inc/lib_memory.h
Normal file
53
lib60870-C/src/hal/inc/lib_memory.h
Normal 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_ */
|
||||
37
lib60870-C/src/hal/inc/platform_endian.h
Normal file
37
lib60870-C/src/hal/inc/platform_endian.h
Normal 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_ */
|
||||
263
lib60870-C/src/hal/inc/tls_ciphers.h
Normal file
263
lib60870-C/src/hal/inc/tls_ciphers.h
Normal 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_ */
|
||||
382
lib60870-C/src/hal/inc/tls_config.h
Normal file
382
lib60870-C/src/hal/inc/tls_config.h
Normal 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_ */
|
||||
122
lib60870-C/src/hal/inc/tls_socket.h
Normal file
122
lib60870-C/src/hal/inc/tls_socket.h
Normal 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_ */
|
||||
68
lib60870-C/src/hal/memory/lib_memory.c
Normal file
68
lib60870-C/src/hal/memory/lib_memory.c
Normal 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);
|
||||
}
|
||||
|
||||
264
lib60870-C/src/hal/serial/linux/serial_port_linux.c
Normal file
264
lib60870-C/src/hal/serial/linux/serial_port_linux.c
Normal 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;
|
||||
}
|
||||
273
lib60870-C/src/hal/serial/win32/serial_port_win32.c
Normal file
273
lib60870-C/src/hal/serial/win32/serial_port_win32.c
Normal 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;
|
||||
}
|
||||
967
lib60870-C/src/hal/socket/bsd/socket_bsd.c
Normal file
967
lib60870-C/src/hal/socket/bsd/socket_bsd.c
Normal 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;
|
||||
}
|
||||
1033
lib60870-C/src/hal/socket/linux/socket_linux.c
Normal file
1033
lib60870-C/src/hal/socket/linux/socket_linux.c
Normal file
File diff suppressed because it is too large
Load Diff
954
lib60870-C/src/hal/socket/win32/socket_win32.c
Normal file
954
lib60870-C/src/hal/socket/win32/socket_win32.c
Normal 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;
|
||||
}
|
||||
109
lib60870-C/src/hal/thread/bsd/thread_bsd.c
Normal file
109
lib60870-C/src/hal/thread/bsd/thread_bsd.c
Normal 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);
|
||||
}
|
||||
|
||||
109
lib60870-C/src/hal/thread/linux/thread_linux.c
Normal file
109
lib60870-C/src/hal/thread/linux/thread_linux.c
Normal 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);
|
||||
}
|
||||
|
||||
149
lib60870-C/src/hal/thread/macos/thread_macos.c
Normal file
149
lib60870-C/src/hal/thread/macos/thread_macos.c
Normal 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);
|
||||
}
|
||||
114
lib60870-C/src/hal/thread/win32/thread_win32.c
Normal file
114
lib60870-C/src/hal/thread/win32/thread_win32.c
Normal 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);
|
||||
}
|
||||
81
lib60870-C/src/hal/time/unix/time.c
Normal file
81
lib60870-C/src/hal/time/unix/time.c
Normal 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;
|
||||
}
|
||||
72
lib60870-C/src/hal/time/win32/time.c
Normal file
72
lib60870-C/src/hal/time/win32/time.c
Normal 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);
|
||||
}
|
||||
70
lib60870-C/src/hal/tls/mbedtls/mbedtls_config.h
Normal file
70
lib60870-C/src/hal/tls/mbedtls/mbedtls_config.h
Normal 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 */
|
||||
2262
lib60870-C/src/hal/tls/mbedtls/tls_mbedtls.c
Normal file
2262
lib60870-C/src/hal/tls/mbedtls/tls_mbedtls.c
Normal file
File diff suppressed because it is too large
Load Diff
73
lib60870-C/src/hal/tls/mbedtls3/mbedtls_config.h
Normal file
73
lib60870-C/src/hal/tls/mbedtls3/mbedtls_config.h
Normal 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 */
|
||||
1956
lib60870-C/src/hal/tls/mbedtls3/tls_mbedtls.c
Normal file
1956
lib60870-C/src/hal/tls/mbedtls3/tls_mbedtls.c
Normal file
File diff suppressed because it is too large
Load Diff
685
lib60870-C/src/iec60870/apl/cpXXtime2a.c
Normal file
685
lib60870-C/src/iec60870/apl/cpXXtime2a.c
Normal 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;
|
||||
}
|
||||
1800
lib60870-C/src/iec60870/cs101/cs101_asdu.c
Normal file
1800
lib60870-C/src/iec60870/cs101/cs101_asdu.c
Normal file
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
Loading…
Reference in New Issue
Block a user