/* ======================================================================== * Copyright (c) 2005-2013 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.ComponentModel; using System.Drawing; using System.Windows.Forms; using System.Threading.Tasks; namespace Opc.Ua.Client.Controls { /// /// A tool bar used to connect to a server. /// public partial class ConnectServerCtrl : UserControl { #region Constructors /// /// Initializes the object. /// public ConnectServerCtrl() { InitializeComponent(); m_CertificateValidation = new CertificateValidationEventHandler(CertificateValidator_CertificateValidation); } #endregion #region Private Fields private ApplicationConfiguration m_configuration; private Session m_session; private int m_reconnectPeriod = 10; private SessionReconnectHandler m_reconnectHandler; private CertificateValidationEventHandler m_CertificateValidation; private EventHandler m_ReconnectComplete; private EventHandler m_ReconnectStarting; private EventHandler m_KeepAliveComplete; private EventHandler m_ConnectComplete; private StatusStrip m_StatusStrip; private ToolStripItem m_ServerStatusLB; private ToolStripItem m_StatusUpateTimeLB; #endregion #region Public Members /// /// A strip used to display session status information. /// public StatusStrip StatusStrip { get { return m_StatusStrip; } set { if (!Object.ReferenceEquals(m_StatusStrip, value)) { m_StatusStrip = value; if (value != null) { m_ServerStatusLB = new ToolStripStatusLabel(); m_StatusUpateTimeLB = new ToolStripStatusLabel(); m_StatusStrip.Items.Add(m_ServerStatusLB); m_StatusStrip.Items.Add(m_StatusUpateTimeLB); } } } } /// /// A control that contains the last time a keep alive was returned from the server. /// public ToolStripItem ServerStatusControl { get { return m_ServerStatusLB; } set { m_ServerStatusLB = value; } } /// /// A control that contains the last time a keep alive was returned from the server. /// public ToolStripItem StatusUpateTimeControl { get { return m_StatusUpateTimeLB; } set { m_StatusUpateTimeLB = value; } } /// /// The name of the session to create. /// public string SessionName { get; set; } /// /// Gets or sets a flag indicating that the domain checks should be ignored when connecting. /// public bool DisableDomainCheck { get; set; } /// /// The URL displayed in the control. /// public string ServerUrl { get { if (UrlCB.SelectedIndex >= 0) { return (string)UrlCB.SelectedItem; } return UrlCB.Text; } set { UrlCB.SelectedIndex = -1; UrlCB.Text = value; } } /// /// Whether to use security when connecting. /// public bool UseSecurity { get { return UseSecurityCK.Checked; } set { UseSecurityCK.Checked = value; } } /// /// The locales to use when creating the session. /// public string[] PreferredLocales { get; set; } /// /// The user identity to use when creating the session. /// public IUserIdentity UserIdentity { get; set; } /// /// The client application configuration. /// public ApplicationConfiguration Configuration { get { return m_configuration; } set { if (!Object.ReferenceEquals(m_configuration, value)) { if (m_configuration != null) { m_configuration.CertificateValidator.CertificateValidation -= m_CertificateValidation; } m_configuration = value; if (m_configuration != null) { m_configuration.CertificateValidator.CertificateValidation += m_CertificateValidation; } } } } /// /// The currently active session. /// public Session Session { get { return m_session; } } /// /// The number of seconds between reconnect attempts (0 means reconnect is disabled). /// [DefaultValue(10)] public int ReconnectPeriod { get { return m_reconnectPeriod; } set { m_reconnectPeriod = value; } } /// /// Raised when a good keep alive from the server arrives. /// public event EventHandler KeepAliveComplete { add { m_KeepAliveComplete += value; } remove { m_KeepAliveComplete -= value; } } /// /// Raised when a reconnect operation starts. /// public event EventHandler ReconnectStarting { add { m_ReconnectStarting += value; } remove { m_ReconnectStarting -= value; } } /// /// Raised when a reconnect operation completes. /// public event EventHandler ReconnectComplete { add { m_ReconnectComplete += value; } remove { m_ReconnectComplete -= value; } } /// /// Raised after successfully connecting to or disconnecing from a server. /// public event EventHandler ConnectComplete { add { m_ConnectComplete += value; } remove { m_ConnectComplete -= value; } } /// /// Sets the URLs shown in the control. /// public void SetAvailableUrls(IList urls) { UrlCB.Items.Clear(); if (urls != null) { foreach (string url in urls) { int index = url.LastIndexOf("/discovery", StringComparison.InvariantCultureIgnoreCase); if (index != -1) { UrlCB.Items.Add(url.Substring(0, index)); continue; } UrlCB.Items.Add(url); } if (UrlCB.Items.Count > 0) { UrlCB.SelectedIndex = 0; } } } /// /// Creates a new session. /// /// The new session object. public async Task Connect() { // disconnect from existing session. Disconnect(); // determine the URL that was selected. string serverUrl = UrlCB.Text; if (UrlCB.SelectedIndex >= 0) { serverUrl = (string)UrlCB.SelectedItem; } if (m_configuration == null) { throw new ArgumentNullException("m_configuration"); } // select the best endpoint. EndpointDescription endpointDescription = ClientUtils.SelectEndpoint(serverUrl, UseSecurityCK.Checked); EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration); ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); m_session = await Session.Create( m_configuration, endpoint, false, !DisableDomainCheck, (String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName, 60000, UserIdentity, PreferredLocales); // set up keep alive callback. m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive); // raise an event. DoConnectComplete(null); // return the new session. return m_session; } /// /// Creates a new session. /// /// The URL of a server endpoint. /// Whether to use security. /// The new session object. public async Task Connect(string serverUrl, bool useSecurity) { UrlCB.Text = serverUrl; UseSecurityCK.Checked = useSecurity; return await Connect(); } /// /// Disconnects from the server. /// public void Disconnect() { UpdateStatus(false, DateTime.UtcNow, "Disconnected"); // stop any reconnect operation. if (m_reconnectHandler != null) { m_reconnectHandler.Dispose(); m_reconnectHandler = null; } // disconnect any existing session. if (m_session != null) { m_session.Close(10000); m_session = null; } // raise an event. DoConnectComplete(null); } /// /// Prompts the user to choose a server on another host. /// public void Discover(string hostName) { string endpointUrl = new DiscoverServerDlg().ShowDialog(m_configuration, hostName); if (endpointUrl != null) { ServerUrl = endpointUrl; } } #endregion #region Private Methods /// /// Raises the connect complete event on the main GUI thread. /// private void DoConnectComplete(object state) { if (m_ConnectComplete != null) { if (this.InvokeRequired) { this.BeginInvoke(new System.Threading.WaitCallback(DoConnectComplete), state); return; } m_ConnectComplete(this, null); } } /// /// Finds the endpoint that best matches the current settings. /// private EndpointDescription SelectEndpoint() { try { Cursor = Cursors.WaitCursor; // determine the URL that was selected. string discoveryUrl = UrlCB.Text; if (UrlCB.SelectedIndex >= 0) { discoveryUrl = (string)UrlCB.SelectedItem; } // return the selected endpoint. return ClientUtils.SelectEndpoint(discoveryUrl, UseSecurityCK.Checked); } finally { Cursor = Cursors.Default; } } #endregion #region Event Handlers /// /// Updates the status control. /// /// Whether the status represents an error. /// The time associated with the status. /// The status message. /// Arguments used to format the status message. private void UpdateStatus(bool error, DateTime time, string status, params object[] args) { if (m_ServerStatusLB != null) { m_ServerStatusLB.Text = String.Format(status, args); m_ServerStatusLB.ForeColor = (error) ? Color.Red : Color.Empty; } if (m_StatusUpateTimeLB != null) { m_StatusUpateTimeLB.Text = time.ToLocalTime().ToString("hh:mm:ss"); m_StatusUpateTimeLB.ForeColor = (error) ? Color.Red : Color.Empty; } } /// /// Handles a keep alive event from a session. /// private void Session_KeepAlive(Session session, KeepAliveEventArgs e) { if (this.InvokeRequired) { this.BeginInvoke(new KeepAliveEventHandler(Session_KeepAlive), session, e); return; } 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 (m_reconnectPeriod <= 0) { UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status); return; } UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod); if (m_reconnectHandler == null) { if (m_ReconnectStarting != null) { m_ReconnectStarting(this, e); } m_reconnectHandler = new SessionReconnectHandler(); m_reconnectHandler.BeginReconnect(m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete); } return; } // update status. UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl); // raise any additional notifications. if (m_KeepAliveComplete != null) { m_KeepAliveComplete(this, e); } } catch (Exception exception) { ClientUtils.HandleException(this.Text, exception); } } /// /// Handles a click on the connect button. /// private async void Server_ConnectMI_Click(object sender, EventArgs e) { try { await Connect(); } catch (Exception exception) { ClientUtils.HandleException(this.Text, exception); } } /// /// Handles a reconnect event complete from the reconnect handler. /// private void Server_ReconnectComplete(object sender, EventArgs e) { if (this.InvokeRequired) { this.BeginInvoke(new EventHandler(Server_ReconnectComplete), sender, e); return; } try { // ignore callbacks from discarded objects. if (!Object.ReferenceEquals(sender, m_reconnectHandler)) { return; } m_session = m_reconnectHandler.Session; m_reconnectHandler.Dispose(); m_reconnectHandler = null; // raise any additional notifications. if (m_ReconnectComplete != null) { m_ReconnectComplete(this, e); } } catch (Exception exception) { ClientUtils.HandleException(this.Text, exception); } } /// /// Handles a certificate validation error. /// private void CertificateValidator_CertificateValidation(CertificateValidator sender, CertificateValidationEventArgs e) { if (this.InvokeRequired) { this.Invoke(new CertificateValidationEventHandler(CertificateValidator_CertificateValidation), sender, e); return; } try { e.Accept = m_configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates; if (!m_configuration.SecurityConfiguration.AutoAcceptUntrustedCertificates) { DialogResult result = MessageBox.Show( e.Certificate.Subject, "Untrusted Certificate", MessageBoxButtons.YesNo, MessageBoxIcon.Warning); e.Accept = (result == DialogResult.Yes); } } catch (Exception exception) { ClientUtils.HandleException(this.Text, exception); } } #endregion } }