From 7a930d75eac944130d30f10d72ba8c2d6fb2bcf4 Mon Sep 17 00:00:00 2001 From: lduchosal Date: Sun, 17 Aug 2025 17:03:57 +0200 Subject: [PATCH 1/5] Feat: operator add IPNetwork, int --- src/System.Net.IPNetwork/IPNetwork2Members.cs | 28 +++- .../IPNetwork2Operators.cs | 22 ++++ src/TestProject/BigIntegerToUnitTest.cs | 122 +++++++++--------- src/TestProject/IPAddressTest.cs | 25 ++++ .../IPNetworkTest/IPNetworkOperatorTests.cs | 32 +++++ 5 files changed, 166 insertions(+), 63 deletions(-) create mode 100644 src/TestProject/IPAddressTest.cs diff --git a/src/System.Net.IPNetwork/IPNetwork2Members.cs b/src/System.Net.IPNetwork/IPNetwork2Members.cs index 0c9bcf5..a9418cf 100644 --- a/src/System.Net.IPNetwork/IPNetwork2Members.cs +++ b/src/System.Net.IPNetwork/IPNetwork2Members.cs @@ -93,7 +93,7 @@ public IPAddress Broadcast return ToIPAddress(this.InternalBroadcast, this.family); } } - + /// /// Gets first usable IPAddress in Network. /// @@ -127,7 +127,31 @@ public IPAddress LastUsable return ToIPAddress(last, this.family); } } - + + /// + /// Gets first IPAddress in Network. + /// + public IPAddress First + { + get + { + BigInteger first = this.InternalNetwork; + return ToIPAddress(first, this.family); + } + } + + /// + /// Gets last IPAddress in Network. + /// + public IPAddress Last + { + get + { + BigInteger last = this.InternalBroadcast; + return ToIPAddress(last, this.family); + } + } + /// /// Gets number of usable IPAddress in Network. /// diff --git a/src/System.Net.IPNetwork/IPNetwork2Operators.cs b/src/System.Net.IPNetwork/IPNetwork2Operators.cs index 33d50e1..ff20b62 100644 --- a/src/System.Net.IPNetwork/IPNetwork2Operators.cs +++ b/src/System.Net.IPNetwork/IPNetwork2Operators.cs @@ -103,4 +103,26 @@ public sealed partial class IPNetwork2 } return [left, right]; } + + /// + /// Add IPNetwork. + /// + /// left instance. + /// number. + /// Try to supernet two consecutive cidr equal subnet into a single one, otherwise return both netowkrs. + public static IEnumerable operator +(IPNetwork2 left, int add) + { + var uintFirstLeft = ToBigInteger(left.First); + var uintLastLeft = ToBigInteger(left.Last); + var uintRight = uintLastLeft+add; + + var start = uintFirstLeft > uintRight ? uintRight : uintFirstLeft; + var end = uintFirstLeft > uintRight ? uintFirstLeft : uintRight; + + var startIp = ToIPAddress(start, left.AddressFamily); + var endIp = ToIPAddress(end, left.AddressFamily); + + InternalParseRange(false, startIp, endIp, out IEnumerable networks); + return networks; + } } \ No newline at end of file diff --git a/src/TestProject/BigIntegerToUnitTest.cs b/src/TestProject/BigIntegerToUnitTest.cs index db3c157..3927ea8 100644 --- a/src/TestProject/BigIntegerToUnitTest.cs +++ b/src/TestProject/BigIntegerToUnitTest.cs @@ -16,12 +16,12 @@ public class BigIntegerToUnitTest [TestMethod] public void TestToOctalString1() { - byte[] bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0x00]; - var convertme = new BigInteger(bytes); - string result = convertme.ToOctalString(); + byte[] bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0x00]; + var convertme = new BigInteger(bytes); + string result = convertme.ToOctalString(); - Assert.AreEqual("037777777777", result); - } + Assert.AreEqual("037777777777", result); + } /// /// Test. @@ -29,12 +29,12 @@ public void TestToOctalString1() [TestMethod] public void TestToOctalString3() { - var bigi = BigInteger.Parse("1048576"); - bigi++; - string result = bigi.ToOctalString(); + var bigi = BigInteger.Parse("1048576"); + bigi++; + string result = bigi.ToOctalString(); - Assert.AreEqual("04000001", result); - } + Assert.AreEqual("04000001", result); + } /// /// Test. @@ -42,12 +42,12 @@ public void TestToOctalString3() [TestMethod] public void TestToOctalString01() { - BigInteger bigi = BigInteger.Zero; - bigi++; - string result = bigi.ToOctalString(); + BigInteger bigi = BigInteger.Zero; + bigi++; + string result = bigi.ToOctalString(); - Assert.AreEqual("01", result); - } + Assert.AreEqual("01", result); + } /// /// Test. @@ -55,12 +55,12 @@ public void TestToOctalString01() [TestMethod] public void TestToOctalString02() { - BigInteger bigi = BigInteger.Zero; - bigi--; - string result = bigi.ToOctalString(); + BigInteger bigi = BigInteger.Zero; + bigi--; + string result = bigi.ToOctalString(); - Assert.AreEqual("377", result); - } + Assert.AreEqual("377", result); + } /// /// Test. @@ -68,18 +68,18 @@ public void TestToOctalString02() [TestMethod] public void TestToOctalString03() { - BigInteger bigi = BigInteger.Zero; - bigi--; - bigi--; - bigi--; - bigi--; - bigi--; - bigi--; - bigi--; - string result = bigi.ToOctalString(); - - Assert.AreEqual("371", result); - } + BigInteger bigi = BigInteger.Zero; + bigi--; + bigi--; + bigi--; + bigi--; + bigi--; + bigi--; + bigi--; + string result = bigi.ToOctalString(); + + Assert.AreEqual("371", result); + } /// /// Test. @@ -87,12 +87,12 @@ public void TestToOctalString03() [TestMethod] public void TestToHexadecimalString1() { - byte[] bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0x00]; - var convertme = new BigInteger(bytes); - string result = convertme.ToHexadecimalString(); + byte[] bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0x00]; + var convertme = new BigInteger(bytes); + string result = convertme.ToHexadecimalString(); - Assert.AreEqual("0FFFFFFFF", result); - } + Assert.AreEqual("0FFFFFFFF", result); + } /// /// Test. @@ -100,12 +100,12 @@ public void TestToHexadecimalString1() [TestMethod] public void TestToBinaryString1() { - byte[] bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0x00]; - var convertme = new BigInteger(bytes); - string result = convertme.ToBinaryString(); + byte[] bytes = [0xFF, 0xFF, 0xFF, 0xFF, 0x00]; + var convertme = new BigInteger(bytes); + string result = convertme.ToBinaryString(); - Assert.AreEqual("011111111111111111111111111111111", result); - } + Assert.AreEqual("011111111111111111111111111111111", result); + } /// /// Test. @@ -113,12 +113,12 @@ public void TestToBinaryString1() [TestMethod] public void TestToBinaryString01() { - BigInteger bigi = BigInteger.Zero; - bigi++; - string result = bigi.ToBinaryString(); + BigInteger bigi = BigInteger.Zero; + bigi++; + string result = bigi.ToBinaryString(); - Assert.AreEqual("01", result); - } + Assert.AreEqual("01", result); + } /// /// Test. @@ -126,11 +126,11 @@ public void TestToBinaryString01() [TestMethod] public void TestToBinaryString2() { - var convertme = new BigInteger(-1); - string result = convertme.ToBinaryString(); + var convertme = new BigInteger(-1); + string result = convertme.ToBinaryString(); - Assert.AreEqual("11111111", result); - } + Assert.AreEqual("11111111", result); + } /// /// Test. @@ -138,14 +138,14 @@ public void TestToBinaryString2() [TestMethod] public void TestToBinaryString3() { - byte[] bytes = - [ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - ]; - var convertme = new BigInteger(bytes); - string result = convertme.ToBinaryString(); - - Assert.AreEqual("11111111", result); - } + byte[] bytes = + [ + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + ]; + var convertme = new BigInteger(bytes); + string result = convertme.ToBinaryString(); + + Assert.AreEqual("11111111", result); + } } \ No newline at end of file diff --git a/src/TestProject/IPAddressTest.cs b/src/TestProject/IPAddressTest.cs new file mode 100644 index 0000000..d9f35d0 --- /dev/null +++ b/src/TestProject/IPAddressTest.cs @@ -0,0 +1,25 @@ +namespace TestProject; + +/// +/// Test IPAddress Behaviour on MacOS +/// +[TestClass] +public class IPAddressTest +{ + /// + /// Mixed IPv6 / IPvç notation + /// Why This Happens + /// .NET automatically uses mixed notation for IPv6 addresses when: + /// The first 96 bits are zero (IPv4-compatible) + /// The first 80 bits are zero and bits 81-96 are either all zeros or all ones (IPv4-mapped) + /// The pattern suggests an embedded IPv4 address + /// Your bytes ::0.1.0.0 are being interpreted as an IPv4-compatible IPv6 address. + /// + [TestMethod] + public void TestIPAddress() + { + byte[] bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]; + var ip = new IPAddress(bytes); + Assert.AreEqual("::0.1.0.0", ip.ToString()); + } +} \ No newline at end of file diff --git a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs index 42c7e4f..5dfcd4b 100644 --- a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs +++ b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs @@ -331,4 +331,36 @@ public void TestOperatorAdd(string left, string right, int count) Assert.HasCount(count, result, "add"); } + + /// + /// Tests Operator functionality with Operator -. + /// + [TestMethod] + [DataRow("10.0.0.0/32", 1, "10.0.0.0/31")] + [DataRow("10.0.0.0/32", 3, "10.0.0.0/30")] + [DataRow("10.0.0.0/32", 7, "10.0.0.0/29")] + [DataRow("10.0.0.0/32", 15, "10.0.0.0/28")] + [DataRow("10.0.0.0/32", 31, "10.0.0.0/27")] + [DataRow("10.0.0.0/32", 63, "10.0.0.0/26")] + [DataRow("10.0.0.0/32", 127, "10.0.0.0/25")] + [DataRow("10.0.0.0/32", 255, "10.0.0.0/24")] + [DataRow("10.0.0.0/32", 511, "10.0.0.0/23")] + [DataRow("10.0.0.0/32", 1023, "10.0.0.0/22")] + [DataRow("10.0.0.0/32", 2047, "10.0.0.0/21")] + [DataRow("10.0.0.0/32", 4095, "10.0.0.0/20")] + [DataRow("10.0.0.0/32", -1, "9.255.255.255/32, 10.0.0.0/32")] + [DataRow("10.0.0.0/32", -10, "9.255.255.246/31, 9.255.255.248/29, 10.0.0.0/32")] + [DataRow("10.0.0.0/32", 2, "10.0.0.0/31, 10.0.0.2/32")] + [DataRow("::/128", 1, "::/127")] + [DataRow("::/128", 3, "::/126")] + [DataRow("::f/128", 1, "::f/128, ::10/128")] + [DataRow("1::ffff/128", 1, "1::ffff/128, 1::1:0/128")] + public void TestOperatorAdd2(string left, int right, string expected) + { + var ipn1 = IPNetwork2.Parse(left); + var result = ipn1 + right; + string sresult = string.Join(", ", result); + + Assert.AreEqual(expected, sresult); + } } \ No newline at end of file From 84cc8d0954742509014b429e424fd80350b8abab Mon Sep 17 00:00:00 2001 From: lduchosal Date: Sun, 17 Aug 2025 17:33:09 +0200 Subject: [PATCH 2/5] Feat: operation -minus --- .../IPNetwork2Operators.cs | 32 ++++++++++++++---- .../IPNetworkTest/IPNetworkOperatorTests.cs | 33 +++++++++++++++++-- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/System.Net.IPNetwork/IPNetwork2Operators.cs b/src/System.Net.IPNetwork/IPNetwork2Operators.cs index ff20b62..567fdd8 100644 --- a/src/System.Net.IPNetwork/IPNetwork2Operators.cs +++ b/src/System.Net.IPNetwork/IPNetwork2Operators.cs @@ -105,19 +105,26 @@ public sealed partial class IPNetwork2 } /// - /// Add IPNetwork. + /// Behavior + /// The addition operator (+) performs the following operations: + /// Network Expansion: Adds the specified number of IP addresses to the network range + /// Optimal Grouping: Attempts to create the most efficient network representation + /// Multiple Networks: When a single contiguous network cannot represent the result, returns multiple networks + /// CIDR Optimization: Automatically calculates the appropriate subnet mask for the expanded range /// /// left instance. /// number. - /// Try to supernet two consecutive cidr equal subnet into a single one, otherwise return both netowkrs. + /// Adds the specified number of IP addresses to the network range. public static IEnumerable operator +(IPNetwork2 left, int add) { - var uintFirstLeft = ToBigInteger(left.First); - var uintLastLeft = ToBigInteger(left.Last); - var uintRight = uintLastLeft+add; + var start = ToBigInteger(left.First); + var last = ToBigInteger(left.Last); + var end = last+ add; - var start = uintFirstLeft > uintRight ? uintRight : uintFirstLeft; - var end = uintFirstLeft > uintRight ? uintFirstLeft : uintRight; + if (end < start) + { + return []; + } var startIp = ToIPAddress(start, left.AddressFamily); var endIp = ToIPAddress(end, left.AddressFamily); @@ -125,4 +132,15 @@ public sealed partial class IPNetwork2 InternalParseRange(false, startIp, endIp, out IEnumerable networks); return networks; } + + /// + /// Add IPNetwork. + /// + /// left instance. + /// number. + /// Try to supernet two consecutive cidr equal subnet into a single one, otherwise return both netowkrs. + public static IEnumerable operator -(IPNetwork2 left, int subtract) + { + return left + (subtract * -1); + } } \ No newline at end of file diff --git a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs index 5dfcd4b..0731ade 100644 --- a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs +++ b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs @@ -333,7 +333,7 @@ public void TestOperatorAdd(string left, string right, int count) } /// - /// Tests Operator functionality with Operator -. + /// Tests Operator functionality with Operator +. /// [TestMethod] [DataRow("10.0.0.0/32", 1, "10.0.0.0/31")] @@ -348,8 +348,8 @@ public void TestOperatorAdd(string left, string right, int count) [DataRow("10.0.0.0/32", 1023, "10.0.0.0/22")] [DataRow("10.0.0.0/32", 2047, "10.0.0.0/21")] [DataRow("10.0.0.0/32", 4095, "10.0.0.0/20")] - [DataRow("10.0.0.0/32", -1, "9.255.255.255/32, 10.0.0.0/32")] - [DataRow("10.0.0.0/32", -10, "9.255.255.246/31, 9.255.255.248/29, 10.0.0.0/32")] + [DataRow("10.0.0.0/32", -1, "")] + [DataRow("10.0.0.0/32", -10, "")] [DataRow("10.0.0.0/32", 2, "10.0.0.0/31, 10.0.0.2/32")] [DataRow("::/128", 1, "::/127")] [DataRow("::/128", 3, "::/126")] @@ -363,4 +363,31 @@ public void TestOperatorAdd2(string left, int right, string expected) Assert.AreEqual(expected, sresult); } + + + /// + /// Tests Operator functionality with Operator -. + /// + [TestMethod] + [DataRow("10.0.0.0/32", 1, "")] + [DataRow("10.0.0.0/32", 3, "")] + [DataRow("10.0.0.0/32", 7, "")] + [DataRow("10.0.0.0/32", 15, "")] + [DataRow("10.0.0.0/32", 31, "")] + [DataRow("10.0.0.0/32", 63, "")] + [DataRow("10.0.0.0/32", 4095, "")] + [DataRow("10.0.0.0/32", -1, "10.0.0.0/31")] + [DataRow("10.0.0.0/32", -10, "10.0.0.0/29, 10.0.0.8/31, 10.0.0.10/32")] + [DataRow("::/128", 1, "")] + [DataRow("::/127", 1, "::/128")] + [DataRow("::/128", 3, "")] + [DataRow("::f/128", 1, "")] + public void TestOperatorsubtract2(string left, int right, string expected) + { + var ipn1 = IPNetwork2.Parse(left); + var result = ipn1 - right; + string sresult = string.Join(", ", result); + + Assert.AreEqual(expected, sresult); + } } \ No newline at end of file From 2fd8bc9cbf7b6f66c141ec3810d6e441aaeb0144 Mon Sep 17 00:00:00 2001 From: lduchosal Date: Sun, 17 Aug 2025 18:01:10 +0200 Subject: [PATCH 3/5] Fix: edge case with overflow --- .../IPNetwork2Operators.cs | 9 ++- src/TestProject/IPAddressTest.cs | 14 ++-- .../IPNetworkTest/IPNetworkOperatorTests.cs | 69 +++++++++++-------- 3 files changed, 56 insertions(+), 36 deletions(-) diff --git a/src/System.Net.IPNetwork/IPNetwork2Operators.cs b/src/System.Net.IPNetwork/IPNetwork2Operators.cs index 567fdd8..f4582a9 100644 --- a/src/System.Net.IPNetwork/IPNetwork2Operators.cs +++ b/src/System.Net.IPNetwork/IPNetwork2Operators.cs @@ -129,7 +129,14 @@ public sealed partial class IPNetwork2 var startIp = ToIPAddress(start, left.AddressFamily); var endIp = ToIPAddress(end, left.AddressFamily); - InternalParseRange(false, startIp, endIp, out IEnumerable networks); + var uintStart = ToBigInteger(startIp); + var uintEnd = ToBigInteger(endIp); + + if (uintEnd <= uintStart) + { + throw new OverflowException("IPNetwork overflow"); + } + InternalParseRange(true, startIp, endIp, out IEnumerable networks); return networks; } diff --git a/src/TestProject/IPAddressTest.cs b/src/TestProject/IPAddressTest.cs index d9f35d0..51b15c9 100644 --- a/src/TestProject/IPAddressTest.cs +++ b/src/TestProject/IPAddressTest.cs @@ -1,18 +1,18 @@ namespace TestProject; /// -/// Test IPAddress Behaviour on MacOS +/// Test IPAddress Behaviour on MacOS /// [TestClass] public class IPAddressTest { /// - /// Mixed IPv6 / IPvç notation - /// Why This Happens - /// .NET automatically uses mixed notation for IPv6 addresses when: - /// The first 96 bits are zero (IPv4-compatible) - /// The first 80 bits are zero and bits 81-96 are either all zeros or all ones (IPv4-mapped) - /// The pattern suggests an embedded IPv4 address + /// Mixed IPv6 / IPvç notation + /// Why This Happens + /// .NET automatically uses mixed notation for IPv6 addresses when: + /// The first 96 bits are zero (IPv4-compatible) + /// The first 80 bits are zero and bits 81-96 are either all zeros or all ones (IPv4-mapped) + /// The pattern suggests an embedded IPv4 address /// Your bytes ::0.1.0.0 are being interpreted as an IPv4-compatible IPv6 address. /// [TestMethod] diff --git a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs index 0731ade..6bc8dcd 100644 --- a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs +++ b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs @@ -5,7 +5,7 @@ namespace TestProject.IPNetworkTest; /// -/// Tests with operators. +/// Tests with operators. /// [TestClass] public class IPNetworkOperatorTests @@ -23,7 +23,7 @@ public void TestOperatorGreater1() Assert.IsFalse(greater, "greater"); } - + /// /// Tests Operator functionality with Operator Greater. /// @@ -37,7 +37,7 @@ public void TestOperatorGreater2() Assert.IsTrue(greater, "greater"); } - + /// /// Tests Operator functionality with Operator Greater. /// @@ -51,8 +51,8 @@ public void TestOperatorGreater3() Assert.IsFalse(greater, "greater"); } - - + + /// /// Tests Operator functionality with Operator Greater. /// @@ -80,7 +80,7 @@ public void TestOperatorGreaterOrEqual1() Assert.IsFalse(greater, "greater"); } - + /// /// Tests Operator functionality with Operator Greater. /// @@ -94,7 +94,7 @@ public void TestOperatorGreaterOrEqual2() Assert.IsTrue(greater, "greater"); } - + /// /// Tests Operator functionality with Operator Greater. /// @@ -108,9 +108,9 @@ public void TestOperatorGreaterOrEqual3() Assert.IsTrue(greater, "greater"); } - + /// - /// Tests Operator functionality with Operator Greater. + /// Tests Operator functionality with Operator Greater. /// [TestMethod] public void TestOperatorGreaterOrEqual4() @@ -122,9 +122,9 @@ public void TestOperatorGreaterOrEqual4() Assert.IsFalse(greater, "greater"); } - + /// - /// Tests Operator functionality with Operator Greater. + /// Tests Operator functionality with Operator Greater. /// [TestMethod] public void TestOperatorGreaterOrEqual5() @@ -166,7 +166,7 @@ public void TestOperatorLower2() } /// - /// Tests Operator functionality with Operator Lower. + /// Tests Operator functionality with Operator Lower. /// [TestMethod] public void TestOperatorLower3() @@ -208,7 +208,7 @@ public void TestOperatorLowerOrEqual2() } /// - /// Tests Operator functionality with Operator Lower. + /// Tests Operator functionality with Operator Lower. /// [TestMethod] public void TestOperatorLowerOrEqual3() @@ -277,9 +277,9 @@ public void TestOperatorEqual2() Assert.IsTrue(eq, "eq"); } - + /// - /// Tests Operator functionality with Operator -. + /// Tests Operator functionality with Operator -. /// [TestMethod] [DataRow("10.0.0.1/32", "10.0.0.1/32", 0)] @@ -297,14 +297,14 @@ public void TestOperatorSubtract(string left, string right, int count) var ipn1 = IPNetwork2.Parse(left); var ipn2 = IPNetwork2.Parse(right); - var result = ipn1 - ipn2; + List result = ipn1 - ipn2; Assert.HasCount(count, result, "subtract"); } - - + + /// - /// Tests Operator functionality with Operator -. + /// Tests Operator functionality with Operator -. /// [TestMethod] [DataRow("10.0.0.0/32", "10.0.0.1/32", 1)] @@ -327,17 +327,19 @@ public void TestOperatorAdd(string left, string right, int count) var ipn1 = IPNetwork2.Parse(left); var ipn2 = IPNetwork2.Parse(right); - var result = ipn1 + ipn2; + List result = ipn1 + ipn2; Assert.HasCount(count, result, "add"); } - + /// - /// Tests Operator functionality with Operator +. + /// Tests Operator functionality with Operator +. /// [TestMethod] [DataRow("10.0.0.0/32", 1, "10.0.0.0/31")] + [DataRow("10.0.0.0/32", 2, "10.0.0.0/31, 10.0.0.2/32")] [DataRow("10.0.0.0/32", 3, "10.0.0.0/30")] + [DataRow("10.0.0.0/32", 4, "10.0.0.0/30, 10.0.0.4/32")] [DataRow("10.0.0.0/32", 7, "10.0.0.0/29")] [DataRow("10.0.0.0/32", 15, "10.0.0.0/28")] [DataRow("10.0.0.0/32", 31, "10.0.0.0/27")] @@ -350,7 +352,6 @@ public void TestOperatorAdd(string left, string right, int count) [DataRow("10.0.0.0/32", 4095, "10.0.0.0/20")] [DataRow("10.0.0.0/32", -1, "")] [DataRow("10.0.0.0/32", -10, "")] - [DataRow("10.0.0.0/32", 2, "10.0.0.0/31, 10.0.0.2/32")] [DataRow("::/128", 1, "::/127")] [DataRow("::/128", 3, "::/126")] [DataRow("::f/128", 1, "::f/128, ::10/128")] @@ -358,15 +359,27 @@ public void TestOperatorAdd(string left, string right, int count) public void TestOperatorAdd2(string left, int right, string expected) { var ipn1 = IPNetwork2.Parse(left); - var result = ipn1 + right; + IEnumerable result = ipn1 + right; string sresult = string.Join(", ", result); Assert.AreEqual(expected, sresult); } - - + + /// + /// Tests Operator functionality with Operator +. + /// + [TestMethod] + [DataRow("0.0.0.0/0", 1, "0.0.0.0/32")] + [DataRow("255.255.255.255/32", 1, "10.0.0.0/31")] + public void TestOperatorAddOverflow(string left, int right, string expected) + { + var ipn1 = IPNetwork2.Parse(left); + Assert.ThrowsExactly(() => ipn1 + right); + } + + /// - /// Tests Operator functionality with Operator -. + /// Tests Operator functionality with Operator -. /// [TestMethod] [DataRow("10.0.0.0/32", 1, "")] @@ -385,7 +398,7 @@ public void TestOperatorAdd2(string left, int right, string expected) public void TestOperatorsubtract2(string left, int right, string expected) { var ipn1 = IPNetwork2.Parse(left); - var result = ipn1 - right; + IEnumerable result = ipn1 - right; string sresult = string.Join(", ", result); Assert.AreEqual(expected, sresult); From ba0929df1f802324992074229cec7712e41d9c95 Mon Sep 17 00:00:00 2001 From: lduchosal Date: Sun, 17 Aug 2025 21:49:29 +0200 Subject: [PATCH 4/5] Fix edge cases --- .../IPNetwork2Operators.cs | 31 ++++++++++++++++++- .../IPNetworkTest/IPNetworkOperatorTests.cs | 19 +++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/System.Net.IPNetwork/IPNetwork2Operators.cs b/src/System.Net.IPNetwork/IPNetwork2Operators.cs index f4582a9..47e3723 100644 --- a/src/System.Net.IPNetwork/IPNetwork2Operators.cs +++ b/src/System.Net.IPNetwork/IPNetwork2Operators.cs @@ -117,6 +117,14 @@ public sealed partial class IPNetwork2 /// Adds the specified number of IP addresses to the network range. public static IEnumerable operator +(IPNetwork2 left, int add) { + if (add < 0) + { + return left - (add * -1); + } + if (add == 0) + { + return new[] { left }; + } var start = ToBigInteger(left.First); var last = ToBigInteger(left.Last); var end = last+ add; @@ -148,6 +156,27 @@ public sealed partial class IPNetwork2 /// Try to supernet two consecutive cidr equal subnet into a single one, otherwise return both netowkrs. public static IEnumerable operator -(IPNetwork2 left, int subtract) { - return left + (subtract * -1); + if (subtract < 0) + { + return left + (subtract * -1); + } + if (subtract == 0) + { + return new[] { left }; + } + var start = ToBigInteger(left.First); + var last = ToBigInteger(left.Last); + var end = last- subtract; + + if (end < start) + { + return []; + } + + var startIp = ToIPAddress(start, left.AddressFamily); + var endIp = ToIPAddress(end, left.AddressFamily); + + InternalParseRange(true, startIp, endIp, out IEnumerable networks); + return networks; } } \ No newline at end of file diff --git a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs index 6bc8dcd..1d4279d 100644 --- a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs +++ b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs @@ -336,6 +336,7 @@ public void TestOperatorAdd(string left, string right, int count) /// Tests Operator functionality with Operator +. /// [TestMethod] + [DataRow("10.0.0.0/32", 0, "10.0.0.0/32")] [DataRow("10.0.0.0/32", 1, "10.0.0.0/31")] [DataRow("10.0.0.0/32", 2, "10.0.0.0/31, 10.0.0.2/32")] [DataRow("10.0.0.0/32", 3, "10.0.0.0/30")] @@ -377,12 +378,12 @@ public void TestOperatorAddOverflow(string left, int right, string expected) Assert.ThrowsExactly(() => ipn1 + right); } - /// /// Tests Operator functionality with Operator -. /// [TestMethod] [DataRow("10.0.0.0/32", 1, "")] + [DataRow("255.255.255.255/32", 1, "")] [DataRow("10.0.0.0/32", 3, "")] [DataRow("10.0.0.0/32", 7, "")] [DataRow("10.0.0.0/32", 15, "")] @@ -403,4 +404,20 @@ public void TestOperatorsubtract2(string left, int right, string expected) Assert.AreEqual(expected, sresult); } + + /// + /// Tests Operator functionality with Operator -. + /// + [TestMethod] + [DataRow("0.0.0.0/0", -1)] + [DataRow("255.255.255.255/32", -1)] + public void TestOperatorsubtractOverflow(string left, int right) + { + var ipn1 = IPNetwork2.Parse(left); + Assert.ThrowsExactly(() => + { + var result = ipn1 - right; + Assert.AreEqual("32", result.ToString()); + }); + } } \ No newline at end of file From c295f6f2fb3169a1bbe3d0a92f0300c93d181918 Mon Sep 17 00:00:00 2001 From: lduchosal Date: Sun, 17 Aug 2025 22:15:12 +0200 Subject: [PATCH 5/5] Fix: ordering --- src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs index 1d4279d..7eb02cd 100644 --- a/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs +++ b/src/TestProject/IPNetworkTest/IPNetworkOperatorTests.cs @@ -382,8 +382,8 @@ public void TestOperatorAddOverflow(string left, int right, string expected) /// Tests Operator functionality with Operator -. /// [TestMethod] + [DataRow("10.0.0.0/32", 0, "10.0.0.0/32")] [DataRow("10.0.0.0/32", 1, "")] - [DataRow("255.255.255.255/32", 1, "")] [DataRow("10.0.0.0/32", 3, "")] [DataRow("10.0.0.0/32", 7, "")] [DataRow("10.0.0.0/32", 15, "")] @@ -392,6 +392,7 @@ public void TestOperatorAddOverflow(string left, int right, string expected) [DataRow("10.0.0.0/32", 4095, "")] [DataRow("10.0.0.0/32", -1, "10.0.0.0/31")] [DataRow("10.0.0.0/32", -10, "10.0.0.0/29, 10.0.0.8/31, 10.0.0.10/32")] + [DataRow("255.255.255.255/32", 1, "")] [DataRow("::/128", 1, "")] [DataRow("::/127", 1, "::/128")] [DataRow("::/128", 3, "")]