Compare commits

...

52 Commits
v1.5 ... v1.3.2

Author SHA1 Message Date
Michael Zillgith 9973ea165f - MMS server: fixed problem in read with component access 7 years ago
Michael Zillgith 080b29464e - prepared for release 1.3.2 7 years ago
Michael Zillgith c77bbe3009 - fixed bug in cmake file 7 years ago
Michael Zillgith 6f608c105f Merge branch 'v1.3' of https://github.com/mz-automation/libiec61850 into v1.3 7 years ago
Michael Zillgith 8074f99538 - MMS client/server: added support for component alternate access for generic variable read requests 7 years ago
Michael Zillgith 233cd20fd0 - MMS client: improved handling of malformed messages when receiving reports
- MMS client: fixed potential memory leak when receiving malformed messages
7 years ago
Michael Zillgith 0d49e8c1d2 - MMS client: improved handling of incorrect messages when reading data 7 years ago
Michael Zillgith 803786c402 Merge branch 'v1.3' of https://github.com/mz-automation/libiec61850 into v1.3 7 years ago
Michael Zillgith bf1ee8b560 - IEC 61850 server: fixed problem with wrong purge buffer invocation when using dynamic data set in buffered report control block 7 years ago
Michael Zillgith 2df6387b99
Merge pull request #92 from aaribaud/python-import-sys-fix
Fix typo in python install path detection
7 years ago
Michael Zillgith e389149f22
Merge pull request #99 from davideandreuzza/bufOvfl-parsing-fix
java SCL parser: fix bufOvfl typo in parsing OptionFields
7 years ago
Davide Andreuzza 7463a2c319 fix bufOvfl typo in parsing OptionFields 7 years ago
Albert ARIBAUD 1bcd1fc071 Fixed typo in python install path detection
Without this fix, cmake -DBUILD_PYTHON_BINDINGS=ON fails.
7 years ago
Michael Zillgith c952aa5a31 - include own stdbool.h only for older versions of VS 7 years ago
Michael Zillgith 1e993f20b0 - added IedConnection_getFileDirectoryEx to DLL export files 7 years ago
Michael Zillgith 66f571f03d - .NET API: add GetFileDirectoryEx function 7 years ago
Michael Zillgith 28e8831029 - modelviewer: show full hierarchy including sub data objects 7 years ago
Michael Zillgith 4ad0567169 - .NET API: DataSet implements IDisposable interface, Report/DataSet GetValues methods return now clones of the original native values to prevent GC issues 7 years ago
Michael Zillgith c4c55d74ca - .NET API: MmsValue - added Clone method and implemented IDisposable interface 7 years ago
Michael Zillgith a4fc173cbf
Merge pull request #90 from sanekas/fix/incorrect_lib_memory_path
Fix makefile, add CLion service dirs to .gitignore
7 years ago
Alexander Sergeev 2a98f95df8 Fix makefile, add CLion dirs to .gitignore 7 years ago
Michael Zillgith edc093e810 - updated CHANGELOG file
- make iec61850_9_2_LE_example to compile with Visual Studio
7 years ago
Michael Zillgith 31f92b5cf0 - .NET API: Added method MmsConnection.ReadMultipleVariables
- .NET API: extended MmsValue.ToString method to print arrays and data access errors
7 years ago
Michael Zillgith 8d728b3ab4 - fixed problem in GOOSE publisher payload length calculation 7 years ago
Michael Zillgith 0d7a3750e4 - IEC 61850 client: implemented tissue 1178 client side (select-response+ is non-NULL) 7 years ago
Michael Zillgith 074f7a8cd1 - Ethernet HAL Linux: limited interface name in Ethernet_getIntefaceMACAddress to prevent #83 7 years ago
Michael Zillgith 3ad71c7a0b - added error handling in iec61850_client_example4 7 years ago
Michael Zillgith 1fbadffd6e - added new functions to windows def files 7 years ago
Michael Zillgith 2bbdfacfe4 - SV: fixed RefrTm and SmpSynch handling in SV publisher 7 years ago
Michael Zillgith 57eb6bd455 - GOOSE publisher: fixed bug in GOOSE message calculation 7 years ago
Michael Zillgith 74a9eb5856 - IEC 61850 client: ControlObjectClient - avoid crash when "ctlVal" is not present in "Oper", also accept "setMag" instead of "ctlVal". 7 years ago
Michael Zillgith 9b4a06e573 - .NET API: ReportControlBlock.GetOwner returns null when no owner available (#79) 7 years ago
Michael Zillgith 4a135fa252 - IEC 61850 server: fixed potential deadlock in report processing 7 years ago
Michael Zillgith f68e97b7a1
Merge pull request #80 from aaribaud/python3-bindings-fix
Fixed Python 3 bindings support
7 years ago
Albert ARIBAUD 8f523b1706 Fixed Python 3 bindings support
1. Allow specifying Python interpreter version with BUILD_PYTHON_VERSION

2. Fix Python modules install path generation (would produce spurious
   line feed which would cause destination directory to be
   /usr/lib/python*/dist-packages\n -- note the final \n!
7 years ago
Michael Zillgith c9fe9b4286 - adopted new functions to version 1.3 7 years ago
Michael Zillgith 1a9de24884 - increased version number to 1.3.1 7 years ago
Michael Zillgith 59e2d19acc - IEC 61850 client: improved support for segmented report handling 7 years ago
Michael Zillgith cd81f9b13d - .NET API: Added ReportControlBlock.GetOwner method (#79) 7 years ago
Michael Zillgith e6ace3a787 - Java SCL parser: added support for timestamp values in "Val" elements 7 years ago
Michael Zillgith 9f0316e4c0 - fixed bug in cmake file (winpcap support) - (#78) 7 years ago
Michael Zillgith ed13cd7ab8 - added C# example code for client side setting group handling 7 years ago
Michael Zillgith 3878a9aad3 - .NET API: added some additional wrapper code for MmsVariableSpecification functions 7 years ago
Michael Zillgith 4d8a768ec5 - .NET API: fixed wrong defintion for ClientReportControlBlock_getResvTms in ReportControlBlock class 7 years ago
Michael Zillgith af0b2f9edb - .NET API: Added missing access functions for ResvTms to ReportControlBlock class (#74) 7 years ago
Michael Zillgith 2a19b2592f - python wrapper: some changes in cmake file 7 years ago
Michael Zillgith eb5b8fba5c
Merge pull request #72 from cedricboudinet/master
python wrapper: correcting version detection for swig
7 years ago
Michael Zillgith c38a0c3d59
Merge pull request #73 from cngkaygusuz/v1.3
Add .gitignore, ignore .build/
7 years ago
Cengiz Kaygusuz f8486de415 Add .gitignore, ignore build/ 7 years ago
Cedric Boudinet c97cac5ad9 correcting version detection for swig 7 years ago
Michael Zillgith 1a8558dcda - Windows Ethernet Hal: fixed memory leak 7 years ago
Michael Zillgith 75ca279489 - Java tools: fixed bug in SCL parser (wrong interpretation of numbers with leading "0" 7 years ago

3
.gitignore vendored

@ -0,0 +1,3 @@
build/
cmake-build-debug/
.idea/

@ -1,3 +1,27 @@
Changes to version 1.3.2
------------------------
- MMS client/server: added support for component alternate access for generic variable read requests
- MMS client: improved handling/stability when receiving malformed messages from the server
- IEC 61850 server: fixed problem with wrong purge buffer invocation when using dynamic data set in buffered report control block
- .NET API: add GetFileDirectoryEx function
- modelviewer: show full hierarchy including sub data objects
- .NET API: DataSet implements IDisposable interface, Report/DataSet GetValues methods return now clones of the original native values to prevent GC issues
- .NET API: MmsValue - added Clone method and implemented IDisposable interface
Changes to version 1.3.1
------------------------
- GOOSE publisher: fixed problem in payload length calculation
- .NET API: Added method MmsConnection.ReadMultipleVariables
- IEC 61850 client: implemented tissue 1178 client side (select-response+ is
non-NULL)
- SV publisher: fixed RefrTm and SmpSynch handling
- IEC 61850 client: improved support for handling segmented reports
- .NET API: Added some additional access function to ReportControlBlock
- Java SCL parser: added support for timestamp values in "Val" elements
- fixed bug in cmake file (winpcap support)
- added C# example code for client side setting group handling
- .NET API: added some additional wrapper code for MmsVariableSpecification functions
Changes to version 1.3.0 Changes to version 1.3.0
------------------------ ------------------------
- IEC 61850 server: more features configurable at runtime - IEC 61850 server: more features configurable at runtime
@ -17,7 +41,6 @@ Changes to version 1.3.0
- MMS server: fixed bug in delete variable list service - scope of delete was not considered optional - MMS server: fixed bug in delete variable list service - scope of delete was not considered optional
- some more small bug fixes and optimizations - some more small bug fixes and optimizations
Changes to version 1.2.2 Changes to version 1.2.2
------------------------ ------------------------

@ -12,7 +12,7 @@ ENABLE_TESTING()
set(LIB_VERSION_MAJOR "1") set(LIB_VERSION_MAJOR "1")
set(LIB_VERSION_MINOR "3") set(LIB_VERSION_MINOR "3")
set(LIB_VERSION_PATCH "0") set(LIB_VERSION_PATCH "2")
set(LIB_VERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}") set(LIB_VERSION "${LIB_VERSION_MAJOR}.${LIB_VERSION_MINOR}.${LIB_VERSION_PATCH}")
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/third_party/cmake/modules/")
@ -115,11 +115,11 @@ set(API_HEADERS
${CMAKE_CURRENT_BINARY_DIR}/config/stack_config.h ${CMAKE_CURRENT_BINARY_DIR}/config/stack_config.h
) )
if(MSVC) if(MSVC AND MSVC_VERSION LESS 1800)
include_directories( include_directories(
${CMAKE_CURRENT_LIST_DIR}/src/vs ${CMAKE_CURRENT_LIST_DIR}/src/vs
) )
endif(MSVC) endif(MSVC AND MSVC_VERSION LESS 1800)
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.6.0) if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/third_party/mbedtls/mbedtls-2.6.0)
set(WITH_MBEDTLS 1) set(WITH_MBEDTLS 1)

@ -89,10 +89,10 @@ LIB_API_HEADER_FILES = hal/inc/hal_time.h
LIB_API_HEADER_FILES += hal/inc/hal_thread.h LIB_API_HEADER_FILES += hal/inc/hal_thread.h
LIB_API_HEADER_FILES += hal/inc/hal_filesystem.h LIB_API_HEADER_FILES += hal/inc/hal_filesystem.h
LIB_API_HEADER_FILES += hal/inc/tls_config.h LIB_API_HEADER_FILES += hal/inc/tls_config.h
LIB_API_HEADER_FILES += hal/inc/lib_memory.h
LIB_API_HEADER_FILES += src/common/inc/libiec61850_common_api.h LIB_API_HEADER_FILES += src/common/inc/libiec61850_common_api.h
LIB_API_HEADER_FILES += src/common/inc/linked_list.h LIB_API_HEADER_FILES += src/common/inc/linked_list.h
LIB_API_HEADER_FILES += src/common/inc/byte_buffer.h LIB_API_HEADER_FILES += src/common/inc/byte_buffer.h
LIB_API_HEADER_FILES += src/common/inc/lib_memory.h
LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_client.h LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_client.h
LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_common.h LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_common.h
LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_server.h LIB_API_HEADER_FILES += src/iec61850/inc/iec61850_server.h
@ -128,20 +128,20 @@ LIB_SOURCES = $(call get_sources,$(LIB_SOURCE_DIRS))
LIB_OBJS = $(call src_to,.o,$(LIB_SOURCES)) LIB_OBJS = $(call src_to,.o,$(LIB_SOURCES))
CFLAGS += -std=gnu99 CFLAGS += -std=gnu99
#CFLAGS += -Wno-error=format #CFLAGS += -Wno-error=format
CFLAGS += -Wstrict-prototypes CFLAGS += -Wstrict-prototypes
ifneq ($(HAL_IMPL), WIN32) ifneq ($(HAL_IMPL), WIN32)
CFLAGS += -Wuninitialized CFLAGS += -Wuninitialized
endif endif
CFLAGS += -Wsign-compare CFLAGS += -Wsign-compare
CFLAGS += -Wpointer-arith CFLAGS += -Wpointer-arith
CFLAGS += -Wnested-externs CFLAGS += -Wnested-externs
CFLAGS += -Wmissing-declarations CFLAGS += -Wmissing-declarations
CFLAGS += -Wshadow CFLAGS += -Wshadow
CFLAGS += -Wall CFLAGS += -Wall
#CFLAGS += -Werror #CFLAGS += -Werror
all: lib all: lib

@ -1,7 +1,7 @@
/* /*
* DataSet.cs * DataSet.cs
* *
* Copyright 2014 Michael Zillgith * Copyright 2014-2018 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -34,7 +34,7 @@ namespace IEC61850
/// This class is used to represent a data set. It is used to store the values /// This class is used to represent a data set. It is used to store the values
/// of a data set. /// of a data set.
/// </summary> /// </summary>
public class DataSet public class DataSet : IDisposable
{ {
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void ClientDataSet_destroy (IntPtr self); static extern void ClientDataSet_destroy (IntPtr self);
@ -91,7 +91,7 @@ namespace IEC61850
values = new MmsValue (nativeValues, false); values = new MmsValue (nativeValues, false);
} }
return values; return values.Clone();
} }
@ -106,6 +106,16 @@ namespace IEC61850
return ClientDataSet_getDataSetSize (nativeObject); return ClientDataSet_getDataSetSize (nativeObject);
} }
public void Dispose()
{
lock (this) {
if (nativeObject != IntPtr.Zero) {
ClientDataSet_destroy (nativeObject);
nativeObject = IntPtr.Zero;
}
}
}
~DataSet () ~DataSet ()
{ {
ClientDataSet_destroy (nativeObject); ClientDataSet_destroy (nativeObject);

@ -28,10 +28,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="AssemblyInfo.cs" /> <Compile Include="AssemblyInfo.cs" />

@ -48,6 +48,9 @@ namespace IEC61850
public string revision; public string revision;
} }
/// <summary>
/// Represents an MmsConnection object (a single connection to an MMS server)
/// </summary>
public class MmsConnection public class MmsConnection
{ {
@ -69,6 +72,25 @@ namespace IEC61850
[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)] [DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
private static extern Int32 MmsConnection_getLocalDetail (IntPtr self); private static extern Int32 MmsConnection_getLocalDetail (IntPtr self);
[DllImport ("iec61850", CallingConvention=CallingConvention.Cdecl)]
private static extern IntPtr MmsConnection_readMultipleVariables(IntPtr self, out int mmsError,
string domainId, IntPtr items);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
private static extern void MmsValue_delete (IntPtr self);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void LinkedListValueDeleteFunction(IntPtr pointer);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
private static extern void LinkedList_destroyDeep(IntPtr list, LinkedListValueDeleteFunction valueDeleteFunction);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr LinkedList_create ();
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
private static extern void LinkedList_add (IntPtr self, IntPtr data);
private IntPtr self = IntPtr.Zero; private IntPtr self = IntPtr.Zero;
private bool selfDestroy = false; private bool selfDestroy = false;
@ -90,6 +112,15 @@ namespace IEC61850
MmsConnection_destroy(self); MmsConnection_destroy(self);
} }
private void FreeHGlobaleDeleteFunction (IntPtr pointer)
{
Marshal.FreeHGlobal(pointer);
}
/// <summary>
/// Requests the server identity information
/// </summary>
/// <returns>The server identity.</returns>
public MmsServerIdentity GetServerIdentity () public MmsServerIdentity GetServerIdentity ()
{ {
int mmsError; int mmsError;
@ -100,6 +131,12 @@ namespace IEC61850
IntPtr identity = MmsConnection_identify(self, out mmsError); IntPtr identity = MmsConnection_identify(self, out mmsError);
if (mmsError != 0)
throw new IedConnectionException("Failed to read server identity", mmsError);
if (identity == IntPtr.Zero)
throw new IedConnectionException("Failed to read server identity");
MmsServerIdentity serverIdentity = (MmsServerIdentity) MmsServerIdentity serverIdentity = (MmsServerIdentity)
Marshal.PtrToStructure(identity, typeof(MmsServerIdentity)); Marshal.PtrToStructure(identity, typeof(MmsServerIdentity));
@ -108,14 +145,54 @@ namespace IEC61850
return serverIdentity; return serverIdentity;
} }
/// <summary>
/// Sets the local detail (maximum MMS PDU size)
/// </summary>
/// <param name="localDetail">maximum accepted MMS PDU size in bytes</param>
public void SetLocalDetail(int localDetail) { public void SetLocalDetail(int localDetail) {
MmsConnection_setLocalDetail (self, localDetail); MmsConnection_setLocalDetail (self, localDetail);
} }
/// <summary>
/// Gets the local detail (maximum MMS PDU size)
/// </summary>
/// <returns>maximum accepted MMS PDU size in bytes</returns>
public int GetLocalDetail() { public int GetLocalDetail() {
return MmsConnection_getLocalDetail (self); return MmsConnection_getLocalDetail (self);
} }
/// <summary>
/// Reads multipe MMS variables from the same domain
/// </summary>
/// <returns>MmsValue of type MMS_ARRAY containing the access results for the requested variables.</returns>
/// <param name="domainName">the domain name (logical device)</param>
/// <param name="variables">list of variable names (in MMS notation e.g. "GGIO$ST$Ind1")</param>
public MmsValue ReadMultipleVariables(string domainName, List<string> variables)
{
IntPtr linkedList = LinkedList_create ();
foreach (string variableName in variables) {
IntPtr handle = System.Runtime.InteropServices.Marshal.StringToHGlobalAnsi (variableName);
LinkedList_add (linkedList, handle);
}
int error;
IntPtr mmsValue = MmsConnection_readMultipleVariables(self, out error, domainName, linkedList);
LinkedList_destroyDeep(linkedList, new LinkedListValueDeleteFunction(FreeHGlobaleDeleteFunction));
if (error != 0)
{
if (mmsValue != IntPtr.Zero)
MmsValue_delete(mmsValue);
throw new IedConnectionException("ReadMultipleVariables failed", error);
}
return new MmsValue(mmsValue, true);
}
} }
@ -359,7 +436,11 @@ namespace IEC61850
static extern IntPtr MmsConnection_getIsoConnectionParameters(IntPtr mmsConnection); static extern IntPtr MmsConnection_getIsoConnectionParameters(IntPtr mmsConnection);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr IedConnection_getFileDirectory(IntPtr self, out int error, string directoryName); static extern IntPtr IedConnection_getFileDirectory(IntPtr self, out int error, string directoryName);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr IedConnection_getFileDirectoryEx(IntPtr self, out int error, string directoryName, string continueAfter,
[MarshalAs(UnmanagedType.I1)] out bool moreFollows);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void IedConnection_deleteFile(IntPtr self, out int error, string fileName); static extern void IedConnection_deleteFile(IntPtr self, out int error, string fileName);
@ -1125,7 +1206,44 @@ namespace IEC61850
LinkedList_destroyStatic(fileEntryList); LinkedList_destroyStatic(fileEntryList);
return fileDirectory; return fileDirectory;
} }
/// <summary>Read the content of a file directory. - single request version</summary>
/// <param name="directoryName">The name of the directory.</param>
/// <param name="continueAfter">The last directory when request is a continuation, or null otherwise</param>
/// <param name="moreFollows">true, when more files are available, false otherwise</param>
/// <exception cref="IedConnectionException">This exception is thrown if there is a connection or service error</exception>
public List<FileDirectoryEntry> GetFileDirectoryEx(string directoryName, string continueAfter, out bool moreFollows)
{
int error;
IntPtr fileEntryList = IedConnection_getFileDirectoryEx(connection, out error, directoryName, continueAfter, out moreFollows);
if (error != 0)
throw new IedConnectionException("Reading file directory failed", error);
List<FileDirectoryEntry> fileDirectory = new List<FileDirectoryEntry>();
IntPtr element = LinkedList_getNext(fileEntryList);
while (element != IntPtr.Zero)
{
IntPtr elementData = LinkedList_getData(element);
FileDirectoryEntry entry = new FileDirectoryEntry(elementData);
fileDirectory.Add(entry);
FileDirectoryEntry_destroy(elementData);
element = LinkedList_getNext(element);
}
LinkedList_destroyStatic(fileEntryList);
return fileDirectory;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate bool InternalIedClientGetFileHandler(IntPtr parameter, IntPtr buffer, UInt32 bytesRead); private delegate bool InternalIedClientGetFileHandler(IntPtr parameter, IntPtr buffer, UInt32 bytesRead);

@ -36,7 +36,7 @@ namespace IEC61850
/// <summary> /// <summary>
/// This class is used to hold MMS data values of different types. /// This class is used to hold MMS data values of different types.
/// </summary> /// </summary>
public class MmsValue : IEnumerable public class MmsValue : IEnumerable, IDisposable
{ {
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr MmsValue_toString (IntPtr self); static extern IntPtr MmsValue_toString (IntPtr self);
@ -179,12 +179,17 @@ namespace IEC61850
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void MmsValue_setElement(IntPtr complexValue, int index, IntPtr elementValue); static extern void MmsValue_setElement(IntPtr complexValue, int index, IntPtr elementValue);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr MmsVariableSpecification_getChildValue(IntPtr self, IntPtr value, string childId);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr MmsValue_clone(IntPtr self);
internal IntPtr valueReference; /* reference to native MmsValue instance */ internal IntPtr valueReference; /* reference to native MmsValue instance */
private bool responsableForDeletion; /* if .NET wrapper is responsable for the deletion of the native MmsValue instance */ private bool responsableForDeletion; /* if .NET wrapper is responsable for the deletion of the native MmsValue instance */
// TODO make internal internal MmsValue (IntPtr value)
public MmsValue (IntPtr value)
{ {
valueReference = value; valueReference = value;
this.responsableForDeletion = false; this.responsableForDeletion = false;
@ -235,11 +240,23 @@ namespace IEC61850
valueReference = MmsValue_newVisibleString(value); valueReference = MmsValue_newVisibleString(value);
} }
~MmsValue () public void Dispose()
{ {
if (responsableForDeletion) lock (this) {
MmsValue_delete (valueReference); if (valueReference != IntPtr.Zero) {
}
if (responsableForDeletion)
MmsValue_delete (valueReference);
valueReference = IntPtr.Zero;
}
}
}
~MmsValue ()
{
Dispose();
}
/// <summary> /// <summary>
/// Create a new MmsValue instance of type MMS_BIT_STRING. /// Create a new MmsValue instance of type MMS_BIT_STRING.
@ -864,6 +881,41 @@ namespace IEC61850
throw new MmsValueException ("Value type is not float"); throw new MmsValueException ("Value type is not float");
} }
/// <summary>
/// Gets the child value with the given name
/// </summary>
/// <returns>the child value or null if no matching child has been found.</returns>
/// <param name="childPath">path specifying the child using '.' or '$' as path element separator.</param>
/// <param name="specification">the variable specification to use.</param>
public MmsValue GetChildValue(string childPath, MmsVariableSpecification specification)
{
StringBuilder childPathStr = new StringBuilder(childPath);
childPathStr.Replace('.', '$');
IntPtr childPtr = MmsVariableSpecification_getChildValue(specification.self, valueReference, childPathStr.ToString());
if (childPtr == IntPtr.Zero)
return null;
else
{
return new MmsValue(childPtr);
}
}
/// <summary>
/// Get an identical copy of this instance
/// </summary>
public MmsValue Clone()
{
IntPtr clonePtr = MmsValue_clone(valueReference);
if (clonePtr == IntPtr.Zero)
return null;
return new MmsValue(clonePtr, true);
}
public override bool Equals (object obj) public override bool Equals (object obj)
{ {
MmsValue otherValue = (MmsValue) obj; MmsValue otherValue = (MmsValue) obj;
@ -914,9 +966,31 @@ namespace IEC61850
return retString; return retString;
} }
case MmsType.MMS_ARRAY:
{
string retString = "[";
bool first = true;
foreach (MmsValue element in this) {
if (first) {
retString += element.ToString ();
first = false;
} else {
retString += ", " + element.ToString ();
}
}
retString += "]";
return retString;
}
case MmsType.MMS_DATA_ACCESS_ERROR:
return "error: " + GetDataAccessError().ToString();
default: default:
return "unknown"; return "unknown (type:" + GetType().ToString() + ")";
} }
} }

