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

Implement IUtf8SpanFormattable on IPAddress and IPNetwork #84487

Merged
merged 4 commits into from
Apr 10, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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