/* ======================================================================== * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections; using System.IO; using System.Threading.Tasks; using Opc.Ua; using Opc.Ua.Client; namespace Quickstarts { /// /// OPC UA Client with examples of basic functionality. /// class UAClient : IDisposable { #region Constructors /// /// Initializes a new instance of the UAClient class. /// public UAClient(ApplicationConfiguration configuration, TextWriter writer, Action validateResponse) { m_validateResponse = validateResponse; m_output = writer; m_configuration = configuration; m_configuration.CertificateValidator.CertificateValidation += CertificateValidation; } #endregion #region IDisposable /// /// Default dispose implementation. /// /// protected virtual void Dispose(bool disposing) { if (!m_disposed) { if (disposing) { // dispose managed state (managed objects) Utils.SilentDispose(m_session); } // TODO: free unmanaged resources (unmanaged objects) and override finalizer // TODO: set large fields to null m_configuration.CertificateValidator.CertificateValidation -= CertificateValidation; m_disposed = true; } } ~UAClient() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: false); } /// /// Dispose /// public void Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method Dispose(disposing: true); GC.SuppressFinalize(this); } #endregion #region Public Properties /// /// Action used /// Action ValidateResponse => m_validateResponse; /// /// Gets the client session. /// public Session Session => m_session; /// /// The session keepalive interval to be used in ms. /// public int KeepAliveInterval { get; set; } = 5000; /// /// The reconnect period to be used in ms. /// public int ReconnectPeriod { get; set; } = 10000; /// /// The session lifetime. /// public uint SessionLifeTime { get; set; } = 30 * 1000; /// /// Auto accept untrusted certificates. /// public bool AutoAccept { get; set; } = false; /// /// The file to use for log output. /// public string LogFile { get; set; } #endregion #region Public Methods /// /// Creates a session with the UA server /// public async Task ConnectAsync(string serverUrl, bool useSecurity = true) { if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl)); try { if (m_session != null && m_session.Connected == true) { m_output.WriteLine("Session already connected!"); } else { m_output.WriteLine("Connecting to... {0}", serverUrl); // Get the endpoint by connecting to server's discovery endpoint. // Try to find the first endopint with security. EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, serverUrl, useSecurity); EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration); ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); // Create the session Session session = await Session.Create( m_configuration, endpoint, false, false, m_configuration.ApplicationName, SessionLifeTime, new UserIdentity(), null ); // Assign the created session if (session != null && session.Connected) { m_session = session; // override keep alive interval m_session.KeepAliveInterval = KeepAliveInterval; // set up keep alive callback. m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive); } // Session created successfully. m_output.WriteLine("New Session Created with SessionName = {0}", m_session.SessionName); } return true; } catch (Exception ex) { // Log Error m_output.WriteLine("Create Session Error : {0}", ex.Message); return false; } } /// /// Disconnects the session. /// public void Disconnect() { try { if (m_session != null) { m_output.WriteLine("Disconnecting..."); m_session.Close(); m_session.Dispose(); m_session = null; // Log Session Disconnected event m_output.WriteLine("Session Disconnected."); } else { m_output.WriteLine("Session not created!"); } } catch (Exception ex) { // Log Error m_output.WriteLine($"Disconnect Error : {ex.Message}"); } } /// /// Handles a keep alive event from a session and triggers a reconnect if necessary. /// private void Session_KeepAlive(Session session, KeepAliveEventArgs e) { try { // check for events from discarded sessions. if (!Object.ReferenceEquals(session, m_session)) { return; } // start reconnect sequence on communication error. if (ServiceResult.IsBad(e.Status)) { if (ReconnectPeriod <= 0) { Utils.LogWarning("KeepAlive status {0}, but reconnect is disabled.", e.Status); return; } lock (m_lock) { if (m_reconnectHandler == null) { Utils.LogInfo("KeepAlive status {0}, reconnecting in {1}ms.", e.Status, ReconnectPeriod); m_output.WriteLine("--- RECONNECTING {0} ---", e.Status); m_reconnectHandler = new SessionReconnectHandler(true); m_reconnectHandler.BeginReconnect(m_session, ReconnectPeriod, Client_ReconnectComplete); } else { Utils.LogInfo("KeepAlive status {0}, reconnect in progress.", e.Status); } } return; } } catch (Exception exception) { Utils.LogError(exception, "Error in OnKeepAlive."); } } /// /// Called when the reconnect attempt was successful. /// private void Client_ReconnectComplete(object sender, EventArgs e) { // ignore callbacks from discarded objects. if (!Object.ReferenceEquals(sender, m_reconnectHandler)) { return; } lock (m_lock) { // if session recovered, Session property is null if (m_reconnectHandler.Session != null) { m_session = m_reconnectHandler.Session; } m_reconnectHandler.Dispose(); m_reconnectHandler = null; } m_output.WriteLine("--- RECONNECTED ---"); } #endregion #region Protected Methods /// /// Handles the certificate validation event. /// This event is triggered every time an untrusted certificate is received from the server. /// protected virtual void CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e) { bool certificateAccepted = false; // **** // Implement a custom logic to decide if the certificate should be // accepted or not and set certificateAccepted flag accordingly. // The certificate can be retrieved from the e.Certificate field // *** ServiceResult error = e.Error; m_output.WriteLine(error); if (error.StatusCode == StatusCodes.BadCertificateUntrusted && AutoAccept) { certificateAccepted = true; } if (certificateAccepted) { m_output.WriteLine("Untrusted Certificate accepted. Subject = {0}", e.Certificate.Subject); e.Accept = true; } else { m_output.WriteLine("Untrusted Certificate rejected. Subject = {0}", e.Certificate.Subject); } } #endregion #region Private Fields private object m_lock = new object(); private ApplicationConfiguration m_configuration; private Session m_session; private bool m_disposed; private SessionReconnectHandler m_reconnectHandler; private readonly TextWriter m_output; private readonly Action m_validateResponse; #endregion } }