Files
UA-.NETStandard/Libraries/Opc.Ua.Server/Server/StandardServer.cs
T
Randy Armstrong 5e627f2ca1 Post IOP Fixes.
2026-02-18 17:25:42 -08:00

3836 lines
156 KiB
C#

/* ========================================================================
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
*
* OPC Foundation MIT License 1.00
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* The complete license agreement can be found here:
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Opc.Ua.Bindings;
namespace Opc.Ua.Server
{
/// <inheritdoc/>
public class StandardServer : SessionServerBase, IStandardServer
{
/// <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)
{
// halt any outstanding timer.
if (m_registrationTimer != null)
{
Utils.SilentDispose(m_registrationTimer);
m_registrationTimer = null;
}
// close the watcher.
if (m_configurationWatcher != null)
{
Utils.SilentDispose(m_configurationWatcher);
m_configurationWatcher = null;
}
// close the server.
if (m_serverInternal != null)
{
Utils.SilentDispose(m_serverInternal);
m_serverInternal = null;
}
}
base.Dispose(disposing);
}
/// <summary>
/// Invokes the FindServers service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="endpointUrl">The endpoint URL.</param>
/// <param name="localeIds">The locale ids.</param>
/// <param name="serverUris">The server uris.</param>
/// <param name="ct">The cancellation token</param>
/// <returns>
/// Returns a <see cref="FindServersResponse"/> object
/// </returns>
public override async Task<FindServersResponse> FindServersAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
string endpointUrl,
StringCollection localeIds,
StringCollection serverUris,
CancellationToken ct)
{
ApplicationDescriptionCollection servers = [];
ValidateRequest(requestHeader);
await m_semaphoreSlim.WaitAsync(ct).ConfigureAwait(false);
try
{
// parse the url provided by the client.
IList<BaseAddress> baseAddresses = BaseAddresses;
Uri parsedEndpointUrl = Utils.ParseUri(endpointUrl);
if (parsedEndpointUrl != null)
{
baseAddresses = FilterByEndpointUrl(parsedEndpointUrl, baseAddresses);
}
// check if nothing to do.
if (baseAddresses.Count == 0)
{
servers = [];
return new FindServersResponse
{
ResponseHeader = CreateResponse(requestHeader, StatusCodes.Good),
Servers = servers
};
}
// build list of unique servers.
var uniqueServers = new Dictionary<string, ApplicationDescription>();
foreach (EndpointDescription description in GetEndpoints())
{
ApplicationDescription server = description.Server;
// skip servers that have been processed.
if (uniqueServers.ContainsKey(server.ApplicationUri))
{
continue;
}
// check client is filtering by server uri.
if (serverUris != null &&
serverUris.Count > 0 &&
!serverUris.Contains(server.ApplicationUri))
{
continue;
}
// localize the application name if requested.
LocalizedText applicationName = server.ApplicationName;
if (localeIds != null && localeIds.Count > 0)
{
applicationName = m_serverInternal.ResourceManager
.Translate(localeIds, applicationName);
}
// get the application description.
ApplicationDescription application = TranslateApplicationDescription(
parsedEndpointUrl,
server,
baseAddresses,
applicationName);
uniqueServers.Add(server.ApplicationUri, application);
// add to list of servers to return.
servers.Add(application);
}
}
finally
{
m_semaphoreSlim.Release();
}
return new FindServersResponse
{
ResponseHeader = CreateResponse(requestHeader, StatusCodes.Good),
Servers = servers
};
}
/// <summary>
/// Invokes the GetEndpoints service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="endpointUrl">The endpoint URL.</param>
/// <param name="localeIds">The locale ids.</param>
/// <param name="profileUris">The profile uris.</param>
/// <param name="ct">The cancellation token</param>
/// <returns>
/// Returns a <see cref="GetEndpointsResponse"/> object
/// </returns>
public override async Task<GetEndpointsResponse> GetEndpointsAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
string endpointUrl,
StringCollection localeIds,
StringCollection profileUris,
CancellationToken ct)
{
EndpointDescriptionCollection endpoints = null;
ValidateRequest(requestHeader);
await m_semaphoreSlim.WaitAsync(ct).ConfigureAwait(false);
try
{
// filter by profile.
IList<BaseAddress> baseAddresses = FilterByProfile(profileUris, BaseAddresses);
// get the descriptions.
endpoints = GetEndpointDescriptions(endpointUrl, baseAddresses, localeIds);
}
finally
{
m_semaphoreSlim.Release();
}
return new GetEndpointsResponse
{
ResponseHeader = CreateResponse(requestHeader, StatusCodes.Good),
Endpoints = endpoints
};
}
/// <summary>
/// Returns the endpoints that match the base address and endpoint url.
/// </summary>
protected EndpointDescriptionCollection GetEndpointDescriptions(
string endpointUrl,
IList<BaseAddress> baseAddresses,
StringCollection localeIds)
{
EndpointDescriptionCollection endpoints = null;
// parse the url provided by the client.
Uri parsedEndpointUrl = Utils.ParseUri(endpointUrl);
if (parsedEndpointUrl != null)
{
baseAddresses = FilterByEndpointUrl(parsedEndpointUrl, baseAddresses);
}
// check if nothing to do.
if (baseAddresses.Count != 0)
{
// localize the application name if requested.
LocalizedText applicationName = ServerDescription.ApplicationName;
if (localeIds != null && localeIds.Count > 0)
{
applicationName = m_serverInternal.ResourceManager
.Translate(localeIds, applicationName);
}
// translate the application description.
ApplicationDescription application = TranslateApplicationDescription(
parsedEndpointUrl,
ServerDescription,
baseAddresses,
applicationName);
// translate the endpoint descriptions.
endpoints = TranslateEndpointDescriptions(
parsedEndpointUrl,
baseAddresses,
Endpoints,
application);
}
return endpoints;
}
/// <inheritdoc/>
public override void ReportAuditOpenSecureChannelEvent(
string globalChannelId,
EndpointDescription endpointDescription,
OpenSecureChannelRequest request,
X509Certificate2 clientCertificate,
Exception exception)
{
ServerInternal?.ReportAuditOpenSecureChannelEvent(
globalChannelId,
endpointDescription,
request,
clientCertificate,
exception,
m_logger);
}
/// <inheritdoc/>
public override void ReportAuditCloseSecureChannelEvent(
string globalChannelId,
Exception exception)
{
ServerInternal?.ReportAuditCloseSecureChannelEvent(globalChannelId, exception, m_logger);
}
/// <inheritdoc/>
public override void ReportAuditCertificateEvent(
X509Certificate2 clientCertificate,
Exception exception)
{
ServerInternal?.ReportAuditCertificateEvent(clientCertificate, exception, m_logger);
}
/// <summary>
/// Invokes the CreateSession service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="clientDescription">Application description for the client application.</param>
/// <param name="serverUri">The server URI.</param>
/// <param name="endpointUrl">The endpoint URL.</param>
/// <param name="sessionName">Name for the Session assigned by the client.</param>
/// <param name="clientNonce">The client nonce.</param>
/// <param name="clientCertificate">The client certificate.</param>
/// <param name="requestedSessionTimeout">The requested session timeout.</param>
/// <param name="maxResponseMessageSize">Size of the max response message.</param>
/// <param name="ct">The cancellation token</param>
/// <returns>
/// Returns a <see cref="CreateSessionResponse"/> object
/// </returns>
public override async Task<CreateSessionResponse> CreateSessionAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
ApplicationDescription clientDescription,
string serverUri,
string endpointUrl,
string sessionName,
byte[] clientNonce,
byte[] clientCertificate,
double requestedSessionTimeout,
uint maxResponseMessageSize,
CancellationToken ct)
{
NodeId sessionId;
NodeId authenticationToken;
double revisedSessionTimeout = 0;
byte[] serverNonce;
byte[] serverCertificate = null;
EndpointDescriptionCollection serverEndpoints = null;
SignatureData serverSignature = null;
uint maxRequestMessageSize = (uint)MessageContext.MaxMessageSize;
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.CreateSession);
ISession session = null;
try
{
// check the server uri.
if (!string.IsNullOrEmpty(serverUri) && serverUri != Configuration.ApplicationUri)
{
throw new ServiceResultException(StatusCodes.BadServerUriInvalid);
}
bool requireEncryption = RequireEncryption(
context?.ChannelContext?.EndpointDescription);
if (!requireEncryption && clientCertificate != null)
{
requireEncryption = true;
}
X509Certificate2Collection clientIssuerCertificates = null;
// validate client application instance certificate.
X509Certificate2 parsedClientCertificate = null;
if (requireEncryption && clientCertificate != null && clientCertificate.Length > 0)
{
try
{
X509Certificate2Collection clientCertificateChain
= Utils.ParseCertificateChainBlob(
clientCertificate,
m_serverInternal.Telemetry);
parsedClientCertificate = clientCertificateChain[0];
if (clientCertificateChain.Count > 1)
{
clientIssuerCertificates = [];
for (int i = 1; i < clientCertificateChain.Count; i++)
{
clientIssuerCertificates.Add(clientCertificateChain[i]);
}
}
if (context.SecurityPolicyUri != SecurityPolicies.None)
{
// verify if applicationUri from ApplicationDescription matches the applicationUris in the client certificate.
if (!string.IsNullOrEmpty(clientDescription.ApplicationUri))
{
if (!X509Utils.CompareApplicationUriWithCertificate(parsedClientCertificate, clientDescription.ApplicationUri))
{
// report the AuditCertificateDataMismatch event for invalid uri
ServerInternal?.ReportAuditCertificateDataMismatchEvent(
parsedClientCertificate,
null,
clientDescription.ApplicationUri,
StatusCodes.BadCertificateUriInvalid,
m_logger);
throw ServiceResultException.Create(
StatusCodes.BadCertificateUriInvalid,
"The URI specified in the ApplicationDescription {0} does not match the URIs in the Certificate.",
clientDescription.ApplicationUri);
}
await CertificateValidator.ValidateAsync(clientCertificateChain, ct).ConfigureAwait(false);
}
}
}
catch (Exception e)
{
// report audit event for client certificate
ReportAuditCertificateEvent(parsedClientCertificate, e);
OnApplicationCertificateError(clientCertificate, new ServiceResult(e));
}
}
// verify the nonce provided by the client.
if (clientNonce != null)
{
if (clientNonce.Length < m_minNonceLength)
{
throw new ServiceResultException(StatusCodes.BadNonceInvalid);
}
// ignore nonce if security policy set to none
if (context.SecurityPolicyUri == SecurityPolicies.None)
{
clientNonce = null;
}
}
// load the certificate for the security profile
X509Certificate2 instanceCertificate = InstanceCertificateTypesProvider
.GetInstanceCertificate(
context.SecurityPolicyUri);
// create the session.
CreateSessionResult result = await ServerInternal.SessionManager.CreateSessionAsync(
context,
instanceCertificate,
sessionName,
clientNonce,
clientDescription,
endpointUrl,
parsedClientCertificate,
clientIssuerCertificates,
requestedSessionTimeout,
maxResponseMessageSize,
ct)
.ConfigureAwait(false);
session = result.Session;
sessionId = result.SessionId;
authenticationToken = result.AuthenticationToken;
serverNonce = result.ServerNonce;
revisedSessionTimeout = result.RevisedSessionTimeout;
if (endpointUrl != null)
{
try
{
// check the endpointurl
var configuredEndpoint = new ConfiguredEndpoint
{
EndpointUrl = new Uri(endpointUrl)
};
CertificateValidator.ValidateDomains(
instanceCertificate,
configuredEndpoint,
true);
}
catch (ServiceResultException sre)
when (sre.StatusCode == StatusCodes.BadCertificateHostNameInvalid)
{
m_logger.LogWarning(
"Server - Client connects with an endpointUrl [{EndpointUrl}] which does not match Server hostnames.",
endpointUrl);
ServerInternal.ReportAuditUrlMismatchEvent(
context?.AuditEntryId,
session,
revisedSessionTimeout,
endpointUrl,
m_logger);
}
}
var parameters =
ExtensionObject.ToEncodeable(
requestHeader.AdditionalHeader) as AdditionalParametersType;
if (parameters != null)
{
parameters = CreateSessionProcessAdditionalParameters(session, parameters);
}
await m_semaphoreSlim.WaitAsync(ct).ConfigureAwait(false);
try
{
// return the application instance certificate for the server.
if (requireEncryption)
{
// check if complete chain should be sent.
if (InstanceCertificateTypesProvider.SendCertificateChain)
{
serverCertificate = InstanceCertificateTypesProvider
.LoadCertificateChainRaw(
instanceCertificate);
}
else
{
serverCertificate = instanceCertificate.RawData;
}
}
// return the endpoints supported by the server.
serverEndpoints = GetEndpointDescriptions(endpointUrl, BaseAddresses, null);
// sign the nonce provided by the client.
serverSignature = null;
// sign the client nonce (if provided).
if (parsedClientCertificate != null && clientNonce != null)
{
SecurityPolicyInfo securityPolicy = SecurityPolicies.GetInfo(context.SecurityPolicyUri);
byte[] dataToSign = securityPolicy.GetServerSignatureData(
context.ChannelContext.ChannelThumbprint,
clientNonce,
context.ChannelContext.ServerChannelCertificate,
parsedClientCertificate.RawData,
context.ChannelContext.ClientChannelCertificate,
serverNonce);
serverSignature = SecurityPolicies.CreateSignatureData(
context.SecurityPolicyUri,
instanceCertificate,
dataToSign);
}
}
finally
{
m_semaphoreSlim.Release();
}
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.CurrentSessionCount++;
ServerInternal.ServerDiagnostics.CumulatedSessionCount++;
}
m_logger.LogInformation("Server - SESSION CREATED. SessionId={SessionId}", sessionId);
// report audit for successful create session
ServerInternal.ReportAuditCreateSessionEvent(
context?.AuditEntryId,
session,
revisedSessionTimeout,
m_logger);
ResponseHeader responseHeader = CreateResponse(requestHeader, StatusCodes.Good);
if (parameters != null)
{
responseHeader.AdditionalHeader = new ExtensionObject(parameters);
}
return new CreateSessionResponse
{
ResponseHeader = responseHeader,
SessionId = sessionId,
AuthenticationToken = authenticationToken,
RevisedSessionTimeout = revisedSessionTimeout,
ServerNonce = serverNonce,
ServerCertificate = serverCertificate,
ServerEndpoints = serverEndpoints,
ServerSignature = serverSignature,
MaxRequestMessageSize = maxRequestMessageSize
};
}
catch (ServiceResultException e)
{
m_logger.LogError("Server - SESSION CREATE failed. {ErrorMessage}", e.Message);
// report the failed AuditCreateSessionEvent
ServerInternal.ReportAuditCreateSessionEvent(
context?.AuditEntryId,
session,
revisedSessionTimeout,
m_logger,
e);
if (session != null)
{
ServerInternal.SessionManager.CloseSession(session.Id);
}
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedSessionCount++;
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedSessionCount++;
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException((DiagnosticsMasks)requestHeader.ReturnDiagnostics, [], e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Process additional parameters during the ECC session creation and set the session's UserToken security policy
/// </summary>
/// <param name="session">The session</param>
/// <param name="parameters">The additional parameters for the session</param>
/// <returns>An AdditionalParametersType object containing the processed parameters</returns>
protected virtual AdditionalParametersType CreateSessionProcessAdditionalParameters(
ISession session,
AdditionalParametersType parameters)
{
AdditionalParametersType response = null;
if (parameters != null && parameters.Parameters != null)
{
response = new AdditionalParametersType();
foreach (KeyValuePair ii in parameters.Parameters)
{
if (ii.Key == AdditionalParameterNames.ECDHPolicyUri)
{
string policyUri = ii.Value.ToString();
m_logger.LogWarning("Received request for new EphmeralKey using {SecurityPolicyUri}.", policyUri);
var securityPolicy = SecurityPolicies.GetInfo(policyUri);
if (securityPolicy.EphemeralKeyAlgorithm != CertificateKeyAlgorithm.None)
{
session.SetUserTokenSecurityPolicy(policyUri);
EphemeralKeyType key = session.GetNewEphemeralKey();
response.Parameters.Add(
new KeyValuePair
{
Key = AdditionalParameterNames.ECDHKey,
Value = new ExtensionObject(key)
});
m_logger.LogWarning("Returning new EphmeralKey: {PublicKey} bytes.", key.PublicKey?.Length ?? 0);
}
else
{
response.Parameters.Add(
new KeyValuePair
{
Key = AdditionalParameterNames.ECDHKey,
Value = StatusCodes.BadSecurityPolicyRejected
});
m_logger.LogWarning("Rejecting request for new EphmeralKey using {SecurityPolicyUri}.", policyUri);
}
}
}
}
return response;
}
/// <summary>
/// Process additional parameters during ECC session activation
/// </summary>
/// <param name="session">The session</param>
/// <param name="parameters">The additional parameters for the session</param>
/// <returns>An AdditionalParametersType object containing the processed parameters</returns>
protected virtual AdditionalParametersType ActivateSessionProcessAdditionalParameters(
ISession session,
AdditionalParametersType parameters)
{
AdditionalParametersType response = null;
EphemeralKeyType key = session.GetNewEphemeralKey();
if (key != null)
{
response = new AdditionalParametersType();
response.Parameters
.Add(new KeyValuePair { Key = AdditionalParameterNames.ECDHKey, Value = new ExtensionObject(key) });
m_logger.LogWarning("Returning new EphmeralKey: {PublicKey} bytes.", key.PublicKey?.Length ?? 0);
}
return response;
}
/// <summary>
/// Invokes the ActivateSession service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="clientSignature">The client signature.</param>
/// <param name="clientSoftwareCertificates">The client software certificates.</param>
/// <param name="localeIds">The locale ids.</param>
/// <param name="userIdentityToken">The user identity token.</param>
/// <param name="userTokenSignature">The user token signature.</param>
/// <param name="ct">The cancellationToken</param>
/// <returns>
/// Returns a <see cref="ActivateSessionResponse"/> object
/// </returns>
public override async Task<ActivateSessionResponse> ActivateSessionAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
SignatureData clientSignature,
SignedSoftwareCertificateCollection clientSoftwareCertificates,
StringCollection localeIds,
ExtensionObject userIdentityToken,
SignatureData userTokenSignature,
CancellationToken ct)
{
byte[] serverNonce;
StatusCodeCollection results = null;
DiagnosticInfoCollection diagnosticInfos = null;
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.ActivateSession);
try
{
// activate the session.
(bool identityChanged, serverNonce) = await ServerInternal.SessionManager.ActivateSessionAsync(
context,
requestHeader.AuthenticationToken,
clientSignature,
userIdentityToken,
userTokenSignature,
localeIds,
ct)
.ConfigureAwait(false);
if (identityChanged)
{
// TBD - call Node Manager and Subscription Manager.
}
ISession session = ServerInternal.SessionManager
.GetSession(requestHeader.AuthenticationToken);
var parameters =
ExtensionObject.ToEncodeable(
requestHeader.AdditionalHeader) as AdditionalParametersType;
parameters = ActivateSessionProcessAdditionalParameters(session, parameters);
m_logger.LogInformation("Server - SESSION ACTIVATED.");
// report the audit event for session activate
ServerInternal.ReportAuditActivateSessionEvent(
m_logger,
context?.AuditEntryId,
session);
ResponseHeader responseHeader = CreateResponse(requestHeader, StatusCodes.Good);
if (parameters != null)
{
responseHeader.AdditionalHeader = new ExtensionObject(parameters);
}
return new ActivateSessionResponse
{
ResponseHeader = responseHeader,
ServerNonce = serverNonce,
Results = results,
DiagnosticInfos = diagnosticInfos
};
}
catch (ServiceResultException e)
{
m_logger.LogInformation("Server - SESSION ACTIVATE failed. {ErrorMessage}", e.Message);
// report the audit event for failed session activate
ISession session = ServerInternal.SessionManager
.GetSession(requestHeader.AuthenticationToken);
ServerInternal.ReportAuditActivateSessionEvent(
m_logger,
context?.AuditEntryId,
session,
e);
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedSessionCount++;
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedSessionCount++;
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(
(DiagnosticsMasks)requestHeader.ReturnDiagnostics,
localeIds,
e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Returns whether the error is a security error.
/// </summary>
/// <param name="error">The error.</param>
/// <returns>
/// <c>true</c> if the error is one of the security errors, otherwise <c>false</c>.
/// </returns>
protected bool IsSecurityError(StatusCode error)
{
switch (error.CodeBits)
{
case StatusCodes.BadUserSignatureInvalid:
case StatusCodes.BadUserAccessDenied:
case StatusCodes.BadSecurityPolicyRejected:
case StatusCodes.BadSecurityModeRejected:
case StatusCodes.BadSecurityChecksFailed:
case StatusCodes.BadSecureChannelTokenUnknown:
case StatusCodes.BadSecureChannelIdInvalid:
case StatusCodes.BadNoValidCertificates:
case StatusCodes.BadIdentityTokenInvalid:
case StatusCodes.BadIdentityTokenRejected:
case StatusCodes.BadIdentityChangeNotSupported:
case StatusCodes.BadCertificateUseNotAllowed:
case StatusCodes.BadCertificateUriInvalid:
case StatusCodes.BadCertificateUntrusted:
case StatusCodes.BadCertificateTimeInvalid:
case StatusCodes.BadCertificateRevoked:
case StatusCodes.BadCertificateRevocationUnknown:
case StatusCodes.BadCertificateIssuerUseNotAllowed:
case StatusCodes.BadCertificateIssuerTimeInvalid:
case StatusCodes.BadCertificateIssuerRevoked:
case StatusCodes.BadCertificateIssuerRevocationUnknown:
case StatusCodes.BadCertificateInvalid:
case StatusCodes.BadCertificateHostNameInvalid:
case StatusCodes.BadCertificatePolicyCheckFailed:
case StatusCodes.BadApplicationSignatureInvalid:
return true;
default:
return false;
}
}
/// <summary>
/// Creates the response header.
/// </summary>
/// <param name="requestHeader">The object that contains description for the RequestHeader DataType.</param>
/// <param name="exception">The exception used to create DiagnosticInfo assigned to the ServiceDiagnostics.</param>
/// <returns>Returns a description for the ResponseHeader DataType. </returns>
protected ResponseHeader CreateResponse(
RequestHeader requestHeader,
ServiceResultException exception)
{
var responseHeader = new ResponseHeader
{
ServiceResult = exception.StatusCode,
Timestamp = DateTime.UtcNow,
RequestHandle = requestHeader.RequestHandle
};
var stringTable = new StringTable();
responseHeader.ServiceDiagnostics = new DiagnosticInfo(
exception,
(DiagnosticsMasks)requestHeader.ReturnDiagnostics,
true,
stringTable,
m_logger);
responseHeader.StringTable = stringTable.ToArray();
return responseHeader;
}
/// <summary>
/// Invokes the CloseSession service using a task based request.
/// </summary>
/// <param name="secureChannelContext">The secure channel context</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="deleteSubscriptions">if set to <c>true</c> subscriptions are deleted.</param>
/// <param name="ct">The cancellation token.</param>
public override async Task<CloseSessionResponse> CloseSessionAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
bool deleteSubscriptions,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.CloseSession);
try
{
ISession session = ServerInternal.SessionManager
.GetSession(requestHeader.AuthenticationToken);
await ServerInternal.CloseSessionAsync(context, context.Session.Id, deleteSubscriptions, ct)
.ConfigureAwait(false);
// report the audit event for close session
ServerInternal.ReportAuditCloseSessionEvent(
context.AuditEntryId,
session,
m_logger,
"Session/CloseSession");
return new CloseSessionResponse
{
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the Cancel service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="requestHandle">The request handle assigned to the request.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="CancelResponse"/> object
/// </returns>
public override Task<CancelResponse> CancelAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
uint requestHandle,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.Cancel);
try
{
m_serverInternal.RequestManager.CancelRequests(requestHandle, out uint cancelCount);
return Task.FromResult(new CancelResponse
{
ResponseHeader = CreateResponse(requestHeader, context.StringTable),
CancelCount = cancelCount
});
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the Browse service using async Task based request.
/// </summary>
public override async Task<BrowseResponse> BrowseAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
ViewDescription view,
uint requestedMaxReferencesPerNode,
BrowseDescriptionCollection nodesToBrowse,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.Browse);
try
{
ValidateOperationLimits(nodesToBrowse, OperationLimits.MaxNodesPerBrowse);
(BrowseResultCollection results, DiagnosticInfoCollection diagnosticInfos) =
await m_serverInternal.NodeManager.BrowseAsync(
context,
view,
requestedMaxReferencesPerNode,
nodesToBrowse,
ct)
.ConfigureAwait(false);
return new BrowseResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the BrowseNext service using async Task based request.
/// </summary>
public override async Task<BrowseNextResponse> BrowseNextAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
bool releaseContinuationPoints,
ByteStringCollection continuationPoints,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.BrowseNext);
try
{
ValidateOperationLimits(continuationPoints, OperationLimits.MaxNodesPerBrowse);
(BrowseResultCollection results, DiagnosticInfoCollection diagnosticInfos) =
await m_serverInternal.NodeManager.BrowseNextAsync(
context,
releaseContinuationPoints,
continuationPoints,
ct)
.ConfigureAwait(false);
return new BrowseNextResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the RegisterNodes service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="nodesToRegister">The list of NodeIds to register.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="RegisterNodesResponse"/> object
/// </returns>
public override Task<RegisterNodesResponse> RegisterNodesAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
NodeIdCollection nodesToRegister,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.RegisterNodes);
try
{
ValidateOperationLimits(nodesToRegister, OperationLimits.MaxNodesPerRegisterNodes);
m_serverInternal.NodeManager
.RegisterNodes(context, nodesToRegister, out NodeIdCollection registeredNodeIds);
return Task.FromResult(new RegisterNodesResponse
{
ResponseHeader = CreateResponse(requestHeader, context.StringTable),
RegisteredNodeIds = registeredNodeIds
});
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the UnregisterNodes service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="nodesToUnregister">The list of NodeIds to unregister</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="UnregisterNodesResponse"/> object
/// </returns>
public override Task<UnregisterNodesResponse> UnregisterNodesAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
NodeIdCollection nodesToUnregister,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.UnregisterNodes);
try
{
ValidateOperationLimits(
nodesToUnregister,
OperationLimits.MaxNodesPerRegisterNodes);
m_serverInternal.NodeManager.UnregisterNodes(context, nodesToUnregister);
return Task.FromResult(new UnregisterNodesResponse
{
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
});
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the TranslateBrowsePathsToNodeIds service using async Task based request.
/// </summary>
public override async Task<TranslateBrowsePathsToNodeIdsResponse> TranslateBrowsePathsToNodeIdsAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
BrowsePathCollection browsePaths,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.TranslateBrowsePathsToNodeIds);
try
{
ValidateOperationLimits(
browsePaths,
OperationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds);
foreach (BrowsePath bp in browsePaths)
{
ValidateOperationLimits(
bp.RelativePath.Elements.Count,
OperationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds);
}
(BrowsePathResultCollection results, DiagnosticInfoCollection diagnosticInfos) =
await m_serverInternal.NodeManager.TranslateBrowsePathsToNodeIdsAsync(
context,
browsePaths,
ct)
.ConfigureAwait(false);
return new TranslateBrowsePathsToNodeIdsResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the Read service using async Task based request.
/// </summary>
public override async Task<ReadResponse> ReadAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
double maxAge,
TimestampsToReturn timestampsToReturn,
ReadValueIdCollection nodesToRead,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.Read);
try
{
ValidateOperationLimits(nodesToRead, OperationLimits.MaxNodesPerRead);
(DataValueCollection results, DiagnosticInfoCollection diagnosticInfos) =
await m_serverInternal.NodeManager.ReadAsync(
context,
maxAge,
timestampsToReturn,
nodesToRead,
ct).ConfigureAwait(false);
return new ReadResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
ServerInternal.ReportAuditEvent(context, "Read", e, m_logger);
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the HistoryRead service using async Task based request.
/// </summary>
public override async Task<HistoryReadResponse> HistoryReadAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
ExtensionObject historyReadDetails,
TimestampsToReturn timestampsToReturn,
bool releaseContinuationPoints,
HistoryReadValueIdCollection nodesToRead,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.HistoryRead);
try
{
if (historyReadDetails?.Body is ReadEventDetails)
{
ValidateOperationLimits(
nodesToRead,
OperationLimits.MaxNodesPerHistoryReadEvents);
}
else
{
ValidateOperationLimits(
nodesToRead,
OperationLimits.MaxNodesPerHistoryReadData);
}
(HistoryReadResultCollection results, DiagnosticInfoCollection diagnosticInfos) =
await m_serverInternal.NodeManager.HistoryReadAsync(
context,
historyReadDetails,
timestampsToReturn,
releaseContinuationPoints,
nodesToRead,
ct)
.ConfigureAwait(false);
return new HistoryReadResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
ServerInternal.ReportAuditEvent(context, "HistoryRead", e, m_logger);
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the Write service using async Task based request.
/// </summary>
public override async Task<WriteResponse> WriteAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
WriteValueCollection nodesToWrite,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.Write);
try
{
ValidateOperationLimits(nodesToWrite, OperationLimits.MaxNodesPerWrite);
(StatusCodeCollection results, DiagnosticInfoCollection diagnosticInfos) =
await m_serverInternal.NodeManager
.WriteAsync(context, nodesToWrite, ct)
.ConfigureAwait(false);
return new WriteResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the HistoryUpdate service using async Task based request.
/// </summary>
public override async Task<HistoryUpdateResponse> HistoryUpdateAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
ExtensionObjectCollection historyUpdateDetails,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.HistoryUpdate);
try
{
// check only for BadNothingToDo here
// MaxNodesPerHistoryUpdateEvents & MaxNodesPerHistoryUpdateData
// must be checked in NodeManager (TODO)
ValidateOperationLimits(historyUpdateDetails);
(HistoryUpdateResultCollection results, DiagnosticInfoCollection diagnosticInfos) =
await m_serverInternal.NodeManager.HistoryUpdateAsync(context, historyUpdateDetails, ct).ConfigureAwait(false);
return new HistoryUpdateResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the CreateSubscription service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="requestedPublishingInterval">The cyclic rate that the Subscription is being requested to return Notifications to the Client.</param>
/// <param name="requestedLifetimeCount">The client-requested lifetime count for the Subscription</param>
/// <param name="requestedMaxKeepAliveCount">The requested max keep alive count.</param>
/// <param name="maxNotificationsPerPublish">The maximum number of notifications that the Client wishes to receive in a single Publish response.</param>
/// <param name="publishingEnabled">If set to <c>true</c> publishing is enabled for the Subscription.</param>
/// <param name="priority">The relative priority of the Subscription.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="CreateSubscriptionResponse"/> object
/// </returns>
public override async Task<CreateSubscriptionResponse> CreateSubscriptionAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
double requestedPublishingInterval,
uint requestedLifetimeCount,
uint requestedMaxKeepAliveCount,
uint maxNotificationsPerPublish,
bool publishingEnabled,
byte priority,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.CreateSubscription);
try
{
CreateSubscriptionResponse response = await ServerInternal.SubscriptionManager.CreateSubscriptionAsync(
context,
requestedPublishingInterval,
requestedLifetimeCount,
requestedMaxKeepAliveCount,
maxNotificationsPerPublish,
publishingEnabled,
priority,
ct).ConfigureAwait(false);
response.ResponseHeader = CreateResponse(requestHeader, context.StringTable);
return response;
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the TransferSubscriptions service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionIds">The list of Subscriptions to transfer.</param>
/// <param name="sendInitialValues">If the initial values should be sent.</param>
/// <param name="ct">The cancellation token.</param>
public override async Task<TransferSubscriptionsResponse> TransferSubscriptionsAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
UInt32Collection subscriptionIds,
bool sendInitialValues,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.TransferSubscriptions);
try
{
ValidateOperationLimits(subscriptionIds);
TransferSubscriptionsResponse response = await ServerInternal.SubscriptionManager.TransferSubscriptionsAsync(
context,
subscriptionIds,
sendInitialValues,
ct).ConfigureAwait(false);
response.ResponseHeader = CreateResponse(requestHeader, context.StringTable);
return response;
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the DeleteSubscriptions service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionIds">The list of Subscriptions to delete.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="DeleteSubscriptionsResponse"/> object
/// </returns>
public override async Task<DeleteSubscriptionsResponse> DeleteSubscriptionsAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
UInt32Collection subscriptionIds,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.DeleteSubscriptions);
try
{
ValidateOperationLimits(subscriptionIds);
DeleteSubscriptionsResponse response = await ServerInternal.SubscriptionManager.DeleteSubscriptionsAsync(
context,
subscriptionIds,
ct).ConfigureAwait(false);
response.ResponseHeader = CreateResponse(requestHeader, context.StringTable);
return response;
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the Publish service using async Task based request.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionAcknowledgements">The list of acknowledgements for one or more Subscriptions.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="PublishResponse"/>
/// </returns>
public override async Task<PublishResponse> PublishAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
SubscriptionAcknowledgementCollection subscriptionAcknowledgements,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.Publish);
try
{
/*
// check if there is an odd delay.
if (DateTime.UtcNow > requestHeader.Timestamp.AddMilliseconds(100))
{
m_logger.LogTrace(m_eventId,
"WARNING. Unexpected delay receiving Publish request. Time={0:hh:mm:ss.fff}, ReceiveTime={1:hh:mm:ss.fff}",
DateTime.UtcNow,
requestHeader.Timestamp);
}
*/
m_logger.LogTrace(
"PUBLISH #{RequestHandle} RECEIVED. TIME={Timestamp:hh:mm:ss.fff}",
requestHeader.RequestHandle,
requestHeader.Timestamp);
PublishResponse response = await ServerInternal.SubscriptionManager.PublishAsync(
context,
subscriptionAcknowledgements,
ct).ConfigureAwait(false);
response.ResponseHeader = CreateResponse(requestHeader, context.StringTable);
/*
if (response.NotificationMessage != null)
{
m_logger.LogTrace(m_eventId,
"PublishResponse: SubId={0} SeqNo={1}, PublishTime={2:mm:ss.fff}, Time={3:mm:ss.fff}",
subscriptionId,
notificationMessage.SequenceNumber,
notificationMessage.PublishTime,
DateTime.UtcNow);
}
*/
return response;
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the Republish service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionId">The subscription id.</param>
/// <param name="retransmitSequenceNumber">The sequence number of a specific NotificationMessage to be republished.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="RepublishResponse"/> object
/// </returns>
public override Task<RepublishResponse> RepublishAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
uint subscriptionId,
uint retransmitSequenceNumber,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.Republish);
try
{
NotificationMessage notificationMessage = ServerInternal.SubscriptionManager.Republish(
context,
subscriptionId,
retransmitSequenceNumber);
return Task.FromResult(new RepublishResponse
{
ResponseHeader = CreateResponse(requestHeader, context.StringTable),
NotificationMessage = notificationMessage
});
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the ModifySubscription service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionId">The subscription id.</param>
/// <param name="requestedPublishingInterval">The cyclic rate that the Subscription is being requested to return Notifications to the Client.</param>
/// <param name="requestedLifetimeCount">The client-requested lifetime count for the Subscription.</param>
/// <param name="requestedMaxKeepAliveCount">The requested max keep alive count.</param>
/// <param name="maxNotificationsPerPublish">The maximum number of notifications that the Client wishes to receive in a single Publish response.</param>
/// <param name="priority">The relative priority of the Subscription.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="ModifySubscriptionResponse"/> object
/// </returns>
public override Task<ModifySubscriptionResponse> ModifySubscriptionAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
uint subscriptionId,
double requestedPublishingInterval,
uint requestedLifetimeCount,
uint requestedMaxKeepAliveCount,
uint maxNotificationsPerPublish,
byte priority,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.ModifySubscription);
try
{
ServerInternal.SubscriptionManager.ModifySubscription(
context,
subscriptionId,
requestedPublishingInterval,
requestedLifetimeCount,
requestedMaxKeepAliveCount,
maxNotificationsPerPublish,
priority,
out double revisedPublishingInterval,
out uint revisedLifetimeCount,
out uint revisedMaxKeepAliveCount);
return Task.FromResult(new ModifySubscriptionResponse
{
RevisedPublishingInterval = revisedPublishingInterval,
RevisedLifetimeCount = revisedLifetimeCount,
RevisedMaxKeepAliveCount = revisedMaxKeepAliveCount,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
});
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the SetPublishingMode service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="publishingEnabled">If set to <c>true</c> publishing of NotificationMessages is enabled for the Subscription.</param>
/// <param name="subscriptionIds">The list of subscription ids.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="SetPublishingModeResponse"/> object
/// </returns>
public override Task<SetPublishingModeResponse> SetPublishingModeAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
bool publishingEnabled,
UInt32Collection subscriptionIds,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.SetPublishingMode);
try
{
ValidateOperationLimits(subscriptionIds);
ServerInternal.SubscriptionManager.SetPublishingMode(
context,
publishingEnabled,
subscriptionIds,
out StatusCodeCollection results,
out DiagnosticInfoCollection diagnosticInfos);
return Task.FromResult(new SetPublishingModeResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
});
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the SetTriggering service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionId">The subscription id.</param>
/// <param name="triggeringItemId">The id for the MonitoredItem used as the triggering item.</param>
/// <param name="linksToAdd">The list of ids of the items to report that are to be added as triggering links.</param>
/// <param name="linksToRemove">The list of ids of the items to report for the triggering links to be deleted.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="SetTriggeringResponse"/> object
/// </returns>
public override Task<SetTriggeringResponse> SetTriggeringAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
uint subscriptionId,
uint triggeringItemId,
UInt32Collection linksToAdd,
UInt32Collection linksToRemove,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.SetTriggering);
try
{
if ((linksToAdd == null || linksToAdd.Count == 0) &&
(linksToRemove == null || linksToRemove.Count == 0))
{
throw new ServiceResultException(StatusCodes.BadNothingToDo);
}
int monitoredItemsCount = 0;
monitoredItemsCount += (linksToAdd?.Count) ?? 0;
monitoredItemsCount += (linksToRemove?.Count) ?? 0;
ValidateOperationLimits(
monitoredItemsCount,
OperationLimits.MaxMonitoredItemsPerCall);
ServerInternal.SubscriptionManager.SetTriggering(
context,
subscriptionId,
triggeringItemId,
linksToAdd,
linksToRemove,
out StatusCodeCollection addResults,
out DiagnosticInfoCollection addDiagnosticInfos,
out StatusCodeCollection removeResults,
out DiagnosticInfoCollection removeDiagnosticInfos);
return Task.FromResult(new SetTriggeringResponse
{
AddResults = addResults,
AddDiagnosticInfos = addDiagnosticInfos,
RemoveResults = removeResults,
RemoveDiagnosticInfos = removeDiagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
});
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the CreateMonitoredItems service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionId">The subscription id that will report notifications.</param>
/// <param name="timestampsToReturn">The type of timestamps to be returned for the MonitoredItems.</param>
/// <param name="itemsToCreate">The list of MonitoredItems to be created and assigned to the specified subscription</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="CreateMonitoredItemsResponse"/> object
/// </returns>
public override async Task<CreateMonitoredItemsResponse> CreateMonitoredItemsAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
uint subscriptionId,
TimestampsToReturn timestampsToReturn,
MonitoredItemCreateRequestCollection itemsToCreate,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.CreateMonitoredItems);
try
{
ValidateOperationLimits(itemsToCreate, OperationLimits.MaxMonitoredItemsPerCall);
CreateMonitoredItemsResponse result = await ServerInternal.SubscriptionManager.CreateMonitoredItemsAsync(
context,
subscriptionId,
timestampsToReturn,
itemsToCreate,
ct).ConfigureAwait(false);
result.ResponseHeader = CreateResponse(requestHeader, context.StringTable);
return result;
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the ModifyMonitoredItems service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionId">The subscription id.</param>
/// <param name="timestampsToReturn">The type of timestamps to be returned for the MonitoredItems.</param>
/// <param name="itemsToModify">The list of MonitoredItems to modify.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="ModifyMonitoredItemsResponse"/> object
/// </returns>
public override async Task<ModifyMonitoredItemsResponse> ModifyMonitoredItemsAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
uint subscriptionId,
TimestampsToReturn timestampsToReturn,
MonitoredItemModifyRequestCollection itemsToModify,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.ModifyMonitoredItems);
try
{
ValidateOperationLimits(itemsToModify, OperationLimits.MaxMonitoredItemsPerCall);
ModifyMonitoredItemsResponse response = await ServerInternal.SubscriptionManager.ModifyMonitoredItemsAsync(
context,
subscriptionId,
timestampsToReturn,
itemsToModify,
ct).ConfigureAwait(false);
response.ResponseHeader = CreateResponse(requestHeader, context.StringTable);
return response;
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the DeleteMonitoredItems service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionId">The subscription id.</param>
/// <param name="monitoredItemIds">The list of MonitoredItems to delete.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="DeleteMonitoredItemsResponse"/> object
/// </returns>
public override async Task<DeleteMonitoredItemsResponse> DeleteMonitoredItemsAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
uint subscriptionId,
UInt32Collection monitoredItemIds,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.DeleteMonitoredItems);
try
{
ValidateOperationLimits(monitoredItemIds, OperationLimits.MaxMonitoredItemsPerCall);
DeleteMonitoredItemsResponse response = await ServerInternal.SubscriptionManager.DeleteMonitoredItemsAsync(
context,
subscriptionId,
monitoredItemIds,
ct).ConfigureAwait(false);
response.ResponseHeader = CreateResponse(requestHeader, context.StringTable);
return response;
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the SetMonitoringMode service.
/// </summary>
/// <param name="secureChannelContext">The secure channel context</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="subscriptionId">The subscription id.</param>
/// <param name="monitoringMode">The monitoring mode to be set for the MonitoredItems.</param>
/// <param name="monitoredItemIds">The list of MonitoredItems to modify.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>
/// Returns a <see cref="SetMonitoringModeResponse"/>
/// </returns>
public override async Task<SetMonitoringModeResponse> SetMonitoringModeAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
uint subscriptionId,
MonitoringMode monitoringMode,
UInt32Collection monitoredItemIds,
CancellationToken ct)
{
OperationContext context = ValidateRequest(
secureChannelContext,
requestHeader,
RequestType.SetMonitoringMode);
try
{
ValidateOperationLimits(monitoredItemIds, OperationLimits.MaxMonitoredItemsPerCall);
(StatusCodeCollection results, DiagnosticInfoCollection diagnosticInfos) =
await ServerInternal.SubscriptionManager.SetMonitoringModeAsync(
context,
subscriptionId,
monitoringMode,
monitoredItemIds,
ct)
.ConfigureAwait(false);
return new SetMonitoringModeResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <summary>
/// Invokes the Call service using async Task based request.
/// </summary>
/// <param name="secureChannelContext">The secure channel context</param>
/// <param name="requestHeader">The request header.</param>
/// <param name="methodsToCall">The methods to call.</param>
/// <param name="ct">The cancellation token</param>
/// <returns>
/// Returns a <see cref="ResponseHeader"/> object
/// </returns>
public override async Task<CallResponse> CallAsync(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
CallMethodRequestCollection methodsToCall,
CancellationToken ct)
{
OperationContext context = ValidateRequest(secureChannelContext, requestHeader, RequestType.Call);
try
{
ValidateOperationLimits(methodsToCall, OperationLimits.MaxNodesPerMethodCall);
(CallMethodResultCollection results, DiagnosticInfoCollection diagnosticInfos) =
await m_serverInternal.NodeManager.CallAsync(context, methodsToCall, ct)
.ConfigureAwait(false);
return new CallResponse
{
Results = results,
DiagnosticInfos = diagnosticInfos,
ResponseHeader = CreateResponse(requestHeader, context.StringTable)
};
}
catch (ServiceResultException e)
{
lock (ServerInternal.DiagnosticsWriteLock)
{
ServerInternal.ServerDiagnostics.RejectedRequestsCount++;
if (IsSecurityError(e.StatusCode))
{
ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++;
}
}
throw TranslateException(context, e);
}
finally
{
OnRequestComplete(context);
}
}
/// <inheritdoc/>
public IServerInternal CurrentInstance
{
get
{
m_semaphoreSlim.Wait();
try
{
if (m_serverInternal == null)
{
throw new ServiceResultException(StatusCodes.BadServerHalted);
}
return m_serverInternal;
}
finally
{
m_semaphoreSlim.Release();
}
}
}
/// <summary>
/// Returns the current status of the server.
/// </summary>
/// <returns>Returns a ServerStatusDataType object</returns>
/// <exception cref="ServiceResultException"></exception>
[Obsolete(
"No longer thread safe. To read the value use CurrentState, to write use CurrentInstance.UpdateServerStatus."
)]
public ServerStatusDataType GetStatus()
{
lock (Lock)
{
if (m_serverInternal == null)
{
throw new ServiceResultException(StatusCodes.BadServerHalted);
}
return m_serverInternal.Status.Value;
}
}
/// <inheritdoc/>
public ServerState CurrentState => m_serverInternal.CurrentState;
/// <summary>
/// Registers the server with the discovery server.
/// </summary>
/// <returns>Boolean value.</returns>
[Obsolete("Use RegisterWithDiscoveryServerAsync instead.")]
public bool RegisterWithDiscoveryServer()
{
return RegisterWithDiscoveryServerAsync().AsTask().GetAwaiter().GetResult();
}
/// <inheritdoc/>
public async ValueTask<bool> RegisterWithDiscoveryServerAsync(CancellationToken ct = default)
{
var configuration = new ApplicationConfiguration(Configuration);
// use a dedicated certificate validator with the registration, but derive behavior from server config
configuration.CertificateValidator = new CertificateValidator(MessageContext.Telemetry);
await configuration
.CertificateValidator.UpdateAsync(
configuration.SecurityConfiguration,
configuration.ApplicationUri,
ct)
.ConfigureAwait(false);
// try each endpoint.
if (m_registrationEndpoints != null)
{
foreach (ConfiguredEndpoint endpoint in m_registrationEndpoints.Endpoints)
{
RegistrationClient client = null;
int i = 0;
while (i++ < 2)
{
try
{
// update from the server.
bool updateRequired = true;
lock (m_registrationLock)
{
updateRequired = endpoint.UpdateBeforeConnect;
}
if (updateRequired)
{
await endpoint.UpdateFromServerAsync(MessageContext.Telemetry, ct).ConfigureAwait(false);
}
lock (m_registrationLock)
{
endpoint.UpdateBeforeConnect = false;
}
var requestHeader = new RequestHeader
{
Timestamp = DateTime.UtcNow
};
// create the client.
X509Certificate2 instanceCertificate =
InstanceCertificateTypesProvider.GetInstanceCertificate(
endpoint.Description?.SecurityPolicyUri ??
SecurityPolicies.None);
client = await RegistrationClient.CreateAsync(
configuration,
endpoint.Description,
endpoint.Configuration,
instanceCertificate,
ct: ct).ConfigureAwait(false);
client.OperationTimeout = 10000;
// register the server.
if (m_useRegisterServer2)
{
var discoveryConfiguration = new ExtensionObjectCollection();
var mdnsDiscoveryConfig = new MdnsDiscoveryConfiguration
{
ServerCapabilities = configuration.ServerConfiguration
.ServerCapabilities,
MdnsServerName = Utils.GetHostName()
};
var extensionObject = new ExtensionObject(mdnsDiscoveryConfig);
discoveryConfiguration.Add(extensionObject);
await client.RegisterServer2Async(
requestHeader,
m_registrationInfo,
discoveryConfiguration,
ct).ConfigureAwait(false);
}
else
{
await client.RegisterServerAsync(
requestHeader,
m_registrationInfo,
ct)
.ConfigureAwait(false);
}
m_registeredWithDiscoveryServer = m_registrationInfo.IsOnline;
return true;
}
catch (Exception e)
{
m_logger.LogWarning(
"RegisterServer{Api} failed for {EndpointUrl}. Exception={ErrorMessage}",
m_useRegisterServer2 ? "2" : string.Empty,
endpoint.EndpointUrl,
e.Message);
m_useRegisterServer2 = !m_useRegisterServer2;
}
finally
{
if (client != null)
{
try
{
await client.CloseAsync(ct).ConfigureAwait(false);
client = null;
}
catch (Exception e)
{
m_logger.LogWarning(
"Could not cleanly close connection with LDS. Exception={ErrorMessage}",
e.Message);
}
}
}
}
}
// retry to start with RegisterServer2 if both failed
m_useRegisterServer2 = true;
}
m_registeredWithDiscoveryServer = false;
return false;
}
/// <summary>
/// Registers the server endpoints with the LDS.
/// </summary>
/// <param name="state">The state.</param>
private async void OnRegisterServerAsync(object state)
{
try
{
lock (m_registrationLock)
{
// halt any outstanding timer.
m_registrationTimer?.Dispose();
m_registrationTimer = null;
}
if (await RegisterWithDiscoveryServerAsync().ConfigureAwait(false))
{
// schedule next registration.
lock (m_registrationLock)
{
if (m_maxRegistrationInterval > 0)
{
m_registrationTimer = new Timer(
OnRegisterServerAsync,
this,
m_maxRegistrationInterval,
Timeout.Infinite);
m_lastRegistrationInterval = m_minRegistrationInterval;
m_logger.LogInformation(
"Register server succeeded. Registering again in {RegistrationInterval} ms",
m_maxRegistrationInterval);
}
}
}
else
{
lock (m_registrationLock)
{
if (m_registrationTimer == null)
{
// calculate next registration attempt.
m_lastRegistrationInterval *= 2;
if (m_lastRegistrationInterval > m_maxRegistrationInterval)
{
m_lastRegistrationInterval = m_maxRegistrationInterval;
}
m_logger.LogInformation(
"Register server failed. Trying again in {RegistrationInterval} ms",
m_lastRegistrationInterval);
// create timer.
m_registrationTimer = new Timer(
OnRegisterServerAsync,
this,
m_lastRegistrationInterval,
Timeout.Infinite);
}
}
}
}
catch (Exception e)
{
m_logger.LogError(e, "Unexpected exception handling registration timer.");
}
}
/// <summary>
/// The synchronization object.
/// </summary>
[Obsolete("Use your own synchronization mechanism.")]
protected object Lock => null;
/// <summary>
/// The state object associated with the server.
/// </summary>
/// <value>The server internal data.</value>
/// <exception cref="ServiceResultException"></exception>
protected IServerInternal ServerInternal =>
m_serverInternal ?? throw new ServiceResultException(StatusCodes.BadServerHalted);
/// <summary>
/// Verifies that the request header is valid.
/// </summary>
/// <param name="requestHeader">The request header.</param>
/// <exception cref="ServiceResultException"></exception>
protected override void ValidateRequest(RequestHeader requestHeader)
{
// check for server error.
ServiceResult error = ServerError;
if (ServiceResult.IsBad(error))
{
throw new ServiceResultException(error);
}
// check server state.
IServerInternal serverInternal = m_serverInternal;
if (serverInternal == null || !serverInternal.IsRunning)
{
throw new ServiceResultException(StatusCodes.BadServerHalted);
}
base.ValidateRequest(requestHeader);
}
/// <summary>
/// Updates the server state.
/// </summary>
/// <param name="state">The state.</param>
/// <exception cref="ServiceResultException"></exception>
protected virtual void SetServerState(ServerState state)
{
m_semaphoreSlim.Wait();
try
{
if (ServiceResult.IsBad(ServerError))
{
throw new ServiceResultException(ServerError);
}
if (m_serverInternal == null)
{
throw new ServiceResultException(StatusCodes.BadServerHalted);
}
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - Enter {State} state.", state);
m_serverInternal.CurrentState = state;
}
finally
{
m_semaphoreSlim.Release();
}
}
/// <summary>
/// Reports an error during initialization after the base server object has been started.
/// </summary>
/// <param name="error">The error.</param>
protected virtual void SetServerError(ServiceResult error)
{
m_semaphoreSlim.Wait();
try
{
ServerError = error;
}
finally
{
m_semaphoreSlim.Release();
}
}
/// <summary>
/// Handles an error when validating the application instance certificate provided by a client.
/// </summary>
/// <param name="clientCertificate">The client certificate.</param>
/// <param name="result">The result.</param>
/// <exception cref="ServiceResultException"></exception>
protected virtual void OnApplicationCertificateError(
byte[] clientCertificate,
ServiceResult result)
{
throw new ServiceResultException(result);
}
/// <summary>
/// Verifies that the request header is valid.
/// </summary>
/// <param name="requestHeader">The request header.</param>
/// <param name="secureChannelContext">The secure channel context.</param>
/// <param name="requestType">Type of the request.</param>
/// <exception cref="ServiceResultException"></exception>
protected virtual OperationContext ValidateRequest(
SecureChannelContext secureChannelContext,
RequestHeader requestHeader,
RequestType requestType)
{
base.ValidateRequest(requestHeader);
if (!ServerInternal.IsRunning)
{
throw new ServiceResultException(StatusCodes.BadServerHalted);
}
OperationContext context = ServerInternal.SessionManager
.ValidateRequest(requestHeader, secureChannelContext, requestType);
ServerUtils.EventLog.ServerCall(context.RequestType, context.RequestId);
m_logger.LogTrace("Server Call={RequestType}, Id={RequestId}", context.RequestType, context.RequestId);
// notify the request manager.
ServerInternal.RequestManager.RequestReceived(context);
return context;
}
/// <summary>
/// Validate operation limits.
/// </summary>
/// <param name="operation">A list of operations.</param>
/// <param name="operationLimit">The operation limit property.</param>
/// <exception cref="ServiceResultException">BadNothingToDo if list is null or empty.</exception>
/// <exception cref="ServiceResultException">BadTooManyOperations if list is larger than operation limit property.</exception>
protected void ValidateOperationLimits(
IList operation,
PropertyState<uint> operationLimit = null)
{
if (operation == null || operation.Count == 0)
{
throw new ServiceResultException(StatusCodes.BadNothingToDo);
}
ValidateOperationLimits(operation.Count, operationLimit);
}
/// <summary>
/// Validate operation limits.
/// </summary>
/// <param name="count">A count of operations.</param>
/// <param name="operationLimit">The operation limit property.</param>
/// <exception cref="ServiceResultException">BadTooManyOperations if count is larger than operation limit property.</exception>
protected void ValidateOperationLimits(int count, PropertyState<uint> operationLimit)
{
uint operationLimitValue = operationLimit != null ? operationLimit.Value : 0;
if (operationLimitValue > 0 && count > operationLimitValue)
{
throw new ServiceResultException(StatusCodes.BadTooManyOperations);
}
}
/// <summary>
/// Translates an exception.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="e">The ServiceResultException e.</param>
/// <returns>Returns an exception thrown when a UA defined error occurs, the return type is <seealso cref="ServiceResultException"/>.</returns>
protected virtual ServiceResultException TranslateException(
OperationContext context,
ServiceResultException e)
{
IList<string> preferredLocales = null;
if (context != null && context.Session != null)
{
preferredLocales = context.Session.PreferredLocales;
}
return TranslateException(context.DiagnosticsMask, preferredLocales, e);
}
/// <summary>
/// Translates an exception.
/// </summary>
/// <param name="diagnosticsMasks">The fields to return.</param>
/// <param name="preferredLocales">The preferred locales.</param>
/// <param name="e">The ServiceResultException e.</param>
/// <returns>Returns an exception thrown when a UA defined error occurs, the return type is <seealso cref="ServiceResultException"/>.</returns>
protected virtual ServiceResultException TranslateException(
DiagnosticsMasks diagnosticsMasks,
IList<string> preferredLocales,
ServiceResultException e)
{
if (e == null)
{
return null;
}
// check if inner result required.
ServiceResult innerResult = null;
if ((
diagnosticsMasks &
(DiagnosticsMasks.ServiceInnerDiagnostics |
DiagnosticsMasks.ServiceInnerStatusCode)
) != 0)
{
innerResult = e.InnerResult;
}
// check if translated text required.
LocalizedText translatedText = null;
if ((diagnosticsMasks & DiagnosticsMasks.ServiceLocalizedText) != 0)
{
translatedText = e.LocalizedText;
}
// create new result object.
var result = new ServiceResult(
e.NamespaceUri,
new StatusCode(e.StatusCode, e.SymbolicId),
translatedText,
e.AdditionalInfo,
innerResult);
// translate result.
result = m_serverInternal.ResourceManager.Translate(preferredLocales, result);
return new ServiceResultException(result);
}
/// <summary>
/// Translates a service result.
/// </summary>
/// <param name="diagnosticsMasks">The fields to return.</param>
/// <param name="preferredLocales">The preferred locales.</param>
/// <param name="result">The result.</param>
/// <returns>Returns a class that combines the status code and diagnostic info structures.</returns>
protected virtual ServiceResult TranslateResult(
DiagnosticsMasks diagnosticsMasks,
IList<string> preferredLocales,
ServiceResult result)
{
if (result == null)
{
return null;
}
return m_serverInternal.ResourceManager.Translate(preferredLocales, result);
}
/// <summary>
/// Verifies that the request header is valid.
/// </summary>
/// <param name="context">The operation context.</param>
/// <exception cref="ServiceResultException"></exception>
protected virtual void OnRequestComplete(OperationContext context)
{
m_semaphoreSlim.Wait();
try
{
if (m_serverInternal == null)
{
throw new ServiceResultException(StatusCodes.BadServerHalted);
}
m_serverInternal.RequestManager.RequestCompleted(context);
}
finally
{
m_semaphoreSlim.Release();
}
}
/// <summary>
/// Raised when the configuration changes.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="args">The <see cref="ConfigurationWatcherEventArgs"/> instance containing the event data.</param>
protected virtual async void OnConfigurationChangedAsync(
object sender,
ConfigurationWatcherEventArgs args)
{
try
{
ApplicationConfiguration configuration = await ApplicationConfiguration
.LoadAsync(
new FileInfo(args.FilePath),
Configuration.ApplicationType,
Configuration.GetType(),
MessageContext.Telemetry)
.ConfigureAwait(false);
await OnUpdateConfigurationAsync(configuration)
.ConfigureAwait(false);
}
catch (Exception e)
{
m_logger.LogError(e, "Could not load updated configuration file from: {FilePath}", args.FilePath);
}
}
/// <summary>
/// Called when the server configuration is changed on disk.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <remarks>
/// Servers are free to ignore changes if it is difficult/impossible to apply them without a restart.
/// </remarks>
protected override async ValueTask OnUpdateConfigurationAsync(
ApplicationConfiguration configuration,
CancellationToken cancellationToken = default)
{
await m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
// update security configuration.
configuration.SecurityConfiguration.Validate(MessageContext.Telemetry);
Configuration.SecurityConfiguration.TrustedIssuerCertificates = configuration
.SecurityConfiguration
.TrustedIssuerCertificates;
Configuration.SecurityConfiguration.TrustedPeerCertificates = configuration
.SecurityConfiguration
.TrustedPeerCertificates;
Configuration.SecurityConfiguration.RejectedCertificateStore = configuration
.SecurityConfiguration
.RejectedCertificateStore;
await Configuration.CertificateValidator.UpdateAsync(
Configuration.SecurityConfiguration,
ct: cancellationToken).ConfigureAwait(false);
// update trace configuration.
Configuration.TraceConfiguration = configuration.TraceConfiguration ??
new TraceConfiguration();
#pragma warning disable CS0618 // Type or member is obsolete
Configuration.TraceConfiguration.ApplySettings();
#pragma warning restore CS0618 // Type or member is obsolete
}
catch (Exception e)
{
m_logger.LogError(e, "Failed to update configuration.");
}
finally
{
m_semaphoreSlim.Release();
}
}
/// <summary>
/// Called before the server starts.
/// </summary>
/// <param name="configuration">The configuration.</param>
protected override void OnServerStarting(ApplicationConfiguration configuration)
{
m_semaphoreSlim.Wait();
try
{
base.OnServerStarting(configuration);
// save minimum nonce length.
m_minNonceLength = configuration.SecurityConfiguration.NonceLength;
// try first RegisterServer2
m_useRegisterServer2 = true;
}
finally
{
m_semaphoreSlim.Release();
}
}
/// <summary>
/// Creates the endpoints and creates the hosts.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="bindingFactory">The transport listener binding factory.</param>
/// <param name="serverDescription">The server description.</param>
/// <param name="endpoints">The endpoints.</param>
/// <returns>
/// Returns IList of a host for a UA service.
/// </returns>
protected override IList<ServiceHost> InitializeServiceHosts(
ApplicationConfiguration configuration,
ITransportListenerBindings bindingFactory,
out ApplicationDescription serverDescription,
out EndpointDescriptionCollection endpoints)
{
serverDescription = null;
endpoints = null;
var hosts = new Dictionary<string, ServiceHost>();
// ensure at least one security policy exists.
if (configuration.ServerConfiguration.SecurityPolicies.Count == 0)
{
configuration.ServerConfiguration.SecurityPolicies.Add(new ServerSecurityPolicy());
}
// ensure at least one user token policy exists.
if (configuration.ServerConfiguration.UserTokenPolicies.Count == 0)
{
var userTokenPolicy = new UserTokenPolicy { TokenType = UserTokenType.Anonymous };
userTokenPolicy.PolicyId = userTokenPolicy.TokenType.ToString();
configuration.ServerConfiguration.UserTokenPolicies.Add(userTokenPolicy);
}
// set server description.
serverDescription = new ApplicationDescription
{
ApplicationUri = configuration.ApplicationUri,
ApplicationName = new LocalizedText("en-US", configuration.ApplicationName),
ApplicationType = configuration.ApplicationType,
ProductUri = configuration.ProductUri,
DiscoveryUrls = GetDiscoveryUrls()
};
endpoints = [];
IList<EndpointDescription> endpointsForHost = null;
StringCollection baseAddresses = configuration.ServerConfiguration.BaseAddresses;
foreach (
string scheme in Utils.DefaultUriSchemes.Where(scheme =>
baseAddresses.Any(a => a.StartsWith(scheme, StringComparison.Ordinal))))
{
ITransportListenerFactory binding = bindingFactory.GetBinding(scheme, MessageContext.Telemetry);
if (binding != null)
{
endpointsForHost = binding.CreateServiceHost(
this,
hosts,
configuration,
configuration.ServerConfiguration.BaseAddresses,
serverDescription,
configuration.ServerConfiguration.SecurityPolicies,
InstanceCertificateTypesProvider);
endpoints.AddRange(endpointsForHost);
}
}
return [.. hosts.Values];
}
/// <summary>
/// Creates an instance of the service host.
/// </summary>
public override ServiceHost CreateServiceHost(ServerBase server, params Uri[] addresses)
{
return new ServiceHost(this, typeof(SessionEndpoint), addresses);
}
/// <summary>
/// Returns the service contract to use.
/// </summary>
protected override Type GetServiceContract()
{
return typeof(ISessionEndpoint);
}
/// <summary>
/// Returns an instance of the endpoint to use.
/// </summary>
protected override EndpointBase GetEndpointInstance(ServerBase server)
{
return new SessionEndpoint(server);
}
/// <summary>
/// Starts the server application.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="cancellationToken">The cancellationToken</param>
/// <exception cref="ServiceResultException"></exception>
protected override async ValueTask StartApplicationAsync(ApplicationConfiguration configuration, CancellationToken cancellationToken = default)
{
await base.StartApplicationAsync(configuration, cancellationToken)
.ConfigureAwait(false);
await m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
m_logger.LogInformation(
Utils.TraceMasks.StartStop,
"Server - Start application {ApplicationName}.",
configuration.ApplicationName);
// Setup the minimum nonce length
Nonce.SetMinNonceValue((uint)configuration.SecurityConfiguration.NonceLength);
// create the datastore for the instance.
m_serverInternal = new ServerInternalData(
ServerProperties,
configuration,
MessageContext,
new CertificateValidator(MessageContext.Telemetry),
InstanceCertificateTypesProvider);
// create the manager responsible for providing localized string resources.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateResourceManager.");
ResourceManager resourceManager = CreateResourceManager(
m_serverInternal,
configuration);
// create the manager responsible for incoming requests.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateRequestManager.");
RequestManager requestManager = CreateRequestManager(
m_serverInternal,
configuration);
//create the main node manager factory
IMainNodeManagerFactory mainNodeManagerFactory = CreateMainNodeManagerFactory(m_serverInternal, configuration);
m_serverInternal.SetMainNodeManagerFactory(mainNodeManagerFactory);
// create the master node manager.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateMasterNodeManager.");
IMasterNodeManager masterNodeManager = CreateMasterNodeManager(
m_serverInternal,
configuration);
// add the node manager to the datastore.
m_serverInternal.SetNodeManager(masterNodeManager);
// put the node manager into a state that allows it to be used by other objects.
await masterNodeManager.StartupAsync(cancellationToken)
.ConfigureAwait(false);
// create the manager responsible for handling events.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateEventManager.");
EventManager eventManager = CreateEventManager(m_serverInternal, configuration);
// creates the server object.
m_serverInternal.CreateServerObject(
eventManager,
resourceManager,
requestManager);
// do any additional processing now that the node manager is up and running.
OnNodeManagerStarted(m_serverInternal);
// create the manager responsible for aggregates.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateAggregateManager.");
m_serverInternal.SetAggregateManager(
CreateAggregateManager(m_serverInternal, configuration));
// create the manager responsible for modelling rules.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateModellingRulesManager.");
m_serverInternal.SetModellingRulesManager(
CreateModellingRulesManager(m_serverInternal, configuration));
// start the session manager.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateSessionManager.");
ISessionManager sessionManager = CreateSessionManager(
m_serverInternal,
configuration);
await sessionManager.StartupAsync(cancellationToken)
.ConfigureAwait(false);
// use event to trigger channel that should not be closed.
sessionManager.SessionChannelKeepAlive += SessionChannelKeepAliveEvent;
//create the MonitoredItemQueueFactory
IMonitoredItemQueueFactory monitoredItemQueueFactory
= CreateMonitoredItemQueueFactory(
m_serverInternal,
configuration);
//add the MonitoredItemQueueFactory to the datastore.
m_serverInternal.SetMonitoredItemQueueFactory(monitoredItemQueueFactory);
//create the SubscriptionStore
ISubscriptionStore subscriptionStore = CreateSubscriptionStore(
m_serverInternal,
configuration);
//add the SubscriptionStore to the datastore
m_serverInternal.SetSubscriptionStore(subscriptionStore);
// start the subscription manager.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateSubscriptionManager.");
ISubscriptionManager subscriptionManager = CreateSubscriptionManager(
m_serverInternal,
configuration);
await subscriptionManager.StartupAsync(cancellationToken)
.ConfigureAwait(false);
// add the session manager to the datastore.
m_serverInternal.SetSessionManager(sessionManager, subscriptionManager);
ServerError = null;
// setup registration information.
lock (m_registrationLock)
{
m_maxRegistrationInterval = configuration.ServerConfiguration
.MaxRegistrationInterval;
ApplicationDescription serverDescription = ServerDescription;
m_registrationInfo = new RegisteredServer
{
ServerUri = serverDescription.ApplicationUri
};
m_registrationInfo.ServerNames.Add(serverDescription.ApplicationName);
m_registrationInfo.ProductUri = serverDescription.ProductUri;
m_registrationInfo.ServerType = serverDescription.ApplicationType;
m_registrationInfo.GatewayServerUri = null;
m_registrationInfo.IsOnline = true;
m_registrationInfo.SemaphoreFilePath = null;
// add all discovery urls.
string computerName = Utils.GetHostName();
for (int ii = 0; ii < BaseAddresses.Count; ii++)
{
var uri = new UriBuilder(BaseAddresses[ii].DiscoveryUrl);
if (string.Equals(
uri.Host,
"localhost",
StringComparison.OrdinalIgnoreCase))
{
uri.Host = computerName;
}
m_registrationInfo.DiscoveryUrls.Add(uri.ToString());
}
// build list of registration endpoints.
m_registrationEndpoints = new ConfiguredEndpointCollection(configuration);
EndpointDescription endpoint = configuration.ServerConfiguration
.RegistrationEndpoint;
if (endpoint == null)
{
endpoint = new EndpointDescription
{
EndpointUrl = Utils.Format(Utils.DiscoveryUrls[0], "localhost"),
SecurityLevel = ServerSecurityPolicy.CalculateSecurityLevel(
MessageSecurityMode.SignAndEncrypt,
SecurityPolicies.Basic256Sha256,
m_logger),
SecurityMode = MessageSecurityMode.SignAndEncrypt,
SecurityPolicyUri = SecurityPolicies.Basic256Sha256
};
endpoint.Server.ApplicationType = ApplicationType.DiscoveryServer;
}
m_registrationEndpoints.Add(endpoint);
m_registeredWithDiscoveryServer = false;
m_minRegistrationInterval = 1000;
m_lastRegistrationInterval = m_minRegistrationInterval;
// start registration timer.
m_registrationTimer?.Dispose();
m_registrationTimer = null;
if (m_maxRegistrationInterval > 0)
{
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - Registration Timer started.");
m_registrationTimer = new Timer(
OnRegisterServerAsync,
this,
m_minRegistrationInterval,
Timeout.Infinite);
}
}
}
catch (Exception e)
{
const string message = "Unexpected error starting application";
m_logger.LogCritical(Utils.TraceMasks.StartStop, e, message);
Utils.SilentDispose(m_serverInternal);
m_serverInternal = null;
var error = ServiceResult.Create(e, StatusCodes.BadInternalError, message);
ServerError = error;
throw new ServiceResultException(error);
}
finally
{
m_semaphoreSlim.Release();
}
// set the server status as running.
SetServerState(ServerState.Running);
// all initialization is complete.
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - Started.");
OnServerStarted(m_serverInternal);
// monitor the configuration file.
if (!string.IsNullOrEmpty(configuration.SourceFilePath))
{
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - Configuration watcher started.");
m_configurationWatcher = new ConfigurationWatcher(configuration, MessageContext.Telemetry);
m_configurationWatcher.Changed += OnConfigurationChangedAsync;
}
CertificateValidator.CertificateUpdate += OnCertificateUpdateAsync;
}
/// <summary>
/// Called before the server stops
/// </summary>
protected override async ValueTask OnServerStoppingAsync(CancellationToken cancellationToken = default)
{
m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - Stopping.");
ShutDownDelay();
// halt any outstanding timer.
lock (m_registrationLock)
{
m_registrationTimer?.Dispose();
m_registrationTimer = null;
}
// attempt graceful shutdown the server.
try
{
if (m_maxRegistrationInterval > 0 && m_registeredWithDiscoveryServer)
{
// unregister from Discovery Server if registered before
m_registrationInfo.IsOnline = false;
await RegisterWithDiscoveryServerAsync(cancellationToken).ConfigureAwait(false);
}
await m_semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
if (m_serverInternal != null)
{
m_serverInternal.SessionManager.SessionChannelKeepAlive
-= SessionChannelKeepAliveEvent;
await m_serverInternal.SubscriptionManager.ShutdownAsync(cancellationToken).ConfigureAwait(false);
m_serverInternal.SessionManager.Shutdown();
await m_serverInternal.NodeManager.ShutdownAsync(cancellationToken).ConfigureAwait(false);
}
}
finally
{
m_semaphoreSlim.Release();
}
}
catch (Exception e)
{
ServerError = new ServiceResult(e);
}
finally
{
// ensure that everything is cleaned up.
if (m_serverInternal != null)
{
Utils.SilentDispose(m_serverInternal);
m_serverInternal = null;
}
}
}
/// <summary>
/// Trys to get the secure channel id for an AuthenticationToken.
/// The ChannelId is known to the sessions of the Server.
/// Each session has an AuthenticationToken which can be used to identify the session.
/// </summary>
/// <param name="authenticationToken">The AuthenticationToken from the RequestHeader</param>
/// <param name="channelId">The Channel id</param>
/// <returns>returns true if a channelId was found for the provided AuthenticationToken</returns>
public override bool TryGetSecureChannelIdForAuthenticationToken(
NodeId authenticationToken,
out uint channelId)
{
ISession session = ServerInternal.SessionManager.GetSession(authenticationToken);
if (session == null)
{
channelId = 0;
return false;
}
return uint.TryParse(session.SecureChannelId, out channelId);
}
/// <summary>
/// Implements the server shutdown delay if session are connected.
/// </summary>
protected void ShutDownDelay()
{
try
{
// check for connected clients.
IList<ISession> currentessions = ServerInternal.SessionManager.GetSessions();
if (currentessions.Count > 0)
{
// provide some time for the connected clients to detect the shutdown state.
ServerInternal.UpdateServerStatus(
(status) =>
{
// set the shutdown reason and state.
status.Value.ShutdownReason = new LocalizedText(
"en-US",
"Application is shutting down.");
status.Variable.ShutdownReason.Value = new LocalizedText(
"en-US",
"Application is shutting down.");
status.Value.State = ServerState.Shutdown;
status.Variable.State.Value = ServerState.Shutdown;
status.Variable
.ClearChangeMasks(ServerInternal.DefaultSystemContext, true);
});
foreach (ISession session in currentessions)
{
// raise close session audit event
ServerInternal.ReportAuditCloseSessionEvent(
null,
session,
m_logger,
"Session/Terminated");
}
for (int timeTillShutdown = Configuration.ServerConfiguration.ShutdownDelay;
timeTillShutdown > 0;
timeTillShutdown--)
{
ServerInternal.UpdateServerStatus(
(status) =>
{
status.Value.SecondsTillShutdown = (uint)timeTillShutdown;
status.Variable.SecondsTillShutdown.Value = (uint)timeTillShutdown;
status.Variable
.ClearChangeMasks(ServerInternal.DefaultSystemContext, true);
});
// exit if all client connections are closed.
int sessionCount = ServerInternal.SessionManager.GetSessions().Count;
if (sessionCount == 0)
{
break;
}
m_logger.LogInformation(
Utils.TraceMasks.StartStop,
"{SessionCount} active sessions. Seconds until shutdown: {TimeTillShutdown}s",
sessionCount,
timeTillShutdown);
Thread.Sleep(1000);
}
}
}
catch
{
// ignore error during shutdown procedure.
}
}
/// <summary>
/// Creates the request manager for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>
/// Returns an object that manages requests from within the server, return type is <seealso cref="RequestManager"/>.
/// </returns>
protected virtual RequestManager CreateRequestManager(
IServerInternal server,
ApplicationConfiguration configuration)
{
return new RequestManager(server);
}
/// <summary>
/// Creates the aggregate manager used by the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The application configuration.</param>
/// <returns>The manager.</returns>
protected virtual AggregateManager CreateAggregateManager(
IServerInternal server,
ApplicationConfiguration configuration)
{
var manager = new AggregateManager(server);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Interpolative,
BrowseNames.AggregateFunction_Interpolative,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Average,
BrowseNames.AggregateFunction_Average,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_TimeAverage,
BrowseNames.AggregateFunction_TimeAverage,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_TimeAverage2,
BrowseNames.AggregateFunction_TimeAverage2,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Total,
BrowseNames.AggregateFunction_Total,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Total2,
BrowseNames.AggregateFunction_Total2,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Minimum,
BrowseNames.AggregateFunction_Minimum,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Maximum,
BrowseNames.AggregateFunction_Maximum,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_MinimumActualTime,
BrowseNames.AggregateFunction_MinimumActualTime,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_MaximumActualTime,
BrowseNames.AggregateFunction_MaximumActualTime,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Range,
BrowseNames.AggregateFunction_Range,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Minimum2,
BrowseNames.AggregateFunction_Minimum2,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Maximum2,
BrowseNames.AggregateFunction_Maximum2,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_MinimumActualTime2,
BrowseNames.AggregateFunction_MinimumActualTime2,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_MaximumActualTime2,
BrowseNames.AggregateFunction_MaximumActualTime2,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Range2,
BrowseNames.AggregateFunction_Range2,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Count,
BrowseNames.AggregateFunction_Count,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_AnnotationCount,
BrowseNames.AggregateFunction_AnnotationCount,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_DurationInStateZero,
BrowseNames.AggregateFunction_DurationInStateZero,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_DurationInStateNonZero,
BrowseNames.AggregateFunction_DurationInStateNonZero,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_NumberOfTransitions,
BrowseNames.AggregateFunction_NumberOfTransitions,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Start,
BrowseNames.AggregateFunction_Start,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_End,
BrowseNames.AggregateFunction_End,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_Delta,
BrowseNames.AggregateFunction_Delta,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_StartBound,
BrowseNames.AggregateFunction_StartBound,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_EndBound,
BrowseNames.AggregateFunction_EndBound,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_DeltaBounds,
BrowseNames.AggregateFunction_DeltaBounds,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_DurationGood,
BrowseNames.AggregateFunction_DurationGood,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_DurationBad,
BrowseNames.AggregateFunction_DurationBad,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_PercentGood,
BrowseNames.AggregateFunction_PercentGood,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_PercentBad,
BrowseNames.AggregateFunction_PercentBad,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_WorstQuality,
BrowseNames.AggregateFunction_WorstQuality,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_WorstQuality2,
BrowseNames.AggregateFunction_WorstQuality2,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_StandardDeviationPopulation,
BrowseNames.AggregateFunction_StandardDeviationPopulation,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_VariancePopulation,
BrowseNames.AggregateFunction_VariancePopulation,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_StandardDeviationSample,
BrowseNames.AggregateFunction_StandardDeviationSample,
Aggregators.CreateStandardCalculator);
manager.RegisterFactory(
ObjectIds.AggregateFunction_VarianceSample,
BrowseNames.AggregateFunction_VarianceSample,
Aggregators.CreateStandardCalculator);
return manager;
}
/// <summary>
/// Creates the modelling rules manager used by the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The application configuration.</param>
/// <returns>The manager.</returns>
protected virtual ModellingRulesManager CreateModellingRulesManager(
IServerInternal server,
ApplicationConfiguration configuration)
{
var manager = new ModellingRulesManager(server);
manager.RegisterModellingRule(
ObjectIds.ModellingRule_Mandatory,
BrowseNames.ModellingRule_Mandatory);
manager.RegisterModellingRule(
ObjectIds.ModellingRule_Optional,
BrowseNames.ModellingRule_Optional);
manager.RegisterModellingRule(
ObjectIds.ModellingRule_ExposesItsArray,
BrowseNames.ModellingRule_ExposesItsArray);
manager.RegisterModellingRule(
ObjectIds.ModellingRule_OptionalPlaceholder,
BrowseNames.ModellingRule_OptionalPlaceholder);
manager.RegisterModellingRule(
ObjectIds.ModellingRule_MandatoryPlaceholder,
BrowseNames.ModellingRule_MandatoryPlaceholder);
return manager;
}
/// <summary>
/// Creates the resource manager for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Returns an object that manages access to localized resources, the return type is <seealso cref="ResourceManager"/>.</returns>
protected virtual ResourceManager CreateResourceManager(
IServerInternal server,
ApplicationConfiguration configuration)
{
var resourceManager = new ResourceManager(configuration);
// load default text for all status codes.
resourceManager.LoadDefaultText();
return resourceManager;
}
/// <summary>
/// Creates the master node manager for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Returns the master node manager for the server, the return type is <seealso cref="MasterNodeManager"/>.</returns>
protected virtual IMasterNodeManager CreateMasterNodeManager(
IServerInternal server,
ApplicationConfiguration configuration)
{
var nodeManagers = new List<INodeManager>();
foreach (INodeManagerFactory nodeManagerFactory in m_nodeManagerFactories)
{
nodeManagers.Add(nodeManagerFactory.Create(server, configuration));
}
var asyncNodeManagers = new List<IAsyncNodeManager>();
foreach (IAsyncNodeManagerFactory nodeManagerFactory in m_asyncNodeManagerFactories)
{
asyncNodeManagers.Add(nodeManagerFactory.CreateAsync(server, configuration).AsTask().GetAwaiter().GetResult());
}
return new MasterNodeManager(server, configuration, null, asyncNodeManagers, nodeManagers);
}
/// <summary>
/// Creates the master node manager for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Returns the master node manager for the server, the return type is <seealso cref="MasterNodeManager"/>.</returns>
protected virtual IMainNodeManagerFactory CreateMainNodeManagerFactory(
IServerInternal server,
ApplicationConfiguration configuration)
{
return new MainNodeManagerFactory(configuration, server);
}
/// <summary>
/// Creates the event manager for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Returns an object that manages all events raised within the server, the return type is <seealso cref="EventManager"/>.</returns>
protected virtual EventManager CreateEventManager(
IServerInternal server,
ApplicationConfiguration configuration)
{
return new EventManager(
server,
(uint)configuration.ServerConfiguration.MaxEventQueueSize,
(uint)configuration.ServerConfiguration.MaxDurableEventQueueSize);
}
/// <summary>
/// Creates the session manager for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Returns a generic session manager object for a server, the return type is <seealso cref="ISessionManager"/>.</returns>
protected virtual ISessionManager CreateSessionManager(
IServerInternal server,
ApplicationConfiguration configuration)
{
return new SessionManager(server, configuration);
}
/// <summary>
/// Creates the session manager for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Returns a generic session manager object for a server, the return type is <seealso cref="SubscriptionManager"/>.</returns>
protected virtual ISubscriptionManager CreateSubscriptionManager(
IServerInternal server,
ApplicationConfiguration configuration)
{
return new SubscriptionManager(server, configuration);
}
/// <summary>
/// Creates the (durable) monitored item queue factory for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Returns a (durable) monitored item queue factory for a server, the return type is <seealso cref="IMonitoredItemQueueFactory"/>.</returns>
protected virtual IMonitoredItemQueueFactory CreateMonitoredItemQueueFactory(
IServerInternal server,
ApplicationConfiguration configuration)
{
return new MonitoredItemQueueFactory(MessageContext.Telemetry);
}
/// <summary>
/// Creates the subscriptionStore for the server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="configuration">The configuration.</param>
/// <returns>Returns a subscriptionStore for a server, the return type is <seealso cref="ISubscriptionStore"/>.</returns>
protected virtual ISubscriptionStore CreateSubscriptionStore(
IServerInternal server,
ApplicationConfiguration configuration)
{
return null;
}
/// <summary>
/// Called after the node managers have been started.
/// </summary>
/// <param name="server">The server.</param>
protected virtual void OnNodeManagerStarted(IServerInternal server)
{
// may be overridden by the subclass.
}
/// <summary>
/// Called after the server has been started.
/// </summary>
/// <param name="server">The server.</param>
protected virtual void OnServerStarted(IServerInternal server)
{
// may be overridden by the subclass.
}
/// <inheritdoc/>
public IEnumerable<INodeManagerFactory> NodeManagerFactories => m_nodeManagerFactories;
/// <inheritdoc/>
public IEnumerable<IAsyncNodeManagerFactory> AsyncNodeManagerFactories
=> m_asyncNodeManagerFactories;
/// <inheritdoc/>
public virtual void AddNodeManager(INodeManagerFactory nodeManagerFactory)
{
m_nodeManagerFactories.Add(nodeManagerFactory);
}
/// <inheritdoc/>
public virtual void AddNodeManager(IAsyncNodeManagerFactory nodeManagerFactory)
{
m_asyncNodeManagerFactories.Add(nodeManagerFactory);
}
/// <inheritdoc/>
public virtual void RemoveNodeManager(INodeManagerFactory nodeManagerFactory)
{
m_nodeManagerFactories.Remove(nodeManagerFactory);
}
/// <inheritdoc/>
public virtual void RemoveNodeManager(IAsyncNodeManagerFactory nodeManagerFactory)
{
m_asyncNodeManagerFactories.Remove(nodeManagerFactory);
}
/// <summary>
/// Reacts to a session channel keep alive event to signal
/// a listener channel that a session is still active.
/// </summary>
private void SessionChannelKeepAliveEvent(ISession session, SessionEventReason reason)
{
Debug.Assert(reason == SessionEventReason.ChannelKeepAlive);
string secureChannelId = session?.SecureChannelId;
if (!string.IsNullOrEmpty(secureChannelId))
{
ITransportListener transportListener = TransportListeners.FirstOrDefault(tl =>
secureChannelId.StartsWith(tl.ListenerId, StringComparison.Ordinal));
transportListener?.UpdateChannelLastActiveTime(secureChannelId);
}
}
private OperationLimitsState OperationLimits
=> ServerInternal.ServerObject.ServerCapabilities.OperationLimits;
private readonly Lock m_registrationLock = new();
private readonly SemaphoreSlim m_semaphoreSlim = new(1, 1);
private IServerInternal m_serverInternal;
private ConfigurationWatcher m_configurationWatcher;
private ConfiguredEndpointCollection m_registrationEndpoints;
private RegisteredServer m_registrationInfo;
private Timer m_registrationTimer;
private int m_minRegistrationInterval;
private int m_maxRegistrationInterval;
private int m_lastRegistrationInterval;
private bool m_registeredWithDiscoveryServer;
private int m_minNonceLength;
private bool m_useRegisterServer2;
private readonly List<INodeManagerFactory> m_nodeManagerFactories = [];
private readonly List<IAsyncNodeManagerFactory> m_asyncNodeManagerFactories = [];
}
}