diff --git a/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs b/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs
index 4adbeebc1..58b1b03d1 100644
--- a/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs
+++ b/Libraries/Opc.Ua.Client/DefaultSessionFactory.cs
@@ -29,6 +29,7 @@
using System;
using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -39,9 +40,21 @@ namespace Opc.Ua.Client
///
public class DefaultSessionFactory : ISessionFactory
{
+ ///
+ /// The default instance of the factory.
+ ///
+ public static readonly DefaultSessionFactory Instance = new DefaultSessionFactory();
+
+ ///
+ /// Force use of the default instance.
+ ///
+ protected DefaultSessionFactory()
+ {
+ }
+
#region Public Methods
///
- public async Task CreateAsync(
+ public async virtual Task CreateAsync(
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint,
bool updateBeforeConnect,
@@ -55,7 +68,7 @@ public async Task CreateAsync(
}
///
- public async Task CreateAsync(
+ public async virtual Task CreateAsync(
ApplicationConfiguration configuration,
ConfiguredEndpoint endpoint,
bool updateBeforeConnect,
@@ -71,7 +84,7 @@ public async Task CreateAsync(
}
///
- public async Task CreateAsync(
+ public async virtual Task CreateAsync(
ApplicationConfiguration configuration,
ITransportWaitingConnection connection,
ConfiguredEndpoint endpoint,
@@ -89,7 +102,7 @@ public async Task CreateAsync(
}
///
- public async Task CreateAsync(
+ public async virtual Task CreateAsync(
ApplicationConfiguration configuration,
ReverseConnectManager reverseConnectManager,
ConfiguredEndpoint endpoint,
@@ -102,7 +115,6 @@ public async Task CreateAsync(
CancellationToken ct = default
)
{
-
if (reverseConnectManager == null)
{
return await CreateAsync(configuration, endpoint, updateBeforeConnect,
@@ -141,7 +153,25 @@ await endpoint.UpdateFromServerAsync(
}
///
- public Task RecreateAsync(ISession sessionTemplate)
+ public virtual ISession Create(
+ ApplicationConfiguration configuration,
+ ITransportChannel channel,
+ ConfiguredEndpoint endpoint,
+ X509Certificate2 clientCertificate,
+ EndpointDescriptionCollection availableEndpoints = null,
+ StringCollection discoveryProfileUris = null)
+ {
+ return Session.Create(configuration, channel, endpoint, clientCertificate, availableEndpoints, discoveryProfileUris);
+ }
+
+ ///
+ public virtual Task CreateChannelAsync(ApplicationConfiguration configuration, ITransportWaitingConnection connection, ConfiguredEndpoint endpoint, bool updateBeforeConnect, bool checkDomain)
+ {
+ return Session.CreateChannelAsync(configuration, connection, endpoint, updateBeforeConnect, checkDomain);
+ }
+
+ ///
+ public virtual Task RecreateAsync(ISession sessionTemplate)
{
if (!(sessionTemplate is Session template))
{
@@ -152,7 +182,7 @@ public Task RecreateAsync(ISession sessionTemplate)
}
///
- public Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection)
+ public virtual Task RecreateAsync(ISession sessionTemplate, ITransportWaitingConnection connection)
{
if (!(sessionTemplate is Session template))
{
diff --git a/Libraries/Opc.Ua.Client/ISessionFactory.cs b/Libraries/Opc.Ua.Client/ISessionFactory.cs
index cb706d669..c01917204 100644
--- a/Libraries/Opc.Ua.Client/ISessionFactory.cs
+++ b/Libraries/Opc.Ua.Client/ISessionFactory.cs
@@ -28,6 +28,7 @@
* ======================================================================*/
using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
@@ -131,6 +132,39 @@ Task CreateAsync(
IList preferredLocales,
CancellationToken ct = default);
+ ///
+ /// Creates a secure channel to the specified endpoint.
+ ///
+ /// The application configuration.
+ /// The client endpoint for the reverse connect.
+ /// A configured endpoint to connect to.
+ /// Update configuration based on server prior connect.
+ /// Check that the certificate specifies a valid domain (computer) name.
+ /// A representing the asynchronous operation.
+ Task CreateChannelAsync(
+ ApplicationConfiguration configuration,
+ ITransportWaitingConnection connection,
+ ConfiguredEndpoint endpoint,
+ bool updateBeforeConnect,
+ bool checkDomain);
+
+ ///
+ /// Creates a new session with a server using the specified channel by invoking the CreateSession service.
+ ///
+ /// The configuration for the client application.
+ /// The channel for the server.
+ /// The endpoint for the server.
+ /// The certificate to use for the client.
+ /// The list of available endpoints returned by server in GetEndpoints() response.
+ /// The value of profileUris used in GetEndpoints() request.
+ ISession Create(
+ ApplicationConfiguration configuration,
+ ITransportChannel channel,
+ ConfiguredEndpoint endpoint,
+ X509Certificate2 clientCertificate,
+ EndpointDescriptionCollection availableEndpoints = null,
+ StringCollection discoveryProfileUris = null);
+
///
/// Recreates a session based on a specified template.
///
diff --git a/Libraries/Opc.Ua.Client/Session.cs b/Libraries/Opc.Ua.Client/Session.cs
index ee88800c4..1fd58585b 100644
--- a/Libraries/Opc.Ua.Client/Session.cs
+++ b/Libraries/Opc.Ua.Client/Session.cs
@@ -251,7 +251,7 @@ private void Initialize(
///
private void Initialize()
{
- m_sessionFactory = new DefaultSessionFactory();
+ m_sessionFactory = DefaultSessionFactory.Instance;
m_sessionTimeout = 0;
m_namespaceUris = new NamespaceTable();
m_serverUris = new StringTable();
diff --git a/Libraries/Opc.Ua.Client/TraceableSession.cs b/Libraries/Opc.Ua.Client/TraceableSession.cs
new file mode 100644
index 000000000..6deb34716
--- /dev/null
+++ b/Libraries/Opc.Ua.Client/TraceableSession.cs
@@ -0,0 +1,1913 @@
+/* ========================================================================
+ * 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 NET6_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Opc.Ua.Client
+{
+ ///
+ /// Decorator class for traceable session with Activity Source.
+ ///
+ public class TraceableSession : ISession
+ {
+ #region Constructors
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TraceableSession(ISession session)
+ {
+ m_session = session;
+ }
+ #endregion
+
+ ///
+ /// Activity Source Name.
+ ///
+ public static readonly string ActivitySourceName = "Opc.Ua.Client-TraceableSession-ActivitySource";
+
+ ///
+ /// Activity Source static instance.
+ ///
+ public static ActivitySource ActivitySource => s_activitySource.Value;
+ private static readonly Lazy s_activitySource = new Lazy(() => new ActivitySource(ActivitySourceName));
+
+ ///
+ /// The ISession which is being traced.
+ ///
+ private ISession m_session;
+
+ ///
+ public ISession Session => m_session;
+
+ #region ISession interface
+ ///
+ public event KeepAliveEventHandler KeepAlive
+ {
+ add => m_session.KeepAlive += value;
+ remove => m_session.KeepAlive -= value;
+ }
+
+ ///
+ public event NotificationEventHandler Notification
+ {
+ add => m_session.Notification += value;
+ remove => m_session.Notification -= value;
+ }
+
+ ///
+ public event PublishErrorEventHandler PublishError
+ {
+ add => m_session.PublishError += value;
+ remove => m_session.PublishError -= value;
+ }
+
+ ///
+ public event PublishSequenceNumbersToAcknowledgeEventHandler PublishSequenceNumbersToAcknowledge
+ {
+ add => m_session.PublishSequenceNumbersToAcknowledge += value;
+ remove => m_session.PublishSequenceNumbersToAcknowledge -= value;
+ }
+
+ ///
+ public event EventHandler SubscriptionsChanged
+ {
+ add => m_session.SubscriptionsChanged += value;
+ remove => m_session.SubscriptionsChanged -= value;
+ }
+
+ ///
+ public event EventHandler SessionClosing
+ {
+ add => m_session.SessionClosing += value;
+ remove => m_session.SessionClosing -= value;
+ }
+
+ ///
+ public event RenewUserIdentityEventHandler RenewUserIdentity
+ {
+ add => m_session.RenewUserIdentity += value;
+ remove => m_session.RenewUserIdentity -= value;
+ }
+
+ ///
+ public ISessionFactory SessionFactory => TraceableSessionFactory.Instance;
+
+ ///
+ public ConfiguredEndpoint ConfiguredEndpoint => m_session.ConfiguredEndpoint;
+
+ ///
+ public string SessionName => m_session.SessionName;
+
+ ///
+ public double SessionTimeout => m_session.SessionTimeout;
+
+ ///
+ public object Handle => m_session.Handle;
+
+ ///
+ public IUserIdentity Identity => m_session.Identity;
+
+ ///
+ public IEnumerable IdentityHistory => m_session.IdentityHistory;
+
+ ///
+ public NamespaceTable NamespaceUris => m_session.NamespaceUris;
+
+ ///
+ public StringTable ServerUris => m_session.ServerUris;
+
+ ///
+ public ISystemContext SystemContext => m_session.SystemContext;
+
+ ///
+ public IEncodeableFactory Factory => m_session.Factory;
+
+ ///
+ public ITypeTable TypeTree => m_session.TypeTree;
+
+ ///
+ public INodeCache NodeCache => m_session.NodeCache;
+
+ ///
+ public FilterContext FilterContext => m_session.FilterContext;
+
+ ///
+ public StringCollection PreferredLocales => m_session.PreferredLocales;
+
+ ///
+ public IReadOnlyDictionary DataTypeSystem => m_session.DataTypeSystem;
+
+ ///
+ public IEnumerable Subscriptions => m_session.Subscriptions;
+
+ ///
+ public int SubscriptionCount => m_session.SubscriptionCount;
+
+ ///
+ public bool DeleteSubscriptionsOnClose
+ {
+ get => m_session.DeleteSubscriptionsOnClose;
+ set => m_session.DeleteSubscriptionsOnClose = value;
+ }
+
+ ///
+ public Subscription DefaultSubscription
+ {
+ get => m_session.DefaultSubscription;
+ set => m_session.DefaultSubscription = value;
+ }
+
+ ///
+ public int KeepAliveInterval
+ {
+ get => m_session.KeepAliveInterval;
+ set => m_session.KeepAliveInterval = value;
+ }
+
+ ///
+ public bool KeepAliveStopped => m_session.KeepAliveStopped;
+
+ ///
+ public DateTime LastKeepAliveTime => m_session.LastKeepAliveTime;
+
+ ///
+ public int OutstandingRequestCount => m_session.OutstandingRequestCount;
+
+ ///
+ public int DefunctRequestCount => m_session.DefunctRequestCount;
+
+ ///
+ public int GoodPublishRequestCount => m_session.GoodPublishRequestCount;
+
+ ///
+ public int MinPublishRequestCount
+ {
+ get => m_session.MinPublishRequestCount;
+ set => m_session.MinPublishRequestCount = value;
+ }
+
+ ///
+ public OperationLimits OperationLimits => m_session.OperationLimits;
+
+ ///
+ public bool TransferSubscriptionsOnReconnect
+ {
+ get => m_session.TransferSubscriptionsOnReconnect;
+ set => m_session.TransferSubscriptionsOnReconnect = value;
+ }
+
+ ///
+ public NodeId SessionId => m_session.SessionId;
+
+ ///
+ public bool Connected => m_session.Connected;
+
+ ///
+ public EndpointDescription Endpoint => m_session.Endpoint;
+
+ ///
+ public EndpointConfiguration EndpointConfiguration => m_session.EndpointConfiguration;
+
+ ///
+ public IServiceMessageContext MessageContext => m_session.MessageContext;
+
+ ///
+ public ITransportChannel TransportChannel => m_session.TransportChannel;
+
+ ///
+ public DiagnosticsMasks ReturnDiagnostics
+ {
+ get => m_session.ReturnDiagnostics;
+ set => m_session.ReturnDiagnostics = value;
+ }
+
+ ///
+ public int OperationTimeout
+ {
+ get => m_session.OperationTimeout;
+ set => m_session.OperationTimeout = value;
+ }
+
+ ///
+ public bool Disposed => m_session.Disposed;
+
+ ///
+ public bool CheckDomain => m_session.CheckDomain;
+
+ ///
+ public void Reconnect()
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Reconnect)))
+ {
+ m_session.Reconnect();
+ }
+ }
+
+ ///
+ public void Reconnect(ITransportWaitingConnection connection)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Reconnect)))
+ {
+ m_session.Reconnect(connection);
+ }
+ }
+
+ ///
+ public void Reconnect(ITransportChannel channel)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Reconnect)))
+ {
+ m_session.Reconnect(channel);
+ }
+ }
+
+ ///
+ public void Save(string filePath)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Save)))
+ {
+ m_session.Save(filePath);
+ }
+ }
+
+ ///
+ public void Save(Stream stream, IEnumerable subscriptions)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Save)))
+ {
+ m_session.Save(stream, subscriptions);
+ }
+ }
+
+ ///
+ public void Save(string filePath, IEnumerable subscriptions)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Save)))
+ {
+ m_session.Save(filePath, subscriptions);
+ }
+ }
+
+ ///
+ public IEnumerable Load(Stream stream, bool transferSubscriptions = false)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Load)))
+ {
+ return m_session.Load(stream, transferSubscriptions);
+ }
+ }
+
+ ///
+ public IEnumerable Load(string filePath, bool transferSubscriptions = false)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Load)))
+ {
+ return m_session.Load(filePath, transferSubscriptions);
+ }
+ }
+
+ ///
+ public void FetchNamespaceTables()
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(FetchNamespaceTables)))
+ {
+ m_session.FetchNamespaceTables();
+ }
+ }
+
+ ///
+ public void FetchTypeTree(ExpandedNodeId typeId)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(FetchTypeTree)))
+ {
+ m_session.FetchTypeTree(typeId);
+ }
+ }
+
+ ///
+ public void FetchTypeTree(ExpandedNodeIdCollection typeIds)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(FetchTypeTree)))
+ {
+ m_session.FetchTypeTree(typeIds);
+ }
+ }
+
+ ///
+ public ReferenceDescriptionCollection ReadAvailableEncodings(NodeId variableId)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ReadAvailableEncodings)))
+ {
+ return m_session.ReadAvailableEncodings(variableId);
+ }
+ }
+
+ ///
+ public ReferenceDescription FindDataDescription(NodeId encodingId)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(FindDataDescription)))
+ {
+ return m_session.FindDataDescription(encodingId);
+ }
+ }
+
+ ///
+ public Task FindDataDictionary(NodeId descriptionId)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(FindDataDictionary)))
+ {
+ return m_session.FindDataDictionary(descriptionId);
+ }
+ }
+
+ ///
+ public async Task LoadDataDictionary(ReferenceDescription dictionaryNode, bool forceReload = false)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(LoadDataDictionary)))
+ {
+ return await m_session.LoadDataDictionary(dictionaryNode, forceReload).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ public async Task> LoadDataTypeSystem(NodeId dataTypeSystem = null)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(LoadDataTypeSystem)))
+ {
+ return await m_session.LoadDataTypeSystem(dataTypeSystem).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ public Node ReadNode(NodeId nodeId)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ReadNode)))
+ {
+ return m_session.ReadNode(nodeId);
+ }
+ }
+
+ ///
+ public Node ReadNode(NodeId nodeId, NodeClass nodeClass, bool optionalAttributes = true)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ReadNode)))
+ {
+ return m_session.ReadNode(nodeId, nodeClass, optionalAttributes);
+ }
+ }
+
+ ///
+ public void ReadNodes(IList nodeIds, out IList nodeCollection, out IList errors, bool optionalAttributes = false)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ReadNodes)))
+ {
+ m_session.ReadNodes(nodeIds, out nodeCollection, out errors, optionalAttributes);
+ }
+ }
+
+ ///
+ public void ReadNodes(IList nodeIds, NodeClass nodeClass, out IList nodeCollection, out IList errors, bool optionalAttributes = false)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ReadNodes)))
+ {
+ m_session.ReadNodes(nodeIds, nodeClass, out nodeCollection, out errors, optionalAttributes);
+ }
+ }
+
+ ///
+ public DataValue ReadValue(NodeId nodeId)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ReadValue)))
+ {
+ return m_session.ReadValue(nodeId);
+ }
+ }
+
+ ///
+ public object ReadValue(NodeId nodeId, Type expectedType)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ReadValue)))
+ {
+ return m_session.ReadValue(nodeId, expectedType);
+ }
+ }
+
+ ///
+ public void ReadValues(IList nodeIds, out DataValueCollection values, out IList errors)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ReadValues)))
+ {
+ m_session.ReadValues(nodeIds, out values, out errors);
+ }
+ }
+
+ ///
+ public ReferenceDescriptionCollection FetchReferences(NodeId nodeId)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(FetchReferences)))
+ {
+ return m_session.FetchReferences(nodeId);
+ }
+ }
+
+ ///
+ public void FetchReferences(IList nodeIds, out IList referenceDescriptions, out IList errors)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(FetchReferences)))
+ {
+ m_session.FetchReferences(nodeIds, out referenceDescriptions, out errors);
+ }
+ }
+
+ ///
+ public void Open(string sessionName, IUserIdentity identity)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Open)))
+ {
+ m_session.Open(sessionName, identity);
+ }
+ }
+
+ ///
+ public void Open(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Open)))
+ {
+ m_session.Open(sessionName, sessionTimeout, identity, preferredLocales);
+ }
+ }
+
+ ///
+ public void Open(string sessionName, uint sessionTimeout, IUserIdentity identity, IList preferredLocales, bool checkDomain)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(Open)))
+ {
+ m_session.Open(sessionName, sessionTimeout, identity, preferredLocales, checkDomain);
+ }
+ }
+
+ ///
+ public void ChangePreferredLocales(StringCollection preferredLocales)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(ChangePreferredLocales)))
+ {
+ m_session.ChangePreferredLocales(preferredLocales);
+ }
+ }
+
+ ///
+ public void UpdateSession(IUserIdentity identity, StringCollection preferredLocales)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(UpdateSession)))
+ {
+ m_session.UpdateSession(identity, preferredLocales);
+ }
+ }
+
+ ///
+ public void FindComponentIds(NodeId instanceId, IList componentPaths, out NodeIdCollection componentIds, out List errors)
+ {
+ using (Activity activity = ActivitySource.StartActivity(nameof(FindComponentIds)))
+ {
+ m_session.FindComponentIds(instanceId, componentPaths, out componentIds, out errors);
+ }
+ }
+
+ ///
+ public void ReadValues(IList variableIds, IList expectedTypes, out List