@ -26,6 +26,7 @@ using System.Runtime.InteropServices;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections; using System.Collections;
using System.Text;
namespace IEC61850 namespace IEC61850
{ {
@ -39,9 +40,6 @@ namespace IEC61850
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void MmsVariableSpecification_destroy(IntPtr self); static extern void MmsVariableSpecification_destroy(IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr MmsVariableSpecification_getChildValue(IntPtr self, IntPtr value, string childId);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr MmsVariableSpecification_getNamedVariableRecursive(IntPtr variable, string nameId); static extern IntPtr MmsVariableSpecification_getNamedVariableRecursive(IntPtr variable, string nameId);
@ -63,13 +61,20 @@ namespace IEC61850
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern int MmsVariableSpecification_getExponentWidth(IntPtr self); static extern int MmsVariableSpecification_getExponentWidth(IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern bool MmsVariableSpecification_isValueOfType(IntPtr self, IntPtr value);
internal IntPtr self; internal IntPtr self;
private bool responsableForDeletion; private bool responsableForDeletion;
internal MmsVariableSpecification (IntPtr self) /* only to prevent garbage collector to destroy parent element */
internal MmsVariableSpecification parent = null;
internal MmsVariableSpecification (IntPtr self, MmsVariableSpecification parent)
{ {
this.self = self; this.self = self;
this.responsableForDeletion = false; this.responsableForDeletion = false;
this.parent = parent;
} }
internal MmsVariableSpecification (IntPtr self, bool responsableForDeletion) internal MmsVariableSpecification (IntPtr self, bool responsableForDeletion)
@ -78,6 +83,27 @@ namespace IEC61850
this.responsableForDeletion = responsableForDeletion; this.responsableForDeletion = responsableForDeletion;
} }
/// <summary>
/// Get a child variable specification by its name
/// </summary>
/// <returns>the varibable specification of the child, or null if no such child is existing.</returns>
/// <param name="name">The child name (can also be a path separating the elements with '.' or '$')</param>
public MmsVariableSpecification GetChildByName(string name)
{
StringBuilder nameId = new StringBuilder(name);
nameId.Replace('.', '$');
IntPtr varSpecPtr = MmsVariableSpecification_getNamedVariableRecursive(self, nameId.ToString());
if (varSpecPtr != IntPtr.Zero)
{
return new MmsVariableSpecification(varSpecPtr, this);
}
else
return null;
}
~MmsVariableSpecification () ~MmsVariableSpecification ()
{ {
if (responsableForDeletion) if (responsableForDeletion)
@ -106,7 +132,7 @@ namespace IEC61850
{ {
if (GetType() == MmsType.MMS_ARRAY) { if (GetType() == MmsType.MMS_ARRAY) {
IntPtr varSpecPtr = MmsVariableSpecification.MmsVariableSpecification_getArrayElementSpecification(self); IntPtr varSpecPtr = MmsVariableSpecification.MmsVariableSpecification_getArrayElementSpecification(self);
return new MmsVariableSpecification(varSpecPtr); return new MmsVariableSpecification(varSpecPtr, this);
} }
else else
throw new MmsValueException ("specification is of wrong type"); throw new MmsValueException ("specification is of wrong type");
@ -127,7 +153,7 @@ namespace IEC61850
if ((index >= 0) && (index < Size ())) { if ((index >= 0) && (index < Size ())) {
IntPtr varSpecPtr = MmsVariableSpecification_getChildSpecificationByIndex(self, index); IntPtr varSpecPtr = MmsVariableSpecification_getChildSpecificationByIndex(self, index);
return new MmsVariableSpecification(varSpecPtr); return new MmsVariableSpecification(varSpecPtr, this);
} }
else else
throw new MmsValueException ("Index out of bounds"); throw new MmsValueException ("Index out of bounds");
@ -157,6 +183,16 @@ namespace IEC61850
return MmsVariableSpecification_getSize(self); return MmsVariableSpecification_getSize(self);
} }
/// <summary>
/// Determines whether the given value object matches this type
/// </summary>
/// <returns><c>true</c> if the value matches this type; otherwise, <c>false</c>.</returns>
/// <param name="value">the value to test.</param>
public bool IsValueOfType(MmsValue value)
{
return MmsVariableSpecification_isValueOfType(self, value.valueReference);
}
IEnumerator IEnumerable.GetEnumerator () IEnumerator IEnumerable.GetEnumerator ()
{ {
return new MmsVariableSpecificationEnumerator (this); return new MmsVariableSpecificationEnumerator (this);

@ -34,6 +34,8 @@ namespace IEC61850
/// <summary> /// <summary>
/// Report handler. /// Report handler.
/// </summary> /// </summary>
/// <param name="report">represents the received report. DON'T use this object
/// outside the scope of the report handler!</param>
public delegate void ReportHandler (Report report, object parameter); public delegate void ReportHandler (Report report, object parameter);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
@ -130,7 +132,7 @@ namespace IEC61850
static extern void ClientReportControlBlock_setPurgeBuf (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool purgeBuf); static extern void ClientReportControlBlock_setPurgeBuf (IntPtr self, [MarshalAs(UnmanagedType.I1)] bool purgeBuf);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern Int32 ClientReportControlBlock_getResvTms (IntPtr self); static extern Int16 ClientReportControlBlock_getResvTms (IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern void ClientReportControlBlock_setResvTms (IntPtr self, Int16 resvTms); static extern void ClientReportControlBlock_setResvTms (IntPtr self, Int16 resvTms);
@ -711,6 +713,51 @@ namespace IEC61850
flagOptFlds = true; flagOptFlds = true;
} }
/// <summary>
/// Gets the ResvTms (reservation time) value
/// </summary>
/// <remarks>
/// Only for BRCB.
/// Value of -1 indicate the BRCB is exclusively reserved for a set of client based upon configuration.
/// Value of 0 means that the BRCB is not reserved.
/// Positive value indicates that the BRCB is reserved dynamically and the value is the number of
/// seconds for reservation after association loss.
/// </remarks>
/// <returns>The reservation time</returns>
public Int16 GetResvTms()
{
return ClientReportControlBlock_getResvTms (self);
}
/// <summary>
/// Sets the ResvTms (reservation time) value
/// </summary>
/// <param name="resvTms">the reservation time value</param>
public void SetResvTms(Int16 resvTms)
{
ClientReportControlBlock_setResvTms (self, resvTms);
flagResvTms = true;
}
/// <summary>
/// Gets the current owner of the RCB
/// </summary>
/// <returns>The owner information, or null when no owner information is available.</returns>
public byte[] GetOwner()
{
IntPtr mmsValuePtr = ClientReportControlBlock_getOwner(self);
if (mmsValuePtr != IntPtr.Zero)
{
MmsValue octetStringVal = new MmsValue(mmsValuePtr);
return octetStringVal.getOctetString();
}
else
return null;
}
} }
} }

@ -1,7 +1,7 @@
/* /*
* Reporting.cs * Reporting.cs
* *
* Copyright 2014 Michael Zillgith * Copyright 2014-2018 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -147,6 +147,15 @@ namespace IEC61850
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern IntPtr ClientReport_getDataReference(IntPtr self, int elementIndex); static extern IntPtr ClientReport_getDataReference(IntPtr self, int elementIndex);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern bool ClientReport_hasSubSeqNum(IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern UInt16 ClientReport_getSubSeqNum(IntPtr self);
[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)]
static extern bool ClientReport_getMoreSeqmentsFollow(IntPtr self);
private IntPtr self; private IntPtr self;
private IntPtr dataSetValues = IntPtr.Zero; private IntPtr dataSetValues = IntPtr.Zero;
@ -213,16 +222,55 @@ namespace IEC61850
return ClientReport_getBufOvfl(self); return ClientReport_getBufOvfl(self);
} }
/// <summary>
/// Indicates if the report contains a sequence number (SeqNum) field
/// </summary>
/// <returns><c>true</c> if this instance has SeqNum; otherwise, <c>false</c>.</returns>
public bool HasSeqNum () public bool HasSeqNum ()
{ {
return ClientReport_hasSeqNum(self); return ClientReport_hasSeqNum(self);
} }
/// <summary>
/// Gets the value of the SeqNum field
/// </summary>
/// <returns>The report sequence number</returns>
public UInt16 GetSeqNum () public UInt16 GetSeqNum ()
{ {
return ClientReport_getSeqNum(self); return ClientReport_getSeqNum(self);
} }
/// <summary>
/// Indicates if the report contains a sub sequence number (SubSeqNum) and more segments follow (MoreSegmentsFollow) field
/// </summary>
/// <returns><c>true</c> if this instance has SubSeqNum and MoreSegmentsFollow; otherwise, <c>false</c>.</returns>
public bool HasSubSeqNum()
{
return ClientReport_hasSubSeqNum(self);
}
/// <summary>
/// Gets the sub sequence number (SubSeqNum) value of a segmented report
/// </summary>
/// <returns>The sub sequence number.</returns>
public UInt16 GetSubSeqNum()
{
return ClientReport_getSubSeqNum(self);
}
/// <summary>
/// Gets the more segments follow (MoreSegmentsFollow) flag
/// </summary>
/// <returns><c>true</c>, if more report segments follow, <c>false</c> otherwise.</returns>
public bool GetMoreSegmentsFollow()
{
return ClientReport_getMoreSeqmentsFollow(self);
}
/// <summary>
/// Determines whether this report contains reason for inclusion information
/// </summary>
/// <returns><c>true</c> if this report contains reason for inclusion information; otherwise, <c>false</c>.</returns>
public bool HasReasonForInclusion () public bool HasReasonForInclusion ()
{ {
return ClientReport_hasReasonForInclusion(self); return ClientReport_hasReasonForInclusion(self);
@ -245,7 +293,7 @@ namespace IEC61850
values = new MmsValue(dataSetValues); values = new MmsValue(dataSetValues);
} }
return values; return values.Clone();
} }
/// <summary> /// <summary>

@ -0,0 +1,78 @@
using System;
using IEC61850.Client;
using IEC61850.Common;
namespace client_examples_setting_groups
{
/// <summary>
/// This class is intended to show how to use setting groups from the client side.
/// It works with server_example_setting_groups.
/// </summary>
class SettingGroupsClientExample
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
IedConnection con = new IedConnection ();
string hostname;
if (args.Length > 0)
hostname = args[0];
else
hostname = "127.0.0.1";
int port = 102;
if (args.Length > 1)
port = Int32.Parse(args [1]);
Console.WriteLine("Connect to " + hostname);
try
{
con.Connect(hostname, port);
/* Get variable specification of the SGCB (optional) */
MmsVariableSpecification sgcbVarSpec = con.GetVariableSpecification("DEMOPROT/LLN0.SGCB", FunctionalConstraint.SP);
/* Read SGCB */
MmsValue sgcbVal = con.ReadValue("DEMOPROT/LLN0.SGCB", FunctionalConstraint.SP);
Console.WriteLine("NumOfSG: {0}", sgcbVal.GetChildValue("NumOfSG", sgcbVarSpec).ToString());
Console.WriteLine("ActSG: {0}", sgcbVal.GetChildValue("ActSG", sgcbVarSpec).ToString());
Console.WriteLine("EditSG: {0}", sgcbVal.GetChildValue("EditSG", sgcbVarSpec).ToString());
Console.WriteLine("CnfEdit: {0}", sgcbVal.GetChildValue("CnfEdit", sgcbVarSpec).ToString());
/* Set active setting group */
con.WriteValue("DEMOPROT/LLN0.SGCB.ActSG", FunctionalConstraint.SP, new MmsValue((uint) 2));
/* Set edit setting group */
con.WriteValue("DEMOPROT/LLN0.SGCB.EditSG", FunctionalConstraint.SP, new MmsValue((uint) 1));
/* Change a setting group value */
con.WriteValue("DEMOPROT/PTOC1.StrVal.setMag.f", FunctionalConstraint.SE, new MmsValue(1.0f));
/* Confirm new setting group values */
con.WriteValue("DEMOPROT/LLN0.SGCB.CnfEdit", FunctionalConstraint.SP, new MmsValue(true));
/* Read SGCB */
con.ReadValue("DEMOPROT/LLN0.SGCB", FunctionalConstraint.SP);
Console.WriteLine("ActSG: {0}",sgcbVal.GetChildValue("ActSG", sgcbVarSpec).ToString());
con.Abort();
}
catch (IedConnectionException e)
{
Console.WriteLine(e.Message);
}
// release all resources - do NOT use the object after this call!!
con.Dispose ();
}
}
}

