diff --git a/.github/workflows/buildandtest.yml b/.github/workflows/buildandtest.yml
index f7c5132a0..41032b056 100644
--- a/.github/workflows/buildandtest.yml
+++ b/.github/workflows/buildandtest.yml
@@ -38,7 +38,7 @@ jobs:
TESTRESULTS: "TestResults-${{matrix.csproj}}-${{matrix.os}}-${{matrix.framework}}-${{matrix.configuration}}"
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index fe35d1000..51b352c3d 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -32,7 +32,7 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
index 06189f7fc..965ebf63c 100644
--- a/.github/workflows/docker-image.yml
+++ b/.github/workflows/docker-image.yml
@@ -34,7 +34,7 @@ jobs:
id-token: write
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -57,13 +57,13 @@ jobs:
# https://github.com/docker/build-push-action
- name: Setup Docker buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
- uses: docker/login-action@v2
+ uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -93,18 +93,18 @@ jobs:
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
- uses: docker/metadata-action@v4
+ uses: docker/metadata-action@v5
with:
images: ${{ env.IMAGE_REPOSITORY }}
- name: Set up QEMU
- uses: docker/setup-qemu-action@v2
+ uses: docker/setup-qemu-action@v3
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
- uses: docker/build-push-action@v4
+ uses: docker/build-push-action@v5
with:
context: .
build-args: |
diff --git a/Applications/ClientControls.Net4/Endpoints/UsernameTokenDlg.cs b/Applications/ClientControls.Net4/Endpoints/UsernameTokenDlg.cs
index f630083bf..30a6f1c9b 100644
--- a/Applications/ClientControls.Net4/Endpoints/UsernameTokenDlg.cs
+++ b/Applications/ClientControls.Net4/Endpoints/UsernameTokenDlg.cs
@@ -73,7 +73,7 @@ public bool ShowDialog(UserNameIdentityToken token)
if (token.Password != null && token.Password.Length > 0)
{
- PasswordTB.Text = new UTF8Encoding().GetString(token.Password);
+ PasswordTB.Text = Encoding.UTF8.GetString(token.Password);
}
}
@@ -86,7 +86,7 @@ public bool ShowDialog(UserNameIdentityToken token)
if (!String.IsNullOrEmpty(PasswordTB.Text))
{
- token.Password = new UTF8Encoding().GetBytes(PasswordTB.Text);
+ token.Password = Encoding.UTF8.GetBytes(PasswordTB.Text);
}
else
{
diff --git a/Applications/ConsoleReferenceClient/ClientSamples.cs b/Applications/ConsoleReferenceClient/ClientSamples.cs
index 8c8efc8a0..4aee8c0a7 100644
--- a/Applications/ConsoleReferenceClient/ClientSamples.cs
+++ b/Applications/ConsoleReferenceClient/ClientSamples.cs
@@ -376,7 +376,7 @@ public void SubscribeToDataChanges(ISession session, uint minLifeTime)
/// Adds the root node to the result.
/// Filters nodes from namespace 0 from the result.
/// The list of nodes on the server.
- public IList FetchAllNodesNodeCache(
+ public async Task> FetchAllNodesNodeCacheAsync(
IUAClient uaClient,
NodeId startingNode,
bool fetchTree = false,
@@ -398,13 +398,13 @@ public IList FetchAllNodesNodeCache(
{
// clear NodeCache to fetch all nodes from server
uaClient.Session.NodeCache.Clear();
- FetchReferenceIdTypes(uaClient.Session);
+ await FetchReferenceIdTypesAsync(uaClient.Session).ConfigureAwait(false);
}
// add root node
if (addRootNode)
{
- var rootNode = uaClient.Session.NodeCache.Find(startingNode);
+ var rootNode = await uaClient.Session.NodeCache.FindAsync(startingNode).ConfigureAwait(false);
nodeDictionary[rootNode.NodeId] = rootNode;
}
@@ -419,11 +419,11 @@ public IList FetchAllNodesNodeCache(
searchDepth++;
Utils.LogInfo("{0}: Find {1} references after {2}ms", searchDepth, nodesToBrowse.Count, stopwatch.ElapsedMilliseconds);
- IList response = uaClient.Session.NodeCache.FindReferences(
+ IList response = await uaClient.Session.NodeCache.FindReferencesAsync(
nodesToBrowse,
references,
false,
- true);
+ true).ConfigureAwait(false);
var nextNodesToBrowse = new ExpandedNodeIdCollection();
int duplicates = 0;
@@ -466,7 +466,7 @@ public IList FetchAllNodesNodeCache(
}
else
{
- nodeDictionary[node.NodeId] = node; ;
+ nodeDictionary[node.NodeId] = node;
}
}
else
@@ -511,10 +511,11 @@ public IList FetchAllNodesNodeCache(
/// The UAClient with a session to use.
/// The node where the browse operation starts.
/// An optional BrowseDescription to use.
- public ReferenceDescriptionCollection BrowseFullAddressSpace(
+ public async Task BrowseFullAddressSpaceAsync(
IUAClient uaClient,
NodeId startingNode = null,
- BrowseDescription browseDescription = null)
+ BrowseDescription browseDescription = null,
+ CancellationToken ct = default)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
@@ -563,9 +564,10 @@ public ReferenceDescriptionCollection BrowseFullAddressSpace(
repeatBrowse = false;
try
{
- _ = uaClient.Session.Browse(null, null,
- kMaxReferencesPerNode, browseCollection,
- out browseResultCollection, out diagnosticsInfoCollection);
+ var browseResponse = await uaClient.Session.BrowseAsync(null, null,
+ kMaxReferencesPerNode, browseCollection, ct).ConfigureAwait(false);
+ browseResultCollection = browseResponse.Results;
+ diagnosticsInfoCollection = browseResponse.DiagnosticInfos;
ClientBase.ValidateResponse(browseResultCollection, browseCollection);
ClientBase.ValidateDiagnosticInfos(diagnosticsInfoCollection, browseCollection);
@@ -629,8 +631,9 @@ public ReferenceDescriptionCollection BrowseFullAddressSpace(
}
Utils.LogInfo("BrowseNext {0} continuation points.", continuationPoints.Count);
- _ = uaClient.Session.BrowseNext(null, false, continuationPoints,
- out var browseNextResultCollection, out diagnosticsInfoCollection);
+ var browseNextResult = await uaClient.Session.BrowseNextAsync(null, false, continuationPoints, ct).ConfigureAwait(false);
+ var browseNextResultCollection = browseNextResult.Results;
+ diagnosticsInfoCollection = browseNextResult.DiagnosticInfos;
ClientBase.ValidateResponse(browseNextResultCollection, continuationPoints);
ClientBase.ValidateDiagnosticInfos(diagnosticsInfoCollection, continuationPoints);
allBrowseResults.AddRange(browseNextResultCollection);
@@ -696,7 +699,7 @@ public ReferenceDescriptionCollection BrowseFullAddressSpace(
/// Outputs elapsed time information for perf testing and lists all
/// types that were successfully added to the session encodeable type factory.
///
- public async Task LoadTypeSystem(ISession session)
+ public async Task LoadTypeSystemAsync(ISession session)
{
m_output.WriteLine("Load the server type system.");
@@ -742,7 +745,7 @@ public async Task LoadTypeSystem(ISession session)
/// The NodeCache needs this information to function properly with subtypes of hierarchical calls.
///
/// The session to use
- void FetchReferenceIdTypes(ISession session)
+ Task FetchReferenceIdTypesAsync(ISession session)
{
// fetch the reference types first, otherwise browse for e.g. hierarchical references with subtypes won't work
var bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public;
@@ -750,7 +753,7 @@ void FetchReferenceIdTypes(ISession session)
var referenceTypes = typeof(ReferenceTypeIds)
.GetFields(bindingFlags)
.Select(field => NodeId.ToExpandedNodeId((NodeId)field.GetValue(null), namespaceUris));
- session.FetchTypeTree(new ExpandedNodeIdCollection(referenceTypes));
+ return session.FetchTypeTreeAsync(new ExpandedNodeIdCollection(referenceTypes));
}
#endregion
@@ -897,7 +900,7 @@ public async Task SubscribeAllValuesAsync(
StartNodeId = item.NodeId,
AttributeId = Attributes.Value,
SamplingInterval = samplingInterval,
- DisplayName = item.DisplayName.Text ?? item.BrowseName.Name,
+ DisplayName = item.DisplayName?.Text ?? item.BrowseName.Name,
QueueSize = queueSize,
DiscardOldest = true,
MonitoringMode = MonitoringMode.Reporting,
@@ -907,7 +910,7 @@ public async Task SubscribeAllValuesAsync(
}
// Create the monitored items on Server side
- subscription.ApplyChanges();
+ await subscription.ApplyChangesAsync().ConfigureAwait(false);
m_output.WriteLine("MonitoredItems {0} created for SubscriptionId = {1}.", subscription.MonitoredItemCount, subscription.Id);
}
catch (Exception ex)
diff --git a/Applications/ConsoleReferenceClient/Program.cs b/Applications/ConsoleReferenceClient/Program.cs
index e586a373f..16450caf6 100644
--- a/Applications/ConsoleReferenceClient/Program.cs
+++ b/Applications/ConsoleReferenceClient/Program.cs
@@ -129,8 +129,7 @@ public static async Task Main(string[] args)
// Define the UA Client application
ApplicationInstance.MessageDlg = new ApplicationMessageDlg(output);
CertificatePasswordProvider PasswordProvider = new CertificatePasswordProvider(password);
- ApplicationInstance application = new ApplicationInstance
- {
+ ApplicationInstance application = new ApplicationInstance {
ApplicationName = applicationName,
ApplicationType = ApplicationType.Client,
ConfigSectionName = configSectionName,
@@ -220,7 +219,7 @@ public static async Task Main(string[] args)
var samples = new ClientSamples(output, ClientBase.ValidateResponse, quitEvent, verbose);
if (loadTypes)
{
- await samples.LoadTypeSystem(uaClient.Session).ConfigureAwait(false);
+ await samples.LoadTypeSystemAsync(uaClient.Session).ConfigureAwait(false);
}
if (browseall || fetchall || jsonvalues)
@@ -230,7 +229,7 @@ public static async Task Main(string[] args)
if (browseall)
{
referenceDescriptions =
- samples.BrowseFullAddressSpace(uaClient, Objects.RootFolder);
+ await samples.BrowseFullAddressSpaceAsync(uaClient, Objects.RootFolder).ConfigureAwait(false);
variableIds = new NodeIdCollection(referenceDescriptions
.Where(r => r.NodeClass == NodeClass.Variable && r.TypeDefinition.NamespaceIndex != 0)
.Select(r => ExpandedNodeId.ToNodeId(r.NodeId, uaClient.Session.NamespaceUris)));
@@ -239,8 +238,7 @@ public static async Task Main(string[] args)
IList allNodes = null;
if (fetchall)
{
- allNodes = samples.FetchAllNodesNodeCache(
- uaClient, Objects.RootFolder, true, true, false);
+ allNodes = await samples.FetchAllNodesNodeCacheAsync(uaClient, Objects.RootFolder, true, true, false).ConfigureAwait(false);
variableIds = new NodeIdCollection(allNodes
.Where(r => r.NodeClass == NodeClass.Variable && r is VariableNode && ((VariableNode)r).DataType.NamespaceIndex != 0)
.Select(r => ExpandedNodeId.ToNodeId(r.NodeId, uaClient.Session.NamespaceUris)));
@@ -248,7 +246,7 @@ public static async Task Main(string[] args)
if (jsonvalues && variableIds != null)
{
- await samples.ReadAllValuesAsync(uaClient, variableIds).ConfigureAwait(false);
+ var (allValues, results) = await samples.ReadAllValuesAsync(uaClient, variableIds).ConfigureAwait(false);
}
if (subscribe && (browseall || fetchall))
diff --git a/Applications/Quickstarts.Servers/Boiler/Boiler.NodeSet2.xml b/Applications/Quickstarts.Servers/Boiler/Boiler.NodeSet2.xml
index f5e210b37..8e764d624 100644
--- a/Applications/Quickstarts.Servers/Boiler/Boiler.NodeSet2.xml
+++ b/Applications/Quickstarts.Servers/Boiler/Boiler.NodeSet2.xml
@@ -1,10 +1,10 @@
-
+
http://opcfoundation.org/UA/Boiler/
-
+
diff --git a/Applications/Quickstarts.Servers/Boiler/Boiler.Types.xsd b/Applications/Quickstarts.Servers/Boiler/Boiler.Types.xsd
index 4c321c10a..e2c6d129f 100644
--- a/Applications/Quickstarts.Servers/Boiler/Boiler.Types.xsd
+++ b/Applications/Quickstarts.Servers/Boiler/Boiler.Types.xsd
@@ -7,7 +7,7 @@
>
-
+
diff --git a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.NodeSet2.xml b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.NodeSet2.xml
index 92343e315..537c3a2a1 100644
--- a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.NodeSet2.xml
+++ b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.NodeSet2.xml
@@ -1,10 +1,10 @@
-
+
http://samples.org/UA/MemoryBuffer
-
+
diff --git a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.Types.xsd b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.Types.xsd
index 599f40e5c..58dd18637 100644
--- a/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.Types.xsd
+++ b/Applications/Quickstarts.Servers/MemoryBuffer/MemoryBuffer.Types.xsd
@@ -7,7 +7,7 @@
>
-
+
diff --git a/Applications/Quickstarts.Servers/TestData/ArrayValueObjectState.cs b/Applications/Quickstarts.Servers/TestData/ArrayValueObjectState.cs
index 872d27566..18df5a97a 100644
--- a/Applications/Quickstarts.Servers/TestData/ArrayValueObjectState.cs
+++ b/Applications/Quickstarts.Servers/TestData/ArrayValueObjectState.cs
@@ -74,6 +74,9 @@ protected override void OnAfterCreate(ISystemContext context, NodeState node)
InitializeVariable(context, IntegerValue, TestData.Variables.ArrayValueObjectType_IntegerValue);
InitializeVariable(context, UIntegerValue, TestData.Variables.ArrayValueObjectType_UIntegerValue);
InitializeVariable(context, VectorValue, TestData.Variables.ArrayValueObjectType_VectorValue);
+ InitializeVariable(context, VectorUnionValue, TestData.Variables.ArrayValueObjectType_VectorUnionValue);
+ InitializeVariable(context, VectorWithOptionalFieldsValue, TestData.Variables.ArrayValueObjectType_VectorWithOptionalFieldsValue);
+ InitializeVariable(context, MultipleVectorsValue, TestData.Variables.ArrayValueObjectType_MultipleVectorsValue);
}
#endregion
@@ -123,6 +126,9 @@ protected override ServiceResult OnGenerateValues(
GenerateValue(system, IntegerValue);
GenerateValue(system, UIntegerValue);
GenerateValue(system, VectorValue);
+ GenerateValue(system, VectorUnionValue);
+ GenerateValue(system, VectorWithOptionalFieldsValue);
+ GenerateValue(system, MultipleVectorsValue);
return base.OnGenerateValues(context, method, objectId, count);
}
diff --git a/Applications/Quickstarts.Servers/TestData/ScalarValueObjectState.cs b/Applications/Quickstarts.Servers/TestData/ScalarValueObjectState.cs
index b444abae2..f8aa7127a 100644
--- a/Applications/Quickstarts.Servers/TestData/ScalarValueObjectState.cs
+++ b/Applications/Quickstarts.Servers/TestData/ScalarValueObjectState.cs
@@ -74,6 +74,9 @@ protected override void OnAfterCreate(ISystemContext context, NodeState node)
InitializeVariable(context, IntegerValue, TestData.Variables.ScalarValueObjectType_IntegerValue);
InitializeVariable(context, UIntegerValue, TestData.Variables.ScalarValueObjectType_UIntegerValue);
InitializeVariable(context, VectorValue, TestData.Variables.ScalarValueObjectType_VectorValue);
+ InitializeVariable(context, VectorUnionValue, TestData.Variables.ScalarValueObjectType_VectorUnionValue);
+ InitializeVariable(context, VectorWithOptionalFieldsValue, TestData.Variables.ScalarValueObjectType_VectorWithOptionalFieldsValue);
+ InitializeVariable(context, MultipleVectorsValue, TestData.Variables.ScalarValueObjectType_MultipleVectorsValue);
}
#endregion
@@ -123,6 +126,9 @@ protected override ServiceResult OnGenerateValues(
GenerateValue(system, IntegerValue);
GenerateValue(system, UIntegerValue);
GenerateValue(system, VectorValue);
+ GenerateValue(system, VectorUnionValue);
+ GenerateValue(system, VectorWithOptionalFieldsValue);
+ GenerateValue(system, MultipleVectorsValue);
return base.OnGenerateValues(context, method, objectId, count);
}
diff --git a/Applications/Quickstarts.Servers/TestData/TestData.Classes.cs b/Applications/Quickstarts.Servers/TestData/TestData.Classes.cs
index 038c82f0c..65452619b 100644
--- a/Applications/Quickstarts.Servers/TestData/TestData.Classes.cs
+++ b/Applications/Quickstarts.Servers/TestData/TestData.Classes.cs
@@ -4606,7 +4606,7 @@ protected override void InitializeOptionalChildren(ISystemContext context)
#region Initialization String
private const string InitializationString =
"AQAAABgAAABodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS//////BGCAAgEAAAABAB0AAABTY2FsYXJWYWx1" +
- "ZU9iamVjdFR5cGVJbnN0YW5jZQEBXAQBAVwEXAQAAAEAAAAAJAABAWAEHwAAADVgiQoCAAAAAQAQAAAA" +
+ "ZU9iamVjdFR5cGVJbnN0YW5jZQEBXAQBAVwEXAQAAAEAAAAAJAABAWAEIgAAADVgiQoCAAAAAQAQAAAA" +
"U2ltdWxhdGlvbkFjdGl2ZQEBXQQDAAAAAEcAAABJZiB0cnVlIHRoZSBzZXJ2ZXIgd2lsbCBwcm9kdWNl" +
"IG5ldyB2YWx1ZXMgZm9yIGVhY2ggbW9uaXRvcmVkIHZhcmlhYmxlLgAuAERdBAAAAAH/////AQH/////" +
"AAAAAARhggoEAAAAAQAOAAAAR2VuZXJhdGVWYWx1ZXMBAV4EAC8BAfkDXgQAAAEB/////wEAAAAXYKkK" +
@@ -4673,7 +4673,10 @@ protected override void InitializeOptionalChildren(ISystemContext context)
"Z2VyVmFsdWUBAbUEAC8AP7UEAAAAHP////8BAf////8AAAAAFWCJCgIAAAABAAsAAABWZWN0b3JWYWx1" +
"ZQEBtgQALwEBYQe2BAAAAQFgB/////8BAf////8DAAAAFWCJCgIAAAABAAEAAABYAQG3BAAuAES3BAAA" +
"AAv/////AQH/////AAAAABVgiQoCAAAAAQABAAAAWQEBuAQALgBEuAQAAAAL/////wEB/////wAAAAAV" +
- "YIkKAgAAAAEAAQAAAFoBAbkEAC4ARLkEAAAAC/////8BAf////8AAAAA";
+ "YIkKAgAAAAEAAQAAAFoBAbkEAC4ARLkEAAAAC/////8BAf////8AAAAAFWCJCgIAAAABABAAAABWZWN0" +
+ "b3JVbmlvblZhbHVlAQH+DQAvAD/+DQAAAQEADv////8BAf////8AAAAAFWCJCgIAAAABAB0AAABWZWN0" +
+ "b3JXaXRoT3B0aW9uYWxGaWVsZHNWYWx1ZQEB/w0ALwA//w0AAAEBAQ7/////AQH/////AAAAABVgiQoC" +
+ "AAAAAQAUAAAATXVsdGlwbGVWZWN0b3JzVmFsdWUBAR4OAC8APx4OAAABAR8O/////wEB/////wAAAAA=";
#endregion
#endif
#endregion
@@ -5210,6 +5213,63 @@ public VectorVariableState VectorValue
m_vectorValue = value;
}
}
+
+ ///
+ public BaseDataVariableState VectorUnionValue
+ {
+ get
+ {
+ return m_vectorUnionValue;
+ }
+
+ set
+ {
+ if (!Object.ReferenceEquals(m_vectorUnionValue, value))
+ {
+ ChangeMasks |= NodeStateChangeMasks.Children;
+ }
+
+ m_vectorUnionValue = value;
+ }
+ }
+
+ ///
+ public BaseDataVariableState VectorWithOptionalFieldsValue
+ {
+ get
+ {
+ return m_vectorWithOptionalFieldsValue;
+ }
+
+ set
+ {
+ if (!Object.ReferenceEquals(m_vectorWithOptionalFieldsValue, value))
+ {
+ ChangeMasks |= NodeStateChangeMasks.Children;
+ }
+
+ m_vectorWithOptionalFieldsValue = value;
+ }
+ }
+
+ ///
+ public BaseDataVariableState MultipleVectorsValue
+ {
+ get
+ {
+ return m_multipleVectorsValue;
+ }
+
+ set
+ {
+ if (!Object.ReferenceEquals(m_multipleVectorsValue, value))
+ {
+ ChangeMasks |= NodeStateChangeMasks.Children;
+ }
+
+ m_multipleVectorsValue = value;
+ }
+ }
#endregion
#region Overridden Methods
@@ -5358,6 +5418,21 @@ public override void GetChildren(
children.Add(m_vectorValue);
}
+ if (m_vectorUnionValue != null)
+ {
+ children.Add(m_vectorUnionValue);
+ }
+
+ if (m_vectorWithOptionalFieldsValue != null)
+ {
+ children.Add(m_vectorWithOptionalFieldsValue);
+ }
+
+ if (m_multipleVectorsValue != null)
+ {
+ children.Add(m_multipleVectorsValue);
+ }
+
base.GetChildren(context, children);
}
@@ -5964,6 +6039,69 @@ protected override BaseInstanceState FindChild(
instance = VectorValue;
break;
}
+
+ case TestData.BrowseNames.VectorUnionValue:
+ {
+ if (createOrReplace)
+ {
+ if (VectorUnionValue == null)
+ {
+ if (replacement == null)
+ {
+ VectorUnionValue = new BaseDataVariableState(this);
+ }
+ else
+ {
+ VectorUnionValue = (BaseDataVariableState)replacement;
+ }
+ }
+ }
+
+ instance = VectorUnionValue;
+ break;
+ }
+
+ case TestData.BrowseNames.VectorWithOptionalFieldsValue:
+ {
+ if (createOrReplace)
+ {
+ if (VectorWithOptionalFieldsValue == null)
+ {
+ if (replacement == null)
+ {
+ VectorWithOptionalFieldsValue = new BaseDataVariableState(this);
+ }
+ else
+ {
+ VectorWithOptionalFieldsValue = (BaseDataVariableState)replacement;
+ }
+ }
+ }
+
+ instance = VectorWithOptionalFieldsValue;
+ break;
+ }
+
+ case TestData.BrowseNames.MultipleVectorsValue:
+ {
+ if (createOrReplace)
+ {
+ if (MultipleVectorsValue == null)
+ {
+ if (replacement == null)
+ {
+ MultipleVectorsValue = new BaseDataVariableState(this);
+ }
+ else
+ {
+ MultipleVectorsValue = (BaseDataVariableState)replacement;
+ }
+ }
+ }
+
+ instance = MultipleVectorsValue;
+ break;
+ }
}
if (instance != null)
@@ -6004,6 +6142,9 @@ protected override BaseInstanceState FindChild(
private BaseDataVariableState m_integerValue;
private BaseDataVariableState m_uIntegerValue;
private VectorVariableState m_vectorValue;
+ private BaseDataVariableState m_vectorUnionValue;
+ private BaseDataVariableState m_vectorWithOptionalFieldsValue;
+ private BaseDataVariableState m_multipleVectorsValue;
#endregion
}
#endif
@@ -7543,7 +7684,7 @@ protected override void InitializeOptionalChildren(ISystemContext context)
#region Initialization String
private const string InitializationString =
"AQAAABgAAABodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS//////BGCAAgEAAAABABwAAABBcnJheVZhbHVl" +
- "T2JqZWN0VHlwZUluc3RhbmNlAQGwBQEBsAWwBQAAAQAAAAAkAAEBtAUfAAAANWCJCgIAAAABABAAAABT" +
+ "T2JqZWN0VHlwZUluc3RhbmNlAQGwBQEBsAWwBQAAAQAAAAAkAAEBtAUiAAAANWCJCgIAAAABABAAAABT" +
"aW11bGF0aW9uQWN0aXZlAQGxBQMAAAAARwAAAElmIHRydWUgdGhlIHNlcnZlciB3aWxsIHByb2R1Y2Ug" +
"bmV3IHZhbHVlcyBmb3IgZWFjaCBtb25pdG9yZWQgdmFyaWFibGUuAC4ARLEFAAAAAf////8BAf////8A" +
"AAAABGGCCgQAAAABAA4AAABHZW5lcmF0ZVZhbHVlcwEBsgUALwEB+QOyBQAAAQH/////AQAAABdgqQoC" +
@@ -7612,7 +7753,10 @@ protected override void InitializeOptionalChildren(ISystemContext context)
"//8AAAAAF2CJCgIAAAABAAwAAABJbnRlZ2VyVmFsdWUBAQgGAC8APwgGAAAAGwEAAAABAAAAAAAAAAEB" +
"/////wAAAAAXYIkKAgAAAAEADQAAAFVJbnRlZ2VyVmFsdWUBAQkGAC8APwkGAAAAHAEAAAABAAAAAAAA" +
"AAEB/////wAAAAAXYIkKAgAAAAEACwAAAFZlY3RvclZhbHVlAQEKBgAvAD8KBgAAAQFgBwEAAAABAAAA" +
- "AAAAAAEB/////wAAAAA=";
+ "AAAAAAEB/////wAAAAAXYIkKAgAAAAEAEAAAAFZlY3RvclVuaW9uVmFsdWUBARgOAC8APxgOAAABAQAO" +
+ "AQAAAAEAAAAAAAAAAQH/////AAAAABdgiQoCAAAAAQAdAAAAVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRz" +
+ "VmFsdWUBARkOAC8APxkOAAABAQEOAQAAAAEAAAAAAAAAAQH/////AAAAABdgiQoCAAAAAQAUAAAATXVs" +
+ "dGlwbGVWZWN0b3JzVmFsdWUBASsOAC8APysOAAABAR8OAQAAAAEAAAAAAAAAAQH/////AAAAAA==";
#endregion
#endif
#endregion
@@ -8149,6 +8293,63 @@ public BaseDataVariableState VectorValue
m_vectorValue = value;
}
}
+
+ ///
+ public BaseDataVariableState VectorUnionValue
+ {
+ get
+ {
+ return m_vectorUnionValue;
+ }
+
+ set
+ {
+ if (!Object.ReferenceEquals(m_vectorUnionValue, value))
+ {
+ ChangeMasks |= NodeStateChangeMasks.Children;
+ }
+
+ m_vectorUnionValue = value;
+ }
+ }
+
+ ///
+ public BaseDataVariableState VectorWithOptionalFieldsValue
+ {
+ get
+ {
+ return m_vectorWithOptionalFieldsValue;
+ }
+
+ set
+ {
+ if (!Object.ReferenceEquals(m_vectorWithOptionalFieldsValue, value))
+ {
+ ChangeMasks |= NodeStateChangeMasks.Children;
+ }
+
+ m_vectorWithOptionalFieldsValue = value;
+ }
+ }
+
+ ///
+ public BaseDataVariableState MultipleVectorsValue
+ {
+ get
+ {
+ return m_multipleVectorsValue;
+ }
+
+ set
+ {
+ if (!Object.ReferenceEquals(m_multipleVectorsValue, value))
+ {
+ ChangeMasks |= NodeStateChangeMasks.Children;
+ }
+
+ m_multipleVectorsValue = value;
+ }
+ }
#endregion
#region Overridden Methods
@@ -8297,6 +8498,21 @@ public override void GetChildren(
children.Add(m_vectorValue);
}
+ if (m_vectorUnionValue != null)
+ {
+ children.Add(m_vectorUnionValue);
+ }
+
+ if (m_vectorWithOptionalFieldsValue != null)
+ {
+ children.Add(m_vectorWithOptionalFieldsValue);
+ }
+
+ if (m_multipleVectorsValue != null)
+ {
+ children.Add(m_multipleVectorsValue);
+ }
+
base.GetChildren(context, children);
}
@@ -8903,6 +9119,69 @@ protected override BaseInstanceState FindChild(
instance = VectorValue;
break;
}
+
+ case TestData.BrowseNames.VectorUnionValue:
+ {
+ if (createOrReplace)
+ {
+ if (VectorUnionValue == null)
+ {
+ if (replacement == null)
+ {
+ VectorUnionValue = new BaseDataVariableState(this);
+ }
+ else
+ {
+ VectorUnionValue = (BaseDataVariableState)replacement;
+ }
+ }
+ }
+
+ instance = VectorUnionValue;
+ break;
+ }
+
+ case TestData.BrowseNames.VectorWithOptionalFieldsValue:
+ {
+ if (createOrReplace)
+ {
+ if (VectorWithOptionalFieldsValue == null)
+ {
+ if (replacement == null)
+ {
+ VectorWithOptionalFieldsValue = new BaseDataVariableState(this);
+ }
+ else
+ {
+ VectorWithOptionalFieldsValue = (BaseDataVariableState)replacement;
+ }
+ }
+ }
+
+ instance = VectorWithOptionalFieldsValue;
+ break;
+ }
+
+ case TestData.BrowseNames.MultipleVectorsValue:
+ {
+ if (createOrReplace)
+ {
+ if (MultipleVectorsValue == null)
+ {
+ if (replacement == null)
+ {
+ MultipleVectorsValue = new BaseDataVariableState(this);
+ }
+ else
+ {
+ MultipleVectorsValue = (BaseDataVariableState)replacement;
+ }
+ }
+ }
+
+ instance = MultipleVectorsValue;
+ break;
+ }
}
if (instance != null)
@@ -8943,6 +9222,9 @@ protected override BaseInstanceState FindChild(
private BaseDataVariableState
+
+ Variable_2
+
+ ns=1;i=3600
+
+
+ 1
+ VectorUnion
+
+
+ i=47
+
+
+ i=69
+
+ 3600
+
+
+ //xs:element[@name='VectorUnion']
+
+
+
+ i=12
+
+ -1
+ 1
+ 1
+
+
+ Variable_2
+
+ ns=1;i=3603
+
+
+ 1
+ VectorWithOptionalFields
+
+
+ i=47
+
+
+ i=69
+
+ 3603
+
+
+ //xs:element[@name='VectorWithOptionalFields']
+
+
+
+ i=12
+
+ -1
+ 1
+ 1
+
+
+ Variable_2
+
+ ns=1;i=3623
+
+
+ 1
+ MultipleVectors
+
+
+ i=47
+
+
+ i=69
+
+ 3623
+
+
+ //xs:element[@name='MultipleVectors']
+
+
+
+ i=12
+
+ -1
+ 1
+ 1
+
Variable_2
@@ -41278,6 +42366,81 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4=
+
+ Object_1
+
+ ns=1;i=3606
+
+
+ 0
+ Default JSON
+
+
+ i=76
+
+ 3606
+
+
+
+ i=38
+
+ true
+
+ ns=1;i=3584
+
+
+
+
+
+ Object_1
+
+ ns=1;i=3607
+
+
+ 0
+ Default JSON
+
+
+ i=76
+
+ 3607
+
+
+
+ i=38
+
+ true
+
+ ns=1;i=3585
+
+
+
+
+
+ Object_1
+
+ ns=1;i=3626
+
+
+ 0
+ Default JSON
+
+
+ i=76
+
+ 3626
+
+
+
+ i=38
+
+ true
+
+ ns=1;i=3615
+
+
+
+
Object_1
diff --git a/Applications/Quickstarts.Servers/TestData/TestData.Types.bsd b/Applications/Quickstarts.Servers/TestData/TestData.Types.bsd
index a2fc611e2..9b0f82f19 100644
--- a/Applications/Quickstarts.Servers/TestData/TestData.Types.bsd
+++ b/Applications/Quickstarts.Servers/TestData/TestData.Types.bsd
@@ -239,6 +239,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Applications/Quickstarts.Servers/TestData/TestData.Types.xsd b/Applications/Quickstarts.Servers/TestData/TestData.Types.xsd
index 5d6b17cf8..abf2afc54 100644
--- a/Applications/Quickstarts.Servers/TestData/TestData.Types.xsd
+++ b/Applications/Quickstarts.Servers/TestData/TestData.Types.xsd
@@ -7,7 +7,7 @@
>
-
+
@@ -235,6 +235,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Applications/Quickstarts.Servers/TestData/TestDataDesign.csv b/Applications/Quickstarts.Servers/TestData/TestDataDesign.csv
index d61bd615a..f21636eff 100644
--- a/Applications/Quickstarts.Servers/TestData/TestDataDesign.csv
+++ b/Applications/Quickstarts.Servers/TestData/TestDataDesign.csv
@@ -2579,3 +2579,51 @@ UserArrayValueDataType_Encoding_DefaultJson,3578,Object
Vector_Encoding_DefaultJson,3579,Object
WorkOrderStatusType_Encoding_DefaultJson,3580,Object
WorkOrderType_Encoding_DefaultJson,3581,Object
+ScalarValueObjectType_VectorUnionValue,3582,Variable
+ScalarValueObjectType_VectorWithOptionalFieldsValue,3583,Variable
+VectorUnion,3584,DataType
+VectorWithOptionalFields,3585,DataType
+Data_Static_Scalar_VectorUnionValue,3586,Variable
+Data_Static_Scalar_VectorWithOptionalFieldsValue,3587,Variable
+Data_Dynamic_Scalar_VectorUnionValue,3588,Variable
+Data_Dynamic_Scalar_VectorWithOptionalFieldsValue,3589,Variable
+VectorUnion_Encoding_DefaultBinary,3590,Object
+VectorWithOptionalFields_Encoding_DefaultBinary,3591,Object
+TestData_BinarySchema_VectorUnion,3592,Variable
+TestData_BinarySchema_VectorUnion_DataTypeVersion,3593,Variable
+TestData_BinarySchema_VectorUnion_DictionaryFragment,3594,Variable
+TestData_BinarySchema_VectorWithOptionalFields,3595,Variable
+TestData_BinarySchema_VectorWithOptionalFields_DataTypeVersion,3596,Variable
+TestData_BinarySchema_VectorWithOptionalFields_DictionaryFragment,3597,Variable
+VectorUnion_Encoding_DefaultXml,3598,Object
+VectorWithOptionalFields_Encoding_DefaultXml,3599,Object
+TestData_XmlSchema_VectorUnion,3600,Variable
+TestData_XmlSchema_VectorUnion_DataTypeVersion,3601,Variable
+TestData_XmlSchema_VectorUnion_DictionaryFragment,3602,Variable
+TestData_XmlSchema_VectorWithOptionalFields,3603,Variable
+TestData_XmlSchema_VectorWithOptionalFields_DataTypeVersion,3604,Variable
+TestData_XmlSchema_VectorWithOptionalFields_DictionaryFragment,3605,Variable
+VectorUnion_Encoding_DefaultJson,3606,Object
+VectorWithOptionalFields_Encoding_DefaultJson,3607,Object
+ArrayValueObjectType_VectorUnionValue,3608,Variable
+ArrayValueObjectType_VectorWithOptionalFieldsValue,3609,Variable
+Data_Static_Array_VectorUnionValue,3610,Variable
+Data_Static_Array_VectorWithOptionalFieldsValue,3611,Variable
+Data_Dynamic_Array_VectorUnionValue,3612,Variable
+Data_Dynamic_Array_VectorWithOptionalFieldsValue,3613,Variable
+ScalarValueObjectType_MultipleVectorsValue,3614,Variable
+MultipleVectors,3615,DataType
+Data_Static_Scalar_MultipleVectorsValue,3616,Variable
+Data_Dynamic_Scalar_MultipleVectorsValue,3617,Variable
+MultipleVectors_Encoding_DefaultBinary,3618,Object
+TestData_BinarySchema_MultipleVectors,3619,Variable
+TestData_BinarySchema_MultipleVectors_DataTypeVersion,3620,Variable
+TestData_BinarySchema_MultipleVectors_DictionaryFragment,3621,Variable
+MultipleVectors_Encoding_DefaultXml,3622,Object
+TestData_XmlSchema_MultipleVectors,3623,Variable
+TestData_XmlSchema_MultipleVectors_DataTypeVersion,3624,Variable
+TestData_XmlSchema_MultipleVectors_DictionaryFragment,3625,Variable
+MultipleVectors_Encoding_DefaultJson,3626,Object
+ArrayValueObjectType_MultipleVectorsValue,3627,Variable
+Data_Static_Array_MultipleVectorsValue,3628,Variable
+Data_Dynamic_Array_MultipleVectorsValue,3629,Variable
diff --git a/Applications/Quickstarts.Servers/TestData/TestDataDesign.xml b/Applications/Quickstarts.Servers/TestData/TestDataDesign.xml
index ed59e1d03..aa85572dd 100644
--- a/Applications/Quickstarts.Servers/TestData/TestDataDesign.xml
+++ b/Applications/Quickstarts.Servers/TestData/TestDataDesign.xml
@@ -258,6 +258,9 @@
+
+
+
@@ -417,6 +420,9 @@
+
+
+
@@ -643,7 +649,7 @@
-
-
+
-
+
@@ -58,7 +58,7 @@
-
+
@@ -81,18 +81,18 @@
-
+
$(BaseIntermediateOutputPath)/zipnodeset2
Schema/Opc.Ua.NodeSet2.xml
-
+
-
+
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs
index 4dd2815d0..2ffdf3a7d 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs
@@ -16,6 +16,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Opc.Ua.Security.Certificates;
@@ -110,53 +111,70 @@ public virtual void Update(
CertificateTrustList trustedStore,
CertificateStoreIdentifier rejectedCertificateStore)
{
- lock (m_lock)
+ try
{
- ResetValidatedCertificates();
+ m_semaphore.Wait();
- m_trustedCertificateStore = null;
- m_trustedCertificateList = null;
+ InternalUpdate(issuerStore, trustedStore, rejectedCertificateStore);
+ }
+ finally
+ {
+ m_semaphore.Release();
+ }
+ }
- if (trustedStore != null)
- {
- m_trustedCertificateStore = new CertificateStoreIdentifier();
+ ///
+ /// Updates the validator with a new set of trust lists.
+ ///
+ private void InternalUpdate(
+ CertificateTrustList issuerStore,
+ CertificateTrustList trustedStore,
+ CertificateStoreIdentifier rejectedCertificateStore)
+ {
+ InternalResetValidatedCertificates();
- m_trustedCertificateStore.StoreType = trustedStore.StoreType;
- m_trustedCertificateStore.StorePath = trustedStore.StorePath;
- m_trustedCertificateStore.ValidationOptions = trustedStore.ValidationOptions;
+ m_trustedCertificateStore = null;
+ m_trustedCertificateList = null;
- if (trustedStore.TrustedCertificates != null)
- {
- m_trustedCertificateList = new CertificateIdentifierCollection();
- m_trustedCertificateList.AddRange(trustedStore.TrustedCertificates);
- }
- }
+ if (trustedStore != null)
+ {
+ m_trustedCertificateStore = new CertificateStoreIdentifier();
- m_issuerCertificateStore = null;
- m_issuerCertificateList = null;
+ m_trustedCertificateStore.StoreType = trustedStore.StoreType;
+ m_trustedCertificateStore.StorePath = trustedStore.StorePath;
+ m_trustedCertificateStore.ValidationOptions = trustedStore.ValidationOptions;
- if (issuerStore != null)
+ if (trustedStore.TrustedCertificates != null)
{
- m_issuerCertificateStore = new CertificateStoreIdentifier();
+ m_trustedCertificateList = new CertificateIdentifierCollection();
+ m_trustedCertificateList.AddRange(trustedStore.TrustedCertificates);
+ }
+ }
- m_issuerCertificateStore.StoreType = issuerStore.StoreType;
- m_issuerCertificateStore.StorePath = issuerStore.StorePath;
- m_issuerCertificateStore.ValidationOptions = issuerStore.ValidationOptions;
+ m_issuerCertificateStore = null;
+ m_issuerCertificateList = null;
- if (issuerStore.TrustedCertificates != null)
- {
- m_issuerCertificateList = new CertificateIdentifierCollection();
- m_issuerCertificateList.AddRange(issuerStore.TrustedCertificates);
- }
- }
+ if (issuerStore != null)
+ {
+ m_issuerCertificateStore = new CertificateStoreIdentifier();
- m_rejectedCertificateStore = null;
+ m_issuerCertificateStore.StoreType = issuerStore.StoreType;
+ m_issuerCertificateStore.StorePath = issuerStore.StorePath;
+ m_issuerCertificateStore.ValidationOptions = issuerStore.ValidationOptions;
- if (rejectedCertificateStore != null)
+ if (issuerStore.TrustedCertificates != null)
{
- m_rejectedCertificateStore = (CertificateStoreIdentifier)rejectedCertificateStore.MemberwiseClone();
+ m_issuerCertificateList = new CertificateIdentifierCollection();
+ m_issuerCertificateList.AddRange(issuerStore.TrustedCertificates);
}
}
+
+ m_rejectedCertificateStore = null;
+
+ if (rejectedCertificateStore != null)
+ {
+ m_rejectedCertificateStore = (CertificateStoreIdentifier)rejectedCertificateStore.MemberwiseClone();
+ }
}
///
@@ -169,12 +187,15 @@ public virtual async Task Update(SecurityConfiguration configuration)
throw new ArgumentNullException(nameof(configuration));
}
- lock (m_lock)
+ try
{
- Update(
+ await m_semaphore.WaitAsync().ConfigureAwait(false);
+
+ InternalUpdate(
configuration.TrustedIssuerCertificates,
configuration.TrustedPeerCertificates,
configuration.RejectedCertificateStore);
+
// protect the flags if application called to set property
if ((m_protectFlags & ProtectFlags.AutoAcceptUntrustedCertificates) == 0)
{
@@ -197,6 +218,10 @@ public virtual async Task Update(SecurityConfiguration configuration)
m_useValidatedCertificates = configuration.UseValidatedCertificates;
}
}
+ finally
+ {
+ m_semaphore.Release();
+ }
if (configuration.ApplicationCertificate != null)
{
@@ -209,13 +234,20 @@ public virtual async Task Update(SecurityConfiguration configuration)
///
public virtual async Task UpdateCertificate(SecurityConfiguration securityConfiguration)
{
- lock (m_lock)
+ try
{
+ await m_semaphore.WaitAsync().ConfigureAwait(false);
+
securityConfiguration.ApplicationCertificate.Certificate = null;
+
+ await securityConfiguration.ApplicationCertificate.LoadPrivateKeyEx(
+ securityConfiguration.CertificatePasswordProvider).ConfigureAwait(false);
+ }
+ finally
+ {
+ m_semaphore.Release();
}
- await securityConfiguration.ApplicationCertificate.LoadPrivateKeyEx(
- securityConfiguration.CertificatePasswordProvider).ConfigureAwait(false);
await Update(securityConfiguration).ConfigureAwait(false);
lock (m_callbackLock)
@@ -233,15 +265,29 @@ await securityConfiguration.ApplicationCertificate.LoadPrivateKeyEx(
///
public void ResetValidatedCertificates()
{
- lock (m_lock)
+ try
{
- // dispose outdated list
- foreach (var cert in m_validatedCertificates.Values)
- {
- Utils.SilentDispose(cert);
- }
- m_validatedCertificates.Clear();
+ m_semaphore.Wait();
+
+ InternalResetValidatedCertificates();
+ }
+ finally
+ {
+ m_semaphore.Release();
+ }
+ }
+
+ ///
+ /// Reset the list of validated certificates.
+ ///
+ private void InternalResetValidatedCertificates()
+ {
+ // dispose outdated list
+ foreach (var cert in m_validatedCertificates.Values)
+ {
+ Utils.SilentDispose(cert);
}
+ m_validatedCertificates.Clear();
}
///
@@ -252,15 +298,21 @@ public bool AutoAcceptUntrustedCertificates
get => m_autoAcceptUntrustedCertificates;
set
{
- lock (m_lock)
+ try
{
+ m_semaphore.Wait();
+
m_protectFlags |= ProtectFlags.AutoAcceptUntrustedCertificates;
if (m_autoAcceptUntrustedCertificates != value)
{
m_autoAcceptUntrustedCertificates = value;
- ResetValidatedCertificates();
+ InternalResetValidatedCertificates();
}
}
+ finally
+ {
+ m_semaphore.Release();
+ }
}
}
@@ -272,15 +324,21 @@ public bool RejectSHA1SignedCertificates
get => m_rejectSHA1SignedCertificates;
set
{
- lock (m_lock)
+ try
{
+ m_semaphore.Wait();
+
m_protectFlags |= ProtectFlags.RejectSHA1SignedCertificates;
if (m_rejectSHA1SignedCertificates != value)
{
m_rejectSHA1SignedCertificates = value;
- ResetValidatedCertificates();
+ InternalResetValidatedCertificates();
}
}
+ finally
+ {
+ m_semaphore.Release();
+ }
}
}
@@ -292,15 +350,21 @@ public bool RejectUnknownRevocationStatus
get => m_rejectUnknownRevocationStatus;
set
{
- lock (m_lock)
+ try
{
+ m_semaphore.Wait();
+
m_protectFlags |= ProtectFlags.RejectUnknownRevocationStatus;
if (m_rejectUnknownRevocationStatus != value)
{
m_rejectUnknownRevocationStatus = value;
- ResetValidatedCertificates();
+ InternalResetValidatedCertificates();
}
}
+ finally
+ {
+ m_semaphore.Release();
+ }
}
}
@@ -312,8 +376,10 @@ public ushort MinimumCertificateKeySize
get => m_minimumCertificateKeySize;
set
{
- lock (m_lock)
+ try
{
+ m_semaphore.Wait();
+
m_protectFlags |= ProtectFlags.MinimumCertificateKeySize;
if (m_minimumCertificateKeySize != value)
{
@@ -321,6 +387,10 @@ public ushort MinimumCertificateKeySize
ResetValidatedCertificates();
}
}
+ finally
+ {
+ m_semaphore.Release();
+ }
}
}
@@ -332,8 +402,10 @@ public bool UseValidatedCertificates
get => m_useValidatedCertificates;
set
{
- lock (m_lock)
+ try
{
+ m_semaphore.Wait();
+
m_protectFlags |= ProtectFlags.UseValidatedCertificates;
if (m_useValidatedCertificates != value)
{
@@ -341,6 +413,11 @@ public bool UseValidatedCertificates
ResetValidatedCertificates();
}
}
+ finally
+ {
+ m_semaphore.Release();
+ }
+
}
}
@@ -357,7 +434,7 @@ public void Validate(X509Certificate2 certificate)
/// Validates a certificate.
///
///
- /// Each UA application may have a list of trusted certificates that is different from
+ /// Each UA application may have a list of trusted certificates that is different from
/// all other UA applications that may be running on the same machine. As a result, the
/// certificate validator cannot rely completely on the Windows certificate store and
/// user or machine specific CTLs (certificate trust lists).
@@ -367,113 +444,196 @@ public virtual void Validate(X509Certificate2Collection chain)
Validate(chain, null);
}
+ ///
+ public Task ValidateAsync(X509Certificate2 certificate, CancellationToken ct)
+ {
+ return ValidateAsync(new X509Certificate2Collection() { certificate }, ct);
+ }
+
+ ///
+ public virtual Task ValidateAsync(X509Certificate2Collection chain, CancellationToken ct)
+ {
+ return ValidateAsync(chain, null, ct);
+ }
+
///
/// Validates a certificate with domain validation check.
- ///
+ ///
///
- public virtual void Validate(X509Certificate2Collection chain, ConfiguredEndpoint endpoint)
+ public virtual async Task ValidateAsync(X509Certificate2Collection chain, ConfiguredEndpoint endpoint, CancellationToken ct)
{
X509Certificate2 certificate = chain[0];
try
{
- lock (m_lock)
+ try
{
- InternalValidate(chain, endpoint).GetAwaiter().GetResult();
+ await m_semaphore.WaitAsync(ct).ConfigureAwait(false);
+
+ await InternalValidate(chain, endpoint, ct).ConfigureAwait(false);
// add to list of validated certificates.
m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData);
+
+ return;
+ }
+ finally
+ {
+ m_semaphore.Release();
}
}
catch (ServiceResultException se)
{
- // check for errors that may be suppressed.
- if (ContainsUnsuppressibleSC(se.Result))
+ HandleCertificateValidationException(se, certificate, chain);
+ }
+
+ // add to list of peers.
+ try
+ {
+ await m_semaphore.WaitAsync(ct).ConfigureAwait(false);
+
+ Utils.LogCertificate(LogLevel.Warning, "Validation errors suppressed: ", certificate);
+ m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData);
+ }
+ finally
+ {
+ m_semaphore.Release();
+ }
+ }
+
+ ///
+ /// Validates a certificate with domain validation check.
+ ///
+ ///
+ public virtual void Validate(X509Certificate2Collection chain, ConfiguredEndpoint endpoint)
+ {
+ X509Certificate2 certificate = chain[0];
+
+ try
+ {
+ try
{
- Utils.LogCertificate(LogLevel.Error, "Certificate rejected. Reason={0}.",
- certificate, se.Result.StatusCode);
+ m_semaphore.Wait();
+
+ InternalValidate(chain, endpoint).GetAwaiter().GetResult();
- // save the chain in rejected store to allow to add certs to a trusted or issuer store
- SaveCertificates(chain);
+ // add to list of validated certificates.
+ m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData);
- LogInnerServiceResults(LogLevel.Error, se.Result.InnerResult);
- throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid);
+ return;
}
- else
+ finally
{
- Utils.LogCertificate(LogLevel.Warning, "Certificate Validation failed. Reason={0}.",
- certificate, se.Result.StatusCode);
- LogInnerServiceResults(LogLevel.Warning, se.Result.InnerResult);
+ m_semaphore.Release();
}
+ }
+ catch (ServiceResultException se)
+ {
+ HandleCertificateValidationException(se, certificate, chain);
+ }
- // invoke callback.
- bool accept = false;
- string applicationErrorMsg = string.Empty;
+ // add to list of peers.
+ try
+ {
+ m_semaphore.Wait();
- ServiceResult serviceResult = se.Result;
- lock (m_callbackLock)
+ Utils.LogCertificate(LogLevel.Warning, "Validation errors suppressed: ", certificate);
+ m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData);
+ }
+ finally
+ {
+ m_semaphore.Release();
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ private void HandleCertificateValidationException(ServiceResultException se, X509Certificate2 certificate, X509Certificate2Collection chain)
+ {
+ // check for errors that may be suppressed.
+ if (ContainsUnsuppressibleSC(se.Result))
+ {
+ Utils.LogCertificate(LogLevel.Error, "Certificate rejected. Reason={0}.",
+ certificate, se.Result.StatusCode);
+
+ // save the chain in rejected store to allow to add certs to a trusted or issuer store
+ SaveCertificates(chain);
+
+ LogInnerServiceResults(LogLevel.Error, se.Result.InnerResult);
+ throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid);
+ }
+ else
+ {
+ Utils.LogCertificate(LogLevel.Warning, "Certificate Validation failed. Reason={0}.",
+ certificate, se.Result.StatusCode);
+ LogInnerServiceResults(LogLevel.Warning, se.Result.InnerResult);
+ }
+
+ // invoke callback.
+ bool accept = false;
+ string applicationErrorMsg = string.Empty;
+
+ ServiceResult serviceResult = se.Result;
+ lock (m_callbackLock)
+ {
+ do
{
- do
+ accept = false;
+ if (m_CertificateValidation != null)
{
- accept = false;
- if (m_CertificateValidation != null)
- {
- CertificateValidationEventArgs args = new CertificateValidationEventArgs(serviceResult, certificate);
- m_CertificateValidation(this, args);
- if (args.AcceptAll)
- {
- accept = true;
- serviceResult = null;
- break;
- }
- applicationErrorMsg = args.ApplicationErrorMsg;
- accept = args.Accept;
- }
- else if (m_autoAcceptUntrustedCertificates &&
- serviceResult.StatusCode == StatusCodes.BadCertificateUntrusted)
+ CertificateValidationEventArgs args = new CertificateValidationEventArgs(serviceResult, certificate);
+ m_CertificateValidation(this, args);
+ if (args.AcceptAll)
{
accept = true;
- Utils.LogCertificate("Auto accepted certificate: ", certificate);
+ serviceResult = null;
+ break;
}
+ applicationErrorMsg = args.ApplicationErrorMsg;
+ accept = args.Accept;
+ }
+ else if (m_autoAcceptUntrustedCertificates &&
+ serviceResult.StatusCode == StatusCodes.BadCertificateUntrusted)
+ {
+ accept = true;
+ Utils.LogCertificate("Auto accepted certificate: ", certificate);
+ }
- if (accept)
+ if (accept)
+ {
+ serviceResult = serviceResult.InnerResult;
+ }
+ else
+ {
+ // report the rejected service result
+ if (string.IsNullOrEmpty(applicationErrorMsg))
{
- serviceResult = serviceResult.InnerResult;
+ se = new ServiceResultException(serviceResult);
}
else
{
- // report the rejected service result
- if (string.IsNullOrEmpty(applicationErrorMsg))
- {
- se = new ServiceResultException(serviceResult);
- }
- else
- {
- se = new ServiceResultException(applicationErrorMsg);
- }
+ se = new ServiceResultException(applicationErrorMsg);
}
- } while (accept && serviceResult != null);
- }
-
- // throw if rejected.
- if (!accept)
- {
- // write the invalid certificate chain to rejected store if specified.
- Utils.LogCertificate(LogLevel.Error, "Certificate rejected. Reason={0}.",
- certificate, serviceResult != null ? serviceResult.StatusCode.ToString() : "Unknown Error");
+ }
+ } while (accept && serviceResult != null);
+ }
- // save the chain in rejected store to allow to add cert to a trusted or issuer store
- SaveCertificates(chain);
+ // throw if rejected.
+ if (!accept)
+ {
+ // write the invalid certificate chain to rejected store if specified.
+ Utils.LogCertificate(LogLevel.Error, "Certificate rejected. Reason={0}.",
+ certificate, serviceResult != null ? serviceResult.StatusCode.ToString() : "Unknown Error");
- throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid);
- }
+ // save the chain in rejected store to allow to add cert to a trusted or issuer store
+ SaveCertificates(chain);
- // add to list of peers.
- lock (m_lock)
- {
- Utils.LogCertificate(LogLevel.Warning, "Validation errors suppressed: ", certificate);
- m_validatedCertificates[certificate.Thumbprint] = new X509Certificate2(certificate.RawData);
- }
+ throw new ServiceResultException(se, StatusCodes.BadCertificateInvalid);
}
}
@@ -524,8 +684,10 @@ private void SaveCertificate(X509Certificate2 certificate)
///
private void SaveCertificates(X509Certificate2Collection certificateChain)
{
- lock (m_lock)
+ try
{
+ m_semaphore.Wait();
+
if (m_rejectedCertificateStore != null)
{
Utils.LogTrace("Writing rejected certificate chain to: {0}", m_rejectedCertificateStore);
@@ -564,6 +726,10 @@ private void SaveCertificates(X509Certificate2Collection certificateChain)
}
}
}
+ finally
+ {
+ m_semaphore.Release();
+ }
}
///
@@ -923,8 +1089,9 @@ await GetIssuerNoException(certificate, explicitList, certificateStore, checkRec
///
/// The certificates to be checked.
/// The endpoint for domain validation.
+ /// The cancellation token.
/// If certificate[0] cannot be accepted
- protected virtual async Task InternalValidate(X509Certificate2Collection certificates, ConfiguredEndpoint endpoint)
+ protected virtual async Task InternalValidate(X509Certificate2Collection certificates, ConfiguredEndpoint endpoint, CancellationToken ct = default)
{
X509Certificate2 certificate = certificates[0];
@@ -959,7 +1126,7 @@ protected virtual async Task InternalValidate(X509Certificate2Collection certifi
#if NET5_0_OR_GREATER
DisableCertificateDownloads = true,
#endif
- };
+ };
foreach (CertificateIdentifier issuer in issuers)
{
@@ -1347,8 +1514,8 @@ private static ServiceResult CheckChainStatus(X509ChainStatus status, Certificat
goto case X509ChainStatusFlags.UntrustedRoot;
case X509ChainStatusFlags.UntrustedRoot:
{
- // self signed cert signature validation
- // .NET Core ChainStatus returns NotSignatureValid only on Windows,
+ // self signed cert signature validation
+ // .NET Core ChainStatus returns NotSignatureValid only on Windows,
// so we have to do the extra cert signature check on all platforms
if (issuer == null && id.Certificate != null &&
X509Utils.IsSelfSigned(id.Certificate))
@@ -1583,7 +1750,7 @@ private enum ProtectFlags
#endregion
#region Private Fields
- private object m_lock = new object();
+ private SemaphoreSlim m_semaphore = new SemaphoreSlim(1, 1);
private object m_callbackLock = new object();
private Dictionary m_validatedCertificates;
private CertificateStoreIdentifier m_trustedCertificateStore;
@@ -1613,7 +1780,7 @@ public class CertificateValidationEventArgs : EventArgs
///
/// Creates a new instance.
///
- internal CertificateValidationEventArgs(ServiceResult error, X509Certificate2 certificate)
+ public CertificateValidationEventArgs(ServiceResult error, X509Certificate2 certificate)
{
m_error = error;
m_certificate = certificate;
@@ -1686,7 +1853,7 @@ public class CertificateUpdateEventArgs : EventArgs
///
/// Creates a new instance.
///
- internal CertificateUpdateEventArgs(
+ public CertificateUpdateEventArgs(
SecurityConfiguration configuration,
ICertificateValidator validator)
{
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/ICertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/ICertificateValidator.cs
index 3219da61c..256350525 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/ICertificateValidator.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/ICertificateValidator.cs
@@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
namespace Opc.Ua
{
@@ -29,5 +31,14 @@ public interface ICertificateValidator
///
void Validate(X509Certificate2Collection certificateChain);
+ ///
+ /// Validates a certificate.
+ ///
+ Task ValidateAsync(X509Certificate2 certificate, CancellationToken ct);
+
+ ///
+ /// Validates a certificate chain.
+ ///
+ Task ValidateAsync(X509Certificate2Collection certificateChain, CancellationToken ct);
}
}
diff --git a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
index 7feb23fcf..13121065f 100644
--- a/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
+++ b/Stack/Opc.Ua.Core/Security/Certificates/X509Utils.cs
@@ -17,6 +17,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using Opc.Ua.Security.Certificates;
@@ -561,6 +562,43 @@ public static X509Certificate2 AddToStore(
return certificate;
}
+ /// e
+ /// Extension to add a certificate to a .
+ ///
+ ///
+ /// Saves also the private key, if available.
+ /// If written to a Pfx file, the password is used for protection.
+ ///
+ /// The certificate to store.
+ /// Type of certificate store (Directory) .
+ /// The store path (syntax depends on storeType).
+ /// The password to use to protect the certificate.
+ /// The cancellation token.
+ public static async Task AddToStoreAsync(
+ this X509Certificate2 certificate,
+ string storeType,
+ string storePath,
+ string password = null,
+ CancellationToken ct = default)
+ {
+ // add cert to the store.
+ if (!String.IsNullOrEmpty(storePath) && !String.IsNullOrEmpty(storeType))
+ {
+ using (ICertificateStore store = Opc.Ua.CertificateStoreIdentifier.CreateStore(storeType))
+ {
+ if (store == null)
+ {
+ throw new ArgumentException("Invalid store type");
+ }
+
+ store.Open(storePath, false);
+ await store.Add(certificate, password).ConfigureAwait(false);
+ store.Close();
+ }
+ }
+ return certificate;
+ }
+
///
/// Get the hash algorithm from the hash size in bits.
///
diff --git a/Stack/Opc.Ua.Core/Stack/Bindings/ArraySegmentStream.cs b/Stack/Opc.Ua.Core/Stack/Bindings/ArraySegmentStream.cs
index 86b39a118..93d61973e 100644
--- a/Stack/Opc.Ua.Core/Stack/Bindings/ArraySegmentStream.cs
+++ b/Stack/Opc.Ua.Core/Stack/Bindings/ArraySegmentStream.cs
@@ -225,7 +225,10 @@ public override long Seek(long offset, SeekOrigin origin)
int position = (int)offset;
- CheckEndOfStream();
+ if (position >= GetAbsolutePosition())
+ {
+ CheckEndOfStream();
+ }
for (int ii = 0; ii < m_buffers.Count; ii++)
{
diff --git a/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs b/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs
index 48c349e85..7d6a00faf 100644
--- a/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs
+++ b/Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs
@@ -13,6 +13,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
using System;
using System.Collections;
using System.Threading;
+using System.Threading.Tasks;
namespace Opc.Ua
{
@@ -274,6 +275,21 @@ public virtual StatusCode Close()
return StatusCodes.Good;
}
+ ///
+ /// Closes the channel using async call.
+ ///
+ public virtual Task CloseAsync(CancellationToken ct = default)
+ {
+ if (m_channel != null)
+ {
+ m_channel.Close();
+ m_channel = null;
+ }
+
+ m_authenticationToken = null;
+ return Task.FromResult(StatusCodes.Good);
+ }
+
///
/// Whether the object has been disposed.
///
diff --git a/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs b/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs
index d81e89ca2..1cc658329 100644
--- a/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs
+++ b/Stack/Opc.Ua.Core/Stack/Client/DiscoveryClient.cs
@@ -162,6 +162,7 @@ public virtual EndpointDescriptionCollection GetEndpoints(StringCollection profi
return PatchEndpointUrls(endpoints);
}
+#if NET_STANDARD_ASYNC
///
/// Invokes the GetEndpoints service async.
///
@@ -172,6 +173,7 @@ public async virtual Task GetEndpointsAsync(Strin
var response = await GetEndpointsAsync(null, this.Endpoint.EndpointUrl, null, profileUris, ct).ConfigureAwait(false);
return PatchEndpointUrls(response.Endpoints);
}
+#endif
///
/// Invokes the FindServers service.
@@ -192,6 +194,7 @@ public virtual ApplicationDescriptionCollection FindServers(StringCollection ser
return servers;
}
+#if NET_STANDARD_ASYNC
///
/// Invokes the FindServers service async.
///
@@ -208,6 +211,7 @@ public virtual async Task FindServersAsync(Str
ct).ConfigureAwait(false);
return response.Servers;
}
+#endif
///
/// Invokes the FindServersOnNetwork service.
diff --git a/Stack/Opc.Ua.Core/Stack/Client/UaChannelBase.cs b/Stack/Opc.Ua.Core/Stack/Client/UaChannelBase.cs
index bb7ba59ee..e58105a9e 100644
--- a/Stack/Opc.Ua.Core/Stack/Client/UaChannelBase.cs
+++ b/Stack/Opc.Ua.Core/Stack/Client/UaChannelBase.cs
@@ -439,6 +439,19 @@ public IServiceResponse EndSendRequest(IAsyncResult result)
#endif
}
+ ///
+ /// Completes an asynchronous operation to send a request over the secure channel.
+ ///
+ public Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct)
+ {
+ if (m_uaBypassChannel != null)
+ {
+ return m_uaBypassChannel.EndSendRequestAsync(result, ct);
+ }
+
+ throw new NotImplementedException();
+ }
+
///
/// Sends a request over the secure channel.
///
diff --git a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs
index 654ef40f2..32eefd215 100644
--- a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs
+++ b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs
@@ -1110,6 +1110,7 @@ public void UpdateFromServer(
}
}
+#if NET_STANDARD_ASYNC
///
/// Updates an endpoint with information from the server's discovery endpoint.
///
@@ -1175,9 +1176,10 @@ public async Task UpdateFromServerAsync(
}
finally
{
- client.Close();
+ await client.CloseAsync(ct).ConfigureAwait(false);
}
}
+#endif
///
/// Returns a discovery url that can be used to update the endpoint description.
@@ -1207,7 +1209,7 @@ public Uri GetDiscoveryUrl(Uri endpointUrl)
{
if (endpointUrl.Scheme.StartsWith(Utils.UriSchemeHttp, StringComparison.Ordinal))
{
- return new Uri(String.Format(CultureInfo.InvariantCulture, "{0}"+ kDiscoverySuffix, endpointUrl));
+ return new Uri(String.Format(CultureInfo.InvariantCulture, "{0}" + kDiscoverySuffix, endpointUrl));
}
else
{
diff --git a/Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs b/Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs
index e5aa6b4ed..fc2294a31 100644
--- a/Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs
+++ b/Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs
@@ -11,6 +11,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
namespace Opc.Ua
{
@@ -128,6 +130,18 @@ public NodeId FindSuperType(NodeId typeId)
}
}
+ ///
+ public Task FindSuperTypeAsync(ExpandedNodeId typeId, CancellationToken ct)
+ {
+ return Task.FromResult(FindSuperType(typeId));
+ }
+
+ ///
+ public Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct)
+ {
+ return Task.FromResult(FindSuperType(typeId));
+ }
+
///
public IList FindSubTypes(ExpandedNodeId typeId)
{
@@ -535,7 +549,7 @@ public void Add(ILocalNode node)
}
}
- // any new encodings.
+ // any new encodings.
IList encodings = node.References.Find(ReferenceTypeIds.HasEncoding, false, false, null);
if (encodings.Count > 0)
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs
index 5ddb1a1a5..efc962edf 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/ChannelAsyncOperation.cs
@@ -68,6 +68,15 @@ protected virtual void Dispose(bool disposing)
m_event.Dispose();
m_event = null;
}
+
+ if (m_tcs != null)
+ {
+ if (!m_tcs.Task.IsCompleted)
+ {
+ m_tcs.TrySetCanceled();
+ }
+ m_tcs = null;
+ }
}
}
}
@@ -192,6 +201,98 @@ public T End(int timeout, bool throwOnError = true)
}
}
+ ///
+ /// The awaitable response returned from the server.
+ ///
+ public async Task EndAsync(int timeout, bool throwOnError = true, CancellationToken ct = default)
+ {
+ // check if the request has already completed.
+ bool mustWait = false;
+
+ lock (m_lock)
+ {
+ mustWait = !m_completed;
+
+ if (mustWait)
+ {
+ m_tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ }
+ }
+
+ // wait for completion.
+ if (mustWait)
+ {
+ bool badRequestInterrupted = false;
+ try
+ {
+ Task awaitableTask = m_tcs.Task;
+#if NET6_0_OR_GREATER
+ if (timeout != Int32.MaxValue)
+ {
+ awaitableTask = m_tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(timeout), ct);
+ }
+ else if (ct != default)
+ {
+ awaitableTask = m_tcs.Task.WaitAsync(ct);
+ }
+#else
+ if (timeout != Int32.MaxValue || ct != default)
+ {
+ Task completedTask = await Task.WhenAny(m_tcs.Task, Task.Delay(timeout, ct)).ConfigureAwait(false);
+ if (m_tcs.Task == completedTask)
+ {
+ if (!m_tcs.Task.Result)
+ {
+ badRequestInterrupted = true;
+ }
+ }
+ else
+ {
+ m_tcs.TrySetCanceled();
+ badRequestInterrupted = true;
+ }
+ }
+ else
+#endif
+ if (!await awaitableTask.ConfigureAwait(false))
+ {
+ badRequestInterrupted = true;
+ }
+ }
+ catch (TimeoutException)
+ {
+ badRequestInterrupted = true;
+ }
+ catch (TaskCanceledException)
+ {
+ badRequestInterrupted = true;
+ }
+ finally
+ {
+ lock (m_lock)
+ {
+ m_tcs = null;
+ }
+ }
+
+ if (badRequestInterrupted && throwOnError)
+ {
+ throw new ServiceResultException(StatusCodes.BadRequestInterrupted);
+ }
+ }
+
+ // return the response.
+ lock (m_lock)
+ {
+ if (m_error != null && throwOnError)
+ {
+ throw new ServiceResultException(m_error);
+ }
+
+ return m_response;
+ }
+ }
+
///
/// Stores additional state information associated with the operation.
///
@@ -210,7 +311,7 @@ public IDictionary Properties
}
}
}
- #endregion
+#endregion
#region IAsyncResult Members
///
@@ -313,14 +414,18 @@ protected virtual bool InternalComplete(bool doNotBlock, object result)
{
m_event.Set();
}
+
+ if (m_tcs != null)
+ {
+ m_tcs.TrySetResult(true);
+ }
}
if (m_callback != null)
{
if (doNotBlock)
{
- Task.Run(() =>
- {
+ Task.Run(() => {
m_callback(this);
});
}
@@ -348,6 +453,7 @@ protected virtual bool InternalComplete(bool doNotBlock, object result)
private bool m_synchronous;
private bool m_completed;
private ManualResetEvent m_event;
+ private TaskCompletionSource m_tcs;
private T m_response;
private ServiceResult m_error;
private Timer m_timer;
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs
index 273dc0196..becf10e83 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Asymmetric.cs
@@ -284,7 +284,7 @@ protected int GetAsymmetricHeaderSize(
if (securityPolicyUri != null)
{
- headerSize += new UTF8Encoding().GetByteCount(securityPolicyUri);
+ headerSize += Encoding.UTF8.GetByteCount(securityPolicyUri);
}
headerSize += TcpMessageLimits.StringLengthSize;
@@ -323,7 +323,7 @@ protected int GetAsymmetricHeaderSize(
if (securityPolicyUri != null)
{
- headerSize += new UTF8Encoding().GetByteCount(securityPolicyUri);
+ headerSize += Encoding.UTF8.GetByteCount(securityPolicyUri);
}
headerSize += TcpMessageLimits.StringLengthSize;
@@ -473,7 +473,7 @@ private int GetMaxSenderCertificateSize(X509Certificate2 senderCertificate, stri
if (securityPolicyUri != null)
{
- occupiedSize += new UTF8Encoding().GetByteCount(securityPolicyUri); //security policy uri size
+ occupiedSize += Encoding.UTF8.GetByteCount(securityPolicyUri); //security policy uri size
}
occupiedSize += TcpMessageLimits.StringLengthSize; //SenderCertificateLength
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs
index f609a5231..527a270d6 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Rsa.cs
@@ -67,7 +67,7 @@ private static bool Rsa_Verify(
// verify signature.
if (!rsa.VerifyData(dataToVerify.Array, dataToVerify.Offset, dataToVerify.Count, signature, algorithm, padding))
{
- string messageType = new UTF8Encoding().GetString(dataToVerify.Array, dataToVerify.Offset, 4);
+ string messageType = Encoding.UTF8.GetString(dataToVerify.Array, dataToVerify.Offset, 4);
int messageLength = BitConverter.ToInt32(dataToVerify.Array, dataToVerify.Offset + 4);
string actualSignature = Utils.ToHexString(signature);
Utils.LogError("Could not validate signature.");
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs
index 5335ab0e6..b0acce037 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.Symmetric.cs
@@ -736,7 +736,7 @@ private bool SymmetricVerify(
{
if (computedSignature[ii] != signature[ii])
{
- string messageType = new UTF8Encoding().GetString(dataToVerify.Array, dataToVerify.Offset, 4);
+ string messageType = Encoding.UTF8.GetString(dataToVerify.Array, dataToVerify.Offset, 4);
int messageLength = BitConverter.ToInt32(dataToVerify.Array, dataToVerify.Offset + 4);
string expectedSignature = Utils.ToHexString(computedSignature);
string actualSignature = Utils.ToHexString(signature);
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs
index f5c1c0ae5..8c17419ca 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryChannel.cs
@@ -82,7 +82,7 @@ public UaSCUaBinaryChannel(
}
}
- if (new UTF8Encoding().GetByteCount(securityPolicyUri) > TcpMessageLimits.MaxSecurityPolicyUriSize)
+ if (Encoding.UTF8.GetByteCount(securityPolicyUri) > TcpMessageLimits.MaxSecurityPolicyUriSize)
{
throw new ArgumentException(
Utils.Format("UTF-8 form of the security policy URI may not be more than {0} bytes.", TcpMessageLimits.MaxSecurityPolicyUriSize),
@@ -540,11 +540,9 @@ protected static void WriteErrorMessageBody(BinaryEncoder encoder, ServiceResult
// check that length is not exceeded.
if (reason != null)
{
- UTF8Encoding encoding = new UTF8Encoding();
-
- if (encoding.GetByteCount(reason) > TcpMessageLimits.MaxErrorReasonLength)
+ if (Encoding.UTF8.GetByteCount(reason) > TcpMessageLimits.MaxErrorReasonLength)
{
- reason = reason.Substring(0, TcpMessageLimits.MaxErrorReasonLength / encoding.GetMaxByteCount(1));
+ reason = reason.Substring(0, TcpMessageLimits.MaxErrorReasonLength / Encoding.UTF8.GetMaxByteCount(1));
}
}
@@ -574,7 +572,12 @@ protected static ServiceResult ReadErrorMessageBody(BinaryDecoder decoder)
reasonBytes[ii] = decoder.ReadByte(null);
}
- reason = new UTF8Encoding().GetString(reasonBytes, 0, reasonLength);
+ reason = Encoding.UTF8.GetString(reasonBytes, 0, reasonLength);
+ }
+
+ if (reason == null)
+ {
+ reason = new ServiceResult(statusCode).ToString();
}
return ServiceResult.Create(statusCode, "Error received from remote host: {0}", reason);
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs
index a3e1bd860..b9a6a441a 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs
@@ -159,7 +159,6 @@ public IAsyncResult BeginConnect(Uri url, int timeout, AsyncCallback callback, o
using (var cts = new CancellationTokenSource(timeout))
{
await (Socket?.BeginConnect(m_via, m_ConnectCallback, operation, cts.Token) ?? Task.FromResult(false)).ConfigureAwait(false);
-
}
});
}
@@ -191,38 +190,78 @@ public void EndConnect(IAsyncResult result)
}
}
+ ///
+ /// Finishes a connect operation.
+ ///
+ public async Task EndConnectAsync(IAsyncResult result, CancellationToken ct = default)
+ {
+ var operation = result as WriteOperation;
+ if (operation == null) throw new ArgumentNullException(nameof(result));
+
+ try
+ {
+ await operation.EndAsync(Int32.MaxValue, true, ct).ConfigureAwait(false);
+ Utils.LogInfo("CLIENTCHANNEL SOCKET CONNECTED: {0:X8}, ChannelId={1}", Socket.Handle, ChannelId);
+ }
+ catch (Exception e)
+ {
+ Shutdown(ServiceResult.Create(e, StatusCodes.BadTcpInternalError, "Fatal error during connect."));
+ throw;
+ }
+ finally
+ {
+ OperationCompleted(operation);
+ }
+ }
+
///
/// Closes a connection with the server.
///
- public void Close(int timeout)
+ public async Task CloseAsync(int timeout, CancellationToken ct = default)
{
- WriteOperation operation = null;
+ WriteOperation operation = InternalClose(timeout);
- lock (DataLock)
+ // wait for the close to succeed.
+ if (operation != null)
{
- // nothing to do if the connection is already closed.
- if (State == TcpChannelState.Closed)
+ try
{
- return;
+ await operation.EndAsync(timeout, false, ct).ConfigureAwait(false);
}
-
- // check if a handshake is in progress.
- if (m_handshakeOperation != null && !m_handshakeOperation.IsCompleted)
+ catch (ServiceResultException e)
{
- m_handshakeOperation.Fault(ServiceResult.Create(StatusCodes.BadConnectionClosed, "Channel was closed by the user."));
- }
-
- Utils.LogTrace("ChannelId {0}: Close", ChannelId);
+ switch (e.StatusCode)
+ {
+ case StatusCodes.BadRequestInterrupted:
+ case StatusCodes.BadSecureChannelClosed:
+ {
+ break;
+ }
- // attempt a graceful shutdown.
- if (State == TcpChannelState.Open)
+ default:
+ {
+ Utils.LogWarning(e, "ChannelId {0}: Could not gracefully close the channel. Reason={1}", ChannelId, e.Result.StatusCode);
+ break;
+ }
+ }
+ }
+ catch (Exception e)
{
- State = TcpChannelState.Closing;
- operation = BeginOperation(timeout, null, null);
- SendCloseSecureChannelRequest(operation);
+ Utils.LogError(e, "ChannelId {0}: Could not gracefully close the channel.", ChannelId);
}
}
+ // shutdown.
+ Shutdown(StatusCodes.BadConnectionClosed);
+ }
+
+ ///
+ /// Closes a connection with the server.
+ ///
+ public void Close(int timeout)
+ {
+ WriteOperation operation = InternalClose(timeout);
+
// wait for the close to succeed.
if (operation != null)
{
@@ -343,6 +382,30 @@ public IServiceResponse EndSendRequest(IAsyncResult result)
return operation.MessageBody as IServiceResponse;
}
+
+ ///
+ /// Returns the response to a previously sent request.
+ ///
+ public async Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct)
+ {
+ WriteOperation operation = result as WriteOperation;
+
+ if (operation == null)
+ {
+ throw new ArgumentNullException(nameof(result));
+ }
+
+ try
+ {
+ await operation.EndAsync(Int32.MaxValue, true, ct).ConfigureAwait(false);
+ }
+ finally
+ {
+ OperationCompleted(operation);
+ }
+
+ return operation.MessageBody as IServiceResponse;
+ }
#endregion
#region Connect/Reconnect Sequence
@@ -1081,8 +1144,6 @@ private void ForceReconnect(ServiceResult reason)
return;
}
- Utils.LogWarning("ChannelId {0}: Force reconnect reason={1}", Id, reason);
-
// check if reconnects are disabled.
if (State == TcpChannelState.Closing || m_waitBetweenReconnects == Timeout.Infinite)
{
@@ -1090,6 +1151,8 @@ private void ForceReconnect(ServiceResult reason)
return;
}
+ Utils.LogWarning("ChannelId {0}: Force reconnect reason={1}", Id, reason);
+
// cancel all requests.
List operations = new List(m_requests.Values);
@@ -1275,6 +1338,37 @@ private void OnConnectOnDemandComplete(object state)
m_queuedOperations = null;
}
}
+
+ private WriteOperation InternalClose(int timeout)
+ {
+ WriteOperation operation = null;
+ lock (DataLock)
+ {
+ // nothing to do if the connection is already closed.
+ if (State == TcpChannelState.Closed)
+ {
+ return null;
+ }
+
+ // check if a handshake is in progress.
+ if (m_handshakeOperation != null && !m_handshakeOperation.IsCompleted)
+ {
+ m_handshakeOperation.Fault(ServiceResult.Create(StatusCodes.BadConnectionClosed, "Channel was closed by the user."));
+ }
+
+ Utils.LogTrace("ChannelId {0}: Close", ChannelId);
+
+ // attempt a graceful shutdown.
+ if (State == TcpChannelState.Open)
+ {
+ State = TcpChannelState.Closing;
+ operation = BeginOperation(timeout, null, null);
+ SendCloseSecureChannelRequest(operation);
+ }
+ }
+
+ return operation;
+ }
#endregion
#region Message Processing
diff --git a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs
index 583633045..fdbd08f97 100644
--- a/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryTransportChannel.cs
@@ -331,7 +331,8 @@ public IServiceResponse SendRequest(IServiceRequest request)
/// Thrown if any communication error occurs.
public Task SendRequestAsync(IServiceRequest request, CancellationToken ct)
{
- return Task.Factory.FromAsync(BeginSendRequest(request, null, null), EndSendRequest);
+ var operation = BeginSendRequest(request, null, null);
+ return EndSendRequestAsync(operation, ct);
}
///
@@ -384,6 +385,26 @@ public IServiceResponse EndSendRequest(IAsyncResult result)
return channel.EndSendRequest(result);
}
+ ///
+ /// Completes an asynchronous operation to send a request over the secure channel.
+ ///
+ /// The result returned from the BeginSendRequest call.
+ ///
+ ///
+ /// Thrown if any communication error occurs.
+ ///
+ public Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct)
+ {
+ UaSCUaBinaryClientChannel channel = m_channel;
+
+ if (channel == null)
+ {
+ throw ServiceResultException.Create(StatusCodes.BadSecureChannelClosed, "Channel has been closed.");
+ }
+
+ return channel.EndSendRequestAsync(result, ct);
+ }
+
///
/// Saves the settings so the channel can be opened later.
///
diff --git a/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs b/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs
index 9b2155028..b23893d0a 100644
--- a/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs
+++ b/Stack/Opc.Ua.Core/Stack/Transport/ITransportChannel.cs
@@ -191,6 +191,16 @@ IAsyncResult BeginOpen(
/// Thrown if any communication error occurs.
///
IServiceResponse EndSendRequest(IAsyncResult result);
+
+ ///
+ /// Completes an asynchronous operation to send a request over the secure channel.
+ /// Awaitable version
+ ///
+ /// The result returned from the BeginSendRequest call.
+ /// The cancellation token.
+ /// Thrown if any communication error occurs.
+ ///
+ Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct);
}
///
diff --git a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs
index cb2409e30..0de5feeaa 100644
--- a/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs
+++ b/Stack/Opc.Ua.Core/Stack/Types/UserIdentityToken.cs
@@ -85,13 +85,13 @@ public override void Encrypt(X509Certificate2 certificate, byte[] senderNonce, s
// handle no encryption.
if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None)
{
- m_password = new UTF8Encoding().GetBytes(m_decryptedPassword);
+ m_password = Encoding.UTF8.GetBytes(m_decryptedPassword);
m_encryptionAlgorithm = null;
return;
}
// encrypt the password.
- byte[] dataToEncrypt = Utils.Append(new UTF8Encoding().GetBytes(m_decryptedPassword), senderNonce);
+ byte[] dataToEncrypt = Utils.Append(Encoding.UTF8.GetBytes(m_decryptedPassword), senderNonce);
EncryptedData encryptedData = SecurityPolicies.Encrypt(
certificate,
@@ -110,7 +110,7 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s
// handle no encryption.
if (String.IsNullOrEmpty(securityPolicyUri) || securityPolicyUri == SecurityPolicies.None)
{
- m_decryptedPassword = new UTF8Encoding().GetString(m_password, 0, m_password.Length);
+ m_decryptedPassword = Encoding.UTF8.GetString(m_password, 0, m_password.Length);
return;
}
@@ -150,7 +150,7 @@ public override void Decrypt(X509Certificate2 certificate, byte[] senderNonce, s
}
// convert to UTF-8.
- m_decryptedPassword = new UTF8Encoding().GetString(decryptedPassword, 0, startOfNonce);
+ m_decryptedPassword = Encoding.UTF8.GetString(decryptedPassword, 0, startOfNonce);
}
#endregion
diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/ITypeTable.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/ITypeTable.cs
index a6b2a6318..4a00771bc 100644
--- a/Stack/Opc.Ua.Core/Types/BuiltIn/ITypeTable.cs
+++ b/Stack/Opc.Ua.Core/Types/BuiltIn/ITypeTable.cs
@@ -11,11 +11,13 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
namespace Opc.Ua
{
///
- /// Stores the type tree for a server.
+ /// Stores the type tree for a server.
///
public interface ITypeTable
{
@@ -51,6 +53,24 @@ public interface ITypeTable
/// The immediate supertype idnetyfier for
NodeId FindSuperType(NodeId typeId);
+#if (NET_STANDARD_ASYNC)
+ ///
+ /// Returns the immediate supertype for the type.
+ ///
+ /// The extended type identifier.
+ ///
+ /// A type identifier of the
+ Task FindSuperTypeAsync(ExpandedNodeId typeId, CancellationToken ct = default);
+
+ ///
+ /// Returns the immediate supertype for the type.
+ ///
+ /// The type identifier.
+ ///
+ /// The immediate supertype idnetyfier for
+ Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default);
+#endif
+
///
/// Returns the immediate subtypes for the type.
///
@@ -93,7 +113,7 @@ public interface ITypeTable
NodeId FindReferenceType(QualifiedName browseName);
///
- /// Checks if the identifier represents a that provides encodings
+ /// Checks if the identifier represents a that provides encodings
/// for the .
///
/// The id the encoding node .
@@ -109,7 +129,7 @@ public interface ITypeTable
/// The identifier of the expected type .
/// The value.
///
- /// true if the value contained in an extension object matches the
+ /// true if the value contained in an extension object matches the
/// expected data type; otherwise, false.
///
bool IsEncodingFor(NodeId expectedTypeId, ExtensionObject value);
diff --git a/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs b/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs
index 8e8965074..5089824ca 100644
--- a/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs
+++ b/Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs
@@ -24,7 +24,7 @@ namespace Opc.Ua
/// and encoded/decoded to/from an underlying stream.
/// x
[DataContract(Name = "Guid", Namespace = Namespaces.OpcUaXsd)]
- public struct Uuid : IComparable, IFormattable
+ public struct Uuid : IComparable, IFormattable, IEquatable
{
#region Constructors
///
@@ -207,6 +207,18 @@ public override bool Equals(object obj)
return (CompareTo(obj) == 0);
}
+ ///
+ /// Returns true if the objects are equal.
+ ///
+ ///
+ /// Returns true if the objects are equal.
+ ///
+ /// The object being compared to *this* object
+ public bool Equals(Uuid other)
+ {
+ return (CompareTo(other) == 0);
+ }
+
///
/// Returns a hash code for the object.
///
@@ -247,7 +259,7 @@ public int CompareTo(object obj)
return ((Uuid)obj).m_guid.CompareTo(m_guid);
}
- // compare guids.
+ // compare guids.
if (obj is Guid)
{
return m_guid.CompareTo((Guid)obj);
@@ -274,7 +286,7 @@ public string ToString(string format, IFormatProvider formatProvider)
#region Private Fields
private Guid m_guid;
- #endregion
+ #endregion
}
#region UuidCollection Class
diff --git a/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs
index eb9e270e7..b925b8944 100644
--- a/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs
+++ b/Stack/Opc.Ua.Core/Types/Encoders/BinaryDecoder.cs
@@ -2126,22 +2126,31 @@ private ExtensionObject ReadExtensionObject()
// verify the decoder did not exceed the length of the encodeable object
int used = Position - start;
- if (length < used)
+ if (length != used)
{
throw ServiceResultException.Create(
- StatusCodes.BadEncodingLimitsExceeded,
- "The encodeable.Decoder operation exceeded the length of the extension object. {0} > {1}",
+ StatusCodes.BadDecodingError,
+ "The encodeable.Decoder operation did not match the length of the extension object. {0} != {1}",
used, length);
}
}
- catch (ServiceResultException sre) when (sre.StatusCode == StatusCodes.BadEncodingLimitsExceeded)
+ catch (EndOfStreamException eofStream)
{
// type was known but decoding failed, reset stream!
m_reader.BaseStream.Position = start;
encodeable = null;
- Utils.LogWarning(sre, "Failed to decode encodeable type '{0}', NodeId='{1}'. BinaryDecoder recovered.",
+ Utils.LogWarning(eofStream, "End of stream, failed to decode encodeable type '{0}', NodeId='{1}'. BinaryDecoder recovered.",
systemType.Name, extension.TypeId);
}
+ catch (ServiceResultException sre) when
+ ((sre.StatusCode == StatusCodes.BadEncodingLimitsExceeded) || (sre.StatusCode == StatusCodes.BadDecodingError))
+ {
+ // type was known but decoding failed, reset stream!
+ m_reader.BaseStream.Position = start;
+ encodeable = null;
+ Utils.LogWarning(sre, "{0}, failed to decode encodeable type '{1}', NodeId='{2}'. BinaryDecoder recovered.",
+ sre.Message, systemType.Name, extension.TypeId);
+ }
finally
{
m_nestingLevel = nestingLevel;
diff --git a/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs
index 58a72292c..95a886e60 100644
--- a/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs
+++ b/Stack/Opc.Ua.Core/Types/Encoders/BinaryEncoder.cs
@@ -90,11 +90,13 @@ protected virtual void Dispose(bool disposing)
{
m_writer.Flush();
m_writer.Dispose();
+ m_writer = null;
}
if (!m_leaveOpen)
{
m_ostrm?.Dispose();
+ m_ostrm = null;
}
}
}
@@ -440,7 +442,7 @@ public void WriteString(string fieldName, string value)
return;
}
- byte[] bytes = new UTF8Encoding().GetBytes(value);
+ byte[] bytes = Encoding.UTF8.GetBytes(value);
if (m_context.MaxStringLength > 0 && m_context.MaxStringLength < bytes.Length)
{
@@ -451,7 +453,7 @@ public void WriteString(string fieldName, string value)
bytes.Length);
}
- WriteByteString(null, new UTF8Encoding().GetBytes(value));
+ WriteByteString(null, Encoding.UTF8.GetBytes(value));
}
///
@@ -534,7 +536,7 @@ public void WriteXmlElement(string fieldName, XmlElement value)
return;
}
- WriteByteString(null, new UTF8Encoding().GetBytes(value.OuterXml));
+ WriteByteString(null, Encoding.UTF8.GetBytes(value.OuterXml));
}
///
diff --git a/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs b/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs
index bebe73356..e9ebdc65a 100644
--- a/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs
+++ b/Stack/Opc.Ua.Core/Types/Encoders/EncodeableFactory.cs
@@ -12,6 +12,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
@@ -72,15 +73,31 @@ public EncodeableFactory(IEncodeableFactory factory)
#if DEBUG
m_instanceId = Interlocked.Increment(ref m_globalInstanceCount);
#endif
+ if (factory != null)
+ {
+ m_encodeableTypes = ((EncodeableFactory)factory.Clone()).m_encodeableTypes;
+ }
+ }
+ #endregion
- lock (factory.SyncRoot)
+ #region IDisposable
+ ///
+ /// An overrideable version of the Dispose.
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
{
- foreach (KeyValuePair current in factory.EncodeableTypes)
- {
- m_encodeableTypes.Add(current.Key, current.Value);
- }
+ m_readerWriterLockSlim?.Dispose();
}
}
+
+ ///
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
#endregion
#region Private Members
@@ -108,61 +125,85 @@ private void AddEncodeableTypes(string assemblyName)
/// A dictionary of unbound typeIds, e.g. JSON type ids referenced by object name.
private void AddEncodeableType(Type systemType, Dictionary unboundTypeIds)
{
- lock (m_lock)
+
+ if (systemType == null)
{
- if (systemType == null)
- {
- return;
- }
+ return;
+ }
- if (!typeof(IEncodeable).GetTypeInfo().IsAssignableFrom(systemType.GetTypeInfo()))
- {
- return;
- }
+ if (!typeof(IEncodeable).GetTypeInfo().IsAssignableFrom(systemType.GetTypeInfo()))
+ {
+ return;
+ }
- IEncodeable encodeable = Activator.CreateInstance(systemType) as IEncodeable;
+ IEncodeable encodeable = Activator.CreateInstance(systemType) as IEncodeable;
- if (encodeable == null)
- {
- return;
- }
+ if (encodeable == null)
+ {
+ return;
+ }
#if DEBUG
- if (m_shared)
- {
- Utils.LogTrace("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId);
- }
+ if (m_shared)
+ {
+ Utils.LogTrace("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId);
+ }
#endif
- ExpandedNodeId nodeId = encodeable.TypeId;
+ // assume write lock
+ Debug.Assert(m_readerWriterLockSlim.IsWriteLockHeld);
- if (!NodeId.IsNull(nodeId))
- {
- // check for default namespace.
- if (nodeId.NamespaceUri == Namespaces.OpcUa)
- {
- nodeId = new ExpandedNodeId(nodeId.InnerNodeId);
- }
+ ExpandedNodeId nodeId = encodeable.TypeId;
- m_encodeableTypes[nodeId] = systemType;
+ if (!NodeId.IsNull(nodeId))
+ {
+ // check for default namespace.
+ if (nodeId.NamespaceUri == Namespaces.OpcUa)
+ {
+ nodeId = new ExpandedNodeId(nodeId.InnerNodeId);
}
- nodeId = encodeable.BinaryEncodingId;
+ m_encodeableTypes[nodeId] = systemType;
+ }
- if (!NodeId.IsNull(nodeId))
+ nodeId = encodeable.BinaryEncodingId;
+
+ if (!NodeId.IsNull(nodeId))
+ {
+ // check for default namespace.
+ if (nodeId.NamespaceUri == Namespaces.OpcUa)
{
- // check for default namespace.
- if (nodeId.NamespaceUri == Namespaces.OpcUa)
- {
- nodeId = new ExpandedNodeId(nodeId.InnerNodeId);
- }
+ nodeId = new ExpandedNodeId(nodeId.InnerNodeId);
+ }
- m_encodeableTypes[nodeId] = systemType;
+ m_encodeableTypes[nodeId] = systemType;
+ }
+
+ try
+ {
+ nodeId = encodeable.XmlEncodingId;
+ }
+ catch (NotSupportedException)
+ {
+ nodeId = NodeId.Null;
+ }
+
+ if (!NodeId.IsNull(nodeId))
+ {
+ // check for default namespace.
+ if (nodeId.NamespaceUri == Namespaces.OpcUa)
+ {
+ nodeId = new ExpandedNodeId(nodeId.InnerNodeId);
}
+ m_encodeableTypes[nodeId] = systemType;
+ }
+
+ if (encodeable is IJsonEncodeable jsonEncodeable)
+ {
try
{
- nodeId = encodeable.XmlEncodingId;
+ nodeId = jsonEncodeable.JsonEncodingId;
}
catch (NotSupportedException)
{
@@ -179,34 +220,11 @@ private void AddEncodeableType(Type systemType, Dictionary
- /// Returns the object used to synchronize access to the factory.
- ///
- ///
- /// Returns the object used to synchronize access to the factory.
- ///
- public object SyncRoot
- {
- get { return m_lock; }
- }
-
///
/// Returns a unique identifier for the table instance. Used to debug problems with shared tables.
///
@@ -338,7 +345,15 @@ public int InstanceId
/// The underlying system type to add to the factory
public void AddEncodeableType(Type systemType)
{
- AddEncodeableType(systemType, null);
+ try
+ {
+ m_readerWriterLockSlim.EnterWriteLock();
+ AddEncodeableType(systemType, null);
+ }
+ finally
+ {
+ m_readerWriterLockSlim.ExitWriteLock();
+ }
}
///
@@ -348,19 +363,23 @@ public void AddEncodeableType(Type systemType)
/// The system type to use for the specified encoding.
public void AddEncodeableType(ExpandedNodeId encodingId, Type systemType)
{
- lock (m_lock)
+ if (systemType != null && !NodeId.IsNull(encodingId))
{
- if (systemType != null && !NodeId.IsNull(encodingId))
- {
#if DEBUG
- if (m_shared)
- {
- Utils.LogWarning("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId);
- }
+ if (m_shared)
+ {
+ Utils.LogWarning("WARNING: Adding type '{0}' to shared Factory #{1}.", systemType.Name, m_instanceId);
+ }
#endif
-
+ try
+ {
+ m_readerWriterLockSlim.EnterWriteLock();
m_encodeableTypes[encodingId] = systemType;
}
+ finally
+ {
+ m_readerWriterLockSlim.ExitWriteLock();
+ }
}
}
@@ -389,8 +408,10 @@ public void AddEncodeableTypes(Assembly assembly)
}
#endif
- lock (m_lock)
+ try
{
+ m_readerWriterLockSlim.EnterWriteLock();
+
Type[] systemTypes = assembly.GetExportedTypes();
var unboundTypeIds = new Dictionary();
@@ -442,6 +463,10 @@ public void AddEncodeableTypes(Assembly assembly)
// only needed while adding assembly types
unboundTypeIds.Clear();
}
+ finally
+ {
+ m_readerWriterLockSlim.ExitWriteLock();
+ }
}
}
@@ -451,8 +476,9 @@ public void AddEncodeableTypes(Assembly assembly)
/// The underlying system types to add to the factory
public void AddEncodeableTypes(IEnumerable systemTypes)
{
- lock (m_lock)
+ try
{
+ m_readerWriterLockSlim.EnterWriteLock();
foreach (var type in systemTypes)
{
if (type.GetTypeInfo().IsAbstract)
@@ -460,9 +486,13 @@ public void AddEncodeableTypes(IEnumerable systemTypes)
continue;
}
- AddEncodeableType(type);
+ AddEncodeableType(type, null);
}
}
+ finally
+ {
+ m_readerWriterLockSlim.ExitWriteLock();
+ }
}
///
@@ -474,8 +504,10 @@ public void AddEncodeableTypes(IEnumerable systemTypes)
/// The type id to return the system-type of
public Type GetSystemType(ExpandedNodeId typeId)
{
- lock (m_lock)
+ try
{
+ m_readerWriterLockSlim.EnterReadLock();
+
Type systemType = null;
if (NodeId.IsNull(typeId) || !m_encodeableTypes.TryGetValue(typeId, out systemType))
@@ -485,6 +517,10 @@ public Type GetSystemType(ExpandedNodeId typeId)
return systemType;
}
+ finally
+ {
+ m_readerWriterLockSlim.ExitReadLock();
+ }
}
///
@@ -493,8 +529,37 @@ public Type GetSystemType(ExpandedNodeId typeId)
public IReadOnlyDictionary EncodeableTypes => m_encodeableTypes;
#endregion
+ #region ICloneable Methods
+ ///
+ public object Clone()
+ {
+ return MemberwiseClone();
+ }
+
+ ///
+ public new object MemberwiseClone()
+ {
+ EncodeableFactory clone = new EncodeableFactory(null);
+
+ try
+ {
+ m_readerWriterLockSlim.EnterReadLock();
+ foreach (KeyValuePair current in m_encodeableTypes)
+ {
+ clone.m_encodeableTypes.Add(current.Key, current.Value);
+ }
+ }
+ finally
+ {
+ m_readerWriterLockSlim.ExitReadLock();
+ }
+
+ return clone;
+ }
+ #endregion
+
#region Private Fields
- private object m_lock = new object();
+ private ReaderWriterLockSlim m_readerWriterLockSlim = new ReaderWriterLockSlim();
private Dictionary m_encodeableTypes;
private static EncodeableFactory s_globalFactory = new EncodeableFactory();
diff --git a/Stack/Opc.Ua.Core/Types/Encoders/IEncodeableFactory.cs b/Stack/Opc.Ua.Core/Types/Encoders/IEncodeableFactory.cs
index 66803dd11..c94715755 100644
--- a/Stack/Opc.Ua.Core/Types/Encoders/IEncodeableFactory.cs
+++ b/Stack/Opc.Ua.Core/Types/Encoders/IEncodeableFactory.cs
@@ -28,16 +28,8 @@ namespace Opc.Ua
/// Once the types exist within the factory, these types can be then easily queried.
///
///
- public interface IEncodeableFactory
+ public interface IEncodeableFactory : ICloneable
{
- ///
- /// Returns the object used to synchronize access to the factory.
- ///
- ///
- /// Returns the object used to synchronize access to the factory.
- ///
- object SyncRoot { get; }
-
///
/// Returns a unique identifier for the table instance. Used to debug problems with shared tables.
///
diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs
index 8df73c32c..fd91e49b3 100644
--- a/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs
+++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonDecoder.cs
@@ -264,6 +264,7 @@ protected virtual void Dispose(bool disposing)
if (m_reader != null)
{
m_reader.Close();
+ m_reader = null;
}
}
}
@@ -827,7 +828,7 @@ public XmlElement ReadXmlElement(string fieldName)
if (bytes != null && bytes.Length > 0)
{
XmlDocument document = new XmlDocument();
- string xmlString = new UTF8Encoding().GetString(bytes, 0, bytes.Length);
+ string xmlString = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
using (XmlReader reader = XmlReader.Create(new StringReader(xmlString), Utils.DefaultXmlReaderSettings()))
{
diff --git a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs
index c53f13399..53abaf951 100644
--- a/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs
+++ b/Stack/Opc.Ua.Core/Types/Encoders/JsonEncoder.cs
@@ -320,6 +320,7 @@ protected virtual void Dispose(bool disposing)
if (m_writer != null)
{
Close();
+ m_writer = null;
}
if (!m_leaveOpen)
diff --git a/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs b/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs
index 113576ee7..ada9ff1e7 100644
--- a/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs
+++ b/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs
@@ -16,7 +16,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
using System.IO;
using System.Linq;
using System.Text;
-using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;
@@ -72,21 +71,21 @@ public BinarySchemaValidator(IDictionary importTable) : base(imp
///
/// Generates the code from the contents of the address space.
///
- public async Task Validate(Stream stream)
+ public void Validate(Stream stream)
{
// read and parse the file.
Dictionary = (TypeDictionary)LoadInput(typeof(TypeDictionary), stream);
- await Validate().ConfigureAwait(false);
+ Validate();
}
///
/// Generates the code from the contents of the address space.
///
- public async Task Validate(string inputPath)
+ public void Validate(string inputPath)
{
// read and parse the file.
Dictionary = (TypeDictionary)LoadInput(typeof(TypeDictionary), inputPath);
- await Validate().ConfigureAwait(false);
+ Validate();
}
///
@@ -128,7 +127,7 @@ public override string GetSchema(string typeName)
writer.Dispose();
}
- return new UTF8Encoding().GetString(ostrm.ToArray(), 0, (int)ostrm.Length);
+ return Encoding.UTF8.GetString(ostrm.ToArray(), 0, (int)ostrm.Length);
}
#endregion
@@ -136,7 +135,7 @@ public override string GetSchema(string typeName)
///
/// Generates the code from the contents of the address space.
///
- private async Task Validate()
+ private void Validate()
{
m_descriptions = new Dictionary();
m_validatedDescriptions = new List();
@@ -147,7 +146,7 @@ private async Task Validate()
{
foreach (ImportDirective directive in Dictionary.Import)
{
- await Import(directive).ConfigureAwait(false);
+ Import(directive);
}
}
else
@@ -156,7 +155,7 @@ private async Task Validate()
if (!WellKnownDictionaries.Any(n => string.Equals(n[0], Dictionary.TargetNamespace, StringComparison.Ordinal)))
{
ImportDirective directive = new ImportDirective { Namespace = Namespaces.OpcUa };
- await Import(directive).ConfigureAwait(false);
+ Import(directive);
}
}
@@ -187,7 +186,7 @@ private async Task Validate()
///
/// Imports a dictionary identified by an import directive.
///
- private async Task Import(ImportDirective directive)
+ private void Import(ImportDirective directive)
{
// check if already loaded.
if (LoadedFiles.ContainsKey(directive.Namespace))
@@ -211,7 +210,7 @@ private async Task Import(ImportDirective directive)
{
for (int ii = 0; ii < dictionary.Import.Length; ii++)
{
- await Import(dictionary.Import[ii]).ConfigureAwait(false);
+ Import(dictionary.Import[ii]);
}
}
diff --git a/Stack/Opc.Ua.Core/Types/Schemas/XmlSchemaValidator.cs b/Stack/Opc.Ua.Core/Types/Schemas/XmlSchemaValidator.cs
index f9c5cacf7..ae0bb641a 100644
--- a/Stack/Opc.Ua.Core/Types/Schemas/XmlSchemaValidator.cs
+++ b/Stack/Opc.Ua.Core/Types/Schemas/XmlSchemaValidator.cs
@@ -159,7 +159,7 @@ public override string GetSchema(string typeName)
writer.Dispose();
}
- return new UTF8Encoding().GetString(ostrm.ToArray());
+ return Encoding.UTF8.GetString(ostrm.ToArray());
}
#endregion
diff --git a/Stack/Opc.Ua.Core/Types/Utils/ServiceResultException.cs b/Stack/Opc.Ua.Core/Types/Utils/ServiceResultException.cs
index 5202419e9..4ddba641e 100644
--- a/Stack/Opc.Ua.Core/Types/Utils/ServiceResultException.cs
+++ b/Stack/Opc.Ua.Core/Types/Utils/ServiceResultException.cs
@@ -21,6 +21,7 @@ namespace Opc.Ua
/// An exception thrown when a UA defined error occurs.
///
[DataContractAttribute]
+ [SerializableAttribute]
public class ServiceResultException : Exception
{
#region Constructors
@@ -112,12 +113,12 @@ public ServiceResultException(ServiceResult status) : base(GetMessage(status))
///
/// The namespace that qualifies symbolic identifier.
- ///
+ ///
public string NamespaceUri => m_status.NamespaceUri;
///
/// The qualified name of the symbolic identifier associated with the status code.
- ///
+ ///
public string SymbolicId => m_status.SymbolicId;
///
diff --git a/Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs b/Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs
index 690c06359..fc49a1feb 100644
--- a/Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs
+++ b/Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs
@@ -12,6 +12,8 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
using System;
using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
using System.Xml;
namespace Opc.Ua
@@ -512,6 +514,44 @@ public static BuiltInType GetBuiltInType(NodeId datatypeId, ITypeTable typeTree)
return BuiltInType.Null;
}
+#if (NET_STANDARD_ASYNC)
+ ///
+ /// Returns the BuiltInType type for the DataTypeId.
+ ///
+ /// The data type identyfier for a node in a server's address space..
+ /// The type tree for a server. .
+ ///
+ ///
+ /// A value for
+ ///
+ public static async Task GetBuiltInTypeAsync(NodeId datatypeId, ITypeTable typeTree, CancellationToken ct = default)
+ {
+ NodeId typeId = datatypeId;
+
+ while (!Opc.Ua.NodeId.IsNull(typeId))
+ {
+ if (typeId != null && typeId.NamespaceIndex == 0 && typeId.IdType == Opc.Ua.IdType.Numeric)
+ {
+ BuiltInType id = (BuiltInType)(int)(uint)typeId.Identifier;
+
+ if (id > BuiltInType.Null && id <= BuiltInType.Enumeration && id != BuiltInType.DiagnosticInfo)
+ {
+ return id;
+ }
+ }
+
+ if (typeTree == null)
+ {
+ break;
+ }
+
+ typeId = await typeTree.FindSuperTypeAsync(typeId, ct).ConfigureAwait(false);
+ }
+
+ return BuiltInType.Null;
+ }
+#endif
+
///
/// Returns the system type for the datatype.
///
@@ -886,7 +926,7 @@ public static TypeInfo IsInstanceOfDataType(
return null;
}
- // check every element in the array or matrix.
+ // check every element in the array or matrix.
Array array = value as Array;
if (array == null)
{
@@ -1214,7 +1254,7 @@ public static TypeInfo Construct(Type systemType)
return TypeInfo.Unknown;
}
- // check for generic type.
+ // check for generic type.
if (systemType.GetTypeInfo().IsGenericType)
{
Type[] argTypes = systemType.GetGenericArguments();
@@ -1310,7 +1350,7 @@ public static TypeInfo Construct(Type systemType)
}
}
- // unknown type.
+ // unknown type.
return TypeInfo.Unknown;
}
diff --git a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
index 91b406d75..e7e54cce3 100644
--- a/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
+++ b/Stack/Opc.Ua.Core/Types/Utils/Utils.cs
@@ -2834,7 +2834,7 @@ private static byte[] PSHA(HMAC hmac, string label, byte[] data, int offset, int
// convert label to UTF-8 byte sequence.
if (!String.IsNullOrEmpty(label))
{
- seed = new UTF8Encoding().GetBytes(label);
+ seed = Encoding.UTF8.GetBytes(label);
}
// append data to label.
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 e176f9441..252a39776 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
@@ -7,7 +7,7 @@
-
+
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs
index 56e8a0cde..3bc9532d8 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/TypeSystemClientTest.cs
@@ -62,6 +62,10 @@ public class TypeSystemClientTest : IUAClient
string m_pkiRoot;
Uri m_url;
+ // for test that fetched and browsed node count match
+ int m_fetchedNodesCount;
+ int m_browsedNodesCount;
+
public TypeSystemClientTest()
{
m_uriScheme = Utils.UriSchemeOpcTcp;
@@ -79,6 +83,9 @@ public TypeSystemClientTest(string uriScheme)
[OneTimeSetUp]
public Task OneTimeSetUp()
{
+ m_fetchedNodesCount = -1;
+ m_browsedNodesCount = -1;
+
return OneTimeSetUpAsync(null);
}
@@ -97,6 +104,7 @@ public async Task OneTimeSetUpAsync(TextWriter writer = null)
SecurityNone = true,
AutoAccept = true,
AllNodeManagers = true,
+ OperationLimits = true,
};
if (writer != null)
{
@@ -192,46 +200,51 @@ public async Task LoadTypeSystem(bool onlyEnumTypes, bool disableDataTypeDefinit
}
[Test, Order(200)]
- public async Task BrowseComplexTypesServer()
+ public async Task BrowseComplexTypesServerAsync()
{
var samples = new ClientSamples(TestContext.Out, null, null, true);
- await samples.LoadTypeSystem(Session).ConfigureAwait(false);
+ await samples.LoadTypeSystemAsync(Session).ConfigureAwait(false);
ReferenceDescriptionCollection referenceDescriptions =
- samples.BrowseFullAddressSpace(this, Objects.RootFolder);
+ await samples.BrowseFullAddressSpaceAsync(this, Objects.RootFolder).ConfigureAwait(false);
TestContext.Out.WriteLine("References: {0}", referenceDescriptions.Count);
+ m_browsedNodesCount = referenceDescriptions.Count;
NodeIdCollection variableIds = new NodeIdCollection(referenceDescriptions
- .Where(r => r.NodeClass == NodeClass.Variable && r.TypeDefinition.NamespaceIndex != 0)
+ .Where(r => r.NodeClass == NodeClass.Variable)
.Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)));
TestContext.Out.WriteLine("VariableIds: {0}", variableIds.Count);
(var values, var serviceResults) = await samples.ReadAllValuesAsync(this, variableIds).ConfigureAwait(false);
+ int ii = 0;
foreach (var serviceResult in serviceResults)
{
- Assert.IsTrue(ServiceResult.IsGood(serviceResult));
+ var result = serviceResults[ii++];
+ Assert.IsTrue(ServiceResult.IsGood(serviceResult), $"Expected good result, but received {serviceResult}");
}
}
[Test, Order(300)]
- public async Task FetchComplexTypesServer()
+ public async Task FetchComplexTypesServerAsync()
{
var samples = new ClientSamples(TestContext.Out, null, null, true);
- await samples.LoadTypeSystem(m_session).ConfigureAwait(false);
+ await samples.LoadTypeSystemAsync(m_session).ConfigureAwait(false);
IList allNodes = null;
- allNodes = samples.FetchAllNodesNodeCache(
- this, Objects.RootFolder, true, true, false);
+ allNodes = await samples.FetchAllNodesNodeCacheAsync(
+ this, Objects.RootFolder, true, false, false).ConfigureAwait(false);
TestContext.Out.WriteLine("References: {0}", allNodes.Count);
+ m_fetchedNodesCount = allNodes.Count;
+
NodeIdCollection variableIds = new NodeIdCollection(allNodes
- .Where(r => r.NodeClass == NodeClass.Variable && ((VariableNode)r).DataType.NamespaceIndex != 0)
+ .Where(r => r.NodeClass == NodeClass.Variable && r is VariableNode && ((VariableNode)r).DataType.NamespaceIndex != 0)
.Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)));
TestContext.Out.WriteLine("VariableIds: {0}", variableIds.Count);
@@ -242,13 +255,99 @@ public async Task FetchComplexTypesServer()
{
Assert.IsTrue(ServiceResult.IsGood(serviceResult));
}
+
+ // check if complex type is properly decoded
+ bool testFailed = false;
+ for (int ii = 0; ii < values.Count; ii++)
+ {
+ DataValue value = values[ii];
+ NodeId variableId = variableIds[ii];
+ ExpandedNodeId variableExpandedNodeId = NodeId.ToExpandedNodeId(variableId, m_session.NamespaceUris);
+ var variableNode = allNodes.Where(n => n.NodeId == variableId).FirstOrDefault() as VariableNode;
+ if (variableNode != null &&
+ variableNode.DataType.NamespaceIndex != 0)
+ {
+ TestContext.Out.WriteLine("Check for custom type: {0}", variableNode);
+ ExpandedNodeId fullTypeId = NodeId.ToExpandedNodeId(variableNode.DataType, m_session.NamespaceUris);
+ Type type = m_session.Factory.GetSystemType(fullTypeId);
+ if (type == null)
+ {
+ // check for opaque type
+ NodeId superType = m_session.NodeCache.FindSuperType(fullTypeId);
+ NodeId lastGoodType = variableNode.DataType;
+ while (!superType.IsNullNodeId && superType != DataTypes.BaseDataType)
+ {
+ if (superType == DataTypeIds.Structure)
+ {
+ testFailed = true;
+ break;
+ }
+ lastGoodType = superType;
+ superType = m_session.NodeCache.FindSuperType(superType);
+ }
+
+ if (testFailed)
+ {
+ TestContext.Out.WriteLine("-- Variable: {0} complex type unavailable --> {1}", variableNode.NodeId, variableNode.DataType);
+ (_, _) = await samples.ReadAllValuesAsync(this, new NodeIdCollection() { variableId }).ConfigureAwait(false);
+ }
+ else
+ {
+ TestContext.Out.WriteLine("-- Variable: {0} opaque typeid --> {1}", variableNode.NodeId, lastGoodType);
+ }
+ continue;
+ }
+
+ if (value.Value is ExtensionObject)
+ {
+ Type valueType = ((ExtensionObject)value.Value).Body.GetType();
+ if (valueType != type)
+ {
+ testFailed = true;
+ TestContext.Out.WriteLine("Variable: {0} type is decoded as ExtensionObject --> {1}", variableNode, value.Value);
+ (_, _) = await samples.ReadAllValuesAsync(this, new NodeIdCollection() { variableId }).ConfigureAwait(false);
+ }
+ continue;
+ }
+
+ if (value.Value is Array array &&
+ array.GetType().GetElementType() == typeof(ExtensionObject))
+ {
+ foreach (ExtensionObject valueItem in array)
+ {
+ Type valueType = valueItem.Body.GetType();
+ if (valueType != type)
+ {
+ testFailed = true;
+ TestContext.Out.WriteLine("Variable: {0} type is decoded as ExtensionObject --> {1}", variableNode, valueItem);
+ (_, _) = await samples.ReadAllValuesAsync(this, new NodeIdCollection() { variableId }).ConfigureAwait(false);
+ }
+ }
+ }
+ }
+ }
+
+ if (testFailed)
+ {
+ Assert.Fail("Test failed, unknown or undecodable complex type detected. See log for information.");
+ }
+ }
+
+ [Test, Order(330)]
+ public void ValidateFetchedAndBrowsedNodesMatch()
+ {
+ if (m_browsedNodesCount < 0 || m_fetchedNodesCount < 0)
+ {
+ Assert.Ignore("The browse or fetch test did not run.");
+ }
+ Assert.AreEqual(m_fetchedNodesCount, m_browsedNodesCount);
}
[Test, Order(400)]
- public async Task ReadWriteScalaVariableType()
+ public async Task ReadWriteScalarVariableTypeAsync()
{
var samples = new ClientSamples(TestContext.Out, null, null, true);
- await samples.LoadTypeSystem(m_session).ConfigureAwait(false);
+ await samples.LoadTypeSystemAsync(m_session).ConfigureAwait(false);
// test the static version of the structure
ExpandedNodeId structureVariable = TestData.VariableIds.Data_Static_Structure_ScalarStructure;
diff --git a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs
index 938ea4324..354ba90c1 100644
--- a/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs
+++ b/Tests/Opc.Ua.Client.ComplexTypes.Tests/Types/MockResolver.cs
@@ -2,7 +2,7 @@
* Copyright (c) 2005-2021 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
@@ -11,7 +11,7 @@
* 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,
@@ -30,6 +30,7 @@
using System.Collections.Generic;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
namespace Opc.Ua.Client.ComplexTypes.Tests.Types
@@ -74,26 +75,26 @@ private void Initialize()
public IEncodeableFactory Factory => m_factory;
///
- public Task> LoadDataTypeSystem(NodeId dataTypeSystem = null)
+ public Task> LoadDataTypeSystem(NodeId dataTypeSystem = null, CancellationToken ct = default)
{
return Task.FromResult(m_dataTypeDictionary);
}
///
- public IList BrowseForEncodings(IList nodeIds, string[] supportedEncodings)
+ public Task> BrowseForEncodingsAsync(IList nodeIds, string[] supportedEncodings, CancellationToken ct = default)
{
- return new List();
+ return Task.FromResult((IList)new List());
}
///
- public IList BrowseForEncodings(
+ public Task<(IList encodings, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId)> BrowseForEncodingsAsync(
ExpandedNodeId nodeId,
string[] supportedEncodings,
- out ExpandedNodeId binaryEncodingId,
- out ExpandedNodeId xmlEncodingId)
+ CancellationToken ct = default)
{
- binaryEncodingId = ExpandedNodeId.Null;
- xmlEncodingId = ExpandedNodeId.Null;
+ var binaryEncodingId = ExpandedNodeId.Null;
+ var xmlEncodingId = ExpandedNodeId.Null;
+ IList encodings = null;
var node = m_dataTypeNodes[ExpandedNodeId.ToNodeId(nodeId, NamespaceUris)];
if (node is DataTypeNode dataTypeNode)
@@ -121,33 +122,26 @@ public IList BrowseForEncodings(
}
result.Add(ExpandedNodeId.ToNodeId(reference.TargetId, NamespaceUris));
}
- return result;
+ encodings = result;
}
- return null;
+ return Task.FromResult((encodings, binaryEncodingId, xmlEncodingId));
}
///
- public bool BrowseTypeIdsForDictionaryComponent(
+ public Task<(ExpandedNodeId typeId, ExpandedNodeId encodingId, DataTypeNode dataTypeNode)> BrowseTypeIdsForDictionaryComponentAsync(
ExpandedNodeId nodeId,
- out ExpandedNodeId typeId,
- out ExpandedNodeId encodingId,
- out DataTypeNode dataTypeNode)
+ CancellationToken ct = default)
{
- typeId = ExpandedNodeId.Null;
- encodingId = ExpandedNodeId.Null;
- dataTypeNode = null;
-
- // not implemented yet
-
- return false;
+ return Task.FromResult<(ExpandedNodeId typeId, ExpandedNodeId encodingId, DataTypeNode dataTypeNode)>((null, null, null));
}
///
- public IList LoadDataTypes(
+ public async Task> LoadDataTypesAsync(
ExpandedNodeId dataType,
bool nestedSubTypes = false,
bool addRootNode = false,
- bool filterUATypes = true)
+ bool filterUATypes = true,
+ CancellationToken ct = default)
{
var result = new List();
var nodesToBrowse = new ExpandedNodeIdCollection {
@@ -156,7 +150,7 @@ public IList LoadDataTypes(
if (addRootNode)
{
- var rootNode = Find(dataType);
+ var rootNode = await FindAsync(dataType, ct).ConfigureAwait(false);
if (!(rootNode is DataTypeNode))
{
throw new ServiceResultException("Root Node is not a DataType node.");
@@ -203,34 +197,33 @@ public IList LoadDataTypes(
}
///
- public INode Find(ExpandedNodeId nodeId)
+ public Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct = default)
{
- return m_dataTypeNodes[ExpandedNodeId.ToNodeId(nodeId, NamespaceUris)];
+ return Task.FromResult(m_dataTypeNodes[ExpandedNodeId.ToNodeId(nodeId, NamespaceUris)]);
}
-
///
- public object GetEnumTypeArray(ExpandedNodeId nodeId)
+ public Task GetEnumTypeArrayAsync(ExpandedNodeId nodeId, CancellationToken ct = default)
{
- return null;
+ return Task.FromResult(null);
}
///
- public NodeId FindSuperType(NodeId typeId)
+ public Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default)
{
var node = m_dataTypeNodes[typeId];
if (node is DataTypeNode dataTypeNode)
{
if (dataTypeNode.DataTypeDefinition.Body is EnumDefinition enumDefinition)
{
- return DataTypeIds.Enumeration;
+ return Task.FromResult(DataTypeIds.Enumeration);
}
else if (dataTypeNode.DataTypeDefinition.Body is StructureDefinition structureDefinition)
{
- return structureDefinition.BaseDataType;
+ return Task.FromResult(structureDefinition.BaseDataType);
}
}
- return DataTypeIds.BaseDataType;
+ return Task.FromResult(DataTypeIds.BaseDataType);
}
#endregion IComplexTypeResolver
diff --git a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs
index 417c9d3a8..952263d77 100644
--- a/Tests/Opc.Ua.Client.Tests/ClientFixture.cs
+++ b/Tests/Opc.Ua.Client.Tests/ClientFixture.cs
@@ -78,6 +78,8 @@ public async Task LoadClientConfiguration(string pkiRoot = null, string clientNa
.Build(
"urn:localhost:opcfoundation.org:" + clientName,
"http://opcfoundation.org/UA/" + clientName)
+ .SetMaxByteStringLength(4 * 1024 * 1024)
+ .SetMaxArrayLength(1024 * 1024)
.AsClient()
.SetClientOperationLimits(new OperationLimits {
MaxNodesPerBrowse = kDefaultOperationLimits,
diff --git a/Tests/Opc.Ua.Client.Tests/ClientTest.cs b/Tests/Opc.Ua.Client.Tests/ClientTest.cs
index 7e9986215..35e036d45 100644
--- a/Tests/Opc.Ua.Client.Tests/ClientTest.cs
+++ b/Tests/Opc.Ua.Client.Tests/ClientTest.cs
@@ -1164,7 +1164,7 @@ public async Task LoadStandardDataTypeSystem()
[Test, Order(710)]
[TestCaseSource(nameof(TypeSystems))]
- public async Task LoadAllServerDataTypeSystems(NodeId dataTypeSystem)
+ public void LoadAllServerDataTypeSystems(NodeId dataTypeSystem)
{
// find the dictionary for the description.
Browser browser = new Browser(Session) {
@@ -1185,7 +1185,7 @@ public async Task LoadAllServerDataTypeSystems(NodeId dataTypeSystem)
NodeId dictionaryId = ExpandedNodeId.ToNodeId(r.NodeId, Session.NamespaceUris);
TestContext.Out.WriteLine(" ReadDictionary {0} {1}", r.BrowseName.Name, dictionaryId);
var dictionaryToLoad = new DataDictionary(Session);
- await dictionaryToLoad.Load(dictionaryId, r.BrowseName.Name).ConfigureAwait(false);
+ dictionaryToLoad.Load(dictionaryId, r.BrowseName.Name);
// internal API for testing only
var dictionary = dictionaryToLoad.ReadDictionary(dictionaryId);
@@ -1195,7 +1195,7 @@ public async Task LoadAllServerDataTypeSystems(NodeId dataTypeSystem)
{
try
{
- await dictionaryToLoad.Validate(dictionary, true).ConfigureAwait(false);
+ dictionaryToLoad.Validate(dictionary, true);
}
catch (Exception ex)
{
@@ -1204,7 +1204,7 @@ public async Task LoadAllServerDataTypeSystems(NodeId dataTypeSystem)
}
else
{
- await dictionaryToLoad.Validate(dictionary, true).ConfigureAwait(false);
+ dictionaryToLoad.Validate(dictionary, true);
}
}
}
diff --git a/Tests/Opc.Ua.Client.Tests/NodeCacheAsyncTest.cs b/Tests/Opc.Ua.Client.Tests/NodeCacheAsyncTest.cs
new file mode 100644
index 000000000..4a4c16685
--- /dev/null
+++ b/Tests/Opc.Ua.Client.Tests/NodeCacheAsyncTest.cs
@@ -0,0 +1,508 @@
+/* ========================================================================
+ * Copyright (c) 2005-2020 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;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using BenchmarkDotNet.Attributes;
+using NUnit.Framework;
+using Opc.Ua.Server.Tests;
+
+namespace Opc.Ua.Client.Tests
+{
+ ///
+ /// Client tests.
+ ///
+ [TestFixture, Category("Client"), Category("NodeCacheAsync")]
+ [SetCulture("en-us"), SetUICulture("en-us")]
+ [TestFixtureSource(nameof(FixtureArgs))]
+ [MemoryDiagnoser]
+ [DisassemblyDiagnoser]
+ public class NodeCacheAsyncTest : ClientTestFramework
+ {
+ private const int kTestSetSize = 100;
+
+ public NodeCacheAsyncTest(string uriScheme = Utils.UriSchemeOpcTcp) :
+ base(uriScheme)
+ {
+ }
+
+ #region Test Setup
+ ///
+ /// Set up a Server and a Client instance.
+ ///
+ [OneTimeSetUp]
+ public new Task OneTimeSetUp()
+ {
+ SupportsExternalServerUrl = true;
+ // create a new session for every test
+ SingleSession = false;
+ return base.OneTimeSetUp();
+ }
+
+ ///
+ /// Tear down the Server and the Client.
+ ///
+ [OneTimeTearDown]
+ public new Task OneTimeTearDownAsync()
+ {
+ return base.OneTimeTearDownAsync();
+ }
+
+ ///
+ /// Test setup.
+ ///
+ [SetUp]
+ public new async Task SetUp()
+ {
+ await base.SetUp().ConfigureAwait(false);
+
+ // clear node cache
+ Session.NodeCache.Clear();
+ }
+
+ ///
+ /// Test teardown.
+ ///
+ [TearDown]
+ public new Task TearDown()
+ {
+ return base.TearDown();
+ }
+ #endregion
+
+ #region Benchmark Setup
+ ///
+ /// Global Setup for benchmarks.
+ ///
+ [GlobalSetup]
+ public new void GlobalSetup()
+ {
+ base.GlobalSetup();
+ }
+
+ ///
+ /// Global cleanup for benchmarks.
+ ///
+ [GlobalCleanup]
+ public new void GlobalCleanup()
+ {
+ base.GlobalCleanup();
+ }
+ #endregion
+
+ #region Test Methods
+ ///
+ /// Load Ua types in node cache.
+ ///
+ [Test, Order(500)]
+ public void NodeCache_LoadUaDefinedTypes()
+ {
+ INodeCache nodeCache = Session.NodeCache;
+ Assert.IsNotNull(nodeCache);
+
+ // load the predefined types
+ nodeCache.LoadUaDefinedTypes(Session.SystemContext);
+
+ // reload the predefined types
+ nodeCache.LoadUaDefinedTypes(Session.SystemContext);
+ }
+
+ ///
+ /// Browse all variables in the objects folder.
+ ///
+ [Test, Order(100)]
+ public async Task NodeCache_BrowseAllVariables()
+ {
+ var result = new List();
+ var nodesToBrowse = new ExpandedNodeIdCollection {
+ ObjectIds.ObjectsFolder
+ };
+
+ await Session.FetchTypeTreeAsync(ReferenceTypeIds.References).ConfigureAwait(false); // TODO: Async
+
+ while (nodesToBrowse.Count > 0)
+ {
+ var nextNodesToBrowse = new ExpandedNodeIdCollection();
+ foreach (var node in nodesToBrowse)
+ {
+ try
+ {
+ var organizers = await Session.NodeCache.FindReferencesAsync(
+ node,
+ ReferenceTypeIds.HierarchicalReferences,
+ false,
+ true).ConfigureAwait(false);
+ nextNodesToBrowse.AddRange(organizers.Select(n => n.NodeId));
+ var objectNodes = organizers.Where(n => n is ObjectNode);
+ var variableNodes = organizers.Where(n => n is VariableNode);
+ result.AddRange(variableNodes);
+ }
+ catch (ServiceResultException sre)
+ {
+ if (sre.StatusCode == StatusCodes.BadUserAccessDenied)
+ {
+ TestContext.Out.WriteLine($"Access denied: Skip node {node}.");
+ }
+ }
+ }
+ nodesToBrowse = new ExpandedNodeIdCollection(nextNodesToBrowse.Distinct());
+ TestContext.Out.WriteLine("Found {0} duplicates", nextNodesToBrowse.Count - nodesToBrowse.Count);
+ }
+
+ TestContext.Out.WriteLine("Found {0} variables", result.Count);
+ }
+
+ ///
+ /// Browse all variables in the objects folder.
+ ///
+ [Test, Order(200)]
+ public async Task NodeCache_BrowseAllVariables_MultipleNodes()
+ {
+ var result = new List();
+ var nodesToBrowse = new ExpandedNodeIdCollection {
+ ObjectIds.ObjectsFolder
+ };
+
+ await Session.FetchTypeTreeAsync(ReferenceTypeIds.References).ConfigureAwait(false);
+ var referenceTypeIds = new NodeIdCollection() { ReferenceTypeIds.HierarchicalReferences };
+ while (nodesToBrowse.Count > 0)
+ {
+ var nextNodesToBrowse = new ExpandedNodeIdCollection();
+ try
+ {
+ var organizers = await Session.NodeCache.FindReferencesAsync(
+ nodesToBrowse,
+ referenceTypeIds,
+ false,
+ true).ConfigureAwait(false);
+ nextNodesToBrowse.AddRange(organizers.Select(n => n.NodeId));
+ var objectNodes = organizers.Where(n => n is ObjectNode);
+ var variableNodes = organizers.Where(n => n is VariableNode);
+ result.AddRange(variableNodes);
+ }
+ catch (ServiceResultException sre)
+ {
+ if (sre.StatusCode == StatusCodes.BadUserAccessDenied)
+ {
+ TestContext.Out.WriteLine($"Access denied: Skipped node.");
+ }
+ }
+ nodesToBrowse = new ExpandedNodeIdCollection(nextNodesToBrowse.Distinct());
+ TestContext.Out.WriteLine("Found {0} duplicates", nextNodesToBrowse.Count - nodesToBrowse.Count);
+ }
+
+ TestContext.Out.WriteLine("Found {0} variables", result.Count);
+ }
+
+ [Test, Order(720)]
+ public async Task NodeCacheFind()
+ {
+ if (ReferenceDescriptions == null)
+ {
+ BrowseFullAddressSpace();
+ }
+
+ foreach (var reference in ReferenceDescriptions.Take(MaxReferences))
+ {
+ var nodeId = ExpandedNodeId.ToNodeId(reference.NodeId, Session.NamespaceUris);
+ var node = await Session.NodeCache.FindAsync(reference.NodeId).ConfigureAwait(false);
+ TestContext.Out.WriteLine("NodeId: {0} Node: {1}", nodeId, node);
+ }
+ }
+
+ [Test, Order(730)]
+ public async Task NodeCacheFetchNode()
+ {
+ if (ReferenceDescriptions == null)
+ {
+ BrowseFullAddressSpace();
+ }
+
+ foreach (var reference in ReferenceDescriptions.Take(MaxReferences))
+ {
+ var nodeId = ExpandedNodeId.ToNodeId(reference.NodeId, Session.NamespaceUris);
+ var node = await Session.NodeCache.FetchNodeAsync(reference.NodeId).ConfigureAwait(false);
+ TestContext.Out.WriteLine("NodeId: {0} Node: {1}", nodeId, node);
+ }
+ }
+
+ [Test, Order(740)]
+ public async Task NodeCacheFetchNodes()
+ {
+ if (ReferenceDescriptions == null)
+ {
+ BrowseFullAddressSpace();
+ }
+
+ var testSet = ReferenceDescriptions.Take(MaxReferences).Select(r => r.NodeId).ToList();
+ IList nodeCollection = await Session.NodeCache.FetchNodesAsync(testSet).ConfigureAwait(false);
+
+ foreach (var node in nodeCollection)
+ {
+ var nodeId = ExpandedNodeId.ToNodeId(node.NodeId, Session.NamespaceUris);
+ TestContext.Out.WriteLine("NodeId: {0} Node: {1}", nodeId, node);
+ }
+ }
+
+ [Test, Order(750)]
+ public async Task NodeCacheFindReferences()
+ {
+ if (ReferenceDescriptions == null)
+ {
+ BrowseFullAddressSpace();
+ }
+
+ var testSet = ReferenceDescriptions.Take(MaxReferences).Select(r => r.NodeId).ToList();
+ IList nodes = await Session.NodeCache.FindReferencesAsync(testSet, new NodeIdCollection() { ReferenceTypeIds.NonHierarchicalReferences }, false, true).ConfigureAwait(false);
+
+ foreach (var node in nodes)
+ {
+ var nodeId = ExpandedNodeId.ToNodeId(node.NodeId, Session.NamespaceUris);
+ TestContext.Out.WriteLine("NodeId: {0} Node: {1}", nodeId, node);
+ }
+ }
+
+ [Test, Order(900)]
+ public async Task FetchTypeTreeAsync()
+ {
+ await Session.FetchTypeTreeAsync(NodeId.ToExpandedNodeId(DataTypeIds.BaseDataType, Session.NamespaceUris)).ConfigureAwait(false);
+ }
+
+ [Test, Order(910)]
+ public async Task FetchAllReferenceTypesAsync()
+ {
+ var bindingFlags =
+ BindingFlags.Instance |
+ BindingFlags.Static |
+ BindingFlags.Public;
+ var fieldValues = typeof(ReferenceTypeIds)
+ .GetFields(bindingFlags)
+ .Select(field => NodeId.ToExpandedNodeId((NodeId)field.GetValue(null), Session.NamespaceUris));
+
+ await Session.FetchTypeTreeAsync(new ExpandedNodeIdCollection(fieldValues)).ConfigureAwait(false);
+ }
+
+ ///
+ /// Test concurrent access of FetchNodes.
+ ///
+ [Test, Order(1000)]
+ public async Task NodeCacheFetchNodesConcurrentAsync()
+ {
+ if (ReferenceDescriptions == null)
+ {
+ BrowseFullAddressSpace();
+ }
+
+ Random random = new Random(62541);
+ var testSet = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList();
+ var taskList = new List();
+
+ // test concurrent access of FetchNodes
+ for (int i = 0; i < 10; i++)
+ {
+ Task t = Session.NodeCache.FetchNodesAsync(testSet);
+ taskList.Add(t);
+ }
+ await Task.WhenAll(taskList.ToArray()).ConfigureAwait(false);
+ }
+
+ ///
+ /// Test concurrent access of Find.
+ ///
+ [Test, Order(1100)]
+ public async Task NodeCacheFindNodesConcurrent()
+ {
+ if (ReferenceDescriptions == null)
+ {
+ BrowseFullAddressSpace();
+ }
+
+ Random random = new Random(62541);
+ var testSet = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList();
+ var taskList = new List();
+
+ // test concurrent access of FetchNodes
+ for (int i = 0; i < 10; i++)
+ {
+ Task t = Session.NodeCache.FindAsync(testSet);
+ taskList.Add(t);
+ }
+ await Task.WhenAll(taskList.ToArray()).ConfigureAwait(false);
+ }
+
+ ///
+ /// Test concurrent access of FindReferences.
+ ///
+ [Test, Order(1200)]
+ public async Task NodeCacheFindReferencesConcurrent()
+ {
+ if (ReferenceDescriptions == null)
+ {
+ BrowseFullAddressSpace();
+ }
+
+ Random random = new Random(62541);
+ var testSet = ReferenceDescriptions.OrderBy(o => random.Next()).Take(kTestSetSize).Select(r => r.NodeId).ToList();
+ var taskList = new List();
+ var refTypeIds = new List() { ReferenceTypeIds.HierarchicalReferences };
+ await FetchAllReferenceTypesAsync().ConfigureAwait(false);
+
+ // test concurrent access of FetchNodes
+ for (int i = 0; i < 10; i++)
+ {
+ Task t = Session.NodeCache.FindReferencesAsync(testSet, refTypeIds, false, true);
+ taskList.Add(t);
+ }
+ await Task.WhenAll(taskList.ToArray()).ConfigureAwait(false);
+ }
+
+ ///
+ /// Test concurrent access of many methods in INodecache interface
+ ///
+ [Test, Order(1300)]
+ public async Task NodeCacheTestAllMethodsConcurrently()
+ {
+ const int testCases = 10;
+ const int testCaseRunTime = 5_000;
+
+ if (ReferenceDescriptions == null)
+ {
+ BrowseFullAddressSpace();
+ }
+
+ Random random = new Random(62541);
+ var testSetAll = ReferenceDescriptions.OrderBy(o => random.Next()).Where(r => r.NodeClass == NodeClass.Variable).Select(r => r.NodeId).ToList();
+ var testSet1 = testSetAll.Take(kTestSetSize).ToList();
+ var testSet2 = testSetAll.Skip(kTestSetSize).Take(kTestSetSize).ToList();
+ var testSet3 = testSetAll.Skip(kTestSetSize * 2).Take(kTestSetSize).ToList();
+
+ var taskList = new List();
+ var refTypeIds = new List() { ReferenceTypeIds.HierarchicalReferences };
+
+ // test concurrent access of many methods in INodecache interface
+ for (int i = 0; i < testCases; i++)
+ {
+ int iteration = i;
+ Task t = Task.Run(async () => {
+ DateTime start = DateTime.UtcNow;
+ do
+ {
+ switch (iteration)
+ {
+ case 0:
+ await FetchAllReferenceTypesAsync().ConfigureAwait(false);
+ IList result = await Session.NodeCache.FindReferencesAsync(testSet1, refTypeIds, false, true).ConfigureAwait(false);
+ break;
+ case 1:
+ IList result1 = await Session.NodeCache.FindAsync(testSet2).ConfigureAwait(false);
+ break;
+ case 2:
+ IList result2 = await Session.NodeCache.FetchNodesAsync(testSet3).ConfigureAwait(false);
+ string displayText = Session.NodeCache.GetDisplayText(result2[0]);
+ break;
+ case 3:
+ IList result3 = await Session.NodeCache.FindReferencesAsync(testSet1[0], refTypeIds[0], false, true).ConfigureAwait(false);
+ break;
+ case 4:
+ INode result4 = await Session.NodeCache.FindAsync(testSet2[0]).ConfigureAwait(false);
+ Assert.NotNull(result4);
+ Assert.True(result4 is VariableNode);
+ break;
+ case 5:
+ Node result5 = await Session.NodeCache.FetchNodeAsync(testSet3[0]).ConfigureAwait(false);
+ Assert.NotNull(result5);
+ Assert.True(result5 is VariableNode);
+ await Session.NodeCache.FetchSuperTypesAsync(result5.NodeId).ConfigureAwait(false);
+ break;
+ case 6:
+ string text = Session.NodeCache.GetDisplayText(testSet2[0]);
+ Assert.NotNull(text);
+ break;
+ case 7:
+ NodeId number = new NodeId((int)BuiltInType.Number);
+ bool isKnown = Session.NodeCache.IsKnown(new ExpandedNodeId((int)BuiltInType.Int64));
+ Assert.True(isKnown);
+ bool isKnown2 = Session.NodeCache.IsKnown(TestData.DataTypeIds.ScalarStructureDataType);
+ Assert.True(isKnown2);
+ NodeId nodeId = await Session.NodeCache.FindSuperTypeAsync(TestData.DataTypeIds.Vector).ConfigureAwait(false);
+ Assert.AreEqual(DataTypeIds.Structure, nodeId);
+ NodeId nodeId2 = await Session.NodeCache.FindSuperTypeAsync(ExpandedNodeId.ToNodeId(TestData.DataTypeIds.Vector, Session.NamespaceUris)).ConfigureAwait(false);
+ Assert.AreEqual(DataTypeIds.Structure, nodeId2);
+ IList subTypes = Session.NodeCache.FindSubTypes(new ExpandedNodeId((int)BuiltInType.Number));
+ bool isTypeOf = Session.NodeCache.IsTypeOf(new ExpandedNodeId((int)BuiltInType.Int32), new ExpandedNodeId((int)BuiltInType.Number));
+ bool isTypeOf2 = Session.NodeCache.IsTypeOf(new NodeId((int)BuiltInType.UInt32), number);
+ break;
+ case 8:
+ bool isEncodingOf = Session.NodeCache.IsEncodingOf(new ExpandedNodeId((int)BuiltInType.Int32), DataTypeIds.Structure);
+ Assert.False(isEncodingOf);
+ bool isEncodingFor = Session.NodeCache.IsEncodingFor(DataTypeIds.Structure,
+ new TestData.ScalarStructureDataType());
+ Assert.True(isEncodingFor);
+ bool isEncodingFor2 = Session.NodeCache.IsEncodingFor(new NodeId((int)BuiltInType.UInt32), new NodeId((int)BuiltInType.UInteger));
+ Assert.False(isEncodingFor2);
+ break;
+ case 9:
+ NodeId findDataTypeId = Session.NodeCache.FindDataTypeId(new ExpandedNodeId((int)Objects.DataTypeAttributes_Encoding_DefaultBinary));
+ NodeId findDataTypeId2 = Session.NodeCache.FindDataTypeId((int)Objects.DataTypeAttributes_Encoding_DefaultBinary);
+ break;
+ default:
+ Assert.Fail("Invalid test case");
+ break;
+ }
+ } while ((DateTime.UtcNow - start).TotalMilliseconds < testCaseRunTime);
+
+ });
+ taskList.Add(t);
+ }
+ await Task.WhenAll(taskList.ToArray()).ConfigureAwait(false);
+ }
+ #endregion
+
+ #region Benchmarks
+ #endregion
+
+ #region Private Methods
+ private void BrowseFullAddressSpace()
+ {
+ var requestHeader = new RequestHeader {
+ Timestamp = DateTime.UtcNow,
+ TimeoutHint = MaxTimeout
+ };
+
+ // Session
+ var clientTestServices = new ClientTestServices(Session);
+ ReferenceDescriptions = CommonTestWorkers.BrowseFullAddressSpaceWorker(clientTestServices, requestHeader);
+ }
+ #endregion
+ }
+}
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 9fd1f08c4..b47a447cc 100644
--- a/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj
+++ b/Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs
index 393fa0f50..da539119a 100644
--- a/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs
+++ b/Tests/Opc.Ua.Client.Tests/SubscriptionTest.cs
@@ -267,7 +267,7 @@ public async Task AddSubscriptionAsync()
await subscription.ConditionRefreshAsync().ConfigureAwait(false);
var sre = Assert.Throws(() => subscription.Republish(subscription.SequenceNumber));
- Assert.AreEqual(StatusCodes.BadMessageNotAvailable, sre.StatusCode);
+ Assert.AreEqual(StatusCodes.BadMessageNotAvailable, sre.StatusCode, $"Expected BadMessageNotAvailable, but received {sre.Message}");
subscription.RemoveItems(list);
await subscription.ApplyChangesAsync().ConfigureAwait(false);
diff --git a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs
index 1e42986c7..8e81fb75b 100644
--- a/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs
+++ b/Tests/Opc.Ua.Configuration.Tests/ApplicationInstanceTests.cs
@@ -533,6 +533,53 @@ public async Task TestInvalidAppCertChainDoNotRecreate(InvalidCertType certType,
}
}
}
+
+ ///
+ /// Test to verify that a new cert is not recreated/replaced if DisableCertificateAutoCreation is set.
+ ///
+ [Theory]
+ public async Task TestDisableCertificateAutoCreationAsync(bool server, bool disableCertificateAutoCreation)
+ {
+ // pki directory root for test runs.
+ var pkiRoot = Path.GetTempPath() + Path.GetRandomFileName() + Path.DirectorySeparatorChar;
+
+ var applicationInstance = new ApplicationInstance() {
+ ApplicationName = ApplicationName,
+ DisableCertificateAutoCreation = disableCertificateAutoCreation
+ };
+ Assert.NotNull(applicationInstance);
+ ApplicationConfiguration config;
+ if (server)
+ {
+ config = await applicationInstance.Build(ApplicationUri, ProductUri)
+ .AsServer(new string[] { "opc.tcp://localhost:12345/Configuration" })
+ .AddSecurityConfiguration(SubjectName, pkiRoot)
+ .Create().ConfigureAwait(false);
+ }
+ else
+ {
+ config = await applicationInstance.Build(ApplicationUri, ProductUri)
+ .AsClient()
+ .AddSecurityConfiguration(SubjectName, pkiRoot)
+ .Create().ConfigureAwait(false);
+ }
+ Assert.NotNull(config);
+
+ CertificateIdentifier applicationCertificate = applicationInstance.ApplicationConfiguration.SecurityConfiguration.ApplicationCertificate;
+ Assert.IsNull(applicationCertificate.Certificate);
+
+ if (disableCertificateAutoCreation)
+ {
+ var sre = Assert.ThrowsAsync(async () =>
+ await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false));
+ Assert.AreEqual(StatusCodes.BadConfigurationError, sre.StatusCode);
+ }
+ else
+ {
+ bool certOK = await applicationInstance.CheckApplicationInstanceCertificate(true, 0).ConfigureAwait(false);
+ Assert.True(certOK);
+ }
+ }
#endregion
#region Private Methods
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 c9a00a5d4..c3055ad6e 100644
--- a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj
+++ b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj
@@ -8,7 +8,7 @@
-
+
@@ -19,7 +19,7 @@
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 fbdf0a20b..74f56f5ea 100644
--- a/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj
+++ b/Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/Tests/Opc.Ua.Core.Tests/Types/Schemas/BinarySchemaWellKnownTests.cs b/Tests/Opc.Ua.Core.Tests/Types/Schemas/BinarySchemaWellKnownTests.cs
index 5a71e7bbd..fd9e59fac 100644
--- a/Tests/Opc.Ua.Core.Tests/Types/Schemas/BinarySchemaWellKnownTests.cs
+++ b/Tests/Opc.Ua.Core.Tests/Types/Schemas/BinarySchemaWellKnownTests.cs
@@ -2,7 +2,7 @@
* Copyright (c) 2005-2018 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
@@ -11,7 +11,7 @@
* 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,
@@ -65,14 +65,14 @@ public void LoadResources(string[] schemaData)
/// Load and validate well known resource type dictionaries.
///
[Theory]
- public async Task ValidateResources(string[] schemaData)
+ public void ValidateResources(string[] schemaData)
{
var assembly = typeof(BinarySchemaValidator).GetTypeInfo().Assembly;
var stream = assembly.GetManifestResourceStream(schemaData[1]);
Assert.IsNotNull(stream);
var schema = new BinarySchemaValidator();
Assert.IsNotNull(schema);
- await schema.Validate(stream).ConfigureAwait(false);
+ schema.Validate(stream);
Assert.IsNotNull(schema.Dictionary);
Assert.AreEqual(schemaData[0], schema.Dictionary.TargetNamespace);
}
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 4982627c6..7dd3d96be 100644
--- a/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj
+++ b/Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj
@@ -12,7 +12,7 @@
-
+
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 772e56873..026198c17 100644
--- a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj
+++ b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj
@@ -8,7 +8,7 @@
-
+
@@ -21,7 +21,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj b/Tests/Opc.Ua.Security.Certificates.Tests/Opc.Ua.Security.Certificates.Tests.csproj
index f557ad658..ed4c7912a 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
@@ -24,7 +24,7 @@
-
+
@@ -35,7 +35,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
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 cf1ea9b51..b1167ab94 100644
--- a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj
+++ b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj
@@ -8,7 +8,7 @@
-
+
@@ -20,7 +20,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs
index 92fb1afee..17deb7ee6 100644
--- a/Tests/Opc.Ua.Server.Tests/ServerFixture.cs
+++ b/Tests/Opc.Ua.Server.Tests/ServerFixture.cs
@@ -67,6 +67,8 @@ public async Task LoadConfiguration(string pkiRoot = null)
var serverConfig = Application.Build(
"urn:localhost:UA:" + typeof(T).Name,
"uri:opcfoundation.org:" + typeof(T).Name)
+ .SetMaxByteStringLength(4 * 1024 * 1024)
+ .SetMaxArrayLength(1024 * 1024)
.AsServer(
new string[] {
endpointUrl
@@ -99,6 +101,12 @@ public async Task LoadConfiguration(string pkiRoot = null)
MaxNodesPerWrite = 1000,
MaxNodesPerMethodCall = 1000,
MaxMonitoredItemsPerCall = 1000,
+ MaxNodesPerHistoryReadData = 1000,
+ MaxNodesPerHistoryReadEvents = 1000,
+ MaxNodesPerHistoryUpdateData = 1000,
+ MaxNodesPerHistoryUpdateEvents = 1000,
+ MaxNodesPerNodeManagement = 1000,
+ MaxNodesPerRegisterNodes = 1000,
MaxNodesPerTranslateBrowsePathsToNodeIds = 1000
});
}