From 67c2ba93c69611fe6a36399e33ef2be21f16470e Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Tue, 29 Aug 2023 16:22:49 +0200 Subject: [PATCH 01/20] Ref server sample: Subscribe to SimulationActive corrupts the variable (#2290) - only update the variable with new value if a new value is supplied by the underlying system --- .../TestData/TestDataSystem.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Applications/Quickstarts.Servers/TestData/TestDataSystem.cs b/Applications/Quickstarts.Servers/TestData/TestDataSystem.cs index c3fb68efd..3be7e7e53 100644 --- a/Applications/Quickstarts.Servers/TestData/TestDataSystem.cs +++ b/Applications/Quickstarts.Servers/TestData/TestDataSystem.cs @@ -971,14 +971,17 @@ void DoSample(object state) } else { - Sample sample = new Sample(); - - sample.Variable = variable; - sample.Value = ReadValue(sample.Variable); - sample.StatusCode = StatusCodes.Good; - sample.Timestamp = DateTime.UtcNow; - - samples.Enqueue(sample); + object value = ReadValue(variable); + if (value != null) + { + Sample sample = new Sample { + Variable = variable, + Value = value, + StatusCode = StatusCodes.Good, + Timestamp = DateTime.UtcNow + }; + samples.Enqueue(sample); + } } } } From 5d4e96231530988bf05c6fb0f489ea1fab70d21e Mon Sep 17 00:00:00 2001 From: bhnaphade Date: Sat, 2 Sep 2023 07:29:27 +0200 Subject: [PATCH 02/20] some TraceableSession functions do not await the activity (#2294) * added await for some TraceableSession functions * added missing configureAwait() --- Libraries/Opc.Ua.Client/TraceableSession.cs | 144 ++++++++++---------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/Libraries/Opc.Ua.Client/TraceableSession.cs b/Libraries/Opc.Ua.Client/TraceableSession.cs index 5b9c5a782..f9e6d0ecf 100644 --- a/Libraries/Opc.Ua.Client/TraceableSession.cs +++ b/Libraries/Opc.Ua.Client/TraceableSession.cs @@ -390,11 +390,11 @@ public ReferenceDescription FindDataDescription(NodeId encodingId) } /// - public Task FindDataDictionary(NodeId descriptionId) + public async Task FindDataDictionary(NodeId descriptionId) { using (Activity activity = ActivitySource.StartActivity(nameof(FindDataDictionary))) { - return m_session.FindDataDictionary(descriptionId); + return await m_session.FindDataDictionary(descriptionId).ConfigureAwait(false); } } @@ -926,11 +926,11 @@ public ResponseHeader EndCancel(IAsyncResult result, out uint cancelCount) } /// - public Task CancelAsync(RequestHeader requestHeader, uint requestHandle, CancellationToken ct) + public async Task CancelAsync(RequestHeader requestHeader, uint requestHandle, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(CancelAsync))) { - return m_session.CancelAsync(requestHeader, requestHandle, ct); + return await m_session.CancelAsync(requestHeader, requestHandle, ct).ConfigureAwait(false); } } @@ -956,11 +956,11 @@ public ResponseHeader EndAddNodes(IAsyncResult result, out AddNodesResultCollect } /// - public Task AddNodesAsync(RequestHeader requestHeader, AddNodesItemCollection nodesToAdd, CancellationToken ct) + public async Task AddNodesAsync(RequestHeader requestHeader, AddNodesItemCollection nodesToAdd, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(AddNodesAsync))) { - return m_session.AddNodesAsync(requestHeader, nodesToAdd, ct); + return await m_session.AddNodesAsync(requestHeader, nodesToAdd, ct).ConfigureAwait(false); } } @@ -986,11 +986,11 @@ public ResponseHeader EndAddReferences(IAsyncResult result, out StatusCodeCollec } /// - public Task AddReferencesAsync(RequestHeader requestHeader, AddReferencesItemCollection referencesToAdd, CancellationToken ct) + public async Task AddReferencesAsync(RequestHeader requestHeader, AddReferencesItemCollection referencesToAdd, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(AddReferencesAsync))) { - return m_session.AddReferencesAsync(requestHeader, referencesToAdd, ct); + return await m_session.AddReferencesAsync(requestHeader, referencesToAdd, ct).ConfigureAwait(false); } } @@ -1016,11 +1016,11 @@ public ResponseHeader EndDeleteNodes(IAsyncResult result, out StatusCodeCollecti } /// - public Task DeleteNodesAsync(RequestHeader requestHeader, DeleteNodesItemCollection nodesToDelete, CancellationToken ct) + public async Task DeleteNodesAsync(RequestHeader requestHeader, DeleteNodesItemCollection nodesToDelete, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(DeleteNodesAsync))) { - return m_session.DeleteNodesAsync(requestHeader, nodesToDelete, ct); + return await m_session.DeleteNodesAsync(requestHeader, nodesToDelete, ct).ConfigureAwait(false); } } @@ -1046,11 +1046,11 @@ public ResponseHeader EndDeleteReferences(IAsyncResult result, out StatusCodeCol } /// - public Task DeleteReferencesAsync(RequestHeader requestHeader, DeleteReferencesItemCollection referencesToDelete, CancellationToken ct) + public async Task DeleteReferencesAsync(RequestHeader requestHeader, DeleteReferencesItemCollection referencesToDelete, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(DeleteReferencesAsync))) { - return m_session.DeleteReferencesAsync(requestHeader, referencesToDelete, ct); + return await m_session.DeleteReferencesAsync(requestHeader, referencesToDelete, ct).ConfigureAwait(false); } } @@ -1076,11 +1076,11 @@ public ResponseHeader EndBrowse(IAsyncResult result, out BrowseResultCollection } /// - public Task BrowseAsync(RequestHeader requestHeader, ViewDescription view, uint requestedMaxReferencesPerNode, BrowseDescriptionCollection nodesToBrowse, CancellationToken ct) + public async Task BrowseAsync(RequestHeader requestHeader, ViewDescription view, uint requestedMaxReferencesPerNode, BrowseDescriptionCollection nodesToBrowse, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(BrowseAsync))) { - return m_session.BrowseAsync(requestHeader, view, requestedMaxReferencesPerNode, nodesToBrowse, ct); + return await m_session.BrowseAsync(requestHeader, view, requestedMaxReferencesPerNode, nodesToBrowse, ct).ConfigureAwait(false); } } @@ -1106,11 +1106,11 @@ public ResponseHeader EndBrowseNext(IAsyncResult result, out BrowseResultCollect } /// - public Task BrowseNextAsync(RequestHeader requestHeader, bool releaseContinuationPoints, ByteStringCollection continuationPoints, CancellationToken ct) + public async Task BrowseNextAsync(RequestHeader requestHeader, bool releaseContinuationPoints, ByteStringCollection continuationPoints, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(BrowseNextAsync))) { - return m_session.BrowseNextAsync(requestHeader, releaseContinuationPoints, continuationPoints, ct); + return await m_session.BrowseNextAsync(requestHeader, releaseContinuationPoints, continuationPoints, ct).ConfigureAwait(false); } } @@ -1136,11 +1136,11 @@ public ResponseHeader EndTranslateBrowsePathsToNodeIds(IAsyncResult result, out } /// - public Task TranslateBrowsePathsToNodeIdsAsync(RequestHeader requestHeader, BrowsePathCollection browsePaths, CancellationToken ct) + public async Task TranslateBrowsePathsToNodeIdsAsync(RequestHeader requestHeader, BrowsePathCollection browsePaths, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(TranslateBrowsePathsToNodeIdsAsync))) { - return m_session.TranslateBrowsePathsToNodeIdsAsync(requestHeader, browsePaths, ct); + return await m_session.TranslateBrowsePathsToNodeIdsAsync(requestHeader, browsePaths, ct).ConfigureAwait(false); } } @@ -1166,11 +1166,11 @@ public ResponseHeader EndRegisterNodes(IAsyncResult result, out NodeIdCollection } /// - public Task RegisterNodesAsync(RequestHeader requestHeader, NodeIdCollection nodesToRegister, CancellationToken ct) + public async Task RegisterNodesAsync(RequestHeader requestHeader, NodeIdCollection nodesToRegister, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(RegisterNodesAsync))) { - return m_session.RegisterNodesAsync(requestHeader, nodesToRegister, ct); + return await m_session.RegisterNodesAsync(requestHeader, nodesToRegister, ct).ConfigureAwait(false); } } @@ -1196,11 +1196,11 @@ public ResponseHeader EndUnregisterNodes(IAsyncResult result) } /// - public Task UnregisterNodesAsync(RequestHeader requestHeader, NodeIdCollection nodesToUnregister, CancellationToken ct) + public async Task UnregisterNodesAsync(RequestHeader requestHeader, NodeIdCollection nodesToUnregister, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(UnregisterNodesAsync))) { - return m_session.UnregisterNodesAsync(requestHeader, nodesToUnregister, ct); + return await m_session.UnregisterNodesAsync(requestHeader, nodesToUnregister, ct).ConfigureAwait(false); } } @@ -1226,11 +1226,11 @@ public ResponseHeader EndQueryFirst(IAsyncResult result, out QueryDataSetCollect } /// - public Task QueryFirstAsync(RequestHeader requestHeader, ViewDescription view, NodeTypeDescriptionCollection nodeTypes, ContentFilter filter, uint maxDataSetsToReturn, uint maxReferencesToReturn, CancellationToken ct) + public async Task QueryFirstAsync(RequestHeader requestHeader, ViewDescription view, NodeTypeDescriptionCollection nodeTypes, ContentFilter filter, uint maxDataSetsToReturn, uint maxReferencesToReturn, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(QueryFirstAsync))) { - return m_session.QueryFirstAsync(requestHeader, view, nodeTypes, filter, maxDataSetsToReturn, maxReferencesToReturn, ct); + return await m_session.QueryFirstAsync(requestHeader, view, nodeTypes, filter, maxDataSetsToReturn, maxReferencesToReturn, ct).ConfigureAwait(false); } } @@ -1256,11 +1256,11 @@ public ResponseHeader EndQueryNext(IAsyncResult result, out QueryDataSetCollecti } /// - public Task QueryNextAsync(RequestHeader requestHeader, bool releaseContinuationPoint, byte[] continuationPoint, CancellationToken ct) + public async Task QueryNextAsync(RequestHeader requestHeader, bool releaseContinuationPoint, byte[] continuationPoint, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(QueryNextAsync))) { - return m_session.QueryNextAsync(requestHeader, releaseContinuationPoint, continuationPoint, ct); + return await m_session.QueryNextAsync(requestHeader, releaseContinuationPoint, continuationPoint, ct).ConfigureAwait(false); } } @@ -1286,11 +1286,11 @@ public ResponseHeader EndRead(IAsyncResult result, out DataValueCollection resul } /// - public Task ReadAsync(RequestHeader requestHeader, double maxAge, TimestampsToReturn timestampsToReturn, ReadValueIdCollection nodesToRead, CancellationToken ct) + public async Task ReadAsync(RequestHeader requestHeader, double maxAge, TimestampsToReturn timestampsToReturn, ReadValueIdCollection nodesToRead, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(ReadAsync))) { - return m_session.ReadAsync(requestHeader, maxAge, timestampsToReturn, nodesToRead, ct); + return await m_session.ReadAsync(requestHeader, maxAge, timestampsToReturn, nodesToRead, ct).ConfigureAwait(false); } } @@ -1316,11 +1316,11 @@ public ResponseHeader EndHistoryRead(IAsyncResult result, out HistoryReadResultC } /// - public Task HistoryReadAsync(RequestHeader requestHeader, ExtensionObject historyReadDetails, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueIdCollection nodesToRead, CancellationToken ct) + public async Task HistoryReadAsync(RequestHeader requestHeader, ExtensionObject historyReadDetails, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, HistoryReadValueIdCollection nodesToRead, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(HistoryReadAsync))) { - return m_session.HistoryReadAsync(requestHeader, historyReadDetails, timestampsToReturn, releaseContinuationPoints, nodesToRead, ct); + return await m_session.HistoryReadAsync(requestHeader, historyReadDetails, timestampsToReturn, releaseContinuationPoints, nodesToRead, ct).ConfigureAwait(false); } } @@ -1346,11 +1346,11 @@ public ResponseHeader EndWrite(IAsyncResult result, out StatusCodeCollection res } /// - public Task WriteAsync(RequestHeader requestHeader, WriteValueCollection nodesToWrite, CancellationToken ct) + public async Task WriteAsync(RequestHeader requestHeader, WriteValueCollection nodesToWrite, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(WriteAsync))) { - return m_session.WriteAsync(requestHeader, nodesToWrite, ct); + return await m_session.WriteAsync(requestHeader, nodesToWrite, ct).ConfigureAwait(false); } } @@ -1376,11 +1376,11 @@ public ResponseHeader EndHistoryUpdate(IAsyncResult result, out HistoryUpdateRes } /// - public Task HistoryUpdateAsync(RequestHeader requestHeader, ExtensionObjectCollection historyUpdateDetails, CancellationToken ct) + public async Task HistoryUpdateAsync(RequestHeader requestHeader, ExtensionObjectCollection historyUpdateDetails, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(HistoryUpdateAsync))) { - return m_session.HistoryUpdateAsync(requestHeader, historyUpdateDetails, ct); + return await m_session.HistoryUpdateAsync(requestHeader, historyUpdateDetails, ct).ConfigureAwait(false); } } @@ -1406,11 +1406,11 @@ public ResponseHeader EndCall(IAsyncResult result, out CallMethodResultCollectio } /// - public Task CallAsync(RequestHeader requestHeader, CallMethodRequestCollection methodsToCall, CancellationToken ct) + public async Task CallAsync(RequestHeader requestHeader, CallMethodRequestCollection methodsToCall, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(CallAsync))) { - return m_session.CallAsync(requestHeader, methodsToCall, ct); + return await m_session.CallAsync(requestHeader, methodsToCall, ct).ConfigureAwait(false); } } @@ -1436,11 +1436,11 @@ public ResponseHeader EndCreateMonitoredItems(IAsyncResult result, out Monitored } /// - public Task CreateMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemCreateRequestCollection itemsToCreate, CancellationToken ct) + public async Task CreateMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemCreateRequestCollection itemsToCreate, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(CreateMonitoredItemsAsync))) { - return m_session.CreateMonitoredItemsAsync(requestHeader, subscriptionId, timestampsToReturn, itemsToCreate, ct); + return await m_session.CreateMonitoredItemsAsync(requestHeader, subscriptionId, timestampsToReturn, itemsToCreate, ct).ConfigureAwait(false); } } @@ -1466,11 +1466,11 @@ public ResponseHeader EndModifyMonitoredItems(IAsyncResult result, out Monitored } /// - public Task ModifyMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemModifyRequestCollection itemsToModify, CancellationToken ct) + public async Task ModifyMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, MonitoredItemModifyRequestCollection itemsToModify, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(ModifyMonitoredItemsAsync))) { - return m_session.ModifyMonitoredItemsAsync(requestHeader, subscriptionId, timestampsToReturn, itemsToModify, ct); + return await m_session.ModifyMonitoredItemsAsync(requestHeader, subscriptionId, timestampsToReturn, itemsToModify, ct).ConfigureAwait(false); } } @@ -1496,11 +1496,11 @@ public ResponseHeader EndSetMonitoringMode(IAsyncResult result, out StatusCodeCo } /// - public Task SetMonitoringModeAsync(RequestHeader requestHeader, uint subscriptionId, MonitoringMode monitoringMode, UInt32Collection monitoredItemIds, CancellationToken ct) + public async Task SetMonitoringModeAsync(RequestHeader requestHeader, uint subscriptionId, MonitoringMode monitoringMode, UInt32Collection monitoredItemIds, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(SetMonitoringModeAsync))) { - return m_session.SetMonitoringModeAsync(requestHeader, subscriptionId, monitoringMode, monitoredItemIds, ct); + return await m_session.SetMonitoringModeAsync(requestHeader, subscriptionId, monitoringMode, monitoredItemIds, ct).ConfigureAwait(false); } } @@ -1526,11 +1526,11 @@ public ResponseHeader EndSetTriggering(IAsyncResult result, out StatusCodeCollec } /// - public Task SetTriggeringAsync(RequestHeader requestHeader, uint subscriptionId, uint triggeringItemId, UInt32Collection linksToAdd, UInt32Collection linksToRemove, CancellationToken ct) + public async Task SetTriggeringAsync(RequestHeader requestHeader, uint subscriptionId, uint triggeringItemId, UInt32Collection linksToAdd, UInt32Collection linksToRemove, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(SetTriggeringAsync))) { - return m_session.SetTriggeringAsync(requestHeader, subscriptionId, triggeringItemId, linksToAdd, linksToRemove, ct); + return await m_session.SetTriggeringAsync(requestHeader, subscriptionId, triggeringItemId, linksToAdd, linksToRemove, ct).ConfigureAwait(false); } } @@ -1556,11 +1556,11 @@ public ResponseHeader EndDeleteMonitoredItems(IAsyncResult result, out StatusCod } /// - public Task DeleteMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, UInt32Collection monitoredItemIds, CancellationToken ct) + public async Task DeleteMonitoredItemsAsync(RequestHeader requestHeader, uint subscriptionId, UInt32Collection monitoredItemIds, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(DeleteMonitoredItemsAsync))) { - return m_session.DeleteMonitoredItemsAsync(requestHeader, subscriptionId, monitoredItemIds, ct); + return await m_session.DeleteMonitoredItemsAsync(requestHeader, subscriptionId, monitoredItemIds, ct).ConfigureAwait(false); } } @@ -1586,11 +1586,11 @@ public ResponseHeader EndCreateSubscription(IAsyncResult result, out uint subscr } /// - public Task CreateSubscriptionAsync(RequestHeader requestHeader, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, bool publishingEnabled, byte priority, CancellationToken ct) + public async Task CreateSubscriptionAsync(RequestHeader requestHeader, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, bool publishingEnabled, byte priority, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(CreateSubscriptionAsync))) { - return m_session.CreateSubscriptionAsync(requestHeader, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, publishingEnabled, priority, ct); + return await m_session.CreateSubscriptionAsync(requestHeader, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, publishingEnabled, priority, ct).ConfigureAwait(false); } } @@ -1616,11 +1616,11 @@ public ResponseHeader EndModifySubscription(IAsyncResult result, out double revi } /// - public Task ModifySubscriptionAsync(RequestHeader requestHeader, uint subscriptionId, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, byte priority, CancellationToken ct) + public async Task ModifySubscriptionAsync(RequestHeader requestHeader, uint subscriptionId, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, byte priority, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(ModifySubscriptionAsync))) { - return m_session.ModifySubscriptionAsync(requestHeader, subscriptionId, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, priority, ct); + return await m_session.ModifySubscriptionAsync(requestHeader, subscriptionId, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, priority, ct).ConfigureAwait(false); } } @@ -1646,11 +1646,11 @@ public ResponseHeader EndSetPublishingMode(IAsyncResult result, out StatusCodeCo } /// - public Task SetPublishingModeAsync(RequestHeader requestHeader, bool publishingEnabled, UInt32Collection subscriptionIds, CancellationToken ct) + public async Task SetPublishingModeAsync(RequestHeader requestHeader, bool publishingEnabled, UInt32Collection subscriptionIds, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(SetPublishingModeAsync))) { - return m_session.SetPublishingModeAsync(requestHeader, publishingEnabled, subscriptionIds, ct); + return await m_session.SetPublishingModeAsync(requestHeader, publishingEnabled, subscriptionIds, ct).ConfigureAwait(false); } } @@ -1676,11 +1676,11 @@ public ResponseHeader EndPublish(IAsyncResult result, out uint subscriptionId, o } /// - public Task PublishAsync(RequestHeader requestHeader, SubscriptionAcknowledgementCollection subscriptionAcknowledgements, CancellationToken ct) + public async Task PublishAsync(RequestHeader requestHeader, SubscriptionAcknowledgementCollection subscriptionAcknowledgements, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(PublishAsync))) { - return m_session.PublishAsync(requestHeader, subscriptionAcknowledgements, ct); + return await m_session.PublishAsync(requestHeader, subscriptionAcknowledgements, ct).ConfigureAwait(false); } } @@ -1706,11 +1706,11 @@ public ResponseHeader EndRepublish(IAsyncResult result, out NotificationMessage } /// - public Task RepublishAsync(RequestHeader requestHeader, uint subscriptionId, uint retransmitSequenceNumber, CancellationToken ct) + public async Task RepublishAsync(RequestHeader requestHeader, uint subscriptionId, uint retransmitSequenceNumber, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(RepublishAsync))) { - return m_session.RepublishAsync(requestHeader, subscriptionId, retransmitSequenceNumber, ct); + return await m_session.RepublishAsync(requestHeader, subscriptionId, retransmitSequenceNumber, ct).ConfigureAwait(false); } } @@ -1736,11 +1736,11 @@ public ResponseHeader EndTransferSubscriptions(IAsyncResult result, out Transfer } /// - public Task TransferSubscriptionsAsync(RequestHeader requestHeader, UInt32Collection subscriptionIds, bool sendInitialValues, CancellationToken ct) + public async Task TransferSubscriptionsAsync(RequestHeader requestHeader, UInt32Collection subscriptionIds, bool sendInitialValues, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(TransferSubscriptionsAsync))) { - return m_session.TransferSubscriptionsAsync(requestHeader, subscriptionIds, sendInitialValues, ct); + return await m_session.TransferSubscriptionsAsync(requestHeader, subscriptionIds, sendInitialValues, ct).ConfigureAwait(false); } } @@ -1766,11 +1766,11 @@ public ResponseHeader EndDeleteSubscriptions(IAsyncResult result, out StatusCode } /// - public Task DeleteSubscriptionsAsync(RequestHeader requestHeader, UInt32Collection subscriptionIds, CancellationToken ct) + public async Task DeleteSubscriptionsAsync(RequestHeader requestHeader, UInt32Collection subscriptionIds, CancellationToken ct) { using (Activity activity = ActivitySource.StartActivity(nameof(DeleteSubscriptionsAsync))) { - return m_session.DeleteSubscriptionsAsync(requestHeader, subscriptionIds, ct); + return await m_session.DeleteSubscriptionsAsync(requestHeader, subscriptionIds, ct).ConfigureAwait(false); } } @@ -1856,47 +1856,47 @@ public bool ReactivateSubscriptions(SubscriptionCollection subscriptions, bool s } /// - public Task RemoveSubscriptionAsync(Subscription subscription, CancellationToken ct = default) + public async Task RemoveSubscriptionAsync(Subscription subscription, CancellationToken ct = default) { using (Activity activity = ActivitySource.StartActivity(nameof(RemoveSubscriptionAsync))) { - return m_session.RemoveSubscriptionAsync(subscription, ct); + return await m_session.RemoveSubscriptionAsync(subscription, ct).ConfigureAwait(false); } } /// - public Task RemoveSubscriptionsAsync(IEnumerable subscriptions, CancellationToken ct = default) + public async Task RemoveSubscriptionsAsync(IEnumerable subscriptions, CancellationToken ct = default) { using (Activity activity = ActivitySource.StartActivity(nameof(RemoveSubscriptionsAsync))) { - return m_session.RemoveSubscriptionsAsync(subscriptions, ct); + return await m_session.RemoveSubscriptionsAsync(subscriptions, ct).ConfigureAwait(false); } } /// - public Task ReactivateSubscriptionsAsync(SubscriptionCollection subscriptions, bool sendInitialValues, CancellationToken ct = default) + public async Task ReactivateSubscriptionsAsync(SubscriptionCollection subscriptions, bool sendInitialValues, CancellationToken ct = default) { using (Activity activity = ActivitySource.StartActivity(nameof(ReactivateSubscriptionsAsync))) { - return m_session.ReactivateSubscriptionsAsync(subscriptions, sendInitialValues, ct); + return await m_session.ReactivateSubscriptionsAsync(subscriptions, sendInitialValues, ct).ConfigureAwait(false); } } /// - public Task TransferSubscriptionsAsync(SubscriptionCollection subscriptions, bool sendInitialValues, CancellationToken ct = default) + public async Task TransferSubscriptionsAsync(SubscriptionCollection subscriptions, bool sendInitialValues, CancellationToken ct = default) { using (Activity activity = ActivitySource.StartActivity(nameof(TransferSubscriptionsAsync))) { - return m_session.TransferSubscriptionsAsync(subscriptions, sendInitialValues, ct); + return await m_session.TransferSubscriptionsAsync(subscriptions, sendInitialValues, ct).ConfigureAwait(false); } } /// - public Task> CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct = default, params object[] args) + public async Task> CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct = default, params object[] args) { using (Activity activity = ActivitySource.StartActivity(nameof(CallAsync))) { - return m_session.CallAsync(objectId, methodId, ct, args); + return await m_session.CallAsync(objectId, methodId, ct, args).ConfigureAwait(false); } } @@ -1910,11 +1910,11 @@ public bool ResendData(IEnumerable subscriptions, out IList - public Task<(bool, IList)> ResendDataAsync(IEnumerable subscriptions, CancellationToken ct = default) + public async Task<(bool, IList)> ResendDataAsync(IEnumerable subscriptions, CancellationToken ct = default) { using (Activity activity = ActivitySource.StartActivity(nameof(ResendDataAsync))) { - return m_session.ResendDataAsync(subscriptions, ct); + return await m_session.ResendDataAsync(subscriptions, ct).ConfigureAwait(false); } } #endregion From 6879cc8e8c4f3d0bec6f3cf0dd02e4db63046061 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Sep 2023 17:33:42 +0300 Subject: [PATCH 03/20] Bump Microsoft.NET.Test.Sdk from 17.7.1 to 17.7.2 (#2295) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.7.1 to 17.7.2. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.7.1...v17.7.2) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Opc.Ua.Client.ComplexTypes.Tests.csproj | 2 +- Tests/Opc.Ua.Client.Tests/Opc.Ua.Client.Tests.csproj | 2 +- .../Opc.Ua.Configuration.Tests.csproj | 2 +- Tests/Opc.Ua.Core.Tests/Opc.Ua.Core.Tests.csproj | 2 +- Tests/Opc.Ua.Gds.Tests/Opc.Ua.Gds.Tests.csproj | 2 +- Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj | 2 +- .../Opc.Ua.Security.Certificates.Tests.csproj | 2 +- Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) 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.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.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj index c9a00a5d4..77671817c 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 @@ - + 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.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..baad34031 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 @@ - + 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..de6c297a3 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 @@ - + 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..f91a32b31 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 @@ - + From 4f452ecd20f7bb8332312b9af714784d72126289 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:39:35 +0300 Subject: [PATCH 04/20] Bump BenchmarkDotNet from 0.13.7 to 0.13.8 (#2304) Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.7 to 0.13.8. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.7...v0.13.8) --- updated-dependencies: - dependency-name: BenchmarkDotNet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Opc.Ua.Configuration.Tests.csproj | 2 +- Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj | 2 +- .../Opc.Ua.Security.Certificates.Tests.csproj | 2 +- Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 77671817c..3aac140ee 100644 --- a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj +++ b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + 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 baad34031..4a13cf5ac 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj +++ b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj @@ -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 de6c297a3..45f0d6d3b 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 @@ -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 f91a32b31..55d2b0fb3 100644 --- a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj +++ b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj @@ -20,7 +20,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 2276c43b61c31615b65b5bb10edbc638dcd08af0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 13 Sep 2023 15:41:58 +0300 Subject: [PATCH 05/20] Bump actions/checkout from 3 to 4 (#2305) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/buildandtest.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/docker-image.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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..79dc7971c 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 From 13f36a858c12f8d0f4a27e59679b18655e558a32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:08:47 +0300 Subject: [PATCH 06/20] Bump docker/login-action from 2 to 3 (#2312) Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 79dc7971c..a4f77770f 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -63,7 +63,7 @@ jobs: # 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 }} From 2f6754335680ba563e86b97afdf1821bc28edad6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:10:39 +0300 Subject: [PATCH 07/20] Bump docker/setup-qemu-action from 2 to 3 (#2314) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index a4f77770f..540313b95 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -98,7 +98,7 @@ jobs: 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 From 49bc6a94766c13e51df3e6019027b4b3cf9caabb Mon Sep 17 00:00:00 2001 From: Suciu Mircea Adrian Date: Wed, 20 Sep 2023 09:51:53 +0300 Subject: [PATCH 08/20] Bump to MQTTnet v4.3.0.858 and replace deprecated call. (#2298) * Replaced obsolete WithTls method call with WithTlsOptions method call as MQTTnet v4.3.0.858 recommends. * Dispose mqtt client certificates --- Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj | 2 +- .../Transport/MqttPubSubConnection.cs | 69 ++++++++++++------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj index a9f12be74..7d70dcf8c 100644 --- a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj +++ b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj @@ -36,7 +36,7 @@ - + diff --git a/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs b/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs index 845a51787..baa6c589e 100644 --- a/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs +++ b/Libraries/Opc.Ua.PubSub/Transport/MqttPubSubConnection.cs @@ -433,29 +433,38 @@ protected override async Task InternalStop() var publisherMqttClient = m_publisherMqttClient; var subscriberMqttClient = m_subscriberMqttClient; - if (publisherMqttClient != null) + void DisposeCerts(X509CertificateCollection certificates) { - if (publisherMqttClient.IsConnected) + if (certificates != null) { - await publisherMqttClient.DisconnectAsync().ContinueWith((e) => publisherMqttClient.Dispose()).ConfigureAwait(false); - } - else - { - publisherMqttClient.Dispose(); + // dispose certificates + foreach (var cert in certificates) + { + Utils.SilentDispose(cert); + } } } - - if (subscriberMqttClient != null) + async Task InternalStop(IMqttClient client) { - if (subscriberMqttClient.IsConnected) + if (client != null) { - await subscriberMqttClient.DisconnectAsync().ContinueWith((e) => subscriberMqttClient.Dispose()).ConfigureAwait(false); - } - else - { - subscriberMqttClient.Dispose(); + X509CertificateCollection certificates = client.Options?.ChannelOptions?.TlsOptions?.ClientCertificatesProvider?.GetCertificates(); + if (client.IsConnected) + { + await client.DisconnectAsync().ContinueWith((e) => { + DisposeCerts(certificates); + Utils.SilentDispose(client); + }).ConfigureAwait(false); + } + else + { + DisposeCerts(certificates); + Utils.SilentDispose(client); + } } } + await InternalStop(publisherMqttClient).ConfigureAwait(false); + await InternalStop(subscriberMqttClient).ConfigureAwait(false); if (m_metaDataPublishers != null) { @@ -670,23 +679,31 @@ private MqttClientOptions GetMqttClientOptions() MqttTlsOptions mqttTlsOptions = ((MqttClientProtocolConfiguration)transportProtocolConfiguration).MqttTlsOptions; + List x509Certificate2s = new List(); + if (mqttTlsOptions?.Certificates != null) + { + foreach (var x509cert in mqttTlsOptions?.Certificates.X509Certificates) + { + x509Certificate2s.Add(new X509Certificate2(x509cert.Handle)); + } + } + MqttClientOptionsBuilder mqttClientOptionsBuilder = new MqttClientOptionsBuilder() .WithTcpServer(m_brokerHostName, m_brokerPort) .WithKeepAlivePeriod(mqttKeepAlive) .WithProtocolVersion(mqttProtocolVersion) .WithClientId(clientId) - .WithTls(new MqttClientOptionsBuilderTlsParameters { - UseTls = true, - Certificates = mqttTlsOptions?.Certificates?.X509Certificates, - SslProtocol = - mqttTlsOptions?.SslProtocolVersion ?? - System.Security.Authentication.SslProtocols.Tls12, - AllowUntrustedCertificates = mqttTlsOptions?.AllowUntrustedCertificates ?? false, - IgnoreCertificateChainErrors = mqttTlsOptions?.IgnoreCertificateChainErrors ?? false, - IgnoreCertificateRevocationErrors = mqttTlsOptions?.IgnoreRevocationListErrors ?? false, - CertificateValidationHandler = ValidateBrokerCertificate + .WithTlsOptions(o => { + o.UseTls(true); + o.WithClientCertificates(x509Certificate2s); + o.WithSslProtocols(mqttTlsOptions?.SslProtocolVersion ?? + System.Security.Authentication.SslProtocols.None);// Allow OS to choose best option + o.WithAllowUntrustedCertificates(mqttTlsOptions?.AllowUntrustedCertificates ?? false); + o.WithIgnoreCertificateChainErrors(mqttTlsOptions?.IgnoreCertificateChainErrors ?? false); + o.WithIgnoreCertificateRevocationErrors(mqttTlsOptions?.IgnoreRevocationListErrors ?? false); + o.WithCertificateValidationHandler(ValidateBrokerCertificate); }); - + // Set user credentials. if (mqttProtocolConfiguration.UseCredentials) { From 229b124f80ac079bf8e6317d3ec36cee7c7577fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Sep 2023 10:51:37 +0300 Subject: [PATCH 09/20] Bump MQTTnet from 4.2.1.781 to 4.3.1.873 (#2303) Bumps [MQTTnet](https://github.com/dotnet/MQTTnet) from 4.2.1.781 to 4.3.1.873. - [Release notes](https://github.com/dotnet/MQTTnet/releases) - [Commits](https://github.com/dotnet/MQTTnet/compare/v4.2.1.781...v4.3.1.873) --- updated-dependencies: - dependency-name: MQTTnet dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj index 7d70dcf8c..c9bd5abb6 100644 --- a/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj +++ b/Libraries/Opc.Ua.PubSub/Opc.Ua.PubSub.csproj @@ -1,4 +1,4 @@ - + Opc.Ua.PubSub @@ -36,7 +36,7 @@ - + From 5af60ef6b6e4dc1872f9738718ab6621738c68f5 Mon Sep 17 00:00:00 2001 From: Martin Regen Date: Fri, 22 Sep 2023 07:45:47 +0200 Subject: [PATCH 10/20] Make async service call use awaitable, session open improvements (#2318) - Improvements when many sessions need to be opened - Introduce a OpenAsync version which usesmore async calls for session creation - add async interfaces to cert validator - refactor lock of encodeable factory - add a `EndAsync` property to WriteRequest to allow to await a service call - add a `EndSendRequestAsync` method to allow implementation of awaitable service calls --- Libraries/Opc.Ua.Client/ISession.cs | 29 + Libraries/Opc.Ua.Client/NodeCache.cs | 20 +- Libraries/Opc.Ua.Client/Session.cs | 585 ++++++++++-------- Libraries/Opc.Ua.Client/SessionAsync.cs | 279 ++++++++- Libraries/Opc.Ua.Client/TraceableSession.cs | 28 + .../Stack/Https/HttpsTransportChannel.cs | 6 + .../Certificates/CertificateValidator.cs | 417 +++++++++---- .../Certificates/ICertificateValidator.cs | 11 + Stack/Opc.Ua.Core/Stack/Client/ClientBase.cs | 16 + .../Opc.Ua.Core/Stack/Client/UaChannelBase.cs | 13 + .../Configuration/ConfiguredEndpoints.cs | 2 +- .../Stack/Tcp/ChannelAsyncOperation.cs | 112 +++- .../Stack/Tcp/UaSCBinaryClientChannel.cs | 132 +++- .../Stack/Tcp/UaSCBinaryTransportChannel.cs | 23 +- .../Stack/Transport/ITransportChannel.cs | 10 + .../Types/Encoders/EncodeableFactory.cs | 253 +++++--- .../Types/Encoders/IEncodeableFactory.cs | 10 +- 17 files changed, 1423 insertions(+), 523 deletions(-) diff --git a/Libraries/Opc.Ua.Client/ISession.cs b/Libraries/Opc.Ua.Client/ISession.cs index ff37758db..44cbcce04 100644 --- a/Libraries/Opc.Ua.Client/ISession.cs +++ b/Libraries/Opc.Ua.Client/ISession.cs @@ -547,6 +547,35 @@ public interface ISession : ISessionClient, IDisposable void ReadDisplayName(IList nodeIds, out IList displayNames, out IList errors); #if (CLIENT_ASYNC) + /// + /// Establishes a session with the server. + /// + /// The name to assign to the session. + /// The user identity. + /// The cancellation token. + Task OpenAsync(string sessionName, IUserIdentity identity, CancellationToken ct); + + /// + /// Establishes a session with the server. + /// + /// The name to assign to the session. + /// The session timeout. + /// The user identity. + /// The list of preferred locales. + /// The cancellation token. + Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, CancellationToken ct); + + /// + /// Establishes a session with the server. + /// + /// The name to assign to the session. + /// The session timeout. + /// The user identity. + /// The list of preferred locales. + /// If set to true then the domain in the certificate must match the endpoint used. + /// The cancellation token. + Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, bool checkDomain, CancellationToken ct); + /// /// Reads the values for the node attributes and returns a node object collection. /// diff --git a/Libraries/Opc.Ua.Client/NodeCache.cs b/Libraries/Opc.Ua.Client/NodeCache.cs index 2f57f4491..bbe9163c6 100644 --- a/Libraries/Opc.Ua.Client/NodeCache.cs +++ b/Libraries/Opc.Ua.Client/NodeCache.cs @@ -38,7 +38,7 @@ namespace Opc.Ua.Client /// /// An implementation of a client side nodecache. /// - public class NodeCache : INodeCache + public class NodeCache : INodeCache, IDisposable { #region Constructors /// @@ -54,17 +54,27 @@ public NodeCache(ISession session) m_uaTypesLoaded = false; m_cacheLock = new ReaderWriterLockSlim(); } + #endregion + #region IDisposable /// - /// Destructor to clean up. + /// An overrideable version of the Dispose. /// - ~NodeCache() + protected virtual void Dispose(bool disposing) { - if (m_cacheLock != null) + if (disposing) { - m_cacheLock.Dispose(); + m_session = null; + m_cacheLock?.Dispose(); } } + + /// + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } #endregion #region INodeTable Members diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index 84c2bcad8..f6092f521 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -1054,7 +1054,7 @@ public static async Task Create( // create the session. try { - session.Open(sessionName, sessionTimeout, identity, preferredLocales, checkDomain); + await session.OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, checkDomain, ct).ConfigureAwait(false); } catch (Exception) { @@ -1111,7 +1111,8 @@ public static async Task Create( await endpoint.UpdateFromServerAsync( endpoint.EndpointUrl, connection, endpoint.Description.SecurityMode, - endpoint.Description.SecurityPolicyUri).ConfigureAwait(false); + endpoint.Description.SecurityPolicyUri, + ct).ConfigureAwait(false); updateBeforeConnect = false; connection = null; } @@ -1606,7 +1607,7 @@ public void FetchNamespaceTables() nodesToRead.Add(valueId); // read from server. - ResponseHeader responseHeader = this.Read( + ResponseHeader responseHeader = base.Read( null, 0, TimestampsToReturn.Neither, @@ -2382,59 +2383,7 @@ public void Open( IList preferredLocales, bool checkDomain) { - // check connection state. - lock (SyncRoot) - { - if (Connected) - { - throw new ServiceResultException(StatusCodes.BadInvalidState, "Already connected to server."); - } - } - - string securityPolicyUri = m_endpoint.Description.SecurityPolicyUri; - - // catch security policies which are not supported by core - if (SecurityPolicies.GetDisplayName(securityPolicyUri) == null) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "The chosen security policy is not supported by the client to connect to the server."); - } - - // get the identity token. - if (identity == null) - { - identity = new UserIdentity(); - } - - // get identity token. - UserIdentityToken identityToken = identity.GetIdentityToken(); - - // check that the user identity is supported by the endpoint. - UserTokenPolicy identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identityToken.PolicyId); - - if (identityPolicy == null) - { - // try looking up by TokenType if the policy id was not found. - identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType); - - if (identityPolicy == null) - { - throw ServiceResultException.Create( - StatusCodes.BadUserAccessDenied, - "Endpoint does not support the user identity type provided."); - } - - identityToken.PolicyId = identityPolicy.PolicyId; - } - - bool requireEncryption = securityPolicyUri != SecurityPolicies.None; - - if (!requireEncryption) - { - requireEncryption = identityPolicy.SecurityPolicyUri != SecurityPolicies.None && - !String.IsNullOrEmpty(identityPolicy.SecurityPolicyUri); - } + OpenValidateIdentity(ref identity, out var identityToken, out var identityPolicy, out string securityPolicyUri, out bool requireEncryption); // validate the server certificate /certificate chain. X509Certificate2 serverCertificate = null; @@ -2476,27 +2425,14 @@ public void Open( SignedSoftwareCertificateCollection serverSoftwareCertificates = null; // send the application instance certificate for the client. - byte[] clientCertificateData = m_instanceCertificate != null ? m_instanceCertificate.RawData : null; - byte[] clientCertificateChainData = null; - - if (m_instanceCertificateChain != null && m_instanceCertificateChain.Count > 0 && m_configuration.SecurityConfiguration.SendCertificateChain) - { - List clientCertificateChain = new List(); + BuildCertificateData(out byte[] clientCertificateData, out byte[] clientCertificateChainData); - for (int i = 0; i < m_instanceCertificateChain.Count; i++) - { - clientCertificateChain.AddRange(m_instanceCertificateChain[i].RawData); - } - - clientCertificateChainData = clientCertificateChain.ToArray(); - } - - ApplicationDescription clientDescription = new ApplicationDescription(); - - clientDescription.ApplicationUri = m_configuration.ApplicationUri; - clientDescription.ApplicationName = m_configuration.ApplicationName; - clientDescription.ApplicationType = ApplicationType.Client; - clientDescription.ProductUri = m_configuration.ProductUri; + ApplicationDescription clientDescription = new ApplicationDescription { + ApplicationUri = m_configuration.ApplicationUri, + ApplicationName = m_configuration.ApplicationName, + ApplicationType = ApplicationType.Client, + ProductUri = m_configuration.ProductUri + }; if (sessionTimeout == 0) { @@ -2561,6 +2497,7 @@ public void Open( out serverSignature, out m_maxRequestMessageSize); } + // save session id. lock (SyncRoot) { @@ -2575,200 +2512,16 @@ public void Open( try { // verify that the server returned the same instance certificate. - if (serverCertificateData != null && - m_endpoint.Description.ServerCertificate != null && - !Utils.IsEqual(serverCertificateData, m_endpoint.Description.ServerCertificate)) - { - try - { - // verify for certificate chain in endpoint. - X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(m_endpoint.Description.ServerCertificate); + ValidateServerCertificateData(serverCertificateData); - if (serverCertificateChain.Count > 0 && !Utils.IsEqual(serverCertificateData, serverCertificateChain[0].RawData)) - { - throw ServiceResultException.Create( - StatusCodes.BadCertificateInvalid, - "Server did not return the certificate used to create the secure channel."); - } - } - catch (Exception) - { - throw ServiceResultException.Create( - StatusCodes.BadCertificateInvalid, - "Server did not return the certificate used to create the secure channel."); - } - } + ValidateServerEndpoints(serverEndpoints); - if (serverSignature == null || serverSignature.Signature == null) - { - Utils.LogInfo("Server signature is null or empty."); - - //throw ServiceResultException.Create( - // StatusCodes.BadSecurityChecksFailed, - // "Server signature is null or empty."); - } - - if (m_discoveryServerEndpoints != null && m_discoveryServerEndpoints.Count > 0) - { - // Compare EndpointDescriptions returned at GetEndpoints with values returned at CreateSession - EndpointDescriptionCollection expectedServerEndpoints = null; + ValidateServerSignature(serverCertificate, serverSignature, clientCertificateData, clientCertificateChainData, clientNonce); - if (serverEndpoints != null && - m_discoveryProfileUris != null && m_discoveryProfileUris.Count > 0) - { - // Select EndpointDescriptions with a transportProfileUri that matches the - // profileUris specified in the original GetEndpoints() request. - expectedServerEndpoints = new EndpointDescriptionCollection(); - - foreach (EndpointDescription serverEndpoint in serverEndpoints) - { - if (m_discoveryProfileUris.Contains(serverEndpoint.TransportProfileUri)) - { - expectedServerEndpoints.Add(serverEndpoint); - } - } - } - else - { - expectedServerEndpoints = serverEndpoints; - } - - if (expectedServerEndpoints == null || - m_discoveryServerEndpoints.Count != expectedServerEndpoints.Count) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "Server did not return a number of ServerEndpoints that matches the one from GetEndpoints."); - } - - for (int ii = 0; ii < expectedServerEndpoints.Count; ii++) - { - EndpointDescription serverEndpoint = expectedServerEndpoints[ii]; - EndpointDescription expectedServerEndpoint = m_discoveryServerEndpoints[ii]; - - if (serverEndpoint.SecurityMode != expectedServerEndpoint.SecurityMode || - serverEndpoint.SecurityPolicyUri != expectedServerEndpoint.SecurityPolicyUri || - serverEndpoint.TransportProfileUri != expectedServerEndpoint.TransportProfileUri || - serverEndpoint.SecurityLevel != expectedServerEndpoint.SecurityLevel) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "The list of ServerEndpoints returned at CreateSession does not match the list from GetEndpoints."); - } - - if (serverEndpoint.UserIdentityTokens.Count != expectedServerEndpoint.UserIdentityTokens.Count) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints."); - } - - for (int jj = 0; jj < serverEndpoint.UserIdentityTokens.Count; jj++) - { - if (!serverEndpoint.UserIdentityTokens[jj].IsEqual(expectedServerEndpoint.UserIdentityTokens[jj])) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints."); - } - } - } - } - - // find the matching description (TBD - check domains against certificate). - bool found = false; - Uri expectedUrl = Utils.ParseUri(m_endpoint.Description.EndpointUrl); - - if (expectedUrl != null) - { - for (int ii = 0; ii < serverEndpoints.Count; ii++) - { - EndpointDescription serverEndpoint = serverEndpoints[ii]; - Uri actualUrl = Utils.ParseUri(serverEndpoint.EndpointUrl); - - if (actualUrl != null && actualUrl.Scheme == expectedUrl.Scheme) - { - if (serverEndpoint.SecurityPolicyUri == m_endpoint.Description.SecurityPolicyUri) - { - if (serverEndpoint.SecurityMode == m_endpoint.Description.SecurityMode) - { - // ensure endpoint has up to date information. - m_endpoint.Description.Server.ApplicationName = serverEndpoint.Server.ApplicationName; - m_endpoint.Description.Server.ApplicationUri = serverEndpoint.Server.ApplicationUri; - m_endpoint.Description.Server.ApplicationType = serverEndpoint.Server.ApplicationType; - m_endpoint.Description.Server.ProductUri = serverEndpoint.Server.ProductUri; - m_endpoint.Description.TransportProfileUri = serverEndpoint.TransportProfileUri; - m_endpoint.Description.UserIdentityTokens = serverEndpoint.UserIdentityTokens; - - found = true; - break; - } - } - } - } - } - - // could be a security risk. - if (!found) - { - throw ServiceResultException.Create( - StatusCodes.BadSecurityChecksFailed, - "Server did not return an EndpointDescription that matched the one used to create the secure channel."); - } - - // validate the server's signature. - byte[] dataToSign = Utils.Append(clientCertificateData, clientNonce); - - if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature)) - { - // validate the signature with complete chain if the check with leaf certificate failed. - if (clientCertificateChainData != null) - { - dataToSign = Utils.Append(clientCertificateChainData, clientNonce); - - if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature)) - { - throw ServiceResultException.Create( - StatusCodes.BadApplicationSignatureInvalid, - "Server did not provide a correct signature for the nonce data provided by the client."); - } - } - else - { - throw ServiceResultException.Create( - StatusCodes.BadApplicationSignatureInvalid, - "Server did not provide a correct signature for the nonce data provided by the client."); - } - } - - // get a validator to check certificates provided by server. - CertificateValidator validator = m_configuration.CertificateValidator; - - // validate software certificates. - List softwareCertificates = new List(); - - foreach (SignedSoftwareCertificate signedCertificate in serverSoftwareCertificates) - { - SoftwareCertificate softwareCertificate = null; - - ServiceResult result = SoftwareCertificate.Validate( - validator, - signedCertificate.CertificateData, - out softwareCertificate); - - if (ServiceResult.IsBad(result)) - { - OnSoftwareCertificateError(signedCertificate, result); - } - - softwareCertificates.Add(softwareCertificate); - } - - // check if software certificates meet application requirements. - ValidateSoftwareCertificates(softwareCertificates); + HandleSignedSoftwareCertificates(serverSoftwareCertificates); // create the client signature. - dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); + byte[] dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign); // select the security policy for the user token. @@ -5448,6 +5201,308 @@ public bool ResendData(IEnumerable subscriptions, out IList + /// Validates the identity for an open call. + /// + private void OpenValidateIdentity( + ref IUserIdentity identity, + out UserIdentityToken identityToken, + out UserTokenPolicy identityPolicy, + out string securityPolicyUri, + out bool requireEncryption) + { + // check connection state. + lock (SyncRoot) + { + if (Connected) + { + throw new ServiceResultException(StatusCodes.BadInvalidState, "Already connected to server."); + } + } + + securityPolicyUri = m_endpoint.Description.SecurityPolicyUri; + + // catch security policies which are not supported by core + if (SecurityPolicies.GetDisplayName(securityPolicyUri) == null) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "The chosen security policy is not supported by the client to connect to the server."); + } + + // get the identity token. + if (identity == null) + { + identity = new UserIdentity(); + } + + // get identity token. + identityToken = identity.GetIdentityToken(); + + // check that the user identity is supported by the endpoint. + identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identityToken.PolicyId); + + if (identityPolicy == null) + { + // try looking up by TokenType if the policy id was not found. + identityPolicy = m_endpoint.Description.FindUserTokenPolicy(identity.TokenType, identity.IssuedTokenType); + + if (identityPolicy == null) + { + throw ServiceResultException.Create( + StatusCodes.BadUserAccessDenied, + "Endpoint does not support the user identity type provided."); + } + + identityToken.PolicyId = identityPolicy.PolicyId; + } + + requireEncryption = securityPolicyUri != SecurityPolicies.None; + + if (!requireEncryption) + { + requireEncryption = identityPolicy.SecurityPolicyUri != SecurityPolicies.None && + !String.IsNullOrEmpty(identityPolicy.SecurityPolicyUri); + } + } + + private void BuildCertificateData(out byte[] clientCertificateData, out byte[] clientCertificateChainData) + { + // send the application instance certificate for the client. + clientCertificateData = m_instanceCertificate != null ? m_instanceCertificate.RawData : null; + clientCertificateChainData = null; + + if (m_instanceCertificateChain != null && m_instanceCertificateChain.Count > 0 && + m_configuration.SecurityConfiguration.SendCertificateChain) + { + List clientCertificateChain = new List(); + + for (int i = 0; i < m_instanceCertificateChain.Count; i++) + { + clientCertificateChain.AddRange(m_instanceCertificateChain[i].RawData); + } + + clientCertificateChainData = clientCertificateChain.ToArray(); + } + } + + /// + /// Validates the server certificate returned. + /// + private void ValidateServerCertificateData(byte[] serverCertificateData) + { + if (serverCertificateData != null && + m_endpoint.Description.ServerCertificate != null && + !Utils.IsEqual(serverCertificateData, m_endpoint.Description.ServerCertificate)) + { + try + { + // verify for certificate chain in endpoint. + X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(m_endpoint.Description.ServerCertificate); + + if (serverCertificateChain.Count > 0 && !Utils.IsEqual(serverCertificateData, serverCertificateChain[0].RawData)) + { + throw ServiceResultException.Create( + StatusCodes.BadCertificateInvalid, + "Server did not return the certificate used to create the secure channel."); + } + } + catch (Exception) + { + throw ServiceResultException.Create( + StatusCodes.BadCertificateInvalid, + "Server did not return the certificate used to create the secure channel."); + } + } + } + + /// + /// Validates the server signature created with the client nonce. + /// + private void ValidateServerSignature(X509Certificate2 serverCertificate, SignatureData serverSignature, + byte[] clientCertificateData, byte[] clientCertificateChainData, byte[] clientNonce) + { + if (serverSignature == null || serverSignature.Signature == null) + { + Utils.LogInfo("Server signature is null or empty."); + + //throw ServiceResultException.Create( + // StatusCodes.BadSecurityChecksFailed, + // "Server signature is null or empty."); + } + + // validate the server's signature. + byte[] dataToSign = Utils.Append(clientCertificateData, clientNonce); + + if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature)) + { + // validate the signature with complete chain if the check with leaf certificate failed. + if (clientCertificateChainData != null) + { + dataToSign = Utils.Append(clientCertificateChainData, clientNonce); + + if (!SecurityPolicies.Verify(serverCertificate, m_endpoint.Description.SecurityPolicyUri, dataToSign, serverSignature)) + { + throw ServiceResultException.Create( + StatusCodes.BadApplicationSignatureInvalid, + "Server did not provide a correct signature for the nonce data provided by the client."); + } + } + else + { + throw ServiceResultException.Create( + StatusCodes.BadApplicationSignatureInvalid, + "Server did not provide a correct signature for the nonce data provided by the client."); + } + } + } + + /// + /// Validates the server endpoints returned. + /// + private void ValidateServerEndpoints(EndpointDescriptionCollection serverEndpoints) + { + if (m_discoveryServerEndpoints != null && m_discoveryServerEndpoints.Count > 0) + { + // Compare EndpointDescriptions returned at GetEndpoints with values returned at CreateSession + EndpointDescriptionCollection expectedServerEndpoints = null; + + if (serverEndpoints != null && + m_discoveryProfileUris != null && m_discoveryProfileUris.Count > 0) + { + // Select EndpointDescriptions with a transportProfileUri that matches the + // profileUris specified in the original GetEndpoints() request. + expectedServerEndpoints = new EndpointDescriptionCollection(); + + foreach (EndpointDescription serverEndpoint in serverEndpoints) + { + if (m_discoveryProfileUris.Contains(serverEndpoint.TransportProfileUri)) + { + expectedServerEndpoints.Add(serverEndpoint); + } + } + } + else + { + expectedServerEndpoints = serverEndpoints; + } + + if (expectedServerEndpoints == null || + m_discoveryServerEndpoints.Count != expectedServerEndpoints.Count) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "Server did not return a number of ServerEndpoints that matches the one from GetEndpoints."); + } + + for (int ii = 0; ii < expectedServerEndpoints.Count; ii++) + { + EndpointDescription serverEndpoint = expectedServerEndpoints[ii]; + EndpointDescription expectedServerEndpoint = m_discoveryServerEndpoints[ii]; + + if (serverEndpoint.SecurityMode != expectedServerEndpoint.SecurityMode || + serverEndpoint.SecurityPolicyUri != expectedServerEndpoint.SecurityPolicyUri || + serverEndpoint.TransportProfileUri != expectedServerEndpoint.TransportProfileUri || + serverEndpoint.SecurityLevel != expectedServerEndpoint.SecurityLevel) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "The list of ServerEndpoints returned at CreateSession does not match the list from GetEndpoints."); + } + + if (serverEndpoint.UserIdentityTokens.Count != expectedServerEndpoint.UserIdentityTokens.Count) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints."); + } + + for (int jj = 0; jj < serverEndpoint.UserIdentityTokens.Count; jj++) + { + if (!serverEndpoint.UserIdentityTokens[jj].IsEqual(expectedServerEndpoint.UserIdentityTokens[jj])) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "The list of ServerEndpoints returned at CreateSession does not match the one from GetEndpoints."); + } + } + } + } + + // find the matching description (TBD - check domains against certificate). + bool found = false; + Uri expectedUrl = Utils.ParseUri(m_endpoint.Description.EndpointUrl); + + if (expectedUrl != null) + { + for (int ii = 0; ii < serverEndpoints.Count; ii++) + { + EndpointDescription serverEndpoint = serverEndpoints[ii]; + Uri actualUrl = Utils.ParseUri(serverEndpoint.EndpointUrl); + + if (actualUrl != null && actualUrl.Scheme == expectedUrl.Scheme) + { + if (serverEndpoint.SecurityPolicyUri == m_endpoint.Description.SecurityPolicyUri) + { + if (serverEndpoint.SecurityMode == m_endpoint.Description.SecurityMode) + { + // ensure endpoint has up to date information. + m_endpoint.Description.Server.ApplicationName = serverEndpoint.Server.ApplicationName; + m_endpoint.Description.Server.ApplicationUri = serverEndpoint.Server.ApplicationUri; + m_endpoint.Description.Server.ApplicationType = serverEndpoint.Server.ApplicationType; + m_endpoint.Description.Server.ProductUri = serverEndpoint.Server.ProductUri; + m_endpoint.Description.TransportProfileUri = serverEndpoint.TransportProfileUri; + m_endpoint.Description.UserIdentityTokens = serverEndpoint.UserIdentityTokens; + + found = true; + break; + } + } + } + } + } + + // could be a security risk. + if (!found) + { + throw ServiceResultException.Create( + StatusCodes.BadSecurityChecksFailed, + "Server did not return an EndpointDescription that matched the one used to create the secure channel."); + } + } + + /// + /// Handles the validation of server software certificates and application callback. + /// + private void HandleSignedSoftwareCertificates(SignedSoftwareCertificateCollection serverSoftwareCertificates) + { + // get a validator to check certificates provided by server. + CertificateValidator validator = m_configuration.CertificateValidator; + + // validate software certificates. + List softwareCertificates = new List(); + + foreach (SignedSoftwareCertificate signedCertificate in serverSoftwareCertificates) + { + SoftwareCertificate softwareCertificate = null; + + ServiceResult result = SoftwareCertificate.Validate( + validator, + signedCertificate.CertificateData, + out softwareCertificate); + + if (ServiceResult.IsBad(result)) + { + OnSoftwareCertificateError(signedCertificate, result); + } + + softwareCertificates.Add(softwareCertificate); + } + + // check if software certificates meet application requirements. + ValidateSoftwareCertificates(softwareCertificates); + } + /// /// Processes the response from a publish request. /// diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index c2b25bfc7..50faf1d5c 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -32,6 +32,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -43,6 +44,282 @@ namespace Opc.Ua.Client /// public partial class Session : SessionClientBatched, ISession, IDisposable { + #region Open Async Methods + /// + public Task OpenAsync( + string sessionName, + IUserIdentity identity, + CancellationToken ct) + { + return OpenAsync(sessionName, 0, identity, null, ct); + } + + /// + public Task OpenAsync( + string sessionName, + uint sessionTimeout, + IUserIdentity identity, + IList preferredLocales, + CancellationToken ct) + { + return OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, true, ct); + } + + /// + public async Task OpenAsync( + string sessionName, + uint sessionTimeout, + IUserIdentity identity, + IList preferredLocales, + bool checkDomain, + CancellationToken ct) + { + OpenValidateIdentity(ref identity, out var identityToken, out var identityPolicy, out string securityPolicyUri, out bool requireEncryption); + + // validate the server certificate /certificate chain. + X509Certificate2 serverCertificate = null; + byte[] certificateData = m_endpoint.Description.ServerCertificate; + + if (certificateData != null && certificateData.Length > 0) + { + X509Certificate2Collection serverCertificateChain = Utils.ParseCertificateChainBlob(certificateData); + + if (serverCertificateChain.Count > 0) + { + serverCertificate = serverCertificateChain[0]; + } + + if (requireEncryption) + { + if (checkDomain) + { + await m_configuration.CertificateValidator.ValidateAsync(serverCertificateChain, m_endpoint, ct).ConfigureAwait(false); + } + else + { + await m_configuration.CertificateValidator.ValidateAsync(serverCertificateChain, ct).ConfigureAwait(false); + } + // save for reconnect + m_checkDomain = checkDomain; + } + } + + // create a nonce. + uint length = (uint)m_configuration.SecurityConfiguration.NonceLength; + byte[] clientNonce = Utils.Nonce.CreateNonce(length); + + // send the application instance certificate for the client. + BuildCertificateData(out byte[] clientCertificateData, out byte[] clientCertificateChainData); + + ApplicationDescription clientDescription = new ApplicationDescription { + ApplicationUri = m_configuration.ApplicationUri, + ApplicationName = m_configuration.ApplicationName, + ApplicationType = ApplicationType.Client, + ProductUri = m_configuration.ProductUri + }; + + if (sessionTimeout == 0) + { + sessionTimeout = (uint)m_configuration.ClientConfiguration.DefaultSessionTimeout; + } + + bool successCreateSession = false; + CreateSessionResponse response = null; + + //if security none, first try to connect without certificate + if (m_endpoint.Description.SecurityPolicyUri == SecurityPolicies.None) + { + //first try to connect with client certificate NULL + try + { + response = await base.CreateSessionAsync( + null, + clientDescription, + m_endpoint.Description.Server.ApplicationUri, + m_endpoint.EndpointUrl.ToString(), + sessionName, + clientNonce, + null, + sessionTimeout, + (uint)MessageContext.MaxMessageSize, + ct).ConfigureAwait(false); + + successCreateSession = true; + } + catch (Exception ex) + { + Utils.LogInfo("Create session failed with client certificate NULL. " + ex.Message); + successCreateSession = false; + } + } + + if (!successCreateSession) + { + response = await base.CreateSessionAsync( + null, + clientDescription, + m_endpoint.Description.Server.ApplicationUri, + m_endpoint.EndpointUrl.ToString(), + sessionName, + clientNonce, + clientCertificateChainData != null ? clientCertificateChainData : clientCertificateData, + sessionTimeout, + (uint)MessageContext.MaxMessageSize, + ct).ConfigureAwait(false); + } + + NodeId sessionId = response.SessionId; + NodeId sessionCookie = response.AuthenticationToken; + byte[] serverNonce = response.ServerNonce; + byte[] serverCertificateData = response.ServerCertificate; + SignatureData serverSignature = response.ServerSignature; + EndpointDescriptionCollection serverEndpoints = response.ServerEndpoints; + SignedSoftwareCertificateCollection serverSoftwareCertificates = response.ServerSoftwareCertificates; + + m_sessionTimeout = response.RevisedSessionTimeout; + m_maxRequestMessageSize = response.MaxRequestMessageSize; + + // save session id. + lock (SyncRoot) + { + base.SessionCreated(sessionId, sessionCookie); + } + + Utils.LogInfo("Revised session timeout value: {0}. ", m_sessionTimeout); + Utils.LogInfo("Max response message size value: {0}. Max request message size: {1} ", + MessageContext.MaxMessageSize, m_maxRequestMessageSize); + + //we need to call CloseSession if CreateSession was successful but some other exception is thrown + try + { + // verify that the server returned the same instance certificate. + ValidateServerCertificateData(serverCertificateData); + + ValidateServerEndpoints(serverEndpoints); + + ValidateServerSignature(serverCertificate, serverSignature, clientCertificateData, clientCertificateChainData, clientNonce); + + HandleSignedSoftwareCertificates(serverSoftwareCertificates); + + // create the client signature. + byte[] dataToSign = Utils.Append(serverCertificate != null ? serverCertificate.RawData : null, serverNonce); + SignatureData clientSignature = SecurityPolicies.Sign(m_instanceCertificate, securityPolicyUri, dataToSign); + + // select the security policy for the user token. + securityPolicyUri = identityPolicy.SecurityPolicyUri; + + if (String.IsNullOrEmpty(securityPolicyUri)) + { + securityPolicyUri = m_endpoint.Description.SecurityPolicyUri; + } + + byte[] previousServerNonce = null; + + if (TransportChannel.CurrentToken != null) + { + previousServerNonce = TransportChannel.CurrentToken.ServerNonce; + } + + // validate server nonce and security parameters for user identity. + ValidateServerNonce( + identity, + serverNonce, + securityPolicyUri, + previousServerNonce, + m_endpoint.Description.SecurityMode); + + // sign data with user token. + SignatureData userTokenSignature = identityToken.Sign(dataToSign, securityPolicyUri); + + // encrypt token. + identityToken.Encrypt(serverCertificate, serverNonce, securityPolicyUri); + + // send the software certificates assigned to the client. + SignedSoftwareCertificateCollection clientSoftwareCertificates = GetSoftwareCertificates(); + + // copy the preferred locales if provided. + if (preferredLocales != null && preferredLocales.Count > 0) + { + m_preferredLocales = new StringCollection(preferredLocales); + } + + // activate session. + ActivateSessionResponse activateResponse = await ActivateSessionAsync( + null, + clientSignature, + clientSoftwareCertificates, + m_preferredLocales, + new ExtensionObject(identityToken), + userTokenSignature, + ct).ConfigureAwait(false); + + serverNonce = activateResponse.ServerNonce; + StatusCodeCollection certificateResults = activateResponse.Results; + DiagnosticInfoCollection certificateDiagnosticInfos = activateResponse.DiagnosticInfos; + + if (certificateResults != null) + { + for (int i = 0; i < certificateResults.Count; i++) + { + Utils.LogInfo("ActivateSession result[{0}] = {1}", i, certificateResults[i]); + } + } + + if (certificateResults == null || certificateResults.Count == 0) + { + Utils.LogInfo("Empty results were received for the ActivateSession call."); + } + + // fetch namespaces. + FetchNamespaceTables(); + // TODO: await FetchNamespaceTablesAsync().ConfigureAwait(false); + + lock (SyncRoot) + { + // save nonces. + m_sessionName = sessionName; + m_identity = identity; + m_previousServerNonce = previousServerNonce; + m_serverNonce = serverNonce; + m_serverCertificate = serverCertificate; + + // update system context. + m_systemContext.PreferredLocales = m_preferredLocales; + m_systemContext.SessionId = this.SessionId; + m_systemContext.UserIdentity = identity; + } + + // fetch operation limits + FetchOperationLimits(); + // TODO: await FetchOperationLimitsAsync().ConfigureAwait(false); + + // start keep alive thread. + StartKeepAliveTimer(); + + // raise event that session configuration chnaged. + IndicateSessionConfigurationChanged(); + } + catch (Exception) + { + try + { + await base.CloseSessionAsync(null, false, ct).ConfigureAwait(false); + CloseChannel(); + } + catch (Exception e) + { + Utils.LogError("Cleanup: CloseSession() or CloseChannel() raised exception. " + e.Message); + } + finally + { + SessionCreated(null, null); + } + + throw; + } + } + #endregion + #region Subscription Async Methods /// public async Task RemoveSubscriptionAsync(Subscription subscription, CancellationToken ct = default) @@ -583,7 +860,7 @@ public async Task> CallAsync(NodeId objectId, NodeId methodId, Can #region Close Async Methods /// - public Task CloseAsync(CancellationToken ct = default) + public override Task CloseAsync(CancellationToken ct = default) { return CloseAsync(m_keepAliveInterval, true, ct); } diff --git a/Libraries/Opc.Ua.Client/TraceableSession.cs b/Libraries/Opc.Ua.Client/TraceableSession.cs index f9e6d0ecf..68f034c37 100644 --- a/Libraries/Opc.Ua.Client/TraceableSession.cs +++ b/Libraries/Opc.Ua.Client/TraceableSession.cs @@ -568,6 +568,34 @@ public void ReadDisplayName(IList nodeIds, out IList displayName m_session.ReadDisplayName(nodeIds, out displayNames, out errors); } } + + /// + public async Task OpenAsync(string sessionName, IUserIdentity identity, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity(nameof(OpenAsync))) + { + await m_session.OpenAsync(sessionName, identity, ct).ConfigureAwait(false); + } + } + + /// + public async Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity(nameof(OpenAsync))) + { + await m_session.OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, ct).ConfigureAwait(false); + } + } + + /// + public async Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, bool checkDomain, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity(nameof(OpenAsync))) + { + await m_session.OpenAsync(sessionName, sessionTimeout, identity, preferredLocales, checkDomain, ct).ConfigureAwait(false); + } + } + /// public async Task<(IList, IList)> ReadNodesAsync(IList nodeIds, NodeClass nodeClass, bool optionalAttributes = false, CancellationToken ct = default) { diff --git a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportChannel.cs b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportChannel.cs index 417ba7fa0..241f59d9c 100644 --- a/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportChannel.cs +++ b/Stack/Opc.Ua.Bindings.Https/Stack/Https/HttpsTransportChannel.cs @@ -335,6 +335,12 @@ public IServiceResponse EndSendRequest(IAsyncResult result) return result2 as IServiceResponse; } + /// + public Task EndSendRequestAsync(IAsyncResult result, CancellationToken ct) + { + throw new NotImplementedException(); + } + /// /// Not implemented here. public IAsyncResult BeginOpen(AsyncCallback callback, object callbackData) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index 4dd2815d0..a0365ca18 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(); + } + } } @@ -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) { @@ -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; 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/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/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..20c089647 100644 --- a/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs +++ b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs @@ -1175,7 +1175,7 @@ public async Task UpdateFromServerAsync( } finally { - client.Close(); + await client.CloseAsync().ConfigureAwait(false); } } 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/UaSCBinaryClientChannel.cs b/Stack/Opc.Ua.Core/Stack/Tcp/UaSCBinaryClientChannel.cs index a3e1bd860..7c0beb364 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 @@ -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/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. /// From c6ee32a11822040be3148f83e5b62acdfc6ce41e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Sep 2023 07:47:03 +0200 Subject: [PATCH 11/20] Bump docker/build-push-action from 4 to 5 (#2311) * Bump docker/setup-buildx-action from 2 to 3 Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump docker/login-action from 2 to 3 Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump docker/metadata-action from 4 to 5 Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4 to 5. - [Release notes](https://github.com/docker/metadata-action/releases) - [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md) - [Commits](https://github.com/docker/metadata-action/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump docker/setup-qemu-action from 2 to 3 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * Bump docker/build-push-action from 4 to 5 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-image.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 540313b95..965ebf63c 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -57,7 +57,7 @@ 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 @@ -93,7 +93,7 @@ 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 }} @@ -104,7 +104,7 @@ jobs: # 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: | From af04893a37e3b9a7b0ba10ad4a1d850461439bdf Mon Sep 17 00:00:00 2001 From: Marc Schier Date: Fri, 22 Sep 2023 12:17:03 +0200 Subject: [PATCH 12/20] Fix degradation to sync calls in the SDK on main paths used. (#2316) - make session create fully async incl. loading of the complex type system --- .../ComplexTypeSystem.cs | 219 +++---- .../IComplexTypeResolver.cs | 58 +- .../TypeResolver/NodeCacheResolver.cs | 117 ++-- Libraries/Opc.Ua.Client/DataDictionary.cs | 45 +- .../Opc.Ua.Client/DefaultSessionFactory.cs | 19 +- Libraries/Opc.Ua.Client/INodeCache.cs | 49 +- Libraries/Opc.Ua.Client/ISession.cs | 69 ++- Libraries/Opc.Ua.Client/NodeCache.cs | 7 +- Libraries/Opc.Ua.Client/NodeCacheAsync.cs | 439 +++++++++++++ Libraries/Opc.Ua.Client/Session.cs | 136 ++-- Libraries/Opc.Ua.Client/SessionAsync.cs | 585 +++++++++++++++++- Libraries/Opc.Ua.Client/TraceableSession.cs | 63 +- .../Stack/Client/DiscoveryClient.cs | 4 + .../Configuration/ConfiguredEndpoints.cs | 6 +- Stack/Opc.Ua.Core/Stack/Nodes/TypeTable.cs | 16 +- Stack/Opc.Ua.Core/Types/BuiltIn/ITypeTable.cs | 26 +- Stack/Opc.Ua.Core/Types/BuiltIn/Uuid.cs | 18 +- .../Types/Schemas/BinarySchemaValidator.cs | 19 +- .../Types/Utils/ServiceResultException.cs | 5 +- Stack/Opc.Ua.Core/Types/Utils/TypeInfo.cs | 46 +- .../Types/MockResolver.cs | 63 +- Tests/Opc.Ua.Client.Tests/ClientTest.cs | 8 +- .../Opc.Ua.Client.Tests/NodeCacheAsyncTest.cs | 508 +++++++++++++++ .../Schemas/BinarySchemaWellKnownTests.cs | 8 +- 24 files changed, 2155 insertions(+), 378 deletions(-) create mode 100644 Libraries/Opc.Ua.Client/NodeCacheAsync.cs create mode 100644 Tests/Opc.Ua.Client.Tests/NodeCacheAsyncTest.cs diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs index b685528d1..7b624e622 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/ComplexTypeSystem.cs @@ -31,6 +31,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Xml; using static Opc.Ua.Utils; @@ -114,14 +115,14 @@ private void Initialize( /// For servers without DataTypeDefinition support, all /// custom types are loaded. /// - public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, bool throwOnError = false) + public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, bool throwOnError = false, CancellationToken ct = default) { try { // add fast path, if no subTypes are requested if (!subTypes) { - var systemType = GetSystemType(nodeId); + Type systemType = GetSystemType(nodeId); if (systemType != null) { return systemType; @@ -129,25 +130,25 @@ public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, b } // cache the server type system - m_complexTypeResolver.LoadDataTypes(DataTypeIds.BaseDataType, true); - var subTypeNodes = m_complexTypeResolver.LoadDataTypes(nodeId, subTypes, true); - var subTypeNodesWithoutKnownTypes = RemoveKnownTypes(subTypeNodes); + _ = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.BaseDataType, true, ct: ct).ConfigureAwait(false); + IList subTypeNodes = await m_complexTypeResolver.LoadDataTypesAsync(nodeId, subTypes, true, ct: ct).ConfigureAwait(false); + IList subTypeNodesWithoutKnownTypes = RemoveKnownTypes(subTypeNodes); if (subTypeNodesWithoutKnownTypes.Count > 0) { IList serverEnumTypes = new List(); IList serverStructTypes = new List(); - foreach (var node in subTypeNodesWithoutKnownTypes) + foreach (INode node in subTypeNodesWithoutKnownTypes) { - AddEnumerationOrStructureType(node, serverEnumTypes, serverStructTypes); + await AddEnumerationOrStructureTypeAsync(node, serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false); } // load server types - if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) + if (DisableDataTypeDefinition || !await LoadBaseDataTypesAsync(serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false)) { if (!DisableDataTypeDictionary) { - await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false).ConfigureAwait(false); + await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false, ct).ConfigureAwait(false); } } } @@ -173,7 +174,7 @@ public async Task LoadType(ExpandedNodeId nodeId, bool subTypes = false, b /// For servers without DataTypeDefinition support all /// custom types are loaded. /// - public async Task LoadNamespace(string nameSpace, bool throwOnError = false) + public async Task LoadNamespace(string nameSpace, bool throwOnError = false, CancellationToken ct = default) { try { @@ -183,20 +184,20 @@ public async Task LoadNamespace(string nameSpace, bool throwOnError = fals throw new ServiceResultException($"Bad argument {nameSpace}. Namespace not found."); } ushort nameSpaceIndex = (ushort)index; - m_complexTypeResolver.LoadDataTypes(DataTypeIds.BaseDataType, true); - var serverEnumTypes = m_complexTypeResolver.LoadDataTypes(DataTypeIds.Enumeration); - var serverStructTypes = m_complexTypeResolver.LoadDataTypes(DataTypeIds.Structure, true); + _ = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.BaseDataType, true, ct: ct).ConfigureAwait(false); + IList serverEnumTypes = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Enumeration, ct: ct).ConfigureAwait(false); + IList serverStructTypes = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Structure, true, ct: ct).ConfigureAwait(false); // filter for namespace serverEnumTypes = serverEnumTypes.Where(rd => rd.NodeId.NamespaceIndex == nameSpaceIndex).ToList(); serverStructTypes = serverStructTypes.Where(rd => rd.NodeId.NamespaceIndex == nameSpaceIndex).ToList(); // load types - if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) + if (DisableDataTypeDefinition || !await LoadBaseDataTypesAsync(serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false)) { if (DisableDataTypeDictionary) { return false; } - return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false).ConfigureAwait(false); + return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, false, ct).ConfigureAwait(false); } return true; } @@ -228,21 +229,22 @@ public async Task LoadNamespace(string nameSpace, bool throwOnError = fals /// - Create all structured types from the dictionaries using the converted DataTypeDefinion attribute.. /// /// true if all DataTypes were loaded. - public async Task Load(bool onlyEnumTypes = false, bool throwOnError = false) + public async Task Load(bool onlyEnumTypes = false, bool throwOnError = false, CancellationToken ct = default) { try { // load server types in cache - m_complexTypeResolver.LoadDataTypes(DataTypeIds.BaseDataType, true); - IList serverEnumTypes = m_complexTypeResolver.LoadDataTypes(DataTypeIds.Enumeration); - IList serverStructTypes = onlyEnumTypes ? new List() : m_complexTypeResolver.LoadDataTypes(DataTypeIds.Structure, true); - if (DisableDataTypeDefinition || !LoadBaseDataTypes(serverEnumTypes, serverStructTypes)) + await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.BaseDataType, true, ct: ct).ConfigureAwait(false); + IList serverEnumTypes = await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Enumeration, ct: ct).ConfigureAwait(false); + IList serverStructTypes = onlyEnumTypes ? new List() : + await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Structure, true, ct: ct).ConfigureAwait(false); + if (DisableDataTypeDefinition || !await LoadBaseDataTypesAsync(serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false)) { if (DisableDataTypeDictionary) { return false; } - return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, true).ConfigureAwait(false); + return await LoadDictionaryDataTypes(serverEnumTypes, serverStructTypes, true, ct).ConfigureAwait(false); } return true; } @@ -298,7 +300,7 @@ void CollectAllDataTypeDefinitions(NodeId nodeId, NodeIdDictionary LoadDictionaryDataTypes( IList serverEnumTypes, IList serverStructTypes, - bool fullTypeList + bool fullTypeList, + CancellationToken ct = default ) { // build a type dictionary with all known new types - var allEnumTypes = fullTypeList ? serverEnumTypes : m_complexTypeResolver.LoadDataTypes(DataTypeIds.Enumeration); + var allEnumTypes = fullTypeList ? serverEnumTypes : await m_complexTypeResolver.LoadDataTypesAsync(DataTypeIds.Enumeration, ct: ct).ConfigureAwait(false); var typeDictionary = new Dictionary(); // strip known types from list serverEnumTypes = RemoveKnownTypes(allEnumTypes); // load the binary schema dictionaries from the server - var typeSystem = await m_complexTypeResolver.LoadDataTypeSystem().ConfigureAwait(false); + Dictionary typeSystem = await m_complexTypeResolver.LoadDataTypeSystem(ct: ct).ConfigureAwait(false); // sort dictionaries with import dependencies to the end of the list var sortedTypeSystem = typeSystem.OrderBy(t => t.Value.TypeDictionary?.Import?.Length).ToList(); @@ -362,18 +365,18 @@ bool fullTypeList bool allTypesLoaded = true; // create custom types for all dictionaries - foreach (var dictionaryId in sortedTypeSystem) + foreach (KeyValuePair dictionaryId in sortedTypeSystem) { try { - var dictionary = dictionaryId.Value; + DataDictionary dictionary = dictionaryId.Value; if (dictionary.TypeDictionary == null || dictionary.TypeDictionary.Items == null) { continue; } - var targetDictionaryNamespace = dictionary.TypeDictionary.TargetNamespace; - var targetNamespaceIndex = m_complexTypeResolver.NamespaceUris.GetIndex(targetDictionaryNamespace); + string targetDictionaryNamespace = dictionary.TypeDictionary.TargetNamespace; + int targetNamespaceIndex = m_complexTypeResolver.NamespaceUris.GetIndex(targetDictionaryNamespace); var structureList = new List(); var enumList = new List(); @@ -382,13 +385,13 @@ bool fullTypeList SplitAndSortDictionary(dictionary, structureList, enumList); // create assembly for all types in the same module - var complexTypeBuilder = m_complexTypeBuilderFactory.Create( + IComplexTypeBuilder complexTypeBuilder = m_complexTypeBuilderFactory.Create( targetDictionaryNamespace, targetNamespaceIndex, dictionary.Name); // Add all unknown enumeration types in dictionary - AddEnumTypes(complexTypeBuilder, typeDictionary, enumList, allEnumTypes, serverEnumTypes); + await AddEnumTypesAsync(complexTypeBuilder, typeDictionary, enumList, allEnumTypes, serverEnumTypes, ct).ConfigureAwait(false); // handle structures int loopCounter = 0; @@ -401,7 +404,7 @@ bool fullTypeList lastStructureCount = structureList.Count; var retryStructureList = new List(); // build structured types - foreach (var item in structureList) + foreach (Schema.Binary.TypeDescription item in structureList) { if (item is Schema.Binary.StructuredType structuredObject) { @@ -413,16 +416,10 @@ bool fullTypeList } // find the data type node and the binary encoding id - ExpandedNodeId typeId; - ExpandedNodeId binaryEncodingId; - DataTypeNode dataTypeNode; - bool newTypeDescription = m_complexTypeResolver.BrowseTypeIdsForDictionaryComponent( - nodeId, - out typeId, - out binaryEncodingId, - out dataTypeNode); - - if (!newTypeDescription) + (ExpandedNodeId typeId, ExpandedNodeId binaryEncodingId, DataTypeNode dataTypeNode) = + await m_complexTypeResolver.BrowseTypeIdsForDictionaryComponentAsync(nodeId, ct).ConfigureAwait(false); + + if (dataTypeNode == null) { Utils.LogError(TraceMasks.Error, "Skip the type definition of {0} because the data type node was not found.", item.Name); continue; @@ -430,7 +427,7 @@ bool fullTypeList if (GetSystemType(typeId) != null) { - var qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); + XmlQualifiedName qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); typeDictionary[qName] = ExpandedNodeId.ToNodeId(typeId, m_complexTypeResolver.NamespaceUris); Utils.LogInfo("Skip the type definition of {0} because the type already exists.", item.Name); continue; @@ -469,20 +466,22 @@ bool fullTypeList Type complexType = null; if (structureDefinition != null) { - var encodingIds = m_complexTypeResolver.BrowseForEncodings(typeId, m_supportedEncodings, - out binaryEncodingId, out ExpandedNodeId xmlEncodingId); + IList encodingIds; + ExpandedNodeId xmlEncodingId; + (encodingIds, binaryEncodingId, xmlEncodingId) = await m_complexTypeResolver.BrowseForEncodingsAsync( + typeId, m_supportedEncodings, ct).ConfigureAwait(false); try { // build the actual .NET structured type in assembly - complexType = AddStructuredType( + (complexType, missingTypeIds) = await AddStructuredTypeAsync( complexTypeBuilder, structureDefinition, dataTypeNode.BrowseName, typeId, binaryEncodingId, xmlEncodingId, - out missingTypeIds - ); + ct + ).ConfigureAwait(false); } catch (DataTypeNotSupportedException typeNotSupportedException) { @@ -500,7 +499,7 @@ out missingTypeIds AddEncodeableType(encodingId, complexType); } AddEncodeableType(typeId, complexType); - var qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); + XmlQualifiedName qName = structuredObject.QName ?? new XmlQualifiedName(structuredObject.Name, targetDictionaryNamespace); typeDictionary[qName] = ExpandedNodeId.ToNodeId(typeId, m_complexTypeResolver.NamespaceUris); } } @@ -529,15 +528,16 @@ out missingTypeIds /// Load all custom types with DataTypeDefinition into the type factory. ///
/// true if all types were loaded, false otherwise - private bool LoadBaseDataTypes( + private async Task LoadBaseDataTypesAsync( IList serverEnumTypes, - IList serverStructTypes + IList serverStructTypes, + CancellationToken ct = default ) { - bool repeatDataTypeLoad = false; IList enumTypesToDoList = new List(); IList structTypesToDoList = new List(); + bool repeatDataTypeLoad; do { // strip known types @@ -547,19 +547,19 @@ IList serverStructTypes repeatDataTypeLoad = false; try { - enumTypesToDoList = LoadBaseEnumDataTypes(serverEnumTypes); - structTypesToDoList = LoadBaseStructureDataTypes(serverStructTypes); + enumTypesToDoList = await LoadBaseEnumDataTypesAsync(serverEnumTypes, ct).ConfigureAwait(false); + structTypesToDoList = await LoadBaseStructureDataTypesAsync(serverStructTypes, ct).ConfigureAwait(false); } catch (DataTypeNotFoundException dtnfex) { Utils.LogWarning(dtnfex.Message); - foreach (var nodeId in dtnfex.NodeIds) + foreach (ExpandedNodeId nodeId in dtnfex.NodeIds) { // add missing types to list - var dataTypeNode = m_complexTypeResolver.Find(nodeId); + var dataTypeNode = await m_complexTypeResolver.FindAsync(nodeId, ct).ConfigureAwait(false); if (dataTypeNode != null) { - AddEnumerationOrStructureType(dataTypeNode, serverEnumTypes, serverStructTypes); + await AddEnumerationOrStructureTypeAsync(dataTypeNode, serverEnumTypes, serverStructTypes, ct).ConfigureAwait(false); repeatDataTypeLoad = true; } else @@ -578,8 +578,9 @@ IList serverStructTypes /// Load all custom types with DataTypeDefinition into the type factory. ///
/// true if all types were loaded, false otherwise - private IList LoadBaseEnumDataTypes( - IList serverEnumTypes + private async Task> LoadBaseEnumDataTypesAsync( + IList serverEnumTypes, + CancellationToken ct = default ) { // strip known types @@ -603,9 +604,9 @@ IList serverEnumTypes targetNamespace, (int)i); } - foreach (var enumType in enumTypes) + foreach (INode enumType in enumTypes) { - var newType = AddEnumType(complexTypeBuilder, enumType as DataTypeNode); + Type newType = await AddEnumTypeAsync(complexTypeBuilder, enumType as DataTypeNode, ct).ConfigureAwait(false); if (newType != null) { // match namespace and add to type factory @@ -627,8 +628,9 @@ IList serverEnumTypes /// Load all structure custom types with DataTypeDefinition into the type factory. ///
/// true if all types were loaded, false otherwise - private IList LoadBaseStructureDataTypes( - IList serverStructTypes + private async Task> LoadBaseStructureDataTypesAsync( + IList serverStructTypes, + CancellationToken ct = default ) { // strip known types @@ -639,11 +641,11 @@ IList serverStructTypes bool retryAddStructType; var structTypesToDoList = new List(); - var structTypesWorkList = serverStructTypes; + IList structTypesWorkList = serverStructTypes; // allow the loader to cache the encodings IList nodeIds = serverStructTypes.Select(n => n.NodeId).ToList(); - m_complexTypeResolver.BrowseForEncodings(nodeIds, m_supportedEncodings); + _ = await m_complexTypeResolver.BrowseForEncodingsAsync(nodeIds, m_supportedEncodings, ct).ConfigureAwait(false); // create structured types for all namespaces int loopCounter = 0; @@ -673,40 +675,41 @@ IList serverStructTypes continue; } - var structureDefinition = GetStructureDefinition(dataTypeNode); + StructureDefinition structureDefinition = GetStructureDefinition(dataTypeNode); if (structureDefinition != null) { - var encodingIds = m_complexTypeResolver.BrowseForEncodings(structType.NodeId, m_supportedEncodings, - out ExpandedNodeId binaryEncodingId, out ExpandedNodeId xmlEncodingId); + (IList encodingIds, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId) + = await m_complexTypeResolver.BrowseForEncodingsAsync(structType.NodeId, m_supportedEncodings, ct).ConfigureAwait(false); try { ExpandedNodeId typeId = NormalizeExpandedNodeId(structType.NodeId); - newType = AddStructuredType( + ExpandedNodeIdCollection missingTypeIds; + (newType, missingTypeIds) = await AddStructuredTypeAsync( complexTypeBuilder, structureDefinition, dataTypeNode.BrowseName, typeId, binaryEncodingId, xmlEncodingId, - out ExpandedNodeIdCollection missingTypeIds - ); + ct + ).ConfigureAwait(false); if (missingTypeIds?.Count > 0) { var missingTypeIdsFromWorkList = new ExpandedNodeIdCollection(); - foreach (var missingTypeId in missingTypeIds) + foreach (ExpandedNodeId missingTypeId in missingTypeIds) { - var typeMatch = structTypesWorkList.FirstOrDefault(n => n.NodeId == missingTypeId); + INode typeMatch = structTypesWorkList.FirstOrDefault(n => n.NodeId == missingTypeId); if (typeMatch == null) { missingTypeIdsFromWorkList.Add(missingTypeId); } } - foreach (var id in missingTypeIdsFromWorkList) + foreach (ExpandedNodeId id in missingTypeIdsFromWorkList) { if (!structTypesToDoList.Where(n => n.NodeId == id).Any()) { - structTypesToDoList.Add(m_complexTypeResolver.Find(id)); + structTypesToDoList.Add(await m_complexTypeResolver.FindAsync(id, ct).ConfigureAwait(false)); } retryAddStructType = true; } @@ -724,7 +727,7 @@ out ExpandedNodeIdCollection missingTypeIds if (newType != null) { - foreach (var encodingId in encodingIds) + foreach (NodeId encodingId in encodingIds) { AddEncodeableType(encodingId, newType); } @@ -778,7 +781,7 @@ private StructureDefinition GetStructureDefinition(DataTypeNode dataTypeNode) return null; } // Validate the structure according to Part3, Table 36 - foreach (var field in structureDefinition.Fields) + foreach (StructureField field in structureDefinition.Fields) { // validate if the DataTypeDefinition is correctly // filled out, some servers don't do it yet... @@ -814,12 +817,12 @@ private ExpandedNodeId NormalizeExpandedNodeId(ExpandedNodeId expandedNodeId) /// /// Add data type to enumeration or structure base type list depending on supertype. /// - private void AddEnumerationOrStructureType(INode dataTypeNode, IList serverEnumTypes, IList serverStructTypes) + private async Task AddEnumerationOrStructureTypeAsync(INode dataTypeNode, IList serverEnumTypes, IList serverStructTypes, CancellationToken ct = default) { NodeId superType = ExpandedNodeId.ToNodeId(dataTypeNode.NodeId, m_complexTypeResolver.NamespaceUris); while (true) { - superType = m_complexTypeResolver.FindSuperType(superType); + superType = await m_complexTypeResolver.FindSuperTypeAsync(superType, ct).ConfigureAwait(false); if (superType.IsNullNodeId) { throw new ServiceResultException(StatusCodes.BadNodeIdInvalid, $"SuperType for {dataTypeNode.NodeId} not found."); @@ -865,14 +868,15 @@ private Type GetSystemType(ExpandedNodeId nodeId) /// /// Add an enum type defined in a binary schema dictionary. /// - private void AddEnumTypes( + private async Task AddEnumTypesAsync( IComplexTypeBuilder complexTypeBuilder, Dictionary typeDictionary, IList enumList, IList allEnumerationTypes, - IList enumerationTypes) + IList enumerationTypes, + CancellationToken ct = default) { - foreach (var item in enumList) + foreach (Schema.Binary.TypeDescription item in enumList) { Type newType = null; DataTypeNode enumDescription = null; @@ -899,8 +903,8 @@ private void AddEnumTypes( if (newType == null) { // 2. use node cache - var dataTypeNode = m_complexTypeResolver.Find(enumType.NodeId) as DataTypeNode; - newType = AddEnumType(complexTypeBuilder, dataTypeNode); + var dataTypeNode = await m_complexTypeResolver.FindAsync(enumType.NodeId, ct).ConfigureAwait(false) as DataTypeNode; + newType = await AddEnumTypeAsync(complexTypeBuilder, dataTypeNode, ct).ConfigureAwait(false); } } else @@ -933,7 +937,7 @@ private void AddEncodeableType(ExpandedNodeId nodeId, Type type) { return; } - var internalNodeId = NormalizeExpandedNodeId(nodeId); + ExpandedNodeId internalNodeId = NormalizeExpandedNodeId(nodeId); Utils.LogDebug("Adding Type {0} as: {1}", type.FullName, internalNodeId); m_complexTypeResolver.Factory.AddEncodeableType(internalNodeId, type); } @@ -941,9 +945,10 @@ private void AddEncodeableType(ExpandedNodeId nodeId, Type type) /// /// Add an enum type defined in a DataType node. /// - private Type AddEnumType( + private async Task AddEnumTypeAsync( IComplexTypeBuilder complexTypeBuilder, - DataTypeNode enumTypeNode + DataTypeNode enumTypeNode, + CancellationToken ct = default ) { Type newType = null; @@ -956,7 +961,7 @@ DataTypeNode enumTypeNode !(enumTypeNode.DataTypeDefinition?.Body is EnumDefinition enumDefinition)) { // browse for EnumFields or EnumStrings property - object enumTypeArray = m_complexTypeResolver.GetEnumTypeArray(enumTypeNode.NodeId); + object enumTypeArray = await m_complexTypeResolver.GetEnumTypeArrayAsync(enumTypeNode.NodeId, ct).ConfigureAwait(false); if (enumTypeArray is ExtensionObject[] extensionObject) { // 2. use EnumValues @@ -988,18 +993,18 @@ DataTypeNode enumTypeNode /// /// Add structured type to assembly with StructureDefinition. /// - private Type AddStructuredType( + private async Task<(Type structureType, ExpandedNodeIdCollection missingTypes)> AddStructuredTypeAsync( IComplexTypeBuilder complexTypeBuilder, StructureDefinition structureDefinition, QualifiedName typeName, ExpandedNodeId complexTypeId, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId, - out ExpandedNodeIdCollection missingTypes + CancellationToken ct = default ) { // init missing type list - missingTypes = null; + ExpandedNodeIdCollection missingTypes = null; var localDataTypeId = ExpandedNodeId.ToNodeId(complexTypeId, m_complexTypeResolver.NamespaceUris); bool allowSubTypes = IsAllowSubTypes(structureDefinition); @@ -1008,7 +1013,7 @@ out ExpandedNodeIdCollection missingTypes var typeList = new List(); foreach (StructureField field in structureDefinition.Fields) { - Type newType = GetFieldType(field, allowSubTypes); + Type newType = await GetFieldTypeAsync(field, allowSubTypes, ct).ConfigureAwait(false); if (newType == null && !IsRecursiveDataType(localDataTypeId, field.DataType)) { @@ -1029,13 +1034,13 @@ out ExpandedNodeIdCollection missingTypes if (missingTypes != null) { - return null; + return (null, missingTypes); } // Add StructureDefinition to cache m_dataTypeDefinitionCache[localDataTypeId] = structureDefinition; - var fieldBuilder = complexTypeBuilder.AddStructuredType( + IComplexTypeFieldBuilder fieldBuilder = complexTypeBuilder.AddStructuredType( typeName, structureDefinition ); @@ -1043,7 +1048,7 @@ out ExpandedNodeIdCollection missingTypes fieldBuilder.AddTypeIdAttribute(complexTypeId, binaryEncodingId, xmlEncodingId); int order = 1; - var typeListEnumerator = typeList.GetEnumerator(); + List.Enumerator typeListEnumerator = typeList.GetEnumerator(); foreach (StructureField field in structureDefinition.Fields) { typeListEnumerator.MoveNext(); @@ -1051,7 +1056,7 @@ out ExpandedNodeIdCollection missingTypes // check for recursive data type: // field has the same data type as the parent structure var nodeId = ExpandedNodeId.ToNodeId(complexTypeId, m_complexTypeResolver.NamespaceUris); - var isRecursiveDataType = IsRecursiveDataType(nodeId, field.DataType); + bool isRecursiveDataType = IsRecursiveDataType(nodeId, field.DataType); if (isRecursiveDataType) { fieldBuilder.AddField(field, fieldBuilder.GetStructureType(field.ValueRank), order); @@ -1063,7 +1068,7 @@ out ExpandedNodeIdCollection missingTypes order++; } - return fieldBuilder.CreateType(); + return (fieldBuilder.CreateType(), missingTypes); } bool IsAllowSubTypes(StructureDefinition structureDefinition) @@ -1077,9 +1082,9 @@ bool IsAllowSubTypes(StructureDefinition structureDefinition) return false; } - private bool IsAbstractType(NodeId fieldDataType) + private async Task IsAbstractTypeAsync(NodeId fieldDataType, CancellationToken ct = default) { - var dataTypeNode = m_complexTypeResolver.Find(fieldDataType) as DataTypeNode; + var dataTypeNode = await m_complexTypeResolver.FindAsync(fieldDataType, ct).ConfigureAwait(false) as DataTypeNode; return dataTypeNode?.IsAbstract == true; } @@ -1089,7 +1094,7 @@ private bool IsRecursiveDataType(NodeId structureDataType, NodeId fieldDataType) /// /// Determine the type of a field in a StructureField definition. /// - private Type GetFieldType(StructureField field, bool allowSubTypes) + private async Task GetFieldTypeAsync(StructureField field, bool allowSubTypes, CancellationToken ct = default) { if (field.ValueRank != ValueRanks.Scalar && field.ValueRank < ValueRanks.OneDimension) @@ -1103,11 +1108,11 @@ private Type GetFieldType(StructureField field, bool allowSubTypes) if (fieldType == null) { - var superType = GetBuiltInSuperType(field.DataType, allowSubTypes, field.IsOptional); + NodeId superType = await GetBuiltInSuperTypeAsync(field.DataType, allowSubTypes, field.IsOptional, ct).ConfigureAwait(false); if (superType?.IsNullNodeId == false) { field.DataType = superType; - return GetFieldType(field, allowSubTypes); + return await GetFieldTypeAsync(field, allowSubTypes, ct).ConfigureAwait(false); } return null; } @@ -1127,12 +1132,12 @@ private Type GetFieldType(StructureField field, bool allowSubTypes) /// /// Find superType for a datatype. /// - private NodeId GetBuiltInSuperType(NodeId dataType, bool allowSubTypes, bool isOptional) + private async Task GetBuiltInSuperTypeAsync(NodeId dataType, bool allowSubTypes, bool isOptional, CancellationToken ct = default) { NodeId superType = dataType; while (true) { - superType = m_complexTypeResolver.FindSuperType(superType); + superType = await m_complexTypeResolver.FindSuperTypeAsync(superType, ct).ConfigureAwait(false); if (superType?.IsNullNodeId != false) { return null; @@ -1156,7 +1161,7 @@ private NodeId GetBuiltInSuperType(NodeId dataType, bool allowSubTypes, bool isO // in such case the encoding as ExtensionObject is undetermined and not specified if ((dataType != DataTypeIds.Structure) && ((allowSubTypes && !isOptional) || !allowSubTypes) && - IsAbstractType(dataType)) + await IsAbstractTypeAsync(dataType, ct).ConfigureAwait(false)) { throw new DataTypeNotSupportedException("Invalid definition of a abstract subtype of a structure."); } @@ -1184,11 +1189,11 @@ private void SplitAndSortDictionary( List enumList ) { - foreach (var item in dictionary.TypeDictionary.Items) + foreach (Schema.Binary.TypeDescription item in dictionary.TypeDictionary.Items) { if (item is Schema.Binary.StructuredType structuredObject) { - var dependentFields = structuredObject.Field.Where(f => f.TypeName.Namespace == dictionary.TypeDictionary.TargetNamespace); + IEnumerable dependentFields = structuredObject.Field.Where(f => f.TypeName.Namespace == dictionary.TypeDictionary.TargetNamespace); if (!dependentFields.Any()) { structureList.Insert(0, structuredObject); diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/IComplexTypeResolver.cs b/Libraries/Opc.Ua.Client.ComplexTypes/IComplexTypeResolver.cs index ef31ad695..293b92b55 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/IComplexTypeResolver.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/IComplexTypeResolver.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; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Opc.Ua.Client.ComplexTypes @@ -53,7 +54,10 @@ public interface IComplexTypeResolver /// Loads all dictionaries of the OPC binary or Xml schema type system. ///
/// The type system. Defaults to OPC Binary schema. - Task> LoadDataTypeSystem(NodeId dataTypeSystem = null); + /// + Task> LoadDataTypeSystem( + NodeId dataTypeSystem = null, + CancellationToken ct = default); /// /// Browse for the type and encoding id for a dictionary component. @@ -64,20 +68,16 @@ public interface IComplexTypeResolver /// and data type dictionary. /// To find the typeId and encodingId for a dictionary type definition: /// i) inverse browse the description to get the encodingid - /// ii) from the description inverse browse for encoding - /// to get the subtype typeid - /// iii) load the DataType node + /// ii) from the description inverse browse for encoding + /// to get the subtype typeid + /// iii) load the DataType node /// /// - /// - /// - /// - /// true if successful, false otherwise - bool BrowseTypeIdsForDictionaryComponent( + /// + /// type id, encoding id and data type node if successful, null otherwise + Task<(ExpandedNodeId typeId, ExpandedNodeId encodingId, DataTypeNode dataTypeNode)> BrowseTypeIdsForDictionaryComponentAsync( ExpandedNodeId nodeId, - out ExpandedNodeId typeId, - out ExpandedNodeId encodingId, - out DataTypeNode dataTypeNode); + CancellationToken ct = default); /// /// Browse for the encodings of a datatype list. @@ -85,38 +85,42 @@ bool BrowseTypeIdsForDictionaryComponent( /// /// Is called to allow for caching of encoding information on the client. /// - IList BrowseForEncodings( + Task> BrowseForEncodingsAsync( IList nodeIds, - string[] supportedEncodings); - + string[] supportedEncodings, + CancellationToken ct = default); + /// /// Browse for the encodings of a type. /// /// /// Browse for binary encoding of a structure datatype. /// - IList BrowseForEncodings( + Task<(IList encodings, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId)> BrowseForEncodingsAsync( ExpandedNodeId nodeId, string[] supportedEncodings, - out ExpandedNodeId binaryEncodingId, - out ExpandedNodeId xmlEncodingId); + CancellationToken ct = default); /// /// Load all subTypes and optionally nested subtypes of a type definition. /// Filter for all subtypes or only subtypes outside the default namespace. /// - IList LoadDataTypes( + Task> LoadDataTypesAsync( ExpandedNodeId dataType, bool nestedSubTypes = false, bool addRootNode = false, - bool filterUATypes = true); + bool filterUATypes = true, + CancellationToken ct = default); /// /// Finds a node in the node set. /// /// The node identifier. + /// /// Returns null if the node does not exist. - INode Find(ExpandedNodeId nodeId); + Task FindAsync( + ExpandedNodeId nodeId, + CancellationToken ct = default); /// /// Reads the enum type array of a enum type definition node. @@ -126,19 +130,21 @@ IList LoadDataTypes( /// reference of the enum type NodeId /// /// The enum type nodeId which has an enum array in the property. + /// /// /// The value of the nodeId, which can be an array of /// or of . - /// null if the enum type array does not exist. + /// null if the enum type array does not exist. /// - object GetEnumTypeArray(ExpandedNodeId nodeId); + Task GetEnumTypeArrayAsync(ExpandedNodeId nodeId, CancellationToken ct = default); /// /// Returns the immediate supertype for the type. /// /// The type identifier. + /// /// The immediate supertype identifier for - NodeId FindSuperType(NodeId typeId); + Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default); } }//namespace diff --git a/Libraries/Opc.Ua.Client.ComplexTypes/TypeResolver/NodeCacheResolver.cs b/Libraries/Opc.Ua.Client.ComplexTypes/TypeResolver/NodeCacheResolver.cs index 05472f5ec..72750c96f 100644 --- a/Libraries/Opc.Ua.Client.ComplexTypes/TypeResolver/NodeCacheResolver.cs +++ b/Libraries/Opc.Ua.Client.ComplexTypes/TypeResolver/NodeCacheResolver.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, @@ -32,6 +32,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace Opc.Ua.Client.ComplexTypes @@ -65,106 +66,107 @@ private void Initialize(ISession session) public IEncodeableFactory Factory => m_session.Factory; /// - public Task> LoadDataTypeSystem(NodeId dataTypeSystem = null) + public Task> LoadDataTypeSystem( + NodeId dataTypeSystem = null, + CancellationToken ct = default) { - return m_session.LoadDataTypeSystem(dataTypeSystem); + return m_session.LoadDataTypeSystem(dataTypeSystem, ct); } /// - public IList BrowseForEncodings( + public async Task> BrowseForEncodingsAsync( IList nodeIds, - string[] supportedEncodings - ) + string[] supportedEncodings, + CancellationToken ct = default) { // cache type encodings - var encodings = m_session.NodeCache.FindReferences( + IList encodings = await m_session.NodeCache.FindReferencesAsync( nodeIds, new NodeIdCollection { ReferenceTypeIds.HasEncoding }, false, - false - ); + false, + ct).ConfigureAwait(false); // cache dictionary descriptions nodeIds = encodings.Select(r => r.NodeId).ToList(); - var descriptions = m_session.NodeCache.FindReferences( + IList descriptions = await m_session.NodeCache.FindReferencesAsync( nodeIds, new NodeIdCollection { ReferenceTypeIds.HasDescription }, false, - false - ); + false, + ct).ConfigureAwait(false); return encodings.Where(r => supportedEncodings.Contains(r.BrowseName.Name)) .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)).ToList(); } /// - public IList BrowseForEncodings( + public async Task<(IList encodings, ExpandedNodeId binaryEncodingId, ExpandedNodeId xmlEncodingId)> BrowseForEncodingsAsync( ExpandedNodeId nodeId, string[] supportedEncodings, - out ExpandedNodeId binaryEncodingId, - out ExpandedNodeId xmlEncodingId) + CancellationToken ct = default) { - var references = m_session.NodeCache.FindReferences( - nodeId, - ReferenceTypeIds.HasEncoding, - false, - false - ); + IList references = await m_session.NodeCache.FindReferencesAsync( + nodeId, + ReferenceTypeIds.HasEncoding, + false, + false, + ct).ConfigureAwait(false); - binaryEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultBinary)?.NodeId; + ExpandedNodeId binaryEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultBinary)?.NodeId; binaryEncodingId = NormalizeExpandedNodeId(binaryEncodingId); - xmlEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultXml)?.NodeId; + ExpandedNodeId xmlEncodingId = references.FirstOrDefault(r => r.BrowseName.Name == BrowseNames.DefaultXml)?.NodeId; xmlEncodingId = NormalizeExpandedNodeId(xmlEncodingId); - return references.Where(r => supportedEncodings.Contains(r.BrowseName.Name)) - .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)).ToList(); + return (references + .Where(r => supportedEncodings.Contains(r.BrowseName.Name)) + .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, m_session.NamespaceUris)) + .ToList(), binaryEncodingId, xmlEncodingId); } /// - public bool BrowseTypeIdsForDictionaryComponent( + public async 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; + ExpandedNodeId encodingId; + DataTypeNode dataTypeNode; - var references = m_session.NodeCache.FindReferences( + IList references = await m_session.NodeCache.FindReferencesAsync( nodeId, ReferenceTypeIds.HasDescription, true, - false - ); + false, + ct).ConfigureAwait(false); if (references.Count == 1) { encodingId = references[0].NodeId; - references = m_session.NodeCache.FindReferences( + references = await m_session.NodeCache.FindReferencesAsync( encodingId, ReferenceTypeIds.HasEncoding, true, - false - ); + false, + ct).ConfigureAwait(false); encodingId = NormalizeExpandedNodeId(encodingId); if (references.Count == 1) { - typeId = references[0].NodeId; + ExpandedNodeId typeId = references[0].NodeId; dataTypeNode = m_session.NodeCache.Find(typeId) as DataTypeNode; typeId = NormalizeExpandedNodeId(typeId); - return true; + return (typeId, encodingId, dataTypeNode); } } - return false; + return (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 { @@ -178,7 +180,7 @@ public IList LoadDataTypes( if (addRootNode) { - var rootNode = m_session.NodeCache.Find(dataType); + INode rootNode = await m_session.NodeCache.FindAsync(dataType, ct).ConfigureAwait(false); if (!(rootNode is DataTypeNode)) { throw new ServiceResultException("Root Node is not a DataType node."); @@ -188,11 +190,12 @@ public IList LoadDataTypes( while (nodesToBrowse.Count > 0) { - var response = m_session.NodeCache.FindReferences( + IList response = await m_session.NodeCache.FindReferencesAsync( nodesToBrowse, new NodeIdCollection { ReferenceTypeIds.HasSubtype }, false, - false); + false, + ct).ConfigureAwait(false); var nextNodesToBrowse = new ExpandedNodeIdCollection(); if (nestedSubTypes) @@ -221,35 +224,35 @@ public IList LoadDataTypes( } /// - public INode Find(ExpandedNodeId nodeId) + public Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct) { - return m_session.NodeCache.Find(nodeId); + return m_session.NodeCache.FindAsync(nodeId, ct); } /// - public object GetEnumTypeArray(ExpandedNodeId nodeId) + public async Task GetEnumTypeArrayAsync(ExpandedNodeId nodeId, CancellationToken ct = default) { // find the property reference for the enum type - var references = m_session.NodeCache.FindReferences( + IList references = await m_session.NodeCache.FindReferencesAsync( nodeId, ReferenceTypeIds.HasProperty, false, - false - ); - var property = references.FirstOrDefault(); + false, + ct).ConfigureAwait(false); + INode property = references.FirstOrDefault(); if (property != null) { // read the enum type array - DataValue value = m_session.ReadValue(ExpandedNodeId.ToNodeId(property.NodeId, NamespaceUris)); + DataValue value = await m_session.ReadValueAsync(ExpandedNodeId.ToNodeId(property.NodeId, NamespaceUris), ct).ConfigureAwait(false); return value?.Value; } return null; } /// - public NodeId FindSuperType(NodeId typeId) + public Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default) { - return m_session.NodeCache.FindSuperType(typeId); + return m_session.NodeCache.FindSuperTypeAsync(typeId, ct); } #endregion IComplexTypeResolver diff --git a/Libraries/Opc.Ua.Client/DataDictionary.cs b/Libraries/Opc.Ua.Client/DataDictionary.cs index e6e024e2e..e1d407879 100644 --- a/Libraries/Opc.Ua.Client/DataDictionary.cs +++ b/Libraries/Opc.Ua.Client/DataDictionary.cs @@ -2,7 +2,7 @@ * 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 @@ -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, @@ -32,6 +32,7 @@ using System.IO; using System.Linq; using System.Runtime.Serialization; +using System.Threading; using System.Threading.Tasks; using Opc.Ua.Schema; @@ -102,20 +103,20 @@ private void Initialize() /// /// Loads the dictionary identified by the node id. /// - public Task Load(INode dictionary) + public void Load(INode dictionary) { if (dictionary == null) { throw new ArgumentNullException(nameof(dictionary)); } NodeId dictionaryId = ExpandedNodeId.ToNodeId(dictionary.NodeId, m_session.NamespaceUris); - return Load(dictionaryId, dictionary.ToString()); + Load(dictionaryId, dictionary.ToString()); } /// /// Loads the dictionary identified by the node id. /// - public async Task Load(NodeId dictionaryId, string name, byte[] schema = null, IDictionary imports = null) + public void Load(NodeId dictionaryId, string name, byte[] schema = null, IDictionary imports = null) { if (dictionaryId == null) { @@ -141,7 +142,7 @@ public async Task Load(NodeId dictionaryId, string name, byte[] schema = null, I Array.Resize(ref schema, zeroTerminator); } - await Validate(schema, imports).ConfigureAwait(false); + Validate(schema, imports); ReadDataTypes(dictionaryId); @@ -225,7 +226,10 @@ private void ReadDataTypes(NodeId dictionaryId) /// /// Reads the contents of multiple data dictionaries. /// - public static async Task> ReadDictionaries(ISessionClientMethods session, IList dictionaryIds) + public static async Task> ReadDictionaries( + ISessionClientMethods session, + IList dictionaryIds, + CancellationToken ct = default) { ReadValueIdCollection itemsToRead = new ReadValueIdCollection(); foreach (var nodeId in dictionaryIds) @@ -240,17 +244,28 @@ public static async Task> ReadDictionaries(ISessionC itemsToRead.Add(itemToRead); } +#if CLIENT_ASYNC // read values. ReadResponse readResponse = await session.ReadAsync( null, 0, TimestampsToReturn.Neither, itemsToRead, - System.Threading.CancellationToken.None).ConfigureAwait(false); + ct).ConfigureAwait(false); DataValueCollection values = readResponse.Results; DiagnosticInfoCollection diagnosticInfos = readResponse.DiagnosticInfos; - + ResponseHeader response = readResponse.ResponseHeader; +#else + // read values. + ResponseHeader response = session.Read( + null, + 0, + TimestampsToReturn.Neither, + itemsToRead, + out DataValueCollection values, + out DiagnosticInfoCollection diagnosticInfos); +#endif ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); @@ -262,7 +277,7 @@ public static async Task> ReadDictionaries(ISessionC // check for error. if (StatusCode.IsBad(values[ii].StatusCode)) { - ServiceResult sr = ClientBase.GetResult(values[ii].StatusCode, 0, diagnosticInfos, readResponse.ResponseHeader); + ServiceResult sr = ClientBase.GetResult(values[ii].StatusCode, 0, diagnosticInfos, response); throw new ServiceResultException(sr); } @@ -322,9 +337,9 @@ public byte[] ReadDictionary(NodeId dictionaryId) /// /// The encoded dictionary to validate. /// Throw if an error occurred. - internal Task Validate(byte[] dictionary, bool throwOnError) + internal void Validate(byte[] dictionary, bool throwOnError) { - return Validate(dictionary, null, throwOnError); + Validate(dictionary, null, throwOnError); } /// @@ -333,7 +348,7 @@ internal Task Validate(byte[] dictionary, bool throwOnError) /// The encoded dictionary to validate. /// A table of imported namespace schemas. /// Throw if an error occurred. - internal async Task Validate(byte[] dictionary, IDictionary imports = null, bool throwOnError = false) + internal void Validate(byte[] dictionary, IDictionary imports = null, bool throwOnError = false) { MemoryStream istrm = new MemoryStream(dictionary); @@ -362,7 +377,7 @@ internal async Task Validate(byte[] dictionary, IDictionary impo var validator = new Schema.Binary.BinarySchemaValidator(imports); try { - await validator.Validate(istrm).ConfigureAwait(false); + validator.Validate(istrm); } catch (Exception e) { @@ -377,7 +392,7 @@ internal async Task Validate(byte[] dictionary, IDictionary impo TypeDictionary = validator.Dictionary; } } - #endregion +#endregion #region Private Members private ISession m_session; diff --git a/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs b/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs index 5f2788717..931a53c67 100644 --- a/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs +++ b/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2022 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, @@ -64,8 +64,7 @@ public async virtual Task CreateAsync( IList preferredLocales, CancellationToken ct = default) { - return await Session.Create(configuration, endpoint, updateBeforeConnect, false, - sessionName, sessionTimeout, identity, preferredLocales).ConfigureAwait(false); + return await Session.Create(configuration, endpoint, updateBeforeConnect, false, sessionName, sessionTimeout, identity, preferredLocales, ct).ConfigureAwait(false); } /// @@ -182,35 +181,35 @@ public virtual Task CreateChannelAsync( } /// - public virtual Task RecreateAsync(ISession sessionTemplate, CancellationToken ct = default) + public virtual async Task RecreateAsync(ISession sessionTemplate, CancellationToken ct = default) { if (!(sessionTemplate is Session template)) { throw new ArgumentOutOfRangeException(nameof(sessionTemplate), "The ISession provided is not of a supported type."); } - return Task.FromResult((ISession)Session.Recreate(template)); + return await Session.RecreateAsync(template, ct).ConfigureAwait(false); } /// - public virtual Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection, CancellationToken ct = default) + public virtual async Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection, CancellationToken ct = default) { if (!(sessionTemplate is Session template)) { throw new ArgumentOutOfRangeException(nameof(sessionTemplate), "The ISession provided is not of a supported type"); } - return Task.FromResult((ISession)Session.Recreate(template, connection)); + return await Session.RecreateAsync(template, connection, ct).ConfigureAwait(false); } /// - public virtual Task RecreateAsync(ISession sessionTemplate, ITransportChannel transportChannel, CancellationToken ct = default) + public virtual async Task RecreateAsync(ISession sessionTemplate, ITransportChannel transportChannel, CancellationToken ct = default) { if (!(sessionTemplate is Session template)) { throw new ArgumentOutOfRangeException(nameof(sessionTemplate), "The ISession provided is not of a supported type"); } - return Task.FromResult((ISession)Session.Recreate(template, transportChannel)); + return await Session.RecreateAsync(template, transportChannel, ct).ConfigureAwait(false); } #endregion } diff --git a/Libraries/Opc.Ua.Client/INodeCache.cs b/Libraries/Opc.Ua.Client/INodeCache.cs index b3c6e7301..292fe9ec1 100644 --- a/Libraries/Opc.Ua.Client/INodeCache.cs +++ b/Libraries/Opc.Ua.Client/INodeCache.cs @@ -2,7 +2,7 @@ * 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 @@ -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, @@ -28,6 +28,8 @@ * ======================================================================*/ using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace Opc.Ua.Client { @@ -64,6 +66,37 @@ public interface INodeCache : INodeTable, ITypeTable /// IList FetchNodes(IList nodeIds); +#if (CLIENT_ASYNC) + /// + /// Finds a set of nodes in the nodeset, + /// fetches missing nodes from server. + /// + /// The node identifier. + /// + Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct = default); + + /// + /// Finds a set of nodes in the nodeset, + /// fetches missing nodes from server. + /// + /// The node identifier collection. + /// + Task> FindAsync(IList nodeIds, CancellationToken ct = default); + + /// + /// Fetches a node from the server and updates the cache. + /// + /// Node id to fetch. + /// + Task FetchNodeAsync(ExpandedNodeId nodeId, CancellationToken ct = default); + + /// + /// Fetches a node collection from the server and updates the cache. + /// + /// The node identifier collection. + /// + Task> FetchNodesAsync(IList nodeIds, CancellationToken ct = default); +#endif /// /// Adds the supertypes of the node to the cache. /// @@ -79,6 +112,18 @@ public interface INodeCache : INodeTable, ITypeTable /// IList FindReferences(IList nodeIds, IList referenceTypeIds, bool isInverse, bool includeSubtypes); +#if (CLIENT_ASYNC) + /// + /// Returns the references of the specified node that meet the criteria specified. + /// + Task> FindReferencesAsync(ExpandedNodeId nodeId, NodeId referenceTypeId, bool isInverse, bool includeSubtypes, CancellationToken ct = default); + + /// + /// Returns the references of the specified nodes that meet the criteria specified. + /// + Task> FindReferencesAsync(IList nodeIds, IList referenceTypeIds, bool isInverse, bool includeSubtypes, CancellationToken ct = default); +#endif + /// /// Returns a display name for a node. /// diff --git a/Libraries/Opc.Ua.Client/ISession.cs b/Libraries/Opc.Ua.Client/ISession.cs index 44cbcce04..f7b1f6f87 100644 --- a/Libraries/Opc.Ua.Client/ISession.cs +++ b/Libraries/Opc.Ua.Client/ISession.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2022 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, @@ -23,7 +23,7 @@ * 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: + * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ @@ -99,7 +99,7 @@ public interface ISession : ISessionClient, IDisposable event PublishErrorEventHandler PublishError; /// - /// Raised when a publish request is about to acknolegde sequence numbers. + /// Raised when a publish request is about to acknowledge sequence numbers. /// /// /// If the client chose to defer acknowledge of sequenece numbers, it is responsible @@ -219,7 +219,7 @@ public interface ISession : ISessionClient, IDisposable int SubscriptionCount { get; } /// - /// If the subscriptions are deleted when a session is closed. + /// If the subscriptions are deleted when a session is closed. /// bool DeleteSubscriptionsOnClose { get; set; } @@ -277,12 +277,12 @@ public interface ISession : ISessionClient, IDisposable OperationLimits OperationLimits { get; } /// - /// If the subscriptions are transferred when a session is reconnected. + /// If the subscriptions are transferred when a session is reconnected. /// /// /// Default false, set to true if subscriptions should /// be transferred after reconnect. Service must be supported by server. - /// + /// bool TransferSubscriptionsOnReconnect { get; set; } /// @@ -378,6 +378,30 @@ public interface ISession : ISessionClient, IDisposable /// void FetchTypeTree(ExpandedNodeIdCollection typeIds); +#if (CLIENT_ASYNC) + /// + /// Updates the local copy of the server's namespace uri and server uri tables. + /// + /// The cancellation token. + Task FetchNamespaceTablesAsync(CancellationToken ct = default); + + /// + /// Updates the cache with the type and its subtypes. + /// + /// + /// This method can be used to ensure the TypeTree is populated. + /// + Task FetchTypeTreeAsync(ExpandedNodeId typeId, CancellationToken ct = default); + + /// + /// Updates the cache with the types and its subtypes. + /// + /// + /// This method can be used to ensure the TypeTree is populated. + /// + Task FetchTypeTreeAsync(ExpandedNodeIdCollection typeIds, CancellationToken ct = default); +#endif + /// /// Returns the available encodings for a node /// @@ -390,11 +414,13 @@ public interface ISession : ISessionClient, IDisposable /// The encoding Id. ReferenceDescription FindDataDescription(NodeId encodingId); +#if (CLIENT_ASYNC) /// /// Returns the data dictionary that contains the description. /// /// The description id. - Task FindDataDictionary(NodeId descriptionId); + /// + Task FindDataDictionary(NodeId descriptionId, CancellationToken ct = default); /// /// Returns the data dictionary that contains the description. @@ -402,13 +428,19 @@ public interface ISession : ISessionClient, IDisposable /// The dictionary id. /// /// The dictionary. - Task LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false); + DataDictionary LoadDataDictionary( + ReferenceDescription dictionaryNode, + bool forceReload = false); /// /// Loads all dictionaries of the OPC binary or Xml schema type system. /// /// The type system. - Task> LoadDataTypeSystem(NodeId dataTypeSystem = null); + /// + Task> LoadDataTypeSystem( + NodeId dataTypeSystem = null, + CancellationToken ct = default); +#endif /// /// Reads the values for the node attributes and returns a node object. @@ -488,6 +520,23 @@ public interface ISession : ISessionClient, IDisposable /// The errors reported by the server. void FetchReferences(IList nodeIds, out IList referenceDescriptions, out IList errors); +#if (CLIENT_ASYNC) + /// + /// Fetches all references for the specified node. + /// + /// The node id. + /// + Task FetchReferencesAsync(NodeId nodeId, CancellationToken ct); + + /// + /// Fetches all references for the specified nodes. + /// + /// The node id collection. + /// + /// A list of reference collections and the errors reported by the server. + Task<(IList, IList)> FetchReferencesAsync(IList nodeIds, CancellationToken ct); +#endif + /// /// Establishes a session with the server. /// diff --git a/Libraries/Opc.Ua.Client/NodeCache.cs b/Libraries/Opc.Ua.Client/NodeCache.cs index bbe9163c6..f8724386a 100644 --- a/Libraries/Opc.Ua.Client/NodeCache.cs +++ b/Libraries/Opc.Ua.Client/NodeCache.cs @@ -2,7 +2,7 @@ * 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 @@ -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, @@ -38,7 +38,7 @@ namespace Opc.Ua.Client /// /// An implementation of a client side nodecache. /// - public class NodeCache : INodeCache, IDisposable + public partial class NodeCache : INodeCache, IDisposable { #region Constructors /// @@ -880,7 +880,6 @@ public IList FetchNodes(IList nodeIds) m_session.ReadNodes(localIds, out IList sourceNodes, out IList readErrors); m_session.FetchReferences(localIds, out IList referenceCollectionList, out IList fetchErrors); - int ii = 0; for (ii = 0; ii < count; ii++) { diff --git a/Libraries/Opc.Ua.Client/NodeCacheAsync.cs b/Libraries/Opc.Ua.Client/NodeCacheAsync.cs new file mode 100644 index 000000000..401110ed4 --- /dev/null +++ b/Libraries/Opc.Ua.Client/NodeCacheAsync.cs @@ -0,0 +1,439 @@ +/* ======================================================================== + * Copyright (c) 2005-2023 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/ + * ======================================================================*/ + +#if CLIENT_ASYNC + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Opc.Ua.Client +{ + /// + /// An implementation of a client side nodecache. + /// + public partial class NodeCache : INodeCache + { + /// + public async Task FindAsync(ExpandedNodeId nodeId, CancellationToken ct = default) + { + // check for null. + if (NodeId.IsNull(nodeId)) + { + return null; + } + + INode node; + try + { + m_cacheLock.EnterReadLock(); + + // check if node alredy exists. + node = m_nodes.Find(nodeId); + } + finally + { + m_cacheLock.ExitReadLock(); + } + + if (node != null) + { + // do not return temporary nodes created after a Browse(). + if (node.GetType() != typeof(Node)) + { + return node; + } + } + + // fetch node from server. + try + { + return await FetchNodeAsync(nodeId, ct).ConfigureAwait(false); + } + catch (Exception e) + { + Utils.LogError("Could not fetch node from server: NodeId={0}, Reason='{1}'.", nodeId, e.Message); + // m_nodes[nodeId] = null; + return null; + } + } + + /// + public async Task> FindAsync(IList nodeIds, CancellationToken ct = default) + { + // check for null. + if (nodeIds == null || nodeIds.Count == 0) + { + return new List(); + } + + int count = nodeIds.Count; + IList nodes = new List(count); + var fetchNodeIds = new ExpandedNodeIdCollection(); + + int ii; + for (ii = 0; ii < count; ii++) + { + INode node; + try + { + m_cacheLock.EnterReadLock(); + + // check if node already exists. + node = m_nodes.Find(nodeIds[ii]); + } + finally + { + m_cacheLock.ExitReadLock(); + } + + // do not return temporary nodes created after a Browse(). + if (node != null && + node?.GetType() != typeof(Node)) + { + nodes.Add(node); + } + else + { + nodes.Add(null); + fetchNodeIds.Add(nodeIds[ii]); + } + } + + if (fetchNodeIds.Count == 0) + { + return nodes; + } + + // fetch missing nodes from server. + IList fetchedNodes; + try + { + fetchedNodes = await FetchNodesAsync(fetchNodeIds, ct).ConfigureAwait(false); + } + catch (Exception e) + { + Utils.LogError("Could not fetch nodes from server: Reason='{0}'.", e.Message); + // m_nodes[nodeId] = null; + return nodes; + } + + ii = 0; + foreach (Node fetchedNode in fetchedNodes) + { + while (ii < count && nodes[ii] != null) + { + ii++; + } + if (ii < count && nodes[ii] == null) + { + nodes[ii++] = fetchedNode; + } + else + { + Utils.LogError("Inconsistency fetching nodes from server. Not all nodes could be assigned."); + break; + } + } + + return nodes; + } + + #region ITypeTable Methods + /// + public async Task FindSuperTypeAsync(ExpandedNodeId typeId, CancellationToken ct) + { + INode type = await FindAsync(typeId, ct).ConfigureAwait(false); + + if (type == null) + { + return null; + } + + try + { + m_cacheLock.EnterReadLock(); + + return m_typeTree.FindSuperType(typeId); + } + finally + { + m_cacheLock.ExitReadLock(); + } + } + + /// + public async Task FindSuperTypeAsync(NodeId typeId, CancellationToken ct = default) + { + INode type = await FindAsync(typeId, ct).ConfigureAwait(false); + + if (type == null) + { + return null; + } + + try + { + m_cacheLock.EnterReadLock(); + + return m_typeTree.FindSuperType(typeId); + } + finally + { + m_cacheLock.ExitReadLock(); + } + } + #endregion + + #region INodeCache Methods + /// + public async Task FetchNodeAsync(ExpandedNodeId nodeId, CancellationToken ct) + { + NodeId localId = ExpandedNodeId.ToNodeId(nodeId, m_session.NamespaceUris); + + if (localId == null) + { + return null; + } + + // fetch node from server. + Node source = await m_session.ReadNodeAsync(localId, ct).ConfigureAwait(false); + + try + { + // fetch references from server. + ReferenceDescriptionCollection references = await m_session.FetchReferencesAsync(localId, ct).ConfigureAwait(false); + + try + { + m_cacheLock.EnterUpgradeableReadLock(); + + foreach (ReferenceDescription reference in references) + { + // create a placeholder for the node if it does not already exist. + if (!m_nodes.Exists(reference.NodeId)) + { + // transform absolute identifiers. + if (reference.NodeId != null && reference.NodeId.IsAbsolute) + { + reference.NodeId = ExpandedNodeId.ToNodeId(reference.NodeId, NamespaceUris); + } + + Node target = new Node(reference); + + InternalWriteLockedAttach(target); + } + + // add the reference. + source.ReferenceTable.Add(reference.ReferenceTypeId, !reference.IsForward, reference.NodeId); + } + } + finally + { + m_cacheLock.ExitUpgradeableReadLock(); + } + } + catch (Exception e) + { + Utils.LogError("Could not fetch references for valid node with NodeId = {0}. Error = {1}", nodeId, e.Message); + } + + InternalWriteLockedAttach(source); + + return source; + } + + /// + public async Task> FetchNodesAsync(IList nodeIds, CancellationToken ct) + { + int count = nodeIds.Count; + if (count == 0) + { + return new List(); + } + + NodeIdCollection localIds = new NodeIdCollection( + nodeIds.Select(nodeId => ExpandedNodeId.ToNodeId(nodeId, m_session.NamespaceUris))); + + // fetch nodes and references from server. + (IList sourceNodes, IList readErrors) = await m_session.ReadNodesAsync(localIds, NodeClass.Unspecified, ct: ct).ConfigureAwait(false); + (IList referenceCollectionList, IList fetchErrors) = await m_session.FetchReferencesAsync(localIds, ct).ConfigureAwait(false); ; + + + int ii = 0; + for (ii = 0; ii < count; ii++) + { + if (ServiceResult.IsBad(readErrors[ii])) + { + continue; + } + + if (!ServiceResult.IsBad(fetchErrors[ii])) + { + // fetch references from server. + ReferenceDescriptionCollection references = referenceCollectionList[ii]; + + foreach (ReferenceDescription reference in references) + { + try + { + m_cacheLock.EnterUpgradeableReadLock(); + + // create a placeholder for the node if it does not already exist. + if (!m_nodes.Exists(reference.NodeId)) + { + // transform absolute identifiers. + if (reference.NodeId != null && reference.NodeId.IsAbsolute) + { + reference.NodeId = ExpandedNodeId.ToNodeId(reference.NodeId, NamespaceUris); + } + + Node target = new Node(reference); + + InternalWriteLockedAttach(target); + } + } + finally + { + m_cacheLock.ExitUpgradeableReadLock(); + } + + // add the reference. + sourceNodes[ii].ReferenceTable.Add(reference.ReferenceTypeId, !reference.IsForward, reference.NodeId); + } + } + + InternalWriteLockedAttach(sourceNodes[ii]); + } + + return sourceNodes; + } + + /// + public async Task> FindReferencesAsync( + ExpandedNodeId nodeId, + NodeId referenceTypeId, + bool isInverse, + bool includeSubtypes, + CancellationToken ct) + { + IList targets = new List(); + + Node source = await FindAsync(nodeId, ct).ConfigureAwait(false) as Node; + + if (source == null) + { + return targets; + } + + IList references; + try + { + m_cacheLock.EnterReadLock(); + + references = source.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); + } + finally + { + m_cacheLock.ExitReadLock(); + } + + var targetIds = new ExpandedNodeIdCollection( + references.Select(reference => reference.TargetId)); + + IList result = await FindAsync(targetIds, ct).ConfigureAwait(false); + + foreach (INode target in result) + { + if (target != null) + { + targets.Add(target); + } + } + return targets; + } + + /// + public async Task> FindReferencesAsync( + IList nodeIds, + IList referenceTypeIds, + bool isInverse, + bool includeSubtypes, + CancellationToken ct) + { + IList targets = new List(); + if (nodeIds.Count == 0 || referenceTypeIds.Count == 0) + { + return targets; + } + ExpandedNodeIdCollection targetIds = new ExpandedNodeIdCollection(); + IList sources = await FindAsync(nodeIds, ct).ConfigureAwait(false); + foreach (INode source in sources) + { + if (!(source is Node node)) + { + continue; + } + + foreach (var referenceTypeId in referenceTypeIds) + { + IList references; + try + { + m_cacheLock.EnterReadLock(); + + references = node.ReferenceTable.Find(referenceTypeId, isInverse, includeSubtypes, m_typeTree); + } + finally + { + m_cacheLock.ExitReadLock(); + } + + targetIds.AddRange( + references.Select(reference => reference.TargetId)); + } + } + + IList result = await FindAsync(targetIds, ct).ConfigureAwait(false); + foreach (INode target in result) + { + if (target != null) + { + targets.Add(target); + } + } + + return targets; + } + #endregion + } +} +#endif diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs index f6092f521..1967d102d 100644 --- a/Libraries/Opc.Ua.Client/Session.cs +++ b/Libraries/Opc.Ua.Client/Session.cs @@ -380,6 +380,9 @@ protected override void Dispose(bool disposing) Utils.SilentDispose(m_defaultSubscription); m_defaultSubscription = null; + Utils.SilentDispose(m_nodeCache); + m_nodeCache = null; + IList subscriptions = null; lock (SyncRoot) { @@ -683,12 +686,12 @@ public int SubscriptionCount } /// - /// If the subscriptions are deleted when a session is closed. + /// If the subscriptions are deleted when a session is closed. /// /// /// Default true, set to false if subscriptions need to /// be transferred or for durable subscriptions. - /// + /// public bool DeleteSubscriptionsOnClose { get { return m_deleteSubscriptionsOnClose; } @@ -696,12 +699,12 @@ public bool DeleteSubscriptionsOnClose } /// - /// If the subscriptions are transferred when a session is reconnected. + /// If the subscriptions are transferred when a session is reconnected. /// /// /// Default false, set to true if subscriptions should /// be transferred after reconnect. Service must be supported by server. - /// + /// public bool TransferSubscriptionsOnReconnect { get { return m_transferSubscriptionsOnReconnect; } @@ -937,7 +940,7 @@ public static Session Create( /// /// The application configuration. /// The client endpoint for the reverse connect. - /// A configured endpoint to connect to. + /// A configured endpoint to connect to. /// Update configuration based on server prior connect. /// Check that the certificate specifies a valid domain (computer) name. /// The cancellation token. @@ -1094,8 +1097,7 @@ public static async Task Create( { if (reverseConnectManager == null) { - return await Create(configuration, endpoint, updateBeforeConnect, - checkDomain, sessionName, sessionTimeout, userIdentity, preferredLocales).ConfigureAwait(false); + return await Create(configuration, endpoint, updateBeforeConnect, checkDomain, sessionName, sessionTimeout, userIdentity, preferredLocales, ct).ConfigureAwait(false); } ITransportWaitingConnection connection = null; @@ -1588,23 +1590,7 @@ public IEnumerable Load(string filePath, bool transferSubscription /// public void FetchNamespaceTables() { - ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); - - // request namespace array. - ReadValueId valueId = new ReadValueId { - NodeId = Variables.Server_NamespaceArray, - AttributeId = Attributes.Value - }; - - nodesToRead.Add(valueId); - - // request server array. - valueId = new ReadValueId { - NodeId = Variables.Server_ServerArray, - AttributeId = Attributes.Value - }; - - nodesToRead.Add(valueId); + ReadValueIdCollection nodesToRead = PrepareNamespaceTableNodesToRead(); // read from server. ResponseHeader responseHeader = base.Read( @@ -1618,29 +1604,7 @@ public void FetchNamespaceTables() ValidateResponse(values, nodesToRead); ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); - // validate namespace array. - ServiceResult result = ValidateDataValue(values[0], typeof(string[]), 0, diagnosticInfos, responseHeader); - - if (ServiceResult.IsBad(result)) - { - Utils.LogError("FetchNamespaceTables: Cannot read NamespaceArray node: {0}", result.StatusCode); - } - else - { - m_namespaceUris.Update((string[])values[0].Value); - } - - // validate server array. - result = ValidateDataValue(values[1], typeof(string[]), 1, diagnosticInfos, responseHeader); - - if (ServiceResult.IsBad(result)) - { - Utils.LogError("FetchNamespaceTables: Cannot read ServerArray node: {0} ", result.StatusCode); - } - else - { - m_serverUris.Update((string[])values[1].Value); - } + UpdateNamespaceTable(values, diagnosticInfos, responseHeader); } /// @@ -1816,7 +1780,7 @@ public ReferenceDescription FindDataDescription(NodeId encodingId) } /// - public async Task FindDataDictionary(NodeId descriptionId) + public async Task FindDataDictionary(NodeId descriptionId, CancellationToken ct = default) { // check if the dictionary has already been loaded. foreach (DataDictionary dictionary in m_dictionaries.Values) @@ -1827,7 +1791,7 @@ public async Task FindDataDictionary(NodeId descriptionId) } } - IList references = this.NodeCache.FindReferences(descriptionId, ReferenceTypeIds.HasComponent, true, false); + IList references = await NodeCache.FindReferencesAsync(descriptionId, ReferenceTypeIds.HasComponent, true, false, ct).ConfigureAwait(false); if (references.Count == 0) { throw ServiceResultException.Create(StatusCodes.BadNodeIdInvalid, "Description does not refer to a valid data dictionary."); @@ -1838,7 +1802,7 @@ public async Task FindDataDictionary(NodeId descriptionId) DataDictionary dictionaryToLoad = new DataDictionary(this); - await dictionaryToLoad.Load(references[0]).ConfigureAwait(false); + dictionaryToLoad.Load(references[0]); m_dictionaries[dictionaryId] = dictionaryToLoad; @@ -1846,7 +1810,7 @@ public async Task FindDataDictionary(NodeId descriptionId) } /// - public async Task LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false) + public DataDictionary LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false) { // check if the dictionary has already been loaded. DataDictionary dictionary; @@ -1859,13 +1823,13 @@ public async Task LoadDataDictionary(ReferenceDescription dictio // load the dictionary. DataDictionary dictionaryToLoad = new DataDictionary(this); - await dictionaryToLoad.Load(dictionaryId, dictionaryNode.ToString()).ConfigureAwait(false); + dictionaryToLoad.Load(dictionaryId, dictionaryNode.ToString()); m_dictionaries[dictionaryId] = dictionaryToLoad; return dictionaryToLoad; } /// - public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null) + public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null, CancellationToken ct = default) { if (dataTypeSystem == null) { @@ -1898,7 +1862,7 @@ public async Task> LoadDataTypeSystem(NodeId var referenceExpandedNodeIds = references .Select(r => ExpandedNodeId.ToNodeId(r.NodeId, this.NamespaceUris)) .Where(n => n.NamespaceIndex != 0).ToList(); - IDictionary schemas = await DataDictionary.ReadDictionaries(this, referenceExpandedNodeIds).ConfigureAwait(false); + IDictionary schemas = await DataDictionary.ReadDictionaries(this, referenceExpandedNodeIds, ct).ConfigureAwait(false); // read namespace property values var namespaces = new Dictionary(); @@ -1941,11 +1905,11 @@ public async Task> LoadDataTypeSystem(NodeId dictionaryToLoad = new DataDictionary(this); if (schemas.TryGetValue(dictionaryId, out var schema)) { - await dictionaryToLoad.Load(dictionaryId, dictionaryId.ToString(), schema, imports).ConfigureAwait(false); + dictionaryToLoad.Load(dictionaryId, dictionaryId.ToString(), schema, imports); } else { - await dictionaryToLoad.Load(dictionaryId, dictionaryId.ToString()).ConfigureAwait(false); + dictionaryToLoad.Load(dictionaryId, dictionaryId.ToString()); } m_dictionaries[dictionaryId] = dictionaryToLoad; } @@ -4123,6 +4087,62 @@ private void CreateNodeClassAttributesReadNodesRequest( } } + /// + /// Prepares the list of node ids to read to fetch the namespace table. + /// + private ReadValueIdCollection PrepareNamespaceTableNodesToRead() + { + var nodesToRead = new ReadValueIdCollection(); + + // request namespace array. + ReadValueId valueId = new ReadValueId { + NodeId = Variables.Server_NamespaceArray, + AttributeId = Attributes.Value + }; + + nodesToRead.Add(valueId); + + // request server array. + valueId = new ReadValueId { + NodeId = Variables.Server_ServerArray, + AttributeId = Attributes.Value + }; + + nodesToRead.Add(valueId); + + return nodesToRead; + } + + /// + /// Updates the NamespaceTable with the result of the read operation. + /// + private void UpdateNamespaceTable(DataValueCollection values, DiagnosticInfoCollection diagnosticInfos, ResponseHeader responseHeader) + { + // validate namespace array. + ServiceResult result = ValidateDataValue(values[0], typeof(string[]), 0, diagnosticInfos, responseHeader); + + if (ServiceResult.IsBad(result)) + { + Utils.LogError("FetchNamespaceTables: Cannot read NamespaceArray node: {0}", result.StatusCode); + } + else + { + m_namespaceUris.Update((string[])values[0].Value); + } + + // validate server array. + result = ValidateDataValue(values[1], typeof(string[]), 1, diagnosticInfos, responseHeader); + + if (ServiceResult.IsBad(result)) + { + Utils.LogError("FetchNamespaceTables: Cannot read ServerArray node: {0} ", result.StatusCode); + } + else + { + m_serverUris.Update((string[])values[1].Value); + } + } + /// /// Creates a read request with attributes determined by the NodeClass. /// @@ -4182,7 +4202,7 @@ bool optionalAttributes } /// - /// Builds the node collection results based on the attribute values of the read response. + /// Builds the node collection results based on the attribute values of the read response. /// /// The collection of all attributes to read passed in the read request. /// The attributes requested per NodeId @@ -5553,7 +5573,7 @@ private void ProcessPublishResponse( } #if DEBUG_SEQUENTIALPUBLISHING - // Checks for debug info only. + // Checks for debug info only. // Once more than a single publish request is queued, the checks are invalid // because a publish response may not include the latest ack information yet. diff --git a/Libraries/Opc.Ua.Client/SessionAsync.cs b/Libraries/Opc.Ua.Client/SessionAsync.cs index 50faf1d5c..f7ede5eee 100644 --- a/Libraries/Opc.Ua.Client/SessionAsync.cs +++ b/Libraries/Opc.Ua.Client/SessionAsync.cs @@ -2,7 +2,7 @@ * 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 @@ -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, @@ -32,6 +32,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -271,8 +272,7 @@ public async Task OpenAsync( } // fetch namespaces. - FetchNamespaceTables(); - // TODO: await FetchNamespaceTablesAsync().ConfigureAwait(false); + await FetchNamespaceTablesAsync(ct).ConfigureAwait(false); lock (SyncRoot) { @@ -290,8 +290,7 @@ public async Task OpenAsync( } // fetch operation limits - FetchOperationLimits(); - // TODO: await FetchOperationLimitsAsync().ConfigureAwait(false); + await FetchOperationLimitsAsync(ct).ConfigureAwait(false); // start keep alive thread. StartKeepAliveTimer(); @@ -387,7 +386,7 @@ public async Task ReactivateSubscriptionsAsync( { try { - await m_reconnectLock.WaitAsync().ConfigureAwait(false); + await m_reconnectLock.WaitAsync(ct).ConfigureAwait(false); m_reconnecting = true; for (int ii = 0; ii < subscriptions.Count; ii++) @@ -453,7 +452,7 @@ public async Task ReactivateSubscriptionsAsync( ClientBase.ValidateDiagnosticInfos(diagnosticInfos, requests); int ii = 0; - foreach (var value in results) + foreach (CallMethodResult value in results) { ServiceResult result = ServiceResult.Good; if (StatusCode.IsNotGood(value.StatusCode)) @@ -487,7 +486,7 @@ public async Task TransferSubscriptionsAsync( { try { - await m_reconnectLock.WaitAsync().ConfigureAwait(false); + await m_reconnectLock.WaitAsync(ct).ConfigureAwait(false); m_reconnecting = true; TransferSubscriptionsResponse response = await base.TransferSubscriptionsAsync(null, subscriptionIds, sendInitialValues, ct).ConfigureAwait(false); @@ -508,12 +507,12 @@ public async Task TransferSubscriptionsAsync( { if (StatusCode.IsGood(results[ii].StatusCode)) { - if (await subscriptions[ii].TransferAsync(this, subscriptionIds[ii], results[ii].AvailableSequenceNumbers).ConfigureAwait(false)) + if (await subscriptions[ii].TransferAsync(this, subscriptionIds[ii], results[ii].AvailableSequenceNumbers, ct).ConfigureAwait(false)) { lock (SyncRoot) { // create ack for available sequence numbers - foreach (var sequenceNumber in results[ii].AvailableSequenceNumbers) + foreach (uint sequenceNumber in results[ii].AvailableSequenceNumbers) { var ack = new SubscriptionAcknowledgement() { SubscriptionId = subscriptionIds[ii], @@ -555,6 +554,132 @@ public async Task TransferSubscriptionsAsync( } #endregion + #region FetchNamespaceTables Async Methods + /// + public async Task FetchNamespaceTablesAsync(CancellationToken ct = default) + { + ReadValueIdCollection nodesToRead = PrepareNamespaceTableNodesToRead(); + + // read from server. + ReadResponse response = await ReadAsync( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + ct).ConfigureAwait(false); + + DataValueCollection values = response.Results; + DiagnosticInfoCollection diagnosticInfos = response.DiagnosticInfos; + ResponseHeader responseHeader = response.ResponseHeader; + + ValidateResponse(values, nodesToRead); + ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); + + UpdateNamespaceTable(values, diagnosticInfos, responseHeader); + } + #endregion + + #region FetchTypeTree Async Methods + /// + public async Task FetchTypeTreeAsync(ExpandedNodeId typeId, CancellationToken ct = default) + { + Node node = await NodeCache.FindAsync(typeId, ct).ConfigureAwait(false) as Node; + + if (node != null) + { + var subTypes = new ExpandedNodeIdCollection(); + foreach (IReference reference in node.Find(ReferenceTypeIds.HasSubtype, false)) + { + subTypes.Add(reference.TargetId); + } + if (subTypes.Count > 0) + { + await FetchTypeTreeAsync(subTypes, ct).ConfigureAwait(false); + } + } + } + + /// + public async Task FetchTypeTreeAsync(ExpandedNodeIdCollection typeIds, CancellationToken ct = default) + { + var referenceTypeIds = new NodeIdCollection() { ReferenceTypeIds.HasSubtype }; + IList nodes = await NodeCache.FindReferencesAsync(typeIds, referenceTypeIds, false, false, ct).ConfigureAwait(false); + var subTypes = new ExpandedNodeIdCollection(); + foreach (INode inode in nodes) + { + if (inode is Node node) + { + foreach (IReference reference in node.Find(ReferenceTypeIds.HasSubtype, false)) + { + if (!typeIds.Contains(reference.TargetId)) + { + subTypes.Add(reference.TargetId); + } + } + } + } + if (subTypes.Count > 0) + { + await FetchTypeTreeAsync(subTypes, ct).ConfigureAwait(false); + } + } + #endregion + + #region FetchOperationLimits Async Methods + /// + /// Fetch the operation limits of the server. + /// + public async Task FetchOperationLimitsAsync(CancellationToken ct) + { + try + { + var operationLimitsProperties = typeof(OperationLimits) + .GetProperties().Select(p => p.Name).ToList(); + + var nodeIds = new NodeIdCollection( + operationLimitsProperties.Select(name => (NodeId)typeof(VariableIds) + .GetField("Server_ServerCapabilities_OperationLimits_" + name, BindingFlags.Public | BindingFlags.Static) + .GetValue(null)) + ); + + (DataValueCollection values, IList errors) = await ReadValuesAsync(nodeIds, ct).ConfigureAwait(false); + + OperationLimits configOperationLimits = m_configuration?.ClientConfiguration?.OperationLimits ?? new OperationLimits(); + var operationLimits = new OperationLimits(); + + for (int ii = 0; ii < nodeIds.Count; ii++) + { + PropertyInfo property = typeof(OperationLimits).GetProperty(operationLimitsProperties[ii]); + uint value = (uint)property.GetValue(configOperationLimits); + if (values[ii] != null && + ServiceResult.IsNotBad(errors[ii])) + { + if (values[ii].Value is uint serverValue) + { + if (serverValue > 0 && + (value == 0 || serverValue < value)) + { + value = serverValue; + } + } + } + property.SetValue(operationLimits, value); + } + + OperationLimits = operationLimits; + } + catch (Exception ex) + { + Utils.LogError(ex, "Failed to read operation limits from server. Using configuration defaults."); + OperationLimits operationLimits = m_configuration?.ClientConfiguration?.OperationLimits; + if (operationLimits != null) + { + OperationLimits = operationLimits; + } + } + } + #endregion + #region ReadNode Async Methods /// public async Task<(IList, IList)> ReadNodesAsync( @@ -694,7 +819,7 @@ public async Task ReadNodeAsync( CancellationToken ct = default) { // build list of attributes. - var attributes = CreateAttributes(nodeClass, optionalAttributes); + IDictionary attributes = CreateAttributes(nodeClass, optionalAttributes); // build list of values to read. ReadValueIdCollection itemsToRead = new ReadValueIdCollection(); @@ -794,7 +919,7 @@ public async Task ReadValueAsync( ClientBase.ValidateResponse(values, itemsToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, itemsToRead); - foreach (var value in values) + foreach (DataValue value in values) { ServiceResult result = ServiceResult.Good; if (StatusCode.IsBad(value.StatusCode)) @@ -808,6 +933,128 @@ public async Task ReadValueAsync( } #endregion + #region Browse Methods + /// + public async Task<( + ResponseHeader responseHeader, + ByteStringCollection continuationPoints, + IList referencesList, + IList errors + )> BrowseAsync( + RequestHeader requestHeader, + ViewDescription view, + IList nodesToBrowse, + uint maxResultsToReturn, + BrowseDirection browseDirection, + NodeId referenceTypeId, + bool includeSubtypes, + uint nodeClassMask, + CancellationToken ct = default) + { + + BrowseDescriptionCollection browseDescription = new BrowseDescriptionCollection(); + foreach (NodeId nodeToBrowse in nodesToBrowse) + { + BrowseDescription description = new BrowseDescription { + NodeId = nodeToBrowse, + BrowseDirection = browseDirection, + ReferenceTypeId = referenceTypeId, + IncludeSubtypes = includeSubtypes, + NodeClassMask = nodeClassMask, + ResultMask = (uint)BrowseResultMask.All + }; + + browseDescription.Add(description); + } + + BrowseResponse browseResponse = await BrowseAsync( + requestHeader, + view, + maxResultsToReturn, + browseDescription, + ct).ConfigureAwait(false); + + ClientBase.ValidateResponse(browseResponse.ResponseHeader); + BrowseResultCollection results = browseResponse.Results; + DiagnosticInfoCollection diagnosticInfos = browseResponse.DiagnosticInfos; + + ClientBase.ValidateResponse(results, browseDescription); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, browseDescription); + + int ii = 0; + var errors = new List(); + var continuationPoints = new ByteStringCollection(); + var referencesList = new List(); + foreach (BrowseResult result in results) + { + if (StatusCode.IsBad(result.StatusCode)) + { + errors.Add(new ServiceResult(result.StatusCode, ii, diagnosticInfos, browseResponse.ResponseHeader.StringTable)); + } + else + { + errors.Add(ServiceResult.Good); + } + continuationPoints.Add(result.ContinuationPoint); + referencesList.Add(result.References); + ii++; + } + + return (browseResponse.ResponseHeader, continuationPoints, referencesList, errors); + } + #endregion + + #region BrowseNext Methods + + /// + public async Task<( + ResponseHeader responseHeader, + ByteStringCollection revisedContinuationPoints, + IList referencesList, + List errors + )> BrowseNextAsync( + RequestHeader requestHeader, + ByteStringCollection continuationPoints, + bool releaseContinuationPoint, + CancellationToken ct = default) + { + BrowseNextResponse response = await base.BrowseNextAsync( + requestHeader, + releaseContinuationPoint, + continuationPoints, + ct).ConfigureAwait(false); + + ClientBase.ValidateResponse(response.ResponseHeader); + + BrowseResultCollection results = response.Results; + DiagnosticInfoCollection diagnosticInfos = response.DiagnosticInfos; + + ClientBase.ValidateResponse(results, continuationPoints); + ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); + + int ii = 0; + var errors = new List(); + var revisedContinuationPoints = new ByteStringCollection(); + var referencesList = new List(); + foreach (BrowseResult result in results) + { + if (StatusCode.IsBad(result.StatusCode)) + { + errors.Add(new ServiceResult(result.StatusCode, ii, diagnosticInfos, response.ResponseHeader.StringTable)); + } + else + { + errors.Add(ServiceResult.Good); + } + revisedContinuationPoints.Add(result.ContinuationPoint); + referencesList.Add(result.References); + ii++; + } + + return (response.ResponseHeader, revisedContinuationPoints, referencesList, errors); + } + #endregion + #region Call Methods /// public async Task> CallAsync(NodeId objectId, NodeId methodId, CancellationToken ct = default, params object[] args) @@ -858,6 +1105,270 @@ public async Task> CallAsync(NodeId objectId, NodeId methodId, Can } #endregion + #region FetchReferences Async Methods + /// + public async Task FetchReferencesAsync( + NodeId nodeId, + CancellationToken ct = default) + { + // browse for all references. + + ReferenceDescriptionCollection results = new ReferenceDescriptionCollection(); + ( + _, + ByteStringCollection continuationPoint, + IList descriptions, + _ + ) = await BrowseAsync( + null, + null, + new[] { nodeId }, + 0, + BrowseDirection.Both, + null, + true, + 0, + ct).ConfigureAwait(false); + + if (descriptions.Count > 0) + { + results.AddRange(descriptions[0]); + + // process any continuation point. + while (continuationPoint != null && continuationPoint.Count > 0 & continuationPoint[0] != null) + { + ( + _, + ByteStringCollection revisedContinuationPoint, + IList additionalDescriptions, + _ + ) = await BrowseNextAsync( + null, + continuationPoint, + false, + ct).ConfigureAwait(false); + + continuationPoint = revisedContinuationPoint; + + if (additionalDescriptions.Count > 0) + results.AddRange(additionalDescriptions[0]); + } + } + return results; + } + + /// + public async Task<(IList, IList)> FetchReferencesAsync( + IList nodeIds, + CancellationToken ct = default) + { + var result = new List(); + + // browse for all references. + ( + _, + ByteStringCollection continuationPoints, + IList descriptions, + IList errors + ) = await BrowseAsync( + null, + null, + nodeIds, + 0, + BrowseDirection.Both, + null, + true, + 0, + ct).ConfigureAwait(false); + + result.AddRange(descriptions); + + // process any continuation point. + List previousResult = result; + IList previousErrors = errors; + while (HasAnyContinuationPoint(continuationPoints)) + { + var nextContinuationPoints = new ByteStringCollection(); + var nextResult = new List(); + var nextErrors = new List(); + + for (int ii = 0; ii < continuationPoints.Count; ii++) + { + byte[] cp = continuationPoints[ii]; + if (cp != null) + { + nextContinuationPoints.Add(cp); + nextResult.Add(previousResult[ii]); + nextErrors.Add(previousErrors[ii]); + } + } + + ( + _, + ByteStringCollection revisedContinuationPoints, + IList nextDescriptions, + IList browseNextErrors + ) = await BrowseNextAsync( + null, + nextContinuationPoints, + false, + ct).ConfigureAwait(false); + + continuationPoints = revisedContinuationPoints; + previousResult = nextResult; + previousErrors = nextErrors; + + for (int ii = 0; ii < nextDescriptions.Count; ii++) + { + nextResult[ii].AddRange(nextDescriptions[ii]); + if (StatusCode.IsBad(browseNextErrors[ii].StatusCode)) + { + nextErrors[ii] = browseNextErrors[ii]; + } + } + } + + return (result, errors); + } + #endregion + + #region Recreate Async Methods + /// + /// Recreates a session based on a specified template. + /// + /// The Session object to use as template + /// + /// The new session object. + public static async Task RecreateAsync(Session template, CancellationToken ct = default) + { + ServiceMessageContext messageContext = template.m_configuration.CreateMessageContext(); + messageContext.Factory = template.Factory; + + // create the channel object used to connect to the server. + ITransportChannel channel = SessionChannel.Create( + template.m_configuration, + template.ConfiguredEndpoint.Description, + template.ConfiguredEndpoint.Configuration, + template.m_instanceCertificate, + template.m_configuration.SecurityConfiguration.SendCertificateChain ? + template.m_instanceCertificateChain : null, + messageContext); + + // create the session object. + Session session = new Session(channel, template, true); + + try + { + // open the session. + await session.OpenAsync( + template.SessionName, + (uint)template.SessionTimeout, + template.Identity, + template.PreferredLocales, + template.m_checkDomain, + ct).ConfigureAwait(false); + + await session.RecreateSubscriptionsAsync(template.Subscriptions, ct).ConfigureAwait(false); + } + catch (Exception e) + { + session.Dispose(); + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, e, "Could not recreate session. {0}", template.SessionName); + } + + return session; + } + + /// + /// Recreates a session based on a specified template. + /// + /// The Session object to use as template + /// The waiting reverse connection. + /// + /// The new session object. + public static async Task RecreateAsync(Session template, ITransportWaitingConnection connection, CancellationToken ct = default) + { + ServiceMessageContext messageContext = template.m_configuration.CreateMessageContext(); + messageContext.Factory = template.Factory; + + // create the channel object used to connect to the server. + ITransportChannel channel = SessionChannel.Create( + template.m_configuration, + connection, + template.m_endpoint.Description, + template.m_endpoint.Configuration, + template.m_instanceCertificate, + template.m_configuration.SecurityConfiguration.SendCertificateChain ? + template.m_instanceCertificateChain : null, + messageContext); + + // create the session object. + Session session = new Session(channel, template, true); + + try + { + // open the session. + await session.OpenAsync( + template.m_sessionName, + (uint)template.m_sessionTimeout, + template.m_identity, + template.m_preferredLocales, + template.m_checkDomain, + ct).ConfigureAwait(false); + + await session.RecreateSubscriptionsAsync(template.Subscriptions, ct).ConfigureAwait(false); + } + catch (Exception e) + { + session.Dispose(); + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, e, "Could not recreate session. {0}", template.m_sessionName); + } + + return session; + } + + /// + /// Recreates a session based on a specified template using the provided channel. + /// + /// The Session object to use as template + /// The waiting reverse connection. + /// + /// The new session object. + public static async Task RecreateAsync(Session template, ITransportChannel transportChannel, CancellationToken ct = default) + { + ServiceMessageContext messageContext = template.m_configuration.CreateMessageContext(); + messageContext.Factory = template.Factory; + + // create the session object. + Session session = new Session(transportChannel, template, true); + + try + { + // open the session. + await session.OpenAsync( + template.m_sessionName, + (uint)template.m_sessionTimeout, + template.m_identity, + template.m_preferredLocales, + template.m_checkDomain, + ct).ConfigureAwait(false); + + // create the subscriptions. + foreach (Subscription subscription in session.Subscriptions) + { + await subscription.CreateAsync(ct).ConfigureAwait(false); + } + } + catch (Exception e) + { + session.Dispose(); + throw ServiceResultException.Create(StatusCodes.BadCommunicationError, e, "Could not recreate session. {0}", template.m_sessionName); + } + + return session; + } + #endregion + #region Close Async Methods /// public override Task CloseAsync(CancellationToken ct = default) @@ -918,7 +1429,7 @@ public virtual async Task CloseAsync(int timeout, bool closeChannel, { // close the session and delete all subscriptions if specified. this.OperationTimeout = timeout; - var response = await base.CloseSessionAsync(null, m_deleteSubscriptionsOnClose, ct).ConfigureAwait(false); + CloseSessionResponse response = await base.CloseSessionAsync(null, m_deleteSubscriptionsOnClose, ct).ConfigureAwait(false); this.OperationTimeout = existingTimeout; if (closeChannel) @@ -959,6 +1470,52 @@ public virtual async Task CloseAsync(int timeout, bool closeChannel, return result; } #endregion + + /// + /// Recreate the subscriptions in a reconnected session. + /// Uses Transfer service if is set to true. + /// + /// The template for the subscriptions. + /// + private async Task RecreateSubscriptionsAsync(IEnumerable subscriptionsTemplate, CancellationToken ct) + { + bool transferred = false; + if (TransferSubscriptionsOnReconnect) + { + try + { + transferred = await TransferSubscriptionsAsync(new SubscriptionCollection(subscriptionsTemplate), false, ct).ConfigureAwait(false); + } + catch (ServiceResultException sre) + { + if (sre.StatusCode == StatusCodes.BadServiceUnsupported) + { + TransferSubscriptionsOnReconnect = false; + Utils.LogWarning("Transfer subscription unsupported, TransferSubscriptionsOnReconnect set to false."); + } + else + { + Utils.LogError(sre, "Transfer subscriptions failed."); + } + } + catch (Exception ex) + { + Utils.LogError(ex, "Unexpected Transfer subscriptions error."); + } + } + + if (!transferred) + { + // Create the subscriptions which were not transferred. + foreach (Subscription subscription in Subscriptions) + { + if (!subscription.Created) + { + await subscription.CreateAsync(ct).ConfigureAwait(false); + } + } + } + } } } #endif diff --git a/Libraries/Opc.Ua.Client/TraceableSession.cs b/Libraries/Opc.Ua.Client/TraceableSession.cs index 68f034c37..88bbd36a2 100644 --- a/Libraries/Opc.Ua.Client/TraceableSession.cs +++ b/Libraries/Opc.Ua.Client/TraceableSession.cs @@ -2,7 +2,7 @@ * Copyright (c) 2005-2023 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, @@ -31,6 +31,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -371,6 +372,24 @@ public void FetchTypeTree(ExpandedNodeIdCollection typeIds) } } + /// + public async Task FetchTypeTreeAsync(ExpandedNodeId typeId, CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity(nameof(FetchTypeTree))) + { + await m_session.FetchTypeTreeAsync(typeId, ct).ConfigureAwait(false); + } + } + + /// + public async Task FetchTypeTreeAsync(ExpandedNodeIdCollection typeIds, CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity(nameof(FetchTypeTree))) + { + await m_session.FetchTypeTreeAsync(typeIds, ct).ConfigureAwait(false); + } + } + /// public ReferenceDescriptionCollection ReadAvailableEncodings(NodeId variableId) { @@ -390,29 +409,29 @@ public ReferenceDescription FindDataDescription(NodeId encodingId) } /// - public async Task FindDataDictionary(NodeId descriptionId) + public async Task FindDataDictionary(NodeId descriptionId, CancellationToken ct = default) { using (Activity activity = ActivitySource.StartActivity(nameof(FindDataDictionary))) { - return await m_session.FindDataDictionary(descriptionId).ConfigureAwait(false); + return await m_session.FindDataDictionary(descriptionId, ct).ConfigureAwait(false); } } /// - public async Task LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false) + public DataDictionary LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false) { using (Activity activity = ActivitySource.StartActivity(nameof(LoadDataDictionary))) { - return await m_session.LoadDataDictionary(dictionaryNode, forceReload).ConfigureAwait(false); + return m_session.LoadDataDictionary(dictionaryNode, forceReload); } } /// - public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null) + public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null, CancellationToken ct = default) { using (Activity activity = ActivitySource.StartActivity(nameof(LoadDataTypeSystem))) { - return await m_session.LoadDataTypeSystem(dataTypeSystem).ConfigureAwait(false); + return await m_session.LoadDataTypeSystem(dataTypeSystem, ct).ConfigureAwait(false); } } @@ -497,6 +516,24 @@ public void FetchReferences(IList nodeIds, out IList + public async Task FetchReferencesAsync(NodeId nodeId, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity(nameof(FetchReferencesAsync))) + { + return await m_session.FetchReferencesAsync(nodeId, ct).ConfigureAwait(false); + } + } + + /// + public async Task<(IList, IList)> FetchReferencesAsync(IList nodeIds, CancellationToken ct) + { + using (Activity activity = ActivitySource.StartActivity(nameof(FetchReferencesAsync))) + { + return await m_session.FetchReferencesAsync(nodeIds, ct).ConfigureAwait(false); + } + } + /// public void Open(string sessionName, IUserIdentity identity) { @@ -596,6 +633,16 @@ public async Task OpenAsync(string sessionName, uint sessionTimeout, IUserIdenti } } + + /// + public async Task FetchNamespaceTablesAsync(CancellationToken ct = default) + { + using (Activity activity = ActivitySource.StartActivity(nameof(FetchNamespaceTablesAsync))) + { + await m_session.FetchNamespaceTablesAsync(ct).ConfigureAwait(false); + } + } + /// public async Task<(IList, IList)> ReadNodesAsync(IList nodeIds, NodeClass nodeClass, bool optionalAttributes = false, CancellationToken ct = default) { 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/Configuration/ConfiguredEndpoints.cs b/Stack/Opc.Ua.Core/Stack/Configuration/ConfiguredEndpoints.cs index 20c089647..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 { - await client.CloseAsync().ConfigureAwait(false); + 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/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/Schemas/BinarySchemaValidator.cs b/Stack/Opc.Ua.Core/Types/Schemas/BinarySchemaValidator.cs index 113576ee7..aa56dbf07 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(); } /// @@ -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/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/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/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..727eb09e6 --- /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 FetchTypeTree() + { + await Session.FetchTypeTreeAsync(NodeId.ToExpandedNodeId(DataTypeIds.BaseDataType, Session.NamespaceUris)).ConfigureAwait(false); + } + + [Test, Order(910)] + public async Task FetchAllReferenceTypes() + { + 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 NodeCacheFetchNodesConcurrent() + { + 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 FetchAllReferenceTypes().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 FetchAllReferenceTypes().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); + Session.NodeCache.FetchSuperTypes(result5.NodeId); + 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.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); } From 88f259296259c21a882c1e315e63b679d489045b Mon Sep 17 00:00:00 2001 From: Stefan Zschocke Date: Tue, 26 Sep 2023 20:41:49 +0200 Subject: [PATCH 13/20] Port to bouncycastle cryptography (#2315) * Port to bouncycastle cryptography * fix X509SignatureFactory.cs for latest bouncy castle --- .../Opc.Ua.Gds.Server.Common.csproj | 5 ++- .../Opc.Ua.Security.Certificates.csproj | 10 +++--- .../Org.BouncyCastle/CertificateBuilder.cs | 5 +-- .../Org.BouncyCastle/X509SignatureFactory.cs | 31 +++---------------- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 4 +-- 5 files changed, 18 insertions(+), 37 deletions(-) diff --git a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj index 8e457e0a1..2f375386b 100644 --- a/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj +++ b/Libraries/Opc.Ua.Gds.Server.Common/Opc.Ua.Gds.Server.Common.csproj @@ -24,9 +24,12 @@ + + + + - diff --git a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj index 41a3bbf1a..aa00dd167 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj +++ b/Libraries/Opc.Ua.Security.Certificates/Opc.Ua.Security.Certificates.csproj @@ -46,23 +46,23 @@ - - + - + - + - + + diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs index 3ceb7facb..ff9a39ca1 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/CertificateBuilder.cs @@ -219,8 +219,9 @@ public static byte[] CreateSigningRequest( if (generalNames.Count > 0) { - IList oids = new ArrayList(); - IList values = new ArrayList(); + IList oids = new List(); + IList< Org.BouncyCastle.Asn1.X509.X509Extension> values + = new List< Org.BouncyCastle.Asn1.X509.X509Extension>(); oids.Add(Org.BouncyCastle.Asn1.X509.X509Extensions.SubjectAlternativeName); values.Add(new Org.BouncyCastle.Asn1.X509.X509Extension(false, new DerOctetString(new GeneralNames(generalNames.ToArray()).GetDerEncoded()))); diff --git a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509SignatureFactory.cs b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509SignatureFactory.cs index 9926427fd..ef39dbcb5 100644 --- a/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509SignatureFactory.cs +++ b/Libraries/Opc.Ua.Security.Certificates/Org.BouncyCastle/X509SignatureFactory.cs @@ -82,7 +82,7 @@ public X509SignatureFactory(HashAlgorithmName hashAlgorithm, X509SignatureGenera public Object AlgorithmDetails => _algID; /// - public IStreamCalculator CreateCalculator() + public IStreamCalculator CreateCalculator() { return new X509StreamCalculator(_generator, _hashAlgorithm); } @@ -90,7 +90,7 @@ public IStreamCalculator CreateCalculator() /// /// Signs a Bouncy Castle digest stream with the .Net X509SignatureGenerator. /// - class X509StreamCalculator : IStreamCalculator + class X509StreamCalculator : IStreamCalculator { private X509SignatureGenerator _generator; private readonly HashAlgorithmName _hashAlgorithm; @@ -117,36 +117,13 @@ public X509StreamCalculator( /// /// Callback signs the digest with X509SignatureGenerator. /// - public object GetResult() + public IBlockResult GetResult() { var memStream = Stream as MemoryStream; if (memStream == null) throw new ArgumentNullException(nameof(Stream)); var digest = memStream.ToArray(); var signature = _generator.SignData(digest, _hashAlgorithm); - return new MemoryBlockResult(signature); - } - } - - /// - /// Helper for Bouncy Castle signing operation to store the result in a memory block. - /// - class MemoryBlockResult : IBlockResult - { - private readonly byte[] _data; - /// - public MemoryBlockResult(byte[] data) - { - _data = data; - } - /// - public byte[] Collect() - { - return _data; - } - /// - public int Collect(byte[] destination, int offset) - { - throw new NotImplementedException(); + return new Org.BouncyCastle.Crypto.SimpleBlockResult(signature); } } } diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 7462d3f59..575e48bc5 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -88,11 +88,11 @@ - + - + From 9f0f4c56ff17aec3c83d66fa5f5a3fcd9a41ed36 Mon Sep 17 00:00:00 2001 From: tomaszras-intive <123160728+tomaszras-intive@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:02:20 +0200 Subject: [PATCH 14/20] Make CertificateValidationEventArgs and CertificateUpdateEventArgs constructors public (#2329) Making these constructors public will unblock testing important scenarios. --- .../Security/Certificates/CertificateValidator.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs index a0365ca18..2ffdf3a7d 100644 --- a/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs +++ b/Stack/Opc.Ua.Core/Security/Certificates/CertificateValidator.cs @@ -434,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). @@ -547,7 +547,7 @@ public virtual void Validate(X509Certificate2Collection chain, ConfiguredEndpoin } /// - /// + /// /// /// /// @@ -1514,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)) @@ -1780,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; @@ -1853,7 +1853,7 @@ public class CertificateUpdateEventArgs : EventArgs /// /// Creates a new instance. /// - internal CertificateUpdateEventArgs( + public CertificateUpdateEventArgs( SecurityConfiguration configuration, ICertificateValidator validator) { From 49c8c00f5bc61c05479c3258154e52385764965a Mon Sep 17 00:00:00 2001 From: Mikael Borg <55872215+mcyborg@users.noreply.github.com> Date: Sun, 15 Oct 2023 17:44:18 +0200 Subject: [PATCH 15/20] .NET target 5.0 and 6.0 use 6.0 version of Microsoft.Extensions.Logging.Abstractions (#2335) Referencing package OPCFoundation.NetStandard.Opc.Ua.Core from a .NET6 targeted project that in it self are using Microsoft.Extensions.Logging.Abstractions 6.0.4 will cause downgrade problems. Instead for .NET6 targeted applications reference 6.0.x versions of Microsoft.Extensions.Logging.Abstractions. --- Stack/Opc.Ua.Core/Opc.Ua.Core.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj index 575e48bc5..c2d750b8f 100644 --- a/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj +++ b/Stack/Opc.Ua.Core/Opc.Ua.Core.csproj @@ -42,12 +42,12 @@ - + - + @@ -58,7 +58,7 @@ - + @@ -81,7 +81,7 @@ - + $(BaseIntermediateOutputPath)/zipnodeset2 Schema/Opc.Ua.NodeSet2.xml From 3f45eb77667934fac2590fec1159fd4ee991a7fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:58:26 +0200 Subject: [PATCH 16/20] Bump BenchmarkDotNet from 0.13.8 to 0.13.9 (#2334) * Bump BenchmarkDotNet from 0.13.8 to 0.13.9 Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.8 to 0.13.9. - [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases) - [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.8...v0.13.9) --- updated-dependencies: - dependency-name: BenchmarkDotNet dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update Opc.Ua.Configuration.Tests.csproj --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Martin Regen --- .../Opc.Ua.Configuration.Tests.csproj | 2 +- Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj | 2 +- .../Opc.Ua.Security.Certificates.Tests.csproj | 2 +- Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) 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 3aac140ee..c3055ad6e 100644 --- a/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj +++ b/Tests/Opc.Ua.Configuration.Tests/Opc.Ua.Configuration.Tests.csproj @@ -19,7 +19,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + 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 4a13cf5ac..026198c17 100644 --- a/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj +++ b/Tests/Opc.Ua.PubSub.Tests/Opc.Ua.PubSub.Tests.csproj @@ -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 45f0d6d3b..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 @@ -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 55d2b0fb3..b1167ab94 100644 --- a/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj +++ b/Tests/Opc.Ua.Server.Tests/Opc.Ua.Server.Tests.csproj @@ -20,7 +20,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 77b1e9bfebcf594e6ba1b6fc690331f44283a266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noah=20H=C3=B6lterhoff?= <143097937+NoahHoelterhoff@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:21:21 +0200 Subject: [PATCH 17/20] Added new DataTypes to Reference Server (#2340) Three new DataTypes plus Variables containing them were added to the Reference Server: - VectorUnion (as Scalar and Array): The VectorUnion contains three fields (X, Y, Z) and a SwitchField. When it is used as value, it is generated randomly (SwitchField set to None or one of the Fields) - VectorWithOptionalFields (as Scalar and Array): The VectorWithOptionalFields contains again three fields (X, Y, Z), but they are all optional. When it is used as a value, it is generated with 0 to all fields activated. - MultipleVectors (as Scalar and Array): The MultipleVectors type contains 6 Components. A regular Vector, a VectorUnion (from before), a VectorWithOptionalFields (also from before) and the same as Arrays. --- .../Boiler/Boiler.NodeSet2.xml | 4 +- .../Boiler/Boiler.Types.xsd | 2 +- .../MemoryBuffer/MemoryBuffer.NodeSet2.xml | 4 +- .../MemoryBuffer/MemoryBuffer.Types.xsd | 2 +- .../TestData/ArrayValueObjectState.cs | 6 + .../TestData/ScalarValueObjectState.cs | 6 + .../TestData/TestData.Classes.cs | 290 +- .../TestData/TestData.Constants.cs | 234 ++ .../TestData/TestData.DataTypes.cs | 803 ++++++ .../TestData/TestData.NodeIds.csv | 36 + .../TestData/TestData.NodeSet.xml | 2370 ++++++++++++++++- .../TestData/TestData.NodeSet2.xml | 484 +++- .../TestData/TestData.PredefinedNodes.uanodes | Bin 128985 -> 136080 bytes .../TestData/TestData.PredefinedNodes.xml | 1251 ++++++++- .../TestData/TestData.Types.bsd | 24 + .../TestData/TestData.Types.xsd | 56 +- .../TestData/TestDataDesign.csv | 48 + .../TestData/TestDataDesign.xml | 22 +- .../TestData/TestDataSystem.cs | 95 +- 19 files changed, 5581 insertions(+), 156 deletions(-) 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 m_integerValue; private BaseDataVariableState m_uIntegerValue; private BaseDataVariableState m_vectorValue; + private BaseDataVariableState m_vectorUnionValue; + private BaseDataVariableState m_vectorWithOptionalFieldsValue; + private BaseDataVariableState m_multipleVectorsValue; #endregion } #endif diff --git a/Applications/Quickstarts.Servers/TestData/TestData.Constants.cs b/Applications/Quickstarts.Servers/TestData/TestData.Constants.cs index 5ba3c9047..e65251d33 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.Constants.cs +++ b/Applications/Quickstarts.Servers/TestData/TestData.Constants.cs @@ -124,6 +124,15 @@ public static partial class DataTypes /// public const uint Vector = 1888; + /// + public const uint VectorUnion = 3584; + + /// + public const uint VectorWithOptionalFields = 3585; + + /// + public const uint MultipleVectors = 3615; + /// public const uint WorkOrderStatusType = 1893; @@ -644,6 +653,15 @@ public static partial class Objects /// public const uint Vector_Encoding_DefaultBinary = 3515; + /// + public const uint VectorUnion_Encoding_DefaultBinary = 3590; + + /// + public const uint VectorWithOptionalFields_Encoding_DefaultBinary = 3591; + + /// + public const uint MultipleVectors_Encoding_DefaultBinary = 3618; + /// public const uint WorkOrderStatusType_Encoding_DefaultBinary = 3516; @@ -665,6 +683,15 @@ public static partial class Objects /// public const uint Vector_Encoding_DefaultXml = 3547; + /// + public const uint VectorUnion_Encoding_DefaultXml = 3598; + + /// + public const uint VectorWithOptionalFields_Encoding_DefaultXml = 3599; + + /// + public const uint MultipleVectors_Encoding_DefaultXml = 3622; + /// public const uint WorkOrderStatusType_Encoding_DefaultXml = 3548; @@ -686,6 +713,15 @@ public static partial class Objects /// public const uint Vector_Encoding_DefaultJson = 3579; + /// + public const uint VectorUnion_Encoding_DefaultJson = 3606; + + /// + public const uint VectorWithOptionalFields_Encoding_DefaultJson = 3607; + + /// + public const uint MultipleVectors_Encoding_DefaultJson = 3626; + /// public const uint WorkOrderStatusType_Encoding_DefaultJson = 3580; @@ -1098,6 +1134,15 @@ public static partial class Variables /// public const uint ScalarValueObjectType_VectorValue_Z = 1209; + /// + public const uint ScalarValueObjectType_VectorUnionValue = 3582; + + /// + public const uint ScalarValueObjectType_VectorWithOptionalFieldsValue = 3583; + + /// + public const uint ScalarValueObjectType_MultipleVectorsValue = 3614; + /// public const uint StructureValueObjectType_GenerateValues_InputArguments = 1213; @@ -1617,6 +1662,15 @@ public static partial class Variables /// public const uint ArrayValueObjectType_VectorValue = 1546; + /// + public const uint ArrayValueObjectType_VectorUnionValue = 3608; + + /// + public const uint ArrayValueObjectType_VectorWithOptionalFieldsValue = 3609; + + /// + public const uint ArrayValueObjectType_MultipleVectorsValue = 3627; + /// public const uint AnalogArrayValueObjectType_GenerateValues_InputArguments = 1550; @@ -2364,6 +2418,15 @@ public static partial class Variables /// public const uint Data_Static_Scalar_VectorValue_Z = 2069; + /// + public const uint Data_Static_Scalar_VectorUnionValue = 3586; + + /// + public const uint Data_Static_Scalar_VectorWithOptionalFieldsValue = 3587; + + /// + public const uint Data_Static_Scalar_MultipleVectorsValue = 3616; + /// public const uint Data_Static_Structure_SimulationActive = 2071; @@ -2724,6 +2787,15 @@ public static partial class Variables /// public const uint Data_Static_Array_VectorValue = 2255; + /// + public const uint Data_Static_Array_VectorUnionValue = 3610; + + /// + public const uint Data_Static_Array_VectorWithOptionalFieldsValue = 3611; + + /// + public const uint Data_Static_Array_MultipleVectorsValue = 3628; + /// public const uint Data_Static_UserScalar_SimulationActive = 2257; @@ -3615,6 +3687,15 @@ public static partial class Variables /// public const uint Data_Dynamic_Scalar_VectorValue_Z = 2833; + /// + public const uint Data_Dynamic_Scalar_VectorUnionValue = 3588; + + /// + public const uint Data_Dynamic_Scalar_VectorWithOptionalFieldsValue = 3589; + + /// + public const uint Data_Dynamic_Scalar_MultipleVectorsValue = 3617; + /// public const uint Data_Dynamic_Structure_SimulationActive = 2835; @@ -3975,6 +4056,15 @@ public static partial class Variables /// public const uint Data_Dynamic_Array_VectorValue = 3019; + /// + public const uint Data_Dynamic_Array_VectorUnionValue = 3612; + + /// + public const uint Data_Dynamic_Array_VectorWithOptionalFieldsValue = 3613; + + /// + public const uint Data_Dynamic_Array_MultipleVectorsValue = 3629; + /// public const uint Data_Dynamic_UserScalar_SimulationActive = 3021; @@ -4719,6 +4809,15 @@ public static partial class Variables /// public const uint TestData_BinarySchema_Vector = 3534; + /// + public const uint TestData_BinarySchema_VectorUnion = 3592; + + /// + public const uint TestData_BinarySchema_VectorWithOptionalFields = 3595; + + /// + public const uint TestData_BinarySchema_MultipleVectors = 3619; + /// public const uint TestData_BinarySchema_WorkOrderStatusType = 3537; @@ -4749,6 +4848,15 @@ public static partial class Variables /// public const uint TestData_XmlSchema_Vector = 3566; + /// + public const uint TestData_XmlSchema_VectorUnion = 3600; + + /// + public const uint TestData_XmlSchema_VectorWithOptionalFields = 3603; + + /// + public const uint TestData_XmlSchema_MultipleVectors = 3623; + /// public const uint TestData_XmlSchema_WorkOrderStatusType = 3569; @@ -4858,6 +4966,15 @@ public static partial class DataTypeIds /// public static readonly ExpandedNodeId Vector = new ExpandedNodeId(TestData.DataTypes.Vector, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId VectorUnion = new ExpandedNodeId(TestData.DataTypes.VectorUnion, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId VectorWithOptionalFields = new ExpandedNodeId(TestData.DataTypes.VectorWithOptionalFields, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId MultipleVectors = new ExpandedNodeId(TestData.DataTypes.MultipleVectors, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId WorkOrderStatusType = new ExpandedNodeId(TestData.DataTypes.WorkOrderStatusType, TestData.Namespaces.TestData); @@ -5378,6 +5495,15 @@ public static partial class ObjectIds /// public static readonly ExpandedNodeId Vector_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.Vector_Encoding_DefaultBinary, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId VectorUnion_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.VectorUnion_Encoding_DefaultBinary, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId VectorWithOptionalFields_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.VectorWithOptionalFields_Encoding_DefaultBinary, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId MultipleVectors_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.MultipleVectors_Encoding_DefaultBinary, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId WorkOrderStatusType_Encoding_DefaultBinary = new ExpandedNodeId(TestData.Objects.WorkOrderStatusType_Encoding_DefaultBinary, TestData.Namespaces.TestData); @@ -5399,6 +5525,15 @@ public static partial class ObjectIds /// public static readonly ExpandedNodeId Vector_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.Vector_Encoding_DefaultXml, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId VectorUnion_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.VectorUnion_Encoding_DefaultXml, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId VectorWithOptionalFields_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.VectorWithOptionalFields_Encoding_DefaultXml, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId MultipleVectors_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.MultipleVectors_Encoding_DefaultXml, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId WorkOrderStatusType_Encoding_DefaultXml = new ExpandedNodeId(TestData.Objects.WorkOrderStatusType_Encoding_DefaultXml, TestData.Namespaces.TestData); @@ -5420,6 +5555,15 @@ public static partial class ObjectIds /// public static readonly ExpandedNodeId Vector_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.Vector_Encoding_DefaultJson, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId VectorUnion_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.VectorUnion_Encoding_DefaultJson, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId VectorWithOptionalFields_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.VectorWithOptionalFields_Encoding_DefaultJson, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId MultipleVectors_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.MultipleVectors_Encoding_DefaultJson, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId WorkOrderStatusType_Encoding_DefaultJson = new ExpandedNodeId(TestData.Objects.WorkOrderStatusType_Encoding_DefaultJson, TestData.Namespaces.TestData); @@ -5832,6 +5976,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId ScalarValueObjectType_VectorValue_Z = new ExpandedNodeId(TestData.Variables.ScalarValueObjectType_VectorValue_Z, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId ScalarValueObjectType_VectorUnionValue = new ExpandedNodeId(TestData.Variables.ScalarValueObjectType_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId ScalarValueObjectType_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.ScalarValueObjectType_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId ScalarValueObjectType_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.ScalarValueObjectType_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId StructureValueObjectType_GenerateValues_InputArguments = new ExpandedNodeId(TestData.Variables.StructureValueObjectType_GenerateValues_InputArguments, TestData.Namespaces.TestData); @@ -6351,6 +6504,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId ArrayValueObjectType_VectorValue = new ExpandedNodeId(TestData.Variables.ArrayValueObjectType_VectorValue, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId ArrayValueObjectType_VectorUnionValue = new ExpandedNodeId(TestData.Variables.ArrayValueObjectType_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId ArrayValueObjectType_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.ArrayValueObjectType_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId ArrayValueObjectType_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.ArrayValueObjectType_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId AnalogArrayValueObjectType_GenerateValues_InputArguments = new ExpandedNodeId(TestData.Variables.AnalogArrayValueObjectType_GenerateValues_InputArguments, TestData.Namespaces.TestData); @@ -7098,6 +7260,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId Data_Static_Scalar_VectorValue_Z = new ExpandedNodeId(TestData.Variables.Data_Static_Scalar_VectorValue_Z, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId Data_Static_Scalar_VectorUnionValue = new ExpandedNodeId(TestData.Variables.Data_Static_Scalar_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Static_Scalar_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.Data_Static_Scalar_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Static_Scalar_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.Data_Static_Scalar_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId Data_Static_Structure_SimulationActive = new ExpandedNodeId(TestData.Variables.Data_Static_Structure_SimulationActive, TestData.Namespaces.TestData); @@ -7458,6 +7629,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId Data_Static_Array_VectorValue = new ExpandedNodeId(TestData.Variables.Data_Static_Array_VectorValue, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId Data_Static_Array_VectorUnionValue = new ExpandedNodeId(TestData.Variables.Data_Static_Array_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Static_Array_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.Data_Static_Array_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Static_Array_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.Data_Static_Array_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId Data_Static_UserScalar_SimulationActive = new ExpandedNodeId(TestData.Variables.Data_Static_UserScalar_SimulationActive, TestData.Namespaces.TestData); @@ -8349,6 +8529,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId Data_Dynamic_Scalar_VectorValue_Z = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Scalar_VectorValue_Z, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId Data_Dynamic_Scalar_VectorUnionValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Scalar_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Dynamic_Scalar_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Scalar_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Dynamic_Scalar_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Scalar_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId Data_Dynamic_Structure_SimulationActive = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Structure_SimulationActive, TestData.Namespaces.TestData); @@ -8709,6 +8898,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId Data_Dynamic_Array_VectorValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Array_VectorValue, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId Data_Dynamic_Array_VectorUnionValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Array_VectorUnionValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Dynamic_Array_VectorWithOptionalFieldsValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Array_VectorWithOptionalFieldsValue, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId Data_Dynamic_Array_MultipleVectorsValue = new ExpandedNodeId(TestData.Variables.Data_Dynamic_Array_MultipleVectorsValue, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId Data_Dynamic_UserScalar_SimulationActive = new ExpandedNodeId(TestData.Variables.Data_Dynamic_UserScalar_SimulationActive, TestData.Namespaces.TestData); @@ -9453,6 +9651,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId TestData_BinarySchema_Vector = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_Vector, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId TestData_BinarySchema_VectorUnion = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_VectorUnion, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId TestData_BinarySchema_VectorWithOptionalFields = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_VectorWithOptionalFields, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId TestData_BinarySchema_MultipleVectors = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_MultipleVectors, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId TestData_BinarySchema_WorkOrderStatusType = new ExpandedNodeId(TestData.Variables.TestData_BinarySchema_WorkOrderStatusType, TestData.Namespaces.TestData); @@ -9483,6 +9690,15 @@ public static partial class VariableIds /// public static readonly ExpandedNodeId TestData_XmlSchema_Vector = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_Vector, TestData.Namespaces.TestData); + /// + public static readonly ExpandedNodeId TestData_XmlSchema_VectorUnion = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_VectorUnion, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId TestData_XmlSchema_VectorWithOptionalFields = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_VectorWithOptionalFields, TestData.Namespaces.TestData); + + /// + public static readonly ExpandedNodeId TestData_XmlSchema_MultipleVectors = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_MultipleVectors, TestData.Namespaces.TestData); + /// public static readonly ExpandedNodeId TestData_XmlSchema_WorkOrderStatusType = new ExpandedNodeId(TestData.Variables.TestData_XmlSchema_WorkOrderStatusType, TestData.Namespaces.TestData); @@ -9636,6 +9852,12 @@ public static partial class BrowseNames /// public const string MonitoredNodeCount = "MonitoredNodeCount"; + /// + public const string MultipleVectors = "MultipleVectors"; + + /// + public const string MultipleVectorsValue = "MultipleVectorsValue"; + /// public const string NewValueCount = "NewValueCount"; @@ -9774,12 +9996,24 @@ public static partial class BrowseNames /// public const string VectorStructure = "VectorStructure"; + /// + public const string VectorUnion = "VectorUnion"; + + /// + public const string VectorUnionValue = "VectorUnionValue"; + /// public const string VectorValue = "VectorValue"; /// public const string VectorVariableType = "VectorVariableType"; + /// + public const string VectorWithOptionalFields = "VectorWithOptionalFields"; + + /// + public const string VectorWithOptionalFieldsValue = "VectorWithOptionalFieldsValue"; + /// public const string WorkOrderStatusType = "WorkOrderStatusType"; diff --git a/Applications/Quickstarts.Servers/TestData/TestData.DataTypes.cs b/Applications/Quickstarts.Servers/TestData/TestData.DataTypes.cs index f842cf32f..add5c653a 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.DataTypes.cs +++ b/Applications/Quickstarts.Servers/TestData/TestData.DataTypes.cs @@ -2863,6 +2863,809 @@ public object Clone() #endif #endregion + #region VectorUnion Class + #if (!OPCUA_EXCLUDE_VectorUnion) + /// + /// + public enum VectorUnionFields : uint + { + /// + None = 0, + /// + X = 1, + /// + Y = 2, + /// + Z = 3 + } + + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [DataContract(Namespace = TestData.Namespaces.TestData)] + public partial class VectorUnion : IEncodeable, IJsonEncodeable + { + #region Constructors + /// + public VectorUnion() + { + Initialize(); + } + + [OnDeserializing] + private void Initialize(StreamingContext context) + { + Initialize(); + } + + private void Initialize() + { + SwitchField = VectorUnionFields.None; + m_x = (double)0; + m_y = (double)0; + m_z = (double)0; + } + #endregion + + #region Public Properties + // + [DataMember(Name = "SwitchField", IsRequired = true, Order = 0)] + public VectorUnionFields SwitchField { get; set; } + + /// + [DataMember(Name = "X", IsRequired = false, Order = 1)] + public double X + { + get { return m_x; } + set { m_x = value; } + } + + /// + [DataMember(Name = "Y", IsRequired = false, Order = 2)] + public double Y + { + get { return m_y; } + set { m_y = value; } + } + + /// + [DataMember(Name = "Z", IsRequired = false, Order = 3)] + public double Z + { + get { return m_z; } + set { m_z = value; } + } + #endregion + + #region IEncodeable Members + /// + public virtual ExpandedNodeId TypeId => DataTypeIds.VectorUnion; + + /// + public virtual ExpandedNodeId BinaryEncodingId => ObjectIds.VectorUnion_Encoding_DefaultBinary; + + /// + public virtual ExpandedNodeId XmlEncodingId => ObjectIds.VectorUnion_Encoding_DefaultXml; + + /// + public virtual ExpandedNodeId JsonEncodingId => ObjectIds.VectorUnion_Encoding_DefaultJson; + + /// + public virtual void Encode(IEncoder encoder) + { + encoder.PushNamespace(TestData.Namespaces.TestData); + encoder.WriteUInt32(nameof(SwitchField), (uint)SwitchField); + + switch (SwitchField) + { + default: { break; } + case VectorUnionFields.X: { encoder.WriteDouble("X", X); break; } + case VectorUnionFields.Y: { encoder.WriteDouble("Y", Y); break; } + case VectorUnionFields.Z: { encoder.WriteDouble("Z", Z); break; } + } + + encoder.PopNamespace(); + } + + /// + public virtual void Decode(IDecoder decoder) + { + decoder.PushNamespace(TestData.Namespaces.TestData); + + SwitchField = (VectorUnionFields)decoder.ReadUInt32(nameof(SwitchField)); + + switch (SwitchField) + { + default: { break; } + case VectorUnionFields.X: { X = decoder.ReadDouble("X"); break; } + case VectorUnionFields.Y: { Y = decoder.ReadDouble("Y"); break; } + case VectorUnionFields.Z: { Z = decoder.ReadDouble("Z"); break; } + } + + decoder.PopNamespace(); + } + + /// + public virtual bool IsEqual(IEncodeable encodeable) + { + if (Object.ReferenceEquals(this, encodeable)) + { + return true; + } + + VectorUnion value = encodeable as VectorUnion; + + if (value == null) + { + return false; + } + + if (value.SwitchField != this.SwitchField) return false; + + switch (SwitchField) + { + default: { break; } + case VectorUnionFields.X: { if (!Utils.IsEqual(m_x, value.m_x)) return false; break; } + case VectorUnionFields.Y: { if (!Utils.IsEqual(m_y, value.m_y)) return false; break; } + case VectorUnionFields.Z: { if (!Utils.IsEqual(m_z, value.m_z)) return false; break; } + } + + return true; + } + + /// + public virtual object Clone() + { + return (VectorUnion)this.MemberwiseClone(); + } + + /// + public new object MemberwiseClone() + { + VectorUnion clone = (VectorUnion)base.MemberwiseClone(); + + clone.SwitchField = this.SwitchField; + + switch (SwitchField) + { + default: { break; } + case VectorUnionFields.X: { clone.m_x = (double)Utils.Clone(this.m_x); break; } + case VectorUnionFields.Y: { clone.m_y = (double)Utils.Clone(this.m_y); break; } + case VectorUnionFields.Z: { clone.m_z = (double)Utils.Clone(this.m_z); break; } + } + + return clone; + } + #endregion + + #region Private Fields + private double m_x; + private double m_y; + private double m_z; + #endregion + } + + #region VectorUnionCollection Class + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [CollectionDataContract(Name = "ListOfVectorUnion", Namespace = TestData.Namespaces.TestData, ItemName = "VectorUnion")] + public partial class VectorUnionCollection : List, ICloneable + { + #region Constructors + /// + public VectorUnionCollection() {} + + /// + public VectorUnionCollection(int capacity) : base(capacity) {} + + /// + public VectorUnionCollection(IEnumerable collection) : base(collection) {} + #endregion + + #region Static Operators + /// + public static implicit operator VectorUnionCollection(VectorUnion[] values) + { + if (values != null) + { + return new VectorUnionCollection(values); + } + + return new VectorUnionCollection(); + } + + /// + public static explicit operator VectorUnion[](VectorUnionCollection values) + { + if (values != null) + { + return values.ToArray(); + } + + return null; + } + #endregion + + #region ICloneable Methods + /// + public object Clone() + { + return (VectorUnionCollection)this.MemberwiseClone(); + } + #endregion + + /// + public new object MemberwiseClone() + { + VectorUnionCollection clone = new VectorUnionCollection(this.Count); + + for (int ii = 0; ii < this.Count; ii++) + { + clone.Add((VectorUnion)Utils.Clone(this[ii])); + } + + return clone; + } + } + #endregion + #endif + #endregion + + #region VectorWithOptionalFields Class + #if (!OPCUA_EXCLUDE_VectorWithOptionalFields) + /// + /// + + public enum VectorWithOptionalFieldsFields : uint + { + None = 0, + /// + X = 0x1, + /// + Y = 0x2, + /// + Z = 0x4 + } + + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [DataContract(Namespace = TestData.Namespaces.TestData)] + public partial class VectorWithOptionalFields : IEncodeable, IJsonEncodeable + { + #region Constructors + /// + public VectorWithOptionalFields() + { + Initialize(); + } + + [OnDeserializing] + private void Initialize(StreamingContext context) + { + Initialize(); + } + + private void Initialize() + { + EncodingMask = VectorWithOptionalFieldsFields.None; + m_x = (double)0; + m_y = (double)0; + m_z = (double)0; + } + #endregion + + #region Public Properties + // + [DataMember(Name = "EncodingMask", IsRequired = true, Order = 0)] + public VectorWithOptionalFieldsFields EncodingMask { get; set; } + + /// + [DataMember(Name = "X", IsRequired = false, Order = 1)] + public double X + { + get { return m_x; } + set { m_x = value; } + } + + /// + [DataMember(Name = "Y", IsRequired = false, Order = 2)] + public double Y + { + get { return m_y; } + set { m_y = value; } + } + + /// + [DataMember(Name = "Z", IsRequired = false, Order = 3)] + public double Z + { + get { return m_z; } + set { m_z = value; } + } + #endregion + + #region IEncodeable Members + /// + public virtual ExpandedNodeId TypeId => DataTypeIds.VectorWithOptionalFields; + + /// + public virtual ExpandedNodeId BinaryEncodingId => ObjectIds.VectorWithOptionalFields_Encoding_DefaultBinary; + + /// + public virtual ExpandedNodeId XmlEncodingId => ObjectIds.VectorWithOptionalFields_Encoding_DefaultXml; + + /// + public virtual ExpandedNodeId JsonEncodingId => ObjectIds.VectorWithOptionalFields_Encoding_DefaultJson; + + /// + public virtual void Encode(IEncoder encoder) + { + encoder.PushNamespace(TestData.Namespaces.TestData); + encoder.WriteUInt32(nameof(EncodingMask), (uint)EncodingMask); + + if ((EncodingMask & VectorWithOptionalFieldsFields.X) != 0) encoder.WriteDouble("X", X); + if ((EncodingMask & VectorWithOptionalFieldsFields.Y) != 0) encoder.WriteDouble("Y", Y); + if ((EncodingMask & VectorWithOptionalFieldsFields.Z) != 0) encoder.WriteDouble("Z", Z); + + encoder.PopNamespace(); + } + + /// + public virtual void Decode(IDecoder decoder) + { + decoder.PushNamespace(TestData.Namespaces.TestData); + + EncodingMask = (VectorWithOptionalFieldsFields)decoder.ReadUInt32(nameof(EncodingMask)); + + if ((EncodingMask & VectorWithOptionalFieldsFields.X) != 0) X = decoder.ReadDouble("X"); + if ((EncodingMask & VectorWithOptionalFieldsFields.Y) != 0) Y = decoder.ReadDouble("Y"); + if ((EncodingMask & VectorWithOptionalFieldsFields.Z) != 0) Z = decoder.ReadDouble("Z"); + + decoder.PopNamespace(); + } + + /// + public virtual bool IsEqual(IEncodeable encodeable) + { + if (Object.ReferenceEquals(this, encodeable)) + { + return true; + } + + VectorWithOptionalFields value = encodeable as VectorWithOptionalFields; + + if (value == null) + { + return false; + } + + if (value.EncodingMask != this.EncodingMask) return false; + + if ((EncodingMask & VectorWithOptionalFieldsFields.X) != 0) if (!Utils.IsEqual(m_x, value.m_x)) return false; + if ((EncodingMask & VectorWithOptionalFieldsFields.Y) != 0) if (!Utils.IsEqual(m_y, value.m_y)) return false; + if ((EncodingMask & VectorWithOptionalFieldsFields.Z) != 0) if (!Utils.IsEqual(m_z, value.m_z)) return false; + + return true; + } + + /// + public virtual object Clone() + { + return (VectorWithOptionalFields)this.MemberwiseClone(); + } + + /// + public new object MemberwiseClone() + { + VectorWithOptionalFields clone = (VectorWithOptionalFields)base.MemberwiseClone(); + + clone.EncodingMask = this.EncodingMask; + + if ((EncodingMask & VectorWithOptionalFieldsFields.X) != 0) clone.m_x = (double)Utils.Clone(this.m_x); + if ((EncodingMask & VectorWithOptionalFieldsFields.Y) != 0) clone.m_y = (double)Utils.Clone(this.m_y); + if ((EncodingMask & VectorWithOptionalFieldsFields.Z) != 0) clone.m_z = (double)Utils.Clone(this.m_z); + + return clone; + } + #endregion + + #region Private Fields + private double m_x; + private double m_y; + private double m_z; + #endregion + } + + #region VectorWithOptionalFieldsCollection Class + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [CollectionDataContract(Name = "ListOfVectorWithOptionalFields", Namespace = TestData.Namespaces.TestData, ItemName = "VectorWithOptionalFields")] + public partial class VectorWithOptionalFieldsCollection : List, ICloneable + { + #region Constructors + /// + public VectorWithOptionalFieldsCollection() {} + + /// + public VectorWithOptionalFieldsCollection(int capacity) : base(capacity) {} + + /// + public VectorWithOptionalFieldsCollection(IEnumerable collection) : base(collection) {} + #endregion + + #region Static Operators + /// + public static implicit operator VectorWithOptionalFieldsCollection(VectorWithOptionalFields[] values) + { + if (values != null) + { + return new VectorWithOptionalFieldsCollection(values); + } + + return new VectorWithOptionalFieldsCollection(); + } + + /// + public static explicit operator VectorWithOptionalFields[](VectorWithOptionalFieldsCollection values) + { + if (values != null) + { + return values.ToArray(); + } + + return null; + } + #endregion + + #region ICloneable Methods + /// + public object Clone() + { + return (VectorWithOptionalFieldsCollection)this.MemberwiseClone(); + } + #endregion + + /// + public new object MemberwiseClone() + { + VectorWithOptionalFieldsCollection clone = new VectorWithOptionalFieldsCollection(this.Count); + + for (int ii = 0; ii < this.Count; ii++) + { + clone.Add((VectorWithOptionalFields)Utils.Clone(this[ii])); + } + + return clone; + } + } + #endregion + #endif + #endregion + + #region MultipleVectors Class + #if (!OPCUA_EXCLUDE_MultipleVectors) + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [DataContract(Namespace = TestData.Namespaces.TestData)] + public partial class MultipleVectors : IEncodeable, IJsonEncodeable + { + #region Constructors + /// + public MultipleVectors() + { + Initialize(); + } + + [OnDeserializing] + private void Initialize(StreamingContext context) + { + Initialize(); + } + + private void Initialize() + { + m_vector = new Vector(); + m_vectorUnion = new VectorUnion(); + m_vectorWithOptionalFields = new VectorWithOptionalFields(); + m_vectorArray = new VectorCollection(); + m_vectorUnionArray = new VectorUnionCollection(); + m_vectorWithOptionalFieldsArray = new VectorWithOptionalFieldsCollection(); + } + #endregion + + #region Public Properties + /// + [DataMember(Name = "Vector", IsRequired = false, Order = 1)] + public Vector Vector + { + get + { + return m_vector; + } + + set + { + m_vector = value; + + if (value == null) + { + m_vector = new Vector(); + } + } + } + + /// + [DataMember(Name = "VectorUnion", IsRequired = false, Order = 2)] + public VectorUnion VectorUnion + { + get + { + return m_vectorUnion; + } + + set + { + m_vectorUnion = value; + + if (value == null) + { + m_vectorUnion = new VectorUnion(); + } + } + } + + /// + [DataMember(Name = "VectorWithOptionalFields", IsRequired = false, Order = 3)] + public VectorWithOptionalFields VectorWithOptionalFields + { + get + { + return m_vectorWithOptionalFields; + } + + set + { + m_vectorWithOptionalFields = value; + + if (value == null) + { + m_vectorWithOptionalFields = new VectorWithOptionalFields(); + } + } + } + + /// + [DataMember(Name = "VectorArray", IsRequired = false, Order = 4)] + public VectorCollection VectorArray + { + get + { + return m_vectorArray; + } + + set + { + m_vectorArray = value; + + if (value == null) + { + m_vectorArray = new VectorCollection(); + } + } + } + + /// + [DataMember(Name = "VectorUnionArray", IsRequired = false, Order = 5)] + public VectorUnionCollection VectorUnionArray + { + get + { + return m_vectorUnionArray; + } + + set + { + m_vectorUnionArray = value; + + if (value == null) + { + m_vectorUnionArray = new VectorUnionCollection(); + } + } + } + + /// + [DataMember(Name = "VectorWithOptionalFieldsArray", IsRequired = false, Order = 6)] + public VectorWithOptionalFieldsCollection VectorWithOptionalFieldsArray + { + get + { + return m_vectorWithOptionalFieldsArray; + } + + set + { + m_vectorWithOptionalFieldsArray = value; + + if (value == null) + { + m_vectorWithOptionalFieldsArray = new VectorWithOptionalFieldsCollection(); + } + } + } + #endregion + + #region IEncodeable Members + /// + public virtual ExpandedNodeId TypeId => DataTypeIds.MultipleVectors; + + /// + public virtual ExpandedNodeId BinaryEncodingId => ObjectIds.MultipleVectors_Encoding_DefaultBinary; + + /// + public virtual ExpandedNodeId XmlEncodingId => ObjectIds.MultipleVectors_Encoding_DefaultXml; + + /// + public virtual ExpandedNodeId JsonEncodingId => ObjectIds.MultipleVectors_Encoding_DefaultJson; + + /// + public virtual void Encode(IEncoder encoder) + { + encoder.PushNamespace(TestData.Namespaces.TestData); + + encoder.WriteEncodeable("Vector", Vector, typeof(Vector)); + encoder.WriteEncodeable("VectorUnion", VectorUnion, typeof(VectorUnion)); + encoder.WriteEncodeable("VectorWithOptionalFields", VectorWithOptionalFields, typeof(VectorWithOptionalFields)); + encoder.WriteEncodeableArray("VectorArray", VectorArray.ToArray(), typeof(Vector)); + encoder.WriteEncodeableArray("VectorUnionArray", VectorUnionArray.ToArray(), typeof(VectorUnion)); + encoder.WriteEncodeableArray("VectorWithOptionalFieldsArray", VectorWithOptionalFieldsArray.ToArray(), typeof(VectorWithOptionalFields)); + + encoder.PopNamespace(); + } + + /// + public virtual void Decode(IDecoder decoder) + { + decoder.PushNamespace(TestData.Namespaces.TestData); + + Vector = (Vector)decoder.ReadEncodeable("Vector", typeof(Vector)); + VectorUnion = (VectorUnion)decoder.ReadEncodeable("VectorUnion", typeof(VectorUnion)); + VectorWithOptionalFields = (VectorWithOptionalFields)decoder.ReadEncodeable("VectorWithOptionalFields", typeof(VectorWithOptionalFields)); + VectorArray = (VectorCollection)decoder.ReadEncodeableArray("VectorArray", typeof(Vector)); + VectorUnionArray = (VectorUnionCollection)decoder.ReadEncodeableArray("VectorUnionArray", typeof(VectorUnion)); + VectorWithOptionalFieldsArray = (VectorWithOptionalFieldsCollection)decoder.ReadEncodeableArray("VectorWithOptionalFieldsArray", typeof(VectorWithOptionalFields)); + + decoder.PopNamespace(); + } + + /// + public virtual bool IsEqual(IEncodeable encodeable) + { + if (Object.ReferenceEquals(this, encodeable)) + { + return true; + } + + MultipleVectors value = encodeable as MultipleVectors; + + if (value == null) + { + return false; + } + + if (!Utils.IsEqual(m_vector, value.m_vector)) return false; + if (!Utils.IsEqual(m_vectorUnion, value.m_vectorUnion)) return false; + if (!Utils.IsEqual(m_vectorWithOptionalFields, value.m_vectorWithOptionalFields)) return false; + if (!Utils.IsEqual(m_vectorArray, value.m_vectorArray)) return false; + if (!Utils.IsEqual(m_vectorUnionArray, value.m_vectorUnionArray)) return false; + if (!Utils.IsEqual(m_vectorWithOptionalFieldsArray, value.m_vectorWithOptionalFieldsArray)) return false; + + return true; + } + + /// + public virtual object Clone() + { + return (MultipleVectors)this.MemberwiseClone(); + } + + /// + public new object MemberwiseClone() + { + MultipleVectors clone = (MultipleVectors)base.MemberwiseClone(); + + clone.m_vector = (Vector)Utils.Clone(this.m_vector); + clone.m_vectorUnion = (VectorUnion)Utils.Clone(this.m_vectorUnion); + clone.m_vectorWithOptionalFields = (VectorWithOptionalFields)Utils.Clone(this.m_vectorWithOptionalFields); + clone.m_vectorArray = (VectorCollection)Utils.Clone(this.m_vectorArray); + clone.m_vectorUnionArray = (VectorUnionCollection)Utils.Clone(this.m_vectorUnionArray); + clone.m_vectorWithOptionalFieldsArray = (VectorWithOptionalFieldsCollection)Utils.Clone(this.m_vectorWithOptionalFieldsArray); + + return clone; + } + #endregion + + #region Private Fields + private Vector m_vector; + private VectorUnion m_vectorUnion; + private VectorWithOptionalFields m_vectorWithOptionalFields; + private VectorCollection m_vectorArray; + private VectorUnionCollection m_vectorUnionArray; + private VectorWithOptionalFieldsCollection m_vectorWithOptionalFieldsArray; + #endregion + } + + #region MultipleVectorsCollection Class + /// + /// + [System.CodeDom.Compiler.GeneratedCodeAttribute("Opc.Ua.ModelCompiler", "1.0.0.0")] + [CollectionDataContract(Name = "ListOfMultipleVectors", Namespace = TestData.Namespaces.TestData, ItemName = "MultipleVectors")] + public partial class MultipleVectorsCollection : List, ICloneable + { + #region Constructors + /// + public MultipleVectorsCollection() {} + + /// + public MultipleVectorsCollection(int capacity) : base(capacity) {} + + /// + public MultipleVectorsCollection(IEnumerable collection) : base(collection) {} + #endregion + + #region Static Operators + /// + public static implicit operator MultipleVectorsCollection(MultipleVectors[] values) + { + if (values != null) + { + return new MultipleVectorsCollection(values); + } + + return new MultipleVectorsCollection(); + } + + /// + public static explicit operator MultipleVectors[](MultipleVectorsCollection values) + { + if (values != null) + { + return values.ToArray(); + } + + return null; + } + #endregion + + #region ICloneable Methods + /// + public object Clone() + { + return (MultipleVectorsCollection)this.MemberwiseClone(); + } + #endregion + + /// + public new object MemberwiseClone() + { + MultipleVectorsCollection clone = new MultipleVectorsCollection(this.Count); + + for (int ii = 0; ii < this.Count; ii++) + { + clone.Add((MultipleVectors)Utils.Clone(this[ii])); + } + + return clone; + } + } + #endregion + #endif + #endregion + #region WorkOrderStatusType Class #if (!OPCUA_EXCLUDE_WorkOrderStatusType) /// diff --git a/Applications/Quickstarts.Servers/TestData/TestData.NodeIds.csv b/Applications/Quickstarts.Servers/TestData/TestData.NodeIds.csv index 0f3adf628..64b174251 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.NodeIds.csv +++ b/Applications/Quickstarts.Servers/TestData/TestData.NodeIds.csv @@ -1292,3 +1292,39 @@ 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_VectorWithOptionalFields,3595,Variable +VectorUnion_Encoding_DefaultXml,3598,Object +VectorWithOptionalFields_Encoding_DefaultXml,3599,Object +TestData_XmlSchema_VectorUnion,3600,Variable +TestData_XmlSchema_VectorWithOptionalFields,3603,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 +MultipleVectors_Encoding_DefaultXml,3622,Object +TestData_XmlSchema_MultipleVectors,3623,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/TestData.NodeSet.xml b/Applications/Quickstarts.Servers/TestData/TestData.NodeSet.xml index fae32aed9..ffaadd61a 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.NodeSet.xml +++ b/Applications/Quickstarts.Servers/TestData/TestData.NodeSet.xml @@ -5167,6 +5167,33 @@ ns=1;i=1206 + + + i=47 + + false + + ns=1;i=3582 + + + + + i=47 + + false + + ns=1;i=3583 + + + + + i=47 + + false + + ns=1;i=3614 + + false @@ -11840,6 +11867,33 @@ ns=1;i=1546 + + + i=47 + + false + + ns=1;i=3608 + + + + + i=47 + + false + + ns=1;i=3609 + + + + + i=47 + + false + + ns=1;i=3627 + + false @@ -25935,6 +25989,33 @@ ns=1;i=2066 + + + i=47 + + false + + ns=1;i=3586 + + + + + i=47 + + false + + ns=1;i=3587 + + + + + i=47 + + false + + ns=1;i=3616 + + 0 @@ -34568,6 +34649,33 @@ ns=1;i=2255 + + + i=47 + + false + + ns=1;i=3610 + + + + + i=47 + + false + + ns=1;i=3611 + + + + + i=47 + + false + + ns=1;i=3628 + + 0 @@ -58945,6 +59053,33 @@ ns=1;i=2830 + + + i=47 + + false + + ns=1;i=3588 + + + + + i=47 + + false + + ns=1;i=3589 + + + + + i=47 + + false + + ns=1;i=3617 + + 0 @@ -67578,6 +67713,33 @@ ns=1;i=3019 + + + i=47 + + false + + ns=1;i=3612 + + + + + i=47 + + false + + ns=1;i=3613 + + + + + i=47 + + false + + ns=1;i=3629 + + 0 @@ -89042,6 +89204,33 @@ ns=1;i=3534 + + + i=47 + + false + + ns=1;i=3592 + + + + + i=47 + + false + + ns=1;i=3595 + + + + + i=47 + + false + + ns=1;i=3619 + + i=47 @@ -89302,20 +89491,44 @@ IE5hbWU9IlZlY3RvciIgQmFzZVR5cGU9InVhOkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpG aWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5h bWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWiIg VHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv -cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgQmFzZVR5cGU9InVh -OkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBY3RvciIgVHlwZU5hbWU9 -Im9wYzpTdHJpbmciIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJUaW1lc3RhbXAiIFR5cGVOYW1l -PSJvcGM6RGF0ZVRpbWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJDb21tZW50IiBUeXBlTmFt -ZT0idWE6TG9jYWxpemVkVGV4dCIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9w -YzpTdHJ1Y3R1cmVkVHlwZSBOYW1lPSJXb3JrT3JkZXJUeXBlIiBCYXNlVHlwZT0idWE6RXh0ZW5z -aW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IklEIiBUeXBlTmFtZT0ib3BjOkd1aWQi -IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBc3NldElEIiBUeXBlTmFtZT0ib3BjOlN0cmluZyIg -Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXJ0VGltZSIgVHlwZU5hbWU9Im9wYzpEYXRlVGlt -ZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZTdGF0dXNDb21tZW50cyIgVHlwZU5hbWU9 -Im9wYzpJbnQzMiIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXR1c0NvbW1lbnRzIiBUeXBl -TmFtZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIExlbmd0aEZpZWxkPSJOb09mU3RhdHVzQ29t -bWVudHMiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQo8L29wYzpUeXBlRGljdGlvbmFy -eT4= +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yVW5pb24iIEJhc2VUeXBlPSJ1YTpVbmlvbiI+ +DQogICAgPG9wYzpGaWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6Rmll +bGQgTmFtZT0iWiIgVHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRU +eXBlPg0KDQogIDxvcGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmll +bGRzIiBCYXNlVHlwZT0idWE6RXh0ZW5zaW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9 +IlgiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWSIgVHlw +ZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJaIiBUeXBlTmFtZT0i +b3BjOkRvdWJsZSIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9wYzpTdHJ1Y3R1 +cmVkVHlwZSBOYW1lPSJNdWx0aXBsZVZlY3RvcnMiIEJhc2VUeXBlPSJ1YTpFeHRlbnNpb25PYmpl +Y3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yIiBUeXBlTmFtZT0idG5zOlZlY3RvciIg +Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlZlY3RvclVuaW9uIiBUeXBlTmFtZT0idG5zOlZlY3Rv +clVuaW9uIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRz +IiBUeXBlTmFtZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIgLz4NCiAgICA8b3BjOkZp +ZWxkIE5hbWU9Ik5vT2ZWZWN0b3JBcnJheSIgVHlwZU5hbWU9Im9wYzpJbnQzMiIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlZlY3RvckFycmF5IiBUeXBlTmFtZT0idG5zOlZlY3RvciIgTGVuZ3Ro +RmllbGQ9Ik5vT2ZWZWN0b3JBcnJheSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZWZWN0 +b3JVbmlvbkFycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAgIDxvcGM6RmllbGQgTmFt +ZT0iVmVjdG9yVW5pb25BcnJheSIgVHlwZU5hbWU9InRuczpWZWN0b3JVbmlvbiIgTGVuZ3RoRmll +bGQ9Ik5vT2ZWZWN0b3JVbmlvbkFycmF5IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iTm9PZlZl +Y3RvcldpdGhPcHRpb25hbEZpZWxkc0FycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAg +IDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJyYXkiIFR5cGVOYW1l +PSJ0bnM6VmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiBMZW5ndGhGaWVsZD0iTm9PZlZlY3Rvcldp +dGhPcHRpb25hbEZpZWxkc0FycmF5IiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KICA8 +b3BjOlN0cnVjdHVyZWRUeXBlIE5hbWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIEJhc2VUeXBlPSJ1 +YTpFeHRlbnNpb25PYmplY3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQWN0b3IiIFR5cGVOYW1l +PSJvcGM6U3RyaW5nIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVGltZXN0YW1wIiBUeXBlTmFt +ZT0ib3BjOkRhdGVUaW1lIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQ29tbWVudCIgVHlwZU5h +bWU9InVhOkxvY2FsaXplZFRleHQiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyVHlwZSIgQmFzZVR5cGU9InVhOkV4dGVu +c2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJJRCIgVHlwZU5hbWU9Im9wYzpHdWlk +IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQXNzZXRJRCIgVHlwZU5hbWU9Im9wYzpTdHJpbmci +IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGFydFRpbWUiIFR5cGVOYW1lPSJvcGM6RGF0ZVRp +bWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJOb09mU3RhdHVzQ29tbWVudHMiIFR5cGVOYW1l +PSJvcGM6SW50MzIiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGF0dXNDb21tZW50cyIgVHlw +ZU5hbWU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBMZW5ndGhGaWVsZD0iTm9PZlN0YXR1c0Nv +bW1lbnRzIiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KPC9vcGM6VHlwZURpY3Rpb25h +cnk+ @@ -90275,6 +90488,33 @@ eT4= ns=1;i=3566 + + + i=47 + + false + + ns=1;i=3600 + + + + + i=47 + + false + + ns=1;i=3603 + + + + + i=47 + + false + + ns=1;i=3623 + + i=47 @@ -90302,7 +90542,7 @@ c2QiDQogIHhtbG5zOnRucz0iaHR0cDovL3Rlc3Qub3JnL1VBL0RhdGEvIg0KICB0YXJnZXROYW1l c3BhY2U9Imh0dHA6Ly90ZXN0Lm9yZy9VQS9EYXRhLyINCiAgZWxlbWVudEZvcm1EZWZhdWx0PSJx dWFsaWZpZWQiDQo+DQogIDx4czphbm5vdGF0aW9uPg0KICAgIDx4czphcHBpbmZvPg0KICAgICAg PHVhOk1vZGVsIE1vZGVsVXJpPSJodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS8iIFZlcnNpb249IjEu -MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMDYtMDZUMDY6MzM6NTEuNTc2OTYyOVoiIC8+DQog +MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMTAtMDlUMDg6NTE6MjQuNTIzNzA4OFoiIC8+DQog ICAgPC94czphcHBpbmZvPg0KICA8L3hzOmFubm90YXRpb24+DQogIA0KICA8eHM6aW1wb3J0IG5h bWVzcGFjZT0iaHR0cDovL29wY2ZvdW5kYXRpb24ub3JnL1VBLzIwMDgvMDIvVHlwZXMueHNkIiAv Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJTY2FsYXJTdHJ1Y3R1cmVEYXRhVHlwZSI+DQog @@ -90541,35 +90781,82 @@ YW1lPSJWZWN0b3IiIHR5cGU9InRuczpWZWN0b3IiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1 bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6 Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZlZlY3RvciIgdHlwZT0idG5z Okxpc3RPZlZlY3RvciIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29t -cGxleFR5cGUgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0K -ICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQWN0b3IiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJz -PSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlRpbWVzdGFt -cCIgdHlwZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50 -IG5hbWU9IkNvbW1lbnQiIHR5cGU9InVhOkxvY2FsaXplZFRleHQiIG1pbk9jY3Vycz0iMCIgbmls -bGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4N -CiAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlwZT0idG5zOldvcmtP -cmRlclN0YXR1c1R5cGUiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZldvcmtP -cmRlclN0YXR1c1R5cGUiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5h -bWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBt -aW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAg -ICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1l -PSJMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3Rh -dHVzVHlwZSIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29tcGxleFR5 -cGUgbmFtZT0iV29ya09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVs -ZW1lbnQgbmFtZT0iSUQiIHR5cGU9InVhOkd1aWQiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4 -czplbGVtZW50IG5hbWU9IkFzc2V0SUQiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJzPSIwIiBu -aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlN0YXJ0VGltZSIgdHlw -ZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 -IlN0YXR1c0NvbW1lbnRzIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSIgbWlu -T2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hz -OmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0 -bnM6V29ya09yZGVyVHlwZSIgLz4NCg0KICA8eHM6Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29y -a09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i -V29ya09yZGVyVHlwZSIgdHlwZT0idG5zOldvcmtPcmRlclR5cGUiIG1pbk9jY3Vycz0iMCIgbWF4 -T2NjdXJzPSJ1bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+ -DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRl -clR5cGUiIHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 -czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= +cGxleFR5cGUgbmFtZT0iVmVjdG9yVW5pb24iPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4 +czplbGVtZW50IG5hbWU9IlN3aXRjaEZpZWxkIiB0eXBlPSJ4czp1bnNpZ25lZEludCIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmNob2ljZT4NCiAgICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i +WCIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9IjAiIC8+DQogICAgICAgIDx4czplbGVtZW50 +IG5hbWU9IlkiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgICA8eHM6 +ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAg +IDwveHM6Y2hvaWNlPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQog +IDx4czplbGVtZW50IG5hbWU9IlZlY3RvclVuaW9uIiB0eXBlPSJ0bnM6VmVjdG9yVW5pb24iIC8+ +DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZlZlY3RvclVuaW9uIj4NCiAgICA8eHM6 +c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JVbmlvbiIgdHlwZT0idG5z +OlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJs +ZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JVbmlvbiIgdHlwZT0idG5zOkxpc3RPZlZlY3Rv +clVuaW9uIiBuaWxsYWJsZT0idHJ1ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlw +ZSBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAg +ICAgIDx4czplbGVtZW50IG5hbWU9IlgiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAv +Pg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iWSIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9 +IjAiIC8+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9j +Y3Vycz0iMCIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5cGU9InRuczpWZWN0 +b3JXaXRoT3B0aW9uYWxGaWVsZHMiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RP +ZlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiB0eXBlPSJ0bnM6VmVjdG9y +V2l0aE9wdGlvbmFsRmllbGRzIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBu +aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBl +Pg0KICA8eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5 +cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG5pbGxhYmxlPSJ0cnVlIj48 +L3hzOmVsZW1lbnQ+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ik11bHRpcGxlVmVjdG9ycyI+ +DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yIiB0eXBl +PSJ0bnM6VmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb24iIHR5cGU9InRuczpWZWN0b3JVbmlvbiIgbWluT2Nj +dXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlZlY3Rv +cldpdGhPcHRpb25hbEZpZWxkcyIgdHlwZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIg +bWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 +IlZlY3RvckFycmF5IiB0eXBlPSJ0bnM6TGlzdE9mVmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxh +YmxlPSJ0cnVlIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb25BcnJheSIg +dHlwZT0idG5zOkxpc3RPZlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVl +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJy +YXkiIHR5cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG1pbk9jY3Vycz0i +MCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4 +VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTXVsdGlwbGVWZWN0b3JzIiB0eXBlPSJ0bnM6TXVs +dGlwbGVWZWN0b3JzIiAvPg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZNdWx0aXBs +ZVZlY3RvcnMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9Ik11 +bHRpcGxlVmVjdG9ycyIgdHlwZT0idG5zOk11bHRpcGxlVmVjdG9ycyIgbWluT2NjdXJzPSIwIiBt +YXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5j +ZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTGlzdE9mTXVsdGlw +bGVWZWN0b3JzIiB0eXBlPSJ0bnM6TGlzdE9mTXVsdGlwbGVWZWN0b3JzIiBuaWxsYWJsZT0idHJ1 +ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJTdGF0 +dXNUeXBlIj4NCiAgICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJBY3Rv +ciIgdHlwZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAg +ICAgPHhzOmVsZW1lbnQgbmFtZT0iVGltZXN0YW1wIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQ29tbWVudCIgdHlwZT0idWE6TG9j +YWxpemVkVGV4dCIgbWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNl +cXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3Jk +ZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6V29ya09yZGVyU3RhdHVzVHlwZSIgLz4NCg0KICA8eHM6 +Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNl +cXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlw +ZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1bmJv +dW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29t +cGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRlclN0YXR1c1R5cGUi +IHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 +czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJUeXBlIj4NCiAg +ICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJJRCIgdHlwZT0idWE6R3Vp +ZCIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQXNzZXRJRCIgdHlw +ZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iU3RhcnRUaW1lIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2NjdXJzPSIw +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iU3RhdHVzQ29tbWVudHMiIHR5cGU9InRuczpM +aXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAv +Pg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50 +IG5hbWU9IldvcmtPcmRlclR5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJUeXBlIiAvPg0KDQogIDx4 +czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZXb3JrT3JkZXJUeXBlIj4NCiAgICA8eHM6c2VxdWVu +Y2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0bnM6V29y +a09yZGVyVHlwZSIgbWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9 +InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhz +OmVsZW1lbnQgbmFtZT0iTGlzdE9mV29ya09yZGVyVHlwZSIgdHlwZT0idG5zOkxpc3RPZldvcmtP +cmRlclR5cGUiIG5pbGxhYmxlPSJ0cnVlIj48L3hzOmVsZW1lbnQ+DQoNCjwveHM6c2NoZW1hPg== @@ -91365,5 +91652,2000 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= 0 + + + ns=1;i=3582 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1116 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3584 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3583 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1116 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3585 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3584 + + DataType_64 + + 1 + VectorUnion + + + + VectorUnion + + + 0 + 0 + + + 0 + + + + i=45 + + true + + i=12756 + + + + + i=38 + + false + + ns=1;i=3590 + + + + + i=38 + + false + + ns=1;i=3598 + + + + + i=38 + + false + + ns=1;i=3606 + + + + false + + + + + ns=1;i=3585 + + DataType_64 + + 1 + VectorWithOptionalFields + + + + VectorWithOptionalFields + + + 0 + 0 + + + 0 + + + + i=45 + + true + + i=22 + + + + + i=38 + + false + + ns=1;i=3591 + + + + + i=38 + + false + + ns=1;i=3599 + + + + + i=38 + + false + + ns=1;i=3607 + + + + false + + + + + ns=1;i=3586 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1976 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3584 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3587 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1976 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3585 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3588 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2740 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3584 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3589 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2740 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3585 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3590 + + Object_1 + + 0 + Default Binary + + + + Default Binary + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3584 + + + + + i=39 + + false + + ns=1;i=3592 + + + + 0 + + + + ns=1;i=3591 + + Object_1 + + 0 + Default Binary + + + + Default Binary + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3585 + + + + + i=39 + + false + + ns=1;i=3595 + + + + 0 + + + + ns=1;i=3592 + + Variable_2 + + 1 + VectorUnion + + + + VectorUnion + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3518 + + + + + i=40 + + false + + i=69 + + + + + + VectorUnion + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3595 + + Variable_2 + + 1 + VectorWithOptionalFields + + + + VectorWithOptionalFields + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3518 + + + + + i=40 + + false + + i=69 + + + + + + VectorWithOptionalFields + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3598 + + Object_1 + + 0 + Default XML + + + + Default XML + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3584 + + + + + i=39 + + false + + ns=1;i=3600 + + + + 0 + + + + ns=1;i=3599 + + Object_1 + + 0 + Default XML + + + + Default XML + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3585 + + + + + i=39 + + false + + ns=1;i=3603 + + + + 0 + + + + ns=1;i=3600 + + Variable_2 + + 1 + VectorUnion + + + + VectorUnion + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3550 + + + + + i=40 + + false + + i=69 + + + + + + //xs:element[@name='VectorUnion'] + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3603 + + Variable_2 + + 1 + VectorWithOptionalFields + + + + VectorWithOptionalFields + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3550 + + + + + i=40 + + false + + i=69 + + + + + + //xs:element[@name='VectorWithOptionalFields'] + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3606 + + Object_1 + + 0 + Default JSON + + + + Default JSON + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3584 + + + + 0 + + + + ns=1;i=3607 + + Object_1 + + 0 + Default JSON + + + + Default JSON + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3585 + + + + 0 + + + + ns=1;i=3608 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1456 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3584 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3609 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1456 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3585 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3610 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2165 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3584 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3611 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2165 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3585 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3612 + + Variable_2 + + 1 + VectorUnionValue + + + + VectorUnionValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2929 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3584 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3613 + + Variable_2 + + 1 + VectorWithOptionalFieldsValue + + + + VectorWithOptionalFieldsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2929 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3585 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3614 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1116 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3615 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3615 + + DataType_64 + + 1 + MultipleVectors + + + + MultipleVectors + + + 0 + 0 + + + 0 + + + + i=45 + + true + + i=22 + + + + + i=38 + + false + + ns=1;i=3618 + + + + + i=38 + + false + + ns=1;i=3622 + + + + + i=38 + + false + + ns=1;i=3626 + + + + false + + + + + ns=1;i=3616 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1976 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3615 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3617 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2740 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3615 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3618 + + Object_1 + + 0 + Default Binary + + + + Default Binary + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3615 + + + + + i=39 + + false + + ns=1;i=3619 + + + + 0 + + + + ns=1;i=3619 + + Variable_2 + + 1 + MultipleVectors + + + + MultipleVectors + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3518 + + + + + i=40 + + false + + i=69 + + + + + + MultipleVectors + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3622 + + Object_1 + + 0 + Default XML + + + + Default XML + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3615 + + + + + i=39 + + false + + ns=1;i=3623 + + + + 0 + + + + ns=1;i=3623 + + Variable_2 + + 1 + MultipleVectors + + + + MultipleVectors + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=3550 + + + + + i=40 + + false + + i=69 + + + + + + //xs:element[@name='MultipleVectors'] + + + + i=12 + + -1 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3626 + + Object_1 + + 0 + Default JSON + + + + Default JSON + + + 0 + 0 + + + 0 + + + + i=40 + + false + + i=76 + + + + + i=38 + + true + + ns=1;i=3615 + + + + 0 + + + + ns=1;i=3627 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=1456 + + + + + i=40 + + false + + i=63 + + + + + i=37 + + false + + i=78 + + + + + + + + + + ns=1;i=3615 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3628 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2165 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3615 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + + + + ns=1;i=3629 + + Variable_2 + + 1 + MultipleVectorsValue + + + + MultipleVectorsValue + + + 0 + 0 + + + 0 + + + + i=47 + + true + + ns=1;i=2929 + + + + + i=40 + + false + + i=63 + + + + + + + + + + ns=1;i=3615 + + 1 + + 0 + + 1 + 1 + 0 + false + 0 + \ No newline at end of file diff --git a/Applications/Quickstarts.Servers/TestData/TestData.NodeSet2.xml b/Applications/Quickstarts.Servers/TestData/TestData.NodeSet2.xml index dca13f191..cf9e37126 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.NodeSet2.xml +++ b/Applications/Quickstarts.Servers/TestData/TestData.NodeSet2.xml @@ -1,10 +1,10 @@  - + http://test.org/UA/Data/ - + @@ -811,6 +811,9 @@ ns=1;i=1204 ns=1;i=1205 ns=1;i=1206 + ns=1;i=3582 + ns=1;i=3583 + ns=1;i=3614 ns=1;i=1015 @@ -1065,6 +1068,30 @@ ns=1;i=1206 + + VectorUnionValue + + i=63 + i=78 + ns=1;i=1116 + + + + VectorWithOptionalFieldsValue + + i=63 + i=78 + ns=1;i=1116 + + + + MultipleVectorsValue + + i=63 + i=78 + ns=1;i=1116 + + StructureValueObjectType @@ -1665,6 +1692,9 @@ ns=1;i=1544 ns=1;i=1545 ns=1;i=1546 + ns=1;i=3608 + ns=1;i=3609 + ns=1;i=3627 ns=1;i=1015 @@ -1892,6 +1922,30 @@ ns=1;i=1456 + + VectorUnionValue + + i=63 + i=78 + ns=1;i=1456 + + + + VectorWithOptionalFieldsValue + + i=63 + i=78 + ns=1;i=1456 + + + + MultipleVectorsValue + + i=63 + i=78 + ns=1;i=1456 + + AnalogArrayValueObjectType @@ -2776,6 +2830,42 @@ ns=1;i=1889 + + VectorUnion + + i=12756 + + + + + + + + + VectorWithOptionalFields + + i=22 + + + + + + + + + MultipleVectors + + i=22 + + + + + + + + + + WorkOrderStatusType @@ -6169,6 +6259,9 @@ ns=1;i=2064 ns=1;i=2065 ns=1;i=2066 + ns=1;i=3586 + ns=1;i=3587 + ns=1;i=3616 ns=1;i=1975 ns=1;i=1980 ns=1;i=1116 @@ -6770,6 +6863,27 @@ ns=1;i=2066 + + VectorUnionValue + + i=63 + ns=1;i=1976 + + + + VectorWithOptionalFieldsValue + + i=63 + ns=1;i=1976 + + + + MultipleVectorsValue + + i=63 + ns=1;i=1976 + + Structure @@ -7446,6 +7560,9 @@ ns=1;i=2253 ns=1;i=2254 ns=1;i=2255 + ns=1;i=3610 + ns=1;i=3611 + ns=1;i=3628 ns=1;i=1975 ns=1;i=2169 ns=1;i=1456 @@ -8023,6 +8140,27 @@ ns=1;i=2165 + + VectorUnionValue + + i=63 + ns=1;i=2165 + + + + VectorWithOptionalFieldsValue + + i=63 + ns=1;i=2165 + + + + MultipleVectorsValue + + i=63 + ns=1;i=2165 + + UserScalar @@ -13647,6 +13785,9 @@ ns=1;i=2828 ns=1;i=2829 ns=1;i=2830 + ns=1;i=3588 + ns=1;i=3589 + ns=1;i=3617 ns=1;i=2739 ns=1;i=2744 ns=1;i=1116 @@ -14251,6 +14392,27 @@ ns=1;i=2830 + + VectorUnionValue + + i=63 + ns=1;i=2740 + + + + VectorWithOptionalFieldsValue + + i=63 + ns=1;i=2740 + + + + MultipleVectorsValue + + i=63 + ns=1;i=2740 + + Structure @@ -14930,6 +15092,9 @@ ns=1;i=3017 ns=1;i=3018 ns=1;i=3019 + ns=1;i=3612 + ns=1;i=3613 + ns=1;i=3629 ns=1;i=2739 ns=1;i=2933 ns=1;i=1456 @@ -15510,6 +15675,27 @@ ns=1;i=2929 + + VectorUnionValue + + i=63 + ns=1;i=2929 + + + + VectorWithOptionalFieldsValue + + i=63 + ns=1;i=2929 + + + + MultipleVectorsValue + + i=63 + ns=1;i=2929 + + UserScalar @@ -18147,6 +18333,30 @@ i=76 + + Default Binary + + ns=1;i=3584 + ns=1;i=3592 + i=76 + + + + Default Binary + + ns=1;i=3585 + ns=1;i=3595 + i=76 + + + + Default Binary + + ns=1;i=3615 + ns=1;i=3619 + i=76 + + Default Binary @@ -18173,6 +18383,9 @@ ns=1;i=3528 ns=1;i=3531 ns=1;i=3534 + ns=1;i=3592 + ns=1;i=3595 + ns=1;i=3619 ns=1;i=3537 ns=1;i=3540 i=93 @@ -18418,20 +18631,44 @@ IE5hbWU9IlZlY3RvciIgQmFzZVR5cGU9InVhOkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpG aWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5h bWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWiIg VHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv -cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgQmFzZVR5cGU9InVh -OkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBY3RvciIgVHlwZU5hbWU9 -Im9wYzpTdHJpbmciIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJUaW1lc3RhbXAiIFR5cGVOYW1l -PSJvcGM6RGF0ZVRpbWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJDb21tZW50IiBUeXBlTmFt -ZT0idWE6TG9jYWxpemVkVGV4dCIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9w -YzpTdHJ1Y3R1cmVkVHlwZSBOYW1lPSJXb3JrT3JkZXJUeXBlIiBCYXNlVHlwZT0idWE6RXh0ZW5z -aW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IklEIiBUeXBlTmFtZT0ib3BjOkd1aWQi -IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBc3NldElEIiBUeXBlTmFtZT0ib3BjOlN0cmluZyIg -Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXJ0VGltZSIgVHlwZU5hbWU9Im9wYzpEYXRlVGlt -ZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZTdGF0dXNDb21tZW50cyIgVHlwZU5hbWU9 -Im9wYzpJbnQzMiIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXR1c0NvbW1lbnRzIiBUeXBl -TmFtZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIExlbmd0aEZpZWxkPSJOb09mU3RhdHVzQ29t -bWVudHMiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQo8L29wYzpUeXBlRGljdGlvbmFy -eT4= +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yVW5pb24iIEJhc2VUeXBlPSJ1YTpVbmlvbiI+ +DQogICAgPG9wYzpGaWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6Rmll +bGQgTmFtZT0iWiIgVHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRU +eXBlPg0KDQogIDxvcGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmll +bGRzIiBCYXNlVHlwZT0idWE6RXh0ZW5zaW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9 +IlgiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWSIgVHlw +ZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJaIiBUeXBlTmFtZT0i +b3BjOkRvdWJsZSIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9wYzpTdHJ1Y3R1 +cmVkVHlwZSBOYW1lPSJNdWx0aXBsZVZlY3RvcnMiIEJhc2VUeXBlPSJ1YTpFeHRlbnNpb25PYmpl +Y3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yIiBUeXBlTmFtZT0idG5zOlZlY3RvciIg +Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlZlY3RvclVuaW9uIiBUeXBlTmFtZT0idG5zOlZlY3Rv +clVuaW9uIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRz +IiBUeXBlTmFtZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIgLz4NCiAgICA8b3BjOkZp +ZWxkIE5hbWU9Ik5vT2ZWZWN0b3JBcnJheSIgVHlwZU5hbWU9Im9wYzpJbnQzMiIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlZlY3RvckFycmF5IiBUeXBlTmFtZT0idG5zOlZlY3RvciIgTGVuZ3Ro +RmllbGQ9Ik5vT2ZWZWN0b3JBcnJheSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZWZWN0 +b3JVbmlvbkFycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAgIDxvcGM6RmllbGQgTmFt +ZT0iVmVjdG9yVW5pb25BcnJheSIgVHlwZU5hbWU9InRuczpWZWN0b3JVbmlvbiIgTGVuZ3RoRmll +bGQ9Ik5vT2ZWZWN0b3JVbmlvbkFycmF5IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iTm9PZlZl +Y3RvcldpdGhPcHRpb25hbEZpZWxkc0FycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAg +IDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJyYXkiIFR5cGVOYW1l +PSJ0bnM6VmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiBMZW5ndGhGaWVsZD0iTm9PZlZlY3Rvcldp +dGhPcHRpb25hbEZpZWxkc0FycmF5IiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KICA8 +b3BjOlN0cnVjdHVyZWRUeXBlIE5hbWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIEJhc2VUeXBlPSJ1 +YTpFeHRlbnNpb25PYmplY3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQWN0b3IiIFR5cGVOYW1l +PSJvcGM6U3RyaW5nIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVGltZXN0YW1wIiBUeXBlTmFt +ZT0ib3BjOkRhdGVUaW1lIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQ29tbWVudCIgVHlwZU5h +bWU9InVhOkxvY2FsaXplZFRleHQiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyVHlwZSIgQmFzZVR5cGU9InVhOkV4dGVu +c2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJJRCIgVHlwZU5hbWU9Im9wYzpHdWlk +IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQXNzZXRJRCIgVHlwZU5hbWU9Im9wYzpTdHJpbmci +IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGFydFRpbWUiIFR5cGVOYW1lPSJvcGM6RGF0ZVRp +bWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJOb09mU3RhdHVzQ29tbWVudHMiIFR5cGVOYW1l +PSJvcGM6SW50MzIiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGF0dXNDb21tZW50cyIgVHlw +ZU5hbWU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBMZW5ndGhGaWVsZD0iTm9PZlN0YXR1c0Nv +bW1lbnRzIiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KPC9vcGM6VHlwZURpY3Rpb25h +cnk+ @@ -18504,6 +18741,36 @@ eT4= Vector + + VectorUnion + + i=69 + ns=1;i=3518 + + + VectorUnion + + + + VectorWithOptionalFields + + i=69 + ns=1;i=3518 + + + VectorWithOptionalFields + + + + MultipleVectors + + i=69 + ns=1;i=3518 + + + MultipleVectors + + WorkOrderStatusType @@ -18564,6 +18831,30 @@ eT4= i=76 + + Default XML + + ns=1;i=3584 + ns=1;i=3600 + i=76 + + + + Default XML + + ns=1;i=3585 + ns=1;i=3603 + i=76 + + + + Default XML + + ns=1;i=3615 + ns=1;i=3623 + i=76 + + Default XML @@ -18590,6 +18881,9 @@ eT4= ns=1;i=3560 ns=1;i=3563 ns=1;i=3566 + ns=1;i=3600 + ns=1;i=3603 + ns=1;i=3623 ns=1;i=3569 ns=1;i=3572 i=92 @@ -18602,7 +18896,7 @@ c2QiDQogIHhtbG5zOnRucz0iaHR0cDovL3Rlc3Qub3JnL1VBL0RhdGEvIg0KICB0YXJnZXROYW1l c3BhY2U9Imh0dHA6Ly90ZXN0Lm9yZy9VQS9EYXRhLyINCiAgZWxlbWVudEZvcm1EZWZhdWx0PSJx dWFsaWZpZWQiDQo+DQogIDx4czphbm5vdGF0aW9uPg0KICAgIDx4czphcHBpbmZvPg0KICAgICAg PHVhOk1vZGVsIE1vZGVsVXJpPSJodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS8iIFZlcnNpb249IjEu -MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMDYtMDZUMDY6MzM6NTEuNTc2OTYyOVoiIC8+DQog +MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMTAtMDlUMDg6NTE6MjQuNTIzNzA4OFoiIC8+DQog ICAgPC94czphcHBpbmZvPg0KICA8L3hzOmFubm90YXRpb24+DQogIA0KICA8eHM6aW1wb3J0IG5h bWVzcGFjZT0iaHR0cDovL29wY2ZvdW5kYXRpb24ub3JnL1VBLzIwMDgvMDIvVHlwZXMueHNkIiAv Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJTY2FsYXJTdHJ1Y3R1cmVEYXRhVHlwZSI+DQog @@ -18841,35 +19135,82 @@ YW1lPSJWZWN0b3IiIHR5cGU9InRuczpWZWN0b3IiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1 bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6 Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZlZlY3RvciIgdHlwZT0idG5z Okxpc3RPZlZlY3RvciIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29t -cGxleFR5cGUgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0K -ICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQWN0b3IiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJz -PSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlRpbWVzdGFt -cCIgdHlwZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50 -IG5hbWU9IkNvbW1lbnQiIHR5cGU9InVhOkxvY2FsaXplZFRleHQiIG1pbk9jY3Vycz0iMCIgbmls -bGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4N -CiAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlwZT0idG5zOldvcmtP -cmRlclN0YXR1c1R5cGUiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZldvcmtP -cmRlclN0YXR1c1R5cGUiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5h -bWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBt -aW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAg -ICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1l -PSJMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3Rh -dHVzVHlwZSIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29tcGxleFR5 -cGUgbmFtZT0iV29ya09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVs -ZW1lbnQgbmFtZT0iSUQiIHR5cGU9InVhOkd1aWQiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4 -czplbGVtZW50IG5hbWU9IkFzc2V0SUQiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJzPSIwIiBu -aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlN0YXJ0VGltZSIgdHlw -ZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 -IlN0YXR1c0NvbW1lbnRzIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSIgbWlu -T2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hz -OmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0 -bnM6V29ya09yZGVyVHlwZSIgLz4NCg0KICA8eHM6Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29y -a09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i -V29ya09yZGVyVHlwZSIgdHlwZT0idG5zOldvcmtPcmRlclR5cGUiIG1pbk9jY3Vycz0iMCIgbWF4 -T2NjdXJzPSJ1bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+ -DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRl -clR5cGUiIHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 -czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= +cGxleFR5cGUgbmFtZT0iVmVjdG9yVW5pb24iPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4 +czplbGVtZW50IG5hbWU9IlN3aXRjaEZpZWxkIiB0eXBlPSJ4czp1bnNpZ25lZEludCIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmNob2ljZT4NCiAgICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i +WCIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9IjAiIC8+DQogICAgICAgIDx4czplbGVtZW50 +IG5hbWU9IlkiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgICA8eHM6 +ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAg +IDwveHM6Y2hvaWNlPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQog +IDx4czplbGVtZW50IG5hbWU9IlZlY3RvclVuaW9uIiB0eXBlPSJ0bnM6VmVjdG9yVW5pb24iIC8+ +DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZlZlY3RvclVuaW9uIj4NCiAgICA8eHM6 +c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JVbmlvbiIgdHlwZT0idG5z +OlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJs +ZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JVbmlvbiIgdHlwZT0idG5zOkxpc3RPZlZlY3Rv +clVuaW9uIiBuaWxsYWJsZT0idHJ1ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlw +ZSBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAg +ICAgIDx4czplbGVtZW50IG5hbWU9IlgiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAv +Pg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iWSIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9 +IjAiIC8+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9j +Y3Vycz0iMCIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5cGU9InRuczpWZWN0 +b3JXaXRoT3B0aW9uYWxGaWVsZHMiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RP +ZlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiB0eXBlPSJ0bnM6VmVjdG9y +V2l0aE9wdGlvbmFsRmllbGRzIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBu +aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBl +Pg0KICA8eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5 +cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG5pbGxhYmxlPSJ0cnVlIj48 +L3hzOmVsZW1lbnQ+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ik11bHRpcGxlVmVjdG9ycyI+ +DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yIiB0eXBl +PSJ0bnM6VmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb24iIHR5cGU9InRuczpWZWN0b3JVbmlvbiIgbWluT2Nj +dXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlZlY3Rv +cldpdGhPcHRpb25hbEZpZWxkcyIgdHlwZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIg +bWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 +IlZlY3RvckFycmF5IiB0eXBlPSJ0bnM6TGlzdE9mVmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxh +YmxlPSJ0cnVlIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb25BcnJheSIg +dHlwZT0idG5zOkxpc3RPZlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVl +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJy +YXkiIHR5cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG1pbk9jY3Vycz0i +MCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4 +VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTXVsdGlwbGVWZWN0b3JzIiB0eXBlPSJ0bnM6TXVs +dGlwbGVWZWN0b3JzIiAvPg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZNdWx0aXBs +ZVZlY3RvcnMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9Ik11 +bHRpcGxlVmVjdG9ycyIgdHlwZT0idG5zOk11bHRpcGxlVmVjdG9ycyIgbWluT2NjdXJzPSIwIiBt +YXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5j +ZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTGlzdE9mTXVsdGlw +bGVWZWN0b3JzIiB0eXBlPSJ0bnM6TGlzdE9mTXVsdGlwbGVWZWN0b3JzIiBuaWxsYWJsZT0idHJ1 +ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJTdGF0 +dXNUeXBlIj4NCiAgICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJBY3Rv +ciIgdHlwZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAg +ICAgPHhzOmVsZW1lbnQgbmFtZT0iVGltZXN0YW1wIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQ29tbWVudCIgdHlwZT0idWE6TG9j +YWxpemVkVGV4dCIgbWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNl +cXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3Jk +ZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6V29ya09yZGVyU3RhdHVzVHlwZSIgLz4NCg0KICA8eHM6 +Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNl +cXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlw +ZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1bmJv +dW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29t +cGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRlclN0YXR1c1R5cGUi +IHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 +czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJUeXBlIj4NCiAg +ICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJJRCIgdHlwZT0idWE6R3Vp +ZCIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQXNzZXRJRCIgdHlw +ZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iU3RhcnRUaW1lIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2NjdXJzPSIw +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iU3RhdHVzQ29tbWVudHMiIHR5cGU9InRuczpM +aXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAv +Pg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50 +IG5hbWU9IldvcmtPcmRlclR5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJUeXBlIiAvPg0KDQogIDx4 +czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZXb3JrT3JkZXJUeXBlIj4NCiAgICA8eHM6c2VxdWVu +Y2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0bnM6V29y +a09yZGVyVHlwZSIgbWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9 +InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhz +OmVsZW1lbnQgbmFtZT0iTGlzdE9mV29ya09yZGVyVHlwZSIgdHlwZT0idG5zOkxpc3RPZldvcmtP +cmRlclR5cGUiIG5pbGxhYmxlPSJ0cnVlIj48L3hzOmVsZW1lbnQ+DQoNCjwveHM6c2NoZW1hPg== @@ -18942,6 +19283,36 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= //xs:element[@name='Vector'] + + VectorUnion + + i=69 + ns=1;i=3550 + + + //xs:element[@name='VectorUnion'] + + + + VectorWithOptionalFields + + i=69 + ns=1;i=3550 + + + //xs:element[@name='VectorWithOptionalFields'] + + + + MultipleVectors + + i=69 + ns=1;i=3550 + + + //xs:element[@name='MultipleVectors'] + + WorkOrderStatusType @@ -18997,6 +19368,27 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= i=76 + + Default JSON + + ns=1;i=3584 + i=76 + + + + Default JSON + + ns=1;i=3585 + i=76 + + + + Default JSON + + ns=1;i=3615 + i=76 + + Default JSON diff --git a/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.uanodes b/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.uanodes index 1a970df542524ba225f63fa5bd1db7d93dfd34ac..9f0c6d45a48b90848197f7ed602e9c04647feeed 100644 GIT binary patch delta 3672 zcmb7GU2GIp6uxI$+NQhAu5M|!+uiQ$_K);;ODU8B(!~@NOXkH93{2NB`HpNBRr6{$;LLhgt?{v7uy*exr1F&y;*>Tu| z2zuimmsUPDs$ED{?+!flw%1P-i`63WX(F;(z-t*vF!nrS?g#jj5Xx%mSj`FCjjh{L-lksS z(4o{Z+EAP8ST|zn$Q4A?Rg}~6bLz$w8aCihPla+y^3P_7T{)8T@<x;L#O3rA}If8TXQ7HHM$=yDPojuZ&>pAy_SG)dB zb#vi94-ywGaOX5I?SV(k`euhFXmyi%#(-l z-un<7b~@=r_X;*z=5cZ)slI}*rzTQB%RB{eLG073a!~0CDmWpy%n30j%%|`ZIp_*{ z5yxfX#)SC|ycKa=CT>hvo5Z>IDx7yMJfSaLg?Xs$jd#VjjK>EC`+EmBZ0zpe(6>20 zFraVJVO_t`jItdr-GCnVGYgsQOXkV+U+j@@94@zI2xCuisOC!eAEKRjJd9>lVFQL^IKnXm4>sNSXeG&bU z%zSq5OBRs(euu4rV%^F`(jujwH`%+>9=F{`B;l;St=QhB$WyL6OHr^UZV!{HU^^ix z`TcibJ^$)Q=q#o5iCFhf97)%g+&>{0n;gbqL@x0Z=5+jsPwbh!AkcO&CD{Ef{ZeoZFL3NHyfHx zlHL3@kj&4pd|0*vyTH_j3enEardYx9cL?_wF{ zr(g7BRNlUelW`JQ>2{#f0H896aK9Ggdw!7O$tzY1Y%jHD>~Ws{qMmUnlbpr&{szWC z)9seK86Pk)a&DK}$7snk-9~^(a(nDSMs}v@mPZ)pFg>=}e*FkzF{7A)nXZ9Zh=G}v zv9Xn@p`NL^nWdSL<@SVQjH0I7IoX&Z*cdsd1I>`%-pIq$!L%J{kSWu2b77`P0FhBt AGXMYp diff --git a/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.xml b/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.xml index 22245b979..7fa8b2f0c 100644 --- a/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.xml +++ b/Applications/Quickstarts.Servers/TestData/TestData.PredefinedNodes.xml @@ -2948,6 +2948,84 @@ 1 + + Variable_2 + + ns=1;i=3582 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + + i=78 + + 3582 + + ns=1;i=3584 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3583 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + + i=78 + + 3583 + + ns=1;i=3585 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3614 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + + i=78 + + 3614 + + ns=1;i=3615 + + -1 + 1 + 1 + ObjectType_8 @@ -5603,6 +5681,87 @@ 1 1 + + Variable_2 + + ns=1;i=3608 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + + i=78 + + 3608 + + ns=1;i=3584 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3609 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + + i=78 + + 3609 + + ns=1;i=3585 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3627 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + + i=78 + + 3627 + + ns=1;i=3615 + + 1 + 0 + 1 + 1 + ObjectType_8 @@ -8476,6 +8635,216 @@ 1 + + DataType_64 + + ns=1;i=3584 + + + 1 + VectorUnion + + + i=12756 + + + + i=14798 + + + + + i=12756 + + Union_2 + + + X + + i=11 + + -1 + + 0 + false + + + Y + + i=11 + + -1 + + 0 + false + + + Z + + i=11 + + -1 + + 0 + false + + + + + + + + DataType_64 + + ns=1;i=3585 + + + 1 + VectorWithOptionalFields + + + i=22 + + + + i=14798 + + + + + i=22 + + StructureWithOptionalFields_1 + + + X + + i=11 + + -1 + + 0 + true + + + Y + + i=11 + + -1 + + 0 + true + + + Z + + i=11 + + -1 + + 0 + true + + + + + + + + DataType_64 + + ns=1;i=3615 + + + 1 + MultipleVectors + + + i=22 + + + + i=14798 + + + + + i=22 + + Structure_0 + + + Vector + + ns=1;i=1888 + + -1 + + 0 + false + + + VectorUnion + + ns=1;i=3584 + + -1 + + 0 + false + + + VectorWithOptionalFields + + ns=1;i=3585 + + -1 + + 0 + false + + + VectorArray + + ns=1;i=1888 + + 1 + + 0 + + 0 + false + + + VectorUnionArray + + ns=1;i=3584 + + 1 + + 0 + + 0 + false + + + VectorWithOptionalFieldsArray + + ns=1;i=3585 + + 1 + + 0 + + 0 + false + + + + + + DataType_64 @@ -14275,6 +14644,75 @@ 1 + + Variable_2 + + ns=1;i=3586 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + 3586 + + ns=1;i=3584 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3587 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + 3587 + + ns=1;i=3585 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3616 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + 3616 + + ns=1;i=3615 + + -1 + 1 + 1 + Object_1 @@ -17646,6 +18084,78 @@ 1 1 + + Variable_2 + + ns=1;i=3610 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + 3610 + + ns=1;i=3584 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3611 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + 3611 + + ns=1;i=3585 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3628 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + 3628 + + ns=1;i=3615 + + 1 + 0 + 1 + 1 + Object_1 @@ -29297,6 +29807,75 @@ 1 + + Variable_2 + + ns=1;i=3588 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + 3588 + + ns=1;i=3584 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3589 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + 3589 + + ns=1;i=3585 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3617 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + 3617 + + ns=1;i=3615 + + -1 + 1 + 1 + Object_1 @@ -32678,6 +33257,78 @@ 1 1 + + Variable_2 + + ns=1;i=3612 + + + 1 + VectorUnionValue + + + i=47 + + + i=63 + + 3612 + + ns=1;i=3584 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3613 + + + 1 + VectorWithOptionalFieldsValue + + + i=47 + + + i=63 + + 3613 + + ns=1;i=3585 + + 1 + 0 + 1 + 1 + + + Variable_2 + + ns=1;i=3629 + + + 1 + MultipleVectorsValue + + + i=47 + + + i=63 + + 3629 + + ns=1;i=3615 + + 1 + 0 + 1 + 1 + Object_1 @@ -39755,6 +40406,105 @@ + + Object_1 + + ns=1;i=3590 + + + 0 + Default Binary + + + i=76 + + 3590 + + + + i=38 + + true + + ns=1;i=3584 + + + + + i=39 + + + ns=1;i=3592 + + + + + + Object_1 + + ns=1;i=3591 + + + 0 + Default Binary + + + i=76 + + 3591 + + + + i=38 + + true + + ns=1;i=3585 + + + + + i=39 + + + ns=1;i=3595 + + + + + + Object_1 + + ns=1;i=3618 + + + 0 + Default Binary + + + i=76 + + 3618 + + + + i=38 + + true + + ns=1;i=3615 + + + + + i=39 + + + ns=1;i=3619 + + + + Object_1 @@ -40075,20 +40825,44 @@ IE5hbWU9IlZlY3RvciIgQmFzZVR5cGU9InVhOkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpG aWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5h bWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWiIg VHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv -cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgQmFzZVR5cGU9InVh -OkV4dGVuc2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBY3RvciIgVHlwZU5hbWU9 -Im9wYzpTdHJpbmciIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJUaW1lc3RhbXAiIFR5cGVOYW1l -PSJvcGM6RGF0ZVRpbWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJDb21tZW50IiBUeXBlTmFt -ZT0idWE6TG9jYWxpemVkVGV4dCIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9w -YzpTdHJ1Y3R1cmVkVHlwZSBOYW1lPSJXb3JrT3JkZXJUeXBlIiBCYXNlVHlwZT0idWE6RXh0ZW5z -aW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IklEIiBUeXBlTmFtZT0ib3BjOkd1aWQi -IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJBc3NldElEIiBUeXBlTmFtZT0ib3BjOlN0cmluZyIg -Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXJ0VGltZSIgVHlwZU5hbWU9Im9wYzpEYXRlVGlt -ZSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZTdGF0dXNDb21tZW50cyIgVHlwZU5hbWU9 -Im9wYzpJbnQzMiIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlN0YXR1c0NvbW1lbnRzIiBUeXBl -TmFtZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIExlbmd0aEZpZWxkPSJOb09mU3RhdHVzQ29t -bWVudHMiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQo8L29wYzpUeXBlRGljdGlvbmFy -eT4= +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yVW5pb24iIEJhc2VUeXBlPSJ1YTpVbmlvbiI+ +DQogICAgPG9wYzpGaWVsZCBOYW1lPSJYIiBUeXBlTmFtZT0ib3BjOkRvdWJsZSIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlkiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6Rmll +bGQgTmFtZT0iWiIgVHlwZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRU +eXBlPg0KDQogIDxvcGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmll +bGRzIiBCYXNlVHlwZT0idWE6RXh0ZW5zaW9uT2JqZWN0Ij4NCiAgICA8b3BjOkZpZWxkIE5hbWU9 +IlgiIFR5cGVOYW1lPSJvcGM6RG91YmxlIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iWSIgVHlw +ZU5hbWU9Im9wYzpEb3VibGUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJaIiBUeXBlTmFtZT0i +b3BjOkRvdWJsZSIgLz4NCiAgPC9vcGM6U3RydWN0dXJlZFR5cGU+DQoNCiAgPG9wYzpTdHJ1Y3R1 +cmVkVHlwZSBOYW1lPSJNdWx0aXBsZVZlY3RvcnMiIEJhc2VUeXBlPSJ1YTpFeHRlbnNpb25PYmpl +Y3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yIiBUeXBlTmFtZT0idG5zOlZlY3RvciIg +Lz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9IlZlY3RvclVuaW9uIiBUeXBlTmFtZT0idG5zOlZlY3Rv +clVuaW9uIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRz +IiBUeXBlTmFtZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIgLz4NCiAgICA8b3BjOkZp +ZWxkIE5hbWU9Ik5vT2ZWZWN0b3JBcnJheSIgVHlwZU5hbWU9Im9wYzpJbnQzMiIgLz4NCiAgICA8 +b3BjOkZpZWxkIE5hbWU9IlZlY3RvckFycmF5IiBUeXBlTmFtZT0idG5zOlZlY3RvciIgTGVuZ3Ro +RmllbGQ9Ik5vT2ZWZWN0b3JBcnJheSIgLz4NCiAgICA8b3BjOkZpZWxkIE5hbWU9Ik5vT2ZWZWN0 +b3JVbmlvbkFycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAgIDxvcGM6RmllbGQgTmFt +ZT0iVmVjdG9yVW5pb25BcnJheSIgVHlwZU5hbWU9InRuczpWZWN0b3JVbmlvbiIgTGVuZ3RoRmll +bGQ9Ik5vT2ZWZWN0b3JVbmlvbkFycmF5IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iTm9PZlZl +Y3RvcldpdGhPcHRpb25hbEZpZWxkc0FycmF5IiBUeXBlTmFtZT0ib3BjOkludDMyIiAvPg0KICAg +IDxvcGM6RmllbGQgTmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJyYXkiIFR5cGVOYW1l +PSJ0bnM6VmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiBMZW5ndGhGaWVsZD0iTm9PZlZlY3Rvcldp +dGhPcHRpb25hbEZpZWxkc0FycmF5IiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KICA8 +b3BjOlN0cnVjdHVyZWRUeXBlIE5hbWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIEJhc2VUeXBlPSJ1 +YTpFeHRlbnNpb25PYmplY3QiPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQWN0b3IiIFR5cGVOYW1l +PSJvcGM6U3RyaW5nIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iVGltZXN0YW1wIiBUeXBlTmFt +ZT0ib3BjOkRhdGVUaW1lIiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQ29tbWVudCIgVHlwZU5h +bWU9InVhOkxvY2FsaXplZFRleHQiIC8+DQogIDwvb3BjOlN0cnVjdHVyZWRUeXBlPg0KDQogIDxv +cGM6U3RydWN0dXJlZFR5cGUgTmFtZT0iV29ya09yZGVyVHlwZSIgQmFzZVR5cGU9InVhOkV4dGVu +c2lvbk9iamVjdCI+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJJRCIgVHlwZU5hbWU9Im9wYzpHdWlk +IiAvPg0KICAgIDxvcGM6RmllbGQgTmFtZT0iQXNzZXRJRCIgVHlwZU5hbWU9Im9wYzpTdHJpbmci +IC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGFydFRpbWUiIFR5cGVOYW1lPSJvcGM6RGF0ZVRp +bWUiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJOb09mU3RhdHVzQ29tbWVudHMiIFR5cGVOYW1l +PSJvcGM6SW50MzIiIC8+DQogICAgPG9wYzpGaWVsZCBOYW1lPSJTdGF0dXNDb21tZW50cyIgVHlw +ZU5hbWU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBMZW5ndGhGaWVsZD0iTm9PZlN0YXR1c0Nv +bW1lbnRzIiAvPg0KICA8L29wYzpTdHJ1Y3R1cmVkVHlwZT4NCg0KPC9vcGM6VHlwZURpY3Rpb25h +cnk+ @@ -40304,6 +41078,90 @@ eT4= 1 1 + + Variable_2 + + ns=1;i=3592 + + + 1 + VectorUnion + + + i=47 + + + i=69 + + 3592 + + + VectorUnion + + + + i=12 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3595 + + + 1 + VectorWithOptionalFields + + + i=47 + + + i=69 + + 3595 + + + VectorWithOptionalFields + + + + i=12 + + -1 + 1 + 1 + + + Variable_2 + + ns=1;i=3619 + + + 1 + MultipleVectors + + + i=47 + + + i=69 + + 3619 + + + MultipleVectors + + + + i=12 + + -1 + 1 + 1 + Variable_2 @@ -40526,6 +41384,105 @@ eT4= + + Object_1 + + ns=1;i=3598 + + + 0 + Default XML + + + i=76 + + 3598 + + + + i=38 + + true + + ns=1;i=3584 + + + + + i=39 + + + ns=1;i=3600 + + + + + + Object_1 + + ns=1;i=3599 + + + 0 + Default XML + + + i=76 + + 3599 + + + + i=38 + + true + + ns=1;i=3585 + + + + + i=39 + + + ns=1;i=3603 + + + + + + Object_1 + + ns=1;i=3622 + + + 0 + Default XML + + + i=76 + + 3622 + + + + i=38 + + true + + ns=1;i=3615 + + + + + i=39 + + + ns=1;i=3623 + + + + Object_1 @@ -40613,7 +41570,7 @@ c2QiDQogIHhtbG5zOnRucz0iaHR0cDovL3Rlc3Qub3JnL1VBL0RhdGEvIg0KICB0YXJnZXROYW1l c3BhY2U9Imh0dHA6Ly90ZXN0Lm9yZy9VQS9EYXRhLyINCiAgZWxlbWVudEZvcm1EZWZhdWx0PSJx dWFsaWZpZWQiDQo+DQogIDx4czphbm5vdGF0aW9uPg0KICAgIDx4czphcHBpbmZvPg0KICAgICAg PHVhOk1vZGVsIE1vZGVsVXJpPSJodHRwOi8vdGVzdC5vcmcvVUEvRGF0YS8iIFZlcnNpb249IjEu -MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMDYtMDZUMDY6MzM6NTEuNTc2OTYyOVoiIC8+DQog +MC4wIiBQdWJsaWNhdGlvbkRhdGU9IjIwMjMtMTAtMDlUMDg6NTE6MjQuNTIzNzA4OFoiIC8+DQog ICAgPC94czphcHBpbmZvPg0KICA8L3hzOmFubm90YXRpb24+DQogIA0KICA8eHM6aW1wb3J0IG5h bWVzcGFjZT0iaHR0cDovL29wY2ZvdW5kYXRpb24ub3JnL1VBLzIwMDgvMDIvVHlwZXMueHNkIiAv Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJTY2FsYXJTdHJ1Y3R1cmVEYXRhVHlwZSI+DQog @@ -40852,35 +41809,82 @@ YW1lPSJWZWN0b3IiIHR5cGU9InRuczpWZWN0b3IiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1 bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6 Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZlZlY3RvciIgdHlwZT0idG5z Okxpc3RPZlZlY3RvciIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29t -cGxleFR5cGUgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0K -ICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQWN0b3IiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJz -PSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlRpbWVzdGFt -cCIgdHlwZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50 -IG5hbWU9IkNvbW1lbnQiIHR5cGU9InVhOkxvY2FsaXplZFRleHQiIG1pbk9jY3Vycz0iMCIgbmls -bGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4N -CiAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlwZT0idG5zOldvcmtP -cmRlclN0YXR1c1R5cGUiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZldvcmtP -cmRlclN0YXR1c1R5cGUiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5h -bWU9IldvcmtPcmRlclN0YXR1c1R5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJTdGF0dXNUeXBlIiBt -aW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAg -ICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1l -PSJMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3Rh -dHVzVHlwZSIgbmlsbGFibGU9InRydWUiPjwveHM6ZWxlbWVudD4NCg0KICA8eHM6Y29tcGxleFR5 -cGUgbmFtZT0iV29ya09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVs -ZW1lbnQgbmFtZT0iSUQiIHR5cGU9InVhOkd1aWQiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4 -czplbGVtZW50IG5hbWU9IkFzc2V0SUQiIHR5cGU9InhzOnN0cmluZyIgbWluT2NjdXJzPSIwIiBu -aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlN0YXJ0VGltZSIgdHlw -ZT0ieHM6ZGF0ZVRpbWUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 -IlN0YXR1c0NvbW1lbnRzIiB0eXBlPSJ0bnM6TGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSIgbWlu -T2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hz -OmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0 -bnM6V29ya09yZGVyVHlwZSIgLz4NCg0KICA8eHM6Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29y -a09yZGVyVHlwZSI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i -V29ya09yZGVyVHlwZSIgdHlwZT0idG5zOldvcmtPcmRlclR5cGUiIG1pbk9jY3Vycz0iMCIgbWF4 -T2NjdXJzPSJ1bmJvdW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+ -DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRl -clR5cGUiIHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 -czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= +cGxleFR5cGUgbmFtZT0iVmVjdG9yVW5pb24iPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4 +czplbGVtZW50IG5hbWU9IlN3aXRjaEZpZWxkIiB0eXBlPSJ4czp1bnNpZ25lZEludCIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmNob2ljZT4NCiAgICAgICAgPHhzOmVsZW1lbnQgbmFtZT0i +WCIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9IjAiIC8+DQogICAgICAgIDx4czplbGVtZW50 +IG5hbWU9IlkiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgICA8eHM6 +ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9jY3Vycz0iMCIgLz4NCiAgICAg +IDwveHM6Y2hvaWNlPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQog +IDx4czplbGVtZW50IG5hbWU9IlZlY3RvclVuaW9uIiB0eXBlPSJ0bnM6VmVjdG9yVW5pb24iIC8+ +DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RPZlZlY3RvclVuaW9uIj4NCiAgICA8eHM6 +c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JVbmlvbiIgdHlwZT0idG5z +OlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBuaWxsYWJs +ZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JVbmlvbiIgdHlwZT0idG5zOkxpc3RPZlZlY3Rv +clVuaW9uIiBuaWxsYWJsZT0idHJ1ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlw +ZSBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAg +ICAgIDx4czplbGVtZW50IG5hbWU9IlgiIHR5cGU9InhzOmRvdWJsZSIgbWluT2NjdXJzPSIwIiAv +Pg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iWSIgdHlwZT0ieHM6ZG91YmxlIiBtaW5PY2N1cnM9 +IjAiIC8+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJaIiB0eXBlPSJ4czpkb3VibGUiIG1pbk9j +Y3Vycz0iMCIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8 +eHM6ZWxlbWVudCBuYW1lPSJWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5cGU9InRuczpWZWN0 +b3JXaXRoT3B0aW9uYWxGaWVsZHMiIC8+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ikxpc3RP +ZlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyI+DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzIiB0eXBlPSJ0bnM6VmVjdG9y +V2l0aE9wdGlvbmFsRmllbGRzIiBtaW5PY2N1cnM9IjAiIG1heE9jY3Vycz0idW5ib3VuZGVkIiBu +aWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNlcXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBl +Pg0KICA8eHM6ZWxlbWVudCBuYW1lPSJMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIHR5 +cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG5pbGxhYmxlPSJ0cnVlIj48 +L3hzOmVsZW1lbnQ+DQoNCiAgPHhzOmNvbXBsZXhUeXBlIG5hbWU9Ik11bHRpcGxlVmVjdG9ycyI+ +DQogICAgPHhzOnNlcXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yIiB0eXBl +PSJ0bnM6VmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb24iIHR5cGU9InRuczpWZWN0b3JVbmlvbiIgbWluT2Nj +dXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9IlZlY3Rv +cldpdGhPcHRpb25hbEZpZWxkcyIgdHlwZT0idG5zOlZlY3RvcldpdGhPcHRpb25hbEZpZWxkcyIg +bWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9 +IlZlY3RvckFycmF5IiB0eXBlPSJ0bnM6TGlzdE9mVmVjdG9yIiBtaW5PY2N1cnM9IjAiIG5pbGxh +YmxlPSJ0cnVlIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yVW5pb25BcnJheSIg +dHlwZT0idG5zOkxpc3RPZlZlY3RvclVuaW9uIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVl +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iVmVjdG9yV2l0aE9wdGlvbmFsRmllbGRzQXJy +YXkiIHR5cGU9InRuczpMaXN0T2ZWZWN0b3JXaXRoT3B0aW9uYWxGaWVsZHMiIG1pbk9jY3Vycz0i +MCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4 +VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTXVsdGlwbGVWZWN0b3JzIiB0eXBlPSJ0bnM6TXVs +dGlwbGVWZWN0b3JzIiAvPg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZNdWx0aXBs +ZVZlY3RvcnMiPg0KICAgIDx4czpzZXF1ZW5jZT4NCiAgICAgIDx4czplbGVtZW50IG5hbWU9Ik11 +bHRpcGxlVmVjdG9ycyIgdHlwZT0idG5zOk11bHRpcGxlVmVjdG9ycyIgbWluT2NjdXJzPSIwIiBt +YXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5j +ZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhzOmVsZW1lbnQgbmFtZT0iTGlzdE9mTXVsdGlw +bGVWZWN0b3JzIiB0eXBlPSJ0bnM6TGlzdE9mTXVsdGlwbGVWZWN0b3JzIiBuaWxsYWJsZT0idHJ1 +ZSI+PC94czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJTdGF0 +dXNUeXBlIj4NCiAgICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJBY3Rv +ciIgdHlwZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAg +ICAgPHhzOmVsZW1lbnQgbmFtZT0iVGltZXN0YW1wIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2Nj +dXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQ29tbWVudCIgdHlwZT0idWE6TG9j +YWxpemVkVGV4dCIgbWluT2NjdXJzPSIwIiBuaWxsYWJsZT0idHJ1ZSIgLz4NCiAgICA8L3hzOnNl +cXVlbmNlPg0KICA8L3hzOmNvbXBsZXhUeXBlPg0KICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3Jk +ZXJTdGF0dXNUeXBlIiB0eXBlPSJ0bnM6V29ya09yZGVyU3RhdHVzVHlwZSIgLz4NCg0KICA8eHM6 +Y29tcGxleFR5cGUgbmFtZT0iTGlzdE9mV29ya09yZGVyU3RhdHVzVHlwZSI+DQogICAgPHhzOnNl +cXVlbmNlPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iV29ya09yZGVyU3RhdHVzVHlwZSIgdHlw +ZT0idG5zOldvcmtPcmRlclN0YXR1c1R5cGUiIG1pbk9jY3Vycz0iMCIgbWF4T2NjdXJzPSJ1bmJv +dW5kZWQiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29t +cGxleFR5cGU+DQogIDx4czplbGVtZW50IG5hbWU9Ikxpc3RPZldvcmtPcmRlclN0YXR1c1R5cGUi +IHR5cGU9InRuczpMaXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBuaWxsYWJsZT0idHJ1ZSI+PC94 +czplbGVtZW50Pg0KDQogIDx4czpjb21wbGV4VHlwZSBuYW1lPSJXb3JrT3JkZXJUeXBlIj4NCiAg +ICA8eHM6c2VxdWVuY2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJJRCIgdHlwZT0idWE6R3Vp +ZCIgbWluT2NjdXJzPSIwIiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iQXNzZXRJRCIgdHlw +ZT0ieHM6c3RyaW5nIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAvPg0KICAgICAgPHhz +OmVsZW1lbnQgbmFtZT0iU3RhcnRUaW1lIiB0eXBlPSJ4czpkYXRlVGltZSIgbWluT2NjdXJzPSIw +IiAvPg0KICAgICAgPHhzOmVsZW1lbnQgbmFtZT0iU3RhdHVzQ29tbWVudHMiIHR5cGU9InRuczpM +aXN0T2ZXb3JrT3JkZXJTdGF0dXNUeXBlIiBtaW5PY2N1cnM9IjAiIG5pbGxhYmxlPSJ0cnVlIiAv +Pg0KICAgIDwveHM6c2VxdWVuY2U+DQogIDwveHM6Y29tcGxleFR5cGU+DQogIDx4czplbGVtZW50 +IG5hbWU9IldvcmtPcmRlclR5cGUiIHR5cGU9InRuczpXb3JrT3JkZXJUeXBlIiAvPg0KDQogIDx4 +czpjb21wbGV4VHlwZSBuYW1lPSJMaXN0T2ZXb3JrT3JkZXJUeXBlIj4NCiAgICA8eHM6c2VxdWVu +Y2U+DQogICAgICA8eHM6ZWxlbWVudCBuYW1lPSJXb3JrT3JkZXJUeXBlIiB0eXBlPSJ0bnM6V29y +a09yZGVyVHlwZSIgbWluT2NjdXJzPSIwIiBtYXhPY2N1cnM9InVuYm91bmRlZCIgbmlsbGFibGU9 +InRydWUiIC8+DQogICAgPC94czpzZXF1ZW5jZT4NCiAgPC94czpjb21wbGV4VHlwZT4NCiAgPHhz +OmVsZW1lbnQgbmFtZT0iTGlzdE9mV29ya09yZGVyVHlwZSIgdHlwZT0idG5zOkxpc3RPZldvcmtP +cmRlclR5cGUiIG5pbGxhYmxlPSJ0cnVlIj48L3hzOmVsZW1lbnQ+DQoNCjwveHM6c2NoZW1hPg== @@ -41096,6 +42100,90 @@ czplbGVtZW50Pg0KDQo8L3hzOnNjaGVtYT4= 1 1 + + 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 @@ - {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)] 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.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 }); }