@ -0,0 +1,27 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("client-example-setting-group")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("mzillgit")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{0DA95476-B149-450B-AC36-01CEECFC1A43}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>clientexamplesettinggroup</RootNamespace>
<AssemblyName>client-example-setting-group</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>full</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\IEC61850forCSharp\IEC61850.NET.csproj">
<Project>{C35D624E-5506-4560-8074-1728F1FA1A4D}</Project>
<Name>IEC61850.NET</Name>
</ProjectReference>
</ItemGroup>
</Project>

@ -44,6 +44,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sv_subscriber", "sv_subscri
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls_server_example", "tls_server_example\tls_server_example.csproj", "{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls_server_example", "tls_server_example\tls_server_example.csproj", "{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_setting_groups", "client_example_setting_groups\client_example_setting_groups.csproj", "{0DA95476-B149-450B-AC36-01CEECFC1A43}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -54,6 +56,10 @@ Global
{0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.Build.0 = Release|Any CPU {0BECEC77-2315-4B95-AFF9-E6007E644BBF}.Release|Any CPU.Build.0 = Release|Any CPU
{0DA95476-B149-450B-AC36-01CEECFC1A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DA95476-B149-450B-AC36-01CEECFC1A43}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DA95476-B149-450B-AC36-01CEECFC1A43}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DA95476-B149-450B-AC36-01CEECFC1A43}.Release|Any CPU.Build.0 = Release|Any CPU
{1285372C-2E62-494A-A661-8D5D3873318C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1285372C-2E62-494A-A661-8D5D3873318C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1285372C-2E62-494A-A661-8D5D3873318C}.Debug|Any CPU.Build.0 = Debug|Any CPU {1285372C-2E62-494A-A661-8D5D3873318C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1285372C-2E62-494A-A661-8D5D3873318C}.Release|Any CPU.ActiveCfg = Release|Any CPU {1285372C-2E62-494A-A661-8D5D3873318C}.Release|Any CPU.ActiveCfg = Release|Any CPU

@ -67,7 +67,18 @@ namespace example1
DataSet dataSet = con.ReadDataSetValues("simpleIOGenericIO/LLN0.Events", null); DataSet dataSet = con.ReadDataSetValues("simpleIOGenericIO/LLN0.Events", null);
Console.WriteLine("Read data set " + dataSet.GetReference()); Console.WriteLine("Read data set " + dataSet.GetReference());
/* read multiple variables (WARNING: this is not IEC 61850 standard compliant but might
* be supported by most servers).
*/
MmsConnection mmsConnection = con.GetMmsConnection();
MmsValue result = mmsConnection.ReadMultipleVariables("simpleIOGenericIO", new List<string>() {
"GGIO1$ST$Ind1", "GGIO1$ST$Ind1", "GGIO1$ST$Ind1","GGIO1$ST$Ind1"
});
Console.WriteLine(result.ToString());
con.Abort(); con.Abort();
} }

