created INetworkProtocolSettings to generalize Udp and later TCP Protocol Settings,

added NetworkProtocolHelpers class
worked on tab header formatting
master
Jonas Arnold 3 years ago
parent 6606e30d5b
commit 47ac0abfe6
  1. 31
      MultiTerm.Protocols/Helpers/NetworkProtocolHelpers.cs
  2. 6
      MultiTerm.Protocols/Network/INetworkProtocolSettings.cs
  3. 129
      MultiTerm.Protocols/Network/NetworkProtocolSettingsViewModel.cs
  4. 31
      MultiTerm.Protocols/Udp/UdpProtocol.cs
  5. 111
      MultiTerm.Protocols/Udp/UdpProtocolSettingsViewModel.cs
  6. 4
      MultiTerm.Wpf/View/ShellView.xaml

@ -0,0 +1,31 @@
using MultiTerm.Protocols.Network;
namespace MultiTerm.Protocols.Helpers;
internal static class NetworkProtocolHelpers
{
/// <summary>
/// Returns the hostname of the <paramref name="settingsObject"/> but limited to <paramref name="maxLength"/> amount of characters.
/// If the hostname is longer than <paramref name="maxLength"/>, '...' will be prefixed. The resulting string may also be <paramref name="maxLength"/>+3 characters long.
/// If the hostname cannot be read, the result will be "invalid".
/// </summary>
/// <param name="settingsObject">settings object containing hostname</param>
/// <param name="maxLength">max amount of characters of the resulting string</param>
/// <returns>string with limited length (maximum amount of characters = <paramref name="maxLength"/> + 3</returns>
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;
}
}

@ -1,12 +1,12 @@
namespace MultiTerm.Protocols.Udp;
namespace MultiTerm.Protocols.Network;
internal interface IUdpProtocolSettings : IProtocolSettings
internal interface INetworkProtocolSettings : IProtocolSettings
{
/// <summary>
/// Hostname to connect to.
/// Can be a hostname with or without domain or an IPv4/IVv6 address.
/// </summary>
string Hostname {get; internal set; }
string Hostname { get; internal set; }
/// <summary>
/// Port of the end device. Will also be the port that is listened on.

@ -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
/// <summary>
/// A property to display the resolved IP address that the protocol connected to.
/// </summary>
[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<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Null or empty"));
return false;
}
if (this.Port < IPEndPoint.MinPort && this.Port > IPEndPoint.MaxPort)
{
this.messenger.Send<IUserInterfaceMessage>(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<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Length is greater than 255 characters"));
return false;
}
catch (SocketException)
{
this.messenger.Send<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Error encountered when resolving hostname"));
return false;
}
catch (ArgumentException)
{
this.messenger.Send<IUserInterfaceMessage>(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;
}
}
/// <summary>
/// Sets the <see cref="ResolvedAddress"/> proprty to the resolved address from the message.
/// </summary>
/// <param name="message"></param>
//void IRecipient<UdpConnectedMessage>.Receive(UdpConnectedMessage message)
//{
// this.ResolvedAddress = message.ResolvedAddress.ToString();
//}
/// <summary>
/// Tries to retrieve the first ethernet adapters subnet address.
/// </summary>
/// <returns>string of <see cref="IPAddress"/> of the first ethernet adapters subnet address, or <see cref="string.Empty"/> if not found</returns>
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;
}
}

@ -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<IUserInterfaceMessage>(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
}

@ -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<UdpConnectedMessage>
public partial class UdpProtocolSettingsViewModel : NetworkProtocolSettingsViewModel, IRecipient<UdpConnectedMessage>
{
public override Types.ProtocolType ProtocolType => Types.ProtocolType.Udp;
#region IUdpProtocolSettings Implementation
[ObservableProperty]
private string hostname = string.Empty;
[ObservableProperty]
private int port = 10000;
#endregion
/// <summary>
/// A property to display the resolved IP address that the protocol connected to.
/// </summary>
[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<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Null or empty"));
return false;
}
if (this.Port < IPEndPoint.MinPort && this.Port > IPEndPoint.MaxPort)
{
this.messenger.Send<IUserInterfaceMessage>(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<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Length is greater than 255 characters"));
return false;
}
catch (SocketException)
{
this.messenger.Send<IUserInterfaceMessage>(new ProtocolSettingsInvalidMessage(nameof(this.Hostname), "Error encountered when resolving hostname"));
return false;
}
catch (ArgumentException)
{
this.messenger.Send<IUserInterfaceMessage>(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;
}
}
/// <summary>
@ -89,41 +21,4 @@ public partial class UdpProtocolSettingsViewModel : ProtocolSettingsViewModel, I
{
this.ResolvedAddress = message.ResolvedAddress.ToString();
}
/// <summary>
/// Tries to retrieve the first ethernet adapters subnet address.
/// </summary>
/// <returns>string of <see cref="IPAddress"/> of the first ethernet adapters subnet address, or <see cref="string.Empty"/> if not found</returns>
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;
}
}

@ -145,12 +145,12 @@
<!-- Tab Template -->
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="15" Height="15" Margin="0 0 8 0"
<Ellipse Width="16" Height="16" Margin="0 0 8 0"
Stroke="Black" StrokeThickness="0.7"
Fill="{Binding Path=CommunicationProtocolConnectionState, Converter={StaticResource PrtclConnectionStateBrushConverter}}"
ToolTip="{Binding Path=CommunicationProtocolConnectionState, Converter={StaticResource EnumDescriptionConverter}}"
ToolTipService.InitialShowDelay="200"/>
<Image Height="20" Stretch="Uniform" Margin="0 0 8 0"
<Image Height="17" Stretch="Fill" Margin="0 0 8 0"
Source="{Binding Path=ProtocolType, Converter={StaticResource ProtocolTypeIconConverter}}"
ToolTip="{Binding Path=ProtocolType,Converter={StaticResource EnumDescriptionConverter}}"
ToolTipService.InitialShowDelay="200"/>

Loading…
Cancel
Save