diff --git a/IPAddressRange.Test/IPAddressRangeTest.cs b/IPAddressRange.Test/IPAddressRangeTest.cs index 405b32c..f72fb7e 100644 --- a/IPAddressRange.Test/IPAddressRangeTest.cs +++ b/IPAddressRange.Test/IPAddressRangeTest.cs @@ -11,13 +11,6 @@ public class IPAddressRangeTest { public TestContext TestContext { get; set; } - - public void MyMethod() - { - IEnumerable x = IPAddressRange.Parse("127.0.0.1/24"); - var y = x.GetEnumerator(); - } - [TestMethod] public void CtorTest_Empty() { @@ -487,4 +480,18 @@ public void Equals_WithNull_ReturnsFalse() var range2 = default(IPAddressRange); range1.Equals(range2).IsFalse(); } + + [TestMethod] + public void Count_IPv4_Test() + { + var ipAddressRange = IPAddressRange.Parse("10.0.0.0/8"); + ipAddressRange.AsEnumerable().Count().Is(16777216); + } + + [TestMethod] + public void Count_IPv6_Test() + { + var ipAddressRange = IPAddressRange.Parse("fe80::0000:0000-fe80::0100:0001"); + ipAddressRange.AsEnumerable().Count().Is(16777218); + } } diff --git a/IPAddressRange.sln b/IPAddressRange.sln index 06cf7f2..e4c9a6d 100644 --- a/IPAddressRange.sln +++ b/IPAddressRange.sln @@ -12,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "README", "README", "{237703 RELEASE-NOTES.txt = RELEASE-NOTES.txt EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPAddressRange.Test", "IPAddressRange.Test\IPAddressRange.Test.csproj", "{9A01AF46-6AE0-425A-BD1E-6575122C6BAB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IPAddressRange.Test", "IPAddressRange.Test\IPAddressRange.Test.csproj", "{9A01AF46-6AE0-425A-BD1E-6575122C6BAB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/IPAddressRange/IPAddressRange.cs b/IPAddressRange/IPAddressRange.cs index bcbc0d5..1b91e02 100644 --- a/IPAddressRange/IPAddressRange.cs +++ b/IPAddressRange/IPAddressRange.cs @@ -5,6 +5,7 @@ using System.Net; using System.Text.RegularExpressions; using System.ComponentModel; +using NetTools.Internals; #if NET45 using System.Runtime.Serialization; @@ -78,6 +79,8 @@ public class IPAddressRange : IEnumerable, IReadOnlyDictionary /// Creates an empty range object, equivalent to "0.0.0.0/0". /// @@ -93,6 +96,7 @@ public IPAddressRange(IPAddress singleAddress) throw new ArgumentNullException(nameof(singleAddress)); Begin = End = singleAddress; + Operator = RangeOperatorFactory.Create(this); } /// @@ -116,6 +120,8 @@ public IPAddressRange(IPAddress begin, IPAddress end) if (Begin.AddressFamily != End.AddressFamily) throw new ArgumentException("Elements must be of the same address family", nameof(end)); if (!Bits.GtECore(endBytes, beginBytes)) throw new ArgumentException("Begin must be smaller than the End", nameof(begin)); + + Operator = RangeOperatorFactory.Create(this); } /// @@ -145,6 +151,7 @@ public IPAddressRange(string ipRangeString) var parsed = Parse(ipRangeString); Begin = parsed.Begin; End = parsed.End; + Operator = RangeOperatorFactory.Create(this); } #if NET45 @@ -159,6 +166,7 @@ protected IPAddressRange(SerializationInfo info, StreamingContext context) this.Begin = deserialize("Begin"); this.End = deserialize("End"); + Operator = RangeOperatorFactory.Create(this); } public virtual void GetObjectData(SerializationInfo info, StreamingContext context) @@ -177,14 +185,7 @@ public bool Contains(IPAddress ipaddress) if (ipaddress.AddressFamily != this.Begin.AddressFamily) return false; - var offset = 0; - if (Begin.IsIPv4MappedToIPv6 && ipaddress.IsIPv4MappedToIPv6) - { - offset = 12; //ipv4 has prefix of 10 zero bytes and two 255 bytes. - } - - var adrBytes = ipaddress.GetAddressBytes(); - return Bits.LtECore(this.Begin.GetAddressBytes(), adrBytes, offset) && Bits.GtECore(this.End.GetAddressBytes(), adrBytes, offset); + return Operator.Contains(ipaddress); } public bool Contains(IPAddressRange range) @@ -194,15 +195,7 @@ public bool Contains(IPAddressRange range) if (this.Begin.AddressFamily != range.Begin.AddressFamily) return false; - var offset = 0; - if (Begin.IsIPv4MappedToIPv6 && range.Begin.IsIPv4MappedToIPv6) - { - offset = 12; //ipv4 has prefix of 10 zero bytes and two 255 bytes. - } - - return - Bits.LtECore(this.Begin.GetAddressBytes(), range.Begin.GetAddressBytes(), offset) && - Bits.GtECore(this.End.GetAddressBytes(), range.End.GetAddressBytes(), offset); + return Operator.Contains(range); } public static IPAddressRange Parse(string ipRangeString) @@ -324,10 +317,7 @@ public static int SubnetMaskLength(IPAddress subnetMask) public IEnumerator GetEnumerator() { - var first = Begin.GetAddressBytes(); - var last = End.GetAddressBytes(); - for (var ip = first; Bits.LtECore(ip, last); ip = Bits.Increment(ip)) - yield return new IPAddress(ip); + return Operator.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -414,7 +404,7 @@ public IPAddressRange(IEnumerable> items) /// /// Returns the input typed as IEnumerable<IPAddress> /// - public IEnumerable AsEnumerable() => (this as IEnumerable); + public IEnumerable AsEnumerable() => Operator; private IEnumerable> GetDictionaryItems() { diff --git a/IPAddressRange/IPAddressRange.csproj b/IPAddressRange/IPAddressRange.csproj index 57a8b75..70cdc37 100644 --- a/IPAddressRange/IPAddressRange.csproj +++ b/IPAddressRange/IPAddressRange.csproj @@ -15,8 +15,8 @@ 1.6.0 $(PackageTargetFallback);dnxcore50 IPAddressRange.nuspec - Copyright © 2012-2019 J.Sakamoto, Mozilla Public License 2.0 - 4.0.0.0 + Copyright © 2012-2020 J.Sakamoto, Mozilla Public License 2.0 + 4.1.0.0-preview1 J.Sakamoto IPAddressRange @@ -48,7 +48,15 @@ IPAddressRange.Signed.nuspec [Superseded] IPAddressRange.Signed - [Superseded] This package became only metadata package for installing "IPAddressRange" NuGet package, because "IPAddressRange" NuGet package became strong-named assembly. Please consider to installing "IPAddress" NuGet package directly instead of this package. ---------------------------- $(Description) + + [Superseded] This package became only metadata package for installing "IPAddressRange" NuGet package, because "IPAddressRange" NuGet package became strong-named assembly. + + Please consider to installing "IPAddress" NuGet package directly instead of this package. + + ---------------------------- + + $(Description) + title=$(TitleForSignedEdition);$(SharedNuspecProperties) description=$(DescriptionForSignedEdition);$(NuspecProperties) @@ -66,6 +74,7 @@ @(ReleaseNoteLines, '%0a') $([System.Text.RegularExpressions.Regex]::Match($(PackageReleaseNotes), "^(v\.[\d\.]+.+?)v\.[\d\.]+", System.Text.RegularExpressions.RegexOptions.Singleline).Groups[1].Value) + releaseNotes=$(PackageReleaseNotes);$(NuspecProperties) releaseNotes=$(PackageReleaseNotes);$(SharedNuspecProperties) diff --git a/IPAddressRange/Internals/IPAddressExtensions.cs b/IPAddressRange/Internals/IPAddressExtensions.cs new file mode 100644 index 0000000..050ffb2 --- /dev/null +++ b/IPAddressRange/Internals/IPAddressExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Net; +using System.Numerics; + +namespace NetTools.Internals +{ + internal static class IPAddressExtensions + { + public static UInt32 ToUInt32(this IPAddress ipAddress) + { + var addressBytes = ipAddress.GetAddressBytes(); + Array.Reverse(addressBytes); + return BitConverter.ToUInt32(addressBytes, 0); + } + + public static BigInteger ToBigInteger(this IPAddress ipAddress) + { + var addressBytes = ipAddress.GetAddressBytes(); + Array.Reverse(addressBytes); + return new BigInteger(addressBytes); + } + + public static IPAddress ToIPv4Address(this UInt32 value) + { + var addressBytes = BitConverter.GetBytes(value); + Array.Reverse(addressBytes); + return new IPAddress(addressBytes); + } + + public static IPAddress ToIPv6Address(ref this BigInteger value) + { + var addressBytes = value.ToByteArray(); + Array.Reverse(addressBytes); + return new IPAddress(addressBytes); + } + } +} diff --git a/IPAddressRange/Internals/IPv4RangeOperator.cs b/IPAddressRange/Internals/IPv4RangeOperator.cs new file mode 100644 index 0000000..ef06552 --- /dev/null +++ b/IPAddressRange/Internals/IPv4RangeOperator.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; + +namespace NetTools.Internals +{ + internal class IPv4RangeOperator : IRangeOperator + { + private UInt32 Begin { get; } + + private UInt32 End { get; } + + public IPv4RangeOperator(IPAddressRange range) + { + Begin = range.Begin.ToUInt32(); + End = range.End.ToUInt32(); + } + + public bool Contains(IPAddress ipaddress) + { + var address = ipaddress.ToUInt32(); + return Begin <= address && address <= End; + } + + public bool Contains(IPAddressRange range) + { + var rangeBegin = range.Begin.ToUInt32(); + var rangeEnd = range.End.ToUInt32(); + return Begin <= rangeBegin && rangeEnd <= End; + } + + public IEnumerator GetEnumerator() + { + for (UInt32 adr = Begin; adr <= End; adr++) + { + yield return adr.ToIPv4Address(); + } + } + + int ICollection.Count => (int)((End - Begin) + 1); + + bool ICollection.IsReadOnly => true; + + void ICollection.Add(IPAddress item) => throw new InvalidOperationException(); + + void ICollection.Clear() => throw new InvalidOperationException(); + + bool ICollection.Contains(IPAddress item) + { + return this.Contains(item); + } + + void ICollection.CopyTo(IPAddress[] array, int arrayIndex) + { + if ((array.Length - arrayIndex) < (this as ICollection).Count) throw new ArgumentException(); + + foreach (var ipAddress in this) + { + array[arrayIndex++] = ipAddress; + } + } + + bool ICollection.Remove(IPAddress item) => throw new InvalidOperationException(); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } +} diff --git a/IPAddressRange/Internals/IPv6RangeOperator.cs b/IPAddressRange/Internals/IPv6RangeOperator.cs new file mode 100644 index 0000000..9b99614 --- /dev/null +++ b/IPAddressRange/Internals/IPv6RangeOperator.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Numerics; + +namespace NetTools.Internals +{ + internal class IPv6RangeOperator : IRangeOperator + { + private BigInteger Begin { get; } + + private BigInteger End { get; } + + public IPv6RangeOperator(IPAddressRange range) + { + Begin = range.Begin.ToBigInteger(); + End = range.End.ToBigInteger(); + } + + public bool Contains(IPAddress ipaddress) + { + var address = ipaddress.ToBigInteger(); + return Begin <= address && address <= End; + } + + public bool Contains(IPAddressRange range) + { + var rangeBegin = range.Begin.ToBigInteger(); + var rangeEnd = range.End.ToBigInteger(); + return Begin <= rangeBegin && rangeEnd <= End; + } + + public IEnumerator GetEnumerator() + { + for (BigInteger adr = Begin; adr <= End; adr++) + { + yield return adr.ToIPv6Address(); + } + } + + int ICollection.Count => (int)((End - Begin) + 1); + + bool ICollection.IsReadOnly => true; + + void ICollection.Add(IPAddress item) => throw new InvalidOperationException(); + + void ICollection.Clear() => throw new InvalidOperationException(); + + bool ICollection.Contains(IPAddress item) + { + return this.Contains(item); + } + + void ICollection.CopyTo(IPAddress[] array, int arrayIndex) + { + if ((array.Length - arrayIndex) < (this as ICollection).Count) throw new ArgumentException(); + + foreach (var ipAddress in this) + { + array[arrayIndex++] = ipAddress; + } + } + + bool ICollection.Remove(IPAddress item) => throw new InvalidOperationException(); + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); + } +} diff --git a/IPAddressRange/Internals/IRangeOperator.cs b/IPAddressRange/Internals/IRangeOperator.cs new file mode 100644 index 0000000..562078b --- /dev/null +++ b/IPAddressRange/Internals/IRangeOperator.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Net; + +namespace NetTools.Internals +{ + internal interface IRangeOperator : ICollection + { + bool Contains(IPAddressRange range); + } +} diff --git a/IPAddressRange/Internals/RangeOperatorFactory.cs b/IPAddressRange/Internals/RangeOperatorFactory.cs new file mode 100644 index 0000000..142b046 --- /dev/null +++ b/IPAddressRange/Internals/RangeOperatorFactory.cs @@ -0,0 +1,14 @@ +using System.Net.Sockets; + +namespace NetTools.Internals +{ + internal static class RangeOperatorFactory + { + public static IRangeOperator Create(IPAddressRange range) + { + return range.Begin.AddressFamily == AddressFamily.InterNetwork ? + new IPv4RangeOperator(range) : + new IPv6RangeOperator(range) as IRangeOperator; + } + } +} diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 59f82fe..fe499df 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,3 +1,6 @@ +v.4.1.0-Preview1 +- Improved operation performance for IPv4 address range. + v.4.0.0 - Implement "IEquatable" interface and "GetHashCode()".