@ -15,7 +15,9 @@ namespace files
{ {
public static void printFiles (IedConnection con, string prefix, string parent) public static void printFiles (IedConnection con, string prefix, string parent)
{ {
List<FileDirectoryEntry> files = con.GetFileDirectory (parent); bool moreFollows = false;
List<FileDirectoryEntry> files = con.GetFileDirectoryEx (parent, null, out moreFollows);
foreach (FileDirectoryEntry file in files) { foreach (FileDirectoryEntry file in files) {
Console.WriteLine (prefix + file.GetFileName () + "\t" + file.GetFileSize () + "\t" + Console.WriteLine (prefix + file.GetFileName () + "\t" + file.GetFileSize () + "\t" +
@ -26,6 +28,8 @@ namespace files
} }
} }
if (moreFollows)
Console.WriteLine("-- MORE FILE AVAILABLE --");
} }
static bool getFileHandler (object parameter, byte[] data) static bool getFileHandler (object parameter, byte[] data)
@ -48,7 +52,7 @@ namespace files
if (args.Length > 0) if (args.Length > 0)
hostname = args [0]; hostname = args [0];
else else
hostname = "10.0.2.2"; hostname = "127.0.0.1";
Console.WriteLine ("Connect to " + hostname); Console.WriteLine ("Connect to " + hostname);

@ -104,6 +104,10 @@ namespace reporting
rcb2.SetRCBValues(); rcb2.SetRCBValues();
rcb2.GetRCBValues();
Console.WriteLine("RCB: " + rcbReference2 + " Owner: " + BitConverter.ToString(rcb2.GetOwner()));
rcb3.GetRCBValues(); rcb3.GetRCBValues();
if (rcb3.IsBuffered()) if (rcb3.IsBuffered())

@ -276,7 +276,10 @@ namespace tests
[Test ()] [Test ()]
public void AccessDataModelClientServer() public void AccessDataModelClientServer()
{ {
IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile ("../../model.cfg"); IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("../../model.cfg");
ModelNode ind1 = iedModel.GetModelNodeByShortObjectReference ("GenericIO/GGIO1.Ind1.stVal"); ModelNode ind1 = iedModel.GetModelNodeByShortObjectReference ("GenericIO/GGIO1.Ind1.stVal");

@ -27,6 +27,7 @@
#include <signal.h> #include <signal.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#define _USE_MATH_DEFINES
#include <math.h> #include <math.h>
/* Include the generated header with the model access handles */ /* Include the generated header with the model access handles */
@ -92,6 +93,7 @@ setupSVPublisher(const char* svInterface)
vol4q = SVPublisher_ASDU_addQuality(asdu); vol4q = SVPublisher_ASDU_addQuality(asdu);
SVPublisher_ASDU_setSmpCntWrap(asdu, 4000); SVPublisher_ASDU_setSmpCntWrap(asdu, 4000);
SVPublisher_ASDU_setRefrTm(asdu, 0);
SVPublisher_setupComplete(svPublisher); SVPublisher_setupComplete(svPublisher);
} }
@ -220,6 +222,8 @@ main(int argc, char** argv)
SVPublisher_ASDU_setINT32(asdu, vol4, voltageN); SVPublisher_ASDU_setINT32(asdu, vol4, voltageN);
SVPublisher_ASDU_setQuality(asdu, vol4q, q); SVPublisher_ASDU_setQuality(asdu, vol4q, q);
SVPublisher_ASDU_setRefrTm(asdu, Hal_getTimeInMs());
SVPublisher_ASDU_setSmpCnt(asdu, (uint16_t) sampleCount); SVPublisher_ASDU_setSmpCnt(asdu, (uint16_t) sampleCount);
SVPublisher_publish(svPublisher); SVPublisher_publish(svPublisher);

@ -67,18 +67,28 @@ int main(int argc, char** argv) {
LinkedList_destroyStatic(newDataSetEntries); LinkedList_destroyStatic(newDataSetEntries);
printf("error: %i\n", error);
/* read data set */ if (error == IED_ERROR_OK ) {
ClientDataSet clientDataSet;
clientDataSet = IedConnection_readDataSetValues(con, &error, "simpleIOGenericIO/LLN0.AnalogueValues", NULL); /* read new data set */
ClientDataSet clientDataSet;
printDataSetValues(ClientDataSet_getValues(clientDataSet)); clientDataSet = IedConnection_readDataSetValues(con, &error, "simpleIOGenericIO/LLN0.AnalogueValues", NULL);
Thread_sleep(1000); if (error == IED_ERROR_OK) {
printDataSetValues(ClientDataSet_getValues(clientDataSet));
IedConnection_deleteDataSet(con, &error, "simpleIOGenericIO/LLN0.AnalogueValues"); ClientDataSet_destroy(clientDataSet);
}
else {
printf("Failed to read data set (error code: %d)\n", error);
}
IedConnection_deleteDataSet(con, &error, "simpleIOGenericIO/LLN0.AnalogueValues");
}
else {
printf("Failed to create data set (error code: %d)\n", error);
}
IedConnection_close(con); IedConnection_close(con);
} }

@ -18,6 +18,7 @@ print_help()
printf("-i show server identity\n"); printf("-i show server identity\n");
printf("-t <domain_name> show domain directory\n"); printf("-t <domain_name> show domain directory\n");
printf("-r <variable_name> read domain variable\n"); printf("-r <variable_name> read domain variable\n");
printf("-c <component_name> specify component name for variable read\n");
printf("-a <domain_name> specify domain for read or write command\n"); printf("-a <domain_name> specify domain for read or write command\n");
printf("-f show file list\n"); printf("-f show file list\n");
printf("-g <filename> get file attributes\n"); printf("-g <filename> get file attributes\n");
@ -105,6 +106,7 @@ int main(int argc, char** argv) {
char* domainName = NULL; char* domainName = NULL;
char* variableName = NULL; char* variableName = NULL;
char* componentName = NULL;
char* filename = NULL; char* filename = NULL;
char* journalName = NULL; char* journalName = NULL;
@ -122,7 +124,7 @@ int main(int argc, char** argv) {
int c; int c;
while ((c = getopt(argc, argv, "mifdh:p:l:t:a:r:g:j:x:v:")) != -1) while ((c = getopt(argc, argv, "mifdh:p:l:t:a:r:g:j:x:v:c:")) != -1)
switch (c) { switch (c) {
case 'm': case 'm':
printRawMmsMessages = 1; printRawMmsMessages = 1;
@ -155,6 +157,9 @@ int main(int argc, char** argv) {
readVariable = 1; readVariable = 1;
variableName = StringUtils_copyString(optarg); variableName = StringUtils_copyString(optarg);
break; break;
case 'c':
componentName = StringUtils_copyString(optarg);
break;
case 'v': case 'v':
readVariableList = 1; readVariableList = 1;
variableName = StringUtils_copyString(optarg); variableName = StringUtils_copyString(optarg);
@ -323,7 +328,13 @@ int main(int argc, char** argv) {
if (readVariable) { if (readVariable) {
if (readWriteHasDomain) { if (readWriteHasDomain) {
MmsValue* result = MmsConnection_readVariable(con, &error, domainName, variableName);
MmsValue* result;
if (componentName == NULL)
result = MmsConnection_readVariable(con, &error, domainName, variableName);
else
result = MmsConnection_readVariableComponent(con, &error, domainName, variableName, componentName);
if (error != MMS_ERROR_NONE) { if (error != MMS_ERROR_NONE) {
printf("Reading variable failed: (ERROR %i)\n", error); printf("Reading variable failed: (ERROR %i)\n", error);

@ -100,9 +100,10 @@ main(int argc, char** argv)
IedServerConfig_enableFileService(config, false); IedServerConfig_enableFileService(config, false);
/* disable dynamic data set service */ /* disable dynamic data set service */
IedServerConfig_enableDynamicDataSetService(config, false); IedServerConfig_enableDynamicDataSetService(config, true);
IedServerConfig_enableLogService(config, true); /* disable log service */
IedServerConfig_enableLogService(config, false);
/* set maximum number of clients */ /* set maximum number of clients */
IedServerConfig_setMaxMmsConnections(config, 2); IedServerConfig_setMaxMmsConnections(config, 2);

@ -26,6 +26,7 @@ if(WIN32)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib") if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib")
message("Found winpcap -> compile ethernet HAL layer (required for GOOSE/SV support)") message("Found winpcap -> compile ethernet HAL layer (required for GOOSE/SV support)")
set(WITH_WPCAP 1) set(WITH_WPCAP 1)
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Include")
else() else()
message("winpcap not found -> skip ethernet HAL layer (no GOOSE/SV support)") message("winpcap not found -> skip ethernet HAL layer (no GOOSE/SV support)")
endif() endif()
@ -154,7 +155,7 @@ ENDIF(MINGW)
iF(WITH_WPCAP) iF(WITH_WPCAP)
target_link_libraries(hal target_link_libraries(hal
${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/lib/wpcap.lib ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/wpcap.lib
${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/lib/packet.lib ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/winpcap/Lib/packet.lib
) )
ENDIF(WITH_WPCAP) ENDIF(WITH_WPCAP)

@ -155,7 +155,7 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr)
memset(&buffer, 0x00, sizeof(buffer)); memset(&buffer, 0x00, sizeof(buffer));
strcpy(buffer.ifr_name, interfaceId); strncpy(buffer.ifr_name, interfaceId, IFNAMSIZ);
ioctl(sock, SIOCGIFHWADDR, &buffer); ioctl(sock, SIOCGIFHWADDR, &buffer);

@ -313,6 +313,8 @@ Ethernet_getInterfaceMACAddress(const char* interfaceId, uint8_t* addr)
char* interfaceName = getInterfaceName((int) interfaceIndex); char* interfaceName = getInterfaceName((int) interfaceIndex);
getAdapterMacAddress(interfaceName, addr); getAdapterMacAddress(interfaceName, addr);
free(interfaceName);
} }
@ -329,9 +331,12 @@ Ethernet_createSocket(const char* interfaceId, uint8_t* destAddress)
if ((pcapSocket = pcap_open_live(interfaceName, 65536, PCAP_OPENFLAG_PROMISCUOUS, 10, errbuf)) == NULL) if ((pcapSocket = pcap_open_live(interfaceName, 65536, PCAP_OPENFLAG_PROMISCUOUS, 10, errbuf)) == NULL)
{ {
printf("Open ethernet socket failed for device %s\n", interfaceName); printf("Open ethernet socket failed for device %s\n", interfaceName);
free(interfaceName);
return NULL; return NULL;
} }
free(interfaceName);
EthernetSocket ethernetSocket = (EthernetSocket) calloc(1, sizeof(struct sEthernetSocket)); EthernetSocket ethernetSocket = (EthernetSocket) calloc(1, sizeof(struct sEthernetSocket));
ethernetSocket->rawSocket = pcapSocket; ethernetSocket->rawSocket = pcapSocket;

@ -1,8 +1,12 @@
# The SWIG functions/macros used in this module, swig_add_module and swig_add_library
# are not available in CMake versions earlier than 3.8
# cmake_minimum_required(VERSION 3.8)
find_package(SWIG REQUIRED) find_package(SWIG REQUIRED)
include(${SWIG_USE_FILE}) include(${SWIG_USE_FILE})
find_package(PythonLibs REQUIRED) find_package(PythonInterp ${BUILD_PYTHON_VERSION} REQUIRED)
find_package(PythonInterp ${PYTHONLIBS_VERSION_STRING} EXACT REQUIRED) find_package(PythonLibs ${PYTHON_VERSION_STRING} EXACT REQUIRED)
include_directories(${PYTHON_INCLUDE_PATH}) include_directories(${PYTHON_INCLUDE_PATH})
include_directories(${CMAKE_CURRENT_SOURCE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR})
@ -16,7 +20,7 @@ else()
set(LIBS iec61850-shared) set(LIBS iec61850-shared)
endif() endif()
if(${SWIG_VERSION} VERSION_LESS 3.0) if(${CMAKE_VERSION} VERSION_LESS 3.8)
swig_add_module(iec61850 python iec61850.i) swig_add_module(iec61850 python iec61850.i)
else() else()
swig_add_library(iec61850 swig_add_library(iec61850
@ -30,7 +34,7 @@ swig_link_libraries(iec61850 ${PYTHON_LIBRARIES} ${LIBS})
# Finding python modules install path # Finding python modules install path
execute_process( execute_process(
COMMAND ${PYTHON_EXECUTABLE} -c COMMAND ${PYTHON_EXECUTABLE} -c
"import site, sys; sys.stdout.write(site.getsitepackages()[-1])" "from distutils.sysconfig import get_python_lib; import sys; sys.stdout.write(get_python_lib())"
OUTPUT_VARIABLE PYTHON_SITE_DIR OUTPUT_VARIABLE PYTHON_SITE_DIR
) )

@ -17,7 +17,7 @@
#include "platform_endian.h" #include "platform_endian.h"
#define LIBIEC61850_VERSION "1.3.0" #define LIBIEC61850_VERSION "1.3.2"
#ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME #ifndef CONFIG_DEFAULT_MMS_VENDOR_NAME
#define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com" #define CONFIG_DEFAULT_MMS_VENDOR_NAME "libiec61850.com"

@ -1,7 +1,7 @@
/* /*
* goose_publisher.c * goose_publisher.c
* *
* Copyright 2013 Michael Zillgith * Copyright 2013-2018 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -248,6 +248,10 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe
goosePduLength += BerEncoder_determineEncodedStringSize(self->goCBRef); goosePduLength += BerEncoder_determineEncodedStringSize(self->goCBRef);
uint32_t timeAllowedToLive = self->timeAllowedToLive;
goosePduLength += 2 + BerEncoder_UInt32determineEncodedSize(timeAllowedToLive);
goosePduLength += BerEncoder_determineEncodedStringSize(self->dataSetRef); goosePduLength += BerEncoder_determineEncodedStringSize(self->dataSetRef);
if (self->goID != NULL) if (self->goID != NULL)
@ -255,10 +259,6 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe
else else
goosePduLength += BerEncoder_determineEncodedStringSize(self->goCBRef); goosePduLength += BerEncoder_determineEncodedStringSize(self->goCBRef);
uint32_t timeAllowedToLive = self->timeAllowedToLive;
goosePduLength += 2 + BerEncoder_UInt32determineEncodedSize(timeAllowedToLive);
goosePduLength += 2 + 8; /* for T (UTCTIME) */ goosePduLength += 2 + 8; /* for T (UTCTIME) */
goosePduLength += 2 + BerEncoder_UInt32determineEncodedSize(self->sqNum); goosePduLength += 2 + BerEncoder_UInt32determineEncodedSize(self->sqNum);
@ -289,7 +289,9 @@ createGoosePayload(GoosePublisher self, LinkedList dataSetValues, uint8_t* buffe
goosePduLength += allDataSize; goosePduLength += allDataSize;
if (goosePduLength > maxPayloadSize) uint32_t payloadSize = 1 + BerEncoder_determineLengthSize(goosePduLength) + goosePduLength;
if (payloadSize > maxPayloadSize)
return -1; return -1;
/* Step 2 - encode to buffer */ /* Step 2 - encode to buffer */

@ -162,8 +162,13 @@ ControlObjectClient_create(const char* objectReference, IedConnection connection
ctlVal = MmsVariableSpecification_getNamedVariableRecursive(oper, "ctlVal"); ctlVal = MmsVariableSpecification_getNamedVariableRecursive(oper, "ctlVal");
if (MmsVariableSpecification_getType(ctlVal) == MMS_STRUCTURE) if (ctlVal == NULL)
isAPC = true; ctlVal = MmsVariableSpecification_getNamedVariableRecursive(oper, "setMag");
if (ctlVal) {
if (MmsVariableSpecification_getType(ctlVal) == MMS_STRUCTURE)
isAPC = true;
}
MmsVariableSpecification* operTm = MmsVariableSpecification_getNamedVariableRecursive(oper, "operTm"); MmsVariableSpecification* operTm = MmsVariableSpecification_getNamedVariableRecursive(oper, "operTm");
@ -211,20 +216,24 @@ ControlObjectClient_create(const char* objectReference, IedConnection connection
self->analogValue = NULL; self->analogValue = NULL;
/* Check for T element type (Binary time -> Ed.1,UTC time -> Ed.2) */ /* Check for T element type (Binary time -> Ed.1,UTC time -> Ed.2) */
if (MmsVariableSpecification_getType(t) == MMS_BINARY_TIME) if (t) {
self->edition = 1; if (MmsVariableSpecification_getType(t) == MMS_BINARY_TIME)
self->edition = 1;
else
self->edition = 2;
}
else else
self->edition = 2; self->edition = 1;
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: Detected edition %i control\n", self->edition); printf("IED_CLIENT: Detected edition %i control\n", self->edition);
private_IedConnection_addControlClient(connection, self); private_IedConnection_addControlClient(connection, self);
free_varspec: free_varspec:
MmsVariableSpecification_destroy(ctlVarSpec); MmsVariableSpecification_destroy(ctlVarSpec);
exit_function: exit_function:
return self; return self;
} }
@ -596,24 +605,16 @@ ControlObjectClient_select(ControlObjectClient self)
goto exit_function; goto exit_function;
} }
char sboReference[130];
snprintf(sboReference, 129, "%s/%s", domainId, itemId);
if (MmsValue_getType(value) == MMS_VISIBLE_STRING) { if (MmsValue_getType(value) == MMS_VISIBLE_STRING) {
if (strcmp(MmsValue_toString(value), "") == 0) { if (strcmp(MmsValue_toString(value), "") == 0) {
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)
printf("select-response-\n"); printf("select-response-\n");
} }
else if (strcmp(MmsValue_toString(value), sboReference) == 0) { else {
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)
printf("select-response+: (%s)\n", MmsValue_toString(value)); printf("select-response+: (%s)\n", MmsValue_toString(value));
selected = true; selected = true;
} }
else {
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: select-response: (%s)\n", MmsValue_toString(value));
}
} }
else { else {
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)

@ -3,7 +3,7 @@
* *
* Client implementation for IEC 61850 reporting. * Client implementation for IEC 61850 reporting.
* *
* Copyright 2013, 2014 Michael Zillgith * Copyright 2013-2018 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -43,6 +43,8 @@ struct sClientReport
char* dataSetName; char* dataSetName;
int dataSetNameSize; /* size of the dataSetName buffer */ int dataSetNameSize; /* size of the dataSetName buffer */
int dataSetSize;
MmsValue* entryId; MmsValue* entryId;
MmsValue* dataReferences; MmsValue* dataReferences;
MmsValue* dataSetValues; MmsValue* dataSetValues;
@ -56,11 +58,15 @@ struct sClientReport
bool hasConfRev; bool hasConfRev;
bool hasTimestamp; bool hasTimestamp;
bool hasBufOverflow; bool hasBufOverflow;
bool hasSubSequenceNumber;
uint64_t timestamp; uint64_t timestamp;
uint16_t seqNum; uint16_t seqNum;
uint32_t confRev; uint32_t confRev;
bool bufOverflow; bool bufOverflow;
uint16_t subSeqNum;
bool moreSegementsFollow;
}; };
char* char*
@ -89,6 +95,8 @@ ClientReport_create()
{ {
ClientReport self = (ClientReport) GLOBAL_CALLOC(1, sizeof(struct sClientReport)); ClientReport self = (ClientReport) GLOBAL_CALLOC(1, sizeof(struct sClientReport));
self->dataSetSize = -1;
return self; return self;
} }
@ -242,6 +250,24 @@ ClientReport_getDataSetValues(ClientReport self)
return self->dataSetValues; return self->dataSetValues;
} }
bool
ClientReport_hasSubSeqNum(ClientReport self)
{
return self->hasSubSequenceNumber;
}
uint16_t
ClientReport_getSubSeqNum(ClientReport self)
{
return self->subSeqNum;
}
bool
ClientReport_getMoreSeqmentsFollow(ClientReport self)
{
return self->moreSegementsFollow;
}
static ClientReport static ClientReport
lookupReportHandler(IedConnection self, const char* rcbReference) lookupReportHandler(IedConnection self, const char* rcbReference)
{ {
@ -386,6 +412,7 @@ private_IedConnection_handleReport(IedConnection self, MmsValue* value)
matchingReport->hasConfRev = false; matchingReport->hasConfRev = false;
matchingReport->hasDataSetName = false; matchingReport->hasDataSetName = false;
matchingReport->hasBufOverflow = false; matchingReport->hasBufOverflow = false;
matchingReport->hasSubSequenceNumber = false;
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: received report with ID %s\n", MmsValue_toString(rptIdValue)); printf("IED_CLIENT: received report with ID %s\n", MmsValue_toString(rptIdValue));
@ -535,10 +562,43 @@ private_IedConnection_handleReport(IedConnection self, MmsValue* value)
inclusionIndex++; inclusionIndex++;
} }
/* handle segmentation fields (check ReportedOptFlds.segmentation) */
if (MmsValue_getBitStringBit(optFlds, 9) == true) {
MmsValue* subSeqNum = MmsValue_getElement(value, inclusionIndex);
inclusionIndex++;
if ((subSeqNum == NULL) || (MmsValue_getType(subSeqNum) != MMS_UNSIGNED)) {
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: received malformed report (SubSeqNum)\n");
goto exit_function;
}
else {
matchingReport->subSeqNum = (uint16_t) MmsValue_toUint32(subSeqNum);
}
MmsValue* moreSegmentsFollow = MmsValue_getElement(value, inclusionIndex);
inclusionIndex++;
if ((moreSegmentsFollow == NULL) || (MmsValue_getType(moreSegmentsFollow) != MMS_BOOLEAN)) {
if ((subSeqNum == NULL) || (MmsValue_getType(subSeqNum) != MMS_UNSIGNED)) {
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: received malformed report (MoreSegmentsFollow)\n");
/* skip segmentation fields */ goto exit_function;
if (MmsValue_getBitStringBit(optFlds, 9) == true) }
inclusionIndex += 2; }
else {
matchingReport->moreSegementsFollow = MmsValue_getBoolean(moreSegmentsFollow);
}
matchingReport->hasSequenceNumber = true;
}
else {
matchingReport->subSeqNum = 0;
matchingReport->moreSegementsFollow = false;
}
MmsValue* inclusion = MmsValue_getElement(value, inclusionIndex); MmsValue* inclusion = MmsValue_getElement(value, inclusionIndex);
@ -551,6 +611,18 @@ private_IedConnection_handleReport(IedConnection self, MmsValue* value)
int dataSetSize = MmsValue_getBitStringSize(inclusion); int dataSetSize = MmsValue_getBitStringSize(inclusion);
if (matchingReport->dataSetSize == -1) {
matchingReport->dataSetSize = dataSetSize;
}
else {
if (dataSetSize != matchingReport->dataSetSize) {
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: received malformed report (inclusion has no plausible size)\n");
goto exit_function;
}
}
int includedElements = MmsValue_getNumberOfSetBits(inclusion); int includedElements = MmsValue_getNumberOfSetBits(inclusion);
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)
@ -636,7 +708,7 @@ private_IedConnection_handleReport(IedConnection self, MmsValue* value)
MmsValue_update(dataSetElement, newElementValue); MmsValue_update(dataSetElement, newElementValue);
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: update element value type: %i\n", MmsValue_getType(newElementValue)); printf("IED_CLIENT: update element value type: %i\n", MmsValue_getType(newElementValue));
valueIndex++; valueIndex++;

@ -95,6 +95,9 @@ iedConnection_mapMmsErrorToIedError(MmsError mmsError)
case MMS_ERROR_ACCESS_OBJECT_VALUE_INVALID: case MMS_ERROR_ACCESS_OBJECT_VALUE_INVALID:
return IED_ERROR_OBJECT_VALUE_INVALID; return IED_ERROR_OBJECT_VALUE_INVALID;
case MMS_ERROR_PARSING_RESPONSE:
return IED_ERROR_OBJECT_VALUE_INVALID;
default: default:
return IED_ERROR_UNKNOWN; return IED_ERROR_UNKNOWN;
} }
@ -434,39 +437,41 @@ informationReportHandler(void* parameter, char* domainName,
if (DEBUG_IED_CLIENT) if (DEBUG_IED_CLIENT)
printf("DEBUG_IED_CLIENT: received information report for %s\n", variableListName); printf("DEBUG_IED_CLIENT: received information report for %s\n", variableListName);
if (domainName == NULL) { if (value) {
if (domainName == NULL) {
if (isVariableListName) { if (isVariableListName) {
private_IedConnection_handleReport(self, value); private_IedConnection_handleReport(self, value);
} }
else {
if (strcmp(variableListName, "LastApplError") == 0)
handleLastApplErrorMessage(self, value);
else { else {
if (DEBUG_IED_CLIENT) if (strcmp(variableListName, "LastApplError") == 0)
printf("IED_CLIENT: Received unknown variable list report for list: %s\n", variableListName); handleLastApplErrorMessage(self, value);
else {
if (DEBUG_IED_CLIENT)
printf("IED_CLIENT: Received unknown variable list report for list: %s\n", variableListName);
}
} }
} }
} else {
else { if (DEBUG_IED_CLIENT)
if (DEBUG_IED_CLIENT) printf("IED_CLIENT: RCVD CommandTermination for %s/%s\n", domainName, variableListName);
printf("IED_CLIENT: RCVD CommandTermination for %s/%s\n", domainName, variableListName);
LinkedList control = LinkedList_getNext(self->clientControls); LinkedList control = LinkedList_getNext(self->clientControls);
while (control != NULL) { while (control != NULL) {
ControlObjectClient object = (ControlObjectClient) control->data; ControlObjectClient object = (ControlObjectClient) control->data;
char* objectRef = ControlObjectClient_getObjectReference(object); char* objectRef = ControlObjectClient_getObjectReference(object);
if (doesReportMatchControlObject(domainName, variableListName, objectRef)) if (doesReportMatchControlObject(domainName, variableListName, objectRef))
private_ControlObjectClient_invokeCommandTerminationHandler(object); private_ControlObjectClient_invokeCommandTerminationHandler(object);
control = LinkedList_getNext(control); control = LinkedList_getNext(control);
}
} }
}
MmsValue_delete(value); MmsValue_delete(value);
}
} }
static IedConnection static IedConnection
@ -2291,7 +2296,7 @@ exit_function:
} }
ClientDataSet ClientDataSet
IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const char* dataSetReference, IedConnection_readDataSetValues(IedConnection self, IedClientError* err, const char* dataSetReference,
ClientDataSet dataSet) ClientDataSet dataSet)
{ {
char domainIdBuffer[65]; char domainIdBuffer[65];
@ -2316,14 +2321,14 @@ IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const
domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer); domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);
if (domainId == NULL) { if (domainId == NULL) {
*error = IED_ERROR_OBJECT_REFERENCE_INVALID; *err = IED_ERROR_OBJECT_REFERENCE_INVALID;
goto exit_function; goto exit_function;
} }
const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1; const char* itemIdRefOrig = dataSetReference + strlen(domainId) + 1;
if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) { if (strlen(itemIdRefOrig) > DATA_SET_MAX_NAME_LENGTH) {
*error = IED_ERROR_OBJECT_REFERENCE_INVALID; *err = IED_ERROR_OBJECT_REFERENCE_INVALID;
goto exit_function; goto exit_function;
} }
@ -2346,15 +2351,15 @@ IedConnection_readDataSetValues(IedConnection self, IedClientError* error, const
dataSetVal = MmsConnection_readNamedVariableListValuesAssociationSpecific(self->connection, dataSetVal = MmsConnection_readNamedVariableListValuesAssociationSpecific(self->connection,
&mmsError, itemId, true); &mmsError, itemId, true);
else else
dataSetVal= MmsConnection_readNamedVariableListValues(self->connection, &mmsError, dataSetVal = MmsConnection_readNamedVariableListValues(self->connection, &mmsError,
domainId, itemId, true); domainId, itemId, true);
if (dataSetVal == NULL) { if (dataSetVal == NULL) {
*error = iedConnection_mapMmsErrorToIedError(mmsError); *err = iedConnection_mapMmsErrorToIedError(mmsError);
goto exit_function; goto exit_function;
} }
else else
*error = IED_ERROR_OK; *err = IED_ERROR_OK;
if (dataSet == NULL) { if (dataSet == NULL) {
dataSet = ClientDataSet_create(dataSetReference); dataSet = ClientDataSet_create(dataSetReference);

@ -1057,6 +1057,42 @@ ClientReport_getDataReference(ClientReport self, int elementIndex);
uint64_t uint64_t
ClientReport_getTimestamp(ClientReport self); ClientReport_getTimestamp(ClientReport self);
/**
* \brief indicates if the report contains a sub sequence number and a more segments follow flags (for segmented reporting)
*
* \param self the ClientReport instance
*
* \returns true if the report contains sub-sequence-number and more-follows-flag, false otherwise
*/
bool
ClientReport_hasSubSeqNum(ClientReport self);
/**
* \brief get the sub sequence number of the report (for segmented reporting)
*
* Returns the sub sequence number of the report. This is 0 for the first report of a segmented report and
* will be increased by one for each report segment.
*
* \param self the ClientReport instance
*
* \return the sub sequence number of the last received report message.
*/
uint16_t
ClientReport_getSubSeqNum(ClientReport self);
/**
* \brief get the more segments follow flag of the received report segment (for segmented reporting)
*
* Will return true in case this is part of a segmented report and more report segments will follow or false, if
* the current report is not a segmented report or is the last segment of a segmented report.
*
* \param self the ClientReport instance
*
* \return true when more segments of the current report will follow, false otherwise
*/
bool
ClientReport_getMoreSeqmentsFollow(ClientReport self);
/** /**
* \brief get the reason for inclusion of as a human readable string * \brief get the reason for inclusion of as a human readable string
* *

@ -316,10 +316,12 @@ ControlObject_create(IedServer iedServer, MmsDomain* domain, char* lnName, char*
self->iedServer = iedServer; self->iedServer = iedServer;
MmsVariableSpecification* ctlValSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "ctlVal", NULL); MmsVariableSpecification* ctlValSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "ctlVal", NULL);
self->ctlVal = MmsValue_newDefaultValue(ctlValSpec); if (ctlValSpec != NULL)
self->ctlVal = MmsValue_newDefaultValue(ctlValSpec);
MmsVariableSpecification* originSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "origin", NULL); MmsVariableSpecification* originSpec = MmsVariableSpecification_getChildSpecificationByName(operSpec, "origin", NULL);
self->origin = MmsValue_newDefaultValue(originSpec); if (originSpec != NULL)
self->origin = MmsValue_newDefaultValue(originSpec);
self->ctlNum = MmsValue_newUnsigned(8); self->ctlNum = MmsValue_newUnsigned(8);

@ -2608,7 +2608,7 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li
if (listType == MMS_DOMAIN_SPECIFIC) { if (listType == MMS_DOMAIN_SPECIFIC) {
if (rc->dataSet->logicalDeviceName != NULL) { if (rc->dataSet->logicalDeviceName != NULL) {
if (strcmp(rc->dataSet->name, listName) == 0) { if (strcmp(rc->dataSet->name, listName) == 0) {
if (strcmp(rc->dataSet->logicalDeviceName, MmsDomain_getName(domain)) == 0) { if (strcmp(rc->dataSet->logicalDeviceName, MmsDomain_getName(domain) + strlen(self->model->name)) == 0) {
allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT;
break; break;
} }
@ -2650,7 +2650,7 @@ variableListChangedHandler (void* parameter, bool create, MmsVariableListType li
if (listType == MMS_DOMAIN_SPECIFIC) { if (listType == MMS_DOMAIN_SPECIFIC) {
if (lc->dataSet->logicalDeviceName != NULL) { if (lc->dataSet->logicalDeviceName != NULL) {
if (strcmp(lc->dataSet->name, listName) == 0) { if (strcmp(lc->dataSet->name, listName) == 0) {
if (strcmp(lc->dataSet->logicalDeviceName, MmsDomain_getName(domain)) == 0) { if (strcmp(lc->dataSet->logicalDeviceName, MmsDomain_getName(domain) + strlen(self->model->name)) == 0) {
allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT; allow = MMS_ERROR_SERVICE_OBJECT_CONSTRAINT_CONFLICT;
break; break;
} }
@ -3196,7 +3196,7 @@ MmsMapping_createDataSetByNamedVariableList(MmsMapping* self, MmsNamedVariableLi
DataSet* dataSet = (DataSet*) GLOBAL_MALLOC(sizeof(DataSet)); DataSet* dataSet = (DataSet*) GLOBAL_MALLOC(sizeof(DataSet));
if (variableList->domain != NULL) if (variableList->domain != NULL)
dataSet->logicalDeviceName = MmsDomain_getName(variableList->domain); dataSet->logicalDeviceName = MmsDomain_getName(variableList->domain) + strlen(self->model->name);
else else
dataSet->logicalDeviceName = NULL; /* name is not relevant for association specific data sets */ dataSet->logicalDeviceName = NULL; /* name is not relevant for association specific data sets */

@ -475,8 +475,12 @@ sendReport(ReportControl* self, bool isIntegrity, bool isGI)
for (i = 0; i < self->dataSet->elementCount; i++) for (i = 0; i < self->dataSet->elementCount; i++)
self->inclusionFlags[i] = REPORT_CONTROL_NONE; self->inclusionFlags[i] = REPORT_CONTROL_NONE;
ReportControl_unlockNotify(self);
MmsServerConnection_sendInformationReportVMDSpecific(self->clientConnection, "RPT", reportElements, false); MmsServerConnection_sendInformationReportVMDSpecific(self->clientConnection, "RPT", reportElements, false);
ReportControl_lockNotify(self);
/* Increase sequence number */ /* Increase sequence number */
self->sqNum++; self->sqNum++;
@ -2393,8 +2397,12 @@ sendNextReportEntry(ReportControl* self)
} }
} }
ReportControl_unlockNotify(self);
MmsServerConnection_sendInformationReportVMDSpecific(self->clientConnection, "RPT", (LinkedList) reportElements, false); MmsServerConnection_sendInformationReportVMDSpecific(self->clientConnection, "RPT", (LinkedList) reportElements, false);
ReportControl_lockNotify(self);
/* Increase sequence number */ /* Increase sequence number */
self->sqNum++; self->sqNum++;
MmsValue_setUint32(sqNum, self->sqNum); MmsValue_setUint32(sqNum, self->sqNum);
@ -2455,12 +2463,13 @@ processEventsForReport(ReportControl* rc, uint64_t currentTimeInMs)
/* send current events in event buffer before GI report */ /* send current events in event buffer before GI report */
if (rc->triggered) { if (rc->triggered) {
rc->triggered = false;
if (rc->buffered) if (rc->buffered)
enqueueReport(rc, false, false, currentTimeInMs); enqueueReport(rc, false, false, currentTimeInMs);
else else
sendReport(rc, false, false); sendReport(rc, false, false);
rc->triggered = false;
} }
if (rc->buffered) if (rc->buffered)

