diff --git a/src/System.Net.Primitives/ref/System.Net.Primitives.cs b/src/System.Net.Primitives/ref/System.Net.Primitives.cs index 16dfbef5ca28..f5123e3d7ec0 100644 --- a/src/System.Net.Primitives/ref/System.Net.Primitives.cs +++ b/src/System.Net.Primitives/ref/System.Net.Primitives.cs @@ -191,7 +191,9 @@ public partial class IPAddress public static readonly System.Net.IPAddress Loopback; public static readonly System.Net.IPAddress None; public IPAddress(byte[] address) { } + public IPAddress(ReadOnlySpan address) { } public IPAddress(byte[] address, long scopeid) { } + public IPAddress(ReadOnlySpan address, long scopeid) { } public IPAddress(long newAddress) { } public System.Net.Sockets.AddressFamily AddressFamily { get { throw null; } } public bool IsIPv4MappedToIPv6 { get { throw null; } } @@ -202,6 +204,7 @@ public IPAddress(long newAddress) { } public long ScopeId { get { throw null; } set { } } public override bool Equals(object comparand) { throw null; } public byte[] GetAddressBytes() { throw null; } + public bool TryWriteBytes(Span destination, out int bytesWritten) { throw null; } public override int GetHashCode() { throw null; } public static short HostToNetworkOrder(short host) { throw null; } public static int HostToNetworkOrder(int host) { throw null; } @@ -213,8 +216,11 @@ public IPAddress(long newAddress) { } public static int NetworkToHostOrder(int network) { throw null; } public static long NetworkToHostOrder(long network) { throw null; } public static System.Net.IPAddress Parse(string ipString) { throw null; } + public static System.Net.IPAddress Parse(ReadOnlySpan ipString) { throw null; } public override string ToString() { throw null; } + public bool TryFormat(Span destination, out int charsWritten) { throw null; } public static bool TryParse(string ipString, out System.Net.IPAddress address) { throw null; } + public static bool TryParse(ReadOnlySpan ipString, out System.Net.IPAddress address) { throw null; } [Obsolete("This property has been deprecated. It is address family dependent. Please use IPAddress.Equals method to perform comparisons. http://go.microsoft.com/fwlink/?linkid=14202")] public long Address { get { throw null; } set { } } } diff --git a/src/System.Net.Primitives/ref/System.Net.Primitives.csproj b/src/System.Net.Primitives/ref/System.Net.Primitives.csproj index 189b9b29b7c1..d89bb66a6a91 100644 --- a/src/System.Net.Primitives/ref/System.Net.Primitives.csproj +++ b/src/System.Net.Primitives/ref/System.Net.Primitives.csproj @@ -13,9 +13,10 @@ + - \ No newline at end of file + diff --git a/src/System.Net.Primitives/src/System.Net.Primitives.csproj b/src/System.Net.Primitives/src/System.Net.Primitives.csproj index be78612c075e..a7b341d4360e 100644 --- a/src/System.Net.Primitives/src/System.Net.Primitives.csproj +++ b/src/System.Net.Primitives/src/System.Net.Primitives.csproj @@ -198,6 +198,7 @@ + diff --git a/src/System.Net.Primitives/src/System/Net/IPAddress.cs b/src/System.Net.Primitives/src/System/Net/IPAddress.cs index 9e1097c1ef04..3a59243b9f14 100644 --- a/src/System.Net.Primitives/src/System/Net/IPAddress.cs +++ b/src/System.Net.Primitives/src/System/Net/IPAddress.cs @@ -121,13 +121,13 @@ public IPAddress(long newAddress) /// Constructor for an IPv6 Address with a specified Scope. /// /// - public IPAddress(byte[] address, long scopeid) + public IPAddress(byte[] address, long scopeid) : + this(new ReadOnlySpan(address ?? throw new ArgumentNullException(nameof(address))), scopeid) { - if (address == null) - { - throw new ArgumentNullException(nameof(address)); - } + } + public IPAddress(ReadOnlySpan address, long scopeid) + { if (address.Length != IPAddressParserStatics.IPv6AddressBytes) { throw new ArgumentException(SR.dns_bad_ip_address, nameof(address)); @@ -200,12 +200,13 @@ private IPAddress(ushort[] numbers, uint scopeid) /// Constructor for IPv4 and IPv6 Address. /// /// - public IPAddress(byte[] address) + public IPAddress(byte[] address) : + this(new ReadOnlySpan(address ?? throw new ArgumentNullException(nameof(address)))) + { + } + + public IPAddress(ReadOnlySpan address) { - if (address == null) - { - throw new ArgumentNullException(nameof(address)); - } if (address.Length != IPAddressParserStatics.IPv4AddressBytes && address.Length != IPAddressParserStatics.IPv6AddressBytes) { throw new ArgumentException(SR.dns_bad_ip_address, nameof(address)); @@ -262,48 +263,99 @@ internal IPAddress(int newAddress) /// public static bool TryParse(string ipString, out IPAddress address) { - address = IPAddressParser.Parse(ipString, true); + if (ipString == null) + { + address = null; + return false; + } + + address = IPAddressParser.Parse(ipString, tryParse: true); + return (address != null); + } + + public static bool TryParse(ReadOnlySpan ipSpan, out IPAddress address) + { + address = IPAddressParser.Parse(ipSpan, tryParse: true); return (address != null); } public static IPAddress Parse(string ipString) { - return IPAddressParser.Parse(ipString, false); + if (ipString == null) + { + throw new ArgumentNullException(nameof(ipString)); + } + + return IPAddressParser.Parse(ipString, tryParse: false); } - /// - /// - /// Provides a copy of the IPAddress internals as an array of bytes. - /// - /// - public byte[] GetAddressBytes() + public static IPAddress Parse(ReadOnlySpan ipSpan) + { + return IPAddressParser.Parse(ipSpan, tryParse: false); + } + + public bool TryWriteBytes(Span destination, out int bytesWritten) { - byte[] bytes; if (IsIPv6) { Debug.Assert(_numbers != null && _numbers.Length == NumberOfLabels); - bytes = new byte[IPAddressParserStatics.IPv6AddressBytes]; + if (destination.Length < IPAddressParserStatics.IPv6AddressBytes) + { + bytesWritten = 0; + return false; + } + int j = 0; for (int i = 0; i < NumberOfLabels; i++) { - bytes[j++] = (byte)((_numbers[i] >> 8) & 0xFF); - bytes[j++] = (byte)((_numbers[i]) & 0xFF); + destination[j++] = (byte)((_numbers[i] >> 8) & 0xFF); + destination[j++] = (byte)((_numbers[i]) & 0xFF); } + + bytesWritten = IPAddressParserStatics.IPv6AddressBytes; } else { - uint address = PrivateAddress; - bytes = new byte[IPAddressParserStatics.IPv4AddressBytes]; + if (destination.Length < IPAddressParserStatics.IPv4AddressBytes) + { + bytesWritten = 0; + return false; + } + uint address = PrivateAddress; unchecked { - bytes[0] = (byte)(address); - bytes[1] = (byte)(address >> 8); - bytes[2] = (byte)(address >> 16); - bytes[3] = (byte)(address >> 24); + destination[0] = (byte)(address); + destination[1] = (byte)(address >> 8); + destination[2] = (byte)(address >> 16); + destination[3] = (byte)(address >> 24); } + + bytesWritten = IPAddressParserStatics.IPv4AddressBytes; + } + + return true; + } + /// + /// + /// Provides a copy of the IPAddress internals as an array of bytes. + /// + /// + public byte[] GetAddressBytes() + { + if (IsIPv6) + { + Debug.Assert(_numbers != null && _numbers.Length == NumberOfLabels); } + + int length = IsIPv6 ? IPAddressParserStatics.IPv6AddressBytes : IPAddressParserStatics.IPv4AddressBytes; + var bytes = new byte[length]; + + bool result = TryWriteBytes(new Span(bytes), out int bytesWritten); + + Debug.Assert(result); + return bytes; } @@ -369,6 +421,13 @@ public override string ToString() return _toString; } + public bool TryFormat(Span destination, out int charsWritten) + { + return IsIPv4 ? + IPAddressParser.IPv4AddressToString(PrivateAddress, destination, out charsWritten) : + IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId, destination, out charsWritten); + } + public static long HostToNetworkOrder(long host) { #if BIGENDIAN diff --git a/src/System.Net.Primitives/src/System/Net/IPAddressParser.cs b/src/System.Net.Primitives/src/System/Net/IPAddressParser.cs index 99fc88d37efc..825aa865666b 100644 --- a/src/System.Net.Primitives/src/System/Net/IPAddressParser.cs +++ b/src/System.Net.Primitives/src/System/Net/IPAddressParser.cs @@ -11,7 +11,7 @@ namespace System.Net { internal class IPAddressParser { - internal static unsafe IPAddress Parse(string ipString, bool tryParse) + internal static IPAddress Parse(string ipString, bool tryParse) { if (ipString == null) { @@ -21,22 +21,30 @@ internal static unsafe IPAddress Parse(string ipString, bool tryParse) } throw new ArgumentNullException(nameof(ipString)); } + + return ParseHelper(ipString.AsReadOnlySpan(), tryParse); + } - if (ipString.IndexOf(':') != -1) + internal static IPAddress Parse(ReadOnlySpan ipSpan, bool tryParse) + { + return ParseHelper(ipSpan, tryParse); + } + + internal static unsafe IPAddress ParseHelper(ReadOnlySpan ipSpan, bool tryParse) + { + if (ipSpan.IndexOf(':') >= 0) { // If the address string contains the colon character then it can only be an IPv6 address. // This is valid because we don't support/parse a port specification at the end of an IPv4 address. - uint scope; ushort* numbers = stackalloc ushort[IPAddressParserStatics.IPv6AddressShorts]; - if (Ipv6StringToAddress(ipString, numbers, IPAddressParserStatics.IPv6AddressShorts, out scope)) + if (Ipv6StringToAddress(ipSpan, numbers, IPAddressParserStatics.IPv6AddressShorts, out uint scope)) { return new IPAddress(numbers, IPAddressParserStatics.IPv6AddressShorts, scope); } } else { - long address; - if (Ipv4StringToAddress(ipString, out address)) + if (Ipv4StringToAddress(ipSpan, out long address)) { return new IPAddress(address); } @@ -54,17 +62,40 @@ internal static unsafe string IPv4AddressToString(uint address) { const int MaxLength = 15; char* addressString = stackalloc char[MaxLength]; - int offset = MaxLength; - FormatIPv4AddressNumber((int)((address >> 24) & 0xFF), addressString, ref offset); - addressString[--offset] = '.'; - FormatIPv4AddressNumber((int)((address >> 16) & 0xFF), addressString, ref offset); - addressString[--offset] = '.'; - FormatIPv4AddressNumber((int)((address >> 8) & 0xFF), addressString, ref offset); - addressString[--offset] = '.'; + int charsWritten = IPv4AddressToStringHelper(address, new Span(addressString, MaxLength)); + + return new string(addressString, 0, charsWritten); + } + + internal static unsafe bool IPv4AddressToString(uint address, Span formatted, out int charsWritten) + { + const int MaxLength = 15; + + if (formatted.Length < MaxLength) + { + charsWritten = 0; + return false; + } + + charsWritten = IPv4AddressToStringHelper(address, formatted); + + return true; + } + + internal static int IPv4AddressToStringHelper(uint address, Span addressString) + { + int offset = 0; + FormatIPv4AddressNumber((int)(address & 0xFF), addressString, ref offset); + addressString[offset++] = '.'; + FormatIPv4AddressNumber((int)((address >> 8) & 0xFF), addressString, ref offset); + addressString[offset++] = '.'; + FormatIPv4AddressNumber((int)((address >> 16) & 0xFF), addressString, ref offset); + addressString[offset++] = '.'; + FormatIPv4AddressNumber((int)((address >> 24) & 0xFF), addressString, ref offset); - return new string(addressString, offset, MaxLength - offset); + return offset; } internal static string IPv6AddressToString(ushort[] address, uint scopeId) @@ -72,6 +103,35 @@ internal static string IPv6AddressToString(ushort[] address, uint scopeId) Debug.Assert(address != null); Debug.Assert(address.Length == IPAddressParserStatics.IPv6AddressShorts); + StringBuilder buffer = IPv6AddressToStringHelper(address, scopeId); + + return StringBuilderCache.GetStringAndRelease(buffer); + } + + internal static bool IPv6AddressToString(ushort[] address, uint scopeId, Span destination, out int charsWritten) + { + Debug.Assert(address != null); + Debug.Assert(address.Length == IPAddressParserStatics.IPv6AddressShorts); + + StringBuilder buffer = IPv6AddressToStringHelper(address, scopeId); + + if (destination.Length < buffer.Length) + { + StringBuilderCache.Release(buffer); + charsWritten = 0; + return false; + } + + buffer.CopyTo(0, destination, buffer.Length); + charsWritten = buffer.Length; + + StringBuilderCache.Release(buffer); + + return true; + } + + internal static StringBuilder IPv6AddressToStringHelper(ushort[] address, uint scopeId) + { const int INET6_ADDRSTRLEN = 65; StringBuilder buffer = StringBuilderCache.Acquire(INET6_ADDRSTRLEN); @@ -99,33 +159,35 @@ internal static string IPv6AddressToString(ushort[] address, uint scopeId) buffer.Append('%').Append(scopeId); } - return StringBuilderCache.GetStringAndRelease(buffer); + return buffer; } - private static unsafe void FormatIPv4AddressNumber(int number, char* addressString, ref int offset) + private static void FormatIPv4AddressNumber(int number, Span addressString, ref int offset) { + // Math.DivRem has no overload for byte, assert here for safety + Debug.Assert(number < 256); + + offset += number > 99 ? 3 : number > 9 ? 2 : 1; + int i = offset; do { - int rem; - number = Math.DivRem(number, 10, out rem); + number = Math.DivRem(number, 10, out int rem); addressString[--i] = (char)('0' + rem); } while (number != 0); - offset = i; } - public static unsafe bool Ipv4StringToAddress(string ipString, out long address) + public static unsafe bool Ipv4StringToAddress(ReadOnlySpan ipSpan, out long address) { - Debug.Assert(ipString != null); - + int end = ipSpan.Length; long tmpAddr; - int end = ipString.Length; - fixed (char* ipStringPtr = ipString) + + fixed (char* ipStringPtr = &ipSpan.DangerousGetPinnableReference()) { tmpAddr = IPv4AddressHelper.ParseNonCanonical(ipStringPtr, 0, ref end, notImplicitFile: true); } - if (tmpAddr != IPv4AddressHelper.Invalid && end == ipString.Length) + if (tmpAddr != IPv4AddressHelper.Invalid && end == ipSpan.Length) { // IPv4AddressHelper.ParseNonCanonical returns the bytes in the inverse order. // Reverse them and return success. @@ -144,49 +206,51 @@ public static unsafe bool Ipv4StringToAddress(string ipString, out long address) } } - public static unsafe bool Ipv6StringToAddress(string ipString, ushort* numbers, int numbersLength, out uint scope) + public static unsafe bool Ipv6StringToAddress(ReadOnlySpan ipSpan, ushort* numbers, int numbersLength, out uint scope) { - Debug.Assert(ipString != null); Debug.Assert(numbers != null); Debug.Assert(numbersLength >= IPAddressParserStatics.IPv6AddressShorts); - int end = ipString.Length; - fixed (char* name = ipString) + int end = ipSpan.Length; + + bool isValid = false; + fixed (char* ipStringPtr = &ipSpan.DangerousGetPinnableReference()) { - if (IPv6AddressHelper.IsValidStrict(name, 0, ref end) || (end != ipString.Length)) + isValid = IPv6AddressHelper.IsValidStrict(ipStringPtr, 0, ref end); + } + if (isValid || (end != ipSpan.Length)) + { + string scopeId = null; + IPv6AddressHelper.Parse(ipSpan, numbers, 0, ref scopeId); + + long result = 0; + if (!string.IsNullOrEmpty(scopeId)) { - string scopeId = null; - IPv6AddressHelper.Parse(ipString, numbers, 0, ref scopeId); + if (scopeId.Length < 2) + { + scope = 0; + return false; + } - long result = 0; - if (!string.IsNullOrEmpty(scopeId)) + for (int i = 1; i < scopeId.Length; i++) { - if (scopeId.Length < 2) + char c = scopeId[i]; + if (c < '0' || c > '9') { scope = 0; return false; } - - for (int i = 1; i < scopeId.Length; i++) + result = (result * 10) + (c - '0'); + if (result > uint.MaxValue) { - char c = scopeId[i]; - if (c < '0' || c > '9') - { - scope = 0; - return false; - } - result = (result * 10) + (c - '0'); - if (result > uint.MaxValue) - { - scope = 0; - return false; - } + scope = 0; + return false; } } - - scope = (uint)result; - return true; } + + scope = (uint)result; + return true; } scope = 0; diff --git a/src/System.Net.Primitives/src/System/Net/IPv4AddressHelper.cs b/src/System.Net.Primitives/src/System/Net/IPv4AddressHelper.cs index 26f958b98b5b..f7c2e7b1248e 100644 --- a/src/System.Net.Primitives/src/System/Net/IPv4AddressHelper.cs +++ b/src/System.Net.Primitives/src/System/Net/IPv4AddressHelper.cs @@ -15,7 +15,7 @@ internal static class IPv4AddressHelper private const int NumberOfLabels = 4; // Only called from the IPv6Helper, only parse the canonical format - internal static unsafe int ParseHostNumber(string str, int start, int end) + internal static unsafe int ParseHostNumber(ReadOnlySpan str, int start, int end) { byte* numbers = stackalloc byte[NumberOfLabels]; ParseCanonical(str, numbers, start, end); @@ -309,7 +309,7 @@ internal static unsafe long ParseNonCanonical(char* name, int start, ref int end // of 8-bit numbers and the characters '.' // Address may terminate with ':' or with the end of the string // - private static unsafe bool ParseCanonical(string name, byte* numbers, int start, int end) + private static unsafe bool ParseCanonical(ReadOnlySpan name, byte* numbers, int start, int end) { for (int i = 0; i < NumberOfLabels; ++i) { diff --git a/src/System.Net.Primitives/src/System/Net/IPv6AddressHelper.cs b/src/System.Net.Primitives/src/System/Net/IPv6AddressHelper.cs index 12582da222ad..0559c427adb7 100644 --- a/src/System.Net.Primitives/src/System/Net/IPv6AddressHelper.cs +++ b/src/System.Net.Primitives/src/System/Net/IPv6AddressHelper.cs @@ -282,7 +282,7 @@ internal unsafe static bool IsValidStrict(char* name, int start, ref int end) // Nothing // - internal static unsafe void Parse(string address, ushort* numbers, int start, ref string scopeId) + internal static unsafe void Parse(ReadOnlySpan address, ushort* numbers, int start, ref string scopeId) { int number = 0; int index = 0; @@ -311,7 +311,7 @@ internal static unsafe void Parse(string address, ushort* numbers, int start, re for (++i; i < address.Length && address[i] != ']' && address[i] != '/'; ++i) { } - scopeId = address.Substring(start, i - start); + scopeId = new string(address.Slice(start, i - start)); // ignore prefix if any for (; i < address.Length && address[i] != ']'; ++i) { diff --git a/src/System.Net.Primitives/tests/FunctionalTests/Configurations.props b/src/System.Net.Primitives/tests/FunctionalTests/Configurations.props index cfc57211a43f..b0d8c5c32a7e 100644 --- a/src/System.Net.Primitives/tests/FunctionalTests/Configurations.props +++ b/src/System.Net.Primitives/tests/FunctionalTests/Configurations.props @@ -4,6 +4,8 @@ netstandard-Unix; netstandard-Windows_NT; + netcoreapp-Windows_NT; + netcoreapp-Unix; - \ No newline at end of file + diff --git a/src/System.Net.Primitives/tests/FunctionalTests/IPAddressParsing.cs b/src/System.Net.Primitives/tests/FunctionalTests/IPAddressParsing.cs index 8832207b87e0..9408feaa388d 100644 --- a/src/System.Net.Primitives/tests/FunctionalTests/IPAddressParsing.cs +++ b/src/System.Net.Primitives/tests/FunctionalTests/IPAddressParsing.cs @@ -7,52 +7,57 @@ namespace System.Net.Primitives.Functional.Tests { - public class IPAddressParsing + public abstract class IPAddressParsing { + public static readonly object[][] ValidIpv4Addresses = + { + // Decimal + new object[] { "192.168.0.1", "192.168.0.1" }, + new object[] { "0.0.0.0", "0.0.0.0" }, + new object[] { "0", "0.0.0.0" }, + new object[] { "12", "0.0.0.12" }, + new object[] { "12.1.7", "12.1.0.7" }, + new object[] { "12.1.7", "12.1.0.7" }, + new object[] { "255.255.255.255", "255.255.255.255" }, + new object[] { "20.65535", "20.0.255.255" }, + new object[] { "157.3873051", "157.59.25.27" }, + new object[] { "157.6427", "157.0.25.27" }, + new object[] { "65535", "0.0.255.255" }, + new object[] { "65536", "0.1.0.0" }, + new object[] { "1434328179", "85.126.28.115" }, + new object[] { "2637895963", "157.59.25.27" }, + new object[] { "3397943208", "202.136.127.168" }, + new object[] { "4294967294", "255.255.255.254" }, + new object[] { "4294967295", "255.255.255.255" }, + //Hex + new object[] { "0xFF.0xFF.0xFF.0xFF", "255.255.255.255" }, + new object[] { "0x0", "0.0.0.0" }, + new object[] { "0xFFFFFFFE", "255.255.255.254" }, + new object[] { "0xFFFFFFFF", "255.255.255.255" }, + new object[] { "0x9D3B191B", "157.59.25.27" }, + new object[] { "0X9D.0x3B.0X19.0x1B", "157.59.25.27" }, + new object[] { "0x89.0xab.0xcd.0xef", "137.171.205.239" }, + new object[] { "0xff.0x7f.0x20.0x01", "255.127.32.1" }, + // Octal + new object[] { "0313.027035210", "203.92.58.136" }, + new object[] { "0313.0134.035210", "203.92.58.136" }, + new object[] { "0377.0377.0377.0377", "255.255.255.255" }, + new object[] { "037777777776", "255.255.255.254" }, + new object[] { "037777777777", "255.255.255.255" }, + new object[] { "023516614433", "157.59.25.27" }, + new object[] { "00000023516614433", "157.59.25.27" }, + new object[] { "000235.000073.0000031.00000033", "157.59.25.27" }, + new object[] { "0235.073.031.033", "157.59.25.27" }, + new object[] { "157.59.25.033", "157.59.25.27" }, // Partial octal + // Mixed base + new object[] { "157.59.25.0x1B", "157.59.25.27" }, + new object[] { "157.59.0x001B", "157.59.0.27" }, + new object[] { "157.0x00001B", "157.0.0.27" }, + new object[] { "157.59.0x25.033", "157.59.37.27" }, + }; + [Theory] - // Decimal - [InlineData("192.168.0.1", "192.168.0.1")] - [InlineData("0.0.0.0", "0.0.0.0")] - [InlineData("0", "0.0.0.0")] - [InlineData("12", "0.0.0.12")] - [InlineData("12.1.7", "12.1.0.7")] - [InlineData("12.1.7", "12.1.0.7")] - [InlineData("255.255.255.255", "255.255.255.255")] - [InlineData("20.65535", "20.0.255.255")] - [InlineData("157.3873051", "157.59.25.27")] - [InlineData("157.6427", "157.0.25.27")] - [InlineData("65535", "0.0.255.255")] - [InlineData("65536", "0.1.0.0")] - [InlineData("1434328179", "85.126.28.115")] - [InlineData("2637895963", "157.59.25.27")] - [InlineData("3397943208", "202.136.127.168")] - [InlineData("4294967294", "255.255.255.254")] - [InlineData("4294967295", "255.255.255.255")] - //Hex - [InlineData("0xFF.0xFF.0xFF.0xFF", "255.255.255.255")] - [InlineData("0x0", "0.0.0.0")] - [InlineData("0xFFFFFFFE", "255.255.255.254")] - [InlineData("0xFFFFFFFF", "255.255.255.255")] - [InlineData("0x9D3B191B", "157.59.25.27")] - [InlineData("0X9D.0x3B.0X19.0x1B", "157.59.25.27")] - [InlineData("0x89.0xab.0xcd.0xef", "137.171.205.239")] - [InlineData("0xff.0x7f.0x20.0x01", "255.127.32.1")] - // Octal - [InlineData("0313.027035210", "203.92.58.136")] - [InlineData("0313.0134.035210", "203.92.58.136")] - [InlineData("0377.0377.0377.0377", "255.255.255.255")] - [InlineData("037777777776", "255.255.255.254")] - [InlineData("037777777777", "255.255.255.255")] - [InlineData("023516614433", "157.59.25.27")] - [InlineData("00000023516614433", "157.59.25.27")] - [InlineData("000235.000073.0000031.00000033", "157.59.25.27")] - [InlineData("0235.073.031.033", "157.59.25.27")] - [InlineData("157.59.25.033", "157.59.25.27")] // Partial octal - // Mixed base - [InlineData("157.59.25.0x1B", "157.59.25.27")] - [InlineData("157.59.0x001B", "157.59.0.27")] - [InlineData("157.0x00001B", "157.0.0.27")] - [InlineData("157.59.0x25.033", "157.59.37.27")] + [MemberData(nameof(ValidIpv4Addresses))] public void ParseIPv4_ValidAddress_Success(string address, string expected) { IPAddress ip = IPAddress.Parse(address); @@ -66,193 +71,209 @@ public void ParseIPv4_ValidAddress_Success(string address, string expected) Assert.Equal(ip, ip2); } + public static readonly object[][] InvalidIpv4Addresses = + { + new object[] { "" }, // empty + new object[] { " " }, // whitespace + new object[] { " " }, // whitespace + new object[] { " 127.0.0.1" }, // leading whitespace + new object[] { "127.0.0.1 " }, // trailing whitespace + new object[] { " 127.0.0.1 " }, // leading and trailing whitespace + new object[] { "192.168.0.0/16" }, // with subnet + new object[] { "157.3B191B" }, // Hex without 0x + new object[] { "1.1.1.0x" }, // Empty trailing hex segment + new object[] { "0000X9D.0x3B.0X19.0x1B" }, // Leading zeros on hex + new object[] { "0x.1.1.1" }, // Empty leading hex segment + new object[] { "0.0.0.089" }, // Octal (leading zero) but with 8 or 9 + new object[] { "260.156" }, // Left dotted segments can't be more than 255 + new object[] { "255.260.156" }, // Left dotted segments can't be more than 255 + new object[] { "255.1.1.256" }, // Right dotted segment can't be more than 255 + new object[] { "0xFF.0xFFFFFF.0xFF" }, // Middle segment too large + new object[] { "0xFFFFFF.0xFF.0xFFFFFF" }, // Leading segment too large + new object[] { "4294967296" }, // Decimal overflow by 1 + new object[] { "040000000000" }, // Octal overflow by 1 + new object[] { "01011101001110110001100100011011" }, // Binary? Read as octal, overflows + new object[] { "10011101001110110001100100011011" }, // Binary? Read as decimal, overflows + new object[] { "0x100000000" }, // Hex overflow by 1 + new object[] { "1.1\u67081.1.1" }, // Invalid char (unicode) + new object[] { "..." }, // Empty sections + new object[] { "1.1.1." }, // Empty trailing section + new object[] { "1..1.1" }, // Empty internal section + new object[] { ".1.1.1" }, // Empty leading section + new object[] { "..11.1" }, // Empty sections + new object[] { " text" }, // alpha text + new object[] { "1.. ." }, // whitespace section + new object[] { "12.1.8. " }, // trailing whitespace section + new object[] { "12.+1.1.4" }, // plus sign in section + new object[] { "12.1.-1.5" }, // minus sign in section + new object[] { "12.1.abc.5" }, // text in section + }; + [Theory] - [InlineData("")] // empty - [InlineData(" ")] // whitespace - [InlineData(" ")] // whitespace - [InlineData(" 127.0.0.1")] // leading whitespace - [InlineData("127.0.0.1 ")] // trailing whitespace - [InlineData(" 127.0.0.1 ")] // leading and trailing whitespace - [InlineData("192.168.0.0/16")] // with subnet - [InlineData("157.3B191B")] // Hex without 0x - [InlineData("1.1.1.0x")] // Empty trailing hex segment - [InlineData("0000X9D.0x3B.0X19.0x1B")] // Leading zeros on hex - [InlineData("0x.1.1.1")] // Empty leading hex segment - [InlineData("0.0.0.089")] // Octal (leading zero) but with 8 or 9 - [InlineData("260.156")] // Left dotted segments can't be more than 255 - [InlineData("255.260.156")] // Left dotted segments can't be more than 255 - [InlineData("255.1.1.256")] // Right dotted segment can't be more than 255 - [InlineData("0xFF.0xFFFFFF.0xFF")] // Middle segment too large - [InlineData("0xFFFFFF.0xFF.0xFFFFFF")] // Leading segment too large - [InlineData("4294967296")] // Decimal overflow by 1 - [InlineData("040000000000")] // Octal overflow by 1 - [InlineData("01011101001110110001100100011011")] // Binary? Read as octal, overflows - [InlineData("10011101001110110001100100011011")] // Binary? Read as decimal, overflows - [InlineData("0x100000000")] // Hex overflow by 1 - [InlineData("1.1\u67081.1.1")] // Invalid char (unicode) - [InlineData("...")] // Empty sections - [InlineData("1.1.1.")] // Empty trailing section - [InlineData("1..1.1")] // Empty internal section - [InlineData(".1.1.1")] // Empty leading section - [InlineData("..11.1")] // Empty sections - [InlineData(" text")] // alpha text - [InlineData("1.. .")] // whitespace section - [InlineData("12.1.8. ")] // trailing whitespace section - [InlineData("12.+1.1.4")] // plus sign in section - [InlineData("12.1.-1.5")] // minus sign in section - [InlineData("12.1.abc.5")] // text in section + [MemberData(nameof(InvalidIpv4Addresses))] public void ParseIPv4_InvalidAddress_Failure(string address) { ParseInvalidAddress(address, hasInnerSocketException: !PlatformDetection.IsFullFramework); } + + public static readonly object[][] Ipv4AddressesWithPort = + { + new object[] { "192.168.0.0:80" }, // with port + new object[] { "192.168.0.1:80" }, // with port + }; + [Theory] - [InlineData("192.168.0.0:80")] // with port - [InlineData("192.168.0.1:80")] // with port + [MemberData(nameof(Ipv4AddressesWithPort))] public void ParseIPv4_InvalidAddress_ThrowsFormatExceptionWithInnerException(string address) { ParseInvalidAddress(address, hasInnerSocketException: true); } + public static readonly object[][] ValidIpv6Addresses = + { + new object[] { "Fe08::1", "fe08::1" }, + new object[] { "0000:0000:0000:0000:0000:0000:0000:0000", "::" }, + new object[] { "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" }, + new object[] { "0:0:0:0:0:0:0:0", "::" }, + new object[] { "1:0:0:0:0:0:0:0", "1::" }, + new object[] { "0:1:0:0:0:0:0:0", "0:1::" }, + new object[] { "0:0:1:0:0:0:0:0", "0:0:1::" }, + new object[] { "0:0:0:1:0:0:0:0", "0:0:0:1::" }, + new object[] { "0:0:0:0:1:0:0:0", "::1:0:0:0" }, + new object[] { "0:0:0:0:0:1:0:0", "::1:0:0" }, + new object[] { "0:0:0:0:0:0:1:0", "::0.1.0.0" }, + new object[] { "0:0:0:0:0:0:2:0", "::0.2.0.0" }, + new object[] { "0:0:0:0:0:0:F:0", "::0.15.0.0" }, + new object[] { "0:0:0:0:0:0:10:0", "::0.16.0.0" }, + new object[] { "0:0:0:0:0:0:A0:0", "::0.160.0.0" }, + new object[] { "0:0:0:0:0:0:F0:0", "::0.240.0.0" }, + new object[] { "0:0:0:0:0:0:FF:0", "::0.255.0.0" }, + new object[] { "0:0:0:0:0:0:0:1", "::1" }, + new object[] { "0:0:0:0:0:0:0:2", "::2" }, + new object[] { "0:0:0:0:0:0:0:F", "::F" }, + new object[] { "0:0:0:0:0:0:0:10", "::10" }, + new object[] { "0:0:0:0:0:0:0:1A", "::1A" }, + new object[] { "0:0:0:0:0:0:0:A0", "::A0" }, + new object[] { "0:0:0:0:0:0:0:F0", "::F0" }, + new object[] { "0:0:0:0:0:0:0:FF", "::FF" }, + new object[] { "0:0:0:0:0:0:0:1001", "::1001" }, + new object[] { "0:0:0:0:0:0:0:1002", "::1002" }, + new object[] { "0:0:0:0:0:0:0:100F", "::100F" }, + new object[] { "0:0:0:0:0:0:0:1010", "::1010" }, + new object[] { "0:0:0:0:0:0:0:10A0", "::10A0" }, + new object[] { "0:0:0:0:0:0:0:10F0", "::10F0" }, + new object[] { "0:0:0:0:0:0:0:10FF", "::10FF" }, + new object[] { "0:0:0:0:0:0:1:1", "::0.1.0.1" }, + new object[] { "0:0:0:0:0:0:2:2", "::0.2.0.2" }, + new object[] { "0:0:0:0:0:0:F:F", "::0.15.0.15" }, + new object[] { "0:0:0:0:0:0:10:10", "::0.16.0.16" }, + new object[] { "0:0:0:0:0:0:A0:A0", "::0.160.0.160" }, + new object[] { "0:0:0:0:0:0:F0:F0", "::0.240.0.240" }, + new object[] { "0:0:0:0:0:0:FF:FF", "::0.255.0.255" }, + new object[] { "0:0:0:0:0:FFFF:0:1", "::FFFF:0:1" }, + new object[] { "0:0:0:0:0:FFFF:0:2", "::FFFF:0:2" }, + new object[] { "0:0:0:0:0:FFFF:0:F", "::FFFF:0:F" }, + new object[] { "0:0:0:0:0:FFFF:0:10", "::FFFF:0:10" }, + new object[] { "0:0:0:0:0:FFFF:0:A0", "::FFFF:0:A0" }, + new object[] { "0:0:0:0:0:FFFF:0:F0", "::FFFF:0:F0" }, + new object[] { "0:0:0:0:0:FFFF:0:FF", "::FFFF:0:FF" }, + new object[] { "0:0:0:0:0:FFFF:1:0", "::FFFF:0.1.0.0" }, + new object[] { "0:0:0:0:0:FFFF:2:0", "::FFFF:0.2.0.0" }, + new object[] { "0:0:0:0:0:FFFF:F:0", "::FFFF:0.15.0.0" }, + new object[] { "0:0:0:0:0:FFFF:10:0", "::FFFF:0.16.0.0" }, + new object[] { "0:0:0:0:0:FFFF:A0:0", "::FFFF:0.160.0.0" }, + new object[] { "0:0:0:0:0:FFFF:F0:0", "::FFFF:0.240.0.0" }, + new object[] { "0:0:0:0:0:FFFF:FF:0", "::FFFF:0.255.0.0" }, + new object[] { "0:0:0:0:0:FFFF:0:1001", "::FFFF:0:1001" }, + new object[] { "0:0:0:0:0:FFFF:0:1002", "::FFFF:0:1002" }, + new object[] { "0:0:0:0:0:FFFF:0:100F", "::FFFF:0:100F" }, + new object[] { "0:0:0:0:0:FFFF:0:1010", "::FFFF:0:1010" }, + new object[] { "0:0:0:0:0:FFFF:0:10A0", "::FFFF:0:10A0" }, + new object[] { "0:0:0:0:0:FFFF:0:10F0", "::FFFF:0:10F0" }, + new object[] { "0:0:0:0:0:FFFF:0:10FF", "::FFFF:0:10FF" }, + new object[] { "0:0:0:0:0:FFFF:1:1", "::FFFF:0.1.0.1" }, + new object[] { "0:0:0:0:0:FFFF:2:2", "::FFFF:0.2.0.2" }, + new object[] { "0:0:0:0:0:FFFF:F:F", "::FFFF:0.15.0.15" }, + new object[] { "0:0:0:0:0:FFFF:10:10", "::FFFF:0.16.0.16" }, + new object[] { "0:0:0:0:0:FFFF:A0:A0", "::FFFF:0.160.0.160" }, + new object[] { "0:0:0:0:0:FFFF:F0:F0", "::FFFF:0.240.0.240" }, + new object[] { "0:0:0:0:0:FFFF:FF:FF", "::FFFF:0.255.0.255" }, + new object[] { "0:7:7:7:7:7:7:7", "0:7:7:7:7:7:7:7" }, + new object[] { "1:0:0:0:0:0:0:1", "1::1" }, + new object[] { "1:1:0:0:0:0:0:0", "1:1::" }, + new object[] { "2:2:0:0:0:0:0:0", "2:2::" }, + new object[] { "1:1:0:0:0:0:0:1", "1:1::1" }, + new object[] { "1:0:1:0:0:0:0:1", "1:0:1::1" }, + new object[] { "1:0:0:1:0:0:0:1", "1:0:0:1::1" }, + new object[] { "1:0:0:0:1:0:0:1", "1::1:0:0:1" }, + new object[] { "1:0:0:0:0:1:0:1", "1::1:0:1" }, + new object[] { "1:0:0:0:0:0:1:1", "1::1:1" }, + new object[] { "1:1:0:0:1:0:0:1", "1:1::1:0:0:1" }, + new object[] { "1:0:1:0:0:1:0:1", "1:0:1::1:0:1" }, + new object[] { "1:0:0:1:0:0:1:1", "1::1:0:0:1:1" }, + new object[] { "1:1:0:0:0:1:0:1", "1:1::1:0:1" }, + new object[] { "1:0:0:0:1:0:1:1", "1::1:0:1:1" }, + new object[] { "1:1:1:1:1:1:1:0", "1:1:1:1:1:1:1:0" }, + new object[] { "7:7:7:7:7:7:7:0", "7:7:7:7:7:7:7:0" }, + new object[] { "E:0:0:0:0:0:0:1", "E::1" }, + new object[] { "E:0:0:0:0:0:2:2", "E::2:2" }, + new object[] { "E:0:6:6:6:6:6:6", "E:0:6:6:6:6:6:6" }, + new object[] { "E:E:0:0:0:0:0:1", "E:E::1" }, + new object[] { "E:E:0:0:0:0:2:2", "E:E::2:2" }, + new object[] { "E:E:0:5:5:5:5:5", "E:E:0:5:5:5:5:5" }, + new object[] { "E:E:E:0:0:0:0:1", "E:E:E::1" }, + new object[] { "E:E:E:0:0:0:2:2", "E:E:E::2:2" }, + new object[] { "E:E:E:0:4:4:4:4", "E:E:E:0:4:4:4:4" }, + new object[] { "E:E:E:E:0:0:0:1", "E:E:E:E::1" }, + new object[] { "E:E:E:E:0:0:2:2", "E:E:E:E::2:2" }, + new object[] { "E:E:E:E:0:3:3:3", "E:E:E:E:0:3:3:3" }, + new object[] { "E:E:E:E:E:0:0:1", "E:E:E:E:E::1" }, + new object[] { "E:E:E:E:E:0:2:2", "E:E:E:E:E:0:2:2" }, + new object[] { "E:E:E:E:E:E:0:1", "E:E:E:E:E:E:0:1" }, + new object[] { "::FFFF:192.168.0.1", "::FFFF:192.168.0.1" }, + new object[] { "::FFFF:0.168.0.1", "::FFFF:0.168.0.1" }, + new object[] { "::0.0.255.255", "::FFFF" }, + new object[] { "::EEEE:10.0.0.1", "::EEEE:A00:1" }, + new object[] { "::10.0.0.1", "::10.0.0.1" }, + new object[] { "1234:0:0:0:0:1234:0:0", "1234::1234:0:0" }, + new object[] { "1:0:1:0:1:0:1:0", "1:0:1:0:1:0:1:0" }, + new object[] { "1:1:1:0:0:1:1:0", "1:1:1::1:1:0" }, + new object[] { "0:0:0:0:0:1234:0:0", "::1234:0:0" }, + new object[] { "3ffe:38e1::0100:1:0001", "3ffe:38e1::100:1:1" }, + new object[] { "0:0:1:2:00:00:000:0000", "0:0:1:2::" }, + new object[] { "100:0:1:2:0:0:000:abcd", "100:0:1:2::abcd" }, + new object[] { "ffff:0:0:0:0:0:00:abcd", "ffff::abcd" }, + new object[] { "ffff:0:0:2:0:0:00:abcd", "ffff:0:0:2::abcd" }, + new object[] { "0:0:1:2:0:00:0000:0000", "0:0:1:2::" }, + new object[] { "0000:0000::1:0000:0000", "::1:0:0" }, + new object[] { "0:0:111:234:5:6:789A:0", "::111:234:5:6:789a:0" }, + new object[] { "11:22:33:44:55:66:77:8", "11:22:33:44:55:66:77:8" }, + new object[] { "::7711:ab42:1230:0:0:0", "0:0:7711:ab42:1230::" }, + new object[] { "::", "::" }, + new object[] { "[Fe08::1]", "fe08::1" }, // brackets dropped + new object[] { "[Fe08::1]:0x80", "fe08::1" }, // brackets and port dropped + new object[] { "[Fe08::1]:0xFA", "fe08::1" }, // brackets and port dropped + new object[] { "2001:0db8::0001", "2001:db8::1" }, // leading 0s suppressed + new object[] { "3731:54:65fe:2::a7", "3731:54:65fe:2::a7" }, // Unicast + new object[] { "3731:54:65fe:2::a8", "3731:54:65fe:2::a8" }, // Anycast + // ScopeID + new object[] { "Fe08::1%13542", "fe08::1%13542" }, + new object[] { "1::%1", "1::%1" }, + new object[] { "::1%12", "::1%12" }, + new object[] { "::%123", "::%123" }, + // v4 as v6 + new object[] { "FE08::192.168.0.1", "fe08::c0a8:1" }, // Output is not IPv4 mapped + new object[] { "::192.168.0.1", "::192.168.0.1" }, + new object[] { "::FFFF:192.168.0.1", "::ffff:192.168.0.1" }, // SIIT + new object[] { "::FFFF:0:192.168.0.1", "::ffff:0:192.168.0.1" }, // SIIT + new object[] { "::5EFE:192.168.0.1", "::5efe:192.168.0.1" }, // ISATAP + new object[] { "1::5EFE:192.168.0.1", "1::5efe:192.168.0.1" }, // ISATAP + new object[] { "::192.168.0.010", "::192.168.0.10" }, // Embedded IPv4 octal, read as decimal + }; + [Theory] - [InlineData("Fe08::1", "fe08::1")] - [InlineData("0000:0000:0000:0000:0000:0000:0000:0000", "::")] - [InlineData("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")] - [InlineData("0:0:0:0:0:0:0:0", "::")] - [InlineData("1:0:0:0:0:0:0:0", "1::")] - [InlineData("0:1:0:0:0:0:0:0", "0:1::")] - [InlineData("0:0:1:0:0:0:0:0", "0:0:1::")] - [InlineData("0:0:0:1:0:0:0:0", "0:0:0:1::")] - [InlineData("0:0:0:0:1:0:0:0", "::1:0:0:0")] - [InlineData("0:0:0:0:0:1:0:0", "::1:0:0")] - [InlineData("0:0:0:0:0:0:1:0", "::0.1.0.0")] - [InlineData("0:0:0:0:0:0:2:0", "::0.2.0.0")] - [InlineData("0:0:0:0:0:0:F:0", "::0.15.0.0")] - [InlineData("0:0:0:0:0:0:10:0", "::0.16.0.0")] - [InlineData("0:0:0:0:0:0:A0:0", "::0.160.0.0")] - [InlineData("0:0:0:0:0:0:F0:0", "::0.240.0.0")] - [InlineData("0:0:0:0:0:0:FF:0", "::0.255.0.0")] - [InlineData("0:0:0:0:0:0:0:1", "::1")] - [InlineData("0:0:0:0:0:0:0:2", "::2")] - [InlineData("0:0:0:0:0:0:0:F", "::F")] - [InlineData("0:0:0:0:0:0:0:10", "::10")] - [InlineData("0:0:0:0:0:0:0:1A", "::1A")] - [InlineData("0:0:0:0:0:0:0:A0", "::A0")] - [InlineData("0:0:0:0:0:0:0:F0", "::F0")] - [InlineData("0:0:0:0:0:0:0:FF", "::FF")] - [InlineData("0:0:0:0:0:0:0:1001", "::1001")] - [InlineData("0:0:0:0:0:0:0:1002", "::1002")] - [InlineData("0:0:0:0:0:0:0:100F", "::100F")] - [InlineData("0:0:0:0:0:0:0:1010", "::1010")] - [InlineData("0:0:0:0:0:0:0:10A0", "::10A0")] - [InlineData("0:0:0:0:0:0:0:10F0", "::10F0")] - [InlineData("0:0:0:0:0:0:0:10FF", "::10FF")] - [InlineData("0:0:0:0:0:0:1:1", "::0.1.0.1")] - [InlineData("0:0:0:0:0:0:2:2", "::0.2.0.2")] - [InlineData("0:0:0:0:0:0:F:F", "::0.15.0.15")] - [InlineData("0:0:0:0:0:0:10:10", "::0.16.0.16")] - [InlineData("0:0:0:0:0:0:A0:A0", "::0.160.0.160")] - [InlineData("0:0:0:0:0:0:F0:F0", "::0.240.0.240")] - [InlineData("0:0:0:0:0:0:FF:FF", "::0.255.0.255")] - [InlineData("0:0:0:0:0:FFFF:0:1", "::FFFF:0:1")] - [InlineData("0:0:0:0:0:FFFF:0:2", "::FFFF:0:2")] - [InlineData("0:0:0:0:0:FFFF:0:F", "::FFFF:0:F")] - [InlineData("0:0:0:0:0:FFFF:0:10", "::FFFF:0:10")] - [InlineData("0:0:0:0:0:FFFF:0:A0", "::FFFF:0:A0")] - [InlineData("0:0:0:0:0:FFFF:0:F0", "::FFFF:0:F0")] - [InlineData("0:0:0:0:0:FFFF:0:FF", "::FFFF:0:FF")] - [InlineData("0:0:0:0:0:FFFF:1:0", "::FFFF:0.1.0.0")] - [InlineData("0:0:0:0:0:FFFF:2:0", "::FFFF:0.2.0.0")] - [InlineData("0:0:0:0:0:FFFF:F:0", "::FFFF:0.15.0.0")] - [InlineData("0:0:0:0:0:FFFF:10:0", "::FFFF:0.16.0.0")] - [InlineData("0:0:0:0:0:FFFF:A0:0", "::FFFF:0.160.0.0")] - [InlineData("0:0:0:0:0:FFFF:F0:0", "::FFFF:0.240.0.0")] - [InlineData("0:0:0:0:0:FFFF:FF:0", "::FFFF:0.255.0.0")] - [InlineData("0:0:0:0:0:FFFF:0:1001", "::FFFF:0:1001")] - [InlineData("0:0:0:0:0:FFFF:0:1002", "::FFFF:0:1002")] - [InlineData("0:0:0:0:0:FFFF:0:100F", "::FFFF:0:100F")] - [InlineData("0:0:0:0:0:FFFF:0:1010", "::FFFF:0:1010")] - [InlineData("0:0:0:0:0:FFFF:0:10A0", "::FFFF:0:10A0")] - [InlineData("0:0:0:0:0:FFFF:0:10F0", "::FFFF:0:10F0")] - [InlineData("0:0:0:0:0:FFFF:0:10FF", "::FFFF:0:10FF")] - [InlineData("0:0:0:0:0:FFFF:1:1", "::FFFF:0.1.0.1")] - [InlineData("0:0:0:0:0:FFFF:2:2", "::FFFF:0.2.0.2")] - [InlineData("0:0:0:0:0:FFFF:F:F", "::FFFF:0.15.0.15")] - [InlineData("0:0:0:0:0:FFFF:10:10", "::FFFF:0.16.0.16")] - [InlineData("0:0:0:0:0:FFFF:A0:A0", "::FFFF:0.160.0.160")] - [InlineData("0:0:0:0:0:FFFF:F0:F0", "::FFFF:0.240.0.240")] - [InlineData("0:0:0:0:0:FFFF:FF:FF", "::FFFF:0.255.0.255")] - [InlineData("0:7:7:7:7:7:7:7", "0:7:7:7:7:7:7:7")] - [InlineData("1:0:0:0:0:0:0:1", "1::1")] - [InlineData("1:1:0:0:0:0:0:0", "1:1::")] - [InlineData("2:2:0:0:0:0:0:0", "2:2::")] - [InlineData("1:1:0:0:0:0:0:1", "1:1::1")] - [InlineData("1:0:1:0:0:0:0:1", "1:0:1::1")] - [InlineData("1:0:0:1:0:0:0:1", "1:0:0:1::1")] - [InlineData("1:0:0:0:1:0:0:1", "1::1:0:0:1")] - [InlineData("1:0:0:0:0:1:0:1", "1::1:0:1")] - [InlineData("1:0:0:0:0:0:1:1", "1::1:1")] - [InlineData("1:1:0:0:1:0:0:1", "1:1::1:0:0:1")] - [InlineData("1:0:1:0:0:1:0:1", "1:0:1::1:0:1")] - [InlineData("1:0:0:1:0:0:1:1", "1::1:0:0:1:1")] - [InlineData("1:1:0:0:0:1:0:1", "1:1::1:0:1")] - [InlineData("1:0:0:0:1:0:1:1", "1::1:0:1:1")] - [InlineData("1:1:1:1:1:1:1:0", "1:1:1:1:1:1:1:0")] - [InlineData("7:7:7:7:7:7:7:0", "7:7:7:7:7:7:7:0")] - [InlineData("E:0:0:0:0:0:0:1", "E::1")] - [InlineData("E:0:0:0:0:0:2:2", "E::2:2")] - [InlineData("E:0:6:6:6:6:6:6", "E:0:6:6:6:6:6:6")] - [InlineData("E:E:0:0:0:0:0:1", "E:E::1")] - [InlineData("E:E:0:0:0:0:2:2", "E:E::2:2")] - [InlineData("E:E:0:5:5:5:5:5", "E:E:0:5:5:5:5:5")] - [InlineData("E:E:E:0:0:0:0:1", "E:E:E::1")] - [InlineData("E:E:E:0:0:0:2:2", "E:E:E::2:2")] - [InlineData("E:E:E:0:4:4:4:4", "E:E:E:0:4:4:4:4")] - [InlineData("E:E:E:E:0:0:0:1", "E:E:E:E::1")] - [InlineData("E:E:E:E:0:0:2:2", "E:E:E:E::2:2")] - [InlineData("E:E:E:E:0:3:3:3", "E:E:E:E:0:3:3:3")] - [InlineData("E:E:E:E:E:0:0:1", "E:E:E:E:E::1")] - [InlineData("E:E:E:E:E:0:2:2", "E:E:E:E:E:0:2:2")] - [InlineData("E:E:E:E:E:E:0:1", "E:E:E:E:E:E:0:1")] - [InlineData("::FFFF:192.168.0.1", "::FFFF:192.168.0.1")] - [InlineData("::FFFF:0.168.0.1", "::FFFF:0.168.0.1")] - [InlineData("::0.0.255.255", "::FFFF")] - [InlineData("::EEEE:10.0.0.1", "::EEEE:A00:1")] - [InlineData("::10.0.0.1", "::10.0.0.1")] - [InlineData("1234:0:0:0:0:1234:0:0", "1234::1234:0:0")] - [InlineData("1:0:1:0:1:0:1:0", "1:0:1:0:1:0:1:0")] - [InlineData("1:1:1:0:0:1:1:0", "1:1:1::1:1:0")] - [InlineData("0:0:0:0:0:1234:0:0", "::1234:0:0")] - [InlineData("3ffe:38e1::0100:1:0001", "3ffe:38e1::100:1:1")] - [InlineData("0:0:1:2:00:00:000:0000", "0:0:1:2::")] - [InlineData("100:0:1:2:0:0:000:abcd", "100:0:1:2::abcd")] - [InlineData("ffff:0:0:0:0:0:00:abcd", "ffff::abcd")] - [InlineData("ffff:0:0:2:0:0:00:abcd", "ffff:0:0:2::abcd")] - [InlineData("0:0:1:2:0:00:0000:0000", "0:0:1:2::")] - [InlineData("0000:0000::1:0000:0000", "::1:0:0")] - [InlineData("0:0:111:234:5:6:789A:0", "::111:234:5:6:789a:0")] - [InlineData("11:22:33:44:55:66:77:8", "11:22:33:44:55:66:77:8")] - [InlineData("::7711:ab42:1230:0:0:0", "0:0:7711:ab42:1230::")] - [InlineData("::", "::")] - [InlineData("[Fe08::1]", "fe08::1")] // brackets dropped - [InlineData("[Fe08::1]:0x80", "fe08::1")] // brackets and port dropped - [InlineData("[Fe08::1]:0xFA", "fe08::1")] // brackets and port dropped - [InlineData("2001:0db8::0001", "2001:db8::1")] // leading 0s suppressed - [InlineData("3731:54:65fe:2::a7", "3731:54:65fe:2::a7")] // Unicast - [InlineData("3731:54:65fe:2::a8", "3731:54:65fe:2::a8")] // Anycast - // ScopeID - [InlineData("Fe08::1%13542", "fe08::1%13542")] - [InlineData("1::%1", "1::%1")] - [InlineData("::1%12", "::1%12")] - [InlineData("::%123", "::%123")] - // v4 as v6 - [InlineData("FE08::192.168.0.1", "fe08::c0a8:1")] // Output is not IPv4 mapped - [InlineData("::192.168.0.1", "::192.168.0.1")] - [InlineData("::FFFF:192.168.0.1", "::ffff:192.168.0.1")] // SIIT - [InlineData("::FFFF:0:192.168.0.1", "::ffff:0:192.168.0.1")] // SIIT - [InlineData("::5EFE:192.168.0.1", "::5efe:192.168.0.1")] // ISATAP - [InlineData("1::5EFE:192.168.0.1", "1::5efe:192.168.0.1")] // ISATAP - [InlineData("::192.168.0.010", "::192.168.0.10")] // Embedded IPv4 octal, read as decimal + [MemberData(nameof(ValidIpv6Addresses))] public void ParseIPv6_ValidAddress_RoundtripMatchesExpected(string address, string expected) { IPAddress ip = IPAddress.Parse(address); @@ -275,56 +296,66 @@ public void ParseIPv6_ValidAddress_RoundtripMatchesExpected(string address, stri } } + public static readonly object[][] InvalidIpv6Addresses = + { + new object[] { ":::4df" }, + new object[] { "4df:::" }, + new object[] { "0:::4df" }, + new object[] { "4df:::0" }, + new object[] { "::4df:::" }, + new object[] { "0::4df:::" }, + new object[] { " ::1" }, + new object[] { ":: 1" }, + new object[] { ":" }, + new object[] { "0:0:0:0:0:0:0:0:0" }, + new object[] { "0:0:0:0:0:0:0" }, + new object[] { "0FFFF::" }, + new object[] { "FFFF0::" }, + new object[] { "[::1" }, + new object[] { "Fe08::/64" }, // with subnet + new object[] { "[Fe08::1]:80Z" }, // brackets and invalid port + new object[] { "[Fe08::1" }, // leading bracket + new object[] { "[[Fe08::1" }, // two leading brackets + new object[] { "Fe08::1]" }, // trailing bracket + new object[] { "Fe08::1]]" }, // two trailing brackets + new object[] { "[Fe08::1]]" }, // one leading and two trailing brackets + new object[] { ":1" }, // leading single colon + new object[] { "1:" }, // trailing single colon + new object[] { " ::1" }, // leading whitespace + new object[] { "::1 " }, // trailing whitespace + new object[] { " ::1 " }, // leading and trailing whitespace + new object[] { "1::1::1" }, // ambiguous failure + new object[] { "1234::ABCD:1234::ABCD:1234:ABCD" }, // can only use :: once + new object[] { "1:1\u67081:1:1" }, // invalid char + new object[] { "FE08::260.168.0.1" }, // out of range + new object[] { "::192.168.0.0x0" }, // hex failure + new object[] { "G::" }, // invalid hex + new object[] { "FFFFF::" }, // invalid value + new object[] { ":%12" }, // colon scope + new object[] { "::%1a" }, // alphanumeric scope + new object[] { "[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:443/" }, // errneous ending slash after ignored port + new object[] { "::1234%0x12" }, // invalid scope ID + }; + [Theory] - [InlineData(":::4df")] - [InlineData("4df:::")] - [InlineData("0:::4df")] - [InlineData("4df:::0")] - [InlineData("::4df:::")] - [InlineData("0::4df:::")] - [InlineData(" ::1")] - [InlineData(":: 1")] - [InlineData(":")] - [InlineData("0:0:0:0:0:0:0:0:0")] - [InlineData("0:0:0:0:0:0:0")] - [InlineData("0FFFF::")] - [InlineData("FFFF0::")] - [InlineData("[::1")] - [InlineData("Fe08::/64")] // with subnet - [InlineData("[Fe08::1]:80Z")] // brackets and invalid port - [InlineData("[Fe08::1")] // leading bracket - [InlineData("[[Fe08::1")] // two leading brackets - [InlineData("Fe08::1]")] // trailing bracket - [InlineData("Fe08::1]]")] // two trailing brackets - [InlineData("[Fe08::1]]")] // one leading and two trailing brackets - [InlineData(":1")] // leading single colon - [InlineData("1:")] // trailing single colon - [InlineData(" ::1")] // leading whitespace - [InlineData("::1 ")] // trailing whitespace - [InlineData(" ::1 ")] // leading and trailing whitespace - [InlineData("1::1::1")] // ambiguous failure - [InlineData("1234::ABCD:1234::ABCD:1234:ABCD")] // can only use :: once - [InlineData("1:1\u67081:1:1")] // invalid char - [InlineData("FE08::260.168.0.1")] // out of range - [InlineData("::192.168.0.0x0")] // hex failure - [InlineData("G::")] // invalid hex - [InlineData("FFFFF::")] // invalid value - [InlineData(":%12")] // colon scope - [InlineData("::%1a")] // alphanumeric scope - [InlineData("[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]:443/")] // errneous ending slash after ignored port - [InlineData("::1234%0x12")] // invalid scope ID + [MemberData(nameof(InvalidIpv6Addresses))] public void ParseIPv6_InvalidAddress_ThrowsFormatException(string invalidAddress) { ParseInvalidAddress(invalidAddress, hasInnerSocketException: true); } + public static readonly object[][] InvalidIpv6AddressesNoInner = + { + new object[] { "" }, // empty + new object[] { " " }, // whitespace + new object[] { " " }, // whitespace + new object[] { "%12" }, // just scope + new object[] { "[192.168.0.1]" }, // raw v4 + new object[] { "[1]" }, // incomplete + }; + [Theory] - [InlineData("")] // empty - [InlineData(" ")] // whitespace - [InlineData(" ")] // whitespace - [InlineData("%12")] // just scope - [InlineData("[192.168.0.1]")] // raw v4 - [InlineData("[1]")] // incomplete + [MemberData(nameof(InvalidIpv6AddressesNoInner))] public void ParseIPv6_InvalidAddress_ThrowsFormatExceptionWithNoInnerExceptionInNetfx(string invalidAddress) { ParseInvalidAddress(invalidAddress, hasInnerSocketException: !PlatformDetection.IsFullFramework); @@ -351,10 +382,10 @@ private static void ParseInvalidAddress(string invalidAddress, bool hasInnerSock [Fact] public void Parse_Null_Throws() { - Assert.Throws(() => { IPAddress.Parse(null); }); + Assert.Throws(() => { IPAddress.Parse((string)null); }); IPAddress ipAddress; - Assert.False(IPAddress.TryParse(null, out ipAddress)); + Assert.False(IPAddress.TryParse((string)null, out ipAddress)); Assert.Null(ipAddress); } } diff --git a/src/System.Net.Primitives/tests/FunctionalTests/IPAddressParsingSpan.cs b/src/System.Net.Primitives/tests/FunctionalTests/IPAddressParsingSpan.cs new file mode 100644 index 000000000000..b5e95ce58947 --- /dev/null +++ b/src/System.Net.Primitives/tests/FunctionalTests/IPAddressParsingSpan.cs @@ -0,0 +1,170 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net.Sockets; +using Xunit; + +namespace System.Net.Primitives.Functional.Tests +{ + public sealed class IPAddressParsingSpan : IPAddressParsing + { + const int IPv4MaxLength = 15; + + [Theory] + [MemberData(nameof(ValidIpv4Addresses))] + public void ParseIPv4Span_ValidAddress_Success(string address, string expected) + { + IPAddress ip = IPAddress.Parse(address.AsReadOnlySpan()); + + // Validate the ToString of the parsed address matches the expected value + Assert.Equal(expected, ip.ToString()); + Assert.Equal(AddressFamily.InterNetwork, ip.AddressFamily); + + // Validate the ToString representation can be parsed as well back into the same IP + IPAddress ip2 = IPAddress.Parse(address.AsReadOnlySpan()); + Assert.Equal(ip, ip2); + } + + [Theory] + [MemberData(nameof(ValidIpv4Addresses))] + public void TryParseIPv4Span_ValidAddress_Success(string address, string expected) + { + Assert.True(IPAddress.TryParse(address.AsReadOnlySpan(), out IPAddress ip)); + + // Validate the ToString of the parsed address matches the expected value + Assert.Equal(expected, ip.ToString()); + Assert.Equal(AddressFamily.InterNetwork, ip.AddressFamily); + + // Validate the ToString representation can be parsed as well back into the same IP + Assert.True(IPAddress.TryParse(ip.ToString().AsReadOnlySpan(), out IPAddress ip2)); + Assert.Equal(ip, ip2); + } + + [Theory] + [MemberData(nameof(ValidIpv4Addresses))] + public void TryFormatIPv4_ValidAddress_Success(string address, string expected) + { + const int IPv4MaxPlusOneLength = IPv4MaxLength + 1; + + var ip = IPAddress.Parse(address); + + var exactSize = new char[IPv4MaxLength]; + Assert.True(ip.TryFormat(new Span(exactSize), out int charsWritten)); + Assert.Equal(expected.Length, charsWritten); + Assert.Equal(expected, new string(exactSize, 0, charsWritten)); + + var largerThanRequired = new char[IPv4MaxPlusOneLength]; + Assert.True(ip.TryFormat(new Span(largerThanRequired), out charsWritten)); + Assert.Equal(expected.Length, charsWritten); + Assert.Equal(expected, new string(largerThanRequired, 0, charsWritten)); + } + + [Theory] + [MemberData(nameof(ValidIpv4Addresses))] + public void TryFormatIPv4_ProvidedBufferTooSmall_Failure(string address, string expected) + { + const int bufferSize = IPv4MaxLength - 1; + var ip = IPAddress.Parse(address); + + var result = new char[bufferSize]; + Assert.False(ip.TryFormat(new Span(result), out int charsWritten)); + Assert.Equal(0, charsWritten); + Assert.Equal(new char[bufferSize], result); + } + + [Theory] + [MemberData(nameof(InvalidIpv4Addresses))] + public void TryParseIPv4_InvalidAddress_Failure(string address) + { + Assert.False(IPAddress.TryParse(address.AsReadOnlySpan(), out IPAddress ip)); + Assert.Equal(null, ip); + } + + [Theory] + [MemberData(nameof(ValidIpv6Addresses))] + public void ParseIPv6Span_ValidAddress_RoundtripMatchesExpected(string address, string expected) + { + IPAddress ip = IPAddress.Parse(address.AsReadOnlySpan()); + + // Validate the ToString of the parsed address matches the expected value + Assert.Equal(expected.ToLowerInvariant(), ip.ToString()); + Assert.Equal(AddressFamily.InterNetworkV6, ip.AddressFamily); + + // Validate the ToString representation can be parsed as well back into the same IP + IPAddress ip2 = IPAddress.Parse(address.AsReadOnlySpan()); + Assert.Equal(ip, ip2); + + // Validate that anything that doesn't already start with brackets + // can be surrounded with brackets and still parse successfully. + if (!address.StartsWith("[")) + { + Assert.Equal( + expected.ToLowerInvariant(), + IPAddress.Parse(("[" + address + "]").AsReadOnlySpan()).ToString()); + } + } + + [Theory] + [MemberData(nameof(ValidIpv6Addresses))] + public void TryParseIPv6Span_ValidAddress_RoundtripMatchesExpected(string address, string expected) + { + Assert.True(IPAddress.TryParse(address.AsReadOnlySpan(), out IPAddress ip)); + + // Validate the ToString of the parsed address matches the expected value + Assert.Equal(expected.ToLowerInvariant(), ip.ToString()); + Assert.Equal(AddressFamily.InterNetworkV6, ip.AddressFamily); + + // Validate the ToString representation can be parsed as well back into the same IP + Assert.True(IPAddress.TryParse(ip.ToString().AsReadOnlySpan(), out IPAddress ip2)); + Assert.Equal(ip, ip2); + + // Validate that anything that doesn't already start with brackets + // can be surrounded with brackets and still parse successfully. + if (!address.StartsWith("[")) + { + Assert.True(IPAddress.TryParse(("[" + address + "]").AsReadOnlySpan(), out IPAddress ip3)); + Assert.Equal(expected.ToLowerInvariant(), ip3.ToString()); + } + } + + [Theory] + [MemberData(nameof(ValidIpv6Addresses))] + public void TryFormatIPv6_ValidAddress_Success(string address, string expected) + { + var ip = IPAddress.Parse(address); + + var exactSize = new char[expected.Length]; + Assert.True(ip.TryFormat(new Span(exactSize), out int charsWritten)); + Assert.Equal(expected.Length, charsWritten); + Assert.Equal(expected.ToLowerInvariant(), new string(exactSize, 0, charsWritten)); + + var largerThanRequired = new char[expected.Length + 1]; + Assert.True(ip.TryFormat(new Span(largerThanRequired), out charsWritten)); + Assert.Equal(expected.Length, charsWritten); + Assert.Equal(expected.ToLowerInvariant(), new string(largerThanRequired, 0, charsWritten)); + } + + [Theory] + [MemberData(nameof(ValidIpv6Addresses))] + public void TryFormatIPv6_ProvidedBufferTooSmall_Failure(string address, string expected) + { + int bufferSize = expected.Length - 1; + var result = new char[bufferSize]; + var ip = IPAddress.Parse(address); + + Assert.False(ip.TryFormat(new Span(result), out int charsWritten)); + Assert.Equal(0, charsWritten); + Assert.Equal(new char[bufferSize], result); + } + + [Theory] + [MemberData(nameof(InvalidIpv6Addresses))] + public void TryParseIPv6_InvalidAddress_ReturnsFalse(string invalidAddress) + { + Assert.False(IPAddress.TryParse(invalidAddress.AsReadOnlySpan(), out IPAddress ip)); + Assert.Equal(null, ip); + } + + } +} diff --git a/src/System.Net.Primitives/tests/FunctionalTests/IPAddressSpanTest.cs b/src/System.Net.Primitives/tests/FunctionalTests/IPAddressSpanTest.cs new file mode 100644 index 000000000000..890ef3b14a0e --- /dev/null +++ b/src/System.Net.Primitives/tests/FunctionalTests/IPAddressSpanTest.cs @@ -0,0 +1,57 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Net.Sockets; + +using Xunit; + +namespace System.Net.Primitives.Functional.Tests +{ + public static class IPAddressSpanTest + { + public static readonly object[][] IpAddresses = + { + new object[] { new byte[] { 0x8f, 0x18, 0x14, 0x24 } }, + new object[] { new byte[] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 } }, + }; + + [Theory] + [MemberData(nameof(IpAddresses))] + public static void TryWriteBytes_RightSizeBuffer_Success(byte[] address) + { + var result = new byte[address.Length]; + IPAddress ip = new IPAddress(address); + + Assert.True(ip.TryWriteBytes(new Span(result), out int bytesWritten)); + Assert.Equal(address, result); + Assert.Equal(address.Length, bytesWritten); + } + + [Theory] + [MemberData(nameof(IpAddresses))] + public static void TryWriteBytes_LargerBuffer_Success(byte[] address) + { + var result = new byte[address.Length + 1]; + IPAddress ip = new IPAddress(address); + + Assert.True(ip.TryWriteBytes(new Span(result), out int bytesWritten)); + Assert.Equal(address, result.AsSpan().Slice(0, bytesWritten).ToArray()); + Assert.Equal(address.Length, bytesWritten); + } + + [Theory] + [MemberData(nameof(IpAddresses))] + public static void TryWriteBytes_TooSmallBuffer_Failure(byte[] address) + { + int bufferSize = address.Length - 1; + var result = new byte[bufferSize]; + IPAddress ip = new IPAddress(address); + + Assert.False(ip.TryWriteBytes(new Span(result), out int bytesWritten)); + Assert.Equal(0, bytesWritten); + Assert.Equal(new byte[bufferSize], result); + } + } +} diff --git a/src/System.Net.Primitives/tests/FunctionalTests/System.Net.Primitives.Functional.Tests.csproj b/src/System.Net.Primitives/tests/FunctionalTests/System.Net.Primitives.Functional.Tests.csproj index ff57bf9bc4e9..c7d631600901 100644 --- a/src/System.Net.Primitives/tests/FunctionalTests/System.Net.Primitives.Functional.Tests.csproj +++ b/src/System.Net.Primitives/tests/FunctionalTests/System.Net.Primitives.Functional.Tests.csproj @@ -8,6 +8,10 @@ + + + + @@ -18,7 +22,9 @@ + + @@ -40,6 +46,9 @@ RemoteExecutorConsoleApp + + + diff --git a/src/System.Net.Primitives/tests/PerformanceTests/Configurations.props b/src/System.Net.Primitives/tests/PerformanceTests/Configurations.props index cfc57211a43f..ebe64f0dc48f 100644 --- a/src/System.Net.Primitives/tests/PerformanceTests/Configurations.props +++ b/src/System.Net.Primitives/tests/PerformanceTests/Configurations.props @@ -4,6 +4,8 @@ netstandard-Unix; netstandard-Windows_NT; + netcoreapp-Unix; + netcoreapp-Windows_NT; - \ No newline at end of file + diff --git a/src/System.Net.Primitives/tests/PerformanceTests/IPAddressPerformanceTests.cs b/src/System.Net.Primitives/tests/PerformanceTests/IPAddressPerformanceTests.cs new file mode 100644 index 000000000000..babc47ab0946 --- /dev/null +++ b/src/System.Net.Primitives/tests/PerformanceTests/IPAddressPerformanceTests.cs @@ -0,0 +1,156 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Xunit.Performance; +using Xunit; + +namespace System.Net.Primitives.Tests +{ + public static class IPAddressPerformanceTests + { + public static readonly object[][] TestAddresses = + { + new object[] { new byte[] { 0x8f, 0x18, 0x14, 0x24 } }, + new object[] { new byte[] { 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, 0x90, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 } }, + }; + + [Benchmark] + [MeasureGCCounts] + [MemberData(nameof(TestAddresses))] + public static void TryWriteBytes(byte[] address) + { + var ip = new IPAddress(address); + var bytes = new byte[address.Length]; + var bytesSpan = new Span(bytes); + int bytesWritten = 0; + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; ++i) + { + ip.TryWriteBytes(bytesSpan, out bytesWritten); ip.TryWriteBytes(bytesSpan, out bytesWritten); + ip.TryWriteBytes(bytesSpan, out bytesWritten); ip.TryWriteBytes(bytesSpan, out bytesWritten); + ip.TryWriteBytes(bytesSpan, out bytesWritten); ip.TryWriteBytes(bytesSpan, out bytesWritten); + ip.TryWriteBytes(bytesSpan, out bytesWritten); ip.TryWriteBytes(bytesSpan, out bytesWritten); + } + } + } + } + + [Benchmark] + [MeasureGCCounts] + [MemberData(nameof(TestAddresses))] + public static void GetAddressBytes(byte[] address) + { + var ip = new IPAddress(address); + byte[] bytes; + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; ++i) + { + bytes = ip.GetAddressBytes(); bytes = ip.GetAddressBytes(); + bytes = ip.GetAddressBytes(); bytes = ip.GetAddressBytes(); + bytes = ip.GetAddressBytes(); bytes = ip.GetAddressBytes(); + } + } + } + } + + [Benchmark] + [MeasureGCCounts] + [MemberData(nameof(TestAddresses))] + public static void Ctor_Bytes(byte[] address) + { + IPAddress ip; + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; ++i) + { + ip = new IPAddress(address); ip = new IPAddress(address); + ip = new IPAddress(address); ip = new IPAddress(address); + ip = new IPAddress(address); ip = new IPAddress(address); + ip = new IPAddress(address); ip = new IPAddress(address); + } + } + } + } + + [Benchmark] + [MeasureGCCounts] + [MemberData(nameof(TestAddresses))] + public static void Ctor_Span(byte[] address) + { + var span = new ReadOnlySpan(address); + IPAddress ip; + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; ++i) + { + ip = new IPAddress(span); ip = new IPAddress(span); + ip = new IPAddress(span); ip = new IPAddress(span); + ip = new IPAddress(span); ip = new IPAddress(span); + ip = new IPAddress(span); ip = new IPAddress(span); + } + } + } + } + + [Benchmark] + [MeasureGCCounts] + [MemberData(nameof(TestAddresses))] + public static void TryFormat(byte[] address) + { + const int INET6_ADDRSTRLEN = 65; + var buffer = new char[INET6_ADDRSTRLEN]; + var result = new Span(buffer); + int charsWritten; + + var ip = new IPAddress(address); + + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; ++i) + { + ip.TryFormat(result, out charsWritten); + } + } + } + } + + [Benchmark] + [MeasureGCCounts] + [MemberData(nameof(TestAddresses))] + public static void ToString(byte[] address) + { + var ip = new IPAddress(address); + + string result; + foreach (var iteration in Benchmark.Iterations) + { + using (iteration.StartMeasurement()) + { + for (int i = 0; i < 10000; ++i) + { + result = ip.ToString(); result = ip.ToString(); + result = ip.ToString(); result = ip.ToString(); + result = ip.ToString(); result = ip.ToString(); + result = ip.ToString(); result = ip.ToString(); + result = ip.ToString(); result = ip.ToString(); + result = ip.ToString(); result = ip.ToString(); + } + } + } + } + } +} diff --git a/src/System.Net.Primitives/tests/PerformanceTests/System.Net.Primitives.Performance.Tests.csproj b/src/System.Net.Primitives/tests/PerformanceTests/System.Net.Primitives.Performance.Tests.csproj index ab3d3f1b29bb..29d032a54657 100644 --- a/src/System.Net.Primitives/tests/PerformanceTests/System.Net.Primitives.Performance.Tests.csproj +++ b/src/System.Net.Primitives/tests/PerformanceTests/System.Net.Primitives.Performance.Tests.csproj @@ -9,8 +9,13 @@ + + + + + @@ -19,4 +24,4 @@ - \ No newline at end of file + diff --git a/src/System.Net.Primitives/tests/UnitTests/Fakes/IPv6AddressHelper.cs b/src/System.Net.Primitives/tests/UnitTests/Fakes/IPv6AddressHelper.cs index 2b0cd555cabe..56e14dba69e3 100644 --- a/src/System.Net.Primitives/tests/UnitTests/Fakes/IPv6AddressHelper.cs +++ b/src/System.Net.Primitives/tests/UnitTests/Fakes/IPv6AddressHelper.cs @@ -12,6 +12,6 @@ internal unsafe static (int longestSequenceStart, int longestSequenceLength) Fin ushort[] numbers, int fromInclusive, int toExclusive) => (-1, -1); internal unsafe static bool ShouldHaveIpv4Embedded(ushort[] numbers) => false; internal unsafe static bool IsValidStrict(char* name, int start, ref int end) => false; - internal static unsafe bool Parse(string address, ushort* numbers, int start, ref string scopeId) => false; + internal static unsafe bool Parse(ReadOnlySpan ipSpan, ushort* numbers, int start, ref string scopeId) => false; } }