From 5276813de3a80d961e1de6b7603f4d3e58989a31 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Fri, 4 Jul 2025 12:57:24 +0200 Subject: [PATCH 01/14] API ControlBlock access handler added --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 58 ++++- dotnet/dotnet.sln | 48 ++-- .../server_example_access_control/Program.cs | 180 ++++++++++++++ .../server_example_access_control/model.cfg | 220 ++++++++++++++++++ .../server_example_access_control.csproj | 24 ++ .../server_example_access_control.c | 1 + 6 files changed, 509 insertions(+), 22 deletions(-) create mode 100644 dotnet/server_example_access_control/Program.cs create mode 100644 dotnet/server_example_access_control/model.cfg create mode 100644 dotnet/server_example_access_control/server_example_access_control.csproj diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 1c021b26..4113cee3 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -2154,6 +2154,12 @@ namespace IEC61850 OBJECT_UNDEFINED = 4 } + public enum ControlBlockAccessType + { + IEC61850_CB_ACCESS_TYPE_READ, + IEC61850_CB_ACCESS_TYPE_WRITE + } + public delegate CheckHandlerResult CheckHandler (ControlAction action, object parameter, MmsValue ctlVal, bool test, bool interlockCheck); public static class SqliteLogStorage @@ -2351,6 +2357,12 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setListObjectsAccessHandler(IntPtr self, IedServer_ListObjectsAccessHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setControlBlockAccessHandler(IntPtr self, IedServer_ControlBlockAccessHandler handler, IntPtr parameter); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool IedServer_ControlBlockAccessHandler(IntPtr parameter, IntPtr connection, int acsiClass, IntPtr ld, IntPtr ln, string objectName, string subObjectName, int accessType); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool IedServer_ListObjectsAccessHandler(IntPtr parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice ld, LogicalNode ln, string objectName, string subObjectName, FunctionalConstraint fc); @@ -2577,11 +2589,55 @@ namespace IEC61850 } } - public void SetListObjectsAccessHandler(IedServer_ListObjectsAccessHandler handler, System.IntPtr parameter) + public void SetListObjectsAccessHandler(IedServer_ListObjectsAccessHandler handler, IntPtr parameter) { IedServer_setListObjectsAccessHandler(self, handler, parameter); } + public delegate bool ControlBlockAccessHandler(object parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice ld, LogicalNode ln, string objectName, string subObjectName, ControlBlockAccessType accessType); + + private ControlBlockAccessHandler rcbControlHandler = null; + + private object rcbControlHandlerParameter = null; + + private IedServer_ControlBlockAccessHandler internalRCBControlHandler = null; + + private bool InternalRCBControlHandlerImplementation(IntPtr parameter, IntPtr connection, int acsiClass, IntPtr ld, IntPtr ln, string objectName, string subObjectName, int accessType) + { + if (rcbControlHandler != null && connection != IntPtr.Zero && ld != IntPtr.Zero && ln != IntPtr.Zero) + { + ClientConnection con = null; + + this.clientConnections.TryGetValue(connection, out con); + + ModelNode ldModelNode = iedModel.GetModelNodeFromNodeRef(ld); + ModelNode lnModelNode = iedModel.GetModelNodeFromNodeRef(ln); + return rcbControlHandler(rcbControlHandlerParameter, con, (ACSIClass)acsiClass, ldModelNode as LogicalDevice, lnModelNode as LogicalNode, objectName, subObjectName, (ControlBlockAccessType)accessType); + } + + return false; + + } + + + /// + /// Set a handler to control read and write access to control blocks and logs + /// + /// the callback handler to be used + /// a user provided parameter that is passed to the handler + public void SetControlBlockAccessHandler(ControlBlockAccessHandler handler, object parameter) + { + rcbControlHandler = handler; + rcbControlHandlerParameter = parameter; + + if (internalRCBControlHandler == null) + { + internalRCBControlHandler = new IedServer_ControlBlockAccessHandler(InternalRCBControlHandlerImplementation); + + IedServer_setControlBlockAccessHandler(self, internalRCBControlHandler, IntPtr.Zero); + } + } + private Dictionary writeAccessHandlers = new Dictionary (); private void ConnectionIndicationHandlerImpl (IntPtr iedServer, IntPtr clientConnection, bool connected, IntPtr parameter) diff --git a/dotnet/dotnet.sln b/dotnet/dotnet.sln index 359ede93..06679868 100644 --- a/dotnet/dotnet.sln +++ b/dotnet/dotnet.sln @@ -3,54 +3,56 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.10.35004.147 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IEC61850.NET", "IEC61850forCSharp\IEC61850.NET.csproj", "{C35D624E-5506-4560-8074-1728F1FA1A4D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IEC61850.NET", "IEC61850forCSharp\IEC61850.NET.csproj", "{C35D624E-5506-4560-8074-1728F1FA1A4D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example1", "example1\example1.csproj", "{C616A6DF-831E-443C-9310-3F343A6E3D1A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "example1", "example1\example1.csproj", "{C616A6DF-831E-443C-9310-3F343A6E3D1A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "model_browsing", "model_browsing\model_browsing.csproj", "{59B85486-F48D-4978-BD35-8F5C3A8288D4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "model_browsing", "model_browsing\model_browsing.csproj", "{59B85486-F48D-4978-BD35-8F5C3A8288D4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "datasets", "datasets\datasets.csproj", "{D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "datasets", "datasets\datasets.csproj", "{D5C7DD38-032A-49B6-B74F-FFD9724A8AE4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "control", "control\control.csproj", "{C351CFA4-E54E-49A1-86CE-69643535541A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "control", "control\control.csproj", "{C351CFA4-E54E-49A1-86CE-69643535541A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "reporting", "reporting\reporting.csproj", "{9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "reporting", "reporting\reporting.csproj", "{9E29B4CE-EE5F-4CA6-85F6-5D1FF8B27BF8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example2", "example2\example2.csproj", "{2A226B6D-1D1F-4BFE-B8CC-158116F71270}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "example2", "example2\example2.csproj", "{2A226B6D-1D1F-4BFE-B8CC-158116F71270}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "authenticate", "authenticate\authenticate.csproj", "{0BECEC77-2315-4B95-AFF9-E6007E644BBF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "authenticate", "authenticate\authenticate.csproj", "{0BECEC77-2315-4B95-AFF9-E6007E644BBF}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "tests\tests.csproj", "{FBDFE530-DBEB-474B-BA54-9AB287DD57B3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "files", "files\files.csproj", "{77127456-19B9-4D1A-AEF9-40F8D1C5695E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "files", "files\files.csproj", "{77127456-19B9-4D1A-AEF9-40F8D1C5695E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example3", "example3\example3.csproj", "{5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "example3", "example3\example3.csproj", "{5E5D0FE0-DF44-48D8-A10E-1FB07D34DEA2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "report_new_dataset", "report_new_dataset\report_new_dataset.csproj", "{71485F99-2976-45E6-B73D-4946E594C15C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "report_new_dataset", "report_new_dataset\report_new_dataset.csproj", "{71485F99-2976-45E6-B73D-4946E594C15C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "log_client", "log_client\log_client.csproj", "{14C71267-2F38-460D-AA55-6803EE80AFB4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "log_client", "log_client\log_client.csproj", "{14C71267-2F38-460D-AA55-6803EE80AFB4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{0D2F61F1-A173-44E7-BFB0-B698A1D44D12}" ProjectSection(SolutionItems) = preProject .nuget\packages.config = .nuget\packages.config EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server1", "server1\server1.csproj", "{9286D2AB-96ED-4631-AB3C-ED20FF5D6E6C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "server1", "server1\server1.csproj", "{9286D2AB-96ED-4631-AB3C-ED20FF5D6E6C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls_client_example", "tls_client_example\tls_client_example.csproj", "{6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tls_client_example", "tls_client_example\tls_client_example.csproj", "{6734BF52-2D0D-476B-8EA2-C9C2D1D69B03}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "goose_subscriber", "goose_subscriber\goose_subscriber.csproj", "{1285372C-2E62-494A-A661-8D5D3873318C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "goose_subscriber", "goose_subscriber\goose_subscriber.csproj", "{1285372C-2E62-494A-A661-8D5D3873318C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sv_subscriber", "sv_subscriber\sv_subscriber.csproj", "{44651D2D-3252-4FD5-8B8B-5552DBE1B499}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sv_subscriber", "sv_subscriber\sv_subscriber.csproj", "{44651D2D-3252-4FD5-8B8B-5552DBE1B499}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tls_server_example", "tls_server_example\tls_server_example.csproj", "{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tls_server_example", "tls_server_example\tls_server_example.csproj", "{B63F7A81-1D3A-4F2F-A7C2-D6F77E5BD307}" 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "client_example_setting_groups", "client_example_setting_groups\client_example_setting_groups.csproj", "{0DA95476-B149-450B-AC36-01CEECFC1A43}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "client_example_async", "client_example_async\client_example_async.csproj", "{71902641-776A-47D8-9C0E-9ACBBEAC1370}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "client_example_async", "client_example_async\client_example_async.csproj", "{71902641-776A-47D8-9C0E-9ACBBEAC1370}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server_goose_publisher", "server_goose_publisher\server_goose_publisher.csproj", "{C14BB883-86B8-401C-B3D6-B655F55F3298}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "server_goose_publisher", "server_goose_publisher\server_goose_publisher.csproj", "{C14BB883-86B8-401C-B3D6-B655F55F3298}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "log_server", "log_server\log_server.csproj", "{96124F40-D38E-499B-9968-674E0D32F933}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "log_server", "log_server\log_server.csproj", "{96124F40-D38E-499B-9968-674E0D32F933}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "server_example_access_control", "server_example_access_control\server_example_access_control.csproj", "{304D9146-1490-46EF-B771-20B0603084F5}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -146,6 +148,10 @@ Global {96124F40-D38E-499B-9968-674E0D32F933}.Debug|Any CPU.Build.0 = Debug|Any CPU {96124F40-D38E-499B-9968-674E0D32F933}.Release|Any CPU.ActiveCfg = Release|Any CPU {96124F40-D38E-499B-9968-674E0D32F933}.Release|Any CPU.Build.0 = Release|Any CPU + {304D9146-1490-46EF-B771-20B0603084F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {304D9146-1490-46EF-B771-20B0603084F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {304D9146-1490-46EF-B771-20B0603084F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {304D9146-1490-46EF-B771-20B0603084F5}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs new file mode 100644 index 00000000..b77c0cb2 --- /dev/null +++ b/dotnet/server_example_access_control/Program.cs @@ -0,0 +1,180 @@ +/* + * server_example_access_control.cs + * + * - How to use access control mechanisms + * - How to implement RBAC features based on access control mechanisms + */ + +using System; +using IEC61850.Server; +using IEC61850.Common; +using System.Threading; +using System.Net; +using static IEC61850.Server.IedServer; +using System.Collections.Generic; + +namespace server_access_control +{ + class MainClass + { + public static void Main(string[] args) + { + bool running = true; + + /* run until Ctrl-C is pressed */ + Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e) + { + e.Cancel = true; + running = false; + }; + + /* Create new server configuration object */ + IedServerConfig config = new IedServerConfig(); + + /* Set buffer size for buffered report control blocks to 200000 bytes */ + config.ReportBufferSize = 200000; + + /* Set stack compliance to a specific edition of the standard (WARNING: data model has also to be checked for compliance) */ + config.Edition = Iec61850Edition.EDITION_2; + + /* Set the base path for the MMS file services */ + config.FileServiceBasePath = "./vmd-filestore/"; + + /* disable MMS file service */ + config.FileServiceEnabled = false; + + /* enable dynamic data set service */ + config.DynamicDataSetServiceEnabled = true; + + /* disable log service */ + config.LogServiceEnabled = false; + + /* set maximum number of clients */ + config.MaxMmsConnections = 2; + + IedModel iedModel = ConfigFileParser.CreateModelFromConfigFile("model.cfg"); + + IedServer iedServer = new IedServer(iedModel, config); + + iedServer.SetServerIdentity("libiec61850.com", "access control example", "1.0.0"); + + DataObject spcso1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO1"); + + iedServer.SetControlHandler(spcso1, delegate (ControlAction action, object parameter, MmsValue value, bool test) + { + if (test) + return ControlHandlerResult.FAILED; + + if (value.GetType() == MmsType.MMS_BOOLEAN) + { + Console.WriteLine("received binary control command: "); + + if (value.GetBoolean()) + Console.WriteLine("on\n"); + else + Console.WriteLine("off\n"); + } + else + return ControlHandlerResult.FAILED; + + return ControlHandlerResult.OK; + }, spcso1); + + void ConnectionCallBack(IedServer server, ClientConnection clientConnection, bool connected, object parameter) + { + if (connected) + Console.WriteLine("Connection opened\n"); + else + Console.WriteLine("Connection closed\n"); + } + + var connectionCallBack = new ConnectionIndicationHandler(ConnectionCallBack); + iedServer.SetConnectionIndicationHandler(connectionCallBack, "127.0.0.1"); + + + /* Install handler to log RCB events */ + + iedServer.SetRCBEventHandler(delegate (object parameter, ReportControlBlock rcb, ClientConnection con, RCBEventType eventType, string parameterName, MmsDataAccessError serviceError) + { + Console.WriteLine("RCB: " + rcb.Parent.GetObjectReference() + "." + rcb.Name + " event: " + eventType.ToString()); + + if (con != null) + { + Console.WriteLine(" caused by client " + con.GetPeerAddress()); + } + else + { + Console.WriteLine(" client = null"); + } + + if ((eventType == RCBEventType.SET_PARAMETER) || (eventType == RCBEventType.GET_PARAMETER)) + { + Console.WriteLine("RCB: "+rcb.Name + " event: "+ eventType .ToString()+ "\n"); + Console.WriteLine(" param: "+ parameterName + "\n"); + Console.WriteLine(" result: "+ serviceError.ToString() + "\n"); + } + + if (eventType == RCBEventType.ENABLED) + { + Console.WriteLine("RCB: "+ rcb.Name + " event: " + eventType.ToString() + "\n"); + string rptId = rcb.RptID; + Console.WriteLine(" rptID: "+ rptId+"\n"); + string dataSet =rcb.DataSet; + Console.WriteLine(" datSet:"+ dataSet+"\n"); + } + + + }, null); + + /* Install handler to control access to control blocks (RCBs, LCBs, GoCBs, SVCBs, SGCBs)*/ + bool ControlBlockAccessCallBack(object parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice ld, LogicalNode ln, string objectName, string subObjectName, ControlBlockAccessType accessType) + { + IedServer iedServer1 = parameter as IedServer; + + Console.WriteLine(acsiClass.ToString() + " "+ accessType.ToString() + " access " + ld.GetName() + ln.GetName() +"/"+ objectName + "." + subObjectName); + + if (objectName == "EventsIndexed03") + return false; + + + Console.WriteLine("Control block access callback"); + return true; + } + + iedServer.SetControlBlockAccessHandler(ControlBlockAccessCallBack, iedServer); + + iedServer.Start(102); + + if (iedServer.IsRunning()) + { + Console.WriteLine("Server started"); + + GC.Collect(); + + DataObject ggio1AnIn1 = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.AnIn1"); + + DataAttribute ggio1AnIn1magF = (DataAttribute)ggio1AnIn1.GetChild("mag.f"); + DataAttribute ggio1AnIn1T = (DataAttribute)ggio1AnIn1.GetChild("t"); + + float floatVal = 1.0f; + + while (running) + { + floatVal += 1f; + iedServer.UpdateTimestampAttributeValue(ggio1AnIn1T, new Timestamp(DateTime.Now)); + iedServer.UpdateFloatAttributeValue(ggio1AnIn1magF, floatVal); + Thread.Sleep(100); + } + + iedServer.Stop(); + Console.WriteLine("Server stopped"); + } + else + { + Console.WriteLine("Failed to start server"); + } + + iedServer.Destroy(); + } + } +} \ No newline at end of file diff --git a/dotnet/server_example_access_control/model.cfg b/dotnet/server_example_access_control/model.cfg new file mode 100644 index 00000000..3c8b17ae --- /dev/null +++ b/dotnet/server_example_access_control/model.cfg @@ -0,0 +1,220 @@ +MODEL(simpleIO){ +LD(GenericIO){ +LN(LLN0){ +DO(Mod 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +DA(ctlModel 0 12 4 16 0)=0; +} +DO(Beh 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Health 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(NamPlt 0){ +DA(vendor 0 20 5 16 0)="MZ Automation"; +DA(swRev 0 20 5 16 0)="1.3.0"; +DA(d 0 20 5 16 0)="libiec61850 server example"; +DA(configRev 0 20 5 16 0); +DA(ldNs 0 20 11 16 0); +} +DS(Events){ +DE(GGIO1$ST$SPCSO1$stVal); +DE(GGIO1$ST$SPCSO2$stVal); +DE(GGIO1$ST$SPCSO3$stVal); +DE(GGIO1$ST$SPCSO4$stVal); +} +DS(Events2){ +DE(GGIO1$ST$SPCSO1); +DE(GGIO1$ST$SPCSO2); +DE(GGIO1$ST$SPCSO3); +DE(GGIO1$ST$SPCSO4); +} +DS(Measurements){ +DE(GGIO1$MX$AnIn1$mag$f); +DE(GGIO1$MX$AnIn1$q); +DE(GGIO1$MX$AnIn2$mag$f); +DE(GGIO1$MX$AnIn2$q); +DE(GGIO1$MX$AnIn3$mag$f); +DE(GGIO1$MX$AnIn3$q); +DE(GGIO1$MX$AnIn4$mag$f); +DE(GGIO1$MX$AnIn4$q); +} +RC(EventsRCB01 Events1 0 Events 1 24 175 50 1000); +RC(EventsRCBPreConf01 Events1 0 Events 1 24 175 50 1000); +RC(EventsBRCB01 Events2 1 Events 1 24 175 50 1000); +RC(EventsBRCBPreConf01 Events2 1 Events 1 24 175 50 1000); +RC(EventsIndexed01 Events2 0 Events 1 24 175 50 1000); +RC(EventsIndexed02 Events2 0 Events 1 24 175 50 1000); +RC(EventsIndexed03 Events2 0 Events 1 24 175 50 1000); +RC(Measurements01 Measurements 1 Measurements 1 16 239 50 1000); +RC(Measurements02 Measurements 1 Measurements 1 16 239 50 1000); +RC(Measurements03 Measurements 1 Measurements 1 16 239 50 1000); +} +LN(LPHD1){ +DO(PhyNam 0){ +DA(vendor 0 20 5 16 0); +} +DO(PhyHealth 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Proxy 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +} +LN(GGIO1){ +DO(Mod 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +DA(ctlModel 0 12 4 16 0)=0; +} +DO(Beh 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Health 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(NamPlt 0){ +DA(vendor 0 20 5 16 0); +DA(swRev 0 20 5 16 0); +DA(d 0 20 5 16 0); +} +DO(AnIn1 0){ +DA(mag 0 27 1 17 0){ +DA(f 0 10 1 16 0); +} +DA(q 0 23 1 18 0); +DA(t 0 22 1 16 0); +} +DO(AnIn2 0){ +DA(mag 0 27 1 17 0){ +DA(f 0 10 1 16 0); +} +DA(q 0 23 1 18 0); +DA(t 0 22 1 16 0); +} +DO(AnIn3 0){ +DA(mag 0 27 1 17 0){ +DA(f 0 10 1 16 0); +} +DA(q 0 23 1 18 0); +DA(t 0 22 1 16 0); +} +DO(AnIn4 0){ +DA(mag 0 27 1 17 0){ +DA(f 0 10 1 16 0); +} +DA(q 0 23 1 18 0); +DA(t 0 22 1 16 0); +} +DO(SPCSO1 0){ +DA(origin 0 27 0 16 0){ +DA(orCat 0 12 0 16 0); +DA(orIdent 0 13 0 16 0); +} +DA(ctlNum 0 6 0 16 0); +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +DA(ctlModel 0 12 4 16 0)=1; +DA(Oper 0 27 12 16 0){ +DA(ctlVal 0 0 12 16 0); +DA(origin 0 27 12 16 0){ +DA(orCat 0 12 12 16 0); +DA(orIdent 0 13 12 16 0); +} +DA(ctlNum 0 6 12 16 0); +DA(T 0 22 12 16 0); +DA(Test 0 0 12 16 0); +DA(Check 0 24 12 16 0); +} +} +DO(SPCSO2 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(Oper 0 27 12 16 0){ +DA(ctlVal 0 0 12 16 0); +DA(origin 0 27 12 16 0){ +DA(orCat 0 12 12 16 0); +DA(orIdent 0 13 12 16 0); +} +DA(ctlNum 0 6 12 16 0); +DA(T 0 22 12 16 0); +DA(Test 0 0 12 16 0); +DA(Check 0 24 12 16 0); +} +DA(ctlModel 0 12 4 16 0)=1; +DA(t 0 22 0 16 0); +} +DO(SPCSO3 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(Oper 0 27 12 16 0){ +DA(ctlVal 0 0 12 16 0); +DA(origin 0 27 12 16 0){ +DA(orCat 0 12 12 16 0); +DA(orIdent 0 13 12 16 0); +} +DA(ctlNum 0 6 12 16 0); +DA(T 0 22 12 16 0); +DA(Test 0 0 12 16 0); +DA(Check 0 24 12 16 0); +} +DA(ctlModel 0 12 4 16 0)=1; +DA(t 0 22 0 16 0); +} +DO(SPCSO4 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(Oper 0 27 12 16 0){ +DA(ctlVal 0 0 12 16 0); +DA(origin 0 27 12 16 0){ +DA(orCat 0 12 12 16 0); +DA(orIdent 0 13 12 16 0); +} +DA(ctlNum 0 6 12 16 0); +DA(T 0 22 12 16 0); +DA(Test 0 0 12 16 0); +DA(Check 0 24 12 16 0); +} +DA(ctlModel 0 12 4 16 0)=1; +DA(t 0 22 0 16 0); +} +DO(Ind1 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Ind2 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Ind3 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Ind4 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +} +} +} diff --git a/dotnet/server_example_access_control/server_example_access_control.csproj b/dotnet/server_example_access_control/server_example_access_control.csproj new file mode 100644 index 00000000..c34022ad --- /dev/null +++ b/dotnet/server_example_access_control/server_example_access_control.csproj @@ -0,0 +1,24 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + PreserveNewest + + + + + + + + diff --git a/examples/server_example_access_control/server_example_access_control.c b/examples/server_example_access_control/server_example_access_control.c index 6b13ca7b..58554d09 100644 --- a/examples/server_example_access_control/server_example_access_control.c +++ b/examples/server_example_access_control/server_example_access_control.c @@ -114,6 +114,7 @@ controlBlockAccessHandler(void* parameter, ClientConnection connection, ACSIClas { printf("%s %s access %s/%s.%s.%s\n", ACSIClassToStr(acsiClass), accessType == IEC61850_CB_ACCESS_TYPE_WRITE ? "write" : "read", ld->name, ln->name, objectName, subObjectName); + return false; /* allow only read access to LCBs */ if (acsiClass == ACSI_CLASS_LCB) { if (accessType == IEC61850_CB_ACCESS_TYPE_READ) From b81279e44240c87742df065a008b0d407fa48722 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Fri, 4 Jul 2025 14:41:51 +0200 Subject: [PATCH 02/14] API DataSet access handler added --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 57 ++++++++++++++++++- .../server_example_access_control/Program.cs | 18 ++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 4113cee3..1980b23b 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -2160,6 +2160,15 @@ namespace IEC61850 IEC61850_CB_ACCESS_TYPE_WRITE } + public enum DataSetOperation + { + DATASET_CREATE, + DATASET_DELETE, + DATASET_READ, + DATASET_WRITE, + DATASET_GET_DIRECTORY + } + public delegate CheckHandlerResult CheckHandler (ControlAction action, object parameter, MmsValue ctlVal, bool test, bool interlockCheck); public static class SqliteLogStorage @@ -2360,6 +2369,12 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setControlBlockAccessHandler(IntPtr self, IedServer_ControlBlockAccessHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setDataSetAccessHandler(IntPtr self, IedServer_DataSetAccessHandler handler, IntPtr parameter); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool IedServer_DataSetAccessHandler(IntPtr parameter, IntPtr connection, int operation, string datasetRef); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool IedServer_ControlBlockAccessHandler(IntPtr parameter, IntPtr connection, int acsiClass, IntPtr ld, IntPtr ln, string objectName, string subObjectName, int accessType); @@ -2619,7 +2634,6 @@ namespace IEC61850 } - /// /// Set a handler to control read and write access to control blocks and logs /// @@ -2638,6 +2652,47 @@ namespace IEC61850 } } + public delegate bool DataSetAccessHandler(object parameter, ClientConnection connection, DataSetOperation operation, string datasetRef); + + private DataSetAccessHandler dataSetAccessHandler = null; + + private object dataSetAccessHandlerParameter = null; + + private IedServer_DataSetAccessHandler internalDataSetAccessHandler = null; + + /// + /// Callback that is called when the client is calling a dataset operation (create, delete, read, write, list directory) + /// note This callback is called before the IedServer_RCBEventHandler and only in case of operations(RCB_EVENT_GET_PARAMETER, RCB_EVENT_SET_PARAMETER, RCB_EVENT_ENABLE + /// + /// the callback handler to be used + /// a user provided parameter that is passed to the handler + public void SetDataSetAccessHandler(DataSetAccessHandler handler, object parameter) + { + dataSetAccessHandler = handler; + dataSetAccessHandlerParameter = parameter; + + if (internalDataSetAccessHandler == null) + { + internalDataSetAccessHandler = new IedServer_DataSetAccessHandler(InternalDataSetlHandlerImplementation); + + IedServer_setDataSetAccessHandler(self, internalDataSetAccessHandler, IntPtr.Zero); + } + } + + private bool InternalDataSetlHandlerImplementation (IntPtr parameter, IntPtr connection, int operation, string datasetRef) + { + if (dataSetAccessHandler != null && connection != IntPtr.Zero) + { + ClientConnection con = null; + + this.clientConnections.TryGetValue(connection, out con); + + return dataSetAccessHandler(dataSetAccessHandlerParameter, con, (DataSetOperation)operation, datasetRef); + } + + return false; + } + private Dictionary writeAccessHandlers = new Dictionary (); private void ConnectionIndicationHandlerImpl (IntPtr iedServer, IntPtr clientConnection, bool connected, IntPtr parameter) diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index b77c0cb2..c6f069d8 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -12,6 +12,7 @@ using System.Threading; using System.Net; using static IEC61850.Server.IedServer; using System.Collections.Generic; +using System.Reflection.Metadata; namespace server_access_control { @@ -143,6 +144,23 @@ namespace server_access_control iedServer.SetControlBlockAccessHandler(ControlBlockAccessCallBack, iedServer); + /* By default access to variables with FC=DC and FC=CF is not allowed. + * This allow to write to simpleIOGenericIO/GGIO1.NamPlt.vendor variable used + * by iec61850_client_example1. + */ + iedServer.SetWriteAccessPolicy(FunctionalConstraint.DC, AccessPolicy.ACCESS_POLICY_ALLOW); + + /* Install handler to perform access control on datasets */ + bool dataSetAccessHandler(object parameter, ClientConnection connection, DataSetOperation operation, string datasetRef) + { + Console.WriteLine("Data set access: "+ datasetRef+" operation: "+ operation.ToString() + "\n"); + + return true; + } + + iedServer.SetDataSetAccessHandler(dataSetAccessHandler, iedServer); + + iedServer.Start(102); if (iedServer.IsRunning()) From 9b90cb219213c111ca7b13d1849c6142561d8674 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Tue, 8 Jul 2025 12:23:50 +0200 Subject: [PATCH 03/14] SetReadAccessHandler added --- CHANGELOG | 8 ++ dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 104 ++++++++++++++++++ .../server_example_access_control/Program.cs | 32 ++++++ 3 files changed, 144 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 0d6c7d86..833e33d6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ + +Changes to version ? +------------------------ + +New features and improvements: + +- .NET API: added additional callbacks to control external access to the data model (required to implement RBAC) + Changes to version 1.6.1 ------------------------ diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 1980b23b..825f7709 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -22,10 +22,13 @@ */ using System; using System.Collections.Generic; +using System.Data; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Xml.Linq; using IEC61850.Common; using IEC61850.TLS; +using static System.Net.Mime.MediaTypeNames; using static IEC61850.Client.IedConnection; // IEC 61850 API for the libiec61850 .NET wrapper library @@ -2366,15 +2369,75 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setListObjectsAccessHandler(IntPtr self, IedServer_ListObjectsAccessHandler handler, IntPtr parameter); + /// + /// Set a handler to control read and write access to control blocks and logs + /// + /// IedServer + /// handler the callback handler to be used + /// a user provided parameter that is passed to the handler [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setControlBlockAccessHandler(IntPtr self, IedServer_ControlBlockAccessHandler handler, IntPtr parameter); + /// + /// Install the global read access handler + /// The read access handler will be called for every read access before the server grants access to the client + /// + /// IedServer + /// the callback function that is invoked if a client tries to read a data object + /// a user provided parameter that is passed to the callback function + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setReadAccessHandler(IntPtr self, ReadAccessHandler handler, IntPtr parameter); + + /// + /// Set a handler to control access to a dataset (create, delete, read, write, list directory) + /// + /// IedServer + /// the callback handler to be used + /// a user provided parameter that is passed to the handler [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setDataSetAccessHandler(IntPtr self, IedServer_DataSetAccessHandler handler, IntPtr parameter); + + /// + /// callback handler to control client read access to data attributes + /// User provided callback function to control MMS client read access to IEC 61850 + /// data objects.The application is to allow read access to data objects for specific clients only. + /// It can be used to implement a role based access control (RBAC). + /// + /// the logical device the client wants to access + /// the logical node the client wants to access + /// the data object the client wants to access + /// the functional constraint of the access + /// the client connection that causes the access + /// the user provided parameter + /// DATA_ACCESS_ERROR_SUCCESS if access is accepted, DATA_ACCESS_ERROR_OBJECT_ACCESS_DENIED if access is denied + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate MmsDataAccessError ReadAccessHandler(IntPtr ld, IntPtr ln, IntPtr dataObject, int fc, IntPtr connection, IntPtr parameter); + /// + /// Callback that is called when the client is calling a dataset operation (create, delete, read, write, list directory) + /// his callback is called before the IedServer_RCBEventHandler and only in case of operations (RCB_EVENT_GET_PARAMETER, RCB_EVENT_SET_PARAMETER, RCB_EVENT_ENABLE + /// + /// user provided parameter + /// client connection that is involved + /// one of the following operation types: DATASET_CREATE, DATASET_DELETE, DATASET_READ, DATASET_WRITE, DATASET_GET_DIRECTORY + /// + /// true to allow operation, false to deny operation [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool IedServer_DataSetAccessHandler(IntPtr parameter, IntPtr connection, int operation, string datasetRef); + /// + /// Callback that is called when a client is invoking a read or write service to a control block or log + /// This callback can be used to control the read and write access to control blocks and logs (SGCB, LCBs, URCBs, BRCBs, GoCBs, SVCBs, logs) + /// + /// user provided parameter + /// client connection that is involved + /// the ACSI class of the object + /// the logical device of the object + /// the logical node of the object + /// the name of the object (e.g. data object name, data set name, log name, RCB name, ...) + /// the name of a sub element of an object or NULL + /// access type (read=IEC61850_CB_ACCESS_TYPE_READ or write=IEC61850_CB_ACCESS_TYPE_WRITE) + /// true to include the object in the service response, otherwise false [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool IedServer_ControlBlockAccessHandler(IntPtr parameter, IntPtr connection, int acsiClass, IntPtr ld, IntPtr ln, string objectName, string subObjectName, int accessType); @@ -2693,6 +2756,47 @@ namespace IEC61850 return false; } + public delegate MmsDataAccessError InternalReadAccessHandler(LogicalDevice ld, LogicalNode ln, DataObject dataObject, FunctionalConstraint fc, ClientConnection connection, object parameter); + + private InternalReadAccessHandler internalReadAccessHandler = null; + + private object internalReadAccessHandlerParameter = null; + + private ReadAccessHandler readAccessHandler = null; + + public void SetReadAccessHandler(InternalReadAccessHandler handler, object parameter) + { + internalReadAccessHandler = handler; + internalReadAccessHandlerParameter = parameter; + + if (readAccessHandler == null) + { + readAccessHandler = new ReadAccessHandler (InternalReadHandlerImplementation); + + IedServer_setReadAccessHandler(self, readAccessHandler, IntPtr.Zero); + } + } + + private MmsDataAccessError InternalReadHandlerImplementation(IntPtr ld, IntPtr ln, IntPtr dataObject, int fc, IntPtr connection, IntPtr parameter) + { + if (internalReadAccessHandler != null && ld != IntPtr.Zero && ln != IntPtr.Zero) + { + ClientConnection con = null; + + this.clientConnections.TryGetValue(connection, out con); + + ModelNode ldModelNode = iedModel.GetModelNodeFromNodeRef(ld); + ModelNode lnModelNode = iedModel.GetModelNodeFromNodeRef(ln); + ModelNode doModelNode = iedModel.GetModelNodeFromNodeRef(dataObject); + + return internalReadAccessHandler(ldModelNode as LogicalDevice, lnModelNode as LogicalNode, doModelNode as DataObject, (FunctionalConstraint)fc, con, internalReadAccessHandlerParameter); + } + + return MmsDataAccessError.UNKNOWN; + } + + + private Dictionary writeAccessHandlers = new Dictionary (); private void ConnectionIndicationHandlerImpl (IntPtr iedServer, IntPtr clientConnection, bool connected, IntPtr parameter) diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index c6f069d8..c56a4a75 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -160,6 +160,38 @@ namespace server_access_control iedServer.SetDataSetAccessHandler(dataSetAccessHandler, iedServer); + /* Install handler to perform read access control on data model elements + * NOTE: when read access to a data model element is blocked this will also prevent the client + * to read the data model element in a data set or enable a RCB instance that uses a dataset + * containing the restricted data model element. + */ + MmsDataAccessError readAccessHandler(LogicalDevice ld, LogicalNode ln, DataObject dataObject, FunctionalConstraint fc, ClientConnection connection, object parameter) + { + if( dataObject!= null) + Console.WriteLine("Read access to "+ld.GetName() + "/"+ln.GetName() + "."+dataObject.GetName() + "\n"); + else + Console.WriteLine("Read access to "+ld.GetName() + "/"+ln.GetName() + "\n"); + + if (dataObject == null) + { + if (ln.GetName() == "GGIO1") + { + return MmsDataAccessError.OBJECT_ACCESS_DENIED; + } + } + else + { + if (ln.GetName() == "GGIO1" && dataObject.GetName() == "AnIn1") + { + return MmsDataAccessError.OBJECT_ACCESS_DENIED; + } + } + + return MmsDataAccessError.SUCCESS; + } + + iedServer.SetReadAccessHandler(readAccessHandler, null); + iedServer.Start(102); From c1a117f0382752c888b77874a6eebd926d35245d Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Tue, 8 Jul 2025 14:37:06 +0200 Subject: [PATCH 04/14] SetDirectoryAccessHandler added --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 62 ++++++++++++++++++- .../server_example_access_control/Program.cs | 23 +++++++ 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 825f7709..d1b7acc3 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -2396,7 +2396,10 @@ namespace IEC61850 /// a user provided parameter that is passed to the handler [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setDataSetAccessHandler(IntPtr self, IedServer_DataSetAccessHandler handler, IntPtr parameter); - + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setDirectoryAccessHandler(IntPtr self, IedServer_DirectoryAccessHandler handler, IntPtr parameter); + /// /// callback handler to control client read access to data attributes /// User provided callback function to control MMS client read access to IEC 61850 @@ -2441,6 +2444,9 @@ namespace IEC61850 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool IedServer_ControlBlockAccessHandler(IntPtr parameter, IntPtr connection, int acsiClass, IntPtr ld, IntPtr ln, string objectName, string subObjectName, int accessType); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool IedServer_DirectoryAccessHandler(IntPtr parameter, IntPtr connection, int category, IntPtr logicalDevice); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool IedServer_ListObjectsAccessHandler(IntPtr parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice ld, LogicalNode ln, string objectName, string subObjectName, FunctionalConstraint fc); @@ -2779,7 +2785,7 @@ namespace IEC61850 private MmsDataAccessError InternalReadHandlerImplementation(IntPtr ld, IntPtr ln, IntPtr dataObject, int fc, IntPtr connection, IntPtr parameter) { - if (internalReadAccessHandler != null && ld != IntPtr.Zero && ln != IntPtr.Zero) + if (internalReadAccessHandler != null && ld != IntPtr.Zero && ln != IntPtr.Zero && connection != IntPtr.Zero) { ClientConnection con = null; @@ -2787,7 +2793,10 @@ namespace IEC61850 ModelNode ldModelNode = iedModel.GetModelNodeFromNodeRef(ld); ModelNode lnModelNode = iedModel.GetModelNodeFromNodeRef(ln); - ModelNode doModelNode = iedModel.GetModelNodeFromNodeRef(dataObject); + ModelNode doModelNode = null; + + if(dataObject != IntPtr.Zero) + doModelNode = iedModel.GetModelNodeFromNodeRef(dataObject); return internalReadAccessHandler(ldModelNode as LogicalDevice, lnModelNode as LogicalNode, doModelNode as DataObject, (FunctionalConstraint)fc, con, internalReadAccessHandlerParameter); } @@ -2795,6 +2804,53 @@ namespace IEC61850 return MmsDataAccessError.UNKNOWN; } + public enum IedServer_DirectoryCategory + { + DIRECTORY_CAT_LD_LIST, + DIRECTORY_CAT_DATA_LIST, + DIRECTORY_CAT_DATASET_LIST, + DIRECTORY_CAT_LOG_LIST + } + + public delegate bool InternalDirectoryAccessHandler(object parameter, ClientConnection connection, IedServer_DirectoryCategory category,LogicalDevice ld); + + private InternalDirectoryAccessHandler internalDirectoryAccessHandler = null; + + private object internalDirectoryAccessHandlerParameter = null; + + private IedServer_DirectoryAccessHandler directoryAccessHandler = null; + + public void SetDirectoryAccessHandler(InternalDirectoryAccessHandler handler, object parameter) + { + internalDirectoryAccessHandler = handler; + internalDirectoryAccessHandlerParameter = parameter; + + if (directoryAccessHandler == null) + { + directoryAccessHandler = new IedServer_DirectoryAccessHandler(DirectoryAccessHandler); + + IedServer_setDirectoryAccessHandler(self, directoryAccessHandler, IntPtr.Zero); + } + } + + private bool DirectoryAccessHandler(IntPtr parameter, IntPtr connection, int category, IntPtr logicalDevice) + { + if (internalDirectoryAccessHandler != null && connection != IntPtr.Zero) + { + ClientConnection con = null; + + this.clientConnections.TryGetValue(connection, out con); + + ModelNode ldModelNode = null; + + if(logicalDevice != IntPtr.Zero) + ldModelNode = iedModel.GetModelNodeFromNodeRef(logicalDevice); + + return internalDirectoryAccessHandler(internalDirectoryAccessHandlerParameter, con, (IedServer_DirectoryCategory)category, ldModelNode as LogicalDevice); + } + + return false; + } private Dictionary writeAccessHandlers = new Dictionary (); diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index c56a4a75..be4b400d 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -192,6 +192,29 @@ namespace server_access_control iedServer.SetReadAccessHandler(readAccessHandler, null); + bool directoryAccessHandler(object parameter, ClientConnection connection, IedServer_DirectoryCategory category, LogicalDevice ld) + { + switch (category) + { + case IedServer_DirectoryCategory.DIRECTORY_CAT_LD_LIST: + + Console.WriteLine("Get list of logical devices from "+ connection.GetPeerAddress()+"\n"); + break; + case IedServer_DirectoryCategory.DIRECTORY_CAT_DATASET_LIST: + Console.WriteLine("Get list of datasets for LD "+ ld.GetName() + " from "+ connection.GetPeerAddress()+"\n"); + break; + case IedServer_DirectoryCategory.DIRECTORY_CAT_DATA_LIST: + Console.WriteLine("Get list of data for LD " + ld.GetName() + " from " + connection.GetPeerAddress() + "\n"); + break; + case IedServer_DirectoryCategory.DIRECTORY_CAT_LOG_LIST: + Console.WriteLine("Get list of logs for LD" + ld.GetName() + " from " + connection.GetPeerAddress() + "\n"); + return false; + } + + return true; + } + + iedServer.SetDirectoryAccessHandler(directoryAccessHandler, null); iedServer.Start(102); From 81af5a159c0cdf8fdc692c2c444f4505693cc0c8 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Sun, 13 Jul 2025 20:41:48 +0200 Subject: [PATCH 05/14] ->SetActiveSettingGroupChangedHandler; ->Update model.cfg with settingGroup --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 223 +++++++++++++++ .../SampledValuesControlBlock.cs | 3 +- .../server_example_access_control/Program.cs | 30 ++ .../server_example_access_control/model.cfg | 263 +++++++++++++++++- 4 files changed, 514 insertions(+), 5 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index d1b7acc3..2cb4e574 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -23,11 +23,14 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Diagnostics; using System.Runtime.InteropServices; using System.Xml.Linq; +using IEC61850.Client; using IEC61850.Common; using IEC61850.TLS; +using static System.Collections.Specialized.BitVector32; using static System.Net.Mime.MediaTypeNames; using static IEC61850.Client.IedConnection; @@ -315,6 +318,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr LogicalDevice_createEx(string name, IntPtr parent, string ldName); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr LogicalDevice_getSettingGroupControlBlock(IntPtr self); + public IedModel IedModel { get; } public LogicalDevice (IntPtr self, IedModel iedModel) : base (self) @@ -346,6 +352,20 @@ namespace IEC61850 self = LogicalDevice_createEx(inst, parent.self, ldName); } + + /// + /// Get the setting group control block (SGCB) of the logical device + /// + /// the SGCB instance or NULL if no SGCB is available + public SettingGroupControlBlock GetSettingGroupControlBlock() + { + IntPtr sgcb = LogicalDevice_getSettingGroupControlBlock(this.self); + + if(sgcb == IntPtr.Zero) + return null; + + return new SettingGroupControlBlock(sgcb); + } } /// @@ -1689,6 +1709,11 @@ namespace IEC61850 { self = SettingGroupControlBlock_create(parent.self, (byte) actSG, (byte) numOfSGs); } + + public SettingGroupControlBlock(IntPtr self) + { + this.self = self ; + } } public class ClientConnection @@ -2400,6 +2425,55 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setDirectoryAccessHandler(IntPtr self, IedServer_DirectoryAccessHandler handler, IntPtr parameter); + /// + /// Set the callback handler for the SetActSG event + /// + /// the instance of IedServer to operate on + /// the handle of the setting group control block of the setting group + /// the user provided callback handler + /// a user provided parameter that is passed to the control handler + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setActiveSettingGroupChangedHandler(IntPtr self, IntPtr sgcb, ActiveSettingGroupChangedHandler handler, IntPtr parameter); + + /// + /// Set the callback handler for the SetEditSG event + /// + /// the instance of IedServer to operate on + /// the handle of the setting group control block of the setting group + /// the user provided callback handler + /// a user provided parameter that is passed to the control handler + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setEditSettingGroupChangedHandler(IntPtr self, IntPtr sgcb, EditSettingGroupChangedHandler handler, IntPtr parameter); + + /// + /// Set the callback handler for the COnfEditSG event + /// + /// the instance of IedServer to operate on + /// the handle of the setting group control block of the setting group + /// the user provided callback handler + /// a user provided parameter that is passed to the control handler + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setEditSettingGroupConfirmationHandler(IntPtr self, IntPtr sgcb, EditSettingGroupConfirmationHandler handler, IntPtr parameter); + + ///// + ///// Set a handler for SVCB control block events (enable/disable) + ///// + ///// the instance of IedServer to operate on. + ///// the SVCB control block instance + ///// the event handler to be used + ///// user provided parameter that is passed to the handler + //[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + //public static extern void IedServer_setSVCBHandler(IntPtr self, IntPtr svcb, SVCBEventHandler handler, IntPtr parameter); + + ///// + ///// callback handler for SVCB events + ///// + ///// the related SVCB instance + ///// event type + ///// user defined parameter + //[UnmanagedFunctionPointer(CallingConvention.Cdecl)] + //public delegate void SVCBEventHandler(IntPtr svcb, int eventType, IntPtr parameter); + /// /// callback handler to control client read access to data attributes /// User provided callback function to control MMS client read access to IEC 61850 @@ -2416,6 +2490,41 @@ namespace IEC61850 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate MmsDataAccessError ReadAccessHandler(IntPtr ld, IntPtr ln, IntPtr dataObject, int fc, IntPtr connection, IntPtr parameter); + /// + /// Callback handler that is invoked when the active setting group is about to be changed by an external client. + /// This function is called BEFORE the active setting group is changed. The user can reject to change the active setting group by returning false. + /// + /// user provided parameter + /// sgcb the setting group control block of the setting group that is about to be changed + /// newActSg the new active setting group + /// connection the client connection that requests the change + /// true if the change is accepted, false otherwise + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool ActiveSettingGroupChangedHandler(IntPtr parameter, IntPtr sgcb, uint newActSg, IntPtr connection); + + /// + /// Callback handler that is invoked when the edit setting group is about to be changed by an external client. + /// In this function the user should update all SE data attributes associated with the given SettingGroupControlBlock. + /// This function is called BEFORE the active setting group is changed.The user can reject to change the + /// edit setting group by returning false. This can be used to implement RBAC. + /// + /// user provided parameter + /// the setting group control block of the setting group that is about to be changed + /// the new edit setting group + /// the client connection that requests the change + /// true if the change is accepted, false otherwise + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool EditSettingGroupChangedHandler(IntPtr parameter, IntPtr sgcb, uint newEditSg, IntPtr connection); + + /// + /// Callback handler that is invoked when the edit setting group has been confirmed by an external client. + /// + /// user provided parameter + /// the setting group control block of the setting group that is about to be changed + /// the edit setting group that has been confirmed + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void EditSettingGroupConfirmationHandler(IntPtr parameter, IntPtr sgcb, uint editSg); + /// /// Callback that is called when the client is calling a dataset operation (create, delete, read, write, list directory) /// his callback is called before the IedServer_RCBEventHandler and only in case of operations (RCB_EVENT_GET_PARAMETER, RCB_EVENT_SET_PARAMETER, RCB_EVENT_ENABLE @@ -2762,6 +2871,120 @@ namespace IEC61850 return false; } + //------------- Setting group + + public delegate bool InternalActiveSettingGroupChangedHandler(object parameter, SettingGroupControlBlock sgcb, uint newActSg, ClientConnection connection); + + private InternalActiveSettingGroupChangedHandler internalActiveSettingGroupChangedHandler = null; + + private object activeSettingGroupChangedHandlerParameter = null; + + private ActiveSettingGroupChangedHandler activeSettingGroupChangedHandler = null; + + + public void SetActiveSettingGroupChangedHandler(InternalActiveSettingGroupChangedHandler handler,SettingGroupControlBlock settingGroupControlBlock, object parameter) + { + internalActiveSettingGroupChangedHandler = handler; + activeSettingGroupChangedHandlerParameter = parameter; + + if (activeSettingGroupChangedHandler == null) + { + activeSettingGroupChangedHandler = new ActiveSettingGroupChangedHandler(InternalActiveSettingGroupChangedImplementation); + + IedServer_setActiveSettingGroupChangedHandler(self, settingGroupControlBlock.self, activeSettingGroupChangedHandler, IntPtr.Zero); + } + } + + private bool InternalActiveSettingGroupChangedImplementation(IntPtr parameter, IntPtr sgcb, uint newActSg, IntPtr connection) + { + if (sgcb != IntPtr.Zero && connection != IntPtr.Zero) + { + ClientConnection con = null; + + this.clientConnections.TryGetValue(connection, out con); + + return internalActiveSettingGroupChangedHandler(activeSettingGroupChangedHandlerParameter, new SettingGroupControlBlock(sgcb), newActSg, con); + } + + return false; + } + + //------------ + + //public delegate bool InternalSVCBEventHandler(SampledValuesControlBlock sampledValuesControlBlock, SMVEvent sMVEvent, object parameter); + + //private InternalSVCBEventHandler internalSVCBEventHandler = null; + + //private object sVCBEventHandlerParameter = null; + + //private SVCBEventHandler sVCBEventHandler = null; + + //internal class SVCHandlerInfo + //{ + // public SampledValuesControlBlock sampledValuesControlBlock = null; + // public GCHandle handle; + + // public InternalSVCBEventHandler internalSVCBEventHandler = null; + // public object svcHandlerParameter = null; + + // public SVCHandlerInfo(SampledValuesControlBlock sampledValuesControlBlock) + // { + // this.sampledValuesControlBlock = sampledValuesControlBlock; + // this.handle = GCHandle.Alloc(this); + // } + + // ~SVCHandlerInfo() + // { + // this.handle.Free(); + // } + //} + + //private Dictionary svcHandlers = new Dictionary(); + + //private SVCHandlerInfo GetSVCHandlerInfo(SampledValuesControlBlock sampledValuesControlBlock) + //{ + // SVCHandlerInfo info; + + // svcHandlers.TryGetValue(sampledValuesControlBlock, out info); + + // if (info == null) + // { + // info = new SVCHandlerInfo(sampledValuesControlBlock); + // svcHandlers.Add(sampledValuesControlBlock, info); + // } + + // return info; + //} + + //public void SetSVCBHandler(InternalSVCBEventHandler handler, SampledValuesControlBlock sampledValuesControlBlock, object parameter) + //{ + // SVCHandlerInfo info = GetSVCHandlerInfo(sampledValuesControlBlock); + + // info.internalSVCBEventHandler = handler; + // info.svcHandlerParameter = parameter; + + // if (sVCBEventHandler == null) + // sVCBEventHandler = new SVCBEventHandler(InternalSVCBEventHandlerImplementation); + + // IedServer_setSVCBHandler(self, sampledValuesControlBlock.Self, sVCBEventHandler, GCHandle.ToIntPtr(info.handle)); + //} + + //public enum SMVEvent + //{ + // IEC61850_SVCB_EVENT_ENABLE = 1, + // IEC61850_SVCB_EVENT_DISABLE = 0, + //} + + //private void InternalSVCBEventHandlerImplementation(IntPtr svcb, int eventType, IntPtr parameter) + //{ + // GCHandle handle = GCHandle.FromIntPtr(parameter); + + // SVCHandlerInfo info = (SVCHandlerInfo)handle.Target; + + // if (info != null && info.internalSVCBEventHandler != null) + // info.internalSVCBEventHandler(info.sampledValuesControlBlock,(SMVEvent)eventType, info.svcHandlerParameter); + //} + public delegate MmsDataAccessError InternalReadAccessHandler(LogicalDevice ld, LogicalNode ln, DataObject dataObject, FunctionalConstraint fc, ClientConnection connection, object parameter); private InternalReadAccessHandler internalReadAccessHandler = null; diff --git a/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs b/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs index dc481140..733c2fb1 100644 --- a/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs +++ b/dotnet/IEC61850forCSharp/SampledValuesControlBlock.cs @@ -96,8 +96,9 @@ namespace IEC61850 private bool isDisposed = false; + public IntPtr Self { get => self;} - internal SampledValuesControlBlock(IntPtr iedConnection, string objectReference) + internal SampledValuesControlBlock(IntPtr iedConnection, string objectReference) { self = ClientSVControlBlock_create (iedConnection, objectReference); this.objectReference = objectReference; diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index be4b400d..133714ea 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -13,6 +13,8 @@ using System.Net; using static IEC61850.Server.IedServer; using System.Collections.Generic; using System.Reflection.Metadata; +using IEC61850.Client; +using ReportControlBlock = IEC61850.Server.ReportControlBlock; namespace server_access_control { @@ -216,6 +218,34 @@ namespace server_access_control iedServer.SetDirectoryAccessHandler(directoryAccessHandler, null); + /* Handler for Sampled values control block + */ + + //void sVCBEventHandler(SampledValuesControlBlock svcb, SMVEvent smvEvent, object parameter) + //{ + // Console.WriteLine(svcb.GetNoASDU() + " event: "+ smvEvent.ToString() ); + //} + + //implement IedModel_getSVControlBlock && SVControlBlock + //SampledValuesControlBlock sampledValuesControlBlock = (SampledValuesControlBlock)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO1"); + + + //iedServer.SetSVCBHandler(sVCBEventHandler,) + + //SettingGroups + + LogicalDevice logicalDevice = (LogicalDevice)iedModel.GetModelNodeByShortObjectReference("GenericIO"); ; + SettingGroupControlBlock settingGroupControlBlock = logicalDevice.GetSettingGroupControlBlock(); + + bool activeSGChangedHandler(object parameter, SettingGroupControlBlock sgcb, uint newActSg, ClientConnection connection) + { + Console.WriteLine("Switch to setting group "+ newActSg +"\n"); + + return true; + } + + iedServer.SetActiveSettingGroupChangedHandler(activeSGChangedHandler, settingGroupControlBlock, null); + iedServer.Start(102); if (iedServer.IsRunning()) diff --git a/dotnet/server_example_access_control/model.cfg b/dotnet/server_example_access_control/model.cfg index 3c8b17ae..b3d21dc4 100644 --- a/dotnet/server_example_access_control/model.cfg +++ b/dotnet/server_example_access_control/model.cfg @@ -1,6 +1,7 @@ MODEL(simpleIO){ LD(GenericIO){ LN(LLN0){ +SG(1 5) DO(Mod 0){ DA(stVal 0 12 0 17 0)=1; DA(q 0 23 0 18 0); @@ -46,16 +47,34 @@ DE(GGIO1$MX$AnIn3$q); DE(GGIO1$MX$AnIn4$mag$f); DE(GGIO1$MX$AnIn4$q); } -RC(EventsRCB01 Events1 0 Events 1 24 175 50 1000); -RC(EventsRCBPreConf01 Events1 0 Events 1 24 175 50 1000); -RC(EventsBRCB01 Events2 1 Events 1 24 175 50 1000); -RC(EventsBRCBPreConf01 Events2 1 Events 1 24 175 50 1000); +DS(ServiceTracking){ +DE(LTRK1$SR$SpcTrk); +DE(LTRK1$SR$DpcTrk); +DE(LTRK1$SR$IncTrk); +DE(LTRK1$SR$BscTrk); +DE(LTRK1$SR$UrcbTrk); +DE(LTRK1$SR$BrcbTrk); +DE(LTRK1$SR$GocbTrk); +DE(LTRK1$SR$SgcbTrk); +DE(LTRK1$SR$LocbTrk); +} +RC(EventsRCB01 Events1 0 Events2 1 24 175 50 1000); RC(EventsIndexed01 Events2 0 Events 1 24 175 50 1000); RC(EventsIndexed02 Events2 0 Events 1 24 175 50 1000); RC(EventsIndexed03 Events2 0 Events 1 24 175 50 1000); RC(Measurements01 Measurements 1 Measurements 1 16 239 50 1000); RC(Measurements02 Measurements 1 Measurements 1 16 239 50 1000); RC(Measurements03 Measurements 1 Measurements 1 16 239 50 1000); +RC(brcbServiceTracking01 ServiceTracking 1 ServiceTracking 1 19 228 0 0); +RC(brcbServiceTracking02 ServiceTracking 1 ServiceTracking 1 19 228 0 0); +RC(brcbServiceTracking03 ServiceTracking 1 ServiceTracking 1 19 228 0 0); +LC(EventLog Events GenericIO/LLN0$EventLog 19 0 1 0); +LC(GeneralLog - - 19 0 0 0); +LOG(); +LOG(EventLog); +GC(gcbEvents events Events 3 0 1000 3000){ +PA(4 1 1000 010CCD010001); +} } LN(LPHD1){ DO(PhyNam 0){ @@ -216,5 +235,241 @@ DA(q 0 23 0 18 0); DA(t 0 22 0 16 0); } } +LN(PTOC1){ +DO(Mod 0){ +DA(stVal 0 12 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +DA(ctlModel 0 12 4 16 0); +} +DO(Beh 0){ +DA(stVal 0 12 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Str 0){ +DA(general 0 0 0 17 0); +DA(dirGeneral 0 12 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Op 0){ +DA(general 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(StrVal 0){ +DA(setMag 0 27 7 16 0){ +DA(f 0 10 7 16 0); +} +} +DO(OpDlTmms 0){ +DA(setVal 0 3 7 17 0); +} +DO(RsDlTmms 0){ +DA(setVal 0 3 7 17 0); +} +DO(RstTms 0){ +DA(setVal 0 3 7 17 0); +} +} +LN(LTRK1){ +DO(Beh 0){ +DA(stVal 0 12 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(SpcTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(ctlVal 0 0 8 16 0); +DA(origin 0 27 8 16 0){ +DA(orCat 0 12 8 16 0); +DA(orIdent 0 13 8 16 0); +} +DA(ctlNum 0 6 8 16 0); +DA(T 0 22 8 16 0); +DA(Test 0 0 8 16 0); +DA(Check 0 24 8 16 0); +DA(respAddCause 0 12 8 16 0); +} +DO(DpcTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(ctlVal 0 0 8 16 0); +DA(origin 0 27 8 16 0){ +DA(orCat 0 12 8 16 0); +DA(orIdent 0 13 8 16 0); +} +DA(ctlNum 0 6 8 16 0); +DA(T 0 22 8 16 0); +DA(Test 0 0 8 16 0); +DA(Check 0 24 8 16 0); +DA(respAddCause 0 12 8 16 0); +} +DO(IncTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(ctlVal 0 3 8 16 0); +DA(origin 0 27 8 16 0){ +DA(orCat 0 12 8 16 0); +DA(orIdent 0 13 8 16 0); +} +DA(ctlNum 0 6 8 16 0); +DA(T 0 22 8 16 0); +DA(Test 0 0 8 16 0); +DA(Check 0 24 8 16 0); +DA(respAddCause 0 12 8 16 0); +} +DO(BscTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(ctlVal 0 25 8 16 0); +DA(origin 0 27 8 16 0){ +DA(orCat 0 12 8 16 0); +DA(orIdent 0 13 8 16 0); +} +DA(ctlNum 0 6 8 16 0); +DA(T 0 22 8 16 0); +DA(Test 0 0 8 16 0); +DA(Check 0 24 8 16 0); +DA(respAddCause 0 12 8 16 0); +} +DO(UrcbTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(rptID 0 19 8 16 0); +DA(rptEna 0 0 8 16 0); +DA(resv 0 0 8 16 0); +DA(datSet 0 31 8 16 0); +DA(confRev 0 9 8 16 0); +DA(optFlds 0 26 8 16 0); +DA(bufTm 0 9 8 16 0); +DA(sqNum 0 6 8 16 0); +DA(trgOps 0 26 8 16 0); +DA(intgPd 0 9 8 16 0); +DA(gi 0 0 8 16 0); +} +DO(BrcbTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(rptID 0 19 8 16 0); +DA(rptEna 0 0 8 16 0); +DA(datSet 0 31 8 16 0); +DA(confRev 0 9 8 16 0); +DA(optFlds 0 26 8 16 0); +DA(bufTm 0 9 8 16 0); +DA(sqNum 0 7 8 16 0); +DA(trgOps 0 26 8 16 0); +DA(intgPd 0 9 8 16 0); +DA(gi 0 0 8 16 0); +DA(purgeBuf 0 0 8 16 0); +DA(entryID 0 15 8 16 0); +DA(timeOfEntry 0 28 8 16 0); +DA(resvTms 0 2 8 16 0); +} +DO(GocbTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(goEna 0 0 8 16 0); +DA(goID 0 19 8 16 0); +DA(datSet 0 31 8 16 0); +DA(confRev 0 9 8 16 0); +DA(ndsCom 0 0 8 16 0); +DA(dstAddress 0 29 8 16 0); +} +DO(SgcbTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(numOfSG 0 6 8 16 0); +DA(actSG 0 6 8 16 0); +DA(editSG 0 6 8 16 0); +DA(cnfEdit 0 0 8 16 0); +DA(lActTm 0 22 8 16 0); +DA(resvTms 0 7 8 16 0); +} +DO(LocbTrk 0){ +DA(objRef 0 31 8 20 0); +DA(serviceType 0 12 8 16 0); +DA(errorCode 0 12 8 16 0); +DA(originatorID 0 13 8 16 0); +DA(t 0 22 8 16 0); +DA(d 0 20 5 16 0); +DA(dU 0 21 5 16 0); +DA(cdcNs 0 20 11 16 0); +DA(cdcName 0 20 11 16 0); +DA(dataNs 0 20 11 16 0); +DA(logEna 0 0 8 16 0); +DA(datSet 0 31 8 16 0); +DA(bufTm 0 9 8 16 0); +DA(trgOps 0 26 8 16 0); +DA(intgPd 0 9 8 16 0); +DA(logRef 0 31 8 16 0); +} +} } } From f3f1ef5cc447a3f2620b8ce9e07c295cd68513f2 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Sun, 13 Jul 2025 20:48:07 +0200 Subject: [PATCH 06/14] ->SetEditSettingGroupChangedHandler --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 36 ++++++++++++++++++- .../server_example_access_control/Program.cs | 8 +++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 2cb4e574..1f0e8bc4 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -2881,7 +2881,6 @@ namespace IEC61850 private ActiveSettingGroupChangedHandler activeSettingGroupChangedHandler = null; - public void SetActiveSettingGroupChangedHandler(InternalActiveSettingGroupChangedHandler handler,SettingGroupControlBlock settingGroupControlBlock, object parameter) { internalActiveSettingGroupChangedHandler = handler; @@ -2909,6 +2908,41 @@ namespace IEC61850 return false; } + public delegate bool InternalEditSettingGroupChangedHandler(object parameter, SettingGroupControlBlock sgcb, uint newEditSg, ClientConnection connection); + + private InternalEditSettingGroupChangedHandler internalEditSettingGroupChangedHandler = null; + + private object editSettingGroupChangedHandlerParameter = null; + + private EditSettingGroupChangedHandler editSettingGroupChangedHandler = null; + + public void SetEditSettingGroupChangedHandler(InternalEditSettingGroupChangedHandler handler, SettingGroupControlBlock settingGroupControlBlock, object parameter) + { + internalEditSettingGroupChangedHandler = handler; + editSettingGroupChangedHandlerParameter = parameter; + + if (editSettingGroupChangedHandler == null) + { + editSettingGroupChangedHandler = new EditSettingGroupChangedHandler(InternalEditSettingGroupChangedImplementation); + + IedServer_setEditSettingGroupChangedHandler(self, settingGroupControlBlock.self, editSettingGroupChangedHandler, IntPtr.Zero); + } + } + + private bool InternalEditSettingGroupChangedImplementation(IntPtr parameter, IntPtr sgcb, uint newEditSg, IntPtr connection) + { + if (sgcb != IntPtr.Zero && connection != IntPtr.Zero) + { + ClientConnection con = null; + + this.clientConnections.TryGetValue(connection, out con); + + return internalEditSettingGroupChangedHandler(editSettingGroupChangedHandlerParameter, new SettingGroupControlBlock(sgcb), newEditSg, con); + } + + return false; + } + //------------ //public delegate bool InternalSVCBEventHandler(SampledValuesControlBlock sampledValuesControlBlock, SMVEvent sMVEvent, object parameter); diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index 133714ea..011948ad 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -244,7 +244,15 @@ namespace server_access_control return true; } + bool editSGChangedHandler(object parameter, SettingGroupControlBlock sgcb, uint newEditSg, ClientConnection connection) + { + Console.WriteLine("Set edit setting group to " + newEditSg + "\n"); + + return true; + } + iedServer.SetActiveSettingGroupChangedHandler(activeSGChangedHandler, settingGroupControlBlock, null); + iedServer.SetEditSettingGroupChangedHandler(editSGChangedHandler, settingGroupControlBlock, null); iedServer.Start(102); From ba9c255aa05deb1e22fb6214b0cd34c093279dc6 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Sun, 13 Jul 2025 20:54:36 +0200 Subject: [PATCH 07/14] ->SetEditSettingGroupConfirmationHandler --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 30 +++++++++++++++++++ .../server_example_access_control/Program.cs | 6 ++++ 2 files changed, 36 insertions(+) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 1f0e8bc4..183df204 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -2943,6 +2943,36 @@ namespace IEC61850 return false; } + public delegate void InternalEditSettingGroupConfirmationHandler(object parameter, SettingGroupControlBlock sgcb, uint editSg); + + private InternalEditSettingGroupConfirmationHandler internalEditSettingGroupConfirmationHandler = null; + + private object editSettingGroupConfirmationHandlerParameter = null; + + private EditSettingGroupConfirmationHandler editSettingGroupConfirmationHandler = null; + + public void SetEditSettingGroupConfirmationHandler(InternalEditSettingGroupConfirmationHandler handler, SettingGroupControlBlock settingGroupControlBlock, object parameter) + { + internalEditSettingGroupConfirmationHandler = handler; + editSettingGroupConfirmationHandlerParameter = parameter; + + if (editSettingGroupConfirmationHandler == null) + { + editSettingGroupConfirmationHandler = new EditSettingGroupConfirmationHandler(InternalEditSettingGroupConfirmationImplementation); + + IedServer_setEditSettingGroupConfirmationHandler(self, settingGroupControlBlock.self, editSettingGroupConfirmationHandler, IntPtr.Zero); + } + } + + private void InternalEditSettingGroupConfirmationImplementation(IntPtr parameter, IntPtr sgcb, uint editSg) + { + if (sgcb != IntPtr.Zero) + { + internalEditSettingGroupConfirmationHandler(editSettingGroupChangedHandlerParameter, new SettingGroupControlBlock(sgcb), editSg); + } + } + + //------------ //public delegate bool InternalSVCBEventHandler(SampledValuesControlBlock sampledValuesControlBlock, SMVEvent sMVEvent, object parameter); diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index 011948ad..e565c011 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -251,8 +251,14 @@ namespace server_access_control return true; } + void editSGConfirmationHandler(object parameter, SettingGroupControlBlock sgcb, uint editSg) + { + Console.WriteLine("Received edit sg confirm for sg " + editSg + "\n"); + } + iedServer.SetActiveSettingGroupChangedHandler(activeSGChangedHandler, settingGroupControlBlock, null); iedServer.SetEditSettingGroupChangedHandler(editSGChangedHandler, settingGroupControlBlock, null); + iedServer.SetEditSettingGroupConfirmationHandler(editSGConfirmationHandler, settingGroupControlBlock, null); iedServer.Start(102); From db0f5b5c9b0a8b115cbc7c571de47185d632597c Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Wed, 16 Jul 2025 11:28:24 +0200 Subject: [PATCH 08/14] Add LoadActiveSgValues on control example --- .../server_example_access_control/Program.cs | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index e565c011..9b4a3c5a 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -20,6 +20,14 @@ namespace server_access_control { class MainClass { + struct PTOC1Settings + { + public float strVal; + public int opDlTmms; + public int rsDlTmms; + public int rstTms; + } + public static void Main(string[] args) { bool running = true; @@ -132,15 +140,8 @@ namespace server_access_control /* Install handler to control access to control blocks (RCBs, LCBs, GoCBs, SVCBs, SGCBs)*/ bool ControlBlockAccessCallBack(object parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice ld, LogicalNode ln, string objectName, string subObjectName, ControlBlockAccessType accessType) { - IedServer iedServer1 = parameter as IedServer; - Console.WriteLine(acsiClass.ToString() + " "+ accessType.ToString() + " access " + ld.GetName() + ln.GetName() +"/"+ objectName + "." + subObjectName); - if (objectName == "EventsIndexed03") - return false; - - - Console.WriteLine("Control block access callback"); return true; } @@ -237,10 +238,29 @@ namespace server_access_control LogicalDevice logicalDevice = (LogicalDevice)iedModel.GetModelNodeByShortObjectReference("GenericIO"); ; SettingGroupControlBlock settingGroupControlBlock = logicalDevice.GetSettingGroupControlBlock(); + List ptoc1Settings = new List(); + ptoc1Settings.Add(new PTOC1Settings { strVal = 1.0f, opDlTmms = 500, rsDlTmms = 500, rstTms = 500 }); + ptoc1Settings.Add(new PTOC1Settings { strVal = 2.0f, opDlTmms = 1500, rsDlTmms = 2500, rstTms = 750 }); + ptoc1Settings.Add(new PTOC1Settings { strVal = 3.0f, opDlTmms = 500, rsDlTmms = 1500, rstTms = 750 }); + ptoc1Settings.Add(new PTOC1Settings { strVal = 3.5f, opDlTmms = 1250, rsDlTmms = 1750, rstTms = 500 }); + ptoc1Settings.Add(new PTOC1Settings { strVal = 3.75f, opDlTmms = 1250, rsDlTmms = 1750, rstTms = 750 }); + + void LoadActiveSgValues(int actSG) + { + DataAttribute dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.StrVal.setMag.f"); + iedServer.UpdateFloatAttributeValue(dataAttribute, ptoc1Settings[actSG - 1].strVal); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.OpDlTmms.setVal"); + iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].opDlTmms); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RsDlTmms.setVal"); + iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].rsDlTmms); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RstTms.setVal"); + iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].rstTms); + } + bool activeSGChangedHandler(object parameter, SettingGroupControlBlock sgcb, uint newActSg, ClientConnection connection) { Console.WriteLine("Switch to setting group "+ newActSg +"\n"); - + LoadActiveSgValues(Convert.ToInt32(newActSg)); return true; } From bfa4631eea81c09fe6c8b5c6a7ca6ed2e3bbe483 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Wed, 16 Jul 2025 17:49:13 +0200 Subject: [PATCH 09/14] =?UTF-8?q?->Add=20=C2=96GetChildWithFc=20on=20DataO?= =?UTF-8?q?bject=20model,=20so=20we=20can=20get=20the=20DA=20fc=3DSE=20for?= =?UTF-8?q?=20the=20SGCB=20handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 26 ++++++++++++++++ .../server_example_access_control/Program.cs | 30 ++++++++++++++++--- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 183df204..6d237e28 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -26,6 +26,7 @@ using System.Data; using System.Data.Common; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Security.Cryptography; using System.Xml.Linq; using IEC61850.Client; using IEC61850.Common; @@ -927,6 +928,9 @@ namespace IEC61850 { [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr DataObject_create(string name, IntPtr parent, int arrayElements); + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr ModelNode_getChildWithFc(IntPtr self, string objectReference, int fc); internal DataObject(IntPtr self, ModelNode parent) : base(self) { @@ -944,6 +948,28 @@ namespace IEC61850 self = DataObject_create (name, parent.self, arrayElements); } + /// + /// return a child model node with a given functional constraint + /// Sometimes the name is not enough to identify a model node.This is the case when + /// editable setting groups are used.In this case the setting group members have two different + /// model nodes associated that differ in their FC (SG and SE). + /// + /// the name of the child model node + /// the functional constraint of the model node + /// the model node instance or NULL if model node does not exist. + public DataAttribute GetChildWithFc(string objRef, FunctionalConstraint fc) + { + DataAttribute dataAttribute = null; + IntPtr da = ModelNode_getChildWithFc(this.self, objRef, (int)fc); + + if (da != IntPtr.Zero) + { + dataAttribute = new DataAttribute(da, this); + } + + return dataAttribute; + } + } public class DataAttribute : ModelNode diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index 9b4a3c5a..7a01724f 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -247,20 +247,40 @@ namespace server_access_control void LoadActiveSgValues(int actSG) { + DataAttribute dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.StrVal.setMag.f"); iedServer.UpdateFloatAttributeValue(dataAttribute, ptoc1Settings[actSG - 1].strVal); - dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.OpDlTmms.setVal"); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.opDlTmms.setVal"); iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].opDlTmms); - dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RsDlTmms.setVal"); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.rsDlTmms.setVal"); iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].rsDlTmms); - dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RstTms.setVal"); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.rstTms.setVal"); iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].rstTms); } + void LoadEditSgValues(int actSG) + { + DataObject strVal = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.StrVal"); + DataAttribute setMagF = strVal.GetChildWithFc("setMag.f", FunctionalConstraint.SE); + iedServer.UpdateFloatAttributeValue(setMagF, ptoc1Settings[actSG - 1].strVal); + + DataObject opDlTmms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.OpDlTmms"); + DataAttribute setVal = opDlTmms.GetChildWithFc("setVal", FunctionalConstraint.SE); + iedServer.UpdateInt32AttributeValue(setVal, ptoc1Settings[actSG - 1].opDlTmms); + + DataObject rsDlTmms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RsDlTmms"); + DataAttribute rsDlTmms_setVal = rsDlTmms.GetChildWithFc("setVal", FunctionalConstraint.SE); + iedServer.UpdateInt32AttributeValue(rsDlTmms_setVal, ptoc1Settings[actSG - 1].rsDlTmms); + + DataObject rstTms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RstTms"); + DataAttribute rstTms_setVal = rstTms.GetChildWithFc("setVal", FunctionalConstraint.SE); + iedServer.UpdateInt32AttributeValue(rstTms_setVal, ptoc1Settings[actSG - 1].rstTms); + } + bool activeSGChangedHandler(object parameter, SettingGroupControlBlock sgcb, uint newActSg, ClientConnection connection) { Console.WriteLine("Switch to setting group "+ newActSg +"\n"); - LoadActiveSgValues(Convert.ToInt32(newActSg)); + return true; } @@ -268,6 +288,8 @@ namespace server_access_control { Console.WriteLine("Set edit setting group to " + newEditSg + "\n"); + LoadEditSgValues(Convert.ToInt32(newEditSg)); + return true; } From cc4f25d1228fee0ea5b4afcc10e1cf129494e331 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Wed, 16 Jul 2025 18:06:16 +0200 Subject: [PATCH 10/14] -> Add wrapper for GetActiveSettingGroupChangedHandler --- dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 14 ++++++++ .../server_example_access_control/Program.cs | 32 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 6d237e28..98779da5 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -2461,6 +2461,15 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] public static extern void IedServer_setActiveSettingGroupChangedHandler(IntPtr self, IntPtr sgcb, ActiveSettingGroupChangedHandler handler, IntPtr parameter); + /// + /// Get the active setting group number + /// + /// the instance of IedServer to operate on + /// the handle of the setting group control block of the setting group + /// the number of the active setting group + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern uint IedServer_getActiveSettingGroup(IntPtr self, IntPtr sgcb); + /// /// Set the callback handler for the SetEditSG event /// @@ -2920,6 +2929,11 @@ namespace IEC61850 } } + public int GetActiveSettingGroupChangedHandler(SettingGroupControlBlock settingGroupControlBlock) + { + return Convert.ToInt32(IedServer_getActiveSettingGroup(self, settingGroupControlBlock.self)); + } + private bool InternalActiveSettingGroupChangedImplementation(IntPtr parameter, IntPtr sgcb, uint newActSg, IntPtr connection) { if (sgcb != IntPtr.Zero && connection != IntPtr.Zero) diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index 7a01724f..630c437e 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -20,7 +20,7 @@ namespace server_access_control { class MainClass { - struct PTOC1Settings + class PTOC1Settings { public float strVal; public int opDlTmms; @@ -281,6 +281,8 @@ namespace server_access_control { Console.WriteLine("Switch to setting group "+ newActSg +"\n"); + LoadActiveSgValues(Convert.ToInt32(newActSg)); + return true; } @@ -296,6 +298,34 @@ namespace server_access_control void editSGConfirmationHandler(object parameter, SettingGroupControlBlock sgcb, uint editSg) { Console.WriteLine("Received edit sg confirm for sg " + editSg + "\n"); + + int edit = Convert.ToInt32(editSg); + + DataObject strVal = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.StrVal"); + DataAttribute setMagF = strVal.GetChildWithFc("setMag.f", FunctionalConstraint.SE); + MmsValue setMagFValue = iedServer.GetAttributeValue(setMagF); + + DataObject opDlTmms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.OpDlTmms"); + DataAttribute setVal = opDlTmms.GetChildWithFc("setVal", FunctionalConstraint.SE); + MmsValue setValValue = iedServer.GetAttributeValue(setVal); + + DataObject rsDlTmms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RsDlTmms"); + DataAttribute rsDlTmmsSetVal = rsDlTmms.GetChildWithFc("setVal", FunctionalConstraint.SE); + MmsValue rsDlTmmsSetValValue = iedServer.GetAttributeValue(rsDlTmmsSetVal); + + DataObject rstTms = (DataObject)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RstTms"); + DataAttribute rstTmsSetVal = rstTms.GetChildWithFc("setVal", FunctionalConstraint.SE); + MmsValue rstTmsSetValVaue = iedServer.GetAttributeValue(rstTmsSetVal); + + ptoc1Settings[edit - 1].strVal = setMagFValue.ToFloat(); + ptoc1Settings[edit - 1].opDlTmms = setValValue.ToInt32(); + ptoc1Settings[edit - 1].rsDlTmms = rsDlTmmsSetValValue.ToInt32(); + ptoc1Settings[edit - 1].rstTms = rsDlTmmsSetValValue.ToInt32(); + + if (iedServer.GetActiveSettingGroupChangedHandler(sgcb) == edit) + { + LoadActiveSgValues(edit); + } } iedServer.SetActiveSettingGroupChangedHandler(activeSGChangedHandler, settingGroupControlBlock, null); From 5c6ef51159fe971fb60053f2dddee669cf1ee549 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Fri, 18 Jul 2025 11:45:18 +0200 Subject: [PATCH 11/14] ->Add ICD test file example to .net Tools; ->Add IEC61850Model/SVControlBlock.cs; ->Add SVCBHandler to .net --- .../IEC61850Model/SVControlBlock.cs | 57 +++++++ dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs | 123 +++++++------- .../server_example_access_control/Program.cs | 25 ++- .../server_example_access_control/model.cfg | 155 +++++++++--------- .../server_example_access_control.csproj | 6 +- .../model_generator_dotnet/Tools/Tools.csproj | 4 + 6 files changed, 221 insertions(+), 149 deletions(-) create mode 100644 dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs diff --git a/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs b/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs new file mode 100644 index 00000000..b1dce114 --- /dev/null +++ b/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs @@ -0,0 +1,57 @@ +using IEC61850.Server; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +// IEC 61850 API for the libiec61850 .NET wrapper library +namespace IEC61850 +{ + // IEC 61850 server API. + namespace Model + { + public enum SMVEvent + { + IEC61850_SVCB_EVENT_ENABLE = 1, + IEC61850_SVCB_EVENT_DISABLE = 0, + } + + public class SVControlBlock : ModelNode + { + private IntPtr self = IntPtr.Zero; + public IedModel parent { get; } + internal IntPtr Self { get => self; } + + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr SVControlBlock_create(string name, IntPtr parent, string svID,string dataSet, UInt32 confRev, uint smpMod, + UInt16 smpRate, uint optFlds, bool isUnicast); + + /// + /// create a new Multicast/Unicast Sampled Value (SV) control block (SvCB) + /// Create a new Sampled Value control block(SvCB) and add it to the given logical node(LN) + /// + /// name of the SvCB relative to the parent LN + /// the parent LN + /// the application ID of the SvCB + /// the data set reference to be used by the SVCB + /// the configuration revision + /// the sampling mode used + /// the sampling rate used + /// + /// the optional element configuration + public SVControlBlock(string name, IedModel parent, string svID, string dataSet, UInt32 confRev, uint smpMod, + UInt16 smpRate, uint optFlds, bool isUnicast) + { + this.self = SVControlBlock_create(name, parent.self, svID, dataSet, confRev, smpMod, smpRate, optFlds, isUnicast); + this.parent = parent; + } + + public SVControlBlock(IntPtr self) + { + this.self = self; + } + + } + } + +} diff --git a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs index 98779da5..e971c547 100644 --- a/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs +++ b/dotnet/IEC61850forCSharp/IEC61850ServerAPI.cs @@ -30,6 +30,7 @@ using System.Security.Cryptography; using System.Xml.Linq; using IEC61850.Client; using IEC61850.Common; +using IEC61850.Model; using IEC61850.TLS; using static System.Collections.Specialized.BitVector32; using static System.Net.Mime.MediaTypeNames; @@ -86,6 +87,9 @@ namespace IEC61850 [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] static extern IntPtr IedModel_getDeviceByInst(IntPtr self, string ldInst); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr IedModel_getSVControlBlock(IntPtr self, IntPtr parentLN, string svcbName); + internal IntPtr self = IntPtr.Zero; internal IedModel(IntPtr self) @@ -306,6 +310,16 @@ namespace IEC61850 return GetModelNodeFromNodeRef (nodeRef); } + public SVControlBlock GetSVControlBlock(LogicalNode logicalNode, string svcbName) + { + IntPtr nodeRef = IedModel_getSVControlBlock(self, logicalNode.self, svcbName); + + if (nodeRef == IntPtr.Zero) + return null; + + return new SVControlBlock(nodeRef); + } + } /// @@ -2497,8 +2511,8 @@ namespace IEC61850 ///// the SVCB control block instance ///// the event handler to be used ///// user provided parameter that is passed to the handler - //[DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] - //public static extern void IedServer_setSVCBHandler(IntPtr self, IntPtr svcb, SVCBEventHandler handler, IntPtr parameter); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + public static extern void IedServer_setSVCBHandler(IntPtr self, IntPtr svcb, SVCBEventHandler handler, IntPtr parameter); ///// ///// callback handler for SVCB events @@ -2506,8 +2520,8 @@ namespace IEC61850 ///// the related SVCB instance ///// event type ///// user defined parameter - //[UnmanagedFunctionPointer(CallingConvention.Cdecl)] - //public delegate void SVCBEventHandler(IntPtr svcb, int eventType, IntPtr parameter); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void SVCBEventHandler(IntPtr svcb, int eventType, IntPtr parameter); /// /// callback handler to control client read access to data attributes @@ -3015,79 +3029,74 @@ namespace IEC61850 //------------ - //public delegate bool InternalSVCBEventHandler(SampledValuesControlBlock sampledValuesControlBlock, SMVEvent sMVEvent, object parameter); + public delegate void InternalSVCBEventHandler(SVControlBlock sampledValuesControlBlock, SMVEvent sMVEvent, object parameter); - //private InternalSVCBEventHandler internalSVCBEventHandler = null; + private InternalSVCBEventHandler internalSVCBEventHandler = null; - //private object sVCBEventHandlerParameter = null; + private object sVCBEventHandlerParameter = null; - //private SVCBEventHandler sVCBEventHandler = null; + private SVCBEventHandler sVCBEventHandler = null; - //internal class SVCHandlerInfo - //{ - // public SampledValuesControlBlock sampledValuesControlBlock = null; - // public GCHandle handle; + internal class SVCHandlerInfo + { + public SVControlBlock sampledValuesControlBlock = null; + public GCHandle handle; - // public InternalSVCBEventHandler internalSVCBEventHandler = null; - // public object svcHandlerParameter = null; + public InternalSVCBEventHandler internalSVCBEventHandler = null; + public object svcHandlerParameter = null; - // public SVCHandlerInfo(SampledValuesControlBlock sampledValuesControlBlock) - // { - // this.sampledValuesControlBlock = sampledValuesControlBlock; - // this.handle = GCHandle.Alloc(this); - // } + public SVCHandlerInfo(SVControlBlock sampledValuesControlBlock) + { + this.sampledValuesControlBlock = sampledValuesControlBlock; + this.handle = GCHandle.Alloc(this); + } - // ~SVCHandlerInfo() - // { - // this.handle.Free(); - // } - //} + ~SVCHandlerInfo() + { + this.handle.Free(); + } + } - //private Dictionary svcHandlers = new Dictionary(); + private Dictionary svcHandlers = new Dictionary(); - //private SVCHandlerInfo GetSVCHandlerInfo(SampledValuesControlBlock sampledValuesControlBlock) - //{ - // SVCHandlerInfo info; + private SVCHandlerInfo GetSVCHandlerInfo(SVControlBlock sampledValuesControlBlock) + { + SVCHandlerInfo info; - // svcHandlers.TryGetValue(sampledValuesControlBlock, out info); + svcHandlers.TryGetValue(sampledValuesControlBlock, out info); - // if (info == null) - // { - // info = new SVCHandlerInfo(sampledValuesControlBlock); - // svcHandlers.Add(sampledValuesControlBlock, info); - // } + if (info == null) + { + info = new SVCHandlerInfo(sampledValuesControlBlock); + svcHandlers.Add(sampledValuesControlBlock, info); + } - // return info; - //} + return info; + } - //public void SetSVCBHandler(InternalSVCBEventHandler handler, SampledValuesControlBlock sampledValuesControlBlock, object parameter) - //{ - // SVCHandlerInfo info = GetSVCHandlerInfo(sampledValuesControlBlock); + public void SetSVCBHandler(InternalSVCBEventHandler handler, SVControlBlock sampledValuesControlBlock, object parameter) + { + SVCHandlerInfo info = GetSVCHandlerInfo(sampledValuesControlBlock); - // info.internalSVCBEventHandler = handler; - // info.svcHandlerParameter = parameter; + info.internalSVCBEventHandler = handler; + info.svcHandlerParameter = parameter; - // if (sVCBEventHandler == null) - // sVCBEventHandler = new SVCBEventHandler(InternalSVCBEventHandlerImplementation); + if (sVCBEventHandler == null) + sVCBEventHandler = new SVCBEventHandler(InternalSVCBEventHandlerImplementation); - // IedServer_setSVCBHandler(self, sampledValuesControlBlock.Self, sVCBEventHandler, GCHandle.ToIntPtr(info.handle)); - //} + IedServer_setSVCBHandler(self, sampledValuesControlBlock.Self, sVCBEventHandler, GCHandle.ToIntPtr(info.handle)); + } - //public enum SMVEvent - //{ - // IEC61850_SVCB_EVENT_ENABLE = 1, - // IEC61850_SVCB_EVENT_DISABLE = 0, - //} - //private void InternalSVCBEventHandlerImplementation(IntPtr svcb, int eventType, IntPtr parameter) - //{ - // GCHandle handle = GCHandle.FromIntPtr(parameter); + private void InternalSVCBEventHandlerImplementation(IntPtr svcb, int eventType, IntPtr parameter) + { + GCHandle handle = GCHandle.FromIntPtr(parameter); - // SVCHandlerInfo info = (SVCHandlerInfo)handle.Target; + SVCHandlerInfo info = (SVCHandlerInfo)handle.Target; - // if (info != null && info.internalSVCBEventHandler != null) - // info.internalSVCBEventHandler(info.sampledValuesControlBlock,(SMVEvent)eventType, info.svcHandlerParameter); - //} + if (info != null && info.internalSVCBEventHandler != null) + info.internalSVCBEventHandler(info.sampledValuesControlBlock, (SMVEvent)eventType, info.svcHandlerParameter); + } public delegate MmsDataAccessError InternalReadAccessHandler(LogicalDevice ld, LogicalNode ln, DataObject dataObject, FunctionalConstraint fc, ClientConnection connection, object parameter); diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index 630c437e..a936d0a2 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -15,6 +15,7 @@ using System.Collections.Generic; using System.Reflection.Metadata; using IEC61850.Client; using ReportControlBlock = IEC61850.Server.ReportControlBlock; +using IEC61850.Model; namespace server_access_control { @@ -222,18 +223,16 @@ namespace server_access_control /* Handler for Sampled values control block */ - //void sVCBEventHandler(SampledValuesControlBlock svcb, SMVEvent smvEvent, object parameter) - //{ - // Console.WriteLine(svcb.GetNoASDU() + " event: "+ smvEvent.ToString() ); - //} - - //implement IedModel_getSVControlBlock && SVControlBlock - //SampledValuesControlBlock sampledValuesControlBlock = (SampledValuesControlBlock)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1.SPCSO1"); - + void SVCBEventHandler(SVControlBlock svcb, SMVEvent smvEvent, object parameter) + { + Console.WriteLine("SVControlBlock event " + smvEvent.ToString()); + } - //iedServer.SetSVCBHandler(sVCBEventHandler,) + //LogicalNode logicalNode = (LogicalNode)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1"); + //SVControlBlock sampledValuesControlBlock = iedModel.GetSVControlBlock(logicalNode, "MSVCB01"); + //iedServer.SetSVCBHandler(SVCBEventHandler, sampledValuesControlBlock, null); - //SettingGroups + /*SettingGroups*/ LogicalDevice logicalDevice = (LogicalDevice)iedModel.GetModelNodeByShortObjectReference("GenericIO"); ; SettingGroupControlBlock settingGroupControlBlock = logicalDevice.GetSettingGroupControlBlock(); @@ -250,11 +249,11 @@ namespace server_access_control DataAttribute dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.StrVal.setMag.f"); iedServer.UpdateFloatAttributeValue(dataAttribute, ptoc1Settings[actSG - 1].strVal); - dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.opDlTmms.setVal"); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.OpDlTmms.setVal"); iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].opDlTmms); - dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.rsDlTmms.setVal"); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RsDlTmms.setVal"); iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].rsDlTmms); - dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.rstTms.setVal"); + dataAttribute = (DataAttribute)iedModel.GetModelNodeByShortObjectReference("GenericIO/PTOC1.RstTms.setVal"); iedServer.UpdateInt32AttributeValue(dataAttribute, ptoc1Settings[actSG - 1].rstTms); } diff --git a/dotnet/server_example_access_control/model.cfg b/dotnet/server_example_access_control/model.cfg index b3d21dc4..ad0b6472 100644 --- a/dotnet/server_example_access_control/model.cfg +++ b/dotnet/server_example_access_control/model.cfg @@ -1,81 +1,5 @@ MODEL(simpleIO){ LD(GenericIO){ -LN(LLN0){ -SG(1 5) -DO(Mod 0){ -DA(stVal 0 12 0 17 0)=1; -DA(q 0 23 0 18 0); -DA(t 0 22 0 16 0); -DA(ctlModel 0 12 4 16 0)=0; -} -DO(Beh 0){ -DA(stVal 0 12 0 17 0)=1; -DA(q 0 23 0 18 0); -DA(t 0 22 0 16 0); -} -DO(Health 0){ -DA(stVal 0 12 0 17 0)=1; -DA(q 0 23 0 18 0); -DA(t 0 22 0 16 0); -} -DO(NamPlt 0){ -DA(vendor 0 20 5 16 0)="MZ Automation"; -DA(swRev 0 20 5 16 0)="1.3.0"; -DA(d 0 20 5 16 0)="libiec61850 server example"; -DA(configRev 0 20 5 16 0); -DA(ldNs 0 20 11 16 0); -} -DS(Events){ -DE(GGIO1$ST$SPCSO1$stVal); -DE(GGIO1$ST$SPCSO2$stVal); -DE(GGIO1$ST$SPCSO3$stVal); -DE(GGIO1$ST$SPCSO4$stVal); -} -DS(Events2){ -DE(GGIO1$ST$SPCSO1); -DE(GGIO1$ST$SPCSO2); -DE(GGIO1$ST$SPCSO3); -DE(GGIO1$ST$SPCSO4); -} -DS(Measurements){ -DE(GGIO1$MX$AnIn1$mag$f); -DE(GGIO1$MX$AnIn1$q); -DE(GGIO1$MX$AnIn2$mag$f); -DE(GGIO1$MX$AnIn2$q); -DE(GGIO1$MX$AnIn3$mag$f); -DE(GGIO1$MX$AnIn3$q); -DE(GGIO1$MX$AnIn4$mag$f); -DE(GGIO1$MX$AnIn4$q); -} -DS(ServiceTracking){ -DE(LTRK1$SR$SpcTrk); -DE(LTRK1$SR$DpcTrk); -DE(LTRK1$SR$IncTrk); -DE(LTRK1$SR$BscTrk); -DE(LTRK1$SR$UrcbTrk); -DE(LTRK1$SR$BrcbTrk); -DE(LTRK1$SR$GocbTrk); -DE(LTRK1$SR$SgcbTrk); -DE(LTRK1$SR$LocbTrk); -} -RC(EventsRCB01 Events1 0 Events2 1 24 175 50 1000); -RC(EventsIndexed01 Events2 0 Events 1 24 175 50 1000); -RC(EventsIndexed02 Events2 0 Events 1 24 175 50 1000); -RC(EventsIndexed03 Events2 0 Events 1 24 175 50 1000); -RC(Measurements01 Measurements 1 Measurements 1 16 239 50 1000); -RC(Measurements02 Measurements 1 Measurements 1 16 239 50 1000); -RC(Measurements03 Measurements 1 Measurements 1 16 239 50 1000); -RC(brcbServiceTracking01 ServiceTracking 1 ServiceTracking 1 19 228 0 0); -RC(brcbServiceTracking02 ServiceTracking 1 ServiceTracking 1 19 228 0 0); -RC(brcbServiceTracking03 ServiceTracking 1 ServiceTracking 1 19 228 0 0); -LC(EventLog Events GenericIO/LLN0$EventLog 19 0 1 0); -LC(GeneralLog - - 19 0 0 0); -LOG(); -LOG(EventLog); -GC(gcbEvents events Events 3 0 1000 3000){ -PA(4 1 1000 010CCD010001); -} -} LN(LPHD1){ DO(PhyNam 0){ DA(vendor 0 20 5 16 0); @@ -235,6 +159,85 @@ DA(q 0 23 0 18 0); DA(t 0 22 0 16 0); } } +LN(LLN0){ +SG(1 5) +DO(Mod 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +DA(ctlModel 0 12 4 16 0)=0; +} +DO(Beh 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Health 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(NamPlt 0){ +DA(vendor 0 20 5 16 0)="MZ Automation"; +DA(swRev 0 20 5 16 0)="1.3.0"; +DA(d 0 20 5 16 0)="libiec61850 server example"; +DA(configRev 0 20 5 16 0); +DA(ldNs 0 20 11 16 0); +} +DS(Events){ +DE(GGIO1$ST$SPCSO1$stVal); +DE(GGIO1$ST$SPCSO2$stVal); +DE(GGIO1$ST$SPCSO3$stVal); +DE(GGIO1$ST$SPCSO4$stVal); +} +DS(Events2){ +DE(GGIO1$ST$SPCSO1); +DE(GGIO1$ST$SPCSO2); +DE(GGIO1$ST$SPCSO3); +DE(GGIO1$ST$SPCSO4); +} +DS(Measurements){ +DE(GGIO1$MX$AnIn1$mag$f); +DE(GGIO1$MX$AnIn1$q); +DE(GGIO1$MX$AnIn2$mag$f); +DE(GGIO1$MX$AnIn2$q); +DE(GGIO1$MX$AnIn3$mag$f); +DE(GGIO1$MX$AnIn3$q); +DE(GGIO1$MX$AnIn4$mag$f); +DE(GGIO1$MX$AnIn4$q); +} +DS(ServiceTracking){ +DE(LTRK1$SR$SpcTrk); +DE(LTRK1$SR$DpcTrk); +DE(LTRK1$SR$IncTrk); +DE(LTRK1$SR$BscTrk); +DE(LTRK1$SR$UrcbTrk); +DE(LTRK1$SR$BrcbTrk); +DE(LTRK1$SR$GocbTrk); +DE(LTRK1$SR$SgcbTrk); +DE(LTRK1$SR$LocbTrk); +} +RC(EventsRCB01 Events1 0 Events2 1 24 175 50 1000); +RC(EventsIndexed01 Events2 0 Events 1 24 175 50 1000); +RC(EventsIndexed02 Events2 0 Events 1 24 175 50 1000); +RC(EventsIndexed03 Events2 0 Events 1 24 175 50 1000); +RC(Measurements01 Measurements 1 Measurements 1 16 239 50 1000); +RC(Measurements02 Measurements 1 Measurements 1 16 239 50 1000); +RC(Measurements03 Measurements 1 Measurements 1 16 239 50 1000); +RC(brcbServiceTracking01 ServiceTracking 1 ServiceTracking 1 19 228 0 0); +RC(brcbServiceTracking02 ServiceTracking 1 ServiceTracking 1 19 228 0 0); +RC(brcbServiceTracking03 ServiceTracking 1 ServiceTracking 1 19 228 0 0); +LC(EventLog Events GenericIO/LLN0$EventLog 19 0 1 0); +LC(GeneralLog - - 19 0 0 0); +LOG(); +LOG(EventLog); +GC(gcbEvents events Events 3 0 1000 3000){ +PA(4 1 1000 010CCD010001); +} +SMVC(MSVCB01 xxxxMUnn01 Events 0 0 0 1 0 ){ +PA(4 123 4000 010CCD040001); +} +} LN(PTOC1){ DO(Mod 0){ DA(stVal 0 12 0 17 0); diff --git a/dotnet/server_example_access_control/server_example_access_control.csproj b/dotnet/server_example_access_control/server_example_access_control.csproj index c34022ad..618a553d 100644 --- a/dotnet/server_example_access_control/server_example_access_control.csproj +++ b/dotnet/server_example_access_control/server_example_access_control.csproj @@ -8,12 +8,12 @@ - + - - PreserveNewest + + Always diff --git a/tools/model_generator_dotnet/Tools/Tools.csproj b/tools/model_generator_dotnet/Tools/Tools.csproj index 09e31c57..10d47403 100644 --- a/tools/model_generator_dotnet/Tools/Tools.csproj +++ b/tools/model_generator_dotnet/Tools/Tools.csproj @@ -16,6 +16,7 @@ + @@ -49,6 +50,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest From f2940775f60864c491375678994466df005d7e4b Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Mon, 21 Jul 2025 09:44:37 +0200 Subject: [PATCH 12/14] Add SV control handler --- .../IEC61850Model/SVControlBlock.cs | 5 + .../server_example_access_control/Program.cs | 13 + .../server_example_access_control.csproj | 4 +- .../simpleIO_access_control.icd | 660 ++++++++++++++++++ 4 files changed, 680 insertions(+), 2 deletions(-) create mode 100644 dotnet/server_example_access_control/simpleIO_access_control.icd diff --git a/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs b/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs index b1dce114..d9dc7105 100644 --- a/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs +++ b/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs @@ -46,6 +46,11 @@ namespace IEC61850 this.parent = parent; } + /// + /// create a new Multicast/Unicast Sampled Value (SV) control block (SvCB) + /// Create a new Sampled Value control block(SvCB) and add it to the given logical node(LN) + /// + /// the svcontrol instance public SVControlBlock(IntPtr self) { this.self = self; diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index a936d0a2..18f54645 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -16,6 +16,7 @@ using System.Reflection.Metadata; using IEC61850.Client; using ReportControlBlock = IEC61850.Server.ReportControlBlock; using IEC61850.Model; +using System.Data.Common; namespace server_access_control { @@ -331,6 +332,18 @@ namespace server_access_control iedServer.SetEditSettingGroupChangedHandler(editSGChangedHandler, settingGroupControlBlock, null); iedServer.SetEditSettingGroupConfirmationHandler(editSGConfirmationHandler, settingGroupControlBlock, null); + LogicalNode logicalNode = (LogicalNode)iedModel.GetModelNodeByShortObjectReference("GenericIO/LLN0"); + SVControlBlock sampledValuesControlBlock_1 = iedModel.GetSVControlBlock(logicalNode, "SMV1"); + SVControlBlock sampledValuesControlBlock_2 = iedModel.GetSVControlBlock(logicalNode, "SMV2"); + + void sVCBEventHandler(SVControlBlock sampledValuesControlBlock, SMVEvent sMVEvent, object parameter) + { + Console.WriteLine("control called"); + } + + iedServer.SetSVCBHandler(sVCBEventHandler, sampledValuesControlBlock_1, null); + iedServer.SetSVCBHandler(sVCBEventHandler, sampledValuesControlBlock_2, null); + iedServer.Start(102); if (iedServer.IsRunning()) diff --git a/dotnet/server_example_access_control/server_example_access_control.csproj b/dotnet/server_example_access_control/server_example_access_control.csproj index 618a553d..941a4574 100644 --- a/dotnet/server_example_access_control/server_example_access_control.csproj +++ b/dotnet/server_example_access_control/server_example_access_control.csproj @@ -8,11 +8,11 @@ - + - + Always diff --git a/dotnet/server_example_access_control/simpleIO_access_control.icd b/dotnet/server_example_access_control/simpleIO_access_control.icd new file mode 100644 index 00000000..b70d8921 --- /dev/null +++ b/dotnet/server_example_access_control/simpleIO_access_control.icd @@ -0,0 +1,660 @@ + + +
+
+ + + Station bus + 10 + +
+

