Skip to content
Open
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
15 changes: 14 additions & 1 deletion src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ public IPAddress(System.ReadOnlySpan<byte> address, long scopeid) { }
static bool System.IUtf8SpanParsable<System.Net.IPAddress>.TryParse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Net.IPAddress? result) { throw null; }
public bool TryWriteBytes(System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public partial class IPEndPoint : System.Net.EndPoint
public partial class IPEndPoint : System.Net.EndPoint, ISpanFormattable, ISpanParsable<IPEndPoint>, IUtf8SpanFormattable, IUtf8SpanParsable<IPEndPoint>
{
public const int MaxPort = 65535;
public const int MinPort = 0;
Expand All @@ -301,6 +301,19 @@ public IPEndPoint(System.Net.IPAddress address, int port) { }
public override string ToString() { throw null; }
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 static IPEndPoint Parse(ReadOnlySpan<byte> utf8Text) { throw null; }
static IPEndPoint ISpanParsable<IPEndPoint>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) { throw null; }
static IPEndPoint IParsable<IPEndPoint>.Parse(string s, IFormatProvider? provider) { throw null; }
static IPEndPoint IUtf8SpanParsable<IPEndPoint>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) { throw null; }
public static bool TryParse(ReadOnlySpan<byte> utf8Text, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out IPEndPoint? result) { throw null; }
static bool ISpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out IPEndPoint? result) { throw null; }
static bool IParsable<IPEndPoint>.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out IPEndPoint? result) { throw null; }
static bool IUtf8SpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out IPEndPoint? result) { throw null; }
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) { throw null; }
public bool TryFormat(Span<char> destination, out int charsWritten) { throw null; }
public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten) { throw null; }
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { throw null; }
bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) { 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>, System.IUtf8SpanFormattable, System.IUtf8SpanParsable<System.Net.IPNetwork>
{
Expand Down
163 changes: 152 additions & 11 deletions src/libraries/System.Net.Primitives/src/System/Net/IPEndPoint.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net.Sockets;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text.Unicode;

namespace System.Net
{
/// <summary>
/// Provides an IP address.
/// </summary>
public class IPEndPoint : EndPoint
public class IPEndPoint : EndPoint, ISpanFormattable, ISpanParsable<IPEndPoint>, IUtf8SpanFormattable, IUtf8SpanParsable<IPEndPoint>
{
/// <summary>
/// Specifies the minimum acceptable value for the <see cref='System.Net.IPEndPoint.Port'/> property.
Expand Down Expand Up @@ -92,41 +96,65 @@ public static bool TryParse(string s, [NotNullWhen(true)] out IPEndPoint? result
return TryParse(s.AsSpan(), out result);
}

public static bool TryParse(ReadOnlySpan<char> s, [NotNullWhen(true)] out IPEndPoint? result)
internal static bool InternalTryParse<TChar>(ReadOnlySpan<TChar> s, [NotNullWhen(true)] out IPEndPoint? result)
where TChar : unmanaged, IBinaryInteger<TChar>
{
Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char));

int addressLength = s.Length; // If there's no port then send the entire string to the address parser
int lastColonPos = s.LastIndexOf(':');
int lastColonPos = s.LastIndexOf(TChar.CreateTruncating(':'));

// Look to see if this is an IPv6 address with a port.
if (lastColonPos > 0)
{
if (s[lastColonPos - 1] == ']')
if (s[lastColonPos - 1] == TChar.CreateTruncating(']'))
{
addressLength = lastColonPos;
}
// Look to see if this is IPv4 with a port (IPv6 will have another colon)
else if (s.Slice(0, lastColonPos).LastIndexOf(':') == -1)
else if (s.Slice(0, lastColonPos).LastIndexOf(TChar.CreateTruncating(':')) == -1)
{
addressLength = lastColonPos;
}
}

if (IPAddress.TryParse(s.Slice(0, addressLength), out IPAddress? address))
IPAddress? address = IPAddressParser.Parse(s.Slice(0, addressLength), true);
if (address is not null)
{
uint port = 0;
if (addressLength == s.Length ||
(uint.TryParse(s.Slice(addressLength + 1), NumberStyles.None, CultureInfo.InvariantCulture, out port) && port <= MaxPort))

if (addressLength == s.Length)
{
result = new IPEndPoint(address, (int)port);
result = new IPEndPoint(address, 0);
return true;
}
else
{
uint port;
ReadOnlySpan<TChar> portSpan = s.Slice(addressLength + 1);
bool isConvertedToInt;

if (typeof(TChar) == typeof(byte))
{
isConvertedToInt = uint.TryParse(MemoryMarshal.Cast<TChar, byte>(portSpan), NumberStyles.None, CultureInfo.InvariantCulture, out port);
}
else
{
isConvertedToInt = uint.TryParse(MemoryMarshal.Cast<TChar, char>(portSpan), NumberStyles.None, CultureInfo.InvariantCulture, out port);
}

if (isConvertedToInt && port <= MaxPort)
{
result = new IPEndPoint(address, (int)port);
return true;
}
}
}

result = null;
return false;
}

public static bool TryParse(ReadOnlySpan<char> s, [NotNullWhen(true)] out IPEndPoint? result) => InternalTryParse(s, out result);
Copy link
Member

@MihaZupan MihaZupan Aug 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also include /// comments on all the new APIs (they seed the api docs)


public static IPEndPoint Parse(string s)
{
ArgumentNullException.ThrowIfNull(s);
Expand Down Expand Up @@ -178,5 +206,118 @@ public override int GetHashCode()
{
return _address.GetHashCode() ^ _port;
}

/// <summary>Converts the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary>
/// <param name="utf8Text">A span containing the characters representing the <see cref="IPEndPoint"/> to convert.</param>
/// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded</returns>
/// <exception cref="FormatException"><paramref name="utf8Text"/> is invalid</exception>
public static IPEndPoint Parse(ReadOnlySpan<byte> utf8Text)
{
if (TryParse(utf8Text, out IPEndPoint? result))
{
return result;
}

throw new FormatException(SR.bad_endpoint_string);
}

/// <summary>Converts the character span to its <see cref="IPEndPoint"/> equivalent.</summary>
/// <param name="s">A span containing the characters representing the <see cref="IPEndPoint"/> to convert.</param>
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
/// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded</returns>
/// <exception cref="FormatException"><paramref name="s"/> is invalid</exception>
static IPEndPoint ISpanParsable<IPEndPoint>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => Parse(s);

/// <summary>Converts the string to its <see cref="IPEndPoint"/> equivalent.</summary>
/// <param name="s">A string containing the characters representing the <see cref="IPEndPoint"/> to convert.</param>
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
/// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded</returns>
/// <exception cref="FormatException"><paramref name="s"/> is invalid</exception>
static IPEndPoint IParsable<IPEndPoint>.Parse(string s, IFormatProvider? provider) => Parse(s);

/// <summary>Converts the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary>
/// <param name="utf8Text">A Span containing the UTF-8 characters representing the <see cref="IPEndPoint"/> to convert.</param>
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="utf8Text" />.</param>
/// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded</returns>
/// <exception cref="FormatException"><paramref name="utf8Text"/> is invalid</exception>
static IPEndPoint IUtf8SpanParsable<IPEndPoint>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) => Parse(utf8Text);

/// <summary>Tries to convert the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary>
/// <param name="utf8Text">A span containing the UTF-8 characters representing the <see cref="IPEndPoint"/> to convert.</param>
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
/// <returns><c>true</c> if <paramref name="utf8Text" /> was converted successfully; otherwise, false.</returns>
public static bool TryParse(ReadOnlySpan<byte> utf8Text, [NotNullWhen(true)] out IPEndPoint? result) => InternalTryParse(utf8Text, out result);

/// <summary>Tries to convert the character span to its <see cref="IPEndPoint"/> equivalent.</summary>
/// <param name="s">A span container the characters representing the <see cref="IPEndPoint"/> to convert.</param>
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
/// <returns><c>true</c> if <paramref name="s" /> was converted successfully; otherwise, false.</returns>
static bool ISpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) => TryParse(s, out result);

/// <summary>Tries to convert the string to its <see cref="IPEndPoint"/> equivalent.</summary>
/// <param name="s">A string representing the <see cref="IPEndPoint"/> to convert.</param>
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param>
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
/// <returns><c>true</c> if <paramref name="s" /> was converted successfully; otherwise, false.</returns>
static bool IParsable<IPEndPoint>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result)
{
if (s is null)
{
result = default;
return false;
}

return TryParse(s, out result);
}

