Skip to content

Commit f199591

Browse files
authored
Implement IUtf8SpanParsable on IPAddress and IPNetwork (#102144)
1 parent 3f65ea7 commit f199591

26 files changed

+734
-186
lines changed

src/libraries/Common/src/Interop/Unix/System.Native/Interop.InterfaceNameToIndex.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ internal static partial class Interop
88
{
99
internal static partial class Sys
1010
{
11-
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_InterfaceNameToIndex", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
12-
public static partial uint InterfaceNameToIndex(string name);
11+
[LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_InterfaceNameToIndex", SetLastError = true)]
12+
public static partial uint InterfaceNameToIndex(ReadOnlySpan<byte> utf8NullTerminatedName);
1313
}
1414
}

src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.if_nametoindex.cs renamed to src/libraries/Common/src/Interop/Windows/IpHlpApi/Interop.ConvertInterfaceIndexToLuid.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ internal static partial class Interop
88
{
99
internal static partial class IpHlpApi
1010
{
11-
[LibraryImport(Interop.Libraries.IpHlpApi, SetLastError = true)]
12-
internal static partial uint if_nametoindex([MarshalAs(UnmanagedType.LPStr)] string name);
11+
[LibraryImport(Libraries.IpHlpApi)]
12+
internal static unsafe partial uint ConvertInterfaceIndexToLuid(uint ifIndex, ref ulong interfaceLuid);
1313
}
1414
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class IpHlpApi
10+
{
11+
[LibraryImport(Libraries.IpHlpApi)]
12+
internal static partial uint ConvertInterfaceLuidToIndex(in ulong interfaceLuid, ref uint ifIndex);
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class IpHlpApi
10+
{
11+
[LibraryImport(Libraries.IpHlpApi, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "ConvertInterfaceLuidToNameW")]
12+
internal static unsafe partial uint ConvertInterfaceLuidToName(in ulong interfaceLuid, Span<char> name, int nameLength);
13+
}
14+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class IpHlpApi
10+
{
11+
/// <summary>
12+
/// Converts a Unicode network interface name to the locally unique identifier (LUID) for the interface.
13+
/// </summary>
14+
/// <seealso href="https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-convertinterfacenametoluidw"/>
15+
/// <param name="interfaceName">The NULL-terminated Unicode string containing the network interface name.</param>
16+
/// <param name="interfaceLuid">A pointer to the NET_LUID for this interface.</param>
17+
/// <returns></returns>
18+
[LibraryImport(Libraries.IpHlpApi, StringMarshalling = StringMarshalling.Utf16, EntryPoint = "ConvertInterfaceNameToLuidW")]
19+
internal static unsafe partial uint ConvertInterfaceNameToLuid(ReadOnlySpan<char> interfaceName, ref ulong interfaceLuid);
20+
}
21+
}

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

Lines changed: 94 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,48 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Buffers.Binary;
5+
using System.Diagnostics;
6+
using System.Numerics;
7+
using System.Runtime.CompilerServices;
58

6-
namespace System
9+
namespace System.Net
710
{
811
internal static partial class IPv4AddressHelper
912
{
1013
internal const long Invalid = -1;
1114
private const long MaxIPv4Value = uint.MaxValue; // the native parser cannot handle MaxIPv4Value, only MaxIPv4Value - 1
15+
1216
private const int Octal = 8;
1317
private const int Decimal = 10;
1418
private const int Hex = 16;
1519

1620
private const int NumberOfLabels = 4;
1721

22+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
23+
internal static ushort ToUShort<TChar>(TChar value)
24+
where TChar : unmanaged, IBinaryInteger<TChar>
25+
{
26+
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
27+
28+
return typeof(TChar) == typeof(char)
29+
? (char)(object)value
30+
: (byte)(object)value;
31+
}
32+
1833
// Only called from the IPv6Helper, only parse the canonical format
19-
internal static int ParseHostNumber(ReadOnlySpan<char> str, int start, int end)
34+
internal static int ParseHostNumber<TChar>(ReadOnlySpan<TChar> str, int start, int end)
35+
where TChar : unmanaged, IBinaryInteger<TChar>
2036
{
37+
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
38+
2139
Span<byte> numbers = stackalloc byte[NumberOfLabels];
2240

2341
for (int i = 0; i < numbers.Length; ++i)
2442
{
2543
int b = 0;
26-
char ch;
44+
int ch;
2745

28-
for (; (start < end) && (ch = str[start]) != '.' && ch != ':'; ++start)
46+
for (; (start < end) && (ch = ToUShort(str[start])) != '.' && ch != ':'; ++start)
2947
{
3048
b = (b * 10) + ch - '0';
3149
}
@@ -79,7 +97,8 @@ internal static int ParseHostNumber(ReadOnlySpan<char> str, int start, int end)
7997
//
8098

8199
//Remark: MUST NOT be used unless all input indexes are verified and trusted.
82-
internal static unsafe bool IsValid(char* name, int start, ref int end, bool allowIPv6, bool notImplicitFile, bool unknownScheme)
100+
internal static unsafe bool IsValid<TChar>(TChar* name, int start, ref int end, bool allowIPv6, bool notImplicitFile, bool unknownScheme)
101+
where TChar : unmanaged, IBinaryInteger<TChar>
83102
{
84103
// IPv6 can only have canonical IPv4 embedded. Unknown schemes will not attempt parsing of non-canonical IPv4 addresses.
85104
if (allowIPv6 || unknownScheme)
@@ -105,32 +124,45 @@ internal static unsafe bool IsValid(char* name, int start, ref int end, bool all
105124
// / "2" %x30-34 DIGIT ; 200-249
106125
// / "25" %x30-35 ; 250-255
107126
//
108-
internal static unsafe bool IsValidCanonical(char* name, int start, ref int end, bool allowIPv6, bool notImplicitFile)
127+
internal static unsafe bool IsValidCanonical<TChar>(TChar* name, int start, ref int end, bool allowIPv6, bool notImplicitFile)
128+
where TChar : unmanaged, IBinaryInteger<TChar>
109129
{
130+
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
131+
110132
int dots = 0;
111-
int number = 0;
133+
long number = 0;
112134
bool haveNumber = false;
113135
bool firstCharIsZero = false;
114136

115137
while (start < end)
116138
{
117-
char ch = name[start];
139+
int ch = ToUShort(name[start]);
140+
118141
if (allowIPv6)
119142
{
120-
// for ipv4 inside ipv6 the terminator is either ScopeId, prefix or ipv6 terminator
143+
// For an IPv4 address nested inside an IPv6 address, the terminator is either the IPv6 address terminator (']'), prefix ('/') or ScopeId ('%')
121144
if (ch == ']' || ch == '/' || ch == '%')
145+
{
122146
break;
147+
}
123148
}
124149
else if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
125150
{
151+
// For a normal IPv4 address, the terminator is the prefix ('/' or its counterpart, '\'). If notImplicitFile is set, the terminator
152+
// is one of the characters which signify the start of the rest of the URI - the port number (':'), query string ('?') or fragment ('#')
153+
126154
break;
127155
}
128156

129-
if (char.IsAsciiDigit(ch))
157+
// An explicit cast to an unsigned integer forces character values preceding '0' to underflow, eliminating one comparison below.
158+
uint parsedCharacter = (uint)(ch - '0');
159+
160+
if (parsedCharacter < IPv4AddressHelper.Decimal)
130161
{
131-
if (!haveNumber && (ch == '0'))
162+
// A number starting with zero should be interpreted in base 8 / octal
163+
if (!haveNumber && parsedCharacter == 0)
132164
{
133-
if ((start + 1 < end) && name[start + 1] == '0')
165+
if ((start + 1 < end) && name[start + 1] == TChar.CreateTruncating('0'))
134166
{
135167
// 00 is not allowed as a prefix.
136168
return false;
@@ -140,14 +172,16 @@ internal static unsafe bool IsValidCanonical(char* name, int start, ref int end,
140172
}
141173

142174
haveNumber = true;
143-
number = number * 10 + (name[start] - '0');
144-
if (number > 255)
175+
number = number * IPv4AddressHelper.Decimal + parsedCharacter;
176+
if (number > byte.MaxValue)
145177
{
146178
return false;
147179
}
148180
}
149181
else if (ch == '.')
150182
{
183+
// If the current character is not an integer, it may be the IPv4 component separator ('.')
184+
151185
if (!haveNumber || (number > 0 && firstCharIsZero))
152186
{
153187
// 0 is not allowed to prefix a number.
@@ -176,68 +210,63 @@ internal static unsafe bool IsValidCanonical(char* name, int start, ref int end,
176210
// Return Invalid (-1) for failures.
177211
// If the address has less than three dots, only the rightmost section is assumed to contain the combined value for
178212
// the missing sections: 0xFF00FFFF == 0xFF.0x00.0xFF.0xFF == 0xFF.0xFFFF
179-
internal static unsafe long ParseNonCanonical(char* name, int start, ref int end, bool notImplicitFile)
213+
internal static unsafe long ParseNonCanonical<TChar>(TChar* name, int start, ref int end, bool notImplicitFile)
214+
where TChar : unmanaged, IBinaryInteger<TChar>
180215
{
181-
int numberBase = Decimal;
182-
char ch;
183-
long* parts = stackalloc long[4];
216+
Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
217+
218+
int numberBase = IPv4AddressHelper.Decimal;
219+
int ch = 0;
220+
long* parts = stackalloc long[3]; // One part per octet. Final octet doesn't have a terminator, so is stored in currentValue.
184221
long currentValue = 0;
185222
bool atLeastOneChar = false;
186223

187224
// Parse one dotted section at a time
188225
int dotCount = 0; // Limit 3
189226
int current = start;
227+
190228
for (; current < end; current++)
191229
{
192-
ch = name[current];
230+
ch = ToUShort(name[current]);
193231
currentValue = 0;
194232

195-
// Figure out what base this section is in
196-
numberBase = Decimal;
233+
// Figure out what base this section is in, default to base 10.
234+
// A number starting with zero should be interpreted in base 8 / octal
235+
// If the number starts with 0x, it should be interpreted in base 16 / hex
236+
numberBase = IPv4AddressHelper.Decimal;
237+
197238
if (ch == '0')
198239
{
199-
numberBase = Octal;
200240
current++;
201241
atLeastOneChar = true;
202242
if (current < end)
203243
{
204-
ch = name[current];
244+
ch = ToUShort(name[current]);
245+
205246
if (ch == 'x' || ch == 'X')
206247
{
207-
numberBase = Hex;
248+
numberBase = IPv4AddressHelper.Hex;
249+
208250
current++;
209251
atLeastOneChar = false;
210252
}
253+
else
254+
{
255+
numberBase = IPv4AddressHelper.Octal;
256+
}
211257
}
212258
}
213259

214260
// Parse this section
215261
for (; current < end; current++)
216262
{
217-
ch = name[current];
218-
int digitValue;
263+
ch = ToUShort(name[current]);
264+
int digitValue = HexConverter.FromChar(ch);
219265

220-
if ((numberBase == Decimal || numberBase == Hex) && char.IsAsciiDigit(ch))
221-
{
222-
digitValue = ch - '0';
223-
}
224-
else if (numberBase == Octal && '0' <= ch && ch <= '7')
225-
{
226-
digitValue = ch - '0';
227-
}
228-
else if (numberBase == Hex && 'a' <= ch && ch <= 'f')
229-
{
230-
digitValue = ch + 10 - 'a';
231-
}
232-
else if (numberBase == Hex && 'A' <= ch && ch <= 'F')
233-
{
234-
digitValue = ch + 10 - 'A';
235-
}
236-
else
266+
if (digitValue >= numberBase)
237267
{
238268
break; // Invalid/terminator
239269
}
240-
241270
currentValue = (currentValue * numberBase) + digitValue;
242271

243272
if (currentValue > MaxIPv4Value) // Overflow
@@ -248,10 +277,10 @@ internal static unsafe long ParseNonCanonical(char* name, int start, ref int end
248277
atLeastOneChar = true;
249278
}
250279

251-
if (current < end && name[current] == '.')
280+
if (current < end && ch == '.')
252281
{
253282
if (dotCount >= 3 // Max of 3 dots and 4 segments
254-
|| !atLeastOneChar // No empty segmets: 1...1
283+
|| !atLeastOneChar // No empty segments: 1...1
255284
// Only the last segment can be more than 255 (if there are less than 3 dots)
256285
|| currentValue > 0xFF)
257286
{
@@ -262,7 +291,7 @@ internal static unsafe long ParseNonCanonical(char* name, int start, ref int end
262291
atLeastOneChar = false;
263292
continue;
264293
}
265-
// We don't get here unless We find an invalid character or a terminator
294+
// We don't get here unless we find an invalid character or a terminator
266295
break;
267296
}
268297

@@ -275,8 +304,11 @@ internal static unsafe long ParseNonCanonical(char* name, int start, ref int end
275304
{
276305
// end of string, allowed
277306
}
278-
else if ((ch = name[current]) == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
307+
else if (ch == '/' || ch == '\\' || (notImplicitFile && (ch == ':' || ch == '?' || ch == '#')))
279308
{
309+
// For a normal IPv4 address, the terminator is the prefix ('/' or its counterpart, '\'). If notImplicitFile is set, the terminator
310+
// is one of the characters which signify the start of the rest of the URI - the port number (':'), query string ('?') or fragment ('#')
311+
280312
end = current;
281313
}
282314
else
@@ -285,35 +317,35 @@ internal static unsafe long ParseNonCanonical(char* name, int start, ref int end
285317
return Invalid;
286318
}
287319

288-
parts[dotCount] = currentValue;
289-
290-
// Parsed, reassemble and check for overflows
320+
// Parsed, reassemble and check for overflows in the last part. Previous parts have already been checked in the loop
291321
switch (dotCount)
292322
{
293323
case 0: // 0xFFFFFFFF
294-
if (parts[0] > MaxIPv4Value)
295-
{
296-
return Invalid;
297-
}
298-
return parts[0];
324+
return currentValue;
299325
case 1: // 0xFF.0xFFFFFF
300-
if (parts[1] > 0xffffff)
326+
Debug.Assert(parts[0] <= 0xFF);
327+
if (currentValue > 0xffffff)
301328
{
302329
return Invalid;
303330
}
304-
return (parts[0] << 24) | (parts[1] & 0xffffff);
331+
return (parts[0] << 24) | currentValue;
305332
case 2: // 0xFF.0xFF.0xFFFF
306-
if (parts[2] > 0xffff)
333+
Debug.Assert(parts[0] <= 0xFF);
334+
Debug.Assert(parts[1] <= 0xFF);
335+
if (currentValue > 0xffff)
307336
{
308337
return Invalid;
309338
}
310-
return (parts[0] << 24) | ((parts[1] & 0xff) << 16) | (parts[2] & 0xffff);
339+
return (parts[0] << 24) | (parts[1] << 16) | currentValue;
311340
case 3: // 0xFF.0xFF.0xFF.0xFF
312-
if (parts[3] > 0xff)
341+
Debug.Assert(parts[0] <= 0xFF);
342+
Debug.Assert(parts[1] <= 0xFF);
343+
Debug.Assert(parts[2] <= 0xFF);
344+
if (currentValue > 0xff)
313345
{
314346
return Invalid;
315347
}
316-
return (parts[0] << 24) | ((parts[1] & 0xff) << 16) | ((parts[2] & 0xff) << 8) | (parts[3] & 0xff);
348+
return (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8) | currentValue;
317349
default:
318350
return Invalid;
319351
}

0 commit comments

Comments
 (0)