Skip to content

Commit f516d02

Browse files
authored
Implement IUtf8SpanFormattable on IPAddress and IPNetwork (#84487)
* 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
1 parent 00600d9 commit f516d02

File tree

7 files changed

+350
-249
lines changed

7 files changed

+350
-249
lines changed

src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ internal static (int longestSequenceStart, int longestSequenceLength) FindCompre
3535

3636
return longestSequenceLength > 1 ?
3737
(longestSequenceStart, longestSequenceStart + longestSequenceLength) :
38-
(-1, -1);
38+
(-1, 0);
3939
}
4040

4141
// Returns true if the IPv6 address should be formatted with an embedded IPv4 address:

src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ public partial interface ICredentialsByHost
218218
{
219219
System.Net.NetworkCredential? GetCredential(string host, int port, string authenticationType);
220220
}
221-
public partial class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>
221+
public partial class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable
222222
{
223223
public static readonly System.Net.IPAddress Any;
224224
public static readonly System.Net.IPAddress Broadcast;
@@ -262,6 +262,7 @@ public IPAddress(System.ReadOnlySpan<byte> address, long scopeid) { }
262262
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) { throw null; }
263263
public bool TryFormat(System.Span<char> destination, out int charsWritten) { throw null; }
264264
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { throw null; }
265+
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
265266
public static bool TryParse(System.ReadOnlySpan<char> ipSpan, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPAddress? address) { throw null; }
266267
public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? ipString, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPAddress? address) { throw null; }
267268
static bool ISpanParsable<IPAddress>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out IPAddress result) { throw null; }
@@ -287,7 +288,7 @@ public IPEndPoint(System.Net.IPAddress address, int port) { }
287288
public static bool TryParse(System.ReadOnlySpan<char> s, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPEndPoint? result) { throw null; }
288289
public static bool TryParse(string s, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPEndPoint? result) { throw null; }
289290
}
290-
public readonly partial struct IPNetwork : System.IEquatable<System.Net.IPNetwork>, System.IFormattable, System.IParsable<System.Net.IPNetwork>, System.ISpanFormattable, System.ISpanParsable<System.Net.IPNetwork>
291+
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
291292
{
292293
private readonly object _dummy;
293294
private readonly int _dummyPrimitive;
@@ -306,6 +307,7 @@ public IPEndPoint(System.Net.IPAddress address, int port) { }
306307
static System.Net.IPNetwork System.IParsable<System.Net.IPNetwork>.Parse([System.Diagnostics.CodeAnalysis.NotNullAttribute] string s, System.IFormatProvider? provider) { throw null; }
307308
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; }
308309
bool System.ISpanFormattable.TryFormat(System.Span<char> destination, out int charsWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
310+
bool System.IUtf8SpanFormattable.TryFormat(System.Span<byte> utf8Destination, out int bytesWritten, System.ReadOnlySpan<char> format, System.IFormatProvider? provider) { throw null; }
309311
static System.Net.IPNetwork System.ISpanParsable<System.Net.IPNetwork>.Parse(System.ReadOnlySpan<char> s, System.IFormatProvider? provider) { throw null; }
310312
static bool System.ISpanParsable<System.Net.IPNetwork>.TryParse(System.ReadOnlySpan<char> s, System.IFormatProvider? provider, out System.Net.IPNetwork result) { throw null; }
311313
public override string ToString() { throw null; }

src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs

Lines changed: 75 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Diagnostics;
66
using System.Diagnostics.CodeAnalysis;
77
using System.Net.Sockets;
8+
using System.Numerics;
89
using System.Runtime.CompilerServices;
910
using System.Runtime.InteropServices;
1011
using System.Runtime.Intrinsics;
@@ -18,7 +19,7 @@ namespace System.Net
1819
/// Provides an Internet Protocol (IP) address.
1920
/// </para>
2021
/// </devdoc>
21-
public class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>
22+
public class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable
2223
{
2324
public static readonly IPAddress Any = new ReadOnlyIPAddress(new byte[] { 0, 0, 0, 0 });
2425
public static readonly IPAddress Loopback = new ReadOnlyIPAddress(new byte[] { 127, 0, 0, 1 });
@@ -375,7 +376,7 @@ public long ScopeId
375376
// Not valid for IPv4 addresses
376377
if (IsIPv4)
377378
{
378-
throw new SocketException(SocketError.OperationNotSupported);
379+
ThrowSocketOperationNotSupported();
379380
}
380381

381382
return PrivateScopeId;
@@ -385,7 +386,7 @@ public long ScopeId
385386
// Not valid for IPv4 addresses
386387
if (IsIPv4)
387388
{
388-
throw new SocketException(SocketError.OperationNotSupported);
389+
ThrowSocketOperationNotSupported();
389390
}
390391

391392
// Consider: Since scope is only valid for link-local and site-local
@@ -403,27 +404,74 @@ public long ScopeId
403404
/// or standard IPv6 representation.
404405
/// </para>
405406
/// </devdoc>
406-
public override string ToString() =>
407-
_toString ??= IsIPv4 ?
408-
IPAddressParser.IPv4AddressToString(PrivateAddress) :
409-
IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId);
407+
public override string ToString()
408+
{
409+
string? toString = _toString;
410+
if (toString is null)
411+
{
412+
Span<char> span = stackalloc char[IPAddressParser.MaxIPv6StringLength];
413+
int length = IsIPv4 ?
414+
IPAddressParser.FormatIPv4Address(_addressOrScopeId, span) :
415+
IPAddressParser.FormatIPv6Address(_numbers, _addressOrScopeId, span);
416+
_toString = toString = new string(span.Slice(0, length));
417+
}
418+
419+
return toString;
420+
}
410421

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

416-
public bool TryFormat(Span<char> destination, out int charsWritten)
417-
{
418-
return IsIPv4 ?
419-
IPAddressParser.IPv4AddressToString(PrivateAddress, destination, out charsWritten) :
420-
IPAddressParser.IPv6AddressToString(_numbers, PrivateScopeId, destination, out charsWritten);
421-
}
427+
public bool TryFormat(Span<char> destination, out int charsWritten) =>
428+
TryFormatCore(destination, out charsWritten);
422429

423430
/// <inheritdoc/>
424431
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
425432
// format and provider are explicitly ignored
426-
TryFormat(destination, out charsWritten);
433+
TryFormatCore(destination, out charsWritten);
434+
435+
/// <inheritdoc/>
436+
bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
437+
// format and provider are explicitly ignored
438+
TryFormatCore(utf8Destination, out bytesWritten);
439+
440+
private bool TryFormatCore<TChar>(Span<TChar> destination, out int charsWritten) where TChar : unmanaged, IBinaryInteger<TChar>
441+
{
442+
if (IsIPv4)
443+
{
444+
if (destination.Length >= IPAddressParser.MaxIPv4StringLength)
445+
{
446+
charsWritten = IPAddressParser.FormatIPv4Address(_addressOrScopeId, destination);
447+
return true;
448+
}
449+
}
450+
else
451+
{
452+
if (destination.Length >= IPAddressParser.MaxIPv6StringLength)
453+
{
454+
charsWritten = IPAddressParser.FormatIPv6Address(_numbers, _addressOrScopeId, destination);
455+
return true;
456+
}
457+
}
458+
459+
Span<TChar> tmpDestination = stackalloc TChar[IPAddressParser.MaxIPv6StringLength];
460+
Debug.Assert(tmpDestination.Length >= IPAddressParser.MaxIPv4StringLength);
461+
462+
int written = IsIPv4 ?
463+
IPAddressParser.FormatIPv4Address(PrivateAddress, tmpDestination) :
464+
IPAddressParser.FormatIPv6Address(_numbers, PrivateScopeId, tmpDestination);
465+
466+
if (tmpDestination.Slice(0, written).TryCopyTo(destination))
467+
{
468+
charsWritten = written;
469+
return true;
470+
}
471+
472+
charsWritten = 0;
473+
return false;
474+
}
427475

428476
public static long HostToNetworkOrder(long host)
429477
{
@@ -551,37 +599,28 @@ public long Address
551599
{
552600
get
553601
{
554-
//
555-
// IPv6 Changes: Can't do this for IPv6, so throw an exception.
556-
//
557-
//
558602
if (AddressFamily == AddressFamily.InterNetworkV6)
559603
{
560-
throw new SocketException(SocketError.OperationNotSupported);
561-
}
562-
else
563-
{
564-
return PrivateAddress;
604+
ThrowSocketOperationNotSupported();
565605
}
606+
607+
return PrivateAddress;
566608
}
567609
set
568610
{
569-
//
570-
// IPv6 Changes: Can't do this for IPv6 addresses
571611
if (AddressFamily == AddressFamily.InterNetworkV6)
572612
{
573-
throw new SocketException(SocketError.OperationNotSupported);
613+
ThrowSocketOperationNotSupported();
574614
}
575-
else
615+
616+
if (PrivateAddress != value)
576617
{
577-
if (PrivateAddress != value)
618+
if (this is ReadOnlyIPAddress)
578619
{
579-
if (this is ReadOnlyIPAddress)
580-
{
581-
throw new SocketException(SocketError.OperationNotSupported);
582-
}
583-
PrivateAddress = unchecked((uint)value);
620+
ThrowSocketOperationNotSupported();
584621
}
622+
623+
PrivateAddress = unchecked((uint)value);
585624
}
586625
}
587626
}
@@ -677,6 +716,9 @@ public IPAddress MapToIPv4()
677716
[DoesNotReturn]
678717
private static byte[] ThrowAddressNullException() => throw new ArgumentNullException("address");
679718

719+
[DoesNotReturn]
720+
private static void ThrowSocketOperationNotSupported() => throw new SocketException(SocketError.OperationNotSupported);
721+
680722
private sealed class ReadOnlyIPAddress : IPAddress
681723
{
682724
public ReadOnlyIPAddress(ReadOnlySpan<byte> newAddress) : base(newAddress)

0 commit comments

Comments
 (0)