Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IPAddress ISpanFormattable/Parsable implementations #82913

Merged
merged 4 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public partial interface ICredentialsByHost
{
System.Net.NetworkCredential? GetCredential(string host, int port, string authenticationType);
}
public partial class IPAddress
public partial class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>
{
public static readonly System.Net.IPAddress Any;
public static readonly System.Net.IPAddress Broadcast;
Expand Down Expand Up @@ -256,10 +256,16 @@ public partial class IPAddress
public static long NetworkToHostOrder(long network) { throw null; }
public static System.Net.IPAddress Parse(System.ReadOnlySpan<char> ipSpan) { throw null; }
public static System.Net.IPAddress Parse(string ipString) { throw null; }
static IPAddress ISpanParsable<IPAddress>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) { throw null; }
static IPAddress IParsable<IPAddress>.Parse(string s, IFormatProvider? provider) { throw null; }
public override string ToString() { throw null; }
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) { throw null; }
public bool TryFormat(System.Span<char> destination, out int charsWritten) { throw null; }
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { throw null; }
public static bool TryParse(System.ReadOnlySpan<char> ipSpan, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPAddress? address) { throw null; }
public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? ipString, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPAddress? address) { throw null; }
static bool ISpanParsable<IPAddress>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out IPAddress result) { throw null; }
static bool IParsable<IPAddress>.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out IPAddress? result) { throw null; }
public bool TryWriteBytes(System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public partial class IPEndPoint : System.Net.EndPoint
Expand Down
34 changes: 33 additions & 1 deletion src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;

#pragma warning disable SA1648 // TODO: https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3595

namespace System.Net
{
/// <devdoc>
/// <para>
/// Provides an Internet Protocol (IP) address.
/// </para>
/// </devdoc>
public class IPAddress
public class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>
{
public static readonly IPAddress Any = new ReadOnlyIPAddress(new byte[] { 0, 0, 0, 0 });
public static readonly IPAddress Loopback = new ReadOnlyIPAddress(new byte[] { 127, 0, 0, 1 });
Expand Down Expand Up @@ -236,6 +238,16 @@ public static bool TryParse(ReadOnlySpan<char> ipSpan, [NotNullWhen(true)] out I
return (address != null);
}

/// <inheritdoc/>
static bool IParsable<IPAddress>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [NotNullWhen(true)] out IPAddress? result) =>
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
// provider is explicitly ignored
TryParse(s, out result);

/// <inheritdoc/>
static bool ISpanParsable<IPAddress>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [NotNullWhen(true)] out IPAddress? result) =>
// provider is explicitly ignored
TryParse(s, out result);

public static IPAddress Parse(string ipString)
{
ArgumentNullException.ThrowIfNull(ipString);
Expand All @@ -248,6 +260,16 @@ public static IPAddress Parse(ReadOnlySpan<char> ipSpan)
return IPAddressParser.Parse(ipSpan, tryParse: false)!;
}

/// <inheritdoc/>
static IPAddress ISpanParsable<IPAddress>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) =>
// provider is explicitly ignored
Parse(s);

/// <inheritdoc/>
static IPAddress IParsable<IPAddress>.Parse(string s, IFormatProvider? provider) =>
// provider is explicitly ignored
Parse(s);

public bool TryWriteBytes(Span<byte> destination, out int bytesWritten)
{
if (IsIPv6)
Expand Down Expand Up @@ -386,13 +408,23 @@ public long ScopeId
IPAddressParser.IPv4AddressToString(PrivateAddress) :
IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId);

/// <inheritdoc/>
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) =>
// format and provider are explicitly ignored
ToString();

public bool TryFormat(Span<char> destination, out int charsWritten)
{
return IsIPv4 ?
IPAddressParser.IPv4AddressToString(PrivateAddress, destination, out charsWritten) :
IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId, destination, out charsWritten);
}

/// <inheritdoc/>
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
// format and provider are explicitly ignored
TryFormat(destination, out charsWritten);

