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 @@
-
-
+
-