diff --git a/MultiTerm.Protocols/Helpers/NetworkProtocolHelpers.cs b/MultiTerm.Protocols/Helpers/NetworkProtocolHelpers.cs new file mode 100644 index 0000000..8adecad --- /dev/null +++ b/MultiTerm.Protocols/Helpers/NetworkProtocolHelpers.cs @@ -0,0 +1,31 @@ +using MultiTerm.Protocols.Network; + +namespace MultiTerm.Protocols.Helpers; + +internal static class NetworkProtocolHelpers +{ + /// + /// Returns the hostname of the but limited to amount of characters. + /// If the hostname is longer than , '...' will be prefixed. The resulting string may also be +3 characters long. + /// If the hostname cannot be read, the result will be "invalid". + /// + /// settings object containing hostname + /// max amount of characters of the resulting string + /// string with limited length (maximum amount of characters = + 3 + public static string GetLimitedLengthHostname(INetworkProtocolSettings settingsObject, int maxLength = 20) + { + string limitedHostname = "invalid"; + string prefix = "..."; + + if (settingsObject.Hostname != null && settingsObject.Hostname.Length >= maxLength) + { + limitedHostname = $"{prefix}{settingsObject.Hostname.Substring(settingsObject.Hostname.Length - maxLength, maxLength)}"; + } + else if (settingsObject.Hostname != null) + { + limitedHostname = $"{settingsObject.Hostname}"; + } + + return limitedHostname; + } +} diff --git a/MultiTerm.Protocols/Udp/IUdpProtocolSettings.cs b/MultiTerm.Protocols/Network/INetworkProtocolSettings.cs similarity index 66% rename from MultiTerm.Protocols/Udp/IUdpProtocolSettings.cs rename to MultiTerm.Protocols/Network/INetworkProtocolSettings.cs index a9ba156..fea5524 100644 --- a/MultiTerm.Protocols/Udp/IUdpProtocolSettings.cs +++ b/MultiTerm.Protocols/Network/INetworkProtocolSettings.cs @@ -1,12 +1,12 @@ -namespace MultiTerm.Protocols.Udp; +namespace MultiTerm.Protocols.Network; -internal interface IUdpProtocolSettings : IProtocolSettings +internal interface INetworkProtocolSettings : IProtocolSettings { /// /// Hostname to connect to. /// Can be a hostname with or without domain or an IPv4/IVv6 address. /// - string Hostname {get; internal set; } + string Hostname { get; internal set; } /// /// Port of the end device. Will also be the port that is listened on. diff --git a/MultiTerm.Protocols/Network/NetworkProtocolSettingsViewModel.cs b/MultiTerm.Protocols/Network/NetworkProtocolSettingsViewModel.cs new file mode 100644 index 0000000..7d3eea7 --- /dev/null +++ b/MultiTerm.Protocols/Network/NetworkProtocolSettingsViewModel.cs @@ -0,0 +1,129 @@ +using Common.Messaging; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using MultiTerm.Protocols.Types; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace MultiTerm.Protocols.Network; + +public abstract partial class NetworkProtocolSettingsViewModel : ProtocolSettingsViewModel, INetworkProtocolSettings +{ + public override abstract Types.ProtocolType ProtocolType { get; } + + #region IUdpProtocolSettings Implementation + [ObservableProperty] + private string hostname = string.Empty; + + [ObservableProperty] + private int port = 10000; + #endregion + + /// + /// A property to display the resolved IP address that the protocol connected to. + /// + [ObservableProperty] + private string resolvedAddress = string.Empty; + + public NetworkProtocolSettingsViewModel(IMessenger messenger) : base(messenger) + { + // register for messages + // messenger.Register(this); + + // initialize IP address with first ethernet adapter local IP + this.Hostname = GetFirstEthernetAdaptersSubnet(); + } + + public bool AreValid() + { + if (String.IsNullOrEmpty(this.Hostname)) + { + this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Null or empty")); + return false; + } + if (this.Port < IPEndPoint.MinPort && this.Port > IPEndPoint.MaxPort) + { + this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Port), "Out of range")); + return false; + } + // try parse to IP address + if (IPAddress.TryParse(this.Hostname, out _)) + { + return true; + } + // else try to resolve hostname + else + { + IPHostEntry host; + try + { + host = Dns.GetHostEntry(this.Hostname); + } + catch (ArgumentOutOfRangeException) + { + this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Length is greater than 255 characters")); + return false; + } + catch (SocketException) + { + this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Error encountered when resolving hostname")); + return false; + } + catch (ArgumentException) + { + this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Invalid IP address")); + return false; + } + + // host not null and found at least one address => success + return host != null && host.AddressList.Length > 0; + } + } + + /// + /// Sets the proprty to the resolved address from the message. + /// + /// + //void IRecipient.Receive(UdpConnectedMessage message) + //{ + // this.ResolvedAddress = message.ResolvedAddress.ToString(); + //} + + /// + /// Tries to retrieve the first ethernet adapters subnet address. + /// + /// string of of the first ethernet adapters subnet address, or if not found + private static string GetFirstEthernetAdaptersSubnet() + { + // iterate through all network interfaces + NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (var adapter in interfaces) + { + // return first found ipv4 adapter subnet address + if (adapter.OperationalStatus == OperationalStatus.Up) + { + try + { + var unicastAddresses = adapter.GetIPProperties().UnicastAddresses; + foreach (var unicastAddress in unicastAddresses) + { + // only interested in IPv4 + if (unicastAddress.Address.AddressFamily != AddressFamily.InterNetwork) + continue; + + // Ignore loopback addresses (e.g., 127.0.0.1) + if (IPAddress.IsLoopback(unicastAddress.Address)) + continue; + + // split and only return first three parts of the IPv4 address + var splitIpv4 = unicastAddress.Address.ToString().Split('.'); + return $"{splitIpv4[0]}.{splitIpv4[1]}.{splitIpv4[2]}."; + } + } + catch { } + } + } + return string.Empty; + } +} diff --git a/MultiTerm.Protocols/Udp/UdpProtocol.cs b/MultiTerm.Protocols/Udp/UdpProtocol.cs index f8b2696..636db17 100644 --- a/MultiTerm.Protocols/Udp/UdpProtocol.cs +++ b/MultiTerm.Protocols/Udp/UdpProtocol.cs @@ -1,7 +1,9 @@ using Common.Logging; using Common.Messaging; using CommunityToolkit.Mvvm.Messaging; +using MultiTerm.Protocols.Helpers; using MultiTerm.Protocols.Model; +using MultiTerm.Protocols.Network; using System.Net.Sockets; namespace MultiTerm.Protocols.Udp; @@ -12,40 +14,27 @@ public class UdpProtocol : CommunicationProtocol public override string InstanceIdentifier { get; protected set; } = string.Empty; - private IUdpProtocolSettings? udpSettings; + private INetworkProtocolSettings? settings; private UdpClient? receivingUdpClient; private UdpClient? sendingUdpClient; - private const int MaxInstanceIdentifierLength = 20; // maximum number of characters for the InstanceIdentifier - public UdpProtocol(ILogger logger, IMessenger messenger) : base(logger, messenger) { } protected override bool InternalConnect(IProtocolSettings settings) { // check if settings are of correct type - if (settings is not IUdpProtocolSettings udpProtocolSettings) + if (settings is not INetworkProtocolSettings networkSettings) { - this.udpSettings = null; + this.settings = null; throw new ArgumentException($"Cannot connect due to wrong type of Protocol Settings. " + $"Check parameter {nameof(settings)}' of '{nameof(InternalConnect)}()' in {nameof(UdpProtocol)}."); } // store locally - this.udpSettings = udpProtocolSettings; + this.settings = networkSettings; // update identifier - if(this.udpSettings.Hostname != null && this.udpSettings.Hostname.Length >= MaxInstanceIdentifierLength) - { - this.InstanceIdentifier = $"...{this.udpSettings.Hostname.Substring(this.udpSettings.Hostname.Length - MaxInstanceIdentifierLength, MaxInstanceIdentifierLength)}"; - } - else if(this.udpSettings.Hostname != null) - { - this.InstanceIdentifier = $"{this.udpSettings.Hostname}"; - } - else - { - this.InstanceIdentifier = "invalid"; - } + this.InstanceIdentifier = NetworkProtocolHelpers.GetLimitedLengthHostname(this.settings); // check if clients are null if(this.receivingUdpClient != null || this.sendingUdpClient != null) @@ -59,7 +48,7 @@ public class UdpProtocol : CommunicationProtocol // try opening receiving udp socket try { - this.receivingUdpClient = new UdpClient(this.udpSettings.Port); + this.receivingUdpClient = new UdpClient(this.settings.Port); } catch (Exception ex) { @@ -74,7 +63,7 @@ public class UdpProtocol : CommunicationProtocol try { this.sendingUdpClient = new UdpClient(); - this.sendingUdpClient.Connect(this.udpSettings.Hostname!, this.udpSettings.Port); + this.sendingUdpClient.Connect(this.settings.Hostname!, this.settings.Port); } catch (Exception ex) { @@ -136,7 +125,7 @@ public class UdpProtocol : CommunicationProtocol { this.logger.LogException(ex, $"Exception while reading data in {nameof(InternalRead)}", nameof(UdpProtocol)); this.messenger.Send(new GenericUserInterfaceMessage($"UDP client ended reading on port " + - $"{this.udpSettings!.Port} because of exception.", MessageImportance.Medium)); + $"{this.settings!.Port} because of exception.", MessageImportance.Medium)); break; // break loop } diff --git a/MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs b/MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs index 43d0e95..f5580aa 100644 --- a/MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs +++ b/MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs @@ -1,84 +1,16 @@ -using Common.Messaging; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using MultiTerm.Protocols.Types; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; +using CommunityToolkit.Mvvm.Messaging; +using MultiTerm.Protocols.Network; namespace MultiTerm.Protocols.Udp; -public partial class UdpProtocolSettingsViewModel : ProtocolSettingsViewModel, IUdpProtocolSettings, IRecipient +public partial class UdpProtocolSettingsViewModel : NetworkProtocolSettingsViewModel, IRecipient { public override Types.ProtocolType ProtocolType => Types.ProtocolType.Udp; - #region IUdpProtocolSettings Implementation - [ObservableProperty] - private string hostname = string.Empty; - - [ObservableProperty] - private int port = 10000; - #endregion - - /// - /// A property to display the resolved IP address that the protocol connected to. - /// - [ObservableProperty] - private string resolvedAddress = string.Empty; - public UdpProtocolSettingsViewModel(IMessenger messenger) : base(messenger) { // register for messages messenger.Register(this); - - // initialize IP address with first ethernet adapter local IP - this.Hostname = GetFirstEthernetAdaptersSubnet(); - } - - public bool AreValid() - { - if (String.IsNullOrEmpty(this.Hostname)) - { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Null or empty")); - return false; - } - if (this.Port < IPEndPoint.MinPort && this.Port > IPEndPoint.MaxPort) - { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Port), "Out of range")); - return false; - } - // try parse to IP address - if (IPAddress.TryParse(this.Hostname, out _)) - { - return true; - } - // else try to resolve hostname - else - { - IPHostEntry host; - try - { - host = Dns.GetHostEntry(this.Hostname); - } - catch (ArgumentOutOfRangeException) - { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Length is greater than 255 characters")); - return false; - } - catch (SocketException) - { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Error encountered when resolving hostname")); - return false; - } - catch (ArgumentException) - { - this.messenger.Send(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Invalid IP address")); - return false; - } - - // host not null and found at least one address => success - return host != null && host.AddressList.Length > 0; - } } /// @@ -89,41 +21,4 @@ public partial class UdpProtocolSettingsViewModel : ProtocolSettingsViewModel, I { this.ResolvedAddress = message.ResolvedAddress.ToString(); } - - /// - /// Tries to retrieve the first ethernet adapters subnet address. - /// - /// string of of the first ethernet adapters subnet address, or if not found - private static string GetFirstEthernetAdaptersSubnet() - { - // iterate through all network interfaces - NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces(); - foreach (var adapter in interfaces) - { - // return first found ipv4 adapter subnet address - if (adapter.OperationalStatus == OperationalStatus.Up) - { - try - { - var unicastAddresses = adapter.GetIPProperties().UnicastAddresses; - foreach (var unicastAddress in unicastAddresses) - { - // only interested in IPv4 - if (unicastAddress.Address.AddressFamily != AddressFamily.InterNetwork) - continue; - - // Ignore loopback addresses (e.g., 127.0.0.1) - if (IPAddress.IsLoopback(unicastAddress.Address)) - continue; - - // split and only return first three parts of the IPv4 address - var splitIpv4 = unicastAddress.Address.ToString().Split('.'); - return $"{splitIpv4[0]}.{splitIpv4[1]}.{splitIpv4[2]}."; - } - } - catch { } - } - } - return string.Empty; - } } diff --git a/MultiTerm.Wpf/View/ShellView.xaml b/MultiTerm.Wpf/View/ShellView.xaml index ca10d39..6a6a6a9 100644 --- a/MultiTerm.Wpf/View/ShellView.xaml +++ b/MultiTerm.Wpf/View/ShellView.xaml @@ -145,12 +145,12 @@ - -