/// <summary>Tries to convert the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary>
/// <param name="utf8Text">A span container the characters representing the <see cref="IPEndPoint"/> to convert.</param>
/// <param name="provider">An object that provides culture-specific formatting information about <paramref name="utf8Text" />.</param>
/// <param name="result">When this method returns, contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded, or default if the conversion failed. This parameter is passed uninitialized; any value originally supplied in result will be overwritten.</param>
/// <returns><c>true</c> if <paramref name="utf8Text" /> was converted successfully; otherwise, false.</returns>
static bool IUtf8SpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) => TryParse(utf8Text, out result);

/// <summary>Returns the string representation of the current instance using the specified format string to define culture-specific formatting.</summary>
/// <param name="format">A standard or custom numeric format string that defines the format of individual elements.</param>
/// <param name="formatProvider">A format provider that supplies culture-specific formatting information.</param>
/// <returns>The string representation of the current instance.</returns>
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString();

/// <summary>Tries to format the value of the current instance as characters into the provided span of characters.</summary>
/// <param name="destination">When this method returns, this parameter is filled with this instance formatted characters.</param>
/// <param name="charsWritten">When this method returns, the number of bytes that were written in <paramref name="destination"/>.</param>
/// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
public bool TryFormat(Span<char> destination, out int charsWritten) =>
_address.AddressFamily == AddressFamily.InterNetworkV6 ?
destination.TryWrite(CultureInfo.InvariantCulture, $"[{_address}]:{_port}", out charsWritten) :
destination.TryWrite(CultureInfo.InvariantCulture, $"{_address}:{_port}", out charsWritten);

