/* ======================================================================== * 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 Opc.Ua; using Opc.Ua.DataChannels; using Opc.Ua.PubSub.Configuration; namespace Quickstarts.ConsoleSecureChannelPubSub { /// /// Ordinary Part 14 datasets, writers and readers; the carrier changes no PubSub identifiers. /// internal static class SampleConfiguration { /// /// Builds the UADP descriptor with opener-relative direction. /// internal static DataChannelParametersDataType Parameters(SampleOptions options) { return new DataChannelParametersDataType { ResourceUri = Resource, Protocol = DataChannelProfiles.UadpPayload, ContentType = "application/opcua+uadp", Direction = options.Reverse ? DataChannelDirection.OpenerToAcceptor : DataChannelDirection.AcceptorToOpener, Delivery = DataChannelDeliveryMode.Ordered, Protection = DataChannelProtectionMode.Confidential, MaxMessageSize = 32768, MaxReceiveBytes = 262144 }; } /// /// Builds two actual writers and independent positive and negative reader filters. /// internal static PubSubConfigurationDataType Build(SampleOptions options, bool publisher) { var builder = PubSubConfigurationBuilder.Create(); if (publisher) { builder.AddPublishedDataSet(TelemetryDataSet, Fields) .AddPublishedDataSet(OtherDataSet, Fields); } builder.AddConnection("Secure channel", connection => { connection.WithPublisherId(new Variant(PublisherId)) .WithDataChannel(options.Carrier, Parameters(options)); if (publisher) { connection.AddWriterGroup("Writers", group => { group.WithWriterGroupId(WriterGroupId) .WithPublishingInterval(50) .WithMessageSettings(GroupSettings()); AddWriter(group, TelemetryDataSet, TelemetryWriterId); AddWriter(group, OtherDataSet, OtherWriterId); }); } else { connection.AddReaderGroup("Readers", group => { group.WithMaxNetworkMessageSize(65536); AddReader(group, TelemetryReader, TelemetryDataSet, PublisherId, WriterGroupId, TelemetryWriterId); AddReader(group, OtherReader, OtherDataSet, PublisherId, WriterGroupId, OtherWriterId); AddReader(group, WrongWriterReader, TelemetryDataSet, PublisherId, WriterGroupId, 999); AddReader(group, WrongPublisherReader, TelemetryDataSet, 999, WriterGroupId, TelemetryWriterId); }); } }); return builder.Build(); } /// /// Returns the same metadata declaration used by the configured writers and readers. /// internal static DataSetMetaDataType MetaData(string name) { return PubSubConfigurationBuilder.Create().AddPublishedDataSet(name, Fields) .Build().PublishedDataSets[0].DataSetMetaData; } /// /// Specifies stable field names, types and metadata version. /// private static void Fields(PublishedDataSetBuilder dataSet) { dataSet.WithoutFieldIds() .WithConfigurationVersion(1, 0) .AddField("Counter", (byte)DataTypes.Int32, DataTypeIds.Int32) .AddField("Toggle", (byte)DataTypes.Boolean, DataTypeIds.Boolean) .AddField("Temperature", (byte)DataTypes.Double, DataTypeIds.Double); } /// /// Adds a writer with ordinary Part 14 message settings and no broker transport settings. /// private static void AddWriter( WriterGroupBuilder group, string dataSet, ushort writerId) { group.AddDataSetWriter(dataSet, writer => writer .WithDataSetName(dataSet) .WithDataSetWriterId(writerId) .WithKeyFrameCount(1) .WithFieldContentMask(DataSetFieldContentMask.RawData) .WithMessageSettings(WriterSettings())); } /// /// Adds a metadata-aware reader; unmatched identities must never reach its sink. /// private static void AddReader( ReaderGroupBuilder group, string readerName, string dataSet, ushort publisherId, ushort groupId, ushort writerId) { group.AddDataSetReader(readerName, reader => reader .WithFilter(new Variant(publisherId), groupId, writerId) .WithMessageReceiveTimeout(15000) .WithFieldContentMask(DataSetFieldContentMask.RawData) .WithMirrorSubscribedDataSet(readerName) .WithDataSetMetaData(dataSet, Fields) .WithMessageSettings(ReaderSettings())); } /// /// Uses the encoder's existing NetworkMessage header model. /// private static UadpWriterGroupMessageDataType GroupSettings() { return new UadpWriterGroupMessageDataType { DataSetOrdering = DataSetOrderingType.AscendingWriterId, NetworkMessageContentMask = (uint)UadpNetworkMask }; } /// /// Carries writer identity, sequence and metadata version in the existing codec. /// private static UadpDataSetWriterMessageDataType WriterSettings() { return new UadpDataSetWriterMessageDataType { DataSetMessageContentMask = (uint)UadpDataSetMask }; } /// /// Selects the matching metadata-driven decoder settings. /// private static UadpDataSetReaderMessageDataType ReaderSettings() { return new UadpDataSetReaderMessageDataType { NetworkMessageContentMask = (uint)UadpNetworkMask, DataSetMessageContentMask = (uint)UadpDataSetMask }; } /// The exact allow-listed application resource. internal const string Resource = "urn:opcfoundation:samples:secure-channel-pubsub:telemetry"; /// The namespace of the optional source model. internal const string ModelNamespace = "urn:opcfoundation:samples:secure-channel-pubsub"; /// The server application identity, never inferred from a HELLO claim. internal const string ServerApplicationUri = "urn:localhost:OPCFoundation:SecureChannelPubSubServer"; /// The only admitted client application identity. internal const string ClientApplicationUri = "urn:localhost:OPCFoundation:SecureChannelPubSubClient"; /// The primary dataset name. internal const string TelemetryDataSet = "Telemetry"; /// The independently filtered second dataset name. internal const string OtherDataSet = "OtherTelemetry"; /// The primary reader name. internal const string TelemetryReader = "TelemetryReader"; /// The second reader name. internal const string OtherReader = "OtherReader"; /// A reader whose writer filter must not match. internal const string WrongWriterReader = "WrongWriter"; /// A reader whose PublisherId filter must not match. internal const string WrongPublisherReader = "WrongPublisher"; /// Stable PublisherId, unrelated to any flow or SecureChannel identifier. internal const ushort PublisherId = 42; /// Stable WriterGroupId. internal const ushort WriterGroupId = 7; /// Stable first writer identifier. internal const ushort TelemetryWriterId = 101; /// Stable second writer identifier. internal const ushort OtherWriterId = 202; /// Complete headers used by the standard UADP encoder. private const UadpNetworkMessageContentMask UadpNetworkMask = UadpNetworkMessageContentMask.PublisherId | UadpNetworkMessageContentMask.GroupHeader | UadpNetworkMessageContentMask.WriterGroupId | UadpNetworkMessageContentMask.PayloadHeader | UadpNetworkMessageContentMask.SequenceNumber; /// Versioned UADP DataSetMessage headers. private const UadpDataSetMessageContentMask UadpDataSetMask = UadpDataSetMessageContentMask.SequenceNumber | UadpDataSetMessageContentMask.MajorVersion | UadpDataSetMessageContentMask.MinorVersion; } }