public static bool TryParse(System.ReadOnlySpan<char> s, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPEndPoint? result) { throw null; }
public static bool TryParse(string s, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPEndPoint? result) { throw null; }
}
+ public readonly partial struct IPNetwork : System.IEquatable<System.Net.IPNetwork>, System.IFormattable, System.IParsable<System.Net.IPNetwork>, System.ISpanFormattable, System.ISpanParsable<System.Net.IPNetwork>
+ {
+ private readonly object _dummy;
+ private readonly int _dummyPrimitive;
+ public IPNetwork(System.Net.IPAddress baseAddress, int prefixLength) { throw null; }
+ public System.Net.IPAddress BaseAddress { get { throw null; } }
+ public int PrefixLength { get { throw null; } }
+ public bool Contains(System.Net.IPAddress address) { throw null; }
+ public bool Equals(System.Net.IPNetwork other) { throw null; }
+ public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; }
+ public override int GetHashCode() { throw null; }
+ public static bool operator ==(System.Net.IPNetwork left, System.Net.IPNetwork right) { throw null; }
+ public static bool operator !=(System.Net.IPNetwork left, System.Net.IPNetwork right) { throw null; }
+ public static System.Net.IPNetwork Parse(System.ReadOnlySpan<char> s) { throw null; }
+ public static System.Net.IPNetwork Parse(string s) { throw null; }
+ string System.IFormattable.ToString(string? format, System.IFormatProvider? provider) { throw null; }
+ static System.Net.IPNetwork System.IParsable<System.Net.IPNetwork>.Parse([System.Diagnostics.CodeAnalysis.NotNullAttribute] string s, System.IFormatProvider? provider) { throw null; }
+ static bool System.IParsable<System.Net.IPNetwork>.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Net.IPNetwork result) { throw null; }
+ bool System.ISpanFormattable.TryFormat(System.Span<char> destination, out int charsWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
+ static System.Net.IPNetwork System.ISpanParsable<System.Net.IPNetwork>.Parse(System.ReadOnlySpan<char> s, System.IFormatProvider? provider) { throw null; }
+ static bool System.ISpanParsable<System.Net.IPNetwork>.TryParse(System.ReadOnlySpan<char> s, System.IFormatProvider? provider, out System.Net.IPNetwork result) { throw null; }
+ public override string ToString() { throw null; }
+ public bool TryFormat(System.Span<char> destination, out int charsWritten) { throw null; }
+ public static bool TryParse(System.ReadOnlySpan<char> s, out System.Net.IPNetwork result) { throw null; }
+ public static bool TryParse(string? s, out System.Net.IPNetwork result) { throw null; }
+ }
public partial interface IWebProxy
{
System.Net.ICredentials? Credentials { get; set; }
<data name="dns_bad_ip_address" xml:space="preserve">
<value>An invalid IP address was specified.</value>
</data>
+ <data name="net_bad_ip_network" xml:space="preserve">
+ <value>An invalid IP network was specified.</value>
+ </data>
+ <data name="net_bad_ip_network_invalid_baseaddress" xml:space="preserve">
+ <value>The specified baseAddress has non-zero bits after the network prefix.</value>
+ </data>
<data name="net_container_add_cookie" xml:space="preserve">
<value>An error occurred when adding a cookie to the container.</value>
</data>
<Compile Include="System\Net\ICredentials.cs" />
<Compile Include="System\Net\ICredentialsByHost.cs" />
<Compile Include="System\Net\IPAddress.cs" />
+ <Compile Include="System\Net\IPNetwork.cs" />
<Compile Include="System\Net\IPAddressParser.cs" />
<Compile Include="System\Net\IPEndPoint.cs" />
<Compile Include="$(CommonPath)System\Net\IPv4AddressHelper.Common.cs"
get { return _numbers != null; }
}
- private uint PrivateAddress
+ internal uint PrivateAddress
{
get
{
Debug.Assert(IsIPv4);
return _addressOrScopeId;
}
- set
+ private set
{
Debug.Assert(IsIPv4);
_toString = null;
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+
+#pragma warning disable SA1648 // TODO: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3595
+
+namespace System.Net
+{
+ /// <summary>
+ /// Represents an IP network with an <see cref="IPAddress"/> containing the network prefix and an <see cref="int"/> defining the prefix length.
+ /// </summary>
+ /// <remarks>
+ /// This type disallows arbitrary IP-address/prefix-length CIDR pairs. <see cref="BaseAddress"/> must be defined so that all bits after the network prefix are set to zero.
+ /// In other words, <see cref="BaseAddress"/> is always the first usable address of the network.
+ /// The constructor and the parsing methods will throw in case there are non-zero bits after the prefix.
+ /// </remarks>
+ public readonly struct IPNetwork : IEquatable<IPNetwork>, ISpanFormattable, ISpanParsable<IPNetwork>
+ {
+ private readonly IPAddress? _baseAddress;
+
+ /// <summary>
+ /// Gets the <see cref="IPAddress"/> that represents the prefix of the network.
+ /// </summary>
+ public IPAddress BaseAddress => _baseAddress ?? IPAddress.Any;
+
+ /// <summary>
+ /// Gets the length of the network prefix in bits.
+ /// </summary>
+ public int PrefixLength { get; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="IPNetwork"/> class with the specified <see cref="IPAddress"/> and prefix length.
+ /// </summary>
+ /// <param name="baseAddress">The <see cref="IPAddress"/> that represents the prefix of the network.</param>
+ /// <param name="prefixLength">The length of the prefix in bits.</param>
+ /// <exception cref="ArgumentNullException">The specified <paramref name="baseAddress"/> is <see langword="null"/>.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The specified <paramref name="prefixLength"/> is smaller than `0` or longer than maximum length of <paramref name="prefixLength"/>'s <see cref="AddressFamily"/>.</exception>
+ /// <exception cref="ArgumentException">The specified <paramref name="baseAddress"/> has non-zero bits after the network prefix.</exception>
+ public IPNetwork(IPAddress baseAddress, int prefixLength)
+ {
+ ArgumentNullException.ThrowIfNull(baseAddress);
+
+ if (prefixLength < 0 || prefixLength > GetMaxPrefixLength(baseAddress))
+ {
+ ThrowArgumentOutOfRangeException();
+ }
+
+ if (HasNonZeroBitsAfterNetworkPrefix(baseAddress, prefixLength))
+ {
+ ThrowInvalidBaseAddressException();
+ }
+
+ _baseAddress = baseAddress;
+ PrefixLength = prefixLength;
+
+ [DoesNotReturn]
+ static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException(nameof(prefixLength));
+
+ [DoesNotReturn]
+ static void ThrowInvalidBaseAddressException() => throw new ArgumentException(SR.net_bad_ip_network_invalid_baseaddress, nameof(baseAddress));
+ }
+
+ // Non-validating ctor
+ private IPNetwork(IPAddress baseAddress, int prefixLength, bool _)
+ {
+ _baseAddress = baseAddress;
+ PrefixLength = prefixLength;
+ }
+
+ /// <summary>
+ /// Determines whether a given <see cref="IPAddress"/> is part of the network.
+ /// </summary>
+ /// <param name="address">The <see cref="IPAddress"/> to check.</param>
+ /// <returns><see langword="true"/> if the <see cref="IPAddress"/> is part of the network; otherwise, <see langword="false"/>.</returns>
+ /// <exception cref="ArgumentNullException">The specified <paramref name="address"/> is <see langword="null"/>.</exception>
+ public bool Contains(IPAddress address)
+ {
+ ArgumentNullException.ThrowIfNull(address);
+
+ if (address.AddressFamily != BaseAddress.AddressFamily)
+ {
+ return false;
+ }
+
+ // This prevents the 'uint.MaxValue << 32' and the 'UInt128.MaxValue << 128' special cases in the code below.
+ if (PrefixLength == 0)
+ {
+ return true;
+ }
+
+ if (address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ uint mask = uint.MaxValue << (32 - PrefixLength);
+ if (BitConverter.IsLittleEndian)
+ {
+ mask = BinaryPrimitives.ReverseEndianness(mask);
+ }
+
+ return BaseAddress.PrivateAddress == (address.PrivateAddress & mask);
+ }
+ else
+ {
+ UInt128 baseAddressValue = default;
+ UInt128 otherAddressValue = default;
+
+ BaseAddress.TryWriteBytes(MemoryMarshal.AsBytes(new Span<UInt128>(ref baseAddressValue)), out int bytesWritten);
+ Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes);
+ address.TryWriteBytes(MemoryMarshal.AsBytes(new Span<UInt128>(ref otherAddressValue)), out bytesWritten);
+ Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes);
+
+ UInt128 mask = UInt128.MaxValue << (128 - PrefixLength);
+ if (BitConverter.IsLittleEndian)
+ {
+ mask = BinaryPrimitives.ReverseEndianness(mask);
+ }
+
+ return baseAddressValue == (otherAddressValue & mask);
+ }
+ }
+
+ /// <summary>
+ /// Converts a CIDR <see cref="string"/> to an <see cref="IPNetwork"/> instance.
+ /// </summary>
+ /// <param name="s">A <see cref="string"/> that defines an IP network in CIDR notation.</param>
+ /// <returns>An <see cref="IPNetwork"/> instance.</returns>
+ /// <exception cref="ArgumentNullException">The specified string is <see langword="null"/>.</exception>
+ /// <exception cref="FormatException"><paramref name="s"/> is not a valid CIDR network string, or the address contains non-zero bits after the network prefix.</exception>
+ public static IPNetwork Parse(string s)
+ {
+ ArgumentNullException.ThrowIfNull(s);
+ return Parse(s.AsSpan());
+ }
+
+ /// <summary>
+ /// Converts a CIDR character span to an <see cref="IPNetwork"/> instance.
+ /// </summary>
+ /// <param name="s">A character span that defines an IP network in CIDR notation.</param>
+ /// <returns>An <see cref="IPNetwork"/> instance.</returns>
+ /// <exception cref="FormatException"><paramref name="s"/> is not a valid CIDR network string, or the address contains non-zero bits after the network prefix.</exception>
+ public static IPNetwork Parse(ReadOnlySpan<char> s)
+ {
+ if (!TryParse(s, out IPNetwork result))
+ {
+ throw new FormatException(SR.net_bad_ip_network);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Converts the specified CIDR string to an <see cref="IPNetwork"/> instance and returns a value indicating whether the conversion succeeded.
+ /// </summary>
+ /// <param name="s">A <see cref="string"/> that defines an IP network in CIDR notation.</param>
+ /// <param name="result">When the method returns, contains an <see cref="IPNetwork"/> instance if the conversion succeeds.</param>
+ /// <returns><see langword="true"/> if the conversion was succesful; otherwise, <see langword="false"/>.</returns>
+ public static bool TryParse(string? s, out IPNetwork result)
+ {
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+
+ return TryParse(s.AsSpan(), out result);
+ }
+
+ /// <summary>
+ /// Converts the specified CIDR character span to an <see cref="IPNetwork"/> instance and returns a value indicating whether the conversion succeeded.
+ /// </summary>
+ /// <param name="s">A <see cref="string"/> that defines an IP network in CIDR notation.</param>
+ /// <param name="result">When the method returns, contains an <see cref="IPNetwork"/> instance if the conversion succeeds.</param>
+ /// <returns><see langword="true"/> if the conversion was succesful; otherwise, <see langword="false"/>.</returns>
+ public static bool TryParse(ReadOnlySpan<char> s, out IPNetwork result)
+ {
+ int separatorIndex = s.LastIndexOf('/');
+ if (separatorIndex >= 0)
+ {
+ ReadOnlySpan<char> ipAddressSpan = s.Slice(0, separatorIndex);
+ ReadOnlySpan<char> prefixLengthSpan = s.Slice(separatorIndex + 1);
+
+ if (IPAddress.TryParse(ipAddressSpan, out IPAddress? address) &&
+ int.TryParse(prefixLengthSpan, NumberStyles.None, CultureInfo.InvariantCulture, out int prefixLength) &&
+ prefixLength <= GetMaxPrefixLength(address) &&
+ !HasNonZeroBitsAfterNetworkPrefix(address, prefixLength))
+ {
+ Debug.Assert(prefixLength >= 0); // Parsing with NumberStyles.None should ensure that prefixLength is always non-negative.
+ result = new IPNetwork(address, prefixLength, false);
+ return true;
+ }
+ }
+
+ result = default;
+ return false;
+ }
+
+ private static int GetMaxPrefixLength(IPAddress baseAddress) => baseAddress.AddressFamily == AddressFamily.InterNetwork ? 32 : 128;
+
+ private static bool HasNonZeroBitsAfterNetworkPrefix(IPAddress baseAddress, int prefixLength)
+ {
+ if (baseAddress.AddressFamily == AddressFamily.InterNetwork)
+ {
+ // The cast to long ensures that the mask becomes 0 for the case where 'prefixLength == 0'.
+ uint mask = (uint)((long)uint.MaxValue << (32 - prefixLength));
+ if (BitConverter.IsLittleEndian)
+ {
+ mask = BinaryPrimitives.ReverseEndianness(mask);
+ }
+
+ return (baseAddress.PrivateAddress & mask) != baseAddress.PrivateAddress;
+ }
+ else
+ {
+ UInt128 value = default;
+ baseAddress.TryWriteBytes(MemoryMarshal.AsBytes(new Span<UInt128>(ref value)), out int bytesWritten);
+ Debug.Assert(bytesWritten == IPAddressParserStatics.IPv6AddressBytes);
+ if (prefixLength == 0)
+ {
+ return value != UInt128.Zero;
+ }
+
+ UInt128 mask = UInt128.MaxValue << (128 - prefixLength);
+ if (BitConverter.IsLittleEndian)
+ {
+ mask = BinaryPrimitives.ReverseEndianness(mask);
+ }
+
+ return (value & mask) != value;
+ }
+ }
+
+ /// <summary>
+ /// Converts the instance to a string containing the <see cref="IPNetwork"/>'s CIDR notation.
+ /// </summary>
+ /// <returns>The <see cref="string"/> containing the <see cref="IPNetwork"/>'s CIDR notation.</returns>
+ public override string ToString() =>
+ string.Create(CultureInfo.InvariantCulture, stackalloc char[128], $"{BaseAddress}/{(uint)PrefixLength}");
+
+ /// <summary>
+ /// Attempts to write the <see cref="IPNetwork"/>'s CIDR notation to the given <paramref name="destination"/> span and returns a value indicating whether the operation succeeded.
+ /// </summary>
+ /// <param name="destination">The destination span of characters.</param>
+ /// <param name="charsWritten">When this method returns, contains the number of characters that were written to <paramref name="destination"/>.</param>
+ /// <returns><see langword="true"/> if the formatting was succesful; otherwise <see langword="false"/>.</returns>
+ public bool TryFormat(Span<char> destination, out int charsWritten) =>
+ destination.TryWrite(CultureInfo.InvariantCulture, $"{BaseAddress}/{(uint)PrefixLength}", out charsWritten);
+
+ /// <summary>
+ /// Determines whether two <see cref="IPNetwork"/> instances are equal.
+ /// </summary>
+ /// <param name="other">The <see cref="IPNetwork"/> instance to compare to this instance.</param>
+ /// <returns><see langword="true"/> if the networks are equal; otherwise <see langword="false"/>.</returns>
+ /// <exception cref="InvalidOperationException">Uninitialized <see cref="IPNetwork"/> instance.</exception>
+ public bool Equals(IPNetwork other) =>
+ PrefixLength == other.PrefixLength &&
+ BaseAddress.Equals(other.BaseAddress);
+
+ /// <summary>
+ /// Determines whether two <see cref="IPNetwork"/> instances are equal.
+ /// </summary>
+ /// <param name="obj">The <see cref="IPNetwork"/> instance to compare to this instance.</param>
+ /// <returns><see langword="true"/> if <paramref name="obj"/> is an <see cref="IPNetwork"/> instance and the networks are equal; otherwise <see langword="false"/>.</returns>
+ /// <exception cref="InvalidOperationException">Uninitialized <see cref="IPNetwork"/> instance.</exception>
+ public override bool Equals([NotNullWhen(true)] object? obj) =>
+ obj is IPNetwork other &&
+ Equals(other);
+
+ /// <summary>
+ /// Determines whether the specified instances of <see cref="IPNetwork"/> are equal.
+ /// </summary>
+ /// <param name="left"></param>
+ /// <param name="right"></param>
+ /// <returns><see langword="true"/> if the networks are equal; otherwise <see langword="false"/>.</returns>
+ public static bool operator ==(IPNetwork left, IPNetwork right) => left.Equals(right);
+
+ /// <summary>
+ /// Determines whether the specified instances of <see cref="IPNetwork"/> are not equal.
+ /// </summary>
+ /// <param name="left"></param>
+ /// <param name="right"></param>
+ /// <returns><see langword="true"/> if the networks are not equal; otherwise <see langword="false"/>.</returns>
+ public static bool operator !=(IPNetwork left, IPNetwork right) => !(left == right);
+
+ /// <summary>
+ /// Returns the hash code for this instance.
+ /// </summary>
+ /// <returns>An integer hash value.</returns>
+ public override int GetHashCode() => HashCode.Combine(BaseAddress, PrefixLength);
+
+ /// <inheritdoc />
+ string IFormattable.ToString(string? format, IFormatProvider? provider) => ToString();
+
+ /// <inheritdoc />
+ bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
+ TryFormat(destination, out charsWritten);
+
+ /// <inheritdoc />
+ static IPNetwork IParsable<IPNetwork>.Parse([NotNull] string s, IFormatProvider? provider) => Parse(s);
+
+ /// <inheritdoc />
+ static bool IParsable<IPNetwork>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, out IPNetwork result) => TryParse(s, out result);
+
+ /// <inheritdoc />
+ static IPNetwork ISpanParsable<IPNetwork>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => Parse(s);
+
+ /// <inheritdoc />
+ static bool ISpanParsable<IPNetwork>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out IPNetwork result) => TryParse(s, out result);
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Xunit;
+
+namespace System.Net.Primitives.Functional.Tests
+{
+ public class IPNetworkTest
+ {
+ public static TheoryData<string> IncorrectFormatData = new TheoryData<string>()
+ {
+ { "127.0.0.1" },
+ { "A.B.C.D/24" },
+ { "127.0.0.1/AB" },
+ { "127.0.0.1/-1" },
+ { "127.0.0.1/+1" },
+ { "2a01:110:8012::/f" },
+ { "" },
+ };
+
+ public static TheoryData<string> InvalidNetworkNotationData = new TheoryData<string>()
+ {
+ { "127.0.0.1/33" }, // PrefixLength max is 32 for IPv4
+ { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/129" }, // PrefixLength max is 128 for IPv6
+ { "127.0.0.1/31" }, // Bits exceed the prefix length of 31 (32nd bit is on)
+ { "198.51.255.0/23" }, // Bits exceed the prefix length of 23
+ { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/127" }, // Bits exceed the prefix length of 31
+ { "2a01:110:8012::/45" }, // Bits exceed the prefix length of 45 (47th bit is on)
+ };
+
+ public static TheoryData<string> ValidIPNetworkData = new TheoryData<string>()
+ {
+ { "0.0.0.0/32" }, // the whole IPv4 space
+ { "0.0.0.0/0" },
+ { "128.0.0.0/1" },
+ { "::/128" }, // the whole IPv6 space
+ { "255.255.255.255/32" },
+ { "198.51.254.0/23" },
+ { "42.42.128.0/17" },
+ { "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" },
+ { "2a01:110:8012::/47" },
+ { "2a01:110:8012::/100" },
+ };
+
+ [Theory]
+ [MemberData(nameof(ValidIPNetworkData))]
+ public void Constructor_Valid_Succeeds(string input)
+ {
+ string[] splitInput = input.Split('/');
+ IPAddress address = IPAddress.Parse(splitInput[0]);
+ int prefixLegth = int.Parse(splitInput[1]);
+
+ IPNetwork network = new IPNetwork(address, prefixLegth);
+
+ Assert.Equal(address, network.BaseAddress);
+ Assert.Equal(prefixLegth, network.PrefixLength);
+ }
+
+ [Fact]
+ public void Constructor_NullIPAddress_ThrowsArgumentNullException()
+ {
+ Assert.Throws<ArgumentNullException>(() => new IPNetwork(null, 1));
+ }
+
+ [Theory]
+ [InlineData("192.168.0.1", -1)]
+ [InlineData("192.168.0.1", 33)]
+ [InlineData("::", -1)]
+ [InlineData("ffff::", 129)]
+ public void Constructor_PrefixLenghtOutOfRange_ThrowsArgumentOutOfRangeException(string ipStr, int prefixLength)
+ {
+ IPAddress address = IPAddress.Parse(ipStr);
+ Assert.Throws<ArgumentOutOfRangeException>(() => new IPNetwork(address, prefixLength));
+ }
+
+ [Theory]
+ [InlineData("192.168.0.1", 31)]
+ [InlineData("42.42.192.0", 17)]
+ [InlineData("128.0.0.0", 0)]
+ [InlineData("2a01:110:8012::", 46)]
+ public void Constructor_NonZeroBitsAfterNetworkPrefix_ThrowsArgumentException(string ipStr, int prefixLength)
+ {
+ IPAddress address = IPAddress.Parse(ipStr);
+ Assert.Throws<ArgumentException>(() => new IPNetwork(address, prefixLength));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncorrectFormatData))]
+ public void Parse_IncorrectFormat_ThrowsFormatException(string input)
+ {
+ Assert.Throws<FormatException>(() => IPNetwork.Parse(input));
+ }
+
+ [Theory]
+ [MemberData(nameof(IncorrectFormatData))]
+ public void TryParse_IncorrectFormat_ReturnsFalse(string input)
+ {
+ Assert.False(IPNetwork.TryParse(input, out _));
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidNetworkNotationData))]
+ public void Parse_InvalidNetworkNotation_ThrowsFormatException(string input)
+ {
+ Assert.Throws<FormatException>(() => IPNetwork.Parse(input));
+ }
+
+ [Theory]
+ [MemberData(nameof(InvalidNetworkNotationData))]
+ public void TryParse_InvalidNetworkNotation_ReturnsFalse(string input)
+ {
+ Assert.False(IPNetwork.TryParse(input, out _));
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidIPNetworkData))]
+ public void Parse_ValidNetworkNotation_Succeeds(string input)
+ {
+ var network = IPNetwork.Parse(input);
+ Assert.Equal(input, network.ToString());
+ }
+
+ [Theory]
+ [MemberData(nameof(ValidIPNetworkData))]
+ public void TryParse_ValidNetworkNotation_Succeeds(string input)
+ {
+ Assert.True(IPNetwork.TryParse(input, out IPNetwork network));
+ Assert.Equal(input, network.ToString());
+ }
+
+ [Fact]
+ public void Contains_Null_ThrowsArgumentNullException()
+ {
+ IPNetwork v4 = IPNetwork.Parse("127.0.0.0/8");
+ IPNetwork v6 = IPNetwork.Parse("::1/128");
+
+ Assert.Throws<ArgumentNullException>(() => v4.Contains(null));
+ Assert.Throws<ArgumentNullException>(() => v6.Contains(null));
+ }
+
+ [Fact]
+ public void Contains_DifferentAddressFamily_ReturnsFalse()
+ {
+ IPNetwork network = IPNetwork.Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128");
+ Assert.False(network.Contains(IPAddress.Loopback));
+ }
+
+ [Theory]
+ [InlineData("0.0.0.0/0", "0.0.0.0", "127.127.127.127", "255.255.255.255")] // the whole IPv4 space
+ [InlineData("::/0", "::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] // the whole IPv6 space
+ [InlineData("255.255.255.255/32", "255.255.255.255")] // single IPv4 address
+ [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] // single IPv6 address
+ [InlineData("255.255.255.0/24", "255.255.255.0", "255.255.255.255")]
+ [InlineData("198.51.248.0/22", "198.51.248.0", "198.51.250.42", "198.51.251.255")]
+ [InlineData("255.255.255.128/25", "255.255.255.128", "255.255.255.129", "255.255.255.255")]
+ [InlineData("2a00::/13", "2a00::", "2a00::1", "2a01::", "2a07::", "2a07:ffff:ffff:ffff:ffff:ffff:ffff:ffff")]
+ [InlineData("2a01:110:8012::/47", "2a01:110:8012::", "2a01:110:8012:42::", "2a01:110:8013::", "2a01:110:8013:ffff:ffff:ffff:ffff:ffff")]
+ [InlineData("2a01:110:8012:1012:314f:2a00::/87", "2a01:110:8012:1012:314f:2a00::", "2a01:110:8012:1012:314f:2a00::1", "2a01:110:8012:1012:314f:2a00:abcd:4242", "2a01:110:8012:1012:314f:2bff:ffff:ffff")]
+ [InlineData("2a01:110:8012:1010:914e:2451:1700:0/105", "2a01:110:8012:1010:914e:2451:1700:0", "2a01:110:8012:1010:914e:2451:1742:4242", "2a01:110:8012:1010:914e:2451:177f:ffff")]
+ public void Contains_WhenInNework_ReturnsTrue(string networkString, params string[] addresses)
+ {
+ var network = IPNetwork.Parse(networkString);
+
+ foreach (string address in addresses)
+ {
+ Assert.True(network.Contains(IPAddress.Parse(address)));
+ }
+ }
+
+ [Theory]
+ [InlineData("255.255.255.255/32", "255.255.255.254")] // single IPv4 address
+ [InlineData("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe")] // single IPv6 address
+ [InlineData("255.255.255.0/24", "255.255.254.0")]
+ [InlineData("198.51.248.0/22", "198.50.248.1", "198.52.248.1", "198.51.247.1", "198.51.252.1")]
+ [InlineData("255.255.255.128/25", "255.255.255.127")]
+ [InlineData("2a00::/13", "2900:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "2a08::", "2a10::", "3a00::", "2b00::")]
+ [InlineData("2a01:110:8012::/47", "2a01:110:8011:1::", "2a01:110:8014::", "2a00:110:8012::1", "2a01:111:8012::")]
+ [InlineData("2a01:110:8012:1012:314f:2a00::/87", "2a01:110:8012:1012:314f:2c00::", "2a01:110:8012:1012:314f:2900::", "2a01:110:8012:1012:324f:2aff:ffff:ffff")]
+ [InlineData("2a01:110:8012:1010:914e:2451:1700:0/105", "2a01:110:8012:1010:914e:2451:16ff:ffff", "2a01:110:8012:1010:914e:2451:1780:0", "2a01:110:8013:1010:914e:2451:1700:0")]
+ public void Contains_WhenNotInNetwork_ReturnsFalse(string networkString, params string[] addresses)
+ {
+ var network = IPNetwork.Parse(networkString);
+
+ foreach (string address in addresses)
+ {
+ Assert.False(network.Contains(IPAddress.Parse(address)));
+ }
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public void Equals_WhenDifferent_ReturnsFalse(bool testOperator)
+ {
+ var network = IPNetwork.Parse("127.0.0.0/24");
+
+ var rangeWithDifferentPrefix = IPNetwork.Parse("127.0.1.0/24");
+ var rangeWithDifferentPrefixLength = IPNetwork.Parse("127.0.0.0/25");
+
+ if (testOperator)
+ {
+ Assert.False(network == rangeWithDifferentPrefix);
+ Assert.False(network == rangeWithDifferentPrefixLength);
+ Assert.True(network != rangeWithDifferentPrefix);
+ Assert.True(network != rangeWithDifferentPrefixLength);
+ }
+ else
+ {
+ Assert.False(network.Equals(rangeWithDifferentPrefix));
+ Assert.False(network.Equals(rangeWithDifferentPrefixLength));
+
+ Assert.False(network.Equals((object)rangeWithDifferentPrefix));
+ Assert.False(network.Equals((object)rangeWithDifferentPrefixLength));
+ }
+ }
+
+ [Theory]
+ [InlineData("127.0.0.0/24")]
+ [InlineData("2a01:110:8012::/47")]
+ public void EqualiyMethods_WhenEqual(string input)
+ {
+ var a = IPNetwork.Parse(input);
+ var b = IPNetwork.Parse(input);
+
+ Assert.True(a.Equals(b));
+ Assert.True(a.Equals((object)b));
+ Assert.True(a == b);
+ Assert.False(a != b);
+ Assert.Equal(a.GetHashCode(), b.GetHashCode());
+ }
+
+ [Fact]
+ public void Equals_WhenNull_ReturnsFalse()
+ {
+ var network = IPNetwork.Parse("127.0.0.0/24");
+
+ Assert.False(network.Equals(null));
+ }
+
+ [Fact]
+ public void TryFormatSpan_EnoughLength_Succeeds()
+ {
+ var input = "127.0.0.0/24";
+ var network = IPNetwork.Parse(input);
+
+ Span<char> span = stackalloc char[15]; // IPAddress.TryFormat requires a size of 15
+
+ Assert.True(network.TryFormat(span, out int charsWritten));
+ Assert.Equal(input.Length, charsWritten);
+ Assert.Equal(input, span.Slice(0, charsWritten).ToString());
+ }
+
+ [Theory]
+ [InlineData("127.127.127.127/32", 15)]
+ [InlineData("127.127.127.127/32", 0)]
+ [InlineData("127.127.127.127/32", 1)]
+ public void TryFormatSpan_NotEnoughLength_ReturnsFalse(string input, int spanLengthToTest)
+ {
+ var network = IPNetwork.Parse(input);
+
+ Span<char> span = stackalloc char[spanLengthToTest];
+
+ Assert.False(network.TryFormat(span, out int charsWritten));
+ }
+
+ [Fact]
+ public void DefaultInstance_IsValid()
+ {
+ IPNetwork network = default;
+ Assert.Equal(IPAddress.Any, network.BaseAddress);
+ Assert.Equal(default, network);
+ Assert.NotEqual(IPNetwork.Parse("10.20.30.0/24"), network);
+ Assert.True(network.Contains(IPAddress.Parse("10.11.12.13")));
+ }
+ }
+}
<Compile Include="IPAddressParsing.cs" />
<Compile Include="IPAddressSpanTest.cs" />
<Compile Include="IPAddressTest.cs" />
+ <Compile Include="IPNetworkTest.cs" />
<Compile Include="IPEndPointParsing.cs" />
<Compile Include="IPEndPointTest.cs" />
<Compile Include="NetworkCredentialTest.cs" />