/* ======================================================================== * 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.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Opc.Ua.Bindings; using Opc.Ua.Security.Certificates; namespace Opc.Ua.Server { /// public class StandardServer : SessionServerBase, IStandardServer { /// public StandardServer(ITelemetryContext telemetry) : this(telemetry, null) { } /// /// Initializes a new instance of /// with an explicit so the server's /// timers and duration calculations can be mocked in tests. /// /// The telemetry context. /// The time provider used for all /// time / duration calculations performed by the server, or /// null to use . public StandardServer(ITelemetryContext telemetry, TimeProvider? timeProvider) : base(telemetry) { TimeProvider = timeProvider ?? TimeProvider.System; } /// /// The used by the server for all /// time / duration calculations and timer scheduling. /// protected TimeProvider TimeProvider { get; } /// protected override void Dispose(bool disposing) { if (disposing) { // halt any outstanding timer. m_registrationTimer?.Dispose(); m_registrationTimer = null; // close the watcher. m_configurationWatcher?.Dispose(); m_configurationWatcher = null; // close the server. m_serverInternal?.Dispose(); m_serverInternal = null; m_certManagerSubscription?.Dispose(); m_semaphoreSlim.Dispose(); } base.Dispose(disposing); } /// public override async ValueTask FindServersAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, string? endpointUrl, ArrayOf localeIds, ArrayOf serverUris, RequestLifetime requestLifetime) { List servers = []; ValidateRequest(requestHeader); await m_semaphoreSlim.WaitAsync(requestLifetime.CancellationToken).ConfigureAwait(false); try { // parse the url provided by the client. IList baseAddresses = BaseAddresses; Uri? parsedEndpointUrl = Utils.ParseUri(endpointUrl); if (parsedEndpointUrl != null) { baseAddresses = FilterByEndpointUrl(parsedEndpointUrl, baseAddresses); } // check if nothing to do. if (baseAddresses.Count == 0) { return new FindServersResponse { ResponseHeader = CreateResponse(requestHeader, StatusCodes.Good), Servers = servers }; } // build list of unique servers. var uniqueServers = new Dictionary(); 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.Count > 0 && !serverUris.Contains(f => f == server.ApplicationUri)) { continue; } // localize the application name if requested. LocalizedText applicationName = server.ApplicationName; if (localeIds.Count > 0) { applicationName = 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 }; } /// public override async ValueTask GetEndpointsAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, string? endpointUrl, ArrayOf localeIds, ArrayOf profileUris, RequestLifetime requestLifetime) { ArrayOf endpoints = default; ValidateRequest(requestHeader); await m_semaphoreSlim.WaitAsync(requestLifetime.CancellationToken).ConfigureAwait(false); try { // filter by profile. IList 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 }; } /// /// Returns the endpoints that match the base address and endpoint url. /// protected ArrayOf GetEndpointDescriptions( string? endpointUrl, IList baseAddresses, ArrayOf localeIds) { ArrayOf endpoints = default; // 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.Count > 0) { applicationName = 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; } /// public override void ReportAuditOpenSecureChannelEvent( string globalChannelId, EndpointDescription endpointDescription, OpenSecureChannelRequest request, Certificate clientCertificate, Exception exception) { ServerInternal?.ReportAuditOpenSecureChannelEvent( globalChannelId, endpointDescription, request, clientCertificate, exception, m_logger); } /// public override void ReportAuditCloseSecureChannelEvent( string globalChannelId, Exception exception) { ServerInternal?.ReportAuditCloseSecureChannelEvent(globalChannelId, exception, m_logger); } /// public override void ReportAuditCertificateEvent( Certificate clientCertificate, Exception exception) { ServerInternal?.ReportAuditCertificateEvent(clientCertificate, exception, m_logger); } /// public override async ValueTask CreateSessionAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ApplicationDescription? clientDescription, string? serverUri, string? endpointUrl, string? sessionName, ByteString clientNonce, ByteString clientCertificate, double requestedSessionTimeout, uint maxResponseMessageSize, RequestLifetime requestLifetime) { NodeId sessionId; NodeId authenticationToken; double revisedSessionTimeout = 0; ByteString serverNonce; ByteString serverCertificate = default; ArrayOf serverEndpoints = default; SignatureData? serverSignature = null; uint maxRequestMessageSize = (uint)MessageContext.MaxMessageSize; OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.CreateSession, requestLifetime).ConfigureAwait(false); 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.IsEmpty) { requireEncryption = true; } CertificateCollection? clientIssuerCertificates = null; // validate client application instance certificate. Certificate? parsedClientCertificate = null; if (requireEncryption && clientCertificate.Length > 0) { try { using CertificateCollection clientCertificateChain = Utils.ParseCertificateChainBlob( clientCertificate, ServerInternal.Telemetry); parsedClientCertificate = clientCertificateChain[0].AddRef(); 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!); } CertificateValidationResult clientCertResult = await CertificateManager! .ValidateAsync( clientCertificateChain, TrustListIdentifier.Peers, options: null, ct: requestLifetime.CancellationToken) .ConfigureAwait(false); if (!clientCertResult.IsValid) { throw new ServiceResultException(clientCertResult.StatusCode); } } } } 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.IsEmpty) { if (clientNonce.Length < m_minNonceLength) { throw new ServiceResultException(StatusCodes.BadNonceInvalid); } // ignore nonce if security policy set to none if (context.SecurityPolicyUri == SecurityPolicies.None) { clientNonce = default; } } // load the certificate for the security profile Certificate instanceCertificate = CertificateManager! .GetInstanceCertificate( context.SecurityPolicyUri)?.Certificate!; // create the session. CreateSessionResult result = await ServerInternal.SessionManager.CreateSessionAsync( context, instanceCertificate, sessionName, clientNonce, clientDescription, endpointUrl, parsedClientCertificate, clientIssuerCertificates, requestedSessionTimeout, maxResponseMessageSize, requestLifetime.CancellationToken) .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) }; CertificateManager.ValidateDomains( instanceCertificate, configuredEndpoint, serverValidation: 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); } } AdditionalParametersType? parameters = CreateSessionProcessAdditionalParameters( session, requestHeader.AdditionalHeader); await m_semaphoreSlim.WaitAsync(requestLifetime.CancellationToken).ConfigureAwait(false); try { // return the application instance certificate for the server. if (requireEncryption) { // check if complete chain should be sent. if (CertificateManager.SendCertificateChain) { serverCertificate = CertificateManager .LoadCertificateChainRaw(instanceCertificate).ToByteString(); } else { serverCertificate = instanceCertificate.RawData.ToByteString(); } } // return the endpoints supported by the server. serverEndpoints = GetEndpointDescriptions(endpointUrl, BaseAddresses, default); // sign the nonce provided by the client. serverSignature = CreateSessionServerSignature( context, instanceCertificate, parsedClientCertificate, clientNonce, serverNonce); } 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) { await ServerInternal.SessionManager.CloseSessionAsync(session.Id, requestLifetime.CancellationToken).ConfigureAwait(false); } 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); } } /// /// Creates the server signature returned during CreateSession. /// /// The request context. /// The instance certificate used to sign data. /// The client certificate supplied in the request. /// The client nonce supplied in the request. /// The server nonce generated for the response. /// The server signature or null when signing is not required. protected virtual SignatureData? CreateSessionServerSignature( OperationContext context, Certificate instanceCertificate, Certificate? parsedClientCertificate, ByteString clientNonce, ByteString serverNonce) { return SessionSecurityPolicyHelper.CreateServerSignature( context, instanceCertificate, parsedClientCertificate, clientNonce, serverNonce); } /// /// Process additional header values during session creation for ephemeral-key user token policies. /// /// The session. /// The additional request header. /// An AdditionalParametersType object containing the processed parameters. protected virtual AdditionalParametersType? CreateSessionProcessAdditionalParameters( ISession session, ExtensionObject additionalHeader) { if (additionalHeader.TryGetValue(out AdditionalParametersType? parameters)) { parameters = CreateSessionProcessAdditionalParameters(session, parameters); } return parameters; } /// /// Process additional parameters during session creation and set the session's user token security policy. /// /// The session /// The additional parameters for the session /// An AdditionalParametersType object containing the processed parameters protected virtual AdditionalParametersType CreateSessionProcessAdditionalParameters( ISession session, AdditionalParametersType parameters) { return SessionSecurityPolicyHelper.ProcessCreateSessionAdditionalParameters( session, parameters, m_logger)!; } /// /// Process additional header values during session activation for ephemeral-key user token policies. /// /// The session. /// The additional request header. /// An AdditionalParametersType object containing the processed parameters. protected virtual AdditionalParametersType? ActivateSessionProcessAdditionalParameters( ISession session, ExtensionObject additionalHeader) { if (additionalHeader.TryGetValue(out AdditionalParametersType? parameters)) { parameters = ActivateSessionProcessAdditionalParameters(session, parameters); } return parameters; } /// /// Process additional parameters during session activation for ephemeral-key user token policies. /// /// The session /// The additional parameters for the session /// An AdditionalParametersType object containing the processed parameters protected virtual AdditionalParametersType ActivateSessionProcessAdditionalParameters( ISession session, AdditionalParametersType parameters) { return SessionSecurityPolicyHelper.ProcessActivateSessionAdditionalParameters( session, parameters); } /// public override async ValueTask ActivateSessionAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, SignatureData? clientSignature, ArrayOf clientSoftwareCertificates, ArrayOf localeIds, ExtensionObject userIdentityToken, SignatureData? userTokenSignature, RequestLifetime requestLifetime) { ByteString serverNonce; OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.ActivateSession, requestLifetime).ConfigureAwait(false); try { // activate the session. (bool identityChanged, serverNonce, ServiceResult activationStatus) = await ServerInternal.SessionManager.ActivateSessionAsync( context, requestHeader.AuthenticationToken, clientSignature, userIdentityToken, userTokenSignature, localeIds, requestLifetime.CancellationToken) .ConfigureAwait(false); if (identityChanged) { ISession? activatedSession = ServerInternal.SessionManager .GetSession(requestHeader.AuthenticationToken); if (activatedSession != null) { await ServerInternal.NodeManager.SessionActivatedAsync( context, activatedSession.Id, requestLifetime.CancellationToken).ConfigureAwait(false); } } ISession? session = ServerInternal.SessionManager .GetSession(requestHeader.AuthenticationToken); AdditionalParametersType? parameters = ActivateSessionProcessAdditionalParameters( session!, requestHeader.AdditionalHeader); m_logger.LogInformation("Server - SESSION ACTIVATED."); // report the audit event for session activate ServerInternal.ReportAuditActivateSessionEvent( m_logger, context.AuditEntryId!, session!); // Use the activation status from SessionManager so spec-defined // success codes (e.g. Good_PasswordChangeRequired per Part 18 // §5.2.8) reach the client. StatusCode resultStatus = ServiceResult.IsGood(activationStatus) ? activationStatus.StatusCode : StatusCodes.Good; ResponseHeader responseHeader = CreateResponse(requestHeader, resultStatus); if (parameters != null) { responseHeader.AdditionalHeader = new ExtensionObject(parameters); } return new ActivateSessionResponse { ResponseHeader = responseHeader, ServerNonce = serverNonce, Results = [], 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); } } /// /// Returns whether the error is a security error. /// /// The error. /// /// true if the error is one of the security errors, otherwise false. /// protected bool IsSecurityError(StatusCode error) { return error == StatusCodes.BadUserSignatureInvalid || error == StatusCodes.BadUserAccessDenied || error == StatusCodes.BadSecurityPolicyRejected || error == StatusCodes.BadSecurityModeRejected || error == StatusCodes.BadSecurityChecksFailed || error == StatusCodes.BadSecureChannelTokenUnknown || error == StatusCodes.BadSecureChannelIdInvalid || error == StatusCodes.BadNoValidCertificates || error == StatusCodes.BadIdentityTokenInvalid || error == StatusCodes.BadIdentityTokenRejected || error == StatusCodes.BadIdentityChangeNotSupported || error == StatusCodes.BadCertificateUseNotAllowed || error == StatusCodes.BadCertificateUriInvalid || error == StatusCodes.BadCertificateUntrusted || error == StatusCodes.BadCertificateTimeInvalid || error == StatusCodes.BadCertificateRevoked || error == StatusCodes.BadCertificateRevocationUnknown || error == StatusCodes.BadCertificateIssuerUseNotAllowed || error == StatusCodes.BadCertificateIssuerTimeInvalid || error == StatusCodes.BadCertificateIssuerRevoked || error == StatusCodes.BadCertificateIssuerRevocationUnknown || error == StatusCodes.BadCertificateInvalid || error == StatusCodes.BadCertificateHostNameInvalid || error == StatusCodes.BadCertificatePolicyCheckFailed || error == StatusCodes.BadApplicationSignatureInvalid; } /// /// Creates the response header. /// /// The object that contains description for the RequestHeader DataType. /// The exception used to create DiagnosticInfo assigned to the ServiceDiagnostics. /// Returns a description for the ResponseHeader DataType. protected ResponseHeader CreateResponse( RequestHeader? requestHeader, ServiceResultException exception) { var responseHeader = new ResponseHeader { ServiceResult = exception.StatusCode, Timestamp = TimeProvider.GetUtcNow().UtcDateTime, 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; } /// public override async ValueTask CloseSessionAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, bool deleteSubscriptions, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.CloseSession, requestLifetime).ConfigureAwait(false); try { ISession? session = ServerInternal.SessionManager .GetSession(requestHeader.AuthenticationToken); await ServerInternal.CloseSessionAsync(context, context.Session.Id, deleteSubscriptions, requestLifetime.CancellationToken) .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); } } /// public override async ValueTask CancelAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, uint requestHandle, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.Cancel, requestLifetime).ConfigureAwait(false); CancelResponse response; try { ServerInternal.RequestManager.CancelRequests(requestHandle, out uint cancelCount); response = 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); } return response; } /// public override async ValueTask BrowseAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ViewDescription? view, uint requestedMaxReferencesPerNode, ArrayOf nodesToBrowse, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.Browse, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(nodesToBrowse, OperationLimits.MaxNodesPerBrowse); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.BrowseAsync( context, view!, requestedMaxReferencesPerNode, nodesToBrowse, requestLifetime.CancellationToken) .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); } } /// public override async ValueTask BrowseNextAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, bool releaseContinuationPoints, ArrayOf continuationPoints, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.BrowseNext, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(continuationPoints, OperationLimits.MaxNodesPerBrowse); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.BrowseNextAsync( context, releaseContinuationPoints, continuationPoints, requestLifetime.CancellationToken) .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); } } /// public override async ValueTask AddNodesAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf nodesToAdd, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.AddNodes, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(nodesToAdd, OperationLimits.MaxNodesPerNodeManagement); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.AddNodesAsync( context, nodesToAdd, requestLifetime.CancellationToken).ConfigureAwait(false); StatusCode auditStatus = AggregateStatusCode(results); ServerInternal.ReportAuditAddNodesEvent( new ServerSystemContext(ServerInternal, context), nodesToAdd, "NodeManagement/AddNodes", auditStatus, m_logger); return new AddNodesResponse { ResponseHeader = CreateResponse(requestHeader, context.StringTable), Results = results, DiagnosticInfos = diagnosticInfos }; } catch (ServiceResultException e) { lock (ServerInternal.DiagnosticsWriteLock) { ServerInternal.ServerDiagnostics.RejectedRequestsCount++; if (IsSecurityError(e.StatusCode)) { ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++; } } ServerInternal.ReportAuditAddNodesEvent( new ServerSystemContext(ServerInternal, context), nodesToAdd, "NodeManagement/AddNodes", e.StatusCode, m_logger); throw TranslateException(context, e); } finally { OnRequestComplete(context); } } /// public override async ValueTask DeleteNodesAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf nodesToDelete, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.DeleteNodes, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(nodesToDelete, OperationLimits.MaxNodesPerNodeManagement); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.DeleteNodesAsync( context, nodesToDelete, requestLifetime.CancellationToken).ConfigureAwait(false); StatusCode auditStatus = AggregateStatusCode(results); ServerInternal.ReportAuditDeleteNodesEvent( new ServerSystemContext(ServerInternal, context), nodesToDelete, "NodeManagement/DeleteNodes", auditStatus, m_logger); return new DeleteNodesResponse { ResponseHeader = CreateResponse(requestHeader, context.StringTable), Results = results, DiagnosticInfos = diagnosticInfos }; } catch (ServiceResultException e) { lock (ServerInternal.DiagnosticsWriteLock) { ServerInternal.ServerDiagnostics.RejectedRequestsCount++; if (IsSecurityError(e.StatusCode)) { ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++; } } ServerInternal.ReportAuditDeleteNodesEvent( new ServerSystemContext(ServerInternal, context), nodesToDelete, "NodeManagement/DeleteNodes", e.StatusCode, m_logger); throw TranslateException(context, e); } finally { OnRequestComplete(context); } } /// public override async ValueTask AddReferencesAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf referencesToAdd, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.AddReferences, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(referencesToAdd, OperationLimits.MaxNodesPerNodeManagement); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.AddReferencesAsync( context, referencesToAdd, requestLifetime.CancellationToken).ConfigureAwait(false); StatusCode auditStatus = AggregateStatusCode(results); ServerInternal.ReportAuditAddReferencesEvent( new ServerSystemContext(ServerInternal, context), referencesToAdd, "NodeManagement/AddReferences", auditStatus, m_logger); return new AddReferencesResponse { ResponseHeader = CreateResponse(requestHeader, context.StringTable), Results = results, DiagnosticInfos = diagnosticInfos }; } catch (ServiceResultException e) { lock (ServerInternal.DiagnosticsWriteLock) { ServerInternal.ServerDiagnostics.RejectedRequestsCount++; if (IsSecurityError(e.StatusCode)) { ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++; } } ServerInternal.ReportAuditAddReferencesEvent( new ServerSystemContext(ServerInternal, context), referencesToAdd, "NodeManagement/AddReferences", e.StatusCode, m_logger); throw TranslateException(context, e); } finally { OnRequestComplete(context); } } /// public override async ValueTask DeleteReferencesAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf referencesToDelete, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.DeleteReferences, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(referencesToDelete, OperationLimits.MaxNodesPerNodeManagement); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.DeleteReferencesAsync( context, referencesToDelete, requestLifetime.CancellationToken).ConfigureAwait(false); StatusCode auditStatus = AggregateStatusCode(results); ServerInternal.ReportAuditDeleteReferencesEvent( new ServerSystemContext(ServerInternal, context), referencesToDelete, "NodeManagement/DeleteReferences", auditStatus, m_logger); return new DeleteReferencesResponse { ResponseHeader = CreateResponse(requestHeader, context.StringTable), Results = results, DiagnosticInfos = diagnosticInfos }; } catch (ServiceResultException e) { lock (ServerInternal.DiagnosticsWriteLock) { ServerInternal.ServerDiagnostics.RejectedRequestsCount++; if (IsSecurityError(e.StatusCode)) { ServerInternal.ServerDiagnostics.SecurityRejectedRequestsCount++; } } ServerInternal.ReportAuditDeleteReferencesEvent( new ServerSystemContext(ServerInternal, context), referencesToDelete, "NodeManagement/DeleteReferences", e.StatusCode, m_logger); throw TranslateException(context, e); } finally { OnRequestComplete(context); } } /// /// Aggregates per-item status codes into a single audit status: /// when every item succeeded, /// the first bad status otherwise. /// private static StatusCode AggregateStatusCode(ArrayOf results) { for (int ii = 0; ii < results.Count; ii++) { StatusCode code = results[ii]; if (StatusCode.IsBad(code)) { return code; } } return StatusCodes.Good; } /// /// Aggregates AddNodes per-item statuses into a single audit status. /// private static StatusCode AggregateStatusCode(ArrayOf results) { for (int ii = 0; ii < results.Count; ii++) { AddNodesResult result = results[ii]; if (result == null) { continue; } if (StatusCode.IsBad(result.StatusCode)) { return result.StatusCode; } } return StatusCodes.Good; } /// public override async ValueTask RegisterNodesAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf nodesToRegister, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.RegisterNodes, requestLifetime).ConfigureAwait(false); RegisterNodesResponse response; try { ValidateOperationLimits(nodesToRegister, OperationLimits.MaxNodesPerRegisterNodes); ServerInternal.NodeManager .RegisterNodes(context, nodesToRegister, out ArrayOf registeredNodeIds); response = 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); } return response; } /// public override async ValueTask UnregisterNodesAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf nodesToUnregister, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.UnregisterNodes, requestLifetime).ConfigureAwait(false); UnregisterNodesResponse response; try { ValidateOperationLimits( nodesToUnregister, OperationLimits.MaxNodesPerRegisterNodes); ServerInternal.NodeManager.UnregisterNodes(context, nodesToUnregister); response = 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); } return response; } /// public override async ValueTask TranslateBrowsePathsToNodeIdsAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf browsePaths, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.TranslateBrowsePathsToNodeIds, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits( browsePaths, OperationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds); foreach (BrowsePath bp in browsePaths) { ValidateOperationLimits( bp.RelativePath.Elements.Count, OperationLimits.MaxNodesPerTranslateBrowsePathsToNodeIds); } (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.TranslateBrowsePathsToNodeIdsAsync( context, browsePaths, requestLifetime.CancellationToken) .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); } } /// public override async ValueTask ReadAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, double maxAge, TimestampsToReturn timestampsToReturn, ArrayOf nodesToRead, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.Read, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(nodesToRead, OperationLimits.MaxNodesPerRead); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.ReadAsync( context, maxAge, timestampsToReturn, nodesToRead, requestLifetime.CancellationToken).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); } } /// public override async ValueTask HistoryReadAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ExtensionObject historyReadDetails, TimestampsToReturn timestampsToReturn, bool releaseContinuationPoints, ArrayOf nodesToRead, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.HistoryRead, requestLifetime).ConfigureAwait(false); try { if (historyReadDetails.TryGetValue(out ReadEventDetails? _)) { ValidateOperationLimits( nodesToRead, OperationLimits.MaxNodesPerHistoryReadEvents); } else { ValidateOperationLimits( nodesToRead, OperationLimits.MaxNodesPerHistoryReadData); } (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.HistoryReadAsync( context, historyReadDetails, timestampsToReturn, releaseContinuationPoints, nodesToRead, requestLifetime.CancellationToken) .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); } } /// public override async ValueTask WriteAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf nodesToWrite, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.Write, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(nodesToWrite, OperationLimits.MaxNodesPerWrite); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager .WriteAsync(context, nodesToWrite, requestLifetime.CancellationToken) .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); } } /// public override async ValueTask HistoryUpdateAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf historyUpdateDetails, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.HistoryUpdate, requestLifetime).ConfigureAwait(false); try { // check only for BadNothingToDo here // MaxNodesPerHistoryUpdateEvents & MaxNodesPerHistoryUpdateData // must be checked in NodeManager (TODO) ValidateOperationLimits(historyUpdateDetails); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.HistoryUpdateAsync( context, historyUpdateDetails, requestLifetime.CancellationToken).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); } } /// public override async ValueTask CreateSubscriptionAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, bool publishingEnabled, byte priority, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.CreateSubscription, requestLifetime).ConfigureAwait(false); try { CreateSubscriptionResponse response = await ServerInternal.SubscriptionManager.CreateSubscriptionAsync( context, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, publishingEnabled, priority, requestLifetime.CancellationToken).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); } } /// public override async ValueTask TransferSubscriptionsAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf subscriptionIds, bool sendInitialValues, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.TransferSubscriptions, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(subscriptionIds); TransferSubscriptionsResponse response = await ServerInternal.SubscriptionManager.TransferSubscriptionsAsync( context, subscriptionIds, sendInitialValues, requestLifetime.CancellationToken).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); } } /// public override async ValueTask DeleteSubscriptionsAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf subscriptionIds, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.DeleteSubscriptions, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(subscriptionIds); DeleteSubscriptionsResponse response = await ServerInternal.SubscriptionManager.DeleteSubscriptionsAsync( context, subscriptionIds, requestLifetime.CancellationToken).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); } } /// public override async ValueTask PublishAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf subscriptionAcknowledgements, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.Publish, requestLifetime).ConfigureAwait(false); 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, requestLifetime.CancellationToken).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); } } /// public override async ValueTask RepublishAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, uint subscriptionId, uint retransmitSequenceNumber, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.Republish, requestLifetime).ConfigureAwait(false); RepublishResponse response; try { NotificationMessage notificationMessage = ServerInternal.SubscriptionManager.Republish( context, subscriptionId, retransmitSequenceNumber); response = 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); } return response; } /// public override async ValueTask ModifySubscriptionAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, uint subscriptionId, double requestedPublishingInterval, uint requestedLifetimeCount, uint requestedMaxKeepAliveCount, uint maxNotificationsPerPublish, byte priority, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.ModifySubscription, requestLifetime).ConfigureAwait(false); ModifySubscriptionResponse response; try { ServerInternal.SubscriptionManager.ModifySubscription( context, subscriptionId, requestedPublishingInterval, requestedLifetimeCount, requestedMaxKeepAliveCount, maxNotificationsPerPublish, priority, out double revisedPublishingInterval, out uint revisedLifetimeCount, out uint revisedMaxKeepAliveCount); response = 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); } return response; } /// public override async ValueTask SetPublishingModeAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, bool publishingEnabled, ArrayOf subscriptionIds, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.SetPublishingMode, requestLifetime).ConfigureAwait(false); SetPublishingModeResponse response; try { ValidateOperationLimits(subscriptionIds); ServerInternal.SubscriptionManager.SetPublishingMode( context, publishingEnabled, subscriptionIds, out ArrayOf results, out ArrayOf diagnosticInfos); response = 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); } return response; } /// public override async ValueTask SetTriggeringAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, uint subscriptionId, uint triggeringItemId, ArrayOf linksToAdd, ArrayOf linksToRemove, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.SetTriggering, requestLifetime).ConfigureAwait(false); SetTriggeringResponse response; try { if (linksToAdd.Count == 0 && linksToRemove.Count == 0) { throw new ServiceResultException(StatusCodes.BadNothingToDo); } int monitoredItemsCount = 0; monitoredItemsCount += linksToAdd.Count; monitoredItemsCount += linksToRemove.Count; ValidateOperationLimits( monitoredItemsCount, OperationLimits.MaxMonitoredItemsPerCall); ServerInternal.SubscriptionManager.SetTriggering( context, subscriptionId, triggeringItemId, linksToAdd, linksToRemove, out ArrayOf addResults, out ArrayOf addDiagnosticInfos, out ArrayOf removeResults, out ArrayOf removeDiagnosticInfos); response = 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); } return response; } /// public override async ValueTask CreateMonitoredItemsAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, ArrayOf itemsToCreate, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.CreateMonitoredItems, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(itemsToCreate, OperationLimits.MaxMonitoredItemsPerCall); CreateMonitoredItemsResponse result = await ServerInternal.SubscriptionManager.CreateMonitoredItemsAsync( context, subscriptionId, timestampsToReturn, itemsToCreate, requestLifetime.CancellationToken).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); } } /// public override async ValueTask ModifyMonitoredItemsAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, uint subscriptionId, TimestampsToReturn timestampsToReturn, ArrayOf itemsToModify, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.ModifyMonitoredItems, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(itemsToModify, OperationLimits.MaxMonitoredItemsPerCall); ModifyMonitoredItemsResponse response = await ServerInternal.SubscriptionManager.ModifyMonitoredItemsAsync( context, subscriptionId, timestampsToReturn, itemsToModify, requestLifetime.CancellationToken).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); } } /// public override async ValueTask DeleteMonitoredItemsAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, uint subscriptionId, ArrayOf monitoredItemIds, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.DeleteMonitoredItems, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(monitoredItemIds, OperationLimits.MaxMonitoredItemsPerCall); DeleteMonitoredItemsResponse response = await ServerInternal.SubscriptionManager.DeleteMonitoredItemsAsync( context, subscriptionId, monitoredItemIds, requestLifetime.CancellationToken).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); } } /// public override async ValueTask SetMonitoringModeAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, uint subscriptionId, MonitoringMode monitoringMode, ArrayOf monitoredItemIds, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.SetMonitoringMode, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(monitoredItemIds, OperationLimits.MaxMonitoredItemsPerCall); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.SubscriptionManager.SetMonitoringModeAsync( context, subscriptionId, monitoringMode, monitoredItemIds, requestLifetime.CancellationToken) .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); } } /// public override async ValueTask CallAsync( SecureChannelContext secureChannelContext, RequestHeader? requestHeader, ArrayOf methodsToCall, RequestLifetime requestLifetime) { OperationContext context = await ValidateRequestAsync( secureChannelContext, requestHeader, RequestType.Call, requestLifetime).ConfigureAwait(false); try { ValidateOperationLimits(methodsToCall, OperationLimits.MaxNodesPerMethodCall); (ArrayOf results, ArrayOf diagnosticInfos) = await ServerInternal.NodeManager.CallAsync(context, methodsToCall, requestLifetime.CancellationToken) .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); } } /// public IServerInternal CurrentInstance { get { m_semaphoreSlim.Wait(); try { if (m_serverInternal == null) { throw new ServiceResultException(StatusCodes.BadServerHalted); } return m_serverInternal; } finally { m_semaphoreSlim.Release(); } } } /// /// Returns the current status of the server. /// /// Returns a ServerStatusDataType object /// [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 ServerInternal.Status.Value; } } /// public ServerState CurrentState => ServerInternal.CurrentState; /// /// Registers the server with the discovery server. /// /// Boolean value. [Obsolete("Use RegisterWithDiscoveryServerAsync instead.")] public bool RegisterWithDiscoveryServer() { return RegisterWithDiscoveryServerAsync().AsTask().GetAwaiter().GetResult(); } /// public async ValueTask RegisterWithDiscoveryServerAsync(CancellationToken ct = default) { var configuration = new ApplicationConfiguration(Configuration!) { // share the server's CertificateManager so the registration channel uses // the same trust list, rejected store, and cached validation results. // The base copy ctor already propagates the legacy CertificateValidator. CertificateManager = CertificateManager! }; // 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 = TimeProvider.GetUtcNow().UtcDateTime }; // create the client. Certificate? instanceCertificate = CertificateManager!.GetInstanceCertificate( endpoint.Description?.SecurityPolicyUri ?? SecurityPolicies.None)?.Certificate; client = await RegistrationClient.CreateAsync( configuration, endpoint.Description!, endpoint.Configuration!, instanceCertificate!, ct: ct).ConfigureAwait(false); client.OperationTimeout = 10000; // register the server. if (m_useRegisterServer2) { var mdnsDiscoveryConfig = new MdnsDiscoveryConfiguration { ServerCapabilities = configuration.ServerConfiguration! .ServerCapabilities, MdnsServerName = Utils.GetHostName() }; var extensionObject = new ExtensionObject(mdnsDiscoveryConfig); ArrayOf discoveryConfiguration = [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; } /// /// Registers the server endpoints with the LDS. /// /// The state. 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 = TimeProvider.CreateTimer( OnRegisterServerAsync, this, TimeSpan.FromMilliseconds(m_maxRegistrationInterval), Timeout.InfiniteTimeSpan); 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 = TimeProvider.CreateTimer( OnRegisterServerAsync, this, TimeSpan.FromMilliseconds(m_lastRegistrationInterval), Timeout.InfiniteTimeSpan); } } } } catch (Exception e) { m_logger.LogError(e, "Unexpected exception handling registration timer."); } } /// /// The synchronization object. /// [Obsolete("Use your own synchronization mechanism.")] protected object? Lock => null; /// /// The state object associated with the server. /// /// The server internal data. /// protected IServerInternal ServerInternal => m_serverInternal ?? throw new ServiceResultException(StatusCodes.BadServerHalted); /// /// Verifies that the request header is valid. /// /// The request header. /// protected override void ValidateRequest([NotNull] RequestHeader? requestHeader) { // check for server error. ServiceResult error = ServerError; if (ServiceResult.IsBad(error)) { throw new ServiceResultException(error); } // check server state. ServerInternalData? serverInternal = m_serverInternal; if (serverInternal == null || !serverInternal.IsRunning) { throw new ServiceResultException(StatusCodes.BadServerHalted); } base.ValidateRequest(requestHeader); } /// /// Updates the server state. /// /// The state. /// 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); ServerInternal.CurrentState = state; } finally { m_semaphoreSlim.Release(); } } /// /// Reports an error during initialization after the base server object has been started. /// /// The error. protected virtual void SetServerError(ServiceResult error) { m_semaphoreSlim.Wait(); try { ServerError = error; } finally { m_semaphoreSlim.Release(); } } /// /// Handles an error when validating the application instance certificate provided by a client. /// /// The client certificate. /// The result. /// protected virtual void OnApplicationCertificateError( ByteString clientCertificate, ServiceResult result) { // see https://reference.opcfoundation.org/Core/Part4/v105/docs/6.1.3 StatusCode resultCode = result.StatusCode; if (resultCode == StatusCodes.BadCertificateInvalid || resultCode == StatusCodes.BadCertificateRevoked || resultCode == StatusCodes.BadCertificateUntrusted || resultCode == StatusCodes.BadCertificateIssuerRevoked || resultCode == StatusCodes.BadCertificateRevocationUnknown || resultCode == StatusCodes.BadCertificateChainIncomplete || resultCode == StatusCodes.BadCertificateIssuerRevocationUnknown) { resultCode = StatusCodes.BadSecurityChecksFailed; } throw new ServiceResultException(new ServiceResult(resultCode, result)); } /// /// Verifies that the request header is valid. /// /// The secure channel context. /// The request header. /// Type of the request. /// The request lifetime. /// protected virtual async ValueTask ValidateRequestAsync( SecureChannelContext secureChannelContext, [NotNull] RequestHeader? requestHeader, RequestType requestType, RequestLifetime requestLifetime) { base.ValidateRequest(requestHeader); if (!ServerInternal.IsRunning) { throw new ServiceResultException(StatusCodes.BadServerHalted); } OperationContext context = await ServerInternal.SessionManager .ValidateRequestAsync(requestHeader, secureChannelContext, requestType, requestLifetime).ConfigureAwait(false); if (ServerUtils.EventLog.IsEnabled()) { string? requestTypeString = Enum.GetName( #if !NET8_0_OR_GREATER typeof(RequestType), #endif context.RequestType); ServerUtils.EventLog.ServerCall(requestTypeString!, context.RequestId); } m_logger.LogTrace("Server Call={RequestType}, Id={RequestId}", context.RequestType, context.RequestId); // notify the request manager. ServerInternal.RequestManager.RequestReceived(context); return context; } /// /// Validate operation limits. /// /// /// A list of operations. /// The operation limit property. /// BadNothingToDo if list is null or empty. /// BadTooManyOperations if list is larger than operation limit property. protected void ValidateOperationLimits( ArrayOf operation, PropertyState? operationLimit = null) { if (operation.IsEmpty) { throw new ServiceResultException(StatusCodes.BadNothingToDo); } ValidateOperationLimits(operation.Count, operationLimit); } /// /// Validate operation limits. /// /// A count of operations. /// The operation limit property. /// BadTooManyOperations if count is larger than operation limit property. protected void ValidateOperationLimits(int count, PropertyState? operationLimit) { uint operationLimitValue = operationLimit != null ? operationLimit.Value : 0; if (operationLimitValue > 0 && count > operationLimitValue) { throw new ServiceResultException(StatusCodes.BadTooManyOperations); } } /// /// Translates an exception. /// /// The context. /// The ServiceResultException e. /// Returns an exception thrown when a UA defined error occurs, the return type is . protected virtual ServiceResultException TranslateException( OperationContext context, ServiceResultException e) { ArrayOf preferredLocales = default; if (context != null && context.Session != null) { preferredLocales = context.Session.PreferredLocales; } return TranslateException(context!.DiagnosticsMask, preferredLocales, e); } /// /// Translates an exception. /// /// The fields to return. /// The preferred locales. /// The ServiceResultException e. /// Returns an exception thrown when a UA defined error occurs, the return type is . /// /// is null. /// protected virtual ServiceResultException TranslateException( DiagnosticsMasks diagnosticsMasks, ArrayOf preferredLocales, ServiceResultException e) { if (e == null) { throw new ArgumentNullException(nameof(e)); } // 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 = default; if ((diagnosticsMasks & DiagnosticsMasks.ServiceLocalizedText) != 0) { translatedText = e.LocalizedText; } // create new result object. var result = new ServiceResult( e.NamespaceUri, new StatusCode(e.StatusCode.Code, e.SymbolicId), translatedText, e.AdditionalInfo, innerResult); // translate result. result = ServerInternal.ResourceManager.Translate(preferredLocales, result)!; return new ServiceResultException(result); } /// /// Translates a service result. /// /// The fields to return. /// The preferred locales. /// The result. /// Returns a class that combines the status code and diagnostic info structures. protected virtual ServiceResult? TranslateResult( DiagnosticsMasks diagnosticsMasks, ArrayOf preferredLocales, ServiceResult result) { if (result == null) { return null; } return ServerInternal.ResourceManager.Translate(preferredLocales, result); } /// /// Verifies that the request header is valid. /// /// The operation context. /// protected virtual void OnRequestComplete(OperationContext context) { m_semaphoreSlim.Wait(); try { if (m_serverInternal == null) { throw new ServiceResultException(StatusCodes.BadServerHalted); } ServerInternal.RequestManager.RequestCompleted(context); } finally { m_semaphoreSlim.Release(); } } /// /// Raised when the configuration changes. /// /// The sender. /// The instance containing the event data. [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2072", Justification = "Configuration.GetType() returns a concrete type whose constructor is preserved at the call site.")] 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); } } /// 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); ApplicationConfiguration currentConfiguration = Configuration!; SecurityConfiguration currentSecurityConfiguration = currentConfiguration .SecurityConfiguration; currentSecurityConfiguration.TrustedIssuerCertificates = configuration .SecurityConfiguration .TrustedIssuerCertificates; currentSecurityConfiguration.TrustedPeerCertificates = configuration .SecurityConfiguration .TrustedPeerCertificates; currentSecurityConfiguration.RejectedCertificateStore = configuration .SecurityConfiguration .RejectedCertificateStore; if (CertificateManager != null) { await CertificateManager.UpdateAsync( Configuration!.SecurityConfiguration, Configuration.ApplicationUri, cancellationToken) .ConfigureAwait(false); } // update trace configuration. currentConfiguration.TraceConfiguration = configuration.TraceConfiguration ?? new TraceConfiguration(); // Legacy Utils trace pipeline; kept for 1.5.378 -> 2.0 migration. #pragma warning disable CS0618 // Type or member is obsolete currentConfiguration.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(); } } /// /// Called before the server starts. /// /// The configuration. 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(); } } /// /// Creates the endpoints and creates the hosts. /// /// The configuration. /// The transport listener binding factory. /// The server description. /// The endpoints. /// /// Returns IList of a host for a UA service. /// protected override IList InitializeServiceHosts( ApplicationConfiguration configuration, ITransportListenerBindings bindingFactory, out ApplicationDescription serverDescription, out ArrayOf endpoints) { var hosts = new Dictionary(); // ensure at least one security policy exists. if (configuration.ServerConfiguration!.SecurityPolicies.IsEmpty) { configuration.ServerConfiguration.SecurityPolicies = configuration.ServerConfiguration.SecurityPolicies.AddItem(new ServerSecurityPolicy()); } // ensure at least one user token policy exists. if (configuration.ServerConfiguration.UserTokenPolicies.IsEmpty) { var userTokenPolicy = new UserTokenPolicy { TokenType = UserTokenType.Anonymous }; userTokenPolicy.PolicyId = userTokenPolicy.TokenType.ToString(); configuration.ServerConfiguration.UserTokenPolicies += 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() }; List endpointsList = []; ArrayOf baseAddresses = configuration.ServerConfiguration.BaseAddresses; foreach ( string scheme in Utils.DefaultUriSchemes.Where(scheme => baseAddresses.Contains(a => a.StartsWith(scheme, StringComparison.Ordinal)))) { ITransportListenerFactory? binding = bindingFactory.GetBinding(scheme, MessageContext.Telemetry); if (binding != null) { List endpointsForHost = binding.CreateServiceHost( this, hosts, configuration, configuration.ServerConfiguration.BaseAddresses, serverDescription, configuration.ServerConfiguration.SecurityPolicies, CertificateManager!, configuration.CertificateManager!); endpointsList.AddRange(endpointsForHost); } } endpoints = endpointsList; return [.. hosts.Values]; } /// /// Creates an instance of the service host. /// public override ServiceHost CreateServiceHost(ServerBase server, params Uri[] addresses) { return new ServiceHost(this, addresses); } /// /// Returns an instance of the endpoint to use. /// protected override EndpointBase GetEndpointInstance(ServerBase server) { return new SessionEndpoint(server); } /// 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, TimeProvider); // 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); 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. 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. await ServerInternal.CreateServerObjectAsync( eventManager, resourceManager, requestManager, cancellationToken).ConfigureAwait(false); // 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."); ServerInternal.SetAggregateManager( await CreateAggregateManagerAsync(m_serverInternal, configuration, cancellationToken).ConfigureAwait(false)); // create the manager responsible for modelling rules. m_logger.LogInformation(Utils.TraceMasks.StartStop, "Server - CreateModellingRulesManager."); ServerInternal.SetModellingRulesManager( await CreateModellingRulesManagerAsync(m_serverInternal, configuration, cancellationToken).ConfigureAwait(false)); // 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. ServerInternal.SetMonitoredItemQueueFactory(monitoredItemQueueFactory!); //create the SubscriptionStore ISubscriptionStore? subscriptionStore = CreateSubscriptionStore( m_serverInternal, configuration); //add the SubscriptionStore to the datastore 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. 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 += 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(); var discoveryUrls = new List(); 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; } discoveryUrls.Add(uri.ToString()); } m_registrationInfo.DiscoveryUrls = discoveryUrls; // 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 = TimeProvider.CreateTimer( OnRegisterServerAsync, this, TimeSpan.FromMilliseconds(m_minRegistrationInterval), Timeout.InfiniteTimeSpan); } } } catch (Exception e) { const string message = "Unexpected error starting application"; m_logger.LogCritical(Utils.TraceMasks.StartStop, e, message); m_serverInternal?.Dispose(); 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."); // Surface the live transport listener registry through the // ITransportListenerRegistryProvider so consumers such as // ConfigurationNodeManager can drive post-response channel // cuts after ApplyChanges (OPC UA Part 12 §7.10.9). m_serverInternal.SetTransportListenerRegistry(TransportListeners.AsReadOnly()); 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; } // Log availability of the new CertificateManager if (CertificateManager != null) { m_logger.LogInformation(Utils.TraceMasks.StartStop, "CertificateManager initialized with {Count} trust lists.", CertificateManager.TrustLists.Count); // Subscribe to CertificateManager change notifications and // fan-out to OnCertificateUpdateAsync so endpoint descriptions // and transport listeners pick up cert hot-updates. m_certManagerSubscription = CertificateManager.CertificateChanges .Subscribe(new CertificateManagerChangeObserver(this, m_logger)); } } /// 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) { ServerInternal.SessionManager.SessionChannelKeepAlive -= SessionChannelKeepAliveEvent; await ServerInternal.SubscriptionManager.ShutdownAsync(cancellationToken).ConfigureAwait(false); ServerInternal.SessionManager.Shutdown(); await ServerInternal.NodeManager.ShutdownAsync(cancellationToken).ConfigureAwait(false); } } finally { m_semaphoreSlim.Release(); } } catch (Exception e) { ServerError = new ServiceResult(e); } finally { // ensure that everything is cleaned up. m_serverInternal?.Dispose(); m_serverInternal = null; } } /// /// 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. /// /// The AuthenticationToken from the RequestHeader /// The Channel id /// returns true if a channelId was found for the provided AuthenticationToken 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); } /// /// Implements the server shutdown delay if session are connected. /// protected void ShutDownDelay() { try { // check for connected clients. IList 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."); ServerStatusState statusVariable = status!.Variable!; statusVariable.ShutdownReason!.Value = new LocalizedText( "en-US", "Application is shutting down."); status.Value.State = ServerState.Shutdown; statusVariable.State!.Value = ServerState.Shutdown; statusVariable .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. } } /// /// Creates the request manager for the server. /// /// The server. /// The configuration. /// /// Returns an object that manages requests from within the server, return type is . /// protected virtual RequestManager CreateRequestManager( IServerInternal server, ApplicationConfiguration configuration) { return new RequestManager(server); } /// /// Creates the aggregate manager used by the server. /// /// The server. /// The application configuration. /// A token to monitor for cancellation requests. /// The manager. protected virtual async ValueTask CreateAggregateManagerAsync( IServerInternal server, ApplicationConfiguration configuration, CancellationToken cancellationToken = default) { var manager = new AggregateManager(server); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Interpolative, BrowseNames.AggregateFunction_Interpolative, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Average, BrowseNames.AggregateFunction_Average, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_TimeAverage, BrowseNames.AggregateFunction_TimeAverage, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_TimeAverage2, BrowseNames.AggregateFunction_TimeAverage2, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Total, BrowseNames.AggregateFunction_Total, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Total2, BrowseNames.AggregateFunction_Total2, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Minimum, BrowseNames.AggregateFunction_Minimum, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Maximum, BrowseNames.AggregateFunction_Maximum, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_MinimumActualTime, BrowseNames.AggregateFunction_MinimumActualTime, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_MaximumActualTime, BrowseNames.AggregateFunction_MaximumActualTime, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Range, BrowseNames.AggregateFunction_Range, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Minimum2, BrowseNames.AggregateFunction_Minimum2, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Maximum2, BrowseNames.AggregateFunction_Maximum2, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_MinimumActualTime2, BrowseNames.AggregateFunction_MinimumActualTime2, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_MaximumActualTime2, BrowseNames.AggregateFunction_MaximumActualTime2, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Range2, BrowseNames.AggregateFunction_Range2, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Count, BrowseNames.AggregateFunction_Count, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_AnnotationCount, BrowseNames.AggregateFunction_AnnotationCount, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_DurationInStateZero, BrowseNames.AggregateFunction_DurationInStateZero, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_DurationInStateNonZero, BrowseNames.AggregateFunction_DurationInStateNonZero, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_NumberOfTransitions, BrowseNames.AggregateFunction_NumberOfTransitions, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Start, BrowseNames.AggregateFunction_Start, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_End, BrowseNames.AggregateFunction_End, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_Delta, BrowseNames.AggregateFunction_Delta, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_StartBound, BrowseNames.AggregateFunction_StartBound, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_EndBound, BrowseNames.AggregateFunction_EndBound, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_DeltaBounds, BrowseNames.AggregateFunction_DeltaBounds, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_DurationGood, BrowseNames.AggregateFunction_DurationGood, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_DurationBad, BrowseNames.AggregateFunction_DurationBad, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_PercentGood, BrowseNames.AggregateFunction_PercentGood, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_PercentBad, BrowseNames.AggregateFunction_PercentBad, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_WorstQuality, BrowseNames.AggregateFunction_WorstQuality, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_WorstQuality2, BrowseNames.AggregateFunction_WorstQuality2, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_StandardDeviationPopulation, BrowseNames.AggregateFunction_StandardDeviationPopulation, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_VariancePopulation, BrowseNames.AggregateFunction_VariancePopulation, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_StandardDeviationSample, BrowseNames.AggregateFunction_StandardDeviationSample, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); await manager.RegisterFactoryAsync( ObjectIds.AggregateFunction_VarianceSample, BrowseNames.AggregateFunction_VarianceSample, Aggregators.CreateStandardCalculator, cancellationToken).ConfigureAwait(false); return manager; } /// /// Creates the modelling rules manager used by the server. /// /// The server. /// The application configuration. /// The cancellation Token /// The manager. protected virtual async ValueTask CreateModellingRulesManagerAsync( IServerInternal server, ApplicationConfiguration configuration, CancellationToken cancellationToken = default) { var manager = new ModellingRulesManager(server); await manager.RegisterModellingRuleAsync(ObjectIds.ModellingRule_Mandatory, BrowseNames.ModellingRule_Mandatory, cancellationToken) .ConfigureAwait(false); await manager.RegisterModellingRuleAsync(ObjectIds.ModellingRule_Optional, BrowseNames.ModellingRule_Optional, cancellationToken) .ConfigureAwait(false); await manager.RegisterModellingRuleAsync(ObjectIds.ModellingRule_ExposesItsArray, BrowseNames.ModellingRule_ExposesItsArray, cancellationToken) .ConfigureAwait(false); await manager.RegisterModellingRuleAsync( ObjectIds.ModellingRule_OptionalPlaceholder, BrowseNames.ModellingRule_OptionalPlaceholder, cancellationToken).ConfigureAwait(false); await manager.RegisterModellingRuleAsync( ObjectIds.ModellingRule_MandatoryPlaceholder, BrowseNames.ModellingRule_MandatoryPlaceholder, cancellationToken).ConfigureAwait(false); return manager; } /// /// Creates the resource manager for the server. /// /// The server. /// The configuration. /// Returns an object that manages access to localized resources, the return type is . protected virtual ResourceManager CreateResourceManager( IServerInternal server, ApplicationConfiguration configuration) { var resourceManager = new ResourceManager(configuration); // load default text for all status codes. resourceManager.LoadDefaultText(); return resourceManager; } /// /// Creates the master node manager for the server (async). /// /// The server. /// The configuration. /// The cancellation token. /// The configured master node manager. /// /// Subclasses that need to plug in custom async node-manager factories should /// override this method. The default implementation awaits each registered /// and delegates the synchronous /// factories to . /// protected virtual async ValueTask CreateMasterNodeManagerAsync( IServerInternal server, ApplicationConfiguration configuration, CancellationToken cancellationToken = default) { var nodeManagers = new List(); foreach (INodeManagerFactory nodeManagerFactory in m_nodeManagerFactories) { nodeManagers.Add(nodeManagerFactory.Create(server, configuration)); } var asyncNodeManagers = new List(); foreach (IAsyncNodeManagerFactory nodeManagerFactory in m_asyncNodeManagerFactories) { asyncNodeManagers.Add(await nodeManagerFactory.CreateAsync(server, configuration, cancellationToken) .ConfigureAwait(false)); } return new MasterNodeManager(server, configuration, null, asyncNodeManagers, nodeManagers); } /// /// Creates the master node manager for the server. /// /// The server. /// The configuration. /// Returns the master node manager for the server, the return type is . protected virtual IMasterNodeManager CreateMasterNodeManager( IServerInternal server, ApplicationConfiguration configuration) { return CreateMasterNodeManagerAsync(server, configuration).AsTask().GetAwaiter().GetResult(); } /// /// Creates the master node manager for the server. /// /// The server. /// The configuration. /// Returns the master node manager for the server, the return type is . protected virtual IMainNodeManagerFactory CreateMainNodeManagerFactory( IServerInternal server, ApplicationConfiguration configuration) { return new MainNodeManagerFactory(configuration, server); } /// /// Creates the event manager for the server. /// /// The server. /// The configuration. /// Returns an object that manages all events raised within the server, the return type is . protected virtual EventManager CreateEventManager( IServerInternal server, ApplicationConfiguration configuration) { return new EventManager( server, (uint)configuration.ServerConfiguration!.MaxEventQueueSize, (uint)configuration.ServerConfiguration.MaxDurableEventQueueSize); } /// /// Creates the session manager for the server. /// /// The server. /// The configuration. /// Returns a generic session manager object for a server, the return type is . protected virtual ISessionManager CreateSessionManager( IServerInternal server, ApplicationConfiguration configuration) { return new SessionManager(server, configuration, TimeProvider); } /// /// Creates the session manager for the server. /// /// The server. /// The configuration. /// Returns a generic session manager object for a server, the return type is . protected virtual ISubscriptionManager CreateSubscriptionManager( IServerInternal server, ApplicationConfiguration configuration) { return new SubscriptionManager(server, configuration); } /// /// Creates the (durable) monitored item queue factory for the server. /// /// The server. /// The configuration. /// Returns a (durable) monitored item queue factory for a server, the return type is . protected virtual IMonitoredItemQueueFactory? CreateMonitoredItemQueueFactory( IServerInternal server, ApplicationConfiguration configuration) { return new MonitoredItemQueueFactory(MessageContext.Telemetry); } /// /// Creates the subscriptionStore for the server. /// /// The server. /// The configuration. /// Returns a subscriptionStore for a server, the return type is . protected virtual ISubscriptionStore? CreateSubscriptionStore( IServerInternal server, ApplicationConfiguration configuration) { return null; } /// /// Called after the node managers have been started. /// /// The server. protected virtual void OnNodeManagerStarted(IServerInternal server) { // may be overridden by the subclass. } /// /// Called after the server has been started. /// /// The server. protected virtual void OnServerStarted(IServerInternal server) { // may be overridden by the subclass. } /// public IEnumerable NodeManagerFactories => m_nodeManagerFactories; /// public IEnumerable AsyncNodeManagerFactories => m_asyncNodeManagerFactories; /// public virtual void AddNodeManager(INodeManagerFactory nodeManagerFactory) { m_nodeManagerFactories.Add(nodeManagerFactory); } /// public virtual void AddNodeManager(IAsyncNodeManagerFactory nodeManagerFactory) { m_asyncNodeManagerFactories.Add(nodeManagerFactory); } /// public virtual void RemoveNodeManager(INodeManagerFactory nodeManagerFactory) { m_nodeManagerFactories.Remove(nodeManagerFactory); } /// public virtual void RemoveNodeManager(IAsyncNodeManagerFactory nodeManagerFactory) { m_asyncNodeManagerFactories.Remove(nodeManagerFactory); } /// /// Reacts to a session channel keep alive event to signal /// a listener channel that a session is still active. /// 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 ServerInternalData? m_serverInternal; private ConfigurationWatcher? m_configurationWatcher; private ConfiguredEndpointCollection? m_registrationEndpoints; private RegisteredServer? m_registrationInfo; private ITimer? 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 m_nodeManagerFactories = []; private readonly List m_asyncNodeManagerFactories = []; private IDisposable? m_certManagerSubscription; private sealed class CertificateManagerChangeObserver : IObserver { private readonly StandardServer _server; private readonly ILogger _logger; public CertificateManagerChangeObserver(StandardServer server, ILogger logger) { _server = server; _logger = logger; } public void OnNext(CertificateChangeEvent value) { if (value.Kind == CertificateChangeKind.ApplicationCertificateUpdated) { _logger.LogInformation( "CertificateManager: Application certificate updated for type {CertType}.", value.CertificateType); // Fan-out the cert update to endpoint descriptions and // transport listeners. The reload itself was performed by // CertificateManager.UpdateAsync / // ReloadApplicationCertificatesAsync before this notification // fired, so we only need to refresh downstream consumers. try { ICertificateValidatorEx validator = _server.CertificateManager!; var args = new CertificateUpdateEventArgs( _server.Configuration?.SecurityConfiguration!, validator); _server.OnCertificateUpdateAsync(_server, args); } catch (Exception ex) { _logger.LogError(ex, "CertificateManager change observer failed to fan-out cert update."); } } } public void OnError(Exception error) { } public void OnCompleted() { } } } }