0.0.0.0

+

255.255.255.0

+

192.168.2.1

+

1,3,9999,33

+

33

+

00000001

+

0001

+

0001

+

102

+
+ +
+

1

+

4

+

01-0c-cd-01-00-01

+

1000

+
+ 1000 + 3000 +
+ +
+

01-0C-CD-04-00-01

+

4000

+

4

+

123

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + ok + + + + + + + on + + + status-only + + + + + on + + + + + ok + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + direct-with-normal-security + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + on + + + status-only + + + + + on + + + + + ok + + + + + MZ Automation + + + 1.3.0 + + + libiec61850 server example + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unknown + Not-supported + Blocked-by-switching-hierarchy + Select-failed + Invalid-position + Position-reached + Parameter-change-in-execution + Step-limit + Blocked-by-Mode + Blocked-by-process + Blocked-by-interlocking + Blocked-by-synchrocheck + Command-already-in-execution + Blocked-by-health + 1-of-n-control + Abortion-by-cancel + Time-limit-over + Abortion-by-trip + Object-not-selected + Object-already-selected + No-access-authority + Ended-with-overshoot + Abortion-due-to-deviation + Abortion-by-communication-loss + Blocked-by-command + None + Inconsistent-parameters + Locked-by-other-client + + + on + blocked + test + test/blocked + off + + + ok + warning + alarm + + + status-only + direct-with-normal-security + sbo-with-normal-security + direct-with-enhanced-security + sbo-with-enhanced-security + + + not-supported + bay-control + station-control + remote-control + automatic-bay + automatic-station + automatic-remote + maintenance + process + + + no-error + instance-not-available + instance-in-use + access-violation + access-not-allowed-in-current-state + parameter-value-inappropriate + parameter-value-inconsistent + class-not-supported + instance-locked-by-other-client + control-must-be-selected + type-conflict + failed-due-to-communications-constraint + failed-due-to-server-constraint + + + Unknown + Associate + Abort + Release + GetServerDirectory + GetLogicalDeviceDirectory + GetAllDataValues + GetDataValues + SetDataValues + GetDataDirectory + GetDataDefinition + GetDataSetValues + SetDataSetValues + CreateDataSet + DeleteDataSet + GetDataSetDirectory + SelectActiveSG + SelectEditSG + SetEditSGValue + ConfirmEditSGValues + GetEditSGValue + GetSGCBValues + Report + GetBRCBValues + SetBRCBValues + GetURCBValues + SetURCBValues + GetLCBValues + SetLCBValues + QueryLogByTime + QueryLogAfter + GetLogStatus + SendGOOSEMessage + GetGoCBValues + SetGoCBValues + GetGoReference + GetGOOSEElementNumber + SendMSVMessage + GetMSVCBValues + SetMSVCBValues + SendUSVMessage + GetUSVCBValues + SetUSVCBValues + Select + SelectWithValue + Cancel + Operate + CommandTermination + TimeActivatedOperate + GetFile + SetFile + DeleteFile + GetFileAttributValues + TimeSynchronisation + InternalChange + + + unknown + forward + backward + both + + +
\ No newline at end of file From 37a857e1282964fc54c8ca9810a7e1727c027318 Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Mon, 21 Jul 2025 10:09:12 +0200 Subject: [PATCH 13/14] C -> LIB61850_API SVControlBlock_getName added .NET -> Wrapper for SVControl ge_name added, called inside of SMVHandler --- .../IEC61850Model/SVControlBlock.cs | 11 ++++++ .../server_example_access_control/Program.cs | 16 ++------- .../server_example_access_control/model.cfg | 35 ++++++++++--------- src/iec61850/inc/iec61850_dynamic_model.h | 3 ++ src/iec61850/server/model/dynamic_model.c | 7 ++++ 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs b/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs index d9dc7105..e9c1300f 100644 --- a/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs +++ b/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs @@ -26,6 +26,9 @@ namespace IEC61850 static extern IntPtr SVControlBlock_create(string name, IntPtr parent, string svID,string dataSet, UInt32 confRev, uint smpMod, UInt16 smpRate, uint optFlds, bool isUnicast); + [DllImport("iec61850", CallingConvention = CallingConvention.Cdecl)] + static extern IntPtr SVControlBlock_getName(IntPtr self); + /// /// create a new Multicast/Unicast Sampled Value (SV) control block (SvCB) /// Create a new Sampled Value control block(SvCB) and add it to the given logical node(LN) @@ -56,6 +59,14 @@ namespace IEC61850 this.self = self; } + public string Name + { + get + { + return Marshal.PtrToStringAnsi(SVControlBlock_getName(self)); + } + } + } } diff --git a/dotnet/server_example_access_control/Program.cs b/dotnet/server_example_access_control/Program.cs index 18f54645..2e6784e1 100644 --- a/dotnet/server_example_access_control/Program.cs +++ b/dotnet/server_example_access_control/Program.cs @@ -142,7 +142,7 @@ namespace server_access_control /* Install handler to control access to control blocks (RCBs, LCBs, GoCBs, SVCBs, SGCBs)*/ bool ControlBlockAccessCallBack(object parameter, ClientConnection connection, ACSIClass acsiClass, LogicalDevice ld, LogicalNode ln, string objectName, string subObjectName, ControlBlockAccessType accessType) { - Console.WriteLine(acsiClass.ToString() + " "+ accessType.ToString() + " access " + ld.GetName() + ln.GetName() +"/"+ objectName + "." + subObjectName); + Console.WriteLine(acsiClass.ToString() + " "+ accessType.ToString() + " access " + ld.GetName() + ln.GetName() +"/"+ objectName + "." + subObjectName + "\n"); return true; } @@ -221,18 +221,6 @@ namespace server_access_control iedServer.SetDirectoryAccessHandler(directoryAccessHandler, null); - /* Handler for Sampled values control block - */ - - void SVCBEventHandler(SVControlBlock svcb, SMVEvent smvEvent, object parameter) - { - Console.WriteLine("SVControlBlock event " + smvEvent.ToString()); - } - - //LogicalNode logicalNode = (LogicalNode)iedModel.GetModelNodeByShortObjectReference("GenericIO/GGIO1"); - //SVControlBlock sampledValuesControlBlock = iedModel.GetSVControlBlock(logicalNode, "MSVCB01"); - //iedServer.SetSVCBHandler(SVCBEventHandler, sampledValuesControlBlock, null); - /*SettingGroups*/ LogicalDevice logicalDevice = (LogicalDevice)iedModel.GetModelNodeByShortObjectReference("GenericIO"); ; @@ -338,7 +326,7 @@ namespace server_access_control void sVCBEventHandler(SVControlBlock sampledValuesControlBlock, SMVEvent sMVEvent, object parameter) { - Console.WriteLine("control called"); + Console.WriteLine("control called " + sampledValuesControlBlock.Name + " Event: " + sMVEvent.ToString() + "\n"); } iedServer.SetSVCBHandler(sVCBEventHandler, sampledValuesControlBlock_1, null); diff --git a/dotnet/server_example_access_control/model.cfg b/dotnet/server_example_access_control/model.cfg index ad0b6472..d01cb427 100644 --- a/dotnet/server_example_access_control/model.cfg +++ b/dotnet/server_example_access_control/model.cfg @@ -1,20 +1,5 @@ MODEL(simpleIO){ LD(GenericIO){ -LN(LPHD1){ -DO(PhyNam 0){ -DA(vendor 0 20 5 16 0); -} -DO(PhyHealth 0){ -DA(stVal 0 12 0 17 0)=1; -DA(q 0 23 0 18 0); -DA(t 0 22 0 16 0); -} -DO(Proxy 0){ -DA(stVal 0 0 0 17 0); -DA(q 0 23 0 18 0); -DA(t 0 22 0 16 0); -} -} LN(GGIO1){ DO(Mod 0){ DA(stVal 0 12 0 17 0)=1; @@ -159,6 +144,21 @@ DA(q 0 23 0 18 0); DA(t 0 22 0 16 0); } } +LN(LPHD1){ +DO(PhyNam 0){ +DA(vendor 0 20 5 16 0); +} +DO(PhyHealth 0){ +DA(stVal 0 12 0 17 0)=1; +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +DO(Proxy 0){ +DA(stVal 0 0 0 17 0); +DA(q 0 23 0 18 0); +DA(t 0 22 0 16 0); +} +} LN(LLN0){ SG(1 5) DO(Mod 0){ @@ -234,7 +234,10 @@ LOG(EventLog); GC(gcbEvents events Events 3 0 1000 3000){ PA(4 1 1000 010CCD010001); } -SMVC(MSVCB01 xxxxMUnn01 Events 0 0 0 1 0 ){ +SMVC(SMV1 xxxxMUnn01 Events 0 0 0 1 0 ){ +PA(4 123 4000 010CCD040001); +} +SMVC(SMV2 null Measurements 0 0 0 0 0 ){ PA(4 123 4000 010CCD040001); } } diff --git a/src/iec61850/inc/iec61850_dynamic_model.h b/src/iec61850/inc/iec61850_dynamic_model.h index ed6a31c4..a3b5aeff 100644 --- a/src/iec61850/inc/iec61850_dynamic_model.h +++ b/src/iec61850/inc/iec61850_dynamic_model.h @@ -468,6 +468,9 @@ LIB61850_API SVControlBlock* SVControlBlock_create(const char* name, LogicalNode* parent, const char* svID, const char* dataSet, uint32_t confRev, uint8_t smpMod, uint16_t smpRate, uint8_t optFlds, bool isUnicast); +LIB61850_API const char* +SVControlBlock_getName(SVControlBlock* self); + LIB61850_API void SVControlBlock_addPhyComAddress(SVControlBlock* self, PhyComAddress* phyComAddress); diff --git a/src/iec61850/server/model/dynamic_model.c b/src/iec61850/server/model/dynamic_model.c index 2290918e..f3c7f3d7 100644 --- a/src/iec61850/server/model/dynamic_model.c +++ b/src/iec61850/server/model/dynamic_model.c @@ -587,6 +587,13 @@ SVControlBlock_create(const char* name, LogicalNode* parent, const char* svID, c return self; } + +const char* +SVControlBlock_getName(SVControlBlock* self) +{ + return self->name; +} + void SVControlBlock_addPhyComAddress(SVControlBlock* self, PhyComAddress* phyComAddress) { From 29403f39f712f23306666425bde7653dc630e23c Mon Sep 17 00:00:00 2001 From: Maxson Ramon dos Anjos Medeiros Date: Mon, 21 Jul 2025 14:27:34 +0200 Subject: [PATCH 14/14] Copyright header added to SMVControlBlock file and CHANGELOG updated --- CHANGELOG | 9 ++----- .../IEC61850Model/SVControlBlock.cs | 24 ++++++++++++++++++- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 833e33d6..87d04bdc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,16 +1,11 @@ -Changes to version ? ------------------------- - -New features and improvements: - -- .NET API: added additional callbacks to control external access to the data model (required to implement RBAC) - Changes to version 1.6.1 ------------------------ New features and improvements: +- .NET API: SVControlBlock added +- .NET API: Added additional callbacks to control external access to the data model (required to implement RBAC) - .NET API: IEC61850ServerAPI -> LogStorage function wrapper added. - .NET API: MmsValue -> MmsValue_encodeMmsData and MmsValue_decodeMmsData added. - .NET API: Fixed issue of not printing log entries in log_client and log_server examples. diff --git a/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs b/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs index e9c1300f..9e5423f1 100644 --- a/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs +++ b/dotnet/IEC61850forCSharp/IEC61850Model/SVControlBlock.cs @@ -1,4 +1,26 @@ -using IEC61850.Server; +/* + * IEC61850ServerAPI.cs + * + * Copyright 2016-2025 Michael Zillgith + * + * This file is part of libIEC61850. + * + * libIEC61850 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * libIEC61850 is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with libIEC61850. If not, see . + * + * See COPYING file for the complete license text. + */ +using IEC61850.Server; using System; using System.Collections.Generic; using System.Runtime.InteropServices;