From 67a0df1960624147d45aefcad3c8cbcf4a30a69a Mon Sep 17 00:00:00 2001 From: romanett Date: Thu, 5 Dec 2024 08:39:09 +0100 Subject: [PATCH 01/10] Fix read of NodeId attribute AccessRestrictions by casting return type from Enum to ushort (#2883) In the read NodeId servcie call an enum is by default cast to Int32, so the cast to ushort is needed here to return the expected type for the AccessRestrictions. --- Stack/Opc.Ua.Core/Stack/State/NodeState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs index c3e81b8e1f..ef96146731 100644 --- a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs @@ -3669,7 +3669,7 @@ protected virtual ServiceResult ReadNonValueAttribute( if (ServiceResult.IsGood(result)) { - value = accessRestrictions; + value = (ushort)accessRestrictions; } if (value != null || result != null) From f431d53c895c19d6c8fe2a01fcb354605ac4eed4 Mon Sep 17 00:00:00 2001 From: romanett Date: Wed, 11 Dec 2024 08:48:09 +0100 Subject: [PATCH 02/10] Fix Null reference Exception in NodeState for AccessRestrictions (#2894) * fix cast that throws when Enum is null --- Stack/Opc.Ua.Core/Stack/State/NodeState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs index ef96146731..f92b26afc2 100644 --- a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs @@ -3667,7 +3667,7 @@ protected virtual ServiceResult ReadNonValueAttribute( result = onReadAccessRestrictions(context, this, ref accessRestrictions); } - if (ServiceResult.IsGood(result)) + if (ServiceResult.IsGood(result) && accessRestrictions != null) { value = (ushort)accessRestrictions; } From dc661bac4655d335bae2857eba89a13e7cafecdf Mon Sep 17 00:00:00 2001 From: Martin Regen <7962757+mregen@users.noreply.github.com> Date: Fri, 13 Dec 2024 08:59:28 +0100 Subject: [PATCH 03/10] Fix server CurrentTime source timestamp and improve readerwriter locks (#2903) Fix server time source timestamp is not updated. There are duplicate objects in generated code, on of which which holds the last timestamp was not updated. Move all readerwriter locks outside of try/finally to avoid a SynchronizationLockException when a reader writer lock is disposed or is cancelled. Currently there is no obvious fundamental bug in how the readerwriter locks are used, but outside the try/finally clause e.g. a disposed lock may not trigger a false exception. --- .../Opc.Ua.Client/NodeCache/NodeCache.cs | 94 ++++++++----------- .../Opc.Ua.Client/NodeCache/NodeCacheAsync.cs | 27 +++--- .../NodeManager/MasterNodeManager.cs | 9 +- .../Server/ServerInternalData.cs | 7 ++ .../Types/Encoders/EncodeableFactory.cs | 14 ++- 5 files changed, 68 insertions(+), 83 deletions(-) diff --git a/Libraries/Opc.Ua.Client/NodeCache/NodeCache.cs b/Libraries/Opc.Ua.Client/NodeCache/NodeCache.cs index 00dddacd2e..d71d362414 100644 --- a/Libraries/Opc.Ua.Client/NodeCache/NodeCache.cs +++ b/Libraries/Opc.Ua.Client/NodeCache/NodeCache.cs @@ -104,10 +104,10 @@ public INode Find(ExpandedNodeId nodeId) } INode node; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - // check if node already exists. node = m_nodes.Find(nodeId); } @@ -155,10 +155,10 @@ public IList Find(IList nodeIds) for (ii = 0; ii < count; ii++) { INode node; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - // check if node already exists. node = m_nodes.Find(nodeIds[ii]); } @@ -235,10 +235,10 @@ public INode Find( } IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - // find all references. references = source.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); } @@ -285,10 +285,9 @@ public IList Find( } IList references; + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - // find all references. references = source.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); } @@ -325,10 +324,9 @@ public bool IsKnown(ExpandedNodeId typeId) return false; } + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - return m_typeTree.IsKnown(typeId); } finally @@ -347,10 +345,9 @@ public bool IsKnown(NodeId typeId) return false; } + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - return m_typeTree.IsKnown(typeId); } finally @@ -369,10 +366,9 @@ public NodeId FindSuperType(ExpandedNodeId typeId) return null; } + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - return m_typeTree.FindSuperType(typeId); } finally @@ -392,10 +388,9 @@ public NodeId FindSuperType(NodeId typeId) return null; } + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - return m_typeTree.FindSuperType(typeId); } finally @@ -409,18 +404,18 @@ public NodeId FindSuperType(NodeId typeId) public IList FindSubTypes(ExpandedNodeId typeId) { ILocalNode type = Find(typeId) as ILocalNode; + List subtypes = new List(); if (type == null) { - return new List(); + return subtypes; } - List subtypes = new List(); IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = type.References.Find(ReferenceTypeIds.HasSubtype, false, true, m_typeTree); } finally @@ -459,10 +454,10 @@ public bool IsTypeOf(ExpandedNodeId subTypeId, ExpandedNodeId superTypeId) while (supertype != null) { ExpandedNodeId currentId; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - currentId = supertype.References.FindTarget(ReferenceTypeIds.HasSubtype, true, true, m_typeTree, 0); } finally @@ -501,10 +496,10 @@ public bool IsTypeOf(NodeId subTypeId, NodeId superTypeId) while (supertype != null) { ExpandedNodeId currentId; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - currentId = supertype.References.FindTarget(ReferenceTypeIds.HasSubtype, true, true, m_typeTree, 0); } finally @@ -526,10 +521,9 @@ public bool IsTypeOf(NodeId subTypeId, NodeId superTypeId) /// public QualifiedName FindReferenceTypeName(NodeId referenceTypeId) { + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - return m_typeTree.FindReferenceTypeName(referenceTypeId); } finally @@ -541,10 +535,9 @@ public QualifiedName FindReferenceTypeName(NodeId referenceTypeId) /// public NodeId FindReferenceType(QualifiedName browseName) { + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - return m_typeTree.FindReferenceType(browseName); } finally @@ -564,10 +557,10 @@ public bool IsEncodingOf(ExpandedNodeId encodingId, ExpandedNodeId datatypeId) } IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = encoding.References.Find(ReferenceTypeIds.HasEncoding, true, true, m_typeTree); } finally @@ -611,10 +604,10 @@ public bool IsEncodingFor(NodeId expectedTypeId, ExtensionObject value) } IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = encoding.References.Find(ReferenceTypeIds.HasEncoding, true, true, m_typeTree); } finally @@ -706,10 +699,10 @@ public NodeId FindDataTypeId(ExpandedNodeId encodingId) } IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = encoding.References.Find(ReferenceTypeIds.HasEncoding, true, true, m_typeTree); } finally @@ -736,10 +729,10 @@ public NodeId FindDataTypeId(NodeId encodingId) } IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = encoding.References.Find(ReferenceTypeIds.HasEncoding, true, true, m_typeTree); } finally @@ -769,10 +762,9 @@ public void LoadUaDefinedTypes(ISystemContext context) var assembly = typeof(ArgumentCollection).GetTypeInfo().Assembly; predefinedNodes.LoadFromBinaryResource(context, "Opc.Ua.Stack.Generated.Opc.Ua.PredefinedNodes.uanodes", assembly, true); + m_cacheLock.EnterWriteLock(); try { - m_cacheLock.EnterWriteLock(); - for (int ii = 0; ii < predefinedNodes.Count; ii++) { BaseTypeState type = predefinedNodes[ii] as BaseTypeState; @@ -795,11 +787,10 @@ public void LoadUaDefinedTypes(ISystemContext context) /// public void Clear() { - m_uaTypesLoaded = false; + m_cacheLock.EnterWriteLock(); try { - m_cacheLock.EnterWriteLock(); - + m_uaTypesLoaded = false; m_nodes.Clear(); } finally @@ -826,10 +817,9 @@ public Node FetchNode(ExpandedNodeId nodeId) // fetch references from server. ReferenceDescriptionCollection references = m_session.FetchReferences(localId); + m_cacheLock.EnterUpgradeableReadLock(); try { - m_cacheLock.EnterUpgradeableReadLock(); - foreach (ReferenceDescription reference in references) { // create a placeholder for the node if it does not already exist. @@ -896,10 +886,9 @@ public IList FetchNodes(IList nodeIds) foreach (ReferenceDescription reference in references) { + m_cacheLock.EnterUpgradeableReadLock(); try { - m_cacheLock.EnterUpgradeableReadLock(); - // create a placeholder for the node if it does not already exist. if (!m_nodes.Exists(reference.NodeId)) { @@ -976,10 +965,10 @@ public IList FindReferences( } IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = source.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); } finally @@ -1026,10 +1015,10 @@ public IList FindReferences( foreach (var referenceTypeId in referenceTypeIds) { IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = node.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); } finally @@ -1077,10 +1066,10 @@ public string GetDisplayText(INode node) NodeId modellingRule = target.ModellingRule; IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = target.ReferenceTable.Find(ReferenceTypeIds.Aggregates, true, true, m_typeTree); } finally @@ -1167,10 +1156,9 @@ public NodeId BuildBrowsePath(ILocalNode node, IList browsePath) #region Private Methods private void InternalWriteLockedAttach(ILocalNode node) { + m_cacheLock.EnterWriteLock(); try { - m_cacheLock.EnterWriteLock(); - // add to cache. m_nodes.Attach(node); } diff --git a/Libraries/Opc.Ua.Client/NodeCache/NodeCacheAsync.cs b/Libraries/Opc.Ua.Client/NodeCache/NodeCacheAsync.cs index ebd8b1ea2c..2255a85847 100644 --- a/Libraries/Opc.Ua.Client/NodeCache/NodeCacheAsync.cs +++ b/Libraries/Opc.Ua.Client/NodeCache/NodeCacheAsync.cs @@ -52,11 +52,10 @@ public async Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct = return null; } + m_cacheLock.EnterReadLock(); INode node; try { - m_cacheLock.EnterReadLock(); - // check if node already exists. node = m_nodes.Find(nodeId); } @@ -104,10 +103,10 @@ public async Task> FindAsync(IList nodeIds, Cancell for (ii = 0; ii < count; ii++) { INode node; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - // check if node already exists. node = m_nodes.Find(nodeIds[ii]); } @@ -179,10 +178,9 @@ public async Task FindSuperTypeAsync(ExpandedNodeId typeId, Cancellation return null; } + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - return m_typeTree.FindSuperType(typeId); } finally @@ -201,10 +199,9 @@ public async Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct return null; } + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - return m_typeTree.FindSuperType(typeId); } finally @@ -233,10 +230,9 @@ public async Task FetchNodeAsync(ExpandedNodeId nodeId, CancellationToken // fetch references from server. ReferenceDescriptionCollection references = await m_session.FetchReferencesAsync(localId, ct).ConfigureAwait(false); + m_cacheLock.EnterUpgradeableReadLock(); try { - m_cacheLock.EnterUpgradeableReadLock(); - foreach (ReferenceDescription reference in references) { // create a placeholder for the node if it does not already exist. @@ -304,10 +300,9 @@ public async Task> FetchNodesAsync(IList nodeIds, Ca foreach (ReferenceDescription reference in references) { + m_cacheLock.EnterUpgradeableReadLock(); try { - m_cacheLock.EnterUpgradeableReadLock(); - // create a placeholder for the node if it does not already exist. if (!m_nodes.Exists(reference.NodeId)) { @@ -356,10 +351,10 @@ public async Task> FindReferencesAsync( } IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = source.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); } finally @@ -407,10 +402,10 @@ public async Task> FindReferencesAsync( foreach (var referenceTypeId in referenceTypeIds) { IList references; + + m_cacheLock.EnterReadLock(); try { - m_cacheLock.EnterReadLock(); - references = node.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); } finally diff --git a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs index 6792c19c8f..f47efe07d8 100644 --- a/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs +++ b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs @@ -421,10 +421,9 @@ public void RegisterNamespaceManager(string namespaceUri, INodeManager nodeManag // allocate a new table (using arrays instead of collections because lookup efficiency is critical). INodeManager[][] namespaceManagers = new INodeManager[m_server.NamespaceUris.Count][]; + m_readWriterLockSlim.EnterWriteLock(); try { - m_readWriterLockSlim.EnterWriteLock(); - // copy existing values. for (int ii = 0; ii < m_namespaceManagers.Length; ii++) { @@ -490,10 +489,9 @@ public bool UnregisterNamespaceManager(string namespaceUri, INodeManager nodeMan // allocate a new table (using arrays instead of collections because lookup efficiency is critical). INodeManager[][] namespaceManagers = new INodeManager[m_server.NamespaceUris.Count][]; + m_readWriterLockSlim.EnterWriteLock(); try { - m_readWriterLockSlim.EnterWriteLock(); - // copy existing values. for (int ii = 0; ii < m_namespaceManagers.Length; ii++) { @@ -559,10 +557,9 @@ public virtual object GetManagerHandle(NodeId nodeId, out INodeManager nodeManag // use the namespace index to select the node manager. int index = nodeId.NamespaceIndex; + m_readWriterLockSlim.EnterReadLock(); try { - m_readWriterLockSlim.EnterReadLock(); - // check if node managers are registered - use the core node manager if unknown. if (index >= m_namespaceManagers.Length || m_namespaceManagers[index] == null) { diff --git a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs index ba9f82df27..5bdf33b7e3 100644 --- a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs +++ b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs @@ -743,6 +743,13 @@ private void OnReadServerStatus( DateTime now = DateTime.UtcNow; m_serverStatus.Timestamp = now; m_serverStatus.Value.CurrentTime = now; + // update other timestamps in NodeState objects which are used to derive the source timestamp + if (variable is ServerStatusValue serverStatusValue && + serverStatusValue.Variable is ServerStatusState serverStatusState) + { + serverStatusState.Timestamp = now; + serverStatusState.CurrentTime.Timestamp = now; + } } } diff --git a/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs b/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs index c47c8742f4..bc938ba9a9 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs @@ -339,9 +339,9 @@ public int InstanceId /// The underlying system type to add to the factory public void AddEncodeableType(Type systemType) { + m_readerWriterLockSlim.EnterWriteLock(); try { - m_readerWriterLockSlim.EnterWriteLock(); AddEncodeableType(systemType, null); } finally @@ -365,9 +365,9 @@ public void AddEncodeableType(ExpandedNodeId encodingId, Type systemType) Utils.LogWarning("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId); } #endif + m_readerWriterLockSlim.EnterWriteLock(); try { - m_readerWriterLockSlim.EnterWriteLock(); m_encodeableTypes[encodingId] = systemType; } finally @@ -402,10 +402,9 @@ public void AddEncodeableTypes(Assembly assembly) } #endif + m_readerWriterLockSlim.EnterWriteLock(); try { - m_readerWriterLockSlim.EnterWriteLock(); - Type[] systemTypes = assembly.GetExportedTypes(); var unboundTypeIds = new Dictionary(); @@ -470,9 +469,9 @@ public void AddEncodeableTypes(Assembly assembly) /// The underlying system types to add to the factory public void AddEncodeableTypes(IEnumerable systemTypes) { + m_readerWriterLockSlim.EnterWriteLock(); try { - m_readerWriterLockSlim.EnterWriteLock(); foreach (var type in systemTypes) { if (type.GetTypeInfo().IsAbstract) @@ -498,10 +497,9 @@ public void AddEncodeableTypes(IEnumerable systemTypes) /// The type id to return the system-type of public Type GetSystemType(ExpandedNodeId typeId) { + m_readerWriterLockSlim.EnterReadLock(); try { - m_readerWriterLockSlim.EnterReadLock(); - Type systemType = null; if (NodeId.IsNull(typeId) || !m_encodeableTypes.TryGetValue(typeId, out systemType)) @@ -535,9 +533,9 @@ public object Clone() { EncodeableFactory clone = new EncodeableFactory(null); + m_readerWriterLockSlim.EnterReadLock(); try { - m_readerWriterLockSlim.EnterReadLock(); foreach (KeyValuePair current in m_encodeableTypes) { clone.m_encodeableTypes.Add(current.Key, current.Value); From 9a434b860c155567028408d7352de54c6b9d7369 Mon Sep 17 00:00:00 2001 From: Martin Regen <7962757+mregen@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:41:14 +0100 Subject: [PATCH 04/10] Fix misc. build issues and warnings (#2908) - Fix errors and warnings on stricter build systems. - Some Linux distri do not support brainpool, detect if tests can be run. --- .editorconfig | 1 + .../AlarmHolders/AlarmConditionTypeHolder.cs | 4 +-- .../Alarms/AlarmHolders/DiscreteHolder.cs | 4 +-- .../MemoryBuffer/MemoryBufferNodeManager.cs | 3 +- .../Opc.Ua.Client.ComplexTypes.csproj | 2 +- Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj | 2 +- .../Opc.Ua.Client/Session/TraceableSession.cs | 13 ++++--- .../Opc.Ua.Configuration.csproj | 2 +- .../Opc.Ua.Gds.Client.Common.csproj | 2 +- .../Opc.Ua.Gds.Server.Common.csproj | 2 +- Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj | 2 +- .../Opc.Ua.Security.Certificates.csproj | 2 +- Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj | 2 +- .../Opc.Ua.Bindings.Https.csproj | 2 +- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 2 +- .../Certificates/CertificateValidator.cs | 1 + .../SecurityConfigurationManager.cs | 10 ++++-- .../Stack/Tcp/TcpTransportListener.cs | 3 +- .../ContinuationPointInBatchTest.cs | 2 +- .../Stack/Buffers/ArraySegmentStreamTests.cs | 7 ++-- .../Types/Utils/UtilsIsEqualTests.cs | 4 +-- .../Encoding/MessagesHelper.cs | 15 ++++---- .../Transport/MqttPubSubConnectionTests.cs | 8 ++--- .../CertificateTestsForECDsa.cs | 36 ++++++++++++++----- common.props | 1 + 25 files changed, 83 insertions(+), 49 deletions(-) diff --git a/.editorconfig b/.editorconfig index 5b57ac1d05..6b60a276e8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,7 @@ root = true # All files [*] indent_style = space +tab_width = 4 # Code files [*.{cs,csx,vb,vbx}] diff --git a/Applications/Quickstarts.Servers/Alarms/AlarmHolders/AlarmConditionTypeHolder.cs b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/AlarmConditionTypeHolder.cs index 616c60ba48..b4254168af 100644 --- a/Applications/Quickstarts.Servers/Alarms/AlarmHolders/AlarmConditionTypeHolder.cs +++ b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/AlarmConditionTypeHolder.cs @@ -28,7 +28,7 @@ * ======================================================================*/ using System; - +using System.Globalization; using Opc.Ua; #pragma warning disable CS1591 @@ -272,7 +272,7 @@ private ServiceResult OnShelve( } else { - dueTo = " due to TimedShelve of " + shelvingTime.ToString(); + dueTo = " due to TimedShelve of " + shelvingTime.ToString(CultureInfo.InvariantCulture); } } else diff --git a/Applications/Quickstarts.Servers/Alarms/AlarmHolders/DiscreteHolder.cs b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/DiscreteHolder.cs index c40953edd8..2d15d627f7 100644 --- a/Applications/Quickstarts.Servers/Alarms/AlarmHolders/DiscreteHolder.cs +++ b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/DiscreteHolder.cs @@ -28,7 +28,7 @@ * ======================================================================*/ using System; - +using System.Globalization; using Opc.Ua; #pragma warning disable CS0219 @@ -85,7 +85,7 @@ public override void SetValue(string message = "") if (message.Length == 0) { - message = "Discrete Alarm analog value = " + value.ToString() + ", active = " + active.ToString(); + message = "Discrete Alarm analog value = " + value.ToString(CultureInfo.InvariantCulture) + ", active = " + active.ToString(); } base.SetValue(message); diff --git a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferNodeManager.cs b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferNodeManager.cs index 59a5e18904..42d79f1908 100644 --- a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferNodeManager.cs +++ b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferNodeManager.cs @@ -38,6 +38,7 @@ using Opc.Ua.Server; using Opc.Ua.Sample; using System.Reflection; +using System.Globalization; namespace MemoryBuffer { @@ -234,7 +235,7 @@ protected override object GetManagerHandle(ISystemContext context, NodeId nodeId } // check range on offset. - uint offset = Convert.ToUInt32(offsetText); + uint offset = Convert.ToUInt32(offsetText, CultureInfo.InvariantCulture); if (offset >= buffer.SizeInBytes.Value) { diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj index 6be179a1d4..764c794d21 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj +++ b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj @@ -3,7 +3,7 @@ Opc.Ua.Client.ComplexTypes $(LibxTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes + $(PackagePrefix).Opc.Ua.Client.ComplexTypes Opc.Ua.Client.ComplexTypes OPC UA Complex Types Client Class Library true diff --git a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj index a808f4a197..c01e640733 100644 --- a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj +++ b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj @@ -4,7 +4,7 @@ $(DefineConstants);CLIENT_ASYNC Opc.Ua.Client $(LibTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Client + $(PackagePrefix).Opc.Ua.Client Opc.Ua.Client OPC UA Client Class Library true diff --git a/Libraries/Opc.Ua.Client/Session/TraceableSession.cs b/Libraries/Opc.Ua.Client/Session/TraceableSession.cs index 6bd4d2bcef..f2c2620d8a 100644 --- a/Libraries/Opc.Ua.Client/Session/TraceableSession.cs +++ b/Libraries/Opc.Ua.Client/Session/TraceableSession.cs @@ -287,7 +287,7 @@ public int OperationTimeout /// public bool CheckDomain => m_session.CheckDomain; - + /// public ContinuationPointPolicy ContinuationPointPolicy { @@ -661,7 +661,7 @@ public void ReadValues(IList variableIds, IList expectedTypes, out m_session.ReadValues(variableIds, expectedTypes, out values, out errors); } } - + /// public byte[] ReadByteStringInChunks(NodeId nodeId) { @@ -671,8 +671,8 @@ public byte[] ReadByteStringInChunks(NodeId nodeId) } } - /// - public void ReadDisplayName(IList nodeIds, out IList displayNames, out IList errors) + /// + public void ReadDisplayName(IList nodeIds, out IList displayNames, out IList errors) { using (Activity activity = ActivitySource.StartActivity()) { @@ -1289,18 +1289,17 @@ public void ManagedBrowse(RequestHeader requestHeader, ViewDescription view, ILi { m_session.ManagedBrowse(requestHeader, view, nodesToBrowse, maxResultsToReturn, browseDirection, referenceTypeId, includeSubtypes, nodeClassMask, out result, out errors); } - } /// - public async Task<( + public Task<( IList, IList )> ManagedBrowseAsync(RequestHeader requestHeader, ViewDescription view, IList nodesToBrowse, uint maxResultsToReturn, BrowseDirection browseDirection, NodeId referenceTypeId, bool includeSubtypes, uint nodeClassMask, CancellationToken ct = default) { using (Activity activity = ActivitySource.StartActivity()) { - return await m_session.ManagedBrowseAsync(requestHeader, view, nodesToBrowse, maxResultsToReturn, browseDirection, referenceTypeId, includeSubtypes, nodeClassMask, ct); + return m_session.ManagedBrowseAsync(requestHeader, view, nodesToBrowse, maxResultsToReturn, browseDirection, referenceTypeId, includeSubtypes, nodeClassMask, ct); } } diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index d1886c4279..be7a06fa49 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -3,7 +3,7 @@ Opc.Ua.Configuration $(LibTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Configuration + $(PackagePrefix).Opc.Ua.Configuration Opc.Ua.Configuration OPC UA Configuration Class Library true diff --git a/Libraries/Opc.Ua.Gds.Client.Common/Opc.Ua.Gds.Client.Common.csproj b/Libraries/Opc.Ua.Gds.Client.Common/Opc.Ua.Gds.Client.Common.csproj index 077b4be878..c4fefa1ef4 100644 --- a/Libraries/Opc.Ua.Gds.Client.Common/Opc.Ua.Gds.Client.Common.csproj +++ b/Libraries/Opc.Ua.Gds.Client.Common/Opc.Ua.Gds.Client.Common.csproj @@ -3,7 +3,7 @@ Opc.Ua.Gds.Client.Common $(LibTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Gds.Client.Common + $(PackagePrefix).Opc.Ua.Gds.Client.Common Opc.Ua.Gds.Client $(NoWarn);CS1591 OPC UA GDS Client Class Library diff --git a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj index d46cc04559..0bb5c0e1f1 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj +++ b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj @@ -3,7 +3,7 @@ Opc.Ua.Gds.Server.Common $(LibTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Gds.Server.Common + $(PackagePrefix).Opc.Ua.Gds.Server.Common Opc.Ua.Gds.Server $(NoWarn);CS1591 OPC UA GDS Server Class Library diff --git a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj index 7286c6b3ec..419ac2c276 100644 --- a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj +++ b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj @@ -3,7 +3,7 @@ Opc.Ua.PubSub $(LibxTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.PubSub + $(PackagePrefix).Opc.Ua.PubSub Opc.Ua.PubSub OPC UA PubSub Class Library true diff --git a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj index 67aa710321..5fc06442de 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj +++ b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj @@ -3,7 +3,7 @@ Opc.Ua.Security.Certificates $(LibCoreTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Security.Certificates + $(PackagePrefix).Opc.Ua.Security.Certificates Opc.Ua.Security.Certificates OPC UA Security X509 Certificates Class Library true diff --git a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj index 5eff9082e9..b6e598fe7e 100644 --- a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj +++ b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj @@ -3,7 +3,7 @@ Opc.Ua.Server $(LibTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Server + $(PackagePrefix).Opc.Ua.Server Opc.Ua.Server OPC UA Server Class Library PackageReference diff --git a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj index d63a36fd5d..da79450376 100644 --- a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj +++ b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj @@ -4,7 +4,7 @@ true $(HttpsTargetFrameworks) Opc.Ua.Bindings.Https - OPCFoundation.NetStandard.Opc.Ua.Bindings.Https + $(PackagePrefix).Opc.Ua.Bindings.Https Opc.Ua.Bindings OPC UA Https Binding Library for a Opc.Ua.Server. true diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 64ef8e948e..58fa34b887 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -4,7 +4,7 @@ $(DefineConstants);NET_STANDARD;NET_STANDARD_ASYNC $(LibCoreTargetFrameworks) Opc.Ua.Core - OPCFoundation.NetStandard.Opc.Ua.Core + $(PackagePrefix).Opc.Ua.Core Opc.Ua OPC UA Core Class Library true diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 2c13adf880..6509c0a64b 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -1120,6 +1120,7 @@ await GetIssuerNoExceptionAsync(certificate, explicitList, certificateStore, che /// The endpoint for domain validation. /// The cancellation token. /// If certificate[0] cannot be accepted + [System.Diagnostics.CodeAnalysis.SuppressMessage("Roslynanalyzer", "IA5352:Do not set X509RevocationMode.NoCheck", Justification = "Revocation is already checked.")] protected virtual async Task InternalValidateAsync(X509Certificate2Collection certificates, ConfiguredEndpoint endpoint, CancellationToken ct = default) { X509Certificate2 certificate = certificates[0]; diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs index 230791f772..4dbd1ca99f 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs @@ -88,11 +88,17 @@ public SecuredApplication ReadConfiguration(string filePath) { FileStream reader = File.Open(configFilePath, FileMode.Open, FileAccess.Read, FileShare.Read); - try { byte[] data = new byte[reader.Length]; - reader.Read(data, 0, (int)reader.Length); + int bytesRead = reader.Read(data, 0, (int)reader.Length); + if (reader.Length != bytesRead) + { + throw ServiceResultException.Create( + StatusCodes.BadNotReadable, + "Cannot read all bytes of the configuration file: {0}<{1}", + bytesRead, reader.Length); + } // find the SecuredApplication element in the file. if (data.ToString().Contains("SecuredApplication")) diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index 27c51eb717..85392a7ffd 100644 --- a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs +++ b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs @@ -14,6 +14,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; @@ -413,7 +414,7 @@ public void UpdateChannelLastActiveTime(string globalChannelId) try { var channelIdString = globalChannelId.Substring(ListenerId.Length + 1); - var channelId = Convert.ToUInt32(channelIdString); + var channelId = Convert.ToUInt32(channelIdString, CultureInfo.InvariantCulture); TcpListenerChannel channel = null; if (channelId > 0 && diff --git a/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs b/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs index a31fd53998..bcfe7dd876 100644 --- a/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs +++ b/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs @@ -1073,7 +1073,7 @@ private void VerifyExpectedResults(List memoryLogPass, // get the part of the error message after the time stamp: string msg = s.Substring(s.IndexOf("ManagedBrowse")); // create error message from expected results - String expectedString = String.Format( + String expectedString = Utils.Format( "ManagedBrowse: in pass {0}, {1} {2} occured with a status code {3}.", pass, expectedResults.ExpectedNumberOfBadNoCPSCs[pass], diff --git a/Tests/Opc.Ua.Core.Tests/Stack/Buffers/ArraySegmentStreamTests.cs b/Tests/Opc.Ua.Core.Tests/Stack/Buffers/ArraySegmentStreamTests.cs index b6bcaca240..cad849eb72 100644 --- a/Tests/Opc.Ua.Core.Tests/Stack/Buffers/ArraySegmentStreamTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Stack/Buffers/ArraySegmentStreamTests.cs @@ -205,6 +205,7 @@ public void ArraySegmentStreamWrite( Assert.That(position, Is.EqualTo(chunkSize * i)); } + int bytesRead; switch (random.Next(3)) { case 0: @@ -215,7 +216,8 @@ public void ArraySegmentStreamWrite( break; default: #if NET5_0_OR_GREATER - writer.Read(buffer.AsSpan(0, chunkSize)); + bytesRead = writer.Read(buffer.AsSpan(0, chunkSize)); + Assert.That(chunkSize, Is.EqualTo(bytesRead)); for (int v = 0; v < chunkSize; v++) { Assert.That(buffer[v], Is.EqualTo((byte)i)); @@ -223,7 +225,8 @@ public void ArraySegmentStreamWrite( break; #endif case 1: - writer.Read(buffer, 0, chunkSize); + bytesRead = writer.Read(buffer, 0, chunkSize); + Assert.That(chunkSize, Is.EqualTo(bytesRead)); for (int v = 0; v < chunkSize; v++) { Assert.That(buffer[v], Is.EqualTo((byte)i)); diff --git a/Tests/Opc.Ua.Core.Tests/Types/Utils/UtilsIsEqualTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Utils/UtilsIsEqualTests.cs index 14339f59e7..5ad041136a 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Utils/UtilsIsEqualTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Utils/UtilsIsEqualTests.cs @@ -54,7 +54,7 @@ public class UtilsIsEqualTests [Params(32, 128, 1024, 4096, 65536)] public int PayLoadSize { get; set; } = 1024; - private bool _windows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private bool m_windows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); /// /// Test IsEqual using the generic IsEqual from previous versions. @@ -134,7 +134,7 @@ public bool ForLoopBinaryCompare() [Benchmark] public bool MemCmpByteArrayCompare() { - if (_windows) + if (m_windows) { // Validate buffers are the same length. // This also ensures that the count does not exceed the length of either buffer. diff --git a/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs b/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs index 066206140d..1c24470535 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs @@ -35,6 +35,7 @@ using System.ComponentModel; using System.Threading; using Opc.Ua.PubSub.PublishedData; +using System.Globalization; namespace Opc.Ua.PubSub.Tests.Encoding { @@ -2841,21 +2842,21 @@ public static void UpdateSnapshotData(UaPubSubApplication pubSubApplication, UIn DataValue boolToggle = pubSubApplication.DataStore.ReadPublishedDataItem(new NodeId("BoolToggle", namespaceIndexAllTypes), Attributes.Value); if (boolToggle.Value is bool) { - bool boolVal = Convert.ToBoolean(boolToggle.Value); + bool boolVal = Convert.ToBoolean(boolToggle.Value, CultureInfo.InvariantCulture); boolToggle.Value = !boolVal; pubSubApplication.DataStore.WritePublishedDataItem(new NodeId("BoolToggle", namespaceIndexAllTypes), Attributes.Value, boolToggle); } DataValue byteValue = pubSubApplication.DataStore.ReadPublishedDataItem(new NodeId("Byte", namespaceIndexAllTypes), Attributes.Value); if (byteValue.Value is byte) { - byte byteVal = Convert.ToByte(byteValue.Value); + byte byteVal = Convert.ToByte(byteValue.Value, CultureInfo.InvariantCulture); byteValue.Value = ++byteVal; pubSubApplication.DataStore.WritePublishedDataItem(new NodeId("Byte", namespaceIndexAllTypes), Attributes.Value, byteValue); } DataValue int16Value = pubSubApplication.DataStore.ReadPublishedDataItem(new NodeId("Int16", namespaceIndexAllTypes), Attributes.Value); if (int16Value.Value is Int16) { - Int16 int16Val = Convert.ToInt16(int16Value.Value); + Int16 int16Val = Convert.ToInt16(int16Value.Value, CultureInfo.InvariantCulture); int intIdentifier = int16Val; Interlocked.CompareExchange(ref intIdentifier, 0, Int16.MaxValue); int16Value.Value = (Int16)Interlocked.Increment(ref intIdentifier); @@ -2864,7 +2865,7 @@ public static void UpdateSnapshotData(UaPubSubApplication pubSubApplication, UIn DataValue int32Value = pubSubApplication.DataStore.ReadPublishedDataItem(new NodeId("Int32", namespaceIndexAllTypes), Attributes.Value); if (int32Value.Value is Int32) { - Int32 int32Val = Convert.ToInt32(int16Value.Value); + Int32 int32Val = Convert.ToInt32(int16Value.Value, CultureInfo.InvariantCulture); int intIdentifier = int32Val; Interlocked.CompareExchange(ref intIdentifier, 0, Int32.MaxValue); int32Value.Value = (Int32)Interlocked.Increment(ref intIdentifier); @@ -2873,7 +2874,7 @@ public static void UpdateSnapshotData(UaPubSubApplication pubSubApplication, UIn DataValue uInt16Value = pubSubApplication.DataStore.ReadPublishedDataItem(new NodeId("UInt16", namespaceIndexAllTypes), Attributes.Value); if (uInt16Value.Value is UInt16) { - UInt16 uInt16Val = Convert.ToUInt16(uInt16Value.Value); + UInt16 uInt16Val = Convert.ToUInt16(uInt16Value.Value, CultureInfo.InvariantCulture); int intIdentifier = uInt16Val; Interlocked.CompareExchange(ref intIdentifier, 0, UInt16.MaxValue); uInt16Value.Value = (UInt16)Interlocked.Increment(ref intIdentifier); @@ -2882,7 +2883,7 @@ public static void UpdateSnapshotData(UaPubSubApplication pubSubApplication, UIn DataValue uInt32Value = pubSubApplication.DataStore.ReadPublishedDataItem(new NodeId("UInt32", namespaceIndexAllTypes), Attributes.Value); if (uInt32Value.Value is UInt32) { - UInt32 uInt32Val = Convert.ToUInt32(uInt32Value.Value); + UInt32 uInt32Val = Convert.ToUInt32(uInt32Value.Value, CultureInfo.InvariantCulture); long longIdentifier = uInt32Val; Interlocked.CompareExchange(ref longIdentifier, 0, UInt32.MaxValue); uInt32Value.Value = (UInt32)Interlocked.Increment(ref longIdentifier); @@ -2891,7 +2892,7 @@ public static void UpdateSnapshotData(UaPubSubApplication pubSubApplication, UIn DataValue doubleValue = pubSubApplication.DataStore.ReadPublishedDataItem(new NodeId("Double", namespaceIndexAllTypes), Attributes.Value); if (doubleValue.Value is double) { - double doubleVal = Convert.ToDouble(doubleValue.Value); + double doubleVal = Convert.ToDouble(doubleValue.Value, CultureInfo.InvariantCulture); Interlocked.CompareExchange(ref doubleVal, 0, double.MaxValue); doubleValue.Value = ++doubleVal; pubSubApplication.DataStore.WritePublishedDataItem(new NodeId("Double", namespaceIndexAllTypes), Attributes.Value, doubleValue); diff --git a/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs b/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs index 8cd5d271ef..7cc541f3f6 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs @@ -79,7 +79,7 @@ public void ValidateMqttLocalPubSubConnectionWithUadp( //Arrange UInt16 writerGroupId = 1; - string mqttLocalBrokerUrl = string.Format(MqttUrlFormat, Utils.UriSchemeMqtt, "localhost"); + string mqttLocalBrokerUrl = Utils.Format(MqttUrlFormat, Utils.UriSchemeMqtt, "localhost"); ITransportProtocolConfiguration mqttConfiguration = new MqttClientProtocolConfiguration(version: EnumMqttProtocolVersion.V500); @@ -190,7 +190,7 @@ public void ValidateMqttLocalPubSubConnectionWithDeltaUadp( //Arrange UInt16 writerGroupId = 1; - string mqttLocalBrokerUrl = string.Format(MqttUrlFormat, Utils.UriSchemeMqtt, "localhost"); + string mqttLocalBrokerUrl = Utils.Format(MqttUrlFormat, Utils.UriSchemeMqtt, "localhost"); ITransportProtocolConfiguration mqttConfiguration = new MqttClientProtocolConfiguration(version: EnumMqttProtocolVersion.V500); @@ -326,7 +326,7 @@ public void ValidateMqttLocalPubSubConnectionWithJson( //Arrange UInt16 writerGroupId = 1; - string mqttLocalBrokerUrl = string.Format(MqttUrlFormat, Utils.UriSchemeMqtt, "localhost"); + string mqttLocalBrokerUrl = Utils.Format(MqttUrlFormat, Utils.UriSchemeMqtt, "localhost"); ITransportProtocolConfiguration mqttConfiguration = new MqttClientProtocolConfiguration(version: EnumMqttProtocolVersion.V500); @@ -452,7 +452,7 @@ public void ValidateMqttLocalPubSubConnectionWithDeltaJson( //Arrange UInt16 writerGroupId = 1; - string mqttLocalBrokerUrl = string.Format(MqttUrlFormat, Utils.UriSchemeMqtt, "localhost"); + string mqttLocalBrokerUrl = Utils.Format(MqttUrlFormat, Utils.UriSchemeMqtt, "localhost"); ITransportProtocolConfiguration mqttConfiguration = new MqttClientProtocolConfiguration(version: EnumMqttProtocolVersion.V500); diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs index 767f96a233..85071c8de2 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs +++ b/Tests/Opc.Ua.Security.Certificates.Tests/CertificateTestsForECDsa.cs @@ -34,7 +34,7 @@ using System.Security.Cryptography.X509Certificates; using NUnit.Framework; using Opc.Ua.Tests; - using Assert = NUnit.Framework.Legacy.ClassicAssert; +using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Opc.Ua.Security.Certificates.Tests { @@ -95,7 +95,7 @@ public void VerifyOneSelfSignedAppCertForAll() .SetECCurve(eCCurveHash.Curve) .CreateForECDsa()) { - + Assert.NotNull(cert); WriteCertificate(cert, $"Default cert with ECDsa {eCCurveHash.Curve.Oid.FriendlyName} {eCCurveHash.HashAlgorithmName} signature."); Assert.AreEqual(eCCurveHash.HashAlgorithmName, Oids.GetHashAlgorithmName(cert.SignatureAlgorithm.Value)); @@ -109,7 +109,7 @@ public void VerifyOneSelfSignedAppCertForAll() } /// - /// Create the default RSA certificate. + /// Create the default ECDsa certificate. /// [Theory, Repeat(10)] public void CreateSelfSignedForECDsaDefaultTest(ECCurveHashPair eccurveHashPair) @@ -355,13 +355,33 @@ private static ECCurveHashPair[] GetECCurveHashPairs() { var result = new ECCurveHashPairCollection { { ECCurve.NamedCurves.nistP256, HashAlgorithmName.SHA256 }, - { ECCurve.NamedCurves.nistP384, HashAlgorithmName.SHA384 } }; - if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - result.AddRange(new ECCurveHashPairCollection { + { ECCurve.NamedCurves.nistP384, HashAlgorithmName.SHA384 }, { ECCurve.NamedCurves.brainpoolP256r1, HashAlgorithmName.SHA256 }, - { ECCurve.NamedCurves.brainpoolP384r1, HashAlgorithmName.SHA384 }}); + { ECCurve.NamedCurves.brainpoolP384r1, HashAlgorithmName.SHA384 } + }; + + int i = 0; + while (i < result.Count) + { + ECDsa key = null; + + // test if curve is supported + try + { + key = ECDsa.Create(result[i].Curve); + } + catch + { + result.RemoveAt(i); + continue; + } + finally + { + Utils.SilentDispose(key); + } + i++; } + return result.ToArray(); } diff --git a/common.props b/common.props index 755327f6f9..c80f958047 100644 --- a/common.props +++ b/common.props @@ -5,6 +5,7 @@ 1.05.374 preview-$([System.DateTime]::Now.ToString("yyyyMMdd")) Copyright © 2004-2024 OPC Foundation, Inc + OPCFoundation.NetStandard OPC Foundation OPC Foundation NU5125;CA2254;CA1307 From de688aca18b632f8a0cf6298bb9e7348de37bbef Mon Sep 17 00:00:00 2001 From: romanett Date: Thu, 19 Dec 2024 17:17:25 +0100 Subject: [PATCH 05/10] [Server] Improve GetManagerHandle & introduce a threadSafe NodeIdDictionary (#2915) * Make NodeIdDictionary threadsafe by changing the underlying dict from `Dictionary` to `ConcurrentDictionary` * reduce blocking in CustomNodeManager2 for GetManagerHandle & other uncritical supporting functions --- .../Diagnostics/CustomNodeManager.cs | 668 +++++++++--------- .../Types/BuiltIn/NodeIdDictionary.cs | 35 +- .../CustomNodeManagerTests.cs | 232 ++++++ 3 files changed, 600 insertions(+), 335 deletions(-) create mode 100644 Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs diff --git a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs index fa25395da6..cc74f527c7 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs @@ -1,3 +1,4 @@ + /* ======================================================================== * Copyright (c) 2005-2022 The OPC Foundation, Inc. All rights reserved. * @@ -32,6 +33,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Threading; namespace Opc.Ua.Server @@ -70,12 +72,10 @@ protected CustomNodeManager2( // set defaults. m_maxQueueSize = 1000; - if (configuration != null) + + if (configuration?.ServerConfiguration != null) { - if (configuration.ServerConfiguration != null) - { - m_maxQueueSize = (uint)configuration.ServerConfiguration.MaxNotificationQueueSize; - } + m_maxQueueSize = (uint)configuration.ServerConfiguration.MaxNotificationQueueSize; } // save a reference to the UA server instance that owns the node manager. @@ -88,23 +88,25 @@ protected CustomNodeManager2( // the strategy used by a NodeManager depends on what kind of information it provides. m_systemContext.NodeIdFactory = this; - // create the table of namespaces that are used by the NodeManager. - m_namespaceUris = namespaceUris; - // add the uris to the server's namespace table and cache the indexes. + ushort[] namespaceIndexes = Array.Empty(); if (namespaceUris != null) { - m_namespaceIndexes = new ushort[m_namespaceUris.Length]; + namespaceIndexes = new ushort[namespaceUris.Length]; - for (int ii = 0; ii < m_namespaceUris.Length; ii++) + for (int ii = 0; ii < namespaceUris.Length; ii++) { - m_namespaceIndexes[ii] = m_server.NamespaceUris.GetIndexOrAppend(m_namespaceUris[ii]); + namespaceIndexes[ii] = m_server.NamespaceUris.GetIndexOrAppend(namespaceUris[ii]); } } + // add the table of namespaces that are used by the NodeManager. + m_namespaceUris = namespaceUris; + m_namespaceIndexes = namespaceIndexes; + // create the table of monitored nodes. // these are created by the node manager whenever a client subscribe to an attribute of the node. - m_monitoredNodes = new Dictionary(); + m_monitoredNodes = new NodeIdDictionary(); } #endregion @@ -125,17 +127,14 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - lock (m_lock) + lock (Lock) { - if (m_predefinedNodes != null) + foreach (NodeState node in m_predefinedNodes?.Values) { - foreach (NodeState node in m_predefinedNodes.Values) - { - Utils.SilentDispose(node); - } - - m_predefinedNodes.Clear(); + Utils.SilentDispose(node); } + + m_predefinedNodes.Clear(); } } } @@ -191,7 +190,7 @@ public ushort NamespaceIndex /// Gets the namespace indexes owned by the node manager. /// /// The namespace indexes. - public ushort[] NamespaceIndexes + public IReadOnlyList NamespaceIndexes { get { return m_namespaceIndexes; } } @@ -220,10 +219,7 @@ public string AliasRoot /// /// The predefined nodes managed by the node manager. /// - protected NodeIdDictionary PredefinedNodes - { - get { return m_predefinedNodes; } - } + protected NodeIdDictionary PredefinedNodes => m_predefinedNodes; /// /// The root notifiers for the node manager. @@ -236,10 +232,7 @@ protected List RootNotifiers /// /// Gets the table of nodes being monitored. /// - protected Dictionary MonitoredNodes - { - get { return m_monitoredNodes; } - } + protected NodeIdDictionary MonitoredNodes => m_monitoredNodes; /// /// Sets the namespaces supported by the NodeManager. @@ -247,16 +240,17 @@ protected Dictionary MonitoredNodes /// The namespace uris. protected void SetNamespaces(params string[] namespaceUris) { - // create the table of namespaces that are used by the NodeManager. - m_namespaceUris = namespaceUris; - // add the uris to the server's namespace table and cache the indexes. - m_namespaceIndexes = new ushort[m_namespaceUris.Length]; + var namespaceIndexes = new ushort[namespaceUris.Length]; - for (int ii = 0; ii < m_namespaceUris.Length; ii++) + for (int ii = 0; ii < namespaceUris.Length; ii++) { - m_namespaceIndexes[ii] = m_server.NamespaceUris.GetIndexOrAppend(m_namespaceUris[ii]); + namespaceIndexes[ii] = m_server.NamespaceUris.GetIndexOrAppend(namespaceUris[ii]); } + + // create the immutable table of namespaces that are used by the NodeManager. + m_namespaceUris = namespaceUris; + m_namespaceIndexes = namespaceIndexes; } /// @@ -264,18 +258,24 @@ protected void SetNamespaces(params string[] namespaceUris) /// protected void SetNamespaceIndexes(ushort[] namespaceIndexes) { - m_namespaceIndexes = namespaceIndexes; - m_namespaceUris = new string[namespaceIndexes.Length]; + var namespaceUris = new string[namespaceIndexes.Length]; for (int ii = 0; ii < namespaceIndexes.Length; ii++) { - m_namespaceUris[ii] = m_server.NamespaceUris.GetString(namespaceIndexes[ii]); + namespaceUris[ii] = m_server.NamespaceUris.GetString(namespaceIndexes[ii]); } + + // create the immutable table of namespaces that are used by the NodeManager. + m_namespaceUris = namespaceUris; + m_namespaceIndexes = namespaceIndexes; } /// /// Returns true if the namespace for the node id is one of the namespaces managed by the node manager. /// + /// + /// It is thread safe to call this method outside the node manager lock. + /// /// The node id to check. /// True if the namespace is one of the nodes. protected virtual bool IsNodeIdInNamespace(NodeId nodeId) @@ -287,20 +287,15 @@ protected virtual bool IsNodeIdInNamespace(NodeId nodeId) } // quickly exclude nodes that not in the namespace. - for (int ii = 0; ii < m_namespaceIndexes.Length; ii++) - { - if (nodeId.NamespaceIndex == m_namespaceIndexes[ii]) - { - return true; - } - } - - return false; + return m_namespaceIndexes.Contains(nodeId.NamespaceIndex); } /// /// Returns the node if the handle refers to a node managed by this manager. /// + /// + /// It is thread safe to call this method outside the node manager lock. + /// /// The handle to check. /// Non-null if the handle belongs to the node manager. protected virtual NodeHandle IsHandleInNamespace(object managerHandle) @@ -325,22 +320,13 @@ protected virtual NodeHandle IsHandleInNamespace(object managerHandle) /// public NodeState Find(NodeId nodeId) { - lock (Lock) + NodeState node = null; + if (m_predefinedNodes?.TryGetValue(nodeId, out node) == true) { - if (PredefinedNodes == null) - { - return null; - } - - NodeState node = null; - - if (!PredefinedNodes.TryGetValue(nodeId, out node)) - { - return null; - } - return node; } + + return null; } /// @@ -359,7 +345,7 @@ public NodeId CreateNode( QualifiedName browseName, BaseInstanceState instance) { - ServerSystemContext contextToUse = (ServerSystemContext)m_systemContext.Copy(context); + ServerSystemContext contextToUse = m_systemContext.Copy(context); lock (Lock) { @@ -401,34 +387,23 @@ public bool DeleteNode( { ServerSystemContext contextToUse = m_systemContext.Copy(context); - bool found = false; List referencesToRemove = new List(); - lock (Lock) + NodeState node = null; + if (m_predefinedNodes?.TryGetValue(nodeId, out node) != true) { - if (m_predefinedNodes == null) - { - return false; - } - - NodeState node = null; - - if (PredefinedNodes.TryGetValue(nodeId, out node)) - { - RemovePredefinedNode(contextToUse, node, referencesToRemove); - found = true; - } - - RemoveRootNotifier(node); + return false; } - // must release the lock before removing cross references to other node managers. + RemovePredefinedNode(contextToUse, node, referencesToRemove); + RemoveRootNotifier(node); + if (referencesToRemove.Count > 0) { Server.NodeManager.RemoveReferences(referencesToRemove); } - return found; + return true; } /// @@ -586,7 +561,7 @@ protected virtual void AddPredefinedNode(ISystemContext context, NodeState node) } NodeState activeNode = AddBehaviourToPredefinedNode(context, node); - m_predefinedNodes[activeNode.NodeId] = activeNode; + m_predefinedNodes.AddOrUpdate(activeNode.NodeId, activeNode, (key, _) => activeNode); BaseTypeState type = activeNode as BaseTypeState; @@ -594,28 +569,30 @@ protected virtual void AddPredefinedNode(ISystemContext context, NodeState node) { AddTypesToTypeTree(type); } - - // update the root notifiers. - if (m_rootNotifiers != null) + lock (Lock) { - for (int ii = 0; ii < m_rootNotifiers.Count; ii++) + // update the root notifiers. + if (m_rootNotifiers != null) { - if (m_rootNotifiers[ii].NodeId == activeNode.NodeId) + for (int ii = 0; ii < m_rootNotifiers.Count; ii++) { - m_rootNotifiers[ii] = activeNode; - - // need to prevent recursion with the server object. - if (activeNode.NodeId != ObjectIds.Server) + if (m_rootNotifiers[ii].NodeId == activeNode.NodeId) { - activeNode.OnReportEvent = OnReportEvent; + m_rootNotifiers[ii] = activeNode; - if (!activeNode.ReferenceExists(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server)) + // need to prevent recursion with the server object. + if (activeNode.NodeId != ObjectIds.Server) { - activeNode.AddReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); + activeNode.OnReportEvent = OnReportEvent; + + if (!activeNode.ReferenceExists(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server)) + { + activeNode.AddReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); + } } - } - break; + break; + } } } } @@ -637,20 +614,16 @@ protected virtual void RemovePredefinedNode( NodeState node, List referencesToRemove) { - if (m_predefinedNodes == null) + if (m_predefinedNodes?.TryRemove(node.NodeId, out _) != true) { return; } - - m_predefinedNodes.Remove(node.NodeId); node.UpdateChangeMasks(NodeStateChangeMasks.Deleted); node.ClearChangeMasks(context, false); OnNodeRemoved(node); // remove from the parent. - BaseInstanceState instance = node as BaseInstanceState; - - if (instance != null && instance.Parent != null) + if (node is BaseInstanceState instance && instance.Parent != null) { instance.Parent.RemoveChild(instance); } @@ -714,14 +687,8 @@ protected virtual void OnNodeRemoved(NodeState node) /// A list of references to add to external targets. protected virtual void AddReverseReferences(IDictionary> externalReferences) { - if (m_predefinedNodes == null) + foreach (NodeState source in m_predefinedNodes?.Values) { - return; - } - - foreach (NodeState source in m_predefinedNodes.Values) - { - IList references = new List(); source.GetReferences(SystemContext, references); @@ -804,11 +771,11 @@ protected void AddExternalReference( } // add reserve reference from external node. - ReferenceNode referenceToAdd = new ReferenceNode(); - - referenceToAdd.ReferenceTypeId = referenceTypeId; - referenceToAdd.IsInverse = isInverse; - referenceToAdd.TargetId = targetId; + ReferenceNode referenceToAdd = new ReferenceNode { + ReferenceTypeId = referenceTypeId, + IsInverse = isInverse, + TargetId = targetId + }; referencesToAdd.Add(referenceToAdd); } @@ -841,16 +808,13 @@ protected void AddTypesToTypeTree(BaseTypeState type) /// protected void AddTypesToTypeTree(NodeId typeId) { - NodeState node = null; - - if (!PredefinedNodes.TryGetValue(typeId, out node)) + if (!m_predefinedNodes.TryGetValue(typeId, out NodeState node)) { return; } - BaseTypeState type = node as BaseTypeState; - if (type == null) + if (!(node is BaseTypeState type)) { return; } @@ -869,9 +833,7 @@ public NodeState FindPredefinedNode(NodeId nodeId, Type expectedType) return null; } - NodeState node = null; - - if (!PredefinedNodes.TryGetValue(nodeId, out node)) + if (!m_predefinedNodes.TryGetValue(nodeId, out NodeState node)) { return null; } @@ -893,16 +855,14 @@ public NodeState FindPredefinedNode(NodeId nodeId, Type expectedType) /// public virtual void DeleteAddressSpace() { - lock (m_lock) + if (m_predefinedNodes != null) { - if (m_predefinedNodes != null) - { - foreach (NodeState node in m_predefinedNodes.Values) - { - Utils.SilentDispose(node); - } + var nodes = m_predefinedNodes.Values.ToArray(); + m_predefinedNodes.Clear(); - m_predefinedNodes.Clear(); + foreach (NodeState node in nodes) + { + Utils.SilentDispose(node); } } } @@ -917,15 +877,15 @@ public virtual void DeleteAddressSpace() /// public virtual object GetManagerHandle(NodeId nodeId) { - lock (m_lock) - { - return GetManagerHandle(m_systemContext, nodeId, null); - } + return GetManagerHandle(m_systemContext, nodeId, null); } /// /// Returns a unique handle for the node. /// + /// + /// It is thread safe to call this method outside the node manager lock. + /// protected virtual NodeHandle GetManagerHandle(ServerSystemContext context, NodeId nodeId, IDictionary cache) { if (!IsNodeIdInNamespace(nodeId)) @@ -934,18 +894,16 @@ protected virtual NodeHandle GetManagerHandle(ServerSystemContext context, NodeI } NodeState node = null; - if (m_predefinedNodes?.TryGetValue(nodeId, out node) == true) { - NodeHandle handle = new NodeHandle(); - - handle.NodeId = nodeId; - handle.Node = node; - handle.Validated = true; + var handle = new NodeHandle { + NodeId = nodeId, + Node = node, + Validated = true + }; return handle; } - return null; } @@ -965,11 +923,12 @@ public virtual void AddReferences(IDictionary> referen NodeHandle source = GetManagerHandle(m_systemContext, current.Key, null); // only support external references to nodes that are stored in memory. - if (source == null || !source.Validated || source.Node == null) + if (source?.Node == null || !source.Validated) { continue; } + // add reference to external target. foreach (IReference reference in current.Value) { @@ -992,22 +951,22 @@ public virtual ServiceResult DeleteReference( ExpandedNodeId targetId, bool deleteBidirectional) { - lock (Lock) - { - // get the handle. - NodeHandle source = IsHandleInNamespace(sourceHandle); + // get the handle. + NodeHandle source = IsHandleInNamespace(sourceHandle); - if (source == null) - { - return StatusCodes.BadNodeIdUnknown; - } + if (source == null) + { + return StatusCodes.BadNodeIdUnknown; + } - // only support external references to nodes that are stored in memory. - if (!source.Validated || source.Node == null) - { - return StatusCodes.BadNotSupported; - } + // only support external references to nodes that are stored in memory. + if (!source.Validated || source.Node == null) + { + return StatusCodes.BadNotSupported; + } + lock (Lock) + { // only support references to Source Areas. source.Node.RemoveReference(referenceTypeId, isInverse, targetId); @@ -1042,16 +1001,16 @@ public virtual NodeMetadata GetNodeMetadata( { ServerSystemContext systemContext = m_systemContext.Copy(context); - lock (Lock) - { - // check for valid handle. - NodeHandle handle = IsHandleInNamespace(targetHandle); + // check for valid handle. + NodeHandle handle = IsHandleInNamespace(targetHandle); - if (handle == null) - { - return null; - } + if (handle == null) + { + return null; + } + lock (Lock) + { // validate node. NodeState target = ValidateNode(systemContext, handle, null); @@ -1078,11 +1037,11 @@ public virtual NodeMetadata GetNodeMetadata( Attributes.UserRolePermissions); // construct the meta-data object. - NodeMetadata metadata = new NodeMetadata(target, target.NodeId); - - metadata.NodeClass = target.NodeClass; - metadata.BrowseName = target.BrowseName; - metadata.DisplayName = target.DisplayName; + NodeMetadata metadata = new NodeMetadata(target, target.NodeId) { + NodeClass = target.NodeClass, + BrowseName = target.BrowseName, + DisplayName = target.DisplayName + }; if (values[0] != null && values[1] != null) { @@ -1131,9 +1090,7 @@ public virtual NodeMetadata GetNodeMetadata( SetDefaultPermissions(systemContext, target, metadata); // get instance references. - BaseInstanceState instance = target as BaseInstanceState; - - if (instance != null) + if (target is BaseInstanceState instance) { metadata.TypeDefinition = instance.TypeDefinitionId; metadata.ModellingRule = instance.ModellingRuleId; @@ -1222,16 +1179,15 @@ public virtual void Browse( INodeBrowser browser = null; + // check for valid handle. + NodeHandle handle = IsHandleInNamespace(continuationPoint.NodeToBrowse); + + if (handle == null) + { + throw new ServiceResultException(StatusCodes.BadNodeIdUnknown); + } lock (Lock) { - // check for valid handle. - NodeHandle handle = IsHandleInNamespace(continuationPoint.NodeToBrowse); - - if (handle == null) - { - throw new ServiceResultException(StatusCodes.BadNodeIdUnknown); - } - // validate node. NodeState source = ValidateNode(systemContext, handle, null); @@ -1487,16 +1443,16 @@ public virtual void TranslateBrowsePath( ServerSystemContext systemContext = m_systemContext.Copy(context); IDictionary operationCache = new NodeIdDictionary(); - lock (Lock) - { - // check for valid handle. - NodeHandle handle = IsHandleInNamespace(sourceHandle); + // check for valid handle. + NodeHandle handle = IsHandleInNamespace(sourceHandle); - if (handle == null) - { - return; - } + if (handle == null) + { + return; + } + lock (Lock) + { // validate node. NodeState source = ValidateNode(systemContext, handle, operationCache); @@ -2945,16 +2901,16 @@ public virtual void Call( MethodState method = null; - lock (Lock) - { - // check for valid handle. - NodeHandle handle = GetManagerHandle(systemContext, methodToCall.ObjectId, operationCache); + // check for valid handle. + NodeHandle handle = GetManagerHandle(systemContext, methodToCall.ObjectId, operationCache); - if (handle == null) - { - continue; - } + if (handle == null) + { + continue; + } + lock (Lock) + { // owned by this node manager. methodToCall.Processed = true; @@ -3106,16 +3062,16 @@ public virtual ServiceResult SubscribeToEvents( { ServerSystemContext systemContext = SystemContext.Copy(context); - lock (Lock) - { - // check for valid handle. - NodeHandle handle = IsHandleInNamespace(sourceId); + // check for valid handle. + NodeHandle handle = IsHandleInNamespace(sourceId); - if (handle == null) - { - return StatusCodes.BadNodeIdInvalid; - } + if (handle == null) + { + return StatusCodes.BadNodeIdInvalid; + } + lock (Lock) + { // check for valid node. NodeState source = ValidateNode(systemContext, handle, null); @@ -3172,58 +3128,61 @@ public virtual ServiceResult SubscribeToAllEvents( /// protected virtual void AddRootNotifier(NodeState notifier) { - if (m_rootNotifiers == null) + lock (Lock) { - m_rootNotifiers = new List(); - } + if (m_rootNotifiers == null) + { + m_rootNotifiers = new List(); + } - bool mustAdd = true; + bool mustAdd = true; - for (int ii = 0; ii < m_rootNotifiers.Count; ii++) - { - if (Object.ReferenceEquals(notifier, m_rootNotifiers[ii])) + for (int ii = 0; ii < m_rootNotifiers.Count; ii++) { - return; + if (Object.ReferenceEquals(notifier, m_rootNotifiers[ii])) + { + return; + } + + if (m_rootNotifiers[ii].NodeId == notifier.NodeId) + { + m_rootNotifiers[ii] = notifier; + mustAdd = false; + break; + } } - if (m_rootNotifiers[ii].NodeId == notifier.NodeId) + if (mustAdd) { - m_rootNotifiers[ii] = notifier; - mustAdd = false; - break; + m_rootNotifiers.Add(notifier); } - } - - if (mustAdd) - { - m_rootNotifiers.Add(notifier); - } - // need to prevent recursion with the server object. - if (notifier.NodeId != ObjectIds.Server) - { - notifier.OnReportEvent = OnReportEvent; - - if (!notifier.ReferenceExists(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server)) + // need to prevent recursion with the server object. + if (notifier.NodeId != ObjectIds.Server) { - notifier.AddReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); - } - } + notifier.OnReportEvent = OnReportEvent; - // subscribe to existing events. - if (m_server.EventManager != null) - { - IList monitoredItems = m_server.EventManager.GetMonitoredItems(); + if (!notifier.ReferenceExists(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server)) + { + notifier.AddReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); + } + } - for (int ii = 0; ii < monitoredItems.Count; ii++) + // subscribe to existing events. + if (m_server.EventManager != null) { - if (monitoredItems[ii].MonitoringAllEvents) + IList monitoredItems = m_server.EventManager.GetMonitoredItems(); + + for (int ii = 0; ii < monitoredItems.Count; ii++) { - SubscribeToEvents( + if (monitoredItems[ii].MonitoringAllEvents) + { + SubscribeToEvents( SystemContext, notifier, monitoredItems[ii], true); + } } } } @@ -3235,16 +3194,19 @@ protected virtual void AddRootNotifier(NodeState notifier) /// The notifier. protected virtual void RemoveRootNotifier(NodeState notifier) { - if (m_rootNotifiers != null) + lock (Lock) { - for (int ii = 0; ii < m_rootNotifiers.Count; ii++) + if (m_rootNotifiers != null) { - if (Object.ReferenceEquals(notifier, m_rootNotifiers[ii])) + for (int ii = 0; ii < m_rootNotifiers.Count; ii++) { - notifier.OnReportEvent = null; - notifier.RemoveReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); - m_rootNotifiers.RemoveAt(ii); - break; + if (Object.ReferenceEquals(notifier, m_rootNotifiers[ii])) + { + notifier.OnReportEvent = null; + notifier.RemoveReference(ReferenceTypeIds.HasNotifier, true, ObjectIds.Server); + m_rootNotifiers.RemoveAt(ii); + break; + } } } } @@ -3305,13 +3267,9 @@ protected virtual ServiceResult SubscribeToEvents( } // only objects or views can be subscribed to. - BaseObjectState instance = source as BaseObjectState; - - if (instance == null || (instance.EventNotifier & EventNotifiers.SubscribeToEvents) == 0) + if (!(source is BaseObjectState instance) || (instance.EventNotifier & EventNotifiers.SubscribeToEvents) == 0) { - ViewState view = source as ViewState; - - if (view == null || (view.EventNotifier & EventNotifiers.SubscribeToEvents) == 0) + if (!(source is ViewState view) || (view.EventNotifier & EventNotifiers.SubscribeToEvents) == 0) { return StatusCodes.BadNotSupported; } @@ -3457,43 +3415,41 @@ public virtual void CreateMonitoredItems( List nodesToValidate = new List(); List createdItems = new List(); - lock (Lock) + + for (int ii = 0; ii < itemsToCreate.Count; ii++) { - for (int ii = 0; ii < itemsToCreate.Count; ii++) - { - MonitoredItemCreateRequest itemToCreate = itemsToCreate[ii]; + MonitoredItemCreateRequest itemToCreate = itemsToCreate[ii]; - // skip items that have already been processed. - if (itemToCreate.Processed) - { - continue; - } + // skip items that have already been processed. + if (itemToCreate.Processed) + { + continue; + } - ReadValueId itemToMonitor = itemToCreate.ItemToMonitor; + ReadValueId itemToMonitor = itemToCreate.ItemToMonitor; - // check for valid handle. - NodeHandle handle = GetManagerHandle(systemContext, itemToMonitor.NodeId, operationCache); + // check for valid handle. + NodeHandle handle = GetManagerHandle(systemContext, itemToMonitor.NodeId, operationCache); - if (handle == null) - { - continue; - } + if (handle == null) + { + continue; + } - // owned by this node manager. - itemToCreate.Processed = true; + // owned by this node manager. + itemToCreate.Processed = true; - // must validate node in a separate operation. - errors[ii] = StatusCodes.BadNodeIdUnknown; + // must validate node in a separate operation. + errors[ii] = StatusCodes.BadNodeIdUnknown; - handle.Index = ii; - nodesToValidate.Add(handle); - } + handle.Index = ii; + nodesToValidate.Add(handle); + } - // check for nothing to do. - if (nodesToValidate.Count == 0) - { - return; - } + // check for nothing to do. + if (nodesToValidate.Count == 0) + { + return; } // validates the nodes (reads values from the underlying data source if required). @@ -3986,27 +3942,43 @@ public virtual void ModifyMonitoredItems( IList filterErrors) { ServerSystemContext systemContext = m_systemContext.Copy(context); - List modifiedItems = new List(); + var nodesInNamespace = new List<(int, NodeHandle)>(monitoredItems.Count); - lock (Lock) + for (int ii = 0; ii < monitoredItems.Count; ii++) { - for (int ii = 0; ii < monitoredItems.Count; ii++) + MonitoredItemModifyRequest itemToModify = itemsToModify[ii]; + + // skip items that have already been processed. + if (itemToModify.Processed || monitoredItems[ii] == null) { - MonitoredItemModifyRequest itemToModify = itemsToModify[ii]; + continue; + } - // skip items that have already been processed. - if (itemToModify.Processed || monitoredItems[ii] == null) - { - continue; - } + // check handle. + NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); - // check handle. - NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); + if (handle == null) + { + continue; + } - if (handle == null) - { - continue; - } + nodesInNamespace.Add((ii, handle)); + } + + if (nodesInNamespace.Count == 0) + { + return; + } + + var modifiedItems = new List(); + + lock (Lock) + { + foreach (var nodeInNamespace in nodesInNamespace) + { + int ii = nodeInNamespace.Item1; + var handle = nodeInNamespace.Item2; + MonitoredItemModifyRequest itemToModify = itemsToModify[ii]; // owned by this node manager. itemToModify.Processed = true; @@ -4168,25 +4140,40 @@ public virtual void DeleteMonitoredItems( IList errors) { ServerSystemContext systemContext = m_systemContext.Copy(context); - List deletedItems = new List(); + var nodesInNamespace = new List<(int, NodeHandle)>(monitoredItems.Count); - lock (Lock) + for (int ii = 0; ii < monitoredItems.Count; ii++) { - for (int ii = 0; ii < monitoredItems.Count; ii++) + // skip items that have already been processed. + if (processedItems[ii] || monitoredItems[ii] == null) { - // skip items that have already been processed. - if (processedItems[ii] || monitoredItems[ii] == null) - { - continue; - } + continue; + } - // check handle. - NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); + // check handle. + NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); - if (handle == null) - { - continue; - } + if (handle == null) + { + continue; + } + + nodesInNamespace.Add((ii, handle)); + } + + if (nodesInNamespace.Count == 0) + { + return; + } + + var deletedItems = new List(); + + lock (Lock) + { + foreach (var nodeInNamespace in nodesInNamespace) + { + int ii = nodeInNamespace.Item1; + var handle = nodeInNamespace.Item2; // owned by this node manager. processedItems[ii] = true; @@ -4239,7 +4226,7 @@ protected virtual ServiceResult DeleteMonitoredItem( // check if node is no longer being monitored. if (!monitoredNode.HasMonitoredItems) { - MonitoredNodes.Remove(handle.NodeId); + m_monitoredNodes.Remove(handle.NodeId); } } @@ -4344,25 +4331,40 @@ public virtual void SetMonitoringMode( IList errors) { ServerSystemContext systemContext = m_systemContext.Copy(context); - List changedItems = new List(); + var nodesInNamespace = new List<(int, NodeHandle)>(monitoredItems.Count); - lock (Lock) + for (int ii = 0; ii < monitoredItems.Count; ii++) { - for (int ii = 0; ii < monitoredItems.Count; ii++) + // skip items that have already been processed. + if (processedItems[ii] || monitoredItems[ii] == null) { - // skip items that have already been processed. - if (processedItems[ii] || monitoredItems[ii] == null) - { - continue; - } + continue; + } - // check handle. - NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); + // check handle. + NodeHandle handle = IsHandleInNamespace(monitoredItems[ii].ManagerHandle); - if (handle == null) - { - continue; - } + if (handle == null) + { + continue; + } + + nodesInNamespace.Add((ii, handle)); + } + + if (nodesInNamespace.Count == 0) + { + return; + } + + var changedItems = new List(); + + lock (Lock) + { + foreach (var nodeInNamespace in nodesInNamespace) + { + int ii = nodeInNamespace.Item1; + var handle = nodeInNamespace.Item2; // indicate whether it was processed or not. processedItems[ii] = true; @@ -4494,16 +4496,16 @@ public virtual NodeMetadata GetPermissionMetadata( { ServerSystemContext systemContext = m_systemContext.Copy(context); - lock (Lock) - { - // check for valid handle. - NodeHandle handle = IsHandleInNamespace(targetHandle); + // check for valid handle. + NodeHandle handle = IsHandleInNamespace(targetHandle); - if (handle == null) - { - return null; - } + if (handle == null) + { + return null; + } + lock (Lock) + { // validate node. NodeState target = ValidateNode(systemContext, handle, null); @@ -4699,7 +4701,7 @@ protected NodeState AddNodeToComponentCache(ISystemContext context, NodeHandle h if (m_componentCache == null) { - m_componentCache = new Dictionary(); + m_componentCache = new NodeIdDictionary(); } // check if a component is actually specified. @@ -4756,10 +4758,10 @@ protected NodeState AddNodeToComponentCache(ISystemContext context, NodeHandle h private readonly object m_lock = new object(); private IServerInternal m_server; private ServerSystemContext m_systemContext; - private string[] m_namespaceUris; - private ushort[] m_namespaceIndexes; - private Dictionary m_monitoredNodes; - private Dictionary m_componentCache; + private IReadOnlyList m_namespaceUris; + private IReadOnlyList m_namespaceIndexes; + private NodeIdDictionary m_monitoredNodes; + private NodeIdDictionary m_componentCache; private NodeIdDictionary m_predefinedNodes; private List m_rootNotifiers; private uint m_maxQueueSize; diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs index 87aa08341f..ef0f1c94f8 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs @@ -19,6 +19,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; namespace Opc.Ua @@ -27,7 +28,7 @@ namespace Opc.Ua /// /// A dictionary designed to provide efficient lookups for objects identified by a NodeId /// - public class NodeIdDictionary : Dictionary + public sealed class NodeIdDictionary : ConcurrentDictionary { private static readonly NodeIdComparer s_comparer = new NodeIdComparer(); @@ -41,9 +42,39 @@ public NodeIdDictionary() : base(s_comparer) /// /// Creates an empty dictionary with capacity. /// - public NodeIdDictionary(int capacity) : base(capacity, s_comparer) + public NodeIdDictionary(int capacity) : base(Environment.ProcessorCount, capacity, s_comparer) { } + + // helpers for the legacy implementation + + /// + public void Add(NodeId key, T value) + { + if (!TryAdd(key, value)) + { + throw new ArgumentException("An element with the same key already exists."); + } + } + + /// + public void Remove(NodeId key) + { + TryRemove(key, out _); + } + + /// + /// remove a entry from the dictionary only if it has the provided value + /// https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/ + /// + /// the key of the entry to remove + /// the value of the entry to remove + /// true if removed, false if not removed + public bool TryRemove(NodeId key, T value) + { + return ((ICollection>)this).Remove( + new KeyValuePair(key, value)); + } } #else // USE_LEGACY_IMPLEMENTATION diff --git a/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs b/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs new file mode 100644 index 0000000000..9565861c5a --- /dev/null +++ b/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs @@ -0,0 +1,232 @@ +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Opc.Ua.Server.Tests +{ + /// + /// Test + /// + [TestFixture, Category("CustomNodeManager")] + [SetCulture("en-us"), SetUICulture("en-us")] + [Parallelizable] + public class CustomNodeManagerTests + { + #region Test Methods + /// + /// Tests the componentCache methods with multiple threads + /// + [Test] + public async Task TestComponentCacheAsync() + { + var fixture = new ServerFixture(); + + try + { + // Arrange + const string ns = "http://test.org/UA/Data/"; + var server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + + var nodeManager = new TestableCustomNodeManger2(server.CurrentInstance, ns); + + + var baseObject = new BaseObjectState(null); + var nodeHandle = new NodeHandle(new NodeId((string)CommonTestWorkers.NodeIdTestSetStatic.First().Identifier, 0), baseObject); + + //Act + await RunTaskInParallel(() => UseComponentCacheAsync(nodeManager, baseObject, nodeHandle), 100); + + + //Assert, that entry was deleted from cache after parallel operations on the same node + NodeState handleFromCache = nodeManager.LookupNodeInComponentCache(nodeManager.SystemContext, nodeHandle); + + Assert.That(handleFromCache, Is.Null); + } + finally + { + await fixture.StopAsync().ConfigureAwait(false); + } + } + + /// + /// Tests the Predefined Nodes methods with multiple threads + /// + [Test] + public async Task TestPredefinedNodes() + { + var fixture = new ServerFixture(); + + try + { + // Arrange + const string ns = "http://test.org/UA/Data/"; + var server = await fixture.StartAsync(TestContext.Out).ConfigureAwait(false); + + var nodeManager = new TestableCustomNodeManger2(server.CurrentInstance, ns); + var index = server.CurrentInstance.NamespaceUris.GetIndex(ns); + + var baseObject = new DataItemState(null); + var nodeId = new NodeId((string)CommonTestWorkers.NodeIdTestSetStatic.First().Identifier, (ushort)index); + + baseObject.NodeId = nodeId; + + + //single threaded test + nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject); + + Assert.That(nodeManager.PredefinedNodes.ContainsKey(nodeId), Is.True); + + NodeState nodeState = nodeManager.Find(nodeId); + Assert.That(nodeState, Is.Not.Null); + + NodeHandle handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle; + Assert.That(handle, Is.Not.Null); + + nodeManager.DeleteNode(nodeManager.SystemContext, nodeId); + + Assert.That(nodeManager.PredefinedNodes, Is.Empty); + + nodeState = nodeManager.Find(nodeId); + Assert.That(nodeState, Is.Null); + + handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle; + Assert.That(handle, Is.Null); + + nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject); + + nodeManager.DeleteAddressSpace(); + + Assert.That(nodeManager.PredefinedNodes, Is.Empty); + + + //Act + await RunTaskInParallel(() => UsePredefinedNodesAsync(nodeManager, baseObject, nodeId), 100); + + //last operation added the Node back into the dictionary + Assert.That(nodeManager.PredefinedNodes.ContainsKey(nodeId), Is.True); + + //delete full adress space + nodeManager.DeleteAddressSpace(); + Assert.That(nodeManager.PredefinedNodes, Is.Empty); + } + finally + { + await fixture.StopAsync().ConfigureAwait(false); + } + } + + private static async Task UsePredefinedNodesAsync(TestableCustomNodeManger2 nodeManager, DataItemState baseObject, NodeId nodeId) + { + nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject); + + NodeState nodeState = nodeManager.Find(nodeId); + + NodeHandle handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle; + + nodeManager.DeleteNode(nodeManager.SystemContext, nodeId); + + nodeState = nodeManager.Find(nodeId); + + handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle; + + nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject); + await Task.CompletedTask; + } + + /// + /// Test Methods AddNodeToComponentCache, RemoveNodeFromComponentCache & LookupNodeInComponentCache & verify the node is added to the cache + /// + /// + private static async Task UseComponentCacheAsync(TestableCustomNodeManger2 nodeManager, BaseObjectState baseObject, NodeHandle nodeHandle) + { + //-- Act + nodeManager.AddNodeToComponentCache(nodeManager.SystemContext, nodeHandle, baseObject); + nodeManager.AddNodeToComponentCache(nodeManager.SystemContext, nodeHandle, baseObject); + + NodeState handleFromCache = nodeManager.LookupNodeInComponentCache(nodeManager.SystemContext, nodeHandle); + + //-- Assert + + Assert.That(handleFromCache, Is.Not.Null); + + nodeManager.RemoveNodeFromComponentCache(nodeManager.SystemContext, nodeHandle); + + handleFromCache = nodeManager.LookupNodeInComponentCache(nodeManager.SystemContext, nodeHandle); + + Assert.That(handleFromCache, Is.Not.Null); + + nodeManager.RemoveNodeFromComponentCache(nodeManager.SystemContext, nodeHandle); + + await Task.CompletedTask; + } + #endregion + + public static async Task<(bool IsSuccess, Exception Error)> RunTaskInParallel(Func task, int iterations) + { + var cancellationTokenSource = new CancellationTokenSource(); + Exception error = null; + int tasksCompletedCount = 0; + var result = Parallel.For(0, iterations, new ParallelOptions(), + async index => { + try + { + await task(); + } + catch (Exception ex) + { + error = ex; + cancellationTokenSource.Cancel(); + } + finally + { + tasksCompletedCount++; + } + + }); + + int spinWaitCount = 0; + int maxSpinWaitCount = 100; + while (iterations > tasksCompletedCount && error is null && spinWaitCount < maxSpinWaitCount) + { + await Task.Delay(TimeSpan.FromMilliseconds(100)); + spinWaitCount++; + } + + return (error == null, error); + } + + } + + public class TestableCustomNodeManger2 : CustomNodeManager2 + { + public TestableCustomNodeManger2(IServerInternal server, params string[] namespaceUris) : base(server, namespaceUris) + { } + + #region componentCache + public new NodeState AddNodeToComponentCache(ISystemContext context, NodeHandle handle, NodeState node) + { + return base.AddNodeToComponentCache(context, handle, node); + } + public new void RemoveNodeFromComponentCache(ISystemContext context, NodeHandle handle) + { + base.RemoveNodeFromComponentCache(context, handle); + } + public new NodeState LookupNodeInComponentCache(ISystemContext context, NodeHandle handle) + { + return base.LookupNodeInComponentCache(context, handle); + } + #endregion + + #region PredefinedNodes + + public new NodeIdDictionary PredefinedNodes => base.PredefinedNodes; + public new virtual void AddPredefinedNode(ISystemContext context, NodeState node) + { + base.AddPredefinedNode(context, node); + } + + #endregion + } +} From 444b2c8e511526829132cf1a893f23de555def55 Mon Sep 17 00:00:00 2001 From: romanett Date: Tue, 7 Jan 2025 17:08:23 +0100 Subject: [PATCH 06/10] Add port number check in the MatchEndpoints method to ensure the correct endpoint is returned (#2925) --- .../Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs index 9410289667..bd22a2c6d7 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs @@ -1411,6 +1411,12 @@ private EndpointDescriptionCollection MatchEndpoints( continue; } + // check for matching port. + if (sessionUrl.Port != endpointUrl.Port) + { + continue; + } + matches.Add(description); } } From df881be3acd833db7b466f16759ae6e1aeab0780 Mon Sep 17 00:00:00 2001 From: Stephan Larws <146172346+larws@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:38:51 +0100 Subject: [PATCH 07/10] Handle status code Uncertain according to the specification (#2898) * #2896: Write output arguments for good and uncertain status code When a method state's call method is invoked the output arguments should be written in case the status code is good or uncertain. This behavior would be conform with the current specification. * #2896: The service result corresponds the method call result The result of the Call method in the CustomNodeManager2 class represents the status of the CallMethodResult. It does not correspond to the ServiceResult of the CallResponse, thus returning Good as a general response is incorrect behavior. --- .../Opc.Ua.Server/Diagnostics/CustomNodeManager.cs | 9 +++++---- Stack/Opc.Ua.Core/Stack/State/MethodState.cs | 2 +- Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs | 13 +++++++++++++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs index cc74f527c7..3e1017b753 100644 --- a/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs +++ b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs @@ -2977,16 +2977,16 @@ protected virtual ServiceResult Call( List argumentErrors = new List(); VariantCollection outputArguments = new VariantCollection(); - ServiceResult error = method.Call( + ServiceResult callResult = method.Call( context, methodToCall.ObjectId, methodToCall.InputArguments, argumentErrors, outputArguments); - if (ServiceResult.IsBad(error)) + if (ServiceResult.IsBad(callResult)) { - return error; + return callResult; } // check for argument errors. @@ -3041,7 +3041,8 @@ protected virtual ServiceResult Call( // return output arguments. result.OutputArguments = outputArguments; - return ServiceResult.Good; + // return the actual result of the original call + return callResult; } diff --git a/Stack/Opc.Ua.Core/Stack/State/MethodState.cs b/Stack/Opc.Ua.Core/Stack/State/MethodState.cs index f3b79abb03..e327a82b4a 100644 --- a/Stack/Opc.Ua.Core/Stack/State/MethodState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/MethodState.cs @@ -706,7 +706,7 @@ public virtual ServiceResult Call( } // copy out arguments. - if (ServiceResult.IsGood(result)) + if (ServiceResult.IsGoodOrUncertain(result)) { for (int ii = 0; ii < outputs.Count; ii++) { diff --git a/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs b/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs index 1d04c49081..40d72b66fc 100644 --- a/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs +++ b/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs @@ -524,6 +524,19 @@ public static bool IsUncertain(ServiceResult status) return false; } + /// + /// Returns true if the status code is good or uncertain. + /// + public static bool IsGoodOrUncertain(ServiceResult status) + { + if (status != null) + { + return StatusCode.IsGood(status.m_code) || StatusCode.IsUncertain(status.m_code); + } + + return false; + } + /// /// Returns true if the status is good or uncertain. /// From d133ec11590d649a8bcaf4bbb26c68072b4ac59b Mon Sep 17 00:00:00 2001 From: romanett Date: Wed, 8 Jan 2025 13:20:57 +0100 Subject: [PATCH 08/10] Add SetHiResClockDisabled & fix Rejected Store Creation in ApplicationConfigurationBuilder (#2909) Add the method SetHiResClockDisabled to IApplicationConfigurationBuilder Fix the type of RejectedStore Created by the Builder to: CertificateStoreIdentifier --- .../ApplicationConfigurationBuilder.cs | 10 ++++++++-- .../IApplicationConfigurationBuilder.cs | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index 015d886d31..4ec122491f 100644 --- a/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs @@ -66,6 +66,12 @@ public ApplicationConfigurationBuilder(ApplicationInstance applicationInstance) #region Public Methods /// + public IApplicationConfigurationBuilderGlobalConfiguration SetHiResClockDisabled(bool disableHiResClock) + { + ApplicationConfiguration.DisableHiResClock = disableHiResClock; + return this; + } + /// public IApplicationConfigurationBuilderClientSelected AsClient() { switch (ApplicationInstance.ApplicationType) @@ -137,7 +143,7 @@ public IApplicationConfigurationBuilderSecurityOptions AddSecurityConfiguration( StorePath = DefaultCertificateStorePath(TrustlistType.IssuerUser, pkiRoot) }, // rejected store - RejectedCertificateStore = new CertificateTrustList() { + RejectedCertificateStore = new CertificateStoreIdentifier() { StoreType = rejectedRootType, StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot) }, @@ -178,7 +184,7 @@ public IApplicationConfigurationBuilderSecurityOptionStores AddSecurityConfigura StorePath = DefaultCertificateStorePath(TrustlistType.Issuer, issuerRoot) }, // rejected store - RejectedCertificateStore = new CertificateTrustList() { + RejectedCertificateStore = new CertificateStoreIdentifier() { StoreType = rejectedRootType, StorePath = DefaultCertificateStorePath(TrustlistType.Rejected, rejectedRoot) }, diff --git a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs index 4d89e47a6d..d8825b8a94 100644 --- a/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs +++ b/Libraries/Opc.Ua.Configuration/IApplicationConfigurationBuilder.cs @@ -450,6 +450,7 @@ string issuerRoot /// Add security options to the configuration. /// public interface IApplicationConfigurationBuilderSecurityOptions : + IApplicationConfigurationBuilderGlobalConfiguration, IApplicationConfigurationBuilderTraceConfiguration, IApplicationConfigurationBuilderExtension, IApplicationConfigurationBuilderCreate @@ -542,6 +543,20 @@ public interface IApplicationConfigurationBuilderExtension : IApplicationConfigurationBuilderExtension AddExtension(XmlQualifiedName elementName, object value); } + /// + /// Add some global configuration settings. + /// + public interface IApplicationConfigurationBuilderGlobalConfiguration : + IApplicationConfigurationBuilderCreate, + IApplicationConfigurationBuilderTraceConfiguration + { + /// + /// Set the high resolution clock to disabled or enabled. + /// + /// true if high resolution clock is disabled; otherwise, false. + IApplicationConfigurationBuilderGlobalConfiguration SetHiResClockDisabled(bool hiResClockDisabled); + } + /// /// Add the trace configuration. /// From a0b12d711622526d454f644989b1b42030fb0887 Mon Sep 17 00:00:00 2001 From: Martin Regen <7962757+mregen@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:33:41 +0100 Subject: [PATCH 09/10] Fix race condition on Nuget build (#2930) - nodeset xmlzip is created in obj folders if multiple targets are built in parallel --- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 35 +++++++++++++++++----------- azure-pipelines.yml | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 58fa34b887..6a112f127f 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -9,6 +9,7 @@ OPC UA Core Class Library true true + true @@ -88,23 +89,31 @@ - - - - - + - $(BaseIntermediateOutputPath)/zipnodeset2 - Schema/Opc.Ua.NodeSet2.xml - true + $(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/zipnodeset2 + Schema/Opc.Ua.NodeSet2.xml + $(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/Opc.Ua.NodeSet2.xml.zip - - - - + + + + + + + + + + - + + + + + + + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0be491de2c..68e57bf235 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -68,7 +68,7 @@ stages: parameters: configuration: Release framework: net6.0 - agents: '@{ windows = "windows-2022"; linux="ubuntu-22.04"; mac = "macOS-12"}' + agents: '@{ windows = "windows-2022"; linux="ubuntu-22.04"; mac = "macOS-15"}' jobnamesuffix: net60 customtestarget: net6.0 - stage: testreleasepr From 61056d0d30df3006905be23e5999182cd18b5133 Mon Sep 17 00:00:00 2001 From: Martin Regen <7962757+mregen@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:45:12 +0100 Subject: [PATCH 10/10] Some .NET 9 maintenance and JSON encoder improvements (#2922) - Reduce memory allocations in JSON encoder by using stackalloc/ArrayPool - Fix some new warnings in .NET9 code analyzers - Fix fuzzing error in JsonDecoder - do not use .NET9 libraries with .NET8 LTS builds #2931, #2940 - Bump langversion to 9 (max VS2019) - Use official ASP.Net 2.3. libraries for .NET Framework (rel. Jan 15th) --- .../AlarmHolders/ConditionTypeHolder.cs | 4 +- .../Boiler/BoilerNodeManager.cs | 1 + .../ReferenceServer/ReferenceNodeManager.cs | 2 +- .../TestData/TestDataObjectState.cs | 5 +- .../Opc.Ua.Encoders.Fuzz.Tests.csproj | 10 +- .../Opc.Ua.Client.ComplexTypes.csproj | 2 +- Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj | 6 +- Libraries/Opc.Ua.Client/Session/Session.cs | 12 +- .../Subscription/Subscription.cs | 16 +- .../Opc.Ua.Configuration.csproj | 2 +- .../Opc.Ua.Gds.Client.Common.csproj | 2 +- .../Opc.Ua.Gds.Server.Common.csproj | 2 +- Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj | 2 +- .../Opc.Ua.Security.Certificates.csproj | 34 ++-- Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj | 2 +- .../Opc.Ua.Bindings.Https.csproj | 12 +- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 15 +- ...egment.cs => ArrayPoolBufferSegment{T}.cs} | 0 ...rWriter.cs => ArrayPoolBufferWriter{T}.cs} | 0 .../Stack/Types/ContentFilter.Evaluate.cs | 32 ++++ .../Stack/Types/NotificationMessage.cs | 17 +- Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs | 81 +++++----- .../Types/BuiltIn/NotificationData.cs | 6 + Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs | 5 - .../Types/Encoders/BinaryDecoder.cs | 68 +++++++- Stack/Opc.Ua.Core/Types/Encoders/IEncoder.cs | 16 +- .../Opc.Ua.Core/Types/Encoders/JsonEncoder.cs | 147 +++++++++++++++--- .../Opc.Ua.Client.ComplexTypes.Tests.csproj | 6 +- .../Opc.Ua.Client.Tests.csproj | 6 +- .../Opc.Ua.Client.Tests/RequestHeaderTest.cs | 34 ++++ Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs | 4 +- .../Opc.Ua.Configuration.Tests.csproj | 8 +- .../Opc.Ua.Core.Tests.csproj | 6 +- .../ApplicationTestDataGenerator.cs | 2 + .../Types/Encoders/EncoderTests.cs | 122 +++++++-------- .../Encoders/JsonEncoderDateTimeBenchmark.cs | 6 + .../JsonEncoderEscapeStringBenchmarks.cs | 1 + .../Types/Encoders/JsonEncoderTests.cs | 6 + .../Types/Encoders/XmlEncoderTests.cs | 1 + Tests/Opc.Ua.Gds.Tests/Common.cs | 2 + .../Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj | 6 +- .../Configuration/PubSubConfiguratorTests.cs | 1 + .../Configuration/UaPubSubDataStoreTests.cs | 1 + .../Encoding/MqttJsonNetworkMessageTests.cs | 1 + .../Opc.Ua.PubSub.Tests.csproj | 8 +- .../Opc.Ua.Security.Certificates.Tests.csproj | 8 +- .../CustomNodeManagerTests.cs | 12 +- .../Opc.Ua.Server.Tests.csproj | 8 +- common.props | 5 +- targets.props | 4 +- version.props | 4 +- 51 files changed, 518 insertions(+), 245 deletions(-) rename Stack/Opc.Ua.Core/Stack/Buffers/{ArrayPoolBufferSegment.cs => ArrayPoolBufferSegment{T}.cs} (100%) rename Stack/Opc.Ua.Core/Stack/Buffers/{ArrayPoolBufferWriter.cs => ArrayPoolBufferWriter{T}.cs} (100%) diff --git a/Applications/Quickstarts.Servers/Alarms/AlarmHolders/ConditionTypeHolder.cs b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/ConditionTypeHolder.cs index 8831779b62..768e327d7a 100644 --- a/Applications/Quickstarts.Servers/Alarms/AlarmHolders/ConditionTypeHolder.cs +++ b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/ConditionTypeHolder.cs @@ -28,7 +28,7 @@ * ======================================================================*/ using System; - +using System.Globalization; using Opc.Ua; #pragma warning disable CS1591 @@ -168,7 +168,7 @@ public void ReportEvent(ConditionState alarm = null) alarm.Time.Value = DateTime.UtcNow; alarm.ReceiveTime.Value = alarm.Time.Value; - Log("ReportEvent", " Value " + m_alarmController.GetValue().ToString() + + Log("ReportEvent", " Value " + m_alarmController.GetValue().ToString(CultureInfo.InvariantCulture) + " Message " + alarm.Message.Value.Text); alarm.ClearChangeMasks(SystemContext, true); diff --git a/Applications/Quickstarts.Servers/Boiler/BoilerNodeManager.cs b/Applications/Quickstarts.Servers/Boiler/BoilerNodeManager.cs index 0a7bf44435..8f5a06d4bb 100644 --- a/Applications/Quickstarts.Servers/Boiler/BoilerNodeManager.cs +++ b/Applications/Quickstarts.Servers/Boiler/BoilerNodeManager.cs @@ -27,6 +27,7 @@ * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ +using System; using System.Collections.Generic; using System.Reflection; using Opc.Ua; diff --git a/Applications/Quickstarts.Servers/ReferenceServer/ReferenceNodeManager.cs b/Applications/Quickstarts.Servers/ReferenceServer/ReferenceNodeManager.cs index 6817f123e6..6a0a4aeb3b 100644 --- a/Applications/Quickstarts.Servers/ReferenceServer/ReferenceNodeManager.cs +++ b/Applications/Quickstarts.Servers/ReferenceServer/ReferenceNodeManager.cs @@ -1690,7 +1690,7 @@ private DataItemState[] CreateDataItemVariables(NodeState parent, string path, s // now to create the remaining NUMBERED items for (uint i = 0; i < numVariables; i++) { - string newName = string.Format(CultureInfo.InvariantCulture, "{0}{1}", name, i.ToString("000")); + string newName = string.Format(CultureInfo.InvariantCulture, "{0}{1}", name, i.ToString("000", CultureInfo.InvariantCulture)); string newPath = string.Format(CultureInfo.InvariantCulture, "{0}/Mass/{1}", path, newName); itemsCreated.Add(CreateDataItemVariable(parent, newPath, newName, dataType, valueRank)); }//for i diff --git a/Applications/Quickstarts.Servers/TestData/TestDataObjectState.cs b/Applications/Quickstarts.Servers/TestData/TestDataObjectState.cs index f646e36124..4ab227624f 100644 --- a/Applications/Quickstarts.Servers/TestData/TestDataObjectState.cs +++ b/Applications/Quickstarts.Servers/TestData/TestDataObjectState.cs @@ -29,6 +29,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using Opc.Ua; using Range = Opc.Ua.Range; @@ -142,7 +143,7 @@ public ServiceResult OnWriteAnalogValue( element = ((Variant)element).Value; } - double elementNumber = Convert.ToDouble(element); + double elementNumber = Convert.ToDouble(element, CultureInfo.InvariantCulture); if (elementNumber > range.High || elementNumber < range.Low) { @@ -153,7 +154,7 @@ public ServiceResult OnWriteAnalogValue( return ServiceResult.Good; } - double number = Convert.ToDouble(value); + double number = Convert.ToDouble(value, CultureInfo.InvariantCulture); if (number > range.High || number < range.Low) { diff --git a/Fuzzing/Encoders/Fuzz.Tests/Opc.Ua.Encoders.Fuzz.Tests.csproj b/Fuzzing/Encoders/Fuzz.Tests/Opc.Ua.Encoders.Fuzz.Tests.csproj index 07f10d1f21..26130413da 100644 --- a/Fuzzing/Encoders/Fuzz.Tests/Opc.Ua.Encoders.Fuzz.Tests.csproj +++ b/Fuzzing/Encoders/Fuzz.Tests/Opc.Ua.Encoders.Fuzz.Tests.csproj @@ -20,18 +20,18 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj index 764c794d21..c343e4f7ea 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj +++ b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj @@ -1,7 +1,7 @@  - Opc.Ua.Client.ComplexTypes + $(AssemblyPrefix).Client.ComplexTypes $(LibxTargetFrameworks) $(PackagePrefix).Opc.Ua.Client.ComplexTypes Opc.Ua.Client.ComplexTypes diff --git a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj index c01e640733..c1cc8a1b49 100644 --- a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj +++ b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj @@ -2,7 +2,7 @@ $(DefineConstants);CLIENT_ASYNC - Opc.Ua.Client + $(AssemblyPrefix).Client $(LibTargetFrameworks) $(PackagePrefix).Opc.Ua.Client Opc.Ua.Client @@ -21,10 +21,6 @@ $(PackageId).Debug - - - - diff --git a/Libraries/Opc.Ua.Client/Session/Session.cs b/Libraries/Opc.Ua.Client/Session/Session.cs index 18885a0ccc..1871f80137 100644 --- a/Libraries/Opc.Ua.Client/Session/Session.cs +++ b/Libraries/Opc.Ua.Client/Session/Session.cs @@ -6084,6 +6084,7 @@ private void ProcessPublishResponse( // ignore messages with a subscription that has been deleted. if (subscription != null) { +#if DEBUG // Validate publish time and reject old values. if (notificationMessage.PublishTime.AddMilliseconds(subscription.CurrentPublishingInterval * subscription.CurrentLifetimeCount) < DateTime.UtcNow) { @@ -6095,12 +6096,15 @@ private void ProcessPublishResponse( { Utils.LogTrace("PublishTime {0} in publish response is newer than actual time for SubscriptionId {1}.", notificationMessage.PublishTime.ToLocalTime(), subscription.Id); } +#endif + // save the information that more notifications are expected + notificationMessage.MoreNotifications = moreNotifications; + + // save the string table that came with the notification. + notificationMessage.StringTable = responseHeader.StringTable; // update subscription cache. - subscription.SaveMessageInCache( - availableSequenceNumbers, - notificationMessage, - responseHeader.StringTable); + subscription.SaveMessageInCache(availableSequenceNumbers, notificationMessage); // raise the notification. NotificationEventHandler publishEventHandler = m_Publish; diff --git a/Libraries/Opc.Ua.Client/Subscription/Subscription.cs b/Libraries/Opc.Ua.Client/Subscription/Subscription.cs index 7b449aa394..f2be09c755 100644 --- a/Libraries/Opc.Ua.Client/Subscription/Subscription.cs +++ b/Libraries/Opc.Ua.Client/Subscription/Subscription.cs @@ -1385,8 +1385,7 @@ public List SetMonitoringMode( /// public void SaveMessageInCache( IList availableSequenceNumbers, - NotificationMessage message, - IList stringTable) + NotificationMessage message) { PublishStateChangedEventHandler callback = null; @@ -1411,12 +1410,10 @@ public void SaveMessageInCache( DateTime now = DateTime.UtcNow; Interlocked.Exchange(ref m_lastNotificationTime, now.Ticks); + int tickCount = HiResClock.TickCount; m_lastNotificationTickCount = tickCount; - // save the string table that came with notification. - message.StringTable = new List(stringTable); - // create queue for the first time. if (m_incomingMessages == null) { @@ -1443,10 +1440,11 @@ public void SaveMessageInCache( if (next != null && next.Value.SequenceNumber > entry.SequenceNumber + 1) { - IncomingMessage placeholder = new IncomingMessage(); - placeholder.SequenceNumber = entry.SequenceNumber + 1; - placeholder.Timestamp = now; - placeholder.TickCount = tickCount; + var placeholder = new IncomingMessage { + SequenceNumber = entry.SequenceNumber + 1, + Timestamp = now, + TickCount = tickCount + }; node = m_incomingMessages.AddAfter(node, placeholder); continue; } diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index be7a06fa49..60c75c4d09 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -1,7 +1,7 @@  - Opc.Ua.Configuration + $(AssemblyPrefix).Configuration $(LibTargetFrameworks) $(PackagePrefix).Opc.Ua.Configuration Opc.Ua.Configuration diff --git a/Libraries/Opc.Ua.Gds.Client.Common/Opc.Ua.Gds.Client.Common.csproj b/Libraries/Opc.Ua.Gds.Client.Common/Opc.Ua.Gds.Client.Common.csproj index c4fefa1ef4..5b37a3f158 100644 --- a/Libraries/Opc.Ua.Gds.Client.Common/Opc.Ua.Gds.Client.Common.csproj +++ b/Libraries/Opc.Ua.Gds.Client.Common/Opc.Ua.Gds.Client.Common.csproj @@ -1,7 +1,7 @@  - Opc.Ua.Gds.Client.Common + $(AssemblyPrefix).Gds.Client.Common $(LibTargetFrameworks) $(PackagePrefix).Opc.Ua.Gds.Client.Common Opc.Ua.Gds.Client diff --git a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj index 0bb5c0e1f1..12d3f38f02 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj +++ b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj @@ -1,7 +1,7 @@  - Opc.Ua.Gds.Server.Common + $(AssemblyPrefix).Gds.Server.Common $(LibTargetFrameworks) $(PackagePrefix).Opc.Ua.Gds.Server.Common Opc.Ua.Gds.Server diff --git a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj index 419ac2c276..06b15a16bf 100644 --- a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj +++ b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj @@ -1,7 +1,7 @@ - Opc.Ua.PubSub + $(AssemblyPrefix).PubSub $(LibxTargetFrameworks) $(PackagePrefix).Opc.Ua.PubSub Opc.Ua.PubSub diff --git a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj index 5fc06442de..2de0fb177e 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj +++ b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj @@ -1,9 +1,8 @@  - - Opc.Ua.Security.Certificates $(LibCoreTargetFrameworks) $(PackagePrefix).Opc.Ua.Security.Certificates + $(AssemblyPrefix).Security.Certificates Opc.Ua.Security.Certificates OPC UA Security X509 Certificates Class Library true @@ -15,6 +14,7 @@ true + @@ -41,17 +41,25 @@ $(DefineConstants);SIGNASSEMBLY - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj index b6e598fe7e..3c5bb25f2b 100644 --- a/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj +++ b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj @@ -1,9 +1,9 @@  - Opc.Ua.Server $(LibTargetFrameworks) $(PackagePrefix).Opc.Ua.Server + $(AssemblyPrefix).Server Opc.Ua.Server OPC UA Server Class Library PackageReference diff --git a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj index da79450376..597a8ce12a 100644 --- a/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj +++ b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj @@ -22,13 +22,11 @@ - - - - - - - + + + + + diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 6a112f127f..992a4d6006 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -1,9 +1,9 @@ - + $(DefineConstants);NET_STANDARD;NET_STANDARD_ASYNC $(LibCoreTargetFrameworks) - Opc.Ua.Core + $(AssemblyPrefix).Core $(PackagePrefix).Opc.Ua.Core Opc.Ua OPC UA Core Class Library @@ -45,7 +45,7 @@ use latest versions only on .NET 5/6/7/8, otherwise 3.1.x --> - + @@ -54,15 +54,20 @@ + + + + + - + - + diff --git a/Stack/Opc.Ua.Core/Stack/Buffers/ArrayPoolBufferSegment.cs b/Stack/Opc.Ua.Core/Stack/Buffers/ArrayPoolBufferSegment{T}.cs similarity index 100% rename from Stack/Opc.Ua.Core/Stack/Buffers/ArrayPoolBufferSegment.cs rename to Stack/Opc.Ua.Core/Stack/Buffers/ArrayPoolBufferSegment{T}.cs diff --git a/Stack/Opc.Ua.Core/Stack/Buffers/ArrayPoolBufferWriter.cs b/Stack/Opc.Ua.Core/Stack/Buffers/ArrayPoolBufferWriter{T}.cs similarity index 100% rename from Stack/Opc.Ua.Core/Stack/Buffers/ArrayPoolBufferWriter.cs rename to Stack/Opc.Ua.Core/Stack/Buffers/ArrayPoolBufferWriter{T}.cs diff --git a/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs b/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs index bc25a7769e..6e5336fa8a 100644 --- a/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs +++ b/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs @@ -431,6 +431,25 @@ private static bool Match(string target, string pattern) { string expression = pattern; +#if NET8_0_OR_GREATER + // 1) Suppress unused regular expression characters with special meaning + // the following characters have special meaning in a regular expression []\^$.|?*+() + // the following characters are OPC UA wildcards %_\[]! + // The specail meaning of the regular expression characters not coincident with the + // OPC UA wildcards must be suppressed so as not to interfere with matching. + // preceed all '^', '$', '.', '|', '?', '*', '+', '(', ')' with a '\' + expression = SuppressUnusedCharacters().Replace(expression, "\\$1"); + + // 2) Replace all OPC UA wildcards with their regular expression equivalents + // replace all '%' with ".+", except "\%" + expression = ReplaceWildcards().Replace(expression, ".*"); + + // replace all '_' with '.', except "\_" + expression = ReplaceUnderscores().Replace(expression, "."); + + // replace all "[!" with "[^", except "\[!" + expression = ReplaceBrackets().Replace(expression, "[^"); +#else // 1) Suppress unused regular expression characters with special meaning // the following characters have special meaning in a regular expression []\^$.|?*+() // the following characters are OPC UA wildcards %_\[]! @@ -448,6 +467,7 @@ private static bool Match(string target, string pattern) // replace all "[!" with "[^", except "\[!" expression = Regex.Replace(expression, "(? - /// A message return in a Publish response. + /// A message returned in a Publish response. /// public partial class NotificationMessage { @@ -26,12 +26,21 @@ public partial class NotificationMessage /// /// The string table that was received with the message. /// - public List StringTable + public StringCollection StringTable { get { return m_stringTable; } set { m_stringTable = value; } } + /// + /// Gets a value indicating whether there are more NotificationMessages for this publish interval. + /// + public bool MoreNotifications + { + get { return m_moreNotifications; } + set { m_moreNotifications = value; } + } + /// /// Gets a value indicating whether this NotificationMessage is empty. /// @@ -120,7 +129,6 @@ public IList GetEvents(bool reverse) continue; } - if (!(extension.Body is EventNotificationList notification)) { continue; @@ -159,7 +167,8 @@ public IList GetEvents(bool reverse) #endregion #region Private Fields - private List m_stringTable; + private bool m_moreNotifications; + private StringCollection m_stringTable; #endregion } } diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs index 59d58b91b4..53f296f7e8 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeId.cs @@ -312,7 +312,7 @@ public static NodeId Parse(IServiceMessageContext context, string text, NodeIdPa if (index < 0) { - throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"Invalid NodeId ({originalText})."); + throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "Invalid NodeId ({0}).", originalText); } var namespaceUri = Utils.UnescapeUri(text.Substring(4, index - 4)); @@ -320,7 +320,7 @@ public static NodeId Parse(IServiceMessageContext context, string text, NodeIdPa if (namespaceIndex < 0) { - throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"No mapping to NamespaceIndex for NamespaceUri ({namespaceUri})."); + throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "No mapping to NamespaceIndex for NamespaceUri ({0}).", namespaceUri); } text = text.Substring(index + 1); @@ -332,7 +332,7 @@ public static NodeId Parse(IServiceMessageContext context, string text, NodeIdPa if (index < 0) { - throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"Invalid ExpandedNodeId ({originalText})."); + throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "Invalid ExpandedNodeId ({0}).", originalText); } if (UInt16.TryParse(text.Substring(3, index - 3), out ushort ns)) @@ -348,58 +348,61 @@ public static NodeId Parse(IServiceMessageContext context, string text, NodeIdPa text = text.Substring(index + 1); } - var idType = text.Substring(0, 1); - text = text.Substring(2); - - switch (idType) + if (text.Length >= 2) { - case "i": - { - if (UInt32.TryParse(text, out uint number)) - { - return new NodeId(number, (ushort)namespaceIndex); - } + char idType = text[0]; + text = text.Substring(2); - break; - } - - case "s": + switch (idType) { - if (!String.IsNullOrWhiteSpace(text)) + case 'i': { - return new NodeId(text, (ushort)namespaceIndex); - } + if (UInt32.TryParse(text, out uint number)) + { + return new NodeId(number, (ushort)namespaceIndex); + } - break; - } + break; + } - case "b": - { - try + case 's': { - var bytes = Convert.FromBase64String(text); - return new NodeId(bytes, (ushort)namespaceIndex); + if (!String.IsNullOrWhiteSpace(text)) + { + return new NodeId(text, (ushort)namespaceIndex); + } + + break; } - catch (Exception) + + case 'b': { - // error handled after the switch statement. - } + try + { + var bytes = Convert.FromBase64String(text); + return new NodeId(bytes, (ushort)namespaceIndex); + } + catch (Exception) + { + // error handled after the switch statement. + } - break; - } + break; + } - case "g": - { - if (Guid.TryParse(text, out var guid)) + case 'g': { - return new NodeId(guid, (ushort)namespaceIndex); - } + if (Guid.TryParse(text, out var guid)) + { + return new NodeId(guid, (ushort)namespaceIndex); + } - break; + break; + } } } - throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"Invalid NodeId Identifier ({originalText})."); + throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "Invalid NodeId Identifier ({0}).", originalText); } /// diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/NotificationData.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/NotificationData.cs index 89ae50a3dd..b221e6e90f 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/NotificationData.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/NotificationData.cs @@ -37,5 +37,11 @@ public partial class NotificationData /// A value of MinTime indicates that the time is not known. /// public DateTime PublishTime { get; set; } + + /// + /// Helper variable for a client to pass the information that more + /// notifications are expected for this publish interval. + /// + public bool MoreNotifications { get; set; } } } diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs index c9334bda14..dd61153f10 100644 --- a/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs +++ b/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs @@ -53,15 +53,10 @@ public Uuid(Guid guid) #endregion #region Static Fields - /// /// A constant containing an empty GUID. /// - /// - /// A constant containing an empty GUID. - /// public static readonly Uuid Empty = new Uuid(); - #endregion #region Public Properties diff --git a/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs index e517198c9f..8543faaeac 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs @@ -11,6 +11,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. */ using System; +using System.Buffers; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; @@ -421,10 +422,49 @@ public string ReadString(string fieldName, int maxStringLength) "MaxStringLength {0} < {1}", maxStringLength, length); } - byte[] bytes = SafeReadBytes(length); - // length is always >= 1 here +#if NET6_0_OR_GREATER + const int maxStackAlloc = 1024; + if (length <= maxStackAlloc) + { + Span bytes = stackalloc byte[length]; + + // throws decoding error if length is not met + int utf8StringLength = SafeReadCharBytes(bytes); + + // If 0 terminated, decrease length to remove 0 terminators before converting to string + while (utf8StringLength > 0 && bytes[utf8StringLength - 1] == 0) + { + utf8StringLength--; + } + return Encoding.UTF8.GetString(bytes.Slice(0, utf8StringLength)); + } + else + { + byte[] buffer = ArrayPool.Shared.Rent(length); + try + { + Span bytes = buffer.AsSpan(0, length); + + // throws decoding error if length is not met + int utf8StringLength = SafeReadCharBytes(bytes); + + // If 0 terminated, decrease length to remove 0 terminators before converting to string + while (utf8StringLength > 0 && bytes[utf8StringLength - 1] == 0) + { + utf8StringLength--; + } + return Encoding.UTF8.GetString(buffer.AsSpan(0, utf8StringLength)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } +#else + byte[] bytes = SafeReadBytes(length); + // If 0 terminated, decrease length to remove 0 terminators before converting to string int utf8StringLength = bytes.Length; while (utf8StringLength > 0 && bytes[utf8StringLength - 1] == 0) @@ -432,6 +472,7 @@ public string ReadString(string fieldName, int maxStringLength) utf8StringLength--; } return Encoding.UTF8.GetString(bytes, 0, utf8StringLength); +#endif } /// @@ -2491,6 +2532,29 @@ private byte[] SafeReadBytes(int length, [CallerMemberName] string functionName return bytes; } +#if NET6_0_OR_GREATER + /// + /// Read char bytes from the stream and validate the length of the returned buffer. + /// Throws decoding error if less than the expected number of bytes were read. + /// + /// A Span with the number of Utf8 characters to read. + /// The name of the calling function. + /// with + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int SafeReadCharBytes(Span bytes, [CallerMemberName] string functionName = null) + { + int length = m_reader.Read(bytes); + + if (bytes.Length != length) + { + throw ServiceResultException.Create(StatusCodes.BadDecodingError, + "Reading {0} bytes of {1} reached end of stream after {2} bytes.", length, functionName, bytes.Length); + } + + return length; + } +#endif + /// /// Safe version of which returns a ServiceResultException on error. /// diff --git a/Stack/Opc.Ua.Core/Types/Encoders/IEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/IEncoder.cs index 11f8e860c2..2c1c22db60 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/IEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/IEncoder.cs @@ -165,12 +165,12 @@ public interface IEncoder : IDisposable void WriteByteString(string fieldName, ReadOnlySpan value); #endif /// - /// Writes an XmlElement to the stream. + /// Writes a XmlElement to the stream. /// void WriteXmlElement(string fieldName, XmlElement value); /// - /// Writes an NodeId to the stream. + /// Writes a NodeId to the stream. /// void WriteNodeId(string fieldName, NodeId value); @@ -180,32 +180,32 @@ public interface IEncoder : IDisposable void WriteExpandedNodeId(string fieldName, ExpandedNodeId value); /// - /// Writes an StatusCode to the stream. + /// Writes a StatusCode to the stream. /// void WriteStatusCode(string fieldName, StatusCode value); /// - /// Writes an DiagnosticInfo to the stream. + /// Writes a DiagnosticInfo to the stream. /// void WriteDiagnosticInfo(string fieldName, DiagnosticInfo value); /// - /// Writes an QualifiedName to the stream. + /// Writes a QualifiedName to the stream. /// void WriteQualifiedName(string fieldName, QualifiedName value); /// - /// Writes an LocalizedText to the stream. + /// Writes a LocalizedText to the stream. /// void WriteLocalizedText(string fieldName, LocalizedText value); /// - /// Writes an Variant array to the stream. + /// Writes a Variant array to the stream. /// void WriteVariant(string fieldName, Variant value); /// - /// Writes an DataValue array to the stream. + /// Writes a DataValue array to the stream. /// void WriteDataValue(string fieldName, DataValue value); diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs index 15555f4160..e9ab4e0af5 100644 --- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs +++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs @@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. using System; using System.Buffers; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.CompilerServices; @@ -704,21 +705,37 @@ private void WriteSimpleFieldNull(string fieldName) } } - private void WriteSimpleField(string fieldName, string value) +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + private void WriteSimpleField(string fieldName, string value, EscapeOptions options = EscapeOptions.None) { - if (!string.IsNullOrEmpty(fieldName)) + // unlike Span, Span can not become null, handle the case here + if (value == null) { - if (value == null) - { - return; - } + WriteSimpleFieldNull(fieldName); + return; + } + + WriteSimpleFieldAsSpan(fieldName, value.AsSpan(), options); + } + private void WriteSimpleFieldAsSpan(string fieldName, ReadOnlySpan value, EscapeOptions options) + { + if (!string.IsNullOrEmpty(fieldName)) + { if (m_commaRequired) { m_writer.Write(s_comma); } - EscapeString(fieldName); + if ((options & EscapeOptions.NoFieldNameEscape) == EscapeOptions.NoFieldNameEscape) + { + m_writer.Write(s_quotation); + m_writer.Write(fieldName); + } + else + { + EscapeString(fieldName); + } m_writer.Write(s_quotationColon); } else @@ -729,19 +746,28 @@ private void WriteSimpleField(string fieldName, string value) } } - if (value != null) + if ((options & EscapeOptions.Quotes) == EscapeOptions.Quotes) { - m_writer.Write(value); + if ((options & EscapeOptions.NoValueEscape) == EscapeOptions.NoValueEscape) + { + m_writer.Write(s_quotation); + m_writer.Write(value); + } + else + { + EscapeString(value); + } + m_writer.Write(s_quotation); } else { - m_writer.Write(s_null); + m_writer.Write(value); } m_commaRequired = true; } - - private void WriteSimpleField(string fieldName, string value, EscapeOptions options) +#else + private void WriteSimpleField(string fieldName, string value, EscapeOptions options = EscapeOptions.None) { if (!string.IsNullOrEmpty(fieldName)) { @@ -801,6 +827,7 @@ private void WriteSimpleField(string fieldName, string value, EscapeOptions opti m_commaRequired = true; } +#endif /// /// Writes a boolean to the stream. @@ -1079,6 +1106,7 @@ public void WriteByteString(string fieldName, byte[] value, int index, int count /// /// Writes a byte string to the stream. /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2265:Do not compare Span to 'null' or 'default'", Justification = "Null compare works with ReadOnlySpan")] public void WriteByteString(string fieldName, ReadOnlySpan value) { if (value == null) @@ -1093,7 +1121,35 @@ public void WriteByteString(string fieldName, ReadOnlySpan value) throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded); } - WriteSimpleField(fieldName, Convert.ToBase64String(value), EscapeOptions.Quotes | EscapeOptions.NoValueEscape); + if (value.Length > 0) + { + const int maxStackLimit = 1024; + int length = ((value.Length + 2) / 3) * 4; + char[] arrayPool = null; + Span chars = length <= maxStackLimit ? + stackalloc char[length] : + (arrayPool = ArrayPool.Shared.Rent(length)).AsSpan(0, length); + try + { + bool success = Convert.TryToBase64Chars(value, chars, out int charsWritten, Base64FormattingOptions.None); + if (success) + { + WriteSimpleFieldAsSpan(fieldName, chars.Slice(0, charsWritten), EscapeOptions.Quotes | EscapeOptions.NoValueEscape); + return; + } + + throw new ServiceResultException(StatusCodes.BadEncodingError, "Failed to convert ByteString to Base64"); + } + finally + { + if (arrayPool != null) + { + ArrayPool.Shared.Return(arrayPool); + } + } + } + + WriteSimpleField(fieldName, "\"\""); } #endif @@ -1606,7 +1662,7 @@ public void WriteEncodeable(string fieldName, IEncodeable value, System.Type sys { PushStructure(fieldName); - value?.Encode(this); + value.Encode(this); PopStructure(); } @@ -2384,7 +2440,7 @@ public void WriteEncodeableArray(string fieldName, IList values, Sy { throw ServiceResultException.Create( StatusCodes.BadEncodingError, - "With Array as top level, encodeables array with filename will create invalid json"); + "With Array as top level, encodeables array with fieldname will create invalid json"); } else { @@ -2771,7 +2827,13 @@ private void WriteDateTime(string fieldName, DateTime value, EscapeOptions escap } else { +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + Span valueString = stackalloc char[DateTimeRoundTripKindLength]; + ConvertUniversalTimeToString(value, valueString, out int charsWritten); + WriteSimpleFieldAsSpan(fieldName, valueString.Slice(0, charsWritten), escapeOptions | EscapeOptions.Quotes); +#else WriteSimpleField(fieldName, ConvertUniversalTimeToString(value), escapeOptions | EscapeOptions.Quotes); +#endif } } @@ -2991,19 +3053,59 @@ private void CheckAndIncrementNestingLevel() m_nestingLevel++; } + // The length of the DateTime string encoded by "o" + internal const int DateTimeRoundTripKindLength = 28; + // the index of the last digit which can be omitted if 0 + const int DateTimeRoundTripKindLastDigit = DateTimeRoundTripKindLength - 2; + // the index of the first digit which can be omitted (7 digits total) + const int DateTimeRoundTripKindFirstDigit = DateTimeRoundTripKindLastDigit - 7; + /// /// Write Utc time in the format "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK". /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string ConvertUniversalTimeToString(DateTime value) +#if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER + internal static void ConvertUniversalTimeToString(DateTime value, Span valueString, out int charsWritten) { - // The length of the DateTime string encoded by "o" - const int DateTimeRoundTripKindLength = 28; - // the index of the last digit which can be omitted if 0 - const int DateTimeRoundTripKindLastDigit = DateTimeRoundTripKindLength - 2; - // the index of the first digit which can be omitted (7 digits total) - const int DateTimeRoundTripKindFirstDigit = DateTimeRoundTripKindLastDigit - 7; + // Note: "o" is a shortcut for "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK" and implicitly + // uses invariant culture and gregorian calendar, but executes up to 10 times faster. + // But in contrary to the explicit format string, trailing zeroes are not omitted! + if (value.Kind != DateTimeKind.Utc) + { + value.ToUniversalTime().TryFormat(valueString, out charsWritten, "o", CultureInfo.InvariantCulture); + } + else + { + value.TryFormat(valueString, out charsWritten, "o", CultureInfo.InvariantCulture); + } + + Debug.Assert(charsWritten == DateTimeRoundTripKindLength); + + // check if trailing zeroes can be omitted + int i = DateTimeRoundTripKindLastDigit; + while (i > DateTimeRoundTripKindFirstDigit) + { + if (valueString[i] != '0') + { + break; + } + i--; + } + if (i < DateTimeRoundTripKindLastDigit) + { + // check if the decimal point has to be removed too + if (i == DateTimeRoundTripKindFirstDigit) + { + i--; + } + valueString[i + 1] = 'Z'; + charsWritten = i + 2; + } + } +#else + internal static string ConvertUniversalTimeToString(DateTime value) + { // Note: "o" is a shortcut for "yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK" and implicitly // uses invariant culture and gregorian calendar, but executes up to 10 times faster. // But in contrary to the explicit format string, trailing zeroes are not omitted! @@ -3032,6 +3134,7 @@ internal static string ConvertUniversalTimeToString(DateTime value) return valueString; } +#endif #endregion } } diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj index c0a3b76193..166ceb5f00 100644 --- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj +++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Opc.Ua.Client.ComplexTypes.Tests.csproj @@ -9,13 +9,13 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj index e0f5439bca..77358dbc3c 100644 --- a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj +++ b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj @@ -11,13 +11,13 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/Opc.Ua.Client.Tests/RequestHeaderTest.cs b/Tests/Opc.Ua.Client.Tests/RequestHeaderTest.cs index 7cb5c2d055..a2f0e9682f 100644 --- a/Tests/Opc.Ua.Client.Tests/RequestHeaderTest.cs +++ b/Tests/Opc.Ua.Client.Tests/RequestHeaderTest.cs @@ -1,3 +1,32 @@ +/* ======================================================================== + * Copyright (c) 2005-2024 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + using System; using System.Collections.Generic; using System.Linq; @@ -9,6 +38,11 @@ namespace Opc.Ua.Client.Tests { + [TestFixture, Category("Client"), Category("SessionClient")] + [SetCulture("en-us"), SetUICulture("en-us")] + [MemoryDiagnoser] + [DisassemblyDiagnoser] + [Parallelizable] public class RequestHeaderTest : ClientTestFramework { #region Test Setup diff --git a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs index e930d1b063..d9910ae4d2 100644 --- a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs +++ b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs @@ -738,9 +738,7 @@ public async Task PublishRequestCount() }; subscription.FastDataChangeCallback = (_, notification, __) => { - notification.MonitoredItems.ForEach(item => { - Interlocked.Increment(ref numOfNotifications); - }); + Interlocked.Add(ref numOfNotifications, notification.MonitoredItems.Count); }; subscriptionList.Add(subscription); diff --git a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj index d1d93a62a5..473932454b 100644 --- a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj +++ b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj @@ -10,17 +10,17 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj b/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj index 3a1bea5ff6..202d7bd04b 100644 --- a/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj +++ b/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj @@ -13,13 +13,13 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/Opc.Ua.Core.Tests/Security/Certificates/ApplicationTestDataGenerator.cs b/Tests/Opc.Ua.Core.Tests/Security/Certificates/ApplicationTestDataGenerator.cs index 21d564ee39..b2484400ce 100644 --- a/Tests/Opc.Ua.Core.Tests/Security/Certificates/ApplicationTestDataGenerator.cs +++ b/Tests/Opc.Ua.Core.Tests/Security/Certificates/ApplicationTestDataGenerator.cs @@ -62,6 +62,7 @@ public IList ApplicationTestSet(int count) return testDataSet; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "Test")] private ApplicationTestData RandomApplicationTestData() { // TODO: set to discoveryserver @@ -104,6 +105,7 @@ private ApplicationTestData RandomApplicationTestData() return testData; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "Test")] private string RandomLocalHost() { string localhost = Regex.Replace(m_dataGenerator.GetRandomSymbol("en").Trim().ToLower(), @"[^\w\d]", ""); diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncoderTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncoderTests.cs index 9f95713bac..f40c42468b 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncoderTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncoderTests.cs @@ -398,45 +398,76 @@ public void ReEncodeVariantCollectionInDataValue( EncodeDecodeDataValue(encoderType, jsonEncodingType, BuiltInType.Variant, MemoryStreamType.ArraySegmentStream, variant); } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2265:Do not compare Span to 'null' or 'default'", Justification = "Null compare works with ReadOnlySpan")] + private string WriteByteStringData(IEncoder encoder) + { + encoder.WriteByteString("ByteString1", new byte[] { 0, 1, 2, 3, 4, 5 }, 1, 3); + encoder.WriteByteString("ByteString2", null); + encoder.WriteByteString("ByteString3", null, 1, 2); +#if SPAN_SUPPORT + var span = new ReadOnlySpan(new byte[] { 0, 1, 2, 3, 4, 5 }, 1, 3); + encoder.WriteByteString("ByteString4", span); + + var nullspan = new ReadOnlySpan(null); + encoder.WriteByteString("ByteString5", nullspan); + Assert.IsTrue(nullspan.IsEmpty); + Assert.IsTrue(nullspan == null); + + ReadOnlySpan defaultspan = default; + encoder.WriteByteString("ByteString6", defaultspan); + Assert.IsTrue(defaultspan.IsEmpty); + Assert.IsTrue(defaultspan == null); + + ReadOnlySpan emptyspan = Array.Empty(); + encoder.WriteByteString("ByteString7", emptyspan); + Assert.IsTrue(emptyspan.IsEmpty); + Assert.IsTrue(emptyspan != null); +#endif + return encoder.CloseAndReturnText(); + } + + private void ReadByteStringData(IDecoder decoder) + { + var result = decoder.ReadByteString("ByteString1"); + Assert.AreEqual(new byte[] { 1, 2, 3 }, result); + result = decoder.ReadByteString("ByteString2"); + Assert.AreEqual(null, result); + result = decoder.ReadByteString("ByteString3"); + Assert.AreEqual(null, result); +#if SPAN_SUPPORT + result = decoder.ReadByteString("ByteString4"); + Assert.AreEqual(new byte[] { 1, 2, 3 }, result); + result = decoder.ReadByteString("ByteString5"); + Assert.AreEqual(null, result); + result = decoder.ReadByteString("ByteString6"); + Assert.AreEqual(null, result); + result = decoder.ReadByteString("ByteString7"); + Assert.AreEqual(Array.Empty(), result); +#endif + } + [Test] [Category("WriteByteString")] public void BinaryEncoder_WriteByteString() { using (var stream = new MemoryStream()) { + string text; using (IEncoder encoder = new BinaryEncoder(stream, new ServiceMessageContext(), true)) { - encoder.WriteByteString("ByteString1", new byte[] { 0, 1, 2, 3, 4, 5 }, 1, 3); -#if SPAN_SUPPORT - var span = new ReadOnlySpan(new byte[] { 0, 1, 2, 3, 4, 5 }, 1, 3); - var nullspan = new ReadOnlySpan(null); - encoder.WriteByteString("ByteString2", span); - encoder.WriteByteString("ByteString3", nullspan); -#endif - encoder.WriteByteString("ByteString4", null); - encoder.WriteByteString("ByteString5", null, 1, 2); + text = WriteByteStringData(encoder); } stream.Position = 0; using (var decoder = new BinaryDecoder(stream, new ServiceMessageContext())) { - var result = decoder.ReadByteString("ByteString1"); - Assert.AreEqual(new byte[] { 1, 2, 3 }, result); -#if SPAN_SUPPORT - result = decoder.ReadByteString("ByteString2"); - Assert.AreEqual(new byte[] { 1, 2, 3 }, result); - result = decoder.ReadByteString("ByteString3"); - Assert.AreEqual(null, result); -#endif - result = decoder.ReadByteString("ByteString4"); - Assert.AreEqual(null, result); - result = decoder.ReadByteString("ByteString5"); - Assert.AreEqual(null, result); + ReadByteStringData(decoder); } } } [Test] [Category("WriteByteString")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2265:Do not compare Span to 'null' or 'default'", Justification = "Null compare works with ReadOnlySpan")] public void XmlEncoder_WriteByteString() { using (var stream = new MemoryStream()) @@ -445,32 +476,13 @@ public void XmlEncoder_WriteByteString() using (XmlWriter writer = XmlWriter.Create(stream, settings)) using (IEncoder encoder = new XmlEncoder(new XmlQualifiedName("ByteStrings", Namespaces.OpcUaXsd), writer, new ServiceMessageContext())) { - encoder.WriteByteString("ByteString1", new byte[] { 0, 1, 2, 3, 4, 5 }, 1, 3); -#if SPAN_SUPPORT - var span = new ReadOnlySpan(new byte[] { 0, 1, 2, 3, 4, 5 }, 1, 3); - var nullspan = new ReadOnlySpan(null); - encoder.WriteByteString("ByteString2", span); - encoder.WriteByteString("ByteString3", nullspan); -#endif - encoder.WriteByteString("ByteString4", null); - encoder.WriteByteString("ByteString5", null, 1, 2); + string text = WriteByteStringData(encoder); } stream.Position = 0; using (XmlReader reader = XmlReader.Create(stream, Utils.DefaultXmlReaderSettings())) using (var decoder = new XmlDecoder(null, reader, new ServiceMessageContext())) { - var result = decoder.ReadByteString("ByteString1"); - Assert.AreEqual(new byte[] { 1, 2, 3 }, result); -#if SPAN_SUPPORT - result = decoder.ReadByteString("ByteString2"); - Assert.AreEqual(new byte[] { 1, 2, 3 }, result); - result = decoder.ReadByteString("ByteString3"); - Assert.AreEqual(null, result); -#endif - result = decoder.ReadByteString("ByteString4"); - Assert.AreEqual(null, result); - result = decoder.ReadByteString("ByteString5"); - Assert.AreEqual(null, result); + ReadByteStringData(decoder); } } } @@ -481,34 +493,18 @@ public void JsonEncoder_WriteByteString() { using (var stream = new MemoryStream()) { + string text; using (IEncoder encoder = new JsonEncoder(new ServiceMessageContext(), true, false, stream, true)) { - encoder.WriteByteString("ByteString1", new byte[] { 0, 1, 2, 3, 4, 5 }, 1, 3); -#if SPAN_SUPPORT - var span = new ReadOnlySpan(new byte[] { 0, 1, 2, 3, 4, 5 }, 1, 3); - var nullspan = new ReadOnlySpan(null); - encoder.WriteByteString("ByteString2", span); - encoder.WriteByteString("ByteString3", nullspan); -#endif - encoder.WriteByteString("ByteString4", null); - encoder.WriteByteString("ByteString5", null, 1, 2); + text = WriteByteStringData(encoder); + } + stream.Position = 0; var jsonTextReader = new JsonTextReader(new StreamReader(stream)); using (var decoder = new JsonDecoder(null, jsonTextReader, new ServiceMessageContext())) { - var result = decoder.ReadByteString("ByteString1"); - Assert.AreEqual(new byte[] { 1, 2, 3 }, result); -#if SPAN_SUPPORT - result = decoder.ReadByteString("ByteString2"); - Assert.AreEqual(new byte[] { 1, 2, 3 }, result); - result = decoder.ReadByteString("ByteString3"); - Assert.AreEqual(null, result); -#endif - result = decoder.ReadByteString("ByteString4"); - Assert.AreEqual(null, result); - result = decoder.ReadByteString("ByteString5"); - Assert.AreEqual(null, result); + ReadByteStringData(decoder); } } } diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderDateTimeBenchmark.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderDateTimeBenchmark.cs index 08a97d4382..0b89d758a0 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderDateTimeBenchmark.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderDateTimeBenchmark.cs @@ -56,7 +56,13 @@ public void DateTimeEncodeToString() [Test] public void ConvertToUniversalTime() { +#if ECC_SUPPORT && (NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER) + Span valueString = stackalloc char[JsonEncoder.DateTimeRoundTripKindLength]; + JsonEncoder.ConvertUniversalTimeToString(m_dateTime, valueString, out int charsWritten); + _ = valueString.Slice(0, charsWritten); +#else _ = JsonEncoder.ConvertUniversalTimeToString(m_dateTime); +#endif } #region Test Setup diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderEscapeStringBenchmarks.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderEscapeStringBenchmarks.cs index 9d8f407bd7..eb9acd2110 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderEscapeStringBenchmarks.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderEscapeStringBenchmarks.cs @@ -248,6 +248,7 @@ public void EscapeStringSystemTextJson() { EscapeStringSystemTextJson(m_testString); } + m_streamWriter.Flush(); } #endif diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs index 614be99acf..2cff40c584 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/JsonEncoderTests.cs @@ -1214,7 +1214,13 @@ public void DataValueWithStatusCodes( public void DateTimeEncodeStringTest(DateTime testDateTime) { string resultString = testDateTime.ToString("yyyy-MM-dd'T'HH:mm:ss.FFFFFFFK", CultureInfo.InvariantCulture); +#if ECC_SUPPORT && (NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER) + Span valueString = stackalloc char[JsonEncoder.DateTimeRoundTripKindLength]; + JsonEncoder.ConvertUniversalTimeToString(testDateTime, valueString, out int charsWritten); + var resultO = valueString.Slice(0, charsWritten).ToString(); +#else string resultO = JsonEncoder.ConvertUniversalTimeToString(testDateTime); +#endif Assert.NotNull(resultString); Assert.NotNull(resultO); diff --git a/Tests/Opc.Ua.Core.Tests/Types/Encoders/XmlEncoderTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/XmlEncoderTests.cs index f034cd35a7..f6112fe692 100644 --- a/Tests/Opc.Ua.Core.Tests/Types/Encoders/XmlEncoderTests.cs +++ b/Tests/Opc.Ua.Core.Tests/Types/Encoders/XmlEncoderTests.cs @@ -44,6 +44,7 @@ namespace Opc.Ua.Core.Tests.Types.Encoders [Parallelizable] public class XmlEncoderTests { + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "Tests")] static Regex REValue = new Regex("Value>([^<]*)<"); #region Test Methods diff --git a/Tests/Opc.Ua.Gds.Tests/Common.cs b/Tests/Opc.Ua.Gds.Tests/Common.cs index f3e810668b..5ef1938473 100644 --- a/Tests/Opc.Ua.Gds.Tests/Common.cs +++ b/Tests/Opc.Ua.Gds.Tests/Common.cs @@ -99,6 +99,7 @@ public IList ApplicationTestSet(int count, bool invalidateS return testDataSet; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "Tests")] private ApplicationTestData RandomApplicationTestData() { // TODO: set to discoveryserver @@ -169,6 +170,7 @@ private StringCollection RandomServerCapabilities() return serverCapabilities; } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "Test")] private string RandomLocalHost() { string localhost = Regex.Replace(m_dataGenerator.GetRandomSymbol("en").Trim().ToLower(), @"[^\w\d]", ""); diff --git a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj index 33621c44d0..b4d2870a8c 100644 --- a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj +++ b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj @@ -14,13 +14,13 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/Opc.Ua.PubSub.Tests/Configuration/PubSubConfiguratorTests.cs b/Tests/Opc.Ua.PubSub.Tests/Configuration/PubSubConfiguratorTests.cs index 19b6f41a7b..86ce2fbb4b 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Configuration/PubSubConfiguratorTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Configuration/PubSubConfiguratorTests.cs @@ -36,6 +36,7 @@ namespace Opc.Ua.PubSub.Tests.Configuration { [TestFixture(Description = "Tests for UaPubSubApplication class")] + [Parallelizable] public class UaPubSubConfiguratorTests { static int CallCountPublishedDataSetAdded = 0; diff --git a/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPubSubDataStoreTests.cs b/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPubSubDataStoreTests.cs index 260cc8fa64..51ff0c7b57 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPubSubDataStoreTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Configuration/UaPubSubDataStoreTests.cs @@ -34,6 +34,7 @@ namespace Opc.Ua.PubSub.Tests.Configuration { [TestFixture(Description = "Tests for UaPubSubDataStore class")] + [Parallelizable] public class UaPubSubDataStoreTests { #region WritePublishedDataItem diff --git a/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttJsonNetworkMessageTests.cs b/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttJsonNetworkMessageTests.cs index f35a4a4c19..a71ee5e703 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttJsonNetworkMessageTests.cs +++ b/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttJsonNetworkMessageTests.cs @@ -50,6 +50,7 @@ namespace Opc.Ua.PubSub.Tests.Encoding { [TestFixture(Description = "Tests for Encoding/Decoding of JsonNetworkMessage objects")] + [Parallelizable] public class MqttJsonNetworkMessageTests { private const UInt16 NamespaceIndexAllTypes = 3; diff --git a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj index bd017a0919..71480d43ea 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj +++ b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj @@ -10,18 +10,18 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj index 3eb757da87..c55596410c 100644 --- a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj +++ b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj @@ -25,17 +25,17 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs b/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs index 9565861c5a..6371f779af 100644 --- a/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs +++ b/Tests/Opc.Ua.Server.Tests/CustomNodeManagerTests.cs @@ -36,7 +36,7 @@ public async Task TestComponentCacheAsync() var nodeHandle = new NodeHandle(new NodeId((string)CommonTestWorkers.NodeIdTestSetStatic.First().Identifier, 0), baseObject); //Act - await RunTaskInParallel(() => UseComponentCacheAsync(nodeManager, baseObject, nodeHandle), 100); + await RunTaskInParallel(() => UseComponentCacheAsync(nodeManager, baseObject, nodeHandle), 100).ConfigureAwait(false); //Assert, that entry was deleted from cache after parallel operations on the same node @@ -102,7 +102,7 @@ public async Task TestPredefinedNodes() //Act - await RunTaskInParallel(() => UsePredefinedNodesAsync(nodeManager, baseObject, nodeId), 100); + await RunTaskInParallel(() => UsePredefinedNodesAsync(nodeManager, baseObject, nodeId), 100).ConfigureAwait(false); //last operation added the Node back into the dictionary Assert.That(nodeManager.PredefinedNodes.ContainsKey(nodeId), Is.True); @@ -132,7 +132,7 @@ private static async Task UsePredefinedNodesAsync(TestableCustomNodeManger2 node handle = nodeManager.GetManagerHandle(nodeId) as NodeHandle; nodeManager.AddPredefinedNode(nodeManager.SystemContext, baseObject); - await Task.CompletedTask; + await Task.CompletedTask.ConfigureAwait(false); } /// @@ -159,7 +159,7 @@ private static async Task UseComponentCacheAsync(TestableCustomNodeManger2 nodeM nodeManager.RemoveNodeFromComponentCache(nodeManager.SystemContext, nodeHandle); - await Task.CompletedTask; + await Task.CompletedTask.ConfigureAwait(false); } #endregion @@ -172,7 +172,7 @@ private static async Task UseComponentCacheAsync(TestableCustomNodeManger2 nodeM async index => { try { - await task(); + await task().ConfigureAwait(false); } catch (Exception ex) { @@ -190,7 +190,7 @@ private static async Task UseComponentCacheAsync(TestableCustomNodeManger2 nodeM int maxSpinWaitCount = 100; while (iterations > tasksCompletedCount && error is null && spinWaitCount < maxSpinWaitCount) { - await Task.Delay(TimeSpan.FromMilliseconds(100)); + await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false); spinWaitCount++; } diff --git a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj index 7fead9e0e0..7ed18dd4e2 100644 --- a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj +++ b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj @@ -11,17 +11,17 @@ - - + + all runtime; build; native; contentfiles; analyzers - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/common.props b/common.props index c80f958047..66a2f0bd96 100644 --- a/common.props +++ b/common.props @@ -4,8 +4,9 @@ https://github.com/OPCFoundation/UA-.NETStandard 1.05.374 preview-$([System.DateTime]::Now.ToString("yyyyMMdd")) - Copyright © 2004-2024 OPC Foundation, Inc + Copyright © 2004-2025 OPC Foundation, Inc OPCFoundation.NetStandard + Opc.Ua OPC Foundation OPC Foundation NU5125;CA2254;CA1307 @@ -15,7 +16,7 @@ AnyCPU true - 7.3 + 9 true diff --git a/targets.props b/targets.props index b1cf79a211..cde7d478ba 100644 --- a/targets.props +++ b/targets.props @@ -1,12 +1,10 @@ - - + -