/* ======================================================================== * Copyright (c) 2005-2025 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.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; namespace Opc.Ua.Server.TestFramework { /// /// Test workers using test services. /// public static class CommonTestWorkers { public const int DefaultMonitoredItemsQueueSize = 0; public const int DefaultMonitoredItemsSamplingInterval = -1; public static readonly ExpandedNodeId[] NodeIdTestSetStatic = [ new ExpandedNodeId( "Scalar_Static_SByte", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_Int16", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_Int32", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_Byte", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_UInt16", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_UInt32", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_NodeId", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_LocalizedText", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_QualifiedName", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Static_Variant", Quickstarts.ReferenceServer.Namespaces.ReferenceServer) ]; /// /// static variables from namespace TestData /// public static readonly ExpandedNodeId[] NodeIdTestDataSetStatic = [ new ExpandedNodeId( TestData.Variables.Data_Static_Scalar_Int16Value, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Static_Scalar_Int32Value, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Static_Scalar_UInt16Value, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Static_Scalar_UInt32Value, TestData.Namespaces.TestData) ]; /// /// CTT simulation data /// public static readonly ExpandedNodeId[] NodeIdTestSetSimulation = [ new ExpandedNodeId( "Scalar_Simulation_SByte", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Simulation_Int16", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Simulation_Int32", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Simulation_Byte", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Simulation_UInt16", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Simulation_UInt32", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Simulation_NodeId", Quickstarts.ReferenceServer.Namespaces.ReferenceServer), new ExpandedNodeId( "Scalar_Simulation_LocalizedText", Quickstarts.ReferenceServer.Namespaces.ReferenceServer ), new ExpandedNodeId( "Scalar_Simulation_QualifiedName", Quickstarts.ReferenceServer.Namespaces.ReferenceServer ), new ExpandedNodeId( "Scalar_Simulation_Variant", Quickstarts.ReferenceServer.Namespaces.ReferenceServer) ]; /// /// Ref server test data node manager. /// public static readonly ExpandedNodeId[] NodeIdTestSetDataSimulation = [ new ExpandedNodeId( TestData.Variables.Data_Dynamic_Scalar_Int16Value, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Dynamic_Scalar_Int32Value, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Dynamic_Scalar_UInt16Value, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Dynamic_Scalar_UInt32Value, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.AnalogScalarValueObjectType_UInt32Value, TestData.Namespaces.TestData ), new ExpandedNodeId( TestData.Variables.Data_Dynamic_AnalogArray_ByteValue, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Dynamic_Scalar_VectorValue, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Dynamic_Scalar_VectorValue_X, TestData.Namespaces.TestData), new ExpandedNodeId( TestData.Variables.Data_Dynamic_Structure_ScalarStructure, TestData.Namespaces.TestData) ]; public static readonly ExpandedNodeId[] NodeIdTestDataHistory = [ new ExpandedNodeId( TestData.Variables.Data_Dynamic_Scalar_Int32Value, TestData.Namespaces.TestData) ]; public static readonly ExpandedNodeId[] NodeIdMemoryBufferSimulation = [ // dynamic variables from namespace MemoryBuffer new ExpandedNodeId("UInt32[64]", MemoryBuffer.Namespaces.MemoryBuffer + "/Instance"), new ExpandedNodeId("Double[40]", MemoryBuffer.Namespaces.MemoryBuffer + "/Instance") ]; /// /// Mass numeric static nodes for performance tests. /// public static readonly (Type Type, ExpandedNodeId[] NodeIds)[] NodeIdTestSetStaticMassNumeric = [ (typeof(short), Enumerable.Range(0, 100).Select(i => new ExpandedNodeId($"Scalar_Static_Mass_Int16_Int16_{i:D2}", Quickstarts.ReferenceServer.Namespaces.ReferenceServer)).ToArray()), (typeof(int), Enumerable.Range(0, 100).Select(i => new ExpandedNodeId($"Scalar_Static_Mass_Int32_Int32_{i:D2}", Quickstarts.ReferenceServer.Namespaces.ReferenceServer)).ToArray()), (typeof(long), Enumerable.Range(0, 100).Select(i => new ExpandedNodeId($"Scalar_Static_Mass_Int64_Int64_{i:D2}", Quickstarts.ReferenceServer.Namespaces.ReferenceServer)).ToArray()), (typeof(ushort), Enumerable.Range(0, 100).Select(i => new ExpandedNodeId($"Scalar_Static_Mass_UInt16_UInt16_{i:D2}", Quickstarts.ReferenceServer.Namespaces.ReferenceServer)).ToArray()), (typeof(uint), Enumerable.Range(0, 100).Select(i => new ExpandedNodeId($"Scalar_Static_Mass_UInt32_UInt32_{i:D2}", Quickstarts.ReferenceServer.Namespaces.ReferenceServer)).ToArray()), (typeof(ulong), Enumerable.Range(0, 100).Select(i => new ExpandedNodeId($"Scalar_Static_Mass_UInt64_UInt64_{i:D2}", Quickstarts.ReferenceServer.Namespaces.ReferenceServer)).ToArray()) ]; /// /// Worker function to browse the full address space of a server. /// /// The service interface. /// The request header. /// The operation limits. /// The browse template. /// Attach the complete listing rather than retaining it in test output. public static async Task> BrowseFullAddressSpaceWorkerAsync( IServerTestServices services, RequestHeader requestHeader, OperationLimits? operationLimits = null, BrowseDescription? browseDescription = null, bool outputResult = false) { operationLimits ??= new OperationLimits(); requestHeader.Timestamp = DateTime.UtcNow; // Browse template NodeId startingNode = ObjectIds.RootFolder; BrowseDescription browseTemplate = browseDescription ?? new BrowseDescription { NodeId = startingNode, BrowseDirection = BrowseDirection.Forward, ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences, IncludeSubtypes = true, NodeClassMask = 0, ResultMask = (uint)BrowseResultMask.All }; ArrayOf browseDescriptionCollection = ServerFixtureUtils.CreateBrowseDescriptionCollectionFromNodeId( [.. new NodeId[] { ObjectIds.RootFolder }], browseTemplate); // Browse uint requestedMaxReferencesPerNode = operationLimits.MaxNodesPerBrowse; bool verifyMaxNodesPerBrowse = operationLimits.MaxNodesPerBrowse > 0; var referenceDescriptions = new List(); // Test if server responds with BadNothingToDo { ServiceResultException sre = Assert.ThrowsAsync(async () => _ = await services.BrowseAsync( requestHeader, null!, 0, []).ConfigureAwait(false))!; Assert.That(sre!.StatusCode, Is.EqualTo(StatusCodes.BadNothingToDo)); } while (browseDescriptionCollection.Count > 0) { var allResults = new List(); if (verifyMaxNodesPerBrowse && browseDescriptionCollection.Count > operationLimits.MaxNodesPerBrowse) { verifyMaxNodesPerBrowse = false; // Test if server responds with BadTooManyOperations ServiceResultException sre = Assert.ThrowsAsync(async () => _ = await services.BrowseAsync( requestHeader, null!, 0, browseDescriptionCollection).ConfigureAwait(false))!; Assert.That( sre!.StatusCode, Is.EqualTo(StatusCodes.BadTooManyOperations)); // Test if server responds with BadTooManyOperations ArrayOf tempBrowsePath = browseDescriptionCollection[..((int)operationLimits.MaxNodesPerBrowse + 1)]; sre = Assert.ThrowsAsync(async () => _ = await services.BrowseAsync( requestHeader, null!, 0, tempBrowsePath).ConfigureAwait(false))!; Assert.That( sre!.StatusCode, Is.EqualTo(StatusCodes.BadTooManyOperations)); } bool repeatBrowse; uint maxNodesPerBrowse = operationLimits.MaxNodesPerBrowse; BrowseResponse? browseResponse = null; do { if (maxNodesPerBrowse >= browseDescriptionCollection.Count) { maxNodesPerBrowse = 0; // Do not slice, take all } ArrayOf browseCollection = maxNodesPerBrowse == 0 ? browseDescriptionCollection : browseDescriptionCollection[..(int)maxNodesPerBrowse]; repeatBrowse = false; try { requestHeader.Timestamp = DateTime.UtcNow; browseResponse = await services.BrowseAsync( requestHeader, null!, requestedMaxReferencesPerNode, browseCollection).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse( browseResponse.ResponseHeader, browseResponse.Results, browseCollection); ServerFixtureUtils.ValidateDiagnosticInfos( browseResponse.DiagnosticInfos, browseCollection, browseResponse.ResponseHeader.StringTable, services.Logger); allResults.AddRange(browseResponse.Results); } catch (ServiceResultException sre) when ( sre.StatusCode == StatusCodes.BadEncodingLimitsExceeded || sre.StatusCode == StatusCodes.BadResponseTooLarge) { // try to address by overriding operation limit maxNodesPerBrowse = maxNodesPerBrowse == 0 ? (uint)browseCollection.Count / 2 : maxNodesPerBrowse / 2; repeatBrowse = true; } } while (repeatBrowse); browseDescriptionCollection = maxNodesPerBrowse == 0 ? default : browseDescriptionCollection[(int)maxNodesPerBrowse..]; // Browse next ArrayOf continuationPoints = ServerFixtureUtils.PrepareBrowseNext( browseResponse!.Results); while (continuationPoints.Count > 0) { requestHeader.Timestamp = DateTime.UtcNow; BrowseNextResponse browseNextResponse = await services.BrowseNextAsync( requestHeader, false, continuationPoints).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse( browseNextResponse.ResponseHeader, browseNextResponse.Results, continuationPoints); ServerFixtureUtils.ValidateDiagnosticInfos( browseNextResponse.DiagnosticInfos, continuationPoints, browseNextResponse.ResponseHeader.StringTable, services.Logger); allResults.AddRange(browseNextResponse.Results); continuationPoints = ServerFixtureUtils.PrepareBrowseNext( browseNextResponse.Results); } // Build browse request for next level var browseTable = new List(); foreach (BrowseResult result in allResults) { referenceDescriptions.AddRange(result.References); foreach (ReferenceDescription reference in result.References) { browseTable.Add(ExpandedNodeId.ToNodeId(reference.NodeId, null!)); } } browseDescriptionCollection = ServerFixtureUtils .CreateBrowseDescriptionCollectionFromNodeId( browseTable, browseTemplate); } referenceDescriptions.Sort((x, y) => x.NodeId.CompareTo(y.NodeId)); int diagnosticNs = services.MessageContext.NamespaceUris .GetIndex(Ua.Namespaces.OpcUa + "Diagnostics"); // Remove diagnostic nodes since they change per session referenceDescriptions.RemoveAll(r => r.NodeId.NamespaceIndex == diagnosticNs); TestContext.Out .WriteLine("Found {0} references on server.", referenceDescriptions.Count); if (outputResult) { string directory = Path.Combine(TestContext.CurrentContext.WorkDirectory, "TestResults", "BrowseResults"); Directory.CreateDirectory(directory); string path = Path.Combine(directory, $"browse-{Guid.NewGuid():N}.txt"); using (var stream = new FileStream( path, FileMode.CreateNew, FileAccess.Write, FileShare.None, 4096, useAsync: true)) using (var writer = new StreamWriter(stream)) { foreach (ReferenceDescription reference in referenceDescriptions) { await writer.WriteLineAsync( $"NodeId {reference.NodeId} {reference.NodeClass} {reference.BrowseName}") .ConfigureAwait(false); } await writer.FlushAsync().ConfigureAwait(false); } TestContext.AddTestAttachment(path, "Full address-space browse results"); } return referenceDescriptions; } /// /// Worker method to translate the browse path. /// public static async Task> TranslateBrowsePathWorkerAsync( IServerTestServices services, ArrayOf referenceDescriptions, RequestHeader requestHeader, OperationLimits operationLimits) { // Browse template NodeId startingNode = ObjectIds.RootFolder; requestHeader.Timestamp = DateTime.UtcNow; // TranslateBrowsePath bool verifyMaxNodesPerBrowse = operationLimits .MaxNodesPerTranslateBrowsePathsToNodeIds > 0; ArrayOf browsePaths = referenceDescriptions.ConvertAll(r => new BrowsePath { RelativePath = new RelativePath(r.BrowseName), StartingNode = startingNode }); var allBrowsePaths = new List(); while (browsePaths.Count > 0) { if (verifyMaxNodesPerBrowse && browsePaths.Count > operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds) { verifyMaxNodesPerBrowse = false; // Test if server responds with BadTooManyOperations ServiceResultException sre = Assert.ThrowsAsync(async () => _ = await services.TranslateBrowsePathsToNodeIdsAsync( requestHeader, browsePaths).ConfigureAwait(false))!; Assert.That( sre!.StatusCode, Is.EqualTo(StatusCodes.BadTooManyOperations)); } ArrayOf browsePathSnippet = operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds == 0 || browsePaths.Count <= operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds ? browsePaths // take all : browsePaths[..(int)operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds]; TranslateBrowsePathsToNodeIdsResponse translateResponse = await services.TranslateBrowsePathsToNodeIdsAsync( requestHeader, browsePathSnippet).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(translateResponse.ResponseHeader, translateResponse.Results, browsePathSnippet); ServerFixtureUtils.ValidateDiagnosticInfos( translateResponse.DiagnosticInfos, browsePathSnippet, translateResponse.ResponseHeader.StringTable, services.Logger); allBrowsePaths.AddRange(translateResponse.Results); foreach (BrowsePathResult result in translateResponse.Results) { if (!result.Targets.IsEmpty) { TestContext.Out.WriteLine("BrowsePath {0}", result.Targets[0].TargetId.ToString()); } } browsePaths = operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds == 0 || browsePaths.Count <= operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds ? default : // done browsePaths[(int)operationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds..]; } return allBrowsePaths; } /// /// Worker method to test subscriptions of a server. /// public static async Task SubscriptionTestAsync( IServerTestServices services, RequestHeader requestHeader) { // start time requestHeader.Timestamp = DateTime.UtcNow; // create subscription const double publishingInterval = 1000.0; const uint lifetimeCount = 60; const uint maxKeepAliveCount = 2; const uint maxNotificationPerPublish = 0; const byte priority = 128; bool enabled = false; const uint queueSize = 5; CreateSubscriptionResponse createSubscriptionResponse = await services.CreateSubscriptionAsync( requestHeader, publishingInterval, lifetimeCount, maxKeepAliveCount, maxNotificationPerPublish, enabled, priority).ConfigureAwait(false); Assert.That(createSubscriptionResponse.RevisedPublishingInterval, Is.EqualTo(publishingInterval)); Assert.That(createSubscriptionResponse.RevisedLifetimeCount, Is.EqualTo(lifetimeCount)); Assert.That(createSubscriptionResponse.RevisedMaxKeepAliveCount, Is.EqualTo(maxKeepAliveCount)); ServerFixtureUtils.ValidateResponse(createSubscriptionResponse.ResponseHeader); uint id = createSubscriptionResponse.SubscriptionId; ArrayOf itemsToCreate = default; // check badnothingtodo ServiceResultException sre = Assert.ThrowsAsync(async () => await services.CreateMonitoredItemsAsync( requestHeader, id, TimestampsToReturn.Neither, itemsToCreate).ConfigureAwait(false))!; Assert.That(sre!.StatusCode, Is.EqualTo(StatusCodes.BadNothingToDo)); // add item uint handleCounter = 1; itemsToCreate = [ new MonitoredItemCreateRequest { ItemToMonitor = new ReadValueId { AttributeId = Attributes.Value, NodeId = VariableIds.Server_ServerStatus_CurrentTime }, MonitoringMode = MonitoringMode.Reporting, RequestedParameters = new MonitoringParameters { ClientHandle = ++handleCounter, SamplingInterval = -1, Filter = default, DiscardOldest = true, QueueSize = queueSize } }, //add event item CreateEventMonitoredItem(queueSize, ref handleCounter) ]; CreateMonitoredItemsResponse createMonitoredItemsResponse = await services.CreateMonitoredItemsAsync( requestHeader, id, TimestampsToReturn.Neither, itemsToCreate).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(createMonitoredItemsResponse.ResponseHeader, createMonitoredItemsResponse.Results, itemsToCreate); ServerFixtureUtils.ValidateDiagnosticInfos( createMonitoredItemsResponse.DiagnosticInfos, itemsToCreate, createMonitoredItemsResponse.ResponseHeader.StringTable, services.Logger); // modify subscription ModifySubscriptionResponse modifySubscriptionResponse = await services.ModifySubscriptionAsync( requestHeader, id, publishingInterval, lifetimeCount, maxKeepAliveCount, maxNotificationPerPublish, priority).ConfigureAwait(false); Assert.That(modifySubscriptionResponse.RevisedPublishingInterval, Is.EqualTo(publishingInterval)); Assert.That(modifySubscriptionResponse.RevisedLifetimeCount, Is.EqualTo(lifetimeCount)); Assert.That(modifySubscriptionResponse.RevisedMaxKeepAliveCount, Is.EqualTo(maxKeepAliveCount)); ServerFixtureUtils.ValidateResponse(modifySubscriptionResponse.ResponseHeader); // modify monitored item, just timestamps to return ArrayOf itemsToModify = createMonitoredItemsResponse.Results .ConvertAll(itemCreated => new MonitoredItemModifyRequest { MonitoredItemId = itemCreated.MonitoredItemId }); ModifyMonitoredItemsResponse modifyMonitoredItemsResponse = await services.ModifyMonitoredItemsAsync( requestHeader, id, TimestampsToReturn.Both, itemsToModify).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(modifyMonitoredItemsResponse.ResponseHeader, modifyMonitoredItemsResponse.Results, itemsToModify); ServerFixtureUtils.ValidateDiagnosticInfos( modifyMonitoredItemsResponse.DiagnosticInfos, itemsToModify, modifyMonitoredItemsResponse.ResponseHeader.StringTable, services.Logger); // publish request ArrayOf acknowledgements = default; PublishResponse publishResponse = await services.PublishAsync( requestHeader, acknowledgements).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(publishResponse.ResponseHeader, publishResponse.Results, acknowledgements); ServerFixtureUtils.ValidateDiagnosticInfos( publishResponse.DiagnosticInfos, acknowledgements, publishResponse.ResponseHeader.StringTable, services.Logger); Assert.That(publishResponse.SubscriptionId, Is.EqualTo(id)); Assert.That(publishResponse.AvailableSequenceNumbers.Count, Is.Zero); // enable publishing enabled = true; ArrayOf subscriptions = [id]; SetPublishingModeResponse setPublishingModeResponse = await services.SetPublishingModeAsync( requestHeader, enabled, subscriptions).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(setPublishingModeResponse.ResponseHeader, setPublishingModeResponse.Results, subscriptions); ServerFixtureUtils.ValidateDiagnosticInfos( setPublishingModeResponse.DiagnosticInfos, subscriptions, setPublishingModeResponse.ResponseHeader.StringTable, services.Logger); // wait some time to fill queue int loopCounter = (int)queueSize; Thread.Sleep(loopCounter * 1000); acknowledgements = []; do { // get publish responses publishResponse = await services.PublishAsync( requestHeader, acknowledgements).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(publishResponse.ResponseHeader, publishResponse.Results, acknowledgements); ServerFixtureUtils.ValidateDiagnosticInfos( publishResponse.DiagnosticInfos, acknowledgements, publishResponse.ResponseHeader.StringTable, services.Logger); Assert.That(publishResponse.SubscriptionId, Is.EqualTo(id)); if (publishResponse.NotificationMessage.NotificationData.Count == 0) { TestContext.Out.WriteLine("No notifications received in publish"); } else { DataChangeNotification dataChangeNotification = (publishResponse.NotificationMessage.NotificationData[0] .TryGetValue(out DataChangeNotification? d) ? d : default)!; EventNotificationList eventNotification = (publishResponse.NotificationMessage.NotificationData[0] .TryGetValue(out EventNotificationList? e) ? e : default)!; TestContext.Out.WriteLine( "Notification: {0} {1}", publishResponse.NotificationMessage.SequenceNumber, publishResponse.NotificationMessage.PublishTime); } acknowledgements = [ new SubscriptionAcknowledgement { SubscriptionId = id, SequenceNumber = publishResponse.NotificationMessage.SequenceNumber } ]; } while (acknowledgements.Count > 0 && --loopCounter > 0); // republish RepublishResponse republishResponse = await services.RepublishAsync( requestHeader, publishResponse.SubscriptionId, publishResponse.NotificationMessage.SequenceNumber).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(republishResponse.ResponseHeader); // disable publishing enabled = false; setPublishingModeResponse = await services.SetPublishingModeAsync( requestHeader, enabled, subscriptions).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(setPublishingModeResponse.ResponseHeader, setPublishingModeResponse.Results, subscriptions); ServerFixtureUtils.ValidateDiagnosticInfos( setPublishingModeResponse.DiagnosticInfos, subscriptions, setPublishingModeResponse.ResponseHeader.StringTable, services.Logger); // disable monitoring ArrayOf monitoredItemIds = createMonitoredItemsResponse.Results.ConvertAll(r => r.MonitoredItemId); SetMonitoringModeResponse setMonitoringModeResponse = await services.SetMonitoringModeAsync( requestHeader, id, MonitoringMode.Disabled, monitoredItemIds).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(setMonitoringModeResponse.ResponseHeader, setMonitoringModeResponse.Results, monitoredItemIds); ServerFixtureUtils.ValidateDiagnosticInfos( setMonitoringModeResponse.DiagnosticInfos, monitoredItemIds, setMonitoringModeResponse.ResponseHeader.StringTable, services.Logger); // delete subscription DeleteSubscriptionsResponse deleteSubscriptionsResponse = await services.DeleteSubscriptionsAsync( requestHeader, subscriptions).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(deleteSubscriptionsResponse.ResponseHeader, deleteSubscriptionsResponse.Results, subscriptions); ServerFixtureUtils.ValidateDiagnosticInfos( deleteSubscriptionsResponse.DiagnosticInfos, subscriptions, deleteSubscriptionsResponse.ResponseHeader.StringTable, services.Logger); } /// /// Worker method to test TransferSubscriptions of a server. /// public static async Task> CreateSubscriptionForTransferAsync( IServerTestServices services, RequestHeader requestHeader, NodeId[] testNodes, uint queueSize = DefaultMonitoredItemsQueueSize, int samplingInterval = DefaultMonitoredItemsSamplingInterval) { // start time requestHeader.Timestamp = DateTime.UtcNow; uint subscriptionId = await CreateSubscriptionAsync(services, requestHeader).ConfigureAwait(false); uint clientHandle = 1; foreach (NodeId testNode in testNodes) { await CreateMonitoredItemAsync( services, requestHeader, subscriptionId, testNode, clientHandle++, queueSize, samplingInterval).ConfigureAwait(false); } ArrayOf subscriptionIds = [subscriptionId]; // enable publishing SetPublishingModeResponse setPublishingModeResponse = await services.SetPublishingModeAsync( requestHeader, true, subscriptionIds).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(setPublishingModeResponse.ResponseHeader, setPublishingModeResponse.Results, subscriptionIds); ServerFixtureUtils.ValidateDiagnosticInfos( setPublishingModeResponse.DiagnosticInfos, subscriptionIds, setPublishingModeResponse.ResponseHeader.StringTable, services.Logger); // wait some time to settle Thread.Sleep(1000); // publish request (use invalid sequence number for status) ArrayOf acknowledgements = [ new SubscriptionAcknowledgement { SubscriptionId = subscriptionId, SequenceNumber = 123 } ]; PublishResponse publishResponse = await services.PublishAsync( requestHeader, acknowledgements).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(publishResponse.ResponseHeader, publishResponse.Results, acknowledgements); ServerFixtureUtils.ValidateDiagnosticInfos( publishResponse.DiagnosticInfos, acknowledgements, publishResponse.ResponseHeader.StringTable, services.Logger); Assert.That(publishResponse.SubscriptionId, Is.EqualTo(subscriptionId)); // static node, do not acknowledge Assert.That(publishResponse.AvailableSequenceNumbers.Count, Is.EqualTo(1)); return subscriptionIds; } /// /// Worker method to test Transfer of subscriptions to new session. /// public static async Task TransferSubscriptionTestAsync( IServerTestServices services, RequestHeader requestHeader, ArrayOf subscriptionIds, bool sendInitialData, bool expectAccessDenied) { Assert.That(subscriptionIds.Count, Is.EqualTo(1)); requestHeader.Timestamp = DateTime.UtcNow; TransferSubscriptionsResponse transferResponse = await services.TransferSubscriptionsAsync( requestHeader, subscriptionIds, sendInitialData).ConfigureAwait(false); Assert.That(transferResponse.ResponseHeader.ServiceResult, Is.EqualTo(StatusCodes.Good)); Assert.That(transferResponse.Results.Count, Is.EqualTo(subscriptionIds.Count)); ServerFixtureUtils.ValidateResponse(transferResponse.ResponseHeader, transferResponse.Results, subscriptionIds); ServerFixtureUtils.ValidateDiagnosticInfos( transferResponse.DiagnosticInfos, subscriptionIds, transferResponse.ResponseHeader.StringTable, services.Logger); foreach (TransferResult transferResult in transferResponse.Results) { TestContext.Out.WriteLine("TransferResult: {0}", transferResult.StatusCode); if (expectAccessDenied) { Assert.That( transferResult.StatusCode, Is.EqualTo(StatusCodes.BadUserAccessDenied)); } else { Assert.That(StatusCode.IsGood(transferResult.StatusCode), Is.True); Assert.That(transferResult.AvailableSequenceNumbers.Count, Is.EqualTo(1)); } } if (expectAccessDenied) { return; } requestHeader.Timestamp = DateTime.UtcNow; ArrayOf acknowledgements = default; PublishResponse publishResponse = await services.PublishAsync( requestHeader, acknowledgements).ConfigureAwait(false); Assert.That(publishResponse.ResponseHeader.ServiceResult, Is.EqualTo(StatusCodes.Good)); ServerFixtureUtils.ValidateResponse(publishResponse.ResponseHeader); ServerFixtureUtils.ValidateDiagnosticInfos( publishResponse.DiagnosticInfos, acknowledgements, publishResponse.ResponseHeader.StringTable, services.Logger); Assert.That(publishResponse.SubscriptionId, Is.EqualTo(subscriptionIds[0])); Assert.That(publishResponse.NotificationMessage.NotificationData.Count, Is.EqualTo(sendInitialData ? 1 : 0)); if (sendInitialData) { ExtensionObject items = publishResponse.NotificationMessage.NotificationData[0]; Assert.That(items.TryGetValue(out DataChangeNotification? dataChangeNotification), Is.True); ArrayOf monitoredItemsCollection = dataChangeNotification!.MonitoredItems; Assert.That(monitoredItemsCollection.IsEmpty, Is.False); } //Assert.AreEqual(0, availableSequenceNumbers.Count); requestHeader.Timestamp = DateTime.UtcNow; DeleteSubscriptionsResponse deleteResponse = await services.DeleteSubscriptionsAsync(requestHeader, subscriptionIds).ConfigureAwait(false); Assert.That(deleteResponse.ResponseHeader.ServiceResult, Is.EqualTo(StatusCodes.Good)); } /// /// Worker method to verify the SubscriptionTransferred message of a server. /// public static async Task VerifySubscriptionTransferredAsync( IServerTestServices services, RequestHeader requestHeader, ArrayOf subscriptionIds, bool deleteSubscriptions) { // start time requestHeader.Timestamp = DateTime.UtcNow; // wait some time to settle Thread.Sleep(100); // publish request ArrayOf acknowledgements = default; PublishResponse publishResponse = await services.PublishAsync( requestHeader, acknowledgements).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(publishResponse.ResponseHeader); ServerFixtureUtils.ValidateDiagnosticInfos( publishResponse.DiagnosticInfos, acknowledgements, publishResponse.ResponseHeader.StringTable, services.Logger); Assert.That(publishResponse.MoreNotifications, Is.False); Assert.That(subscriptionIds.ToArray(), Does.Contain(publishResponse.SubscriptionId)); Assert.That(publishResponse.NotificationMessage.NotificationData.Count, Is.EqualTo(1)); string statusMessage = publishResponse.NotificationMessage.NotificationData[0].ToString(); // Should contain GoodSubscriptionTransferred status code Assert.That(statusMessage, Does.StartWith("StatusChangeNotification")); Assert.That(statusMessage, Does.Contain("Status=GoodSubscriptionTransferred")); // static node, do not acknowledge Assert.That(publishResponse.AvailableSequenceNumbers.Count, Is.Zero); if (deleteSubscriptions) { DeleteSubscriptionsResponse deleteResponse = await services.DeleteSubscriptionsAsync( requestHeader, subscriptionIds).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(deleteResponse.ResponseHeader, deleteResponse.Results, subscriptionIds); ServerFixtureUtils.ValidateDiagnosticInfos( deleteResponse.DiagnosticInfos, subscriptionIds, deleteResponse.ResponseHeader.StringTable, services.Logger); } } private static async Task CreateSubscriptionAsync( IServerTestServices services, RequestHeader requestHeader) { // start time requestHeader.Timestamp = DateTime.UtcNow; // create subscription const double publishingInterval = 1000.0; const uint lifetimeCount = 60; const uint maxKeepAliveCount = 2; const uint maxNotificationPerPublish = 0; const byte priority = 128; const bool enabled = false; CreateSubscriptionResponse response = await services.CreateSubscriptionAsync( requestHeader, publishingInterval, lifetimeCount, maxKeepAliveCount, maxNotificationPerPublish, enabled, priority).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(response.ResponseHeader); return response.SubscriptionId; } private static async Task CreateMonitoredItemAsync( IServerTestServices services, RequestHeader requestHeader, uint subscriptionId, NodeId nodeId, uint clientHandle, uint queueSize, int samplingInterval) { ArrayOf itemsToCreate = [ // add item new MonitoredItemCreateRequest { ItemToMonitor = new ReadValueId { AttributeId = Attributes.Value, NodeId = nodeId }, MonitoringMode = MonitoringMode.Reporting, RequestedParameters = new MonitoringParameters { ClientHandle = clientHandle, SamplingInterval = samplingInterval, Filter = default, DiscardOldest = true, QueueSize = queueSize } } ]; CreateMonitoredItemsResponse response = await services.CreateMonitoredItemsAsync( requestHeader, subscriptionId, TimestampsToReturn.Neither, itemsToCreate).ConfigureAwait(false); ServerFixtureUtils.ValidateResponse(response.ResponseHeader, response.Results, itemsToCreate); ServerFixtureUtils.ValidateDiagnosticInfos( response.DiagnosticInfos, itemsToCreate, response.ResponseHeader.StringTable, services.Logger); } private static MonitoredItemCreateRequest CreateEventMonitoredItem( uint queueSize, ref uint handleCounter) { var whereClause = new ContentFilter(); whereClause.Push( FilterOperator.Equals, [ Variant.FromStructure(new SimpleAttributeOperand { AttributeId = Attributes.Value, TypeDefinitionId = ObjectTypeIds.BaseEventType, BrowsePath = [.. new QualifiedName[] { QualifiedName.From("EventType") }] }), Variant.FromStructure(new LiteralOperand { Value = Variant.From(ObjectTypeIds.BaseEventType) }) ]); return new MonitoredItemCreateRequest { ItemToMonitor = new ReadValueId { AttributeId = Attributes.EventNotifier, NodeId = ObjectIds.Server }, MonitoringMode = MonitoringMode.Reporting, RequestedParameters = new MonitoringParameters { ClientHandle = ++handleCounter, SamplingInterval = -1, Filter = new ExtensionObject( new EventFilter { SelectClauses = [ .. new SimpleAttributeOperand[] { new() { AttributeId = Attributes.Value, TypeDefinitionId = ObjectTypeIds.BaseEventType, BrowsePath = [.. new QualifiedName[] { QualifiedName.From(BrowseNames.Message) }] } } ], WhereClause = whereClause } ), DiscardOldest = true, QueueSize = queueSize } }; } } }