diff --git a/.editorconfig b/.editorconfig index 5b57ac1d0..6b60a276e 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 616c60ba4..b4254168a 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/ConditionTypeHolder.cs b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/ConditionTypeHolder.cs index 8831779b6..768e327d7 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/Alarms/AlarmHolders/DiscreteHolder.cs b/Applications/Quickstarts.Servers/Alarms/AlarmHolders/DiscreteHolder.cs index c40953edd..2d15d627f 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/Boiler/BoilerNodeManager.cs b/Applications/Quickstarts.Servers/Boiler/BoilerNodeManager.cs index 0a7bf4443..8f5a06d4b 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/MemoryBuffer/MemoryBufferNodeManager.cs b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBufferNodeManager.cs index 59a5e1890..42d79f190 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/Applications/Quickstarts.Servers/ReferenceServer/ReferenceNodeManager.cs b/Applications/Quickstarts.Servers/ReferenceServer/ReferenceNodeManager.cs index 6817f123e..6a0a4aeb3 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 f646e3612..4ab227624 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 07f10d1f2..26130413d 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 6be179a1d..c343e4f7e 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj +++ b/Libraries/Opc.Ua.Client.ComplexTypes/Opc.Ua.Client.ComplexTypes.csproj @@ -1,9 +1,9 @@  - Opc.Ua.Client.ComplexTypes + $(AssemblyPrefix).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/NodeCache/NodeCache.cs b/Libraries/Opc.Ua.Client/NodeCache/NodeCache.cs index 00dddacd2..d71d36241 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 ebd8b1ea2..2255a8584 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.Client/Opc.Ua.Client.csproj b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj index a808f4a19..c1cc8a1b4 100644 --- a/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj +++ b/Libraries/Opc.Ua.Client/Opc.Ua.Client.csproj @@ -2,9 +2,9 @@ $(DefineConstants);CLIENT_ASYNC - Opc.Ua.Client + $(AssemblyPrefix).Client $(LibTargetFrameworks) - OPCFoundation.NetStandard.Opc.Ua.Client + $(PackagePrefix).Opc.Ua.Client Opc.Ua.Client OPC UA Client Class Library true @@ -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 18885a0cc..1871f8013 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/Session/TraceableSession.cs b/Libraries/Opc.Ua.Client/Session/TraceableSession.cs index 6bd4d2bce..f2c2620d8 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.Client/Subscription/Subscription.cs b/Libraries/Opc.Ua.Client/Subscription/Subscription.cs index 7b449aa39..f2be09c75 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/ApplicationConfigurationBuilder.cs b/Libraries/Opc.Ua.Configuration/ApplicationConfigurationBuilder.cs index 015d886d3..4ec122491 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 4d89e47a6..d8825b8a9 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. /// diff --git a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj index d1886c427..60c75c4d0 100644 --- a/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj +++ b/Libraries/Opc.Ua.Configuration/Opc.Ua.Configuration.csproj @@ -1,9 +1,9 @@  - Opc.Ua.Configuration + $(AssemblyPrefix).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 077b4be87..5b37a3f15 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,9 +1,9 @@  - Opc.Ua.Gds.Client.Common + $(AssemblyPrefix).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 d46cc0455..12d3f38f0 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,9 +1,9 @@  - Opc.Ua.Gds.Server.Common + $(AssemblyPrefix).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 7286c6b3e..06b15a16b 100644 --- a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj +++ b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj @@ -1,9 +1,9 @@ - Opc.Ua.PubSub + $(AssemblyPrefix).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 67aa71032..2de0fb177 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) - OPCFoundation.NetStandard.Opc.Ua.Security.Certificates + $(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/Diagnostics/CustomNodeManager.cs b/Libraries/Opc.Ua.Server/Diagnostics/CustomNodeManager.cs index fa25395da..3e1017b75 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; @@ -3021,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. @@ -3085,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; } @@ -3106,16 +3063,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 +3129,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 +3195,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 +3268,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 +3416,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 +3943,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 +4141,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 +4227,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 +4332,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 +4497,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 +4702,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 +4759,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/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs b/Libraries/Opc.Ua.Server/NodeManager/MasterNodeManager.cs index 6792c19c8..f47efe07d 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/Opc.Ua.Server.csproj b/Libraries/Opc.Ua.Server/Opc.Ua.Server.csproj index 5eff9082e..3c5bb25f2 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) - OPCFoundation.NetStandard.Opc.Ua.Server + $(PackagePrefix).Opc.Ua.Server + $(AssemblyPrefix).Server Opc.Ua.Server OPC UA Server Class Library PackageReference diff --git a/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs b/Libraries/Opc.Ua.Server/Server/ServerInternalData.cs index ba9f82df2..5bdf33b7e 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.Bindings.Https/Opc.Ua.Bindings.Https.csproj b/Stack/Opc.Ua.Bindings.Https/Opc.Ua.Bindings.Https.csproj index d63a36fd5..597a8ce12 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 @@ -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 64ef8e948..992a4d600 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -1,14 +1,15 @@ - + $(DefineConstants);NET_STANDARD;NET_STANDARD_ASYNC $(LibCoreTargetFrameworks) - Opc.Ua.Core - OPCFoundation.NetStandard.Opc.Ua.Core + $(AssemblyPrefix).Core + $(PackagePrefix).Opc.Ua.Core Opc.Ua OPC UA Core Class Library true true + true @@ -44,7 +45,7 @@ use latest versions only on .NET 5/6/7/8, otherwise 3.1.x --> - + @@ -53,15 +54,20 @@ + + + + + - + - + @@ -88,23 +94,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/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 2c13adf88..6509c0a64 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/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/Configuration/ConfiguredEndpoints.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs index 941028966..bd22a2c6d 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); } } diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs b/Stack/Opc.Ua.Core/Stack/Configuration/SecurityConfigurationManager.cs index 230791f77..4dbd1ca99 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/State/MethodState.cs b/Stack/Opc.Ua.Core/Stack/State/MethodState.cs index f3b79abb0..e327a82b4 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/Stack/State/NodeState.cs b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs index c3e81b8e1..f92b26afc 100644 --- a/Stack/Opc.Ua.Core/Stack/State/NodeState.cs +++ b/Stack/Opc.Ua.Core/Stack/State/NodeState.cs @@ -3667,9 +3667,9 @@ protected virtual ServiceResult ReadNonValueAttribute( result = onReadAccessRestrictions(context, this, ref accessRestrictions); } - if (ServiceResult.IsGood(result)) + if (ServiceResult.IsGood(result) && accessRestrictions != null) { - value = accessRestrictions; + value = (ushort)accessRestrictions; } if (value != null || result != null) diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs b/Stack/Opc.Ua.Core/Stack/Tcp/TcpTransportListener.cs index 27c51eb71..85392a7ff 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/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs b/Stack/Opc.Ua.Core/Stack/Types/ContentFilter.Evaluate.cs index bc25a7769..6e5336fa8 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 59d58b91b..53f296f7e 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/NodeIdDictionary.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/NodeIdDictionary.cs index 87aa08341..ef0f1c94f 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/Stack/Opc.Ua.Core/Types/BuiltIn/NotificationData.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/NotificationData.cs index 89ae50a3d..b221e6e90 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 c9334bda1..dd61153f1 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 e517198c9..8543faaea 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/EncodeableFactory.cs b/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs index c47c8742f..bc938ba9a 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); diff --git a/Stack/Opc.Ua.Core/Types/Encoders/IEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/IEncoder.cs index 11f8e860c..2c1c22db6 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 15555f416..e9ab4e0af 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/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs b/Stack/Opc.Ua.Core/Types/Utils/ServiceResult.cs index 1d04c4908..40d72b66f 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. /// 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 c0a3b7619..166ceb5f0 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/ContinuationPointInBatchTest.cs b/Tests/Opc.Ua.Client.Tests/ContinuationPointInBatchTest.cs index a31fd5399..bcfe7dd87 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.Client.Tests/Opc.Ua.Client.Tests.csproj b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj index e0f5439bc..77358dbc3 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 7cb5c2d05..a2f0e9682 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 e930d1b06..d9910ae4d 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 d1d93a62a..473932454 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 3a1bea5ff..202d7bd04 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 21d564ee3..b2484400c 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/Stack/Buffers/ArraySegmentStreamTests.cs b/Tests/Opc.Ua.Core.Tests/Stack/Buffers/ArraySegmentStreamTests.cs index b6bcaca24..cad849eb7 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/Encoders/EncoderTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Encoders/EncoderTests.cs index 9f95713ba..f40c42468 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 08a97d438..0b89d758a 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 9d8f407bd..eb9acd211 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 614be99ac..2cff40c58 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 f034cd35a..f6112fe69 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.Core.Tests/Types/Utils/UtilsIsEqualTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Utils/UtilsIsEqualTests.cs index 14339f59e..5ad041136 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.Gds.Tests/Common.cs b/Tests/Opc.Ua.Gds.Tests/Common.cs index f3e810668..5ef193847 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 33621c44d..b4d2870a8 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 19b6f41a7..86ce2fbb4 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 260cc8fa6..51ff0c7b5 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/MessagesHelper.cs b/Tests/Opc.Ua.PubSub.Tests/Encoding/MessagesHelper.cs index 066206140..1c2447053 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/Encoding/MqttJsonNetworkMessageTests.cs b/Tests/Opc.Ua.PubSub.Tests/Encoding/MqttJsonNetworkMessageTests.cs index f35a4a4c1..a71ee5e70 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 bd017a091..71480d43e 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.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs b/Tests/Opc.Ua.PubSub.Tests/Transport/MqttPubSubConnectionTests.cs index 8cd5d271e..7cc541f3f 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 767f96a23..85071c8de 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/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 3eb757da8..c55596410 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 new file mode 100644 index 000000000..6371f779a --- /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).ConfigureAwait(false); + + + //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).ConfigureAwait(false); + + //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.ConfigureAwait(false); + } + + /// + /// 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.ConfigureAwait(false); + } + #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().ConfigureAwait(false); + } + 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)).ConfigureAwait(false); + 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 + } +} 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 7fead9e0e..7ed18dd4e 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/azure-pipelines.yml b/azure-pipelines.yml index 0be491de2..68e57bf23 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 diff --git a/common.props b/common.props index 755327f6f..66a2f0bd9 100644 --- a/common.props +++ b/common.props @@ -4,7 +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 @@ -14,7 +16,7 @@ AnyCPU true - 7.3 + 9 true diff --git a/targets.props b/targets.props index b1cf79a21..cde7d478b 100644 --- a/targets.props +++ b/targets.props @@ -1,12 +1,10 @@ - - + -