@ -373,6 +373,23 @@ MmsConnection_getVariableListNamesAssociationSpecific(MmsConnection self, MmsErr
MmsValue* MmsValue*
MmsConnection_readVariable(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId); MmsConnection_readVariable(MmsConnection self, MmsError* mmsError, const char* domainId, const char* itemId);
/**
* \brief Read a component of a single variable from the server.
*
* \param self MmsConnection instance to operate on
* \param mmsError user provided variable to store error code
* \param domainId the domain name of the variable to be read or NULL to read a VMD specific named variable
* \param itemId name of the variable to be read
* \param componentId the component name
*
* \return Returns a MmsValue object or NULL if the request failed. The MmsValue object can
* either be a simple value or a complex value or array. It is also possible that the return value is NULL
* even if mmsError = MMS_ERROR_NON. This is the case when the servers returns an empty result list.
*/
MmsValue*
MmsConnection_readVariableComponent(MmsConnection self, MmsError* mmsError,
const char* domainId, const char* itemId, const char* componentId);
/** /**
* \brief Read one or more elements of a single array variable from the server. * \brief Read one or more elements of a single array variable from the server.
* *

@ -160,6 +160,9 @@ mmsClient_parseReadResponse(ByteBuffer* message, uint32_t* invokeId, bool create
int int
mmsClient_createReadRequest(uint32_t invokeId, const char* domainId, const char* itemId, ByteBuffer* writeBuffer); mmsClient_createReadRequest(uint32_t invokeId, const char* domainId, const char* itemId, ByteBuffer* writeBuffer);
int
mmsClient_createReadRequestComponent(uint32_t invokeId, const char* domainId, const char* itemId, const char* component, ByteBuffer* writeBuffer);
int int
mmsClient_createReadRequestAlternateAccessIndex(uint32_t invokeId, const char* domainId, const char* itemId, mmsClient_createReadRequestAlternateAccessIndex(uint32_t invokeId, const char* domainId, const char* itemId,
uint32_t index, uint32_t elementCount, ByteBuffer* writeBuffer); uint32_t index, uint32_t elementCount, ByteBuffer* writeBuffer);

@ -357,9 +357,13 @@ mmsServer_handleObtainFileRequest(
uint32_t invokeId, uint32_t invokeId,
ByteBuffer* response); ByteBuffer* response);
int
bool
mmsServer_isIndexAccess(AlternateAccess_t* alternateAccess); mmsServer_isIndexAccess(AlternateAccess_t* alternateAccess);
bool
mmsServer_isComponentAccess(AlternateAccess_t* alternateAccess);
int int
mmsServer_getLowIndex(AlternateAccess_t* alternateAccess); mmsServer_getLowIndex(AlternateAccess_t* alternateAccess);

@ -837,6 +837,7 @@ SET_OF_constraint(asn_TYPE_descriptor_t *td, const void *sptr,
return 0; return 0;
} }
#if 0
asn_dec_rval_t asn_dec_rval_t
SET_OF_decode_uper(asn_codec_ctx_t *opt_codec_ctx, asn_TYPE_descriptor_t *td, SET_OF_decode_uper(asn_codec_ctx_t *opt_codec_ctx, asn_TYPE_descriptor_t *td,
asn_per_constraints_t *constraints, void **sptr, asn_per_data_t *pd) { asn_per_constraints_t *constraints, void **sptr, asn_per_data_t *pd) {
@ -909,4 +910,4 @@ SET_OF_decode_uper(asn_codec_ctx_t *opt_codec_ctx, asn_TYPE_descriptor_t *td,
rv.consumed = 0; rv.consumed = 0;
return rv; return rv;
} }
#endif

@ -1589,6 +1589,34 @@ MmsConnection_readVariable(MmsConnection self, MmsError* mmsError,
return value; return value;
} }
MmsValue*
MmsConnection_readVariableComponent(MmsConnection self, MmsError* mmsError,
const char* domainId, const char* itemId, const char* componentId)
{
MmsValue* value = NULL;
if (getAssociationState(self) != MMS_STATE_CONNECTED) {
*mmsError = MMS_ERROR_CONNECTION_LOST;
goto exit_function;
}
ByteBuffer* payload = IsoClientConnection_allocateTransmitBuffer(self->isoClient);
uint32_t invokeId = getNextInvokeId(self);
mmsClient_createReadRequestComponent(invokeId, domainId, itemId, componentId, payload);
ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload, mmsError);
if (responseMessage != NULL)
value = mmsClient_parseReadResponse(self->lastResponse, NULL, false);
releaseResponse(self);
exit_function:
return value;
}
MmsValue* MmsValue*
MmsConnection_readArrayElements(MmsConnection self, MmsError* mmsError, MmsConnection_readArrayElements(MmsConnection self, MmsError* mmsError,
const char* domainId, const char* itemId, const char* domainId, const char* itemId,
@ -1697,9 +1725,13 @@ MmsConnection_readNamedVariableListValues(MmsConnection self, MmsError* mmsError
ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload, mmsError); ByteBuffer* responseMessage = sendRequestAndWaitForResponse(self, invokeId, payload, mmsError);
if (responseMessage != NULL) if (responseMessage != NULL) {
value = mmsClient_parseReadResponse(self->lastResponse, NULL, true); value = mmsClient_parseReadResponse(self->lastResponse, NULL, true);
if (value == NULL)
*mmsError = MMS_ERROR_PARSING_RESPONSE;
}
releaseResponse(self); releaseResponse(self);
exit_function: exit_function:

@ -138,23 +138,23 @@ mmsClient_parseGetVariableAccessAttributesResponse(ByteBuffer* message, uint32_t
asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu, asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu,
(void**) &mmsPdu, ByteBuffer_getBuffer(message), ByteBuffer_getSize(message)); (void**) &mmsPdu, ByteBuffer_getBuffer(message), ByteBuffer_getSize(message));
if (rval.code != RC_OK) if (rval.code == RC_OK) {
return NULL;
if (mmsPdu->present == MmsPdu_PR_confirmedResponsePdu) { if (mmsPdu->present == MmsPdu_PR_confirmedResponsePdu) {
if (invokeId != NULL) if (invokeId != NULL)
*invokeId = mmsClient_getInvokeId(&mmsPdu->choice.confirmedResponsePdu); *invokeId = mmsClient_getInvokeId(&mmsPdu->choice.confirmedResponsePdu);
if (mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.present == if (mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.present ==
ConfirmedServiceResponse_PR_getVariableAccessAttributes) ConfirmedServiceResponse_PR_getVariableAccessAttributes)
{ {
GetVariableAccessAttributesResponse_t* response; GetVariableAccessAttributesResponse_t* response;
response = &(mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.choice.getVariableAccessAttributes); response = &(mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.choice.getVariableAccessAttributes);
TypeSpecification_t* asnTypeSpec = &response->typeSpecification; TypeSpecification_t* asnTypeSpec = &response->typeSpecification;
typeSpec = createTypeSpecification(asnTypeSpec); typeSpec = createTypeSpecification(asnTypeSpec);
}
} }
} }

@ -46,6 +46,9 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi
int i = 0; int i = 0;
for (i = 0; i < elementCount; i++) { for (i = 0; i < elementCount; i++) {
value = NULL;
AccessResult_PR presentType = accessResultList[i]->present; AccessResult_PR presentType = accessResultList[i]->present;
if (presentType == AccessResult_PR_failure) { if (presentType == AccessResult_PR_failure) {
@ -66,72 +69,138 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi
value = MmsValue_newDataAccessError(DATA_ACCESS_ERROR_UNKNOWN); value = MmsValue_newDataAccessError(DATA_ACCESS_ERROR_UNKNOWN);
} }
else if (presentType == AccessResult_PR_array) { else if (presentType == AccessResult_PR_array) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_ARRAY;
int arrayElementCount = int arrayElementCount =
accessResultList[i]->choice.array.list.count; accessResultList[i]->choice.array.list.count;
value->value.structure.size = arrayElementCount; if (arrayElementCount > 0) {
value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(arrayElementCount, sizeof(MmsValue*)); value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_ARRAY;
int j; value->value.structure.size = arrayElementCount;
value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(arrayElementCount, sizeof(MmsValue*));
for (j = 0; j < arrayElementCount; j++) {
value->value.structure.components[j] = mmsMsg_parseDataElement( int j;
accessResultList[i]->choice.array.list.array[j]);
for (j = 0; j < arrayElementCount; j++) {
value->value.structure.components[j] = mmsMsg_parseDataElement(
accessResultList[i]->choice.array.list.array[j]);
if (value->value.structure.components[j] == NULL) {
MmsValue_delete(value);
value = NULL;
break;
}
}
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (invalid array size)!\n");
} }
} }
else if (presentType == AccessResult_PR_structure) { else if (presentType == AccessResult_PR_structure) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_STRUCTURE;
int componentCount = int componentCount =
accessResultList[i]->choice.structure.list.count; accessResultList[i]->choice.structure.list.count;
value->value.structure.size = componentCount; if (componentCount > 0) {
value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*)); value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_STRUCTURE;
int j; value->value.structure.size = componentCount;
for (j = 0; j < componentCount; j++) { value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*));
value->value.structure.components[j] = mmsMsg_parseDataElement(
accessResultList[i]->choice.structure.list.array[j]); int j;
for (j = 0; j < componentCount; j++) {
value->value.structure.components[j] = mmsMsg_parseDataElement(
accessResultList[i]->choice.structure.list.array[j]);
if (value->value.structure.components[j] == NULL) {
MmsValue_delete(value);
value = NULL;
break;
}
}
} }
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (invalid structure size)!\n");
}
} }
else if (presentType == AccessResult_PR_bitstring) { else if (presentType == AccessResult_PR_bitstring) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_BIT_STRING;
int size = accessResultList[i]->choice.bitstring.size; int size = accessResultList[i]->choice.bitstring.size;
value->value.bitString.size = (size * 8) if (size > 0) {
- accessResultList[i]->choice.bitstring.bits_unused;
int maxSize = (size * 8);
int bitSize = maxSize - accessResultList[i]->choice.bitstring.bits_unused;
if ((bitSize > 0) && (maxSize >= bitSize)) {
value->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size); value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
memcpy(value->value.bitString.buf, value->type = MMS_BIT_STRING;
accessResultList[i]->choice.bitstring.buf, size);
value->value.bitString.size = (size * 8)
- accessResultList[i]->choice.bitstring.bits_unused;
value->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size);
memcpy(value->value.bitString.buf,
accessResultList[i]->choice.bitstring.buf, size);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (bit string padding problem)!\n");
}
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (bit string size 0 or negative)!\n");
}
} }
else if (presentType == AccessResult_PR_integer) { else if (presentType == AccessResult_PR_integer) {
Asn1PrimitiveValue* berInteger =
BerInteger_createFromBuffer(accessResultList[i]->choice.integer.buf,
accessResultList[i]->choice.integer.size);
value = MmsValue_newIntegerFromBerInteger(berInteger); int size = accessResultList[i]->choice.integer.size;
if (size > 0) {
Asn1PrimitiveValue* berInteger =
BerInteger_createFromBuffer(accessResultList[i]->choice.integer.buf, size);
value = MmsValue_newIntegerFromBerInteger(berInteger);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (invalid integer size)!\n");
}
} }
else if (presentType == AccessResult_PR_unsigned) { else if (presentType == AccessResult_PR_unsigned) {
Asn1PrimitiveValue* berInteger =
BerInteger_createFromBuffer(accessResultList[i]->choice.Unsigned.buf,
accessResultList[i]->choice.Unsigned.size);
value = MmsValue_newUnsignedFromBerInteger(berInteger); int size = accessResultList[i]->choice.Unsigned.size;
if (size > 0) {
Asn1PrimitiveValue* berInteger =
BerInteger_createFromBuffer(accessResultList[i]->choice.Unsigned.buf,
accessResultList[i]->choice.Unsigned.size);
value = MmsValue_newUnsignedFromBerInteger(berInteger);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (invalid unsigned size)!\n");
}
} }
else if (presentType == AccessResult_PR_floatingpoint) { else if (presentType == AccessResult_PR_floatingpoint) {
int size = accessResultList[i]->choice.floatingpoint.size;
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); int size = accessResultList[i]->choice.floatingpoint.size;
value->type = MMS_FLOAT;
if (size == 5) { /* FLOAT32 */ if (size == 5) { /* FLOAT32 */
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_FLOAT;
value->value.floatingPoint.formatWidth = 32; value->value.floatingPoint.formatWidth = 32;
value->value.floatingPoint.exponentWidth = accessResultList[i]->choice.floatingpoint.buf[0]; value->value.floatingPoint.exponentWidth = accessResultList[i]->choice.floatingpoint.buf[0];
@ -146,8 +215,11 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi
#endif #endif
} }
else if (size == 9) { /* FLOAT64 */
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_FLOAT;
if (size == 9) { /* FLOAT64 */
value->value.floatingPoint.formatWidth = 64; value->value.floatingPoint.formatWidth = 64;
value->value.floatingPoint.exponentWidth = accessResultList[i]->choice.floatingpoint.buf[0]; value->value.floatingPoint.exponentWidth = accessResultList[i]->choice.floatingpoint.buf[0];
@ -161,46 +233,70 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi
memcpy(value->value.floatingPoint.buf, floatBuf, 8); memcpy(value->value.floatingPoint.buf, floatBuf, 8);
#endif #endif
} }
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing float (size must be 5 or 9, is %i)\n", size);
}
} }
else if (presentType == AccessResult_PR_visiblestring) { else if (presentType == AccessResult_PR_visiblestring) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_VISIBLE_STRING;
int strSize = accessResultList[i]->choice.visiblestring.size; int strSize = accessResultList[i]->choice.visiblestring.size;
value->value.visibleString.buf = (char*) GLOBAL_MALLOC(strSize + 1); if (strSize >= 0) {
value->value.visibleString.size = strSize; value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_VISIBLE_STRING;
value->value.visibleString.buf = (char*) GLOBAL_MALLOC(strSize + 1);
value->value.visibleString.size = strSize;
memcpy(value->value.visibleString.buf, memcpy(value->value.visibleString.buf,
accessResultList[i]->choice.visiblestring.buf, accessResultList[i]->choice.visiblestring.buf,
strSize); strSize);
value->value.visibleString.buf[strSize] = 0;
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (invalid visible-string size)\n");
}
value->value.visibleString.buf[strSize] = 0;
} }
else if (presentType == AccessResult_PR_mMSString) { else if (presentType == AccessResult_PR_mMSString) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_STRING;
int strSize = accessResultList[i]->choice.mMSString.size; int strSize = accessResultList[i]->choice.mMSString.size;
value->value.visibleString.buf = (char*) GLOBAL_MALLOC(strSize + 1); if (strSize >= 0) {
value->value.visibleString.size = strSize; value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
memcpy(value->value.visibleString.buf, value->type = MMS_STRING;
accessResultList[i]->choice.mMSString.buf, strSize); value->value.visibleString.buf = (char*) GLOBAL_MALLOC(strSize + 1);
value->value.visibleString.size = strSize;
value->value.visibleString.buf[strSize] = 0; memcpy(value->value.visibleString.buf,
accessResultList[i]->choice.mMSString.buf, strSize);
value->value.visibleString.buf[strSize] = 0;
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (invalid mms-string size)\n");
}
} }
else if (presentType == AccessResult_PR_utctime) { else if (presentType == AccessResult_PR_utctime) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_UTC_TIME; int size = accessResultList[i]->choice.utctime.size;
memcpy(value->value.utcTime,
accessResultList[i]->choice.utctime.buf, 8); if (size == 8) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_UTC_TIME;
memcpy(value->value.utcTime, accessResultList[i]->choice.utctime.buf, 8);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing UTC time (size is %i instead of 8\n", size);
}
} }
else if (presentType == AccessResult_PR_boolean) { else if (presentType == AccessResult_PR_boolean) {
value = MmsValue_newBoolean(accessResultList[i]->choice.boolean); value = MmsValue_newBoolean(accessResultList[i]->choice.boolean);
@ -208,25 +304,36 @@ mmsClient_parseListOfAccessResults(AccessResult_t** accessResultList, int listSi
else if (presentType == AccessResult_PR_binarytime) { else if (presentType == AccessResult_PR_binarytime) {
int size = accessResultList[i]->choice.binarytime.size; int size = accessResultList[i]->choice.binarytime.size;
if (size <= 6) { if ((size == 4) || (size == 6)) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_BINARY_TIME; value->type = MMS_BINARY_TIME;
value->value.binaryTime.size = size; value->value.binaryTime.size = size;
memcpy(value->value.binaryTime.buf, accessResultList[i]->choice.binarytime.buf, size); memcpy(value->value.binaryTime.buf, accessResultList[i]->choice.binarytime.buf, size);
} }
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing binary time (size must be 4 or 6, is %i\n", size);
}
} }
else if (presentType == AccessResult_PR_octetstring) { else if (presentType == AccessResult_PR_octetstring) {
int size = accessResultList[i]->choice.octetstring.size; int size = accessResultList[i]->choice.octetstring.size;
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); if (size >= 0) {
value->type = MMS_OCTET_STRING; value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->value.octetString.maxSize = size; value->type = MMS_OCTET_STRING;
value->value.octetString.size = size; value->value.octetString.maxSize = size;
value->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size); value->value.octetString.size = size;
memcpy(value->value.octetString.buf, accessResultList[i]->choice.octetstring.buf, size); value->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size);
memcpy(value->value.octetString.buf, accessResultList[i]->choice.octetstring.buf, size);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing access result (invalid octet-string size)\n");
}
} }
else { else {
printf("unknown type %i\n", presentType); if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: unknown type %i in access result\n", presentType);
value = MmsValue_newDataAccessError(DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID); value = MmsValue_newDataAccessError(DATA_ACCESS_ERROR_OBJECT_VALUE_INVALID);
} }
@ -254,21 +361,20 @@ mmsClient_parseReadResponse(ByteBuffer* message, uint32_t* invokeId, bool create
asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu, asn_dec_rval_t rval = ber_decode(NULL, &asn_DEF_MmsPdu,
(void**) &mmsPdu, ByteBuffer_getBuffer(message), ByteBuffer_getSize(message)); (void**) &mmsPdu, ByteBuffer_getBuffer(message), ByteBuffer_getSize(message));
if (rval.code != RC_OK) if (rval.code == RC_OK) {
return NULL; if (mmsPdu->present == MmsPdu_PR_confirmedResponsePdu) {
if (mmsPdu->present == MmsPdu_PR_confirmedResponsePdu) {
if (invokeId != NULL) if (invokeId != NULL)
*invokeId = mmsClient_getInvokeId(&mmsPdu->choice.confirmedResponsePdu); *invokeId = mmsClient_getInvokeId(&mmsPdu->choice.confirmedResponsePdu);
if (mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.present == ConfirmedServiceResponse_PR_read) { if (mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.present == ConfirmedServiceResponse_PR_read) {
ReadResponse_t* response = &(mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.choice.read); ReadResponse_t* response = &(mmsPdu->choice.confirmedResponsePdu.confirmedServiceResponse.choice.read);
int elementCount = response->listOfAccessResult.list.count; int elementCount = response->listOfAccessResult.list.count;
valueList = mmsClient_parseListOfAccessResults(response->listOfAccessResult.list.array, valueList = mmsClient_parseListOfAccessResults(response->listOfAccessResult.list.array,
elementCount, createArray); elementCount, createArray);
}
} }
} }
@ -422,7 +528,7 @@ mmsClient_createReadRequest(uint32_t invokeId, const char* domainId, const char*
} }
static AlternateAccess_t* static AlternateAccess_t*
createAlternateAccess(uint32_t index, uint32_t elementCount) createAlternateAccessComponent(const char* componentName)
{ {
AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t)); AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t));
alternateAccess->list.count = 1; alternateAccess->list.count = 1;
@ -432,37 +538,114 @@ createAlternateAccess(uint32_t index, uint32_t elementCount)
alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t));
alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess; const char* separator = strchr(componentName, '$');
if (elementCount > 0) {
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present =
AlternateAccessSelection__selectAccess_PR_indexRange;
INTEGER_t* asnIndex = if (separator) {
&(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.lowIndex); int size = separator - componentName;
asn_long2INTEGER(asnIndex, index); alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAlternateAccess;
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.present =
AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_component;
asnIndex = alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.buf =
&(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.numberOfElements); (uint8_t*) StringUtils_copySubString((char*) componentName, (char*) separator);
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.size = size;
asn_long2INTEGER(asnIndex, elementCount); alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess = createAlternateAccessComponent(separator + 1);
} }
else { else {
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present = int size = strlen(componentName);
AlternateAccessSelection__selectAccess_PR_index;
INTEGER_t* asnIndex = alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess;
&(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.index);
asn_long2INTEGER(asnIndex, index); alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present =
AlternateAccessSelection__selectAccess_PR_component;
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.buf =
(uint8_t*) StringUtils_copyString(componentName);
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.size = size;
} }
return alternateAccess; return alternateAccess;
} }
static void
deleteAlternateAccessComponent(AlternateAccess_t* alternateAccess)
{
GLOBAL_FREEMEM(alternateAccess->list.array[0]->choice.unnamed);
GLOBAL_FREEMEM(alternateAccess->list.array[0]);
GLOBAL_FREEMEM(alternateAccess->list.array);
GLOBAL_FREEMEM(alternateAccess);
}
static ListOfVariableSeq_t*
createNewVariableSpecification(const char* domainId, const char* itemId, const char* componentName, bool associationSpecific)
{
ListOfVariableSeq_t* varSpec = (ListOfVariableSeq_t*) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t));
varSpec->variableSpecification.present = VariableSpecification_PR_name;
if (domainId) {
varSpec->variableSpecification.choice.name.present = ObjectName_PR_domainspecific;
varSpec->variableSpecification.choice.name.choice.domainspecific.domainId.buf = (uint8_t*) domainId;
varSpec->variableSpecification.choice.name.choice.domainspecific.domainId.size = strlen(domainId);
varSpec->variableSpecification.choice.name.choice.domainspecific.itemId.buf = (uint8_t*) itemId;
varSpec->variableSpecification.choice.name.choice.domainspecific.itemId.size = strlen(itemId);
}
else if (associationSpecific) {
varSpec->variableSpecification.choice.name.present = ObjectName_PR_aaspecific;
varSpec->variableSpecification.choice.name.choice.aaspecific.buf = (uint8_t*) itemId;
varSpec->variableSpecification.choice.name.choice.aaspecific.size = strlen(itemId);
}
else {
varSpec->variableSpecification.choice.name.present = ObjectName_PR_vmdspecific;
varSpec->variableSpecification.choice.name.choice.vmdspecific.buf = (uint8_t*) itemId;
varSpec->variableSpecification.choice.name.choice.vmdspecific.size = strlen(itemId);
}
if (componentName)
varSpec->alternateAccess = createAlternateAccessComponent(componentName);
return varSpec;
}
/**
* Request a single value with optional component
*/
int
mmsClient_createReadRequestComponent(uint32_t invokeId, const char* domainId, const char* itemId, const char* component, ByteBuffer* writeBuffer)
{
MmsPdu_t* mmsPdu = mmsClient_createConfirmedRequestPdu(invokeId);
ReadRequest_t* readRequest = createReadRequest(mmsPdu);
readRequest->specificationWithResult = NULL;
readRequest->variableAccessSpecification.present = VariableAccessSpecification_PR_listOfVariable;
readRequest->variableAccessSpecification.choice.listOfVariable.list.count = 1;
readRequest->variableAccessSpecification.choice.listOfVariable.list.size = 1;
readRequest->variableAccessSpecification.choice.listOfVariable.list.array =
(ListOfVariableSeq_t**) GLOBAL_CALLOC(1, sizeof(ListOfVariableSeq_t*));
readRequest->variableAccessSpecification.choice.listOfVariable.list.array[0] = createNewVariableSpecification(domainId, itemId, component, false);
asn_enc_rval_t rval;
rval = der_encode(&asn_DEF_MmsPdu, mmsPdu,
(asn_app_consume_bytes_f*) mmsClient_write_out, (void*) writeBuffer);
/* clean up data structures */
deleteAlternateAccessComponent(readRequest->variableAccessSpecification.choice.listOfVariable.list.array[0]->alternateAccess);
GLOBAL_FREEMEM(readRequest->variableAccessSpecification.choice.listOfVariable.list.array);
readRequest->variableAccessSpecification.choice.listOfVariable.list.array = NULL;
readRequest->variableAccessSpecification.choice.listOfVariable.list.count = 0;
asn_DEF_MmsPdu.free_struct(&asn_DEF_MmsPdu, mmsPdu, 0);
return rval.encoded;
}
static AlternateAccess_t* static AlternateAccess_t*
createAlternateAccessComponent(const char* componentName) createAlternateAccess(uint32_t index, uint32_t elementCount)
{ {
AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t)); AlternateAccess_t* alternateAccess = (AlternateAccess_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccess_t));
alternateAccess->list.count = 1; alternateAccess->list.count = 1;
@ -472,32 +655,30 @@ createAlternateAccessComponent(const char* componentName)
alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t)); alternateAccess->list.array[0]->choice.unnamed = (AlternateAccessSelection_t*) GLOBAL_CALLOC(1, sizeof(AlternateAccessSelection_t));
const char* separator = strchr(componentName, '$'); alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess;
if (separator) { if (elementCount > 0) {
int size = separator - componentName; alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present =
AlternateAccessSelection__selectAccess_PR_indexRange;
alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAlternateAccess; INTEGER_t* asnIndex =
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.present = &(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.lowIndex);
AlternateAccessSelection__selectAlternateAccess__accessSelection_PR_component;
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.buf = asn_long2INTEGER(asnIndex, index);
(uint8_t*) StringUtils_copySubString((char*) componentName, (char*) separator);
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.accessSelection.choice.component.size = size;
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess = createAlternateAccessComponent(separator + 1); asnIndex =
&(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.indexRange.numberOfElements);
asn_long2INTEGER(asnIndex, elementCount);
} }
else { else {
int size = strlen(componentName);
alternateAccess->list.array[0]->choice.unnamed->present = AlternateAccessSelection_PR_selectAccess;
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present = alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present =
AlternateAccessSelection__selectAccess_PR_component; AlternateAccessSelection__selectAccess_PR_index;
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.buf = INTEGER_t* asnIndex =
(uint8_t*) StringUtils_copyString(componentName); &(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.index);
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component.size = size;
asn_long2INTEGER(asnIndex, index);
} }
return alternateAccess; return alternateAccess;

