PubSub server: organize PublishedDataSets under PublishSubscribe.PublishedDataSets (#4793)

# Description

PubSubNodeManager looked up the parent of the PublishedDataSet and
DataSetFolder objects it materializes by NodeId i=14478. That is
DataSetFolderType's <DataSetFolderName> placeholder, an
InstanceDeclaration
of the type, not the PublishSubscribe.PublishedDataSets object
(i=17371).
Every PublishedDataSet therefore became a child of the type definition
and
the standard PublishedDataSets folder stayed empty.

Use ObjectIds.PublishSubscribe_PublishedDataSets as the parent. The test
harnesses now expose the real folder and the placeholder, and a new test
asserts that a PublishedDataSet ends up under the folder and that the
placeholder gains no children.

## Related Issues

None.

## Checklist

- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [x] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [x] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Marc Schier <marcschier@users.noreply.github.com>
This commit is contained in:
Wolfgang Colsman
2026-10-15 08:27:52 -04:00
committed by GitHub
parent 95850b7e55
commit 12a76ade55
7 changed files with 4016 additions and 3335 deletions
+5 -1
View File
@@ -1440,6 +1440,8 @@ What the server side adds:
`PubSubKeyPushTargetFolder`.
- One Object per `PubSubConnection`, `WriterGroup`, `ReaderGroup`,
`DataSetWriter`, `DataSetReader`, `PublishedDataSet`, and DataSet folder.
PublishedDataSets and DataSet folders are organized below the standard
`PublishSubscribe.PublishedDataSets` folder (`i=17371`).
- Per-instance `Status` / `State` and `ConfigurationVersion` Variables so a
client can observe the same runtime state the scheduler uses.
2. Method bindings ([§9.1.5](https://reference.opcfoundation.org/specs/OPC-10000-14/v1.05.06/9.1.5)):
@@ -1447,7 +1449,9 @@ What the server side adds:
`AddReaderGroup`, `AddDataSetWriter`, `AddDataSetReader`, `Remove*`,
`Enable`, and `Disable`, plus `AddPublishedDataItems`,
`AddPublishedEvents`, `AddPublishedDataItemsTemplate`, `AddVariables`,
`RemoveVariables`, `AddDataSetFolder`, and `RemoveDataSetFolder`.
`RemoveVariables`, `AddDataSetFolder`, and `RemoveDataSetFolder`. The
folder methods are bound on the `PublishSubscribe.PublishedDataSets`
instance and on every DataSet folder created through `AddDataSetFolder`.
3. `PubSubConfigurationType` File import/export: clients can open/read the
current `PubSubConfigurationDataType` file or write a replacement file; the
server applies it through `ReplaceConfigurationAsync` and returns per-item
File diff suppressed because it is too large Load Diff
+163 -16
View File
@@ -87,13 +87,7 @@ namespace Opc.Ua.PubSub.Server
private const uint RemoveSecurityGroupNodeId = 15447;
private const uint AddPushTargetNodeId = 25441;
private const uint RemovePushTargetNodeId = 25444;
private const uint AddPublishedDataItemsNodeId = 14479;
private const uint AddPublishedEventsNodeId = 14482;
private const uint AddPublishedDataItemsTemplateNodeId = 16842;
private const uint RemovePublishedDataSetNodeId = 14485;
private const uint AddDataSetFolderNodeId = 16884;
private const uint RemoveDataSetFolderNodeId = 16923;
private static readonly NodeId s_publishedDataSetsNodeId = new(14478u);
private static readonly NodeId s_publishedDataSetsNodeId = ObjectIds.PublishSubscribe_PublishedDataSets;
private static readonly NodeId s_securityGroupsNodeId = new(15443u);
private static readonly NodeId s_keyPushTargetsNodeId = new(25440u);
@@ -312,6 +306,118 @@ namespace Opc.Ua.PubSub.Server
BindStandardMethod(file.ReserveIds, OnReservePubSubConfigurationIds);
BindStandardMethod(file.CloseAndUpdate, OnCloseAndUpdatePubSubConfigurationFile);
diagnosticsNodeManager.AddNode(file);
// The DataSetFolderType methods of PublishSubscribe.PublishedDataSets
// are Optional, so the standard address space does not contain them.
if (publishSubscribe.PublishedDataSets is { } publishedDataSets)
{
foreach (MethodState method in AddPublishedDataSetsMethods(SystemContext, publishedDataSets))
{
diagnosticsNodeManager.AddNode(method);
}
}
}
/// <summary>
/// Materializes the implemented DataSetFolderType methods on the standard
/// <c>PublishSubscribe.PublishedDataSets</c> instance with their well-known
/// instance NodeIds. The generated <c>Add*</c> extensions create them with the
/// NodeIds of the DataSetFolderType declarations, which the address space
/// already holds.
/// </summary>
internal static ArrayOf<MethodState> AddPublishedDataSetsMethods(
ISystemContext context,
DataSetFolderState publishedDataSets)
{
publishedDataSets.AddAddPublishedDataItems(context);
publishedDataSets.AddAddPublishedEvents(context);
publishedDataSets.AddAddPublishedDataItemsTemplate(context);
publishedDataSets.AddRemovePublishedDataSet(context);
publishedDataSets.AddAddDataSetFolder(context);
publishedDataSets.AddRemoveDataSetFolder(context);
return
[
AssignInstanceIds(
publishedDataSets.AddPublishedDataItems,
nameof(publishedDataSets.AddPublishedDataItems),
PublishedDataSetsIds.AddPublishedDataItems,
PublishedDataSetsIds.AddPublishedDataItemsInputArguments,
PublishedDataSetsIds.AddPublishedDataItemsOutputArguments),
AssignInstanceIds(
publishedDataSets.AddPublishedEvents,
nameof(publishedDataSets.AddPublishedEvents),
PublishedDataSetsIds.AddPublishedEvents,
PublishedDataSetsIds.AddPublishedEventsInputArguments,
PublishedDataSetsIds.AddPublishedEventsOutputArguments),
AssignInstanceIds(
publishedDataSets.AddPublishedDataItemsTemplate,
nameof(publishedDataSets.AddPublishedDataItemsTemplate),
PublishedDataSetsIds.AddPublishedDataItemsTemplate,
PublishedDataSetsIds.AddPublishedDataItemsTemplateInputArguments,
PublishedDataSetsIds.AddPublishedDataItemsTemplateOutputArguments),
AssignInstanceIds(
publishedDataSets.RemovePublishedDataSet,
nameof(publishedDataSets.RemovePublishedDataSet),
PublishedDataSetsIds.RemovePublishedDataSet,
PublishedDataSetsIds.RemovePublishedDataSetInputArguments,
outputArgumentsId: null),
AssignInstanceIds(
publishedDataSets.AddDataSetFolder,
nameof(publishedDataSets.AddDataSetFolder),
PublishedDataSetsIds.AddDataSetFolder,
PublishedDataSetsIds.AddDataSetFolderInputArguments,
PublishedDataSetsIds.AddDataSetFolderOutputArguments),
AssignInstanceIds(
publishedDataSets.RemoveDataSetFolder,
nameof(publishedDataSets.RemoveDataSetFolder),
PublishedDataSetsIds.RemoveDataSetFolder,
PublishedDataSetsIds.RemoveDataSetFolderInputArguments,
outputArgumentsId: null)
];
}
/// <summary>
/// Gives a materialized method and its argument variables their instance
/// NodeIds. A method without output arguments passes a null
/// <paramref name="outputArgumentsId"/>; an argument variable the generated
/// method does not match is a configuration error.
/// </summary>
internal static MethodState AssignInstanceIds(
MethodState? method,
string name,
uint methodId,
uint inputArgumentsId,
uint? outputArgumentsId)
{
if (method == null)
{
throw new ServiceResultException(StatusCodes.BadConfigurationError,
$"The generated PublishedDataSets method {name} (i={methodId}) was not created.");
}
if (method.InputArguments == null)
{
throw new ServiceResultException(StatusCodes.BadConfigurationError,
$"The generated PublishedDataSets method {name} (i={methodId}) has no InputArguments.");
}
if (method.OutputArguments != null && outputArgumentsId == null)
{
throw new ServiceResultException(StatusCodes.BadConfigurationError,
$"The generated PublishedDataSets method {name} (i={methodId}) has OutputArguments " +
"that its standard declaration does not have.");
}
if (method.OutputArguments == null && outputArgumentsId != null)
{
throw new ServiceResultException(StatusCodes.BadConfigurationError,
$"The generated PublishedDataSets method {name} (i={methodId}) has no OutputArguments.");
}
method.NodeId = new NodeId(methodId);
method.InputArguments.NodeId = new NodeId(inputArgumentsId);
if (method.OutputArguments != null && outputArgumentsId is uint outputId)
{
method.OutputArguments.NodeId = new NodeId(outputId);
}
return method;
}
private void BindMethods(IDiagnosticsNodeManager diagnosticsNodeManager)
@@ -373,28 +479,44 @@ namespace Opc.Ua.PubSub.Server
m_methodsBound = enable is not null || disable is not null;
}
/// <summary>
/// Binds the methods of the standard <c>PublishSubscribe.PublishedDataSets</c>
/// instance, the folder that holds the PublishedDataSets and DataSetFolders.
/// </summary>
private void BindPublishedDataSetFolderMethods(IDiagnosticsNodeManager diagnosticsNodeManager)
{
BindStandardMethod(
diagnosticsNodeManager, AddPublishedDataItemsNodeId, MethodHandlers.OnAddPublishedDataItems);
BindStandardMethod(diagnosticsNodeManager, AddPublishedEventsNodeId, MethodHandlers.OnAddPublishedEvents);
diagnosticsNodeManager,
new NodeId(PublishedDataSetsIds.AddPublishedDataItems),
MethodHandlers.OnAddPublishedDataItems);
BindStandardMethod(
diagnosticsNodeManager,
AddPublishedDataItemsTemplateNodeId,
new NodeId(PublishedDataSetsIds.AddPublishedEvents),
MethodHandlers.OnAddPublishedEvents);
BindStandardMethod(
diagnosticsNodeManager,
new NodeId(PublishedDataSetsIds.AddPublishedDataItemsTemplate),
MethodHandlers.OnAddPublishedDataItemsTemplate);
BindStandardMethod(
diagnosticsNodeManager, RemovePublishedDataSetNodeId, MethodHandlers.OnRemovePublishedDataSet);
BindStandardMethod(diagnosticsNodeManager, AddDataSetFolderNodeId, OnAddDataSetFolder);
BindStandardMethod(diagnosticsNodeManager, RemoveDataSetFolderNodeId, OnRemoveDataSetFolder);
diagnosticsNodeManager,
new NodeId(PublishedDataSetsIds.RemovePublishedDataSet),
MethodHandlers.OnRemovePublishedDataSet);
BindStandardMethod(
diagnosticsNodeManager,
new NodeId(PublishedDataSetsIds.AddDataSetFolder),
OnAddDataSetFolder);
BindStandardMethod(
diagnosticsNodeManager,
new NodeId(PublishedDataSetsIds.RemoveDataSetFolder),
OnRemoveDataSetFolder);
}
private static void BindStandardMethod(
IDiagnosticsNodeManager diagnosticsNodeManager,
uint nodeId,
NodeId nodeId,
GenericMethodCalledEventHandler handler)
{
MethodState? method = diagnosticsNodeManager
.FindPredefinedNode<MethodState>(new NodeId(nodeId));
MethodState? method = diagnosticsNodeManager.FindPredefinedNode<MethodState>(nodeId);
BindStandardMethod(method, handler);
}
@@ -1907,6 +2029,31 @@ namespace Opc.Ua.PubSub.Server
}
}
/// <summary>
/// The NodeIds of the methods of the <c>PublishSubscribe.PublishedDataSets</c>
/// instance (i=17371) and of their argument variables, not those of the
/// DataSetFolderType declarations.
/// </summary>
private static class PublishedDataSetsIds
{
public const uint AddPublishedDataItems = 17372;
public const uint AddPublishedDataItemsInputArguments = 17373;
public const uint AddPublishedDataItemsOutputArguments = 17374;
public const uint AddPublishedEvents = 17375;
public const uint AddPublishedEventsInputArguments = 17376;
public const uint AddPublishedEventsOutputArguments = 17377;
public const uint AddPublishedDataItemsTemplate = 17378;
public const uint AddPublishedDataItemsTemplateInputArguments = 17379;
public const uint AddPublishedDataItemsTemplateOutputArguments = 17380;
public const uint RemovePublishedDataSet = 17384;
public const uint RemovePublishedDataSetInputArguments = 17385;
public const uint AddDataSetFolder = 17398;
public const uint AddDataSetFolderInputArguments = 17401;
public const uint AddDataSetFolderOutputArguments = 17402;
public const uint RemoveDataSetFolder = 17403;
public const uint RemoveDataSetFolderInputArguments = 17404;
}
private sealed class PubSubKeyPushTargetRegistration
{
public PubSubKeyPushTargetRegistration(
@@ -1,464 +1,470 @@
/* ========================================================================
* Copyright (c) 2005-2026 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.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.PubSub.Application;
using Opc.Ua.PubSub.Tests;
using Opc.Ua.PubSub.Transports;
using Opc.Ua.Tests;
namespace Opc.Ua.PubSub.Server.Tests
{
[TestFixture]
[TestSpec("9.1.3.4", Summary = "AddConnection handler")]
[TestSpec("9.1.3.5", Summary = "RemoveConnection handler")]
[TestSpec("9.1.6", Summary = "Configuration methods")]
[TestSpec("9.1.4.3", Summary = "PublishedDataItemsType variable methods")]
[TestSpec("9.1.4.5", Summary = "DataSetFolderType dataset methods")]
public class PubSubMethodHandlersMutationTests
{
[Test]
[TestSpec("9.1.3.4")]
public void OnAddConnection_ValidInput_ReturnsGoodAndNodeId()
{
PubSubMethodHandlers handlers = CreateHandlers();
var connCfg = new PubSubConnectionDataType
{
Name = "handler-conn",
TransportProfileUri =
"http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp",
Address = new ExtensionObject(
new NetworkAddressUrlDataType { Url = "opc.udp://224.0.0.22:4840" })
};
ArrayOf<Variant> inputs = BuildArray(Variant.From(new ExtensionObject(connCfg)));
var outputs = new List<Variant>();
ServiceResult result = handlers.OnAddConnection(
BuildContext(), method: null!, inputArguments: inputs, outputArguments: outputs);
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(1));
Assert.That(outputs[0].TryGetValue(out NodeId id), Is.True);
Assert.That(id.IsNull, Is.False);
});
}
[Test]
[TestSpec("9.1.3.5")]
public void OnRemoveConnection_ValidInput_ReturnsGood()
{
PubSubMethodHandlers handlers = CreateHandlers();
var connCfg = new PubSubConnectionDataType
{
Name = "to-remove",
TransportProfileUri =
"http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp",
Address = new ExtensionObject(
new NetworkAddressUrlDataType { Url = "opc.udp://224.0.0.22:4840" })
};
ArrayOf<Variant> addInputs = BuildArray(Variant.From(new ExtensionObject(connCfg)));
var addOutputs = new List<Variant>();
handlers.OnAddConnection(
BuildContext(), method: null!, inputArguments: addInputs, outputArguments: addOutputs);
Assert.That(addOutputs[0].TryGetValue(out NodeId connId), Is.True);
ArrayOf<Variant> removeInputs = BuildArray(Variant.From(connId));
var removeOutputs = new List<Variant>();
ServiceResult result = handlers.OnRemoveConnection(
BuildContext(), method: null!, inputArguments: removeInputs,
outputArguments: removeOutputs);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
}
[Test]
[TestSpec("9.1.6")]
public void OnGetConfiguration_ReturnsGood()
{
PubSubMethodHandlers handlers = CreateHandlers();
var outputs = new List<Variant>();
ServiceResult result = handlers.OnGetConfiguration(
BuildContext(), method: null!, inputArguments: default, outputArguments: outputs);
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(1));
});
}
[Test]
[TestSpec("9.1.6")]
public void OnSetConfiguration_ValidInput_ReturnsGood()
{
PubSubMethodHandlers handlers = CreateHandlers();
var cfg = new PubSubConfigurationDataType
{
Connections = [],
PublishedDataSets = []
};
ArrayOf<Variant> inputs = BuildArray(Variant.From(new ExtensionObject(cfg)));
var outputs = new List<Variant>();
ServiceResult result = handlers.OnSetConfiguration(
BuildContext(), method: null!, inputArguments: inputs, outputArguments: outputs);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
}
[Test]
[TestSpec("9.1.4.5")]
public void OnAddPublishedDataItems_RegistersPublishedDataItemsDataSet()
{
IPubSubApplication application = CreateApplication();
PubSubMethodHandlers handlers = CreateHandlers(application);
var outputs = new List<Variant>();
var variable = new PublishedVariableDataType
{
PublishedVariable = new NodeId(Variables.Server_ServerStatus_CurrentTime),
AttributeId = Attributes.Value
};
ServiceResult result = handlers.OnAddPublishedDataItems(
BuildContext(),
method: null!,
inputArguments: BuildArray(
Variant.From("items"),
Variant.From(new ArrayOf<string>(s_currentTimeAlias)),
Variant.From(Array.Empty<DataSetFieldFlags>()),
Variant.FromStructure(new ArrayOf<PublishedVariableDataType>(
new[] { variable }))),
outputArguments: outputs);
PubSubConfigurationDataType configuration = application.GetConfiguration();
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(3));
Assert.That(configuration.PublishedDataSets, Has.Count.EqualTo(1));
Assert.That(configuration.PublishedDataSets[0].DataSetSource.TryGetValue(
out PublishedDataItemsDataType? _), Is.True);
});
}
[Test]
[TestSpec("9.1.4.5")]
public void OnAddPublishedDataItemsTemplate_UsesProvidedMetaData()
{
IPubSubApplication application = CreateApplication();
PubSubMethodHandlers handlers = CreateHandlers(application);
var outputs = new List<Variant>();
var variable = new PublishedVariableDataType
{
PublishedVariable = new NodeId(Variables.Server_ServerStatus_CurrentTime),
AttributeId = Attributes.Value
};
var metaData = new DataSetMetaDataType
{
Name = "template",
Fields = new ArrayOf<FieldMetaData>(new[]
{
new FieldMetaData
{
Name = "FromTemplate",
DataType = DataTypeIds.DateTime,
ValueRank = ValueRanks.Scalar
}
})
};
ServiceResult result = handlers.OnAddPublishedDataItemsTemplate(
BuildContext(),
method: null!,
inputArguments: BuildArray(
Variant.From("template"),
Variant.From(new ExtensionObject(metaData)),
Variant.FromStructure(new ArrayOf<PublishedVariableDataType>(
new[] { variable }))),
outputArguments: outputs);
PubSubConfigurationDataType configuration = application.GetConfiguration();
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(2));
Assert.That(configuration.PublishedDataSets[0].Name, Is.EqualTo("template"));
Assert.That(configuration.PublishedDataSets[0].DataSetSource.TryGetValue(
out PublishedDataItemsDataType? _), Is.True);
});
}
[Test]
[TestSpec("9.1.4.5")]
public void OnAddPublishedEvents_RegistersPublishedEventsDataSet()
{
IPubSubApplication application = CreateApplication();
PubSubMethodHandlers handlers = CreateHandlers(application);
var outputs = new List<Variant>();
var selectedField = new SimpleAttributeOperand
{
TypeDefinitionId = ObjectTypeIds.BaseEventType,
BrowsePath = new ArrayOf<QualifiedName>(new[] { new QualifiedName(BrowseNames.EventId) }),
AttributeId = Attributes.Value
};
ServiceResult result = handlers.OnAddPublishedEvents(
BuildContext(),
method: NewPublishedDataItemsMethod("items", "AddVariables"),
inputArguments: BuildArray(
Variant.From("events"),
Variant.From(ObjectIds.Server),
Variant.From(new ArrayOf<string>(s_eventIdAlias)),
Variant.From(Array.Empty<DataSetFieldFlags>()),
Variant.FromStructure(new ArrayOf<SimpleAttributeOperand>(
new[] { selectedField })),
Variant.From(new ExtensionObject(new ContentFilter()))),
outputArguments: outputs);
PubSubConfigurationDataType configuration = application.GetConfiguration();
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(3));
Assert.That(configuration.PublishedDataSets[0].DataSetSource.TryGetValue(
out PublishedEventsDataType? _), Is.True);
});
}
[Test]
[TestSpec("9.1.4.3")]
public void OnAddVariablesAndRemoveVariables_MutateFieldsAndBumpConfigurationVersion()
{
IPubSubApplication application = CreateApplication();
PubSubMethodHandlers handlers = CreateHandlers(application);
var addDataSetOutputs = new List<Variant>();
var variable = new PublishedVariableDataType
{
PublishedVariable = new NodeId(Variables.Server_ServerStatus_CurrentTime),
AttributeId = Attributes.Value
};
handlers.OnAddPublishedDataItems(
BuildContext(),
method: NewPublishedDataItemsMethod("items", "RemoveVariables"),
inputArguments: BuildArray(
Variant.From("items"),
Variant.From(new ArrayOf<string>(s_currentTimeAlias)),
Variant.From(Array.Empty<DataSetFieldFlags>()),
Variant.FromStructure(new ArrayOf<PublishedVariableDataType>(
new[] { variable }))),
outputArguments: addDataSetOutputs);
var addedVariable = new PublishedVariableDataType
{
PublishedVariable = new NodeId(Variables.Server_ServerStatus_StartTime),
AttributeId = Attributes.Value
};
var addVariableOutputs = new List<Variant>();
ServiceResult addResult = handlers.OnAddVariables(
BuildContext(),
method: NewPublishedDataItemsMethod("items", "AddVariables"),
inputArguments: BuildArray(
addDataSetOutputs[1],
Variant.From(new ArrayOf<string>(s_startTimeAlias)),
Variant.From(s_notPromotedField),
Variant.FromStructure(new ArrayOf<PublishedVariableDataType>(
new[] { addedVariable }))),
outputArguments: addVariableOutputs);
Assert.That(StatusCode.IsGood(addResult.StatusCode), Is.True);
Assert.That(addVariableOutputs, Has.Count.EqualTo(2));
var removeVariableOutputs = new List<Variant>();
ServiceResult removeResult = handlers.OnRemoveVariables(
BuildContext(),
method: NewPublishedDataItemsMethod("items", "RemoveVariables"),
inputArguments: BuildArray(addVariableOutputs[0], Variant.From(new ArrayOf<uint>(s_firstVariableIndex))),
outputArguments: removeVariableOutputs);
PubSubConfigurationDataType configuration = application.GetConfiguration();
Assert.That(configuration.PublishedDataSets[0].DataSetSource.TryGetValue(
out PublishedDataItemsDataType? items), Is.True);
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(addResult.StatusCode), Is.True);
Assert.That(StatusCode.IsGood(removeResult.StatusCode), Is.True);
Assert.That(items!.PublishedData, Has.Count.EqualTo(1));
Assert.That(configuration.PublishedDataSets[0].DataSetMetaData.ConfigurationVersion.MinorVersion,
Is.GreaterThan(1u));
});
}
[Test]
[TestSpec("9.1.5")]
public void OnAddDataSetFolder_ReturnsGoodWithNodeId()
{
PubSubMethodHandlers handlers = CreateHandlers();
ArrayOf<Variant> inputs = BuildArray(Variant.From("my-folder"));
var outputs = new List<Variant>();
ServiceResult result = handlers.OnAddDataSetFolder(
BuildContext(), method: null!, inputArguments: inputs, outputArguments: outputs);
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(1));
});
}
private static PubSubMethodHandlers CreateHandlers()
{
return CreateHandlers(CreateApplication());
}
private static PubSubMethodHandlers CreateHandlers(IPubSubApplication app)
{
var options = new PubSubServerOptions
{
ExposeConfigurationMethods = true
};
return new PubSubMethodHandlers(
app, null, options, NUnitTelemetryContext.Create());
}
private static IPubSubApplication CreateApplication()
{
return new PubSubApplicationBuilder(
NUnitTelemetryContext.Create())
.WithApplicationId("handler-mutation-tests")
.UseConfiguration(new PubSubConfigurationDataType
{
Connections = [],
PublishedDataSets = []
})
.UseAllStandardEncoders()
.AddTransportFactory(new StubTransportFactory())
.Build();
}
private static SystemContext BuildContext()
{
return new SystemContext(NUnitTelemetryContext.Create());
}
private static ArrayOf<Variant> BuildArray(params Variant[] values)
{
return new ArrayOf<Variant>(values);
}
private static MethodState NewPublishedDataItemsMethod(string dataSetName, string methodName)
{
var parent = new BaseObjectState(null)
{
NodeId = new NodeId($"pubsub:published-data-set:{dataSetName}", 0),
BrowseName = new QualifiedName(dataSetName)
};
var method = new MethodState(parent)
{
NodeId = new NodeId($"pubsub:published-data-set:{dataSetName}:{methodName}", 0),
BrowseName = new QualifiedName(methodName)
};
parent.AddChild(method);
return method;
}
private static readonly string[] s_currentTimeAlias = ["CurrentTime"];
private static readonly string[] s_eventIdAlias = ["EventId"];
private static readonly string[] s_startTimeAlias = ["StartTime"];
private static readonly bool[] s_notPromotedField = [false];
private static readonly uint[] s_firstVariableIndex = [0u];
private sealed class StubTransportFactory : IPubSubTransportFactory
{
public string TransportProfileUri => Profiles.PubSubUdpUadpTransport;
public IPubSubTransport Create(
PubSubConnectionDataType connection,
ITelemetryContext telemetry,
TimeProvider timeProvider)
{
_ = connection;
_ = telemetry;
_ = timeProvider;
return new StubTransport();
}
}
private sealed class StubTransport : IPubSubTransport
{
private bool m_isConnected;
public string TransportProfileUri => Profiles.PubSubUdpUadpTransport;
public PubSubTransportDirection Direction => PubSubTransportDirection.SendReceive;
public bool IsConnected => m_isConnected;
public event EventHandler<PubSubTransportStateChangedEventArgs>? StateChanged
{
add { }
remove { }
}
public ValueTask OpenAsync(CancellationToken cancellationToken = default)
{
_ = cancellationToken;
m_isConnected = true;
return default;
}
public ValueTask CloseAsync(CancellationToken cancellationToken = default)
{
_ = cancellationToken;
m_isConnected = false;
return default;
}
public ValueTask SendAsync(
ReadOnlyMemory<byte> payload,
string? topic = null,
CancellationToken cancellationToken = default)
{
_ = payload;
_ = topic;
_ = cancellationToken;
return default;
}
public IAsyncEnumerable<PubSubTransportFrame> ReceiveAsync(
CancellationToken cancellationToken = default)
{
_ = cancellationToken;
return TestAsyncEnumerable.Empty<PubSubTransportFrame>();
}
public ValueTask DisposeAsync()
{
m_isConnected = false;
return default;
}
}
}
}
/* ========================================================================
* Copyright (c) 2005-2026 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.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Opc.Ua.PubSub.Application;
using Opc.Ua.PubSub.Tests;
using Opc.Ua.PubSub.Transports;
using Opc.Ua.Tests;
namespace Opc.Ua.PubSub.Server.Tests
{
[TestFixture]
[TestSpec("9.1.3.4", Summary = "AddConnection handler")]
[TestSpec("9.1.3.5", Summary = "RemoveConnection handler")]
[TestSpec("9.1.6", Summary = "Configuration methods")]
[TestSpec("9.1.4.3", Summary = "PublishedDataItemsType variable methods")]
[TestSpec("9.1.4.5", Summary = "DataSetFolderType dataset methods")]
public class PubSubMethodHandlersMutationTests
{
[Test]
[TestSpec("9.1.3.4")]
public void OnAddConnection_ValidInput_ReturnsGoodAndNodeId()
{
PubSubMethodHandlers handlers = CreateHandlers();
var connCfg = new PubSubConnectionDataType
{
Name = "handler-conn",
TransportProfileUri =
"http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp",
Address = new ExtensionObject(
new NetworkAddressUrlDataType { Url = "opc.udp://224.0.0.22:4840" })
};
ArrayOf<Variant> inputs = BuildArray(Variant.From(new ExtensionObject(connCfg)));
var outputs = new List<Variant>();
ServiceResult result = handlers.OnAddConnection(
BuildContext(), method: null!, inputArguments: inputs, outputArguments: outputs);
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(1));
Assert.That(outputs[0].TryGetValue(out NodeId id), Is.True);
Assert.That(id.IsNull, Is.False);
});
}
[Test]
[TestSpec("9.1.3.5")]
public void OnRemoveConnection_ValidInput_ReturnsGood()
{
PubSubMethodHandlers handlers = CreateHandlers();
var connCfg = new PubSubConnectionDataType
{
Name = "to-remove",
TransportProfileUri =
"http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp",
Address = new ExtensionObject(
new NetworkAddressUrlDataType { Url = "opc.udp://224.0.0.22:4840" })
};
ArrayOf<Variant> addInputs = BuildArray(Variant.From(new ExtensionObject(connCfg)));
var addOutputs = new List<Variant>();
handlers.OnAddConnection(
BuildContext(), method: null!, inputArguments: addInputs, outputArguments: addOutputs);
Assert.That(addOutputs[0].TryGetValue(out NodeId connId), Is.True);
ArrayOf<Variant> removeInputs = BuildArray(Variant.From(connId));
var removeOutputs = new List<Variant>();
ServiceResult result = handlers.OnRemoveConnection(
BuildContext(), method: null!, inputArguments: removeInputs,
outputArguments: removeOutputs);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
}
[Test]
[TestSpec("9.1.6")]
public void OnGetConfiguration_ReturnsGood()
{
PubSubMethodHandlers handlers = CreateHandlers();
var outputs = new List<Variant>();
ServiceResult result = handlers.OnGetConfiguration(
BuildContext(), method: null!, inputArguments: default, outputArguments: outputs);
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(1));
});
}
[Test]
[TestSpec("9.1.6")]
public void OnSetConfiguration_ValidInput_ReturnsGood()
{
PubSubMethodHandlers handlers = CreateHandlers();
var cfg = new PubSubConfigurationDataType
{
Connections = [],
PublishedDataSets = []
};
ArrayOf<Variant> inputs = BuildArray(Variant.From(new ExtensionObject(cfg)));
var outputs = new List<Variant>();
ServiceResult result = handlers.OnSetConfiguration(
BuildContext(), method: null!, inputArguments: inputs, outputArguments: outputs);
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
}
[Test]
[TestSpec("9.1.4.5")]
public void OnAddPublishedDataItems_RegistersPublishedDataItemsDataSet()
{
IPubSubApplication application = CreateApplication();
PubSubMethodHandlers handlers = CreateHandlers(application);
var outputs = new List<Variant>();
var variable = new PublishedVariableDataType
{
PublishedVariable = new NodeId(Variables.Server_ServerStatus_CurrentTime),
AttributeId = Attributes.Value
};
ServiceResult result = handlers.OnAddPublishedDataItems(
BuildContext(),
method: null!,
inputArguments: BuildArray(
Variant.From("items"),
Variant.From(new ArrayOf<string>(s_currentTimeAlias)),
Variant.From(Array.Empty<DataSetFieldFlags>()),
Variant.FromStructure(new ArrayOf<PublishedVariableDataType>(
new[] { variable }))),
outputArguments: outputs);
PubSubConfigurationDataType configuration = application.GetConfiguration();
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(3));
Assert.That(configuration.PublishedDataSets, Has.Count.EqualTo(1));
Assert.That(configuration.PublishedDataSets[0].DataSetSource.TryGetValue(
out PublishedDataItemsDataType? _), Is.True);
});
}
[Test]
[TestSpec("9.1.4.5")]
public void OnAddPublishedDataItemsTemplate_UsesProvidedMetaData()
{
IPubSubApplication application = CreateApplication();
PubSubMethodHandlers handlers = CreateHandlers(application);
var outputs = new List<Variant>();
var variable = new PublishedVariableDataType
{
PublishedVariable = new NodeId(Variables.Server_ServerStatus_CurrentTime),
AttributeId = Attributes.Value
};
var metaData = new DataSetMetaDataType
{
Name = "template",
Fields = new ArrayOf<FieldMetaData>(new[]
{
new FieldMetaData
{
Name = "FromTemplate",
DataType = DataTypeIds.DateTime,
ValueRank = ValueRanks.Scalar
}
})
};
ServiceResult result = handlers.OnAddPublishedDataItemsTemplate(
BuildContext(),
method: null!,
inputArguments: BuildArray(
Variant.From("template"),
Variant.From(new ExtensionObject(metaData)),
Variant.FromStructure(new ArrayOf<PublishedVariableDataType>(
new[] { variable }))),
outputArguments: outputs);
PubSubConfigurationDataType configuration = application.GetConfiguration();
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(2));
Assert.That(configuration.PublishedDataSets[0].Name, Is.EqualTo("template"));
Assert.That(configuration.PublishedDataSets[0].DataSetSource.TryGetValue(
out PublishedDataItemsDataType? _), Is.True);
});
}
[Test]
[TestSpec("9.1.4.5")]
public void OnAddPublishedEvents_RegistersPublishedEventsDataSet()
{
IPubSubApplication application = CreateApplication();
PubSubMethodHandlers handlers = CreateHandlers(application);
var outputs = new List<Variant>();
var selectedField = new SimpleAttributeOperand
{
TypeDefinitionId = ObjectTypeIds.BaseEventType,
BrowsePath = new ArrayOf<QualifiedName>(new[] { new QualifiedName(BrowseNames.EventId) }),
AttributeId = Attributes.Value
};
ServiceResult result = handlers.OnAddPublishedEvents(
BuildContext(),
method: NewPublishedDataItemsMethod("items", "AddVariables"),
inputArguments: BuildArray(
Variant.From("events"),
Variant.From(ObjectIds.Server),
Variant.From(new ArrayOf<string>(s_eventIdAlias)),
Variant.From(Array.Empty<DataSetFieldFlags>()),
Variant.FromStructure(new ArrayOf<SimpleAttributeOperand>(
new[] { selectedField })),
Variant.From(new ExtensionObject(new ContentFilter()))),
outputArguments: outputs);
PubSubConfigurationDataType configuration = application.GetConfiguration();
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(2));
Assert.That(outputs[0].TryGetValue(out ExtensionObject configurationVersion), Is.True);
Assert.That(
configurationVersion.TryGetValue(out ConfigurationVersionDataType? _),
Is.True);
Assert.That(outputs[1].TryGetValue(out NodeId dataSetId), Is.True);
Assert.That(dataSetId.IsNull, Is.False);
Assert.That(configuration.PublishedDataSets[0].DataSetSource.TryGetValue(
out PublishedEventsDataType? _), Is.True);
});
}
[Test]
[TestSpec("9.1.4.3")]
public void OnAddVariablesAndRemoveVariables_MutateFieldsAndBumpConfigurationVersion()
{
IPubSubApplication application = CreateApplication();
PubSubMethodHandlers handlers = CreateHandlers(application);
var addDataSetOutputs = new List<Variant>();
var variable = new PublishedVariableDataType
{
PublishedVariable = new NodeId(Variables.Server_ServerStatus_CurrentTime),
AttributeId = Attributes.Value
};
handlers.OnAddPublishedDataItems(
BuildContext(),
method: NewPublishedDataItemsMethod("items", "RemoveVariables"),
inputArguments: BuildArray(
Variant.From("items"),
Variant.From(new ArrayOf<string>(s_currentTimeAlias)),
Variant.From(Array.Empty<DataSetFieldFlags>()),
Variant.FromStructure(new ArrayOf<PublishedVariableDataType>(
new[] { variable }))),
outputArguments: addDataSetOutputs);
var addedVariable = new PublishedVariableDataType
{
PublishedVariable = new NodeId(Variables.Server_ServerStatus_StartTime),
AttributeId = Attributes.Value
};
var addVariableOutputs = new List<Variant>();
ServiceResult addResult = handlers.OnAddVariables(
BuildContext(),
method: NewPublishedDataItemsMethod("items", "AddVariables"),
inputArguments: BuildArray(
addDataSetOutputs[1],
Variant.From(new ArrayOf<string>(s_startTimeAlias)),
Variant.From(s_notPromotedField),
Variant.FromStructure(new ArrayOf<PublishedVariableDataType>(
new[] { addedVariable }))),
outputArguments: addVariableOutputs);
Assert.That(StatusCode.IsGood(addResult.StatusCode), Is.True);
Assert.That(addVariableOutputs, Has.Count.EqualTo(2));
var removeVariableOutputs = new List<Variant>();
ServiceResult removeResult = handlers.OnRemoveVariables(
BuildContext(),
method: NewPublishedDataItemsMethod("items", "RemoveVariables"),
inputArguments: BuildArray(addVariableOutputs[0], Variant.From(new ArrayOf<uint>(s_firstVariableIndex))),
outputArguments: removeVariableOutputs);
PubSubConfigurationDataType configuration = application.GetConfiguration();
Assert.That(configuration.PublishedDataSets[0].DataSetSource.TryGetValue(
out PublishedDataItemsDataType? items), Is.True);
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(addResult.StatusCode), Is.True);
Assert.That(StatusCode.IsGood(removeResult.StatusCode), Is.True);
Assert.That(items!.PublishedData, Has.Count.EqualTo(1));
Assert.That(configuration.PublishedDataSets[0].DataSetMetaData.ConfigurationVersion.MinorVersion,
Is.GreaterThan(1u));
});
}
[Test]
[TestSpec("9.1.5")]
public void OnAddDataSetFolder_ReturnsGoodWithNodeId()
{
PubSubMethodHandlers handlers = CreateHandlers();
ArrayOf<Variant> inputs = BuildArray(Variant.From("my-folder"));
var outputs = new List<Variant>();
ServiceResult result = handlers.OnAddDataSetFolder(
BuildContext(), method: null!, inputArguments: inputs, outputArguments: outputs);
Assert.Multiple(() =>
{
Assert.That(StatusCode.IsGood(result.StatusCode), Is.True);
Assert.That(outputs, Has.Count.EqualTo(1));
});
}
private static PubSubMethodHandlers CreateHandlers()
{
return CreateHandlers(CreateApplication());
}
private static PubSubMethodHandlers CreateHandlers(IPubSubApplication app)
{
var options = new PubSubServerOptions
{
ExposeConfigurationMethods = true
};
return new PubSubMethodHandlers(
app, null, options, NUnitTelemetryContext.Create());
}
private static IPubSubApplication CreateApplication()
{
return new PubSubApplicationBuilder(
NUnitTelemetryContext.Create())
.WithApplicationId("handler-mutation-tests")
.UseConfiguration(new PubSubConfigurationDataType
{
Connections = [],
PublishedDataSets = []
})
.UseAllStandardEncoders()
.AddTransportFactory(new StubTransportFactory())
.Build();
}
private static SystemContext BuildContext()
{
return new SystemContext(NUnitTelemetryContext.Create());
}
private static ArrayOf<Variant> BuildArray(params Variant[] values)
{
return new ArrayOf<Variant>(values);
}
private static MethodState NewPublishedDataItemsMethod(string dataSetName, string methodName)
{
var parent = new BaseObjectState(null)
{
NodeId = new NodeId($"pubsub:published-data-set:{dataSetName}", 0),
BrowseName = new QualifiedName(dataSetName)
};
var method = new MethodState(parent)
{
NodeId = new NodeId($"pubsub:published-data-set:{dataSetName}:{methodName}", 0),
BrowseName = new QualifiedName(methodName)
};
parent.AddChild(method);
return method;
}
private static readonly string[] s_currentTimeAlias = ["CurrentTime"];
private static readonly string[] s_eventIdAlias = ["EventId"];
private static readonly string[] s_startTimeAlias = ["StartTime"];
private static readonly bool[] s_notPromotedField = [false];
private static readonly uint[] s_firstVariableIndex = [0u];
private sealed class StubTransportFactory : IPubSubTransportFactory
{
public string TransportProfileUri => Profiles.PubSubUdpUadpTransport;
public IPubSubTransport Create(
PubSubConnectionDataType connection,
ITelemetryContext telemetry,
TimeProvider timeProvider)
{
_ = connection;
_ = telemetry;
_ = timeProvider;
return new StubTransport();
}
}
private sealed class StubTransport : IPubSubTransport
{
private bool m_isConnected;
public string TransportProfileUri => Profiles.PubSubUdpUadpTransport;
public PubSubTransportDirection Direction => PubSubTransportDirection.SendReceive;
public bool IsConnected => m_isConnected;
public event EventHandler<PubSubTransportStateChangedEventArgs>? StateChanged
{
add { }
remove { }
}
public ValueTask OpenAsync(CancellationToken cancellationToken = default)
{
_ = cancellationToken;
m_isConnected = true;
return default;
}
public ValueTask CloseAsync(CancellationToken cancellationToken = default)
{
_ = cancellationToken;
m_isConnected = false;
return default;
}
public ValueTask SendAsync(
ReadOnlyMemory<byte> payload,
string? topic = null,
CancellationToken cancellationToken = default)
{
_ = payload;
_ = topic;
_ = cancellationToken;
return default;
}
public IAsyncEnumerable<PubSubTransportFrame> ReceiveAsync(
CancellationToken cancellationToken = default)
{
_ = cancellationToken;
return TestAsyncEnumerable.Empty<PubSubTransportFrame>();
}
public ValueTask DisposeAsync()
{
m_isConnected = false;
return default;
}
}
}
}
@@ -722,8 +722,8 @@ namespace Opc.Ua.PubSub.Server.Tests
RemoveSecurityGroupMethod = NewMethod(15447);
AddPushTargetMethod = NewMethod(25441);
RemovePushTargetMethod = NewMethod(25444);
AddDataSetFolderMethod = NewMethod(16884);
RemoveDataSetFolderMethod = NewMethod(16923);
AddDataSetFolderMethod = NewMethod(PublishedDataSetsNodeIds.AddDataSetFolder);
RemoveDataSetFolderMethod = NewMethod(PublishedDataSetsNodeIds.RemoveDataSetFolder);
StatusVariable = new BaseDataVariableState(null)
{
NodeId = new NodeId(17406u),
@@ -736,7 +736,7 @@ namespace Opc.Ua.PubSub.Server.Tests
};
PublishedDataSetsObject = new BaseObjectState(PublishSubscribeObject)
{
NodeId = new NodeId(14478u),
NodeId = ObjectIds.PublishSubscribe_PublishedDataSets,
BrowseName = new QualifiedName("PublishedDataSets")
};
SecurityGroupsObject = new BaseObjectState(PublishSubscribeObject)
@@ -762,14 +762,16 @@ namespace Opc.Ua.PubSub.Server.Tests
diagnosticsNm.Setup(m => m.FindPredefinedNode<MethodState>(new NodeId(15447u))).Returns(RemoveSecurityGroupMethod);
diagnosticsNm.Setup(m => m.FindPredefinedNode<MethodState>(new NodeId(25441u))).Returns(AddPushTargetMethod);
diagnosticsNm.Setup(m => m.FindPredefinedNode<MethodState>(new NodeId(25444u))).Returns(RemovePushTargetMethod);
diagnosticsNm.Setup(m => m.FindPredefinedNode<MethodState>(new NodeId(16884u))).Returns(AddDataSetFolderMethod);
diagnosticsNm.Setup(m => m.FindPredefinedNode<MethodState>(new NodeId(16923u))).Returns(RemoveDataSetFolderMethod);
diagnosticsNm.Setup(m => m.FindPredefinedNode<MethodState>(
new NodeId(PublishedDataSetsNodeIds.AddDataSetFolder))).Returns(AddDataSetFolderMethod);
diagnosticsNm.Setup(m => m.FindPredefinedNode<MethodState>(
new NodeId(PublishedDataSetsNodeIds.RemoveDataSetFolder))).Returns(RemoveDataSetFolderMethod);
diagnosticsNm.Setup(m => m.FindPredefinedNode<BaseVariableState>(new NodeId(17406u))).Returns(StatusVariable);
diagnosticsNm.Setup(m => m.FindPredefinedNode<BaseVariableState>(It.IsAny<NodeId>()))
.Returns((NodeId id) => id == new NodeId(17406u) ? StatusVariable : null!);
diagnosticsNm.Setup(m => m.FindPredefinedNode<BaseObjectState>(ObjectIds.PublishSubscribe))
.Returns(PublishSubscribeObject);
diagnosticsNm.Setup(m => m.FindPredefinedNode<BaseObjectState>(new NodeId(14478u)))
diagnosticsNm.Setup(m => m.FindPredefinedNode<BaseObjectState>(ObjectIds.PublishSubscribe_PublishedDataSets))
.Returns(PublishedDataSetsObject);
diagnosticsNm.Setup(m => m.FindPredefinedNode<BaseObjectState>(new NodeId(15443u)))
.Returns(SecurityGroupsObject);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,61 @@
/* ========================================================================
* Copyright (c) 2005-2026 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/
* ======================================================================*/
namespace Opc.Ua.PubSub.Server.Tests
{
/// <summary>
/// The standard NodeIds of the methods of <c>PublishSubscribe.PublishedDataSets</c>
/// (i=17371) and of their argument variables, as declared by the OPC UA NodeSet.
/// The tests keep their own copy so that they check the NodeIds the node manager
/// assigns against the standard, not against its own constants. The
/// <c>Placeholder*</c> ids are the method declarations of the DataSetFolderType
/// <c>&lt;DataSetFolderName&gt;</c> placeholder, which must not be bound.
/// </summary>
internal static class PublishedDataSetsNodeIds
{
public const uint AddPublishedDataItems = 17372;
public const uint AddPublishedDataItemsInputArguments = 17373;
public const uint AddPublishedDataItemsOutputArguments = 17374;
public const uint AddPublishedEvents = 17375;
public const uint AddPublishedEventsInputArguments = 17376;
public const uint AddPublishedEventsOutputArguments = 17377;
public const uint AddPublishedDataItemsTemplate = 17378;
public const uint AddPublishedDataItemsTemplateInputArguments = 17379;
public const uint AddPublishedDataItemsTemplateOutputArguments = 17380;
public const uint RemovePublishedDataSet = 17384;
public const uint RemovePublishedDataSetInputArguments = 17385;
public const uint AddDataSetFolder = 17398;
public const uint AddDataSetFolderInputArguments = 17401;
public const uint AddDataSetFolderOutputArguments = 17402;
public const uint RemoveDataSetFolder = 17403;
public const uint RemoveDataSetFolderInputArguments = 17404;
public const uint PlaceholderAddPublishedDataItems = 14479;
public const uint PlaceholderAddDataSetFolder = 16884;
}
}