diff --git a/.net8/lib60870.csproj b/.net8/lib60870.csproj
new file mode 100644
index 0000000..8ade006
--- /dev/null
+++ b/.net8/lib60870.csproj
@@ -0,0 +1,94 @@
+
+
+ Library
+ Michael Zillgith
+ MZ Automation
+ lib60870
+ 2.3.0
+ lib60870
+ lib60870
+ 2.3.0
+ net8.0
+ false
+ icon.png
+
+ lib60870.NET library for IEC 60870-5 based protocols in C#
+ The current implementation contains code for the IEC 60870-5-101/104 protocols only.
+
+ readme.md
+ nupkg
+ true
+ GPL-3.0-only
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG b/CHANGELOG
index 9b75587..cb35dbf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -5,6 +5,27 @@ Changes to version 2.3.0
- changed examples target frameworks to .NET 6
- Added copy constructors for messages types in monitoring direction
- CS104 client: Allow setting of local IP address and TCP port (to allow redundancy setups)
+- Allow user to set TLS version
+- Updated TLS client/server examples
+- Extend test cases
+- CS101 slave: updated behavior in unbalanced mode when FCB bit did not change (L870NET-21)
+- CS104 server: implement pending unconfirmed stop state (L870NET-22)
+- CS104 server: connection is closed by server when receiving S message in inactive state (L870NET-23)
+- CS101 balanced link layer: Send request-status-of-link before calling reset-of-remote-link (L870NET-25)
+- CS101 unbalanced master: Send request-status-of-link before calling reset-of-remote-link, added delay before repeating request-status-of-link. (L870NET-26)
+- CS104 slave: check IOA of received commands where IOA is fixed to 0 (L870NET-28)
+- 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 (L870NET-29)
+- Disable support for C_TS_NA_1 and C_CD_NA_1 for CS104 by default (L870NET-30)
+- TLS: support for TLS 1.3 (L870NET-31)
+- Implementing Master connection state (Stopped, Started, and Unconfirmed Stopped) (L870NET-32)
+- Divide the queue into low and high priority (L870NET-33)
+- Implementing CS104 Client Connection State (L870NET-34)
+- Fixed: Problem in normalized value handling and add scaled value test case
+- Check COT = FileTranfer on fileHandler for every case and return UNKNOWN_CAUSE_OF_TRANSMISSION asdu when different
+- Fixed: Confirm all unconfirmed messages before stopping the connection; -> Fix - send unconfirmed messages before closing the socket
+- Only send last ASDU if lastSendASDU!= null -> on linkLayer.SendVariableLengthFramePrimary
+- Separate the ASDUQueue in another .cs file -> Manage high and low priorities queues -> Fix bug on sending ASDU in HighPriorityQueue
+
Changes to version 2.2.0
------------------------
diff --git a/README.md b/README.md
index 417dab1..052be0e 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# README lib60870.NET v2
+# README lib60870.NET v2.3.0
lib60870.NET library for IEC 60870-5 based protocols in C#
diff --git a/doxygen/Doxyfile b/doxygen/Doxyfile
index 3a7eae6..dbcc3a8 100644
--- a/doxygen/Doxyfile
+++ b/doxygen/Doxyfile
@@ -1,19 +1,145 @@
-# Doxyfile 1.8.11
+# Doxyfile 1.9.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+#
+# Note:
+#
+# Use doxygen to compare the used configuration file with the template
+# configuration file:
+# doxygen -x [configFile]
+# Use doxygen to compare the used configuration file with the template
+# configuration file without replacing the environment variables or CMake type
+# replacement variables:
+# doxygen -x_noenv [configFile]
#---------------------------------------------------------------------------
# Project related configuration options
#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the configuration
+# file that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# https://www.gnu.org/software/libiconv/ for the list of possible encodings.
+# The default value is: UTF-8.
+
DOXYFILE_ENCODING = UTF-8
-PROJECT_NAME = "lib60870.NET"
-PROJECT_NUMBER = "2.2.0"
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME = lib60870.NET
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER = 2.3.0
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
PROJECT_BRIEF = "IEC 60870-5-101/104 Protocol Source Code Library for C#/.NET"
-PROJECT_LOGO = doxygen/mz-automation.ico
+
+# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
+# in the documentation. The maximum height of the logo should not exceed 55
+# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
+# the logo to the output directory.
+
+PROJECT_LOGO = logo.png
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
OUTPUT_DIRECTORY = doxydoc.NET
+
+# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
+# sub-directories (in 2 levels) under the output directory of each output format
+# and will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
+# control the number of sub-directories.
+# The default value is: NO.
+
CREATE_SUBDIRS = NO
+
+# Controls the number of sub-directories that will be created when
+# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
+# level increment doubles the number of directories, resulting in 4096
+# directories at level 8 which is the default and also the maximum value. The
+# sub-directories are organized in 2 levels, the first level always has a fixed
+# number of 16 directories.
+# Minimum value: 0, maximum value: 8, default value: 8.
+# This tag requires that the tag CREATE_SUBDIRS is set to YES.
+
+CREATE_SUBDIRS_LEVEL = 8
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
ALLOW_UNICODE_NAMES = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
+# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
+# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
+# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
+# English messages), Korean, Korean-en (Korean with English messages), Latvian,
+# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
+# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
+# Swedish, Turkish, Ukrainian and Vietnamese.
+# The default value is: English.
+
OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
ABBREVIATE_BRIEF = "The $name class" \
"The $name widget" \
"The $name file" \
@@ -25,96 +151,838 @@ ABBREVIATE_BRIEF = "The $name class" \
a \
an \
the
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
FULL_PATH_NAMES = YES
-STRIP_FROM_PATH =
-STRIP_FROM_INC_PATH =
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
JAVADOC_AUTOBRIEF = NO
+
+# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line
+# such as
+# /***************
+# as being the beginning of a Javadoc-style comment "banner". If set to NO, the
+# Javadoc-style will behave just like regular comments and it will not be
+# interpreted by doxygen.
+# The default value is: NO.
+
+JAVADOC_BANNER = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
QT_AUTOBRIEF = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
MULTILINE_CPP_IS_BRIEF = NO
+
+# By default Python docstrings are displayed as preformatted text and doxygen's
+# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the
+# doxygen's special commands can be used and the contents of the docstring
+# documentation blocks is shown as doxygen documentation.
+# The default value is: YES.
+
+PYTHON_DOCSTRING = YES
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new
+# page for each member. If set to NO, the documentation of a member will be part
+# of the file/class/namespace that contains it.
+# The default value is: NO.
+
SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
TAB_SIZE = 4
-ALIASES =
-TCL_SUBST =
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:^^"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". Note that you cannot put \n's in the value part of an alias
+# to insert newlines (in the resulting output). You can put ^^ in the value part
+# of an alias to insert a newline as if a physical newline was in the original
+# file. When you need a literal { or } or , in the value part of an alias you
+# have to escape them by means of a backslash (\), this can lead to conflicts
+# with the commands \{ and \} for these it is advised to use the version @{ and
+# @} or use a double escape (\\{ and \\})
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
OPTIMIZE_OUTPUT_FOR_C = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
OPTIMIZE_OUTPUT_JAVA = YES
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
OPTIMIZE_OUTPUT_VHDL = NO
-EXTENSION_MAPPING =
+
+# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice
+# sources only. Doxygen will then generate output that is more tailored for that
+# language. For instance, namespaces will be presented as modules, types will be
+# separated into more groups, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_SLICE = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,
+# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,
+# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:
+# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser
+# tries to guess whether the code is fixed or free formatted code, this is the
+# default for Fortran type files). For instance to make doxygen treat .inc files
+# as Fortran files (default is PHP), and .f files as C (default is Fortran),
+# use: inc=Fortran f=C.
+#
+# Note: For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen. When specifying no_extension you should add
+# * to the FILE_PATTERNS.
+#
+# Note see also the list of default file extension mappings.
+
+EXTENSION_MAPPING =
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See https://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
MARKDOWN_SUPPORT = YES
+
+# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up
+# to that level are automatically included in the table of contents, even if
+# they do not have an id attribute.
+# Note: This feature currently applies only to Markdown headings.
+# Minimum value: 0, maximum value: 99, default value: 5.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+TOC_INCLUDE_HEADINGS = 5
+
+# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to
+# generate identifiers for the Markdown headings. Note: Every identifier is
+# unique.
+# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a
+# sequence number starting at 0 and GITHUB use the lower case version of title
+# with any whitespace replaced by '-' and punctuation characters removed.
+# The default value is: DOXYGEN.
+# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
+
+MARKDOWN_ID_STYLE = DOXYGEN
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
AUTOLINK_SUPPORT = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
DISTRIBUTE_GROUP_DOC = NO
+
+# If one adds a struct or class to a group and this option is enabled, then also
+# any nested class or struct is added to the same group. By default this option
+# is disabled and one has to add nested compounds explicitly via \ingroup.
+# The default value is: NO.
+
GROUP_NESTED_COMPOUNDS = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
INLINE_SIMPLE_STRUCTS = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
TYPEDEF_HIDES_STRUCT = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
LOOKUP_CACHE_SIZE = 0
+
+# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
+# during processing. When set to 0 doxygen will based this on the number of
+# cores available in the system. You can set it explicitly to a value larger
+# than 0 to get more control over the balance between CPU load and processing
+# speed. At this moment only the input processing can be done using multiple
+# threads. Since this is still an experimental feature the default is set to 1,
+# which effectively disables parallel processing. Please report any issues you
+# encounter. Generating dot graphs in parallel is controlled by the
+# DOT_NUM_THREADS setting.
+# Minimum value: 0, maximum value: 32, default value: 1.
+
+NUM_PROC_THREADS = 1
+
+# If the TIMESTAMP tag is set different from NO then each generated page will
+# contain the date or date and time when the page was generated. Setting this to
+# NO can help when comparing the output of multiple runs.
+# Possible values are: YES, NO, DATETIME and DATE.
+# The default value is: NO.
+
+TIMESTAMP = NO
+
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
EXTRACT_ALL = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual
+# methods of a class will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIV_VIRTUAL = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
EXTRACT_PACKAGE = NO
+
+# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
EXTRACT_STATIC = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO,
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. If set to YES, local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO, only methods in the interface are
+# included.
+# The default value is: NO.
+
EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
EXTRACT_ANON_NSPACES = NO
+
+# If this flag is set to YES, the name of an unnamed parameter in a declaration
+# will be determined by the corresponding definition. By default unnamed
+# parameters remain unnamed in the output.
+# The default value is: YES.
+
+RESOLVE_UNNAMED_PARAMS = YES
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO, these classes will be included in the various overviews. This option
+# will also hide undocumented C++ concepts if enabled. This option has no effect
+# if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# declarations. If set to NO, these declarations will be included in the
+# documentation.
+# The default value is: NO.
+
HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO, these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
INTERNAL_DOCS = NO
+
+# With the correct setting of option CASE_SENSE_NAMES doxygen will better be
+# able to match the capabilities of the underlying filesystem. In case the
+# filesystem is case sensitive (i.e. it supports files in the same directory
+# whose names only differ in casing), the option must be set to YES to properly
+# deal with such files in case they appear in the input. For filesystems that
+# are not case sensitive the option should be set to NO to properly deal with
+# output files written for symbols that only differ in casing, such as for two
+# classes, one named CLASS and the other named Class, and to also support
+# references to files without having to specify the exact matching casing. On
+# Windows (including Cygwin) and MacOS, users should typically set this option
+# to NO, whereas on Linux or other Unix flavors it should typically be set to
+# YES.
+# Possible values are: SYSTEM, NO and YES.
+# The default value is: SYSTEM.
+
CASE_SENSE_NAMES = NO
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES, the
+# scope will be hidden.
+# The default value is: NO.
+
HIDE_SCOPE_NAMES = NO
+
+# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will
+# append additional text to a page's title, such as Class Reference. If set to
+# YES the compound reference will be hidden.
+# The default value is: NO.
+
HIDE_COMPOUND_REFERENCE= NO
+
+# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class
+# will show which file needs to be included to use the class.
+# The default value is: YES.
+
+SHOW_HEADERFILE = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
SHOW_INCLUDE_FILES = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
SHOW_GROUPED_MEMB_INC = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order.
+# The default value is: YES.
+
SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO, the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo
+# list. This list is created by putting \todo commands in the documentation.
+# The default value is: YES.
+
GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test
+# list. This list is created by putting \test commands in the documentation.
+# The default value is: YES.
+
GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
GENERATE_DEPRECATEDLIST= YES
-ENABLED_SECTIONS =
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if ... \endif and \cond
+# ... \endcond blocks.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES, the
+# list will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
SHOW_USED_FILES = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
SHOW_NAMESPACES = YES
-FILE_VERSION_FILTER =
-LAYOUT_FILE =
-CITE_BIB_FILES =
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file. See also section "Changing the
+# layout of pages" for information.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES =
+
#---------------------------------------------------------------------------
# Configuration options related to warning and progress messages
#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
WARNINGS = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
WARN_IF_UNDOCUMENTED = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as documenting some parameters in
+# a documented function twice, or documenting parameters that don't exist or
+# using markup commands wrongly.
+# The default value is: YES.
+
WARN_IF_DOC_ERROR = YES
+
+# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete
+# function parameter documentation. If set to NO, doxygen will accept that some
+# parameters have no documentation without warning.
+# The default value is: YES.
+
+WARN_IF_INCOMPLETE_DOC = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO, doxygen will only warn about wrong parameter
+# documentation, but not about the absence of documentation. If EXTRACT_ALL is
+# set to YES then this flag will automatically be disabled. See also
+# WARN_IF_INCOMPLETE_DOC
+# The default value is: NO.
+
WARN_NO_PARAMDOC = NO
+
+# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
+# undocumented enumeration values. If set to NO, doxygen will accept
+# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: NO.
+
+WARN_IF_UNDOC_ENUM_VAL = NO
+
+# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
+# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
+# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
+# at the end of the doxygen process doxygen will return with a non-zero status.
+# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves
+# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not
+# write the warning messages in between other messages but write them at the end
+# of a run, in case a WARN_LOGFILE is defined the warning messages will be
+# besides being in the defined file also be shown at the end of a run, unless
+# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case
+# the behavior will remain as with the setting FAIL_ON_WARNINGS.
+# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
+# The default value is: NO.
+
WARN_AS_ERROR = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# See also: WARN_LINE_FORMAT
+# The default value is: $file:$line: $text.
+
WARN_FORMAT = "$file:$line: $text"
-WARN_LOGFILE =
+
+# In the $text part of the WARN_FORMAT command it is possible that a reference
+# to a more specific place is given. To make it easier to jump to this place
+# (outside of doxygen) the user can define a custom "cut" / "paste" string.
+# Example:
+# WARN_LINE_FORMAT = "'vi $file +$line'"
+# See also: WARN_FORMAT
+# The default value is: at line $line of file $file.
+
+WARN_LINE_FORMAT = "at line $line of file $file"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr). In case the file specified cannot be opened for writing the
+# warning and error messages are written to standard error. When as file - is
+# specified the warning and error messages are written to standard output
+# (stdout).
+
+WARN_LOGFILE =
+
#---------------------------------------------------------------------------
# Configuration options related to the input files
#---------------------------------------------------------------------------
-INPUT = README.md
-INPUT += lib60870
-
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
+# Note: If this tag is empty the current directory is searched.
+
+INPUT = ../lib60870
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see:
+# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
+# See also: INPUT_FILE_ENCODING
+# The default value is: UTF-8.
+
INPUT_ENCODING = UTF-8
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
+# character encoding on a per file pattern basis. Doxygen will compare the file
+# name with each pattern and apply the encoding instead of the default
+# INPUT_ENCODING) if there is a match. The character encodings are a list of the
+# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
+# "INPUT_ENCODING" for further information on supported encodings.
+
+INPUT_FILE_ENCODING =
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# read by doxygen.
+#
+# Note the list of default checked file patterns might differ from the list of
+# default file extension mappings.
+#
+# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
+# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl,
+# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php,
+# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be
+# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
+# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
+
FILE_PATTERNS = *.c \
*.cc \
*.cxx \
@@ -158,217 +1026,1830 @@ FILE_PATTERNS = *.c \
*.qsf \
*.as \
*.js
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
RECURSIVE = YES
-EXCLUDE =
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
EXCLUDE_SYMLINKS = NO
-EXCLUDE_PATTERNS =
-EXCLUDE_SYMBOLS =
-EXAMPLE_PATH = examples
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# ANamespace::AClass, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH = ../examples
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
EXAMPLE_PATTERNS = *
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
EXAMPLE_RECURSIVE = NO
-IMAGE_PATH =
-INPUT_FILTER =
-FILTER_PATTERNS =
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+#
+#
+# where is the value of the INPUT_FILTER tag, and is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+#
+# Note that doxygen will use the data processed and written to standard output
+# for further processing, therefore nothing else, like debug statements or used
+# commands (so in case of a Windows batch file always use @echo OFF), should be
+# written to standard output.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+#
+# Note that for custom extensions or not directly supported extensions you also
+# need to set EXTENSION_MAPPING for the extension otherwise the files are not
+# properly processed by doxygen.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
FILTER_SOURCE_FILES = NO
-FILTER_SOURCE_PATTERNS =
-USE_MDFILE_AS_MAINPAGE = README.md
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS =
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = ../README.md
+
+# The Fortran standard specifies that for fixed formatted Fortran code all
+# characters from position 72 are to be considered as comment. A common
+# extension is to allow longer lines before the automatic comment starts. The
+# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
+# be processed before the automatic comment starts.
+# Minimum value: 7, maximum value: 10000, default value: 72.
+
+FORTRAN_COMMENT_AFTER = 72
+
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# entity all documented functions referencing it will be listed.
+# The default value is: NO.
+
REFERENCED_BY_RELATION = NO
-REFERENCES_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
SOURCE_TOOLTIPS = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see https://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
VERBATIM_HEADERS = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
+# clang parser (see:
+# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
+# performance. This can be particularly helpful with template rich C++ code for
+# which doxygen's built-in parser lacks the necessary type information.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+# The default value is: NO.
+
CLANG_ASSISTED_PARSING = NO
-CLANG_OPTIONS =
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
+# tag is set to YES then doxygen will add the directory of each input to the
+# include path.
+# The default value is: YES.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_ADD_INC_PATHS = YES
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS =
+
+# If clang assisted parsing is enabled you can provide the clang parser with the
+# path to the directory containing a file called compile_commands.json. This
+# file is the compilation database (see:
+# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
+# options used when the source files were built. This is equivalent to
+# specifying the -p option to a clang tool, such as clang-check. These options
+# will then be passed to the parser. Any options specified with CLANG_OPTIONS
+# will be added as well.
+# Note: The availability of this option depends on whether or not doxygen was
+# generated with the -Duse_libclang=ON option for CMake.
+
+CLANG_DATABASE_PATH =
+
#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
ALPHABETICAL_INDEX = YES
-COLS_IN_ALPHA_INDEX = 5
-IGNORE_PREFIX =
+
+# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
+# that should be ignored while generating the index headers. The IGNORE_PREFIX
+# tag works for classes, function and member names. The entity will be placed in
+# the alphabetical list under the first letter of the entity name that remains
+# after removing the prefix.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX =
+
#---------------------------------------------------------------------------
# Configuration options related to the HTML output
#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output
+# The default value is: YES.
+
GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_FILE_EXTENSION = .html
-HTML_HEADER =
-HTML_FOOTER =
-HTML_STYLESHEET =
-HTML_EXTRA_STYLESHEET =
-HTML_EXTRA_FILES =
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefore more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# Note: Since the styling of scrollbars can currently not be overruled in
+# Webkit/Chromium, the styling will be left out of the default doxygen.css if
+# one or more extra stylesheets have been specified. So if scrollbar
+# customization is desired it has to be added explicitly. For an example see the
+# documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
+# should be rendered with a dark or light theme.
+# Possible values are: LIGHT always generate light mode output, DARK always
+# generate dark mode output, AUTO_LIGHT automatically set the mode according to
+# the user preference, use light mode if no preference is set (the default),
+# AUTO_DARK automatically set the mode according to the user preference, use
+# dark mode if no preference is set and TOGGLE allow to user to switch between
+# light and dark mode via a button.
+# The default value is: AUTO_LIGHT.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE = AUTO_LIGHT
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the style sheet and background images according to
+# this color. Hue is specified as an angle on a color-wheel, see
+# https://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use gray-scales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_COLORSTYLE_GAMMA = 80
-HTML_TIMESTAMP = NO
+
+# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
+# documentation will contain a main index with vertical navigation menus that
+# are dynamically created via JavaScript. If disabled, the navigation index will
+# consists of multiple levels of tabs that are statically embedded in every HTML
+# page. Disable this option to support browsers that do not have JavaScript,
+# like the Qt help browser.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_MENUS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_DYNAMIC_SECTIONS = NO
+
+# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be
+# dynamically folded and expanded in the generated HTML source code.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_CODE_FOLDING = YES
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see:
+# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To
+# create a documentation set, doxygen will generate a Makefile in the HTML
+# output directory. Running make will produce the docset in that directory and
+# running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy
+# genXcode/_index.html for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
GENERATE_DOCSET = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# This tag determines the URL of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDURL =
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# on Windows. In the beginning of 2021 Microsoft took the original page, with
+# a.o. the download links, offline the HTML help workshop was already many years
+# in maintenance mode). You can download the HTML help workshop from the web
+# archives at Installation executable (see:
+# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo
+# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
GENERATE_HTMLHELP = NO
-CHM_FILE =
-HHC_LOCATION =
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE =
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler (hhc.exe). If non-empty,
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION =
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated
+# (YES) or that it should be included in the main .chm file (NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
GENERATE_CHI = NO
-CHM_INDEX_ENCODING =
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING =
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated
+# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
TOC_EXPAND = NO
+
+# The SITEMAP_URL tag is used to specify the full URL of the place where the
+# generated documentation will be placed on the server by the user during the
+# deployment of the documentation. The generated sitemap is called sitemap.xml
+# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL
+# is specified no sitemap is generated. For information about the sitemap
+# protocol see https://www.sitemaps.org
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SITEMAP_URL =
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
GENERATE_QHP = NO
-QCH_FILE =
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
QHP_VIRTUAL_FOLDER = doc
-QHP_CUST_FILTER_NAME =
-QHP_CUST_FILTER_ATTRS =
-QHP_SECT_FILTER_ATTRS =
-QHG_LOCATION =
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS =
+
+# The QHG_LOCATION tag can be used to specify the location (absolute path
+# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to
+# run qhelpgenerator on the generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
ECLIPSE_DOC_ID = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
DISABLE_INDEX = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine tune the look of the index (see "Fine-tuning the output"). As an
+# example, the default style sheet generated by doxygen has an example that
+# shows how to put an image at the root of the tree instead of the PROJECT_NAME.
+# Since the tree basically has the same information as the tab index, you could
+# consider setting DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
GENERATE_TREEVIEW = YES
+
+# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the
+# FULL_SIDEBAR option determines if the side bar is limited to only the treeview
+# area (value NO) or if it should extend to the full height of the window (value
+# YES). Setting this to YES gives a layout similar to
+# https://docs.readthedocs.io with more room for contents, but less room for the
+# project logo, title, and description. If either GENERATE_TREEVIEW or
+# DISABLE_INDEX is set to NO, this option has no effect.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FULL_SIDEBAR = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
ENUM_VALUES_PER_LINE = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
TREEVIEW_WIDTH = 250
+
+# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
EXT_LINKS_IN_WINDOW = NO
+
+# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email
+# addresses.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+OBFUSCATE_EMAILS = YES
+
+# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg
+# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see
+# https://inkscape.org) to generate formulas as SVG images instead of PNGs for
+# the HTML output. These images will generally look nicer at scaled resolutions.
+# Possible values are: png (the default) and svg (looks nicer but requires the
+# pdf2svg or inkscape tool).
+# The default value is: png.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FORMULA_FORMAT = png
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
FORMULA_FONTSIZE = 10
-FORMULA_TRANSPARENT = YES
+
+# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
+# to create new LaTeX commands to be used in formulas as building blocks. See
+# the section "Including formulas" for details.
+
+FORMULA_MACROFILE =
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# https://www.mathjax.org) which uses client side JavaScript for the rendering
+# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
USE_MATHJAX = NO
+
+# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.
+# Note that the different versions of MathJax have different requirements with
+# regards to the different settings, so it is possible that also other MathJax
+# settings have to be changed when switching between the different MathJax
+# versions.
+# Possible values are: MathJax_2 and MathJax_3.
+# The default value is: MathJax_2.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_VERSION = MathJax_2
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. For more details about the output format see MathJax
+# version 2 (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3
+# (see:
+# http://docs.mathjax.org/en/latest/web/components/output.html).
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility. This is the name for Mathjax version 2, for MathJax version 3
+# this will be translated into chtml), NativeMML (i.e. MathML. Only supported
+# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This
+# is the name for Mathjax version 3, for MathJax version 2 this will be
+# translated into HTML-CSS) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
MATHJAX_FORMAT = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from https://www.mathjax.org before deployment. The default value is:
+# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2
+# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
-MATHJAX_EXTENSIONS =
-MATHJAX_CODEFILE =
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# for MathJax version 2 (see
+# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# For example for MathJax version 3 (see
+# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):
+# MATHJAX_EXTENSIONS = ams
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS =
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see:
+# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use + S
+# (what the is depends on the OS and browser, but it is typically
+# , /, or both). Inside the search box use the to jump into the search results window, the results can be navigated
+# using the . Press to select an item or to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing +. Also here use the
+# to select a filter and or to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using JavaScript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
SERVER_BASED_SEARCH = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
EXTERNAL_SEARCH = NO
-SEARCHENGINE_URL =
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer (doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see:
+# https://xapian.org/). See the section "External Indexing and Searching" for
+# details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
SEARCHDATA_FILE = searchdata.xml
-EXTERNAL_SEARCH_ID =
-EXTRA_SEARCH_MAPPINGS =
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID =
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS =
+
#---------------------------------------------------------------------------
# Configuration options related to the LaTeX output
#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
+# The default value is: YES.
+
GENERATE_LATEX = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when not enabling USE_PDFLATEX the default is latex when enabling
+# USE_PDFLATEX the default is pdflatex and when in the later case latex is
+# chosen this is overwritten by pdflatex. For specific output languages the
+# default can have been set differently, this depends on the implementation of
+# the output language.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# Note: This tag is used in the Makefile / make.bat.
+# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file
+# (.tex).
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
MAKEINDEX_CMD_NAME = makeindex
+
+# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to
+# generate index for LaTeX. In case there is no backslash (\) as first character
+# it will be automatically added in the LaTeX code.
+# Note: This tag is used in the generated output file (.tex).
+# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.
+# The default value is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_MAKEINDEX_CMD = makeindex
+
+# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
PAPER_TYPE = a4
-EXTRA_PACKAGES =
-LATEX_HEADER =
-LATEX_FOOTER =
-LATEX_EXTRA_STYLESHEET =
-LATEX_EXTRA_FILES =
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. The package can be specified just
+# by its name or with the correct syntax as to be used with the LaTeX
+# \usepackage command. To get the times font for instance you can specify :
+# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}
+# To use the option intlimits with the amsmath package you can specify:
+# EXTRA_PACKAGES=[intlimits]{amsmath}
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for
+# the generated LaTeX document. The header should contain everything until the
+# first chapter. If it is left blank doxygen will generate a standard header. It
+# is highly recommended to start with a default header using
+# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty
+# and then modify the file new_header.tex. See also section "Doxygen usage" for
+# information on how to generate the default header that doxygen normally uses.
+#
+# Note: Only use a user-defined header if you know what you are doing!
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. The following
+# commands have a special meaning inside the header (and footer): For a
+# description of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for
+# the generated LaTeX document. The footer should contain everything after the
+# last chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer. See also section "Doxygen
+# usage" for information on how to generate the default footer that doxygen
+# normally uses. Note: Only use a user-defined footer if you know what you are
+# doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER =
+
+# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# LaTeX style sheets that are included after the standard style sheets created
+# by doxygen. Using this option one can overrule certain style aspects. Doxygen
+# will copy the style sheet files to the output directory.
+# Note: The order of the extra style sheet files is of importance (e.g. the last
+# style sheet in the list overrules the setting of the previous ones in the
+# list).
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_STYLESHEET =
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
PDF_HYPERLINKS = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as
+# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX
+# files. Set this option to YES, to get a higher quality PDF documentation.
+#
+# See also section LATEX_CMD_NAME for selecting the engine.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
USE_PDFLATEX = YES
+
+# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
+# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
+# mode nothing is printed on the terminal, errors are scrolled as if is
+# hit at every error; missing files that TeX tries to input or request from
+# keyboard input (\read on a not open input stream) cause the job to abort,
+# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,
+# but there is no possibility of user interaction just like in batch mode,
+# SCROLL In scroll mode, TeX will stop only for missing files to input or if
+# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at
+# each error, asking for user intervention.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_BATCHMODE = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_HIDE_INDICES = NO
-LATEX_SOURCE_CODE = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# https://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
LATEX_BIB_STYLE = plain
-LATEX_TIMESTAMP = NO
+
+# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
+# path from which the emoji images will be read. If a relative path is entered,
+# it will be relative to the LATEX_OUTPUT directory. If left blank the
+# LATEX_OUTPUT directory will be used.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EMOJI_DIRECTORY =
+
#---------------------------------------------------------------------------
# Configuration options related to the RTF output
#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
RTF_HYPERLINKS = NO
-RTF_STYLESHEET_FILE =
-RTF_EXTENSIONS_FILE =
-RTF_SOURCE_CODE = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# configuration file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's configuration file. A template extensions file can be
+# generated using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE =
+
#---------------------------------------------------------------------------
# Configuration options related to the man page output
#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
MAN_EXTENSION = .3
-MAN_SUBDIR =
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR =
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
MAN_LINKS = NO
+
#---------------------------------------------------------------------------
# Configuration options related to the XML output
#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
XML_OUTPUT = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
XML_PROGRAMLISTING = YES
+
+# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include
+# namespace members in file scope as well, matching the HTML output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_NS_MEMB_FILE_SCOPE = NO
+
#---------------------------------------------------------------------------
# Configuration options related to the DOCBOOK output
#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
GENERATE_DOCBOOK = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
DOCBOOK_OUTPUT = docbook
-DOCBOOK_PROGRAMLISTING = NO
+
#---------------------------------------------------------------------------
# Configuration options for the AutoGen Definitions output
#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
+# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
+# the structure of the code including all documentation. Note that this feature
+# is still experimental and incomplete at the moment.
+# The default value is: NO.
+
GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to Sqlite3 output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3
+# database with symbols found by doxygen stored in tables.
+# The default value is: NO.
+
+GENERATE_SQLITE3 = NO
+
+# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
+# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
+# in front of it.
+# The default directory is: sqlite3.
+# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
+
+SQLITE3_OUTPUT = sqlite3
+
+# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db
+# database file will be recreated with each doxygen run. If set to NO, doxygen
+# will warn if an a database file is already found and not modify it.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
+
+SQLITE3_RECREATE_DB = YES
+
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO, the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
PERLMOD_PRETTY = YES
-PERLMOD_MAKEVAR_PREFIX =
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX =
+
#---------------------------------------------------------------------------
# Configuration options related to the preprocessor
#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names
+# in the source code. If set to NO, only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES, the include files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
SEARCH_INCLUDES = YES
-INCLUDE_PATH =
-INCLUDE_FILE_PATTERNS =
-PREDEFINED =
-EXPAND_AS_DEFINED =
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
+# RECURSIVE has no effect here.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
SKIP_FUNCTION_MACROS = YES
+
#---------------------------------------------------------------------------
# Configuration options related to external references
#---------------------------------------------------------------------------
-TAGFILES =
-GENERATE_TAGFILE =
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE =
+
+# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
+# will be listed in the class and namespace index. If set to NO, only the
+# inherited external classes will be listed.
+# The default value is: NO.
+
ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
+# in the topic index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
EXTERNAL_GROUPS = YES
+
+# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
EXTERNAL_PAGES = YES
-PERL_PATH = /usr/bin/perl
+
#---------------------------------------------------------------------------
-# Configuration options related to the dot tool
+# Configuration options related to diagram generator tools
#---------------------------------------------------------------------------
-CLASS_DIAGRAMS = YES
-MSCGEN_PATH =
-DIA_PATH =
+
+# If set to YES the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: NO.
+
HAVE_DOT = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOT_NUM_THREADS = 0
-DOT_FONTNAME = Helvetica
-DOT_FONTSIZE = 10
-DOT_FONTPATH =
+
+# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
+# subgraphs. When you want a differently looking font in the dot files that
+# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
+# For details please see Node,
+# Edge and Graph Attributes specification You need to make sure dot is able
+# to find the font, which can be done by putting it in a standard location or by
+# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font. Default graphviz fontsize is 14.
+# The default value is: fontname=Helvetica,fontsize=10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
+
+# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
+# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. Complete documentation about
+# arrows shapes.
+# The default value is: labelfontname=Helvetica,labelfontsize=10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
+
+# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
+# around nodes set 'shape=plain' or 'shape=plaintext' Shapes specification
+# The default value is: shape=box,height=0.2,width=0.4.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
+
+# You can set the path where dot can find font specified with fontname in
+# DOT_COMMON_ATTR and others dot attributes.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
+# generate a graph for each documented class showing the direct and indirect
+# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
+# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
+# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
+# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
+# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance
+# relations will be shown as texts / links.
+# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.
+# The default value is: YES.
+
CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes. Explicit enabling a collaboration graph,
+# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the
+# command \collaborationgraph. Disabling a collaboration graph can be
+# accomplished by means of the command \hidecollaborationgraph.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies. Explicit enabling a group
+# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means
+# of the command \groupgraph. Disabling a directory graph can be accomplished by
+# means of the command \hidegroupgraph. See also the chapter Grouping in the
+# manual.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
UML_LOOK = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag UML_LOOK is set to YES.
+
UML_LIMIT_NUM_FIELDS = 10
+
+# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and
+# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS
+# tag is set to YES, doxygen will add type and arguments for attributes and
+# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen
+# will not generate fields with class member information in the UML graphs. The
+# class diagrams will look similar to the default class diagrams but using UML
+# notation for the relationships.
+# Possible values are: NO, YES and NONE.
+# The default value is: NO.
+# This tag requires that the tag UML_LOOK is set to YES.
+
+DOT_UML_DETAILS = NO
+
+# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters
+# to display on a single line. If the actual line length exceeds this threshold
+# significantly it will wrapped across multiple lines. Some heuristics are apply
+# to avoid ugly line breaks.
+# Minimum value: 0, maximum value: 1000, default value: 17.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_WRAP_THRESHOLD = 17
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
TEMPLATE_RELATIONS = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,
+# can be accomplished by means of the command \includegraph. Disabling an
+# include graph can be accomplished by means of the command \hideincludegraph.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
INCLUDE_GRAPH = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set
+# to NO, can be accomplished by means of the command \includedbygraph. Disabling
+# an included by graph can be accomplished by means of the command
+# \hideincludedbygraph.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command. Disabling a call graph can be
+# accomplished by means of the command \hidecallgraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
CALL_GRAPH = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command. Disabling a caller graph can be
+# accomplished by means of the command \hidecallergraph.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories. Explicit enabling a directory graph, when
+# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command
+# \directorygraph. Disabling a directory graph can be accomplished by means of
+# the command \hidedirectorygraph.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DIRECTORY_GRAPH = YES
+
+# The DIR_GRAPH_MAX_DEPTH tag can be used to limit the maximum number of levels
+# of child directories generated in directory dependency graphs by dot.
+# Minimum value: 1, maximum value: 25, default value: 1.
+# This tag requires that the tag DIRECTORY_GRAPH is set to YES.
+
+DIR_GRAPH_MAX_DEPTH = 1
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. For an explanation of the image formats see the section
+# output formats in the documentation of the dot tool (Graphviz (see:
+# https://www.graphviz.org/)).
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
+# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
+# png:gdiplus:gdiplus.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOT_IMAGE_FORMAT = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
INTERACTIVE_SVG = NO
-DOT_PATH =
-DOTFILE_DIRS =
-MSCFILE_DIRS =
-DIAFILE_DIRS =
-PLANTUML_JAR_PATH =
-PLANTUML_INCLUDE_PATH =
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS =
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH =
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS =
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file or to the filename of jar file
+# to be used. If left blank, it is assumed PlantUML is not used or called during
+# a preprocessing step. Doxygen will generate a warning when it encounters a
+# \startuml command in this case and will not generate output for the diagram.
+
+PLANTUML_JAR_PATH =
+
+# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a
+# configuration file for plantuml.
+
+PLANTUML_CFG_FILE =
+
+# When using plantuml, the specified paths are searched for files specified by
+# the !include statement in a plantuml block.
+
+PLANTUML_INCLUDE_PATH =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
MAX_DOT_GRAPH_DEPTH = 0
-DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# Note: This tag requires that UML_LOOK isn't set, i.e. the doxygen internal
+# graphical representation for inheritance and collaboration diagrams is used.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate
+# files that are used to generate the various graphs.
+#
+# Note: This setting is not only used for dot files but also for msc temporary
+# files.
+# The default value is: YES.
+
DOT_CLEANUP = YES
+# You can define message sequence charts within doxygen comments using the \msc
+# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
+# use a built-in version of mscgen tool to produce the charts. Alternatively,
+# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
+# specifying prog as the value, doxygen will call the tool as prog -T
+# -o . The external tool should support
+# output file formats "png", "eps", "svg", and "ismap".
+
+MSCGEN_TOOL =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS =
diff --git a/doxygen/logo.png b/doxygen/logo.png
new file mode 100644
index 0000000..bd0925a
Binary files /dev/null and b/doxygen/logo.png differ
diff --git a/doxygen/mz-automation.ico b/doxygen/mz-automation.ico
deleted file mode 100644
index 8a0a94e..0000000
Binary files a/doxygen/mz-automation.ico and /dev/null differ
diff --git a/doxygen/mz_logo.ico b/doxygen/mz_logo.ico
new file mode 100644
index 0000000..81b7786
Binary files /dev/null and b/doxygen/mz_logo.ico differ
diff --git a/examples/cs101-master-balanced/Program.cs b/examples/cs101-master-balanced/Program.cs
index 62091d5..cc61c68 100644
--- a/examples/cs101-master-balanced/Program.cs
+++ b/examples/cs101-master-balanced/Program.cs
@@ -32,7 +32,7 @@ public static void Main (string [] args)
running = false;
};
- string portName = "/dev/ttyUSB1";
+ string portName = "COM1";
if (args.Length > 0)
portName = args [0];
diff --git a/examples/cs101-master-balanced/cs101-master-balanced.csproj b/examples/cs101-master-balanced/cs101-master-balanced.csproj
index e803db1..3f52daa 100644
--- a/examples/cs101-master-balanced/cs101-master-balanced.csproj
+++ b/examples/cs101-master-balanced/cs101-master-balanced.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs101_master_balanced
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs101-master-tcp/Program.cs b/examples/cs101-master-tcp/Program.cs
index c6b5040..c4dcd8f 100644
--- a/examples/cs101-master-tcp/Program.cs
+++ b/examples/cs101-master-tcp/Program.cs
@@ -8,91 +8,91 @@
namespace cs101_master_tcp
{
- class MainClass
- {
+ class MainClass
+ {
- private static bool rcvdRawMessageHandler (object parameter, byte[] message, int messageSize)
- {
- Console.WriteLine ("RECV " + BitConverter.ToString (message, 0, messageSize));
+ private static bool rcvdRawMessageHandler (object parameter, byte[] message, int messageSize)
+ {
+ Console.WriteLine ("RECV " + BitConverter.ToString (message, 0, messageSize));
- return true;
- }
+ return true;
+ }
- private static void linkLayerStateChanged (object parameter, int address, lib60870.linklayer.LinkLayerState newState)
- {
- Console.WriteLine ("LL state event: " + newState.ToString ());
- }
+ private static void linkLayerStateChanged (object parameter, int address, lib60870.linklayer.LinkLayerState newState)
+ {
+ Console.WriteLine ("LL state event: " + newState.ToString ());
+ }
- private static bool asduReceivedHandler(object parameter, int address, ASDU asdu)
- {
- Console.WriteLine (asdu.ToString ());
+ private static bool asduReceivedHandler(object parameter, int address, ASDU asdu)
+ {
+ Console.WriteLine (asdu.ToString ());
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- // use Ctrl-C to stop the programm
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ // use Ctrl-C to stop the programm
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- string hostname = "127.0.0.1";
- int tcpPort = 2404;
+ string hostname = "127.0.0.1";
+ int tcpPort = 2404;
- if (args.Length > 0)
- hostname = args [0];
+ if (args.Length > 0)
+ hostname = args [0];
- if (args.Length > 1)
- int.TryParse (args [1], out tcpPort);
+ if (args.Length > 1)
+ int.TryParse (args [1], out tcpPort);
- // Setup virtual serial port
- TcpClientVirtualSerialPort port = new TcpClientVirtualSerialPort(hostname, tcpPort);
- port.DebugOutput = false;
- port.Start ();
+ // Setup virtual serial port
+ TcpClientVirtualSerialPort port = new TcpClientVirtualSerialPort(hostname, tcpPort);
+ port.DebugOutput = false;
+ port.Start ();
- // Setup balanced CS101 master
- LinkLayerParameters llParameters = new LinkLayerParameters();
- llParameters.AddressLength = 1;
- llParameters.UseSingleCharACK = true;
+ // Setup balanced CS101 master
+ LinkLayerParameters llParameters = new LinkLayerParameters();
+ llParameters.AddressLength = 1;
+ llParameters.UseSingleCharACK = true;
- CS101Master master = new CS101Master (port, LinkLayerMode.BALANCED, llParameters);
- master.DebugOutput = false;
- master.OwnAddress = 1;
+ CS101Master master = new CS101Master (port, LinkLayerMode.BALANCED, llParameters);
+ master.DebugOutput = false;
+ master.OwnAddress = 1;
master.SlaveAddress = 3;
- master.SetASDUReceivedHandler (asduReceivedHandler, null);
- master.SetLinkLayerStateChangedHandler (linkLayerStateChanged, null);
- master.SetReceivedRawMessageHandler (rcvdRawMessageHandler, null);
+ master.SetASDUReceivedHandler (asduReceivedHandler, null);
+ master.SetLinkLayerStateChangedHandler (linkLayerStateChanged, null);
+ master.SetReceivedRawMessageHandler (rcvdRawMessageHandler, null);
- long lastTimestamp = SystemUtils.currentTimeMillis ();
+ long lastTimestamp = SystemUtils.currentTimeMillis ();
- // This will start a separate thread!
- // alternativley you can you master.Run() inside the loop
- master.Start ();
+ // This will start a separate thread!
+ // alternativley you can you master.Run() inside the loop
+ master.Start ();
- while (running) {
+ while (running) {
- if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 5000) {
+ if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 5000) {
- lastTimestamp = SystemUtils.currentTimeMillis ();
+ lastTimestamp = SystemUtils.currentTimeMillis ();
- if (master.GetLinkLayerState () == lib60870.linklayer.LinkLayerState.AVAILABLE) {
- master.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, 20);
- } else {
- Console.WriteLine ("Link layer: " + master.GetLinkLayerState ().ToString ());
- }
- }
+ if (master.GetLinkLayerState () == lib60870.linklayer.LinkLayerState.AVAILABLE) {
+ master.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, 20);
+ } else {
+ Console.WriteLine ("Link layer: " + master.GetLinkLayerState ().ToString ());
+ }
+ }
- Thread.Sleep (100);
- }
+ Thread.Sleep (100);
+ }
- master.Stop ();
+ master.Stop ();
- port.Stop ();
- }
- }
+ port.Stop ();
+ }
+ }
}
diff --git a/examples/cs101-master-tcp/cs101-master-tcp.csproj b/examples/cs101-master-tcp/cs101-master-tcp.csproj
index 1fe5948..90960a1 100644
--- a/examples/cs101-master-tcp/cs101-master-tcp.csproj
+++ b/examples/cs101-master-tcp/cs101-master-tcp.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs101_master_tcp
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs101-master-unbalanced/Program.cs b/examples/cs101-master-unbalanced/Program.cs
index 0601037..80dcba9 100644
--- a/examples/cs101-master-unbalanced/Program.cs
+++ b/examples/cs101-master-unbalanced/Program.cs
@@ -8,185 +8,185 @@
namespace cs101_master_unbalanced
{
- class MainClass
- {
- private static bool asduReceivedHandler(object parameter, int slaveAddress, ASDU asdu)
- {
- Console.WriteLine ("Slave: {0} - {1}", slaveAddress, asdu.ToString ());
+ class MainClass
+ {
+ private static bool asduReceivedHandler(object parameter, int slaveAddress, ASDU asdu)
+ {
+ Console.WriteLine ("Slave: {0} - {1}", slaveAddress, asdu.ToString ());
- if (asdu.TypeId == TypeID.M_SP_NA_1) {
+ if (asdu.TypeId == TypeID.M_SP_NA_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointInformation)asdu.GetElement (i);
+ var val = (SinglePointInformation)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- }
- }
- else if (asdu.TypeId == TypeID.M_ME_TE_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ }
+ }
+ else if (asdu.TypeId == TypeID.M_ME_TE_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
+ var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- Console.WriteLine (" " + msv.Timestamp.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ Console.WriteLine (" " + msv.Timestamp.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
+ var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- Console.WriteLine (" " + val.Timestamp.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ Console.WriteLine (" " + val.Timestamp.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShort)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShort)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaled)asdu.GetElement (i);
+ var msv = (MeasuredValueScaled)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ }
- }
+ }
- return true;
- }
+ return true;
+ }
- private static void linkLayerStateChanged (object parameter, int address, lib60870.linklayer.LinkLayerState newState)
- {
- Console.WriteLine ("LL state event {0} for slave {1}", newState.ToString (), address);
- }
+ private static void linkLayerStateChanged (object parameter, int address, lib60870.linklayer.LinkLayerState newState)
+ {
+ Console.WriteLine ("LL state event {0} for slave {1}", newState.ToString (), address);
+ }
- public class Receiver : IFileReceiver
- {
- public void Finished(FileErrorCode result)
- {
- Console.WriteLine ("File download finished - code: " + result.ToString ());
- }
-
- public void SegmentReceived(byte sectionName, int offset, int size, byte[] data)
- {
- Console.WriteLine ("File segment - sectionName: {0} offset: {1} size: {2}", sectionName, offset, size);
- }
- }
+ public class Receiver : IFileReceiver
+ {
+ public void Finished(FileErrorCode result)
+ {
+ Console.WriteLine ("File download finished - code: " + result.ToString ());
+ }
+
+ public void SegmentReceived(byte sectionName, int offset, int size, byte[] data)
+ {
+ Console.WriteLine ("File segment - sectionName: {0} offset: {1} size: {2}", sectionName, offset, size);
+ }
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- // use Ctrl-C to stop the programm
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ // use Ctrl-C to stop the programm
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- string portName = "/dev/ttyUSB0";
+ string portName = "COM1";
- if (args.Length > 0)
- portName = args [0];
+ if (args.Length > 0)
+ portName = args [0];
- SerialPort port = new SerialPort ();
+ SerialPort port = new SerialPort ();
- port.PortName = portName;
- port.BaudRate = 9600;
- port.Parity = Parity.Even;
- port.Handshake = Handshake.None;
- port.Open ();
- port.DiscardInBuffer ();
+ port.PortName = portName;
+ port.BaudRate = 9600;
+ port.Parity = Parity.Even;
+ port.Handshake = Handshake.None;
+ port.Open ();
+ port.DiscardInBuffer ();
- /* set link layer address length */
- LinkLayerParameters llParameters = new LinkLayerParameters ();
- llParameters.AddressLength = 1;
+ /* set link layer address length */
+ LinkLayerParameters llParameters = new LinkLayerParameters ();
+ llParameters.AddressLength = 1;
- /* unbalanced mode allows multiple slaves on a single serial line */
- CS101Master master = new CS101Master(port, LinkLayerMode.UNBALANCED, llParameters);
- master.DebugOutput = false;
- master.SetASDUReceivedHandler (asduReceivedHandler, null);
+ /* unbalanced mode allows multiple slaves on a single serial line */
+ CS101Master master = new CS101Master(port, LinkLayerMode.UNBALANCED, llParameters);
+ master.DebugOutput = false;
+ master.SetASDUReceivedHandler (asduReceivedHandler, null);
- master.SetLinkLayerStateChangedHandler (linkLayerStateChanged, null);
+ master.SetLinkLayerStateChangedHandler (linkLayerStateChanged, null);
- master.AddSlave (1);
- master.AddSlave (2);
- master.AddSlave (3);
+ master.AddSlave (1);
+ master.AddSlave (2);
+ master.AddSlave (3);
- long lastTimestamp = SystemUtils.currentTimeMillis ();
+ long lastTimestamp = SystemUtils.currentTimeMillis ();
- master.SlaveAddress = 1;
- //master.GetFile (1, 30000, NameOfFile.TRANSPARENT_FILE, new Receiver ());
+ master.SlaveAddress = 1;
+ //master.GetFile (1, 30000, NameOfFile.TRANSPARENT_FILE, new Receiver ());
- while (running) {
+ while (running) {
- master.PollSingleSlave(1);
+ master.PollSingleSlave(1);
- master.Run ();
+ master.Run ();
- master.PollSingleSlave(2);
+ master.PollSingleSlave(2);
- master.Run ();
+ master.Run ();
- master.PollSingleSlave(3);
+ master.PollSingleSlave(3);
- master.Run ();
+ master.Run ();
- if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 20000) {
+ if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 20000) {
- lastTimestamp = SystemUtils.currentTimeMillis ();
+ lastTimestamp = SystemUtils.currentTimeMillis ();
- try {
- master.SlaveAddress = 1;
- master.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, 20);
- }
- catch (LinkLayerBusyException) {
- Console.WriteLine ("Slave 1: Link layer busy or not ready");
- }
+ try {
+ master.SlaveAddress = 1;
+ master.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, 20);
+ }
+ catch (LinkLayerBusyException) {
+ Console.WriteLine ("Slave 1: Link layer busy or not ready");
+ }
- try {
- master.SlaveAddress = 2;
- master.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 2, 20);
- }
- catch (LinkLayerBusyException) {
- Console.WriteLine ("Slave 2: Link layer busy or not ready");
- }
-
- try {
- master.SlaveAddress = 3;
- master.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 3, 20);
- }
- catch (LinkLayerBusyException) {
- Console.WriteLine ("Slave 2: Link layer busy or not ready");
- }
- }
-
- }
-
- port.Close ();
- }
- }
+ try {
+ master.SlaveAddress = 2;
+ master.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 2, 20);
+ }
+ catch (LinkLayerBusyException) {
+ Console.WriteLine ("Slave 2: Link layer busy or not ready");
+ }
+
+ try {
+ master.SlaveAddress = 3;
+ master.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 3, 20);
+ }
+ catch (LinkLayerBusyException) {
+ Console.WriteLine ("Slave 2: Link layer busy or not ready");
+ }
+ }
+
+ }
+
+ port.Close ();
+ }
+ }
}
diff --git a/examples/cs101-master-unbalanced/cs101-master-unbalanced.csproj b/examples/cs101-master-unbalanced/cs101-master-unbalanced.csproj
index 1b1f522..a3aed90 100644
--- a/examples/cs101-master-unbalanced/cs101-master-unbalanced.csproj
+++ b/examples/cs101-master-unbalanced/cs101-master-unbalanced.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs101_master_unbalanced
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs101-slave-balanced/Program.cs b/examples/cs101-slave-balanced/Program.cs
index 8456176..6b4d995 100644
--- a/examples/cs101-slave-balanced/Program.cs
+++ b/examples/cs101-slave-balanced/Program.cs
@@ -8,144 +8,144 @@
namespace cs101_slave_balanced
{
- public class CS101TestSlave {
+ public class CS101TestSlave {
- private static bool myInterrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
- {
- Console.WriteLine("Interrogation for group " + qoi);
+ private static bool myInterrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
+ {
+ Console.WriteLine("Interrogation for group " + qoi);
- connection.SendACT_CON (asdu, false);
+ connection.SendACT_CON (asdu, false);
- // send information objects
- ASDU newAsdu = new ASDU(connection.GetApplicationLayerParameters(), CauseOfTransmission.INTERROGATED_BY_STATION,
- false, false, 2, 1, false);
+ // send information objects
+ ASDU newAsdu = new ASDU(connection.GetApplicationLayerParameters(), CauseOfTransmission.INTERROGATED_BY_STATION,
+ false, false, 2, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- // send sequence of information objects
- newAsdu = new ASDU (connection.GetApplicationLayerParameters(), CauseOfTransmission.INTERROGATED_BY_STATION,
- false, false, 2, 1, true);
+ // send sequence of information objects
+ newAsdu = new ASDU (connection.GetApplicationLayerParameters(), CauseOfTransmission.INTERROGATED_BY_STATION,
+ false, false, 2, 1, true);
- newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- connection.SendACT_TERM (asdu);
+ connection.SendACT_TERM (asdu);
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- // use Ctrl-C to stop the programm
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ // use Ctrl-C to stop the programm
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- string portName = "COM5"; // e.g. "COM3" on windows
+ string portName = "COM2"; // e.g. "COM3" on windows
- if (args.Length > 0)
- portName = args [0];
+ if (args.Length > 0)
+ portName = args [0];
- SerialPort port = new SerialPort ();
+ SerialPort port = new SerialPort ();
- port.PortName = portName;
- port.BaudRate = 9600;
- port.Parity = Parity.Even;
- port.Handshake = Handshake.None;
- port.Open ();
+ port.PortName = portName;
+ port.BaudRate = 9600;
+ port.Parity = Parity.Even;
+ port.Handshake = Handshake.None;
+ port.Open ();
- LinkLayerParameters llParameters = new LinkLayerParameters ();
- llParameters.AddressLength = 1;
- llParameters.TimeoutForACK = 500;
- llParameters.UseSingleCharACK = false;
+ LinkLayerParameters llParameters = new LinkLayerParameters ();
+ llParameters.AddressLength = 1;
+ llParameters.TimeoutForACK = 500;
+ llParameters.UseSingleCharACK = false;
- CS101Slave slave = new CS101Slave (port, llParameters);
- slave.DebugOutput = true;
- slave.LinkLayerAddress = 2;
- slave.LinkLayerAddressOtherStation = 1;
+ CS101Slave slave = new CS101Slave (port, llParameters);
+ slave.DebugOutput = true;
+ slave.LinkLayerAddress = 2;
+ slave.LinkLayerAddressOtherStation = 1;
- slave.LinkLayerMode = lib60870.linklayer.LinkLayerMode.BALANCED;
+ slave.LinkLayerMode = lib60870.linklayer.LinkLayerMode.BALANCED;
- slave.SetInterrogationHandler (myInterrogationHandler, null);
+ slave.SetInterrogationHandler (myInterrogationHandler, null);
- slave.SetUserDataQueueSizes (50, 20);
+ slave.SetUserDataQueueSizes (50, 20);
- ASDU asdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
- asdu.AddInformationObject (new StepPositionInformation (301, 1, false, new QualityDescriptor()));
- slave.EnqueueUserDataClass1 (asdu);
+ ASDU asdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ asdu.AddInformationObject (new StepPositionInformation (301, 1, false, new QualityDescriptor()));
+ slave.EnqueueUserDataClass1 (asdu);
- long lastTimestamp = SystemUtils.currentTimeMillis ();
- Int16 measuredValue = 0;
+ long lastTimestamp = SystemUtils.currentTimeMillis ();
+ Int16 measuredValue = 0;
- TransparentFile file = new TransparentFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ TransparentFile file = new TransparentFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
- byte[] fileData = new byte[1025];
+ byte[] fileData = new byte[1025];
- for (int i = 0; i < 1025; i++)
- fileData [i] = (byte)(i + 1);
+ for (int i = 0; i < 1025; i++)
+ fileData [i] = (byte)(i + 1);
- file.AddSection (fileData);
+ file.AddSection (fileData);
- slave.GetAvailableFiles().AddFile (file);
+ slave.GetAvailableFiles().AddFile (file);
- while (running) {
+ while (running) {
- slave.Run (); // call the protocol stack
+ slave.Run (); // call the protocol stack
- if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 5000) {
+ if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 5000) {
- lastTimestamp = SystemUtils.currentTimeMillis ();
+ lastTimestamp = SystemUtils.currentTimeMillis ();
- ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (110, measuredValue, new QualityDescriptor ()));
- slave.EnqueueUserDataClass2 (newAsdu);
+ ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ newAsdu.AddInformationObject (new MeasuredValueScaled (110, measuredValue, new QualityDescriptor ()));
+ slave.EnqueueUserDataClass2 (newAsdu);
- measuredValue++;
- }
+ measuredValue++;
+ }
- if (Console.KeyAvailable) {
+ if (Console.KeyAvailable) {
- ConsoleKeyInfo keyInfo = Console.ReadKey ();
+ ConsoleKeyInfo keyInfo = Console.ReadKey ();
- if (keyInfo.KeyChar == 't') {
- slave.SendLinkLayerTestFunction ();
- }
- else {
- Console.WriteLine ("Send spontaneous message");
+ if (keyInfo.KeyChar == 't') {
+ slave.SendLinkLayerTestFunction ();
+ }
+ else {
+ Console.WriteLine ("Send spontaneous message");
- bool value = false;
+ bool value = false;
- if (keyInfo.KeyChar == 's') {
- value = true;
- }
+ if (keyInfo.KeyChar == 's') {
+ value = true;
+ }
- ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
- newAsdu.AddInformationObject (new SinglePointInformation (100, value, new QualityDescriptor ()));
+ ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ newAsdu.AddInformationObject (new SinglePointInformation (100, value, new QualityDescriptor ()));
- slave.EnqueueUserDataClass1 (newAsdu);
- }
- }
+ slave.EnqueueUserDataClass1 (newAsdu);
+ }
+ }
Thread.Sleep(1);
- }
+ }
- }
- }
+ }
+ }
}
diff --git a/examples/cs101-slave-balanced/cs101-slave-balanced.csproj b/examples/cs101-slave-balanced/cs101-slave-balanced.csproj
index 4db8969..d7c0dc9 100644
--- a/examples/cs101-slave-balanced/cs101-slave-balanced.csproj
+++ b/examples/cs101-slave-balanced/cs101-slave-balanced.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs101_slave_balanced
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs101-slave-tcp/Program.cs b/examples/cs101-slave-tcp/Program.cs
index fc09484..9df01fe 100644
--- a/examples/cs101-slave-tcp/Program.cs
+++ b/examples/cs101-slave-tcp/Program.cs
@@ -8,139 +8,139 @@
namespace cs101_slave_tcp
{
- public class CS101TestSlave
- {
+ public class CS101TestSlave
+ {
- private static bool myInterrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
- {
- Console.WriteLine("Interrogation for group " + qoi);
+ private static bool myInterrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
+ {
+ Console.WriteLine("Interrogation for group " + qoi);
- connection.SendACT_CON (asdu, false);
+ connection.SendACT_CON (asdu, false);
- // send information objects
- ASDU newAsdu = new ASDU(connection.GetApplicationLayerParameters(), CauseOfTransmission.INTERROGATED_BY_STATION,
- false, false, 2, 1, false);
+ // send information objects
+ ASDU newAsdu = new ASDU(connection.GetApplicationLayerParameters(), CauseOfTransmission.INTERROGATED_BY_STATION,
+ false, false, 2, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- // send sequence of information objects
- newAsdu = new ASDU (connection.GetApplicationLayerParameters(), CauseOfTransmission.INTERROGATED_BY_STATION,
- false, false, 2, 1, true);
+ // send sequence of information objects
+ newAsdu = new ASDU (connection.GetApplicationLayerParameters(), CauseOfTransmission.INTERROGATED_BY_STATION,
+ false, false, 2, 1, true);
- newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- connection.SendACT_TERM (asdu);
+ connection.SendACT_TERM (asdu);
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- // use Ctrl-C to stop the programm
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
-
- LinkLayerParameters llParameters = new LinkLayerParameters ();
- llParameters.AddressLength = 1;
- llParameters.TimeoutForACK = 500;
- llParameters.UseSingleCharACK = true;
+ // use Ctrl-C to stop the programm
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
+
+ LinkLayerParameters llParameters = new LinkLayerParameters ();
+ llParameters.AddressLength = 1;
+ llParameters.TimeoutForACK = 500;
+ llParameters.UseSingleCharACK = true;
- TcpServerVirtualSerialPort port = new TcpServerVirtualSerialPort ();
- //TcpClientVirtualSerialPort port = new TcpClientVirtualSerialPort("192.168.2.9", 2404);
+ TcpServerVirtualSerialPort port = new TcpServerVirtualSerialPort ();
+ //TcpClientVirtualSerialPort port = new TcpClientVirtualSerialPort("192.168.2.9", 2404);
- port.DebugOutput = true;
- port.Start ();
+ port.DebugOutput = true;
+ port.Start ();
- CS101Slave slave = new CS101Slave (port, llParameters);
- slave.DebugOutput = true;
- slave.LinkLayerAddress = 3;
- slave.LinkLayerAddressOtherStation = 1;
+ CS101Slave slave = new CS101Slave (port, llParameters);
+ slave.DebugOutput = true;
+ slave.LinkLayerAddress = 3;
+ slave.LinkLayerAddressOtherStation = 1;
- slave.LinkLayerMode = lib60870.linklayer.LinkLayerMode.BALANCED;
+ slave.LinkLayerMode = lib60870.linklayer.LinkLayerMode.BALANCED;
- slave.SetInterrogationHandler (myInterrogationHandler, null);
+ slave.SetInterrogationHandler (myInterrogationHandler, null);
- slave.SetUserDataQueueSizes (50, 20);
+ slave.SetUserDataQueueSizes (50, 20);
- ASDU asdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
- asdu.AddInformationObject (new StepPositionInformation (301, 1, false, new QualityDescriptor()));
- slave.EnqueueUserDataClass1 (asdu);
+ ASDU asdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ asdu.AddInformationObject (new StepPositionInformation (301, 1, false, new QualityDescriptor()));
+ slave.EnqueueUserDataClass1 (asdu);
- long lastTimestamp = SystemUtils.currentTimeMillis ();
- Int16 measuredValue = 0;
+ long lastTimestamp = SystemUtils.currentTimeMillis ();
+ Int16 measuredValue = 0;
- TransparentFile file = new TransparentFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ TransparentFile file = new TransparentFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
- byte[] fileData = new byte[1025];
+ byte[] fileData = new byte[1025];
- for (int i = 0; i < 1025; i++)
- fileData [i] = (byte)(i + 1);
+ for (int i = 0; i < 1025; i++)
+ fileData [i] = (byte)(i + 1);
- file.AddSection (fileData);
+ file.AddSection (fileData);
- slave.GetAvailableFiles().AddFile (file);
+ slave.GetAvailableFiles().AddFile (file);
- while (running) {
+ while (running) {
- slave.Run (); // call the protocol stack
+ slave.Run (); // call the protocol stack
- if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 5000) {
+ if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 5000) {
- lastTimestamp = SystemUtils.currentTimeMillis ();
+ lastTimestamp = SystemUtils.currentTimeMillis ();
- ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (110, measuredValue, new QualityDescriptor ()));
- slave.EnqueueUserDataClass2 (newAsdu);
+ ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ newAsdu.AddInformationObject (new MeasuredValueScaled (110, measuredValue, new QualityDescriptor ()));
+ slave.EnqueueUserDataClass2 (newAsdu);
- measuredValue++;
- }
+ measuredValue++;
+ }
- if (Console.KeyAvailable) {
+ if (Console.KeyAvailable) {
- ConsoleKeyInfo keyInfo = Console.ReadKey ();
+ ConsoleKeyInfo keyInfo = Console.ReadKey ();
- if (keyInfo.KeyChar == 't') {
- slave.SendLinkLayerTestFunction ();
- }
- else {
- Console.WriteLine ("Send spontaneous message");
+ if (keyInfo.KeyChar == 't') {
+ slave.SendLinkLayerTestFunction ();
+ }
+ else {
+ Console.WriteLine ("Send spontaneous message");
- bool value = false;
+ bool value = false;
- if (keyInfo.KeyChar == 's') {
- value = true;
- }
+ if (keyInfo.KeyChar == 's') {
+ value = true;
+ }
- ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
- newAsdu.AddInformationObject (new SinglePointInformation (100, value, new QualityDescriptor ()));
+ ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ newAsdu.AddInformationObject (new SinglePointInformation (100, value, new QualityDescriptor ()));
- slave.EnqueueUserDataClass1 (newAsdu);
- }
- }
+ slave.EnqueueUserDataClass1 (newAsdu);
+ }
+ }
Thread.Sleep(1);
- }
+ }
- port.Stop ();
- }
- }
+ port.Stop ();
+ }
+ }
}
diff --git a/examples/cs101-slave-tcp/cs101-slave-tcp.csproj b/examples/cs101-slave-tcp/cs101-slave-tcp.csproj
index 246fa55..be725d7 100644
--- a/examples/cs101-slave-tcp/cs101-slave-tcp.csproj
+++ b/examples/cs101-slave-tcp/cs101-slave-tcp.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs101_slave_tcp
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs101-slave-unbalanced/Program.cs b/examples/cs101-slave-unbalanced/Program.cs
index c0ff4d4..22ac900 100644
--- a/examples/cs101-slave-unbalanced/Program.cs
+++ b/examples/cs101-slave-unbalanced/Program.cs
@@ -7,151 +7,151 @@
namespace cs101_slave_unbalanced {
- public class CS101TestSlave {
+ public class CS101TestSlave {
- private static int slaveAddress = 1;
+ private static int slaveAddress = 1;
- private static bool myInterrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
- {
-
- if (asdu.Ca == slaveAddress) {
+ private static bool myInterrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
+ {
+
+ if (asdu.Ca == slaveAddress) {
- Console.WriteLine("Interrogation for group " + qoi);
+ Console.WriteLine("Interrogation for group " + qoi);
- connection.SendACT_CON (asdu, false);
+ connection.SendACT_CON (asdu, false);
- // send information objects
- ASDU newAsdu = new ASDU (connection.GetApplicationLayerParameters (), CauseOfTransmission.INTERROGATED_BY_STATION,
- false, false, 2, slaveAddress, false);
+ // send information objects
+ ASDU newAsdu = new ASDU (connection.GetApplicationLayerParameters (), CauseOfTransmission.INTERROGATED_BY_STATION,
+ false, false, 2, slaveAddress, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- // send sequence of information objects
- newAsdu = new ASDU (connection.GetApplicationLayerParameters (), CauseOfTransmission.INTERROGATED_BY_STATION,
- false, false, 2, slaveAddress, true);
+ // send sequence of information objects
+ newAsdu = new ASDU (connection.GetApplicationLayerParameters (), CauseOfTransmission.INTERROGATED_BY_STATION,
+ false, false, 2, slaveAddress, true);
- newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- connection.SendACT_TERM (asdu);
+ connection.SendACT_TERM (asdu);
- }
+ }
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- // use Ctrl-C to stop the programm
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ // use Ctrl-C to stop the programm
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- string portName = "COM12";
+ string portName = "COM2";
- if (args.Length > 0)
- portName = args [0];
+ if (args.Length > 0)
+ portName = args [0];
- if (args.Length > 1)
- int.TryParse (args [1], out slaveAddress);
+ if (args.Length > 1)
+ int.TryParse (args [1], out slaveAddress);
- SerialPort port = new SerialPort ();
+ SerialPort port = new SerialPort ();
- port.PortName = portName;
- port.BaudRate = 9600;
- port.Parity = Parity.Even;
- port.Handshake = Handshake.None;
- port.Open ();
+ port.PortName = portName;
+ port.BaudRate = 9600;
+ port.Parity = Parity.Even;
+ port.Handshake = Handshake.None;
+ port.Open ();
- LinkLayerParameters llParameters = new LinkLayerParameters ();
- llParameters.AddressLength = 1;
- llParameters.UseSingleCharACK = true;
+ LinkLayerParameters llParameters = new LinkLayerParameters ();
+ llParameters.AddressLength = 1;
+ llParameters.UseSingleCharACK = true;
- CS101Slave slave = new CS101Slave (port, llParameters);
- slave.DebugOutput = true;
- slave.LinkLayerAddress = slaveAddress;
+ CS101Slave slave = new CS101Slave (port, llParameters);
+ slave.DebugOutput = true;
+ slave.LinkLayerAddress = slaveAddress;
- // for using the slave in balanced mode simple change the mode here:
- slave.LinkLayerMode = lib60870.linklayer.LinkLayerMode.UNBALANCED;
+ // for using the slave in balanced mode simple change the mode here:
+ slave.LinkLayerMode = lib60870.linklayer.LinkLayerMode.UNBALANCED;
- slave.SetInterrogationHandler (myInterrogationHandler, null);
+ slave.SetInterrogationHandler (myInterrogationHandler, null);
- slave.SetUserDataQueueSizes (50, 20);
+ slave.SetUserDataQueueSizes (50, 20);
- ASDU asdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
- asdu.AddInformationObject (new StepPositionInformation (301, 1, false, new QualityDescriptor()));
- slave.EnqueueUserDataClass1 (asdu);
+ ASDU asdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ asdu.AddInformationObject (new StepPositionInformation (301, 1, false, new QualityDescriptor()));
+ slave.EnqueueUserDataClass1 (asdu);
- long lastTimestamp = SystemUtils.currentTimeMillis ();
- Int16 measuredValue = 0;
+ long lastTimestamp = SystemUtils.currentTimeMillis ();
+ Int16 measuredValue = 0;
- TransparentFile file = new TransparentFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ TransparentFile file = new TransparentFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
- byte[] fileData = new byte[1025];
+ byte[] fileData = new byte[1025];
- for (int i = 0; i < 1025; i++)
- fileData [i] = (byte)(i + 1);
+ for (int i = 0; i < 1025; i++)
+ fileData [i] = (byte)(i + 1);
- file.AddSection (fileData);
+ file.AddSection (fileData);
- slave.GetAvailableFiles().AddFile (file);
+ slave.GetAvailableFiles().AddFile (file);
- while (running) {
+ while (running) {
- slave.Run (); // call the protocol stack
+ slave.Run (); // call the protocol stack
- if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 5000) {
+ if ((SystemUtils.currentTimeMillis() - lastTimestamp) >= 5000) {
- lastTimestamp = SystemUtils.currentTimeMillis ();
+ lastTimestamp = SystemUtils.currentTimeMillis ();
- ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.PERIODIC, false, false, 0, slaveAddress, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (110, measuredValue, new QualityDescriptor ()));
- slave.EnqueueUserDataClass2 (newAsdu);
+ ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.PERIODIC, false, false, 0, slaveAddress, false);
+ newAsdu.AddInformationObject (new MeasuredValueScaled (110, measuredValue, new QualityDescriptor ()));
+ slave.EnqueueUserDataClass2 (newAsdu);
- measuredValue++;
- }
+ measuredValue++;
+ }
- if (Console.KeyAvailable) {
+ if (Console.KeyAvailable) {
- ConsoleKeyInfo keyInfo = Console.ReadKey ();
+ ConsoleKeyInfo keyInfo = Console.ReadKey ();
- if (keyInfo.KeyChar == 't') {
- slave.SendLinkLayerTestFunction ();
- }
- else {
- Console.WriteLine ("Send spontaneous message");
+ if (keyInfo.KeyChar == 't') {
+ slave.SendLinkLayerTestFunction ();
+ }
+ else {
+ Console.WriteLine ("Send spontaneous message");
- bool value = false;
+ bool value = false;
- if (keyInfo.KeyChar == 's') {
- value = true;
- }
+ if (keyInfo.KeyChar == 's') {
+ value = true;
+ }
- ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, slaveAddress, false);
- newAsdu.AddInformationObject (new SinglePointInformation (100, value, new QualityDescriptor ()));
+ ASDU newAsdu = new ASDU (slave.Parameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, slaveAddress, false);
+ newAsdu.AddInformationObject (new SinglePointInformation (100, value, new QualityDescriptor ()));
- slave.EnqueueUserDataClass1 (newAsdu);
- }
- }
- }
+ slave.EnqueueUserDataClass1 (newAsdu);
+ }
+ }
+ }
- }
- }
+ }
+ }
}
diff --git a/examples/cs101-slave-unbalanced/cs101-slave-unbalanced.csproj b/examples/cs101-slave-unbalanced/cs101-slave-unbalanced.csproj
index 7e6f4e1..f861909 100644
--- a/examples/cs101-slave-unbalanced/cs101-slave-unbalanced.csproj
+++ b/examples/cs101-slave-unbalanced/cs101-slave-unbalanced.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs101_slave_unbalanced
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-client-file-upload/Program.cs b/examples/cs104-client-file-upload/Program.cs
index 23cb296..97e4c86 100644
--- a/examples/cs104-client-file-upload/Program.cs
+++ b/examples/cs104-client-file-upload/Program.cs
@@ -39,7 +39,7 @@ public static void Main (string [] args)
int fileIoa = 30001;
if (args.Length == 0) {
- Console.WriteLine ("upload [CA] [IOA]");
+ Console.WriteLine ("upload "+ hostname + " ["+ fileCa.ToString() + "] ["+ fileIoa.ToString()+ "]");
} else {
for (int i = 0; i < args.Length; i++) {
if (i == 0)
@@ -73,7 +73,7 @@ public static void Main (string [] args)
file.AddSection (fileData);
}
- con.SendFile (fileCa, fileIoa, (NameOfFile) 12, file);
+ con.SendFile (fileCa, fileIoa, NameOfFile.TRANSPARENT_FILE, file);
file.WaitUntilTransferIsComplete ();
diff --git a/examples/cs104-client-file-upload/cs104-client-file-upload.csproj b/examples/cs104-client-file-upload/cs104-client-file-upload.csproj
index 66a9aa5..a2bd290 100644
--- a/examples/cs104-client-file-upload/cs104-client-file-upload.csproj
+++ b/examples/cs104-client-file-upload/cs104-client-file-upload.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_client_file_upload
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-client1/Program.cs b/examples/cs104-client1/Program.cs
index ff0d697..048944d 100644
--- a/examples/cs104-client1/Program.cs
+++ b/examples/cs104-client1/Program.cs
@@ -7,193 +7,193 @@
namespace cs104_client1
{
- class MainClass
- {
+ class MainClass
+ {
- private static void ConnectionHandler (object parameter, ConnectionEvent connectionEvent)
- {
- switch (connectionEvent) {
- case ConnectionEvent.OPENED:
- Console.WriteLine ("Connected");
- break;
- case ConnectionEvent.CLOSED:
- Console.WriteLine ("Connection closed");
- break;
- case ConnectionEvent.STARTDT_CON_RECEIVED:
- Console.WriteLine ("STARTDT CON received");
- break;
- case ConnectionEvent.STOPDT_CON_RECEIVED:
- Console.WriteLine ("STOPDT CON received");
- break;
- }
- }
+ private static void ConnectionHandler (object parameter, ConnectionEvent connectionEvent)
+ {
+ switch (connectionEvent) {
+ case ConnectionEvent.OPENED:
+ Console.WriteLine ("Connected");
+ break;
+ case ConnectionEvent.CLOSED:
+ Console.WriteLine ("Connection closed");
+ break;
+ case ConnectionEvent.STARTDT_CON_RECEIVED:
+ Console.WriteLine ("STARTDT CON received");
+ break;
+ case ConnectionEvent.STOPDT_CON_RECEIVED:
+ Console.WriteLine ("STOPDT CON received");
+ break;
+ }
+ }
- private static bool asduReceivedHandler(object parameter, ASDU asdu)
- {
- Console.WriteLine (asdu.ToString ());
+ private static bool asduReceivedHandler(object parameter, ASDU asdu)
+ {
+ Console.WriteLine (asdu.ToString ());
- if (asdu.TypeId == TypeID.M_SP_NA_1) {
+ if (asdu.TypeId == TypeID.M_SP_NA_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointInformation)asdu.GetElement (i);
+ var val = (SinglePointInformation)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_TE_1) {
-
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_TE_1) {
+
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
+ var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- Console.WriteLine (" " + msv.Timestamp.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ Console.WriteLine (" " + msv.Timestamp.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
+ var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- Console.WriteLine (" " + val.Timestamp.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ Console.WriteLine (" " + val.Timestamp.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShort)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShort)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaled)asdu.GetElement (i);
+ var msv = (MeasuredValueScaled)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_ND_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_ND_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueNormalizedWithoutQuality)asdu.GetElement (i);
+ var msv = (MeasuredValueNormalizedWithoutQuality)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.NormalizedValue);
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.NormalizedValue);
+ }
- } else if (asdu.TypeId == TypeID.C_IC_NA_1) {
- if (asdu.Cot == CauseOfTransmission.ACTIVATION_CON)
- Console.WriteLine ((asdu.IsNegative ? "Negative" : "Positive") + "confirmation for interrogation command");
- else if (asdu.Cot == CauseOfTransmission.ACTIVATION_TERMINATION)
- Console.WriteLine ("Interrogation command terminated");
- } else if (asdu.TypeId == TypeID.F_DR_TA_1) {
- Console.WriteLine ("Received file directory:\n------------------------");
- int ca = asdu.Ca;
+ } else if (asdu.TypeId == TypeID.C_IC_NA_1) {
+ if (asdu.Cot == CauseOfTransmission.ACTIVATION_CON)
+ Console.WriteLine ((asdu.IsNegative ? "Negative" : "Positive") + "confirmation for interrogation command");
+ else if (asdu.Cot == CauseOfTransmission.ACTIVATION_TERMINATION)
+ Console.WriteLine ("Interrogation command terminated");
+ } else if (asdu.TypeId == TypeID.F_DR_TA_1) {
+ Console.WriteLine ("Received file directory:\n------------------------");
+ int ca = asdu.Ca;
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- FileDirectory fd = (FileDirectory)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ FileDirectory fd = (FileDirectory)asdu.GetElement (i);
- Console.Write (fd.FOR ? "DIR: " : "FILE: ");
+ Console.Write (fd.FOR ? "DIR: " : "FILE: ");
- Console.WriteLine ("CA: {0} IOA: {1} Type: {2}", ca, fd.ObjectAddress, fd.NOF.ToString ());
- }
+ Console.WriteLine ("CA: {0} IOA: {1} Type: {2}", ca, fd.ObjectAddress, fd.NOF.ToString ());
+ }
- }
- else {
- Console.WriteLine ("Unknown message type!");
- }
+ }
+ else {
+ Console.WriteLine ("Unknown message type!");
+ }
- return true;
- }
+ return true;
+ }
- public class Receiver : IFileReceiver
- {
- public void Finished(FileErrorCode result)
- {
- Console.WriteLine ("File download finished - code: " + result.ToString ());
- }
+ public class Receiver : IFileReceiver
+ {
+ public void Finished(FileErrorCode result)
+ {
+ Console.WriteLine ("File download finished - code: " + result.ToString ());
+ }
- public void SegmentReceived(byte sectionName, int offset, int size, byte[] data)
- {
- Console.WriteLine ("File segment - sectionName: {0} offset: {1} size: {2}", sectionName, offset, size);
- }
- }
+ public void SegmentReceived(byte sectionName, int offset, int size, byte[] data)
+ {
+ Console.WriteLine ("File segment - sectionName: {0} offset: {1} size: {2}", sectionName, offset, size);
+ }
+ }
- public static void Main (string[] args)
- {
- Console.WriteLine ("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString ());
+ public static void Main (string[] args)
+ {
+ Console.WriteLine ("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString ());
- Connection con = new Connection ("192.168.2.222");
+ Connection con = new Connection ("127.0.0.1");
- con.LocalIpAddress = "192.168.2.244";
- con.LocalTcpPort = 22222;
+ //con.LocalIpAddress = "192.168.2.244";
+ //con.LocalTcpPort = 22222;
- con.DebugOutput = false;
+ con.DebugOutput = false;
- con.SetASDUReceivedHandler (asduReceivedHandler, null);
- con.SetConnectionHandler (ConnectionHandler, null);
+ con.SetASDUReceivedHandler (asduReceivedHandler, null);
+ con.SetConnectionHandler (ConnectionHandler, null);
con.Connect ();
- con.GetDirectory (1);
+ con.GetDirectory (1);
- con.GetFile (1, 30000, NameOfFile.TRANSPARENT_FILE, new Receiver ());
+ con.GetFile (1, 30000, NameOfFile.TRANSPARENT_FILE, new Receiver ());
Thread.Sleep (5000);
- con.SendTestCommand (1);
+ con.SendTestCommand (1);
- con.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, QualifierOfInterrogation.STATION);
+ con.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, QualifierOfInterrogation.STATION);
- Thread.Sleep (5000);
+ Thread.Sleep (5000);
- con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new SingleCommand (5000, true, false, 0));
+ con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new SingleCommand (5000, true, false, 0));
- con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new DoubleCommand (5001, DoubleCommand.ON, false, 0));
+ con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new DoubleCommand (5001, DoubleCommand.ON, false, 0));
- con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new StepCommand (5002, StepCommandValue.HIGHER, false, 0));
+ con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new StepCommand (5002, StepCommandValue.HIGHER, false, 0));
- con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1,
- new SingleCommandWithCP56Time2a (5000, false, false, 0, new CP56Time2a (DateTime.Now)));
+ con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1,
+ new SingleCommandWithCP56Time2a (5000, false, false, 0, new CP56Time2a (DateTime.Now)));
- /* Synchronize clock of the controlled station */
- con.SendClockSyncCommand (1 /* CA */, new CP56Time2a (DateTime.Now));
+ /* Synchronize clock of the controlled station */
+ con.SendClockSyncCommand (1 /* CA */, new CP56Time2a (DateTime.Now));
- Console.WriteLine ("CLOSE");
+ Console.WriteLine ("CLOSE");
- con.Close ();
+ con.Close ();
- Console.WriteLine ("RECONNECT");
+ Console.WriteLine ("RECONNECT");
- con.Connect ();
+ con.Connect ();
- Thread.Sleep (5000);
+ Thread.Sleep (5000);
Console.WriteLine ("CLOSE 2");
con.Close ();
- Console.WriteLine("Press any key to terminate...");
- Console.ReadKey();
- }
- }
+ Console.WriteLine("Press any key to terminate...");
+ Console.ReadKey();
+ }
+ }
}
diff --git a/examples/cs104-client1/cs104-client1.csproj b/examples/cs104-client1/cs104-client1.csproj
index c63b8d8..20aa81d 100644
--- a/examples/cs104-client1/cs104-client1.csproj
+++ b/examples/cs104-client1/cs104-client1.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_client1
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-client2/Program.cs b/examples/cs104-client2/Program.cs
index 668477e..0d1fa88 100644
--- a/examples/cs104-client2/Program.cs
+++ b/examples/cs104-client2/Program.cs
@@ -7,153 +7,153 @@
namespace cs104_client2
{
- class MainClass
- {
- static int interrogationTerminationReceived = 0;
- static int interrogationConfirmationReceived = 0;
-
- private static void ConnectionHandler (object parameter, ConnectionEvent connectionEvent)
- {
- switch (connectionEvent) {
- case ConnectionEvent.OPENED:
- Console.WriteLine ("Connected");
- break;
- case ConnectionEvent.CLOSED:
- Console.WriteLine ("Connection closed");
- break;
- case ConnectionEvent.STARTDT_CON_RECEIVED:
- Console.WriteLine ("STARTDT CON received");
- break;
- case ConnectionEvent.STOPDT_CON_RECEIVED:
- Console.WriteLine ("STOPDT CON received");
- break;
- }
- }
+ class MainClass
+ {
+ static int interrogationTerminationReceived = 0;
+ static int interrogationConfirmationReceived = 0;
+
+ private static void ConnectionHandler (object parameter, ConnectionEvent connectionEvent)
+ {
+ switch (connectionEvent) {
+ case ConnectionEvent.OPENED:
+ Console.WriteLine ("Connected");
+ break;
+ case ConnectionEvent.CLOSED:
+ Console.WriteLine ("Connection closed");
+ break;
+ case ConnectionEvent.STARTDT_CON_RECEIVED:
+ Console.WriteLine ("STARTDT CON received");
+ break;
+ case ConnectionEvent.STOPDT_CON_RECEIVED:
+ Console.WriteLine ("STOPDT CON received");
+ break;
+ }
+ }
- private static bool asduReceivedHandler(object parameter, ASDU asdu)
- {
- Console.WriteLine (asdu.ToString ());
+ private static bool asduReceivedHandler(object parameter, ASDU asdu)
+ {
+ Console.WriteLine (asdu.ToString ());
- if (asdu.TypeId == TypeID.M_SP_NA_1) {
+ if (asdu.TypeId == TypeID.M_SP_NA_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointInformation)asdu.GetElement (i);
+ var val = (SinglePointInformation)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_TE_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_TE_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
+ var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- Console.WriteLine (" " + msv.Timestamp.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ Console.WriteLine (" " + msv.Timestamp.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
+ var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- Console.WriteLine (" " + val.Timestamp.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ Console.WriteLine (" " + val.Timestamp.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShort)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShort)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaled)asdu.GetElement (i);
+ var msv = (MeasuredValueScaled)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_ND_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_ND_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueNormalizedWithoutQuality)asdu.GetElement (i);
+ var msv = (MeasuredValueNormalizedWithoutQuality)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.NormalizedValue);
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.NormalizedValue);
+ }
- } else if (asdu.TypeId == TypeID.C_IC_NA_1) {
- if (asdu.Cot == CauseOfTransmission.ACTIVATION_CON) {
- Console.WriteLine ((asdu.IsNegative ? "Negative" : "Positive") + "confirmation for interrogation command");
- interrogationConfirmationReceived++;
- } else if (asdu.Cot == CauseOfTransmission.ACTIVATION_TERMINATION) {
- Console.WriteLine ("Interrogation command terminated");
- interrogationTerminationReceived++;
- }
- }
- else {
- Console.WriteLine ("Unknown message type!");
- }
+ } else if (asdu.TypeId == TypeID.C_IC_NA_1) {
+ if (asdu.Cot == CauseOfTransmission.ACTIVATION_CON) {
+ Console.WriteLine ((asdu.IsNegative ? "Negative" : "Positive") + "confirmation for interrogation command");
+ interrogationConfirmationReceived++;
+ } else if (asdu.Cot == CauseOfTransmission.ACTIVATION_TERMINATION) {
+ Console.WriteLine ("Interrogation command terminated");
+ interrogationTerminationReceived++;
+ }
+ }
+ else {
+ Console.WriteLine ("Unknown message type!");
+ }
- Console.WriteLine ("interrogationConfirmationReceived: " + interrogationConfirmationReceived);
- Console.WriteLine ("interrogationTerminationReceived: " + interrogationTerminationReceived);
+ Console.WriteLine ("interrogationConfirmationReceived: " + interrogationConfirmationReceived);
+ Console.WriteLine ("interrogationTerminationReceived: " + interrogationTerminationReceived);
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- Console.WriteLine ("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString ());
+ public static void Main (string[] args)
+ {
+ Console.WriteLine ("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString ());
- Connection con = new Connection ("127.0.0.1");
+ Connection con = new Connection ("127.0.0.1");
- con.DebugOutput = true;
- con.UseSendMessageQueue = false;
+ con.DebugOutput = true;
+ con.UseSendMessageQueue = false;
- con.SetASDUReceivedHandler (asduReceivedHandler, null);
- con.SetConnectionHandler (ConnectionHandler, null);
+ con.SetASDUReceivedHandler (asduReceivedHandler, null);
+ con.SetConnectionHandler (ConnectionHandler, null);
- con.Connect ();
+ con.Connect ();
- int loopRuns = 6000;
+ int loopRuns = 6000;
- for (int i = 0; i < loopRuns; i++) {
- Console.WriteLine ("Send GI " + i);
+ for (int i = 0; i < loopRuns; i++) {
+ Console.WriteLine ("Send GI " + i);
- if (con.IsTransmitBufferFull()) {
- while (con.IsTransmitBufferFull() && (con.IsRunning == true))
- Thread.Sleep (50);
-
- }
- con.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, QualifierOfInterrogation.STATION);
- }
+ if (con.IsTransmitBufferFull()) {
+ while (con.IsTransmitBufferFull() && (con.IsRunning == true))
+ Thread.Sleep (50);
+
+ }
+ con.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, QualifierOfInterrogation.STATION);
+ }
- while (interrogationTerminationReceived < loopRuns)
- Thread.Sleep (100);
+ while (interrogationTerminationReceived < loopRuns)
+ Thread.Sleep (100);
- Console.WriteLine ("interrogationTerminationReceived: " + interrogationTerminationReceived);
+ Console.WriteLine ("interrogationTerminationReceived: " + interrogationTerminationReceived);
- con.Close ();
+ con.Close ();
- }
- }
+ }
+ }
}
diff --git a/examples/cs104-client2/cs104-client2.csproj b/examples/cs104-client2/cs104-client2.csproj
index 9d7bbad..aad543e 100644
--- a/examples/cs104-client2/cs104-client2.csproj
+++ b/examples/cs104-client2/cs104-client2.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_client2
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-client3/Program.cs b/examples/cs104-client3/Program.cs
index 0bdff0a..aa982af 100644
--- a/examples/cs104-client3/Program.cs
+++ b/examples/cs104-client3/Program.cs
@@ -7,137 +7,137 @@
namespace cs104_client3
{
- class MainClass
- {
+ class MainClass
+ {
- private static void ConnectionHandler (object parameter, ConnectionEvent connectionEvent)
- {
- switch (connectionEvent) {
- case ConnectionEvent.OPENED:
- Console.WriteLine ("Connected");
- break;
- case ConnectionEvent.CLOSED:
- Console.WriteLine ("Connection closed");
- break;
- case ConnectionEvent.STARTDT_CON_RECEIVED:
- Console.WriteLine ("STARTDT CON received");
- break;
- case ConnectionEvent.STOPDT_CON_RECEIVED:
- Console.WriteLine ("STOPDT CON received");
- break;
- }
- }
+ private static void ConnectionHandler (object parameter, ConnectionEvent connectionEvent)
+ {
+ switch (connectionEvent) {
+ case ConnectionEvent.OPENED:
+ Console.WriteLine ("Connected");
+ break;
+ case ConnectionEvent.CLOSED:
+ Console.WriteLine ("Connection closed");
+ break;
+ case ConnectionEvent.STARTDT_CON_RECEIVED:
+ Console.WriteLine ("STARTDT CON received");
+ break;
+ case ConnectionEvent.STOPDT_CON_RECEIVED:
+ Console.WriteLine ("STOPDT CON received");
+ break;
+ }
+ }
- private static bool asduReceivedHandler(object parameter, ASDU asdu)
- {
- Console.WriteLine (asdu.ToString ());
+ private static bool asduReceivedHandler(object parameter, ASDU asdu)
+ {
+ Console.WriteLine (asdu.ToString ());
- if (asdu.TypeId == TypeID.M_SP_NA_1) {
+ if (asdu.TypeId == TypeID.M_SP_NA_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointInformation)asdu.GetElement (i);
+ var val = (SinglePointInformation)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_TE_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_TE_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
+ var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- Console.WriteLine (" " + msv.Timestamp.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ Console.WriteLine (" " + msv.Timestamp.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
+ var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- Console.WriteLine (" " + val.Timestamp.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ Console.WriteLine (" " + val.Timestamp.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShort)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShort)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaled)asdu.GetElement (i);
+ var msv = (MeasuredValueScaled)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_ND_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_ND_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueNormalizedWithoutQuality)asdu.GetElement (i);
+ var msv = (MeasuredValueNormalizedWithoutQuality)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.NormalizedValue);
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.NormalizedValue);
+ }
- } else if (asdu.TypeId == TypeID.C_IC_NA_1) {
- if (asdu.Cot == CauseOfTransmission.ACTIVATION_CON)
- Console.WriteLine ((asdu.IsNegative ? "Negative" : "Positive") + "confirmation for interrogation command");
- else if (asdu.Cot == CauseOfTransmission.ACTIVATION_TERMINATION)
- Console.WriteLine ("Interrogation command terminated");
- }
- else {
- Console.WriteLine ("Unknown message type!");
- }
+ } else if (asdu.TypeId == TypeID.C_IC_NA_1) {
+ if (asdu.Cot == CauseOfTransmission.ACTIVATION_CON)
+ Console.WriteLine ((asdu.IsNegative ? "Negative" : "Positive") + "confirmation for interrogation command");
+ else if (asdu.Cot == CauseOfTransmission.ACTIVATION_TERMINATION)
+ Console.WriteLine ("Interrogation command terminated");
+ }
+ else {
+ Console.WriteLine ("Unknown message type!");
+ }
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- Console.WriteLine ("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString ());
+ public static void Main (string[] args)
+ {
+ Console.WriteLine ("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString ());
- //Connection con = new Connection ("10.0.4.6");
- Connection con = new Connection ("127.0.0.1");
+ //Connection con = new Connection ("10.0.4.6");
+ Connection con = new Connection ("127.0.0.1");
- con.DebugOutput = true;
+ con.DebugOutput = true;
- con.SetASDUReceivedHandler (asduReceivedHandler, null);
- con.SetConnectionHandler (ConnectionHandler, null);
+ con.SetASDUReceivedHandler (asduReceivedHandler, null);
+ con.SetConnectionHandler (ConnectionHandler, null);
- bool running = true;
+ bool running = true;
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- con.Connect ();
+ con.Connect ();
- while (running) {
- Thread.Sleep(100);
- }
+ while (running) {
+ Thread.Sleep(100);
+ }
- con.Close ();
- }
- }
+ con.Close ();
+ }
+ }
}
diff --git a/examples/cs104-client3/cs104-client3.csproj b/examples/cs104-client3/cs104-client3.csproj
index 3360564..92825d7 100644
--- a/examples/cs104-client3/cs104-client3.csproj
+++ b/examples/cs104-client3/cs104-client3.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_client3
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-multi-client-server/Program.cs b/examples/cs104-multi-client-server/Program.cs
index 531affe..95062d7 100644
--- a/examples/cs104-multi-client-server/Program.cs
+++ b/examples/cs104-multi-client-server/Program.cs
@@ -10,164 +10,164 @@
namespace cs104_multi_client_server
{
- class MainClass
- {
- private static bool connectionRequestHandler(object parameter, IPAddress ipAddress)
- {
- Console.WriteLine ("New connection request from IP " + ipAddress.ToString ());
+ class MainClass
+ {
+ private static bool connectionRequestHandler(object parameter, IPAddress ipAddress)
+ {
+ Console.WriteLine ("New connection request from IP " + ipAddress.ToString ());
- // Allow only known IP addresses!
- // You can implement your allowed client whitelist here
- if (ipAddress.ToString ().Equals ("127.0.0.1"))
- return true;
- else if (ipAddress.ToString ().Equals ("192.168.178.70"))
- return true;
- else if (ipAddress.ToString ().Equals ("192.168.2.9"))
- return true;
- else
- return false;
- }
+ // Allow only known IP addresses!
+ // You can implement your allowed client whitelist here
+ if (ipAddress.ToString ().Equals ("127.0.0.1"))
+ return true;
+ else if (ipAddress.ToString ().Equals ("192.168.178.70"))
+ return true;
+ else if (ipAddress.ToString ().Equals ("192.168.2.9"))
+ return true;
+ else
+ return false;
+ }
- private static void connectionEventHandler(object parameter, ClientConnection connection, ClientConnectionEvent conEvent)
- {
- Console.WriteLine ("Connection {0}:{1} - {2}", connection.RemoteEndpoint.Address.ToString (),
- connection.RemoteEndpoint.Port, conEvent.ToString ());
- }
+ private static void connectionEventHandler(object parameter, ClientConnection connection, ClientConnectionEvent conEvent)
+ {
+ Console.WriteLine ("Connection {0}:{1} - {2}", connection.RemoteEndpoint.Address.ToString (),
+ connection.RemoteEndpoint.Port, conEvent.ToString ());
+ }
- private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
- {
- Console.WriteLine ("Interrogation for group " + qoi);
+ private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
+ {
+ Console.WriteLine ("Interrogation for group " + qoi);
- ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
+ ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
- connection.SendACT_CON (asdu, false);
+ connection.SendACT_CON (asdu, false);
- // send information objects
- ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ // send information objects
+ ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
- newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
+ newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
+ newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- // send sequence of information objects
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
+ // send sequence of information objects
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
- newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- connection.SendACT_TERM (asdu);
+ connection.SendACT_TERM (asdu);
- return true;
- }
+ return true;
+ }
- private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
- {
+ private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
+ {
- if (asdu.TypeId == TypeID.C_SC_NA_1) {
- Console.WriteLine ("Single command");
+ if (asdu.TypeId == TypeID.C_SC_NA_1) {
+ Console.WriteLine ("Single command");
- SingleCommand sc = (SingleCommand)asdu.GetElement (0);
+ SingleCommand sc = (SingleCommand)asdu.GetElement (0);
- Console.WriteLine (sc.ToString ());
- }
- else if (asdu.TypeId == TypeID.C_CS_NA_1){
+ Console.WriteLine (sc.ToString ());
+ }
+ else if (asdu.TypeId == TypeID.C_CS_NA_1){
- ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
+ ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
- Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
- }
+ Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
+ }
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- Server server = new Server ();
+ Server server = new Server ();
- server.SetLocalAddress ("0.0.0.0");
+ server.SetLocalAddress ("0.0.0.0");
- server.ServerMode = ServerMode.CONNECTION_IS_REDUNDANCY_GROUP;
- server.MaxQueueSize = 10;
- server.MaxOpenConnections = 2;
+ server.ServerMode = ServerMode.CONNECTION_IS_REDUNDANCY_GROUP;
+ server.MaxQueueSize = 10;
+ server.MaxOpenConnections = 2;
- server.SetConnectionRequestHandler (connectionRequestHandler, null);
+ server.SetConnectionRequestHandler (connectionRequestHandler, null);
- server.SetConnectionEventHandler (connectionEventHandler, null);
+ server.SetConnectionEventHandler (connectionEventHandler, null);
- server.SetInterrogationHandler (interrogationHandler, null);
+ server.SetInterrogationHandler (interrogationHandler, null);
- server.SetASDUHandler (asduHandler, null);
+ server.SetASDUHandler (asduHandler, null);
- server.Start ();
+ server.Start ();
- int waitTime = 1000;
+ int waitTime = 1000;
- while (running) {
- Thread.Sleep(100);
+ while (running) {
+ Thread.Sleep(100);
- if (waitTime > 0)
- waitTime -= 100;
- else {
+ if (waitTime > 0)
+ waitTime -= 100;
+ else {
- ASDU newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 2, 1, false);
+ ASDU newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
- server.EnqueueASDU (newAsdu);
+ server.EnqueueASDU (newAsdu);
- waitTime = 1000;
- }
- }
+ waitTime = 1000;
+ }
+ }
- Console.WriteLine ("Stop server");
- server.Stop ();
- }
- }
+ Console.WriteLine ("Stop server");
+ server.Stop ();
+ }
+ }
}
diff --git a/examples/cs104-multi-client-server/cs104-multi-client-server.csproj b/examples/cs104-multi-client-server/cs104-multi-client-server.csproj
index e786d7d..11f0256 100644
--- a/examples/cs104-multi-client-server/cs104-multi-client-server.csproj
+++ b/examples/cs104-multi-client-server/cs104-multi-client-server.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_multi_client_server
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-redundancy-server/Program.cs b/examples/cs104-redundancy-server/Program.cs
index 85c2df3..b1b7f5d 100644
--- a/examples/cs104-redundancy-server/Program.cs
+++ b/examples/cs104-redundancy-server/Program.cs
@@ -1,10 +1,11 @@
using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
using lib60870;
using lib60870.CS101;
using lib60870.CS104;
-using System.Net;
-using System.Threading;
namespace cs104_redundancy_server
{
diff --git a/examples/cs104-redundancy-server/cs104-redundancy-server.csproj b/examples/cs104-redundancy-server/cs104-redundancy-server.csproj
index b241e02..ad92115 100644
--- a/examples/cs104-redundancy-server/cs104-redundancy-server.csproj
+++ b/examples/cs104-redundancy-server/cs104-redundancy-server.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_redundancy_server
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-server-file/Program.cs b/examples/cs104-server-file/Program.cs
index f5f5e62..54291a2 100644
--- a/examples/cs104-server-file/Program.cs
+++ b/examples/cs104-server-file/Program.cs
@@ -11,18 +11,18 @@ namespace cs104_server_file
///
/// Extend TransparentFile or implement IFileProvider to allow file downloads to the master
///
- public class SimpleFile : TransparentFile
- {
- public SimpleFile (int ca, int ioa, NameOfFile nof)
- : base (ca, ioa, nof)
- {
- }
-
- public override void TransferComplete (bool success)
- {
- Console.WriteLine ("Transfer complete: " + success.ToString());
- }
- }
+ public class SimpleFile : TransparentFile
+ {
+ public SimpleFile (int ca, int ioa, NameOfFile nof)
+ : base (ca, ioa, nof)
+ {
+ }
+
+ public override void TransferComplete (bool success)
+ {
+ Console.WriteLine ("Transfer complete: " + success.ToString());
+ }
+ }
///
/// Implement IFileReceiver to allow file uploads from the master
@@ -61,74 +61,74 @@ public void SegmentReceived (byte sectionName, int offset, int size, byte [] dat
}
class MainClass
- {
- private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
- {
- Console.WriteLine ("Interrogation for group " + qoi);
+ {
+ private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
+ {
+ Console.WriteLine ("Interrogation for group " + qoi);
- ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
+ ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
- connection.SendACT_CON (asdu, false);
+ connection.SendACT_CON (asdu, false);
- // send information objects
- ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ // send information objects
+ ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
- newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
+ newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
+ newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- // send sequence of information objects
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
+ // send sequence of information objects
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
- newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- connection.SendACT_TERM (asdu);
+ connection.SendACT_TERM (asdu);
- return true;
- }
+ return true;
+ }
- private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
- {
+ private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
+ {
if (asdu.TypeId == TypeID.C_SC_NA_1) {
SingleCommand sc = (SingleCommand)asdu.GetElement (0);
@@ -147,48 +147,48 @@ private static bool asduHandler(object parameter, IMasterConnection connection,
}
- if (asdu.TypeId == TypeID.C_SC_NA_1) {
- Console.WriteLine ("Single command");
+ if (asdu.TypeId == TypeID.C_SC_NA_1) {
+ Console.WriteLine ("Single command");
- SingleCommand sc = (SingleCommand)asdu.GetElement (0);
+ SingleCommand sc = (SingleCommand)asdu.GetElement (0);
- Console.WriteLine (sc.ToString ());
- } else if (asdu.TypeId == TypeID.M_EI_NA_1) {
- Console.WriteLine ("End of initialization received");
- }
- else if (asdu.TypeId == TypeID.F_DR_TA_1) {
-
- Console.WriteLine ("Received file directory");
- }
- else if (asdu.TypeId == TypeID.C_CS_NA_1){
-
+ Console.WriteLine (sc.ToString ());
+ } else if (asdu.TypeId == TypeID.M_EI_NA_1) {
+ Console.WriteLine ("End of initialization received");
+ }
+ else if (asdu.TypeId == TypeID.F_DR_TA_1) {
+
+ Console.WriteLine ("Received file directory");
+ }
+ else if (asdu.TypeId == TypeID.C_CS_NA_1){
+
- ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
+ ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
- Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
- }
+ Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
+ }
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- Server server = new Server ();
+ Server server = new Server ();
- server.DebugOutput = true;
+ server.DebugOutput = true;
- server.MaxQueueSize = 10;
+ server.MaxQueueSize = 10;
- server.SetInterrogationHandler (interrogationHandler, null);
+ server.SetInterrogationHandler (interrogationHandler, null);
- server.SetASDUHandler (asduHandler, null);
+ server.SetASDUHandler (asduHandler, null);
// Install a handler to allow file downloads (will be called when the master sends a file ready ASDU to anounce a file transfer)
server.SetFileReadyHandler (delegate (object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile) {
@@ -213,32 +213,32 @@ public static void Main (string[] args)
server.Start ();
- SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
- byte[] fileData = new byte[1025];
+ byte[] fileData = new byte[1025];
- for (int i = 0; i < 1025; i++)
- fileData [i] = (byte)(i + 1);
+ for (int i = 0; i < 1025; i++)
+ fileData [i] = (byte)(i + 1);
- file.AddSection (fileData);
+ file.AddSection (fileData);
- SimpleFile file2 = new SimpleFile (1, 30001, NameOfFile.TRANSPARENT_FILE);
- file2.AddSection (fileData);
+ SimpleFile file2 = new SimpleFile (1, 30001, NameOfFile.TRANSPARENT_FILE);
+ file2.AddSection (fileData);
- server.GetAvailableFiles().AddFile (file);
- server.GetAvailableFiles().AddFile (file2);
+ server.GetAvailableFiles().AddFile (file);
+ server.GetAvailableFiles().AddFile (file2);
ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
EndOfInitialization eoi = new EndOfInitialization(0);
newAsdu.AddInformationObject(eoi);
server.EnqueueASDU(newAsdu);
- while (running) {
- Thread.Sleep(100);
- }
+ while (running) {
+ Thread.Sleep(100);
+ }
- Console.WriteLine ("Stop server");
- server.Stop ();
- }
- }
+ Console.WriteLine ("Stop server");
+ server.Stop ();
+ }
+ }
}
diff --git a/examples/cs104-server-file/cs104-server-file.csproj b/examples/cs104-server-file/cs104-server-file.csproj
index 379f08f..e010798 100644
--- a/examples/cs104-server-file/cs104-server-file.csproj
+++ b/examples/cs104-server-file/cs104-server-file.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_server_file
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-server/Program.cs b/examples/cs104-server/Program.cs
index ca1090e..253ebfd 100644
--- a/examples/cs104-server/Program.cs
+++ b/examples/cs104-server/Program.cs
@@ -1,126 +1,124 @@
// This example shows how to send periodic messages and handle commands from clients
-using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-
using lib60870;
using lib60870.CS101;
using lib60870.CS104;
namespace cs104_server
{
-
- class MainClass
- {
- private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
- {
- Console.WriteLine ("Interrogation for group " + qoi);
- ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
+ class MainClass
+ {
+ private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
+ {
+ Console.WriteLine ("Interrogation for group " + qoi);
+
+ ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
byte oa = asdu.Oa;
- connection.SendACT_CON (asdu, false);
+ connection.SendACT_CON (asdu, false);
+
+ // send information objects
+ ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, false);
- // send information objects
- ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, false);
+ newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
+ connection.SendASDU (newAsdu);
- connection.SendASDU (newAsdu);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, false);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, false);
+ newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
- newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
+ connection.SendASDU (newAsdu);
- connection.SendASDU (newAsdu);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, false);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, false);
+ newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
- newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
+ connection.SendASDU (newAsdu);
- connection.SendASDU (newAsdu);
+ // send sequence of information objects
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, true);
- // send sequence of information objects
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, true);
+ newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
+ connection.SendASDU (newAsdu);
- connection.SendASDU (newAsdu);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, true);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, oa, 1, true);
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
+ connection.SendASDU (newAsdu);
- connection.SendASDU (newAsdu);
+ connection.SendACT_TERM (asdu);
- connection.SendACT_TERM (asdu);
+ return true;
+ }
- return true;
- }
+ private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
+ {
+ Console.WriteLine("ASDU received:" + asdu.ToString());
- private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
- {
-
- if (asdu.TypeId == TypeID.C_SC_NA_1) {
- Console.WriteLine ("Single command");
+ if (asdu.TypeId == TypeID.C_SC_NA_1)
+ {
+ Console.WriteLine ("Single command");
- SingleCommand sc = (SingleCommand)asdu.GetElement (0);
+ SingleCommand sc = (SingleCommand)asdu.GetElement (0);
- if (sc.Select)
- {
- Console.WriteLine(" received select");
- }
- else
- {
- Console.WriteLine(" received execute");
- }
+ if (sc.Select)
+ {
+ Console.WriteLine(" received select");
+ }
+ else
+ {
+ Console.WriteLine(" received execute");
+ }
- Console.WriteLine (sc.ToString ());
+ Console.WriteLine (sc.ToString ());
connection.SendACT_CON(asdu, false);
- }
- else if (asdu.TypeId == TypeID.C_CS_NA_1){
- ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
+ }
+ else if (asdu.TypeId == TypeID.C_CS_NA_1)
+ {
+ ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
- Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
+ Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
connection.SendACT_CON(asdu, false);
}
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
// specify application layer parameters (CS 101 and CS 104)
var alParams = new ApplicationLayerParameters();
@@ -139,51 +137,52 @@ public static void Main (string[] args)
Server server = new Server (apciParameters, alParams);
- server.DebugOutput = true;
+ server.DebugOutput = true;
- server.MaxQueueSize = 10;
+ server.MaxQueueSize = 10;
server.EnqueueMode = EnqueueMode.REMOVE_OLDEST;
- server.SetInterrogationHandler (interrogationHandler, null);
+ server.SetInterrogationHandler (interrogationHandler, null);
- server.SetASDUHandler (asduHandler, null);
+ server.SetASDUHandler (asduHandler, null);
- server.Start ();
+ server.Start ();
ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
EndOfInitialization eoi = new EndOfInitialization(0);
newAsdu.AddInformationObject(eoi);
server.EnqueueASDU(newAsdu);
- int waitTime = 1000;
+ int waitTime = 1000;
- while (running && server.IsRunning()) {
- Thread.Sleep(100);
+ while (running && server.IsRunning())
+ {
+ Thread.Sleep(100);
- if (waitTime > 0)
- waitTime -= 100;
- else {
+ if (waitTime > 0)
+ waitTime -= 100;
+ else {
- newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
-
- server.EnqueueASDU (newAsdu);
+ newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
+
+ server.EnqueueASDU (newAsdu);
- waitTime = 1000;
- }
- }
+ waitTime = 30000;
+ }
+ }
- if (server.IsRunning())
- {
- Console.WriteLine("Stop server");
- server.Stop();
- }
- else
- {
- Console.WriteLine("Server stopped");
- }
+ if (server.IsRunning())
+ {
+ Console.WriteLine("Stop server");
+ server.Stop();
+ }
+ else
+ {
+ Console.WriteLine("Server stopped");
+ }
- }
- }
+ }
+ }
}
diff --git a/examples/cs104-server/cs104-server.csproj b/examples/cs104-server/cs104-server.csproj
index f9fe7e6..159488d 100644
--- a/examples/cs104-server/cs104-server.csproj
+++ b/examples/cs104-server/cs104-server.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_server
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-server2/Program.cs b/examples/cs104-server2/Program.cs
index 7b1e552..7625d16 100644
--- a/examples/cs104-server2/Program.cs
+++ b/examples/cs104-server2/Program.cs
@@ -10,93 +10,93 @@
namespace cs104_server2
{
- ///
- /// This example shows how to handle a large number of information objects
- ///
- class MainClass
- {
- static SinglePointInformation[] spiObjects = new SinglePointInformation[400];
- static StepPositionWithCP56Time2a[] stepPositionObjects = new StepPositionWithCP56Time2a[100];
+ ///
+ /// This example shows how to handle a large number of information objects
+ ///
+ class MainClass
+ {
+ static SinglePointInformation[] spiObjects = new SinglePointInformation[400];
+ static StepPositionWithCP56Time2a[] stepPositionObjects = new StepPositionWithCP56Time2a[100];
- public static void Main (string[] args)
- {
- /* Initialize data objects */
- for (int i = 0; i < 400; i++)
- spiObjects [i] = new SinglePointInformation (1000 + i, true, new QualityDescriptor ());
+ public static void Main (string[] args)
+ {
+ /* Initialize data objects */
+ for (int i = 0; i < 400; i++)
+ spiObjects [i] = new SinglePointInformation (1000 + i, true, new QualityDescriptor ());
- for (int i = 0; i < 100; i++)
- stepPositionObjects [i] = new StepPositionWithCP56Time2a (10000 + i, 0, false,
- new QualityDescriptor (), new CP56Time2a ());
+ for (int i = 0; i < 100; i++)
+ stepPositionObjects [i] = new StepPositionWithCP56Time2a (10000 + i, 0, false,
+ new QualityDescriptor (), new CP56Time2a ());
- bool running = true;
+ bool running = true;
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- Server server = new Server ();
+ Server server = new Server ();
- server.DebugOutput = false;
- server.MaxQueueSize = 100;
+ server.DebugOutput = false;
+ server.MaxQueueSize = 100;
- server.Start ();
+ server.Start ();
- int waitTime = 2000;
+ int waitTime = 2000;
- while (running) {
- Thread.Sleep(100);
+ while (running) {
+ Thread.Sleep(100);
- if (waitTime > 0)
- waitTime -= 100;
- else {
+ if (waitTime > 0)
+ waitTime -= 100;
+ else {
- ASDU? newAsdu = null;
+ ASDU? newAsdu = null;
- /* send SPI objects */
- for (int i = 0; i < 400; i++) {
- spiObjects [i].Value = !(spiObjects [i].Value);
+ /* send SPI objects */
+ for (int i = 0; i < 400; i++) {
+ spiObjects [i].Value = !(spiObjects [i].Value);
- if (newAsdu == null)
- newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 1, 1, false);
+ if (newAsdu == null)
+ newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 1, 1, false);
- if (newAsdu.AddInformationObject (spiObjects [i]) == false) {
- server.EnqueueASDU (newAsdu);
- newAsdu = null;
- i--;
- }
- }
+ if (newAsdu.AddInformationObject (spiObjects [i]) == false) {
+ server.EnqueueASDU (newAsdu);
+ newAsdu = null;
+ i--;
+ }
+ }
- if (newAsdu != null)
- server.EnqueueASDU (newAsdu);
+ if (newAsdu != null)
+ server.EnqueueASDU (newAsdu);
- /* send step position objects */
- newAsdu = null;
+ /* send step position objects */
+ newAsdu = null;
- for (int i = 0; i < 100; i++) {
+ for (int i = 0; i < 100; i++) {
- stepPositionObjects [i].Value = (stepPositionObjects [i].Value + 1) % 63;
+ stepPositionObjects [i].Value = (stepPositionObjects [i].Value + 1) % 63;
- if (newAsdu == null)
- newAsdu = new ASDU (server.GetApplicationLayerParameters (), CauseOfTransmission.PERIODIC, false, false, 1, 1, false);
+ if (newAsdu == null)
+ newAsdu = new ASDU (server.GetApplicationLayerParameters (), CauseOfTransmission.PERIODIC, false, false, 1, 1, false);
- if (newAsdu.AddInformationObject (stepPositionObjects [i]) == false) {
- server.EnqueueASDU (newAsdu);
- newAsdu = null;
- i--;
- }
- }
+ if (newAsdu.AddInformationObject (stepPositionObjects [i]) == false) {
+ server.EnqueueASDU (newAsdu);
+ newAsdu = null;
+ i--;
+ }
+ }
- if (newAsdu != null)
- server.EnqueueASDU (newAsdu);
+ if (newAsdu != null)
+ server.EnqueueASDU (newAsdu);
- waitTime = 1000;
- }
- }
+ waitTime = 1000;
+ }
+ }
- Console.WriteLine ("Stop server");
- server.Stop ();
- }
- }
+ Console.WriteLine ("Stop server");
+ server.Stop ();
+ }
+ }
}
diff --git a/examples/cs104-server2/cs104-server2.csproj b/examples/cs104-server2/cs104-server2.csproj
index 999b443..3710de0 100644
--- a/examples/cs104-server2/cs104-server2.csproj
+++ b/examples/cs104-server2/cs104-server2.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_server2
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-server3/Program.cs b/examples/cs104-server3/Program.cs
index 4b34fc8..6d625ef 100644
--- a/examples/cs104-server3/Program.cs
+++ b/examples/cs104-server3/Program.cs
@@ -12,216 +12,216 @@
namespace cs104_server3
{
- class Integer32Object : InformationObject, IPrivateIOFactory
- {
- private int value = 0;
+ class Integer32Object : InformationObject, IPrivateIOFactory
+ {
+ private int value = 0;
- public Integer32Object ()
- : base (0)
- {
- }
+ public Integer32Object ()
+ : base (0)
+ {
+ }
- public Integer32Object(int ioa, int value)
- :base (ioa)
- {
- this.value = value;
- }
+ public Integer32Object(int ioa, int value)
+ :base (ioa)
+ {
+ this.value = value;
+ }
- public int Value {
- get {
- return this.value;
- }
- set {
- this.value = value;
- }
- }
-
- private Integer32Object (ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
- :base(parameters, msg, startIndex, isSequence)
- {
- if (!isSequence)
- startIndex += parameters.SizeOfIOA; /* skip IOA */
+ public int Value {
+ get {
+ return this.value;
+ }
+ set {
+ this.value = value;
+ }
+ }
+
+ private Integer32Object (ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
+ :base(parameters, msg, startIndex, isSequence)
+ {
+ if (!isSequence)
+ startIndex += parameters.SizeOfIOA; /* skip IOA */
- value = msg [startIndex++];
- value += ((int)msg [startIndex++] * 0x100);
- value += ((int)msg [startIndex++] * 0x10000);
- value += ((int)msg [startIndex++] * 0x1000000);
- }
+ value = msg [startIndex++];
+ value += ((int)msg [startIndex++] * 0x100);
+ value += ((int)msg [startIndex++] * 0x10000);
+ value += ((int)msg [startIndex++] * 0x1000000);
+ }
- public override bool SupportsSequence {
- get {
- return true;
- }
- }
+ public override bool SupportsSequence {
+ get {
+ return true;
+ }
+ }
- public override TypeID Type {
- get {
- return (TypeID)41;
- }
- }
+ public override TypeID Type {
+ get {
+ return (TypeID)41;
+ }
+ }
- InformationObject IPrivateIOFactory.Decode (ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
- {
- return new Integer32Object (parameters, msg, startIndex, isSequence);
- }
+ InformationObject IPrivateIOFactory.Decode (ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
+ {
+ return new Integer32Object (parameters, msg, startIndex, isSequence);
+ }
- public override int GetEncodedSize()
- {
- return 4;
- }
+ public override int GetEncodedSize()
+ {
+ return 4;
+ }
- public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence) {
- base.Encode(frame, parameters, isSequence);
+ public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence) {
+ base.Encode(frame, parameters, isSequence);
- frame.SetNextByte((byte) (value % 0x100));
- frame.SetNextByte((byte) ((value / 0x100) % 0x100));
- frame.SetNextByte((byte) ((value / 0x10000) % 0x100));
- frame.SetNextByte((byte) (value / 0x1000000));
- }
- }
+ frame.SetNextByte((byte) (value % 0x100));
+ frame.SetNextByte((byte) ((value / 0x100) % 0x100));
+ frame.SetNextByte((byte) ((value / 0x10000) % 0x100));
+ frame.SetNextByte((byte) (value / 0x1000000));
+ }
+ }
- class MainClass
- {
- private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
- {
- Console.WriteLine ("Interrogation for group " + qoi);
+ class MainClass
+ {
+ private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
+ {
+ Console.WriteLine ("Interrogation for group " + qoi);
- ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
+ ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
- connection.SendACT_CON (asdu, false);
+ connection.SendACT_CON (asdu, false);
- // send information objects
- ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ // send information objects
+ ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
- newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
+ newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
+ newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- // send sequence of information objects
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
+ // send sequence of information objects
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
- newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new Integer32Object (400, 1023));
- newAsdu.AddInformationObject (new Integer32Object (402, 1024));
+ newAsdu.AddInformationObject (new Integer32Object (400, 1023));
+ newAsdu.AddInformationObject (new Integer32Object (402, 1024));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- connection.SendACT_TERM (asdu);
+ connection.SendACT_TERM (asdu);
- return true;
- }
+ return true;
+ }
- private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
- {
+ private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
+ {
- if (asdu.TypeId == TypeID.C_SC_NA_1) {
- Console.WriteLine ("Single command");
+ if (asdu.TypeId == TypeID.C_SC_NA_1) {
+ Console.WriteLine ("Single command");
- SingleCommand sc = (SingleCommand)asdu.GetElement (0);
+ SingleCommand sc = (SingleCommand)asdu.GetElement (0);
- Console.WriteLine (sc.ToString ());
- }
- else if (asdu.TypeId == TypeID.C_CS_NA_1){
+ Console.WriteLine (sc.ToString ());
+ }
+ else if (asdu.TypeId == TypeID.C_CS_NA_1){
- ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
+ ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
- Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
- }
+ Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
+ }
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
- Server server = new Server ();
+ Server server = new Server ();
- server.DebugOutput = true;
+ server.DebugOutput = true;
- server.MaxQueueSize = 10;
+ server.MaxQueueSize = 10;
- server.SetInterrogationHandler (interrogationHandler, null);
+ server.SetInterrogationHandler (interrogationHandler, null);
- server.SetASDUHandler (asduHandler, null);
+ server.SetASDUHandler (asduHandler, null);
- server.Start ();
+ server.Start ();
- ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
- EndOfInitialization eoi = new EndOfInitialization(0);
- newAsdu.AddInformationObject(eoi);
- server.EnqueueASDU(newAsdu);
+ ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
+ EndOfInitialization eoi = new EndOfInitialization(0);
+ newAsdu.AddInformationObject(eoi);
+ server.EnqueueASDU(newAsdu);
- int waitTime = 1000;
+ int waitTime = 1000;
- while (running) {
- Thread.Sleep(100);
+ while (running) {
+ Thread.Sleep(100);
- if (waitTime > 0)
- waitTime -= 100;
- else {
+ if (waitTime > 0)
+ waitTime -= 100;
+ else {
- newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 2, 1, false);
+ newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
- server.EnqueueASDU (newAsdu);
+ server.EnqueueASDU (newAsdu);
- waitTime = 5000;
- }
- }
+ waitTime = 5000;
+ }
+ }
- Console.WriteLine ("Stop server");
- server.Stop ();
- }
- }
+ Console.WriteLine ("Stop server");
+ server.Stop ();
+ }
+ }
}
diff --git a/examples/cs104-server3/cs104-server3.csproj b/examples/cs104-server3/cs104-server3.csproj
index 4af9ea8..53e0415 100644
--- a/examples/cs104-server3/cs104-server3.csproj
+++ b/examples/cs104-server3/cs104-server3.csproj
@@ -1,15 +1,12 @@
-
-
Exe
- net6.0
+ net8.0
cs104_server3
enable
enable
-
-
+
\ No newline at end of file
diff --git a/examples/cs104-tls-client/Program.cs b/examples/cs104-tls-client/Program.cs
index 792186c..130be13 100644
--- a/examples/cs104-tls-client/Program.cs
+++ b/examples/cs104-tls-client/Program.cs
@@ -10,113 +10,113 @@
namespace cs104_tls_client
{
- class MainClass
- {
-
- private static void ConnectionHandler (object parameter, ConnectionEvent connectionEvent)
- {
- switch (connectionEvent) {
- case ConnectionEvent.OPENED:
- Console.WriteLine ("Connected");
- break;
- case ConnectionEvent.CLOSED:
- Console.WriteLine ("Connection closed");
- break;
- case ConnectionEvent.STARTDT_CON_RECEIVED:
- Console.WriteLine ("STARTDT CON received");
- break;
- case ConnectionEvent.STOPDT_CON_RECEIVED:
- Console.WriteLine ("STOPDT CON received");
- break;
- }
- }
+ class MainClass
+ {
+
+ private static void ConnectionHandler (object parameter, ConnectionEvent connectionEvent)
+ {
+ switch (connectionEvent) {
+ case ConnectionEvent.OPENED:
+ Console.WriteLine ("Connected");
+ break;
+ case ConnectionEvent.CLOSED:
+ Console.WriteLine ("Connection closed");
+ break;
+ case ConnectionEvent.STARTDT_CON_RECEIVED:
+ Console.WriteLine ("STARTDT CON received");
+ break;
+ case ConnectionEvent.STOPDT_CON_RECEIVED:
+ Console.WriteLine ("STOPDT CON received");
+ break;
+ }
+ }
- private static bool asduReceivedHandler(object parameter, ASDU asdu)
- {
- Console.WriteLine (asdu.ToString ());
+ private static bool asduReceivedHandler(object parameter, ASDU asdu)
+ {
+ Console.WriteLine (asdu.ToString ());
- if (asdu.TypeId == TypeID.M_SP_NA_1) {
+ if (asdu.TypeId == TypeID.M_SP_NA_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointInformation)asdu.GetElement (i);
+ var val = (SinglePointInformation)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_TE_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_TE_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
+ var msv = (MeasuredValueScaledWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- Console.WriteLine (" " + msv.Timestamp.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ Console.WriteLine (" " + msv.Timestamp.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_TF_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShortWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.ToString ());
- Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.ToString ());
+ Console.WriteLine (" " + mfv.Timestamp.GetDateTime ().ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_SP_TB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
+ var val = (SinglePointWithCP56Time2a)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
- Console.WriteLine (" " + val.Quality.ToString ());
- Console.WriteLine (" " + val.Timestamp.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
+ Console.WriteLine (" IOA: " + val.ObjectAddress + " SP value: " + val.Value);
+ Console.WriteLine (" " + val.Quality.ToString ());
+ Console.WriteLine (" " + val.Timestamp.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NC_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
- var mfv = (MeasuredValueShort)asdu.GetElement (i);
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
+ var mfv = (MeasuredValueShort)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
- Console.WriteLine (" " + mfv.Quality.ToString ());
- }
- } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
+ Console.WriteLine (" IOA: " + mfv.ObjectAddress + " float value: " + mfv.Value);
+ Console.WriteLine (" " + mfv.Quality.ToString ());
+ }
+ } else if (asdu.TypeId == TypeID.M_ME_NB_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueScaled)asdu.GetElement (i);
+ var msv = (MeasuredValueScaled)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
- Console.WriteLine (" " + msv.Quality.ToString ());
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.ScaledValue);
+ Console.WriteLine (" " + msv.Quality.ToString ());
+ }
- } else if (asdu.TypeId == TypeID.M_ME_ND_1) {
+ } else if (asdu.TypeId == TypeID.M_ME_ND_1) {
- for (int i = 0; i < asdu.NumberOfElements; i++) {
+ for (int i = 0; i < asdu.NumberOfElements; i++) {
- var msv = (MeasuredValueNormalizedWithoutQuality)asdu.GetElement (i);
+ var msv = (MeasuredValueNormalizedWithoutQuality)asdu.GetElement (i);
- Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.NormalizedValue);
- }
+ Console.WriteLine (" IOA: " + msv.ObjectAddress + " scaled value: " + msv.NormalizedValue);
+ }
- } else if (asdu.TypeId == TypeID.C_IC_NA_1) {
- if (asdu.Cot == CauseOfTransmission.ACTIVATION_CON)
- Console.WriteLine ((asdu.IsNegative ? "Negative" : "Positive") + "confirmation for interrogation command");
- else if (asdu.Cot == CauseOfTransmission.ACTIVATION_TERMINATION)
- Console.WriteLine ("Interrogation command terminated");
- }
- else {
- Console.WriteLine ("Unknown message type!");
- }
+ } else if (asdu.TypeId == TypeID.C_IC_NA_1) {
+ if (asdu.Cot == CauseOfTransmission.ACTIVATION_CON)
+ Console.WriteLine ((asdu.IsNegative ? "Negative" : "Positive") + "confirmation for interrogation command");
+ else if (asdu.Cot == CauseOfTransmission.ACTIVATION_TERMINATION)
+ Console.WriteLine ("Interrogation command terminated");
+ }
+ else {
+ Console.WriteLine ("Unknown message type!");
+ }
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
+ public static void Main (string[] args)
+ {
string hostname = "127.0.0.1";
if (args.Length > 0)
@@ -126,75 +126,76 @@ public static void Main (string[] args)
Console.WriteLine("Using hostname: " + hostname);
}
+ Console.WriteLine ("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString ());
- Console.WriteLine ("Using lib60870.NET version " + LibraryCommon.GetLibraryVersionString ());
-
- // Own certificate has to be a pfx file that contains the private key
- X509Certificate2 ownCertificate = new X509Certificate2 ("client1.pfx");
+ // Own certificate has to be a pfx file that contains the private key
+ X509Certificate2 ownCertificate = new X509Certificate2 ("client1.pfx");
- // Create a new security information object to configure TLS
- TlsSecurityInformation secInfo = new TlsSecurityInformation (null, ownCertificate);
+ // Create a new security information object to configure TLS
+ TlsSecurityInformation secInfo = new TlsSecurityInformation (null, ownCertificate);
- // Add allowed server certificates - not required when AllowOnlySpecificCertificates == false
- secInfo.AddAllowedCertificate (new X509Certificate2 ("server.cer"));
+ // Add allowed server certificates - not required when AllowOnlySpecificCertificates == false
+ secInfo.AddAllowedCertificate (new X509Certificate2 ("server.cer"));
- // Add a CA certificate to check the certificate provided by the server - not required when ChainValidation == false
- secInfo.AddCA (new X509Certificate2 ("root.cer"));
+ // Add a CA certificate to check the certificate provided by the server - not required when ChainValidation == false
+ secInfo.AddCA (new X509Certificate2 ("root.cer"));
- // Check if the certificate is signed by a provided CA
- secInfo.ChainValidation = true;
+ // Check if the certificate is signed by a provided CA
+ secInfo.ChainValidation = false;
- // Check that the shown server certificate is in the list of allowed certificates
- secInfo.AllowOnlySpecificCertificates = true;
+ secInfo.TlsVersion = SslProtocols.Tls13;
- Connection con = new Connection (hostname);
+ // Check that the shown server certificate is in the list of allowed certificates
+ secInfo.AllowOnlySpecificCertificates = true;
- // Set security information object, this will force the connection using TLS (using TCP port 19998)
- con.SetTlsSecurity (secInfo);
+ Connection con = new Connection (hostname, 19998);
- con.DebugOutput = true;
+ // Set security information object, this will force the connection using TLS (using TCP port 19998)
+ con.SetTlsSecurity (secInfo);
- con.SetASDUReceivedHandler (asduReceivedHandler, null);
- con.SetConnectionHandler (ConnectionHandler, null);
+ con.DebugOutput = true;
+
+ con.SetASDUReceivedHandler (asduReceivedHandler, null);
+ con.SetConnectionHandler (ConnectionHandler, null);
- con.Connect ();
+ con.Connect ();
- Thread.Sleep (5000);
+ Thread.Sleep (5000);
- con.SendTestCommand (1);
+ con.SendTestCommand (1);
- con.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, QualifierOfInterrogation.STATION);
+ con.SendInterrogationCommand (CauseOfTransmission.ACTIVATION, 1, QualifierOfInterrogation.STATION);
- Thread.Sleep (5000);
+ Thread.Sleep (5000);
- con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new SingleCommand (5000, true, false, 0));
+ con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new SingleCommand (5000, true, false, 0));
- con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new DoubleCommand (5001, DoubleCommand.ON, false, 0));
+ con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new DoubleCommand (5001, DoubleCommand.ON, false, 0));
- con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new StepCommand (5002, StepCommandValue.HIGHER, false, 0));
+ con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1, new StepCommand (5002, StepCommandValue.HIGHER, false, 0));
- con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1,
- new SingleCommandWithCP56Time2a (5000, false, false, 0, new CP56Time2a (DateTime.Now)));
+ con.SendControlCommand (CauseOfTransmission.ACTIVATION, 1,
+ new SingleCommandWithCP56Time2a (5000, false, false, 0, new CP56Time2a (DateTime.Now)));
- /* Synchronize clock of the controlled station */
- con.SendClockSyncCommand (1 /* CA */, new CP56Time2a (DateTime.Now));
+ /* Synchronize clock of the controlled station */
+ con.SendClockSyncCommand (1 /* CA */, new CP56Time2a (DateTime.Now));
- Console.WriteLine ("CLOSE");
+ Console.WriteLine ("CLOSE");
- con.Close ();
+ con.Close ();
- Console.WriteLine ("RECONNECT");
+ Console.WriteLine ("RECONNECT");
- con.Connect ();
+ con.Connect ();
- Thread.Sleep (5000);
+ Thread.Sleep (5000);
- Console.WriteLine ("CLOSE 2");
+ Console.WriteLine ("CLOSE 2");
- con.Close ();
+ con.Close ();
- Console.WriteLine("Press any key to terminate...");
- Console.ReadKey();
- }
- }
+ Console.WriteLine("Press any key to terminate...");
+ Console.ReadKey();
+ }
+ }
}
diff --git a/examples/cs104-tls-client/client1.pfx b/examples/cs104-tls-client/client1.pfx
new file mode 100644
index 0000000..12217ca
Binary files /dev/null and b/examples/cs104-tls-client/client1.pfx differ
diff --git a/examples/cs104-tls-client/cs104-tls-client.csproj b/examples/cs104-tls-client/cs104-tls-client.csproj
index 17c91f3..d6cebd7 100644
--- a/examples/cs104-tls-client/cs104-tls-client.csproj
+++ b/examples/cs104-tls-client/cs104-tls-client.csproj
@@ -1,30 +1,23 @@
-
-
-
- Exe
- net6.0
- cs104_tls_client
- enable
- enable
-
-
-
- client1.pfx
+
PreserveNewest
-
- root.cer
+
PreserveNewest
-
- server.cer
+
PreserveNewest
-
+
+ Exe
+ net8.0
+ cs104_tls_client
+ enable
+ enable
+
\ No newline at end of file
diff --git a/examples/cs104-tls-client/root.cer b/examples/cs104-tls-client/root.cer
new file mode 100644
index 0000000..8768344
Binary files /dev/null and b/examples/cs104-tls-client/root.cer differ
diff --git a/examples/cs104-tls-client/server.cer b/examples/cs104-tls-client/server.cer
new file mode 100644
index 0000000..957bdc3
Binary files /dev/null and b/examples/cs104-tls-client/server.cer differ
diff --git a/examples/cs104-tls-server/Program.cs b/examples/cs104-tls-server/Program.cs
index 6584f47..55d8866 100644
--- a/examples/cs104-tls-server/Program.cs
+++ b/examples/cs104-tls-server/Program.cs
@@ -1,9 +1,5 @@
-using System;
-using System.Security.Cryptography.X509Certificates;
-using System.Net.Security;
-using System.Net;
+using System.Security.Cryptography.X509Certificates;
using System.Security.Authentication;
-using System.Threading;
using lib60870;
using lib60870.CS101;
@@ -11,168 +7,171 @@
namespace cs104_tls_server
{
- class MainClass
- {
+ class MainClass
+ {
- private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
- {
- Console.WriteLine ("Interrogation for group " + qoi);
+ private static bool interrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi)
+ {
+ Console.WriteLine ("Interrogation for group " + qoi);
- ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
+ ApplicationLayerParameters cp = connection.GetApplicationLayerParameters ();
- try {
+ try {
- connection.SendACT_CON (asdu, false);
+ connection.SendACT_CON (asdu, false);
- // send information objects
- ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ // send information objects
+ ASDU newAsdu = new ASDU(cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (100, -1, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (101, 23, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new MeasuredValueScaled (102, 2300, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 3, 1, false);
- newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
+ newAsdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(103, 3456, new QualityDescriptor (), new CP56Time2a(DateTime.Now)));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, false);
- newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
+ newAsdu.AddInformationObject (new SinglePointWithCP56Time2a (104, true, new QualityDescriptor (), new CP56Time2a (DateTime.Now)));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- // send sequence of information objects
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
+ // send sequence of information objects
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
- newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
- newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (200, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (201, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (202, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (203, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (204, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (205, false, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (206, true, new QualityDescriptor ()));
+ newAsdu.AddInformationObject (new SinglePointInformation (207, false, new QualityDescriptor ()));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
+ newAsdu = new ASDU (cp, CauseOfTransmission.INTERROGATED_BY_STATION, false, false, 2, 1, true);
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
- newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (300, -1.0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (301, -0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (302, -0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (303, .0f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (304, 0.1f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (305, 0.2f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (306, 0.5f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (307, 0.7f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (308, 0.99f));
+ newAsdu.AddInformationObject (new MeasuredValueNormalizedWithoutQuality (309, 1f));
- connection.SendASDU (newAsdu);
+ connection.SendASDU (newAsdu);
- connection.SendACT_TERM (asdu);
- }
- catch (ConnectionException) {
- Console.WriteLine ("Client exception closed unexpectedly");
- }
+ connection.SendACT_TERM (asdu);
+ }
+ catch (ConnectionException) {
+ Console.WriteLine ("Client exception closed unexpectedly");
+ }
- return true;
- }
+ return true;
+ }
- private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
- {
+ private static bool asduHandler(object parameter, IMasterConnection connection, ASDU asdu)
+ {
- if (asdu.TypeId == TypeID.C_SC_NA_1) {
- Console.WriteLine ("Single command");
+ if (asdu.TypeId == TypeID.C_SC_NA_1) {
+ Console.WriteLine ("Single command");
- SingleCommand sc = (SingleCommand)asdu.GetElement (0);
+ SingleCommand sc = (SingleCommand)asdu.GetElement (0);
- Console.WriteLine (sc.ToString ());
- }
- else if (asdu.TypeId == TypeID.C_CS_NA_1){
+ Console.WriteLine (sc.ToString ());
+ }
+ else if (asdu.TypeId == TypeID.C_CS_NA_1){
- ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
+ ClockSynchronizationCommand qsc = (ClockSynchronizationCommand)asdu.GetElement (0);
- Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
- }
+ Console.WriteLine ("Received clock sync command with time " + qsc.NewTime.ToString());
+ }
- return true;
- }
+ return true;
+ }
- public static void Main (string[] args)
- {
- bool running = true;
+ public static void Main (string[] args)
+ {
+ bool running = true;
- Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
- e.Cancel = true;
- running = false;
- };
-
- // Own certificate has to be a pfx file that contains the private key
- X509Certificate2 ownCertificate = new X509Certificate2 ("server.pfx");
+ Console.CancelKeyPress += delegate(object? sender, ConsoleCancelEventArgs e) {
+ e.Cancel = true;
+ running = false;
+ };
+
+ // Own certificate has to be a pfx file that contains the private key
+ X509Certificate2 ownCertificate = new X509Certificate2 ("server.pfx");
- // Create a new security information object to configure TLS
- TlsSecurityInformation secInfo = new TlsSecurityInformation (ownCertificate);
+ // Create a new security information object to configure TLS
+ TlsSecurityInformation secInfo = new TlsSecurityInformation (ownCertificate);
- // Add allowed client certificates - not required when AllowOnlySpecificCertificates == false
- secInfo.AddAllowedCertificate (new X509Certificate2 ("client1.cer"));
- secInfo.AddAllowedCertificate (new X509Certificate2 ("client2.cer"));
+ // Add allowed client certificates - not required when AllowOnlySpecificCertificates == false
+ secInfo.AddAllowedCertificate (new X509Certificate2 ("client1.cer"));
+ //secInfo.AddAllowedCertificate (new X509Certificate2 ("client_CA1_2.pem"));
- // Add a CA certificate to check the certificate provided by the server - not required when ChainValidation == false
- secInfo.AddCA (new X509Certificate2 ("root.cer"));
+ // Add a CA certificate to check the certificate provided by the server - not required when ChainValidation == false
+ secInfo.AddCA (new X509Certificate2 ("root.cer"));
- // Check if the certificate is signed by a provided CA
- secInfo.ChainValidation = true;
+ // Check if the certificate is signed by a provided CA
+ secInfo.ChainValidation = true;
- // Check that the shown client certificate is in the list of allowed certificates
- secInfo.AllowOnlySpecificCertificates = true;
+ // Allow only TLS versions 1.2 and 1.3
+ secInfo.TlsVersion = SslProtocols.Tls12 | SslProtocols.Tls13;
- // Use constructor with security information object, this will force the connection using TLS (using TCP port 19998)
- Server server = new Server (secInfo);
+ // Check that the shown client certificate is in the list of allowed certificates
+ secInfo.AllowOnlySpecificCertificates = false;
- server.DebugOutput = true;
+ // Use constructor with security information object, this will force the connection using TLS (using TCP port 19998)
+ Server server = new Server (secInfo);
- server.MaxQueueSize = 10;
+ server.DebugOutput = true;
- server.SetInterrogationHandler (interrogationHandler, null);
+ server.MaxQueueSize = 10;
- server.SetASDUHandler (asduHandler, null);
+ server.SetInterrogationHandler (interrogationHandler, null);
- server.Start ();
+ server.SetASDUHandler (asduHandler, null);
- ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
- EndOfInitialization eoi = new EndOfInitialization(0);
- newAsdu.AddInformationObject(eoi);
- server.EnqueueASDU(newAsdu);
+ server.Start ();
- int waitTime = 1000;
+ ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
+ EndOfInitialization eoi = new EndOfInitialization(0);
+ newAsdu.AddInformationObject(eoi);
+ server.EnqueueASDU(newAsdu);
- while (running) {
- Thread.Sleep(100);
+ int waitTime = 1000;
- if (waitTime > 0)
- waitTime -= 100;
- else {
+ while (running) {
+ Thread.Sleep(100);
- newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 2, 1, false);
+ if (waitTime > 0)
+ waitTime -= 100;
+ else {
- newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
+ newAsdu = new ASDU (server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 2, 1, false);
- server.EnqueueASDU (newAsdu);
+ newAsdu.AddInformationObject (new MeasuredValueScaled (110, -1, new QualityDescriptor ()));
- waitTime = 1000;
- }
- }
+ server.EnqueueASDU (newAsdu);
- Console.WriteLine ("Stop server");
- server.Stop ();;
- }
- }
+ waitTime = 1000;
+ }
+ }
+
+ Console.WriteLine ("Stop server");
+ server.Stop ();;
+ }
+ }
}
diff --git a/examples/cs104-tls-server/client1.cer b/examples/cs104-tls-server/client1.cer
new file mode 100644
index 0000000..a5aa8db
Binary files /dev/null and b/examples/cs104-tls-server/client1.cer differ
diff --git a/examples/cs104-tls-server/cs104-tls-server.csproj b/examples/cs104-tls-server/cs104-tls-server.csproj
index 401c5c1..002bc14 100644
--- a/examples/cs104-tls-server/cs104-tls-server.csproj
+++ b/examples/cs104-tls-server/cs104-tls-server.csproj
@@ -1,34 +1,26 @@
-
+
+
+
-
-
- Exe
- net6.0
- cs104_tls_server
- enable
- enable
-
-
-
- client1.cer
+
PreserveNewest
-
- client2.cer
+
PreserveNewest
-
- root.cer
- PreserveNewest
-
-
- server.pfx
+
PreserveNewest
-
+
+ Exe
+ net8.0
+ cs104_tls_server
+ enable
+ enable
+
\ No newline at end of file
diff --git a/examples/cs104-tls-server/root.cer b/examples/cs104-tls-server/root.cer
new file mode 100644
index 0000000..8768344
Binary files /dev/null and b/examples/cs104-tls-server/root.cer differ
diff --git a/examples/cs104-tls-server/server.pfx b/examples/cs104-tls-server/server.pfx
new file mode 100644
index 0000000..953ea66
Binary files /dev/null and b/examples/cs104-tls-server/server.pfx differ
diff --git a/examples/tls-certs/new/client_CA1_1.key b/examples/tls-certs/new/client_CA1_1.key
new file mode 100644
index 0000000..23e672f
--- /dev/null
+++ b/examples/tls-certs/new/client_CA1_1.key
@@ -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-----
diff --git a/examples/tls-certs/new/client_CA1_1.pem b/examples/tls-certs/new/client_CA1_1.pem
new file mode 100644
index 0000000..580cff4
--- /dev/null
+++ b/examples/tls-certs/new/client_CA1_1.pem
@@ -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-----
diff --git a/examples/tls-certs/new/client_CA1_1.pfx b/examples/tls-certs/new/client_CA1_1.pfx
new file mode 100644
index 0000000..f2437b2
Binary files /dev/null and b/examples/tls-certs/new/client_CA1_1.pfx differ
diff --git a/examples/tls-certs/new/client_CA1_2.pem b/examples/tls-certs/new/client_CA1_2.pem
new file mode 100644
index 0000000..156b3ee
--- /dev/null
+++ b/examples/tls-certs/new/client_CA1_2.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWzCCAkMCFFTJkICEIidmnrisIFxZ99KKLhDGMA0GCSqGSIb3DQEBCwUAMGox
+CzAJBgNVBAYTAkRFMQswCQYDVQQIDAJCVzERMA8GA1UEBwwIRnJlaWJ1cmcxGzAZ
+BgNVBAoMEk1aIEF1dG9tYXRpb24gR21iSDEMMAoGA1UECwwDUiZEMRAwDgYDVQQD
+DAdyb290X0NBMB4XDTIyMDMxODA5MzY0OVoXDTIyMDQxNzA5MzY0OVowajELMAkG
+A1UEBhMCREUxCzAJBgNVBAgMAkJXMREwDwYDVQQHDAhGcmVpYnVyZzEbMBkGA1UE
+CgwSTVogQXV0b21hdGlvbiBHbWJIMQwwCgYDVQQLDANSJkQxEDAOBgNVBAMMB2Ns
+aWVudDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8hnd/sd/nLJrN
+wfei5lphgS71fpP567xgkrGaEy0/lyISeuYK8nkrHI3T5cz0IHsVRGai0cQPdrP5
+0Vdn7kj2rM/WzD7RzSRZIBydd4tnCZBz/bktTRtYNVve7+HaP9t4FxQdkMF4K3Jm
+E/j/gYSYimR49DFjXI2om9kXWZ6pdQUyaKUr+zxgmovtQjtc7wdTdKmsKFQxQQhd
+zWQajEpaNTL+JTJyrCZctryaILgYv4cvQbpF54zbcRsdoPRkP6azsR9GDhCYydwz
+UGMr2CZ2C4BRwB8ChswDsU6iww5enF2vWIUY36VvjmSDDrsH1w4V9UCkSjYMg0VQ
+Mmwc8kerAgMBAAEwDQYJKoZIhvcNAQELBQADggEBALYG8KSPm82uvgmeto76kL+N
+nUgV1ojxj+X9yBrbrkgo4rnsXFU1NUqncdCfpvA7u9mqAjZ4KN+ORZIUp1SXUl3Z
+TIpBClO5ML7wz1Iy6QrExeFwb2783Gl1jeq0lSQwWffNMwkPEqG1QYr/2IK9eSRJ
+hDrure/Ys3s5F7grQ6vTWBQrEXynd81YqqZuBFFs7FuLhg0GK/OdpJ5i2BsLS+B3
+nEOxmgxZ1qLSqbYDjhawsjiSItvO8XxZjA99n3MpBVharqqwp0Xpkm+X30rWolUp
+wur154X0dMZh8jF98KVrB/3Te9aidtyuO9EiGU2Qbkre7jK+Ol3nITR50Gy8yYU=
+-----END CERTIFICATE-----
diff --git a/examples/tls-certs/new/root_CA1.pem b/examples/tls-certs/new/root_CA1.pem
new file mode 100644
index 0000000..0e7045a
--- /dev/null
+++ b/examples/tls-certs/new/root_CA1.pem
@@ -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-----
diff --git a/examples/tls-certs/new/server_CA1_1.key b/examples/tls-certs/new/server_CA1_1.key
new file mode 100644
index 0000000..e0402b2
--- /dev/null
+++ b/examples/tls-certs/new/server_CA1_1.key
@@ -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-----
diff --git a/examples/tls-certs/new/server_CA1_1.pem b/examples/tls-certs/new/server_CA1_1.pem
new file mode 100644
index 0000000..1a55213
--- /dev/null
+++ b/examples/tls-certs/new/server_CA1_1.pem
@@ -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-----
diff --git a/examples/tls-certs/new/server_CA1_1.pfx b/examples/tls-certs/new/server_CA1_1.pfx
new file mode 100644
index 0000000..2fb59ec
Binary files /dev/null and b/examples/tls-certs/new/server_CA1_1.pfx differ
diff --git a/lib60870.NET.sln b/lib60870.NET.sln
index e043503..86d7037 100644
--- a/lib60870.NET.sln
+++ b/lib60870.NET.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
+VisualStudioVersion = 17.9.34714.143
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lib60870", "lib60870\lib60870.csproj", "{905602CE-60D0-4890-8E39-1FA7E695DD2E}"
EndProject
diff --git a/lib60870/ASDUParsingException.cs b/lib60870/ASDUParsingException.cs
index 1183b0a..b123507 100644
--- a/lib60870/ASDUParsingException.cs
+++ b/lib60870/ASDUParsingException.cs
@@ -1,23 +1,23 @@
- /*
- * Copyright 2016-2022 Michael Zillgith
- *
- * This file is part of lib60870.NET
- *
- * lib60870.NET 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.NET 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.NET. If not, see .
- *
- * See COPYING file for the complete license text.
- */
+/*
+ * Copyright 2016-2025 Michael Zillgith
+ *
+ * This file is part of lib60870.NET
+ *
+ * lib60870.NET 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.NET 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.NET. If not, see .
+ *
+ * See COPYING file for the complete license text.
+ */
using System;
diff --git a/lib60870/ASDUQueue.cs b/lib60870/ASDUQueue.cs
new file mode 100644
index 0000000..a38fb83
--- /dev/null
+++ b/lib60870/ASDUQueue.cs
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2016-2025 Michael Zillgith
+ *
+ * This file is part of lib60870.NET
+ *
+ * lib60870.NET 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.NET 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.NET. If not, see .
+ *
+ * See COPYING file for the complete license text.
+ */
+
+using lib60870.CS101;
+using lib60870.CS104;
+using System;
+using System.Threading;
+
+namespace lib60870
+{
+ public class ASDUQueue
+ {
+
+ private enum QueueEntryState
+ {
+ NOT_USED,
+ WAITING_FOR_TRANSMISSION,
+ SENT_BUT_NOT_CONFIRMED
+ }
+
+ private struct ASDUQueueEntry
+ {
+ public long entryTimestamp;
+ public BufferFrame asdu;
+ public QueueEntryState state;
+ }
+
+ private ASDUQueueEntry[] enqueuedASDUs = null;
+ private int oldestQueueEntry = -1; /* first entry in FIFO */
+ private int latestQueueEntry = -1; /* last entry in FIFO */
+ private int numberOfAsduInQueue = 0; /* number of messages (ASDU) in the queue */
+ public SemaphoreSlim queueLock = new SemaphoreSlim(1, 1);
+ private int maxQueueSize;
+
+ private EnqueueMode enqueueMode;
+
+ private ApplicationLayerParameters parameters;
+
+ private Action DebugLog = null;
+
+ public int NumberOfAsduInQueue { get => numberOfAsduInQueue; set => numberOfAsduInQueue = value; }
+
+ public ASDUQueue(int maxQueueSize, EnqueueMode enqueueMode, ApplicationLayerParameters parameters, Action DebugLog)
+ {
+ enqueuedASDUs = new ASDUQueueEntry[maxQueueSize];
+
+ for (int i = 0; i < maxQueueSize; i++)
+ {
+ enqueuedASDUs[i].asdu = new BufferFrame(new byte[260], 6);
+ enqueuedASDUs[i].state = QueueEntryState.NOT_USED;
+ }
+
+ this.enqueueMode = enqueueMode;
+ oldestQueueEntry = -1;
+ latestQueueEntry = -1;
+ NumberOfAsduInQueue = 0;
+ this.maxQueueSize = maxQueueSize;
+ this.parameters = parameters;
+ this.DebugLog = DebugLog;
+ }
+
+ public void EnqueueAsdu(ASDU asdu)
+ {
+ lock (enqueuedASDUs)
+ {
+ if (NumberOfAsduInQueue == 0)
+ {
+ oldestQueueEntry = 0;
+ latestQueueEntry = 0;
+ NumberOfAsduInQueue = 1;
+
+ enqueuedASDUs[0].asdu.ResetFrame();
+ asdu.Encode(enqueuedASDUs[0].asdu, parameters);
+
+ enqueuedASDUs[0].entryTimestamp = SystemUtils.currentTimeMillis();
+ enqueuedASDUs[0].state = QueueEntryState.WAITING_FOR_TRANSMISSION;
+ }
+ else
+ {
+ bool enqueue = true;
+
+ if (NumberOfAsduInQueue == maxQueueSize)
+ {
+ if (enqueueMode == EnqueueMode.REMOVE_OLDEST)
+ {
+ }
+ else if (enqueueMode == EnqueueMode.IGNORE)
+ {
+ DebugLog("Queue is full. Ignore new ASDU.");
+ enqueue = false;
+ }
+ else if (enqueueMode == EnqueueMode.THROW_EXCEPTION)
+ {
+ throw new ASDUQueueException("Event queue is full.");
+ }
+ }
+
+ if (enqueue)
+ {
+ latestQueueEntry = (latestQueueEntry + 1) % maxQueueSize;
+
+ if (latestQueueEntry == oldestQueueEntry)
+ oldestQueueEntry = (oldestQueueEntry + 1) % maxQueueSize;
+ else
+ NumberOfAsduInQueue++;
+
+ enqueuedASDUs[latestQueueEntry].asdu.ResetFrame();
+ asdu.Encode(enqueuedASDUs[latestQueueEntry].asdu, parameters);
+ enqueuedASDUs[latestQueueEntry].entryTimestamp = SystemUtils.currentTimeMillis();
+ enqueuedASDUs[latestQueueEntry].state = QueueEntryState.WAITING_FOR_TRANSMISSION;
+
+
+ }
+
+ }
+
+ }
+
+ DebugLog("Queue contains " + NumberOfAsduInQueue + " messages (oldest: " + oldestQueueEntry + " latest: " + latestQueueEntry + ")");
+
+ }
+
+ public void LockASDUQueue()
+ {
+ Monitor.Enter(enqueuedASDUs);
+ }
+
+ public void UnlockASDUQueue()
+ {
+ Monitor.Exit(enqueuedASDUs);
+ }
+
+ public bool MessageQueue_hasUnconfirmedIMessages()
+ {
+ bool retVal = false;
+
+ if (NumberOfAsduInQueue != 0)
+ {
+ int currentIndex = oldestQueueEntry;
+
+ while (currentIndex > 0)
+ {
+ if (enqueuedASDUs[currentIndex].state == QueueEntryState.SENT_BUT_NOT_CONFIRMED)
+ {
+ retVal = true;
+ break;
+ }
+
+ if (currentIndex == latestQueueEntry)
+ break;
+
+ if (enqueuedASDUs[currentIndex].state == QueueEntryState.NOT_USED)
+ break;
+
+ currentIndex = (currentIndex + 1) % maxQueueSize;
+ }
+ }
+ return retVal;
+ }
+
+ internal bool IsAsduAvailable()
+ {
+ if (enqueuedASDUs == null)
+ return false;
+
+ if (NumberOfAsduInQueue > 0)
+ {
+ int currentIndex = oldestQueueEntry;
+
+ while (enqueuedASDUs[currentIndex].state != QueueEntryState.WAITING_FOR_TRANSMISSION)
+ {
+ if (currentIndex == latestQueueEntry)
+ break;
+
+ if (enqueuedASDUs[currentIndex].state == QueueEntryState.NOT_USED)
+ break;
+
+ currentIndex = (currentIndex + 1) % maxQueueSize;
+
+ }
+
+ if (enqueuedASDUs[currentIndex].state == QueueEntryState.WAITING_FOR_TRANSMISSION)
+ {
+ enqueuedASDUs[currentIndex].state = QueueEntryState.SENT_BUT_NOT_CONFIRMED;
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+ }
+
+ internal bool IsHighPriorityAsduAvailable()
+ {
+ if (enqueuedASDUs == null)
+ return false;
+
+ if (NumberOfAsduInQueue > 0)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ internal BufferFrame GetNextWaitingASDU(out long timestamp, out int index)
+ {
+ timestamp = 0;
+ index = -1;
+
+ if (enqueuedASDUs == null)
+ return null;
+
+ if (NumberOfAsduInQueue > 0)
+ {
+ int currentIndex = oldestQueueEntry;
+
+ while (enqueuedASDUs[currentIndex].state != QueueEntryState.WAITING_FOR_TRANSMISSION)
+ {
+ if (currentIndex == latestQueueEntry)
+ break;
+
+ if (enqueuedASDUs[currentIndex].state == QueueEntryState.NOT_USED)
+ break;
+
+ currentIndex = (currentIndex + 1) % maxQueueSize;
+
+ }
+
+ if (enqueuedASDUs[currentIndex].state == QueueEntryState.WAITING_FOR_TRANSMISSION)
+ {
+ enqueuedASDUs[currentIndex].state = QueueEntryState.SENT_BUT_NOT_CONFIRMED;
+ timestamp = enqueuedASDUs[currentIndex].entryTimestamp;
+ index = currentIndex;
+ return enqueuedASDUs[currentIndex].asdu;
+ }
+
+ return null;
+ }
+
+ return null;
+ }
+
+ internal BufferFrame GetNextHighPriorityWaitingASDU(out long timestamp, out int index)
+ {
+ timestamp = 0;
+ index = -1;
+
+ if (enqueuedASDUs == null)
+ return null;
+
+ if (NumberOfAsduInQueue > 0)
+ {
+ NumberOfAsduInQueue--;
+
+ int currentIndex = oldestQueueEntry;
+ oldestQueueEntry = (oldestQueueEntry + 1) % maxQueueSize;
+
+ BufferFrame bufferFrame = enqueuedASDUs[currentIndex].asdu;
+
+ timestamp = enqueuedASDUs[currentIndex].entryTimestamp;
+ index = currentIndex;
+
+ return bufferFrame;
+ }
+
+ return null;
+ }
+
+ public void UnmarkAllASDUs()
+ {
+ lock (enqueuedASDUs)
+ {
+ if (NumberOfAsduInQueue > 0)
+ {
+ for (int i = 0; i < enqueuedASDUs.Length; i++)
+ {
+ if (enqueuedASDUs[i].state == QueueEntryState.SENT_BUT_NOT_CONFIRMED)
+ enqueuedASDUs[i].state = QueueEntryState.WAITING_FOR_TRANSMISSION;
+ }
+ }
+ }
+ }
+
+ public void MarkASDUAsConfirmed(int index, long timestamp)
+ {
+ if (enqueuedASDUs == null)
+ return;
+
+ if ((index < 0) || (index > enqueuedASDUs.Length))
+ return;
+
+ lock (enqueuedASDUs)
+ {
+ if (numberOfAsduInQueue > 0)
+ {
+ if (enqueuedASDUs[index].state == QueueEntryState.SENT_BUT_NOT_CONFIRMED)
+ {
+ if (enqueuedASDUs[index].entryTimestamp == timestamp)
+ {
+ int currentIndex = index;
+
+ while (enqueuedASDUs[currentIndex].state == QueueEntryState.SENT_BUT_NOT_CONFIRMED)
+ {
+ DebugLog("Remove from queue with index " + currentIndex);
+
+ enqueuedASDUs[currentIndex].state = QueueEntryState.NOT_USED;
+ enqueuedASDUs[currentIndex].entryTimestamp = 0;
+ NumberOfAsduInQueue -= 1;
+
+ if (NumberOfAsduInQueue == 0)
+ {
+ oldestQueueEntry = -1;
+ latestQueueEntry = -1;
+
+ break;
+ }
+
+ if (currentIndex == oldestQueueEntry)
+ {
+ oldestQueueEntry = (index + 1) % maxQueueSize;
+
+ if (NumberOfAsduInQueue == 1)
+ latestQueueEntry = oldestQueueEntry;
+
+ break;
+ }
+
+ currentIndex = currentIndex - 1;
+
+ if (currentIndex < 0)
+ currentIndex = maxQueueSize - 1;
+
+ if (currentIndex == index)
+ break;
+
+ }
+
+ DebugLog("queue state: noASDUs: " + NumberOfAsduInQueue + " oldest: " + oldestQueueEntry + " latest: " + latestQueueEntry);
+ }
+ }
+ }
+ }
+
+ }
+
+ }
+
+}
diff --git a/lib60870/BufferFrame.cs b/lib60870/BufferFrame.cs
index 1fa1669..d426606 100644
--- a/lib60870/BufferFrame.cs
+++ b/lib60870/BufferFrame.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870
{
///
@@ -36,7 +34,7 @@ public BufferFrame(byte[] buffer, int startPos)
{
this.buffer = buffer;
this.startPos = startPos;
- this.bufPos = startPos;
+ bufPos = startPos;
}
public BufferFrame Clone()
@@ -83,4 +81,3 @@ public override byte[] GetBuffer()
}
}
}
-
\ No newline at end of file
diff --git a/lib60870/CP16Time2a.cs b/lib60870/CP16Time2a.cs
index 3449841..43076e7 100644
--- a/lib60870/CP16Time2a.cs
+++ b/lib60870/CP16Time2a.cs
@@ -1,7 +1,7 @@
/*
* CP16Time2a.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870
{
public class CP16Time2a
@@ -63,7 +61,7 @@ public override bool Equals(object obj)
if (!(obj is CP16Time2a))
return false;
- return (this.GetHashCode() == obj.GetHashCode());
+ return (GetHashCode() == obj.GetHashCode());
}
public override int GetHashCode()
diff --git a/lib60870/CP24Time2a.cs b/lib60870/CP24Time2a.cs
index 231ca1a..2109574 100644
--- a/lib60870/CP24Time2a.cs
+++ b/lib60870/CP24Time2a.cs
@@ -1,7 +1,7 @@
/*
* CP24Time2a.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870
{
public class CP24Time2a
@@ -65,7 +63,7 @@ public override bool Equals(object obj)
if (!(obj is CP24Time2a))
return false;
- return (this.GetHashCode() == obj.GetHashCode());
+ return (GetHashCode() == obj.GetHashCode());
}
public override int GetHashCode()
@@ -113,7 +111,7 @@ public int Second
{
get
{
- return (encodedValue[0] + (encodedValue[1] * 0x100)) / 1000;
+ return (encodedValue[0] + (encodedValue[1] * 0x100)) / 1000;
}
set
@@ -196,7 +194,7 @@ public override string ToString()
{
return string.Format("[CP24Time2a: Millisecond={0}, Second={1}, Minute={2}, Invalid={3}, Substitued={4}]", Millisecond, Second, Minute, Invalid, Substitued);
}
-
+
}
}
diff --git a/lib60870/CP32Time2a.cs b/lib60870/CP32Time2a.cs
index e275351..483a1f7 100644
--- a/lib60870/CP32Time2a.cs
+++ b/lib60870/CP32Time2a.cs
@@ -1,7 +1,7 @@
/*
* CP32Time2a.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -76,7 +76,7 @@ public override bool Equals(object obj)
if (!(obj is CP32Time2a))
return false;
- return (this.GetHashCode() == obj.GetHashCode());
+ return (GetHashCode() == obj.GetHashCode());
}
public override int GetHashCode()
@@ -130,7 +130,7 @@ public int Second
{
get
{
- return (encodedValue[0] + (encodedValue[1] * 0x100)) / 1000;
+ return (encodedValue[0] + (encodedValue[1] * 0x100)) / 1000;
}
set
diff --git a/lib60870/CP56Time2a.cs b/lib60870/CP56Time2a.cs
index 04cd2ce..46951ef 100644
--- a/lib60870/CP56Time2a.cs
+++ b/lib60870/CP56Time2a.cs
@@ -1,7 +1,7 @@
/*
* CP56Time2a.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -69,7 +69,7 @@ public override bool Equals(object obj)
if (!(obj is CP56Time2a))
return false;
- return (this.GetHashCode() == obj.GetHashCode());
+ return (GetHashCode() == obj.GetHashCode());
}
public override int GetHashCode()
@@ -86,15 +86,15 @@ public DateTime GetDateTime(int startYear)
{
int baseYear = (startYear / 100) * 100;
- if (this.Year < (startYear % 100))
+ if (Year < (startYear % 100))
baseYear += 100;
- int month = this.Month;
+ int month = Month;
if (month == 0)
month = 1;
- int dayOfMonth = this.DayOfMonth;
+ int dayOfMonth = DayOfMonth;
if (dayOfMonth == 0)
dayOfMonth = 1;
@@ -103,7 +103,7 @@ public DateTime GetDateTime(int startYear)
try
{
- value = new DateTime(baseYear + this.Year, month, dayOfMonth, this.Hour, this.Minute, this.Second, this.Millisecond);
+ value = new DateTime(baseYear + Year, month, dayOfMonth, Hour, Minute, Second, Millisecond);
}
catch (ArgumentOutOfRangeException)
{
@@ -147,7 +147,7 @@ public int Second
{
get
{
- return (encodedValue[0] + (encodedValue[1] * 0x100)) / 1000;
+ return (encodedValue[0] + (encodedValue[1] * 0x100)) / 1000;
}
set
diff --git a/lib60870/CS101/ASDU.cs b/lib60870/CS101/ASDU.cs
index 0339332..6516d00 100644
--- a/lib60870/CS101/ASDU.cs
+++ b/lib60870/CS101/ASDU.cs
@@ -1,7 +1,7 @@
/*
* ASDU.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -73,7 +73,7 @@ public class ASDU
public ASDU(ApplicationLayerParameters parameters, CauseOfTransmission cot, bool isTest, bool isNegative, byte oa, int ca, bool isSequence)
: this(parameters, TypeID.M_SP_NA_1, cot, isTest, isNegative, oa, ca, isSequence)
{
- this.hasTypeId = false;
+ hasTypeId = false;
}
internal ASDU(ApplicationLayerParameters parameters, TypeID typeId, CauseOfTransmission cot, bool isTest, bool isNegative, byte oa, int ca, bool isSequence)
@@ -85,15 +85,15 @@ internal ASDU(ApplicationLayerParameters parameters, TypeID typeId, CauseOfTrans
this.isNegative = isNegative;
this.oa = oa;
this.ca = ca;
- this.spaceLeft = parameters.MaxAsduLength -
+ spaceLeft = parameters.MaxAsduLength -
parameters.SizeOfTypeId - parameters.SizeOfVSQ - parameters.SizeOfCA - parameters.SizeOfCOT;
if (isSequence)
- this.vsq = 0x80;
+ vsq = 0x80;
else
- this.vsq = 0;
+ vsq = 0;
- this.hasTypeId = true;
+ hasTypeId = true;
}
///
@@ -132,14 +132,14 @@ public bool AddInformationObject(InformationObject io)
else
{
if (informationObjects.Count == 0) // is first object?
- objectSize += parameters.SizeOfIOA;
+ objectSize += parameters.SizeOfIOA;
else
{
if (io.ObjectAddress != (informationObjects[0].ObjectAddress + informationObjects.Count))
return false;
}
}
-
+
if (objectSize <= spaceLeft)
{
@@ -166,7 +166,7 @@ public ASDU(ApplicationLayerParameters parameters, byte[] msg, int bufPos, int m
typeId = (TypeID)msg[bufPos++];
vsq = msg[bufPos++];
- this.hasTypeId = true;
+ hasTypeId = true;
byte cotByte = msg[bufPos++];
@@ -216,7 +216,7 @@ internal void Encode(Frame frame, ApplicationLayerParameters parameters)
frame.SetNextByte(cotByte);
if (parameters.SizeOfCOT == 2)
- frame.SetNextByte((byte)oa);
+ frame.SetNextByte(oa);
frame.SetNextByte((byte)(ca % 256));
@@ -227,12 +227,10 @@ internal void Encode(Frame frame, ApplicationLayerParameters parameters)
frame.AppendBytes(payload);
else
{
-
bool isFirst = true;
foreach (InformationObject io in informationObjects)
{
-
if (isFirst)
{
io.Encode(frame, parameters, false);
@@ -245,7 +243,6 @@ internal void Encode(Frame frame, ApplicationLayerParameters parameters)
else
io.Encode(frame, parameters, false);
}
-
}
}
}
@@ -272,7 +269,7 @@ public TypeID TypeId
{
get
{
- return this.typeId;
+ return typeId;
}
}
@@ -284,11 +281,11 @@ public CauseOfTransmission Cot
{
get
{
- return this.cot;
+ return cot;
}
set
{
- this.cot = value;
+ cot = value;
}
}
@@ -300,7 +297,7 @@ public byte Oa
{
get
{
- return this.oa;
+ return oa;
}
}
@@ -312,7 +309,7 @@ public bool IsTest
{
get
{
- return this.isTest;
+ return isTest;
}
}
@@ -324,7 +321,7 @@ public bool IsNegative
{
get
{
- return this.isNegative;
+ return isNegative;
}
set
{
@@ -340,7 +337,7 @@ public int Ca
{
get
{
- return this.ca;
+ return ca;
}
}
@@ -400,7 +397,7 @@ public InformationObject GetElement(int index, PrivateInformationObjectTypes pri
public InformationObject GetElement(int index, IPrivateIOFactory ioFactory)
{
InformationObject retVal = null;
-
+
if (ioFactory != null)
{
@@ -456,11 +453,11 @@ public InformationObject GetElement(int index)
}
else
retVal = new SinglePointInformation(parameters, payload, index * (parameters.SizeOfIOA + elementSize), false);
-
+
break;
case TypeID.M_SP_TA_1: /* 2 */
-
+
elementSize = 4;
if (IsSequence)
@@ -599,7 +596,7 @@ public InformationObject GetElement(int index)
}
else
retVal = new MeasuredValueNormalized(parameters, payload, index * (parameters.SizeOfIOA + elementSize), false);
-
+
break;
case TypeID.M_ME_TA_1: /* 10 */
@@ -672,7 +669,7 @@ public InformationObject GetElement(int index)
}
else
retVal = new MeasuredValueShort(parameters, payload, index * (parameters.SizeOfIOA + elementSize), false);
-
+
break;
case TypeID.M_ME_TC_1: /* 14 */
@@ -708,7 +705,7 @@ public InformationObject GetElement(int index)
}
else
retVal = new IntegratedTotals(parameters, payload, index * (parameters.SizeOfIOA + elementSize), false);
-
+
break;
case TypeID.M_IT_TA_1: /* 16 */
@@ -798,7 +795,7 @@ public InformationObject GetElement(int index)
}
else
retVal = new PackedSinglePointWithSCD(parameters, payload, index * (parameters.SizeOfIOA + elementSize), false);
-
+
break;
@@ -820,7 +817,7 @@ public InformationObject GetElement(int index)
break;
- /* 22 - 29 reserved */
+ /* 22 - 29 reserved */
case TypeID.M_SP_TB_1: /* 30 */
@@ -927,7 +924,7 @@ public InformationObject GetElement(int index)
}
else
retVal = new MeasuredValueScaledWithCP56Time2a(parameters, payload, index * (parameters.SizeOfIOA + elementSize), false);
-
+
break;
case TypeID.M_ME_TF_1: /* 36 */
@@ -981,7 +978,7 @@ public InformationObject GetElement(int index)
}
else
retVal = new EventOfProtectionEquipmentWithCP56Time2a(parameters, payload, index * (parameters.SizeOfIOA + elementSize), false);
-
+
break;
case TypeID.M_EP_TE_1: /* 39 */
@@ -999,7 +996,7 @@ public InformationObject GetElement(int index)
}
else
retVal = new PackedStartEventsOfProtectionEquipmentWithCP56Time2a(parameters, payload, index * (parameters.SizeOfIOA + elementSize), false);
-
+
break;
case TypeID.M_EP_TF_1: /* 40 */
@@ -1020,7 +1017,7 @@ public InformationObject GetElement(int index)
break;
- /* 41 - 44 reserved */
+ /* 41 - 44 reserved */
case TypeID.C_SC_NA_1: /* 45 */
@@ -1078,7 +1075,7 @@ public InformationObject GetElement(int index)
break;
- /* 52 - 57 reserved */
+ /* 52 - 57 reserved */
case TypeID.C_SC_TA_1: /* 58 - Single command with CP56Time2a */
@@ -1136,7 +1133,7 @@ public InformationObject GetElement(int index)
break;
- /* 65 - 69 reserved */
+ /* 65 - 69 reserved */
case TypeID.M_EI_NA_1: /* 70 - End of initialization */
elementSize = parameters.SizeOfCA + 1;
@@ -1209,7 +1206,7 @@ public InformationObject GetElement(int index)
break;
- /* C_TS_TA_1 (107) is handled by the stack automatically */
+ /* C_TS_TA_1 (107) is handled by the stack automatically */
case TypeID.P_ME_NA_1: /* 110 - Parameter of measured values, normalized value */
@@ -1298,22 +1295,22 @@ public InformationObject GetElement(int index)
break;
- /* 114 - 119 reserved */
+ /* 114 - 119 reserved */
default:
if (privateObjectTypes != null)
{
-
+
IPrivateIOFactory ioFactory = privateObjectTypes.GetFactory(typeId);
if (ioFactory != null)
{
-
+
elementSize = parameters.SizeOfIOA + ioFactory.GetEncodedSize();
if (IsSequence)
{
-
+
int ioa = InformationObject.ParseInformationObjectAddress(parameters, payload, 0);
retVal = ioFactory.Decode(parameters, payload, index * elementSize, true);
diff --git a/lib60870/CS101/ApplicationLayerParameters.cs b/lib60870/CS101/ApplicationLayerParameters.cs
index 5d84929..7afd021 100644
--- a/lib60870/CS101/ApplicationLayerParameters.cs
+++ b/lib60870/CS101/ApplicationLayerParameters.cs
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
public class ApplicationLayerParameters
@@ -71,7 +69,7 @@ public int SizeOfCOT
{
get
{
- return this.sizeOfCOT;
+ return sizeOfCOT;
}
set
{
@@ -83,7 +81,7 @@ public int OA
{
get
{
- return this.originatorAddress;
+ return originatorAddress;
}
set
{
@@ -95,7 +93,7 @@ public int SizeOfCA
{
get
{
- return this.sizeOfCA;
+ return sizeOfCA;
}
set
{
@@ -107,7 +105,7 @@ public int SizeOfIOA
{
get
{
- return this.sizeOfIOA;
+ return sizeOfIOA;
}
set
{
@@ -120,7 +118,7 @@ public int SizeOfTypeId
{
get
{
- return this.sizeOfTypeId;
+ return sizeOfTypeId;
}
}
@@ -128,7 +126,7 @@ public int SizeOfVSQ
{
get
{
- return this.sizeOfVSQ;
+ return sizeOfVSQ;
}
}
@@ -136,7 +134,7 @@ public int MaxAsduLength
{
get
{
- return this.maxAsduLength;
+ return maxAsduLength;
}
set
{
diff --git a/lib60870/CS101/BinaryCounterReading.cs b/lib60870/CS101/BinaryCounterReading.cs
index 8d99b7e..a1919e2 100644
--- a/lib60870/CS101/BinaryCounterReading.cs
+++ b/lib60870/CS101/BinaryCounterReading.cs
@@ -1,7 +1,7 @@
/*
* BinaryCounterReading.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
diff --git a/lib60870/CS101/Bitstring32.cs b/lib60870/CS101/Bitstring32.cs
index 18b8cf2..8236042 100644
--- a/lib60870/CS101/Bitstring32.cs
+++ b/lib60870/CS101/Bitstring32.cs
@@ -1,7 +1,7 @@
/*
* BitString32.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -25,7 +25,7 @@
namespace lib60870.CS101
{
-
+
public class Bitstring32 : InformationObject
{
override public int GetEncodedSize()
@@ -55,7 +55,7 @@ public UInt32 Value
{
get
{
- return this.value;
+ return value;
}
}
@@ -65,7 +65,7 @@ public QualityDescriptor Quality
{
get
{
- return this.quality;
+ return quality;
}
}
@@ -79,8 +79,8 @@ public Bitstring32(int ioa, UInt32 value, QualityDescriptor quality)
public Bitstring32(Bitstring32 original)
: base(original.ObjectAddress)
{
- this.value = original.value;
- this.quality = new QualityDescriptor(original.quality);
+ value = original.value;
+ quality = new QualityDescriptor(original.quality);
}
internal Bitstring32(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -143,7 +143,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -156,7 +156,7 @@ public Bitstring32WithCP24Time2a(int ioa, UInt32 value, QualityDescriptor qualit
public Bitstring32WithCP24Time2a(Bitstring32WithCP24Time2a original)
: base(original)
{
- this.timestamp = new CP24Time2a(original.timestamp);
+ timestamp = new CP24Time2a(original.timestamp);
}
internal Bitstring32WithCP24Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -211,7 +211,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -224,7 +224,7 @@ public Bitstring32WithCP56Time2a(int ioa, UInt32 value, QualityDescriptor qualit
public Bitstring32WithCP56Time2a(Bitstring32WithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal Bitstring32WithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
diff --git a/lib60870/CS101/CS101Master.cs b/lib60870/CS101/CS101Master.cs
index ea4666a..6d1f53b 100644
--- a/lib60870/CS101/CS101Master.cs
+++ b/lib60870/CS101/CS101Master.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,13 +19,12 @@
* See COPYING file for the complete license text.
*/
+using lib60870.linklayer;
using System;
-using System.IO.Ports;
using System.Collections.Generic;
-
-using lib60870.linklayer;
-using System.Threading;
using System.IO;
+using System.IO.Ports;
+using System.Threading;
namespace lib60870.CS101
{
@@ -76,14 +75,14 @@ public bool DIR
///
public void Run()
{
- if(fatalError == false)
+ if (fatalError == false)
{
linkLayer.Run();
if (fileClient != null)
fileClient.HandleFileService();
}
-
+
}
private void fatalErrorHandler(object sender, EventArgs eventArgs)
@@ -102,7 +101,7 @@ public void AddPortDeniedHandler(EventHandler eventHandler)
public void Start()
{
linkLayer.AddPortDeniedHandler(fatalErrorHandler);
-
+
if (port != null)
{
if (port.IsOpen == false)
@@ -194,17 +193,17 @@ private void DebugLog(string msg)
public CS101Master(SerialPort port, LinkLayerMode mode, LinkLayerParameters llParams = null, ApplicationLayerParameters alParams = null)
{
if (llParams == null)
- this.linkLayerParameters = new LinkLayerParameters();
+ linkLayerParameters = new LinkLayerParameters();
else
- this.linkLayerParameters = llParams;
+ linkLayerParameters = llParams;
if (alParams == null)
- this.appLayerParameters = new ApplicationLayerParameters();
+ appLayerParameters = new ApplicationLayerParameters();
else
- this.appLayerParameters = alParams;
+ appLayerParameters = alParams;
- this.transceiver = new SerialTransceiverFT12(port, linkLayerParameters, DebugLog);
+ transceiver = new SerialTransceiverFT12(port, linkLayerParameters, DebugLog);
linkLayer = new LinkLayer(buffer, linkLayerParameters, transceiver, DebugLog);
linkLayer.LinkLayerMode = mode;
@@ -216,7 +215,7 @@ public CS101Master(SerialPort port, LinkLayerMode mode, LinkLayerParameters llPa
primaryLinkLayer = new PrimaryLinkLayerBalanced(linkLayer, GetUserData, DebugLog);
linkLayer.SetPrimaryLinkLayer(primaryLinkLayer);
- secondaryLinkLayer = new SecondaryLinkLayerBalanced (linkLayer, 0, HandleApplicationLayer, DebugLog);
+ secondaryLinkLayer = new SecondaryLinkLayerBalanced(linkLayer, 0, HandleApplicationLayer, DebugLog);
linkLayer.SetSecondaryLinkLayer(secondaryLinkLayer);
userDataQueue = new Queue();
@@ -229,23 +228,23 @@ public CS101Master(SerialPort port, LinkLayerMode mode, LinkLayerParameters llPa
this.port = port;
- this.fileClient = null;
+ fileClient = null;
}
public CS101Master(Stream serialStream, LinkLayerMode mode, LinkLayerParameters llParams = null, ApplicationLayerParameters alParams = null)
{
if (llParams == null)
- this.linkLayerParameters = new LinkLayerParameters();
+ linkLayerParameters = new LinkLayerParameters();
else
- this.linkLayerParameters = llParams;
+ linkLayerParameters = llParams;
if (alParams == null)
- this.appLayerParameters = new ApplicationLayerParameters();
+ appLayerParameters = new ApplicationLayerParameters();
else
- this.appLayerParameters = alParams;
+ appLayerParameters = alParams;
- this.transceiver = new SerialTransceiverFT12(serialStream, linkLayerParameters, DebugLog);
+ transceiver = new SerialTransceiverFT12(serialStream, linkLayerParameters, DebugLog);
linkLayer = new LinkLayer(buffer, linkLayerParameters, transceiver, DebugLog);
linkLayer.LinkLayerMode = mode;
@@ -267,7 +266,7 @@ public CS101Master(Stream serialStream, LinkLayerMode mode, LinkLayerParameters
linkLayer.SetPrimaryLinkLayer(linkLayerUnbalanced);
}
- this.fileClient = null;
+ fileClient = null;
}
///
@@ -277,7 +276,7 @@ public CS101Master(Stream serialStream, LinkLayerMode mode, LinkLayerParameters
/// Timeout to wait for next characters in a message
public void SetTimeouts(int messageTimeout, int characterTimeout)
{
- this.transceiver.SetTimeouts(messageTimeout, characterTimeout);
+ transceiver.SetTimeouts(messageTimeout, characterTimeout);
}
public void SetASDUReceivedHandler(ASDUReceivedHandler handler, object parameter)
@@ -288,7 +287,7 @@ public void SetASDUReceivedHandler(ASDUReceivedHandler handler, object parameter
public void AddSlave(int slaveAddress)
{
- if(linkLayerUnbalanced != null)
+ if (linkLayerUnbalanced != null)
linkLayerUnbalanced.AddSlaveConnection(slaveAddress);
}
@@ -325,7 +324,7 @@ public int SlaveAddress
get
{
if (primaryLinkLayer == null)
- return this.slaveAddress;
+ return slaveAddress;
else
return primaryLinkLayer.LinkLayerAddressOtherStation;
}
@@ -401,7 +400,7 @@ public void RequestClass1Data(int address)
try
{
if (linkLayerUnbalanced != null)
- linkLayerUnbalanced.RequestClass1Data(address);
+ linkLayerUnbalanced.RequestClass1Data(address);
}
catch (LinkLayerBusyException)
{
@@ -608,12 +607,12 @@ public override void GetFile(int ca, int ioa, NameOfFile nof, IFileReceiver rece
fileClient.RequestFile(ca, ioa, nof, receiver);
}
- public override void SendFile (int ca, int ioa, NameOfFile nof, IFileProvider fileProvider)
+ public override void SendFile(int ca, int ioa, NameOfFile nof, IFileProvider fileProvider)
{
if (fileClient == null)
- fileClient = new FileClient (this, DebugLog);
+ fileClient = new FileClient(this, DebugLog);
- fileClient.SendFile (ca, ioa, nof, fileProvider);
+ fileClient.SendFile(ca, ioa, nof, fileProvider);
}
}
diff --git a/lib60870/CS101/CS101Slave.cs b/lib60870/CS101/CS101Slave.cs
index dea1967..016c5bb 100644
--- a/lib60870/CS101/CS101Slave.cs
+++ b/lib60870/CS101/CS101Slave.cs
@@ -1,7 +1,7 @@
/*
* CS101Slave.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2024 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,12 +21,11 @@
* See COPYING file for the complete license text.
*/
+using lib60870.linklayer;
using System;
-using System.IO.Ports;
using System.Collections.Generic;
-using lib60870;
-using lib60870.linklayer;
using System.IO;
+using System.IO.Ports;
namespace lib60870
{
@@ -47,10 +46,10 @@ private void DebugLog(string msg)
Console.WriteLine(msg);
}
}
-
+
/********************************************
- * IASDUSender
- ********************************************/
+ * IASDUSender
+ ********************************************/
void IMasterConnection.SendASDU(ASDU asdu)
{
@@ -79,8 +78,8 @@ ApplicationLayerParameters IMasterConnection.GetApplicationLayerParameters()
}
/********************************************
- * ISecondaryApplicationLayer
- ********************************************/
+ * ISecondaryApplicationLayer
+ ********************************************/
bool ISecondaryApplicationLayer.IsClass1DataAvailable()
{
@@ -109,7 +108,6 @@ bool ISecondaryApplicationLayer.HandleReceivedData(byte[] msg, bool isBroadcast,
void ISecondaryApplicationLayer.ResetCUReceived(bool onlyFcb)
{
- //TODO delete data queues
lock (userDataClass1Queue)
{
userDataClass1Queue.Clear();
@@ -121,10 +119,8 @@ void ISecondaryApplicationLayer.ResetCUReceived(bool onlyFcb)
}
/********************************************
- * END ISecondaryApplicationLayer
- ********************************************/
-
- // private bool sendLinkLayerTestFunction = false;
+ * END ISecondaryApplicationLayer
+ ********************************************/
private LinkLayer linkLayer = null;
@@ -138,7 +134,7 @@ void ISecondaryApplicationLayer.ResetCUReceived(bool onlyFcb)
private int linkLayerAddress = 0;
private int linkLayerAddressOtherStation;
- // link layer address of other station in balanced mode
+ /* link layer address of other station in balanced mode */
private Queue userDataClass1Queue = new Queue();
private int userDataClass1QueueMaxSize = 100;
@@ -163,7 +159,7 @@ public ApplicationLayerParameters Parameters
{
get
{
- return this.parameters;
+ return parameters;
}
set
{
@@ -195,7 +191,7 @@ public LinkLayerMode LinkLayerMode
{
get
{
- return this.linkLayerMode;
+ return linkLayerMode;
}
set
{
@@ -336,7 +332,7 @@ public int LinkLayerAddress
{
get
{
- return this.linkLayerAddress;
+ return linkLayerAddress;
}
set
{
@@ -352,7 +348,7 @@ public int LinkLayerAddressOtherStation
{
get
{
- return this.linkLayerAddressOtherStation;
+ return linkLayerAddressOtherStation;
}
set
{
@@ -407,10 +403,8 @@ internal void SendASDU(ASDU asdu)
EnqueueUserDataClass1(asdu);
}
-
private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart, int userDataLength)
{
-
ASDU asdu;
try
@@ -427,19 +421,18 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
switch (asdu.TypeId)
{
-
case TypeID.C_IC_NA_1: /* 100 - interrogation command */
DebugLog("Rcvd interrogation command C_IC_NA_1");
if ((asdu.Cot == CauseOfTransmission.ACTIVATION) || (asdu.Cot == CauseOfTransmission.DEACTIVATION))
{
- if (this.interrogationHandler != null)
+ if (interrogationHandler != null)
{
InterrogationCommand irc = (InterrogationCommand)asdu.GetElement(0);
- if (this.interrogationHandler(this.InterrogationHandlerParameter, this, asdu, irc.QOI))
+ if (interrogationHandler(InterrogationHandlerParameter, this, asdu, irc.QOI))
messageHandled = true;
}
}
@@ -447,7 +440,7 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDU(asdu);
+ SendASDU(asdu);
}
break;
@@ -458,12 +451,12 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
if ((asdu.Cot == CauseOfTransmission.ACTIVATION) || (asdu.Cot == CauseOfTransmission.DEACTIVATION))
{
- if (this.counterInterrogationHandler != null)
+ if (counterInterrogationHandler != null)
{
CounterInterrogationCommand cic = (CounterInterrogationCommand)asdu.GetElement(0);
- if (this.counterInterrogationHandler(this.counterInterrogationHandlerParameter, this, asdu, cic.QCC))
+ if (counterInterrogationHandler(counterInterrogationHandlerParameter, this, asdu, cic.QCC))
messageHandled = true;
}
}
@@ -471,7 +464,7 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDU(asdu);
+ SendASDU(asdu);
}
break;
@@ -485,11 +478,11 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
DebugLog("Read request for object: " + asdu.Ca);
- if (this.readHandler != null)
+ if (readHandler != null)
{
ReadCommand rc = (ReadCommand)asdu.GetElement(0);
- if (this.readHandler(this.readHandlerParameter, this, asdu, rc.ObjectAddress))
+ if (readHandler(readHandlerParameter, this, asdu, rc.ObjectAddress))
messageHandled = true;
}
@@ -499,7 +492,7 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDU(asdu);
+ SendASDU(asdu);
}
break;
@@ -511,12 +504,12 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
if (asdu.Cot == CauseOfTransmission.ACTIVATION)
{
- if (this.clockSynchronizationHandler != null)
+ if (clockSynchronizationHandler != null)
{
ClockSynchronizationCommand csc = (ClockSynchronizationCommand)asdu.GetElement(0);
- if (this.clockSynchronizationHandler(this.clockSynchronizationHandlerParameter,
+ if (clockSynchronizationHandler(clockSynchronizationHandlerParameter,
this, asdu, csc.NewTime))
messageHandled = true;
}
@@ -526,7 +519,7 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDU(asdu);
+ SendASDU(asdu);
}
break;
@@ -543,7 +536,7 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
else
asdu.Cot = CauseOfTransmission.ACTIVATION_CON;
- this.SendASDU(asdu);
+ SendASDU(asdu);
messageHandled = true;
@@ -556,12 +549,12 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
if (asdu.Cot == CauseOfTransmission.ACTIVATION)
{
- if (this.resetProcessHandler != null)
+ if (resetProcessHandler != null)
{
ResetProcessCommand rpc = (ResetProcessCommand)asdu.GetElement(0);
- if (this.resetProcessHandler(this.resetProcessHandlerParameter,
+ if (resetProcessHandler(resetProcessHandlerParameter,
this, asdu, rpc.QRP))
messageHandled = true;
}
@@ -571,7 +564,7 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDU(asdu);
+ SendASDU(asdu);
}
@@ -583,12 +576,12 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
if ((asdu.Cot == CauseOfTransmission.ACTIVATION) || (asdu.Cot == CauseOfTransmission.SPONTANEOUS))
{
- if (this.delayAcquisitionHandler != null)
+ if (delayAcquisitionHandler != null)
{
DelayAcquisitionCommand dac = (DelayAcquisitionCommand)asdu.GetElement(0);
- if (this.delayAcquisitionHandler(this.delayAcquisitionHandlerParameter,
+ if (delayAcquisitionHandler(delayAcquisitionHandlerParameter,
this, asdu, dac.Delay))
messageHandled = true;
}
@@ -597,7 +590,7 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDU(asdu);
+ SendASDU(asdu);
}
break;
@@ -607,15 +600,15 @@ private bool HandleApplicationLayer(int address, byte[] msg, int userDataStart,
if (messageHandled == false)
messageHandled = fileServer.HandleFileAsdu(asdu);
- if ((messageHandled == false) && (this.asduHandler != null))
- if (this.asduHandler(this.asduHandlerParameter, this, asdu))
- messageHandled = true;
+ if ((messageHandled == false) && (asduHandler != null))
+ if (asduHandler(asduHandlerParameter, this, asdu))
+ messageHandled = true;
if (messageHandled == false)
{
asdu.Cot = CauseOfTransmission.UNKNOWN_TYPE_ID;
asdu.IsNegative = true;
- this.SendASDU(asdu);
+ SendASDU(asdu);
}
return true;
@@ -647,7 +640,7 @@ public void Run()
{
if (initialized == false)
{
-
+
linkLayer = new LinkLayer(buffer, linkLayerParameters, transceiver, DebugLog);
linkLayer.LinkLayerMode = linkLayerMode;
@@ -702,22 +695,42 @@ public void ReceiveMessageLoop()
port.Close();
}
- public override int FileTimeout
+ public override int FileTimeout
{
- get {
+ get
+ {
if (fileServer != null)
return (int)fileServer.Timeout;
else
return 0;
}
- set {
+ set
+ {
if (fileServer != null)
fileServer.Timeout = value;
}
}
- }
+ ///
+ /// Sets the raw message handler for received messages
+ ///
+ /// Handler/delegate that will be invoked when a message is received
+ /// will be passed to the delegate
+ public void SetReceivedRawMessageHandler(RawMessageHandler handler, object parameter)
+ {
+ linkLayer.SetReceivedRawMessageHandler(handler, parameter);
+ }
+ ///
+ /// Sets the sent message handler for sent messages.
+ ///
+ /// Handler/delegate that will be invoked when a message is sent<
+ /// will be passed to the delegate
+ public void SetSentRawMessageHandler(RawMessageHandler handler, object parameter)
+ {
+ linkLayer.SetSentRawMessageHandler(handler, parameter);
+ }
+ }
}
}
diff --git a/lib60870/CS101/CauseOfTransmission.cs b/lib60870/CS101/CauseOfTransmission.cs
index 1441869..9590078 100644
--- a/lib60870/CS101/CauseOfTransmission.cs
+++ b/lib60870/CS101/CauseOfTransmission.cs
@@ -1,7 +1,7 @@
/*
* CauseOfTransmission.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,13 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-
namespace lib60870.CS101
{
///
@@ -47,7 +40,7 @@ public enum CauseOfTransmission
ACTIVATION_TERMINATION = 10,
RETURN_INFO_REMOTE = 11,
RETURN_INFO_LOCAL = 12,
- FILE_TRANSFER = 13,
+ FILE_TRANSFER = 13,
AUTHENTICATION = 14,
MAINTENANCE_OF_AUTH_SESSION_KEY = 15,
MAINTENANCE_OF_USER_ROLE_AND_UPDATE_KEY = 16,
@@ -74,9 +67,9 @@ public enum CauseOfTransmission
REQUESTED_BY_GROUP_3_COUNTER = 40,
REQUESTED_BY_GROUP_4_COUNTER = 41,
UNKNOWN_TYPE_ID = 44,
- UNKNOWN_CAUSE_OF_TRANSMISSION = 45,
+ UNKNOWN_CAUSE_OF_TRANSMISSION = 45,
UNKNOWN_COMMON_ADDRESS_OF_ASDU = 46,
UNKNOWN_INFORMATION_OBJECT_ADDRESS = 47
}
-
+
}
diff --git a/lib60870/CS101/DoublePointInformation.cs b/lib60870/CS101/DoublePointInformation.cs
index a6849c1..de9ccc6 100644
--- a/lib60870/CS101/DoublePointInformation.cs
+++ b/lib60870/CS101/DoublePointInformation.cs
@@ -1,7 +1,7 @@
/*
* DoublePointInformation.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
///
@@ -68,7 +66,7 @@ public DoublePointValue Value
{
get
{
- return this.value;
+ return value;
}
}
@@ -78,7 +76,7 @@ public QualityDescriptor Quality
{
get
{
- return this.quality;
+ return quality;
}
}
@@ -92,8 +90,8 @@ public DoublePointInformation(int ioa, DoublePointValue value, QualityDescriptor
public DoublePointInformation(DoublePointInformation original)
: base(original.ObjectAddress)
{
- this.value = original.value;
- this.quality = new QualityDescriptor(original.quality);
+ value = original.value;
+ quality = new QualityDescriptor(original.quality);
}
internal DoublePointInformation(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -157,7 +155,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -170,7 +168,7 @@ public DoublePointWithCP24Time2a(int ioa, DoublePointValue value, QualityDescrip
public DoublePointWithCP24Time2a(DoublePointWithCP24Time2a original)
: base(original)
{
- this.timestamp = new CP24Time2a(original.timestamp);
+ timestamp = new CP24Time2a(original.timestamp);
}
internal DoublePointWithCP24Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -228,7 +226,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -241,7 +239,7 @@ public DoublePointWithCP56Time2a(int ioa, DoublePointValue value, QualityDescrip
public DoublePointWithCP56Time2a(DoublePointWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal DoublePointWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
diff --git a/lib60870/CS101/EndOfInitialization.cs b/lib60870/CS101/EndOfInitialization.cs
index e42ca5a..0fc16c4 100644
--- a/lib60870/CS101/EndOfInitialization.cs
+++ b/lib60870/CS101/EndOfInitialization.cs
@@ -1,7 +1,7 @@
/*
* EndOfInitialization.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
///
@@ -77,7 +75,7 @@ public EndOfInitialization(byte coi)
public EndOfInitialization(EndOfInitialization original)
: base(original.ObjectAddress)
{
- this.coi = original.coi;
+ coi = original.coi;
}
internal EndOfInitialization(ApplicationLayerParameters parameters, byte[] msg, int startIndex)
diff --git a/lib60870/CS101/EventOfProtectionEquipment.cs b/lib60870/CS101/EventOfProtectionEquipment.cs
index 593e0b1..61bac1f 100644
--- a/lib60870/CS101/EventOfProtectionEquipment.cs
+++ b/lib60870/CS101/EventOfProtectionEquipment.cs
@@ -1,7 +1,7 @@
/*
* EventOfProtectionEquipment.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
///
@@ -67,7 +65,7 @@ public CP16Time2a ElapsedTime
{
get
{
- return this.elapsedTime;
+ return elapsedTime;
}
}
@@ -77,7 +75,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -92,9 +90,9 @@ public EventOfProtectionEquipment(int ioa, SingleEvent singleEvent, CP16Time2a e
public EventOfProtectionEquipment(EventOfProtectionEquipment original)
: base(original.ObjectAddress)
{
- this.singleEvent = new SingleEvent(original.singleEvent);
- this.elapsedTime = new CP16Time2a(original.elapsedTime);
- this.timestamp = new CP24Time2a(original.timestamp);
+ singleEvent = new SingleEvent(original.singleEvent);
+ elapsedTime = new CP16Time2a(original.elapsedTime);
+ timestamp = new CP24Time2a(original.timestamp);
}
internal EventOfProtectionEquipment(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -105,7 +103,7 @@ internal EventOfProtectionEquipment(ApplicationLayerParameters parameters, byte[
if ((msg.Length - startIndex) < GetEncodedSize())
throw new ASDUParsingException("Message too small");
-
+
singleEvent = new SingleEvent(msg[startIndex++]);
elapsedTime = new CP16Time2a(msg, startIndex);
@@ -169,7 +167,7 @@ public CP16Time2a ElapsedTime
{
get
{
- return this.elapsedTime;
+ return elapsedTime;
}
}
@@ -179,7 +177,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -194,9 +192,9 @@ public EventOfProtectionEquipmentWithCP56Time2a(int ioa, SingleEvent singleEvent
public EventOfProtectionEquipmentWithCP56Time2a(EventOfProtectionEquipmentWithCP56Time2a original)
: base(original.ObjectAddress)
{
- this.singleEvent = new SingleEvent(original.singleEvent);
- this.elapsedTime = new CP16Time2a(original.elapsedTime);
- this.timestamp = new CP56Time2a(original.timestamp);
+ singleEvent = new SingleEvent(original.singleEvent);
+ elapsedTime = new CP16Time2a(original.elapsedTime);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal EventOfProtectionEquipmentWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
diff --git a/lib60870/CS101/FileObjects.cs b/lib60870/CS101/FileObjects.cs
index 26d8069..24517ee 100644
--- a/lib60870/CS101/FileObjects.cs
+++ b/lib60870/CS101/FileObjects.cs
@@ -1,7 +1,7 @@
/*
* FileObjects.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,9 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-using System.Collections.Generic;
-
namespace lib60870.CS101
{
///
@@ -127,7 +124,7 @@ public NameOfFile NOF
{
get
{
- return this.nof;
+ return nof;
}
set
{
@@ -139,7 +136,7 @@ public int LengthOfFile
{
get
{
- return this.lengthOfFile;
+ return lengthOfFile;
}
set
{
@@ -154,7 +151,7 @@ public byte FRQ
{
get
{
- return this.frq;
+ return frq;
}
set
{
@@ -262,7 +259,7 @@ public NameOfFile NOF
{
get
{
- return this.nof;
+ return nof;
}
set
{
@@ -274,7 +271,7 @@ public byte NameOfSection
{
get
{
- return this.nameOfSection;
+ return nameOfSection;
}
set
{
@@ -286,7 +283,7 @@ public int LengthOfSection
{
get
{
- return this.lengthOfSection;
+ return lengthOfSection;
}
set
{
@@ -302,7 +299,7 @@ public byte SRQ
{
get
{
- return this.srq;
+ return srq;
}
set
{
@@ -406,7 +403,7 @@ public NameOfFile NOF
{
get
{
- return this.nof;
+ return nof;
}
set
{
@@ -418,7 +415,7 @@ public byte NameOfSection
{
get
{
- return this.nameOfSection;
+ return nameOfSection;
}
set
{
@@ -434,7 +431,7 @@ public SelectAndCallQualifier SCQ
{
get
{
- return this.scq;
+ return scq;
}
set
{
@@ -522,7 +519,7 @@ public NameOfFile NOF
{
get
{
- return this.nof;
+ return nof;
}
set
{
@@ -534,7 +531,7 @@ public byte NameOfSection
{
get
{
- return this.nameOfSection;
+ return nameOfSection;
}
set
{
@@ -546,7 +543,7 @@ public LastSectionOrSegmentQualifier LSQ
{
get
{
- return this.lsq;
+ return lsq;
}
set
{
@@ -562,7 +559,7 @@ public byte CHS
{
get
{
- return this.chs;
+ return chs;
}
set
{
@@ -600,7 +597,7 @@ public FileLastSegmentOrSection(int objectAddress, NameOfFile nof, byte nameOfSe
this.nof = nof;
this.nameOfSection = nameOfSection;
this.lsq = lsq;
- this.chs = checksum;
+ chs = checksum;
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -652,7 +649,7 @@ public NameOfFile NOF
{
get
{
- return this.nof;
+ return nof;
}
set
{
@@ -664,7 +661,7 @@ public byte NameOfSection
{
get
{
- return this.nameOfSection;
+ return nameOfSection;
}
set
{
@@ -680,7 +677,7 @@ public AcknowledgeQualifier AckQualifier
{
get
{
- return (AcknowledgeQualifier)(this.afq & 0x0f);
+ return (AcknowledgeQualifier)(afq & 0x0f);
}
set
{
@@ -741,8 +738,8 @@ public FileACK(int objectAddress, NameOfFile nof, byte nameOfSection, Acknowledg
{
this.nof = nof;
this.nameOfSection = nameOfSection;
- this.AckQualifier = qualifier;
- this.ErrorCode = errorCode;
+ AckQualifier = qualifier;
+ ErrorCode = errorCode;
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -795,7 +792,7 @@ public NameOfFile NOF
{
get
{
- return this.nof;
+ return nof;
}
set
{
@@ -807,11 +804,11 @@ public int LengthOfFile
{
get
{
- return this.lengthOfFile;
+ return lengthOfFile;
}
set
{
- this.lengthOfFile = value;
+ lengthOfFile = value;
}
}
@@ -823,7 +820,7 @@ public byte SOF
{
get
{
- return this.sof;
+ return sof;
}
set
{
@@ -835,7 +832,7 @@ public int STATUS
{
get
{
- return (int)(sof & 0x1f);
+ return sof & 0x1f;
}
}
@@ -935,7 +932,7 @@ public override void Encode(Frame frame, ApplicationLayerParameters parameters,
frame.SetNextByte((byte)((lengthOfFile / 0x100) % 0x100));
frame.SetNextByte((byte)((lengthOfFile / 0x10000) % 0x100));
- frame.SetNextByte((byte)sof);
+ frame.SetNextByte(sof);
frame.AppendBytes(creationTime.GetEncodedValue());
}
@@ -981,7 +978,7 @@ public NameOfFile NOF
{
get
{
- return this.nof;
+ return nof;
}
set
{
@@ -993,7 +990,7 @@ public byte NameOfSection
{
get
{
- return this.nameOfSection;
+ return nameOfSection;
}
set
{
@@ -1051,7 +1048,7 @@ internal FileSegment(ApplicationLayerParameters parameters, byte[] msg, int star
for (int i = 0; i < los; i++)
data[i] = msg[startIndex++];
-
+
}
public FileSegment(int objectAddress, NameOfFile nof, byte nameOfSection, byte[] data)
@@ -1060,7 +1057,7 @@ public FileSegment(int objectAddress, NameOfFile nof, byte nameOfSection, byte[]
this.nof = nof;
this.nameOfSection = nameOfSection;
this.data = data;
- this.los = (byte)data.Length;
+ los = (byte)data.Length;
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -1090,7 +1087,7 @@ public static int GetMaxDataSize(ApplicationLayerParameters parameters)
- parameters.SizeOfCOT
- parameters.SizeOfIOA
- ENCODED_SIZE;
-
+
return maxSize;
}
}
diff --git a/lib60870/CS101/FileServices.cs b/lib60870/CS101/FileServices.cs
index 0061dff..68615ea 100644
--- a/lib60870/CS101/FileServices.cs
+++ b/lib60870/CS101/FileServices.cs
@@ -1,7 +1,7 @@
/*
* FileServices.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -16,7 +16,9 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with lib60870.NET. If not, see .
+ * along with lib60870.NET. If not, see .
*
* See COPYING file for the complete license text.
*/
@@ -103,7 +105,7 @@ public interface IFileProvider
///
/// File ready handler. Will be called by the FileServer when a master sends a FILE READY (file download announcement) message to the slave.
///
- public delegate IFileReceiver FileReadyHandler(object parameter,int ca,int ioa,NameOfFile nof,int lengthOfFile);
+ public delegate IFileReceiver FileReadyHandler(object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile);
///
/// Simple implementation of IFileProvider that can be used to provide transparent files. Derived classed should override the
@@ -197,7 +199,7 @@ internal enum FileClientState
IDLE,
/* states for file upload (monitor direction) */
-
+
WAITING_FOR_FILE_READY,
WAITING_FOR_SECTION_READY, /* or for LAST_SECTION */
@@ -248,18 +250,21 @@ public FileClient(Master master, DebugLogger debugLog)
{
this.master = master;
DebugLog = debugLog;
- maxSegmentSize = FileSegment.GetMaxDataSize (master.GetApplicationLayerParameters());
+ maxSegmentSize = FileSegment.GetMaxDataSize(master.GetApplicationLayerParameters());
}
///
/// Gets or sets the timeout for file transfers
///
/// timeout in ms
- public long Timeout {
- get {
+ public long Timeout
+ {
+ get
+ {
return timeout;
}
- set {
+ set
+ {
timeout = value;
}
}
@@ -273,59 +278,62 @@ private ASDU NewAsdu(InformationObject io)
return asdu;
}
- private void SendLastSegment ()
+ private void SendLastSegment()
{
- ASDU fileAsdu = NewAsdu(new FileLastSegmentOrSection (ioa, nof, (byte)numberOfSection,
+ ASDU fileAsdu = NewAsdu(new FileLastSegmentOrSection(ioa, nof, (byte)numberOfSection,
LastSectionOrSegmentQualifier.SECTION_TRANSFER_WITHOUT_DEACT,
sectionChecksum));
fileChecksum += sectionChecksum;
sectionChecksum = 0;
- DebugLog ("Send LAST SEGMENT (NoS=" + numberOfSection + ")");
+ DebugLog("Send LAST SEGMENT (NoS=" + numberOfSection + ")");
- master.SendASDU (fileAsdu);
+ master.SendASDU(fileAsdu);
}
- private byte CalculateChecksum (byte [] data)
+ private byte CalculateChecksum(byte[] data)
{
byte checksum = 0;
- foreach (byte octet in data) {
+ foreach (byte octet in data)
+ {
checksum += octet;
}
return checksum;
}
- private bool SendSegment ()
+ private bool SendSegment()
{
int currentSegmentSize = currentSectionSize - currentSectionOffset;
- if (currentSegmentSize > 0) {
+ if (currentSegmentSize > 0)
+ {
if (currentSegmentSize > maxSegmentSize)
currentSegmentSize = maxSegmentSize;
- byte [] segmentData = new byte [currentSegmentSize];
+ byte[] segmentData = new byte[currentSegmentSize];
- fileProvider.GetSegmentData (numberOfSection - 1,
+ fileProvider.GetSegmentData(numberOfSection - 1,
currentSectionOffset,
currentSegmentSize,
segmentData);
- ASDU fileAsdu = NewAsdu (new FileSegment (ioa, nof, (byte)numberOfSection, segmentData));
+ ASDU fileAsdu = NewAsdu(new FileSegment(ioa, nof, (byte)numberOfSection, segmentData));
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
- master.SendASDU (fileAsdu);
+ master.SendASDU(fileAsdu);
- sectionChecksum += CalculateChecksum (segmentData);
+ sectionChecksum += CalculateChecksum(segmentData);
- DebugLog ("Send SEGMENT (NoS=" + numberOfSection + ", CHS=" + sectionChecksum + ")");
+ DebugLog("Send SEGMENT (NoS=" + numberOfSection + ", CHS=" + sectionChecksum + ")");
currentSectionOffset += currentSegmentSize;
return true;
- } else
+ }
+ else
return false;
}
@@ -344,7 +352,7 @@ private void AbortFileTransfer(FileErrorCode errorCode)
master.SendASDU(deactivateFile);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
if (fileReceiver != null)
fileReceiver.Finished(errorCode);
@@ -352,11 +360,11 @@ private void AbortFileTransfer(FileErrorCode errorCode)
ResetStateToIdle();
}
- private void FileUploadFailed ()
+ private void FileUploadFailed()
{
if (fileProvider != null)
- fileProvider.TransferComplete (false);
- ResetStateToIdle ();
+ fileProvider.TransferComplete(false);
+ ResetStateToIdle();
}
public bool HandleFileAsdu(ASDU asdu)
@@ -366,318 +374,388 @@ public bool HandleFileAsdu(ASDU asdu)
switch (asdu.TypeId)
{
- case TypeID.F_SC_NA_1: /* File/Section/Directory Call/Select */
+ case TypeID.F_SC_NA_1: /* File/Section/Directory Call/Select */
- DebugLog ("Received F_SC_NA_1 (select/call)");
+ DebugLog("Received F_SC_NA_1 (select/call)");
- if (state == FileClientState.WAITING_FOR_FILE_READY) /* file download */
- {
- FileErrorCode errCode = FileErrorCode.PROTOCOL_ERROR;
+ if (state == FileClientState.WAITING_FOR_FILE_READY) /* file download */
+ {
+ FileErrorCode errCode = FileErrorCode.PROTOCOL_ERROR;
- if (asdu.Cot == CauseOfTransmission.UNKNOWN_TYPE_ID)
- errCode = FileErrorCode.UNKNOWN_SERVICE;
- else if (asdu.Cot == CauseOfTransmission.UNKNOWN_COMMON_ADDRESS_OF_ASDU)
- errCode = FileErrorCode.UNKNOWN_CA;
- else if (asdu.Cot == CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS)
- errCode = FileErrorCode.UNKNOWN_IOA;
+ if (asdu.Cot == CauseOfTransmission.UNKNOWN_TYPE_ID)
+ errCode = FileErrorCode.UNKNOWN_SERVICE;
+ else if (asdu.Cot == CauseOfTransmission.UNKNOWN_COMMON_ADDRESS_OF_ASDU)
+ errCode = FileErrorCode.UNKNOWN_CA;
+ else if (asdu.Cot == CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS)
+ errCode = FileErrorCode.UNKNOWN_IOA;
- if (fileReceiver != null)
- fileReceiver.Finished (errCode);
+ if (fileReceiver != null)
+ fileReceiver.Finished(errCode);
- ResetStateToIdle ();
- } else if (state == FileClientState.WAITING_FOR_REQUEST_FILE) /* file upload */
- {
- if ((asdu.Ca == ca)) {
+ ResetStateToIdle();
+ }
+ else if (state == FileClientState.WAITING_FOR_REQUEST_FILE) /* file upload */
+ {
+ if ((asdu.Ca == ca))
+ {
- numberOfSection = 1;
- currentSectionSize = fileProvider.GetSectionSize (0);
+ numberOfSection = 1;
+ currentSectionSize = fileProvider.GetSectionSize(0);
- ASDU sectionReady = NewAsdu (new SectionReady (ioa, nof, 1, currentSectionSize, false));
- master.SendASDU (sectionReady);
+ ASDU sectionReady = NewAsdu(new SectionReady(ioa, nof, 1, currentSectionSize, false));
+ master.SendASDU(sectionReady);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
- state = FileClientState.SECTION_READY;
- } else {
- fileProvider.TransferComplete (false);
- ResetStateToIdle ();
+ state = FileClientState.SECTION_READY;
+ }
+ else
+ {
+ fileProvider.TransferComplete(false);
+ ResetStateToIdle();
+ }
}
- } else if (state == FileClientState.SECTION_READY) {
+ else if (state == FileClientState.SECTION_READY)
+ {
+
+ if ((asdu.Ca == ca))
+ {
- if ((asdu.Ca == ca)) {
+ /* send first segment */
- // send first segment
+ currentSectionOffset = 0;
- currentSectionOffset = 0;
+ SendSegment();
- SendSegment ();
+ state = FileClientState.SEND_SECTION;
+ }
+
+ }
+ else
+ {
+ if (fileReceiver != null)
+ fileReceiver.Finished(FileErrorCode.PROTOCOL_ERROR);
- state = FileClientState.SEND_SECTION;
+ ResetStateToIdle();
}
- } else {
- if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.PROTOCOL_ERROR);
+ break;
- ResetStateToIdle ();
- }
+ case TypeID.F_FR_NA_1: /* File ready */
- break;
+ DebugLog("Received F_FR_NA_1 (file ready)");
- case TypeID.F_FR_NA_1: /* File ready */
+ if (state == FileClientState.WAITING_FOR_FILE_READY)
+ {
- DebugLog("Received F_FR_NA_1 (file ready)");
+ FileReady fileReady = (FileReady)asdu.GetElement(0);
- if (state == FileClientState.WAITING_FOR_FILE_READY) {
+ if ((asdu.Ca == ca) && (fileReady.ObjectAddress == ioa) && (fileReady.NOF == nof))
+ {
- FileReady fileReady = (FileReady)asdu.GetElement (0);
+ if (fileReady.Positive)
+ {
+
+ /* send call file */
- if ((asdu.Ca == ca) && (fileReady.ObjectAddress == ioa) && (fileReady.NOF == nof)) {
+ ASDU callFile = NewAsdu(new FileCallOrSelect(ioa, nof, 0, SelectAndCallQualifier.REQUEST_FILE));
+ master.SendASDU(callFile);
- if (fileReady.Positive) {
+ lastSentTime = SystemUtils.currentTimeMillis();
- /* send call file */
+ DebugLog("Send CALL FILE");
- ASDU callFile = NewAsdu (new FileCallOrSelect (ioa, nof, 0, SelectAndCallQualifier.REQUEST_FILE));
- master.SendASDU (callFile);
+ state = FileClientState.WAITING_FOR_SECTION_READY;
- lastSentTime = SystemUtils.currentTimeMillis ();
+ }
+ else
+ {
+ if (fileReceiver != null)
+ fileReceiver.Finished(FileErrorCode.FILE_NOT_READY);
- DebugLog ("Send CALL FILE");
+ ResetStateToIdle();
+ }
- state = FileClientState.WAITING_FOR_SECTION_READY;
+ }
+ else
+ {
+ DebugLog("Unexpected CA, IOA, or NOF");
- } else {
if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.FILE_NOT_READY);
+ fileReceiver.Finished(FileErrorCode.PROTOCOL_ERROR);
- ResetStateToIdle ();
+ ResetStateToIdle();
}
- } else {
- DebugLog ("Unexpected CA, IOA, or NOF");
- if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.PROTOCOL_ERROR);
-
- ResetStateToIdle ();
}
+ else if (state == FileClientState.IDLE)
+ {
+ state = FileClientState.WAITING_FOR_SECTION_READY;
- } else if (state == FileClientState.IDLE) {
-
- state = FileClientState.WAITING_FOR_SECTION_READY;
+ }
+ else if (state == FileClientState.WAITING_FOR_REQUEST_FILE)
+ {
+
+ if (asdu.IsNegative)
+ {
+ DebugLog("Slave rejected file download: " + asdu.Cot.ToString());
+ }
+ else
+ {
+ DebugLog("Unexpected file ready while trying to start file download");
+ }
- } else if (state == FileClientState.WAITING_FOR_REQUEST_FILE) {
+ if (fileProvider != null)
+ fileProvider.TransferComplete(false);
- if (asdu.IsNegative) {
- DebugLog ("Slave rejected file download: " + asdu.Cot.ToString ());
- } else {
- DebugLog ("Unexpected file ready while trying to start file download");
+ }
+ else
+ {
+ AbortFileTransfer(FileErrorCode.PROTOCOL_ERROR);
}
- if (fileProvider != null)
- fileProvider.TransferComplete (false);
+ break;
- } else {
- AbortFileTransfer (FileErrorCode.PROTOCOL_ERROR);
- }
+ case TypeID.F_SR_NA_1: /* Section ready */
- break;
+ DebugLog("Received F_SR_NA_1 (section ready)");
- case TypeID.F_SR_NA_1: /* Section ready */
+ if (state == FileClientState.WAITING_FOR_SECTION_READY)
+ {
- DebugLog ("Received F_SR_NA_1 (section ready)");
+ SectionReady sc = (SectionReady)asdu.GetElement(0);
- if (state == FileClientState.WAITING_FOR_SECTION_READY) {
+ if (sc.NotReady == false)
+ {
+ DebugLog("Received SECTION READY(NoF=" + sc.NOF + ", NoS=" + sc.NameOfSection + ")");
- SectionReady sc = (SectionReady)asdu.GetElement (0);
+ ASDU callSection = NewAsdu(new FileCallOrSelect(ioa, nof, sc.NameOfSection, SelectAndCallQualifier.REQUEST_SECTION));
+ master.SendASDU(callSection);
- if (sc.NotReady == false) {
- DebugLog ("Received SECTION READY(NoF=" + sc.NOF + ", NoS=" + sc.NameOfSection + ")");
+ lastSentTime = SystemUtils.currentTimeMillis();
- ASDU callSection = NewAsdu (new FileCallOrSelect (ioa, nof, sc.NameOfSection, SelectAndCallQualifier.REQUEST_SECTION));
- master.SendASDU (callSection);
+ DebugLog("Send CALL SECTION(NoF=" + sc.NOF + ", NoS=" + sc.NameOfSection + ")");
- lastSentTime = SystemUtils.currentTimeMillis ();
+ currentSectionOffset = 0;
+ sectionChecksum = 0;
+ state = FileClientState.RECEIVING_SECTION;
- DebugLog ("Send CALL SECTION(NoF=" + sc.NOF + ", NoS=" + sc.NameOfSection + ")");
+ }
+ else
+ {
+ AbortFileTransfer(FileErrorCode.SECTION_NOT_READY);
+ }
- currentSectionOffset = 0;
- sectionChecksum = 0;
- state = FileClientState.RECEIVING_SECTION;
+ }
+ else if (state == FileClientState.IDLE)
+ {
+ }
+ else
+ {
+ if (fileReceiver != null)
+ fileReceiver.Finished(FileErrorCode.PROTOCOL_ERROR);
- } else {
- AbortFileTransfer (FileErrorCode.SECTION_NOT_READY);
+ ResetStateToIdle();
}
- } else if (state == FileClientState.IDLE) {
- } else {
- if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.PROTOCOL_ERROR);
+ break;
- ResetStateToIdle ();
- }
+ case TypeID.F_SG_NA_1: /* Segment */
- break;
+ DebugLog("Received F_SG_NA_1 (segment)");
- case TypeID.F_SG_NA_1: /* Segment */
+ if (state == FileClientState.RECEIVING_SECTION)
+ {
- DebugLog ("Received F_SG_NA_1 (segment)");
+ FileSegment segment = (FileSegment)asdu.GetElement(0);
- if (state == FileClientState.RECEIVING_SECTION) {
+ DebugLog("Received segment (NoS=" + segment.NameOfSection + ", LoS=" + segment.LengthOfSegment + ")");
- FileSegment segment = (FileSegment)asdu.GetElement (0);
+ sectionChecksum += CalculateChecksum(segment.SegmentData);
- DebugLog ("Received segment (NoS=" + segment.NameOfSection + ", LoS=" + segment.LengthOfSegment + ")");
+ if (fileReceiver != null)
+ {
+ fileReceiver.SegmentReceived(segment.NameOfSection, currentSectionOffset, segment.LengthOfSegment, segment.SegmentData);
+ }
- sectionChecksum += CalculateChecksum (segment.SegmentData);
+ currentSectionOffset += segment.LengthOfSegment;
- if (fileReceiver != null) {
- fileReceiver.SegmentReceived (segment.NameOfSection, currentSectionOffset, segment.LengthOfSegment, segment.SegmentData);
+ }
+ else if (state == FileClientState.IDLE)
+ {
+ }
+ else
+ {
+ AbortFileTransfer(FileErrorCode.PROTOCOL_ERROR);
}
- currentSectionOffset += segment.LengthOfSegment;
+ break;
- } else if (state == FileClientState.IDLE) {
- } else {
- AbortFileTransfer (FileErrorCode.PROTOCOL_ERROR);
- }
- break;
+ case TypeID.F_LS_NA_1: /* Last segment or section */
+ DebugLog("Received F_LS_NA_1 (last segment/section)");
- case TypeID.F_LS_NA_1: /* Last segment or section */
+ if (state != FileClientState.IDLE)
+ {
- DebugLog ("Received F_LS_NA_1 (last segment/section)");
+ FileLastSegmentOrSection lastSection = (FileLastSegmentOrSection)asdu.GetElement(0);
- if (state != FileClientState.IDLE) {
+ if (lastSection.LSQ == LastSectionOrSegmentQualifier.SECTION_TRANSFER_WITHOUT_DEACT)
+ {
- FileLastSegmentOrSection lastSection = (FileLastSegmentOrSection)asdu.GetElement (0);
+ if (state == FileClientState.RECEIVING_SECTION)
+ {
+
+ ASDU segmentAck;
- if (lastSection.LSQ == LastSectionOrSegmentQualifier.SECTION_TRANSFER_WITHOUT_DEACT) {
+ if (lastSection.CHS == sectionChecksum)
+ {
+ segmentAck = NewAsdu(new FileACK(ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.POS_ACK_SECTION, FileError.DEFAULT));
+ DebugLog("Send SEGMENT ACK");
+ }
+ else
+ {
+ segmentAck = NewAsdu(new FileACK(ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.NEG_ACK_SECTION, FileError.CHECKSUM_FAILED));
+ DebugLog("checksum check failed! Send SEGMENT NACK");
+ }
- if (state == FileClientState.RECEIVING_SECTION) {
+ master.SendASDU(segmentAck);
- ASDU segmentAck;
+ lastSentTime = SystemUtils.currentTimeMillis();
- if (lastSection.CHS == sectionChecksum) {
- segmentAck = NewAsdu (new FileACK (ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.POS_ACK_SECTION, FileError.DEFAULT));
- DebugLog ("Send SEGMENT ACK");
+ state = FileClientState.WAITING_FOR_SECTION_READY;
}
else
{
- segmentAck = NewAsdu (new FileACK (ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.NEG_ACK_SECTION, FileError.CHECKSUM_FAILED));
- DebugLog ("checksum check failed! Send SEGMENT NACK");
+ AbortFileTransfer(FileErrorCode.PROTOCOL_ERROR);
}
-
- master.SendASDU (segmentAck);
-
- lastSentTime = SystemUtils.currentTimeMillis ();
-
- state = FileClientState.WAITING_FOR_SECTION_READY;
- } else {
- AbortFileTransfer (FileErrorCode.PROTOCOL_ERROR);
}
- } else if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITH_DEACT) {
- /* slave aborted transfer */
+ else if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITH_DEACT)
+ {
+ /* slave aborted transfer */
- if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.ABORTED_BY_REMOTE);
+ if (fileReceiver != null)
+ fileReceiver.Finished(FileErrorCode.ABORTED_BY_REMOTE);
- ResetStateToIdle ();
- } else if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT) {
+ ResetStateToIdle();
+ }
+ else if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT)
+ {
- if (state == FileClientState.WAITING_FOR_SECTION_READY) {
- ASDU fileAck = NewAsdu (new FileACK (ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.POS_ACK_FILE, FileError.DEFAULT));
+ if (state == FileClientState.WAITING_FOR_SECTION_READY)
+ {
+ ASDU fileAck = NewAsdu(new FileACK(ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.POS_ACK_FILE, FileError.DEFAULT));
- master.SendASDU (fileAck);
+ master.SendASDU(fileAck);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
- DebugLog ("Send FILE ACK");
+ DebugLog("Send FILE ACK");
- if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.SUCCESS);
+ if (fileReceiver != null)
+ fileReceiver.Finished(FileErrorCode.SUCCESS);
- ResetStateToIdle ();
- } else {
+ ResetStateToIdle();
+ }
+ else
+ {
- DebugLog ("Illegal state: " + state.ToString ());
+ DebugLog("Illegal state: " + state.ToString());
- AbortFileTransfer (FileErrorCode.PROTOCOL_ERROR);
+ AbortFileTransfer(FileErrorCode.PROTOCOL_ERROR);
+ }
}
}
- }
- break;
+ break;
- case TypeID.F_AF_NA_1: /* Section or File ACK */
+ case TypeID.F_AF_NA_1: /* Section or File ACK */
- DebugLog ("Received F_AF_NA_1 (section/file ACK)");
+ DebugLog("Received F_AF_NA_1 (section/file ACK)");
- FileACK ack = (FileACK)asdu.GetElement (0);
+ FileACK ack = (FileACK)asdu.GetElement(0);
- if (state == FileClientState.WAITING_FOR_SECTION_ACK) {
- if ((asdu.Ca == ca) && (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)) {
+ if (state == FileClientState.WAITING_FOR_SECTION_ACK)
+ {
+ if ((asdu.Ca == ca) && (asdu.Cot == CauseOfTransmission.FILE_TRANSFER))
+ {
- if (ack.AckQualifier == AcknowledgeQualifier.POS_ACK_SECTION) {
+ if (ack.AckQualifier == AcknowledgeQualifier.POS_ACK_SECTION)
+ {
- numberOfSection++;
+ numberOfSection++;
- int nextSectionSize = fileProvider.GetSectionSize (numberOfSection - 1);
+ int nextSectionSize = fileProvider.GetSectionSize(numberOfSection - 1);
- if (nextSectionSize > 0) {
- currentSectionSize = nextSectionSize;
- currentSectionOffset = 0;
+ if (nextSectionSize > 0)
+ {
+ currentSectionSize = nextSectionSize;
+ currentSectionOffset = 0;
+
+ ASDU sectionReady = NewAsdu(new SectionReady(ioa, nof, (byte)numberOfSection, currentSectionSize, false));
+ master.SendASDU(sectionReady);
- ASDU sectionReady = NewAsdu (new SectionReady (ioa, nof, (byte)numberOfSection, currentSectionSize, false));
- master.SendASDU (sectionReady);
+ lastSentTime = SystemUtils.currentTimeMillis();
- lastSentTime = SystemUtils.currentTimeMillis ();
+ state = FileClientState.SECTION_READY;
+ }
+ else
+ {
- state = FileClientState.SECTION_READY;
- } else {
+ ASDU lastSection = NewAsdu(new FileLastSegmentOrSection(ioa, nof, (byte)numberOfSection, LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT, fileChecksum));
+ master.SendASDU(lastSection);
- ASDU lastSection = NewAsdu (new FileLastSegmentOrSection (ioa, nof, (byte)numberOfSection, LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT, fileChecksum));
- master.SendASDU (lastSection);
+ lastSentTime = SystemUtils.currentTimeMillis();
- lastSentTime = SystemUtils.currentTimeMillis ();
+ state = FileClientState.WAITING_FOR_FILE_ACK;
+ }
- state = FileClientState.WAITING_FOR_FILE_ACK;
+ }
+ else
+ {
+ FileUploadFailed();
}
- } else {
- FileUploadFailed ();
}
-
- } else {
- FileUploadFailed ();
+ else
+ {
+ FileUploadFailed();
+ }
}
- } else if (state == FileClientState.WAITING_FOR_FILE_ACK) {
- if ((asdu.Ca == ca) && (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)) {
+ else if (state == FileClientState.WAITING_FOR_FILE_ACK)
+ {
+ if ((asdu.Ca == ca) && (asdu.Cot == CauseOfTransmission.FILE_TRANSFER))
+ {
- if (ack.AckQualifier == AcknowledgeQualifier.POS_ACK_FILE) {
- if (fileProvider != null)
- fileProvider.TransferComplete (true);
+ if (ack.AckQualifier == AcknowledgeQualifier.POS_ACK_FILE)
+ {
+ if (fileProvider != null)
+ fileProvider.TransferComplete(true);
- ResetStateToIdle ();
+ ResetStateToIdle();
- } else {
- FileUploadFailed ();
- }
+ }
+ else
+ {
+ FileUploadFailed();
+ }
- } else {
- FileUploadFailed ();
+ }
+ else
+ {
+ FileUploadFailed();
+ }
}
- }
- break;
+ break;
- default:
+ default:
- asduHandled = false;
- break;
+ asduHandled = false;
+ break;
}
@@ -687,28 +765,29 @@ public bool HandleFileAsdu(ASDU asdu)
public void HandleFileService()
{
- if (state == FileClientState.SEND_SECTION)
+ if (state == FileClientState.SEND_SECTION)
{
- if (SendSegment () == false) {
- SendLastSegment ();
+ if (SendSegment() == false)
+ {
+ SendLastSegment();
state = FileClientState.WAITING_FOR_SECTION_ACK;
}
}
- // Check for timeout
- if (state != FileClientState.IDLE)
+ /* Check for timeout */
+ if (state != FileClientState.IDLE)
{
- if (SystemUtils.currentTimeMillis () > lastSentTime + timeout)
+ if (SystemUtils.currentTimeMillis() > lastSentTime + timeout)
{
- DebugLog ("Abort file transfer due to timeout");
+ DebugLog("Abort file transfer due to timeout");
if (fileProvider != null)
- fileProvider.TransferComplete (false);
+ fileProvider.TransferComplete(false);
if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.TIMEOUT);
+ fileReceiver.Finished(FileErrorCode.TIMEOUT);
- ResetStateToIdle ();
+ ResetStateToIdle();
}
}
@@ -725,23 +804,23 @@ public void RequestFile(int ca, int ioa, NameOfFile nof, IFileReceiver fileRecei
master.SendASDU(selectFile);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
state = FileClientState.WAITING_FOR_FILE_READY;
}
- public void SendFile (int ca, int ioa, NameOfFile nof, IFileProvider fileProvider)
+ public void SendFile(int ca, int ioa, NameOfFile nof, IFileProvider fileProvider)
{
this.ca = ca;
this.ioa = ioa;
this.nof = nof;
this.fileProvider = fileProvider;
- ASDU fileReady = NewAsdu (new FileReady (ioa, nof, fileProvider.GetFileSize (), true));
+ ASDU fileReady = NewAsdu(new FileReady(ioa, nof, fileProvider.GetFileSize(), true));
- master.SendASDU (fileReady);
+ master.SendASDU(fileReady);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
state = FileClientState.WAITING_FOR_REQUEST_FILE;
}
@@ -770,7 +849,7 @@ internal class CS101n104File
public CS101n104File(IFileProvider file)
{
- this.provider = file;
+ provider = file;
}
public IFileProvider provider = null;
@@ -829,11 +908,11 @@ internal void SendDirectoy(IMasterConnection masterConnection, bool spontaneous)
int currentCa = -1;
int currentIOA = -1;
- ASDU directoryAsdu = null;
+ ASDU directoryAsdu = null;
foreach (CS101n104File file in availableFiles)
{
-
+
bool newAsdu = false;
if (file.provider.GetCA() != currentCa)
@@ -902,7 +981,7 @@ public void AddFile(IFileProvider file)
availableFiles.Add(new CS101n104File(file));
}
-
+
}
///
@@ -931,12 +1010,13 @@ public void RemoveFile(IFileProvider file)
/// Gets the list of available files
///
/// the list of available files
- public List GetFiles ()
+ public List GetFiles()
{
- List files = new List ();
+ List files = new List();
- foreach (CS101n104File file in availableFiles) {
- files.Add (file.provider);
+ foreach (CS101n104File file in availableFiles)
+ {
+ files.Add(file.provider);
}
return files;
@@ -956,7 +1036,7 @@ public FileServer(IMasterConnection masterConnection, FilesAvailable availableFi
maxSegmentSize = FileSegment.GetMaxDataSize(alParameters);
this.availableFiles = availableFiles;
this.logger = logger;
- this.connection = masterConnection;
+ connection = masterConnection;
}
private FilesAvailable availableFiles;
@@ -1001,13 +1081,13 @@ public void SetFileReadyHandler(FileReadyHandler handler, object parameter)
/// Gets or sets the timeout for file transfers
///
/// timeout in ms
- public long Timeout
+ public long Timeout
{
- get
+ get
{
return timeout;
}
- set
+ set
{
timeout = value;
}
@@ -1019,559 +1099,618 @@ public bool HandleFileAsdu(ASDU asdu)
switch (asdu.TypeId)
{
- case TypeID.F_FR_NA_1: /* File Ready */
+ case TypeID.F_FR_NA_1: /* File Ready */
- logger ("Received file ready F_FR_NA_1");
+ logger("Received file ready F_FR_NA_1");
- if (fileReadyHandler != null) {
+ if (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)
+ {
+ if (fileReadyHandler != null)
+ {
- FileReady fileReady = (FileReady)asdu.GetElement (0);
+ FileReady fileReady = (FileReady)asdu.GetElement(0);
- fileReceiver = fileReadyHandler (fileReadyHandlerParameter, asdu.Ca, fileReady.ObjectAddress, fileReady.NOF, fileReady.LengthOfFile);
+ fileReceiver = fileReadyHandler(fileReadyHandlerParameter, asdu.Ca, fileReady.ObjectAddress, fileReady.NOF, fileReady.LengthOfFile);
- if (fileReceiver == null) {
- asdu.IsNegative = true;
- asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
- connection.SendASDU (asdu);
- } else {
+ if (fileReceiver == null)
+ {
+ asdu.IsNegative = true;
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ connection.SendASDU(asdu);
+ }
+ else
+ {
- ca = asdu.Ca;
- ioa = fileReady.ObjectAddress;
- nof = fileReady.NOF;
+ ca = asdu.Ca;
+ ioa = fileReady.ObjectAddress;
+ nof = fileReady.NOF;
- // send call file
+ /* send call file */
- ASDU callFile = new ASDU (alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
+ ASDU callFile = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
- callFile.AddInformationObject (new FileCallOrSelect (fileReady.ObjectAddress, fileReady.NOF, 0, SelectAndCallQualifier.REQUEST_FILE));
+ callFile.AddInformationObject(new FileCallOrSelect(fileReady.ObjectAddress, fileReady.NOF, 0, SelectAndCallQualifier.REQUEST_FILE));
- connection.SendASDU (callFile);
+ connection.SendASDU(callFile);
- lastSentTime = SystemUtils.currentTimeMillis ();
- transferState = FileServerState.WAITING_FOR_SECTION_READY;
- }
+ lastSentTime = SystemUtils.currentTimeMillis();
+ transferState = FileServerState.WAITING_FOR_SECTION_READY;
+ }
- } else {
- asdu.IsNegative = true;
- asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
- connection.SendASDU (asdu);
- }
+ }
+ else
+ {
+ asdu.IsNegative = true;
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ connection.SendASDU(asdu);
+ }
+ }
+ else
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ connection.SendASDU(asdu);
+ }
- break;
+ break;
- case TypeID.F_SR_NA_1: /* Section Ready */
+ case TypeID.F_SR_NA_1: /* Section Ready */
- if (transferState == FileServerState.WAITING_FOR_SECTION_READY) {
+ if (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)
+ {
+ if (transferState == FileServerState.WAITING_FOR_SECTION_READY)
+ {
- SectionReady sectionReady = (SectionReady)asdu.GetElement (0);
+ SectionReady sectionReady = (SectionReady)asdu.GetElement(0);
- currentSectionNumber = sectionReady.NameOfSection;
- currentSectionOffset = 0;
- currentSectionSize = sectionReady.LengthOfSection;
+ currentSectionNumber = sectionReady.NameOfSection;
+ currentSectionOffset = 0;
+ currentSectionSize = sectionReady.LengthOfSection;
- // send call section
+ /* send call section */
- ASDU callSection = new ASDU (alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, ca, false);
+ ASDU callSection = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, ca, false);
- callSection.AddInformationObject (new FileCallOrSelect (ioa, nof, (byte)currentSectionNumber, SelectAndCallQualifier.REQUEST_SECTION));
+ callSection.AddInformationObject(new FileCallOrSelect(ioa, nof, currentSectionNumber, SelectAndCallQualifier.REQUEST_SECTION));
- connection.SendASDU (callSection);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ connection.SendASDU(callSection);
+ lastSentTime = SystemUtils.currentTimeMillis();
- transferState = FileServerState.RECEIVE_SECTION;
- } else {
+ transferState = FileServerState.RECEIVE_SECTION;
+ }
+ }
+ else
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ connection.SendASDU(asdu);
+ }
- }
+ break;
- break;
+ case TypeID.F_SG_NA_1: /* Segment */
- case TypeID.F_SG_NA_1: /* Segment */
+ if (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)
+ {
+ if (transferState == FileServerState.RECEIVE_SECTION)
+ {
- if (transferState == FileServerState.RECEIVE_SECTION) {
+ FileSegment segment = (FileSegment)asdu.GetElement(0);
- FileSegment segment = (FileSegment)asdu.GetElement (0);
+ logger("Received F_SG_NA_1(segment) (NoS=" + segment.NameOfSection + ", LoS=" + segment.LengthOfSegment + ")");
- logger ("Received F_SG_NA_1(segment) (NoS=" + segment.NameOfSection + ", LoS=" + segment.LengthOfSegment + ")");
+ if (fileReceiver != null)
+ {
+ fileReceiver.SegmentReceived(segment.NameOfSection, currentSectionOffset, segment.LengthOfSegment, segment.SegmentData);
+ }
- if (fileReceiver != null) {
- fileReceiver.SegmentReceived (segment.NameOfSection, currentSectionOffset, segment.LengthOfSegment, segment.SegmentData);
+ currentSectionOffset += segment.LengthOfSegment;
+ }
+ else
+ {
+ logger("Unexpected F_SG_NA_1(file segment)");
+ }
+ }
+ else
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ connection.SendASDU(asdu);
}
+
+ break;
- currentSectionOffset += segment.LengthOfSegment;
- } else {
- logger ("Unexpected F_SG_NA_1(file segment)");
- }
+ case TypeID.F_LS_NA_1: /* Last Segment/Section */
+ logger("Received F_LS_NA_1 (last segment/section)");
- break;
+ if (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)
+ {
+ if (transferState == FileServerState.RECEIVE_SECTION)
+ {
- case TypeID.F_LS_NA_1: /* Last Segment/Section */
+ FileLastSegmentOrSection lastSection = (FileLastSegmentOrSection)asdu.GetElement(0);
- logger ("Received F_LS_NA_1 (last segment/section)");
+ if (lastSection.LSQ == LastSectionOrSegmentQualifier.SECTION_TRANSFER_WITHOUT_DEACT)
+ {
- if (transferState == FileServerState.RECEIVE_SECTION) {
+ ASDU sectionAck = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
- FileLastSegmentOrSection lastSection = (FileLastSegmentOrSection)asdu.GetElement (0);
+ sectionAck.AddInformationObject(new FileACK(ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.POS_ACK_SECTION, FileError.DEFAULT));
- if (lastSection.LSQ == LastSectionOrSegmentQualifier.SECTION_TRANSFER_WITHOUT_DEACT) {
+ connection.SendASDU(sectionAck);
+ lastSentTime = SystemUtils.currentTimeMillis();
- ASDU sectionAck = new ASDU (alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
+ logger("Send section ACK");
- sectionAck.AddInformationObject (new FileACK (ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.POS_ACK_SECTION, FileError.DEFAULT));
+ transferState = FileServerState.WAITING_FOR_SECTION_READY;
- connection.SendASDU (sectionAck);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ }
+ else if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITH_DEACT)
+ {
+ /* master aborted transfer */
- logger ("Send section ACK");
+ if (fileReceiver != null)
+ fileReceiver.Finished(FileErrorCode.ABORTED_BY_REMOTE);
- transferState = FileServerState.WAITING_FOR_SECTION_READY;
+ transferState = FileServerState.UNSELECTED_IDLE;
+ }
+ else
+ {
- } else if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITH_DEACT) {
- /* master aborted transfer */
+ }
- if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.ABORTED_BY_REMOTE);
- transferState = FileServerState.UNSELECTED_IDLE;
- } else {
+ }
+ else if (transferState == FileServerState.WAITING_FOR_SECTION_READY)
+ {
- }
+ FileLastSegmentOrSection lastSection = (FileLastSegmentOrSection)asdu.GetElement(0);
+ if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT)
+ {
- } else if (transferState == FileServerState.WAITING_FOR_SECTION_READY) {
+ ASDU fileAck = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
- FileLastSegmentOrSection lastSection = (FileLastSegmentOrSection)asdu.GetElement (0);
+ fileAck.AddInformationObject(new FileACK(ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.POS_ACK_FILE, FileError.DEFAULT));
- if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT) {
+ connection.SendASDU(fileAck);
+ lastSentTime = SystemUtils.currentTimeMillis();
- ASDU fileAck = new ASDU (alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
+ logger("Send file ACK");
- fileAck.AddInformationObject(new FileACK (ioa, nof, lastSection.NameOfSection, AcknowledgeQualifier.POS_ACK_FILE, FileError.DEFAULT));
+ if (fileReceiver != null)
+ fileReceiver.Finished(FileErrorCode.SUCCESS);
- connection.SendASDU (fileAck);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ transferState = FileServerState.UNSELECTED_IDLE;
- logger ("Send file ACK");
+ logger("Received file success");
+ }
+ else if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITH_DEACT)
+ {
+ /* master aborted transfer */
- if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.SUCCESS);
+ if (fileReceiver != null)
+ fileReceiver.Finished(FileErrorCode.ABORTED_BY_REMOTE);
- transferState = FileServerState.UNSELECTED_IDLE;
+ transferState = FileServerState.UNSELECTED_IDLE;
+ }
+ else
+ {
+ logger("F_LS_NA_1 with unexpected LSQ: " + lastSection.LSQ.ToString());
+ }
+ }
- logger ("Received file success");
}
- else if (lastSection.LSQ == LastSectionOrSegmentQualifier.FILE_TRANSFER_WITH_DEACT) {
- /* master aborted transfer */
+ else
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ connection.SendASDU(asdu);
+ }
- if (fileReceiver != null)
- fileReceiver.Finished (FileErrorCode.ABORTED_BY_REMOTE);
+ break;
- transferState = FileServerState.UNSELECTED_IDLE;
- } else {
- logger ("F_LS_NA_1 with unexpected LSQ: " + lastSection.LSQ.ToString ());
- }
- }
+ case TypeID.F_AF_NA_1: /* 124 - ACK file, ACK section */
- break;
+ logger("Received file/section ACK F_AF_NA_1");
- case TypeID.F_AF_NA_1: /* 124 - ACK file, ACK section */
+ if (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)
+ {
- logger("Received file/section ACK F_AF_NA_1");
+ if (transferState != FileServerState.UNSELECTED_IDLE)
+ {
- //TODO move COT check to beginning of function!
- if (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)
- {
+ IFileProvider file = selectedFile.provider;
- if (transferState != FileServerState.UNSELECTED_IDLE)
- {
+ FileACK ack = (FileACK)asdu.GetElement(0);
- IFileProvider file = selectedFile.provider;
+ if (ack.AckQualifier == AcknowledgeQualifier.POS_ACK_FILE)
+ {
- FileACK ack = (FileACK)asdu.GetElement(0);
+ logger("Received positive file ACK");
- if (ack.AckQualifier == AcknowledgeQualifier.POS_ACK_FILE)
- {
+ if (transferState == FileServerState.WAITING_FOR_FILE_ACK)
+ {
- logger("Received positive file ACK");
+ selectedFile.provider.TransferComplete(true);
+ selectedFile.selectedBy = null;
- if (transferState == FileServerState.WAITING_FOR_FILE_ACK)
- {
+ availableFiles.RemoveFile(selectedFile.provider);
- selectedFile.provider.TransferComplete(true);
- selectedFile.selectedBy = null;
+ selectedFile = null;
- availableFiles.RemoveFile(selectedFile.provider);
+ transferState = FileServerState.UNSELECTED_IDLE;
+ }
+ else
+ {
+ logger("Unexpected file transfer state --> abort file transfer");
- selectedFile = null;
+ transferState = FileServerState.SEND_ABORT;
+ }
- transferState = FileServerState.UNSELECTED_IDLE;
- }
- else
- {
- logger("Unexpected file transfer state --> abort file transfer");
- transferState = FileServerState.SEND_ABORT;
}
+ else if (ack.AckQualifier == AcknowledgeQualifier.NEG_ACK_FILE)
+ {
+ logger("Received negative file ACK - stop transfer");
- }
- else if (ack.AckQualifier == AcknowledgeQualifier.NEG_ACK_FILE)
- {
+ if (transferState == FileServerState.WAITING_FOR_FILE_ACK)
+ {
- logger("Received negative file ACK - stop transfer");
+ selectedFile.provider.TransferComplete(false);
- if (transferState == FileServerState.WAITING_FOR_FILE_ACK)
- {
+ selectedFile.selectedBy = null;
+ selectedFile = null;
- selectedFile.provider.TransferComplete(false);
+ transferState = FileServerState.UNSELECTED_IDLE;
+ }
+ else
+ {
+ logger("Unexpected file transfer state --> abort file transfer");
- selectedFile.selectedBy = null;
- selectedFile = null;
+ transferState = FileServerState.SEND_ABORT;
+ }
- transferState = FileServerState.UNSELECTED_IDLE;
}
- else
+ else if (ack.AckQualifier == AcknowledgeQualifier.NEG_ACK_SECTION)
{
- logger("Unexpected file transfer state --> abort file transfer");
- transferState = FileServerState.SEND_ABORT;
- }
+ logger("Received negative file section ACK - repeat section");
- }
- else if (ack.AckQualifier == AcknowledgeQualifier.NEG_ACK_SECTION)
- {
+ if (transferState == FileServerState.WAITING_FOR_SECTION_ACK)
+ {
+ currentSectionOffset = 0;
+ sectionChecksum = 0;
- logger("Received negative file section ACK - repeat section");
+ ASDU sectionReady = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, file.GetCA(), false);
- if (transferState == FileServerState.WAITING_FOR_SECTION_ACK)
- {
- currentSectionOffset = 0;
- sectionChecksum = 0;
+ sectionReady.AddInformationObject(
+ new SectionReady(selectedFile.provider.GetIOA(), selectedFile.provider.GetNameOfFile(), currentSectionNumber, currentSectionSize, false));
- ASDU sectionReady = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, file.GetCA(), false);
+ connection.SendASDU(sectionReady);
- sectionReady.AddInformationObject(
- new SectionReady(selectedFile.provider.GetIOA(), selectedFile.provider.GetNameOfFile(), currentSectionNumber, currentSectionSize, false));
- connection.SendASDU(sectionReady);
+ transferState = FileServerState.TRANSMIT_SECTION;
+ }
+ else
+ {
+ logger("Unexpected file transfer state --> abort file transfer");
+ transferState = FileServerState.SEND_ABORT;
+ }
- transferState = FileServerState.TRANSMIT_SECTION;
}
- else
+ else if (ack.AckQualifier == AcknowledgeQualifier.POS_ACK_SECTION)
{
- logger("Unexpected file transfer state --> abort file transfer");
-
- transferState = FileServerState.SEND_ABORT;
- }
- }
- else if (ack.AckQualifier == AcknowledgeQualifier.POS_ACK_SECTION)
- {
+ if (transferState == FileServerState.WAITING_FOR_SECTION_ACK)
+ {
+ currentSectionNumber++;
- if (transferState == FileServerState.WAITING_FOR_SECTION_ACK)
- {
- currentSectionNumber++;
+ int nextSectionSize =
+ selectedFile.provider.GetSectionSize(currentSectionNumber - 1);
- int nextSectionSize =
- selectedFile.provider.GetSectionSize(currentSectionNumber - 1);
+ currentSectionOffset = 0;
- currentSectionOffset = 0;
+ ASDU responseAsdu = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, file.GetCA(), false);
- ASDU responseAsdu = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, file.GetCA(), false);
+ if (nextSectionSize == -1)
+ {
+ logger("Received positive file section ACK - send last section indication");
- if (nextSectionSize == -1)
- {
- logger("Received positive file section ACK - send last section indication");
-
- responseAsdu.AddInformationObject(
- new FileLastSegmentOrSection(file.GetIOA(), file.GetNameOfFile(),
- (byte)currentSectionNumber,
- LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT,
- fileChecksum));
+ responseAsdu.AddInformationObject(
+ new FileLastSegmentOrSection(file.GetIOA(), file.GetNameOfFile(),
+ currentSectionNumber,
+ LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT,
+ fileChecksum));
- transferState = FileServerState.WAITING_FOR_FILE_ACK;
- }
- else
- {
- logger("Received positive file section ACK - send next section ready indication");
+ transferState = FileServerState.WAITING_FOR_FILE_ACK;
+ }
+ else
+ {
+ logger("Received positive file section ACK - send next section ready indication");
- currentSectionSize = nextSectionSize;
+ currentSectionSize = nextSectionSize;
- responseAsdu.AddInformationObject(
- new SectionReady(selectedFile.provider.GetIOA(), selectedFile.provider.GetNameOfFile(), currentSectionNumber, currentSectionSize, false));
+ responseAsdu.AddInformationObject(
+ new SectionReady(selectedFile.provider.GetIOA(), selectedFile.provider.GetNameOfFile(), currentSectionNumber, currentSectionSize, false));
- transferState = FileServerState.WAITING_FOR_SECTION_CALL;
- }
+ transferState = FileServerState.WAITING_FOR_SECTION_CALL;
+ }
- connection.SendASDU(responseAsdu);
+ connection.SendASDU(responseAsdu);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
- sectionChecksum = 0;
- }
- else
- {
- logger("Unexpected file transfer state --> abort file transfer");
+ sectionChecksum = 0;
+ }
+ else
+ {
+ logger("Unexpected file transfer state --> abort file transfer");
- transferState = FileServerState.SEND_ABORT;
+ transferState = FileServerState.SEND_ABORT;
+ }
}
}
+ else
+ {
+ logger("Unexpected File ACK message -> ignore");
+ }
+
}
else
{
- // No file transmission in progress --> what to do?
- logger("Unexpected File ACK message -> ignore");
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ connection.SendASDU(asdu);
}
+ break;
- }
- else
- {
- asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
- connection.SendASDU(asdu);
- }
- break;
-
- case TypeID.F_SC_NA_1: /* 122 - Call/Select directory/file/section */
-
- logger("Received call/select F_SC_NA_1");
-
- if (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)
- {
-
- FileCallOrSelect sc = (FileCallOrSelect)asdu.GetElement(0);
+ case TypeID.F_SC_NA_1: /* 122 - Call/Select directory/file/section */
+ logger("Received call/select F_SC_NA_1");
- if (sc.SCQ == SelectAndCallQualifier.SELECT_FILE)
+ if (asdu.Cot == CauseOfTransmission.FILE_TRANSFER)
{
- if (transferState == FileServerState.UNSELECTED_IDLE)
- {
+ FileCallOrSelect sc = (FileCallOrSelect)asdu.GetElement(0);
- logger("Received SELECT FILE");
- CS101n104File file = availableFiles.GetFile(asdu.Ca, sc.ObjectAddress, sc.NOF);
+ if (sc.SCQ == SelectAndCallQualifier.SELECT_FILE)
+ {
- if (file == null)
+ if (transferState == FileServerState.UNSELECTED_IDLE)
{
- asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
- connection.SendASDU(asdu);
- }
- else
- {
-
- ASDU fileReady = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
-
- // check if already selected
- if (file.selectedBy == null)
- {
- file.selectedBy = this;
-
- fileReady.AddInformationObject(new FileReady(sc.ObjectAddress, sc.NOF, file.provider.GetFileSize(), true));
-
- lastSentTime = SystemUtils.currentTimeMillis ();
- selectedFile = file;
+ logger("Received SELECT FILE");
- transferState = FileServerState.WAITING_FOR_FILE_CALL;
+ CS101n104File file = availableFiles.GetFile(asdu.Ca, sc.ObjectAddress, sc.NOF);
+ if (file == null)
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ connection.SendASDU(asdu);
}
else
{
- fileReady.AddInformationObject(new FileReady(sc.ObjectAddress, sc.NOF, 0, false));
- transferState = FileServerState.UNSELECTED_IDLE;
- }
+ ASDU fileReady = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
- connection.SendASDU(fileReady);
+ /* check if already selected */
+ if (file.selectedBy == null)
+ {
+ file.selectedBy = this;
+ fileReady.AddInformationObject(new FileReady(sc.ObjectAddress, sc.NOF, file.provider.GetFileSize(), true));
- }
+ lastSentTime = SystemUtils.currentTimeMillis();
- }
- else
- {
- logger("Unexpected SELECT FILE message");
- }
+ selectedFile = file;
- }
- else if (sc.SCQ == SelectAndCallQualifier.DEACTIVATE_FILE)
- {
+ transferState = FileServerState.WAITING_FOR_FILE_CALL;
- logger("Received DEACTIVATE FILE");
+ }
+ else
+ {
+ fileReady.AddInformationObject(new FileReady(sc.ObjectAddress, sc.NOF, 0, false));
- if (transferState != FileServerState.UNSELECTED_IDLE)
- {
+ transferState = FileServerState.UNSELECTED_IDLE;
+ }
+
+ connection.SendASDU(fileReady);
- if (selectedFile != null)
+
+ }
+
+ }
+ else
{
- selectedFile.selectedBy = null;
- selectedFile = null;
+ logger("Unexpected SELECT FILE message");
}
- transferState = FileServerState.UNSELECTED_IDLE;
-
}
- else
+ else if (sc.SCQ == SelectAndCallQualifier.DEACTIVATE_FILE)
{
- logger("Unexpected DEACTIVATE FILE message");
- }
- }
- else if (sc.SCQ == SelectAndCallQualifier.REQUEST_FILE)
- {
+ logger("Received DEACTIVATE FILE");
+
+ if (transferState != FileServerState.UNSELECTED_IDLE)
+ {
- logger("Received CALL FILE");
+ if (selectedFile != null)
+ {
+ selectedFile.selectedBy = null;
+ selectedFile = null;
+ }
- if (transferState == FileServerState.WAITING_FOR_FILE_CALL)
- {
+ transferState = FileServerState.UNSELECTED_IDLE;
- if (selectedFile.provider.GetIOA() != sc.ObjectAddress)
- {
- logger("Unkown IOA");
- asdu.IsNegative = true;
- asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
- connection.SendASDU(asdu);
}
else
{
+ logger("Unexpected DEACTIVATE FILE message");
+ }
- ASDU sectionReady = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
-
- currentSectionNumber = 1;
- currentSectionOffset = 0;
- currentSectionSize = selectedFile.provider.GetSectionSize (0);
+ }
+ else if (sc.SCQ == SelectAndCallQualifier.REQUEST_FILE)
+ {
- sectionReady.AddInformationObject(new SectionReady(sc.ObjectAddress, selectedFile.provider.GetNameOfFile(), currentSectionNumber, currentSectionSize, false));
+ logger("Received CALL FILE");
- connection.SendASDU(sectionReady);
+ if (transferState == FileServerState.WAITING_FOR_FILE_CALL)
+ {
- lastSentTime = SystemUtils.currentTimeMillis ();
+ if (selectedFile.provider.GetIOA() != sc.ObjectAddress)
+ {
+ logger("Unkown IOA");
+ asdu.IsNegative = true;
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ connection.SendASDU(asdu);
+ }
+ else
+ {
- logger ("Send SECTION READY");
+ ASDU sectionReady = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
- transferState = FileServerState.WAITING_FOR_SECTION_CALL;
- }
+ currentSectionNumber = 1;
+ currentSectionOffset = 0;
+ currentSectionSize = selectedFile.provider.GetSectionSize(0);
- }
- else
- {
- logger("Unexpected FILE CALL message");
- }
+ sectionReady.AddInformationObject(new SectionReady(sc.ObjectAddress, selectedFile.provider.GetNameOfFile(), currentSectionNumber, currentSectionSize, false));
+ connection.SendASDU(sectionReady);
- }
- else if (sc.SCQ == SelectAndCallQualifier.REQUEST_SECTION)
- {
+ lastSentTime = SystemUtils.currentTimeMillis();
- logger ("Received CALL SECTION (NoS=" + sc.NameOfSection + ") current section: " + currentSectionNumber);
+ logger("Send SECTION READY");
- if (transferState == FileServerState.WAITING_FOR_SECTION_CALL)
- {
+ transferState = FileServerState.WAITING_FOR_SECTION_CALL;
+ }
- if (selectedFile.provider.GetIOA() != sc.ObjectAddress)
- {
- logger("Unkown IOA");
- asdu.IsNegative = true;
- asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
- connection.SendASDU(asdu);
}
else
{
- if (asdu.IsNegative) {
+ logger("Unexpected FILE CALL message");
+ }
- currentSectionNumber++;
- currentSectionOffset = 0;
- currentSectionSize = selectedFile.provider.GetSectionSize (currentSectionNumber - 1);
+ }
+ else if (sc.SCQ == SelectAndCallQualifier.REQUEST_SECTION)
+ {
+
+ logger("Received CALL SECTION (NoS=" + sc.NameOfSection + ") current section: " + currentSectionNumber);
- if (currentSectionSize > 0) {
+ if (transferState == FileServerState.WAITING_FOR_SECTION_CALL)
+ {
- // send section ready with new section number
+ if (selectedFile.provider.GetIOA() != sc.ObjectAddress)
+ {
+ logger("Unkown IOA");
+ asdu.IsNegative = true;
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ connection.SendASDU(asdu);
+ }
+ else
+ {
+ if (asdu.IsNegative)
+ {
- ASDU sectionReady = new ASDU (alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
+ currentSectionNumber++;
+ currentSectionOffset = 0;
- currentSectionSize = selectedFile.provider.GetSectionSize (0);
+ currentSectionSize = selectedFile.provider.GetSectionSize(currentSectionNumber - 1);
- sectionReady.AddInformationObject (new SectionReady (sc.ObjectAddress, selectedFile.provider.GetNameOfFile (), currentSectionNumber, currentSectionSize, false));
+ if (currentSectionSize > 0)
+ {
- connection.SendASDU (sectionReady);
+ /* send section ready with new section number */
- lastSentTime = SystemUtils.currentTimeMillis ();
+ ASDU sectionReady = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
- logger ("Send F_SR_NA_1 (section ready) (NoS = " + currentSectionNumber + ")");
+ currentSectionSize = selectedFile.provider.GetSectionSize(0);
- transferState = FileServerState.WAITING_FOR_SECTION_CALL;
+ sectionReady.AddInformationObject(new SectionReady(sc.ObjectAddress, selectedFile.provider.GetNameOfFile(), currentSectionNumber, currentSectionSize, false));
- } else {
- // send last section PDU
+ connection.SendASDU(sectionReady);
- ASDU lastSection = new ASDU (alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
+ lastSentTime = SystemUtils.currentTimeMillis();
+ logger("Send F_SR_NA_1 (section ready) (NoS = " + currentSectionNumber + ")");
- lastSection.AddInformationObject (
- new FileLastSegmentOrSection (selectedFile.provider.GetIOA (), selectedFile.provider.GetNameOfFile (),
- (byte)currentSectionNumber,
- LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT,
- fileChecksum));
+ transferState = FileServerState.WAITING_FOR_SECTION_CALL;
- connection.SendASDU (lastSection);
+ }
+ else
+ {
+ /* send last section PDU */
- logger ("Send F_LS_NA_1 (last section))");
+ ASDU lastSection = new ASDU(alParameters, CauseOfTransmission.FILE_TRANSFER, false, false, 0, asdu.Ca, false);
- lastSentTime = SystemUtils.currentTimeMillis ();
- transferState = FileServerState.WAITING_FOR_FILE_ACK;
- }
+ lastSection.AddInformationObject(
+ new FileLastSegmentOrSection(selectedFile.provider.GetIOA(), selectedFile.provider.GetNameOfFile(),
+ currentSectionNumber,
+ LastSectionOrSegmentQualifier.FILE_TRANSFER_WITHOUT_DEACT,
+ fileChecksum));
- }
- else {
- currentSectionSize = selectedFile.provider.GetSectionSize (sc.NameOfSection - 1);
+ connection.SendASDU(lastSection);
- if (currentSectionSize > 0) {
- currentSectionNumber = sc.NameOfSection;
- currentSectionOffset = 0;
+ logger("Send F_LS_NA_1 (last section))");
- transferState = FileServerState.TRANSMIT_SECTION;
- } else {
- logger ("Unexpected number of section");
- logger ("Send negative confirm");
- asdu.IsNegative = true;
+ lastSentTime = SystemUtils.currentTimeMillis();
- lastSentTime = SystemUtils.currentTimeMillis ();
+ transferState = FileServerState.WAITING_FOR_FILE_ACK;
+ }
- connection.SendASDU (asdu);
}
- }
+ else
+ {
+ currentSectionSize = selectedFile.provider.GetSectionSize(sc.NameOfSection - 1);
+
+ if (currentSectionSize > 0)
+ {
+ currentSectionNumber = sc.NameOfSection;
+ currentSectionOffset = 0;
+
+ transferState = FileServerState.TRANSMIT_SECTION;
+ }
+ else
+ {
+ logger("Unexpected number of section");
+ logger("Send negative confirm");
+ asdu.IsNegative = true;
+
+ lastSentTime = SystemUtils.currentTimeMillis();
+
+ connection.SendASDU(asdu);
+ }
+ }
+
-
+ }
+ }
+ else
+ {
+ logger("Unexpected SECTION CALL message");
}
}
- else
- {
- logger("Unexpected SECTION CALL message");
- }
- }
- }
- else if (asdu.Cot == CauseOfTransmission.REQUEST)
- {
- logger("Call directory received");
+ }
+ else if (asdu.Cot == CauseOfTransmission.REQUEST)
+ {
+ logger("Call directory received");
- availableFiles.SendDirectoy(connection, false);
+ availableFiles.SendDirectoy(connection, false);
- }
- else
- {
- asdu.IsNegative = true;
- asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
- connection.SendASDU(asdu);
- }
- break;
+ }
+ else
+ {
+ asdu.IsNegative = true;
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ connection.SendASDU(asdu);
+ }
+ break;
- default:
- handled = false;
- break;
+ default:
+ handled = false;
+ break;
}
return handled;
@@ -1598,11 +1737,11 @@ public void HandleFileTransmission()
if (currentSectionOffset == currentSectionSize)
{
- // send last segment
+ /* send last segment */
fileAsdu.AddInformationObject(
- new FileLastSegmentOrSection(file.GetIOA(), file.GetNameOfFile(),
- currentSectionNumber,
+ new FileLastSegmentOrSection(file.GetIOA(), file.GetNameOfFile(),
+ currentSectionNumber,
LastSectionOrSegmentQualifier.SECTION_TRANSFER_WITHOUT_DEACT,
sectionChecksum));
@@ -1614,7 +1753,7 @@ public void HandleFileTransmission()
connection.SendASDU(fileAsdu);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
transferState = FileServerState.WAITING_FOR_SECTION_ACK;
@@ -1635,7 +1774,7 @@ public void HandleFileTransmission()
segmentData);
fileAsdu.AddInformationObject(
- new FileSegment(file.GetIOA(), file.GetNameOfFile(), currentSectionNumber,
+ new FileSegment(file.GetIOA(), file.GetNameOfFile(), currentSectionNumber,
segmentData));
byte checksum = 0;
@@ -1647,22 +1786,24 @@ public void HandleFileTransmission()
connection.SendASDU(fileAsdu);
- lastSentTime = SystemUtils.currentTimeMillis ();
+ lastSentTime = SystemUtils.currentTimeMillis();
sectionChecksum += checksum;
- logger("Send SEGMENT (NoS=" + currentSectionNumber + ", CHS=" + sectionChecksum + ")");
+ logger("Send SEGMENT (NoS=" + currentSectionNumber + ", CHS=" + sectionChecksum + ")");
currentSectionOffset += currentSegmentSize;
}
}
}
- // check for timeout
- if (SystemUtils.currentTimeMillis () > lastSentTime + timeout) {
- logger ("Abort file transfer due to timeout");
+ /* check for timeout */
+ if (SystemUtils.currentTimeMillis() > lastSentTime + timeout)
+ {
+ logger("Abort file transfer due to timeout");
- if (selectedFile != null) {
+ if (selectedFile != null)
+ {
selectedFile.selectedBy = null;
selectedFile = null;
}
diff --git a/lib60870/CS101/InformationObject.cs b/lib60870/CS101/InformationObject.cs
index 631eaf0..91fa690 100644
--- a/lib60870/CS101/InformationObject.cs
+++ b/lib60870/CS101/InformationObject.cs
@@ -1,7 +1,7 @@
/*
* InformationObject.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,7 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
using System.Collections.Generic;
namespace lib60870.CS101
@@ -38,7 +37,7 @@ public override string ToString()
ret += " Type: " + Type.ToString();
-
+
return ret;
}
@@ -79,7 +78,7 @@ public int ObjectAddress
{
get
{
- return this.objectAddress;
+ return objectAddress;
}
internal set
{
@@ -118,7 +117,7 @@ public virtual void Encode(Frame frame, ApplicationLayerParameters parameters, b
frame.SetNextByte((byte)((objectAddress / 0x10000) & 0xff));
}
}
-
+
}
public interface IPrivateIOFactory
@@ -149,18 +148,18 @@ public class PrivateInformationObjectTypes
public void AddPrivateInformationObjectType(TypeID typeId, IPrivateIOFactory iot)
{
- privateTypes.Add(typeId, iot);
+ privateTypes.Add(typeId, iot);
}
internal IPrivateIOFactory GetFactory(TypeID typeId)
{
IPrivateIOFactory factory = null;
- privateTypes.TryGetValue(typeId, out factory);
+ privateTypes.TryGetValue(typeId, out factory);
return factory;
}
}
-
+
}
diff --git a/lib60870/CS101/IntegratedTotals.cs b/lib60870/CS101/IntegratedTotals.cs
index 709a13d..1cc068a 100644
--- a/lib60870/CS101/IntegratedTotals.cs
+++ b/lib60870/CS101/IntegratedTotals.cs
@@ -1,7 +1,7 @@
/*
* IntegratedTotals.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -70,7 +70,7 @@ public IntegratedTotals(int ioa, BinaryCounterReading bcr)
public IntegratedTotals(IntegratedTotals original)
: base(original.ObjectAddress)
{
- this.bcr = new BinaryCounterReading(original.bcr);
+ bcr = new BinaryCounterReading(original.bcr);
}
internal IntegratedTotals(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSquence)
@@ -125,7 +125,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -136,15 +136,15 @@ public IntegratedTotalsWithCP24Time2a(int ioa, BinaryCounterReading bcr, CP24Tim
}
public IntegratedTotalsWithCP24Time2a(IntegratedTotalsWithCP24Time2a original)
- : base (original)
+ : base(original)
{
- this.timestamp = new CP24Time2a(timestamp);
+ timestamp = new CP24Time2a(timestamp);
}
public IntegratedTotalsWithCP24Time2a(IntegratedTotals original)
: base(original)
{
- this.timestamp = new CP24Time2a();
+ timestamp = new CP24Time2a();
}
internal IntegratedTotalsWithCP24Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -201,7 +201,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -212,15 +212,15 @@ public IntegratedTotalsWithCP56Time2a(int ioa, BinaryCounterReading bcr, CP56Tim
}
public IntegratedTotalsWithCP56Time2a(IntegratedTotalsWithCP56Time2a original)
- : base (original)
+ : base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
public IntegratedTotalsWithCP56Time2a(IntegratedTotals original)
: base(original)
{
- this.timestamp = new CP56Time2a(DateTime.Now);
+ timestamp = new CP56Time2a(DateTime.Now);
}
public IntegratedTotalsWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
diff --git a/lib60870/CS101/Master.cs b/lib60870/CS101/Master.cs
index 27a5d0b..48413b9 100644
--- a/lib60870/CS101/Master.cs
+++ b/lib60870/CS101/Master.cs
@@ -1,7 +1,7 @@
/*
* Master.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,14 +21,12 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
///
/// Handler that is called when a new ASDU is received
///
- public delegate bool ASDUReceivedHandler(object parameter,int slaveAddress,ASDU asdu);
+ public delegate bool ASDUReceivedHandler(object parameter, int slaveAddress, ASDU asdu);
///
/// Common interface for CS104 and CS101 balanced and unbalanced master
@@ -42,7 +40,7 @@ public bool DebugOutput
{
get
{
- return this.debugOutput;
+ return debugOutput;
}
set
{
@@ -169,7 +167,7 @@ public bool DebugOutput
/// IOA
/// Name of file (file type)
/// File provider instance
- public abstract void SendFile (int ca, int ioa, NameOfFile nof, IFileProvider fileProvider);
+ public abstract void SendFile(int ca, int ioa, NameOfFile nof, IFileProvider fileProvider);
///
/// Get the application layer parameters used by this master instance
@@ -190,8 +188,7 @@ public bool DebugOutput
/// Handler/delegate that will be invoked when a message is sent<
/// will be passed to the delegate
public abstract void SetSentRawMessageHandler(RawMessageHandler handler, object parameter);
-
}
-
+
}
diff --git a/lib60870/CS101/MeasuredValueNormalized.cs b/lib60870/CS101/MeasuredValueNormalized.cs
index bcb648f..8e63fae 100644
--- a/lib60870/CS101/MeasuredValueNormalized.cs
+++ b/lib60870/CS101/MeasuredValueNormalized.cs
@@ -1,7 +1,7 @@
/*
* MeasuredValueNormalized.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
///
@@ -69,37 +67,31 @@ public float NormalizedValue
{
get
{
- return (float)(scaledValue.Value + 0.5) / (float)32767.5;
+ return scaledValue.GetNormalizedValue();
}
set
{
- /* Check value range */
- if (value > 1.0f)
- value = 1.0f;
- else if (value < -1.0f)
- value = -1.0f;
-
- this.scaledValue.Value = (int)((value * 32767.5) - 0.5);
+ scaledValue.SetScaledFromNormalizedValue(value);
}
}
public MeasuredValueNormalizedWithoutQuality(int objectAddress, float normalizedValue)
: base(objectAddress)
{
- this.scaledValue = new ScaledValue();
- this.NormalizedValue = normalizedValue;
+ scaledValue = new ScaledValue();
+ NormalizedValue = normalizedValue;
}
public MeasuredValueNormalizedWithoutQuality(MeasuredValueNormalizedWithoutQuality original)
: base(original.ObjectAddress)
{
- this.scaledValue = new ScaledValue(original.scaledValue);
+ scaledValue = new ScaledValue(original.scaledValue);
}
public MeasuredValueNormalizedWithoutQuality(int objectAddress, short rawValue)
: base(objectAddress)
{
- this.scaledValue = new ScaledValue(rawValue);
+ scaledValue = new ScaledValue(rawValue);
}
internal MeasuredValueNormalizedWithoutQuality(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -154,7 +146,7 @@ public QualityDescriptor Quality
{
get
{
- return this.quality;
+ return quality;
}
}
@@ -173,7 +165,7 @@ public MeasuredValueNormalized(int objectAddress, short value, QualityDescriptor
public MeasuredValueNormalized(MeasuredValueNormalized original)
: base(original)
{
- this.quality = new QualityDescriptor(original.quality);
+ quality = new QualityDescriptor(original.quality);
}
internal MeasuredValueNormalized(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -231,7 +223,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -251,7 +243,7 @@ public MeasuredValueNormalizedWithCP24Time2a(int objectAddress, short value, Qua
public MeasuredValueNormalizedWithCP24Time2a(MeasuredValueNormalizedWithCP24Time2a original)
: base(original)
{
- this.timestamp = new CP24Time2a(original.timestamp);
+ timestamp = new CP24Time2a(original.timestamp);
}
internal MeasuredValueNormalizedWithCP24Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -309,7 +301,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -328,7 +320,7 @@ public MeasuredValueNormalizedWithCP56Time2a(int objectAddress, short value, Qua
public MeasuredValueNormalizedWithCP56Time2a(MeasuredValueNormalizedWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal MeasuredValueNormalizedWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
diff --git a/lib60870/CS101/MeasuredValueScaled.cs b/lib60870/CS101/MeasuredValueScaled.cs
index 006aef0..f22bfee 100644
--- a/lib60870/CS101/MeasuredValueScaled.cs
+++ b/lib60870/CS101/MeasuredValueScaled.cs
@@ -1,7 +1,7 @@
/*
* MeasuredValueScaled.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
public class MeasuredValueScaled : InformationObject
@@ -54,7 +52,7 @@ public ScaledValue ScaledValue
{
get
{
- return this.scaledValue;
+ return scaledValue;
}
}
@@ -64,7 +62,7 @@ public QualityDescriptor Quality
{
get
{
- return this.quality;
+ return quality;
}
}
@@ -77,7 +75,7 @@ public QualityDescriptor Quality
public MeasuredValueScaled(int objectAddress, int value, QualityDescriptor quality)
: base(objectAddress)
{
- this.scaledValue = new ScaledValue(value);
+ scaledValue = new ScaledValue(value);
this.quality = quality;
}
@@ -100,8 +98,8 @@ internal MeasuredValueScaled(ApplicationLayerParameters parameters, byte[] msg,
public MeasuredValueScaled(MeasuredValueScaled original)
: base(original.ObjectAddress)
{
- this.scaledValue = new ScaledValue(original.ScaledValue);
- this.quality = new QualityDescriptor(original.quality);
+ scaledValue = new ScaledValue(original.ScaledValue);
+ quality = new QualityDescriptor(original.quality);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -144,7 +142,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -157,7 +155,7 @@ public MeasuredValueScaledWithCP24Time2a(int objectAddress, int value, QualityDe
public MeasuredValueScaledWithCP24Time2a(MeasuredValueScaledWithCP24Time2a original)
: base(original)
{
- this.timestamp = new CP24Time2a(timestamp);
+ timestamp = new CP24Time2a(timestamp);
}
internal MeasuredValueScaledWithCP24Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -213,7 +211,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -226,7 +224,7 @@ public MeasuredValueScaledWithCP56Time2a(int objectAddress, int value, QualityDe
public MeasuredValueScaledWithCP56Time2a(MeasuredValueScaledWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal MeasuredValueScaledWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -252,6 +250,6 @@ public override void Encode(Frame frame, ApplicationLayerParameters parameters,
}
}
-
+
}
diff --git a/lib60870/CS101/MeasuredValueShort.cs b/lib60870/CS101/MeasuredValueShort.cs
index 7801a67..65cc0ec 100644
--- a/lib60870/CS101/MeasuredValueShort.cs
+++ b/lib60870/CS101/MeasuredValueShort.cs
@@ -1,7 +1,7 @@
/*
* MeasuredValueShortFloat.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -54,7 +54,7 @@ public float Value
{
get
{
- return this.value;
+ return value;
}
set
{
@@ -68,7 +68,7 @@ public QualityDescriptor Quality
{
get
{
- return this.quality;
+ return quality;
}
}
@@ -82,8 +82,8 @@ public MeasuredValueShort(int objectAddress, float value, QualityDescriptor qual
public MeasuredValueShort(MeasuredValueShort original)
: base(original.ObjectAddress)
{
- this.value = original.value;
- this.quality = new QualityDescriptor(original.Quality);
+ value = original.value;
+ quality = new QualityDescriptor(original.Quality);
}
internal MeasuredValueShort(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -147,7 +147,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -160,7 +160,7 @@ public MeasuredValueShortWithCP24Time2a(int objectAddress, float value, QualityD
public MeasuredValueShortWithCP24Time2a(MeasuredValueShortWithCP24Time2a original)
: base(original)
{
- this.timestamp = new CP24Time2a(original.timestamp);
+ timestamp = new CP24Time2a(original.timestamp);
}
internal MeasuredValueShortWithCP24Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -216,7 +216,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -229,7 +229,7 @@ public MeasuredValueShortWithCP56Time2a(int objectAddress, float value, QualityD
public MeasuredValueShortWithCP56Time2a(MeasuredValueShortWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal MeasuredValueShortWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
diff --git a/lib60870/CS101/OutputCircuitInfo.cs b/lib60870/CS101/OutputCircuitInfo.cs
index 70b27a6..c67440c 100644
--- a/lib60870/CS101/OutputCircuitInfo.cs
+++ b/lib60870/CS101/OutputCircuitInfo.cs
@@ -1,7 +1,7 @@
/*
* OutputCircuitInfo.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,7 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
using System.Text;
namespace lib60870.CS101
@@ -40,7 +39,7 @@ public byte EncodedValue
{
get
{
- return this.encodedValue;
+ return encodedValue;
}
set
{
@@ -50,12 +49,12 @@ public byte EncodedValue
public OutputCircuitInfo()
{
- this.encodedValue = 0;
+ encodedValue = 0;
}
public OutputCircuitInfo(OutputCircuitInfo original)
{
- this.encodedValue = original.encodedValue;
+ encodedValue = original.encodedValue;
}
public override bool Equals(object obj)
@@ -66,12 +65,12 @@ public override bool Equals(object obj)
if (!(obj is OutputCircuitInfo))
return false;
- return (this.encodedValue == ((OutputCircuitInfo)obj).encodedValue);
+ return (encodedValue == ((OutputCircuitInfo)obj).encodedValue);
}
public override int GetHashCode()
{
- return this.encodedValue.GetHashCode();
+ return encodedValue.GetHashCode();
}
public OutputCircuitInfo(byte encodedValue)
diff --git a/lib60870/CS101/PackedOutputCircuitInfo.cs b/lib60870/CS101/PackedOutputCircuitInfo.cs
index 2ed170b..9e5d848 100644
--- a/lib60870/CS101/PackedOutputCircuitInfo.cs
+++ b/lib60870/CS101/PackedOutputCircuitInfo.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
@@ -53,7 +51,7 @@ public OutputCircuitInfo OCI
{
get
{
- return this.oci;
+ return oci;
}
}
@@ -63,7 +61,7 @@ public QualityDescriptorP QDP
{
get
{
- return this.qdp;
+ return qdp;
}
}
@@ -73,7 +71,7 @@ public CP16Time2a OperatingTime
{
get
{
- return this.operatingTime;
+ return operatingTime;
}
}
@@ -83,7 +81,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -99,10 +97,10 @@ public PackedOutputCircuitInfo(int objectAddress, OutputCircuitInfo oci, Quality
public PackedOutputCircuitInfo(PackedOutputCircuitInfo original)
: base(original.ObjectAddress)
{
- this.oci = new OutputCircuitInfo(original.oci);
- this.qdp = new QualityDescriptorP(original.qdp);
- this.operatingTime = new CP16Time2a(operatingTime);
- this.timestamp = new CP24Time2a(timestamp);
+ oci = new OutputCircuitInfo(original.oci);
+ qdp = new QualityDescriptorP(original.qdp);
+ operatingTime = new CP16Time2a(operatingTime);
+ timestamp = new CP24Time2a(timestamp);
}
internal PackedOutputCircuitInfo(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -168,7 +166,7 @@ public OutputCircuitInfo OCI
{
get
{
- return this.oci;
+ return oci;
}
}
@@ -178,7 +176,7 @@ public QualityDescriptorP QDP
{
get
{
- return this.qdp;
+ return qdp;
}
}
@@ -188,7 +186,7 @@ public CP16Time2a OperatingTime
{
get
{
- return this.operatingTime;
+ return operatingTime;
}
}
@@ -198,7 +196,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -215,10 +213,10 @@ public PackedOutputCircuitInfoWithCP56Time2a(int objectAddress, OutputCircuitInf
public PackedOutputCircuitInfoWithCP56Time2a(PackedOutputCircuitInfoWithCP56Time2a original)
: base(original.ObjectAddress)
{
- this.oci = new OutputCircuitInfo(original.oci);
- this.qdp = new QualityDescriptorP(original.qdp);
- this.operatingTime = new CP16Time2a(operatingTime);
- this.timestamp = new CP56Time2a(timestamp);
+ oci = new OutputCircuitInfo(original.oci);
+ qdp = new QualityDescriptorP(original.qdp);
+ operatingTime = new CP16Time2a(operatingTime);
+ timestamp = new CP56Time2a(timestamp);
}
internal PackedOutputCircuitInfoWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
diff --git a/lib60870/CS101/PackedStartEventsOfProtectionEquipment.cs b/lib60870/CS101/PackedStartEventsOfProtectionEquipment.cs
index 3c838b9..c585134 100644
--- a/lib60870/CS101/PackedStartEventsOfProtectionEquipment.cs
+++ b/lib60870/CS101/PackedStartEventsOfProtectionEquipment.cs
@@ -1,7 +1,7 @@
/*
* PackedStartEventsOfProtectionEquipment.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,9 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-using System.Collections.Generic;
-
namespace lib60870.CS101
{
@@ -76,7 +73,7 @@ public CP16Time2a ElapsedTime
{
get
{
- return this.elapsedTime;
+ return elapsedTime;
}
}
@@ -86,7 +83,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -102,10 +99,10 @@ public PackedStartEventsOfProtectionEquipment(int objectAddress, StartEvent spe,
public PackedStartEventsOfProtectionEquipment(PackedStartEventsOfProtectionEquipment original)
: base(original.ObjectAddress)
{
- this.spe = new StartEvent(original.spe);
- this.qdp = new QualityDescriptorP(original.qdp);
- this.elapsedTime = new CP16Time2a(original.elapsedTime);
- this.timestamp = new CP24Time2a(original.timestamp);
+ spe = new StartEvent(original.spe);
+ qdp = new QualityDescriptorP(original.qdp);
+ elapsedTime = new CP16Time2a(original.elapsedTime);
+ timestamp = new CP24Time2a(original.timestamp);
}
internal PackedStartEventsOfProtectionEquipment(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -190,7 +187,7 @@ public CP16Time2a ElapsedTime
{
get
{
- return this.elapsedTime;
+ return elapsedTime;
}
}
@@ -200,7 +197,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -216,10 +213,10 @@ public PackedStartEventsOfProtectionEquipmentWithCP56Time2a(int objectAddress, S
public PackedStartEventsOfProtectionEquipmentWithCP56Time2a(PackedStartEventsOfProtectionEquipmentWithCP56Time2a original)
: base(original.ObjectAddress)
{
- this.spe = new StartEvent(original.spe);
- this.qdp = new QualityDescriptorP(original.qdp);
- this.elapsedTime = new CP16Time2a(original.elapsedTime);
- this.timestamp = new CP56Time2a(original.timestamp);
+ spe = new StartEvent(original.spe);
+ qdp = new QualityDescriptorP(original.qdp);
+ elapsedTime = new CP16Time2a(original.elapsedTime);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal PackedStartEventsOfProtectionEquipmentWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
diff --git a/lib60870/CS101/ParameterMeasuredValues.cs b/lib60870/CS101/ParameterMeasuredValues.cs
index d1ed918..f63ad10 100644
--- a/lib60870/CS101/ParameterMeasuredValues.cs
+++ b/lib60870/CS101/ParameterMeasuredValues.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -65,17 +65,11 @@ public float NormalizedValue
{
get
{
- return (float)(scaledValue.Value + 0.5) / (float)32767.5;
+ return scaledValue.GetNormalizedValue();
}
set
{
- /* Check value range */
- if (value > 1.0f)
- value = 1.0f;
- else if (value < -1.0f)
- value = -1.0f;
-
- this.scaledValue.Value = (int)((value * 32767.5) - 0.5);
+ scaledValue.SetScaledFromNormalizedValue(value);
}
}
@@ -94,7 +88,7 @@ public ParameterNormalizedValue(int objectAddress, float normalizedValue, byte q
{
scaledValue = new ScaledValue();
- this.NormalizedValue = normalizedValue;
+ NormalizedValue = normalizedValue;
this.qpm = qpm;
}
@@ -240,7 +234,7 @@ public float Value
{
get
{
- return this.value;
+ return value;
}
}
diff --git a/lib60870/CS101/QualityDescriptor.cs b/lib60870/CS101/QualityDescriptor.cs
index e1d0743..3a62085 100644
--- a/lib60870/CS101/QualityDescriptor.cs
+++ b/lib60870/CS101/QualityDescriptor.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
public class QualityDescriptor
@@ -41,12 +39,12 @@ public static QualityDescriptor INVALID()
public QualityDescriptor()
{
- this.encodedValue = 0;
+ encodedValue = 0;
}
public QualityDescriptor(QualityDescriptor original)
{
- this.encodedValue = original.encodedValue;
+ encodedValue = original.encodedValue;
}
public QualityDescriptor(byte encodedValue)
@@ -62,12 +60,12 @@ public override bool Equals(object obj)
if (!(obj is QualityDescriptor))
return false;
- return (this.encodedValue == ((QualityDescriptor)obj).encodedValue);
+ return (encodedValue == ((QualityDescriptor)obj).encodedValue);
}
public override int GetHashCode()
{
- return this.encodedValue.GetHashCode();
+ return encodedValue.GetHashCode();
}
public bool Overflow
@@ -171,7 +169,7 @@ public byte EncodedValue
{
get
{
- return this.encodedValue;
+ return encodedValue;
}
set
{
@@ -184,6 +182,6 @@ public override string ToString()
return string.Format("[QualityDescriptor: Overflow={0}, Blocked={1}, Substituted={2}, NonTopical={3}, Invalid={4}]", Overflow, Blocked, Substituted, NonTopical, Invalid);
}
}
-
+
}
diff --git a/lib60870/CS101/QualityDescriptorP.cs b/lib60870/CS101/QualityDescriptorP.cs
index 3444dc1..f7e90c5 100644
--- a/lib60870/CS101/QualityDescriptorP.cs
+++ b/lib60870/CS101/QualityDescriptorP.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
@@ -34,7 +32,7 @@ public class QualityDescriptorP
public QualityDescriptorP()
{
- this.encodedValue = 0;
+ encodedValue = 0;
}
public QualityDescriptorP(byte encodedValue)
@@ -44,7 +42,7 @@ public QualityDescriptorP(byte encodedValue)
public QualityDescriptorP(QualityDescriptorP original)
{
- this.encodedValue = original.encodedValue;
+ encodedValue = original.encodedValue;
}
public override bool Equals(object obj)
@@ -55,12 +53,12 @@ public override bool Equals(object obj)
if (!(obj is QualityDescriptorP))
return false;
- return (this.encodedValue == ((QualityDescriptorP)obj).encodedValue);
+ return (encodedValue == ((QualityDescriptorP)obj).encodedValue);
}
public override int GetHashCode()
{
- return this.encodedValue.GetHashCode();
+ return encodedValue.GetHashCode();
}
public bool Reserved
@@ -175,7 +173,7 @@ public byte EncodedValue
{
get
{
- return this.encodedValue;
+ return encodedValue;
}
set
{
diff --git a/lib60870/CS101/ScaledValue.cs b/lib60870/CS101/ScaledValue.cs
index 646f0e6..cf98b10 100644
--- a/lib60870/CS101/ScaledValue.cs
+++ b/lib60870/CS101/ScaledValue.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -26,6 +26,10 @@ namespace lib60870.CS101
public class ScaledValue
{
private byte[] encodedValue = new byte[2];
+ const int SCALED_VALUE_MAX = 32767;
+ const int SCALED_VALUE_MIN = -32768;
+ const float NORMALIZED_VALUE_MAX = 32767f / 32768f;
+ const float NORMALIZED_VALUE_MIN = -1.0f;
public ScaledValue(byte[] msg, int startIndex)
{
@@ -42,17 +46,17 @@ public ScaledValue()
public ScaledValue(int value)
{
- this.Value = value;
+ Value = value;
}
public ScaledValue(short value)
{
- this.ShortValue = value;
+ ShortValue = value;
}
public ScaledValue(ScaledValue original)
{
- this.ShortValue = original.ShortValue;
+ ShortValue = original.ShortValue;
}
public byte[] GetEncodedValue()
@@ -66,25 +70,29 @@ public int Value
{
int value;
- value = encodedValue[0];
- value += (encodedValue[1] * 0x100);
+ value = encodedValue[0] | (encodedValue[1] << 8);
+ /* Sign-extend for negative values*/
if (value > 32767)
- value = value - 65536;
+ value -= 65536;
+ /* Clamp within valid range*/
+ if (value > SCALED_VALUE_MAX)
+ value = SCALED_VALUE_MAX;
+ else if (value < SCALED_VALUE_MIN)
+ value = SCALED_VALUE_MIN;
+
+
return value;
}
set
- {
- if (value > 32767)
- value = 32767;
- else if (value < -32768)
- value = -32768;
-
- short valueToEncode = (short)value;
+ { if (value > SCALED_VALUE_MAX)
+ value = SCALED_VALUE_MAX;
+ else if (value < SCALED_VALUE_MIN)
+ value = SCALED_VALUE_MIN;
- encodedValue[0] = (byte)(valueToEncode & 255);
- encodedValue[1] = (byte)(valueToEncode >> 8);
+ encodedValue[0] = (byte)(value & 0xFF); /* Lower byte*/
+ encodedValue[1] = (byte)((value >> 8) & 0xFF); /* Upper byte*/
}
}
@@ -113,6 +121,54 @@ public override string ToString()
{
return "" + Value;
}
+
+ public float GetNormalizedValue()
+ {
+ /* Ensure correct floating-point division*/
+ double result = Value / 32767.0;
+
+ /* Ensure proper clamping*/
+ if (result > NORMALIZED_VALUE_MAX)
+ result = NORMALIZED_VALUE_MAX;
+ if (result < -1.0)
+ result = -1.0;
+
+ return (float)result;
+ }
+
+ public int ConvertNormalizedValueToScaled(float value)
+ {
+
+ if (value > NORMALIZED_VALUE_MAX)
+
+ value = NORMALIZED_VALUE_MAX;
+
+ else if (value < NORMALIZED_VALUE_MIN)
+
+ value = NORMALIZED_VALUE_MIN;
+
+ float scaledValue = value * 32768f;
+
+ return (int)(scaledValue < 0 ? scaledValue - 0.5f : scaledValue + 0.5f);
+
+ }
+
+ public void SetScaledFromNormalizedValue(float value)
+ {
+
+ if (value > NORMALIZED_VALUE_MAX)
+
+ value = NORMALIZED_VALUE_MAX;
+
+ else if (value < NORMALIZED_VALUE_MIN)
+
+ value = NORMALIZED_VALUE_MIN;
+
+ float scaledValue = value * 32768f;
+
+ Value = (int)(scaledValue < 0 ? scaledValue - 0.5f : scaledValue + 0.5f);
+
+ }
}
-
+
}
diff --git a/lib60870/CS101/SetpointCommandQualifier.cs b/lib60870/CS101/SetpointCommandQualifier.cs
index 60fdeaf..be4716b 100644
--- a/lib60870/CS101/SetpointCommandQualifier.cs
+++ b/lib60870/CS101/SetpointCommandQualifier.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
public class SetpointCommandQualifier
diff --git a/lib60870/CS101/SetpointCommands.cs b/lib60870/CS101/SetpointCommands.cs
index d9bea56..bbc0be9 100644
--- a/lib60870/CS101/SetpointCommands.cs
+++ b/lib60870/CS101/SetpointCommands.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -65,17 +65,11 @@ public float NormalizedValue
{
get
{
- return (float)(scaledValue.Value + 0.5) / (float)32767.5;
+ return scaledValue.GetNormalizedValue();
}
set
{
- /* Check value range */
- if (value > 1.0f)
- value = 1.0f;
- else if (value < -1.0f)
- value = -1.0f;
-
- this.scaledValue.Value = (int)((value * 32767.5) - 0.5);
+ scaledValue.SetScaledFromNormalizedValue(value);
}
}
@@ -92,14 +86,14 @@ public SetpointCommandQualifier QOS
public SetpointCommandNormalized(int objectAddress, float value, SetpointCommandQualifier qos)
: base(objectAddress)
{
- this.scaledValue = new ScaledValue((int)((value * 32767.5) - 0.5));
+ scaledValue = new ScaledValue((int)((value * 32767.5) - 0.5));
this.qos = qos;
}
public SetpointCommandNormalized(int ObjectAddress, short value, SetpointCommandQualifier qos)
: base(ObjectAddress)
{
- this.scaledValue = new ScaledValue(value);
+ scaledValue = new ScaledValue(value);
this.qos = qos;
}
@@ -114,7 +108,7 @@ internal SetpointCommandNormalized(ApplicationLayerParameters parameters, byte[]
scaledValue = new ScaledValue(msg, startIndex);
startIndex += 2;
- this.qos = new SetpointCommandQualifier(msg[startIndex++]);
+ qos = new SetpointCommandQualifier(msg[startIndex++]);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -123,7 +117,7 @@ public override void Encode(Frame frame, ApplicationLayerParameters parameters,
frame.AppendBytes(scaledValue.GetEncodedValue());
- frame.SetNextByte(this.qos.GetEncodedValue());
+ frame.SetNextByte(qos.GetEncodedValue());
}
}
@@ -182,14 +176,14 @@ internal SetpointCommandNormalizedWithCP56Time2a(ApplicationLayerParameters para
startIndex += 3; /* normalized value + qualifier */
- this.timestamp = new CP56Time2a(msg, startIndex);
+ timestamp = new CP56Time2a(msg, startIndex);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
{
base.Encode(frame, parameters, isSequence);
- frame.AppendBytes(this.timestamp.GetEncodedValue());
+ frame.AppendBytes(timestamp.GetEncodedValue());
}
}
@@ -239,7 +233,7 @@ public SetpointCommandQualifier QOS
public SetpointCommandScaled(int objectAddress, ScaledValue value, SetpointCommandQualifier qos)
: base(objectAddress)
{
- this.scaledValue = value;
+ scaledValue = value;
this.qos = qos;
}
@@ -254,16 +248,16 @@ internal SetpointCommandScaled(ApplicationLayerParameters parameters, byte[] msg
scaledValue = new ScaledValue(msg, startIndex);
startIndex += 2;
- this.qos = new SetpointCommandQualifier(msg[startIndex++]);
+ qos = new SetpointCommandQualifier(msg[startIndex++]);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
{
base.Encode(frame, parameters, isSequence);
- frame.AppendBytes(this.scaledValue.GetEncodedValue());
+ frame.AppendBytes(scaledValue.GetEncodedValue());
- frame.SetNextByte(this.qos.GetEncodedValue());
+ frame.SetNextByte(qos.GetEncodedValue());
}
}
@@ -316,14 +310,14 @@ internal SetpointCommandScaledWithCP56Time2a(ApplicationLayerParameters paramete
startIndex += 3; /* scaled value + qualifier */
- this.timestamp = new CP56Time2a(msg, startIndex);
+ timestamp = new CP56Time2a(msg, startIndex);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
{
base.Encode(frame, parameters, isSequence);
- frame.AppendBytes(this.timestamp.GetEncodedValue());
+ frame.AppendBytes(timestamp.GetEncodedValue());
}
}
@@ -389,7 +383,7 @@ internal SetpointCommandShort(ApplicationLayerParameters parameters, byte[] msg,
value = System.BitConverter.ToSingle(msg, startIndex);
startIndex += 4;
- this.qos = new SetpointCommandQualifier(msg[startIndex++]);
+ qos = new SetpointCommandQualifier(msg[startIndex++]);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -398,7 +392,7 @@ public override void Encode(Frame frame, ApplicationLayerParameters parameters,
frame.AppendBytes(System.BitConverter.GetBytes(value));
- frame.SetNextByte(this.qos.GetEncodedValue());
+ frame.SetNextByte(qos.GetEncodedValue());
}
}
@@ -451,14 +445,14 @@ internal SetpointCommandShortWithCP56Time2a(ApplicationLayerParameters parameter
startIndex += 5; /* skip IOA + float + QOS*/
- this.timestamp = new CP56Time2a(msg, startIndex);
+ timestamp = new CP56Time2a(msg, startIndex);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
{
base.Encode(frame, parameters, isSequence);
- frame.AppendBytes(this.timestamp.GetEncodedValue());
+ frame.AppendBytes(timestamp.GetEncodedValue());
}
}
@@ -492,14 +486,14 @@ public UInt32 Value
{
get
{
- return this.value;
+ return value;
}
}
public Bitstring32Command(int objectAddress, UInt32 bitstring)
: base(objectAddress)
{
- this.value = bitstring;
+ value = bitstring;
}
internal Bitstring32Command(ApplicationLayerParameters parameters, byte[] msg, int startIndex)
@@ -576,14 +570,14 @@ internal Bitstring32CommandWithCP56Time2a(ApplicationLayerParameters parameters,
startIndex += 4; /* bitstring */
- this.timestamp = new CP56Time2a(msg, startIndex);
+ timestamp = new CP56Time2a(msg, startIndex);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
{
base.Encode(frame, parameters, isSequence);
- frame.AppendBytes(this.timestamp.GetEncodedValue());
+ frame.AppendBytes(timestamp.GetEncodedValue());
}
}
}
diff --git a/lib60870/CS101/SingleCommand.cs b/lib60870/CS101/SingleCommand.cs
index 1e91ad9..415f6c6 100644
--- a/lib60870/CS101/SingleCommand.cs
+++ b/lib60870/CS101/SingleCommand.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
@@ -76,7 +74,7 @@ public SingleCommand(int ioa, bool command, bool selectCommand, int qu)
public SingleCommand(SingleCommand original)
: base(original.ObjectAddress)
{
- this.sco = original.sco;
+ sco = original.sco;
}
internal SingleCommand(ApplicationLayerParameters parameters, byte[] msg, int startIndex)
@@ -152,7 +150,7 @@ public override string ToString()
{
return string.Format("[SingleCommand: QU={0}, State={1}, Select={2}]", QU, State, Select);
}
-
+
}
public class SingleCommandWithCP56Time2a : SingleCommand
@@ -192,7 +190,7 @@ public SingleCommandWithCP56Time2a(int ioa, bool command, bool selectCommand, in
public SingleCommandWithCP56Time2a(SingleCommandWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal SingleCommandWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex)
@@ -257,7 +255,7 @@ public DoubleCommand(int ioa, int command, bool select, int quality)
public DoubleCommand(DoubleCommand original)
: base(original.ObjectAddress)
{
- this.dcq = original.dcq;
+ dcq = original.dcq;
}
internal DoubleCommand(ApplicationLayerParameters parameters, byte[] msg, int startIndex)
@@ -340,7 +338,7 @@ public DoubleCommandWithCP56Time2a(int ioa, int command, bool select, int qualit
public DoubleCommandWithCP56Time2a(DoubleCommandWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal DoubleCommandWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex)
@@ -362,7 +360,7 @@ public override void Encode(Frame frame, ApplicationLayerParameters parameters,
frame.AppendBytes(timestamp.GetEncodedValue());
}
-
+
}
public class StepCommand : DoubleCommand
@@ -444,7 +442,7 @@ public StepCommandWithCP56Time2a(int ioa, StepCommandValue command, bool select,
public StepCommandWithCP56Time2a(StepCommandWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal StepCommandWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex)
@@ -466,7 +464,7 @@ public override void Encode(Frame frame, ApplicationLayerParameters parameters,
frame.AppendBytes(timestamp.GetEncodedValue());
}
-
+
}
diff --git a/lib60870/CS101/SingleCommandQualifier.cs b/lib60870/CS101/SingleCommandQualifier.cs
index 110c343..9d819ff 100644
--- a/lib60870/CS101/SingleCommandQualifier.cs
+++ b/lib60870/CS101/SingleCommandQualifier.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870
{
public class SingleCommandQualifier
diff --git a/lib60870/CS101/SingleEvent.cs b/lib60870/CS101/SingleEvent.cs
index 5cad368..c785bab 100644
--- a/lib60870/CS101/SingleEvent.cs
+++ b/lib60870/CS101/SingleEvent.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
@@ -41,14 +39,14 @@ public class SingleEvent
public SingleEvent()
{
- this.eventState = EventState.INDETERMINATE_0;
- this.qdp = new QualityDescriptorP();
+ eventState = EventState.INDETERMINATE_0;
+ qdp = new QualityDescriptorP();
}
public SingleEvent(SingleEvent orignal)
{
- this.eventState = orignal.eventState;
- this.qdp = new QualityDescriptorP(orignal.qdp);
+ eventState = orignal.eventState;
+ qdp = new QualityDescriptorP(orignal.qdp);
}
public override bool Equals(object obj)
@@ -59,19 +57,19 @@ public override bool Equals(object obj)
if (!(obj is SingleEvent))
return false;
- return (this.EncodedValue == ((SingleEvent)obj).EncodedValue);
+ return (EncodedValue == ((SingleEvent)obj).EncodedValue);
}
public override int GetHashCode()
{
- return this.EncodedValue.GetHashCode();
+ return EncodedValue.GetHashCode();
}
public SingleEvent(byte encodedValue)
{
- this.eventState = (EventState)(encodedValue & 0x03);
+ eventState = (EventState)(encodedValue & 0x03);
- this.qdp = new QualityDescriptorP(encodedValue);
+ qdp = new QualityDescriptorP(encodedValue);
}
public EventState State
diff --git a/lib60870/CS101/SinglePointInformation.cs b/lib60870/CS101/SinglePointInformation.cs
index 3691b6d..0d61e20 100644
--- a/lib60870/CS101/SinglePointInformation.cs
+++ b/lib60870/CS101/SinglePointInformation.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
public class SinglePointInformation : InformationObject
@@ -52,7 +50,7 @@ public bool Value
{
get
{
- return this.value;
+ return value;
}
set
{
@@ -66,7 +64,7 @@ public QualityDescriptor Quality
{
get
{
- return this.quality;
+ return quality;
}
}
@@ -98,8 +96,8 @@ public SinglePointInformation(int objectAddress, bool value, QualityDescriptor q
public SinglePointInformation(SinglePointInformation original)
: base(original.ObjectAddress)
{
- this.value = original.Value;
- this.quality = original.Quality;
+ value = original.Value;
+ quality = original.Quality;
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -145,7 +143,7 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -157,7 +155,7 @@ internal SinglePointWithCP24Time2a(ApplicationLayerParameters parameters, byte[]
if ((msg.Length - startIndex) < GetEncodedSize())
throw new ASDUParsingException("Message too small");
-
+
startIndex += 1; /* skip SIQ */
/* parse CP24Time2a (time stamp) */
@@ -173,7 +171,7 @@ public SinglePointWithCP24Time2a(int objectAddress, bool value, QualityDescripto
public SinglePointWithCP24Time2a(SinglePointWithCP24Time2a original)
: base(original)
{
- this.timestamp = new CP24Time2a(original.Timestamp);
+ timestamp = new CP24Time2a(original.Timestamp);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -216,7 +214,7 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
}
@@ -244,7 +242,7 @@ public SinglePointWithCP56Time2a(int objectAddress, bool value, QualityDescripto
public SinglePointWithCP56Time2a(SinglePointWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
@@ -286,7 +284,7 @@ public StatusAndStatusChangeDetection SCD
{
get
{
- return this.scd;
+ return scd;
}
set
{
@@ -298,7 +296,7 @@ public QualityDescriptor QDS
{
get
{
- return this.qds;
+ return qds;
}
set
{
@@ -325,14 +323,14 @@ public PackedSinglePointWithSCD(int objectAddress, StatusAndStatusChangeDetectio
: base(objectAddress)
{
this.scd = scd;
- this.qds = quality;
+ qds = quality;
}
public PackedSinglePointWithSCD(PackedSinglePointWithSCD original)
: base(original.ObjectAddress)
{
- this.scd = new StatusAndStatusChangeDetection(original.SCD);
- this.qds = new QualityDescriptor(original.QDS);
+ scd = new StatusAndStatusChangeDetection(original.SCD);
+ qds = new QualityDescriptor(original.QDS);
}
public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
diff --git a/lib60870/CS101/Slave.cs b/lib60870/CS101/Slave.cs
index e5c3d05..661a791 100644
--- a/lib60870/CS101/Slave.cs
+++ b/lib60870/CS101/Slave.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2024 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,7 +19,6 @@
* See COPYING file for the complete license text.
*/
-
using System;
namespace lib60870.CS101
@@ -31,7 +30,6 @@ public ASDUQueueException()
{
}
-
public ASDUQueueException(string message)
: base(message)
{
@@ -40,7 +38,6 @@ public ASDUQueueException(string message)
public ASDUQueueException(string message, Exception innerException)
: base(message, innerException)
{
-
}
}
@@ -61,49 +58,48 @@ public interface IMasterConnection
///
/// Handler for interrogation command (C_IC_NA_1 - 100).
///
- public delegate bool InterrogationHandler(object parameter,IMasterConnection connection,ASDU asdu,byte qoi);
+ public delegate bool InterrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi);
///
/// Handler for counter interrogation command (C_CI_NA_1 - 101).
///
- public delegate bool CounterInterrogationHandler(object parameter,IMasterConnection connection,ASDU asdu,byte qoi);
+ public delegate bool CounterInterrogationHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qoi);
///
/// Handler for read command (C_RD_NA_1 - 102)
///
- public delegate bool ReadHandler(object parameter,IMasterConnection connection,ASDU asdu,int ioa);
+ public delegate bool ReadHandler(object parameter, IMasterConnection connection, ASDU asdu, int ioa);
///
/// Handler for clock synchronization command (C_CS_NA_1 - 103)
///
- public delegate bool ClockSynchronizationHandler(object parameter,IMasterConnection connection,ASDU asdu,CP56Time2a newTime);
+ public delegate bool ClockSynchronizationHandler(object parameter, IMasterConnection connection, ASDU asdu, CP56Time2a newTime);
///
/// Handler for reset process command (C_RP_NA_1 - 105)
///
- public delegate bool ResetProcessHandler(object parameter,IMasterConnection connection,ASDU asdu,byte qrp);
+ public delegate bool ResetProcessHandler(object parameter, IMasterConnection connection, ASDU asdu, byte qrp);
///
/// Handler for delay acquisition command (C_CD_NA:1 - 106)
///
- public delegate bool DelayAcquisitionHandler(object parameter,IMasterConnection connection,ASDU asdu,CP16Time2a delayTime);
+ public delegate bool DelayAcquisitionHandler(object parameter, IMasterConnection connection, ASDU asdu, CP16Time2a delayTime);
///
/// Handler for ASDUs that are not handled by other handlers (default handler)
///
- public delegate bool ASDUHandler(object parameter,IMasterConnection connection,ASDU asdu);
+ public delegate bool ASDUHandler(object parameter, IMasterConnection connection, ASDU asdu);
- public class Slave
+ public abstract class Slave
{
-
protected bool debugOutput;
public bool DebugOutput
{
get
{
- return this.debugOutput;
+ return debugOutput;
}
set
{
@@ -129,7 +125,7 @@ public bool DebugOutput
public DelayAcquisitionHandler delayAcquisitionHandler = null;
public object delayAcquisitionHandlerParameter = null;
- public ASDUHandler asduHandler = null;
+ public ASDUHandler asduHandler = null;
public object asduHandlerParameter = null;
internal FileReadyHandler fileReadyHandler = null;
@@ -142,8 +138,8 @@ public bool DebugOutput
/// user provided parameter that is passed to the callback
public void SetInterrogationHandler(InterrogationHandler handler, object parameter)
{
- this.interrogationHandler = handler;
- this.InterrogationHandlerParameter = parameter;
+ interrogationHandler = handler;
+ InterrogationHandlerParameter = parameter;
}
///
@@ -153,8 +149,8 @@ public void SetInterrogationHandler(InterrogationHandler handler, object paramet
/// user provided parameter that is passed to the callback
public void SetCounterInterrogationHandler(CounterInterrogationHandler handler, object parameter)
{
- this.counterInterrogationHandler = handler;
- this.counterInterrogationHandlerParameter = parameter;
+ counterInterrogationHandler = handler;
+ counterInterrogationHandlerParameter = parameter;
}
///
@@ -164,8 +160,8 @@ public void SetCounterInterrogationHandler(CounterInterrogationHandler handler,
/// user provided parameter that is passed to the callback
public void SetReadHandler(ReadHandler handler, object parameter)
{
- this.readHandler = handler;
- this.readHandlerParameter = parameter;
+ readHandler = handler;
+ readHandlerParameter = parameter;
}
///
@@ -175,20 +171,20 @@ public void SetReadHandler(ReadHandler handler, object parameter)
/// user provided parameter that is passed to the callback
public void SetClockSynchronizationHandler(ClockSynchronizationHandler handler, object parameter)
{
- this.clockSynchronizationHandler = handler;
- this.clockSynchronizationHandlerParameter = parameter;
+ clockSynchronizationHandler = handler;
+ clockSynchronizationHandlerParameter = parameter;
}
public void SetResetProcessHandler(ResetProcessHandler handler, object parameter)
{
- this.resetProcessHandler = handler;
- this.resetProcessHandlerParameter = parameter;
+ resetProcessHandler = handler;
+ resetProcessHandlerParameter = parameter;
}
public void SetDelayAcquisitionHandler(DelayAcquisitionHandler handler, object parameter)
{
- this.delayAcquisitionHandler = handler;
- this.delayAcquisitionHandlerParameter = parameter;
+ delayAcquisitionHandler = handler;
+ delayAcquisitionHandlerParameter = parameter;
}
///
@@ -199,8 +195,8 @@ public void SetDelayAcquisitionHandler(DelayAcquisitionHandler handler, object p
/// user provided parameter that is passed to the callback
public void SetASDUHandler(ASDUHandler handler, object parameter)
{
- this.asduHandler = handler;
- this.asduHandlerParameter = parameter;
+ asduHandler = handler;
+ asduHandlerParameter = parameter;
}
///
@@ -231,6 +227,5 @@ public FilesAvailable GetAvailableFiles()
/// The file service timeout in ms
public virtual int FileTimeout { get; set; }
}
-
}
diff --git a/lib60870/CS101/StartEvent.cs b/lib60870/CS101/StartEvent.cs
index 7b65746..c3b9471 100644
--- a/lib60870/CS101/StartEvent.cs
+++ b/lib60870/CS101/StartEvent.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-using System.Collections.Generic;
using System.Text;
namespace lib60870.CS101
@@ -35,7 +33,7 @@ public class StartEvent
public StartEvent()
{
- this.encodedValue = 0;
+ encodedValue = 0;
}
public StartEvent(byte encodedValue)
@@ -45,7 +43,7 @@ public StartEvent(byte encodedValue)
public StartEvent(StartEvent orignal)
{
- this.encodedValue = orignal.encodedValue;
+ encodedValue = orignal.encodedValue;
}
public override bool Equals(object obj)
@@ -56,12 +54,12 @@ public override bool Equals(object obj)
if (!(obj is StartEvent))
return false;
- return (this.EncodedValue == ((StartEvent)obj).EncodedValue);
+ return (EncodedValue == ((StartEvent)obj).EncodedValue);
}
public override int GetHashCode()
{
- return this.EncodedValue.GetHashCode();
+ return EncodedValue.GetHashCode();
}
///
@@ -244,7 +242,7 @@ public byte EncodedValue
{
get
{
- return this.encodedValue;
+ return encodedValue;
}
set
{
@@ -276,5 +274,5 @@ public override string ToString()
return sb.ToString();
}
}
-
+
}
diff --git a/lib60870/CS101/StatusAndStatusChangeDetection.cs b/lib60870/CS101/StatusAndStatusChangeDetection.cs
index 3853da7..291bd59 100644
--- a/lib60870/CS101/StatusAndStatusChangeDetection.cs
+++ b/lib60870/CS101/StatusAndStatusChangeDetection.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -58,7 +58,7 @@ public UInt16 CDn
public bool ST(int i)
{
if ((i >= 0) && (i < 16))
- return ((int)(STn & (1 << i)) != 0);
+ return ((STn & (1 << i)) != 0);
else
return false;
}
@@ -77,7 +77,7 @@ public void ST(int i, bool value)
public bool CD(int i)
{
if ((i >= 0) && (i < 16))
- return ((int)(CDn & (1 << i)) != 0);
+ return ((CDn & (1 << i)) != 0);
else
return false;
}
@@ -99,8 +99,8 @@ public StatusAndStatusChangeDetection()
public StatusAndStatusChangeDetection(StatusAndStatusChangeDetection original)
{
- this.STn = original.STn;
- this.CDn = original.CDn;
+ STn = original.STn;
+ CDn = original.CDn;
}
public StatusAndStatusChangeDetection(byte[] msg, int startIndex)
diff --git a/lib60870/CS101/StepPositionInformation.cs b/lib60870/CS101/StepPositionInformation.cs
index 7670cd1..8b054de 100644
--- a/lib60870/CS101/StepPositionInformation.cs
+++ b/lib60870/CS101/StepPositionInformation.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -59,7 +59,7 @@ public int Value
{
get
{
- return this.value;
+ return value;
}
set
{
@@ -82,11 +82,11 @@ public bool Transient
{
get
{
- return this.isTransient;
+ return isTransient;
}
set
{
- this.isTransient = value;
+ isTransient = value;
}
}
@@ -96,7 +96,7 @@ public QualityDescriptor Quality
{
get
{
- return this.quality;
+ return quality;
}
}
@@ -112,11 +112,11 @@ public StepPositionInformation(int ioa, int value, bool isTransient, QualityDesc
}
public StepPositionInformation(StepPositionInformation original)
- : base (original.ObjectAddress)
+ : base(original.ObjectAddress)
{
- this.Value = original.Value;
- this.Transient = original.Transient;
- this.quality = new QualityDescriptor(original.quality);
+ Value = original.Value;
+ Transient = original.Transient;
+ quality = new QualityDescriptor(original.quality);
}
internal StepPositionInformation(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -156,7 +156,7 @@ public override void Encode(Frame frame, ApplicationLayerParameters parameters,
vti += 0x80;
frame.SetNextByte(vti);
-
+
frame.SetNextByte(quality.EncodedValue);
}
@@ -194,11 +194,11 @@ public CP24Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
set
{
- this.timestamp = value;
+ timestamp = value;
}
}
@@ -211,7 +211,7 @@ public StepPositionWithCP24Time2a(int ioa, int value, bool isTransient, QualityD
public StepPositionWithCP24Time2a(StepPositionWithCP24Time2a original)
: base(original)
{
- this.timestamp = new CP24Time2a(original.timestamp);
+ timestamp = new CP24Time2a(original.timestamp);
}
internal StepPositionWithCP24Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -270,11 +270,11 @@ public CP56Time2a Timestamp
{
get
{
- return this.timestamp;
+ return timestamp;
}
set
{
- this.timestamp = value;
+ timestamp = value;
}
}
@@ -287,7 +287,7 @@ public StepPositionWithCP56Time2a(int ioa, int value, bool isTransient, QualityD
public StepPositionWithCP56Time2a(StepPositionWithCP56Time2a original)
: base(original)
{
- this.timestamp = new CP56Time2a(original.timestamp);
+ timestamp = new CP56Time2a(original.timestamp);
}
internal StepPositionWithCP56Time2a(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
@@ -312,6 +312,6 @@ public override void Encode(Frame frame, ApplicationLayerParameters parameters,
frame.AppendBytes(timestamp.GetEncodedValue());
}
}
-
+
}
diff --git a/lib60870/CS101/SystemInformationCommands.cs b/lib60870/CS101/SystemInformationCommands.cs
index c695d80..714bfb0 100644
--- a/lib60870/CS101/SystemInformationCommands.cs
+++ b/lib60870/CS101/SystemInformationCommands.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS101
{
@@ -74,7 +72,7 @@ public byte QOI
{
get
{
- return this.qoi;
+ return qoi;
}
set
{
@@ -141,7 +139,7 @@ public byte QCC
{
get
{
- return this.qcc;
+ return qcc;
}
set
{
@@ -152,7 +150,7 @@ public byte QCC
public CounterInterrogationCommand(int ioa, byte qoi)
: base(ioa)
{
- this.qcc = qoi;
+ qcc = qoi;
}
internal CounterInterrogationCommand(ApplicationLayerParameters parameters, byte[] msg, int startIndex)
@@ -399,7 +397,7 @@ public CP56Time2a NewTime
{
get
{
- return this.newTime;
+ return newTime;
}
set
{
@@ -466,7 +464,7 @@ public byte QRP
{
get
{
- return this.qrp;
+ return qrp;
}
set
{
@@ -529,7 +527,7 @@ public CP16Time2a Delay
{
get
{
- return this.delay;
+ return delay;
}
set
{
diff --git a/lib60870/CS101/TypeID.cs b/lib60870/CS101/TypeID.cs
index 7d768d9..553b8c3 100644
--- a/lib60870/CS101/TypeID.cs
+++ b/lib60870/CS101/TypeID.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,13 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-
namespace lib60870.CS101
{
@@ -103,5 +96,5 @@ public enum TypeID
F_DR_TA_1 = 126,
F_SC_NB_1 = 127
}
-
+
}
diff --git a/lib60870/CS104/APCIParameters.cs b/lib60870/CS104/APCIParameters.cs
index b8069e0..de83c17 100644
--- a/lib60870/CS104/APCIParameters.cs
+++ b/lib60870/CS104/APCIParameters.cs
@@ -1,7 +1,7 @@
/*
* ApplicationLayerParameters.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.CS104
{
///
@@ -69,7 +67,7 @@ public int K
{
get
{
- return this.k;
+ return k;
}
set
{
@@ -86,7 +84,7 @@ public int W
{
get
{
- return this.w;
+ return w;
}
set
{
@@ -102,7 +100,7 @@ public int T0
{
get
{
- return this.t0;
+ return t0;
}
set
{
@@ -120,7 +118,7 @@ public int T1
{
get
{
- return this.t1;
+ return t1;
}
set
{
@@ -136,7 +134,7 @@ public int T2
{
get
{
- return this.t2;
+ return t2;
}
set
{
@@ -152,7 +150,7 @@ public int T3
{
get
{
- return this.t3;
+ return t3;
}
set
{
diff --git a/lib60870/CS104/ClientConnection.cs b/lib60870/CS104/ClientConnection.cs
index 12941fe..b8f2b9e 100644
--- a/lib60870/CS104/ClientConnection.cs
+++ b/lib60870/CS104/ClientConnection.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,22 +19,25 @@
* See COPYING file for the complete license text.
*/
+using lib60870.CS101;
using System;
-
+using System.Collections.Concurrent;
+using System.IO;
using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-using System.Collections.Generic;
using System.Net.Security;
+using System.Net.Sockets;
+using System.Reflection;
using System.Security.Cryptography.X509Certificates;
-using System.IO;
-
-using lib60870;
-using lib60870.CS101;
-using System.Collections.Concurrent;
+using System.Threading;
namespace lib60870.CS104
{
+ public enum MasterConnectionState
+ {
+ M_CON_STATE_STOPPED = 0, /* only U frames allowed */
+ M_CON_STATE_STARTED = 1, /* U, I, S frames allowed */
+ M_CON_STATE_UNCONFIRMED_STOPPED = 2 /* only U, S frames allowed */
+ }
///
/// Represents a client (master) connection
///
@@ -63,6 +66,8 @@ private void DebugLog(string msg)
static byte[] TESTFR_ACT_MSG = new byte[] { 0x68, 0x04, 0x43, 0x00, 0x00, 0x00 };
+
+ private MasterConnectionState state;
private int sendCount = 0;
private int receiveCount = 0;
@@ -81,7 +86,7 @@ private void DebugLog(string msg)
/* timestamp when the last confirmation message was sent */
private UInt64 lastConfirmationTime = System.UInt64.MaxValue;
-
+
private TlsSecurityInformation tlsSecInfo = null;
private APCIParameters apciParameters;
@@ -89,16 +94,17 @@ private void DebugLog(string msg)
private Server server;
+ private bool allowTestCommand = false;
+ private bool allowDelayAcquisition = false;
+
private ConcurrentQueue receivedASDUs = null;
private Thread callbackThread = null;
private bool callbackThreadRunning = false;
- private Queue waitingASDUsHighPrio = null;
-
/* data structure for k-size sent ASDU buffer */
private struct SentASDU
{
- // required to identify message in server (low-priority) queue
+ /* required to identify message in server (low-priority) queue */
public long entryTime;
/* -1 if ASDU is not from low-priority queue */
@@ -116,15 +122,26 @@ private struct SentASDU
private int newestSentASDU = -1;
private SentASDU[] sentASDUs = null;
- private ASDUQueue asduQueue = null;
+ private ASDUQueue lowPrioQueue = null;
+
+ private ASDUQueue highPrioQueue = null;
private FileServer fileServer;
- internal ASDUQueue GetASDUQueue()
+ ///
+ /// Retrieves the ASDU queue used for processing ASDUs in the system. This queue holds the low-priority ASDUs for further handling.
+ ///
+ ///
+ /// The ASDU queue containing low-priority ASDUs.
+ ///
+ public ASDUQueue GetASDUQueue()
{
- return asduQueue;
+ return lowPrioQueue;
}
+ ///
+ /// Continuously processes the received ASDUs in a separate callback thread. The method dequeues ASDUs from the queue and handles them using the `HandleASDU` method.
+ ///
private void ProcessASDUs()
{
callbackThreadRunning = true;
@@ -136,14 +153,14 @@ private void ProcessASDUs()
{
while ((receivedASDUs.Count > 0) && (callbackThreadRunning) && (running))
{
-
+
ASDU asdu;
if (receivedASDUs.TryDequeue(out asdu))
{
HandleASDU(asdu);
}
-
+
}
Thread.Sleep(50);
@@ -173,37 +190,48 @@ public IPEndPoint RemoteEndpoint
}
}
+ ///
+ /// Initializes a new instance of the class, establishing a connection using the provided socket and configuration details.
+ ///
+ /// The socket used for the connection, representing the network connection with the client.
+ /// The TLS security information used to manage the security of the connection, including certificates and chain validation.
+ /// The parameters related to the Application Protocol Control Information (APCI), such as the sequence numbers and connection settings.
+ /// The application layer parameters for the communication, including configuration for encoding/decoding data.
+ /// The server object that manages the overall communication and handles requests related to files and ASDUs.
+ /// The ASDU queue that holds the low-priority ASDUs for processing in the connection.
+ /// A flag indicating whether debug output should be enabled for logging the connection's activities.
internal ClientConnection(Socket socket, TlsSecurityInformation tlsSecInfo, APCIParameters apciParameters, ApplicationLayerParameters parameters, Server server, ASDUQueue asduQueue, bool debugOutput)
{
+ state = MasterConnectionState.M_CON_STATE_STOPPED;
connectionsCounter++;
connectionID = connectionsCounter;
- this.remoteEndpoint = (IPEndPoint)socket.RemoteEndPoint;
+ remoteEndpoint = (IPEndPoint)socket.RemoteEndPoint;
this.apciParameters = apciParameters;
- this.alParameters = parameters;
+ alParameters = parameters;
this.server = server;
- this.asduQueue = asduQueue;
+ lowPrioQueue = asduQueue;
this.debugOutput = debugOutput;
ResetT3Timeout((UInt64)SystemUtils.currentTimeMillis());
maxSentASDUs = apciParameters.K;
- this.sentASDUs = new SentASDU[maxSentASDUs];
+ sentASDUs = new SentASDU[maxSentASDUs];
receivedASDUs = new ConcurrentQueue();
- waitingASDUsHighPrio = new Queue();
+ highPrioQueue = new ASDUQueue(server.MaxHighPrioQueueSize, server.EnqueueMode, alParameters, DebugLog);
socketStream = new NetworkStream(socket);
this.socket = socket;
this.tlsSecInfo = tlsSecInfo;
- this.fileServer = new FileServer(this, server.GetAvailableFiles(), DebugLog);
+ fileServer = new FileServer(this, server.GetAvailableFiles(), DebugLog);
if (server.fileTimeout != null)
- this.fileServer.Timeout = (long) server.fileTimeout;
+ fileServer.Timeout = (long)server.fileTimeout;
- this.fileServer.SetFileReadyHandler (server.fileReadyHandler, server.fileReadyHandlerParameter);
+ fileServer.SetFileReadyHandler(server.fileReadyHandler, server.fileReadyHandlerParameter);
Thread workerThread = new Thread(HandleConnection);
@@ -219,11 +247,22 @@ public ApplicationLayerParameters GetApplicationLayerParameters()
return alParameters;
}
+ ///
+ /// Resets the T3 timeout value based on the current system time.
+ ///
+ /// The current time in milliseconds, used to set the next T3 timeout.
private void ResetT3Timeout(UInt64 currentTime)
{
nextT3Timeout = (UInt64)SystemUtils.currentTimeMillis() + (UInt64)(apciParameters.T3 * 1000);
}
+ ///
+ /// Checks whether the T3 timeout has occurred based on the current system time.
+ ///
+ /// The current time in milliseconds, used to compare with the next T3 timeout.
+ ///
+ /// true if the T3 timeout has occurred; otherwise, false .
+ ///
private bool CheckT3Timeout(UInt64 currentTime)
{
if (waitingForTestFRcon)
@@ -241,11 +280,22 @@ private bool CheckT3Timeout(UInt64 currentTime)
return false;
}
+ ///
+ /// Resets the Test Frame Reception Confirmation (TestFRCon) timeout value based on the current system time.
+ ///
+ /// The current time in milliseconds, used to set the next TestFRCon timeout.
private void ResetTestFRConTimeout(UInt64 currentTime)
{
nextTestFRConTimeout = currentTime + (UInt64)(apciParameters.T1 * 1000);
}
+ ///
+ /// Checks whether the Test Frame Reception Confirmation (TestFRCon) timeout has occurred based on the current system time.
+ ///
+ /// The current time in milliseconds, used to compare with the next TestFRCon timeout.
+ ///
+ /// true if the TestFRCon timeout has occurred; otherwise, false .
+ ///
private bool CheckTestFRConTimeout(UInt64 currentTime)
{
if (nextTestFRConTimeout > (currentTime + (UInt64)(apciParameters.T1 * 1000)))
@@ -277,7 +327,7 @@ public bool IsActive
{
get
{
- return this.isActive;
+ return isActive;
}
set
{
@@ -288,13 +338,18 @@ public bool IsActive
isActive = value;
if (isActive)
+ {
DebugLog("is active");
+ state = MasterConnectionState.M_CON_STATE_STARTED;
+ }
else
DebugLog("is not active");
}
}
}
+ public MasterConnectionState State { get => state; set => state = value; }
+
private Socket socket;
private Stream socketStream;
@@ -308,6 +363,9 @@ public bool IsActive
private int remainingReadLength = 0;
private long currentReadTimeout = 0;
+ ///
+ /// Receives a message from the socket stream, handling timeout and message parsing.
+ ///
private int receiveMessage(byte[] buffer)
{
/* check receive timeout */
@@ -325,7 +383,7 @@ private int receiveMessage(byte[] buffer)
if (readState == 0)
{
- // wait for start byte
+ /* wait for start byte */
if (socketStream.Read(buffer, 0, 1) != 1)
return -1;
@@ -341,7 +399,7 @@ private int receiveMessage(byte[] buffer)
if (readState == 1)
{
- // read length byte
+ /* read length byte */
if (socketStream.Read(buffer, 1, 1) != 1)
return 0;
@@ -378,6 +436,9 @@ private int receiveMessage(byte[] buffer)
return 0;
}
+ ///
+ /// Sends an S message via the socket stream.
+ ///
private void SendSMessage()
{
DebugLog("Send S message");
@@ -400,12 +461,18 @@ private void SendSMessage()
}
catch (System.IO.IOException)
{
- // socket error --> close connection
running = false;
}
}
}
+ ///
+ /// Sends an I message containing the ASDU via the socket stream.
+ ///
+ /// The ASDU object containing the message data to be sent.
+ ///
+ /// The updated send count, which is incremented after sending the message.
+ ///
private int SendIMessage(BufferFrame asdu)
{
@@ -429,7 +496,7 @@ private int SendIMessage(BufferFrame asdu)
lock (socketStream)
{
socketStream.Write(buffer, 0, msgSize);
- DebugLog("SEND I (size = " + msgSize + ") : " + BitConverter.ToString(buffer, 0, msgSize));
+ DebugLog("SEND I (size = " + msgSize + ") : " + BitConverter.ToString(buffer, 0, msgSize));
sendCount = (sendCount + 1) % 32768;
unconfirmedReceivedIMessages = 0;
timeoutT2Triggered = false;
@@ -437,13 +504,42 @@ private int SendIMessage(BufferFrame asdu)
}
catch (System.IO.IOException)
{
- // socket error --> close connection
running = false;
}
return sendCount;
}
+ ///
+ /// Checks if there are any unconfirmed messages in the low-priority or high-priority queues.
+ ///
+ ///
+ /// true if there are unconfirmed messages in either the low-priority or high-priority queues;
+ /// false if no unconfirmed messages are found.
+ ///
+ public bool MasterConnection_hasUnconfirmedMessages()
+ {
+ bool retVal = false;
+
+ if (lowPrioQueue != null)
+ {
+ if (lowPrioQueue.MessageQueue_hasUnconfirmedIMessages())
+ return true;
+
+ if (highPrioQueue.MessageQueue_hasUnconfirmedIMessages())
+ return true;
+ }
+
+ return retVal;
+ }
+
+ ///
+ /// Checks whether the sent ASDU buffer is full.
+ ///
+ ///
+ /// true if the sent ASDU buffer is full;
+ /// false if the buffer is not full.
+ ///
private bool isSentBufferFull()
{
@@ -458,6 +554,9 @@ private bool isSentBufferFull()
return false;
}
+ ///
+ /// Prints the contents of the sent ASDU buffer for debugging purposes.
+ ///
private void PrintSendBuffer()
{
if (debugOutput)
@@ -484,11 +583,14 @@ private void PrintSendBuffer()
} while (nextIndex != -1);
DebugLog("--------------------");
-
+
}
}
}
+ ///
+ /// Sends the next available ASDU from the low-priority queue, if the sent ASDU buffer is not full.
+ ///
private void sendNextAvailableASDU()
{
lock (sentASDUs)
@@ -499,8 +601,8 @@ private void sendNextAvailableASDU()
long timestamp;
int index;
- asduQueue.LockASDUQueue();
- BufferFrame asdu = asduQueue.GetNextWaitingASDU(out timestamp, out index);
+ lowPrioQueue.LockASDUQueue();
+ BufferFrame asdu = lowPrioQueue.GetNextWaitingASDU(out timestamp, out index);
try
{
@@ -512,13 +614,12 @@ private void sendNextAvailableASDU()
{
oldestSentASDU = 0;
newestSentASDU = 0;
-
}
else
{
currentIndex = (newestSentASDU + 1) % maxSentASDUs;
}
-
+
sentASDUs[currentIndex].entryTime = timestamp;
sentASDUs[currentIndex].queueIndex = index;
sentASDUs[currentIndex].seqNo = SendIMessage(asdu);
@@ -531,11 +632,18 @@ private void sendNextAvailableASDU()
}
finally
{
- asduQueue.UnlockASDUQueue();
+ lowPrioQueue.UnlockASDUQueue();
}
}
}
+ ///
+ /// Sends the next available high-priority ASDU from the high-priority queue, if the sent ASDU buffer is not full.
+ ///
+ ///
+ /// true if a high-priority ASDU was successfully sent;
+ /// false if the sent ASDU buffer is full and no message was sent.
+ ///
private bool sendNextHighPriorityASDU()
{
lock (sentASDUs)
@@ -543,76 +651,104 @@ private bool sendNextHighPriorityASDU()
if (isSentBufferFull())
return false;
- BufferFrame asdu = waitingASDUsHighPrio.Dequeue();
+ long timestamp;
+ int index;
- if (asdu != null)
- {
+ highPrioQueue.LockASDUQueue();
- int currentIndex = 0;
+ BufferFrame asdu = highPrioQueue.GetNextHighPriorityWaitingASDU(out timestamp, out index);
- if (oldestSentASDU == -1)
+ try
+ {
+ if (asdu != null)
{
- oldestSentASDU = 0;
- newestSentASDU = 0;
+ int currentIndex = 0;
- }
- else
- {
- currentIndex = (newestSentASDU + 1) % maxSentASDUs;
- }
-
- sentASDUs[currentIndex].queueIndex = -1;
- sentASDUs[currentIndex].seqNo = SendIMessage(asdu);
- sentASDUs[currentIndex].sentTime = SystemUtils.currentTimeMillis();
+ if (oldestSentASDU == -1)
+ {
+ oldestSentASDU = 0;
+ newestSentASDU = 0;
+
+ }
+ else
+ {
+ currentIndex = (newestSentASDU + 1) % maxSentASDUs;
+ }
- newestSentASDU = currentIndex;
+ sentASDUs[currentIndex].entryTime = timestamp;
+ sentASDUs[currentIndex].queueIndex = -1; // highPrioQueue does not need confirmation
+ sentASDUs[currentIndex].seqNo = SendIMessage(asdu);
+ sentASDUs[currentIndex].sentTime = SystemUtils.currentTimeMillis();
- PrintSendBuffer();
+ newestSentASDU = currentIndex;
+
+ PrintSendBuffer();
+ }
}
- else
- return false;
+ finally
+ {
+ highPrioQueue.UnlockASDUQueue();
+ }
+
}
return true;
}
- private void SendWaitingASDUs()
+ ///
+ /// Send all high-priority ASDUs and the last waiting ASDU from the low-priority queue.
+ /// Returns true if ASDUs are still waiting.This can happen when there are more ASDUs
+ /// in the event (low-priority) buffer, or the connection is unavailable to send the high-priority
+ /// ASDUs (congestion or connection lost).
+ ///
+ private bool SendWaitingASDUs()
{
- lock (waitingASDUsHighPrio)
+ lock (highPrioQueue)
{
-
- while (waitingASDUsHighPrio.Count > 0)
+ /* send all available high priority ASDUs first */
+ while (highPrioQueue.IsHighPriorityAsduAvailable())
{
if (sendNextHighPriorityASDU() == false)
- return;
+ return true;
if (running == false)
- return;
+ return true;
}
}
- // send messages from low-priority queue
+ /* send messages from low-priority queue */
sendNextAvailableASDU();
+
+ if (lowPrioQueue.NumberOfAsduInQueue > 0)
+ return true;
+ else
+ return false;
}
+ ///
+ /// Encodes and enqueues an ASDU for transmission, then attempts to send any waiting ASDUs.
+ ///
+ ///
+ /// The ASDU to be encoded and enqueued for sending.
+ ///
private void SendASDUInternal(ASDU asdu)
{
if (isActive)
{
- lock (waitingASDUsHighPrio)
+ lock (highPrioQueue)
{
BufferFrame frame = new BufferFrame(new byte[256], 6);
asdu.Encode(frame, alParameters);
- waitingASDUsHighPrio.Enqueue(frame);
+ highPrioQueue.EnqueueAsdu(asdu);
}
SendWaitingASDUs();
- }
+ }
}
///
@@ -628,6 +764,15 @@ public void SendASDU(ASDU asdu)
throw new ConnectionException("Connection not active");
}
+ ///
+ /// Sends an Activation Confirmation (ACT_CON) ASDU with the specified negative flag.
+ ///
+ ///
+ /// The ASDU to be sent as an Activation Confirmation (ACT_CON).
+ ///
+ ///
+ /// A boolean flag indicating whether the Activation Confirmation is negative.
+ ///
public void SendACT_CON(ASDU asdu, bool negative)
{
asdu.Cot = CauseOfTransmission.ACTIVATION_CON;
@@ -636,6 +781,12 @@ public void SendACT_CON(ASDU asdu, bool negative)
SendASDU(asdu);
}
+ ///
+ /// Sends an Activation Termination (ACT_TERM) ASDU with a negative flag set to false.
+ ///
+ ///
+ /// The ASDU to be sent as an Activation Termination (ACT_TERM).
+ ///
public void SendACT_TERM(ASDU asdu)
{
asdu.Cot = CauseOfTransmission.ACTIVATION_TERMINATION;
@@ -644,8 +795,15 @@ public void SendACT_TERM(ASDU asdu)
SendASDU(asdu);
}
+ //
+ /// Handles the received ASDU (Application Service Data Unit) based on its TypeID and Cause of Transmission (COT).
+ /// It processes various commands such as interrogation, counter interrogation, read, clock synchronization, test, and reset commands.
+ ///
+ ///
+ /// The ASDU received that needs to be processed.
+ ///
private void HandleASDU(ASDU asdu)
- {
+ {
DebugLog("Handle received ASDU");
bool messageHandled = false;
@@ -655,7 +813,7 @@ private void HandleASDU(ASDU asdu)
case TypeID.C_IC_NA_1: /* 100 - interrogation command */
- DebugLog("Rcvd interrogation command C_IC_NA_1");
+ DebugLog("Rcvd interrogation command C_IC_NA_1\n");
if ((asdu.Cot == CauseOfTransmission.ACTIVATION) || (asdu.Cot == CauseOfTransmission.DEACTIVATION))
{
@@ -664,22 +822,36 @@ private void HandleASDU(ASDU asdu)
InterrogationCommand irc = (InterrogationCommand)asdu.GetElement(0);
- if (server.interrogationHandler(server.InterrogationHandlerParameter, this, asdu, irc.QOI))
- messageHandled = true;
+ if (irc != null)
+ {
+ /* Verify IOA = 0 */
+ if (irc.ObjectAddress != 0)
+ {
+ DebugLog("CS104 SLAVE: interrogation command has invalid IOA - should be 0\n");
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ messageHandled = true;
+ }
+ else
+ {
+ if (server.interrogationHandler(server.InterrogationHandlerParameter, this, asdu, irc.QOI))
+ messageHandled = true;
+ }
+ }
}
}
else
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDUInternal(asdu);
+ SendASDUInternal(asdu);
+ messageHandled = true;
}
break;
case TypeID.C_CI_NA_1: /* 101 - counter interrogation command */
- DebugLog("Rcvd counter interrogation command C_CI_NA_1");
+ DebugLog("Rcvd counter interrogation command C_CI_NA_1\n");
if ((asdu.Cot == CauseOfTransmission.ACTIVATION) || (asdu.Cot == CauseOfTransmission.DEACTIVATION))
{
@@ -688,22 +860,36 @@ private void HandleASDU(ASDU asdu)
CounterInterrogationCommand cic = (CounterInterrogationCommand)asdu.GetElement(0);
- if (server.counterInterrogationHandler(server.counterInterrogationHandlerParameter, this, asdu, cic.QCC))
- messageHandled = true;
+ if (cic != null)
+ {
+ /* Verify IOA = 0 */
+ if (cic.ObjectAddress != 0)
+ {
+ DebugLog("CS104 SLAVE: counter interrogation command has invalid IOA - should be 0\n");
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ messageHandled = true;
+ }
+ else
+ {
+ if (server.interrogationHandler(server.InterrogationHandlerParameter, this, asdu, cic.QCC))
+ messageHandled = true;
+ }
+ }
}
}
else
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDUInternal(asdu);
+ SendASDUInternal(asdu);
+ messageHandled = true;
}
break;
case TypeID.C_RD_NA_1: /* 102 - read command */
- DebugLog("Rcvd read command C_RD_NA_1");
+ DebugLog("Rcvd read command C_RD_NA_1\n");
if (asdu.Cot == CauseOfTransmission.REQUEST)
{
@@ -714,24 +900,26 @@ private void HandleASDU(ASDU asdu)
{
ReadCommand rc = (ReadCommand)asdu.GetElement(0);
- if (server.readHandler(server.readHandlerParameter, this, asdu, rc.ObjectAddress))
- messageHandled = true;
-
+ if (rc != null)
+ {
+ if (server.readHandler(server.readHandlerParameter, this, asdu, rc.ObjectAddress))
+ messageHandled = true;
+ }
}
-
}
else
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDUInternal(asdu);
+ SendASDUInternal(asdu);
+ messageHandled = true;
}
break;
case TypeID.C_CS_NA_1: /* 103 - Clock synchronization command */
- DebugLog("Rcvd clock sync command C_CS_NA_1");
+ DebugLog("Rcvd clock sync command C_CS_NA_1\n");
if (asdu.Cot == CauseOfTransmission.ACTIVATION)
{
@@ -741,9 +929,35 @@ private void HandleASDU(ASDU asdu)
ClockSynchronizationCommand csc = (ClockSynchronizationCommand)asdu.GetElement(0);
- if (server.clockSynchronizationHandler(server.clockSynchronizationHandlerParameter,
- this, asdu, csc.NewTime))
+ if (csc != null)
+ {
+ /* Verify IOA = 0 */
+ if (csc.ObjectAddress != 0)
+ {
+ DebugLog("CS104 SLAVE: Clock synchronization command has invalid IOA - should be 0\n");
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ }
+ else
+ {
+ if (server.clockSynchronizationHandler(server.clockSynchronizationHandlerParameter,
+ this, asdu, csc.NewTime))
+ {
+ csc.ObjectAddress = 0;
+ asdu.AddInformationObject(csc);
+ asdu.Cot = CauseOfTransmission.ACTIVATION_CON;
+ SendASDUInternal(asdu);
+ }
+ else
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ asdu.IsNegative = true;
+ SendASDUInternal(asdu);
+ }
+
+ }
+
messageHandled = true;
+ }
}
}
@@ -751,32 +965,56 @@ private void HandleASDU(ASDU asdu)
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDUInternal(asdu);
+ SendASDUInternal(asdu);
+ messageHandled = true;
}
break;
case TypeID.C_TS_NA_1: /* 104 - test command */
- DebugLog("Rcvd test command C_TS_NA_1");
+ DebugLog("Rcvd test command C_TS_NA_1\n");
- if (asdu.Cot != CauseOfTransmission.ACTIVATION)
+ if (allowTestCommand)
{
- asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
- asdu.IsNegative = true;
- }
- else
- asdu.Cot = CauseOfTransmission.ACTIVATION_CON;
+ if (asdu.Cot == CauseOfTransmission.ACTIVATION)
+ {
+ TestCommand tc = (TestCommand)asdu.GetElement(0);
- this.SendASDUInternal(asdu);
+ /* Verify IOA = 0 */
+ if (tc.ObjectAddress != 0)
+ {
+ DebugLog("CS104 SLAVE: test command has invalid IOA - should be 0\n");
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ }
+ else
+ {
+ asdu.Cot = CauseOfTransmission.ACTIVATION_CON;
+ SendASDUInternal(asdu);
+ }
- messageHandled = true;
+ messageHandled = true;
+ }
+ else
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ asdu.IsNegative = true;
+ messageHandled = true;
+ SendASDUInternal(asdu);
+ }
+ }
+ else
+ {
+ /* this command is not supported/allowed for IEC 104 */
+ DebugLog("CS104 SLAVE: Rcvd test command C_TS_NA_1 -> not allowed\n");
+ messageHandled = false;
+ }
break;
case TypeID.C_RP_NA_1: /* 105 - Reset process command */
- DebugLog("Rcvd reset process command C_RP_NA_1");
+ DebugLog("Rcvd reset process command C_RP_NA_1\n");
if (asdu.Cot == CauseOfTransmission.ACTIVATION)
{
@@ -786,9 +1024,21 @@ private void HandleASDU(ASDU asdu)
ResetProcessCommand rpc = (ResetProcessCommand)asdu.GetElement(0);
- if (server.resetProcessHandler(server.resetProcessHandlerParameter,
- this, asdu, rpc.QRP))
- messageHandled = true;
+ if (rpc != null)
+ {
+ /* Verify IOA = 0 */
+ if (rpc.ObjectAddress != 0)
+ {
+ DebugLog("CS104 SLAVE: reset process command has invalid IOA - should be 0\n");
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ messageHandled = true;
+ }
+ else
+ {
+ if (server.interrogationHandler(server.InterrogationHandlerParameter, this, asdu, rpc.QRP))
+ messageHandled = true;
+ }
+ }
}
}
@@ -796,35 +1046,90 @@ private void HandleASDU(ASDU asdu)
{
asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
asdu.IsNegative = true;
- this.SendASDUInternal(asdu);
+ SendASDUInternal(asdu);
+ messageHandled = true;
}
-
break;
case TypeID.C_CD_NA_1: /* 106 - Delay acquisition command */
- DebugLog("Rcvd delay acquisition command C_CD_NA_1");
+ DebugLog("Rcvd delay acquisition command C_CD_NA_1\n");
- if ((asdu.Cot == CauseOfTransmission.ACTIVATION) || (asdu.Cot == CauseOfTransmission.SPONTANEOUS))
+ if (allowDelayAcquisition)
{
- if (server.delayAcquisitionHandler != null)
+ if ((asdu.Cot == CauseOfTransmission.ACTIVATION) || (asdu.Cot == CauseOfTransmission.SPONTANEOUS))
{
+ if (server.delayAcquisitionHandler != null)
+ {
- DelayAcquisitionCommand dac = (DelayAcquisitionCommand)asdu.GetElement(0);
+ DelayAcquisitionCommand dac = (DelayAcquisitionCommand)asdu.GetElement(0);
- if (server.delayAcquisitionHandler(server.delayAcquisitionHandlerParameter,
- this, asdu, dac.Delay))
- messageHandled = true;
+ if (dac != null)
+ {
+ /* Verify IOA = 0 */
+ if (dac.ObjectAddress != 0)
+ {
+ DebugLog("CS104 SLAVE: delay acquisition command has invalid IOA - should be 0\n");
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ messageHandled = true;
+ }
+ else
+ {
+ if (server.delayAcquisitionHandler(server.delayAcquisitionHandlerParameter,
+ this, asdu, dac.Delay))
+ messageHandled = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ asdu.IsNegative = true;
+ messageHandled = true;
+ SendASDUInternal(asdu);
}
}
else
{
- asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
- asdu.IsNegative = true;
- this.SendASDUInternal(asdu);
+ /* this command is not supported/allowed for IEC 104 */
+ DebugLog("CS104 SLAVE: Rcvd delay acquisition command C_CD_NA_1 -> not allowed\n");
+ messageHandled = false;
+ }
+
+ break;
+
+ case TypeID.C_TS_TA_1: /* 107 - test command with timestamp */
+
+ DebugLog("Rcvd test command with CP56Time2a C_TS_TA_1\n");
+
+ if (asdu.Cot == CauseOfTransmission.ACTIVATION)
+ {
+ TestCommandWithCP56Time2a tc = (TestCommandWithCP56Time2a)asdu.GetElement(0);
+
+ if (tc.ObjectAddress != 0)
+ {
+ DebugLog("CS104 SLAVE: test command with CP56Time2a has invalid IOA - should be 0\n");
+ asdu.Cot = CauseOfTransmission.UNKNOWN_INFORMATION_OBJECT_ADDRESS;
+ }
+ else
+ {
+ asdu.Cot = CauseOfTransmission.UNKNOWN_CAUSE_OF_TRANSMISSION;
+ asdu.IsNegative = true;
+ }
+
+ messageHandled = true;
}
+ else
+ asdu.Cot = CauseOfTransmission.ACTIVATION_CON;
+
+ messageHandled = true;
+ SendASDUInternal(asdu);
+
+ break;
+ default: /* no special handler available -> use default handler */
break;
}
@@ -839,11 +1144,21 @@ private void HandleASDU(ASDU asdu)
{
asdu.Cot = CauseOfTransmission.UNKNOWN_TYPE_ID;
asdu.IsNegative = true;
- this.SendASDUInternal(asdu);
+ SendASDUInternal(asdu);
}
}
+ ///
+ /// Checks if the given sequence number is valid based on the sequence numbers of previously sent ASDUs.
+ /// This method ensures that the sequence numbers follow the correct order and handles the possibility of a sequence number overflow.
+ ///
+ ///
+ /// The sequence number to be checked for validity.
+ ///
+ ///
+ /// Returns true if the sequence number is valid; otherwise, false if it is out of range or invalid.
+ ///
private bool CheckSequenceNumber(int seqNo)
{
lock (sentASDUs)
@@ -861,7 +1176,7 @@ private bool CheckSequenceNumber(int seqNo)
}
else
{
- // Two cases are required to reflect sequence number overflow
+ /* Two cases are required to reflect sequence number overflow */
if (sentASDUs[oldestSentASDU].seqNo <= sentASDUs[newestSentASDU].seqNo)
{
if ((seqNo >= sentASDUs[oldestSentASDU].seqNo) &&
@@ -889,13 +1204,13 @@ private bool CheckSequenceNumber(int seqNo)
if (oldestValidSeqNo == seqNo)
seqNoIsValid = true;
}
-
+
if (seqNoIsValid == false)
{
DebugLog("Received sequence number out of range");
return false;
}
-
+
if (oldestSentASDU != -1)
{
/* remove confirmed messages from list */
@@ -914,7 +1229,7 @@ private bool CheckSequenceNumber(int seqNo)
/* remove from server (low-priority) queue if required */
if (sentASDUs[oldestSentASDU].queueIndex != -1)
{
- asduQueue.MarkASDUAsConfirmed(sentASDUs[oldestSentASDU].queueIndex,
+ lowPrioQueue.MarkASDUAsConfirmed(sentASDUs[oldestSentASDU].queueIndex,
sentASDUs[oldestSentASDU].entryTime);
}
@@ -947,151 +1262,249 @@ private bool CheckSequenceNumber(int seqNo)
return true;
}
+ ///
+ /// Processes an incoming message based on its type and takes appropriate actions according to the message content.
+ /// This method handles different types of messages, such as I-message, TESTFR_ACT, STARTDT_ACT, STOPDT_ACT,
+ /// and S-message, as well as error handling and state transitions of the connection.
+ ///
+ ///
+ /// The byte array containing the incoming message to be processed.
+ ///
+ ///
+ /// The size of the incoming message (in bytes).
+ ///
+ ///
+ /// Returns true if the message was processed successfully; otherwise, false if the message was invalid
+ /// or if an error occurred while processing it.
+ ///
private bool HandleMessage(byte[] buffer, int msgSize)
{
- UInt64 currentTime = (UInt64) SystemUtils.currentTimeMillis();
+ UInt64 currentTime = (UInt64)SystemUtils.currentTimeMillis();
- if ((buffer[2] & 1) == 0)
+ if (msgSize >= 3)
{
-
- if (msgSize < 7)
+ if (buffer[0] != 0x68)
{
- DebugLog("I msg too small!");
+ DebugLog("Invalid START character!");
return false;
}
-
- if (timeoutT2Triggered == false)
- {
- timeoutT2Triggered = true;
- lastConfirmationTime = currentTime; /* start timeout T2 */
- }
-
- int frameSendSequenceNumber = ((buffer[3] * 0x100) + (buffer[2] & 0xfe)) / 2;
- int frameRecvSequenceNumber = ((buffer[5] * 0x100) + (buffer[4] & 0xfe)) / 2;
- DebugLog("Received I frame: N(S) = " + frameSendSequenceNumber + " N(R) = " + frameRecvSequenceNumber);
+ byte lengthOfApdu = buffer[1];
- /* check the receive sequence number N(R) - connection will be closed on an unexpected value */
- if (frameSendSequenceNumber != receiveCount)
+ if (lengthOfApdu != msgSize - 2)
{
- DebugLog("Sequence error: Close connection!");
+ DebugLog("Invalid length of APDU");
return false;
}
- if (CheckSequenceNumber(frameRecvSequenceNumber) == false)
+ if ((buffer[2] & 1) == 0) /* I message */
{
- DebugLog("Sequence number check failed");
- return false;
- }
+ if (msgSize < 7)
+ {
+ DebugLog("I msg too small!");
+ return false;
+ }
- receiveCount = (receiveCount + 1) % 32768;
- unconfirmedReceivedIMessages++;
+ if (state != MasterConnectionState.M_CON_STATE_STARTED)
+ {
+ DebugLog("Received I message while connection not active -> close connection");
+ return false;
+ }
- if (isActive)
- {
- try
+ if (timeoutT2Triggered == false)
+ {
+ timeoutT2Triggered = true;
+ lastConfirmationTime = currentTime; /* start timeout T2 */
+ }
+
+ int frameSendSequenceNumber = ((buffer[3] * 0x100) + (buffer[2] & 0xfe)) / 2;
+ int frameRecvSequenceNumber = ((buffer[5] * 0x100) + (buffer[4] & 0xfe)) / 2;
+
+ DebugLog("Received I frame: N(S) = " + frameSendSequenceNumber + " N(R) = " + frameRecvSequenceNumber);
+
+ /* check the receive sequence number N(R) - connection will be closed on an unexpected value */
+ if (frameSendSequenceNumber != receiveCount)
+ {
+ DebugLog("Sequence error: Close connection!");
+ return false;
+ }
+
+ if (CheckSequenceNumber(frameRecvSequenceNumber) == false)
+ {
+ DebugLog("Sequence number check failed");
+ return false;
+ }
+
+ receiveCount = (receiveCount + 1) % 32768;
+ unconfirmedReceivedIMessages++;
+
+ if (isActive)
{
- ASDU asdu = new ASDU(alParameters, buffer, 6, msgSize);
-
- // push to handler thread for processing
- DebugLog("Enqueue received I-message for processing");
- receivedASDUs.Enqueue(asdu);
+ try
+ {
+ ASDU asdu = new ASDU(alParameters, buffer, 6, msgSize);
+
+ /* push to handler thread for processing */
+ DebugLog("Enqueue received I-message for processing");
+ receivedASDUs.Enqueue(asdu);
+ }
+ catch (ASDUParsingException e)
+ {
+ DebugLog("ASDU parsing failed: " + e.Message);
+ return false;
+ }
}
- catch (ASDUParsingException e)
+ else
{
- DebugLog("ASDU parsing failed: " + e.Message);
+ /* connection not active */
+ DebugLog("Connection not active -> close connection");
+
return false;
}
}
- else
+
+ /* Check for TESTFR_ACT message */
+ else if ((buffer[2] & 0x43) == 0x43)
{
- // connection not active
- DebugLog("Connection not active -> close connection");
+ DebugLog("Send TESTFR_CON");
- return false;
+ socketStream.Write(TESTFR_CON_MSG, 0, TESTFR_CON_MSG.Length);
}
- }
-
- // Check for TESTFR_ACT message
- else if ((buffer[2] & 0x43) == 0x43)
- {
- DebugLog("Send TESTFR_CON");
+ /* Check for STARTDT_ACT message */
+ else if ((buffer[2] & 0x07) == 0x07)
+ {
+ if (isActive == false)
+ {
+ isActive = true;
- socketStream.Write(TESTFR_CON_MSG, 0, TESTFR_CON_MSG.Length);
- }
+ server.Activated(this);
+ }
- // Check for STARTDT_ACT message
- else if ((buffer[2] & 0x07) == 0x07)
- {
+ DebugLog("Send STARTDT_CON");
- DebugLog("Send STARTDT_CON");
+ socketStream.Write(STARTDT_CON_MSG, 0, TESTFR_CON_MSG.Length);
+ }
- if (this.isActive == false)
+ /* Check for STOPDT_ACT message */
+ else if ((buffer[2] & 0x13) == 0x13)
{
- this.isActive = true;
+ DebugLog("Received STARTDT_ACT");
- this.server.Activated(this);
- }
+ if (isActive == true)
+ {
+ isActive = false;
- socketStream.Write(STARTDT_CON_MSG, 0, TESTFR_CON_MSG.Length);
- }
+ server.Deactivated(this);
+ }
- // Check for STOPDT_ACT message
- else if ((buffer[2] & 0x13) == 0x13)
- {
-
- DebugLog("Send STOPDT_CON");
+ /* Send S-Message to confirm all outstanding messages */
+
+ if (unconfirmedReceivedIMessages > 0)
+ {
+ lastConfirmationTime = currentTime;
+ unconfirmedReceivedIMessages = 0;
+ timeoutT2Triggered = false;
+ SendSMessage();
+ }
+
+ if (MasterConnection_hasUnconfirmedMessages())
+ {
+ DebugLog("CS104 SLAVE: Unconfirmed messages after STOPDT_ACT -> pending unconfirmed stopped state\n");
+ }
+ else
+ {
+ DebugLog("Send STOPDT_CON");
+
+ state = MasterConnectionState.M_CON_STATE_STOPPED;
+
+ try
+ {
+ socketStream.Write(STOPDT_CON_MSG, 0, STOPDT_CON_MSG.Length);
+ }
+ catch (IOException)
+ {
+ DebugLog("Failed to send STOPDT_CON");
+ return false;
+ }
+ }
+ }
- if (this.isActive == true)
+ /* Check for TESTFR_CON message */
+ else if ((buffer[2] & 0x83) == 0x83)
{
- this.isActive = false;
+ DebugLog("Recv TESTFR_CON");
+
+ waitingForTestFRcon = false;
- this.server.Deactivated(this);
+ ResetT3Timeout(currentTime);
}
- socketStream.Write(STOPDT_CON_MSG, 0, TESTFR_CON_MSG.Length);
- }
+ /* S-message */
+ else if (buffer[2] == 0x01)
+ {
+ int seqNo = (buffer[4] + buffer[5] * 0x100) / 2;
- // Check for TESTFR_CON message
- else if ((buffer[2] & 0x83) == 0x83)
- {
- DebugLog("Recv TESTFR_CON");
+ DebugLog("Recv S(" + seqNo + ") (own sendcounter = " + sendCount + ")");
- waitingForTestFRcon = false;
+ if (CheckSequenceNumber(seqNo) == false)
+ {
+ DebugLog("S message - sequence number mismatch");
+ return false;
+ }
- ResetT3Timeout(currentTime);
- }
+ if (state == MasterConnectionState.M_CON_STATE_UNCONFIRMED_STOPPED)
+ {
+ if (MasterConnection_hasUnconfirmedMessages() == false)
+ {
+ state = MasterConnectionState.M_CON_STATE_STOPPED;
- // S-message
- else if (buffer[2] == 0x01)
- {
- if (isActive == false)
- {
- // connection not active
- DebugLog("Connection not active -> close connection");
+ DebugLog("Send STOPDT_CON\n");
- return false;
+ try
+ {
+ socketStream.Write(STOPDT_CON_MSG, 0, STOPDT_CON_MSG.Length);
+ }
+ catch (IOException ex)
+ {
+ DebugLog("Failed to send STOPDT_CON: " + ex.Message);
+ return false;
+ }
+ }
+ }
+ else if (state == MasterConnectionState.M_CON_STATE_STOPPED)
+ {
+ DebugLog("S message sin stopped state -> active close\n");
+ /* actively close connection */
+ return false;
+ }
+ }
+ else
+ {
+ DebugLog("Unknown message - IGNORE");
+ return true;
}
- int seqNo = (buffer[4] + buffer[5] * 0x100) / 2;
-
- DebugLog("Recv S(" + seqNo + ") (own sendcounter = " + sendCount + ")");
+ ResetT3Timeout(currentTime);
- if (CheckSequenceNumber(seqNo) == false)
- return false;
-
+ return true;
}
else
{
- DebugLog("Unknown message");
+ DebugLog("Invalid message (too small)");
+ return false;
}
-
- ResetT3Timeout(currentTime);
-
- return true;
}
+ ///
+ /// Handles various timeout conditions in the communication process, such as T3 timeouts, TESTFR_CON timeouts,
+ /// and I-message timeouts. It checks for timeouts, sends necessary messages (such as TESTFR_ACT or S-message),
+ /// and ensures the connection is managed correctly based on timeout events.
+ ///
+ ///
+ /// Returns true if no timeouts were detected or handled successfully, otherwise returns false
+ /// if a timeout condition was detected and the connection should be closed or processed further.
+ ///
private bool handleTimeouts()
{
UInt64 currentTime = (UInt64)SystemUtils.currentTimeMillis();
@@ -1112,7 +1525,7 @@ private bool handleTimeouts()
waitingForTestFRcon = true;
- ResetTestFRConTimeout(currentTime);
+ ResetTestFRConTimeout(currentTime);
}
/* Check for TEST FR con timeout */
@@ -1122,14 +1535,12 @@ private bool handleTimeouts()
{
DebugLog("Timeout for TESTFR_CON message");
- // close connection
return false;
}
}
if (unconfirmedReceivedIMessages > 0)
{
-
if ((currentTime - lastConfirmationTime) >= (UInt64)(apciParameters.T2 * 1000))
{
@@ -1139,7 +1550,7 @@ private bool handleTimeouts()
SendSMessage();
}
}
-
+
/* check if counterpart confirmed I messages */
lock (sentASDUs)
{
@@ -1147,7 +1558,6 @@ private bool handleTimeouts()
{
if (((long)currentTime - sentASDUs[oldestSentASDU].sentTime) >= (apciParameters.T1 * 1000))
{
-
PrintSendBuffer();
DebugLog("I message timeout for " + oldestSentASDU + " seqNo: " + sentASDUs[oldestSentASDU].seqNo);
return false;
@@ -1158,11 +1568,22 @@ private bool handleTimeouts()
return true;
}
+ ///
+ /// Compares two byte arrays for equality by checking if they are of the same length and if each byte in the arrays is equal.
+ ///
+ ///
+ /// The first byte array to be compared.
+ ///
+ ///
+ /// The second byte array to be compared.
+ ///
+ ///
+ /// Returns true if the byte arrays are equal (same length and same content), otherwise returns false .
+ ///
private bool AreByteArraysEqual(byte[] array1, byte[] array2)
{
if (array1.Length == array2.Length)
{
-
for (int i = 0; i < array1.Length; i++)
{
if (array1[i] != array2[i])
@@ -1175,6 +1596,26 @@ private bool AreByteArraysEqual(byte[] array1, byte[] array2)
return false;
}
+ ///
+ /// Callback method to handle certificate validation in SSL/TLS communication. It checks various conditions
+ /// such as certificate chain validation, specific certificate validation, and SSL policy errors to determine
+ /// whether the certificate is valid for the current connection.
+ ///
+ ///
+ /// The source of the event. This is typically the SSL/TLS connection.
+ ///
+ ///
+ /// The certificate being validated.
+ ///
+ ///
+ /// The certificate chain associated with the certificate.
+ ///
+ ///
+ /// The SSL policy errors that occurred during certificate validation.
+ ///
+ ///
+ /// Returns true if the certificate is considered valid based on the specified criteria; otherwise, returns false .
+ ///
public bool CertificateValidationCallback(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None || sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors)
@@ -1191,7 +1632,7 @@ public bool CertificateValidationCallback(object sender, X509Certificate cert, X
foreach (X509Certificate2 caCert in tlsSecInfo.CaCertificates)
newChain.ChainPolicy.ExtraStore.Add(caCert);
-
+
bool certificateStatus = newChain.Build(new X509Certificate2(cert.GetRawCertData()));
if (certificateStatus == false)
@@ -1217,6 +1658,10 @@ public bool CertificateValidationCallback(object sender, X509Certificate cert, X
return false;
}
+ ///
+ /// Handles the connection lifecycle, including setting up the SSL/TLS connection,
+ /// receiving and processing messages, managing timeouts, and closing the connection.
+ ///
private void HandleConnection()
{
byte[] bytes = new byte[300];
@@ -1242,14 +1687,21 @@ private void HandleConnection()
try
{
- sslStream.AuthenticateAsServer(tlsSecInfo.OwnCertificate, true, System.Security.Authentication.SslProtocols.Tls, false);
-
+ System.Security.Authentication.SslProtocols tlsVersion = System.Security.Authentication.SslProtocols.None;
+
+ if (tlsSecInfo != null)
+ tlsVersion = tlsSecInfo.TlsVersion;
+
+ DebugLog("Using TLS version: " + tlsVersion.ToString());
+
+ sslStream.AuthenticateAsServer(tlsSecInfo.OwnCertificate, true, tlsVersion, false);
+
if (sslStream.IsAuthenticated == true)
{
socketStream = sslStream;
authenticationSuccess = true;
}
-
+
}
catch (IOException e)
{
@@ -1262,7 +1714,7 @@ private void HandleConnection()
DebugLog("TLS authentication error: " + e.Message);
}
}
-
+
if (authenticationSuccess == true)
socketStream = sslStream;
else
@@ -1284,16 +1736,15 @@ private void HandleConnection()
while (running)
{
-
try
{
- // Receive the response from the remote device.
+ /* Receive the response from the remote device */
int bytesRec = receiveMessage(bytes);
if (bytesRec > 0)
{
-
- DebugLog("RCVD: " + BitConverter.ToString(bytes, 0, bytesRec));
+
+ DebugLog("RCVD: " + BitConverter.ToString(bytes, 0, bytesRec));
if (HandleMessage(bytes, bytesRec) == false)
{
@@ -1307,11 +1758,11 @@ private void HandleConnection()
unconfirmedReceivedIMessages = 0;
timeoutT2Triggered = false;
SendSMessage();
- }
+ }
}
else if (bytesRec == -1)
{
- running = false;
+ running = false;
}
}
catch (System.IO.IOException)
@@ -1338,8 +1789,6 @@ private void HandleConnection()
DebugLog("CLOSE CONNECTION!");
- // Release the socket.
-
socket.Shutdown(SocketShutdown.Both);
socket.Close();
@@ -1368,9 +1817,9 @@ private void HandleConnection()
DebugLog(e.ToString());
}
- // unmark unconfirmed messages in queue if k-buffer not empty
+ /* unmark unconfirmed messages in queue if k-buffer not empty */
if (oldestSentASDU != -1)
- asduQueue.UnmarkAllASDUs();
+ lowPrioQueue.UnmarkAllASDUs();
server.Remove(this);
@@ -1383,13 +1832,14 @@ private void HandleConnection()
DebugLog("Connection thread finished");
}
- void HandleRemoteCertificateValidationCallback (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ void HandleRemoteCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
}
public void Close()
{
running = false;
+ state = MasterConnectionState.M_CON_STATE_STOPPED;
}
public void ASDUReadyToSend()
@@ -1399,5 +1849,5 @@ public void ASDUReadyToSend()
}
}
-
+
}
diff --git a/lib60870/CS104/Connection.cs b/lib60870/CS104/Connection.cs
index f5ab6d3..78d26ec 100644
--- a/lib60870/CS104/Connection.cs
+++ b/lib60870/CS104/Connection.cs
@@ -1,7 +1,7 @@
/*
* Connection.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,18 +21,15 @@
* See COPYING file for the complete license text.
*/
+using lib60870.CS101;
using System;
-
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
using System.Collections.Generic;
using System.IO;
-using System.Security.Cryptography.X509Certificates;
+using System.Net;
using System.Net.Security;
-
-using lib60870.CS101;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
namespace lib60870.CS104
{
@@ -60,13 +57,22 @@ public enum ConnectionEvent
/// Conformation of STOP DT command received (server will no longer send or accept application layer messages)
///
STOPDT_CON_RECEIVED = 3,
-
+
///
/// The connect attempt has failed
///
CONNECT_FAILED = 4
}
+ public enum CS104_ConState
+ {
+ STATE_IDLE = 0,
+ STATE_INACTIVE = 1,
+ STATE_ACTIVE = 2,
+ STATE_WAITING_FOR_STARTDT_CON = 3,
+ STATE_WAITING_FOR_STOPDT_CON = 4
+ }
+
///
/// Provides some Connection statistics.
///
@@ -94,11 +100,11 @@ public int SentMsgCounter
{
get
{
- return this.sentMsgCounter;
+ return sentMsgCounter;
}
internal set
{
- this.sentMsgCounter = value;
+ sentMsgCounter = value;
}
}
@@ -110,11 +116,11 @@ public int RcvdMsgCounter
{
get
{
- return this.rcvdMsgCounter;
+ return rcvdMsgCounter;
}
internal set
{
- this.rcvdMsgCounter = value;
+ rcvdMsgCounter = value;
}
}
@@ -126,11 +132,11 @@ public int RcvdTestFrActCounter
{
get
{
- return this.rcvdTestFrActCounter;
+ return rcvdTestFrActCounter;
}
internal set
{
- this.rcvdTestFrActCounter = value;
+ rcvdTestFrActCounter = value;
}
}
@@ -142,11 +148,11 @@ public int RcvdTestFrConCounter
{
get
{
- return this.rcvdTestFrConCounter;
+ return rcvdTestFrConCounter;
}
internal set
{
- this.rcvdTestFrConCounter = value;
+ rcvdTestFrConCounter = value;
}
}
}
@@ -154,12 +160,12 @@ internal set
///
/// ASDU received handler.
///
- public delegate bool ASDUReceivedHandler(object parameter,ASDU asdu);
+ public delegate bool ASDUReceivedHandler(object parameter, ASDU asdu);
///
/// Callback handler for connection events
///
- public delegate void ConnectionHandler(object parameter,ConnectionEvent connectionEvent);
+ public delegate void ConnectionHandler(object parameter, ConnectionEvent connectionEvent);
///
/// A single connection to a CS 104 (IEC 60870-5-104) server. Implements the \ref Master interface.
@@ -203,6 +209,8 @@ private struct SentASDU
/**********************************************/
+ CS104_ConState conState;
+
private bool checkSequenceNumbers = true;
private Queue waitingToBeSent = null;
@@ -278,7 +286,7 @@ public bool UseSendMessageQueue
{
get
{
- return this.useSendMessageQueue;
+ return useSendMessageQueue;
}
set
{
@@ -295,11 +303,11 @@ public int SendSequenceNumber
{
get
{
- return this.sendSequenceNumber;
+ return sendSequenceNumber;
}
set
{
- this.sendSequenceNumber = value;
+ sendSequenceNumber = value;
}
}
@@ -324,11 +332,11 @@ public int ReceiveSequenceNumber
{
get
{
- return this.receiveSequenceNumber;
+ return receiveSequenceNumber;
}
set
{
- this.receiveSequenceNumber = value;
+ receiveSequenceNumber = value;
}
}
@@ -341,11 +349,11 @@ public bool Autostart
{
get
{
- return this.autostart;
+ return autostart;
}
set
{
- this.autostart = value;
+ autostart = value;
}
}
@@ -357,6 +365,11 @@ private void DebugLog(string message)
private ConnectionStatistics statistics = new ConnectionStatistics();
+ ///
+ /// Resets the connection state by clearing sequence numbers, message counters,
+ /// and connection-specific flags. This method is used to initialize or reset
+ /// the connection before establishing a new session or after a connection is lost.
+ ///
private void ResetConnection()
{
sendSequenceNumber = 0;
@@ -379,6 +392,8 @@ private void ResetConnection()
if (useSendMessageQueue)
waitingToBeSent = new Queue();
+ conState = CS104_ConState.STATE_IDLE;
+
statistics.Reset();
}
@@ -389,7 +404,7 @@ public ApplicationLayerParameters Parameters
{
get
{
- return this.alParameters;
+ return alParameters;
}
}
@@ -405,7 +420,11 @@ public ApplicationLayerParameters Parameters
private RawMessageHandler sentMessageHandler = null;
private object sentMessageHandlerParameter = null;
- private void SendSMessage()
+ ///
+ /// Sends an S-Message to the remote device.
+ /// This message contains a sequence number and is used for communication control.
+ ///
+ public void SendSMessage()
{
byte[] msg = new byte[6];
@@ -426,6 +445,13 @@ private void SendSMessage()
}
}
+ ///
+ /// Checks the validity of a received sequence number.
+ ///
+ /// The sequence number to be validated.
+ ///
+ /// Returns true if the sequence number is valid, false otherwise.
+ ///
private bool CheckSequenceNumber(int seqNo)
{
if (checkSequenceNumbers)
@@ -527,6 +553,13 @@ private bool CheckSequenceNumber(int seqNo)
return true;
}
+ ///
+ /// Checks if the send buffer is full, i.e., if there are no more available spaces
+ /// to add new sent messages in the circular buffer.
+ ///
+ ///
+ /// Returns true if the send buffer is full, otherwise false.
+ ///
private bool IsSentBufferFull()
{
if (oldestSentASDU == -1)
@@ -540,6 +573,13 @@ private bool IsSentBufferFull()
return false;
}
+ ///
+ /// Sends an I-Message (Information message) to the remote device.
+ ///
+ /// The ASDU (Application Service Data Unit) to be sent.
+ ///
+ /// Returns the sequence number used for the I-Message after it has been sent.
+ ///
private int SendIMessage(ASDU asdu)
{
BufferFrame frame = new BufferFrame(new byte[260], 6); /* reserve space for ACPI */
@@ -586,6 +626,9 @@ private int SendIMessage(ASDU asdu)
}
}
+ ///
+ /// Prints the contents of the k-buffer, showing the sequence numbers and sent times of the messages.
+ ///
private void PrintSendBuffer()
{
if (oldestSentASDU != -1)
@@ -612,6 +655,10 @@ private void PrintSendBuffer()
}
}
+ ///
+ /// Sends an I-Message and updates the k-buffer with the new ASDU (Application Service Data Unit).
+ ///
+ /// The ASDU to be sent.
private void SendIMessageAndUpdateSentASDUs(ASDU asdu)
{
lock (sentASDUs)
@@ -637,6 +684,12 @@ private void SendIMessageAndUpdateSentASDUs(ASDU asdu)
}
}
+ ///
+ /// Sends the next ASDU in the waiting queue if the k-buffer is not full.
+ ///
+ ///
+ /// Returns `true` if an ASDU was sent, otherwise `false` if no ASDU was sent.
+ ///
private bool SendNextWaitingASDU()
{
bool sentAsdu = false;
@@ -677,6 +730,10 @@ private bool SendNextWaitingASDU()
return sentAsdu;
}
+ ///
+ /// Sends an ASDU either immediately or by queuing it for later sending.
+ ///
+ /// The ASDU to be sent.
private void SendASDUInternal(ASDU asdu)
{
lock (socket)
@@ -704,13 +761,20 @@ private void SendASDUInternal(ASDU asdu)
}
}
+ ///
+ /// Sets up the connection parameters for the communication session.
+ ///
+ /// The hostname or IP address of the remote server.
+ /// The APCI (Application Protocol Control Information) parameters for the connection.
+ /// The application layer parameters for the connection.
+ /// The TCP port number for the connection.
private void Setup(string hostname, APCIParameters apciParameters, ApplicationLayerParameters alParameters, int tcpPort)
{
this.hostname = hostname;
this.alParameters = alParameters;
this.apciParameters = apciParameters;
this.tcpPort = tcpPort;
- this.connectTimeoutInMs = apciParameters.T0 * 1000;
+ connectTimeoutInMs = apciParameters.T0 * 1000;
connectionCounter++;
connectionID = connectionCounter;
@@ -790,7 +854,7 @@ public void SetTlsSecurity(TlsSecurityInformation securityInfo)
/// The connection statistics.
public ConnectionStatistics GetStatistics()
{
- return this.statistics;
+ return statistics;
}
///
@@ -799,7 +863,7 @@ public ConnectionStatistics GetStatistics()
/// timeout value in milliseconds (ms)
public void SetConnectTimeout(int millies)
{
- this.connectTimeoutInMs = millies;
+ connectTimeoutInMs = millies;
}
///
@@ -809,11 +873,11 @@ public int ConnectTimeout
{
get
{
- return this.connectTimeoutInMs;
+ return connectTimeoutInMs;
}
set
{
- this.connectTimeoutInMs = value;
+ connectTimeoutInMs = value;
}
}
@@ -824,11 +888,11 @@ public int ReceiveTimeout
{
get
{
- return this.receiveTimeoutInMs;
+ return receiveTimeoutInMs;
}
set
{
- this.receiveTimeoutInMs = value;
+ receiveTimeoutInMs = value;
}
}
@@ -1016,6 +1080,7 @@ public void SendStartDT()
{
try
{
+ conState = CS104_ConState.STATE_WAITING_FOR_STARTDT_CON;
netStream.Write(STARTDT_ACT_MSG, 0, STARTDT_ACT_MSG.Length);
}
catch (Exception ex)
@@ -1061,12 +1126,14 @@ public void SendStopDT()
}
netStream.Write(STOPDT_ACT_MSG, 0, STOPDT_ACT_MSG.Length);
+
+ conState = CS104_ConState.STATE_WAITING_FOR_STOPDT_CON;
}
catch (Exception ex)
{
throw new ConnectionException("Failed to write to socket", ex);
}
-
+
statistics.SentMsgCounter++;
if (sentMessageHandler != null)
{
@@ -1097,7 +1164,7 @@ protected void SendStartDT_CON()
{
throw new ConnectionException("Failed to write to socket", ex);
}
-
+
statistics.SentMsgCounter++;
if (sentMessageHandler != null)
{
@@ -1113,6 +1180,9 @@ protected void SendStartDT_CON()
}
}
+ ///
+ /// Sends a STOPDT_CON message to the remote server if the connection is active.
+ ///
protected void SendStopDT_CON()
{
if (running)
@@ -1141,6 +1211,9 @@ protected void SendStopDT_CON()
}
}
+ ///
+ /// Sends a TESTFR_ACT message to the remote server if the connection is active.
+ ///
protected void SendTestFR_ACT()
{
if (running)
@@ -1153,7 +1226,7 @@ protected void SendTestFR_ACT()
{
throw new ConnectionException("Failed to write to socket", ex);
}
-
+
statistics.SentMsgCounter++;
if (sentMessageHandler != null)
{
@@ -1169,6 +1242,9 @@ protected void SendTestFR_ACT()
}
}
+ ///
+ /// Sends a TESTFR_CON message to the remote server if the connection is active.
+ ///
protected void SendTestFR_CON()
{
if (running)
@@ -1181,7 +1257,7 @@ protected void SendTestFR_CON()
{
throw new ConnectionException("Failed to write to socket", ex);
}
-
+
statistics.SentMsgCounter++;
if (sentMessageHandler != null)
{
@@ -1216,6 +1292,9 @@ public void Connect()
throw new ConnectionException(lastException.Message, lastException);
}
+ ///
+ /// Resets the T3 timeout based on the current system time and the T3 parameter from the APCI configuration.
+ ///
private void ResetT3Timeout()
{
nextT3Timeout = (UInt64)SystemUtils.currentTimeMillis() + (UInt64)(apciParameters.T3 * 1000);
@@ -1254,6 +1333,15 @@ public void ConnectAsync()
private int remainingReadLength = 0;
private long currentReadTimeout = 0;
+ ///
+ /// Receives a message from the network stream and processes it byte by byte.
+ ///
+ /// The byte array to store the received data.
+ ///
+ /// Returns the total number of bytes read if a message is successfully received.
+ /// Returns -1 if there is a timeout or invalid data (e.g., missing SOF or incorrect length).
+ /// Returns 0 if the message is incomplete and the method needs to be called again.
+ ///
private int receiveMessage(byte[] buffer)
{
/* check receive timeout */
@@ -1271,7 +1359,7 @@ private int receiveMessage(byte[] buffer)
if (readState == 0)
{
- // wait for start byte
+ /* wait for start byte */
if (netStream.Read(buffer, 0, 1) != 1)
return -1;
@@ -1287,7 +1375,7 @@ private int receiveMessage(byte[] buffer)
if (readState == 1)
{
- // read length byte
+ /* read length byte */
if (netStream.Read(buffer, 1, 1) != 1)
return 0;
@@ -1317,13 +1405,20 @@ private int receiveMessage(byte[] buffer)
if (currentReadTimeout == 0)
{
- currentReadTimeout = SystemUtils.currentTimeMillis() + this.receiveTimeoutInMs;
+ currentReadTimeout = SystemUtils.currentTimeMillis() + receiveTimeoutInMs;
}
}
return 0;
}
+ ///
+ /// Checks if the confirmation timeout (T2) has elapsed since the last confirmation time.
+ ///
+ /// The current time in milliseconds.
+ ///
+ /// Returns `true` if the confirmation timeout (T2) has elapsed, otherwise returns `false`.
+ ///
private bool checkConfirmTimeout(long currentTime)
{
if ((currentTime - lastConfirmationTime) >= (apciParameters.T2 * 1000))
@@ -1332,6 +1427,15 @@ private bool checkConfirmTimeout(long currentTime)
return false;
}
+ ///
+ /// Processes an incoming message, checks its validity, and handles different types of frames.
+ ///
+ /// The byte array containing the received message.
+ /// The size of the received message in bytes.
+ ///
+ /// Returns `true` if the message is valid and successfully processed.
+ /// Returns `false` if the message is invalid or an error occurs during processing.
+ ///
private bool checkMessage(byte[] buffer, int msgSize)
{
long currentTime = SystemUtils.currentTimeMillis();
@@ -1408,7 +1512,8 @@ private bool checkMessage(byte[] buffer, int msgSize)
uMessageTimeout = 0;
if (buffer[2] == 0x43)
- { // Check for TESTFR_ACT message
+ {
+ /* Check for TESTFR_ACT message */
statistics.RcvdTestFrActCounter++;
DebugLog("RCVD TESTFR_ACT");
DebugLog("SEND TESTFR_CON");
@@ -1439,6 +1544,8 @@ private bool checkMessage(byte[] buffer, int msgSize)
{
sentMessageHandler(sentMessageHandlerParameter, STARTDT_CON_MSG, 6);
}
+
+ conState = CS104_ConState.STATE_ACTIVE;
}
else if (buffer[2] == 0x0b)
{ /* STARTDT_CON */
@@ -1447,6 +1554,8 @@ private bool checkMessage(byte[] buffer, int msgSize)
if (connectionHandler != null)
connectionHandler(connectionHandlerParameter, ConnectionEvent.STARTDT_CON_RECEIVED);
+ conState = CS104_ConState.STATE_ACTIVE;
+
}
else if (buffer[2] == 0x23)
{ /* STOPDT_CON */
@@ -1454,6 +1563,8 @@ private bool checkMessage(byte[] buffer, int msgSize)
if (connectionHandler != null)
connectionHandler(connectionHandlerParameter, ConnectionEvent.STOPDT_CON_RECEIVED);
+
+ conState = CS104_ConState.STATE_INACTIVE;
}
}
@@ -1468,6 +1579,13 @@ private bool checkMessage(byte[] buffer, int msgSize)
return true;
}
+ ///
+ /// Checks the connection status by attempting to send a dummy byte to the socket.
+ ///
+ ///
+ /// Returns `true` if the connection is active (i.e., the socket is still connected).
+ /// Returns `false` if the socket is disconnected or an error occurs.
+ ///
private bool isConnected()
{
try
@@ -1493,6 +1611,10 @@ private bool isConnected()
}
}
+ ///
+ /// Establishes a socket connection to the specified remote endpoint with a timeout.
+ /// If the connection cannot be established within the specified timeout, it throws a timeout exception.
+ ///
private void ConnectSocketWithTimeout()
{
IPAddress ipAddress;
@@ -1505,22 +1627,27 @@ private void ConnectSocketWithTimeout()
}
catch (Exception)
{
- throw new SocketException(87); // wrong argument
+ throw new SocketException(87); /* wrong argument */
}
- // Create a TCP/IP socket.
+ /* Create a TCP/IP socket. */
socket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
+ socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ LingerOption lingerOption = new LingerOption(true, 0);
+ socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lingerOption);
+
if (LocalIpAddress != null)
{
try
{
socket.Bind(new IPEndPoint(IPAddress.Parse(localIpAddress), localTcpPort));
}
- catch (Exception)
+ catch (Exception ex)
{
- throw new SocketException(87); // wrong argument
+ Console.WriteLine(ex.Message);
+ throw new SocketException(87); /* wrong argument */
}
}
@@ -1542,18 +1669,41 @@ private void ConnectSocketWithTimeout()
DebugLog("ObjectDisposedException -> Connect canceled");
- throw new SocketException(995); // WSA_OPERATION_ABORTED
+ throw new SocketException(995); /* WSA_OPERATION_ABORTED */
}
}
else
{
- socket.Close();
+ if (socket.Connected)
+ {
+ try
+ {
+ socket.Shutdown(SocketShutdown.Receive);
+ }
+ catch (SocketException ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+
+ socket.Disconnect(true);
+ }
+
+
+ socket.Close(0);
socket = null;
- throw new SocketException(10060); // Connection timed out (WSAETiMEDOUT)
+ throw new SocketException(10060); /* Connection timed out (WSAETiMEDOUT) */
}
}
+ ///
+ /// Handles various timeouts associated with the communication protocol, including T3, T1, and confirmation timeouts.
+ /// The method checks for expired timeouts and takes appropriate actions, such as resending messages or throwing exceptions.
+ ///
+ ///
+ /// - **true** if no timeouts or issues are detected, allowing normal processing to continue.
+ /// - **false** if a timeout (such as T3) has occurred, indicating the need for further action (like message retransmission).
+ ///
private bool handleTimeouts()
{
UInt64 currentTime = (UInt64)SystemUtils.currentTimeMillis();
@@ -1565,7 +1715,6 @@ private bool handleTimeouts()
{
DebugLog("Timeout for TESTFR_CON message");
- // close connection
return false;
}
else
@@ -1574,7 +1723,7 @@ private bool handleTimeouts()
statistics.SentMsgCounter++;
DebugLog("U message T3 timeout");
- uMessageTimeout = (UInt64)currentTime + (UInt64)(apciParameters.T1 * 1000);
+ uMessageTimeout = currentTime + (UInt64)(apciParameters.T1 * 1000);
outStandingTestFRConMessages++;
ResetT3Timeout();
if (sentMessageHandler != null)
@@ -1622,6 +1771,15 @@ private bool handleTimeouts()
return true;
}
+ ///
+ /// Compares two byte arrays to check if they are equal.
+ ///
+ /// The first byte array to compare.
+ /// The second byte array to compare.
+ ///
+ /// - **true** if both byte arrays have the same length and the same content.
+ /// - **false** if the byte arrays have different lengths or any byte at a corresponding index is different.
+ ///
private bool AreByteArraysEqual(byte[] array1, byte[] array2)
{
if (array1.Length == array2.Length)
@@ -1639,7 +1797,19 @@ private bool AreByteArraysEqual(byte[] array1, byte[] array2)
return false;
}
- private bool CertificateValidationCallback (Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
+ ///
+ /// Callback function for validating a server certificate in an SSL/TLS connection.
+ ///
+ /// The source of the certificate validation request (typically the sender of the SSL/TLS connection).
+ /// The server's certificate to validate.
+ /// The certificate chain containing the server's certificate and any intermediary certificates.
+ /// Any SSL policy errors encountered during the validation process.
+ ///
+ /// - **true** if the certificate is valid according to the validation rules.
+ /// - **false** if the certificate is invalid or fails validation based on specified rules.
+ ///
+ ///
+ private bool CertificateValidationCallback(Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (certificate != null)
{
@@ -1683,11 +1853,27 @@ private bool CertificateValidationCallback (Object sender, X509Certificate certi
return false;
}
+ ///
+ /// Callback function for selecting a local certificate during an SSL/TLS handshake.
+ ///
+ /// The source of the certificate selection request (typically the client in an SSL/TLS connection).
+ /// The name of the server being connected to (host name or IP address).
+ /// The collection of local certificates available for selection.
+ /// The remote server's certificate being validated (not used in this implementation).
+ /// A list of acceptable issuers for the certificate (not used in this implementation).
+ ///
+ /// The local certificate to be used for the SSL/TLS connection. In this case, the first certificate in the collection.
+ ///
public X509Certificate LocalCertificateSelectionCallback(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
{
return localCertificates[0];
}
+ ///
+ /// Establishes and maintains the connection to a remote device, handling socket connection,
+ /// TLS handshake, message reception, and timeouts. It processes incoming data, manages connection state,
+ /// and ensures proper handling of message timeouts and unconfirmed messages.
+ ///
private void HandleConnection()
{
byte[] bytes = new byte[300];
@@ -1700,7 +1886,7 @@ private void HandleConnection()
try
{
- // Connect to a remote device.
+ /* Connect to a remote device. */
ConnectSocketWithTimeout();
DebugLog("Socket connected to " + socket.RemoteEndPoint.ToString());
@@ -1709,6 +1895,8 @@ private void HandleConnection()
{
DebugLog("Setup TLS");
+ System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | (SecurityProtocolType)12288 /* TLS 1.3 */;
+
RemoteCertificateValidationCallback validationCallback = CertificateValidationCallback;
if (tlsSecInfo.CertificateValidationCallback != null)
@@ -1800,6 +1988,7 @@ private void HandleConnection()
if (running)
{
+ conState = CS104_ConState.STATE_INACTIVE;
bool loopRunning = running;
@@ -1810,7 +1999,7 @@ private void HandleConnection()
try
{
- // Receive a message from from the remote device.
+ /* Receive a message from the remote device. */
int bytesRec = receiveMessage(bytes);
if (bytesRec > 0)
@@ -1822,6 +2011,8 @@ private void HandleConnection()
bool handleMessage = true;
+ CS104_ConState oldState = conState;
+
if (recvRawMessageHandler != null)
handleMessage = recvRawMessageHandler(recvRawMessageHandlerParameter, bytes, bytesRec);
@@ -1834,7 +2025,17 @@ private void HandleConnection()
}
}
- if (unconfirmedReceivedIMessages >= apciParameters.W)
+ CS104_ConState newState = conState;
+
+ if ((newState != oldState) && connectionHandler != null)
+ {
+ if (newState == CS104_ConState.STATE_ACTIVE)
+ connectionHandler(connectionHandlerParameter, ConnectionEvent.STARTDT_CON_RECEIVED);
+ else if (newState == CS104_ConState.STATE_INACTIVE)
+ connectionHandler(connectionHandlerParameter, ConnectionEvent.STOPDT_CON_RECEIVED);
+ }
+
+ if (unconfirmedReceivedIMessages >= apciParameters.W || conState == CS104_ConState.STATE_WAITING_FOR_STOPDT_CON)
{
lastConfirmationTime = SystemUtils.currentTimeMillis();
@@ -1854,7 +2055,7 @@ private void HandleConnection()
loopRunning = false;
if (fileClient != null)
- fileClient.HandleFileService ();
+ fileClient.HandleFileService();
if (isConnected() == false)
loopRunning = false;
@@ -1889,19 +2090,39 @@ private void HandleConnection()
DebugLog("CLOSE CONNECTION!");
- // Release the socket.
- try
- {
- socket.Shutdown(SocketShutdown.Both);
- }
- catch (SocketException)
+
+ if (unconfirmedReceivedIMessages > 0)
{
+ /* confirm all unconfirmed messages before stopping the connection */
+
+ lastConfirmationTime = SystemUtils.currentTimeMillis();
+
+ unconfirmedReceivedIMessages = 0;
+ timeoutT2Triggered = false;
+
+ SendSMessage();
}
+
running = false;
socketError = true;
- socket.Close();
+ if (socket.Connected)
+ {
+ try
+ {
+ socket.Shutdown(SocketShutdown.Receive);
+ }
+ catch (SocketException ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+
+ socket.Disconnect(true);
+ }
+
+
+ socket.Close(0);
netStream.Dispose();
@@ -1938,6 +2159,8 @@ private void HandleConnection()
DebugLog(e.ToString());
}
+ conState = CS104_ConState.STATE_IDLE;
+
running = false;
connecting = false;
}
@@ -1950,14 +2173,33 @@ public bool IsRunning
{
get
{
- return this.running;
+ return running;
}
}
public void Cancel()
{
if (socket != null)
- socket.Close();
+ {
+ if (socket.Connected)
+ {
+ try
+ {
+ socket.Shutdown(SocketShutdown.Receive);
+ }
+ catch (SocketException ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+
+ socket.Disconnect(true);
+
+ }
+
+
+ socket.Close(0);
+ }
+
}
public void Close()
@@ -2026,6 +2268,13 @@ public bool IsTransmitBufferFull()
return IsSentBufferFull();
}
+ ///
+ /// Initiates a request to retrieve a file from the remote system.
+ ///
+ /// The communication address of the remote system.
+ /// The information object address for the file.
+ /// The name of the file to be retrieved.
+ /// The receiver implementation to handle the file data.
public override void GetFile(int ca, int ioa, NameOfFile nof, IFileReceiver receiver)
{
if (fileClient == null)
@@ -2034,15 +2283,43 @@ public override void GetFile(int ca, int ioa, NameOfFile nof, IFileReceiver rece
fileClient.RequestFile(ca, ioa, nof, receiver);
}
+ ///
+ /// Initiates a request to retrieve a file from the remote system with a specified timeout.
+ ///
+ /// The communication address of the remote system.
+ /// The information object address for the file.
+ /// The name of the file to be retrieved.
+ /// The receiver implementation to handle the file data.
+ /// The timeout duration in milliseconds for the file request.
+ public void GetFile(int ca, int ioa, NameOfFile nof, IFileReceiver receiver, int timeout)
+ {
+ if (fileClient == null)
+ fileClient = new FileClient(this, DebugLog);
+
+ fileClient.Timeout = timeout;
- public override void SendFile (int ca, int ioa, NameOfFile nof, IFileProvider fileProvider)
+ fileClient.RequestFile(ca, ioa, nof, receiver);
+ }
+
+ ///
+ /// Initiates a request to send a file to the remote system.
+ ///
+ /// The communication address of the remote system.
+ /// The information object address for the file.
+ /// The name of the file to be sent.
+ /// The file provider interface that supplies the file data to be sent.
+ public override void SendFile(int ca, int ioa, NameOfFile nof, IFileProvider fileProvider)
{
if (fileClient == null)
- fileClient = new FileClient (this, DebugLog);
+ fileClient = new FileClient(this, DebugLog);
- fileClient.SendFile (ca, ioa, nof, fileProvider);
+ fileClient.SendFile(ca, ioa, nof, fileProvider);
}
+ ///
+ /// Requests the directory listing from the remote system.
+ ///
+ /// The communication address of the remote system.
public void GetDirectory(int ca)
{
ASDU getDirectoryAsdu = new ASDU(GetApplicationLayerParameters(), CauseOfTransmission.REQUEST, false, false, 0, ca, false);
diff --git a/lib60870/CS104/Server.cs b/lib60870/CS104/Server.cs
index 89246b3..fd563df 100644
--- a/lib60870/CS104/Server.cs
+++ b/lib60870/CS104/Server.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -18,31 +18,67 @@
*
* See COPYING file for the complete license text.
*/
-
+#define CONFIG_USE_SEMAPHORES
+using lib60870.CS101;
using System;
-
+using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
-using System.Collections.Generic;
-using System.Collections.Concurrent;
-
-using lib60870.CS101;
namespace lib60870.CS104
{
-
+ ///
+ /// Server events concerning listening
+ ///
+ public class ServerStateEvent {
+
+ private bool nowListening;
+ private bool fatal;
+ private Exception occurredException;
+ ///
+ /// Either true (by Start()) or false (by Stop() or exception)
+ ///
+ public bool NowListening
+ {
+ get { return nowListening; }
+ }
+ ///
+ /// Is it a fatal exception (while listening) or not (no exception or while closing)
+ ///
+ public bool Fatal
+ {
+ get { return fatal; }
+ }
+ ///
+ /// Either null (by Start() and Stop() or caught exception)
+ ///
+ public Exception OccurredException
+ {
+ get { return occurredException; }
+ }
+ internal ServerStateEvent(bool nowListening, bool fatal, Exception occurredException)
+ {
+ this.nowListening = nowListening;
+ this.fatal = fatal;
+ this.occurredException = occurredException;
+ }
+ }
+ public delegate void ServerStateEventHandler(object parameter, ServerStateEvent stateEvent);
+
///
/// Connection request handler is called when a client tries to connect to the server.
///
/// User provided parameter
/// IP address of the connecting client
/// true if the connection has to be accepted, false otherwise
- public delegate bool ConnectionRequestHandler(object parameter,IPAddress ipAddress);
+ public delegate bool ConnectionRequestHandler(object parameter, IPAddress ipAddress);
///
/// Connection events for the Server
///
+ ///
+
public enum ClientConnectionEvent
{
///
@@ -56,7 +92,7 @@ public enum ClientConnectionEvent
ACTIVE,
///
- /// The connection enterend inactive state
+ /// The connection entered inactive state
///
INACTIVE,
@@ -66,7 +102,8 @@ public enum ClientConnectionEvent
CLOSED
}
- public delegate void ConnectionEventHandler(object parameter,ClientConnection connection,ClientConnectionEvent eventType);
+
+ public delegate void ConnectionEventHandler(object parameter, ClientConnection connection, ClientConnectionEvent eventType);
///
/// Server mode (redundancy group support)
@@ -112,244 +149,6 @@ public enum EnqueueMode
THROW_EXCEPTION
}
- internal class ASDUQueue
- {
- private enum QueueEntryState
- {
- NOT_USED,
- WAITING_FOR_TRANSMISSION,
- SENT_BUT_NOT_CONFIRMED
- }
-
- private struct ASDUQueueEntry
- {
- public long entryTimestamp;
- public BufferFrame asdu;
- public QueueEntryState state;
- }
-
- // Queue for messages (ASDUs)
- private ASDUQueueEntry[] enqueuedASDUs = null;
- private int oldestQueueEntry = -1;
- private int latestQueueEntry = -1;
- private int numberOfAsduInQueue = 0;
- private int maxQueueSize;
-
- private EnqueueMode enqueueMode;
-
- private ApplicationLayerParameters parameters;
-
- private Action DebugLog = null;
-
- public ASDUQueue(int maxQueueSize, EnqueueMode enqueueMode, ApplicationLayerParameters parameters, Action DebugLog)
- {
- enqueuedASDUs = new ASDUQueueEntry[maxQueueSize];
-
- for (int i = 0; i < maxQueueSize; i++)
- {
- enqueuedASDUs[i].asdu = new BufferFrame(new byte[260], 6);
- enqueuedASDUs[i].state = QueueEntryState.NOT_USED;
- }
-
- this.enqueueMode = enqueueMode;
- this.oldestQueueEntry = -1;
- this.latestQueueEntry = -1;
- this.numberOfAsduInQueue = 0;
- this.maxQueueSize = maxQueueSize;
- this.parameters = parameters;
- this.DebugLog = DebugLog;
- }
-
- public void EnqueueAsdu(ASDU asdu)
- {
- lock (enqueuedASDUs)
- {
-
- if (oldestQueueEntry == -1)
- {
- oldestQueueEntry = 0;
- latestQueueEntry = 0;
- numberOfAsduInQueue = 1;
-
- enqueuedASDUs[0].asdu.ResetFrame();
- asdu.Encode(enqueuedASDUs[0].asdu, parameters);
-
- enqueuedASDUs[0].entryTimestamp = SystemUtils.currentTimeMillis();
- enqueuedASDUs[0].state = QueueEntryState.WAITING_FOR_TRANSMISSION;
- }
- else
- {
- bool enqueue = true;
-
- if (numberOfAsduInQueue == maxQueueSize)
- {
- if (enqueueMode == EnqueueMode.REMOVE_OLDEST)
- {
- }
- else if (enqueueMode == EnqueueMode.IGNORE)
- {
- DebugLog("Queue is full. Ignore new ASDU.");
- enqueue = false;
- }
- else if (enqueueMode == EnqueueMode.THROW_EXCEPTION)
- {
- throw new ASDUQueueException("Event queue is full.");
- }
- }
-
- if (enqueue)
- {
- latestQueueEntry = (latestQueueEntry + 1) % maxQueueSize;
-
- if (latestQueueEntry == oldestQueueEntry)
- oldestQueueEntry = (oldestQueueEntry + 1) % maxQueueSize;
- else
- numberOfAsduInQueue++;
-
- enqueuedASDUs[latestQueueEntry].asdu.ResetFrame();
- asdu.Encode(enqueuedASDUs[latestQueueEntry].asdu, parameters);
-
- enqueuedASDUs[latestQueueEntry].entryTimestamp = SystemUtils.currentTimeMillis();
- enqueuedASDUs[latestQueueEntry].state = QueueEntryState.WAITING_FOR_TRANSMISSION;
- }
- }
- }
-
- DebugLog("Queue contains " + numberOfAsduInQueue + " messages (oldest: " + oldestQueueEntry + " latest: " + latestQueueEntry + ")");
- }
-
- public void LockASDUQueue()
- {
- Monitor.Enter(enqueuedASDUs);
- }
-
- public void UnlockASDUQueue()
- {
- Monitor.Exit(enqueuedASDUs);
- }
-
- public BufferFrame GetNextWaitingASDU(out long timestamp, out int index)
- {
- timestamp = 0;
- index = -1;
-
- if (enqueuedASDUs == null)
- return null;
-
- if (numberOfAsduInQueue > 0)
- {
-
- int currentIndex = oldestQueueEntry;
-
- while (enqueuedASDUs[currentIndex].state != QueueEntryState.WAITING_FOR_TRANSMISSION)
- {
-
- if (enqueuedASDUs[currentIndex].state == QueueEntryState.NOT_USED)
- break;
-
- currentIndex = (currentIndex + 1) % maxQueueSize;
-
- // break if we reached the oldest entry again
- if (currentIndex == oldestQueueEntry)
- break;
- }
-
- if (enqueuedASDUs[currentIndex].state == QueueEntryState.WAITING_FOR_TRANSMISSION)
- {
- enqueuedASDUs[currentIndex].state = QueueEntryState.SENT_BUT_NOT_CONFIRMED;
- timestamp = enqueuedASDUs[currentIndex].entryTimestamp;
- index = currentIndex;
- return enqueuedASDUs[currentIndex].asdu;
- }
-
- return null;
- }
-
- return null;
- }
-
- public void UnmarkAllASDUs()
- {
- lock (enqueuedASDUs)
- {
- if (numberOfAsduInQueue > 0)
- {
- for (int i = 0; i < enqueuedASDUs.Length; i++)
- {
- if (enqueuedASDUs[i].state == QueueEntryState.SENT_BUT_NOT_CONFIRMED)
- enqueuedASDUs[i].state = QueueEntryState.WAITING_FOR_TRANSMISSION;
- }
- }
- }
- }
-
- public void MarkASDUAsConfirmed(int index, long timestamp)
- {
- if (enqueuedASDUs == null)
- return;
-
- if ((index < 0) || (index > enqueuedASDUs.Length))
- return;
-
- lock (enqueuedASDUs)
- {
-
- if (numberOfAsduInQueue > 0)
- {
-
- if (enqueuedASDUs[index].state == QueueEntryState.SENT_BUT_NOT_CONFIRMED)
- {
-
- if (enqueuedASDUs[index].entryTimestamp == timestamp)
- {
-
- int currentIndex = index;
-
- while (enqueuedASDUs[currentIndex].state == QueueEntryState.SENT_BUT_NOT_CONFIRMED)
- {
-
- DebugLog("Remove from queue with index " + currentIndex);
-
- enqueuedASDUs[currentIndex].state = QueueEntryState.NOT_USED;
- enqueuedASDUs[currentIndex].entryTimestamp = 0;
- numberOfAsduInQueue -= 1;
-
- if (numberOfAsduInQueue == 0)
- {
- oldestQueueEntry = -1;
- latestQueueEntry = -1;
- break;
- }
-
- if (currentIndex == oldestQueueEntry)
- {
- oldestQueueEntry = (index + 1) % maxQueueSize;
-
- if (numberOfAsduInQueue == 1)
- latestQueueEntry = oldestQueueEntry;
-
- break;
- }
-
- currentIndex = currentIndex - 1;
-
- if (currentIndex < 0)
- currentIndex = maxQueueSize - 1;
-
- // break if we reached the first deleted entry again
- if (currentIndex == index)
- break;
-
- }
-
- DebugLog("queue state: noASDUs: " + numberOfAsduInQueue + " oldest: " + oldestQueueEntry + " latest: " + latestQueueEntry);
- }
- }
- }
- }
- }
- }
-
///
/// Representation of a redundancy group. A redundancy group is a group of connections that share a unique
/// event queue. Only one connection in a redundancy group can be active.
@@ -371,6 +170,7 @@ public RedundancyGroup()
{
}
+
///
/// Initializes a new instance of the class.
///
@@ -434,6 +234,7 @@ internal void AddConnection(ClientConnection connection)
connections.Add(connection);
}
+
internal void RemoveConnection(ClientConnection connection)
{
connections.Remove(connection);
@@ -454,7 +255,7 @@ internal bool Matches(IPAddress ipAddress)
}
}
}
-
+
return matches;
}
@@ -475,12 +276,10 @@ internal void Activate(ClientConnection activeConnection)
{
if (HasConnection(activeConnection))
{
-
foreach (ClientConnection connection in connections)
{
if (connection != activeConnection)
{
-
if (connection.IsActive)
{
server.CallConnectionEventHandler(connection, ClientConnectionEvent.INACTIVE);
@@ -488,7 +287,6 @@ internal void Activate(ClientConnection activeConnection)
}
}
}
-
}
}
@@ -521,14 +319,19 @@ public class Server : CS101.Slave
private Socket listeningSocket;
private int maxQueueSize = 1000;
+ private int maxHighPrioQueueSize = 1000;
private int maxOpenConnections = 10;
+ private static readonly SemaphoreSlim queueLock = new SemaphoreSlim(1, 1);
+
internal int? fileTimeout = null;
private int receiveTimeoutInMs = 1000; /* maximum allowed time between SOF byte and last message byte */
private List redGroups = new List();
+
+
private ServerMode serverMode = ServerMode.SINGLE_REDUNDANCY_GROUP;
///
@@ -541,6 +344,8 @@ public ServerMode ServerMode
set { serverMode = value; }
}
+
+
private EnqueueMode enqueueMode = EnqueueMode.REMOVE_OLDEST;
///
@@ -572,7 +377,7 @@ public int MaxQueueSize
{
get
{
- return this.maxQueueSize;
+ return maxQueueSize;
}
set
{
@@ -580,6 +385,23 @@ public int MaxQueueSize
}
}
+ ///
+ /// Gets or sets the maximum size of the ASDU high priotity queue. Setting this property has no
+ /// effect after calling the Start method.
+ ///
+ /// The size of the max high priotity queue.
+ public int MaxHighPrioQueueSize
+ {
+ get
+ {
+ return maxHighPrioQueueSize;
+ }
+ set
+ {
+ maxHighPrioQueueSize = value;
+ }
+ }
+
///
/// Gets or sets the maximum number of open TCP connections
///
@@ -588,7 +410,7 @@ public int MaxOpenConnections
{
get
{
- return this.maxOpenConnections;
+ return maxOpenConnections;
}
set
{
@@ -627,8 +449,9 @@ public APCIParameters GetAPCIParameters()
///
public Server()
{
- this.apciParameters = new APCIParameters();
- this.alParameters = new ApplicationLayerParameters();
+ apciParameters = new APCIParameters();
+ alParameters = new ApplicationLayerParameters();
+
}
///
@@ -637,13 +460,13 @@ public Server()
/// TLS layer configuation, or null when not using TLS
public Server(TlsSecurityInformation securityInfo)
{
- this.apciParameters = new APCIParameters();
- this.alParameters = new ApplicationLayerParameters();
+ apciParameters = new APCIParameters();
+ alParameters = new ApplicationLayerParameters();
this.securityInfo = securityInfo;
if (securityInfo != null)
- this.localPort = 19998;
+ localPort = 19998;
}
///
@@ -669,7 +492,7 @@ public Server(APCIParameters apciParameters, ApplicationLayerParameters alParame
this.securityInfo = securityInfo;
if (securityInfo != null)
- this.localPort = 19998;
+ localPort = 19998;
}
///
@@ -681,6 +504,19 @@ public void AddRedundancyGroup(RedundancyGroup redundancyGroup)
redGroups.Add(redundancyGroup);
}
+ public ServerStateEventHandler serverStateEventHandler = null;
+ public object serverStateEventHandlerParameter = null;
+ ///
+ /// Sets a callback handler for server state changes.
+ ///
+ /// Handler.
+ /// Parameter.
+ public void SetServerStateEventHandler(ServerStateEventHandler handler, object parameter)
+ {
+ this.serverStateEventHandler = handler;
+ this.serverStateEventHandlerParameter = parameter;
+ }
+
public ConnectionRequestHandler connectionRequestHandler = null;
public object connectionRequestHandlerParameter = null;
@@ -692,8 +528,8 @@ public void AddRedundancyGroup(RedundancyGroup redundancyGroup)
/// Parameter.
public void SetConnectionRequestHandler(ConnectionRequestHandler handler, object parameter)
{
- this.connectionRequestHandler = handler;
- this.connectionRequestHandlerParameter = parameter;
+ connectionRequestHandler = handler;
+ connectionRequestHandlerParameter = parameter;
}
private ConnectionEventHandler connectionEventHandler = null;
@@ -707,8 +543,8 @@ public void SetConnectionRequestHandler(ConnectionRequestHandler handler, object
/// Parameter.
public void SetConnectionEventHandler(ConnectionEventHandler handler, object parameter)
{
- this.connectionEventHandler = handler;
- this.connectionEventHandlerParameter = parameter;
+ connectionEventHandler = handler;
+ connectionEventHandlerParameter = parameter;
}
///
@@ -719,7 +555,7 @@ public int OpenConnections
{
get
{
- return this.allOpenConnections.Count;
+ return allOpenConnections.Count;
}
}
@@ -730,40 +566,95 @@ public int ReceiveTimeout
{
get
{
- return this.receiveTimeoutInMs;
+ return receiveTimeoutInMs;
}
set
{
- this.receiveTimeoutInMs = value;
+ receiveTimeoutInMs = value;
}
}
+ private int GetEntryCount(ASDUQueue queue)
+ {
+ int count = 0;
+ if (queue != null)
+ {
+ try
+ {
+ queueLock.Wait();
+ count = queue.NumberOfAsduInQueue;
+ queueLock.Release();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex.Message);
+ }
+ }
+
+ return count;
+ }
+
+ ///
+ /// Returns the number of elements in the low priority queue
+ ///
+ /// the redundancy group object (only required for ServerMode.MULTIPLE_REDUNDANCY_GROUPS
+ /// The number of ASDUs in the queue, or 0 when the queue is empty
+ public int GetNumberOfQueueEntries(RedundancyGroup redundancyGroup = null)
+ {
+ if (serverMode == ServerMode.CONNECTION_IS_REDUNDANCY_GROUP)
+ {
+ foreach (ClientConnection connection in allOpenConnections)
+ {
+ if (connection.IsActive)
+ {
+ return GetEntryCount(connection.GetASDUQueue());
+ }
+ }
+ }
+ else if (serverMode == ServerMode.MULTIPLE_REDUNDANCY_GROUPS)
+ {
+ if (redundancyGroup != null)
+ {
+ return GetEntryCount(redundancyGroup.asduQueue);
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ RedundancyGroup singleGroup = redGroups[0];
+ return GetEntryCount(singleGroup.asduQueue);
+ }
+
+ return 0;
+ }
+
private void ServerAcceptThread()
{
running = true;
+ CallServerStateEventHandler(new ServerStateEvent(true, false, null));
DebugLog("Waiting for connections...");
while (running)
{
-
try
{
-
Socket newSocket = listeningSocket.Accept();
if (newSocket != null)
{
-
newSocket.NoDelay = true;
DebugLog("New connection");
IPEndPoint ipEndPoint = (IPEndPoint)newSocket.RemoteEndPoint;
-
+
DebugLog(" from IP: " + ipEndPoint.Address.ToString());
-
+
bool acceptConnection = true;
if (OpenConnections >= maxOpenConnections)
@@ -776,7 +667,6 @@ private void ServerAcceptThread()
if (acceptConnection)
{
-
ClientConnection connection = null;
if ((serverMode == ServerMode.SINGLE_REDUNDANCY_GROUP) || (serverMode == ServerMode.MULTIPLE_REDUNDANCY_GROUPS))
@@ -803,7 +693,6 @@ private void ServerAcceptThread()
if (matchingGroup != null)
{
-
connection = new ClientConnection(newSocket, securityInfo, apciParameters, alParameters, this,
matchingGroup.asduQueue, debugOutput);
@@ -816,7 +705,6 @@ private void ServerAcceptThread()
DebugLog("Found no matching redundancy group -> close connection");
newSocket.Close();
}
-
}
else
{
@@ -830,18 +718,23 @@ private void ServerAcceptThread()
CallConnectionEventHandler(connection, ClientConnectionEvent.OPENED);
}
-
+
}
else
newSocket.Close();
}
-
}
- catch (Exception)
+ catch (Exception e)
{
- running = false;
+ DebugLog("Exception: " + e.Message);
+ bool socketExceptionDuringShutdown = !running && (e is SocketException);
+ if (!socketExceptionDuringShutdown)
+ {
+ running = false;
+ CallServerStateEventHandler(new ServerStateEvent(false, true, e));
+ }
}
-
+
}
}
@@ -867,7 +760,7 @@ internal void Remove(ClientConnection connection)
/// Local IP address or hostname to bind.
public void SetLocalAddress(string localAddress)
{
- this.localHostname = localAddress;
+ localHostname = localAddress;
}
///
@@ -876,7 +769,7 @@ public void SetLocalAddress(string localAddress)
/// Local TCP port to bind.
public void SetLocalPort(int tcpPort)
{
- this.localPort = tcpPort;
+ localPort = tcpPort;
}
///
@@ -888,7 +781,7 @@ public void Start()
IPEndPoint localEP = new IPEndPoint(ipAddress, localPort);
// Create a TCP/IP socket.
- listeningSocket = new Socket(AddressFamily.InterNetwork,
+ listeningSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
listeningSocket.Bind(localEP);
@@ -911,7 +804,7 @@ public void Start()
redGroups.Add(singleGroup);
}
}
-
+
if (serverMode == ServerMode.SINGLE_REDUNDANCY_GROUP || serverMode == ServerMode.MULTIPLE_REDUNDANCY_GROUPS)
{
foreach (RedundancyGroup redGroup in redGroups)
@@ -930,6 +823,7 @@ public void Start()
public void Stop()
{
running = false;
+ CallServerStateEventHandler(new ServerStateEvent(false, false, null));
try
{
@@ -937,9 +831,10 @@ public void Stop()
{
listeningSocket.Shutdown(SocketShutdown.Both);
}
- catch (SocketException ex)
+ catch (SocketException ex) // seems to be side effect of Shutdown(). No idea how to avoid it.
{
- DebugLog("SocketException: " + ex.Message);
+ DebugLog("SocketException: " + ex.Message + " with code " + ex.ErrorCode);
+ // CallServerStateEventHandler(new ServerStateEvent(false, false, ex));
}
listeningSocket.Close();
@@ -949,14 +844,16 @@ public void Stop()
{
connection.Close();
}
-
}
catch (Exception e)
{
DebugLog("Exception: " + e.Message);
+ CallServerStateEventHandler(new ServerStateEvent(false, false, e));
}
}
+
+
///
/// Check if the server is running (listening to client connections and handling connections) or not
///
@@ -975,6 +872,8 @@ public bool IsRunning()
/// when the ASDU queue is full and mode is EnqueueMode.THROW_EXCEPTION.
public void EnqueueASDU(ASDU asdu)
{
+ /*ASDUQueue queue = new ASDUQueue(MaxQueueSize, enqueueMode, alParameters, DebugLog);
+ this.lowPrioQueue = queue;*/
if (serverMode == ServerMode.CONNECTION_IS_REDUNDANCY_GROUP)
{
@@ -995,10 +894,24 @@ public void EnqueueASDU(ASDU asdu)
}
}
+ public IEnumerable GetAllOpenConnections()
+ {
+ return allOpenConnections.ToArray();
+ }
+
+ internal void CallServerStateEventHandler(ServerStateEvent e)
+ {
+ if (serverStateEventHandler != null)
+ serverStateEventHandler(serverStateEventHandlerParameter, e);
+ }
+
internal void CallConnectionEventHandler(ClientConnection connection, ClientConnectionEvent e)
{
- if (connectionEventHandler != null)
- connectionEventHandler(connectionEventHandlerParameter, connection, e);
+ // if (connection.State == MasterConnectionState.M_CON_STATE_STARTED)
+ // {
+ if (connectionEventHandler != null)
+ connectionEventHandler(connectionEventHandlerParameter, connection, e);
+ // }
}
internal void Activated(ClientConnection activeConnection)
@@ -1013,25 +926,34 @@ internal void Activated(ClientConnection activeConnection)
}
}
+
+ activeConnection.State = MasterConnectionState.M_CON_STATE_STARTED;
+
}
internal void Deactivated(ClientConnection activeConnection)
{
+
CallConnectionEventHandler(activeConnection, ClientConnectionEvent.INACTIVE);
+
+ activeConnection.State = MasterConnectionState.M_CON_STATE_UNCONFIRMED_STOPPED;
}
- public override int FileTimeout {
- get {
+ public override int FileTimeout
+ {
+ get
+ {
if (fileTimeout != null)
return FileTimeout;
else
return -1;
}
- set {
+ set
+ {
fileTimeout = value;
}
}
}
-
+
}
diff --git a/lib60870/CS104/TlsSecurityInformation.cs b/lib60870/CS104/TlsSecurityInformation.cs
index de19d13..f8b084f 100644
--- a/lib60870/CS104/TlsSecurityInformation.cs
+++ b/lib60870/CS104/TlsSecurityInformation.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,11 +19,10 @@
* See COPYING file for the complete license text.
*/
-using System;
-using System.Security.Cryptography.X509Certificates;
using System.Collections.Generic;
-using System.Security.Authentication;
using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
namespace lib60870.CS104
{
@@ -55,7 +54,7 @@ public bool ChainValidation
{
get
{
- return this.chainValidation;
+ return chainValidation;
}
set
{
@@ -71,7 +70,7 @@ public bool AllowOnlySpecificCertificates
{
get
{
- return this.allowOnlySpecificCertificates;
+ return allowOnlySpecificCertificates;
}
set
{
@@ -102,7 +101,7 @@ public X509Certificate2 OwnCertificate
{
get
{
- return this.ownCertificate;
+ return ownCertificate;
}
set
{
@@ -114,7 +113,7 @@ public List AllowedCertificates
{
get
{
- return this.otherCertificates;
+ return otherCertificates;
}
set
{
@@ -126,7 +125,7 @@ public List CaCertificates
{
get
{
- return this.caCertificates;
+ return caCertificates;
}
}
@@ -134,7 +133,7 @@ public string TargetHostName
{
get
{
- return this.targetHostName;
+ return targetHostName;
}
}
@@ -164,13 +163,13 @@ public SslProtocols TlsVersion
/// Gets or sets the certificate validation callback.
///
/// The certificate validation callback, or null to use the library internal certificate validation callbacks
- public RemoteCertificateValidationCallback CertificateValidationCallback
+ public RemoteCertificateValidationCallback CertificateValidationCallback
{
- get
+ get
{
return certificateValidationCallback;
}
- set
+ set
{
certificateValidationCallback = value;
}
diff --git a/lib60870/ConnectionException.cs b/lib60870/ConnectionException.cs
index 208bf95..4dc5ac6 100644
--- a/lib60870/ConnectionException.cs
+++ b/lib60870/ConnectionException.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
diff --git a/lib60870/Frame.cs b/lib60870/Frame.cs
index 2301e0c..0dc6d17 100644
--- a/lib60870/Frame.cs
+++ b/lib60870/Frame.cs
@@ -1,7 +1,7 @@
/*
* Frame.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,12 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-
namespace lib60870
{
///
diff --git a/lib60870/LibraryCommon.cs b/lib60870/LibraryCommon.cs
index 24cf596..5e46201 100644
--- a/lib60870/LibraryCommon.cs
+++ b/lib60870/LibraryCommon.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -19,8 +19,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870
{
@@ -58,6 +56,6 @@ public static string GetLibraryVersionString()
/// Raw message handler. Can be used to access the raw message.
/// Returns true when message should be handled by the protocol stack, false, otherwise.
///
- public delegate bool RawMessageHandler(object parameter,byte[] message,int messageSize);
+ public delegate bool RawMessageHandler(object parameter, byte[] message, int messageSize);
}
diff --git a/lib60870/LinkLayer/LinkLayer.cs b/lib60870/LinkLayer/LinkLayer.cs
index f2538af..fee6109 100644
--- a/lib60870/LinkLayer/LinkLayer.cs
+++ b/lib60870/LinkLayer/LinkLayer.cs
@@ -1,7 +1,7 @@
/*
* LinkLayer.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -22,7 +22,6 @@
*/
using System;
-using System.IO.Ports;
namespace lib60870.linklayer
{
@@ -30,7 +29,7 @@ namespace lib60870.linklayer
/// Will be called by the stack when the state of a link layer connection changes
///
/// Address of the slave (only used for unbalanced master mode)
- public delegate void LinkLayerStateChanged(object parameter,int address,LinkLayerState newState);
+ public delegate void LinkLayerStateChanged(object parameter, int address, LinkLayerState newState);
public enum LinkLayerState
{
@@ -104,7 +103,7 @@ public int AddressLength
{
get
{
- return this.addressLength;
+ return addressLength;
}
set
{
@@ -120,7 +119,7 @@ public int TimeoutForACK
{
get
{
- return this.timeoutForACK;
+ return timeoutForACK;
}
set
{
@@ -136,7 +135,7 @@ public long TimeoutRepeat
{
get
{
- return this.timeoutRepeat;
+ return timeoutRepeat;
}
set
{
@@ -152,11 +151,11 @@ public bool UseSingleCharACK
{
get
{
- return this.useSingleCharACK;
+ return useSingleCharACK;
}
set
{
- this.useSingleCharACK = value;
+ useSingleCharACK = value;
}
}
@@ -168,11 +167,11 @@ public int TimeoutLinkState
{
get
{
- return this.timeoutLinkState;
+ return timeoutLinkState;
}
set
{
- this.timeoutLinkState = value;
+ timeoutLinkState = value;
}
}
}
@@ -192,7 +191,7 @@ internal enum PrimaryLinkLayerState
internal class LinkLayer
{
protected Action DebugLog;
-
+
protected byte[] buffer;
/* byte buffer to receice and send frames */
@@ -217,9 +216,9 @@ internal class LinkLayer
public LinkLayer(byte[] buffer, LinkLayerParameters parameters, SerialTransceiverFT12 transceiver, Action debugLog)
{
this.buffer = buffer;
- this.linkLayerParameters = parameters;
+ linkLayerParameters = parameters;
this.transceiver = transceiver;
- this.DebugLog = debugLog;
+ DebugLog = debugLog;
}
public void SetReceivedRawMessageHandler(RawMessageHandler handler, object parameter)
@@ -276,7 +275,7 @@ public bool DIR
{
get
{
- return this.dir;
+ return dir;
}
set
{
@@ -308,7 +307,7 @@ public LinkLayerMode LinkLayerMode
{
get
{
- return this.linkLayerMode;
+ return linkLayerMode;
}
set
{
@@ -361,14 +360,14 @@ public void SendFixedFrame(byte fc, int address, bool prm, bool dir, bool acd, b
if (dfc)
c += 0x10;
- buffer[bufPos++] = (byte)c;
+ buffer[bufPos++] = c;
if (linkLayerParameters.AddressLength > 0)
{
buffer[bufPos++] = (byte)(address % 0x100);
if (linkLayerParameters.AddressLength > 1)
- buffer[bufPos++] = (byte)((address / 0x100) % 0x100);
+ buffer[bufPos++] = (byte)((address / 0x100) % 0x100);
}
byte checksum = 0;
@@ -414,7 +413,7 @@ public void SendVariableLengthFramePrimary(FunctionCodePrimary fc, int address,
buffer[bufPos++] = (byte)(address % 0x100);
if (linkLayerParameters.AddressLength > 1)
- buffer[bufPos++] = (byte)((address / 0x100) % 0x100);
+ buffer[bufPos++] = (byte)((address / 0x100) % 0x100);
}
byte[] userData = frame.GetBuffer();
@@ -474,7 +473,7 @@ internal void SendVariableLengthFrameSecondary(FunctionCodeSecondary fc, int add
buffer[bufPos++] = (byte)(address % 0x100);
if (linkLayerParameters.AddressLength > 1)
- buffer[bufPos++] = (byte)((address / 0x100) % 0x100);
+ buffer[bufPos++] = (byte)((address / 0x100) % 0x100);
}
byte[] userData = frame.GetBuffer();
@@ -508,7 +507,7 @@ internal void SendVariableLengthFrameSecondary(FunctionCodeSecondary fc, int add
private void ParseHeaderSecondaryUnbalanced(byte[] msg, int msgSize)
- {
+ {
int userDataLength = 0;
int userDataStart = 0;
byte c;
@@ -525,7 +524,7 @@ private void ParseHeaderSecondaryUnbalanced(byte[] msg, int msgSize)
return;
}
- userDataLength = (int)msg[1] - linkLayerParameters.AddressLength - 1;
+ userDataLength = msg[1] - linkLayerParameters.AddressLength - 1;
userDataStart = 5 + linkLayerParameters.AddressLength;
csStart = 4;
@@ -621,7 +620,7 @@ private void ParseHeaderSecondaryUnbalanced(byte[] msg, int msgSize)
DebugLog("ERROR: Received secondary message in unbalanced slave mode!");
return;
}
-
+
bool fcb = ((c & 0x20) == 0x20);
bool fcv = ((c & 0x10) == 0x10);
@@ -657,7 +656,7 @@ public void HandleMessageBalancedAndPrimaryUnbalanced(byte[] msg, int msgSize)
return;
}
- userDataLength = (int)msg[1] - linkLayerParameters.AddressLength - 1;
+ userDataLength = msg[1] - linkLayerParameters.AddressLength - 1;
userDataStart = 5 + linkLayerParameters.AddressLength;
csStart = 4;
@@ -740,7 +739,7 @@ public void HandleMessageBalancedAndPrimaryUnbalanced(byte[] msg, int msgSize)
}
else
- { /* we are primary link layer */
+ { /* we are primary link layer */
bool dir = ((c & 0x80) == 0x80); /* DIR - direction for balanced transmission */
bool dfc = ((c & 0x10) == 0x10); /* DFC - Data flow control */
bool acd = ((c & 0x20) == 0x20); /* ACD - access demand for class 1 data - for unbalanced transmission */
@@ -762,7 +761,7 @@ public void HandleMessageBalancedAndPrimaryUnbalanced(byte[] msg, int msgSize)
DebugLog("No primary link layer available!");
}
-
+
}
else
{ /* Single byte ACK */
@@ -821,15 +820,15 @@ public void Run()
if (primaryLinkLayer != null)
primaryLinkLayer.RunStateMachine();
else if (secondaryLinkLayer != null)
- secondaryLinkLayer.RunStateMachine();
+ secondaryLinkLayer.RunStateMachine();
}
}
- public void AddPortDeniedHandler (EventHandler eventHandler)
- {
+ public void AddPortDeniedHandler(EventHandler eventHandler)
+ {
transceiver.AddPortDeniedHandler(eventHandler);
- }
+ }
}
}
diff --git a/lib60870/LinkLayer/PrimaryLinkLayer.cs b/lib60870/LinkLayer/PrimaryLinkLayer.cs
index d2005c1..aaaca4a 100644
--- a/lib60870/LinkLayer/PrimaryLinkLayer.cs
+++ b/lib60870/LinkLayer/PrimaryLinkLayer.cs
@@ -1,7 +1,7 @@
/*
* PrimaryLinkLayer.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -22,7 +22,6 @@
*/
using System;
-using System.Collections.Generic;
namespace lib60870.linklayer
{
@@ -67,7 +66,7 @@ internal interface IPrimaryLinkLayerCallbacks
internal abstract class PrimaryLinkLayer
{
- public abstract void HandleMessage(FunctionCodeSecondary fcs, bool dir, bool dfc,
+ public abstract void HandleMessage(FunctionCodeSecondary fcs, bool dir, bool dfc,
int address, byte[] msg, int userDataStart, int userDataLength);
public abstract void RunStateMachine();
diff --git a/lib60870/LinkLayer/PrimaryLinkLayerBalanced.cs b/lib60870/LinkLayer/PrimaryLinkLayerBalanced.cs
index 88d4583..9dc8b52 100644
--- a/lib60870/LinkLayer/PrimaryLinkLayerBalanced.cs
+++ b/lib60870/LinkLayer/PrimaryLinkLayerBalanced.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -36,7 +36,7 @@ internal class PrimaryLinkLayerBalanced : PrimaryLinkLayer
private bool sendLinkLayerTestFunction = false;
private bool nextFcb = true;
- private BufferFrame lastSendASDU = null; // last send ASDU for message repetition after timeout
+ private BufferFrame lastSendASDU = null; /* last send ASDU for message repetition after timeout */
private int linkLayerAddressOtherStation = 0;
@@ -49,8 +49,8 @@ internal class PrimaryLinkLayerBalanced : PrimaryLinkLayer
public PrimaryLinkLayerBalanced(LinkLayer linkLayer, Func getUserData, Action debugLog)
{
- this.DebugLog = debugLog;
- this.GetUserData = getUserData;
+ DebugLog = debugLog;
+ GetUserData = getUserData;
this.linkLayer = linkLayer;
}
@@ -68,8 +68,8 @@ public LinkLayerState GetLinkLayerState()
public int LinkLayerAddressOtherStation
{
set
- {
- linkLayerAddressOtherStation = value;
+ {
+ linkLayerAddressOtherStation = value;
}
get
@@ -89,7 +89,7 @@ private void SetNewState(LinkLayerState newState)
}
}
- public override void HandleMessage(FunctionCodeSecondary fcs, bool dir, bool dfc,
+ public override void HandleMessage(FunctionCodeSecondary fcs, bool dir, bool dfc,
int address, byte[] msg, int userDataStart, int userDataLength)
{
PrimaryLinkLayerState newState = primaryState;
@@ -132,16 +132,18 @@ public override void HandleMessage(FunctionCodeSecondary fcs, bool dir, bool dfc
if (sendLinkLayerTestFunction)
sendLinkLayerTestFunction = false;
-
+
newState = PrimaryLinkLayerState.LINK_LAYERS_AVAILABLE;
SetNewState(LinkLayerState.AVAILABLE);
waitingForResponse = false;
}
- else if (primaryState == PrimaryLinkLayerState.EXECUTE_REQUEST_STATUS_OF_LINK) {
+ else if (primaryState == PrimaryLinkLayerState.EXECUTE_REQUEST_STATUS_OF_LINK)
+ {
DebugLog("PLL - ACK (FC 0) unexpected -> expected status-of-link (FC 11)");
}
- else {
+ else
+ {
waitingForResponse = false;
}
@@ -167,14 +169,14 @@ public override void HandleMessage(FunctionCodeSecondary fcs, bool dir, bool dfc
case FunctionCodeSecondary.RESP_NACK_NO_DATA:
- DebugLog ("PLL - RECV FC 09 - RESP NACK - NO DATA\n");
+ DebugLog("PLL - RECV FC 09 - RESP NACK - NO DATA\n");
newState = PrimaryLinkLayerState.IDLE;
SetNewState(LinkLayerState.ERROR);
break;
- case FunctionCodeSecondary.STATUS_OF_LINK_OR_ACCESS_DEMAND:
+ case FunctionCodeSecondary.STATUS_OF_LINK_OR_ACCESS_DEMAND:
DebugLog("PLL - RECV FC 11 - STATUS OF LINK");
if (primaryState == PrimaryLinkLayerState.EXECUTE_REQUEST_STATUS_OF_LINK)
@@ -252,6 +254,8 @@ public override void RunStateMachine()
if (waitingForResponse)
{
if (lastSendTime > currentTime)
+
+ /* last sent time not plausible! */
lastSendTime = currentTime;
if (currentTime > (lastSendTime + linkLayer.TimeoutForACK))
@@ -267,7 +271,7 @@ public override void RunStateMachine()
lastSendTime = currentTime;
waitingForResponse = true;
- newState = PrimaryLinkLayerState.EXECUTE_RESET_REMOTE_LINK;
+ newState = PrimaryLinkLayerState.EXECUTE_RESET_REMOTE_LINK;
}
break;
@@ -277,6 +281,8 @@ public override void RunStateMachine()
if (waitingForResponse)
{
if (lastSendTime > currentTime)
+
+ /* last sent time not plausible! */
lastSendTime = currentTime;
if (currentTime > (lastSendTime + linkLayer.TimeoutForACK))
@@ -297,14 +303,16 @@ public override void RunStateMachine()
case PrimaryLinkLayerState.LINK_LAYERS_AVAILABLE:
if (lastSendTime > currentTime)
- lastSendTime = currentTime;
- //TODO????
+ /* last sent time not plausible! */
+ lastSendTime = currentTime;
if (sendLinkLayerTestFunction)
{
DebugLog("PLL - SEND TEST LINK");
+
linkLayer.SendFixedFramePrimary(FunctionCodePrimary.TEST_FUNCTION_FOR_LINK, linkLayerAddressOtherStation, nextFcb, true);
+
nextFcb = !nextFcb;
lastSendTime = currentTime;
originalSendTime = lastSendTime;
@@ -316,11 +324,9 @@ public override void RunStateMachine()
if (asdu != null)
{
-
linkLayer.SendVariableLengthFramePrimary(FunctionCodePrimary.USER_DATA_CONFIRMED, linkLayerAddressOtherStation, nextFcb, true, asdu);
nextFcb = !nextFcb;
- lastSendASDU = asdu;
lastSendTime = currentTime;
originalSendTime = lastSendTime;
waitingForResponse = true;
@@ -333,6 +339,11 @@ public override void RunStateMachine()
case PrimaryLinkLayerState.EXECUTE_SERVICE_SEND_CONFIRM:
+ if (lastSendTime > currentTime)
+
+ /* last sent time not plausible! */
+ lastSendTime = currentTime;
+
if (currentTime > (lastSendTime + linkLayer.TimeoutForACK))
{
@@ -354,7 +365,8 @@ public override void RunStateMachine()
else
{
DebugLog("PLL - repeat last ASDU");
- linkLayer.SendVariableLengthFramePrimary(FunctionCodePrimary.USER_DATA_CONFIRMED, linkLayerAddressOtherStation, !nextFcb, true, lastSendASDU);
+ if(lastSendASDU != null)
+ linkLayer.SendVariableLengthFramePrimary(FunctionCodePrimary.USER_DATA_CONFIRMED, linkLayerAddressOtherStation, !nextFcb, true, lastSendASDU);
}
lastSendTime = currentTime;
@@ -364,7 +376,7 @@ public override void RunStateMachine()
break;
case PrimaryLinkLayerState.SECONDARY_LINK_LAYER_BUSY:
- //TODO - reject new requests from application layer?
+ //TODO - reject new requests from application layer?
break;
}
diff --git a/lib60870/LinkLayer/PrimaryLinkLayerUnbalanced.cs b/lib60870/LinkLayer/PrimaryLinkLayerUnbalanced.cs
index 79a73be..deb3d47 100644
--- a/lib60870/LinkLayer/PrimaryLinkLayerUnbalanced.cs
+++ b/lib60870/LinkLayer/PrimaryLinkLayerUnbalanced.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -113,7 +113,7 @@ public SlaveConnection(int address, LinkLayer linkLayer, Action debugLog
{
this.address = address;
this.linkLayer = linkLayer;
- this.DebugLog = debugLog;
+ DebugLog = debugLog;
this.linkLayerUnbalanced = linkLayerUnbalanced;
}
@@ -125,7 +125,7 @@ public bool IsMessageWaitingToSend()
return false;
}
- internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
+ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
int addr, byte[] msg, int userDataStart, int userDataLength)
{
PrimaryLinkLayerState newState = primaryState;
@@ -143,7 +143,7 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
newState = PrimaryLinkLayerState.EXECUTE_REQUEST_STATUS_OF_LINK;
break;
case PrimaryLinkLayerState.EXECUTE_SERVICE_SEND_CONFIRM:
- //TODO message must be handled and switched to BUSY state later!
+ //TODO message must be handled and switched to BUSY state later!
case PrimaryLinkLayerState.SECONDARY_LINK_LAYER_BUSY:
newState = PrimaryLinkLayerState.SECONDARY_LINK_LAYER_BUSY;
break;
@@ -171,7 +171,7 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
if (primaryState == PrimaryLinkLayerState.EXECUTE_RESET_REMOTE_LINK)
{
newState = PrimaryLinkLayerState.LINK_LAYERS_AVAILABLE;
-
+
SetState(LinkLayerState.AVAILABLE);
}
else if (primaryState == PrimaryLinkLayerState.EXECUTE_SERVICE_SEND_CONFIRM)
@@ -180,7 +180,7 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
if (sendLinkLayerTestFunction)
sendLinkLayerTestFunction = false;
- SetState(LinkLayerState.AVAILABLE);
+ SetState(LinkLayerState.AVAILABLE);
newState = PrimaryLinkLayerState.LINK_LAYERS_AVAILABLE;
}
@@ -200,14 +200,14 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
break;
case FunctionCodeSecondary.NACK:
-
+
DebugLog("[SLAVE " + address + "] PLL - received NACK");
-
+
if (primaryState == PrimaryLinkLayerState.EXECUTE_SERVICE_SEND_CONFIRM)
{
-
+
SetState(LinkLayerState.BUSY);
-
+
newState = PrimaryLinkLayerState.SECONDARY_LINK_LAYER_BUSY;
}
@@ -215,12 +215,12 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
break;
case FunctionCodeSecondary.STATUS_OF_LINK_OR_ACCESS_DEMAND:
-
+
DebugLog("[SLAVE " + address + "] PLL - received STATUS OF LINK");
if (primaryState == PrimaryLinkLayerState.EXECUTE_REQUEST_STATUS_OF_LINK)
{
-
+
DebugLog("[SLAVE " + address + "] PLL - SEND RESET REMOTE LINK");
linkLayer.SendFixedFramePrimary(FunctionCodePrimary.RESET_REMOTE_LINK, address, false, false);
@@ -235,7 +235,7 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
else
{ /* illegal message */
newState = PrimaryLinkLayerState.IDLE;
-
+
SetState(LinkLayerState.ERROR);
waitingForResponse = false;
@@ -244,7 +244,7 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
break;
case FunctionCodeSecondary.RESP_USER_DATA:
-
+
DebugLog("[SLAVE " + address + "] PLL - received USER DATA");
if (primaryState == PrimaryLinkLayerState.EXECUTE_SERVICE_REQUEST_RESPOND)
@@ -270,7 +270,7 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
break;
case FunctionCodeSecondary.RESP_NACK_NO_DATA:
-
+
DebugLog("[SLAVE " + address + "] PLL - received RESP NO DATA");
if (primaryState == PrimaryLinkLayerState.EXECUTE_SERVICE_REQUEST_RESPOND)
@@ -285,7 +285,7 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
else
{ /* illegal message */
newState = PrimaryLinkLayerState.IDLE;
-
+
SetState(LinkLayerState.ERROR);
}
@@ -295,7 +295,7 @@ internal void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
case FunctionCodeSecondary.LINK_SERVICE_NOT_FUNCTIONING:
case FunctionCodeSecondary.LINK_SERVICE_NOT_IMPLEMENTED:
-
+
DebugLog("[SLAVE " + address + "] PLL - link layer service not functioning/not implemented in secondary station ");
if (primaryState == PrimaryLinkLayerState.EXECUTE_SERVICE_SEND_CONFIRM)
@@ -331,15 +331,17 @@ public void RunStateMachine()
long currentTime = SystemUtils.currentTimeMillis();
- if (lastSendTime > currentTime)
- currentTime = lastSendTime;
-
switch (primaryState)
{
-
case PrimaryLinkLayerState.TIMEOUT:
- if (currentTime > (lastSendTime + linkLayer.linkLayerParameters.TimeoutLinkState)) {
+ if (lastSendTime > currentTime)
+
+ /* last sent time not plausible! */
+ lastSendTime = currentTime;
+
+ if (currentTime > (lastSendTime + linkLayer.linkLayerParameters.TimeoutLinkState))
+ {
newState = PrimaryLinkLayerState.IDLE;
}
@@ -350,11 +352,12 @@ public void RunStateMachine()
originalSendTime = 0;
sendLinkLayerTestFunction = false;
+ DebugLog("[SLAVE " + address + "] PLL - SEND FC 09 - REQUEST LINK STATUS\n");
+
linkLayer.SendFixedFramePrimary(FunctionCodePrimary.REQUEST_LINK_STATUS, address, false, false);
lastSendTime = currentTime;
waitingForResponse = true;
-
newState = PrimaryLinkLayerState.EXECUTE_REQUEST_STATUS_OF_LINK;
break;
@@ -363,30 +366,30 @@ public void RunStateMachine()
if (waitingForResponse)
{
-
+ if (lastSendTime > currentTime)
+
+ /* last sent time not plausible! */
+ lastSendTime = currentTime;
+
if (currentTime > (lastSendTime + linkLayer.TimeoutForACK))
{
waitingForResponse = false;
lastSendTime = currentTime;
-
newState = PrimaryLinkLayerState.TIMEOUT;
}
}
else
{
-
+
DebugLog("[SLAVE " + address + "] PLL - SEND RESET REMOTE LINK");
linkLayer.SendFixedFramePrimary(FunctionCodePrimary.RESET_REMOTE_LINK, address, false, false);
lastSendTime = currentTime;
- originalSendTime = lastSendTime;
waitingForResponse = true;
-
nextFcb = true;
-
- newState = PrimaryLinkLayerState.EXECUTE_RESET_REMOTE_LINK;
+ newState = PrimaryLinkLayerState.EXECUTE_RESET_REMOTE_LINK;
}
break;
@@ -395,10 +398,16 @@ public void RunStateMachine()
if (waitingForResponse)
{
+ if (lastSendTime > currentTime)
+
+ /* last sent time not plausible! */
+ lastSendTime = currentTime;
+
if (currentTime > (lastSendTime + linkLayer.TimeoutForACK))
{
waitingForResponse = false;
- newState = PrimaryLinkLayerState.IDLE;
+ lastSendTime = currentTime;
+ newState = PrimaryLinkLayerState.TIMEOUT;
SetState(LinkLayerState.ERROR);
}
@@ -422,7 +431,7 @@ public void RunStateMachine()
nextFcb = !nextFcb;
lastSendTime = currentTime;
- originalSendTime = lastSendTime;
+ originalSendTime = currentTime;
waitingForResponse = true;
newState = PrimaryLinkLayerState.EXECUTE_SERVICE_SEND_CONFIRM;
@@ -435,18 +444,21 @@ public void RunStateMachine()
DebugLog("[SLAVE " + address + "] PLL - SEND FC 10 - REQ UD 1");
linkLayer.SendFixedFramePrimary(FunctionCodePrimary.REQUEST_USER_DATA_CLASS_1, address, nextFcb, true);
+
+ requestClass1Data = false;
}
else
{
DebugLog("[SLAVE " + address + "] PLL - SEND FC 11 - REQ UD 2");
linkLayer.SendFixedFramePrimary(FunctionCodePrimary.REQUEST_USER_DATA_CLASS_2, address, nextFcb, true);
+
+ requestClass2Data = false;
}
nextFcb = !nextFcb;
-
lastSendTime = currentTime;
- originalSendTime = lastSendTime;
+ originalSendTime = currentTime;
waitingForResponse = true;
newState = PrimaryLinkLayerState.EXECUTE_SERVICE_REQUEST_RESPOND;
}
@@ -465,14 +477,12 @@ public void RunStateMachine()
linkLayer.SendVariableLengthFramePrimary(FunctionCodePrimary.USER_DATA_CONFIRMED, address, nextFcb, true, asdu);
- lastSentASDU = nextMessage;
- nextMessage = null;
-
+ //lastSentASDU = nextMessage;
+ //nextMessage = null;
nextFcb = !nextFcb;
-
lastSendTime = currentTime;
- originalSendTime = lastSendTime;
+ originalSendTime = currentTime;
waitingForResponse = true;
newState = PrimaryLinkLayerState.EXECUTE_SERVICE_SEND_CONFIRM;
@@ -484,13 +494,21 @@ public void RunStateMachine()
case PrimaryLinkLayerState.EXECUTE_SERVICE_SEND_CONFIRM:
+ if (lastSendTime > currentTime)
+
+ /* last sent time not plausible! */
+ lastSendTime = currentTime;
+
if (currentTime > (lastSendTime + linkLayer.TimeoutForACK))
{
if (currentTime > (originalSendTime + linkLayer.TimeoutRepeat))
{
DebugLog("[SLAVE " + address + "] TIMEOUT SC: ASDU not confirmed after repeated transmission");
- newState = PrimaryLinkLayerState.IDLE;
+
+ waitingForResponse = false;
+ lastSendTime = currentTime;
+ newState = PrimaryLinkLayerState.TIMEOUT;
SetState(LinkLayerState.ERROR);
}
@@ -504,7 +522,7 @@ public void RunStateMachine()
DebugLog("[SLAVE " + address + "] PLL - SEND FC 02 - RESET REMOTE LINK [REPEAT]");
linkLayer.SendFixedFramePrimary(FunctionCodePrimary.TEST_FUNCTION_FOR_LINK, address, !nextFcb, true);
-
+
}
else
{
@@ -512,7 +530,7 @@ public void RunStateMachine()
DebugLog("[SLAVE " + address + "] PLL - SEND FC 03 - USER DATA CONFIRMED [REPEAT]");
linkLayer.SendVariableLengthFramePrimary(FunctionCodePrimary.USER_DATA_CONFIRMED, address, !nextFcb, true, lastSentASDU);
-
+
}
lastSendTime = currentTime;
@@ -523,6 +541,11 @@ public void RunStateMachine()
case PrimaryLinkLayerState.EXECUTE_SERVICE_REQUEST_RESPOND:
+ if (lastSendTime > currentTime)
+
+ /* last sent time not plausible! */
+ lastSendTime = currentTime;
+
if (currentTime > (lastSendTime + linkLayer.TimeoutForACK))
{
@@ -552,7 +575,7 @@ public void RunStateMachine()
linkLayer.SendFixedFramePrimary(FunctionCodePrimary.REQUEST_USER_DATA_CLASS_2, address, !nextFcb, true);
}
-
+
lastSendTime = currentTime;
}
}
@@ -560,7 +583,7 @@ public void RunStateMachine()
break;
case PrimaryLinkLayerState.SECONDARY_LINK_LAYER_BUSY:
- //TODO - reject new requests from application layer?
+ //TODO - reject new requests from application layer?
break;
}
@@ -574,12 +597,12 @@ public void RunStateMachine()
}
/********************************
- * IPrimaryLinkLayerUnbalanced
- ********************************/
+ * IPrimaryLinkLayerUnbalanced
+ ********************************/
public void ResetCU(int slaveAddress)
- {
+ {
SlaveConnection slave = GetSlaveConnection(slaveAddress);
if (slave != null)
@@ -672,8 +695,8 @@ public PrimaryLinkLayerUnbalanced(LinkLayer linkLayer, IPrimaryLinkLayerCallback
{
this.linkLayer = linkLayer;
this.callbacks = callbacks;
- this.DebugLog = debugLog;
- this.slaveConnections = new List();
+ DebugLog = debugLog;
+ slaveConnections = new List();
}
private SlaveConnection GetSlaveConnection(int slaveAddres)
@@ -696,7 +719,7 @@ public void AddSlaveConnection(int slaveAddress)
}
public LinkLayerState GetStateOfSlave(int slaveAddress)
- {
+ {
SlaveConnection connection = GetSlaveConnection(slaveAddress);
if (connection != null)
@@ -705,7 +728,7 @@ public LinkLayerState GetStateOfSlave(int slaveAddress)
throw new ArgumentException("No slave with this address found");
}
- public override void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
+ public override void HandleMessage(FunctionCodeSecondary fcs, bool acd, bool dfc,
int address, byte[] msg, int userDataStart, int userDataLength)
{
SlaveConnection slave = null;
diff --git a/lib60870/LinkLayer/SecondaryLinkLayer.cs b/lib60870/LinkLayer/SecondaryLinkLayer.cs
index 04d24fb..1ba2187 100644
--- a/lib60870/LinkLayer/SecondaryLinkLayer.cs
+++ b/lib60870/LinkLayer/SecondaryLinkLayer.cs
@@ -1,7 +1,7 @@
/*
* SecondaryLinkLayer.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,6 @@
* See COPYING file for the complete license text.
*/
-using System;
-
namespace lib60870.linklayer
{
diff --git a/lib60870/LinkLayer/SecondaryLinkLayerBalanced.cs b/lib60870/LinkLayer/SecondaryLinkLayerBalanced.cs
index 954ae3e..c3f6b68 100644
--- a/lib60870/LinkLayer/SecondaryLinkLayerBalanced.cs
+++ b/lib60870/LinkLayer/SecondaryLinkLayerBalanced.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -38,9 +38,9 @@ public SecondaryLinkLayerBalanced(LinkLayer linkLayer, int address,
Func handleApplicationLayer, Action debugLog)
{
this.linkLayer = linkLayer;
- this.linkLayerAddress = address;
- this.DebugLog = debugLog;
- this.HandleApplicationLayer = handleApplicationLayer;
+ linkLayerAddress = address;
+ DebugLog = debugLog;
+ HandleApplicationLayer = handleApplicationLayer;
}
@@ -90,12 +90,12 @@ public override void HandleMessage(FunctionCodePrimary fcp, bool isBroadcast, in
linkLayer.SendSingleCharACK();
else
linkLayer.SendFixedFrameSecondary(FunctionCodeSecondary.ACK, linkLayerAddress, false, false);
-
+
break;
case FunctionCodePrimary.TEST_FUNCTION_FOR_LINK:
DebugLog("SLL -TEST FUNCTION FOR LINK");
- // TODO check if DCF has to be sent
+ // TODO check if DCF has to be sent
if (linkLayer.linkLayerParameters.UseSingleCharACK)
linkLayer.SendSingleCharACK();
else
diff --git a/lib60870/LinkLayer/SecondaryLinkLayerUnbalanced.cs b/lib60870/LinkLayer/SecondaryLinkLayerUnbalanced.cs
index 41bb280..04f7ef2 100644
--- a/lib60870/LinkLayer/SecondaryLinkLayerUnbalanced.cs
+++ b/lib60870/LinkLayer/SecondaryLinkLayerUnbalanced.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -37,8 +37,8 @@ internal class SecondaryLinkLayerUnbalanced : SecondaryLinkLayer
public SecondaryLinkLayerUnbalanced(LinkLayer linkLayer, int address, ISecondaryApplicationLayer applicationLayer, Action debugLog)
{
this.linkLayer = linkLayer;
- this.linkLayerAddress = address;
- this.DebugLog = debugLog;
+ linkLayerAddress = address;
+ DebugLog = debugLog;
this.applicationLayer = applicationLayer;
}
@@ -65,19 +65,20 @@ private bool CheckFCB(bool fcb)
public override void HandleMessage(FunctionCodePrimary fcp, bool isBroadcast, int address, bool fcb, bool fcv, byte[] msg, int userDataStart, int userDataLength)
{
- // check frame count bit if fcv == true
- if (fcv)
- {
- if (CheckFCB(fcb) == false)
- return;
- }
-
+
switch (fcp)
{
case FunctionCodePrimary.REQUEST_LINK_STATUS:
DebugLog("SLL - REQUEST LINK STATUS");
{
+ /* check that FCV=0 */
+ if (fcv)
+ {
+ DebugLog("SLL - REQUEST LINK STATUS failed - invalid FCV\n");
+ return;
+ }
+
bool accessDemand = applicationLayer.IsClass1DataAvailable();
linkLayer.SendFixedFrameSecondary(FunctionCodeSecondary.STATUS_OF_LINK_OR_ACCESS_DEMAND, linkLayerAddress, accessDemand, false);
@@ -87,6 +88,13 @@ public override void HandleMessage(FunctionCodePrimary fcp, bool isBroadcast, in
case FunctionCodePrimary.RESET_REMOTE_LINK:
DebugLog("SLL - RESET REMOTE LINK");
{
+ /* check that FCB=0 and FCV=0 */
+ if ((fcv) || (fcb))
+ {
+ DebugLog("SLL - RESET REMOTE LINK failed - invalid FCV/FCB\n");
+ return;
+ }
+
expectedFcb = true;
if (linkLayer.linkLayerParameters.UseSingleCharACK)
@@ -102,6 +110,15 @@ public override void HandleMessage(FunctionCodePrimary fcp, bool isBroadcast, in
case FunctionCodePrimary.RESET_FCB:
DebugLog("SLL - RESET FCB");
{
+ /* used by CS103 */
+
+ /* check that FCV=0 */
+ if ((fcv) || (fcb))
+ {
+ DebugLog("SLL - RESET FCB failed - invalid FCV/FCB");
+ return;
+ }
+
expectedFcb = true;
if (linkLayer.linkLayerParameters.UseSingleCharACK)
@@ -116,6 +133,14 @@ public override void HandleMessage(FunctionCodePrimary fcp, bool isBroadcast, in
case FunctionCodePrimary.REQUEST_USER_DATA_CLASS_2:
DebugLog("SLL - REQUEST USER DATA CLASS 2");
{
+ if (fcv)
+ {
+ if (CheckFCB(fcb) == false)
+ {
+ DebugLog("SLL - REQ UD2 - unexpected FCB\n");
+ }
+ }
+
BufferFrame asdu = applicationLayer.GetCLass2Data();
bool accessDemand = applicationLayer.IsClass1DataAvailable();
@@ -136,8 +161,15 @@ public override void HandleMessage(FunctionCodePrimary fcp, bool isBroadcast, in
case FunctionCodePrimary.REQUEST_USER_DATA_CLASS_1:
DebugLog("SLL - REQUEST USER DATA CLASS 1");
{
- BufferFrame asdu = applicationLayer.GetClass1Data();
+ if (fcv)
+ {
+ if (CheckFCB(fcb) == false)
+ {
+ DebugLog("SLL - REQ UD1 - unexpected FCB\n");
+ }
+ }
+ BufferFrame asdu = applicationLayer.GetClass1Data();
bool accessDemand = applicationLayer.IsClass1DataAvailable();
if (asdu != null)
@@ -155,23 +187,45 @@ public override void HandleMessage(FunctionCodePrimary fcp, bool isBroadcast, in
case FunctionCodePrimary.USER_DATA_CONFIRMED:
DebugLog("SLL - USER DATA CONFIRMED");
- if (userDataLength > 0)
{
- if (applicationLayer.HandleReceivedData(msg, isBroadcast, userDataStart, userDataLength))
- {
+ bool indicateUserData = true;
- bool accessDemand = applicationLayer.IsClass1DataAvailable();
+ if (fcv)
+ {
+ if (CheckFCB(fcb) == false)
+ {
+ DebugLog("SLL - FCB check failed -> ignore UD confirmed\n");
+ indicateUserData = false;
+ }
+ }
- linkLayer.SendFixedFrameSecondary(FunctionCodeSecondary.ACK, linkLayerAddress, accessDemand, false);
+ if ((indicateUserData == true) && (userDataLength) > 0)
+ {
+ applicationLayer.HandleReceivedData(msg, isBroadcast, userDataStart, userDataLength);
}
+
+ bool accessDemand = applicationLayer.IsClass1DataAvailable();
+
+ if (linkLayer.linkLayerParameters.UseSingleCharACK && !accessDemand)
+ linkLayer.SendSingleCharACK();
+ else
+ linkLayer.SendFixedFrameSecondary(FunctionCodeSecondary.ACK, linkLayerAddress, false, false);
+
}
break;
case FunctionCodePrimary.USER_DATA_NO_REPLY:
DebugLog("SLL - USER DATA NO REPLY");
- if (userDataLength > 0)
{
- applicationLayer.HandleReceivedData(msg, isBroadcast, userDataStart, userDataLength);
+ if (fcv)
+ {
+ DebugLog("SLL - USER DATA NO REPL - invalid FCV");
+ return;
+ }
+ if (userDataLength > 0)
+ {
+ applicationLayer.HandleReceivedData(msg, isBroadcast, userDataStart, userDataLength);
+ }
}
break;
diff --git a/lib60870/LinkLayer/SerialTransceiverFT12.cs b/lib60870/LinkLayer/SerialTransceiverFT12.cs
index 776ade8..2f16793 100644
--- a/lib60870/LinkLayer/SerialTransceiverFT12.cs
+++ b/lib60870/LinkLayer/SerialTransceiverFT12.cs
@@ -1,7 +1,7 @@
/*
* SerialTransceiverFT12.cs
*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -22,9 +22,8 @@
*/
using System;
-using System.IO.Ports;
using System.IO;
-using System.Threading;
+using System.IO.Ports;
namespace lib60870.linklayer
{
@@ -53,16 +52,16 @@ internal class SerialTransceiverFT12
public SerialTransceiverFT12(SerialPort port, LinkLayerParameters linkLayerParameters, Action debugLog)
{
this.port = port;
- this.serialStream = port.BaseStream;
- this.DebugLog = debugLog;
+ serialStream = port.BaseStream;
+ DebugLog = debugLog;
this.linkLayerParameters = linkLayerParameters;
}
public SerialTransceiverFT12(Stream serialStream, LinkLayerParameters linkLayerParameters, Action debugLog)
{
- this.port = null;
+ port = null;
this.serialStream = serialStream;
- this.DebugLog = debugLog;
+ DebugLog = debugLog;
this.linkLayerParameters = linkLayerParameters;
}
@@ -103,11 +102,11 @@ public void SendMessage(byte[] msg, int msgSize)
serialStream.Write(msg, 0, msgSize);
serialStream.Flush();
}
- catch(UnauthorizedAccessException)
+ catch (UnauthorizedAccessException)
{
}
-
+
}
// read the next block of the message
@@ -133,9 +132,9 @@ private int ReadBytesWithTimeout(byte[] buffer, int startIndex, int count, int t
catch (TimeoutException)
{
}
- catch(IOException ex)
+ catch (IOException ex)
{
- DebugLog("READ: IOException - " + ex.Message);
+ DebugLog("READ: IOException - " + ex.Message);
}
catch (UnauthorizedAccessException)
{
@@ -153,7 +152,7 @@ private int ReadBytesWithTimeout(byte[] buffer, int startIndex, int count, int t
private event EventHandler accessDenied = null;
- public void AddPortDeniedHandler (EventHandler eventHandler)
+ public void AddPortDeniedHandler(EventHandler eventHandler)
{
accessDenied += eventHandler;
}
diff --git a/lib60870/LinkLayer/TcpClientVirtualSerialPort.cs b/lib60870/LinkLayer/TcpClientVirtualSerialPort.cs
index 48e9e60..de237eb 100644
--- a/lib60870/LinkLayer/TcpClientVirtualSerialPort.cs
+++ b/lib60870/LinkLayer/TcpClientVirtualSerialPort.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -22,8 +22,8 @@
using System;
using System.IO;
-using System.Net.Sockets;
using System.Net;
+using System.Net.Sockets;
using System.Threading;
namespace lib60870.linklayer
@@ -68,7 +68,7 @@ public bool Connected
{
get
{
- return this.connected;
+ return connected;
}
}
@@ -76,7 +76,7 @@ public bool DebugOutput
{
get
{
- return this.debugOutput;
+ return debugOutput;
}
set
{
@@ -107,7 +107,7 @@ private void ConnectSocketWithTimeout()
}
catch (Exception)
{
- throw new SocketException(87); // wrong argument
+ throw new SocketException(87); /* wrong argument */
}
if (!running)
@@ -181,7 +181,7 @@ private void ConnectionThread()
connected = false;
- if (!this.running)
+ if (!running)
return;
if (socketStream != null)
@@ -208,7 +208,7 @@ private void ConnectionThread()
socketStream = null;
conSocket = null;
}
-
+
if (running)
Thread.Sleep(waitRetryConnect);
}
@@ -235,7 +235,7 @@ public void Stop()
if (running == true)
{
running = false;
- this.connected = false;
+ connected = false;
if (socketStream != null)
{
@@ -246,10 +246,10 @@ public void Stop()
if (conSocket != null)
{
-
+
try
{
- conSocket.Shutdown(SocketShutdown.Both);
+ conSocket.Shutdown(SocketShutdown.Both);
}
catch (SocketException)
{
@@ -266,8 +266,8 @@ public void Stop()
/*************************
- * Stream implementation
- */
+ * Stream implementation
+ */
public override int Read(byte[] buffer, int offset, int count)
{
@@ -289,7 +289,7 @@ public override int Read(byte[] buffer, int offset, int count)
catch (Exception e)
{
DebugLog("Socket error: " + e.ToString());
- this.connected = false;
+ connected = false;
return 0;
}
diff --git a/lib60870/LinkLayer/TcpServerVirtualSerialPort.cs b/lib60870/LinkLayer/TcpServerVirtualSerialPort.cs
index 28217bf..e589bbc 100644
--- a/lib60870/LinkLayer/TcpServerVirtualSerialPort.cs
+++ b/lib60870/LinkLayer/TcpServerVirtualSerialPort.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,8 +21,8 @@
using System;
using System.IO;
-using System.Net.Sockets;
using System.Net;
+using System.Net.Sockets;
using System.Threading;
namespace lib60870.linklayer
@@ -68,7 +68,7 @@ public bool DebugOutput
{
get
{
- return this.debugOutput;
+ return debugOutput;
}
set
{
@@ -82,8 +82,8 @@ public TcpServerVirtualSerialPort()
public void SetConnectionRequestHandler(TcpConnectionEventHandler handler, object parameter)
{
- this.connectionEventHandler = handler;
- this.connectionEventHandlerParameter = parameter;
+ connectionEventHandler = handler;
+ connectionEventHandlerParameter = parameter;
}
///
@@ -176,7 +176,7 @@ public void Start()
IPEndPoint localEP = new IPEndPoint(ipAddress, localPort);
// Create a TCP/IP socket.
- listeningSocket = new Socket(AddressFamily.InterNetwork,
+ listeningSocket = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
listeningSocket.Bind(localEP);
@@ -203,8 +203,8 @@ public void Stop()
/*************************
- * Stream implementation
- */
+ * Stream implementation
+ */
public override int Read(byte[] buffer, int offset, int count)
{
diff --git a/lib60870/SystemUtils.cs b/lib60870/SystemUtils.cs
index b1fc6c2..3759810 100644
--- a/lib60870/SystemUtils.cs
+++ b/lib60870/SystemUtils.cs
@@ -1,5 +1,5 @@
/*
- * Copyright 2016-2022 Michael Zillgith
+ * Copyright 2016-2025 Michael Zillgith
*
* This file is part of lib60870.NET
*
@@ -21,11 +21,6 @@
using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-
namespace lib60870
{
///
diff --git a/lib60870/images/icon.png b/lib60870/images/icon.png
new file mode 100644
index 0000000..a078d4c
Binary files /dev/null and b/lib60870/images/icon.png differ
diff --git a/lib60870/lib60870.csproj b/lib60870/lib60870.csproj
index 042ca42..d8e508f 100644
--- a/lib60870/lib60870.csproj
+++ b/lib60870/lib60870.csproj
@@ -1,13 +1,27 @@
Library
+ Michael Zillgith
+ MZ Automation
+ lib60870
+ 2.3.0
lib60870
lib60870
2.3.0
netstandard2.0
false
+ icon.png
+
+ lib60870.NET library for IEC 60870-5 based protocols in C#
+ The current implementation contains code for the IEC 60870-5-101/104 protocols only.
+
+ readme.md
+ nupkg
+ true
+ GPL-3.0-only
+
@@ -75,6 +89,8 @@
+
+
diff --git a/lib60870/readme.md b/lib60870/readme.md
new file mode 100644
index 0000000..052be0e
--- /dev/null
+++ b/lib60870/readme.md
@@ -0,0 +1,31 @@
+# README lib60870.NET v2.3.0
+
+lib60870.NET library for IEC 60870-5 based protocols in C#
+
+The current implementation contains code for the IEC 60870-5-101/104 protocols only.
+
+Please also consider the User Guide.
+
+
+## Compiling and running the examples:
+
+
+lib60870.NET:
+
+Open the provided solution file in the lib60870.NET folder with MonoDevelop or Visual Studio. You should be able to build and run the library and examples with any recent version of MonoDevelop or Visual Studio
+
+## Contact:
+
+For bug reports, hints or support please contact info@mz-automation.de
+
+## Licensing
+
+This software is licensed under the GPLv3 (https://www.gnu.org/licenses/gpl-3.0.en.html).
+
+## Commercial licenses and support
+
+Support and commercial license options are provided by MZ Automation GmbH. Please contact info@mz-automation.de for more details. Support for IEC 60870-5-103 (companion standard for protection devices) is only available with a commercial license.
+
+## Contributing
+
+If you want to contribute to the improvement and development of the library please send me comments, feature requests, bug reports, or patches. For more than trivial contributions I require you to sign a Contributor License Agreement. Please contact info@mz-automation.de.
diff --git a/release_script.sh b/release_script.sh
new file mode 100644
index 0000000..eb70f92
--- /dev/null
+++ b/release_script.sh
@@ -0,0 +1,102 @@
+#!/bin/bash
+
+###### Function to replace tabs with spaces in all .cs files################
+format_cs_files() {
+ echo "Formatting .cs files: Replacing tabs with spaces..."
+ find . -type f -name "*.cs" -exec sed -i 's/\t/ /g' {} +
+ echo "Formatting completed!"
+}
+
+#########Clean directory#########################
+clean_build_directories()
+{
+ echo "Removing ./vs, bin/, obj/ folders, any .git directories or files, and doxydoc.NET folder..."
+ find "$FOLDER_NAME" -type d \( -name "vs" -o -name "bin" -o -name "obj" -o -name "doxydoc.NET" -o -name ".git" \) -exec rm -rf {} +
+ find "$FOLDER_NAME" -type f -name ".git" -exec rm -f {} +
+ echo "Cleanup completed!"
+}
+
+#########Prepare release#############################
+# Function to prepare folder
+prepare_folder() {
+ #Create doxigen
+ rm -rf doxydoc.NET
+ #doxygen doxygen/Doxyfile
+
+ #Create user guide
+ asciidoctor user_guide_dotnet.adoc
+}
+
+##########Create release folder#####################
+create_release_folder()
+{
+ # Print the value
+ echo "Creating folder: $FOLDER_NAME"
+
+ # Create the folder
+ mkdir -p "$FOLDER_NAME"
+
+ echo "Folder '$FOLDER_NAME' created successfully!"
+
+ cp -rf .net8 $FOLDER_NAME
+ cp -rf examples $FOLDER_NAME
+ cp -rf lib60870 $FOLDER_NAME
+ cp -rf tests $FOLDER_NAME
+ cp -rf CHANGELOG $FOLDER_NAME
+ cp -rf COPYING $FOLDER_NAME
+ cp -rf lib60870.NET.sln $FOLDER_NAME
+ cp -rf README.md $FOLDER_NAME
+ cp -rf user_guide_dotnet.adoc $FOLDER_NAME
+ cp -rf doxygen $FOLDER_NAME
+}
+
+################ Function to create a tar.gz archive############################
+compress_to_tar() {
+ ARCHIVE_NAME="$FOLDER_NAME.tar.gz"
+ echo "Creating archive: $ARCHIVE_NAME"
+ tar -czf "$ARCHIVE_NAME" -C "$(dirname "$FOLDER_NAME")" "$(basename "$FOLDER_NAME")"
+ echo "Archive '$ARCHIVE_NAME' created successfully!"
+}
+
+# Wait for user input if arguments are missing
+while [ -z "$1" ]; do
+ read -p "Enter version: " VERSION_NAME_INPUT
+ set -- "$VERSION_NAME_INPUT" "$2"
+done
+
+while [ -z "$2" ]; do
+ read -p "Enter option ([0]prepare/[1]release/[2]formatFiles/[3]all): " OPTION_INPUT
+ set -- "$1" "$OPTION_INPUT"
+done
+
+# Store arguments
+PREFIX="../lib60870.NET-"
+FOLDER_NAME="${PREFIX}${1}"
+OPTION="$2"
+
+# Execute option case
+case "$OPTION" in
+ 0)
+ prepare_folder
+ ;;
+ 1)
+ create_release_folder
+ ;;
+2)
+ format_cs_files
+ ;;
+ 3)
+ format_cs_files
+ prepare_folder
+ create_release_folder
+ clean_build_directories
+ compress_to_tar
+ ;;
+ *)
+ echo "Invalid option. Use 'prepare', 'release', or 'delete'."
+ exit 1
+ ;;
+esac
+
+
+#####################################################
diff --git a/tests/Test.cs b/tests/Test.cs
index 5142edb..60f4c21 100644
--- a/tests/Test.cs
+++ b/tests/Test.cs
@@ -1,191 +1,214 @@
-using NUnit.Framework;
-using System;
-using System.Threading;
-using System.Net.Sockets;
-using lib60870;
+using lib60870;
using lib60870.CS101;
using lib60870.CS104;
+using NUnit.Framework;
+using System;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading;
namespace tests
{
- class TestInteger32Object : InformationObject, IPrivateIOFactory
- {
- private int value = 0;
-
- public TestInteger32Object ()
- : base (0)
- {
- }
-
- public TestInteger32Object(int ioa, int value)
- :base (ioa)
- {
- this.value = value;
- }
-
- public int Value {
- get {
- return this.value;
- }
- set {
- this.value = value;
- }
- }
-
- private TestInteger32Object (ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
- :base(parameters, msg, startIndex, isSequence)
- {
- if (!isSequence)
- startIndex += parameters.SizeOfIOA; /* skip IOA */
-
- value = msg [startIndex++];
- value += ((int)msg [startIndex++] * 0x100);
- value += ((int)msg [startIndex++] * 0x10000);
- value += ((int)msg [startIndex++] * 0x1000000);
- }
-
- public override bool SupportsSequence {
- get {
- return true;
- }
- }
-
- public override TypeID Type {
- get {
- return (TypeID)41;
- }
- }
-
- InformationObject IPrivateIOFactory.Decode (ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
- {
- return new TestInteger32Object (parameters, msg, startIndex, isSequence);
- }
-
- public override int GetEncodedSize()
- {
- return 4;
- }
-
- public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence) {
- base.Encode(frame, parameters, isSequence);
-
- frame.SetNextByte((byte) (value % 0x100));
- frame.SetNextByte((byte) ((value / 0x100) % 0x100));
- frame.SetNextByte((byte) ((value / 0x10000) % 0x100));
- frame.SetNextByte((byte) (value / 0x1000000));
- }
- }
-
-
-
- [TestFixture ()]
+
+ class TestInteger32Object : InformationObject, IPrivateIOFactory
+ {
+ private int value = 0;
+
+ public TestInteger32Object()
+ : base(0)
+ {
+ }
+
+ public TestInteger32Object(int ioa, int value)
+ : base(ioa)
+ {
+ this.value = value;
+ }
+
+ public int Value
+ {
+ get
+ {
+ return value;
+ }
+ set
+ {
+ this.value = value;
+ }
+ }
+
+ private TestInteger32Object(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
+ : base(parameters, msg, startIndex, isSequence)
+ {
+ if (!isSequence)
+ startIndex += parameters.SizeOfIOA; /* skip IOA */
+
+ value = msg[startIndex++];
+ value += (msg[startIndex++] * 0x100);
+ value += (msg[startIndex++] * 0x10000);
+ value += (msg[startIndex++] * 0x1000000);
+ }
+
+ public override bool SupportsSequence
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public override TypeID Type
+ {
+ get
+ {
+ return (TypeID)41;
+ }
+ }
+
+ InformationObject IPrivateIOFactory.Decode(ApplicationLayerParameters parameters, byte[] msg, int startIndex, bool isSequence)
+ {
+ return new TestInteger32Object(parameters, msg, startIndex, isSequence);
+ }
+
+ public override int GetEncodedSize()
+ {
+ return 4;
+ }
+
+ public override void Encode(Frame frame, ApplicationLayerParameters parameters, bool isSequence)
+ {
+ base.Encode(frame, parameters, isSequence);
+
+ frame.SetNextByte((byte)(value % 0x100));
+ frame.SetNextByte((byte)((value / 0x100) % 0x100));
+ frame.SetNextByte((byte)((value / 0x10000) % 0x100));
+ frame.SetNextByte((byte)(value / 0x1000000));
+ }
+ }
+
+ [TestFixture()]
public class Test
{
- [Test ()]
- public void TestStatusAndStatusChangedDetection ()
+ private int port = 24000;
+ private static readonly object _lockObject = new object();
+
+ private int GetPort()
+ {
+ int newPort;
+
+ lock (_lockObject)
+ {
+ port++;
+ newPort = port;
+ }
+
+ return newPort;
+ }
+
+ [Test()]
+ public void TestStatusAndStatusChangedDetection()
{
- StatusAndStatusChangeDetection scd = new StatusAndStatusChangeDetection ();
- Assert.AreEqual (false, scd.ST (0));
- Assert.AreEqual (false, scd.ST (15));
- Assert.AreEqual (false, scd.CD (0));
- Assert.AreEqual (false, scd.CD (15));
+ StatusAndStatusChangeDetection scd = new StatusAndStatusChangeDetection();
+
+ Assert.AreEqual(false, scd.ST(0));
+ Assert.AreEqual(false, scd.ST(15));
+ Assert.AreEqual(false, scd.CD(0));
+ Assert.AreEqual(false, scd.CD(15));
- Assert.AreEqual (false, scd.CD (1));
+ Assert.AreEqual(false, scd.CD(1));
- scd.CD (0, true);
+ scd.CD(0, true);
- Assert.AreEqual (true, scd.CD (0));
- Assert.AreEqual (false, scd.CD (1));
+ Assert.AreEqual(true, scd.CD(0));
+ Assert.AreEqual(false, scd.CD(1));
- scd.CD (15, true);
+ scd.CD(15, true);
- Assert.AreEqual (true, scd.CD (15));
- Assert.AreEqual (false, scd.CD (14));
+ Assert.AreEqual(true, scd.CD(15));
+ Assert.AreEqual(false, scd.CD(14));
}
- [Test ()]
- public void TestBCR ()
+ [Test()]
+ public void TestBCR()
{
- BinaryCounterReading bcr = new BinaryCounterReading ();
+ BinaryCounterReading bcr = new BinaryCounterReading();
bcr.Value = 1000;
- Assert.AreEqual (1000, bcr.Value);
+ Assert.AreEqual(1000, bcr.Value);
bcr.Value = -1000;
- Assert.AreEqual (-1000, bcr.Value);
+ Assert.AreEqual(-1000, bcr.Value);
bcr.SequenceNumber = 31;
- Assert.AreEqual (31, bcr.SequenceNumber);
+ Assert.AreEqual(31, bcr.SequenceNumber);
bcr.SequenceNumber = 0;
- Assert.AreEqual (0, bcr.SequenceNumber);
+ Assert.AreEqual(0, bcr.SequenceNumber);
/* Out of range sequenceNumber */
bcr.SequenceNumber = 32;
- Assert.AreEqual (0, bcr.SequenceNumber);
+ Assert.AreEqual(0, bcr.SequenceNumber);
- bcr = new BinaryCounterReading ();
+ bcr = new BinaryCounterReading();
bcr.Invalid = true;
- Assert.AreEqual (true, bcr.Invalid);
- Assert.AreEqual (false, bcr.Carry);
- Assert.AreEqual (false, bcr.Adjusted);
- Assert.AreEqual (0, bcr.SequenceNumber);
- Assert.AreEqual (0, bcr.Value);
+ Assert.AreEqual(true, bcr.Invalid);
+ Assert.AreEqual(false, bcr.Carry);
+ Assert.AreEqual(false, bcr.Adjusted);
+ Assert.AreEqual(0, bcr.SequenceNumber);
+ Assert.AreEqual(0, bcr.Value);
- bcr = new BinaryCounterReading ();
+ bcr = new BinaryCounterReading();
bcr.Carry = true;
- Assert.AreEqual (false, bcr.Invalid);
- Assert.AreEqual (true, bcr.Carry);
- Assert.AreEqual (false, bcr.Adjusted);
- Assert.AreEqual (0, bcr.SequenceNumber);
- Assert.AreEqual (0, bcr.Value);
+ Assert.AreEqual(false, bcr.Invalid);
+ Assert.AreEqual(true, bcr.Carry);
+ Assert.AreEqual(false, bcr.Adjusted);
+ Assert.AreEqual(0, bcr.SequenceNumber);
+ Assert.AreEqual(0, bcr.Value);
- bcr = new BinaryCounterReading ();
+ bcr = new BinaryCounterReading();
bcr.Adjusted = true;
- Assert.AreEqual (false, bcr.Invalid);
- Assert.AreEqual (false, bcr.Carry);
- Assert.AreEqual (true, bcr.Adjusted);
- Assert.AreEqual (0, bcr.SequenceNumber);
- Assert.AreEqual (0, bcr.Value);
-
-
+ Assert.AreEqual(false, bcr.Invalid);
+ Assert.AreEqual(false, bcr.Carry);
+ Assert.AreEqual(true, bcr.Adjusted);
+ Assert.AreEqual(0, bcr.SequenceNumber);
+ Assert.AreEqual(0, bcr.Value);
}
- [Test ()]
- public void TestScaledValue ()
+ [Test()]
+ public void TestScaledValue()
{
- ScaledValue scaledValue = new ScaledValue (0);
+ ScaledValue scaledValue = new ScaledValue(0);
- Assert.AreEqual (0, scaledValue.Value);
- Assert.AreEqual ((short)0, scaledValue.ShortValue);
+ Assert.AreEqual(0, scaledValue.Value);
+ Assert.AreEqual((short)0, scaledValue.ShortValue);
- scaledValue = new ScaledValue (32767);
- Assert.AreEqual (32767, scaledValue.Value);
- Assert.AreEqual ((short)32767, scaledValue.ShortValue);
+ scaledValue = new ScaledValue(32767);
+ Assert.AreEqual(32767, scaledValue.Value);
+ Assert.AreEqual((short)32767, scaledValue.ShortValue);
- scaledValue = new ScaledValue (32768);
- Assert.AreEqual (32767, scaledValue.Value);
- Assert.AreEqual ((short)32767, scaledValue.ShortValue);
+ scaledValue = new ScaledValue(32768);
+ Assert.AreEqual(32767, scaledValue.Value);
+ Assert.AreEqual((short)32767, scaledValue.ShortValue);
- scaledValue = new ScaledValue (-32768);
- Assert.AreEqual (-32768, scaledValue.Value);
- Assert.AreEqual ((short)-32768, scaledValue.ShortValue);
+ scaledValue = new ScaledValue(-32768);
+ Assert.AreEqual(-32768, scaledValue.Value);
+ Assert.AreEqual((short)-32768, scaledValue.ShortValue);
- scaledValue = new ScaledValue (-32769);
- Assert.AreEqual (-32768, scaledValue.Value);
- Assert.AreEqual ((short)-32768, scaledValue.ShortValue);
+ scaledValue = new ScaledValue(-32769);
+ Assert.AreEqual(-32768, scaledValue.Value);
+ Assert.AreEqual((short)-32768, scaledValue.ShortValue);
scaledValue = new ScaledValue(-1);
Assert.AreEqual(-1, scaledValue.Value);
@@ -194,1566 +217,3126 @@ public void TestScaledValue ()
Assert.AreEqual(-300, scaledValue.Value);
}
- [Test ()]
- public void TestSetpointCommandNormalized ()
+ [Test()]
+ public void TestReadCommand()
{
- SetpointCommandNormalized sc = new SetpointCommandNormalized (102, -0.5f,
- new SetpointCommandQualifier (true, 0));
+ ReadCommand rc = new ReadCommand(101);
- Assert.AreEqual (102, sc.ObjectAddress);
+ Assert.AreEqual(101, rc.ObjectAddress);
- Assert.AreEqual (-0.5f, sc.NormalizedValue, 0.001f);
+ rc = new ReadCommand(102);
- Assert.AreEqual (true, sc.QOS.Select);
-
- sc = new SetpointCommandNormalized (102, 32767, new SetpointCommandQualifier (true, 0));
-
- Assert.AreEqual (1.0, sc.NormalizedValue, 0.001f);
-
- Assert.AreEqual (32767, sc.RawValue);
+ Assert.AreEqual(102, rc.ObjectAddress);
+ }
- sc = new SetpointCommandNormalized (102, -32768, new SetpointCommandQualifier (true, 0));
+ [Test()]
+ public void TestTestCommand()
+ {
+ TestCommand tc = new TestCommand();
- Assert.AreEqual (-1.0, sc.NormalizedValue, 0.001f);
+ Assert.IsTrue(tc.Valid);
- Assert.AreEqual (-32768, sc.RawValue);
}
- [Test ()]
- public void TestStepPositionInformation ()
+ public void TestClockSynchronizationCommand()
{
- StepPositionInformation spi = new StepPositionInformation (103, 27, false, new QualityDescriptor ());
+ DateTime dateTime = DateTime.UtcNow;
- Assert.IsFalse (spi.Transient);
- Assert.NotNull (spi.Quality);
+ CP56Time2a time = new CP56Time2a(dateTime);
- spi = null;
+ ClockSynchronizationCommand csc = new ClockSynchronizationCommand(101, time);
- try {
- spi = new StepPositionInformation (103, 64, false, new QualityDescriptor ());
- } catch (ArgumentOutOfRangeException) {
- }
+ Assert.AreEqual(101, csc.ObjectAddress);
+ Assert.AreEqual(time.Year, csc.NewTime.Year);
+ Assert.AreEqual(time.Month, csc.NewTime.Month);
+ Assert.AreEqual(time.DayOfMonth, csc.NewTime.DayOfMonth);
+ Assert.AreEqual(time.Minute, csc.NewTime.Minute);
+ Assert.AreEqual(time.Second, csc.NewTime.Second);
+ Assert.AreEqual(time.Millisecond, csc.NewTime.Millisecond);
- Assert.IsNull (spi);
+ csc = new ClockSynchronizationCommand(102, time);
- try {
- spi = new StepPositionInformation (103, -65, false, new QualityDescriptor ());
- } catch (ArgumentOutOfRangeException) {
- }
+ Assert.AreEqual(102, csc.ObjectAddress);
+ Assert.AreEqual(time.Year, csc.NewTime.Year);
+ Assert.AreEqual(time.Month, csc.NewTime.Month);
+ Assert.AreEqual(time.DayOfMonth, csc.NewTime.DayOfMonth);
+ Assert.AreEqual(time.Minute, csc.NewTime.Minute);
+ Assert.AreEqual(time.Second, csc.NewTime.Second);
+ Assert.AreEqual(time.Millisecond, csc.NewTime.Millisecond);
}
- [Test ()]
- //[Ignore("Ignore to save execution time")]
- public void TestConnectWhileAlreadyConnected ()
+ [Test()]
+ public void TestResetProcessCommand()
{
- ApplicationLayerParameters parameters = new ApplicationLayerParameters ();
- APCIParameters apciParameters = new APCIParameters ();
+ ResetProcessCommand rp = new ResetProcessCommand(101, 0);
- Server server = new Server (apciParameters, parameters);
+ Assert.AreEqual(101, rp.ObjectAddress);
+ Assert.AreEqual(0, rp.QRP);
- server.SetLocalPort (20213);
+ rp = new ResetProcessCommand(102, 1);
- server.Start ();
-
- Connection connection = new Connection ("127.0.0.1", 20213, apciParameters, parameters);
-
- ConnectionException se = null;
+ Assert.AreEqual(102, rp.ObjectAddress);
+ Assert.AreEqual(1, rp.QRP);
+ }
- try {
- connection.Connect ();
- } catch (ConnectionException ex) {
- se = ex;
- }
+ [Test()]
+ public void TestDelayAcquisitionCommand()
+ {
+ CP16Time2a time = new CP16Time2a(24123);
- Assert.IsNull (se);
+ DelayAcquisitionCommand da = new DelayAcquisitionCommand(101, time);
- Thread.Sleep (100);
+ Assert.AreEqual(101, da.ObjectAddress);
+ Assert.AreEqual(24123, da.Delay.ElapsedTimeInMs);
+ }
- try {
- connection.Connect ();
- } catch (ConnectionException ex) {
- se = ex;
- }
+ [Test()]
+ public void TestBitString32()
+ {
+ Bitstring32 bs = new Bitstring32(101, 0xaaaa_aaaa, new QualityDescriptor());
- Assert.IsNotNull (se);
- Assert.AreEqual (se.Message, "already connected");
- Assert.AreEqual (10056, ((SocketException)se.InnerException).ErrorCode);
+ Assert.AreEqual(101, bs.ObjectAddress);
+ Assert.AreEqual(0xaaaa_aaaa, bs.Value);
- connection.Close ();
+ bs = new Bitstring32(101, 0xffff_0000, new QualityDescriptor());
- server.Stop ();
+ Assert.AreEqual(101, bs.ObjectAddress);
+ Assert.AreEqual(0xffff_0000, bs.Value);
}
[Test()]
- //[Ignore("Ignore to save execution time")]
- public void TestSendIMessageAfterStopDT()
+ public void TestBitString32CommandWithCP56Time2a()
{
- ApplicationLayerParameters parameters = new ApplicationLayerParameters();
- APCIParameters apciParameters = new APCIParameters();
+ DateTime dateTime = DateTime.UtcNow;
- Server server = new Server(apciParameters, parameters);
+ CP56Time2a time = new CP56Time2a(dateTime);
- server.SetLocalPort(20213);
+ Bitstring32CommandWithCP56Time2a bsc = new Bitstring32CommandWithCP56Time2a(101, 0x00000000, time);
- server.Start();
+ Assert.AreEqual(101, bsc.ObjectAddress);
+ Assert.AreEqual(0x00000000, bsc.Value);
- Connection connection = new Connection("127.0.0.1", 20213, apciParameters, parameters);
+ bsc = new Bitstring32CommandWithCP56Time2a(101, 0x12345678, time);
- ConnectionException se = null;
+ Assert.AreEqual(101, bsc.ObjectAddress);
+ Assert.AreEqual(0x12345678, bsc.Value);
+ Assert.AreEqual(time.Year, bsc.Timestamp.Year);
+ Assert.AreEqual(time.Month, bsc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, bsc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, bsc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, bsc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, bsc.Timestamp.Millisecond);
+ }
- try
- {
- connection.Connect();
+ [Test()]
+ public void TestEventOfProtectionEquipmentWithTime()
+ {
+ CP16Time2a elapsedTime = new CP16Time2a(24123);
+ CP24Time2a timestamp = new CP24Time2a(45, 23, 538);
- connection.SendStartDT();
+ EventOfProtectionEquipment e = new EventOfProtectionEquipment(101, new SingleEvent(), elapsedTime, timestamp);
- Thread.Sleep(200);
+ Assert.AreEqual(101, e.ObjectAddress);
+ Assert.AreEqual(24123, e.ElapsedTime.ElapsedTimeInMs);
+ Assert.AreEqual(45, e.Timestamp.Minute);
+ Assert.AreEqual(23, e.Timestamp.Second);
+ Assert.AreEqual(538, e.Timestamp.Millisecond);
+ }
- connection.SendStopDT();
+ [Test()]
+ public void TestInterrogationCommand()
+ {
+ InterrogationCommand ic = new InterrogationCommand(101, 20);
- // send command (should trigger server disconnect)
- connection.SendControlCommand(CauseOfTransmission.ACTIVATION, 1, new SingleCommand(5000, true, false, 0));
+ Assert.AreEqual(101, ic.ObjectAddress);
+ Assert.AreEqual(20, ic.QOI);
- Thread.Sleep(500);
+ ic = new InterrogationCommand(101, 21);
- // send command (should throw exception - not connected)
- connection.SendControlCommand(CauseOfTransmission.ACTIVATION, 1, new SingleCommand(5000, true, false, 0));
- }
- catch (ConnectionException ex)
- {
- se = ex;
- }
+ Assert.AreEqual(101, ic.ObjectAddress);
+ Assert.AreEqual(21, ic.QOI);
- Assert.IsNotNull(se);
- Assert.AreEqual(se.Message, "not connected");
- Assert.AreEqual(10057, ((SocketException)se.InnerException).ErrorCode);
+ ic = new InterrogationCommand(101, 24);
- server.Stop();
+ Assert.AreEqual(101, ic.ObjectAddress);
+ Assert.AreEqual(24, ic.QOI);
}
- [Test ()]
- //[Ignore("Ignore to save execution time")]
- public void TestConnectSameConnectionMultipleTimes ()
+ [Test()]
+ public void TestCounterInterrogationCommand()
{
- ApplicationLayerParameters parameters = new ApplicationLayerParameters ();
- APCIParameters apciParameters = new APCIParameters ();
+ CounterInterrogationCommand cic = new CounterInterrogationCommand(101, 20);
- Server server = new Server (apciParameters, parameters);
+ Assert.AreEqual(101, cic.ObjectAddress);
+ Assert.AreEqual(20, cic.QCC);
- server.SetLocalPort (20213);
+ cic = new CounterInterrogationCommand(101, 21);
- server.Start ();
+ Assert.AreEqual(101, cic.ObjectAddress);
+ Assert.AreEqual(21, cic.QCC);
- Connection connection = new Connection ("127.0.0.1", 20213, apciParameters, parameters);
+ cic = new CounterInterrogationCommand(101, 24);
- SocketException se = null;
-
- try {
- connection.Connect ();
-
- connection.Close ();
- } catch (SocketException ex) {
- se = ex;
- }
+ Assert.AreEqual(101, cic.ObjectAddress);
+ Assert.AreEqual(24, cic.QCC);
+ }
- Assert.IsNull (se);
+ [Test()]
+ public void TestSetpointCommandShort()
+ {
+ SetpointCommandShort sc = new SetpointCommandShort(101, 10.5f, new SetpointCommandQualifier(true, 0));
- try {
- connection.Connect ();
+ Assert.AreEqual(101, sc.ObjectAddress);
+ Assert.AreEqual(10.5f, sc.Value, 0.001f);
+ Assert.AreEqual(true, sc.QOS.Select);
- connection.Close ();
- } catch (SocketException ex) {
- se = ex;
- }
+ sc = new SetpointCommandShort(102, 1.0f, new SetpointCommandQualifier(true, 0));
- Assert.Null (se);
+ Assert.AreEqual(102, sc.ObjectAddress);
+ Assert.AreEqual(1.0f, sc.Value, 0.001f);
+ Assert.AreEqual(true, sc.QOS.Select);
- connection.Close ();
+ sc = new SetpointCommandShort(102, -1.0f, new SetpointCommandQualifier(true, 0));
- server.Stop ();
+ Assert.AreEqual(102, sc.ObjectAddress);
+ Assert.AreEqual(-1.0f, sc.Value, 0.001f);
+ Assert.AreEqual(true, sc.QOS.Select);
}
[Test()]
- //[Ignore("Ignore to save execution time")]
- public void TestConnectSameConnectionMultipleTimesServerDisconnects()
+ public void TestSetpointCommandShortWithCP56Time2a()
{
- ApplicationLayerParameters parameters = new ApplicationLayerParameters();
- APCIParameters apciParameters = new APCIParameters();
+ DateTime dateTime = DateTime.UtcNow;
- Server server = new Server(apciParameters, parameters);
+ CP56Time2a time = new CP56Time2a(dateTime);
+
+ SetpointCommandShortWithCP56Time2a sc = new SetpointCommandShortWithCP56Time2a(101, 10.5f, new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(101, sc.ObjectAddress);
+ Assert.AreEqual(10.5f, sc.Value, 0.001f);
+ Assert.AreEqual(true, sc.QOS.Select);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+
+ sc = new SetpointCommandShortWithCP56Time2a(102, 1.0f, new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(102, sc.ObjectAddress);
+ Assert.AreEqual(1.0f, sc.Value, 0.001f);
+ Assert.AreEqual(true, sc.QOS.Select);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+
+ sc = new SetpointCommandShortWithCP56Time2a(102, -1.0f, new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(102, sc.ObjectAddress);
+ Assert.AreEqual(-1.0f, sc.Value, 0.001f);
+ Assert.AreEqual(true, sc.QOS.Select);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+ }
- server.DebugOutput = true;
+ [Test()]
+ public void TestSetpointCommandScaled()
+ {
+ SetpointCommandScaled sc = new SetpointCommandScaled(101, new ScaledValue(-32767), new SetpointCommandQualifier(true, 0));
- server.SetLocalPort(20213);
+ Assert.AreEqual(101, sc.ObjectAddress);
+ Assert.AreEqual(-32767, sc.ScaledValue.Value);
+ Assert.AreEqual(true, sc.QOS.Select);
- server.Start();
+ sc = new SetpointCommandScaled(101, new ScaledValue(32767), new SetpointCommandQualifier(true, 0));
- Connection connection = new Connection("127.0.0.1", 20213, apciParameters, parameters);
+ Assert.AreEqual(101, sc.ObjectAddress);
+ Assert.AreEqual(32767, sc.ScaledValue.ShortValue);
+ Assert.AreEqual(true, sc.QOS.Select);
- connection.DebugOutput = true;
+ sc = new SetpointCommandScaled(101, new ScaledValue(-32768), new SetpointCommandQualifier(true, 0));
- for (int i = 0; i < 3; i++)
- {
- ConnectionException se = null;
+ Assert.AreEqual(101, sc.ObjectAddress);
+ Assert.AreEqual(-32768, sc.ScaledValue.ShortValue);
+ Assert.AreEqual(true, sc.QOS.Select);
+ }
- connection.Connect();
+ [Test()]
+ public void TestSetpointCommandScaledWithCP56Time2a()
+ {
+ DateTime dateTime = DateTime.UtcNow;
- server.Stop();
+ CP56Time2a time = new CP56Time2a(dateTime);
+
+ SetpointCommandScaledWithCP56Time2a sc = new SetpointCommandScaledWithCP56Time2a(101, new ScaledValue(1), new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(101, sc.ObjectAddress);
+ Assert.AreEqual(1, sc.ScaledValue.Value);
+ Assert.AreEqual(true, sc.QOS.Select);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+
+ sc = new SetpointCommandScaledWithCP56Time2a(101, new ScaledValue(32767), new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(101, sc.ObjectAddress);
+ Assert.AreEqual(32767, sc.ScaledValue.ShortValue);
+ Assert.AreEqual(true, sc.QOS.Select);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+
+ sc = new SetpointCommandScaledWithCP56Time2a(101, new ScaledValue(-32768), new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(101, sc.ObjectAddress);
+ Assert.AreEqual(-32768, sc.ScaledValue.ShortValue);
+ Assert.AreEqual(true, sc.QOS.Select);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+ }
- Thread.Sleep(1000);
+ [Test()]
+ public void TestSetpointCommandNormalized()
+ {
+ SetpointCommandNormalized sc = new SetpointCommandNormalized(102, -0.5f,
+ new SetpointCommandQualifier(true, 0));
- try
- {
- connection.SendStartDT();
+ Assert.AreEqual(102, sc.ObjectAddress);
+ Assert.AreEqual(-0.5f, sc.NormalizedValue, 0.001f);
+ Assert.AreEqual(true, sc.QOS.Select);
- ASDU newASDU = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ sc = new SetpointCommandNormalized(102, 32767, new SetpointCommandQualifier(true, 0));
- newASDU.AddInformationObject(new MeasuredValueShort(1001, 0.1f, QualityDescriptor.INVALID()));
+ Assert.AreEqual(1.0, sc.NormalizedValue, 0.001f);
+ Assert.AreEqual(32767, sc.RawValue);
- server.EnqueueASDU(newASDU);
+ sc = new SetpointCommandNormalized(102, -32768, new SetpointCommandQualifier(true, 0));
- newASDU = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ Assert.AreEqual(-1.0, sc.NormalizedValue, 0.001f);
+ Assert.AreEqual(-32768, sc.RawValue);
+ }
- newASDU.AddInformationObject(new MeasuredValueShort(1001, 0.2f, QualityDescriptor.INVALID()));
+ [Test()]
+ public void TestSetpointCommandNormalizedWithCP56Time2a()
+ {
+ DateTime dateTime = DateTime.UtcNow;
- server.EnqueueASDU(newASDU);
+ CP56Time2a time = new CP56Time2a(dateTime);
+
+ SetpointCommandNormalizedWithCP56Time2a sc = new SetpointCommandNormalizedWithCP56Time2a(102, -0.5f, new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(102, sc.ObjectAddress);
+ Assert.AreEqual(-0.5f, sc.NormalizedValue, 0.001f);
+ Assert.AreEqual(true, sc.QOS.Select);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+
+ sc = new SetpointCommandNormalizedWithCP56Time2a(102, 32767, new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(1.0, sc.NormalizedValue, 0.001f);
+ Assert.AreEqual(32767, sc.RawValue);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+
+ sc = new SetpointCommandNormalizedWithCP56Time2a(102, -32768, new SetpointCommandQualifier(true, 0), time);
+
+ Assert.AreEqual(-1.0, sc.NormalizedValue, 0.001f);
+ Assert.AreEqual(-32768, sc.RawValue);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+ }
- Thread.Sleep(1000);
+ [Test()]
+ public void TestStepPositionInformation()
+ {
+ StepPositionInformation spi = new StepPositionInformation(103, 27, false, new QualityDescriptor());
- connection.Close();
- }
- catch (ConnectionException ex)
- {
- se = ex;
- }
+ Assert.IsFalse(spi.Transient);
+ Assert.NotNull(spi.Quality);
- Assert.IsNotNull(se);
+ spi = null;
- server.Start();
+ try
+ {
+ spi = new StepPositionInformation(103, 64, false, new QualityDescriptor());
+ }
+ catch (ArgumentOutOfRangeException)
+ {
}
- server.Stop();
+ Assert.IsNull(spi);
- connection.Close();
+ try
+ {
+ spi = new StepPositionInformation(103, -65, false, new QualityDescriptor());
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ }
}
- [Test ()]
- public void TestASDUAddInformationObjects () {
- ApplicationLayerParameters cp = new ApplicationLayerParameters ();
+ [Test()]
+ public void TestStepPositionInformationWithCP24Time2a()
+ {
+ CP24Time2a time = new CP24Time2a(45, 23, 538);
- ASDU asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ StepPositionWithCP24Time2a spi = new StepPositionWithCP24Time2a(103, 27, false, new QualityDescriptor(), time);
- asdu.AddInformationObject (new SinglePointInformation (100, false, new QualityDescriptor ()));
- asdu.AddInformationObject (new SinglePointInformation (101, false, new QualityDescriptor ()));
+ Assert.IsFalse(spi.Transient);
+ Assert.NotNull(spi.Quality);
+ Assert.AreEqual(45, spi.Timestamp.Minute);
+ Assert.AreEqual(23, spi.Timestamp.Second);
+ Assert.AreEqual(538, spi.Timestamp.Millisecond);
- // wrong InformationObject type expect exception
- ArgumentException ae = null;
+ spi = null;
- try {
- asdu.AddInformationObject (new DoublePointInformation (102, DoublePointValue.ON, new QualityDescriptor ()));
- } catch (ArgumentException e) {
- ae = e;
+ try
+ {
+ spi = new StepPositionWithCP24Time2a(103, 64, false, new QualityDescriptor(), time);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
}
- Assert.NotNull (ae);
- }
+ Assert.IsNull(spi);
- [Test ()]
- public void TestASDUAddTooMuchInformationObjects () {
- ApplicationLayerParameters cp = new ApplicationLayerParameters ();
+ try
+ {
+ spi = new StepPositionWithCP24Time2a(103, -65, false, new QualityDescriptor(), time);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
+ }
- ASDU asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ }
- int addedCounter = 0;
- int ioa = 100;
+ [Test()]
+ public void TestStepPositionInformationWithCP56Time2a()
+ {
+ DateTime dateTime = DateTime.UtcNow;
- while (asdu.AddInformationObject (new SinglePointInformation (ioa, false, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ CP56Time2a time = new CP56Time2a(dateTime);
- Assert.AreEqual (60, addedCounter);
+ StepPositionWithCP56Time2a spi = new StepPositionWithCP56Time2a(103, 27, false, new QualityDescriptor(), time);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ Assert.IsFalse(spi.Transient);
+ Assert.NotNull(spi.Quality);
+ Assert.AreEqual(time.Year, spi.Timestamp.Year);
+ Assert.AreEqual(time.Month, spi.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, spi.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, spi.Timestamp.Minute);
+ Assert.AreEqual(time.Second, spi.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, spi.Timestamp.Millisecond);
- addedCounter = 0;
- ioa = 100;
+ spi = null;
- while (asdu.AddInformationObject (new SinglePointInformation (ioa, false, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
+ try
+ {
+ spi = new StepPositionWithCP56Time2a(103, 64, false, new QualityDescriptor(), time);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
}
- Assert.AreEqual (127, addedCounter);
-
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
-
- addedCounter = 0;
- ioa = 100;
+ Assert.IsNull(spi);
- while (asdu.AddInformationObject (new SinglePointWithCP24Time2a (ioa, false, new QualityDescriptor (), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
+ try
+ {
+ spi = new StepPositionWithCP56Time2a(103, -65, false, new QualityDescriptor(), time);
+ }
+ catch (ArgumentOutOfRangeException)
+ {
}
- Assert.AreEqual (34, addedCounter);
+ }
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ [Test()]
+ //[Ignore("Ignore to save execution time")]
+ public void TestConnectWhileAlreadyConnected()
+ {
+ ApplicationLayerParameters parameters = new ApplicationLayerParameters();
+ APCIParameters apciParameters = new APCIParameters();
- addedCounter = 0;
- ioa = 100;
+ Server server = new Server(apciParameters, parameters);
- while (asdu.AddInformationObject (new SinglePointWithCP56Time2a (ioa, false, new QualityDescriptor (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ int port = GetPort();
- Assert.AreEqual (30, addedCounter);
+ server.SetLocalPort(port);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ server.Start();
- addedCounter = 0;
- ioa = 100;
+ Connection connection = new Connection("127.0.0.1", port, apciParameters, parameters);
- while (asdu.AddInformationObject (new MeasuredValueShortWithCP56Time2a (ioa, 0.0f, QualityDescriptor.VALID (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
+ ConnectionException se = null;
+
+ try
+ {
+ connection.Connect();
+ }
+ catch (ConnectionException ex)
+ {
+ se = ex;
+ }
+
+ Assert.IsNull(se);
+
+ Thread.Sleep(100);
+
+ try
+ {
+ connection.Connect();
+ }
+ catch (ConnectionException ex)
+ {
+ se = ex;
+ }
+
+ Assert.IsNotNull(se);
+ Assert.AreEqual(se.Message, "already connected");
+
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ Assert.AreEqual(106, ((SocketException)se.InnerException).ErrorCode);
+ }
+
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ Assert.AreEqual(10056, ((SocketException)se.InnerException).ErrorCode);
+ }
+
+ connection.Close();
+
+ server.Stop();
+ }
+
+ struct CS104SlaveEventQueue1
+ {
+ public int asduHandlerCalled;
+ public int spontCount;
+ public short lastScaledValue;
+ }
+
+ private static bool EventQueue1_asduReceivedHandler(object param, ASDU asdu)
+ {
+ CS104SlaveEventQueue1 info = (CS104SlaveEventQueue1)param;
+ Console.WriteLine($"[Handler] Received ASDU with Type: {asdu.TypeId}, COT: {asdu.Cot}");
+ info.asduHandlerCalled++;
+
+ if (asdu.Cot == CauseOfTransmission.SPONTANEOUS)
+ {
+ info.spontCount++;
+
+ if (asdu.TypeId == TypeID.M_ME_NB_1)
+ {
+ MeasuredValueScaled mv = (MeasuredValueScaled)asdu.GetElement(0);
+ info.lastScaledValue = (short)mv.ScaledValue.Value;
+ Console.WriteLine($"[Handler] Updated lastScaledValue: {info.lastScaledValue}");
+ }
+ }
+ return true;
+ }
+
+ [Test()]
+ public void TestAddUntilOverflow()
+ {
+
+ ASDU asdu = new ASDU(new ApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+
+ int i = 0;
+
+ for (i = 0; i < 60; i++)
+ {
+ SinglePointInformation sp = new SinglePointInformation(100 + i, true, new QualityDescriptor());
+
+ bool added = asdu.AddInformationObject(sp);
+
+ Assert.IsTrue(added);
+
+ Assert.AreEqual(i + 1, asdu.NumberOfElements);
+ }
+
+ SinglePointInformation sp2 = new SinglePointInformation(100 + i, true, new QualityDescriptor());
+
+ bool addedIO = asdu.AddInformationObject(sp2);
+
+ Assert.IsFalse(addedIO);
+
+ }
+
+ [Test()]
+ public void TestUnconfirmedStoppedState()
+ {
+ ApplicationLayerParameters parameters = new ApplicationLayerParameters();
+ APCIParameters apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, parameters);
+
+ server.ServerMode = ServerMode.SINGLE_REDUNDANCY_GROUP;
+
+ int port = GetPort();
+ server.SetLocalPort(port);
+
+ server.Start();
+
+ server.DebugOutput = true;
+
+ CS104SlaveEventQueue1 info = new CS104SlaveEventQueue1
+ {
+ asduHandlerCalled = 0,
+ spontCount = 0,
+ lastScaledValue = 0
+ };
+
+ //short scaledValue = 0;
+
+ for (int i = 0; i < 15; i++)
+ {
+ ASDU newASDU = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+
+ info.lastScaledValue++;
+
+ newASDU.AddInformationObject(new MeasuredValueScaled(110, info.lastScaledValue, new QualityDescriptor()));
+
+ Console.WriteLine($"[Test] Enqueuing ASDU with ScaledValue: {info.lastScaledValue}");
+
+ server.EnqueueASDU(newASDU);
+
+ Thread.Sleep(50); // Increase delay slightly
+ }
+
+ Thread.Sleep(1000); // Ensure processing time
+
+ Connection connection = new Connection("127.0.0.1", port, apciParameters, parameters);
+
+ connection.SetASDUReceivedHandler(EventQueue1_asduReceivedHandler, info);
+
+ connection.Connect();
+
+ connection.SendStartDT();
+
+ Thread.Sleep(500);
+
+ connection.SendStopDT();
+
+ connection.Close();
+
+ Assert.AreEqual(15, info.lastScaledValue);
+
+ info.asduHandlerCalled = 0;
+ info.spontCount = 0;
+
+ connection.Connect();
+
+ connection.SendStartDT();
+
+ Thread.Sleep(500);
+
+ for (int i = 0; i < 5; i++)
+ {
+ Thread.Sleep(10);
+
+ ASDU newASDU = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+
+ info.asduHandlerCalled++;
+
+ info.spontCount++;
+
+ info.lastScaledValue++;
+
+ newASDU.AddInformationObject(new MeasuredValueScaled(110, info.lastScaledValue, new QualityDescriptor()));
+
+ Console.WriteLine($"[Test] Enqueuing ASDU with ScaledValue: {info.lastScaledValue}");
+
+ server.EnqueueASDU(newASDU);
+ }
+
+ Thread.Sleep(500);
+
+ connection.SendStopDT();
+
+ Thread.Sleep(5000);
+
+ connection.Close();
+
+ Assert.AreEqual(5, info.asduHandlerCalled);
+ Assert.AreEqual(5, info.spontCount);
+ Assert.AreEqual(20, info.lastScaledValue);
+
+ server.Stop();
+ server = null;
+ }
+
+ [Test()]
+ //[Ignore("Ignore to save execution time")]
+ public void TestSendIMessageAfterStopDT()
+ {
+ ApplicationLayerParameters parameters = new ApplicationLayerParameters();
+ APCIParameters apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, parameters);
+
+ int port = GetPort();
+ server.SetLocalPort(port);
+
+ server.Start();
+
+ Connection connection = new Connection("127.0.0.1", port, apciParameters, parameters);
+
+ ConnectionException se = null;
+
+ try
+ {
+ connection.Connect();
+
+ connection.SendStartDT();
+
+ Thread.Sleep(200);
+
+ connection.SendStopDT();
+
+ // send command (should trigger server disconnect)
+ connection.SendControlCommand(CauseOfTransmission.ACTIVATION, 1, new SingleCommand(5000, true, false, 0));
+
+ Thread.Sleep(500);
+
+ // send command (should throw exception - not connected)
+ connection.SendControlCommand(CauseOfTransmission.ACTIVATION, 1, new SingleCommand(5000, true, false, 0));
+ }
+ catch (ConnectionException ex)
+ {
+ se = ex;
+ }
+
+ Assert.IsNotNull(se);
+ Assert.AreEqual(se.Message, "not connected");
+
+
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ Assert.AreEqual(107, ((SocketException)se.InnerException).ErrorCode);
+ }
+
+ if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ Assert.AreEqual(10057, ((SocketException)se.InnerException).ErrorCode);
+ }
+
+ server.Stop();
+ }
+
+ [Test()]
+ //[Ignore("Ignore to save execution time")]
+ public void TestConnectSameConnectionMultipleTimes()
+ {
+ ApplicationLayerParameters parameters = new ApplicationLayerParameters();
+ APCIParameters apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, parameters);
+ int port = GetPort();
+ server.SetLocalPort(port);
+
+ server.Start();
+
+ Connection connection = new Connection("127.0.0.1", port, apciParameters, parameters);
+
+ SocketException se = null;
+
+ try
+ {
+ connection.Connect();
+
+ connection.Close();
+ }
+ catch (SocketException ex)
+ {
+ se = ex;
+ }
+
+ Assert.IsNull(se);
+
+ try
+ {
+ connection.Connect();
+
+ connection.Close();
+ }
+ catch (SocketException ex)
+ {
+ se = ex;
+ }
+
+ Assert.Null(se);
+
+ connection.Close();
+
+ server.Stop();
+ }
+
+ [Test()]
+ //[Ignore("Ignore to save execution time")]
+ public void TestConnectSameConnectionMultipleTimesServerDisconnects()
+ {
+ ApplicationLayerParameters parameters = new ApplicationLayerParameters();
+ APCIParameters apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, parameters);
+
+ server.DebugOutput = true;
+ int port = GetPort();
+ server.SetLocalPort(port);
+
+ server.Start();
+
+ Connection connection = new Connection("127.0.0.1", port, apciParameters, parameters);
+
+ connection.DebugOutput = true;
+
+ for (int i = 0; i < 3; i++)
+ {
+ ConnectionException se = null;
+
+ connection.Connect();
+
+ Thread.Sleep(1000);
+
+ server.Stop();
+
+ Thread.Sleep(1000);
+
+ try
+ {
+ connection.SendStartDT();
+
+ ASDU newASDU = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+
+ newASDU.AddInformationObject(new MeasuredValueShort(1001, 0.1f, QualityDescriptor.INVALID()));
+
+ server.EnqueueASDU(newASDU);
+
+ newASDU = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+
+ newASDU.AddInformationObject(new MeasuredValueShort(1001, 0.2f, QualityDescriptor.INVALID()));
+
+ server.EnqueueASDU(newASDU);
+
+ Thread.Sleep(1000);
+
+ connection.Close();
+ }
+ catch (ConnectionException ex)
+ {
+ se = ex;
+ }
+
+ Assert.IsNotNull(se);
+
+ server.Start();
+ }
+
+ server.Stop();
+
+ connection.Close();
+ }
+
+ [Test()]
+ public void TestCS104ConnectionUseAfterClose()
+ {
+ ApplicationLayerParameters parameters = new ApplicationLayerParameters();
+ APCIParameters apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, parameters);
+
+ Assert.NotNull(server);
+
+ server.ServerMode = ServerMode.SINGLE_REDUNDANCY_GROUP;
+
+ int port = GetPort();
+ server.SetLocalPort(port);
+
+ server.Start();
+
+ Connection con = new Connection("127.0.0.1", port);
+
+ Assert.NotNull(con);
+
+ con.Connect();
+
+ con.Close();
+
+ try
+ {
+ con.SendInterrogationCommand(CauseOfTransmission.ACTIVATION, 1, 20);
+ Assert.Fail("Expected ConnectionException was not thrown.");
+ }
+ catch (ConnectionException ex)
+ {
+ Console.WriteLine("Expected exception caught: " + ex.Message);
+ }
+
+ server.Stop();
+
+ }
+
+ [Test()]
+ public void TestCS104ConnectionUseAfterServerCloseConnection()
+ {
+ ApplicationLayerParameters parameters = new ApplicationLayerParameters();
+ APCIParameters apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, parameters);
+
+ Assert.NotNull(server);
+
+ server.ServerMode = ServerMode.SINGLE_REDUNDANCY_GROUP;
+
+ int port = GetPort();
+
+ server.SetLocalPort(port);
+
+ server.Start();
+
+ Connection con = new Connection("127.0.0.1", port);
+
+ Assert.NotNull(con);
+
+ con.Connect();
+
+ server.Stop();
+
+ /* wait to allow client side to detect connection loss */
+ Thread.Sleep(500);
+
+ try
+ {
+ con.SendInterrogationCommand(CauseOfTransmission.ACTIVATION, 1, 20);
+ Assert.Fail("Expected ConnectionException was not thrown.");
+ }
+ catch (ConnectionException ex)
+ {
+ Console.WriteLine("Expected exception: " + ex.Message);
+ }
+
+ con.Close();
+
+ }
+
+ [Test()]
+ public void TestASDUAddInformationObjects()
+ {
+ ApplicationLayerParameters cp = new ApplicationLayerParameters();
+
+ ASDU asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+
+ asdu.AddInformationObject(new SinglePointInformation(100, false, new QualityDescriptor()));
+ asdu.AddInformationObject(new SinglePointInformation(101, false, new QualityDescriptor()));
+
+ // wrong InformationObject type expect exception
+ ArgumentException ae = null;
+
+ try
+ {
+ asdu.AddInformationObject(new DoublePointInformation(102, DoublePointValue.ON, new QualityDescriptor()));
+ }
+ catch (ArgumentException e)
+ {
+ ae = e;
+ }
+
+ Assert.NotNull(ae);
+ }
+
+ [Test()]
+ public void TestASDUAddTooMuchInformationObjects()
+ {
+ ApplicationLayerParameters cp = new ApplicationLayerParameters();
+
+ ASDU asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+
+ int addedCounter = 0;
+ int ioa = 100;
+
+ while (asdu.AddInformationObject(new SinglePointInformation(ioa, false, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(60, addedCounter);
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new SinglePointInformation(ioa, false, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(127, addedCounter);
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new SinglePointWithCP24Time2a(ioa, false, new QualityDescriptor(), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(34, addedCounter);
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new SinglePointWithCP56Time2a(ioa, false, new QualityDescriptor(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(30, addedCounter);
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueShortWithCP56Time2a(ioa, 0.0f, QualityDescriptor.VALID(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(16, addedCounter);
+ }
+
+ [Test()]
+ public void TestASDUAddInformationObjectsInWrongOrderToSequence()
+ {
+ ApplicationLayerParameters cp = new ApplicationLayerParameters();
+
+ ASDU asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+
+ bool encoded = asdu.AddInformationObject(new SinglePointInformation(100, false, new QualityDescriptor()));
+
+ Assert.IsTrue(encoded);
+
+ encoded = asdu.AddInformationObject(new SinglePointInformation(101, false, new QualityDescriptor()));
+
+ Assert.IsTrue(encoded);
+
+ encoded = asdu.AddInformationObject(new SinglePointInformation(102, false, new QualityDescriptor()));
+
+ Assert.IsTrue(encoded);
+
+ encoded = asdu.AddInformationObject(new SinglePointInformation(104, false, new QualityDescriptor()));
+
+ Assert.IsFalse(encoded);
+
+ encoded = asdu.AddInformationObject(new SinglePointInformation(102, false, new QualityDescriptor()));
+
+ Assert.IsFalse(encoded);
+
+ Assert.AreEqual(3, asdu.NumberOfElements);
+ }
+
+
+
+ [Test()]
+ public void TestEncodeASDUsWithManyInformationObjects()
+ {
+ ApplicationLayerParameters cp = new ApplicationLayerParameters();
+
+ ASDU asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ int addedCounter = 0;
+ int ioa = 100;
+
+ while (asdu.AddInformationObject(new SinglePointInformation(ioa, false, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(60, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new SinglePointWithCP24Time2a(ioa, true, new QualityDescriptor(), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(34, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new SinglePointWithCP56Time2a(ioa, true, new QualityDescriptor(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(22, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new DoublePointInformation(ioa, DoublePointValue.ON, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(60, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new DoublePointWithCP24Time2a(ioa, DoublePointValue.ON, new QualityDescriptor(), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(34, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new DoublePointWithCP56Time2a(ioa, DoublePointValue.ON, new QualityDescriptor(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(22, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueNormalized(ioa, 1f, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(40, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueNormalizedWithCP24Time2a(ioa, 1f, new QualityDescriptor(), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(27, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueNormalizedWithCP56Time2a(ioa, 1f, new QualityDescriptor(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(18, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueScaled(ioa, 0, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(40, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueScaledWithCP24Time2a(ioa, 0, new QualityDescriptor(), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(27, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueScaledWithCP56Time2a(ioa, 0, new QualityDescriptor(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(18, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueShort(ioa, 0f, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(30, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueShortWithCP24Time2a(ioa, 0f, new QualityDescriptor(), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(22, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueShortWithCP56Time2a(ioa, 0f, new QualityDescriptor(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(16, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new StepPositionInformation(ioa, 0, false, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(48, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new StepPositionWithCP24Time2a(ioa, 0, false, new QualityDescriptor(), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(30, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new StepPositionWithCP56Time2a(ioa, 0, false, new QualityDescriptor(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(20, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new IntegratedTotals(ioa, new BinaryCounterReading())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(30, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new IntegratedTotalsWithCP24Time2a(ioa, new BinaryCounterReading(), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(22, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new IntegratedTotalsWithCP56Time2a(ioa, new BinaryCounterReading(), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(16, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new EventOfProtectionEquipment(ioa, new SingleEvent(), new CP16Time2a(10), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(27, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new EventOfProtectionEquipmentWithCP56Time2a(ioa, new SingleEvent(), new CP16Time2a(10), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(18, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new PackedSinglePointWithSCD(ioa, new StatusAndStatusChangeDetection(), new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(30, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new PackedOutputCircuitInfo(ioa, new OutputCircuitInfo(), new QualityDescriptorP(), new CP16Time2a(10), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(24, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new PackedOutputCircuitInfoWithCP56Time2a(ioa, new OutputCircuitInfo(), new QualityDescriptorP(), new CP16Time2a(10), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(17, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new PackedStartEventsOfProtectionEquipment(ioa, new StartEvent(), new QualityDescriptorP(), new CP16Time2a(10), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(24, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new PackedStartEventsOfProtectionEquipmentWithCP56Time2a(ioa, new StartEvent(), new QualityDescriptorP(), new CP16Time2a(10), new CP56Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(17, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+ //TODO add missing tests
+ }
+
+ [Test()]
+ public void TestEncodeASDUsWithManyInformationObjectsSequenceOfIO()
+ {
+
+ ApplicationLayerParameters cp = new ApplicationLayerParameters();
+
+ ASDU asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ int addedCounter = 0;
+ int ioa = 100;
+
+ while (asdu.AddInformationObject(new SinglePointInformation(ioa, false, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(127, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new DoublePointInformation(ioa, DoublePointValue.OFF, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(127, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new MeasuredValueNormalized(ioa, 1f, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
}
- Assert.AreEqual (16, addedCounter);
- }
- [Test ()]
- public void TestASDUAddInformationObjectsInWrongOrderToSequence () {
- ApplicationLayerParameters cp = new ApplicationLayerParameters ();
+ Assert.AreEqual(80, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
- ASDU asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ addedCounter = 0;
+ ioa = 100;
- bool encoded = asdu.AddInformationObject (new SinglePointInformation (100, false, new QualityDescriptor ()));
+ while (asdu.AddInformationObject(new MeasuredValueScaled(ioa, 0, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
- Assert.IsTrue (encoded);
+ Assert.AreEqual(80, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
- encoded = asdu.AddInformationObject (new SinglePointInformation (101, false, new QualityDescriptor ()));
- Assert.IsTrue (encoded);
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ addedCounter = 0;
+ ioa = 100;
- encoded = asdu.AddInformationObject (new SinglePointInformation (102, false, new QualityDescriptor ()));
+ while (asdu.AddInformationObject(new MeasuredValueShort(ioa, 0f, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
- Assert.IsTrue (encoded);
+ Assert.AreEqual(48, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
- encoded = asdu.AddInformationObject (new SinglePointInformation (104, false, new QualityDescriptor ()));
- Assert.IsFalse (encoded);
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ addedCounter = 0;
+ ioa = 100;
- encoded = asdu.AddInformationObject (new SinglePointInformation (102, false, new QualityDescriptor ()));
+ while (asdu.AddInformationObject(new StepPositionInformation(ioa, 0, false, new QualityDescriptor())))
+ {
+ ioa++;
+ addedCounter++;
+ }
- Assert.IsFalse (encoded);
+ Assert.AreEqual(120, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
- Assert.AreEqual (3, asdu.NumberOfElements);
- }
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ addedCounter = 0;
+ ioa = 100;
+ while (asdu.AddInformationObject(new IntegratedTotals(ioa, new BinaryCounterReading())))
+ {
+ ioa++;
+ addedCounter++;
+ }
- [Test ()]
- public void TestEncodeASDUsWithManyInformationObjects () {
- ApplicationLayerParameters cp = new ApplicationLayerParameters ();
+ Assert.AreEqual(48, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
- ASDU asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- int addedCounter = 0;
- int ioa = 100;
- while (asdu.AddInformationObject (new SinglePointInformation (ioa, false, new QualityDescriptor ()))) {
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new PackedSinglePointWithSCD(ioa, new StatusAndStatusChangeDetection(), new QualityDescriptor())))
+ {
ioa++;
addedCounter++;
}
- Assert.AreEqual (60, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ Assert.AreEqual(48, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
+ addedCounter = 0;
+ ioa = 100;
+
+ while (asdu.AddInformationObject(new PackedOutputCircuitInfo(ioa, new OutputCircuitInfo(), new QualityDescriptorP(), new CP16Time2a(10), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+ Assert.AreEqual(34, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
+ asdu = new ASDU(cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
addedCounter = 0;
ioa = 100;
- while (asdu.AddInformationObject (new SinglePointWithCP24Time2a (ioa, true, new QualityDescriptor (), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ while (asdu.AddInformationObject(new PackedStartEventsOfProtectionEquipment(ioa, new StartEvent(), new QualityDescriptorP(), new CP16Time2a(0), new CP24Time2a())))
+ {
+ ioa++;
+ addedCounter++;
+ }
+
+ Assert.AreEqual(34, addedCounter);
+ Assert.NotNull(asdu.AsByteArray());
+
+ }
+
+ [Test()]
+ //[Ignore("Ignore to save execution time")]
+ public void TestSendTestFR()
+ {
+ ApplicationLayerParameters clientParameters = new ApplicationLayerParameters();
+ APCIParameters clientApciParamters = new APCIParameters();
+ ApplicationLayerParameters serverParameters = new ApplicationLayerParameters();
+ APCIParameters serverApciParamters = new APCIParameters();
+
+ clientApciParamters.T3 = 1;
+
+ Server server = new Server(serverApciParamters, serverParameters);
+ int port = GetPort();
+ server.SetLocalPort(port);
+ server.DebugOutput = true;
+ server.Start();
+
+ Connection connection = new Connection("127.0.0.1", port, clientApciParamters, clientParameters);
+
+ connection.Connect();
+
+ connection.DebugOutput = true;
+ connection.SetReceivedRawMessageHandler(testSendTestFRTimeoutMasterRawMessageHandler, null);
+
+ ASDU asdu = new ASDU(clientParameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ asdu.AddInformationObject(new SinglePointInformation(100, false, new QualityDescriptor()));
+
+ Assert.AreEqual(1, connection.GetStatistics().SentMsgCounter); /* STARTDT + ASDU */
+
+ while (connection.GetStatistics().RcvdMsgCounter < 2)
+ Thread.Sleep(1);
+
+ Assert.AreEqual(2, connection.GetStatistics().RcvdMsgCounter); /* STARTDT_CON + ASDU */
+
+ Thread.Sleep(6000);
+
+ Assert.IsFalse(connection.IsRunning);
+
+ try
+ {
+ connection.SendASDU(asdu);
+ }
+ catch (ConnectionException)
+ {
+ }
+
+
+ while (connection.IsRunning == true)
+ Thread.Sleep(10);
+
+ connection.Close();
+ server.Stop();
+
+ Assert.AreEqual(4, connection.GetStatistics().RcvdMsgCounter); /* STARTDT_CON + ASDU + TESTFR_CON */
+
+ Assert.AreEqual(0, connection.GetStatistics().RcvdTestFrConCounter);
+ }
+
+ private static bool testSendTestFRTimeoutMasterRawMessageHandler(object param, byte[] msg, int msgSize)
+ {
+ // intercept TESTFR_CON message
+ if ((msgSize == 6) && (msg[2] == 0x83))
+ return false;
+ else
+ return true;
+ }
+
+ ///
+ /// This test checks that the connection will be closed when the master
+ /// doesn't receive the TESTFR_CON messages
+ ///
+ [Test()]
+ //[Ignore("Ignore to save execution time")]
+ public void TestSendTestFRTimeoutMaster()
+ {
+ ApplicationLayerParameters clientParameters = new ApplicationLayerParameters();
+ APCIParameters clientApciParamters = new APCIParameters();
+ ApplicationLayerParameters serverParameters = new ApplicationLayerParameters();
+ APCIParameters serverApciParamters = new APCIParameters();
+
+ clientApciParamters.T3 = 1;
+
+ Server server = new Server(serverApciParamters, serverParameters);
+ int port = GetPort();
+ server.SetLocalPort(port);
+
+ server.Start();
+
+ Connection connection = new Connection("127.0.0.1", port, clientApciParamters, clientParameters);
+
+ connection.Connect();
+
+ connection.DebugOutput = true;
+ connection.SetReceivedRawMessageHandler(testSendTestFRTimeoutMasterRawMessageHandler, null);
+
+ ASDU asdu = new ASDU(clientParameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ asdu.AddInformationObject(new SinglePointInformation(100, false, new QualityDescriptor()));
+
+ Assert.AreEqual(1, connection.GetStatistics().SentMsgCounter); /* STARTDT + ASDU */
+
+ while (connection.GetStatistics().RcvdMsgCounter < 2)
+ Thread.Sleep(1);
+
+ Assert.AreEqual(2, connection.GetStatistics().RcvdMsgCounter); /* STARTDT_CON + ASDU */
+
+ Thread.Sleep(6000);
+
+ // Expect connection to be closed due to three missing TESTFR_CON responses
+ Assert.IsFalse(connection.IsRunning);
+
+ // Connection is closed. SendASDU should fail
+ try
+ {
+ connection.SendASDU(asdu);
+ }
+ catch (ConnectionException)
+ {
+ }
+
+
+ while (connection.IsRunning == true)
+ Thread.Sleep(10);
+
+ connection.Close();
+ server.Stop();
+
+ Assert.AreEqual(4, connection.GetStatistics().RcvdMsgCounter); /* STARTDT_CON + ASDU + TESTFR_CON */
+
+ Assert.AreEqual(0, connection.GetStatistics().RcvdTestFrConCounter);
+ }
+
+ private static bool testSendTestFRTimeoutSlaveRawMessageHandler(object param, byte[] msg, int msgSize)
+ {
+ // intercept TESTFR_ACT messages for so that the master doesn't response
+ if ((msgSize == 6) && (msg[2] == 0x43))
+ return false;
+ else
+ return true;
+ }
+
+ ///
+ /// This test checks that the connection will be closed when the master
+ /// doesn't send the TESTFR_CON messages
+ ///
+ [Test()]
+ //[Ignore("Ignore to save execution time")]
+ public void TestSendTestFRTimeoutSlave()
+ {
+ ApplicationLayerParameters clientParameters = new ApplicationLayerParameters();
+ APCIParameters clientApciParamters = new APCIParameters();
+ ApplicationLayerParameters serverParameters = new ApplicationLayerParameters();
+ APCIParameters serverApciParamters = new APCIParameters();
+
+ serverApciParamters.T3 = 1;
+
+ Server server = new Server(serverApciParamters, serverParameters);
+ int port = GetPort();
+ server.SetLocalPort(port);
+
+ server.Start();
+
+ Connection connection = new Connection("127.0.0.1", port, clientApciParamters, clientParameters);
+
+ connection.DebugOutput = true;
+ connection.SetReceivedRawMessageHandler(testSendTestFRTimeoutSlaveRawMessageHandler, null);
+
+ connection.Connect();
+
+ Assert.AreEqual(1, connection.GetStatistics().SentMsgCounter); /* STARTDT */
+
+ while (connection.GetStatistics().RcvdMsgCounter < 1)
+ Thread.Sleep(1);
+
+ Assert.AreEqual(1, connection.GetStatistics().RcvdMsgCounter); /* STARTDT_CON */
+
+ Thread.Sleep(6000);
+
+
+ // Connection is closed. SendASDU should fail
+ try
+ {
+ ASDU asdu = new ASDU(clientParameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
+ asdu.AddInformationObject(new SinglePointInformation(100, false, new QualityDescriptor()));
+
+ connection.SendASDU(asdu);
+ }
+ catch (ConnectionException)
+ {
+ }
+
+
+ while (connection.IsRunning == true)
+ Thread.Sleep(10);
+
+ connection.Close();
+ server.Stop();
+
+ // Assert.AreEqual (5, connection.GetStatistics ().RcvdMsgCounter); /* STARTDT_CON + ASDU + TESTFR_CON */
+
+ // Assert.AreEqual (0, connection.GetStatistics ().RcvdTestFrConCounter);
+ }
+
+
+ [Test()]
+ public void TestEncodeDecodeSetpointCommandNormalized()
+ {
+ Server server = new Server();
+ int port = GetPort();
+
+ server.SetLocalPort(port);
+
+ float recvValue = 0f;
+ float sendValue = 1.0f;
+ bool hasReceived = false;
+
+ server.SetASDUHandler(delegate (object parameter, IMasterConnection con, ASDU asdu)
+ {
+
+ if (asdu.TypeId == TypeID.C_SE_NA_1)
+ {
+ SetpointCommandNormalized spn = (SetpointCommandNormalized)asdu.GetElement(0);
+
+ recvValue = spn.NormalizedValue;
+ hasReceived = true;
+ }
+
+ return true;
+ }, null);
+ server.Start();
+
+ Connection connection = new Connection("127.0.0.1", port);
+ connection.Connect();
+
+ ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.ACTIVATION, false, false, 0, 1, false);
+ newAsdu.AddInformationObject(new SetpointCommandNormalized(100, sendValue, new SetpointCommandQualifier(false, 0)));
+
+ connection.SendASDU(newAsdu);
+
+ while (hasReceived == false)
+ Thread.Sleep(50);
+
+ connection.Close();
+ server.Stop();
+
+ Assert.AreEqual(sendValue, recvValue, 0.001);
+ }
+
+ [Test()]
+ public void TestEncodeDecodePrivateInformationObject()
+ {
+ Server server = new Server();
+ int port = GetPort();
+
+ server.SetLocalPort(port);
+
+ server.DebugOutput = true;
+
+ int recvValue = 0;
+ int sendValue = 12345;
+ bool hasReceived = false;
+
+ PrivateInformationObjectTypes privateObjects = new PrivateInformationObjectTypes();
+ privateObjects.AddPrivateInformationObjectType((TypeID)41, new TestInteger32Object());
+
+ server.SetASDUHandler(delegate (object parameter, IMasterConnection con, ASDU asdu)
+ {
+
+ if (asdu.TypeId == (TypeID)41)
+ {
+
+ TestInteger32Object spn = (TestInteger32Object)asdu.GetElement(0, privateObjects);
+
+ recvValue = spn.Value;
+ hasReceived = true;
+ }
+
+ return true;
+ }, null);
+
+ server.Start();
+
+ Connection connection = new Connection("127.0.0.1", port);
+ connection.Connect();
+
+ ASDU newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.ACTIVATION, false, false, 0, 1, false);
+
+ newAsdu.AddInformationObject(new TestInteger32Object(100, sendValue));
+
+ connection.SendASDU(newAsdu);
+
+ while (hasReceived == false)
+ Thread.Sleep(50);
+
+ connection.Close();
+ server.Stop();
+
+ Assert.AreEqual(sendValue, recvValue);
+
+ }
+
+ [Test()]
+ public void TestDoubleCommand()
+ {
+ DoubleCommand dc = new DoubleCommand(10001, 2, false, 12);
+
+ Assert.AreEqual(10001, dc.ObjectAddress);
+ Assert.AreEqual(2, dc.State);
+ Assert.AreEqual(false, dc.Select);
+ Assert.AreEqual(12, dc.QU);
+
+ dc = new DoubleCommand(10001, 2, false, 3);
+
+ Assert.AreEqual(10001, dc.ObjectAddress);
+ Assert.AreEqual(2, dc.State);
+ Assert.AreEqual(false, dc.Select);
+ Assert.AreEqual(3, dc.QU);
- Assert.AreEqual (34, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ }
+ [Test()]
+ public void TestDoubleCommandWithCP56Time2a()
+ {
+ DateTime dateTime = DateTime.UtcNow;
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ CP56Time2a time = new CP56Time2a(dateTime);
+
+ DoubleCommandWithCP56Time2a dc = new DoubleCommandWithCP56Time2a(10001, 2, false, 12, time);
+
+ Assert.AreEqual(10001, dc.ObjectAddress);
+ Assert.AreEqual(2, dc.State);
+ Assert.AreEqual(false, dc.Select);
+ Assert.AreEqual(12, dc.QU);
+ Assert.AreEqual(time.Year, dc.Timestamp.Year);
+ Assert.AreEqual(time.Month, dc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, dc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, dc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, dc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, dc.Timestamp.Millisecond);
+
+ dc = new DoubleCommandWithCP56Time2a(10001, 2, false, 3, time);
+
+ Assert.AreEqual(10001, dc.ObjectAddress);
+ Assert.AreEqual(2, dc.State);
+ Assert.AreEqual(false, dc.Select);
+ Assert.AreEqual(3, dc.QU);
+ Assert.AreEqual(time.Year, dc.Timestamp.Year);
+ Assert.AreEqual(time.Month, dc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, dc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, dc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, dc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, dc.Timestamp.Millisecond);
+ }
- while (asdu.AddInformationObject (new SinglePointWithCP56Time2a (ioa, true, new QualityDescriptor (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
- Assert.AreEqual (22, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ [Test()]
+ public void TestStepCommandValue()
+ {
+ StepCommand scv = new StepCommand(10001, StepCommandValue.INVALID_0, false, 10);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(10001, scv.ObjectAddress);
+ Assert.AreEqual(StepCommandValue.INVALID_0, scv.State);
+ Assert.AreEqual(false, scv.Select);
+ Assert.AreEqual(10, scv.QU);
- while (asdu.AddInformationObject (new DoublePointInformation (ioa, DoublePointValue.ON, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ scv = new StepCommand(10002, StepCommandValue.HIGHER, true, 3);
- Assert.AreEqual (60, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ Assert.AreEqual(10002, scv.ObjectAddress);
+ Assert.AreEqual(StepCommandValue.HIGHER, scv.State);
+ Assert.AreEqual(true, scv.Select);
+ Assert.AreEqual(3, scv.QU);
+ }
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ [Test()]
+ public void TestStepCommandValueWithCP56Time2a()
+ {
+ DateTime dateTime = DateTime.UtcNow;
- while (asdu.AddInformationObject (new DoublePointWithCP24Time2a (ioa, DoublePointValue.ON, new QualityDescriptor (), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ CP56Time2a time = new CP56Time2a(dateTime);
+
+ StepCommandWithCP56Time2a scv = new StepCommandWithCP56Time2a(10001, StepCommandValue.INVALID_0, false, 12, time);
+
+ Assert.AreEqual(10001, scv.ObjectAddress);
+ Assert.AreEqual(StepCommandValue.INVALID_0, scv.State);
+ Assert.AreEqual(false, scv.Select);
+ Assert.AreEqual(12, scv.QU);
+ Assert.AreEqual(time.Year, scv.Timestamp.Year);
+ Assert.AreEqual(time.Month, scv.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, scv.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, scv.Timestamp.Minute);
+ Assert.AreEqual(time.Second, scv.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, scv.Timestamp.Millisecond);
+
+ scv = new StepCommandWithCP56Time2a(10002, StepCommandValue.HIGHER, true, 3, time);
+
+ Assert.AreEqual(10002, scv.ObjectAddress);
+ Assert.AreEqual(StepCommandValue.HIGHER, scv.State);
+ Assert.AreEqual(true, scv.Select);
+ Assert.AreEqual(3, scv.QU);
+ Assert.AreEqual(time.Year, scv.Timestamp.Year);
+ Assert.AreEqual(time.Month, scv.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, scv.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, scv.Timestamp.Minute);
+ Assert.AreEqual(time.Second, scv.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, scv.Timestamp.Millisecond);
+ }
- Assert.AreEqual (34, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ [Test()]
+ public void TestSingleCommand()
+ {
+ SingleCommand sc = new SingleCommand(10002, true, false, 12);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(10002, sc.ObjectAddress);
+ Assert.AreEqual(true, sc.State);
+ Assert.AreEqual(false, sc.Select);
+ Assert.AreEqual(12, sc.QU);
- while (asdu.AddInformationObject (new DoublePointWithCP56Time2a (ioa, DoublePointValue.ON, new QualityDescriptor (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ sc = new SingleCommand(10002, false, true, 3);
- Assert.AreEqual (22, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ Assert.AreEqual(10002, sc.ObjectAddress);
+ Assert.AreEqual(false, sc.State);
+ Assert.AreEqual(true, sc.Select);
+ Assert.AreEqual(3, sc.QU);
+ sc.QU = 17;
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(17, sc.QU);
+ Assert.AreEqual(false, sc.State);
+ Assert.AreEqual(true, sc.Select);
+ }
- while (asdu.AddInformationObject (new MeasuredValueNormalized (ioa, 1f, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ [Test()]
+ public void TestSingleCommandWithCP56Time2a()
+ {
+ DateTime dateTime = DateTime.UtcNow;
- Assert.AreEqual (40, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ CP56Time2a time = new CP56Time2a(dateTime);
+ SingleCommandWithCP56Time2a sc = new SingleCommandWithCP56Time2a(10002, true, false, 12, time);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(10002, sc.ObjectAddress);
+ Assert.AreEqual(true, sc.State);
+ Assert.AreEqual(false, sc.Select);
+ Assert.AreEqual(12, sc.QU);
- while (asdu.AddInformationObject (new MeasuredValueNormalizedWithCP24Time2a (ioa, 1f, new QualityDescriptor (), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ sc = new SingleCommandWithCP56Time2a(10002, false, true, 3, time);
- Assert.AreEqual (27, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ Assert.AreEqual(10002, sc.ObjectAddress);
+ Assert.AreEqual(false, sc.State);
+ Assert.AreEqual(true, sc.Select);
+ Assert.AreEqual(3, sc.QU);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+ sc.QU = 17;
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(17, sc.QU);
+ Assert.AreEqual(false, sc.State);
+ Assert.AreEqual(true, sc.Select);
+ Assert.AreEqual(time.Year, sc.Timestamp.Year);
+ Assert.AreEqual(time.Month, sc.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, sc.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, sc.Timestamp.Minute);
+ Assert.AreEqual(time.Second, sc.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, sc.Timestamp.Millisecond);
+ }
- while (asdu.AddInformationObject (new MeasuredValueNormalizedWithCP56Time2a (ioa, 1f, new QualityDescriptor (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ [Test()]
+ public void TestSinglePointInformationClientServer()
+ {
+ SinglePointInformation spi = new SinglePointInformation(101, true, new QualityDescriptor());
- Assert.AreEqual (18, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ ASDU newAsdu = new ASDU(new ApplicationLayerParameters(), CauseOfTransmission.PERIODIC,
+ false, false, 0, 1, false);
+ newAsdu.AddInformationObject(spi);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Server server = new Server();
+ int port = GetPort();
- while (asdu.AddInformationObject (new MeasuredValueScaled (ioa, 0, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ server.SetLocalPort(port);
- Assert.AreEqual (40, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ bool hasReceived = false;
+ server.SetASDUHandler(delegate (object parameter, IMasterConnection con, ASDU asdu)
+ {
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ if (asdu.TypeId == TypeID.M_SP_NA_1)
+ {
- while (asdu.AddInformationObject (new MeasuredValueScaledWithCP24Time2a (ioa, 0, new QualityDescriptor (), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ SinglePointInformation spn = (SinglePointInformation)asdu.GetElement(0);
- Assert.AreEqual (27, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ Assert.AreEqual(spi.Value, spn.Value);
+ hasReceived = true;
+ }
+ return true;
+ }, null);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ server.Start();
- while (asdu.AddInformationObject (new MeasuredValueScaledWithCP56Time2a (ioa, 0, new QualityDescriptor (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ Connection connection = new Connection("127.0.0.1", port);
+ connection.Connect();
- Assert.AreEqual (18, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ connection.SendASDU(newAsdu);
+ while (hasReceived == false)
+ Thread.Sleep(50);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ connection.Close();
+ server.Stop();
+ }
- while (asdu.AddInformationObject (new MeasuredValueShort (ioa, 0f, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ [Test()]
+ public void TestIntegratedTotals()
+ {
+ byte[] buffer = new byte[257];
- Assert.AreEqual (30, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ BufferFrame bf = new BufferFrame(buffer, 0);
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ BinaryCounterReading bcr = new BinaryCounterReading();
- while (asdu.AddInformationObject (new MeasuredValueShortWithCP24Time2a (ioa, 0f, new QualityDescriptor (), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ IntegratedTotals it = new IntegratedTotals(101, bcr);
- Assert.AreEqual (22, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ it.Encode(bf, alParameters, true);
+ Assert.AreEqual(5, bf.GetMsgSize());
+ bf.ResetFrame();
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ it.Encode(bf, alParameters, false);
- while (asdu.AddInformationObject (new MeasuredValueShortWithCP56Time2a (ioa, 0f, new QualityDescriptor (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ Assert.AreEqual(alParameters.SizeOfIOA + it.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(8, bf.GetMsgSize());
- Assert.AreEqual (16, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ IntegratedTotals it2 = new IntegratedTotals(alParameters, buffer, 0, false);
+ Assert.AreEqual(101, it2.ObjectAddress);
+ Assert.AreEqual(0, it2.BCR.Value);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ }
- while (asdu.AddInformationObject (new StepPositionInformation (ioa, 0, false, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ [Test()]
+ public void TestIntegratedTotalsWithCp24Time2a()
+ {
+ byte[] buffer = new byte[257];
- Assert.AreEqual (48, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ BufferFrame bf = new BufferFrame(buffer, 0);
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ CP24Time2a time = new CP24Time2a(45, 23, 538);
- while (asdu.AddInformationObject (new StepPositionWithCP24Time2a (ioa, 0, false, new QualityDescriptor (), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ BinaryCounterReading bcr = new BinaryCounterReading();
- Assert.AreEqual (30, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ IntegratedTotalsWithCP24Time2a it = new IntegratedTotalsWithCP24Time2a(101, bcr, time);
+ it.Encode(bf, alParameters, true);
+ Assert.AreEqual(8, bf.GetMsgSize());
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ bf.ResetFrame();
- while (asdu.AddInformationObject (new StepPositionWithCP56Time2a (ioa, 0, false, new QualityDescriptor (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ it.Encode(bf, alParameters, false);
- Assert.AreEqual (20, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ Assert.AreEqual(alParameters.SizeOfIOA + it.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(11, bf.GetMsgSize());
+ IntegratedTotalsWithCP24Time2a it2 = new IntegratedTotalsWithCP24Time2a(alParameters, buffer, 0, false);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(101, it2.ObjectAddress);
+ Assert.AreEqual(0, it2.BCR.Value);
- while (asdu.AddInformationObject (new IntegratedTotals (ioa, new BinaryCounterReading ()))) {
- ioa++;
- addedCounter++;
- }
+ Assert.AreEqual(45, it2.Timestamp.Minute);
+ Assert.AreEqual(23, it2.Timestamp.Second);
+ Assert.AreEqual(538, it2.Timestamp.Millisecond);
+ }
- Assert.AreEqual (30, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ [Test()]
+ public void TestIntegratedTotalsWithCp56Time2a()
+ {
+ byte[] buffer = new byte[257];
+ BufferFrame bf = new BufferFrame(buffer, 0);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- while (asdu.AddInformationObject (new IntegratedTotalsWithCP24Time2a (ioa, new BinaryCounterReading (), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ DateTime dateTime = DateTime.UtcNow;
- Assert.AreEqual (22, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ CP56Time2a time = new CP56Time2a(dateTime);
+ BinaryCounterReading bcr = new BinaryCounterReading();
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ IntegratedTotalsWithCP56Time2a it = new IntegratedTotalsWithCP56Time2a(101, bcr, time);
- while (asdu.AddInformationObject (new IntegratedTotalsWithCP56Time2a (ioa, new BinaryCounterReading (), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ it.Encode(bf, alParameters, true);
+ Assert.AreEqual(12, bf.GetMsgSize());
- Assert.AreEqual (16, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ bf.ResetFrame();
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ it.Encode(bf, alParameters, false);
- while (asdu.AddInformationObject (new EventOfProtectionEquipment (ioa, new SingleEvent (), new CP16Time2a (10), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ Assert.AreEqual(alParameters.SizeOfIOA + it.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(15, bf.GetMsgSize());
- Assert.AreEqual (27, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ IntegratedTotalsWithCP56Time2a it2 = new IntegratedTotalsWithCP56Time2a(alParameters, buffer, 0, false);
+ Assert.AreEqual(101, it2.ObjectAddress);
+ Assert.AreEqual(0, it2.BCR.Value);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(time.Year, it2.Timestamp.Year);
+ Assert.AreEqual(time.Month, it2.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, it2.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, it2.Timestamp.Minute);
+ Assert.AreEqual(time.Second, it2.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, it2.Timestamp.Millisecond);
- while (asdu.AddInformationObject (new EventOfProtectionEquipmentWithCP56Time2a (ioa, new SingleEvent (), new CP16Time2a (10), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ }
- Assert.AreEqual (18, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ [Test()]
+ public void TestSinglePointInformation()
+ {
+ byte[] buffer = new byte[257];
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ BufferFrame bf = new BufferFrame(buffer, 0);
- while (asdu.AddInformationObject (new PackedSinglePointWithSCD (ioa, new StatusAndStatusChangeDetection (), new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- Assert.AreEqual (30, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ SinglePointInformation spi = new SinglePointInformation(101, true, new QualityDescriptor());
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ spi.Encode(bf, alParameters, true);
+ Assert.AreEqual(1, bf.GetMsgSize());
- while (asdu.AddInformationObject (new PackedOutputCircuitInfo (ioa, new OutputCircuitInfo (), new QualityDescriptorP (), new CP16Time2a (10), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ bf.ResetFrame();
- Assert.AreEqual (24, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ spi.Encode(bf, alParameters, false);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(alParameters.SizeOfIOA + spi.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(4, bf.GetMsgSize());
- while (asdu.AddInformationObject (new PackedOutputCircuitInfoWithCP56Time2a (ioa, new OutputCircuitInfo (), new QualityDescriptorP (), new CP16Time2a (10), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ SinglePointInformation spi2 = new SinglePointInformation(alParameters, buffer, 0, false);
- Assert.AreEqual (17, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ Assert.AreEqual(101, spi2.ObjectAddress);
+ Assert.AreEqual(true, spi2.Value);
+ }
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ [Test()]
+ public void TestSinglePointInformationWithCp24Time2a()
+ {
+ byte[] buffer = new byte[257];
- while (asdu.AddInformationObject (new PackedStartEventsOfProtectionEquipment (ioa, new StartEvent (), new QualityDescriptorP (), new CP16Time2a (10), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ BufferFrame bf = new BufferFrame(buffer, 0);
- Assert.AreEqual (24, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
+ CP24Time2a time = new CP24Time2a(45, 23, 538);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- addedCounter = 0;
- ioa = 100;
+ SinglePointWithCP24Time2a spi = new SinglePointWithCP24Time2a(102, false, new QualityDescriptor(), time);
- while (asdu.AddInformationObject (new PackedStartEventsOfProtectionEquipmentWithCP56Time2a (ioa, new StartEvent (), new QualityDescriptorP (), new CP16Time2a (10), new CP56Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ spi.Encode(bf, alParameters, false);
- Assert.AreEqual (17, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
- //TODO add missing tests
- }
+ Assert.AreEqual(alParameters.SizeOfIOA + spi.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(7, bf.GetMsgSize());
- [Test ()]
- public void TestEncodeASDUsWithManyInformationObjectsSequenceOfIO () {
+ SinglePointWithCP24Time2a spi2 = new SinglePointWithCP24Time2a(alParameters, buffer, 0, false);
- ApplicationLayerParameters cp = new ApplicationLayerParameters ();
+ Assert.AreEqual(102, spi2.ObjectAddress);
+ Assert.AreEqual(false, spi2.Value);
+ Assert.AreEqual(45, spi2.Timestamp.Minute);
+ Assert.AreEqual(23, spi2.Timestamp.Second);
+ Assert.AreEqual(538, spi2.Timestamp.Millisecond);
+ }
- ASDU asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- int addedCounter = 0;
- int ioa = 100;
+ [Test()]
+ public void TestSinglePointInformationWithCP56Time2a()
+ {
+ byte[] buffer = new byte[257];
- while (asdu.AddInformationObject (new SinglePointInformation (ioa, false, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ BufferFrame bf = new BufferFrame(buffer, 0);
- Assert.AreEqual (127, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
+ DateTime dateTime = DateTime.UtcNow;
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ CP56Time2a time = new CP56Time2a(dateTime);
- while (asdu.AddInformationObject (new DoublePointInformation (ioa, DoublePointValue.OFF, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ SinglePointWithCP56Time2a spi = new SinglePointWithCP56Time2a(103, true, new QualityDescriptor(), time);
- Assert.AreEqual (127, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ spi.Encode(bf, alParameters, false);
+ Assert.AreEqual(alParameters.SizeOfIOA + spi.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(11, bf.GetMsgSize());
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ SinglePointWithCP56Time2a spi2 = new SinglePointWithCP56Time2a(alParameters, buffer, 0, false);
- while (asdu.AddInformationObject (new MeasuredValueNormalized (ioa, 1f, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ Assert.AreEqual(103, spi2.ObjectAddress);
+ Assert.AreEqual(true, spi2.Value);
+ Assert.AreEqual(time.Year, spi2.Timestamp.Year);
+ Assert.AreEqual(time.Month, spi2.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, spi2.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, spi2.Timestamp.Minute);
+ Assert.AreEqual(time.Second, spi2.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, spi2.Timestamp.Millisecond);
+ }
- Assert.AreEqual (80, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ [Test()]
+ public void TestDoublePointInformation()
+ {
+ byte[] buffer = new byte[257];
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ BufferFrame bf = new BufferFrame(buffer, 0);
- while (asdu.AddInformationObject (new MeasuredValueScaled (ioa, 0, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- Assert.AreEqual (80, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ DoublePointInformation dpi = new DoublePointInformation(101, DoublePointValue.OFF, new QualityDescriptor());
+ dpi.Encode(bf, alParameters, true);
+ Assert.AreEqual(1, bf.GetMsgSize());
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ bf.ResetFrame();
- while (asdu.AddInformationObject (new MeasuredValueShort (ioa, 0f, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ dpi.Encode(bf, alParameters, false);
- Assert.AreEqual (48, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ Assert.AreEqual(alParameters.SizeOfIOA + dpi.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(4, bf.GetMsgSize());
+ DoublePointInformation dpi2 = new DoublePointInformation(alParameters, buffer, 0, false);
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ Assert.AreEqual(101, dpi2.ObjectAddress);
+ Assert.AreEqual(DoublePointValue.OFF, dpi2.Value);
+ }
- while (asdu.AddInformationObject (new StepPositionInformation (ioa, 0, false, new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ [Test()]
+ public void TestDoublePointInformationWithCP24Time2a()
+ {
+ byte[] buffer = new byte[257];
- Assert.AreEqual (120, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ BufferFrame bf = new BufferFrame(buffer, 0);
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ CP24Time2a time = new CP24Time2a(45, 23, 538);
- while (asdu.AddInformationObject (new IntegratedTotals (ioa, new BinaryCounterReading ()))) {
- ioa++;
- addedCounter++;
- }
+ DoublePointWithCP24Time2a dpi = new DoublePointWithCP24Time2a(101, DoublePointValue.ON, new QualityDescriptor(), time);
- Assert.AreEqual (48, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ dpi.Encode(bf, alParameters, true);
+ Assert.AreEqual(4, bf.GetMsgSize());
+ bf.ResetFrame();
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ dpi.Encode(bf, alParameters, false);
- while (asdu.AddInformationObject (new PackedSinglePointWithSCD (ioa, new StatusAndStatusChangeDetection (), new QualityDescriptor ()))) {
- ioa++;
- addedCounter++;
- }
+ Assert.AreEqual(alParameters.SizeOfIOA + dpi.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(7, bf.GetMsgSize());
- Assert.AreEqual (48, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ DoublePointWithCP24Time2a dpi2 = new DoublePointWithCP24Time2a(alParameters, buffer, 0, false);
+ Assert.AreEqual(101, dpi2.ObjectAddress);
+ Assert.AreEqual(DoublePointValue.ON, dpi2.Value);
+ Assert.AreEqual(45, dpi2.Timestamp.Minute);
+ Assert.AreEqual(23, dpi2.Timestamp.Second);
+ Assert.AreEqual(538, dpi2.Timestamp.Millisecond);
+ }
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ [Test()]
+ public void TestMeasuredValueShortWithCP24Time2a()
+ {
+ byte[] buffer = new byte[257];
- while (asdu.AddInformationObject (new PackedOutputCircuitInfo (ioa, new OutputCircuitInfo (), new QualityDescriptorP (), new CP16Time2a (10), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ BufferFrame bf = new BufferFrame(buffer, 0);
- Assert.AreEqual (34, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- asdu = new ASDU (cp, CauseOfTransmission.PERIODIC, false, false, 0, 1, true);
- addedCounter = 0;
- ioa = 100;
+ CP24Time2a time = new CP24Time2a(45, 23, 538);
- while (asdu.AddInformationObject (new PackedStartEventsOfProtectionEquipment (ioa, new StartEvent (), new QualityDescriptorP (), new CP16Time2a (0), new CP24Time2a ()))) {
- ioa++;
- addedCounter++;
- }
+ MeasuredValueShortWithCP24Time2a mvs = new MeasuredValueShortWithCP24Time2a(101, 0, new QualityDescriptor(), time);
- Assert.AreEqual (34, addedCounter);
- Assert.NotNull (asdu.AsByteArray ());
+ mvs.Encode(bf, alParameters, true);
+ Assert.AreEqual(8, bf.GetMsgSize());
- }
+ bf.ResetFrame();
- [Test ()]
- //[Ignore("Ignore to save execution time")]
- public void TestSendTestFR () {
- ApplicationLayerParameters clientParameters = new ApplicationLayerParameters ();
- APCIParameters clientApciParamters = new APCIParameters ();
- ApplicationLayerParameters serverParameters = new ApplicationLayerParameters ();
- APCIParameters serverApciParamters = new APCIParameters ();
+ mvs.Encode(bf, alParameters, false);
- clientApciParamters.T3 = 1;
+ Assert.AreEqual(alParameters.SizeOfIOA + mvs.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(11, bf.GetMsgSize());
- Server server = new Server (serverApciParamters, serverParameters);
+ MeasuredValueShortWithCP24Time2a mvs2 = new MeasuredValueShortWithCP24Time2a(alParameters, buffer, 0, false);
- server.SetLocalPort (20213);
+ Assert.AreEqual(101, mvs2.ObjectAddress);
+ Assert.AreEqual(0, mvs2.Value);
+ Assert.AreEqual(45, mvs2.Timestamp.Minute);
+ Assert.AreEqual(23, mvs2.Timestamp.Second);
+ Assert.AreEqual(538, mvs2.Timestamp.Millisecond);
+ }
- server.Start ();
+ [Test()]
+ public void TestMeasuredValueShortWithCP56Time2a()
+ {
+ byte[] buffer = new byte[257];
- Connection connection = new Connection ("127.0.0.1", 20213, clientApciParamters, clientParameters);
+ BufferFrame bf = new BufferFrame(buffer, 0);
- connection.Connect ();
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- ASDU asdu = new ASDU (clientParameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
- asdu.AddInformationObject (new SinglePointInformation (100, false, new QualityDescriptor ()));
+ DateTime dateTime = DateTime.UtcNow;
- connection.SendASDU (asdu);
+ CP56Time2a time = new CP56Time2a(dateTime);
- Assert.AreEqual (2, connection.GetStatistics ().SentMsgCounter); /* STARTDT + ASDU */
+ MeasuredValueShortWithCP56Time2a mvs = new MeasuredValueShortWithCP56Time2a(101, 0, new QualityDescriptor(), time);
- while (connection.GetStatistics ().RcvdMsgCounter < 2)
- Thread.Sleep (1);
+ mvs.Encode(bf, alParameters, true);
+ Assert.AreEqual(12, bf.GetMsgSize());
- Assert.AreEqual (2, connection.GetStatistics ().RcvdMsgCounter); /* STARTDT_CON + ASDU */
+ bf.ResetFrame();
- Thread.Sleep (2500);
+ mvs.Encode(bf, alParameters, false);
- connection.Close ();
- server.Stop ();
+ Assert.AreEqual(alParameters.SizeOfIOA + mvs.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(15, bf.GetMsgSize());
- Assert.AreEqual (4, connection.GetStatistics ().RcvdMsgCounter); /* STARTDT_CON + ASDU + TESTFR_CON */
+ MeasuredValueShortWithCP56Time2a mvs2 = new MeasuredValueShortWithCP56Time2a(alParameters, buffer, 0, false);
- Assert.AreEqual (2, connection.GetStatistics ().RcvdTestFrConCounter);
+ Assert.AreEqual(101, mvs2.ObjectAddress);
+ Assert.AreEqual(0, mvs2.Value);
+ Assert.AreEqual(time.Year, mvs2.Timestamp.Year);
+ Assert.AreEqual(time.Month, mvs2.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, mvs2.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, mvs2.Timestamp.Minute);
+ Assert.AreEqual(time.Second, mvs2.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, mvs2.Timestamp.Millisecond);
}
- private static bool testSendTestFRTimeoutMasterRawMessageHandler (object param, byte [] msg, int msgSize)
+ [Test()]
+ public void TestMeasuredValueNormalizedWithCP24Time2a()
{
- // intercept TESTFR_CON message
- if ((msgSize == 6) && (msg [2] == 0x83))
- return false;
- else
- return true;
- }
+ byte[] buffer = new byte[257];
- ///
- /// This test checks that the connection will be closed when the master
- /// doesn't receive the TESTFR_CON messages
- ///
- [Test ()]
- //[Ignore("Ignore to save execution time")]
- public void TestSendTestFRTimeoutMaster () {
- ApplicationLayerParameters clientParameters = new ApplicationLayerParameters ();
- APCIParameters clientApciParamters = new APCIParameters ();
- ApplicationLayerParameters serverParameters = new ApplicationLayerParameters ();
- APCIParameters serverApciParamters = new APCIParameters ();
+ BufferFrame bf = new BufferFrame(buffer, 0);
- clientApciParamters.T3 = 1;
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- Server server = new Server (serverApciParamters, serverParameters);
+ CP24Time2a time = new CP24Time2a(45, 23, 538);
- server.SetLocalPort (20213);
+ MeasuredValueNormalizedWithCP24Time2a mvn = new MeasuredValueNormalizedWithCP24Time2a(201, 0.5f, new QualityDescriptor(), time);
- server.Start ();
+ mvn.Encode(bf, alParameters, true);
+ Assert.AreEqual(6, bf.GetMsgSize());
- Connection connection = new Connection ("127.0.0.1", 20213, clientApciParamters, clientParameters);
+ bf.ResetFrame();
- connection.Connect ();
+ mvn.Encode(bf, alParameters, false);
- connection.SetReceivedRawMessageHandler (testSendTestFRTimeoutMasterRawMessageHandler, null);
+ Assert.AreEqual(alParameters.SizeOfIOA + mvn.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(9, bf.GetMsgSize());
- ASDU asdu = new ASDU (clientParameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
- asdu.AddInformationObject (new SinglePointInformation (100, false, new QualityDescriptor ()));
+ MeasuredValueNormalizedWithCP24Time2a mvn2 = new MeasuredValueNormalizedWithCP24Time2a(alParameters, buffer, 0, false);
- connection.SendASDU (asdu);
+ Assert.AreEqual(201, mvn2.ObjectAddress);
+ Assert.AreEqual(0.5f, mvn2.NormalizedValue, 0.001);
+ Assert.AreEqual(45, mvn2.Timestamp.Minute);
+ Assert.AreEqual(23, mvn2.Timestamp.Second);
+ Assert.AreEqual(538, mvn2.Timestamp.Millisecond);
+ }
+
+ [Test()]
+ public void TestMeasuredValueNormalizedWithCP56Time2a()
+ {
+ byte[] buffer = new byte[257];
- Assert.AreEqual (2, connection.GetStatistics ().SentMsgCounter); /* STARTDT + ASDU */
+ BufferFrame bf = new BufferFrame(buffer, 0);
- while (connection.GetStatistics ().RcvdMsgCounter < 2)
- Thread.Sleep (1);
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- Assert.AreEqual (2, connection.GetStatistics ().RcvdMsgCounter); /* STARTDT_CON + ASDU */
+ DateTime dateTime = DateTime.UtcNow;
- Thread.Sleep (6000);
+ CP56Time2a time = new CP56Time2a(dateTime);
- // Expect connection to be closed due to three missing TESTFR_CON responses
- Assert.IsFalse (connection.IsRunning);
+ MeasuredValueNormalizedWithCP56Time2a mvn = new MeasuredValueNormalizedWithCP56Time2a(201, 0.5f, new QualityDescriptor(), time);
- ConnectionException ce = null;
+ mvn.Encode(bf, alParameters, true);
+ Assert.AreEqual(10, bf.GetMsgSize());
- // Connection is closed. SendASDU should fail
- try {
- connection.SendASDU (asdu);
- } catch (ConnectionException e) {
- ce = e;
- }
+ bf.ResetFrame();
- Assert.IsNotNull (ce);
- Assert.AreEqual ("not connected", ce.Message);
+ mvn.Encode(bf, alParameters, false);
- connection.Close ();
- server.Stop ();
+ Assert.AreEqual(alParameters.SizeOfIOA + mvn.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(13, bf.GetMsgSize());
- Assert.AreEqual (5, connection.GetStatistics ().RcvdMsgCounter); /* STARTDT_CON + ASDU + TESTFR_CON */
+ MeasuredValueNormalizedWithCP56Time2a mvn2 = new MeasuredValueNormalizedWithCP56Time2a(alParameters, buffer, 0, false);
- Assert.AreEqual (0, connection.GetStatistics ().RcvdTestFrConCounter);
+ Assert.AreEqual(201, mvn2.ObjectAddress);
+ Assert.AreEqual(0.5f, mvn2.NormalizedValue, 0.001);
+ Assert.AreEqual(time.Year, mvn2.Timestamp.Year);
+ Assert.AreEqual(time.Month, mvn2.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, mvn2.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, mvn2.Timestamp.Minute);
+ Assert.AreEqual(time.Second, mvn2.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, mvn2.Timestamp.Millisecond);
}
- private static bool testSendTestFRTimeoutSlaveRawMessageHandler (object param, byte [] msg, int msgSize)
+
+ [Test()]
+ public void TestMeasuredValueScaledWithCP24Time2a()
{
- // intercept TESTFR_ACT messages for so that the master doesn't response
- if ((msgSize == 6) && (msg [2] == 0x43))
- return false;
- else
- return true;
- }
+ byte[] buffer = new byte[257];
- ///
- /// This test checks that the connection will be closed when the master
- /// doesn't send the TESTFR_CON messages
- ///
- [Test ()]
- //[Ignore("Ignore to save execution time")]
- public void TestSendTestFRTimeoutSlave () {
- ApplicationLayerParameters clientParameters = new ApplicationLayerParameters ();
- APCIParameters clientApciParamters = new APCIParameters ();
- ApplicationLayerParameters serverParameters = new ApplicationLayerParameters ();
- APCIParameters serverApciParamters = new APCIParameters ();
+ BufferFrame bf = new BufferFrame(buffer, 0);
- serverApciParamters.T3 = 1;
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- Server server = new Server (serverApciParamters, serverParameters);
+ CP24Time2a time = new CP24Time2a(45, 23, 538);
- server.SetLocalPort (20213);
+ MeasuredValueScaledWithCP24Time2a mvs = new MeasuredValueScaledWithCP24Time2a(101, 0, new QualityDescriptor(), time);
- server.Start ();
+ mvs.Encode(bf, alParameters, true);
+ Assert.AreEqual(6, bf.GetMsgSize());
- Connection connection = new Connection ("127.0.0.1", 20213, clientApciParamters, clientParameters);
+ bf.ResetFrame();
- connection.DebugOutput = true;
- connection.SetReceivedRawMessageHandler (testSendTestFRTimeoutSlaveRawMessageHandler, null);
+ mvs.Encode(bf, alParameters, false);
- connection.Connect ();
+ Assert.AreEqual(alParameters.SizeOfIOA + mvs.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(9, bf.GetMsgSize());
- Assert.AreEqual (1, connection.GetStatistics ().SentMsgCounter); /* STARTDT */
+ MeasuredValueScaledWithCP24Time2a mvs2 = new MeasuredValueScaledWithCP24Time2a(alParameters, buffer, 0, false);
- while (connection.GetStatistics ().RcvdMsgCounter < 1)
- Thread.Sleep (1);
+ Assert.AreEqual(101, mvs2.ObjectAddress);
+ Assert.AreEqual(0, mvs2.ScaledValue.Value);
+ Assert.AreEqual(45, mvs2.Timestamp.Minute);
+ Assert.AreEqual(23, mvs2.Timestamp.Second);
+ Assert.AreEqual(538, mvs2.Timestamp.Millisecond);
+ }
- Assert.AreEqual (1, connection.GetStatistics ().RcvdMsgCounter); /* STARTDT_CON */
+ [Test()]
+ public void TestMeasuredValueScaledWithCP56Time2a()
+ {
+ byte[] buffer = new byte[257];
- Thread.Sleep (6000);
+ BufferFrame bf = new BufferFrame(buffer, 0);
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- // Connection is closed. SendASDU should fail
- try {
- ASDU asdu = new ASDU (clientParameters, CauseOfTransmission.SPONTANEOUS, false, false, 0, 1, false);
- asdu.AddInformationObject (new SinglePointInformation (100, false, new QualityDescriptor ()));
+ DateTime dateTime = DateTime.UtcNow;
- connection.SendASDU (asdu);
- } catch (ConnectionException) {
- }
+ CP56Time2a time = new CP56Time2a(dateTime);
+ MeasuredValueScaledWithCP56Time2a mvs = new MeasuredValueScaledWithCP56Time2a(101, 0, new QualityDescriptor(), time);
- while (connection.IsRunning == true)
- Thread.Sleep (10);
+ mvs.Encode(bf, alParameters, true);
+ Assert.AreEqual(10, bf.GetMsgSize());
- connection.Close ();
- server.Stop ();
+ bf.ResetFrame();
- // Assert.AreEqual (5, connection.GetStatistics ().RcvdMsgCounter); /* STARTDT_CON + ASDU + TESTFR_CON */
+ mvs.Encode(bf, alParameters, false);
- // Assert.AreEqual (0, connection.GetStatistics ().RcvdTestFrConCounter);
- }
+ Assert.AreEqual(alParameters.SizeOfIOA + mvs.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(13, bf.GetMsgSize());
+ MeasuredValueScaledWithCP56Time2a mvs2 = new MeasuredValueScaledWithCP56Time2a(alParameters, buffer, 0, false);
- [Test ()]
- public void TestEncodeDecodeSetpointCommandNormalized () {
- Server server = new Server ();
- server.SetLocalPort (20213);
+ Assert.AreEqual(101, mvs2.ObjectAddress);
+ Assert.AreEqual(0, mvs2.ScaledValue.Value);
+ Assert.AreEqual(time.Year, mvs2.Timestamp.Year);
+ Assert.AreEqual(time.Month, mvs2.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, mvs2.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, mvs2.Timestamp.Minute);
+ Assert.AreEqual(time.Second, mvs2.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, mvs2.Timestamp.Millisecond);
+ }
- float recvValue = 0f;
- float sendValue = 1.0f;
- bool hasReceived = false;
+ [Test()]
+ public void TestDoublePointInformationWithCP56Time2a()
+ {
+ byte[] buffer = new byte[257];
- server.SetASDUHandler (delegate (object parameter, IMasterConnection con, ASDU asdu) {
+ BufferFrame bf = new BufferFrame(buffer, 0);
- if (asdu.TypeId == TypeID.C_SE_NA_1) {
- SetpointCommandNormalized spn = (SetpointCommandNormalized)asdu.GetElement (0);
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- recvValue = spn.NormalizedValue;
- hasReceived = true;
- }
+ DateTime dateTime = DateTime.UtcNow;
- return true;
- }, null);
- server.Start ();
+ CP56Time2a time = new CP56Time2a(dateTime);
- Connection connection = new Connection ("127.0.0.1", 20213);
- connection.Connect ();
+ DoublePointWithCP56Time2a dpi = new DoublePointWithCP56Time2a(101, DoublePointValue.INTERMEDIATE, new QualityDescriptor(), time);
- ASDU newAsdu = new ASDU (server.GetApplicationLayerParameters (), CauseOfTransmission.ACTIVATION, false, false, 0, 1, false);
- newAsdu.AddInformationObject (new SetpointCommandNormalized (100, sendValue, new SetpointCommandQualifier (false, 0)));
+ dpi.Encode(bf, alParameters, true);
+ Assert.AreEqual(8, bf.GetMsgSize());
- connection.SendASDU (newAsdu);
+ bf.ResetFrame();
- while (hasReceived == false)
- Thread.Sleep (50);
+ dpi.Encode(bf, alParameters, false);
+
+ Assert.AreEqual(alParameters.SizeOfIOA + dpi.GetEncodedSize(), bf.GetMsgSize());
+ Assert.AreEqual(11, bf.GetMsgSize());
- connection.Close ();
- server.Stop ();
+ DoublePointWithCP56Time2a dpi2 = new DoublePointWithCP56Time2a(alParameters, buffer, 0, false);
- Assert.AreEqual (sendValue, recvValue, 0.001);
+ Assert.AreEqual(101, dpi2.ObjectAddress);
+ Assert.AreEqual(DoublePointValue.INTERMEDIATE, dpi2.Value);
+ Assert.AreEqual(time.Year, dpi2.Timestamp.Year);
+ Assert.AreEqual(time.Month, dpi2.Timestamp.Month);
+ Assert.AreEqual(time.DayOfMonth, dpi2.Timestamp.DayOfMonth);
+ Assert.AreEqual(time.Minute, dpi2.Timestamp.Minute);
+ Assert.AreEqual(time.Second, dpi2.Timestamp.Second);
+ Assert.AreEqual(time.Millisecond, dpi2.Timestamp.Millisecond);
}
- [Test ()]
- public void TestEncodeDecodePrivateInformationObject ()
+ [Test()]
+ public void TestCP56Time2a()
{
- Server server = new Server ();
- server.SetLocalPort (20213);
- server.DebugOutput = true;
-
- int recvValue = 0;
- int sendValue = 12345;
- bool hasReceived = false;
+ CP56Time2a time = new CP56Time2a();
- PrivateInformationObjectTypes privateObjects = new PrivateInformationObjectTypes ();
- privateObjects.AddPrivateInformationObjectType ((TypeID)41, new TestInteger32Object ());
+ Assert.AreEqual(time.Year, 0);
- server.SetASDUHandler (delegate (object parameter, IMasterConnection con, ASDU asdu) {
-
- if (asdu.TypeId == (TypeID)41) {
+ time.Year = 2017;
- TestInteger32Object spn = (TestInteger32Object)asdu.GetElement (0, privateObjects);
+ Assert.AreEqual(time.Year, 17);
- recvValue = spn.Value;
- hasReceived = true;
- }
+ time.Year = 1980;
- return true;
- }, null);
+ Assert.AreEqual(time.Year, 80);
+ }
- server.Start ();
+ [Test()]
+ public void TestMeasuredValueWithoutQuality()
+ {
+ byte[] buffer = new byte[257];
- Connection connection = new Connection ("127.0.0.1", 20213);
- connection.Connect ();
+ BufferFrame bf = new BufferFrame(buffer, 0);
- ASDU newAsdu = new ASDU (server.GetApplicationLayerParameters (), CauseOfTransmission.ACTIVATION, false, false, 0, 1, false);
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- newAsdu.AddInformationObject (new TestInteger32Object (100, sendValue));
+ MeasuredValueNormalizedWithoutQuality mvn = new MeasuredValueNormalizedWithoutQuality(201, 0.5f);
- connection.SendASDU (newAsdu);
+ mvn.Encode(bf, alParameters, true);
+ Assert.AreEqual(2, bf.GetMsgSize());
- while (hasReceived == false)
- Thread.Sleep (50);
+ bf.ResetFrame();
- connection.Close ();
- server.Stop ();
+ mvn.Encode(bf, alParameters, false);
+ Assert.AreEqual(alParameters.SizeOfIOA + mvn.GetEncodedSize(), bf.GetMsgSize());
- Assert.AreEqual (sendValue, recvValue);
+ MeasuredValueNormalizedWithoutQuality mvn2 = new MeasuredValueNormalizedWithoutQuality(alParameters, buffer, 0, false);
+ Assert.AreEqual(201, mvn2.ObjectAddress);
+ Assert.AreEqual(0.5f, mvn2.NormalizedValue, 0.001);
}
- [Test ()]
- public void TestSingleCommand ()
+ [Test()]
+ public void TestMeasuredValueNormalized()
{
- SingleCommand sc = new SingleCommand (10002, true, false, 12);
-
- Assert.AreEqual (10002, sc.ObjectAddress);
- Assert.AreEqual (true, sc.State);
- Assert.AreEqual (false, sc.Select);
- Assert.AreEqual (12, sc.QU);
+ byte[] buffer = new byte[257];
- sc = new SingleCommand (10002, false, true, 3);
+ BufferFrame bf = new BufferFrame(buffer, 0);
- Assert.AreEqual (10002, sc.ObjectAddress);
- Assert.AreEqual (false, sc.State);
- Assert.AreEqual (true, sc.Select);
- Assert.AreEqual (3, sc.QU);
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- sc.QU = 17;
-
- Assert.AreEqual (17, sc.QU);
- Assert.AreEqual (false, sc.State);
- Assert.AreEqual (true, sc.Select);
- }
+ MeasuredValueNormalized mvn = new MeasuredValueNormalized(201, 0.5f, new QualityDescriptor());
- [Test ()]
- public void TestSinglePointInformationClientServer ()
- {
- SinglePointInformation spi = new SinglePointInformation (101, true, new QualityDescriptor ());
+ mvn.Encode(bf, alParameters, true);
+ Assert.AreEqual(3, bf.GetMsgSize());
- ASDU newAsdu = new ASDU (new ApplicationLayerParameters (), CauseOfTransmission.PERIODIC,
- false, false, 0, 1, false);
+ bf.ResetFrame();
- newAsdu.AddInformationObject (spi);
+ mvn.Encode(bf, alParameters, false);
+ Assert.AreEqual(alParameters.SizeOfIOA + mvn.GetEncodedSize(), bf.GetMsgSize());
- Server server = new Server ();
- server.SetLocalPort (20213);
+ MeasuredValueNormalized mvn2 = new MeasuredValueNormalized(alParameters, buffer, 0, false);
- bool hasReceived = false;
+ Assert.AreEqual(201, mvn2.ObjectAddress);
+ Assert.AreEqual(0.5f, mvn2.NormalizedValue, 0.001);
+ }
- server.SetASDUHandler (delegate (object parameter, IMasterConnection con, ASDU asdu) {
- if (asdu.TypeId == TypeID.M_SP_NA_1) {
+ [Test()]
+ public void TestMeasuredValueShort()
+ {
+ byte[] buffer = new byte[257];
- SinglePointInformation spn = (SinglePointInformation)asdu.GetElement (0);
+ BufferFrame bf = new BufferFrame(buffer, 0);
- Assert.AreEqual (spi.Value, spn.Value);
- hasReceived = true;
- }
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- return true;
- }, null);
+ MeasuredValueShort mvs = new MeasuredValueShort(201, 0.5f, new QualityDescriptor());
- server.Start ();
+ mvs.Encode(bf, alParameters, true);
+ Assert.AreEqual(5, bf.GetMsgSize());
- Connection connection = new Connection ("127.0.0.1", 20213);
- connection.Connect ();
+ bf.ResetFrame();
- connection.SendASDU (newAsdu);
+ mvs.Encode(bf, alParameters, false);
+ Assert.AreEqual(alParameters.SizeOfIOA + mvs.GetEncodedSize(), bf.GetMsgSize());
- while (hasReceived == false)
- Thread.Sleep (50);
+ MeasuredValueShort mvs2 = new MeasuredValueShort(alParameters, buffer, 0, false);
- connection.Close ();
- server.Stop ();
+ Assert.AreEqual(201, mvs2.ObjectAddress);
+ Assert.AreEqual(0.5f, mvs2.Value, 0.001);
}
- [Test ()]
- public void TestSinglePointInformation ()
+
+ [Test()]
+ public void TestMeasuredValueScaled()
{
- byte [] buffer = new byte [257];
+ byte[] buffer = new byte[257];
- BufferFrame bf = new BufferFrame (buffer, 0);
+ BufferFrame bf = new BufferFrame(buffer, 0);
- ApplicationLayerParameters alParameters = new ApplicationLayerParameters ();
+ ApplicationLayerParameters alParameters = new ApplicationLayerParameters();
- SinglePointInformation spi = new SinglePointInformation (101, true, new QualityDescriptor ());
+ MeasuredValueScaled mvs = new MeasuredValueScaled(201, 0, new QualityDescriptor());
- spi.Encode (bf, alParameters, true);
- Assert.AreEqual (1, bf.GetMsgSize ());
+ mvs.Encode(bf, alParameters, true);
+ Assert.AreEqual(3, bf.GetMsgSize());
- bf.ResetFrame ();
+ bf.ResetFrame();
- spi.Encode (bf, alParameters, false);
+ mvs.Encode(bf, alParameters, false);
+ Assert.AreEqual(alParameters.SizeOfIOA + mvs.GetEncodedSize(), bf.GetMsgSize());
- Assert.AreEqual (alParameters.SizeOfIOA + spi.GetEncodedSize (), bf.GetMsgSize ());
- Assert.AreEqual (4, bf.GetMsgSize ());
+ MeasuredValueScaled mvs2 = new MeasuredValueScaled(alParameters, buffer, 0, false);
- SinglePointInformation spi2 = new SinglePointInformation (alParameters, buffer, 0, false);
+ Assert.AreEqual(201, mvs2.ObjectAddress);
+ Assert.AreEqual(0, mvs2.ScaledValue.Value);
- Assert.AreEqual (101, spi2.ObjectAddress);
- Assert.AreEqual (true, spi2.Value);
}
- [Test ()]
- public void TestSinglePointInformationWithCp24Time2a ()
- {
- byte [] buffer = new byte [257];
- BufferFrame bf = new BufferFrame (buffer, 0);
+ public class SimpleFile : TransparentFile
+ {
+ public SimpleFile(int ca, int ioa, NameOfFile nof)
+ : base(ca, ioa, nof)
+ {
+ }
- ApplicationLayerParameters alParameters = new ApplicationLayerParameters ();
+ public bool transferComplete = false;
+ public bool success = false;
- CP24Time2a time = new CP24Time2a (45, 23, 538);
+ public override void TransferComplete(bool success)
+ {
+ Console.WriteLine("Transfer complete: " + success.ToString());
+ transferComplete = true;
+ this.success = success;
+ }
+ }
- SinglePointWithCP24Time2a spi = new SinglePointWithCP24Time2a (102, false, new QualityDescriptor (), time);
+ public class Receiver : IFileReceiver
+ {
+ public bool finishedCalled = false;
- spi.Encode (bf, alParameters, false);
+ public byte[] recvBuffer = new byte[10000];
+ public int recvdBytes = 0;
+ public byte lastSection = 0;
- Assert.AreEqual (alParameters.SizeOfIOA + spi.GetEncodedSize (), bf.GetMsgSize ());
- Assert.AreEqual (7, bf.GetMsgSize ());
+ public void Finished(FileErrorCode result)
+ {
+ Console.WriteLine("File download finished - code: " + result.ToString());
+ finishedCalled = true;
+ }
- SinglePointWithCP24Time2a spi2 = new SinglePointWithCP24Time2a (alParameters, buffer, 0, false);
- Assert.AreEqual (102, spi2.ObjectAddress);
- Assert.AreEqual (false, spi2.Value);
- Assert.AreEqual (45, spi2.Timestamp.Minute);
- Assert.AreEqual (23, spi2.Timestamp.Second);
- Assert.AreEqual (538, spi2.Timestamp.Millisecond);
+ public void SegmentReceived(byte sectionName, int offset, int size, byte[] data)
+ {
+ lastSection = sectionName;
+ Array.Copy(data, 0, recvBuffer, recvdBytes, size);
+ recvdBytes += size;
+ Console.WriteLine("File segment - sectionName: {0} offset: {1} size: {2}", sectionName, offset, size);
+ for (int i = 0; i < size; i++)
+ {
+ Console.Write(" " + data[i]);
+ }
+ Console.WriteLine();
+ }
}
- [Test ()]
- public void TestSinglePointInformationWithCP56Time2a ()
+ [Test()]
+ public void TestFileUploadSingleSection()
{
- byte [] buffer = new byte [257];
+ Server server = new Server();
+ int port = GetPort();
- BufferFrame bf = new BufferFrame (buffer, 0);
+ server.SetLocalPort(port);
+ server.Start();
- ApplicationLayerParameters alParameters = new ApplicationLayerParameters ();
+ SimpleFile file = new SimpleFile(1, 30000, NameOfFile.TRANSPARENT_FILE);
- DateTime dateTime = DateTime.UtcNow;
+ byte[] fileData = new byte[100];
+
+ for (int i = 0; i < 100; i++)
+ fileData[i] = (byte)(i);
- CP56Time2a time = new CP56Time2a (dateTime);
+ file.AddSection(fileData);
- SinglePointWithCP56Time2a spi = new SinglePointWithCP56Time2a (103, true, new QualityDescriptor (), time);
+ server.GetAvailableFiles().AddFile(file);
- spi.Encode (bf, alParameters, false);
+ Thread.Sleep(2000);
- Assert.AreEqual (alParameters.SizeOfIOA + spi.GetEncodedSize (), bf.GetMsgSize ());
- Assert.AreEqual (11, bf.GetMsgSize ());
+ Connection con = new Connection("127.0.0.1", port);
+ con.Connect();
- SinglePointWithCP56Time2a spi2 = new SinglePointWithCP56Time2a (alParameters, buffer, 0, false);
+ Thread.Sleep(2000);
- Assert.AreEqual (103, spi2.ObjectAddress);
- Assert.AreEqual (true, spi2.Value);
+ Receiver receiver = new Receiver();
- Assert.AreEqual (time.Year, spi2.Timestamp.Year);
- Assert.AreEqual (time.Month, spi2.Timestamp.Month);
- Assert.AreEqual (time.DayOfMonth, spi2.Timestamp.DayOfMonth);
- Assert.AreEqual (time.Minute, spi2.Timestamp.Minute);
- Assert.AreEqual (time.Second, spi2.Timestamp.Second);
- Assert.AreEqual (time.Millisecond, spi2.Timestamp.Millisecond);
- }
+ Assert.IsTrue(con.IsRunning);
+ Assert.IsTrue(server.IsRunning());
- [Test ()]
- public void TestDoublePointInformation ()
- {
- byte [] buffer = new byte [257];
+ try
+ {
+ con.GetFile(1, 30000, NameOfFile.TRANSPARENT_FILE, receiver, 30000);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[Test] GetFile threw an exception: {ex.Message}");
+ Assert.Fail("GetFile failed!");
+ }
+
+ Thread.Sleep(30000);
+ Assert.IsTrue(receiver.finishedCalled);
+ Assert.AreEqual(100, receiver.recvdBytes);
+ Assert.AreEqual(1, receiver.lastSection);
- BufferFrame bf = new BufferFrame (buffer, 0);
+ for (int i = 0; i < 100; i++)
+ {
+ Assert.AreEqual(receiver.recvBuffer[i], i);
+ }
- ApplicationLayerParameters alParameters = new ApplicationLayerParameters ();
+ con.Close();
- DoublePointInformation dpi = new DoublePointInformation (101, DoublePointValue.OFF, new QualityDescriptor ());
+ server.Stop();
+ }
- dpi.Encode (bf, alParameters, true);
- Assert.AreEqual (1, bf.GetMsgSize ());
+ [Test()]
+ public void TestFileUploadMultipleSections()
+ {
+ Server server = new Server();
+ int port = GetPort();
- bf.ResetFrame ();
+ server.SetLocalPort(port);
+ server.Start();
- dpi.Encode (bf, alParameters, false);
+ SimpleFile file = new SimpleFile(1, 30000, NameOfFile.TRANSPARENT_FILE);
- Assert.AreEqual (alParameters.SizeOfIOA + dpi.GetEncodedSize (), bf.GetMsgSize ());
- Assert.AreEqual (4, bf.GetMsgSize ());
+ byte[] fileData = new byte[100];
- DoublePointInformation dpi2 = new DoublePointInformation (alParameters, buffer, 0, false);
+ for (int i = 0; i < 100; i++)
+ fileData[i] = (byte)(i);
- Assert.AreEqual (101, dpi2.ObjectAddress);
- Assert.AreEqual (DoublePointValue.OFF, dpi2.Value);
- }
+ byte[] fileData2 = new byte[100];
- [Test ()]
- public void TestDoublePointInformationWithCP24Time2a ()
- {
- byte [] buffer = new byte [257];
+ for (int i = 0; i < 100; i++)
+ fileData2[i] = (byte)(100 + i);
- BufferFrame bf = new BufferFrame (buffer, 0);
+ file.AddSection(fileData);
+ file.AddSection(fileData2);
- ApplicationLayerParameters alParameters = new ApplicationLayerParameters ();
+ server.GetAvailableFiles().AddFile(file);
- CP24Time2a time = new CP24Time2a (45, 23, 538);
+ Connection con = new Connection("127.0.0.1", port);
+ con.Connect();
- DoublePointWithCP24Time2a dpi = new DoublePointWithCP24Time2a (101, DoublePointValue.ON, new QualityDescriptor (), time);
- dpi.Encode (bf, alParameters, true);
- Assert.AreEqual (4, bf.GetMsgSize ());
+ Receiver receiver = new Receiver();
- bf.ResetFrame ();
+ con.GetFile(1, 30000, NameOfFile.TRANSPARENT_FILE, receiver);
- dpi.Encode (bf, alParameters, false);
+ Thread.Sleep(3000);
+ Assert.IsTrue(receiver.finishedCalled);
+ Assert.AreEqual(200, receiver.recvdBytes);
+ Assert.AreEqual(2, receiver.lastSection);
- Assert.AreEqual (alParameters.SizeOfIOA + dpi.GetEncodedSize (), bf.GetMsgSize ());
- Assert.AreEqual (7, bf.GetMsgSize ());
+ for (int i = 0; i < 200; i++)
+ {
+ Assert.AreEqual(receiver.recvBuffer[i], i);
+ }
- DoublePointWithCP24Time2a dpi2 = new DoublePointWithCP24Time2a (alParameters, buffer, 0, false);
+ con.Close();
- Assert.AreEqual (101, dpi2.ObjectAddress);
- Assert.AreEqual (DoublePointValue.ON, dpi2.Value);
- Assert.AreEqual (45, dpi2.Timestamp.Minute);
- Assert.AreEqual (23, dpi2.Timestamp.Second);
- Assert.AreEqual (538, dpi2.Timestamp.Millisecond);
+ server.Stop();
}
- [Test ()]
- public void TestDoublePointInformationWithCP56Time2a ()
+ [Test()]
+ public void TestFileUploadMultipleSectionsFreeFileName()
{
- byte [] buffer = new byte [257];
-
- BufferFrame bf = new BufferFrame (buffer, 0);
-
- ApplicationLayerParameters alParameters = new ApplicationLayerParameters ();
+ Server server = new Server();
+ int port = GetPort();
+ server.SetLocalPort(port);
+ server.Start();
- DateTime dateTime = DateTime.UtcNow;
+ SimpleFile file = new SimpleFile(1, 30000, (NameOfFile)12);
- CP56Time2a time = new CP56Time2a (dateTime);
+ byte[] fileData = new byte[100];
- DoublePointWithCP56Time2a dpi = new DoublePointWithCP56Time2a (101, DoublePointValue.INTERMEDIATE, new QualityDescriptor (), time);
+ for (int i = 0; i < 100; i++)
+ fileData[i] = (byte)(i);
- dpi.Encode (bf, alParameters, true);
- Assert.AreEqual (8, bf.GetMsgSize ());
+ byte[] fileData2 = new byte[100];
- bf.ResetFrame ();
+ for (int i = 0; i < 100; i++)
+ fileData2[i] = (byte)(100 + i);
- dpi.Encode (bf, alParameters, false);
+ file.AddSection(fileData);
+ file.AddSection(fileData2);
- Assert.AreEqual (alParameters.SizeOfIOA + dpi.GetEncodedSize (), bf.GetMsgSize ());
- Assert.AreEqual (11, bf.GetMsgSize ());
+ server.GetAvailableFiles().AddFile(file);
- DoublePointWithCP56Time2a dpi2 = new DoublePointWithCP56Time2a (alParameters, buffer, 0, false);
+ Connection con = new Connection("127.0.0.1", port);
+ con.Connect();
- Assert.AreEqual (101, dpi2.ObjectAddress);
- Assert.AreEqual (DoublePointValue.INTERMEDIATE, dpi2.Value);
- Assert.AreEqual (time.Year, dpi2.Timestamp.Year);
- Assert.AreEqual (time.Month, dpi2.Timestamp.Month);
- Assert.AreEqual (time.DayOfMonth, dpi2.Timestamp.DayOfMonth);
- Assert.AreEqual (time.Minute, dpi2.Timestamp.Minute);
- Assert.AreEqual (time.Second, dpi2.Timestamp.Second);
- Assert.AreEqual (time.Millisecond, dpi2.Timestamp.Millisecond);
- }
- [Test ()]
- public void TestCP56Time2a ()
- {
- CP56Time2a time = new CP56Time2a ();
+ Receiver receiver = new Receiver();
- Assert.AreEqual (time.Year, 0);
+ con.GetFile(1, 30000, (NameOfFile)12, receiver);
- time.Year = 2017;
+ Thread.Sleep(3000);
+ Assert.IsTrue(receiver.finishedCalled);
+ Assert.AreEqual(200, receiver.recvdBytes);
+ Assert.AreEqual(2, receiver.lastSection);
- Assert.AreEqual (time.Year, 17);
+ for (int i = 0; i < 200; i++)
+ {
+ Assert.AreEqual(receiver.recvBuffer[i], i);
+ }
- time.Year = 1980;
+ con.Close();
- Assert.AreEqual (time.Year, 80);
+ server.Stop();
}
- [Test ()]
- public void TestMeasuredValueNormalized ()
+ [Test()]
+ public void TestFileUploadMultipleSegments()
{
- byte [] buffer = new byte [257];
+ Server server = new Server();
+ int port = GetPort();
+ server.SetLocalPort(port);
+ server.Start();
- BufferFrame bf = new BufferFrame (buffer, 0);
+ SimpleFile file = new SimpleFile(1, 30000, NameOfFile.TRANSPARENT_FILE);
- ApplicationLayerParameters alParameters = new ApplicationLayerParameters ();
+ byte[] fileData = new byte[1000];
- MeasuredValueNormalized mvn = new MeasuredValueNormalized (201, 0.5f, new QualityDescriptor ());
+ for (int i = 0; i < 1000; i++)
+ fileData[i] = (byte)(i);
- mvn.Encode (bf, alParameters, true);
- Assert.AreEqual (3, bf.GetMsgSize ());
+ file.AddSection(fileData);
- bf.ResetFrame ();
+ server.GetAvailableFiles().AddFile(file);
- mvn.Encode (bf, alParameters, false);
- Assert.AreEqual (alParameters.SizeOfIOA + mvn.GetEncodedSize (), bf.GetMsgSize ());
+ Connection con = new Connection("127.0.0.1", port);
+ con.Connect();
- MeasuredValueNormalized mvn2 = new MeasuredValueNormalized (alParameters, buffer, 0, false);
- Assert.AreEqual (201, mvn2.ObjectAddress);
- Assert.AreEqual (0.5f, mvn2.NormalizedValue, 0.001);
- }
+ Receiver receiver = new Receiver();
- public class SimpleFile : TransparentFile
- {
- public SimpleFile (int ca, int ioa, NameOfFile nof)
- : base (ca, ioa, nof)
- {
- }
+ con.GetFile(1, 30000, NameOfFile.TRANSPARENT_FILE, receiver);
- public bool transferComplete = false;
- public bool success = false;
+ Thread.Sleep(3000);
+ Assert.IsTrue(receiver.finishedCalled);
+ Assert.AreEqual(1000, receiver.recvdBytes);
+ Assert.AreEqual(1, receiver.lastSection);
+ Assert.IsTrue(file.transferComplete);
+ Assert.IsTrue(file.success);
- public override void TransferComplete (bool success)
+ for (int i = 0; i < 1000; i++)
{
- Console.WriteLine ("Transfer complete: " + success.ToString ());
- transferComplete = true;
- this.success = success;
+ Assert.AreEqual(receiver.recvBuffer[i], (byte)i);
}
+
+ con.Close();
+
+ server.Stop();
}
- public class Receiver : IFileReceiver
- {
- public bool finishedCalled = false;
- public byte[] recvBuffer = new byte [10000];
- public int recvdBytes = 0;
- public byte lastSection = 0;
+ [Test()]
+ public void TestFileDownloadSingleSection()
+ {
+ Server server = new Server();
+ int port = GetPort();
+ server.SetLocalPort(port);
+ server.DebugOutput = true;
- public void Finished (FileErrorCode result)
- {
- Console.WriteLine ("File download finished - code: " + result.ToString ());
- finishedCalled = true;
- }
+ Receiver receiver = new Receiver();
- public void SegmentReceived (byte sectionName, int offset, int size, byte [] data)
+ server.SetFileReadyHandler((object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile) =>
{
- lastSection = sectionName;
- Array.Copy (data, 0, recvBuffer, recvdBytes, size);
- recvdBytes += size;
- Console.WriteLine ("File segment - sectionName: {0} offset: {1} size: {2}", sectionName, offset, size);
- for (int i = 0; i < size; i++) {
- Console.Write (" " + data [i]);
- }
- Console.WriteLine ();
- }
- }
-
- [Test ()]
- public void TestFileUploadSingleSection ()
- {
- Server server = new Server ();
- server.SetLocalPort (20213);
- server.Start ();
+ return receiver;
+ }, null);
+ server.Start();
- SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ SimpleFile file = new SimpleFile(1, 30000, NameOfFile.TRANSPARENT_FILE);
- byte [] fileData = new byte [100];
+ byte[] fileData = new byte[100];
for (int i = 0; i < 100; i++)
- fileData [i] = (byte)(i);
-
- file.AddSection (fileData);
+ fileData[i] = (byte)(i);
- server.GetAvailableFiles ().AddFile (file);
+ file.AddSection(fileData);
+ server.GetAvailableFiles().AddFile(file);
- Connection con = new Connection ("127.0.0.1", 20213);
- con.Connect ();
+ Connection con = new Connection("127.0.0.1", port);
+ con.Connect();
+ Thread.Sleep(2000);
+ try
+ {
+ con.SendFile(1, 30000, NameOfFile.TRANSPARENT_FILE, file);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[Test] SendFile threw an exception: {ex.Message}");
+ Assert.Fail("SendFile failed!");
+ }
- Receiver receiver = new Receiver ();
- con.GetFile (1, 30000, NameOfFile.TRANSPARENT_FILE, receiver);
+ Thread.Sleep(10000);
- Thread.Sleep (3000);
- Assert.IsTrue (receiver.finishedCalled);
- Assert.AreEqual (100, receiver.recvdBytes);
- Assert.AreEqual (1, receiver.lastSection);
+ Assert.IsTrue(receiver.finishedCalled);
+ Assert.AreEqual(100, receiver.recvdBytes);
+ Assert.AreEqual(1, receiver.lastSection);
- for (int i = 0; i < 100; i++) {
- Assert.AreEqual (receiver.recvBuffer [i], i);
+ for (int i = 0; i < 100; i++)
+ {
+ Assert.AreEqual(receiver.recvBuffer[i], i);
}
- con.Close ();
-
- server.Stop ();
+ con.Close();
+ Thread.Sleep(2000);
+ server.Stop();
}
- [Test ()]
- public void TestFileUploadMultipleSections ()
+ [Test()]
+ public void TestFileDownloadMultipleSegments()
{
- Server server = new Server ();
- server.SetLocalPort (20213);
- server.Start ();
+ Server server = new Server();
+ int port = GetPort();
+ server.SetLocalPort(port);
- SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ Receiver receiver = new Receiver();
- byte [] fileData = new byte [100];
+ server.DebugOutput = true;
- for (int i = 0; i < 100; i++)
- fileData [i] = (byte)(i);
+ server.SetFileReadyHandler(delegate (object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile)
+ {
+ return receiver;
+ }, null);
- byte [] fileData2 = new byte [100];
+ server.Start();
- for (int i = 0; i < 100; i++)
- fileData2 [i] = (byte)(100 + i);
+ SimpleFile file = new SimpleFile(1, 30000, NameOfFile.TRANSPARENT_FILE);
- file.AddSection (fileData);
- file.AddSection (fileData2);
+ byte[] fileData = new byte[1000];
- server.GetAvailableFiles().AddFile (file);
+ for (int i = 0; i < 1000; i++)
+ fileData[i] = (byte)(i);
+
+ file.AddSection(fileData);
- Connection con = new Connection ("127.0.0.1", 20213);
- con.Connect ();
+ server.GetAvailableFiles().AddFile(file);
+ Connection con = new Connection("127.0.0.1", port);
+ con.Connect();
+ Thread.Sleep(2000);
- Receiver receiver = new Receiver ();
+ try
+ {
+ con.SendFile(1, 30000, NameOfFile.TRANSPARENT_FILE, file);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[Test] SendFile threw an exception: {ex.Message}");
+ Assert.Fail("SendFile failed!");
+ }
- con.GetFile (1, 30000, NameOfFile.TRANSPARENT_FILE, receiver);
+ Thread.Sleep(10000);
- Thread.Sleep (3000);
- Assert.IsTrue (receiver.finishedCalled);
- Assert.AreEqual (200, receiver.recvdBytes);
- Assert.AreEqual (2, receiver.lastSection);
+ Assert.IsTrue(receiver.finishedCalled);
+ Assert.AreEqual(1000, receiver.recvdBytes);
+ Assert.AreEqual(1, receiver.lastSection);
+ Assert.IsTrue(file.transferComplete);
+ Assert.IsTrue(file.success);
- for (int i = 0; i < 200; i++) {
- Assert.AreEqual (receiver.recvBuffer [i], i);
+ for (int i = 0; i < 1000; i++)
+ {
+ Assert.AreEqual(receiver.recvBuffer[i], (byte)i);
}
- con.Close ();
+ con.Close();
- server.Stop ();
+ server.Stop();
}
[Test()]
- public void TestFileUploadMultipleSectionsFreeFileName()
+ public void TestFileDownloadMultipleSegmentsMultipleSections()
{
Server server = new Server();
- server.SetLocalPort(20213);
+ int port = GetPort();
+ server.SetLocalPort(port);
+ Receiver receiver = new Receiver();
+
+ server.SetFileReadyHandler(delegate (object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile)
+ {
+ return receiver;
+ }, null);
+
server.Start();
- SimpleFile file = new SimpleFile(1, 30000, (NameOfFile) 12);
+ SimpleFile file = new SimpleFile(1, 30000, NameOfFile.TRANSPARENT_FILE);
byte[] fileData = new byte[100];
@@ -1770,15 +3353,14 @@ public void TestFileUploadMultipleSectionsFreeFileName()
server.GetAvailableFiles().AddFile(file);
- Connection con = new Connection("127.0.0.1", 20213);
+ Connection con = new Connection("127.0.0.1", port);
con.Connect();
+ Thread.Sleep(2000);
- Receiver receiver = new Receiver();
-
- con.GetFile(1, 30000, (NameOfFile) 12, receiver);
+ con.SendFile(1, 30000, NameOfFile.TRANSPARENT_FILE, file);
- Thread.Sleep(3000);
+ Thread.Sleep(10000);
Assert.IsTrue(receiver.finishedCalled);
Assert.AreEqual(200, receiver.recvdBytes);
Assert.AreEqual(2, receiver.lastSection);
@@ -1793,301 +3375,422 @@ public void TestFileUploadMultipleSectionsFreeFileName()
server.Stop();
}
- [Test ()]
- public void TestFileUploadMultipleSegments ()
+ [Test()]
+ public void TestFileDownloadSlaveRejectsFile()
{
- Server server = new Server ();
- server.SetLocalPort (20213);
- server.Start ();
+ Server server = new Server();
+ int port = GetPort();
+ server.SetLocalPort(port);
- SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ Receiver receiver = new Receiver();
- byte [] fileData = new byte [1000];
+ server.SetFileReadyHandler(delegate (object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile)
+ {
+ return null;
+ }, null);
- for (int i = 0; i < 1000; i++)
- fileData [i] = (byte)(i);
-;
- file.AddSection (fileData);
+ server.Start();
- server.GetAvailableFiles ().AddFile (file);
+ SimpleFile file = new SimpleFile(1, 30000, NameOfFile.TRANSPARENT_FILE);
- Connection con = new Connection ("127.0.0.1", 20213);
- con.Connect ();
+ byte[] fileData = new byte[100];
+ for (int i = 0; i < 100; i++)
+ fileData[i] = (byte)(i);
- Receiver receiver = new Receiver ();
+ file.AddSection(fileData);
- con.GetFile (1, 30000, NameOfFile.TRANSPARENT_FILE, receiver);
+ server.GetAvailableFiles().AddFile(file);
- Thread.Sleep (3000);
- Assert.IsTrue (receiver.finishedCalled);
- Assert.AreEqual (1000, receiver.recvdBytes);
- Assert.AreEqual (1, receiver.lastSection);
- Assert.IsTrue (file.transferComplete);
- Assert.IsTrue (file.success);
+ Connection con = new Connection("127.0.0.1", port);
+ con.Connect();
+ Thread.Sleep(2000);
- for (int i = 0; i < 1000; i++) {
- Assert.AreEqual (receiver.recvBuffer [i], (byte) i);
+ try
+ {
+ con.SendFile(1, 30000, NameOfFile.TRANSPARENT_FILE, file);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[Test] SendFile threw an exception: {ex.Message}");
+ Assert.Fail("SendFile failed!");
}
- con.Close ();
+ Thread.Sleep(10000);
- server.Stop ();
- }
+ Assert.IsFalse(receiver.finishedCalled);
+ Assert.AreEqual(0, receiver.recvdBytes);
+ Assert.AreEqual(0, receiver.lastSection);
+
+ con.Close();
+ server.Stop();
+ }
- [Test ()]
- public void TestFileDownloadSingleSection ()
+ [Test()]
+ public void TestInformationObjectCopyConstructors()
{
- Server server = new Server ();
- server.SetLocalPort (20213);
- server.DebugOutput = true;
- server.Start ();
+ SinglePointInformation si = new SinglePointInformation(101, true, QualityDescriptor.VALID());
- SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ SinglePointInformation copySi = new SinglePointInformation(si);
- byte [] fileData = new byte [100];
+ Assert.AreEqual(copySi.ObjectAddress, 101);
+ Assert.AreEqual(copySi.Value, true);
+ Assert.AreEqual(copySi.Quality.Invalid, false);
- for (int i = 0; i < 100; i++)
- fileData [i] = (byte)(i);
+ CP56Time2a time = new CP56Time2a(DateTime.Now);
- file.AddSection (fileData);
+ SinglePointWithCP56Time2a siWithTs = new SinglePointWithCP56Time2a(102, false, QualityDescriptor.INVALID(), time);
- Receiver receiver = new Receiver ();
+ copySi = new SinglePointInformation(siWithTs);
- server.SetFileReadyHandler (delegate (object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile) {
- return receiver;
- }, null);
+ Assert.AreEqual(copySi.ObjectAddress, 102);
+ Assert.AreEqual(copySi.Value, false);
+ Assert.AreEqual(copySi.Quality.Invalid, true);
+
+ copySi = new SinglePointWithCP56Time2a(siWithTs);
- Connection con = new Connection ("127.0.0.1", 20213);
- con.DebugOutput = true;
- con.Connect ();
+ Assert.AreEqual(copySi.ObjectAddress, 102);
+ Assert.AreEqual(copySi.Value, false);
+ Assert.AreEqual(copySi.Quality.Invalid, true);
- con.SendFile (1, 30000, NameOfFile.TRANSPARENT_FILE, file);
+ Assert.AreNotSame(((SinglePointWithCP56Time2a)copySi).Timestamp, time);
+ Assert.AreEqual(((SinglePointWithCP56Time2a)copySi).Timestamp, time);
- Thread.Sleep (3000);
- Assert.IsTrue (receiver.finishedCalled);
- Assert.AreEqual (100, receiver.recvdBytes);
- Assert.AreEqual (1, receiver.lastSection);
+ PackedSinglePointWithSCD packedScd = new PackedSinglePointWithSCD(103, new StatusAndStatusChangeDetection(), QualityDescriptor.INVALID());
- for (int i = 0; i < 100; i++) {
- Assert.AreEqual (receiver.recvBuffer [i], i);
- }
+ packedScd.SCD.CD(1, true);
+ packedScd.SCD.CD(7, true);
+
+ PackedSinglePointWithSCD packedScdCopy = new PackedSinglePointWithSCD(packedScd);
+
+ Assert.AreEqual(packedScdCopy.ObjectAddress, 103);
+ Assert.AreEqual(packedScdCopy.SCD.CD(0), false);
+ Assert.AreEqual(packedScdCopy.SCD.CD(1), true);
+ Assert.AreEqual(packedScdCopy.SCD.CD(7), true);
+ Assert.AreEqual(packedScdCopy.QDS.Invalid, true);
+
+ BinaryCounterReading bcr = new BinaryCounterReading();
+ bcr.Value = 1234;
+ bcr.Invalid = true;
+ bcr.SequenceNumber = 2;
+
+ IntegratedTotals integratedTotalsOriginal = new IntegratedTotals(104, bcr);
+
+ IntegratedTotals integratedTotalsCopy = new IntegratedTotals(integratedTotalsOriginal);
+
+ Assert.AreEqual(integratedTotalsCopy.ObjectAddress, 104);
+ Assert.AreNotSame(integratedTotalsOriginal.BCR, integratedTotalsCopy.BCR);
+ Assert.AreEqual(integratedTotalsOriginal.BCR.Value, integratedTotalsCopy.BCR.Value);
+ Assert.AreEqual(integratedTotalsOriginal.BCR.Invalid, integratedTotalsCopy.BCR.Invalid);
+ Assert.AreEqual(integratedTotalsOriginal.BCR.SequenceNumber, integratedTotalsCopy.BCR.SequenceNumber);
- con.Close ();
+ IntegratedTotalsWithCP56Time2a integratedTotalsWithCP56Copy = new IntegratedTotalsWithCP56Time2a(integratedTotalsOriginal);
- server.Stop ();
+ Assert.AreEqual(integratedTotalsWithCP56Copy.ObjectAddress, 104);
+ Assert.AreNotSame(integratedTotalsOriginal.BCR, integratedTotalsWithCP56Copy.BCR);
+ Assert.AreEqual(integratedTotalsOriginal.BCR.Value, integratedTotalsWithCP56Copy.BCR.Value);
+ Assert.AreEqual(integratedTotalsOriginal.BCR.Invalid, integratedTotalsWithCP56Copy.BCR.Invalid);
+ Assert.AreEqual(integratedTotalsOriginal.BCR.SequenceNumber, integratedTotalsWithCP56Copy.BCR.SequenceNumber);
}
- [Test ()]
- public void TestFileDownloadMultipleSegments ()
+ [Test()]
+ public void TestSingleRedundancyGroup()
{
- Server server = new Server ();
- server.SetLocalPort (20213);
+ bool running = true;
+
+ Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
+ {
+ e.Cancel = true;
+ running = false;
+ };
+
+ // specify application layer parameters (CS 101 and CS 104)
+ var alParams = new ApplicationLayerParameters();
+
+ // specify APCI parameters (only CS 104)
+ var apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, alParams);
+
server.DebugOutput = true;
- server.Start ();
- SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ server.ServerMode = ServerMode.SINGLE_REDUNDANCY_GROUP;
- byte [] fileData = new byte [1000];
+ server.MaxQueueSize = 10;
+ server.MaxOpenConnections = 6;
- for (int i = 0; i < 1000; i++)
- fileData [i] = (byte)(i);
+ server.EnqueueMode = EnqueueMode.REMOVE_OLDEST;
+ server.SetLocalPort(GetPort());
+ server.Start();
- file.AddSection (fileData);
+ int waitTime = 1000;
- Receiver receiver = new Receiver ();
+ int enqueuedMessage = 0;
+ int maxLoop = server.MaxQueueSize + 3;
+ int loopCount = 0;
+ while (running && server.IsRunning())
+ {
+ Thread.Sleep(100);
- server.SetFileReadyHandler (delegate (object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile) {
- return receiver;
- }, null);
+ if (waitTime > 0)
+ waitTime -= 100;
+ else
+ {
+ ASDU newAsdu = new ASDU
+ (server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
- Connection con = new Connection ("127.0.0.1", 20213);
- con.DebugOutput = true;
- con.Connect ();
+ newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- con.SendFile (1, 30000, NameOfFile.TRANSPARENT_FILE, file);
+ newAsdu.AddInformationObject(new MeasuredValueScaled(110, -1, new QualityDescriptor()));
+ server.EnqueueASDU(newAsdu);
+ enqueuedMessage++;
- Thread.Sleep (3000);
- Assert.IsTrue (receiver.finishedCalled);
- Assert.AreEqual (1000, receiver.recvdBytes);
- Assert.AreEqual (1, receiver.lastSection);
- Assert.IsTrue (file.transferComplete);
- Assert.IsTrue (file.success);
+ int numberOfQueueEntries = server.GetNumberOfQueueEntries();
+ Console.WriteLine($"Number of queue entries: {numberOfQueueEntries}");
+ waitTime = 1000;
- for (int i = 0; i < 1000; i++) {
- Assert.AreEqual (receiver.recvBuffer [i], (byte)i);
- }
+ if (enqueuedMessage >= server.MaxQueueSize)
+ Assert.AreEqual(server.MaxQueueSize, numberOfQueueEntries);
+ else
+ Assert.AreEqual(enqueuedMessage, numberOfQueueEntries);
+
+ maxLoop++;
+ }
+
+ if (loopCount == maxLoop)
+ break;
- con.Close ();
+ loopCount++;
+ }
- server.Stop ();
+ if (server.IsRunning())
+ {
+ Console.WriteLine("Stop server");
+ server.Stop();
+ }
+ else
+ {
+ Console.WriteLine("Server stopped");
+ }
}
- [Test ()]
- public void TestFileDownloadMultipleSegmentsMultipleSections ()
+ [Test()]
+ public void TestMultipleRedundancyGroup()
{
- Server server = new Server ();
- server.SetLocalPort (20213);
+ bool running = true;
+ int port = GetPort();
+
+ Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
+ {
+ e.Cancel = true;
+ running = false;
+ };
+
+ // specify application layer parameters (CS 101 and CS 104)
+ var alParams = new ApplicationLayerParameters();
+
+ // specify APCI parameters (only CS 104)
+ var apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, alParams);
+
server.DebugOutput = true;
- server.Start ();
- SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ server.ServerMode = ServerMode.MULTIPLE_REDUNDANCY_GROUPS;
- byte [] fileData = new byte [1000];
+ server.MaxQueueSize = 10;
+ server.MaxOpenConnections = 6;
- for (int i = 0; i < 1000; i++)
- fileData [i] = (byte)(i);
+ RedundancyGroup redGroup = new RedundancyGroup("red-group");
+ redGroup.AddAllowedClient("127.0.0.1");
- file.AddSection (fileData);
+ server.AddRedundancyGroup(redGroup);
- byte [] fileData2 = new byte [1000];
+ server.EnqueueMode = EnqueueMode.REMOVE_OLDEST;
+ server.SetLocalPort(port);
- for (int i = 0; i < 1000; i++)
- fileData2 [i] = (byte)(i * 2);
+ server.Start();
- file.AddSection (fileData2);
+ int waitTime = 1000;
+ int enqueuedMessage = 0;
+ int maxLoop = server.MaxQueueSize + 3;
+ while (running && server.IsRunning())
+ {
+ Thread.Sleep(100);
- Receiver receiver = new Receiver ();
+ if (waitTime > 0)
+ waitTime -= 100;
+ else
+ {
+ ASDU newAsdu = new ASDU
+ (server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
- server.SetFileReadyHandler (delegate (object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile) {
- return receiver;
- }, null);
+ newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- Connection con = new Connection ("127.0.0.1", 20213);
- con.DebugOutput = true;
- con.Connect ();
+ newAsdu.AddInformationObject(new MeasuredValueScaled(110, -1, new QualityDescriptor()));
- con.SendFile (1, 30000, NameOfFile.TRANSPARENT_FILE, file);
+ server.EnqueueASDU(newAsdu);
+ enqueuedMessage++;
- Thread.Sleep (3000);
- Assert.IsTrue (receiver.finishedCalled);
- Assert.AreEqual (2000, receiver.recvdBytes);
- Assert.AreEqual (2, receiver.lastSection);
- Assert.IsTrue (file.transferComplete);
- Assert.IsTrue (file.success);
+ int numberOfQueueEntries = server.GetNumberOfQueueEntries(redGroup);
+ Console.WriteLine($"Number of queue entries: {numberOfQueueEntries}");
+ waitTime = 1000;
+ if (enqueuedMessage >= server.MaxQueueSize)
+ Assert.AreEqual(server.MaxQueueSize, numberOfQueueEntries);
+ else
+ Assert.AreEqual(enqueuedMessage, numberOfQueueEntries);
- for (int i = 0; i < 1000; i++) {
- Assert.AreEqual (receiver.recvBuffer [i], (byte)i);
+ maxLoop++;
+ }
}
- for (int i = 0; i < 1000; i++) {
- Assert.AreEqual (receiver.recvBuffer [i + 1000], (byte)(i * 2));
+ if (server.IsRunning())
+ {
+ Console.WriteLine("Stop server");
+ server.Stop();
+ }
+ else
+ {
+ Console.WriteLine("Server stopped");
}
-
- con.Close ();
-
- server.Stop ();
}
- [Test ()]
- public void TestFileDownloadSlaveRejectsFile()
+ [Test()]
+ public void TestConnectionIsRedundancyGroup()
{
- Server server = new Server ();
- server.SetLocalPort (20213);
+ bool running = true;
+
+ Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
+ {
+ e.Cancel = true;
+ running = false;
+ };
+
+ // specify application layer parameters (CS 101 and CS 104)
+ var alParams = new ApplicationLayerParameters();
+
+ // specify APCI parameters (only CS 104)
+ var apciParameters = new APCIParameters();
+
+ Server server = new Server(apciParameters, alParams);
+
server.DebugOutput = true;
- server.Start ();
- SimpleFile file = new SimpleFile (1, 30000, NameOfFile.TRANSPARENT_FILE);
+ server.ServerMode = ServerMode.CONNECTION_IS_REDUNDANCY_GROUP;
- byte [] fileData = new byte [100];
+ server.MaxQueueSize = 10;
+ server.MaxOpenConnections = 6;
- for (int i = 0; i < 100; i++)
- fileData [i] = (byte)(i);
+ server.EnqueueMode = EnqueueMode.REMOVE_OLDEST;
- file.AddSection (fileData);
+ server.Start();
- Receiver receiver = new Receiver ();
+ int waitTime = 1000;
- server.SetFileReadyHandler (delegate (object parameter, int ca, int ioa, NameOfFile nof, int lengthOfFile) {
- return null;
- }, null);
+ int enqueuedMessage = 0;
+ int maxLoop = server.MaxQueueSize + 3;
+ int loopCount = 0;
+ while (running && server.IsRunning())
+ {
+ Thread.Sleep(100);
+
+ if (waitTime > 0)
+ waitTime -= 100;
+ else
+ {
+ ASDU newAsdu = new ASDU
+ (server.GetApplicationLayerParameters(), CauseOfTransmission.INITIALIZED, false, false, 0, 1, false);
- Connection con = new Connection ("127.0.0.1", 20213);
- con.DebugOutput = true;
- con.Connect ();
+ newAsdu = new ASDU(server.GetApplicationLayerParameters(), CauseOfTransmission.PERIODIC, false, false, 0, 1, false);
- con.SendFile (1, 30000, NameOfFile.TRANSPARENT_FILE, file);
+ newAsdu.AddInformationObject(new MeasuredValueScaled(110, -1, new QualityDescriptor()));
+ server.EnqueueASDU(newAsdu);
+ enqueuedMessage++;
- Thread.Sleep (1000);
+ int numberOfQueueEntries = server.GetNumberOfQueueEntries();
+ Console.WriteLine($"Number of queue entries: {numberOfQueueEntries}");
+ waitTime = 1000;
- Assert.IsTrue (file.transferComplete);
- Assert.IsFalse (file.success);
+ if (enqueuedMessage == server.MaxQueueSize)
+ Assert.AreEqual(server.MaxQueueSize, numberOfQueueEntries);
+ else
+ Assert.AreEqual(enqueuedMessage, numberOfQueueEntries);
- Assert.IsFalse (receiver.finishedCalled);
- Assert.AreEqual (0, receiver.recvdBytes);
- Assert.AreEqual (0, receiver.lastSection);
+ maxLoop++;
+ }
+
+ if (loopCount == maxLoop)
+ break;
- con.Close ();
+ loopCount++;
+ }
- server.Stop ();
+ if (server.IsRunning())
+ {
+ Console.WriteLine("Stop server");
+ server.Stop();
+ }
+ else
+ {
+ Console.WriteLine("Server stopped");
+ }
}
[Test()]
- public void TestInformationObjectCopyConstructors()
+ public void TestSingleEventType()
{
- SinglePointInformation si = new SinglePointInformation(101, true, QualityDescriptor.VALID());
+ SingleEvent singleEvent = new SingleEvent();
- SinglePointInformation copySi = new SinglePointInformation(si);
+ EventState eventState = singleEvent.State;
- Assert.AreEqual(copySi.ObjectAddress, 101);
- Assert.AreEqual(copySi.Value, true);
- Assert.AreEqual(copySi.Quality.Invalid, false);
+ Assert.AreEqual(EventState.INDETERMINATE_0, eventState);
- CP56Time2a time = new CP56Time2a(DateTime.Now);
+ QualityDescriptorP qdp = singleEvent.QDP;
- SinglePointWithCP56Time2a siWithTs = new SinglePointWithCP56Time2a(102, false, QualityDescriptor.INVALID(), time);
+ Assert.AreEqual(0, qdp.EncodedValue);
+ }
- copySi = new SinglePointInformation(siWithTs);
+ [Test()]
+ public void TestScaledNormalizedConversion()
+ {
+ const float NORMALIZED_VALUE_MAX = 32767.0f / 32768.0f;
- Assert.AreEqual(copySi.ObjectAddress, 102);
- Assert.AreEqual(copySi.Value, false);
- Assert.AreEqual(copySi.Quality.Invalid, true);
+ Assert.AreEqual(32767, new ScaledValue().ConvertNormalizedValueToScaled(NORMALIZED_VALUE_MAX));
- copySi = new SinglePointWithCP56Time2a(siWithTs);
+ Assert.AreEqual(32767, new ScaledValue().ConvertNormalizedValueToScaled(1.0f));
- Assert.AreEqual(copySi.ObjectAddress, 102);
- Assert.AreEqual(copySi.Value, false);
- Assert.AreEqual(copySi.Quality.Invalid, true);
+ Assert.AreEqual(32767, new ScaledValue().ConvertNormalizedValueToScaled(2.0f));
- Assert.AreNotSame(((SinglePointWithCP56Time2a)copySi).Timestamp, time);
- Assert.AreEqual(((SinglePointWithCP56Time2a)copySi).Timestamp, time);
+ Assert.AreEqual(-32768, new ScaledValue().ConvertNormalizedValueToScaled(-1.0f));
- PackedSinglePointWithSCD packedScd = new PackedSinglePointWithSCD(103, new StatusAndStatusChangeDetection(), QualityDescriptor.INVALID());
+ Assert.AreEqual(-32768, new ScaledValue().ConvertNormalizedValueToScaled(-2.0f));
- packedScd.SCD.CD(1, true);
- packedScd.SCD.CD(7, true);
+ Assert.AreEqual(0, new ScaledValue().ConvertNormalizedValueToScaled(0.0f));
- PackedSinglePointWithSCD packedScdCopy = new PackedSinglePointWithSCD(packedScd);
+ Assert.AreEqual(0, new ScaledValue().ConvertNormalizedValueToScaled(-0.0f));
- Assert.AreEqual(packedScdCopy.ObjectAddress, 103);
- Assert.AreEqual(packedScdCopy.SCD.CD(0), false);
- Assert.AreEqual(packedScdCopy.SCD.CD(1), true);
- Assert.AreEqual(packedScdCopy.SCD.CD(7), true);
- Assert.AreEqual(packedScdCopy.QDS.Invalid, true);
+ float normalizedUnit = (1f - NORMALIZED_VALUE_MAX);
- BinaryCounterReading bcr = new BinaryCounterReading();
- bcr.Value = 1234;
- bcr.Invalid = true;
- bcr.SequenceNumber = 2;
+ /*Allow a small tolerance (0.0001f) in floating-point assertions.*/
- IntegratedTotals integratedTotalsOriginal = new IntegratedTotals(104, bcr);
+ Assert.AreEqual(NORMALIZED_VALUE_MAX - normalizedUnit, new ScaledValue(32766).GetNormalizedValue(), 0.0001f);
- IntegratedTotals integratedTotalsCopy = new IntegratedTotals(integratedTotalsOriginal);
+ Assert.AreEqual(NORMALIZED_VALUE_MAX, new ScaledValue(32767).GetNormalizedValue(), 0.0001f);
- Assert.AreEqual(integratedTotalsCopy.ObjectAddress, 104);
- Assert.AreNotSame(integratedTotalsOriginal.BCR, integratedTotalsCopy.BCR);
- Assert.AreEqual(integratedTotalsOriginal.BCR.Value, integratedTotalsCopy.BCR.Value);
- Assert.AreEqual(integratedTotalsOriginal.BCR.Invalid, integratedTotalsCopy.BCR.Invalid);
- Assert.AreEqual(integratedTotalsOriginal.BCR.SequenceNumber, integratedTotalsCopy.BCR.SequenceNumber);
+ Assert.AreEqual(NORMALIZED_VALUE_MAX, new ScaledValue(32768).GetNormalizedValue(), 0.0001f);
- IntegratedTotalsWithCP56Time2a integratedTotalsWithCP56Copy = new IntegratedTotalsWithCP56Time2a(integratedTotalsOriginal);
+ Assert.AreEqual(NORMALIZED_VALUE_MAX, new ScaledValue(99999).GetNormalizedValue(), 0.0001f);
- Assert.AreEqual(integratedTotalsWithCP56Copy.ObjectAddress, 104);
- Assert.AreNotSame(integratedTotalsOriginal.BCR, integratedTotalsWithCP56Copy.BCR);
- Assert.AreEqual(integratedTotalsOriginal.BCR.Value, integratedTotalsWithCP56Copy.BCR.Value);
- Assert.AreEqual(integratedTotalsOriginal.BCR.Invalid, integratedTotalsWithCP56Copy.BCR.Invalid);
- Assert.AreEqual(integratedTotalsOriginal.BCR.SequenceNumber, integratedTotalsWithCP56Copy.BCR.SequenceNumber);
+ Assert.AreEqual(-1.0f + (normalizedUnit * 2f), new ScaledValue(-32766).GetNormalizedValue(), 0.0001f);
+
+ Assert.AreEqual(-1.0f + normalizedUnit, new ScaledValue(-32767).GetNormalizedValue(), 0.0001f);
+
+ Assert.AreEqual(-1.0f, new ScaledValue(-32768).GetNormalizedValue());
+
+ Assert.AreEqual(-1.0f, new ScaledValue(-32769).GetNormalizedValue());
+
+ Assert.AreEqual(0.0f, new ScaledValue(0).GetNormalizedValue());
}
}
diff --git a/tests/lib60870-tests.csproj b/tests/lib60870-tests.csproj
index db77a46..4523f63 100644
--- a/tests/lib60870-tests.csproj
+++ b/tests/lib60870-tests.csproj
@@ -1,21 +1,16 @@
-
- net6.0
+ net8.0
enable
-
false
-
-
-
-
+
+
-
-
+
\ No newline at end of file
diff --git a/user_guide_dotnet.adoc b/user_guide_dotnet.adoc
index 49cf7b9..b4662bf 100644
--- a/user_guide_dotnet.adoc
+++ b/user_guide_dotnet.adoc
@@ -1,4 +1,6 @@
-= lib60870.NET 2.3 User Guide
+= lib60870.NET 2.3.0 User Guide
+
+*Copyright 2025 MZ Automation GmbH*
== Introduction