Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
87 changes: 86 additions & 1 deletion src/libraries/System.Net.Primitives/src/System/Net/IPEndPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net.Sockets;
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 @@ -178,5 +179,89 @@ public override int GetHashCode()
{
return _address.GetHashCode() ^ _port;
}

public static IPEndPoint Parse(ReadOnlySpan<byte> utf8Text)
{
if (TryParse(utf8Text, out IPEndPoint? result))
{
return result;
}

throw new FormatException(SR.bad_endpoint_string);
}

static IPEndPoint ISpanParsable<IPEndPoint>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => Parse(s);

static IPEndPoint IParsable<IPEndPoint>.Parse(string s, IFormatProvider? provider) => Parse(s);

static IPEndPoint IUtf8SpanParsable<IPEndPoint>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) => Parse(utf8Text);

public static bool TryParse(ReadOnlySpan<byte> utf8Text, [NotNullWhen(true)] out IPEndPoint? result)
{
int addressLength = utf8Text.Length; // If there's no port then send the entire string to the address parser
int lastColonPos = utf8Text.LastIndexOf((byte)':');

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

if (IPAddress.TryParse(utf8Text.Slice(0, addressLength), out IPAddress? address))
{
uint port = 0;
if (addressLength == utf8Text.Length ||
(uint.TryParse(utf8Text.Slice(addressLength + 1), NumberStyles.None, CultureInfo.InvariantCulture, out port) && port <= MaxPort))

{
result = new IPEndPoint(address, (int)port);
return true;
}
}

result = null;
return false;
}

static bool ISpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) => TryParse(s, out result);

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);
}

static bool IUtf8SpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) => TryParse(utf8Text, out result);

string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString();

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);

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);

bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
TryFormat(destination, out charsWritten);

bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
TryFormat(utf8Destination, out bytesWritten);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Globalization;
using System.Net.Sockets;
using System.Text;
using Xunit;

namespace System.Net.Primitives.Functional.Tests
Expand Down Expand Up @@ -260,5 +262,145 @@ public static void Equals_Invoke_ReturnsExpected(IPEndPoint endPoint, object obj
Assert.Equal(expected, endPoint.GetHashCode().Equals(obj.GetHashCode()));
}
}

[Fact]
public static void ParseByteSpan()
{
ReadOnlySpan<byte> input = Encoding.UTF8.GetBytes("10.12.13.14:5040");

IPEndPoint result = IPEndPoint.Parse(input);

Assert.Equal(5040, result.Port);
Assert.Equal("10.12.13.14", result.Address.ToString());
}

[Fact]
public static void TryParseByteSpan()
{
ReadOnlySpan<byte> input = Encoding.UTF8.GetBytes("10.12.13.14:5040");

Assert.True(IPEndPoint.TryParse(input, out IPEndPoint result));
Assert.Equal(5040, result.Port);
Assert.Equal("10.12.13.14", result.Address.ToString());
}

[Theory]
[InlineData("10.12.13.14:5040")]
[InlineData("[::1]:5040")]
public static void TryFormatCharSpan(string expected)
{
Span<char> destination = stackalloc char[expected.Length];
IPEndPoint input = IPEndPoint.Parse(expected);

Assert.True(input.TryFormat(destination, out int charsWritten));
Assert.Equal(expected.Length, charsWritten);
Assert.True(MemoryExtensions.Equals(destination, expected, StringComparison.Ordinal));
}

[Theory]
[InlineData("10.12.13.14:5040")]
[InlineData("[::1]:5040")]
public static void TryFormatByteSpan(string expected)
{
Span<byte> destination = stackalloc byte[expected.Length];
IPEndPoint input = IPEndPoint.Parse(expected);

Assert.True(input.TryFormat(destination, out int charsWritten));
Assert.Equal(expected.Length, charsWritten);
Assert.True(MemoryExtensions.SequenceEqual(destination, Encoding.UTF8.GetBytes(expected)));
}

[Fact]
public static void ToStringIFormattable()
{
IPEndPoint input = IPEndPoint.Parse("10.12.13.14:5040");
string result = string.Format("display {0:G}", input);

Assert.Equal("display 10.12.13.14:5040", result);
}

private static class ParsableHelper<TSelf> where TSelf : IParsable<TSelf>
{
public static TSelf Parse(string s, IFormatProvider provider) => TSelf.Parse(s, provider);

public static bool TryParse(string s, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, provider, out result);
}

[Fact]
public static void ParseIParsable()
{
string input = "10.12.13.14:5040";

IPEndPoint result = ParsableHelper<IPEndPoint>.Parse(input, CultureInfo.InvariantCulture);

Assert.Equal(5040, result.Port);
Assert.Equal("10.12.13.14", result.Address.ToString());
}

[Fact]
public static void TryParseIParsable()
{
string input = "10.12.13.14:5040";

Assert.True(ParsableHelper<IPEndPoint>.TryParse(input, CultureInfo.InvariantCulture, out IPEndPoint result));
Assert.Equal(5040, result.Port);
Assert.Equal("10.12.13.14", result.Address.ToString());
}

private static class SpanParsableHelper<TSelf> where TSelf : ISpanParsable<TSelf>
{
public static TSelf Parse(ReadOnlySpan<char> s, IFormatProvider provider) => TSelf.Parse(s, provider);

public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider provider, out TSelf result) => TSelf.TryParse(s, provider, out result);
}

[Fact]
public static void ParseISpanParsable()
{
string input = "10.12.13.14:5040";

IPEndPoint result = SpanParsableHelper<IPEndPoint>.Parse(input, CultureInfo.InvariantCulture);

Assert.Equal(5040, result.Port);
Assert.Equal("10.12.13.14", result.Address.ToString());
}

[Fact]
public static void TryParseISpanParsable()
{
string input = "10.12.13.14:5040";

Assert.True(SpanParsableHelper<IPEndPoint>.TryParse(input, CultureInfo.InvariantCulture, out IPEndPoint result));
Assert.Equal(5040, result.Port);
Assert.Equal("10.12.13.14", result.Address.ToString());
}

private static class Utf8SpanParsableHelper<TSelf> where TSelf : IUtf8SpanParsable<TSelf>
{
public static TSelf Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider provider) => TSelf.Parse(utf8Text, provider);

public static bool TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider provider, out TSelf result) => TSelf.TryParse(utf8Text, provider, out result);
}

[Fact]
public static void ParseIUtf8SpanParsable()
{
ReadOnlySpan<byte> input = Encoding.UTF8.GetBytes("10.12.13.14:5040");

IPEndPoint result = Utf8SpanParsableHelper<IPEndPoint>.Parse(input, CultureInfo.InvariantCulture);

Assert.Equal(5040, result.Port);
Assert.Equal("10.12.13.14", result.Address.ToString());
}

[Fact]
public static void TryParseIUtf8SpanParsable()
{
ReadOnlySpan<byte> input = Encoding.UTF8.GetBytes("10.12.13.14:5040");

Assert.True(Utf8SpanParsableHelper<IPEndPoint>.TryParse(input, CultureInfo.InvariantCulture, out IPEndPoint result));
Assert.Equal(5040, result.Port);
Assert.Equal("10.12.13.14", result.Address.ToString());
}
}
}
Loading