Skip to content

Commit

Permalink
docs: Add for Helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkallesen committed Feb 14, 2024
1 parent a08ab10 commit 054f382
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 9 deletions.
2 changes: 2 additions & 0 deletions src/Atc.Network/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
global using System.Net.NetworkInformation;
global using System.Net.Sockets;
global using System.Text;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Text.RegularExpressions;

global using Atc.Helpers;
Expand Down
27 changes: 27 additions & 0 deletions src/Atc.Network/Helpers/ArpHelper.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
namespace Atc.Network.Helpers;

/// <summary>
/// Provides utilities for fetching and parsing ARP (Address Resolution Protocol) table results.
/// </summary>
public static class ArpHelper
{
private static DateTimeOffset lastLookup = DateTimeOffset.MinValue;
private static ArpEntity[]? arpEntities;

/// <summary>
/// Retrieves the ARP table results, caching them for 90 seconds to limit frequent lookups.
/// </summary>
/// <returns>
/// An array of <see cref="ArpEntity"/> representing the current ARP table entries.
/// Returns an empty array if no connection is available or if the ARP lookup fails.
/// </returns>
/// <remarks>
/// This method first checks if the results are cached and valid (less than 90 seconds old). If valid, cached results are returned.
/// Otherwise, it performs a new ARP lookup using the system's 'arp' command. The results are parsed, cached, and then returned.
/// If there's no network connection, an empty array is returned.
/// </remarks>
public static ArpEntity[] GetArpResult()
{
var timeSpan = DateTimeOffset.Now - lastLookup;
Expand Down Expand Up @@ -38,6 +53,18 @@ public static ArpEntity[] GetArpResult()
return ParseArpResult(output).ToArray();
}

/// <summary>
/// Parses the output from the ARP command into a collection of <see cref="ArpEntity"/>.
/// </summary>
/// <param name="output">The raw string output from the ARP command.</param>
/// <returns>
/// An enumerable of <see cref="ArpEntity"/> parsed from the command output.
/// </returns>
/// <remarks>
/// This method splits the command output into lines, then splits each line into parts based on whitespace.
/// It expects each line to have exactly three parts: IP address, physical address, and type. Lines not matching this format are ignored.
/// Parsed entries are cached for use by subsequent calls to GetArpResult within the cache period.
/// </remarks>
private static IEnumerable<ArpEntity> ParseArpResult(
string output)
{
Expand Down
19 changes: 19 additions & 0 deletions src/Atc.Network/Helpers/DnsLookupHelper.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
namespace Atc.Network.Helpers;

/// <summary>
/// Provides utilities for performing DNS lookups.
/// </summary>
public static class DnsLookupHelper
{
private static readonly SemaphoreSlim SyncLock = new(1, 1);
private static string? hostname;
private static IPAddress[]? hostAddresses;

/// <summary>
/// Resolves the hostname for a given IP address asynchronously.
/// </summary>
/// <param name="ipAddress">The IP address to resolve the hostname for.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>
/// The hostname associated with the specified IP address or null if the lookup fails or the
/// address is a private IP address for which the local hostname cannot be resolved.
/// </returns>
/// <remarks>
/// This method uses a SemaphoreSlim to ensure thread-safe access to the hostname and hostAddresses static fields.
/// It first checks if the IP address is a private address. If so, and if the hostname and hostAddresses have not
/// been previously set, it attempts to set them by resolving the local machine's hostname and IP addresses.
/// For public IP addresses, it performs a DNS lookup to resolve the hostname.
/// This method suppresses all exceptions, returning null in case of any errors or if the operation is canceled.
/// </remarks>
[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")]
public static async Task<string?> GetHostname(
IPAddress ipAddress,
Expand Down
56 changes: 56 additions & 0 deletions src/Atc.Network/Helpers/IPv4AddressHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,22 @@
// ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault
namespace Atc.Network.Helpers;

/// <summary>
/// Provides utilities for validating and working with IPv4 addresses.
/// </summary>
public static class IPv4AddressHelper
{
/// <summary>
/// Validates if a string is a valid IPv4 address.
/// </summary>
/// <param name="ipAddress">The IP address in string format to validate.</param>
/// <returns>
/// True if the IP address is valid; otherwise, false.
/// </returns>
/// <remarks>
/// This method checks if the string can be parsed into an IPAddress object and belongs to the IPv4 address family.
/// It also ensures that the IP address string has exactly four octets.
/// </remarks>
public static bool IsValid(
string ipAddress)
{
Expand All @@ -22,6 +36,14 @@ public static bool IsValid(
return octetCount == 4;
}

/// <summary>
/// Validates that two IP addresses are valid IPv4 addresses and that the start IP is less than or equal to the end IP.
/// </summary>
/// <param name="startIpAddress">The starting IP address of the range.</param>
/// <param name="endIpAddress">The ending IP address of the range.</param>
/// <returns>
/// A tuple containing a boolean indicating if the addresses are valid and an error message if they are not.
/// </returns>
public static (bool IsValid, string? ErrorMessage) ValidateAddresses(
IPAddress startIpAddress,
IPAddress endIpAddress)
Expand All @@ -44,13 +66,27 @@ public static (bool IsValid, string? ErrorMessage) ValidateAddresses(
: (true, null);
}

/// <summary>
/// Retrieves the local machine's IPv4 address.
/// </summary>
/// <returns>
/// The local IPv4 address, or null if not found.
/// </returns>
public static IPAddress? GetLocalAddress()
{
var hostName = Dns.GetHostName();
var host = Dns.GetHostEntry(hostName);
return host.AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork);
}

/// <summary>
/// Generates a collection of IPv4 addresses within a specified range.
/// </summary>
/// <param name="startIpAddress">The starting IP address of the range.</param>
/// <param name="endIpAddress">The ending IP address of the range.</param>
/// <returns>
/// A read-only collection of IP addresses within the specified range.
/// </returns>
public static IReadOnlyCollection<IPAddress> GetAddressesInRange(
IPAddress startIpAddress,
IPAddress endIpAddress)
Expand All @@ -72,6 +108,18 @@ public static IReadOnlyCollection<IPAddress> GetAddressesInRange(
return list;
}

/// <summary>
/// Generates a collection of IPv4 addresses within a subnet defined by an IP address and CIDR notation length.
/// </summary>
/// <param name="ipAddress">The IP address within the subnet.</param>
/// <param name="cidrLength">The CIDR notation length of the subnet mask.</param>
/// <returns>
/// A read-only collection of IP addresses within the specified subnet.
/// </returns>
/// <remarks>
/// This method calculates the first and last IP addresses in the subnet range based on the provided IP address and CIDR length.
/// It then generates all IP addresses within this range. This is particularly useful for subnet exploration and network analysis tasks.
/// </remarks>
public static IReadOnlyCollection<IPAddress> GetAddressesInRange(
IPAddress ipAddress,
int cidrLength)
Expand All @@ -82,6 +130,14 @@ public static IReadOnlyCollection<IPAddress> GetAddressesInRange(
return GetAddressesInRange(startIpAddress, endIpAddress);
}

/// <summary>
/// Calculates the first and last IP addresses in a subnet given an IP address and CIDR length.
/// </summary>
/// <param name="ipAddress">The IP address within the subnet.</param>
/// <param name="cidrLength">The CIDR notation length of the subnet mask.</param>
/// <returns>
/// A tuple containing the first and last IP addresses in the subnet range.
/// </returns>
public static (IPAddress StartIpAddress, IPAddress EndIpAddress) GetFirstAndLastAddressInRange(
IPAddress ipAddress,
int cidrLength)
Expand Down
14 changes: 8 additions & 6 deletions src/Atc.Network/Helpers/MacAddressVendorLookupHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public static class MacAddressVendorLookupHelper
private const string NotFound = "NotFound";
private const string AtcCacheFolder = "AtcCache";
private const string AtcCacheFile = "macvendors.txt";
private const int MinimumCallDelay = 1_200;
private const int SyncLockTimeout = 3_000;

private static readonly SemaphoreSlim SyncLock = new(1, 1);
private static readonly Uri MacVendorsApiUrl = new("http://api.macvendors.com/");
Expand All @@ -22,7 +24,7 @@ public static class MacAddressVendorLookupHelper

try
{
await SyncLock.WaitAsync(cancellationToken);
await SyncLock.WaitAsync(SyncLockTimeout, cancellationToken);

macAddress = macAddress.ToUpper(GlobalizationConstants.EnglishCultureInfo);
var cacheVendorName = GetVendorFromCacheFileLines(macAddress);
Expand Down Expand Up @@ -52,7 +54,7 @@ public static class MacAddressVendorLookupHelper
}
}

var vendorName = await CallMacVendorAsync(macAddress, cancellationToken);
var vendorName = await CallMacVendor(macAddress, cancellationToken);

cacheFileLines.Add($"{macAddress}={vendorName}");
await File.WriteAllLinesAsync(cacheFile, cacheFileLines, cancellationToken);
Expand All @@ -68,7 +70,7 @@ public static class MacAddressVendorLookupHelper
private static string? GetVendorFromCacheFileLines(
string macAddress)
{
if (!cacheFileLines.Any())
if (cacheFileLines.Count == 0)
{
return null;
}
Expand All @@ -91,14 +93,14 @@ public static class MacAddressVendorLookupHelper
}

[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")]
private static async Task<string?> CallMacVendorAsync(
private static async Task<string?> CallMacVendor(
string macAddress,
CancellationToken cancellationToken)
{
var timeSpan = DateTimeOffset.Now - lastLookup;
if (timeSpan.TotalMilliseconds < 1200)
if (timeSpan.TotalMilliseconds < MinimumCallDelay)
{
await Task.Delay(1200 - (int)timeSpan.TotalMilliseconds, cancellationToken);
await Task.Delay(MinimumCallDelay - (int)timeSpan.TotalMilliseconds, cancellationToken);
}

lastLookup = DateTimeOffset.Now;
Expand Down
28 changes: 28 additions & 0 deletions src/Atc.Network/Helpers/OpcUaAddressHelper.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
namespace Atc.Network.Helpers;

/// <summary>
/// Provides utilities for validating OPC UA (Open Platform Communications Unified Architecture) addresses.
/// </summary>
public static class OpcUaAddressHelper
{
private const int MinPortNumber = 1;
private const int MaxPortNumber = ushort.MaxValue;

/// <summary>
/// Validates the format of a given OPC UA address specified as a URL string.
/// </summary>
/// <param name="url">The OPC UA address to validate.</param>
/// <param name="restrictToIp4Address">Indicates whether to restrict validation to IPv4 addresses only.</param>
/// <returns>
/// True if the address is a valid OPC UA address; otherwise, false.
/// </returns>
/// <remarks>
/// This method checks if the URL is an absolute URI with the scheme "opc.tcp".
/// If <paramref name="restrictToIp4Address"/> is true, it further validates that the host part
/// of the URI is a valid IPv4 address.
/// </remarks>
public static bool IsValid(
string url,
bool restrictToIp4Address = false)
Expand All @@ -15,6 +31,18 @@ public static bool IsValid(
IsValid(uriResult, restrictToIp4Address);
}

/// <summary>
/// Validates the format of a given OPC UA address specified as a Uri object.
/// </summary>
/// <param name="uri">The Uri object representing the OPC UA address to validate.</param>
/// <param name="restrictToIp4Address">Indicates whether to restrict validation to IPv4 addresses only.</param>
/// <returns>
/// True if the address is a valid OPC UA address; otherwise, false.
/// </returns>
/// <remarks>
/// Validates that the Uri uses the "opc.tcp" scheme and, optionally, that its host is a valid IPv4 address
/// if <paramref name="restrictToIp4Address"/> is true. Also checks that the port number is within the valid range.
/// </remarks>
public static bool IsValid(
Uri uri,
bool restrictToIp4Address = false)
Expand Down
29 changes: 29 additions & 0 deletions src/Atc.Network/Helpers/PingHelper.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
namespace Atc.Network.Helpers;

/// <summary>
/// Provides utilities for performing ping operations to assess network connectivity and response time.
/// </summary>
public static class PingHelper
{
/// <summary>
/// Initiates a ping request to a specified IP address with a timeout.
/// </summary>
/// <param name="ipAddress">The IP address to ping.</param>
/// <param name="timeout">The maximum amount of time to wait for a ping response.</param>
/// <returns>
/// A task that represents the asynchronous operation, resulting in a <see cref="PingStatusResult"/>.
/// </returns>
/// <remarks>
/// This overload accepts a <see cref="TimeSpan"/> for the timeout and converts it to milliseconds
/// before calling the main asynchronous ping method.
/// </remarks>
public static Task<PingStatusResult> GetStatus(
IPAddress ipAddress,
TimeSpan timeout)
=> GetStatus(ipAddress, (int)timeout.TotalMilliseconds);

/// <summary>
/// Initiates a ping request to a specified IP address with a timeout specified in milliseconds.
/// </summary>
/// <param name="ipAddress">The IP address to ping.</param>
/// <param name="timeoutInMs">The maximum amount of time, in milliseconds, to wait for a ping response.
/// Defaults to 1000 milliseconds.</param>
/// <returns>
/// A task that represents the asynchronous operation, resulting in a <see cref="PingStatusResult"/>,
/// which includes the IP address, ping status, and response time.
/// </returns>
/// <remarks>
/// This method sends an asynchronous ping request to the specified IP address, measuring the response time.
/// If an exception occurs during the ping operation, it returns a <see cref="PingStatusResult"/> with the exception details.
/// </remarks>
[SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "OK.")]
public static async Task<PingStatusResult> GetStatus(
IPAddress ipAddress,
Expand Down
13 changes: 13 additions & 0 deletions src/Atc.Network/Helpers/TerminationHelper.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
namespace Atc.Network.Helpers;

/// <summary>
/// Provides utilities for appending termination sequences to data arrays.
/// </summary>
public static class TerminationHelper
{
/// <summary>
/// Appends termination bytes to a data array if the specified termination type is not already present at the end of the array.
/// </summary>
/// <param name="data">The data array to append the termination bytes to, if necessary.</param>
/// <param name="terminationType">The type of termination sequence to append.</param>
/// <remarks>
/// This method first checks if the termination type is None, in which case it does nothing. If the termination type is specified,
/// it converts the termination type to its byte array representation and checks if the data array already ends with this sequence.
/// If not, it appends the termination bytes to the end of the data array.
/// </remarks>
public static void AppendTerminationBytesIfNeeded(
ref byte[] data,
TerminationType terminationType)
Expand Down
Loading

0 comments on commit 054f382

Please sign in to comment.