diff --git a/docs/CodeDoc/Atc.Network/Atc.Network.Helpers.md b/docs/CodeDoc/Atc.Network/Atc.Network.Helpers.md index 0d94c35..2dd8d48 100644 --- a/docs/CodeDoc/Atc.Network/Atc.Network.Helpers.md +++ b/docs/CodeDoc/Atc.Network/Atc.Network.Helpers.md @@ -61,6 +61,10 @@ >```csharp >IPAddress GetLocalAddress() >``` +#### IsValid +>```csharp +>bool IsValid(string ipAddress) +>``` #### ValidateAddresses >```csharp >ValueTuple ValidateAddresses(IPAddress startIpAddress, IPAddress endIpAddress) @@ -83,6 +87,25 @@
+## OpcUaAddressHelper + +>```csharp +>public static class OpcUaAddressHelper +>``` + +### Static Methods + +#### IsValid +>```csharp +>bool IsValid(string url, bool restrictToIp4Address = False) +>``` +#### IsValid +>```csharp +>bool IsValid(Uri uri, bool restrictToIp4Address = False) +>``` + +
+ ## PingHelper >```csharp diff --git a/docs/CodeDoc/Atc.Network/Index.md b/docs/CodeDoc/Atc.Network/Index.md index dc045fe..833cee2 100644 --- a/docs/CodeDoc/Atc.Network/Index.md +++ b/docs/CodeDoc/Atc.Network/Index.md @@ -32,6 +32,7 @@ - [DnsLookupHelper](Atc.Network.Helpers.md#dnslookuphelper) - [IPv4AddressHelper](Atc.Network.Helpers.md#ipv4addresshelper) - [MacAddressVendorLookupHelper](Atc.Network.Helpers.md#macaddressvendorlookuphelper) +- [OpcUaAddressHelper](Atc.Network.Helpers.md#opcuaaddresshelper) - [PingHelper](Atc.Network.Helpers.md#pinghelper) - [TerminationHelper](Atc.Network.Helpers.md#terminationhelper) - [TerminationTypeHelper](Atc.Network.Helpers.md#terminationtypehelper) diff --git a/docs/CodeDoc/Atc.Network/IndexExtended.md b/docs/CodeDoc/Atc.Network/IndexExtended.md index 9b4dfa1..8420733 100644 --- a/docs/CodeDoc/Atc.Network/IndexExtended.md +++ b/docs/CodeDoc/Atc.Network/IndexExtended.md @@ -83,10 +83,15 @@ - GetAddressesInRange(IPAddress startIpAddress, IPAddress endIpAddress) - GetFirstAndLastAddressInRange(IPAddress ipAddress, int cidrLength) - GetLocalAddress() + - IsValid(string ipAddress) - ValidateAddresses(IPAddress startIpAddress, IPAddress endIpAddress) - [MacAddressVendorLookupHelper](Atc.Network.Helpers.md#macaddressvendorlookuphelper) - Static Methods - LookupVendorNameFromMacAddress(string macAddress, CancellationToken cancellationToken = null) +- [OpcUaAddressHelper](Atc.Network.Helpers.md#opcuaaddresshelper) + - Static Methods + - IsValid(Uri uri, bool restrictToIp4Address = False) + - IsValid(string url, bool restrictToIp4Address = False) - [PingHelper](Atc.Network.Helpers.md#pinghelper) - Static Methods - GetStatus(IPAddress ipAddress, TimeSpan timeout) diff --git a/src/Atc.Network/Helpers/IPv4AddressHelper.cs b/src/Atc.Network/Helpers/IPv4AddressHelper.cs index 63af8d9..5bd9a57 100644 --- a/src/Atc.Network/Helpers/IPv4AddressHelper.cs +++ b/src/Atc.Network/Helpers/IPv4AddressHelper.cs @@ -5,6 +5,23 @@ namespace Atc.Network.Helpers; public static class IPv4AddressHelper { + public static bool IsValid( + string ipAddress) + { + ArgumentNullException.ThrowIfNull(ipAddress); + + var isValid = IPAddress.TryParse(ipAddress, out var address) && + address.AddressFamily == AddressFamily.InterNetwork; + + if (!isValid) + { + return false; + } + + var octetCount = ipAddress.Split('.').Length; + return octetCount == 4; + } + public static (bool IsValid, string? ErrorMessage) ValidateAddresses( IPAddress startIpAddress, IPAddress endIpAddress) diff --git a/src/Atc.Network/Helpers/OpcUaAddressHelper.cs b/src/Atc.Network/Helpers/OpcUaAddressHelper.cs new file mode 100644 index 0000000..394675f --- /dev/null +++ b/src/Atc.Network/Helpers/OpcUaAddressHelper.cs @@ -0,0 +1,46 @@ +namespace Atc.Network.Helpers; + +public static class OpcUaAddressHelper +{ + private const int MinPortNumber = 1; + private const int MaxPortNumber = ushort.MaxValue; + + public static bool IsValid( + string url, + bool restrictToIp4Address = false) + { + ArgumentNullException.ThrowIfNull(url); + + return Uri.TryCreate(url, UriKind.Absolute, out var uriResult) && + IsValid(uriResult, restrictToIp4Address); + } + + public static bool IsValid( + Uri uri, + bool restrictToIp4Address = false) + { + ArgumentNullException.ThrowIfNull(uri); + + if (!string.Equals(uri.Scheme, "opc.tcp", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (restrictToIp4Address) + { + if (!IPv4AddressHelper.IsValid(uri.Host)) + { + return false; + } + } + else + { + if (string.IsNullOrEmpty(uri.Host)) + { + return false; + } + } + + return uri.Port is >= MinPortNumber and <= MaxPortNumber; + } +} \ No newline at end of file diff --git a/test/Atc.Network.Test/Data/IPServicePortListsTests.cs b/test/Atc.Network.Test/Data/IPServicePortListsTests.cs index 6daedf1..c03b2c9 100644 --- a/test/Atc.Network.Test/Data/IPServicePortListsTests.cs +++ b/test/Atc.Network.Test/Data/IPServicePortListsTests.cs @@ -5,7 +5,7 @@ public class IPServicePortListsTests [Fact] public void GetWellKnown() { - // Atc + // Act var actual = IPServicePortLists.GetWellKnown(); // Assert @@ -24,7 +24,7 @@ public void GetWellKnown() [InlineData(1, ServiceProtocolType.Telnet)] public void GetWellKnown_ServiceProtocolType(int expected, ServiceProtocolType serviceProtocolType) { - // Atc + // Act var actual = IPServicePortLists.GetWellKnown(serviceProtocolType); // Assert @@ -35,7 +35,7 @@ public void GetWellKnown_ServiceProtocolType(int expected, ServiceProtocolType s [Fact] public void GetWellKnownOrCommon() { - // Atc + // Act var actual = IPServicePortLists.GetWellKnownOrCommon(); // Assert @@ -54,7 +54,7 @@ public void GetWellKnownOrCommon() [InlineData(1, ServiceProtocolType.Telnet)] public void GetWellKnownOrCommon_ServiceProtocolType(int expected, ServiceProtocolType serviceProtocolType) { - // Atc + // Act var actual = IPServicePortLists.GetWellKnownOrCommon(serviceProtocolType); // Assert diff --git a/test/Atc.Network.Test/Extensions/IPScannerConfigExtensionsTests.cs b/test/Atc.Network.Test/Extensions/IPScannerConfigExtensionsTests.cs index 3fd9396..e0c08b5 100644 --- a/test/Atc.Network.Test/Extensions/IPScannerConfigExtensionsTests.cs +++ b/test/Atc.Network.Test/Extensions/IPScannerConfigExtensionsTests.cs @@ -39,7 +39,7 @@ public void GetTasksToProcessCount( ipScannerConfig.PortNumbers.Add((ushort)(i + 10)); } - // Atc + // Act var actual = ipScannerConfig.GetTasksToProcessCount(); // Assert diff --git a/test/Atc.Network.Test/Extensions/TcpClientExtensionsTests.cs b/test/Atc.Network.Test/Extensions/TcpClientExtensionsTests.cs index 2fc9d93..9041dd3 100644 --- a/test/Atc.Network.Test/Extensions/TcpClientExtensionsTests.cs +++ b/test/Atc.Network.Test/Extensions/TcpClientExtensionsTests.cs @@ -19,7 +19,7 @@ public void SetBufferSizeAndTimeouts(int sendTimeout, int sendBufferSize, int re // Arrange var tcpClient = new System.Net.Sockets.TcpClient(); - // Atc + // Act tcpClient.SetBufferSizeAndTimeouts(sendTimeout, sendBufferSize, receiveTimeout, receiveBufferSize); // Assert @@ -38,7 +38,7 @@ public void SetKeepAlive(bool expected, int tcpKeepAliveTime, int tcpKeepAliveIn // Arrange var tcpClient = new System.Net.Sockets.TcpClient(); - // Atc + // Act bool? result = null; if (expected) { @@ -67,7 +67,7 @@ public void DisableKeepAlive() // Arrange var tcpClient = new System.Net.Sockets.TcpClient(); - // Atc + // Act const bool result = true; tcpClient.DisableKeepAlive(); diff --git a/test/Atc.Network.Test/Helpers/IPv4AddressHelperTests.cs b/test/Atc.Network.Test/Helpers/IPv4AddressHelperTests.cs index fc84d23..006ab98 100644 --- a/test/Atc.Network.Test/Helpers/IPv4AddressHelperTests.cs +++ b/test/Atc.Network.Test/Helpers/IPv4AddressHelperTests.cs @@ -3,6 +3,27 @@ namespace Atc.Network.Test.Helpers; public class IPv4AddressHelperTests { + [Theory] + [InlineData(false, "")] // Empty string + [InlineData(false, "192.168")] // Missing octets + [InlineData(false, "192.168.1")] // Missing one octet + [InlineData(true, "192.168.1.1")] // Valid IP + [InlineData(false, "192.168.1.256")] // Octet above 255 + [InlineData(false, "192.168.1.-1")] // Negative octet + [InlineData(false, "abc.def.ghi.jkl")] // Non-numeric octets + [InlineData(true, "0.0.0.0")] // Valid, all zeros + [InlineData(false, "256.256.256.256")] // All octets above 255 + [InlineData(true, "255.255.255.255")] // All octets at max value 255 + + public void IsValid(bool expected, string ipAddress) + { + // Act + var isValid = IPv4AddressHelper.IsValid(ipAddress); + + // Assert + Assert.Equal(expected, isValid); + } + [Theory] [InlineData(true, "10.50.30.7", "10.50.30.7")] [InlineData(true, "10.50.30.7", "10.50.30.70")] @@ -11,12 +32,12 @@ public class IPv4AddressHelperTests [InlineData(false, "10.50.31.7", "10.50.30.7")] public void ValidateAddresses(bool expected, string ipAddressStart, string ipAddressEnd) { - // Atc + // Act var (isValid, _) = IPv4AddressHelper.ValidateAddresses( IPAddress.Parse(ipAddressStart), IPAddress.Parse(ipAddressEnd)); - // Asset + // Assert Assert.Equal(expected, isValid); } @@ -31,12 +52,12 @@ public void GetLocalAddress() [InlineData(320, "10.50.30.7", "10.50.31.70")] public void GetAddressesInRange(int expected, string ipAddressStart, string ipAddressEnd) { - // Atc + // Act var actual = IPv4AddressHelper.GetAddressesInRange( IPAddress.Parse(ipAddressStart), IPAddress.Parse(ipAddressEnd)); - // Asset + // Assert Assert.Equal(expected, actual.Count); } @@ -52,12 +73,12 @@ public void GetAddressesInRange(int expected, string ipAddressStart, string ipAd [InlineData(1, "10.0.0.0", 32)] public void GetAddressesInRange_Cidr(int expected, string ipAddress, int cidrMaskLength) { - // Atc + // Act var actual = IPv4AddressHelper.GetAddressesInRange( IPAddress.Parse(ipAddress), cidrMaskLength); - // Asset + // Assert Assert.Equal(expected, actual.Count); } @@ -74,12 +95,12 @@ public void GetAddressesInRange_Cidr(int expected, string ipAddress, int cidrMas [InlineData("192.168.0.0", "192.168.0.255", "192.168.0.7", 24)] public void GetFirstAndLastAddressInRange(string expected1, string expected2, string ipAddress, int cidrMaskLength) { - // Atc + // Act var actual = IPv4AddressHelper.GetFirstAndLastAddressInRange( IPAddress.Parse(ipAddress), cidrMaskLength); - // Asset + // Assert Assert.Equal(IPAddress.Parse(expected1), actual.StartIpAddress); Assert.Equal(IPAddress.Parse(expected2), actual.EndIpAddress); } diff --git a/test/Atc.Network.Test/Helpers/OpcUaAddressHelperTests.cs b/test/Atc.Network.Test/Helpers/OpcUaAddressHelperTests.cs new file mode 100644 index 0000000..400168d --- /dev/null +++ b/test/Atc.Network.Test/Helpers/OpcUaAddressHelperTests.cs @@ -0,0 +1,62 @@ +// ReSharper disable StringLiteralTypo +namespace Atc.Network.Test.Helpers; + +[SuppressMessage("Design", "CA1054:URI parameters should not be strings", Justification = "OK")] +[SuppressMessage("Usage", "CA2234:Pass system uri objects instead of strings", Justification = "OK")] +public class OpcUaAddressHelperTests +{ + [Theory] + [InlineData(true, "opc.tcp://milo.digitalpetri.com:62541", false)] + [InlineData(true, "opc.tcp://milo.digitalpetri.com:62541/", false)] + [InlineData(true, "opc.tcp://milo.digitalpetri.com:62541/milo", false)] + [InlineData(true, "opc.tcp://192.168.1.1:62541", true)] + [InlineData(true, "opc.tcp://192.168.1.1:62541/", true)] + [InlineData(true, "opc.tcp://192.168.1.1:62541/milo", true)] + [InlineData(false, "", false)] + [InlineData(false, "milo.digitalpetri.com:62541/milo", false)] + [InlineData(false, "opc.tcp://62541/milo", false)] + [InlineData(false, "opc.tcp://milo.digitalpetri.com:62541/milo", true)] + [InlineData(false, "opc.tcp://192.168.1:62541", true)] + [InlineData(false, "opc.tcp://192.168.1:62541/", true)] + [InlineData(false, "opc.tcp://192.168.1:62541/milo", true)] + public void IsValid( + bool expected, + string url, + bool restrictToIp4Address) + { + // Act + var isValid = OpcUaAddressHelper.IsValid(url, restrictToIp4Address); + + // Assert + Assert.Equal(expected, isValid); + } + + [Theory] + [InlineData(true, "opc.tcp://milo.digitalpetri.com:62541", false)] + [InlineData(true, "opc.tcp://milo.digitalpetri.com:62541/", false)] + [InlineData(true, "opc.tcp://milo.digitalpetri.com:62541/milo", false)] + [InlineData(true, "opc.tcp://192.168.1.1:62541", true)] + [InlineData(true, "opc.tcp://192.168.1.1:62541/", true)] + [InlineData(true, "opc.tcp://192.168.1.1:62541/milo", true)] + [InlineData(false, "milo.digitalpetri.com:62541/milo", false)] + [InlineData(false, "opc.tcp://62541/milo", false)] + [InlineData(false, "opc.tcp://milo.digitalpetri.com:62541/milo", true)] + [InlineData(false, "opc.tcp://192.168.1:62541", true)] + [InlineData(false, "opc.tcp://192.168.1:62541/", true)] + [InlineData(false, "opc.tcp://192.168.1:62541/milo", true)] + + public void IsValid_AsUri( + bool expected, + string url, + bool restrictToIp4Address) + { + // Arrange + var uri = new Uri(url); + + // Act + var isValid = OpcUaAddressHelper.IsValid(uri, restrictToIp4Address); + + // Assert + Assert.Equal(expected, isValid); + } +} \ No newline at end of file diff --git a/test/Atc.Network.Test/Helpers/TerminationHelperTests.cs b/test/Atc.Network.Test/Helpers/TerminationHelperTests.cs index 8048438..41bb906 100644 --- a/test/Atc.Network.Test/Helpers/TerminationHelperTests.cs +++ b/test/Atc.Network.Test/Helpers/TerminationHelperTests.cs @@ -14,7 +14,7 @@ public void AppendTerminationBytesIfNeeded(int expectedLength, string value, Ter // Arrange var bytes = Encoding.ASCII.GetBytes(value); - // Atc + // Act TerminationHelper.AppendTerminationBytesIfNeeded(ref bytes, terminationType); // Assert