/* ======================================================================== * 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.Generic; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Reflection; using System.Threading; using System.Security.Cryptography.X509Certificates; using Opc.Ua.Security.Certificates; namespace Opc.Ua.Client.Controls { /// /// Prompts the user to edit a ComPseudoServerDlg. /// public partial class ConfiguredServerDlg : Form { #region Constructors /// /// Initializes the dialog. /// public ConfiguredServerDlg() { InitializeComponent(); this.Icon = ClientUtils.GetAppIcon(); m_userIdentities = new Dictionary(); m_statusObject = new StatusObject((int)StatusChannel.MaxStatusChannels); } #endregion #region Private Fields /// /// The possible encodings. /// private enum Encoding { Default, Xml, Binary } /// /// The type of status (for coloring the status textbox). /// private enum StatusType { Ok = 0, Warning = 1, Error = 2 } /// /// The status channel inside the StatusObject. /// private enum StatusChannel { Discovery = 0, SelectedSecurityMode = 1, ApplicationType = 2, SelectedProtocol = 3, ApplicationUri = 4, DiscoveryURLs = 5, Server = 6, DifferentCertificate = 7, SecurityPolicyUri = 8, TransportProfileUri = 9, SelectedSecurityPolicy = 10, MaxStatusChannels = 11 } /// /// Whether to override limits /// private enum UseDefaultLimits { Yes, No } /// /// This class merges multiple error/warning/status codes from multiple sources. /// Initialize it with the number of status channels and update "StatusChannel" accordingly. /// Provides a general view of all the statuses (joined texts, worst status). /// private class StatusObject { public StatusObject(int maxChannels) { m_maxChannels = maxChannels; m_statusTexts = new string[maxChannels]; m_statusTypes = new StatusType[maxChannels]; for (int i = 0; i < m_maxChannels; ++i) { m_statusTexts[i] = String.Empty; m_statusTypes[i] = StatusType.Ok; } } public String StatusString { get { String status = String.Empty; for (int i = 0; i < m_maxChannels; ++i) { if (!String.IsNullOrEmpty(m_statusTexts[i])) { if (!String.IsNullOrEmpty(status)) { status += " | "; } status += m_statusTexts[i]; } } return status; } } public StatusType StatusType { get { StatusType type = StatusType.Ok; for (int i = 0; i < m_maxChannels; ++i) { if (m_statusTypes[i] > type) { type = m_statusTypes[i]; } } return type; } } public void SetStatus(StatusChannel channel, String text, StatusType type) { int intChannel = (int)channel; if ((intChannel >= 0) && (intChannel < m_maxChannels)) { m_statusTexts[intChannel] = text; m_statusTypes[intChannel] = type; } } public void ClearStatus(StatusChannel channel) { int intChannel = (int)channel; if ((intChannel >= 0) && (intChannel < m_maxChannels)) { m_statusTexts[intChannel] = String.Empty; m_statusTypes[intChannel] = StatusType.Ok; } } private int m_maxChannels; private String[] m_statusTexts; private StatusType[] m_statusTypes; } /// /// This class is used by the EndopintListLB (list box). /// Holds references to the received EndpointDescription and its MessageSecurityMode, SecurityPolicyUri, MessageSecurityMode and EncodingSupport. /// Also prepares a user-friendly text representation of all the endpoint-rellevant characteristics. /// The extracted EndpointDescription properties are used in selecting the right combo-box values when user clicks in the endpoint list box. /// private class EndpointDescriptionString { public EndpointDescriptionString(EndpointDescription endpointDescription) { m_endpointDescription = endpointDescription; m_protocol = new Protocol(endpointDescription); m_currentPolicy = SecurityPolicies.GetDisplayName(endpointDescription.SecurityPolicyUri); m_messageSecurityMode = endpointDescription.SecurityMode; switch (m_endpointDescription.EncodingSupport) { case BinaryEncodingSupport.None: { m_encoding = Encoding.Xml; break; } case BinaryEncodingSupport.Optional: case BinaryEncodingSupport.Required: { m_encoding = Encoding.Binary; break; } } BuildEndpointDescription(); } public EndpointDescription EndpointDescription { get { return m_endpointDescription; } } public Protocol Protocol { get { return m_protocol; } } public string CurrentPolicy { get { return m_currentPolicy; } } public MessageSecurityMode MessageSecurityMode { get { return m_messageSecurityMode; } } public Encoding Encoding { get { return m_encoding; } } public override string ToString() { return m_stringRepresentation; } private void BuildEndpointDescription() { m_stringRepresentation = m_protocol.ToString() + " - "; m_stringRepresentation += m_endpointDescription.SecurityMode + " - "; m_stringRepresentation += SecurityPolicies.GetDisplayName(m_endpointDescription.SecurityPolicyUri) + " - "; switch (m_endpointDescription.EncodingSupport) { case BinaryEncodingSupport.None: { m_stringRepresentation += Encoding.Xml; break; } case BinaryEncodingSupport.Required: { m_stringRepresentation += Encoding.Binary; break; } case BinaryEncodingSupport.Optional: { m_stringRepresentation += Encoding.Binary + "/" + Encoding.Xml; break; } } } private Protocol m_protocol; private EndpointDescription m_endpointDescription; private MessageSecurityMode m_messageSecurityMode; private string m_currentPolicy; private Encoding m_encoding; private string m_stringRepresentation; } private ConfiguredEndpoint m_endpoint; private EndpointDescription m_currentDescription; private EndpointDescriptionCollection m_availableEndpoints; private List m_availableEndpointsDescriptions; private int m_discoveryTimeout; private int m_discoverCount; private ApplicationConfiguration m_configuration; private bool m_updating; private bool m_selecting; private Dictionary m_userIdentities; private EndpointConfiguration m_endpointConfiguration; private bool m_discoverySucceeded; private Uri m_discoveryUrl; private bool m_showAllOptions; private StatusObject m_statusObject; #endregion #region Public Interface public EndpointDescriptionCollection AvailableEnpoints { get { return m_availableEndpoints; } } /// /// The timeout in milliseconds to use when discovering servers. /// [System.ComponentModel.DefaultValue(20000)] public int DiscoveryTimeout { get { return m_discoveryTimeout; } set { Interlocked.Exchange(ref m_discoveryTimeout, value); } } /// /// Displays the dialog. /// public ConfiguredEndpoint ShowDialog(ApplicationDescription server, ApplicationConfiguration configuration) { if (server == null) throw new ArgumentNullException("server"); m_configuration = configuration; // construct a list of available endpoint descriptions for the application. m_availableEndpoints = new EndpointDescriptionCollection(); m_availableEndpointsDescriptions = new List(); m_endpointConfiguration = EndpointConfiguration.Create(configuration); // create a default endpoint description. m_endpoint = null; m_currentDescription = null; // initializing the protocol will trigger an update to all other controls. InitializeProtocols(m_availableEndpoints); BuildEndpointDescriptionStrings(m_availableEndpoints); // discover endpoints in the background. m_discoverySucceeded = false; Interlocked.Increment(ref m_discoverCount); ThreadPool.QueueUserWorkItem(new WaitCallback(OnDiscoverEndpoints), server); if (ShowDialog() != DialogResult.OK) { return null; } return m_endpoint; } /// /// Displays the dialog. /// public ConfiguredEndpoint ShowDialog(ConfiguredEndpoint endpoint, ApplicationConfiguration configuration) { if (endpoint == null) throw new ArgumentNullException("endpoint"); m_endpoint = endpoint; m_configuration = configuration; // construct a list of available endpoint descriptions for the application. m_availableEndpoints = new EndpointDescriptionCollection(); m_availableEndpointsDescriptions = new List(); m_availableEndpoints.Add(endpoint.Description); m_currentDescription = endpoint.Description; m_endpointConfiguration = endpoint.Configuration; if (m_endpointConfiguration == null) { m_endpointConfiguration = EndpointConfiguration.Create(configuration); } if (endpoint.Collection != null) { foreach (ConfiguredEndpoint existingEndpoint in endpoint.Collection.Endpoints) { if (existingEndpoint.Description.Server.ApplicationUri == endpoint.Description.Server.ApplicationUri) { m_availableEndpoints.Add(existingEndpoint.Description); } } } BuildEndpointDescriptionStrings(m_availableEndpoints); UserTokenPolicy policy = m_endpoint.SelectedUserTokenPolicy; if (policy == null) { if (m_endpoint.Description.UserIdentityTokens.Count > 0) { policy = m_endpoint.Description.UserIdentityTokens[0]; } } if (policy != null) { UserTokenItem userTokenItem = new UserTokenItem(policy); if (policy.TokenType == UserTokenType.UserName && m_endpoint.UserIdentity is UserNameIdentityToken) { m_userIdentities[userTokenItem.ToString()] = m_endpoint.UserIdentity; } if (policy.TokenType == UserTokenType.Certificate && m_endpoint.UserIdentity is X509IdentityToken) { m_userIdentities[userTokenItem.ToString()] = m_endpoint.UserIdentity; } if (policy.TokenType == UserTokenType.IssuedToken && m_endpoint.UserIdentity is IssuedIdentityToken) { m_userIdentities[userTokenItem.ToString()] = m_endpoint.UserIdentity; } } // initializing the protocol will trigger an update to all other controls. InitializeProtocols(m_availableEndpoints); // check if the current settings match the defaults. EndpointConfiguration defaultConfiguration = EndpointConfiguration.Create(configuration); // discover endpoints in the background. Interlocked.Increment(ref m_discoverCount); ThreadPool.QueueUserWorkItem(new WaitCallback(OnDiscoverEndpoints), m_endpoint.Description.Server); if (ShowDialog() != DialogResult.OK) { return null; } return m_endpoint; } #endregion #region Private Methods /// /// Creates the string representation of each EndpointDescription - to be used in the Endpoint Description List /// private void BuildEndpointDescriptionStrings(EndpointDescriptionCollection endpoints) { lock (m_availableEndpointsDescriptions) { m_availableEndpointsDescriptions.Clear(); foreach (EndpointDescription endpoint in endpoints) { m_availableEndpointsDescriptions.Add(new EndpointDescriptionString(endpoint)); } InitializeEndpointList(m_availableEndpointsDescriptions); } } /// /// Returns true if the configuration is the same as the default. /// private bool SameAsDefaults(EndpointConfiguration defaultConfiguration, EndpointConfiguration currentConfiguration) { if (defaultConfiguration.ChannelLifetime != currentConfiguration.ChannelLifetime) { return false; } if (defaultConfiguration.MaxArrayLength != currentConfiguration.MaxArrayLength) { return false; } if (defaultConfiguration.MaxBufferSize != currentConfiguration.MaxBufferSize) { return false; } if (defaultConfiguration.MaxByteStringLength != currentConfiguration.MaxByteStringLength) { return false; } if (defaultConfiguration.MaxMessageSize != currentConfiguration.MaxMessageSize) { return false; } if (defaultConfiguration.MaxStringLength != currentConfiguration.MaxStringLength) { return false; } if (defaultConfiguration.OperationTimeout != currentConfiguration.OperationTimeout) { return false; } if (defaultConfiguration.SecurityTokenLifetime != currentConfiguration.SecurityTokenLifetime) { return false; } if (defaultConfiguration.UseBinaryEncoding != currentConfiguration.UseBinaryEncoding) { return false; } return true; } /// /// Finds the best match for the current protocol and security selections. /// private EndpointDescription FindBestEndpointDescription(EndpointDescriptionCollection endpoints) { // filter by the current protocol. Protocol currentProtocol = (Protocol)ProtocolCB.SelectedItem; // filter by the current security mode. MessageSecurityMode currentMode = MessageSecurityMode.None; if (SecurityModeCB.SelectedIndex != -1) { currentMode = (MessageSecurityMode)SecurityModeCB.SelectedItem; } // filter by the current security policy. string currentPolicy = (string)SecurityPolicyCB.SelectedItem; // find all matching descriptions. EndpointDescriptionCollection matches = new EndpointDescriptionCollection(); if (endpoints != null) { foreach (EndpointDescription endpoint in endpoints) { Uri url = Utils.ParseUri(endpoint.EndpointUrl); if (url == null) { continue; } if ((currentProtocol != null) && (!currentProtocol.Matches(url))) { continue; } if (currentMode != endpoint.SecurityMode) { continue; } if (currentPolicy != SecurityPolicies.GetDisplayName(endpoint.SecurityPolicyUri)) { continue; } matches.Add(endpoint); } } // check for no matches. if (matches.Count == 0) { return null; } // check for single match. if (matches.Count == 1) { return matches[0]; } // choose highest priority. EndpointDescription bestMatch = matches[0]; for (int ii = 1; ii < matches.Count; ii++) { if (bestMatch.SecurityLevel < matches[ii].SecurityLevel) { bestMatch = matches[ii]; } } return bestMatch; } private class Protocol { public Uri Url; public string Profile; public Protocol(string url) { Url = Utils.ParseUri(url); } public Protocol(EndpointDescription url) { Url = null; if (url != null) { Url = Utils.ParseUri(url.EndpointUrl); if ((Url != null) && (Url.Scheme == Utils.UriSchemeHttps)) { switch (url.TransportProfileUri) { case Profiles.HttpsBinaryTransport: { Profile = "REST"; break; } } } } } public bool Matches(Uri url) { if (url == null || Url == null) { return false; } if (url.Scheme != Url.Scheme) { return false; } if (url.DnsSafeHost != Url.DnsSafeHost) { return false; } if (url.Port != Url.Port) { return false; } return true; } public override string ToString() { if (Url == null) { return String.Empty; } StringBuilder builder = new StringBuilder(); builder.Append(Url.Scheme); if (!String.IsNullOrEmpty(Profile)) { builder.Append(" "); builder.Append(Profile); } builder.Append(" ["); builder.Append(Url.DnsSafeHost); if (Url.Port != -1) { builder.Append(":"); builder.Append(Url.Port); } builder.Append("]"); return builder.ToString(); } } /// /// Initializes the protocol dropdown. /// private void InitializeProtocols(EndpointDescriptionCollection endpoints) { // preserve the existing value. Protocol currentProtocol = (Protocol)ProtocolCB.SelectedItem; ProtocolCB.Items.Clear(); // set all available protocols. if (m_showAllOptions) { ProtocolCB.Items.Add(new Protocol("http://localhost")); ProtocolCB.Items.Add(new Protocol("https://localhost")); ProtocolCB.Items.Add(new Protocol("opc.tcp://localhost")); } // find all unique protocols. else { if (endpoints != null) { foreach (EndpointDescription endpoint in endpoints) { Uri url = Utils.ParseUri(endpoint.EndpointUrl); if (url != null) { bool found = false; for (int ii = 0; ii < ProtocolCB.Items.Count; ii++) { if (((Protocol)ProtocolCB.Items[ii]).Matches(url)) { found = true; break; } } if (!found) { ProtocolCB.Items.Add(new Protocol(endpoint)); } } } } // add at least one protocol. if (ProtocolCB.Items.Count == 0) { ProtocolCB.Items.Add(new Protocol("opc.tcp://localhost")); } } // set the current value. int index = 0; if (currentProtocol != null) { index = 0; for (int ii = 0; ii < ProtocolCB.Items.Count; ii++) { if (((Protocol)ProtocolCB.Items[ii]).Matches(currentProtocol.Url)) { index = ii; break; } } } ProtocolCB.SelectedIndex = index; } /// /// Initializes the security modes dropdown. /// private void InitializeSecurityModes(EndpointDescriptionCollection endpoints) { // filter by the current protocol. Protocol currentProtocol = (Protocol)ProtocolCB.SelectedItem; // preserve the existing value. MessageSecurityMode currentMode = MessageSecurityMode.None; if (SecurityModeCB.SelectedIndex != -1) { currentMode = (MessageSecurityMode)SecurityModeCB.SelectedItem; } SecurityModeCB.Items.Clear(); // set all available security modes. if (m_showAllOptions) { SecurityModeCB.Items.Add(MessageSecurityMode.None); SecurityModeCB.Items.Add(MessageSecurityMode.Sign); SecurityModeCB.Items.Add(MessageSecurityMode.SignAndEncrypt); } // find all unique security modes. else { if (endpoints != null) { foreach (EndpointDescription endpoint in endpoints) { Uri url = Utils.ParseUri(endpoint.EndpointUrl); if ((url != null) && (currentProtocol != null)) { if (!currentProtocol.Matches(url)) { continue; } if (!SecurityModeCB.Items.Contains(endpoint.SecurityMode)) { SecurityModeCB.Items.Add(endpoint.SecurityMode); } } } } // add at least one policy. if (SecurityModeCB.Items.Count == 0) { SecurityModeCB.Items.Add(MessageSecurityMode.None); } } // set the current value. int index = SecurityModeCB.Items.IndexOf(currentMode); if (index == -1) { index = 0; } SecurityModeCB.SelectedIndex = index; } /// /// Initializes the security policies dropdown. /// private void InitializeSecurityPolicies(EndpointDescriptionCollection endpoints) { // filter by the current protocol. Protocol currentProtocol = (Protocol)ProtocolCB.SelectedItem; // filter by the current security mode. MessageSecurityMode currentMode = MessageSecurityMode.None; if (SecurityModeCB.SelectedIndex != -1) { currentMode = (MessageSecurityMode)SecurityModeCB.SelectedItem; } // preserve the existing value. string currentPolicy = (string)SecurityPolicyCB.SelectedItem; SecurityPolicyCB.Items.Clear(); // set all available security policies. if (m_showAllOptions) { var securityPolicies = SecurityPolicies.GetDisplayNames(); foreach (var policy in securityPolicies) { SecurityPolicyCB.Items.Add(policy); } } // find all unique security policies. else { if (endpoints != null) { foreach (EndpointDescription endpoint in endpoints) { Uri url = Utils.ParseUri(endpoint.EndpointUrl); if ((url != null) && (currentProtocol != null)) { if (!currentProtocol.Matches(url)) { continue; } if (currentMode != endpoint.SecurityMode) { continue; } string policyName = SecurityPolicies.GetDisplayName(endpoint.SecurityPolicyUri); if (policyName != null) { int existingIndex = SecurityPolicyCB.FindStringExact(policyName); if (existingIndex == -1) { SecurityPolicyCB.Items.Add(policyName); } } } } } } // add at least one policy. if (SecurityPolicyCB.Items.Count == 0) { SecurityPolicyCB.Items.Add(SecurityPolicies.GetDisplayName(SecurityPolicies.None)); } // set the current value. int index = 0; if (!String.IsNullOrEmpty(currentPolicy)) { index = SecurityPolicyCB.FindStringExact(currentPolicy); if (index == -1) { index = 0; } } SecurityPolicyCB.SelectedIndex = index; } /// /// Initializes the message encodings dropdown. /// private void InitializeEncodings(EndpointDescriptionCollection endpoints, EndpointDescription endpoint) { // preserve the existing value. Encoding currentEncoding = Encoding.Default; if (EncodingCB.SelectedIndex != -1) { currentEncoding = (Encoding)EncodingCB.SelectedItem; } EncodingCB.Items.Clear(); if (endpoint != null) { Protocol protocol = new Protocol(endpoint); String securityPolicy = SecurityPolicies.GetDisplayName(endpoint.SecurityPolicyUri); foreach (EndpointDescription endpointDescription in endpoints) { if ((protocol.Matches(Utils.ParseUri(endpointDescription.EndpointUrl))) && (endpoint.SecurityMode == endpointDescription.SecurityMode) && (securityPolicy == SecurityPolicies.GetDisplayName(endpointDescription.SecurityPolicyUri))) { switch (endpointDescription.EncodingSupport) { case BinaryEncodingSupport.None: { if (!EncodingCB.Items.Contains(Encoding.Xml)) { EncodingCB.Items.Add(Encoding.Xml); } break; } case BinaryEncodingSupport.Required: { if (!EncodingCB.Items.Contains(Encoding.Binary)) { EncodingCB.Items.Add(Encoding.Binary); } break; } case BinaryEncodingSupport.Optional: { if (!EncodingCB.Items.Contains(Encoding.Binary)) { EncodingCB.Items.Add(Encoding.Binary); } if (!EncodingCB.Items.Contains(Encoding.Xml)) { EncodingCB.Items.Add(Encoding.Xml); } break; } } } } } // add at least one encoding. if (EncodingCB.Items.Count == 0) { EncodingCB.Items.Add(Encoding.Default); } // set the current value. int index = EncodingCB.Items.IndexOf(currentEncoding); if (index == -1) { index = 0; } EncodingCB.SelectedIndex = index; } private class UserTokenItem { public UserTokenPolicy Policy; public UserTokenItem(UserTokenPolicy policy) { Policy = policy; } public UserTokenItem(UserTokenType tokenType) { Policy = new UserTokenPolicy(tokenType); } public override string ToString() { if (Policy != null) { if (String.IsNullOrEmpty(Policy.PolicyId)) { return Policy.TokenType.ToString(); } return Utils.Format("{0} [{1}]", Policy.TokenType, Policy.PolicyId); } return UserTokenType.Anonymous.ToString(); } } /// /// Initializes the endpoint list control. /// private void InitializeEndpointList(List endpoints) { EndpointListLB.Items.Clear(); foreach (EndpointDescriptionString endpointString in endpoints) { EndpointListLB.Items.Add(endpointString); } } private void SelectCorrespondingEndpointFromList(EndpointDescription endpoint) { if (!m_selecting) { int index = -1; // try to match endpoint description id if (endpoint != null) { for (int ii = 0; ii < EndpointListLB.Items.Count; ii++) { if (endpoint == ((EndpointDescriptionString)EndpointListLB.Items[ii]).EndpointDescription) { index = ii; break; } } } EndpointListLB.SelectedIndex = index; } } /// /// Attempts fetch the list of servers from the discovery server. /// private void OnDiscoverEndpoints(object state) { int discoverCount = m_discoverCount; // do nothing if a valid list is not provided. ApplicationDescription server = state as ApplicationDescription; if (server == null) { return; } OnUpdateStatus(new Tuple("Attempting to read latest configuration options from server.", StatusType.Ok)); String discoveryMessage = String.Empty; // process each url. foreach (string discoveryUrl in server.DiscoveryUrls) { Uri url = Utils.ParseUri(discoveryUrl); if (url != null) { if (DiscoverEndpoints(url, out discoveryMessage)) { m_discoverySucceeded = true; m_discoveryUrl = url; OnUpdateStatus(new Tuple("Configuration options are up to date.", StatusType.Ok)); return; } // check if another discover operation has started. if (discoverCount != m_discoverCount) { return; } } } OnUpdateEndpoints(m_availableEndpoints); OnUpdateStatus(new Tuple("Warning: Configuration options may not be correct because the server is not available (" + discoveryMessage + ").", StatusType.Warning)); } /// /// Fetches the servers from the discovery server. /// private bool DiscoverEndpoints(Uri discoveryUrl, out String message) { // use a short timeout. EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration); endpointConfiguration.OperationTimeout = m_discoveryTimeout; DiscoveryClient client = DiscoveryClient.Create( discoveryUrl, EndpointConfiguration.Create(m_configuration), m_configuration); try { EndpointDescriptionCollection endpoints = client.GetEndpoints(null); OnUpdateEndpoints(endpoints); message = String.Empty; return true; } catch (Exception e) { Utils.Trace("Could not fetch endpoints from url: {0}. Reason={1}", discoveryUrl, e.Message); message = e.Message; return false; } finally { client.Close(); } } /// /// Updates the status displayed in the dialog. /// private void OnUpdateStatus(object status) { if (this.InvokeRequired) { this.BeginInvoke(new WaitCallback(OnUpdateStatus), status); return; } Tuple statusTuple = status as Tuple; m_statusObject.SetStatus(StatusChannel.Discovery, statusTuple.Item1, statusTuple.Item2); UpdateStatus(); } /// /// Updates the list of servers displayed in the control. /// private void OnUpdateEndpoints(object state) { if (this.InvokeRequired) { this.BeginInvoke(new WaitCallback(OnUpdateEndpoints), state); return; } try { // get the updated descriptions. EndpointDescriptionCollection endpoints = state as EndpointDescriptionCollection; if (endpoints == null) { m_showAllOptions = true; InitializeProtocols(m_availableEndpoints); } else { m_showAllOptions = false; m_availableEndpoints = endpoints; BuildEndpointDescriptionStrings(m_availableEndpoints); if (endpoints.Count > 0) { m_currentDescription = endpoints[0]; } // initializing the protocol will trigger an update to all other controls. InitializeProtocols(m_availableEndpoints); // select the best security mode. MessageSecurityMode bestMode = MessageSecurityMode.Invalid; foreach (MessageSecurityMode securityMode in SecurityModeCB.Items) { if (securityMode > bestMode) { bestMode = securityMode; } } SecurityModeCB.SelectedItem = bestMode; // select the best encoding. Encoding bestEncoding = Encoding.Default; foreach (Encoding encoding in EncodingCB.Items) { if (encoding > bestEncoding) { bestEncoding = encoding; } } EncodingCB.SelectedItem = bestEncoding; } if (m_endpoint != null) { Uri url = m_endpoint.EndpointUrl; foreach (Protocol protocol in ProtocolCB.Items) { if (protocol.Matches(url)) { ProtocolCB.SelectedItem = protocol; break; } } foreach (MessageSecurityMode securityMode in SecurityModeCB.Items) { if (securityMode == m_endpoint.Description.SecurityMode) { SecurityModeCB.SelectedItem = securityMode; break; } } foreach (string securityPolicy in SecurityPolicyCB.Items) { if (securityPolicy == m_endpoint.Description.SecurityPolicyUri) { SecurityPolicyCB.SelectedItem = securityPolicy; break; } } foreach (Encoding encoding in EncodingCB.Items) { if (encoding == Encoding.Binary && m_endpoint.Configuration.UseBinaryEncoding) { EncodingCB.SelectedItem = encoding; break; } if (encoding == Encoding.Xml && !m_endpoint.Configuration.UseBinaryEncoding) { EncodingCB.SelectedItem = encoding; break; } } } } catch (Exception e) { Utils.Trace(e, "Unexpected error updating endpoints."); } } /// /// Creates the endpoint description from current selections. /// private EndpointDescription CreateDescriptionFromSelections() { Protocol currentProtocol = (Protocol)ProtocolCB.SelectedItem; EndpointDescription endpoint = null; for (int ii = 0; ii < m_availableEndpoints.Count; ii++) { Uri url = Utils.ParseUri(m_availableEndpoints[ii].EndpointUrl); if (url == null) { continue; } if (endpoint == null) { endpoint = m_availableEndpoints[ii]; } if (currentProtocol.Matches(url)) { endpoint = m_availableEndpoints[ii]; break; } } UriBuilder builder = null; string scheme = Utils.UriSchemeOpcTcp; if (currentProtocol != null && currentProtocol.Url != null) { scheme = currentProtocol.Url.Scheme; } if (endpoint == null) { builder = new UriBuilder(); builder.Host = "localhost"; if (scheme == Utils.UriSchemeOpcTcp) { builder.Port = Utils.UaTcpDefaultPort; } } else { builder = new UriBuilder(endpoint.EndpointUrl); } builder.Scheme = scheme; endpoint = new EndpointDescription(); endpoint.EndpointUrl = builder.ToString(); endpoint.SecurityMode = (MessageSecurityMode)SecurityModeCB.SelectedItem; endpoint.SecurityPolicyUri = SecurityPolicies.GetUri((string)SecurityPolicyCB.SelectedItem); endpoint.Server.ApplicationName = endpoint.EndpointUrl; endpoint.Server.ApplicationType = ApplicationType.Server; endpoint.Server.ApplicationUri = endpoint.EndpointUrl; return endpoint; } #endregion #region Event Handlers private void OkBTN_Click(object sender, EventArgs e) { try { // check that discover has completed. if (!m_discoverySucceeded) { DialogResult result = MessageBox.Show( "Endpoint information may be out of date because the discovery process has not completed. Continue anyways?", this.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Warning); if (result != DialogResult.Yes) { return; } } EndpointConfiguration configuration = m_endpointConfiguration; if (configuration == null) { configuration = EndpointConfiguration.Create(m_configuration); } if (m_currentDescription == null) { m_currentDescription = CreateDescriptionFromSelections(); } // the discovery endpoint should always be on the same machine as the server. // if there is a mismatch it is likely because the server has multiple addresses // and was not configured to return the current address to the client. // The code automatically updates the domain in the url. Uri endpointUrl = Utils.ParseUri(m_currentDescription.EndpointUrl); if (m_discoverySucceeded) { if (!Utils.AreDomainsEqual(endpointUrl, m_discoveryUrl)) { UriBuilder url = new UriBuilder(endpointUrl); url.Host = m_discoveryUrl.DnsSafeHost; if (url.Scheme == m_discoveryUrl.Scheme) { url.Port = m_discoveryUrl.Port; } endpointUrl = url.Uri; m_currentDescription.EndpointUrl = endpointUrl.ToString(); } } // set the encoding. Encoding encoding = (Encoding)EncodingCB.SelectedItem; configuration.UseBinaryEncoding = encoding != Encoding.Xml; if (m_endpoint == null) { m_endpoint = new ConfiguredEndpoint(null, m_currentDescription, configuration); } else { m_endpoint.Update(m_currentDescription); m_endpoint.Update(configuration); } DialogResult = DialogResult.OK; } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } private void ProtocolCB_SelectedIndexChanged(object sender, EventArgs e) { try { InitializeSecurityModes(m_availableEndpoints); if (!m_updating) { try { m_updating = true; // update current description. m_currentDescription = FindBestEndpointDescription(m_availableEndpoints); InitializeEncodings(m_availableEndpoints, m_currentDescription); SelectCorrespondingEndpointFromList(m_currentDescription); } finally { m_updating = false; } } if (ProtocolCB.SelectedItem != null) { if (((Protocol)ProtocolCB.SelectedItem).Url.DnsSafeHost != m_endpoint.EndpointUrl.DnsSafeHost) { m_statusObject.SetStatus(StatusChannel.SelectedProtocol, "Warning: Selected Endpoint hostname is different than initial hostname.", StatusType.Warning); } else { m_statusObject.ClearStatus(StatusChannel.SelectedProtocol); } } else { m_statusObject.SetStatus(StatusChannel.SelectedProtocol, "Error: Selected Protocol is invalid.", StatusType.Warning); } UpdateStatus(); } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } private void SecurityModeCB_SelectedIndexChanged(object sender, EventArgs e) { try { InitializeSecurityPolicies(m_availableEndpoints); if (!m_updating) { try { m_updating = true; // update current description. m_currentDescription = FindBestEndpointDescription(m_availableEndpoints); InitializeEncodings(m_availableEndpoints, m_currentDescription); SelectCorrespondingEndpointFromList(m_currentDescription); } finally { m_updating = false; } } if (SecurityModeCB.SelectedItem != null) { if ((((MessageSecurityMode)SecurityModeCB.SelectedItem) == MessageSecurityMode.None) && (ProtocolCB.SelectedItem != null) && (((Protocol)ProtocolCB.SelectedItem).ToString().IndexOf("https") != 0)) { m_statusObject.SetStatus(StatusChannel.SelectedSecurityMode, "Warning: Selected Endpoint has no security.", StatusType.Warning); } else if (((MessageSecurityMode)SecurityModeCB.SelectedItem) == MessageSecurityMode.Invalid) { m_statusObject.SetStatus(StatusChannel.SelectedSecurityMode, "Error: Selected Endpoint Security Mode is unsupported.", StatusType.Warning); } else { m_statusObject.ClearStatus(StatusChannel.SelectedSecurityMode); } } else { m_statusObject.SetStatus(StatusChannel.SelectedSecurityMode, "Error: Selected Endpoint Security Mode is invalid.", StatusType.Warning); } UpdateStatus(); } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } private void SecurityPolicyCB_SelectedIndexChanged(object sender, EventArgs e) { try { if (!m_updating) { try { m_updating = true; // update current description. m_currentDescription = FindBestEndpointDescription(m_availableEndpoints); InitializeEncodings(m_availableEndpoints, m_currentDescription); SelectCorrespondingEndpointFromList(m_currentDescription); } finally { m_updating = false; } } if (SecurityPolicyCB.SelectedItem != null) { m_statusObject.ClearStatus(StatusChannel.SelectedSecurityPolicy); } else { m_statusObject.SetStatus(StatusChannel.SelectedSecurityPolicy, "Error: Selected Security Policy is invalid.", StatusType.Warning); } UpdateStatus(); } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } private void EndpointListLB_SelectedIndexChanged(object sender, EventArgs e) { if (!m_updating) { try { m_updating = true; m_selecting = true; int selectedIndex = EndpointListLB.SelectedIndex; if (selectedIndex != -1) { EndpointDescriptionString selection = (EndpointDescriptionString)EndpointListLB.SelectedItem; int index = -1; for (int i = 0; i < ProtocolCB.Items.Count; ++i) { if (((Protocol)ProtocolCB.Items[i]).ToString() == selection.Protocol.ToString()) { index = i; break; } } ProtocolCB.SelectedIndex = index; InitializeSecurityModes(m_availableEndpoints); m_currentDescription = m_availableEndpoints[selectedIndex]; InitializeEncodings(m_availableEndpoints, m_currentDescription); index = -1; for (int i = 0; i < SecurityModeCB.Items.Count; ++i) { if ((MessageSecurityMode)SecurityModeCB.Items[i] == selection.MessageSecurityMode) { index = i; break; } } SecurityModeCB.SelectedIndex = index; index = -1; for (int i = 0; i < SecurityPolicyCB.Items.Count; ++i) { if ((string)SecurityPolicyCB.Items[i] == selection.CurrentPolicy) { index = i; break; } } SecurityPolicyCB.SelectedIndex = index; index = -1; for (int i = 0; i < EncodingCB.Items.Count; ++i) { if ((Encoding)EncodingCB.Items[i] == selection.Encoding) { index = i; break; } } EncodingCB.SelectedIndex = index; } } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } finally { m_updating = false; m_selecting = false; } } UpdateAdvancedEndpointInformation(); } /// /// Updates advanced endpoint information. /// private void UpdateAdvancedEndpointInformation() { try { ApplicationNameTB.Text = String.Empty; ApplicationTypeTB.Text = String.Empty; ApplicationUriTB.Text = String.Empty; ProductUriTB.Text = String.Empty; GatewayServerUriTB.Text = String.Empty; DiscoveryProfileUriTB.Text = String.Empty; TransportProfileUriTB.Text = String.Empty; UserSecurityPoliciesTB.Text = String.Empty; SecurityLevelTB.Text = String.Empty; if (m_currentDescription != null) { UserSecurityPoliciesTB.Text = "Anonymous"; if (m_currentDescription.Server != null) { if (m_currentDescription.Server.ApplicationName != null) { ApplicationNameTB.Text = m_currentDescription.Server.ApplicationName.ToString(); } ApplicationTypeTB.Text = m_currentDescription.Server.ApplicationType.ToString(); ApplicationUriTB.Text = m_currentDescription.Server.ApplicationUri; ProductUriTB.Text = m_currentDescription.Server.ProductUri; GatewayServerUriTB.Text = m_currentDescription.Server.GatewayServerUri; DiscoveryProfileUriTB.Text = m_currentDescription.Server.DiscoveryProfileUri; } SecurityLevelTB.Text = m_currentDescription.SecurityLevel.ToString(); TransportProfileUriTB.Text = m_currentDescription.TransportProfileUri; if (m_currentDescription.UserIdentityTokens.Count > 0) { UserSecurityPoliciesTB.Text = String.Join(", ", m_currentDescription.UserIdentityTokens); } } UpdateStatus(); } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } /// /// Updates the StatusTB text and color. /// Also enables/disables the OK button, should any error occurr (unsupported stuff etc). /// private void UpdateStatus() { try { if ((m_currentDescription != null) && (m_currentDescription.Server != null)) { m_statusObject.ClearStatus(StatusChannel.Server); if (m_currentDescription.Server.ApplicationType == ApplicationType.Client) { m_statusObject.SetStatus(StatusChannel.ApplicationType, "Warning: Application type is unsupported.", StatusType.Warning); } else { m_statusObject.ClearStatus(StatusChannel.ApplicationType); } if (string.IsNullOrEmpty(m_currentDescription.Server.ApplicationUri)) { m_statusObject.SetStatus(StatusChannel.ApplicationUri, "Warning: Application URI is missing.", StatusType.Warning); } else { m_statusObject.ClearStatus(StatusChannel.ApplicationUri); } if (string.IsNullOrEmpty(m_currentDescription.TransportProfileUri)) { m_statusObject.SetStatus(StatusChannel.TransportProfileUri, "Warning: Transport Profile URI is missing.", StatusType.Warning); } else if (Utils.ParseUri(m_currentDescription.TransportProfileUri) == null) { m_statusObject.SetStatus(StatusChannel.TransportProfileUri, "Warning: Transport Profile URI is invalid.", StatusType.Warning); } if ((m_currentDescription.Server.DiscoveryUrls == null) || (m_currentDescription.Server.DiscoveryUrls.Count == 0)) { m_statusObject.SetStatus(StatusChannel.DiscoveryURLs, "Warning: Discovery URLs are missing.", StatusType.Warning); } else { m_statusObject.ClearStatus(StatusChannel.DiscoveryURLs); } if ((m_currentDescription.ServerCertificate != null) && (m_currentDescription.ServerCertificate.Length > 0)) { X509Certificate2 serverCertificate = new X509Certificate2(m_currentDescription.ServerCertificate); String certificateApplicationUri = X509Utils.GetApplicationUriFromCertificate(serverCertificate); if (certificateApplicationUri != m_currentDescription.Server.ApplicationUri) { m_statusObject.SetStatus(StatusChannel.DifferentCertificate, "Warning: Application URI host different than the certificate host.", StatusType.Warning); } else { m_statusObject.ClearStatus(StatusChannel.DifferentCertificate); } } if (string.IsNullOrEmpty(m_currentDescription.SecurityPolicyUri)) { m_statusObject.SetStatus(StatusChannel.SecurityPolicyUri, "Error: Security Policy URI is missing.", StatusType.Warning); } else if (string.IsNullOrEmpty(SecurityPolicies.GetDisplayName(m_currentDescription.SecurityPolicyUri))) { m_statusObject.SetStatus(StatusChannel.SecurityPolicyUri, "Error: Security Policy URI is invalid.", StatusType.Warning); } else { m_statusObject.ClearStatus(StatusChannel.SecurityPolicyUri); } } else { m_statusObject.SetStatus(StatusChannel.Server, "Warning: Server endpoint is invalid.", StatusType.Warning); } OkBTN.Enabled = true; StatusTB.ForeColor = SystemColors.WindowText; StatusTB.Text = m_statusObject.StatusString; if (m_statusObject.StatusType == StatusType.Error) { OkBTN.Enabled = false; StatusTB.ForeColor = Color.Red; } else if (m_statusObject.StatusType == StatusType.Warning) { StatusTB.ForeColor = Color.DarkOrange; } // hack for WinForms to update color StatusTB.BackColor = StatusTB.BackColor; } catch (Exception exception) { GuiUtils.HandleException(this.Text, MethodBase.GetCurrentMethod(), exception); } } #endregion } }