/// <summary>Tries to format the value of the current instance as UTF-8 bytes into the provided span.</summary>
/// <param name="utf8Destination">When this method returns, this parameter is filled with this instance formatted UTF68 bytes.</param>
/// <param name="bytesWritten">When this method returns, the number of bytes that were written in <paramref name="utf8Destination"/>.</param>
/// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten) =>
_address.AddressFamily == AddressFamily.InterNetworkV6 ?
Utf8.TryWrite(utf8Destination, CultureInfo.InvariantCulture, $"[{_address}]:{_port}", out bytesWritten) :
Utf8.TryWrite(utf8Destination, CultureInfo.InvariantCulture, $"{_address}:{_port}", out bytesWritten);

/// <summary>Tries to format the value of the current instance as characters into the provided span of characters.</summary>
/// <param name="destination">When this method returns, this parameter is filled with this instance formatted characters.</param>
/// <param name="charsWritten">When this method returns, the number of bytes that were written in <paramref name="destination"/>.</param>
/// <param name="format">A span containing the characters that represent a standard or custom format string that defines the acceptable format for <paramref name="destination"/>.</param>
/// <param name="provider">An optional object that supplies culture-specific formatting information for <paramref name="destination"/>.</param>
/// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
TryFormat(destination, out charsWritten);

/// <summary>Tries to format the value of the current instance as UTF-8 bytes into the provided span.</summary>
/// <param name="utf8Destination">When this method returns, this parameter is filled with this instance formatted UTF68 bytes.</param>
/// <param name="bytesWritten">When this method returns, the number of bytes that were written in <paramref name="utf8Destination"/>.</param>
/// <param name="format">A span containing the characters that represent a standard or custom format string that defines the acceptable format for <paramref name="utf8Destination"/>.</param>
/// <param name="provider">An optional object that supplies culture-specific formatting information for <paramref name="utf8Destination"/>.</param>
/// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns>
bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
TryFormat(utf8Destination, out bytesWritten);
}
}
Loading