@ -185,80 +185,175 @@ mmsMsg_parseDataElement(Data_t* dataElement)
{ {
MmsValue* value = NULL; MmsValue* value = NULL;
if (dataElement->present == Data_PR_structure) { if (dataElement->present == Data_PR_array) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
int componentCount = dataElement->choice.array->list.count;
if (componentCount > 0) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_ARRAY;
value->value.structure.size = componentCount;
value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*));
int i;
for (i = 0; i < componentCount; i++) {
value->value.structure.components[i] =
mmsMsg_parseDataElement(dataElement->choice.array->list.array[i]);
if (value->value.structure.components[i] == NULL) {
MmsValue_delete(value);
value = NULL;
break;
}
}
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing data element (invalid array size)!\n");
}
}
else if (dataElement->present == Data_PR_structure) {
int componentCount = dataElement->choice.structure->list.count; int componentCount = dataElement->choice.structure->list.count;
value->type = MMS_STRUCTURE; if (componentCount > 0) {
value->value.structure.size = componentCount; value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*));
value->type = MMS_STRUCTURE;
value->value.structure.size = componentCount;
value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*));
int i;
int i; for (i = 0; i < componentCount; i++) {
value->value.structure.components[i] =
mmsMsg_parseDataElement(dataElement->choice.structure->list.array[i]);
for (i = 0; i < componentCount; i++) { if (value->value.structure.components[i] == NULL) {
value->value.structure.components[i] = MmsValue_delete(value);
mmsMsg_parseDataElement(dataElement->choice.structure->list.array[i]); value = NULL;
break;
}
}
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing data element (invalid structure size)!\n");
} }
} }
else if (dataElement->present == Data_PR_array) { else if (dataElement->present == Data_PR_array) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
int componentCount = dataElement->choice.array->list.count; int componentCount = dataElement->choice.array->list.count;
value->type = MMS_ARRAY; if (componentCount > 0) {
value->value.structure.size = componentCount; value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*));
int i; value->type = MMS_ARRAY;
value->value.structure.size = componentCount;
value->value.structure.components = (MmsValue**) GLOBAL_CALLOC(componentCount, sizeof(MmsValue*));
for (i = 0; i < componentCount; i++) { int i;
value->value.structure.components[i] =
mmsMsg_parseDataElement(dataElement->choice.array->list.array[i]); for (i = 0; i < componentCount; i++) {
value->value.structure.components[i] =
mmsMsg_parseDataElement(dataElement->choice.array->list.array[i]);
if (value->value.structure.components[i] == NULL) {
MmsValue_delete(value);
value = NULL;
break;
}
}
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing data element (invalid array size)!\n");
} }
} }
else { else {
if (dataElement->present == Data_PR_integer) { if (dataElement->present == Data_PR_integer) {
Asn1PrimitiveValue* berInteger = BerInteger_createFromBuffer(
dataElement->choice.integer.buf, dataElement->choice.integer.size);
value = MmsValue_newIntegerFromBerInteger(berInteger); if (dataElement->choice.integer.size > 0) {
Asn1PrimitiveValue* berInteger = BerInteger_createFromBuffer(
dataElement->choice.integer.buf, dataElement->choice.integer.size);
value = MmsValue_newIntegerFromBerInteger(berInteger);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing data element (invalid integer size)!\n");
}
} }
else if (dataElement->present == Data_PR_unsigned) { else if (dataElement->present == Data_PR_unsigned) {
Asn1PrimitiveValue* berInteger = BerInteger_createFromBuffer(
dataElement->choice.Unsigned.buf, dataElement->choice.Unsigned.size);
value = MmsValue_newUnsignedFromBerInteger(berInteger); if (dataElement->choice.Unsigned.size > 0) {
Asn1PrimitiveValue* berInteger = BerInteger_createFromBuffer(
dataElement->choice.Unsigned.buf, dataElement->choice.Unsigned.size);
value = MmsValue_newUnsignedFromBerInteger(berInteger);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing data element (invalid unsigned size)!\n");
}
} }
else if (dataElement->present == Data_PR_visiblestring) { else if (dataElement->present == Data_PR_visiblestring) {
value = MmsValue_newVisibleStringFromByteArray(dataElement->choice.visiblestring.buf,
dataElement->choice.visiblestring.size); if (dataElement->choice.visiblestring.size >= 0) {
value = MmsValue_newVisibleStringFromByteArray(dataElement->choice.visiblestring.buf,
dataElement->choice.visiblestring.size);
}
} }
else if (dataElement->present == Data_PR_mMSString) { else if (dataElement->present == Data_PR_mMSString) {
value = MmsValue_newMmsStringFromByteArray(dataElement->choice.mMSString.buf,
dataElement->choice.mMSString.size); if ( dataElement->choice.mMSString.size >= 0) {
value = MmsValue_newMmsStringFromByteArray(dataElement->choice.mMSString.buf,
dataElement->choice.mMSString.size);
}
} }
else if (dataElement->present == Data_PR_bitstring) { else if (dataElement->present == Data_PR_bitstring) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_BIT_STRING;
int size = dataElement->choice.bitstring.size; int size = dataElement->choice.bitstring.size;
value->value.bitString.size = (size * 8) if (size > 0) {
- dataElement->choice.bitstring.bits_unused;
int maxSize = (size * 8);
int bitSize = maxSize - dataElement->choice.bitstring.bits_unused;
value->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size); if ((bitSize > 0) && (maxSize >= bitSize)) {
memcpy(value->value.bitString.buf, value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
dataElement->choice.bitstring.buf, size);
value->type = MMS_BIT_STRING;
value->value.bitString.size = bitSize;
value->value.bitString.buf = (uint8_t*) GLOBAL_MALLOC(size);
memcpy(value->value.bitString.buf,
dataElement->choice.bitstring.buf, size);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing data element (bit string padding problem)!\n");
}
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing data element (bit string size 0 or negative)!\n");
}
} }
else if (dataElement->present == Data_PR_floatingpoint) { else if (dataElement->present == Data_PR_floatingpoint) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
int size = dataElement->choice.floatingpoint.size;
value->type = MMS_FLOAT; int size = dataElement->choice.floatingpoint.size;
if (size == 5) { /* FLOAT32 */ if (size == 5) { /* FLOAT32 */
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_FLOAT;
value->value.floatingPoint.formatWidth = 32; value->value.floatingPoint.formatWidth = 32;
value->value.floatingPoint.exponentWidth = dataElement->choice.floatingpoint.buf[0]; value->value.floatingPoint.exponentWidth = dataElement->choice.floatingpoint.buf[0];
@ -273,6 +368,10 @@ mmsMsg_parseDataElement(Data_t* dataElement)
} }
if (size == 9) { /* FLOAT64 */ if (size == 9) { /* FLOAT64 */
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_FLOAT;
value->value.floatingPoint.formatWidth = 64; value->value.floatingPoint.formatWidth = 64;
value->value.floatingPoint.exponentWidth = dataElement->choice.floatingpoint.buf[0]; value->value.floatingPoint.exponentWidth = dataElement->choice.floatingpoint.buf[0];
@ -287,33 +386,58 @@ mmsMsg_parseDataElement(Data_t* dataElement)
} }
} }
else if (dataElement->present == Data_PR_utctime) { else if (dataElement->present == Data_PR_utctime) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_UTC_TIME; int size = dataElement->choice.utctime.size;
memcpy(value->value.utcTime, dataElement->choice.utctime.buf, 8);
if (size == 8) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_UTC_TIME;
memcpy(value->value.utcTime, dataElement->choice.utctime.buf, 8);
}
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing UTC time (size is %i instead of 8\n", size);
}
} }
else if (dataElement->present == Data_PR_octetstring) { else if (dataElement->present == Data_PR_octetstring) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_OCTET_STRING; if (dataElement->choice.octetstring.size >= 0) {
int size = dataElement->choice.octetstring.size; value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->value.octetString.size = size; value->type = MMS_OCTET_STRING;
value->value.octetString.maxSize = size; int size = dataElement->choice.octetstring.size;
value->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size); value->value.octetString.size = size;
memcpy(value->value.octetString.buf, dataElement->choice.octetstring.buf, size); value->value.octetString.maxSize = size;
value->value.octetString.buf = (uint8_t*) GLOBAL_MALLOC(size);
memcpy(value->value.octetString.buf, dataElement->choice.octetstring.buf, size);
}
} }
else if (dataElement->present == Data_PR_binarytime) { else if (dataElement->present == Data_PR_binarytime) {
int size = dataElement->choice.binarytime.size; int size = dataElement->choice.binarytime.size;
if (size <= 6) { if ((size == 4) || (size == 6)) {
value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); value = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
value->type = MMS_BINARY_TIME; value->type = MMS_BINARY_TIME;
value->value.binaryTime.size = size; value->value.binaryTime.size = size;
memcpy(value->value.binaryTime.buf, dataElement->choice.binarytime.buf, size); memcpy(value->value.binaryTime.buf, dataElement->choice.binarytime.buf, size);
} }
else {
if (DEBUG_MMS_CLIENT)
printf("MMS CLIENT: error parsing binary time (size must be 4 or 6, is %i\n", size);
}
} }
else if (dataElement->present == Data_PR_boolean) { else if (dataElement->present == Data_PR_boolean) {
value = MmsValue_newBoolean(dataElement->choice.boolean); value = MmsValue_newBoolean(dataElement->choice.boolean);
} }
else if (dataElement->present == Data_PR_booleanArray) {
}
}
if (DEBUG_MMS_CLIENT) {
if (value == NULL)
printf("MMS CLIENT: error parsing data element\n");
} }
return value; return value;
@ -350,7 +474,6 @@ void
mmsMsg_createExtendedFilename(const char* basepath, char* extendedFileName, char* fileName) mmsMsg_createExtendedFilename(const char* basepath, char* extendedFileName, char* fileName)
{ {
#if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1) #if (CONFIG_SET_FILESTORE_BASEPATH_AT_RUNTIME == 1)
// strncpy(extendedFileName, MmsServerConnection_getFilesystemBasepath(self), 512);
strncpy(extendedFileName, basepath, 512); strncpy(extendedFileName, basepath, 512);
strncat(extendedFileName, fileName, 512); strncat(extendedFileName, fileName, 512);
#else #else

@ -1081,10 +1081,12 @@ MmsValue_cloneToBuffer(const MmsValue* self, uint8_t* destinationAddress)
return destinationAddress; return destinationAddress;
} }
// create a deep clone
MmsValue* MmsValue*
MmsValue_clone(const MmsValue* self) MmsValue_clone(const MmsValue* self)
{ {
if (self == NULL)
return NULL;
MmsValue* newValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue)); MmsValue* newValue = (MmsValue*) GLOBAL_CALLOC(1, sizeof(MmsValue));
if (newValue == NULL) if (newValue == NULL)
@ -1188,6 +1190,9 @@ MmsValue_deleteIfNotNull(MmsValue* self)
void void
MmsValue_delete(MmsValue* self) MmsValue_delete(MmsValue* self)
{ {
if (self == NULL)
return;
switch (self->type) switch (self->type)
{ {
case MMS_INTEGER: case MMS_INTEGER:
@ -2010,6 +2015,16 @@ MmsValue_getTypeString(MmsValue* self)
const char* const char*
MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize) MmsValue_printToBuffer(const MmsValue* self, char* buffer, int bufferSize)
{ {
if (self == NULL) {
strncpy(buffer, "(null)", bufferSize);
/* Ensure buffer is always 0 terminated */
if (bufferSize > 0)
buffer[bufferSize - 1] = 0;
return buffer;
}
switch (MmsValue_getType(self)) switch (MmsValue_getType(self))
{ {
case MMS_STRUCTURE: case MMS_STRUCTURE:

@ -155,6 +155,57 @@ isAccessToArrayComponent(AlternateAccess_t* alternateAccess)
return false; return false;
} }
static MmsValue*
getComponent(MmsServerConnection connection, MmsDomain* domain, AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable, char* variableName)
{
MmsValue* retValue = NULL;
if (mmsServer_isComponentAccess(alternateAccess)) {
Identifier_t component =
alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.choice.component;
if (component.size > 129)
goto exit_function;
if (namedVariable->type == MMS_STRUCTURE) {
int i;
for (i = 0; i < namedVariable->typeSpec.structure.elementCount; i++) {
if ((int) strlen(namedVariable->typeSpec.structure.elements[i]->name)
== component.size) {
if (!strncmp(namedVariable->typeSpec.structure.elements[i]->name,
(char*) component.buf, component.size))
{
if (strlen(variableName) + component.size < 199) {
strcat(variableName, "$");
strncat(variableName, (const char*) component.buf, component.size);
if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess
!= NULL) {
retValue =
getComponent(connection, domain,
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess,
namedVariable->typeSpec.structure.elements[i],
variableName);
}
else {
retValue = mmsServer_getValue(connection->server, domain, variableName, connection);
}
}
}
}
}
}
}
exit_function:
return retValue;
}
static MmsValue* static MmsValue*
getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable, getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVariableSpecification* namedVariable,
MmsValue* structuredValue) MmsValue* structuredValue)
@ -180,19 +231,26 @@ getComponentOfArrayElement(AlternateAccess_t* alternateAccess, MmsVariableSpecif
int i; int i;
for (i = 0; i < structSpec->typeSpec.structure.elementCount; i++) { for (i = 0; i < structSpec->typeSpec.structure.elementCount; i++) {
if (strncmp (structSpec->typeSpec.structure.elements[i]->name, (char*) component.buf,
component.size) == 0)
{
MmsValue* value = MmsValue_getElement(structuredValue, i);
if (isAccessToArrayComponent(alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess)) {
retValue = getComponentOfArrayElement(alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess,
structSpec->typeSpec.structure.elements[i], value);
}
else
retValue = value;
goto exit_function; if ((int) strlen(structSpec->typeSpec.structure.elements[i]->name)
== component.size) {
if (strncmp(structSpec->typeSpec.structure.elements[i]->name,
(char*) component.buf, component.size) == 0) {
MmsValue* value = MmsValue_getElement(structuredValue, i);
if (isAccessToArrayComponent(
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess)) {
retValue =
getComponentOfArrayElement(
alternateAccess->list.array[0]->choice.unnamed->choice.selectAlternateAccess.alternateAccess,
structSpec->typeSpec.structure.elements[i],
value);
}
else
retValue = value;
goto exit_function;
}
} }
} }
} }
@ -288,12 +346,29 @@ addNamedVariableToResultList(MmsVariableSpecification* namedVariable, MmsDomain*
MmsValue* value = mmsServer_getValue(connection->server, domain, nameIdStr, connection); MmsValue* value = mmsServer_getValue(connection->server, domain, nameIdStr, connection);
if (value != NULL) { if (alternateAccess != NULL) {
appendValueToResultList(value, values);
char variableName[200];
variableName[0] = 0;
strcat(variableName, nameIdStr);
value = getComponent(connection, domain, alternateAccess, namedVariable, variableName);
if (value != NULL) {
appendValueToResultList(value, values);
}
else {
appendErrorToResultList(values, DATA_ACCESS_ERROR_OBJECT_NONE_EXISTENT);
}
} }
else { else {
addComplexValueToResultList(namedVariable, if (value != NULL) {
values, connection, domain, nameIdStr); appendValueToResultList(value, values);
}
else {
addComplexValueToResultList(namedVariable,
values, connection, domain, nameIdStr);
}
} }
} }
else if (namedVariable->type == MMS_ARRAY) { else if (namedVariable->type == MMS_ARRAY) {

@ -208,7 +208,7 @@ mmsMsg_createServiceErrorPdu(uint32_t invokeId, ByteBuffer* response, MmsError e
mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, NULL, 0); mmsServer_createServiceErrorPduWithServiceSpecificInfo(invokeId, response, errorType, NULL, 0);
} }
int bool
mmsServer_isIndexAccess(AlternateAccess_t* alternateAccess) mmsServer_isIndexAccess(AlternateAccess_t* alternateAccess)
{ {
if (alternateAccess->list.array[0]->present == AlternateAccess__Member_PR_unnamed) { if (alternateAccess->list.array[0]->present == AlternateAccess__Member_PR_unnamed) {
@ -217,13 +217,25 @@ mmsServer_isIndexAccess(AlternateAccess_t* alternateAccess)
(alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present (alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present
== AlternateAccessSelection__selectAccess_PR_indexRange)) == AlternateAccessSelection__selectAccess_PR_indexRange))
{ {
return 1; return true;
} }
else
return 0;
} }
else
return 0; return false;
}
bool
mmsServer_isComponentAccess(AlternateAccess_t* alternateAccess)
{
if (alternateAccess->list.array[0]->present
== AlternateAccess__Member_PR_unnamed) {
if (alternateAccess->list.array[0]->choice.unnamed->choice.selectAccess.present
== AlternateAccessSelection__selectAccess_PR_component) {
return true;
}
}
return false;
} }
int int

