Skip to content

Commit

Permalink
Add IPAddress ISpanFormattable/Parsable implementations (#82913)
Browse files Browse the repository at this point in the history
* Add IPAddress ISpanFormattable/Parsable implementations

* Address PR feedback
  • Loading branch information
stephentoub authored Mar 6, 2023
1 parent 7cd9a5a commit d39dbac
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 62 deletions.
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 IPAddress(System.ReadOnlySpan<byte> address, long scopeid) { }
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) =>
// 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 override string ToString() =>
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

0 comments on commit d39dbac

Please sign in to comment.