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 IUtf8SpanParsable on IPAddress and IPNetwork #102144

Merged
merged 45 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
81ab847
Changed IP address parsers to use generics
edwardneal May 6, 2024
2bf97eb
Moved generic type definitions to parser classes
edwardneal May 6, 2024
223cece
Implemented IUtf8SpanParsable
edwardneal May 6, 2024
b9e3a62
System.Private.Uri changes
edwardneal May 7, 2024
4e580ac
Post-Uri testing, corrected additional uses of IPvXAddressHelper
edwardneal May 13, 2024
8ff7241
Added IUtf8SpanParsable unit tests
edwardneal May 13, 2024
f627e44
Implemented IUtf8SpanParsable on IPNetwork, added tests
edwardneal May 13, 2024
1f52945
Brief tidy-up of System.Net.Primitives ref project and csproj
edwardneal May 13, 2024
3710a7a
Further cleanup of System.Net.Primitives.Pal.Tests csproj
edwardneal May 13, 2024
1f4968c
Further cleanup of System.Net.Primitives.UnitTests.Tests csproj
edwardneal May 13, 2024
ed8c666
Correctly setting bytesConsumed in IPv4AddressHelper.ParseNonCanonical
edwardneal May 13, 2024
3c6e190
Merge branch 'dotnet:main' into issue-81500-ipaddress-ipnetwork
edwardneal Jun 14, 2024
ea95067
Changes following API review
edwardneal Jun 14, 2024
d478e82
Code review changes (round 1)
edwardneal Jun 19, 2024
c42c706
Removed generic type definition from classes
edwardneal Jun 22, 2024
49bc3a5
Replaced ref parameter with out parameter, propagated
edwardneal Jun 22, 2024
3378947
Addressing @jkotas code review comments
edwardneal Jun 23, 2024
66ec8c2
Inlined all constant-value variables
edwardneal Jun 23, 2024
afdee99
Swapped CreateChecked to CreateTruncating
edwardneal Jun 23, 2024
043120f
Code review feedback: initial work
edwardneal Jun 23, 2024
c593694
Removed unnecessary lastSequence modification
edwardneal Jun 23, 2024
4aff60e
Code review changes
edwardneal Jul 2, 2024
8c41d9b
Optimisations to IPv[4/6]AddressHelper
edwardneal Jul 4, 2024
64c639c
Cleaned up IPAddressParser.Common.cs
edwardneal Jul 4, 2024
f290a3a
.sln file cleanup
edwardneal Jul 4, 2024
65027db
Testing micro-optimisations in IPv4 address parser
edwardneal Jul 6, 2024
cd73915
Added "in" qualifier to Span/ROS parameters
edwardneal Jul 6, 2024
385ee62
Correcting trailing "in" reference
edwardneal Jul 6, 2024
f7d4298
Reverted addition of "in" modifier
edwardneal Jul 6, 2024
cb1d208
Code review
edwardneal Jul 12, 2024
0f3bbaf
Additional optimisations post-benchmark
edwardneal Jul 12, 2024
1f8a166
Performance improvement
edwardneal Jul 15, 2024
21f1f86
Changes following code review
edwardneal Jul 17, 2024
70a60a5
Initial response to newest review
edwardneal Jul 20, 2024
e0c49c2
Reverted hexadecimal prefix to be case-sensitive
edwardneal Jul 20, 2024
94b2dd1
Flowed Spans through to PInvoke
edwardneal Jul 21, 2024
02a7a84
PInvoke/PAL cleanup
edwardneal Jul 22, 2024
c352e01
Added assertion
edwardneal Jul 22, 2024
2da63cb
Replaced usage of if_nametoindex
edwardneal Jul 28, 2024
1ba7107
Moved interface name tests
edwardneal Jul 30, 2024
038d3a6
Corrected PInvoke signatures and Unix InterfaceInfoPal
edwardneal Oct 5, 2024
b79a6f5
Continued work with code review feedback & benchmark
edwardneal Oct 6, 2024
827e5f8
Performance optimisations
edwardneal Oct 18, 2024
1b5ad81
Merge remote-tracking branch 'upstream/main' into issue-81500-ipaddre…
edwardneal Oct 19, 2024
dfb68d2
Following code review
edwardneal Oct 23, 2024
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
179 changes: 105 additions & 74 deletions src/libraries/Common/src/System/Net/IPv4AddressHelper.Common.cs

Large diffs are not rendered by default.