@ -63,6 +63,8 @@ struct sSVPublisher_ASDU {
uint16_t smpRate; uint16_t smpRate;
uint8_t* smpCntBuf; uint8_t* smpCntBuf;
uint8_t* refrTmBuf;
uint8_t* smpSynchBuf;
SVPublisher_ASDU _next; SVPublisher_ASDU _next;
}; };
@ -386,11 +388,13 @@ SVPublisher_ASDU_encodeToBuffer(SVPublisher_ASDU self, uint8_t* buffer, int bufP
/* RefrTm */ /* RefrTm */
if (self->hasRefrTm) { if (self->hasRefrTm) {
bufPos = BerEncoder_encodeTL(0x84, 8, buffer, bufPos); bufPos = BerEncoder_encodeTL(0x84, 8, buffer, bufPos);
self->refrTmBuf = buffer + bufPos;
bufPos = encodeUtcTime(self->refrTm, buffer, bufPos); bufPos = encodeUtcTime(self->refrTm, buffer, bufPos);
} }
/* SmpSynch */ /* SmpSynch */
bufPos = BerEncoder_encodeTL(0x85, 1, buffer, bufPos); bufPos = BerEncoder_encodeTL(0x85, 1, buffer, bufPos);
self->smpSynchBuf = buffer + bufPos;
buffer[bufPos++] = self->smpSynch; buffer[bufPos++] = self->smpSynch;
/* SmpRate */ /* SmpRate */
@ -651,7 +655,8 @@ SVPublisher_ASDU_setSmpCnt(SVPublisher_ASDU self, uint16_t value)
{ {
self->smpCnt = value; self->smpCnt = value;
encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0); if (self->smpCntBuf != NULL)
encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0);
} }
void void
@ -665,7 +670,14 @@ SVPublisher_ASDU_increaseSmpCnt(SVPublisher_ASDU self)
{ {
self->smpCnt = ((self->smpCnt + 1) % self->smpCntLimit); self->smpCnt = ((self->smpCnt + 1) % self->smpCntLimit);
encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0); if (self->smpCntBuf != NULL)
encodeUInt16FixedSize(self->smpCnt, self->smpCntBuf, 0);
}
void
SVPublisher_ASDU_enableRefrTm(SVPublisher_ASDU self)
{
self->hasRefrTm = true;
} }
void void
@ -673,6 +685,9 @@ SVPublisher_ASDU_setRefrTm(SVPublisher_ASDU self, uint64_t refrTm)
{ {
self->hasRefrTm = true; self->hasRefrTm = true;
self->refrTm = refrTm; self->refrTm = refrTm;
if (self->refrTmBuf != NULL)
encodeUtcTime(self->refrTm, self->refrTmBuf, 0);
} }
void void
@ -689,6 +704,12 @@ SVPublisher_ASDU_setSmpRate(SVPublisher_ASDU self, uint16_t smpRate)
self->smpRate = smpRate; self->smpRate = smpRate;
} }
void
SVPublisher_ASDU_setSmpSynch(SVPublisher_ASDU self, uint16_t smpSynch)
{
self->smpSynch = smpSynch;
*(self->smpSynchBuf) = self->smpSynch;
}
/******************************************************************* /*******************************************************************
* Wrapper functions to support old API (remove in future versions) * Wrapper functions to support old API (remove in future versions)

@ -1,7 +1,7 @@
/* /*
* sv_publisher.h * sv_publisher.h
* *
* Copyright 2016 Michael Zillgith * Copyright 2016-2018 Michael Zillgith
* *
* This file is part of libIEC61850. * This file is part of libIEC61850.
* *
@ -304,6 +304,16 @@ SVPublisher_ASDU_increaseSmpCnt(SVPublisher_ASDU self);
void void
SVPublisher_ASDU_setSmpCntWrap(SVPublisher_ASDU self, uint16_t value); SVPublisher_ASDU_setSmpCntWrap(SVPublisher_ASDU self, uint16_t value);
/**
* \brief Enables the transmission of refresh time attribute of the ASDU
*
* The refresh time is the time when the data in put into the sampled values buffer
*
* \param[in] self the Sampled Values ASDU instance.
*/
void
SVPublisher_ASDU_enableRefrTm(SVPublisher_ASDU self);
/** /**
* \brief Set the refresh time attribute of the ASDU. * \brief Set the refresh time attribute of the ASDU.
* *
@ -318,8 +328,10 @@ SVPublisher_ASDU_setRefrTm(SVPublisher_ASDU self, uint64_t refrTm);
* The attribute SmpMod shall specify if the sample rate is defined in units of samples per nominal period, samples per second or seconds per sample. * The attribute SmpMod shall specify if the sample rate is defined in units of samples per nominal period, samples per second or seconds per sample.
* If it is missing, the default value is samples per period. * If it is missing, the default value is samples per period.
* *
* NOTE: Function has to be called before calling \ref SVPublisher_setupComplete
*
* \param[in] self the Sampled Values ASDU instance. * \param[in] self the Sampled Values ASDU instance.
* \param smpMod one of IEC61850_SV_SMPMOD_PER_NOMINAL_PERIOD, IEC61850_SV_SMPMOD_SAMPLES_PER_SECOND or IEC61850_SV_SMPMOD_SECONDS_PER_SAMPLE * \param[in] smpMod one of IEC61850_SV_SMPMOD_PER_NOMINAL_PERIOD, IEC61850_SV_SMPMOD_SAMPLES_PER_SECOND or IEC61850_SV_SMPMOD_SECONDS_PER_SAMPLE
*/ */
void void
SVPublisher_ASDU_setSmpMod(SVPublisher_ASDU self, uint8_t smpMod); SVPublisher_ASDU_setSmpMod(SVPublisher_ASDU self, uint8_t smpMod);
@ -330,12 +342,31 @@ SVPublisher_ASDU_setSmpMod(SVPublisher_ASDU self, uint8_t smpMod);
* The attribute SmpRate shall specify the sample rate. * The attribute SmpRate shall specify the sample rate.
* The value shall be interpreted depending on the value of the SmpMod attribute. * The value shall be interpreted depending on the value of the SmpMod attribute.
* *
* NOTE: Function has to be called before calling \ref SVPublisher_setupComplete
*
* \param[in] self the Sampled Values ASDU instance. * \param[in] self the Sampled Values ASDU instance.
* \param smpRate Amount of samples (default per nominal period, see SmpMod). * \param[in] smpRate Amount of samples (default per nominal period, see SmpMod).
*/ */
void void
SVPublisher_ASDU_setSmpRate(SVPublisher_ASDU self, uint16_t smpRate); SVPublisher_ASDU_setSmpRate(SVPublisher_ASDU self, uint16_t smpRate);
/**
* \brief Set the clock synchronization information
*
* Default value is not synchronized (0).
* Possible values are:
* 0 = SV are not synchronized by an external clock signal.
* 1 = SV are synchronized by a clock signal from an unspecified local area clock.
* 2 = SV are synchronized by a global area clock signal (time traceable).
* 5 to 254 = SV are synchronized by a clock signal from a local area clock identified by this value.
* 3;4;255 = Reserved values Do not use.
*
* \param[in] self the Sampled Values ASDU instance.
* \param[in] smpSynch the clock synchronization state
*/
void
SVPublisher_ASDU_setSmpSynch(SVPublisher_ASDU self, uint16_t smpSynch);
/**@} @}*/ /**@} @}*/
#ifndef DEPRECATED #ifndef DEPRECATED

