Skip to content

Commit

Permalink
Implement IUtf8SpanFormattable on IPAddress and IPNetwork (#84487)
Browse files Browse the repository at this point in the history
* Implement IUtf8SpanFormattable on IPAddress and IPNetwork

Implements IUtf8SpanFormattable explicitly on both IPAddress and IPNetwork.  For IPNetwork, we just use Utf8.TryWrite just as the existing ISpanFormattable uses MemoryExtensions.TryWrite.  For IPAddress, the existing formatting code is made to work generically for either byte or char.

In the process, I removed the unsafe pointer-based code from the formatting logic while also making it faster.

* Fix parameter names
  • Loading branch information
stephentoub authored Apr 10, 2023
1 parent 00600d9 commit f516d02
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 249 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal static (int longestSequenceStart, int longestSequenceLength) FindCompre

return longestSequenceLength > 1 ?
(longestSequenceStart, longestSequenceStart + longestSequenceLength) :
(-1, -1);
(-1, 0);
}

// Returns true if the IPv6 address should be formatted with an embedded IPv4 address:
Expand Down
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 : ISpanFormattable, ISpanParsable<IPAddress>
public partial class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable
{
public static readonly System.Net.IPAddress Any;
public static readonly System.Net.IPAddress Broadcast;
Expand Down Expand Up @@ -262,6 +262,7 @@ public IPAddress(System.ReadOnlySpan<byte> address, long scopeid) { }
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; }
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.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; }
Expand All @@ -287,7 +288,7 @@ public IPEndPoint(System.Net.IPAddress address, int port) { }
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>
public readonly partial struct IPNetwork : System.IEquatable<System.Net.IPNetwork>, System.IFormattable, System.IParsable<System.Net.IPNetwork>, System.ISpanFormattable, System.ISpanParsable<System.Net.IPNetwork>, System.IUtf8SpanFormattable
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
Expand All @@ -306,6 +307,7 @@ public IPEndPoint(System.Net.IPAddress address, int port) { }
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; }
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, 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; }
Expand Down
108 changes: 75 additions & 33 deletions src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Net.Sockets;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
Expand All @@ -18,7 +19,7 @@ namespace System.Net
/// Provides an Internet Protocol (IP) address.
/// </para>
/// </devdoc>
public class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>
public class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable
{
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 @@ -375,7 +376,7 @@ public long ScopeId
// Not valid for IPv4 addresses
if (IsIPv4)
{
throw new SocketException(SocketError.OperationNotSupported);
ThrowSocketOperationNotSupported();
}

return PrivateScopeId;
Expand All @@ -385,7 +386,7 @@ public long ScopeId
// Not valid for IPv4 addresses
if (IsIPv4)
{
throw new SocketException(SocketError.OperationNotSupported);
ThrowSocketOperationNotSupported();
}

// Consider: Since scope is only valid for link-local and site-local
Expand All @@ -403,27 +404,74 @@ public long ScopeId
/// or standard IPv6 representation.
/// </para>
/// </devdoc>
public override string ToString() =>
_toString ??= IsIPv4 ?
IPAddressParser.IPv4AddressToString(PrivateAddress) :
IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId);
public override string ToString()
{
string? toString = _toString;
if (toString is null)
{
Span<char> span = stackalloc char[IPAddressParser.MaxIPv6StringLength];
int length = IsIPv4 ?
IPAddressParser.FormatIPv4Address(_addressOrScopeId, span) :
IPAddressParser.FormatIPv6Address(_numbers, _addressOrScopeId, span);
_toString = toString = new string(span.Slice(0, length));
}

return toString;
}

/// <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);
}
public bool TryFormat(Span<char> destination, out int charsWritten) =>
TryFormatCore(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);
TryFormatCore(destination, out charsWritten);

/// <inheritdoc/>
bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
// format and provider are explicitly ignored
TryFormatCore(utf8Destination, out bytesWritten);

private bool TryFormatCore<TChar>(Span<TChar> destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger<TChar>
{
if (IsIPv4)
{
if (destination.Length >= IPAddressParser.MaxIPv4StringLength)
{
charsWritten = IPAddressParser.FormatIPv4Address(_addressOrScopeId, destination);
return true;
}
}
else
{
if (destination.Length >= IPAddressParser.MaxIPv6StringLength)
{
charsWritten = IPAddressParser.FormatIPv6Address(_numbers, _addressOrScopeId, destination);
return true;
}
}

Span<TChar> tmpDestination = stackalloc TChar[IPAddressParser.MaxIPv6StringLength];
Debug.Assert(tmpDestination.Length >= IPAddressParser.MaxIPv4StringLength);

int written = IsIPv4 ?
IPAddressParser.FormatIPv4Address(PrivateAddress, tmpDestination) :
IPAddressParser.FormatIPv6Address(_numbers, PrivateScopeId, tmpDestination);

if (tmpDestination.Slice(0, written).TryCopyTo(destination))
{
charsWritten = written;
return true;
}

charsWritten = 0;
return false;
}

public static long HostToNetworkOrder(long host)
{
Expand Down Expand Up @@ -551,37 +599,28 @@ public long Address
{
get
{
//
// IPv6 Changes: Can't do this for IPv6, so throw an exception.
//
//
if (AddressFamily == AddressFamily.InterNetworkV6)
{
throw new SocketException(SocketError.OperationNotSupported);
}
else
{
return PrivateAddress;
ThrowSocketOperationNotSupported();
}

return PrivateAddress;
}
set
{
//
// IPv6 Changes: Can't do this for IPv6 addresses
if (AddressFamily == AddressFamily.InterNetworkV6)
{
throw new SocketException(SocketError.OperationNotSupported);
ThrowSocketOperationNotSupported();
}
else

if (PrivateAddress != value)
{
if (PrivateAddress != value)
if (this is ReadOnlyIPAddress)
{
if (this is ReadOnlyIPAddress)
{
throw new SocketException(SocketError.OperationNotSupported);
}
PrivateAddress = unchecked((uint)value);
ThrowSocketOperationNotSupported();
}

PrivateAddress = unchecked((uint)value);
}
}
}
Expand Down Expand Up @@ -677,6 +716,9 @@ public IPAddress MapToIPv4()
[DoesNotReturn]
private static byte[] ThrowAddressNullException() => throw new ArgumentNullException("address");

[DoesNotReturn]
private static void ThrowSocketOperationNotSupported() => throw new SocketException(SocketError.OperationNotSupported);

private sealed class ReadOnlyIPAddress : IPAddress
{
public ReadOnlyIPAddress(ReadOnlySpan<byte> newAddress) : base(newAddress)
Expand Down
Loading

0 comments on commit f516d02

Please sign in to comment.