419 changes: 234 additions & 185 deletions src/libraries/Common/src/System/Net/IPv6AddressHelper.Common.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal static string NormalizeHostName(string? targetHost)

// Simplified version of IPAddressParser.Parse to avoid allocations and dependencies.
// It purposely ignores scopeId as we don't really use so we do not need to map it to actual interface id.
internal static unsafe bool IsValidAddress(string? hostname)
internal static bool IsValidAddress(string? hostname)
{
if (string.IsNullOrEmpty(hostname))
{
Expand All @@ -49,27 +49,17 @@ internal static unsafe bool IsValidAddress(string? hostname)

ReadOnlySpan<char> ipSpan = hostname.AsSpan();

int end = ipSpan.Length;

if (ipSpan.Contains(':'))
{
// The address is parsed as IPv6 if and only if it contains a colon. This is valid because
// we don't support/parse a port specification at the end of an IPv4 address.
Span<ushort> numbers = stackalloc ushort[IPAddressParserStatics.IPv6AddressShorts];

fixed (char* ipStringPtr = &MemoryMarshal.GetReference(ipSpan))
{
return IPv6AddressHelper.IsValidStrict(ipStringPtr, 0, ref end);
}
return IPv6AddressHelper.IsValidStrict(in ipSpan);
}
else if (char.IsDigit(ipSpan[0]))
{
long tmpAddr;

fixed (char* ipStringPtr = &MemoryMarshal.GetReference(ipSpan))
{
tmpAddr = IPv4AddressHelper.ParseNonCanonical(ipStringPtr, 0, ref end, notImplicitFile: true);
}
long tmpAddr = IPv4AddressHelper.ParseNonCanonical(in ipSpan, out int end, notImplicitFile: true);

if (tmpAddr != IPv4AddressHelper.Invalid && end == ipSpan.Length)
{
Expand Down
24 changes: 16 additions & 8 deletions src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ public partial interface ICredentialsByHost
{
System.Net.NetworkCredential? GetCredential(string host, int port, string authenticationType);
}
public partial class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable
public partial class IPAddress : ISpanFormattable, ISpanParsable<System.Net.IPAddress>, IUtf8SpanFormattable, IUtf8SpanParsable<System.Net.IPAddress>
{
public static readonly System.Net.IPAddress Any;
public static readonly System.Net.IPAddress Broadcast;
Expand Down Expand Up @@ -261,18 +261,22 @@ public IPAddress(System.ReadOnlySpan<byte> address, long scopeid) { }
public static long NetworkToHostOrder(long network) { throw null; }
public static System.Net.IPAddress Parse(System.ReadOnlySpan<char> ipSpan) { throw null; }
public static System.Net.IPAddress Parse(string ipString) { throw null; }
static IPAddress ISpanParsable<IPAddress>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) { throw null; }
static IPAddress IParsable<IPAddress>.Parse(string s, IFormatProvider? provider) { throw null; }
public static System.Net.IPAddress Parse(System.ReadOnlySpan<byte> utf8Text) { throw null; }
static System.Net.IPAddress ISpanParsable<System.Net.IPAddress>.Parse(System.ReadOnlySpan<char> s, System.IFormatProvider? provider) { throw null; }
static System.Net.IPAddress IParsable<System.Net.IPAddress>.Parse(string s, System.IFormatProvider? provider) { throw null; }
static System.Net.IPAddress IUtf8SpanParsable<System.Net.IPAddress>.Parse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider) { throw null; }
public override string ToString() { throw null; }
string IFormattable.ToString(string? format, IFormatProvider? formatProvider) { throw null; }
string System.IFormattable.ToString(string? format, System.IFormatProvider? formatProvider) { throw null; }
public bool TryFormat(System.Span<char> destination, out int charsWritten) { throw null; }
public bool TryFormat(System.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 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; }
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; }
static bool IParsable<IPAddress>.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out IPAddress? result) { throw null; }
public static bool TryParse(System.ReadOnlySpan<byte> utf8Text, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Net.IPAddress? result) { throw null; }
static bool System.ISpanParsable<IPAddress>.TryParse(System.ReadOnlySpan<char> s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out System.Net.IPAddress? result) { throw null; }
static bool System.IParsable<IPAddress>.TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] out System.Net.IPAddress? result) { throw null; }
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
Expand All @@ -294,7 +298,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>, System.IUtf8SpanFormattable
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>
{
private readonly object _dummy;
private readonly int _dummyPrimitive;
Expand All @@ -309,18 +313,22 @@ public IPEndPoint(System.Net.IPAddress address, int port) { }
public static bool operator !=(System.Net.IPNetwork left, System.Net.IPNetwork right) { throw null; }
public static System.Net.IPNetwork Parse(System.ReadOnlySpan<char> s) { throw null; }
public static System.Net.IPNetwork Parse(string s) { throw null; }
public static IPNetwork Parse(ReadOnlySpan<byte> utf8Text) { throw null; }
string System.IFormattable.ToString(string? format, System.IFormatProvider? provider) { throw null; }
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; }
static System.Net.IPNetwork System.IUtf8SpanParsable<System.Net.IPNetwork>.Parse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider) { throw null; }
static bool System.IUtf8SpanParsable<System.Net.IPNetwork>.TryParse(System.ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider, out System.Net.IPNetwork result) { throw null; }
public override string ToString() { throw null; }
public bool TryFormat(System.Span<char> destination, out int charsWritten) { throw null; }
public bool TryFormat(System.Span<byte> utf8Destination, out int bytesWritten) { throw null; }
public static bool TryParse(System.ReadOnlySpan<char> s, out System.Net.IPNetwork result) { throw null; }
public static bool TryParse(string? s, out System.Net.IPNetwork result) { throw null; }
public static bool TryParse(ReadOnlySpan<byte> utf8Text, out IPNetwork result) { throw null; }
}
public partial interface IWebProxy
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-unix;$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-wasi;$(NetCoreAppCurrent)</TargetFrameworks>
Expand Down
33 changes: 32 additions & 1 deletion src/libraries/System.Net.Primitives/src/System/Net/IPAddress.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace System.Net
/// Provides an Internet Protocol (IP) address.
/// </para>
/// </devdoc>
public class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable
public class IPAddress : ISpanFormattable, ISpanParsable<IPAddress>, IUtf8SpanFormattable, IUtf8SpanParsable<IPAddress>
{
public static readonly IPAddress Any = new ReadOnlyIPAddress([0, 0, 0, 0]);
public static readonly IPAddress Loopback = new ReadOnlyIPAddress([127, 0, 0, 1]);
Expand Down Expand Up @@ -232,12 +232,28 @@ public static bool TryParse([NotNullWhen(true)] string? ipString, [NotNullWhen(t
return (address != null);
}

/// <summary>
/// Tries to parse a span of UTF-8 characters into a value.
/// </summary>
/// <param name="utf8Text">The span of UTF-8 characters to parse.</param>
/// <param name="result">On return, contains the result of successfully parsing <paramref name="utf8Text"/> or an undefined value on failure.</param>
/// <returns><c>true</c> if <paramref name="utf8Text"/> was successfully parsed; otherwise, <c>false</c>.</returns>
public static bool TryParse(ReadOnlySpan<byte> utf8Text, [NotNullWhen(true)] out IPAddress? result)
{
result = IPAddressParser.Parse(utf8Text, tryParse: true);
return (result != null);
}

public static bool TryParse(ReadOnlySpan<char> ipSpan, [NotNullWhen(true)] out IPAddress? address)
{
address = IPAddressParser.Parse(ipSpan, tryParse: true);
return (address != null);
}

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

/// <inheritdoc/>
static bool IParsable<IPAddress>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [NotNullWhen(true)] out IPAddress? result) =>
// provider is explicitly ignored
Expand All @@ -255,11 +271,26 @@ public static IPAddress Parse(string ipString)
return IPAddressParser.Parse(ipString.AsSpan(), tryParse: false)!;
}

/// <summary>
/// Parses a span of UTF-8 characters into a value.
/// </summary>
/// <param name="utf8Text">The span of UTF-8 characters to parse.</param>
/// <returns>The result of parsing <paramref name="utf8Text"/>.</returns>
public static IPAddress Parse(ReadOnlySpan<byte> utf8Text)
{
return IPAddressParser.Parse(utf8Text, tryParse: false)!;
}

public static IPAddress Parse(ReadOnlySpan<char> ipSpan)
{
return IPAddressParser.Parse(ipSpan, tryParse: false)!;
}

/// <inheritdoc/>
static IPAddress IUtf8SpanParsable<IPAddress>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) =>
// provider is explicitly ignored
Parse(utf8Text);

/// <inheritdoc/>
static IPAddress ISpanParsable<IPAddress>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) =>
// provider is explicitly ignored
Expand Down
Loading