@ -621,3 +621,7 @@ EXPORTS
IedServerConfig_setMaxDomainSpecificDataSets IedServerConfig_setMaxDomainSpecificDataSets
IedServerConfig_getMaxDomainSpecificDataSets IedServerConfig_getMaxDomainSpecificDataSets
IedServer_setReadAccessHandler IedServer_setReadAccessHandler
ClientReport_hasSubSeqNum
ClientReport_getSubSeqNum
ClientReport_getMoreSeqmentsFollow
IedConnection_getFileDirectoryEx

@ -749,3 +749,9 @@ EXPORTS
IedServerConfig_setMaxDomainSpecificDataSets IedServerConfig_setMaxDomainSpecificDataSets
IedServerConfig_getMaxDomainSpecificDataSets IedServerConfig_getMaxDomainSpecificDataSets
IedServer_setReadAccessHandler IedServer_setReadAccessHandler
ClientReport_hasSubSeqNum
ClientReport_getSubSeqNum
ClientReport_getMoreSeqmentsFollow
SVPublisher_ASDU_enableRefrTm
SVPublisher_ASDU_setSmpSynch
IedConnection_getFileDirectoryEx

@ -1,5 +1,7 @@
package com.libiec61850.scl.model; package com.libiec61850.scl.model;
import java.text.SimpleDateFormat;
/* /*
* DataModelValue.java * DataModelValue.java
* *
@ -24,6 +26,7 @@ package com.libiec61850.scl.model;
*/ */
import java.util.Base64; import java.util.Base64;
import java.util.Date;
import com.libiec61850.scl.types.EnumerationType; import com.libiec61850.scl.types.EnumerationType;
import com.libiec61850.scl.types.IllegalValueException; import com.libiec61850.scl.types.IllegalValueException;
@ -86,7 +89,7 @@ public class DataModelValue {
if (trimmedValue.isEmpty()) if (trimmedValue.isEmpty())
this.value = new Long(0); this.value = new Long(0);
else else
this.value = Long.decode(trimmedValue); this.value = Long.parseLong(trimmedValue);
break; break;
case BOOLEAN: case BOOLEAN:
@ -162,6 +165,25 @@ public class DataModelValue {
this.value = null; this.value = null;
System.out.println("Warning: Initialization of QUALITY is unsupported!"); System.out.println("Warning: Initialization of QUALITY is unsupported!");
break; break;
case TIMESTAMP:
case ENTRY_TIME:
try {
String modValueString = value.replace(',', '.');
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-d'T'HH:mm:ss.SSS");
Date date = parser.parse(modValueString);
this.value = new Long(date.toInstant().toEpochMilli());
}
catch (java.text.ParseException e) {
this.value = null;
System.out.println("Warning: Val element does not contain a valid time stamp: " + e.getMessage());
}
break;
default: default:
throw new IllegalValueException("Unsupported type " + type.toString() + " value: " + value); throw new IllegalValueException("Unsupported type " + type.toString() + " value: " + value);
} }

@ -94,7 +94,7 @@ public class OptionFields {
if (boolVal != null) if (boolVal != null)
this.configRef = boolVal; this.configRef = boolVal;
boolVal = ParserUtils.parseBooleanAttribute(optFieldsNode, "bufOvlf"); boolVal = ParserUtils.parseBooleanAttribute(optFieldsNode, "bufOvfl");
if (boolVal != null) if (boolVal != null)
this.bufOvfl = boolVal; this.bufOvfl = boolVal;
} }

@ -71,6 +71,22 @@ public class ModelViewer {
} }
} }
private static void printDataObjects(List<DataObject> dataObjects, PrintStream output, String indent, String add)
{
for (DataObject dObject : dataObjects) {
output.println(indent + dObject.getName());
printDataObjects(dObject.getSubDataObjects(), output, indent + add, add);
for (DataAttribute dAttribute : dObject.getDataAttributes()) {
output.println(indent + add + dAttribute.getName() + " [" + dAttribute.getFc().toString() + "]");
printSubAttributes(dAttribute, output, indent + add, add);
}
}
}
private static void printModelStructure(InputStream stream, String icdFile, PrintStream output, String iedName, String accessPointName) private static void printModelStructure(InputStream stream, String icdFile, PrintStream output, String iedName, String accessPointName)
throws SclParserException { throws SclParserException {
@ -97,16 +113,7 @@ public class ModelViewer {
for (LogicalNode lNode : lNodes) { for (LogicalNode lNode : lNodes) {
output.println(" " + lNode.getName()); output.println(" " + lNode.getName());
for (DataObject dObject : lNode.getDataObjects()) { printDataObjects(lNode.getDataObjects(), output, " ", " ");
output.println(" " + dObject.getName());
for (DataAttribute dAttribute : dObject.getDataAttributes()) {
output.println(" " + dAttribute.getName() + " [" + dAttribute.getFc().toString() + "]");
printSubAttributes(dAttribute, output, " ", " ");
}
}
} }
} }
@ -128,6 +135,24 @@ public class ModelViewer {
} }
} }
private static void printObjectList(List<DataObject> dataObjects, PrintStream output, String prefix)
{
for (DataObject dataObject : dataObjects) {
String dOPrefix = prefix + "." + dataObject.getName();
printObjectList(dataObject.getSubDataObjects(), output, dOPrefix);
for (DataAttribute dAttribute : dataObject.getDataAttributes()) {
String daPrefix = dOPrefix + "." + dAttribute.getName();
output.println(daPrefix + " [" + dAttribute.getFc().toString() + "]");
printSubAttributeList(dAttribute, output, daPrefix);
}
}
}
private static void printAttributeList(InputStream stream, String icdFile, PrintStream output, String iedName, String accessPointName) private static void printAttributeList(InputStream stream, String icdFile, PrintStream output, String iedName, String accessPointName)
throws SclParserException { throws SclParserException {
@ -158,20 +183,22 @@ public class ModelViewer {
for (LogicalNode lNode : lNodes) { for (LogicalNode lNode : lNodes) {
String lNodePrefix = devPrefix + lNode.getName(); String lNodePrefix = devPrefix + lNode.getName();
for (DataObject dObject : lNode.getDataObjects()) { printObjectList(lNode.getDataObjects(), output, lNodePrefix);
String dOPrefix = lNodePrefix + "." + dObject.getName(); // for (DataObject dObject : lNode.getDataObjects()) {
//
for (DataAttribute dAttribute : dObject.getDataAttributes()) { // String dOPrefix = lNodePrefix + "." + dObject.getName();
//
String daPrefix = dOPrefix + "." + dAttribute.getName(); // for (DataAttribute dAttribute : dObject.getDataAttributes()) {
//
output.println(daPrefix + " [" + dAttribute.getFc().toString() + "]"); // String daPrefix = dOPrefix + "." + dAttribute.getName();
//
printSubAttributeList(dAttribute, output, daPrefix); // output.println(daPrefix + " [" + dAttribute.getFc().toString() + "]");
} //
// printSubAttributeList(dAttribute, output, daPrefix);
} // }
//
// }
} }
} }

@ -805,6 +805,11 @@ public class StaticModelGenerator {
case FLOAT64: case FLOAT64:
buffer.append("MmsValue_newDouble(" + value.getValue() + ");"); buffer.append("MmsValue_newDouble(" + value.getValue() + ");");
break; break;
case TIMESTAMP:
buffer.append("MmsValue_newUtcTimeByMsTime("+ value.getValue() + ");");
break;
default: default:
System.out.println("Unknown default value for " + daName + " type: " + dataAttribute.getType()); System.out.println("Unknown default value for " + daName + " type: " + dataAttribute.getType());
buffer.append("NULL;"); buffer.append("NULL;");

Loading…
Cancel
Save