mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-25 03:43:05 +08:00
672 lines
21 KiB
C#
672 lines
21 KiB
C#
/* Copyright (c) 1996-2016, OPC Foundation. All rights reserved.
|
|
The source code in this file is covered under a dual-license scenario:
|
|
- RCL: for OPC Foundation members in good-standing
|
|
- GPL V2: everybody else
|
|
RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
|
|
GNU General Public License as published by the Free Software Foundation;
|
|
version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
|
|
This source code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Threading;
|
|
using System.Reflection;
|
|
|
|
namespace Opc.Ua
|
|
{
|
|
/// <summary>
|
|
/// The client side interface with a UA server.
|
|
/// </summary>
|
|
public partial class ClientBase : IDisposable
|
|
{
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Intializes the object with a channel and a message context.
|
|
/// </summary>
|
|
/// <param name="channel">The channel.</param>
|
|
public ClientBase(ITransportChannel channel)
|
|
{
|
|
if (channel == null) throw new ArgumentNullException("channel");
|
|
|
|
m_channel = channel;
|
|
m_useTransportChannel = true;
|
|
|
|
WcfChannelBase wcfChannel = channel as WcfChannelBase;
|
|
|
|
if (wcfChannel != null)
|
|
{
|
|
m_useTransportChannel = wcfChannel.m_wcfBypassChannel != null || wcfChannel.UseBinaryEncoding;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region IDisposable Members
|
|
/// <summary>
|
|
/// Frees any unmanaged resources.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// An overrideable version of the Dispose.
|
|
/// </summary>
|
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
CloseChannel();
|
|
|
|
m_disposed = true;
|
|
}
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
/// <summary>
|
|
/// The description of the endpoint.
|
|
/// </summary>
|
|
public EndpointDescription Endpoint
|
|
{
|
|
get
|
|
{
|
|
ITransportChannel channel = TransportChannel;
|
|
|
|
if (channel != null)
|
|
{
|
|
return channel.EndpointDescription;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The configuration for the endpoint.
|
|
/// </summary>
|
|
public EndpointConfiguration EndpointConfiguration
|
|
{
|
|
get
|
|
{
|
|
ITransportChannel channel = TransportChannel;
|
|
|
|
if (channel != null)
|
|
{
|
|
return channel.EndpointConfiguration;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The message context used when serializing messages.
|
|
/// </summary>
|
|
/// <value>The message context.</value>
|
|
public ServiceMessageContext MessageContext
|
|
{
|
|
get
|
|
{
|
|
ITransportChannel channel = TransportChannel;
|
|
|
|
if (channel != null)
|
|
{
|
|
return channel.MessageContext;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or set the channel being wrapped by the client object.
|
|
/// </summary>
|
|
/// <value>The transport channel.</value>
|
|
public ITransportChannel TransportChannel
|
|
{
|
|
get
|
|
{
|
|
ITransportChannel channel = m_channel;
|
|
|
|
if (channel != null)
|
|
{
|
|
if (m_disposed)
|
|
{
|
|
throw new ObjectDisposedException("ClientBase has been disposed.");
|
|
}
|
|
}
|
|
|
|
return channel;
|
|
}
|
|
|
|
protected set
|
|
{
|
|
ITransportChannel channel = m_channel;
|
|
m_channel = null;
|
|
|
|
if (channel != null)
|
|
{
|
|
try
|
|
{
|
|
channel.Close();
|
|
channel.Dispose();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// ignore errors.
|
|
}
|
|
}
|
|
|
|
m_channel = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The channel being wrapped by the client object.
|
|
/// </summary>
|
|
internal IChannelBase InnerChannel
|
|
{
|
|
get
|
|
{
|
|
ITransportChannel channel = TransportChannel;
|
|
|
|
if (channel != null)
|
|
{
|
|
return m_channel as IChannelBase;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// What diagnostics the server should return in the response.
|
|
/// </summary>
|
|
/// <value>The diagnostics.</value>
|
|
public DiagnosticsMasks ReturnDiagnostics
|
|
{
|
|
get
|
|
{
|
|
return m_returnDiagnostics;
|
|
}
|
|
|
|
set
|
|
{
|
|
m_returnDiagnostics = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the timeout for an operation.
|
|
/// </summary>
|
|
public int OperationTimeout
|
|
{
|
|
get
|
|
{
|
|
ITransportChannel channel = TransportChannel;
|
|
|
|
if (channel != null)
|
|
{
|
|
return m_channel.OperationTimeout;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
set
|
|
{
|
|
ITransportChannel channel = TransportChannel;
|
|
|
|
if (channel != null)
|
|
{
|
|
m_channel.OperationTimeout = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a value that indicates whether to use the TransportChannel when sending requests.
|
|
/// </summary>
|
|
protected bool UseTransportChannel
|
|
{
|
|
get
|
|
{
|
|
ITransportChannel channel = TransportChannel;
|
|
|
|
if (channel == null)
|
|
{
|
|
throw new ObjectDisposedException("TransportChannel is not available.");
|
|
}
|
|
|
|
return m_useTransportChannel;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
/// <summary>
|
|
/// Closes the channel.
|
|
/// </summary>
|
|
public virtual StatusCode Close()
|
|
{
|
|
if (m_channel != null)
|
|
{
|
|
m_channel.Close();
|
|
m_channel = null;
|
|
}
|
|
|
|
m_authenticationToken = null;
|
|
return StatusCodes.Good;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether the object has been disposed.
|
|
/// </summary>
|
|
/// <value><c>true</c> if disposed; otherwise, <c>false</c>.</value>
|
|
public bool Disposed
|
|
{
|
|
get
|
|
{
|
|
return m_disposed;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a unique request handle.
|
|
/// </summary>
|
|
public uint NewRequestHandle()
|
|
{
|
|
return (uint)Utils.IncrementIdentifier(ref m_nextRequestHandle);
|
|
}
|
|
#endregion
|
|
|
|
#region Protected Methods
|
|
/// <summary>
|
|
/// Closes the channel.
|
|
/// </summary>
|
|
protected void CloseChannel()
|
|
{
|
|
if (m_channel != null)
|
|
{
|
|
try
|
|
{
|
|
m_channel.Close();
|
|
}
|
|
catch
|
|
{
|
|
// ignore errors.
|
|
}
|
|
|
|
DisposeChannel();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes the channel.
|
|
/// </summary>
|
|
protected void DisposeChannel()
|
|
{
|
|
if (m_channel != null)
|
|
{
|
|
try
|
|
{
|
|
m_channel.Dispose();
|
|
}
|
|
catch
|
|
{
|
|
// ignore errors.
|
|
}
|
|
|
|
m_channel = null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An object used to synchronize access to the session state.
|
|
/// </summary>
|
|
/// <value>The synchronization object.</value>
|
|
protected object SyncRoot
|
|
{
|
|
get { return m_lock; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The authorization token used to connect to the server.
|
|
/// </summary>
|
|
/// <value>The authentication token.</value>
|
|
protected NodeId AuthenticationToken
|
|
{
|
|
get
|
|
{
|
|
return m_authenticationToken;
|
|
}
|
|
|
|
set
|
|
{
|
|
m_authenticationToken = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the header of a service request.
|
|
/// </summary>
|
|
/// <param name="request">The request.</param>
|
|
[Obsolete("Must override the version with useDefault parameter.")]
|
|
protected virtual void UpdateRequestHeader(IServiceRequest request)
|
|
{
|
|
UpdateRequestHeader(request, request == null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the header of a service request.
|
|
/// </summary>
|
|
/// <param name="request">The request.</param>
|
|
/// <param name="useDefaults">if set to <c>true</c> use defaults].</param>
|
|
protected virtual void UpdateRequestHeader(IServiceRequest request, bool useDefaults)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
if (request.RequestHeader == null)
|
|
{
|
|
request.RequestHeader = new RequestHeader();
|
|
}
|
|
|
|
if (useDefaults)
|
|
{
|
|
request.RequestHeader.ReturnDiagnostics = (uint)(int)m_returnDiagnostics;
|
|
}
|
|
|
|
if (request.RequestHeader.RequestHandle == 0)
|
|
{
|
|
request.RequestHeader.RequestHandle = (uint)Utils.IncrementIdentifier(ref m_nextRequestHandle);
|
|
}
|
|
|
|
if (NodeId.IsNull(request.RequestHeader.AuthenticationToken))
|
|
{
|
|
request.RequestHeader.AuthenticationToken = m_authenticationToken;
|
|
}
|
|
|
|
request.RequestHeader.Timestamp = DateTime.UtcNow;
|
|
request.RequestHeader.AuditEntryId = CreateAuditLogEntry(request);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the header of a service request.
|
|
/// </summary>
|
|
/// <param name="request">The request.</param>
|
|
/// <param name="useDefaults">if set to <c>true</c> the no request header was provided.</param>
|
|
/// <param name="serviceName">The name of the service.</param>
|
|
protected virtual void UpdateRequestHeader(IServiceRequest request, bool useDefaults, string serviceName)
|
|
{
|
|
UpdateRequestHeader(request, useDefaults);
|
|
|
|
Utils.Trace(
|
|
(int)Utils.TraceMasks.Service,
|
|
"{0} Called. RequestHandle={1}, PendingRequestCount={2}",
|
|
serviceName,
|
|
request.RequestHeader.RequestHandle,
|
|
Interlocked.Increment(ref m_pendingRequestCount));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a request completes.
|
|
/// </summary>
|
|
/// <param name="request">The request.</param>
|
|
/// <param name="response">The response.</param>
|
|
/// <param name="serviceName">The name of the service.</param>
|
|
protected virtual void RequestCompleted(IServiceRequest request, IServiceResponse response, string serviceName)
|
|
{
|
|
uint requestHandle = 0;
|
|
StatusCode statusCode = StatusCodes.Good;
|
|
|
|
if (request != null)
|
|
{
|
|
requestHandle = request.RequestHeader.RequestHandle;
|
|
}
|
|
else if (response != null)
|
|
{
|
|
requestHandle = response.ResponseHeader.RequestHandle;
|
|
statusCode = response.ResponseHeader.ServiceResult;
|
|
}
|
|
|
|
if (response == null)
|
|
{
|
|
statusCode = StatusCodes.Bad;
|
|
}
|
|
|
|
int pendingRequestCount = Interlocked.Decrement(ref m_pendingRequestCount);
|
|
|
|
if (statusCode != StatusCodes.Good)
|
|
{
|
|
Utils.Trace(
|
|
(int)Utils.TraceMasks.Service,
|
|
"{0} Completed. RequestHandle={1}, PendingRequestCount={3}, StatusCode={2}",
|
|
serviceName,
|
|
requestHandle,
|
|
statusCode,
|
|
pendingRequestCount);
|
|
}
|
|
else
|
|
{
|
|
Utils.Trace(
|
|
(int)Utils.TraceMasks.Service,
|
|
"{0} Completed. RequestHandle={1}, PendingRequestCount={2}",
|
|
serviceName,
|
|
requestHandle,
|
|
pendingRequestCount);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an audit log entry for the request.
|
|
/// </summary>
|
|
/// <param name="request">The request.</param>
|
|
/// <returns>log entry</returns>
|
|
protected virtual string CreateAuditLogEntry(IServiceRequest request)
|
|
{
|
|
return request.RequestHeader.AuditEntryId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Throws an exception if a response contains an error.
|
|
/// </summary>
|
|
/// <param name="header">The header.</param>
|
|
protected static void ValidateResponse(ResponseHeader header)
|
|
{
|
|
if (header == null)
|
|
{
|
|
throw new ServiceResultException(StatusCodes.BadUnknownResponse, "Null header in response.");
|
|
}
|
|
|
|
if (StatusCode.IsBad(header.ServiceResult))
|
|
{
|
|
throw new ServiceResultException(new ServiceResult(header.ServiceResult, header.ServiceDiagnostics, header.StringTable));
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Static Methods
|
|
/// <summary>
|
|
/// Validates a response returned by the server.
|
|
/// </summary>
|
|
/// <param name="response">The response.</param>
|
|
/// <param name="request">The request.</param>
|
|
public static void ValidateResponse(IList response, IList request)
|
|
{
|
|
if (response is DiagnosticInfoCollection)
|
|
{
|
|
throw new ArgumentException("Must call ValidateDiagnosticInfos() for DiagnosticInfoCollections.", "response");
|
|
}
|
|
|
|
if (response == null || response.Count != request.Count)
|
|
{
|
|
throw new ServiceResultException(StatusCodes.BadUnexpectedError, "The server returned a list without the expected number of elements.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates a response returned by the server.
|
|
/// </summary>
|
|
/// <param name="response">The response.</param>
|
|
/// <param name="request">The request.</param>
|
|
public static void ValidateDiagnosticInfos(DiagnosticInfoCollection response, IList request)
|
|
{
|
|
// returning an empty list for diagnostic info arrays is allowed.
|
|
if (response != null && response.Count != 0 && response.Count != request.Count)
|
|
{
|
|
throw new ServiceResultException(StatusCodes.BadUnexpectedError, "The server forgot to fill in the DiagnosticInfos array correctly when returning an operation level error.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converts a service response to a ServiceResult object.
|
|
/// </summary>
|
|
/// <param name="statusCode">The status code.</param>
|
|
/// <param name="index">The index.</param>
|
|
/// <param name="diagnosticInfos">The diagnostic information.</param>
|
|
/// <param name="responseHeader">The response header.</param>
|
|
/// <returns>Converted service response.</returns>
|
|
public static ServiceResult GetResult(
|
|
StatusCode statusCode,
|
|
int index,
|
|
DiagnosticInfoCollection diagnosticInfos,
|
|
ResponseHeader responseHeader)
|
|
{
|
|
if (diagnosticInfos != null && diagnosticInfos.Count > index)
|
|
{
|
|
return new ServiceResult(statusCode.Code, diagnosticInfos[index], responseHeader.StringTable);
|
|
}
|
|
|
|
return new ServiceResult(statusCode.Code);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates a DataValue returned from the server.
|
|
/// </summary>
|
|
/// <param name="value">The value.</param>
|
|
/// <param name="expectedType">The expected type.</param>
|
|
/// <param name="index">The index.</param>
|
|
/// <param name="diagnosticInfos">The diagnostic information.</param>
|
|
/// <param name="responseHeader">The response header.</param>
|
|
/// <returns>Result of the vaidation</returns>
|
|
public static ServiceResult ValidateDataValue(
|
|
DataValue value,
|
|
Type expectedType,
|
|
int index,
|
|
DiagnosticInfoCollection diagnosticInfos,
|
|
ResponseHeader responseHeader)
|
|
{
|
|
// check for null.
|
|
if (value == null)
|
|
{
|
|
return new ServiceResult(StatusCodes.BadUnexpectedError, "The server returned a value for a data value.");
|
|
}
|
|
|
|
// check status code.
|
|
if (StatusCode.IsBad(value.StatusCode))
|
|
{
|
|
return GetResult(value.StatusCode, index, diagnosticInfos, responseHeader);
|
|
}
|
|
|
|
// check data type.
|
|
if (expectedType != null)
|
|
{
|
|
if (!expectedType.IsInstanceOfType(value.Value))
|
|
{
|
|
return ServiceResult.Create(
|
|
StatusCodes.BadUnexpectedError,
|
|
"The server returned data value of type {0} when a value of type {1} was expected.",
|
|
(value.Value != null) ? value.Value.GetType().Name : "(null)",
|
|
expectedType.Name);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
private object m_lock = new object();
|
|
private ITransportChannel m_channel;
|
|
private NodeId m_authenticationToken;
|
|
private DiagnosticsMasks m_returnDiagnostics;
|
|
private int m_nextRequestHandle;
|
|
private int m_pendingRequestCount;
|
|
private bool m_disposed;
|
|
private bool m_useTransportChannel;
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// The client side interface with a UA server.
|
|
/// </summary>
|
|
public partial class SessionClient
|
|
{
|
|
#region IDisposable Implementation
|
|
/// <summary>
|
|
/// An overrideable version of the Dispose.
|
|
/// </summary>
|
|
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
m_sessionId = null;
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
/// <summary>
|
|
/// The server assigned identifier for the current session.
|
|
/// </summary>
|
|
/// <value>The session id.</value>
|
|
public NodeId SessionId
|
|
{
|
|
get
|
|
{
|
|
return m_sessionId;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Whether a session has beed created with the server.
|
|
/// </summary>
|
|
/// <value><c>true</c> if connected; otherwise, <c>false</c>.</value>
|
|
public bool Connected
|
|
{
|
|
get
|
|
{
|
|
return m_sessionId != null;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Protected Methods
|
|
/// <summary>
|
|
/// Called when a new session is created.
|
|
/// </summary>
|
|
/// <param name="sessionId">The session id.</param>
|
|
/// <param name="sessionCookie">The session cookie.</param>
|
|
public virtual void SessionCreated(NodeId sessionId, NodeId sessionCookie)
|
|
{
|
|
lock (m_lock)
|
|
{
|
|
m_sessionId = sessionId;
|
|
AuthenticationToken = sessionCookie;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
private object m_lock = new object();
|
|
private NodeId m_sessionId;
|
|
#endregion
|
|
}
|
|
}
|