public static long HostToNetworkOrder(long host)
{
return BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(host) : host;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@

namespace System.Net.Primitives.Functional.Tests
{
public sealed class IPAddressParsing_String : IPAddressParsing
public class IPAddressParsingFormatting_String : IPAddressParsingFormatting
{
public override IPAddress Parse(string ipString) => IPAddress.Parse(ipString);
public override bool TryParse(string ipString, out IPAddress address) => IPAddress.TryParse(ipString, out address);
public virtual string ToString(IPAddress address) => address.ToString();

[Fact]
public void Parse_Null_Throws()
Expand All @@ -20,9 +21,78 @@ public void Parse_Null_Throws()
Assert.False(TryParse((string)null, out IPAddress ipAddress));
Assert.Null(ipAddress);
}

[Theory]
[MemberData(nameof(ValidIpv4Addresses))]
[MemberData(nameof(ValidIpv6Addresses))]
public void ToString_MatchesExpected(string addressString, string expected)
{
IPAddress address = Parse(addressString);
Assert.Equal(expected.ToLowerInvariant(), ToString(address));
}
}

public class IPAddressParsingFormatting_Span : IPAddressParsingFormatting
{
public override IPAddress Parse(string ipString) => IPAddress.Parse(ipString.AsSpan());
public override bool TryParse(string ipString, out IPAddress address) => IPAddress.TryParse(ipString.AsSpan(), out address);
public virtual bool TryFormat(IPAddress address, Span<char> destination, out int charsWritten) => address.TryFormat(destination, out charsWritten);

[Theory]
[MemberData(nameof(ValidIpv4Addresses))]
[MemberData(nameof(ValidIpv6Addresses))]
public void TryFormat_ProvidedBufferTooSmall_Failure(string addressString, string expected)
{
_ = expected;
IPAddress address = Parse(addressString);
var result = new char[address.ToString().Length - 1];
Assert.False(TryFormat(address, new Span<char>(result), out int charsWritten));
Assert.Equal(0, charsWritten);
Assert.Equal<char>(new char[result.Length], result);
}

[Theory]
[MemberData(nameof(ValidIpv4Addresses))]
[MemberData(nameof(ValidIpv6Addresses))]
public void TryFormat_ProvidedBufferLargerThanNeeded_Success(string addressString, string expected)
{
IPAddress address = Parse(addressString);

const int IPv4MaxLength = 15; // TryFormat currently requires at least this amount of space for IPv4 addresses
int requiredLength = address.AddressFamily == AddressFamily.InterNetwork ?
IPv4MaxLength :
address.ToString().Length;

var largerThanRequired = new char[requiredLength + 1];
Assert.True(TryFormat(address, new Span<char>(largerThanRequired), out int charsWritten));
Assert.Equal(expected.Length, charsWritten);
Assert.Equal(
address.AddressFamily == AddressFamily.InterNetworkV6 ? expected.ToLowerInvariant() : expected,
new string(largerThanRequired, 0, charsWritten));
}
}

public sealed class IPAddressParsingFormatting_IParsable_IFormattable : IPAddressParsingFormatting_String
{
public override IPAddress Parse(string ipString) => Parse<IPAddress>(ipString);
public override bool TryParse(string ipString, out IPAddress address) => TryParse<IPAddress>(ipString, out address);
public override string ToString(IPAddress address) => ((IFormattable)address).ToString(null, null);

private static T Parse<T>(string s) where T : IParsable<T> => T.Parse(s, null);
private static bool TryParse<T>(string s, out T result) where T : IParsable<T> => T.TryParse(s, null, out result);
}

public sealed class IPAddressParsingFormatting_ISpanParsable_ISpanFormattable : IPAddressParsingFormatting_Span
{
public override IPAddress Parse(string ipString) => Parse<IPAddress>(ipString);
public override bool TryParse(string ipString, out IPAddress address) => TryParse<IPAddress>(ipString, out address);
public override bool TryFormat(IPAddress address, Span<char> destination, out int charsWritten) => ((ISpanFormattable)address).TryFormat(destination, out charsWritten, default, null);

private static T Parse<T>(string s) where T : ISpanParsable<T> => T.Parse(s.AsSpan(), null);
private static bool TryParse<T>(string s, out T result) where T : ISpanParsable<T> => T.TryParse(s.AsSpan(), null, out result);
}

public abstract class IPAddressParsing
public abstract class IPAddressParsingFormatting
{
public abstract IPAddress Parse(string ipString);
public abstract bool TryParse(string ipString, out IPAddress address);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -304,8 +304,8 @@ public static void GetHashCode_ValidIPAddresses_Success(IPAddress ip)

public static IEnumerable<object[]> GetValidIPAddresses()
{
return IPAddressParsing.ValidIpv4Addresses
.Concat(IPAddressParsing.ValidIpv6Addresses)
return IPAddressParsingFormatting.ValidIpv4Addresses
.Concat(IPAddressParsingFormatting.ValidIpv6Addresses)
.Select(array => new object[] {IPAddress.Parse((string)array[0])});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace System.Net.Primitives.Functional.Tests
public class IPEndPointParsing
{
[Theory]
[MemberData(nameof(IPAddressParsing.ValidIpv4Addresses), MemberType = typeof(IPAddressParsing))] // Just borrow the list from IPAddressParsing
[MemberData(nameof(IPAddressParsingFormatting.ValidIpv4Addresses), MemberType = typeof(IPAddressParsingFormatting))] // Just borrow the list from IPAddressParsing
public void Parse_ValidEndPoint_IPv4_Success(string address, string expectedAddress)
{
Parse_ValidEndPoint_Success(address, expectedAddress, true);
Expand Down Expand Up @@ -62,16 +62,16 @@ private void Parse_ValidEndPoint_Success(string address, string expectedAddress,
}

[Theory]
[MemberData(nameof(IPAddressParsing.InvalidIpv4Addresses), MemberType = typeof(IPAddressParsing))]
[MemberData(nameof(IPAddressParsing.InvalidIpv4AddressesStandalone), MemberType = typeof(IPAddressParsing))]
[MemberData(nameof(IPAddressParsingFormatting.InvalidIpv4Addresses), MemberType = typeof(IPAddressParsingFormatting))]
[MemberData(nameof(IPAddressParsingFormatting.InvalidIpv4AddressesStandalone), MemberType = typeof(IPAddressParsingFormatting))]
public void Parse_InvalidAddress_IPv4_Throws(string address)
{
Parse_InvalidAddress_Throws(address, true);
}

[Theory]
[MemberData(nameof(IPAddressParsing.InvalidIpv6Addresses), MemberType = typeof(IPAddressParsing))]
[MemberData(nameof(IPAddressParsing.InvalidIpv6AddressesNoInner), MemberType = typeof(IPAddressParsing))]
[MemberData(nameof(IPAddressParsingFormatting.InvalidIpv6Addresses), MemberType = typeof(IPAddressParsingFormatting))]
[MemberData(nameof(IPAddressParsingFormatting.InvalidIpv6AddressesNoInner), MemberType = typeof(IPAddressParsingFormatting))]
public void Parse_InvalidAddress_IPv6_Throws(string address)
{
Parse_InvalidAddress_Throws(address, false);
Expand Down Expand Up @@ -106,15 +106,15 @@ private void Parse_InvalidAddress_Throws(string address, bool isIPv4)
}

[Theory]
[MemberData(nameof(IPAddressParsing.ValidIpv4Addresses), MemberType = typeof(IPAddressParsing))]
[MemberData(nameof(IPAddressParsingFormatting.ValidIpv4Addresses), MemberType = typeof(IPAddressParsingFormatting))]
public void Parse_InvalidPort_IPv4_Throws(string address, string expectedAddress)
{
_ = expectedAddress;
Parse_InvalidPort_Throws(address, isIPv4: true);
}

[Theory]
[MemberData(nameof(IPAddressParsing.ValidIpv6Addresses), MemberType = typeof(IPAddressParsing))]
[MemberData(nameof(IPAddressParsingFormatting.ValidIpv6Addresses), MemberType = typeof(IPAddressParsingFormatting))]
public void Parse_InvalidPort_IPv6_Throws(string address, string expectedAddress)
{
_ = expectedAddress;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
<Compile Include="EndPointTest.cs" />
<Compile Include="IPAddressMappingTest.cs" />
<Compile Include="IPAddressParsing.cs" />
<Compile Include="IPAddressParsingSpan.cs" />
<Compile Include="IPAddressSpanTest.cs" />
<Compile Include="IPAddressTest.cs" />
<Compile Include="IPEndPointParsing.cs" />
Expand Down